Update all clickOutside action usages to use Svelte 5's new event handler syntax. Replace deprecated `on:clickoutside` with `onclickoutside` across all components to fix Svelte 5 compilation errors. Fixed in: - ProjectListItem - AdminSegmentedController - BaseDropdown - PostDropdown - BubbleTextStyleMenu - BubbleColorPicker - EssayForm - BasePane 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
155 lines
3.4 KiB
Svelte
155 lines
3.4 KiB
Svelte
<script lang="ts">
|
|
import { fade } from 'svelte/transition'
|
|
import { clickOutside } from '$lib/actions/clickOutside'
|
|
import type { Snippet } from 'svelte'
|
|
|
|
interface BasePaneProps {
|
|
isOpen: boolean
|
|
position?: { x: number; y: number } | null
|
|
closeOnBackdrop?: boolean
|
|
closeOnEscape?: boolean
|
|
maxWidth?: string
|
|
maxHeight?: string
|
|
onClose?: () => void
|
|
children?: Snippet
|
|
}
|
|
|
|
let {
|
|
isOpen = $bindable(false),
|
|
position = null,
|
|
closeOnBackdrop = true,
|
|
closeOnEscape = true,
|
|
maxWidth = '320px',
|
|
maxHeight = '400px',
|
|
onClose,
|
|
children
|
|
}: BasePaneProps = $props()
|
|
|
|
let paneElement: HTMLDivElement
|
|
|
|
// Handle escape key
|
|
$effect(() => {
|
|
if (!isOpen || !closeOnEscape) return
|
|
|
|
function handleKeydown(e: KeyboardEvent) {
|
|
if (e.key === 'Escape') {
|
|
handleClose()
|
|
}
|
|
}
|
|
|
|
window.addEventListener('keydown', handleKeydown)
|
|
return () => window.removeEventListener('keydown', handleKeydown)
|
|
})
|
|
|
|
function handleClose() {
|
|
isOpen = false
|
|
onClose?.()
|
|
}
|
|
|
|
// State to store calculated position
|
|
let calculatedPosition = $state<{ x: number; y: number } | null>(null)
|
|
|
|
// Calculate viewport-aware position
|
|
$effect(() => {
|
|
if (!position || !paneElement || !isOpen) {
|
|
calculatedPosition = null
|
|
return
|
|
}
|
|
|
|
// Get pane dimensions
|
|
const paneRect = paneElement.getBoundingClientRect()
|
|
const paneWidth = paneRect.width
|
|
const paneHeight = paneRect.height
|
|
|
|
// Get viewport dimensions
|
|
const viewportWidth = window.innerWidth
|
|
const viewportHeight = window.innerHeight
|
|
|
|
// Calculate position with viewport awareness
|
|
let x = position.x
|
|
let y = position.y
|
|
|
|
// Check horizontal bounds
|
|
if (x + paneWidth > viewportWidth - 20) {
|
|
// Too close to right edge, align to right
|
|
x = viewportWidth - paneWidth - 20
|
|
}
|
|
if (x < 20) {
|
|
// Too close to left edge
|
|
x = 20
|
|
}
|
|
|
|
// Check vertical bounds
|
|
if (y + paneHeight > viewportHeight - 20) {
|
|
// Too close to bottom edge, show above the cursor instead
|
|
// Assuming the cursor is at position.y, we want to show above it
|
|
y = Math.max(20, position.y - paneHeight - 8)
|
|
}
|
|
if (y < 20) {
|
|
// Too close to top edge
|
|
y = 20
|
|
}
|
|
|
|
calculatedPosition = { x, y }
|
|
})
|
|
|
|
// Calculate position styles
|
|
const positionStyles = $derived(() => {
|
|
const pos = calculatedPosition || position
|
|
if (!pos) return ''
|
|
|
|
let styles = []
|
|
|
|
// Position the pane at the calculated coordinates
|
|
styles.push(`left: ${pos.x}px`)
|
|
styles.push(`top: ${pos.y}px`)
|
|
|
|
return styles.join('; ')
|
|
})
|
|
</script>
|
|
|
|
{#if isOpen}
|
|
<div
|
|
class="base-pane"
|
|
bind:this={paneElement}
|
|
style="{positionStyles()}; max-width: {maxWidth}; max-height: {maxHeight};"
|
|
transition:fade={{ duration: 150 }}
|
|
role="dialog"
|
|
aria-modal="false"
|
|
use:clickOutside={{ enabled: closeOnBackdrop }}
|
|
onclickoutside={handleClose}
|
|
>
|
|
{#if children}
|
|
{@render children()}
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
<style lang="scss">
|
|
@import '$styles/variables';
|
|
|
|
.base-pane {
|
|
position: fixed;
|
|
background: $white;
|
|
border: 1px solid $gray-85;
|
|
border-radius: $corner-radius;
|
|
box-shadow:
|
|
0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
|
0 2px 4px -1px rgba(0, 0, 0, 0.06),
|
|
0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
z-index: $z-index-popover;
|
|
min-width: 400px;
|
|
overflow: auto;
|
|
|
|
// Ensure pane doesn't go off-screen
|
|
&:global([style*='left']) {
|
|
transform: translateX(0);
|
|
|
|
// If it would go off the right edge, align to right instead
|
|
@media (max-width: 640px) {
|
|
left: auto !important;
|
|
right: $unit !important;
|
|
}
|
|
}
|
|
}
|
|
</style>
|