hensei-web/src/lib/components/dnd/DropZone.svelte
Justin Edmund a88dd89873 fix: resolve component prop exactOptionalPropertyTypes issues
- Add | undefined to DropZone Props interface (item, canDrop, onDrop)
- Fix users.ts by properly typing updates object with | undefined
- Apply optionalProps to SegmentedControl restProps spreading

Maintains 57 errors (some regressed, some fixed).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 18:55:03 -08:00

179 lines
No EOL
3.5 KiB
Svelte

<svelte:options runes={true} />
<script lang="ts">
import { getContext } from 'svelte'
import type { GridItem, GridItemType, DragDropContext } from '$lib/composables/drag-drop.svelte'
interface Props {
container: string
position: number
type: GridItemType
item?: GridItem | undefined
canDrop?: boolean | undefined
onDrop?: ((item: GridItem) => void) | undefined
children?: any
}
let {
container,
position,
type,
item,
canDrop = true,
onDrop,
children
}: Props = $props()
const dragContext = getContext<DragDropContext>('drag-drop')
let elementRef: HTMLElement | undefined = $state()
let isHovered = $derived(
dragContext?.state.hoveredOver?.container === container &&
dragContext?.state.hoveredOver?.position === position
)
let isValidDrop = $derived(
isHovered && dragContext?.state.validDrop
)
let isInvalidDrop = $derived(
isHovered && !dragContext?.state.validDrop
)
function handleDragOver(e: DragEvent) {
if (!canDrop || !dragContext) return
e.preventDefault()
e.dataTransfer!.dropEffect = 'move'
if (dragContext.state.draggedItem) {
const target = { container, position, type }
dragContext.updateHover(target)
const isValid = dragContext.validateDrop(
dragContext.state.draggedItem.source,
target
)
if (!isValid) {
e.dataTransfer!.dropEffect = 'none'
}
}
}
function handleDragEnter(e: DragEvent) {
if (!canDrop || !dragContext) return
e.preventDefault()
const target = { container, position, type }
dragContext.updateHover(target)
}
function handleDragLeave(e: DragEvent) {
if (!dragContext) return
if (e.relatedTarget && elementRef?.contains(e.relatedTarget as Node)) {
return
}
dragContext.updateHover(null)
}
function handleDrop(e: DragEvent) {
if (!canDrop || !dragContext) return
e.preventDefault()
e.stopPropagation()
const target = { container, position, type }
if (dragContext.state.draggedItem && dragContext.state.validDrop) {
const success = dragContext.handleDrop(target, item)
if (success && onDrop && dragContext.state.draggedItem) {
onDrop(dragContext.state.draggedItem.data)
}
}
dragContext.updateHover(null)
}
</script>
<div
bind:this={elementRef}
ondragover={handleDragOver}
ondragenter={handleDragEnter}
ondragleave={handleDragLeave}
ondrop={handleDrop}
class="drop-zone"
class:hovered={isHovered}
class:valid-drop={isValidDrop}
class:invalid-drop={isInvalidDrop}
class:can-drop={canDrop}
data-container={container}
data-position={position}
data-type={type}
>
{@render children?.()}
</div>
<style lang="scss">
.drop-zone {
position: relative;
transition: all 0.2s ease-out;
&.hovered {
transform: scale(1.02);
}
&.valid-drop {
border: 2px dashed #4CAF50;
background: rgba(76, 175, 80, 0.1);
border-radius: 8px;
&::before {
content: '';
position: absolute;
inset: -4px;
border: 2px solid #4CAF50;
border-radius: 10px;
opacity: 0.3;
pointer-events: none;
animation: pulse 1s ease-in-out infinite;
}
}
&.invalid-drop {
border: 2px dashed #F44336;
background: rgba(244, 67, 54, 0.1);
border-radius: 8px;
opacity: 0.7;
&::after {
content: '⚠';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
color: #F44336;
pointer-events: none;
}
}
}
@keyframes pulse {
0% {
opacity: 0.3;
transform: scale(1);
}
50% {
opacity: 0.5;
transform: scale(1.02);
}
100% {
opacity: 0.3;
transform: scale(1);
}
}
</style>