- 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>
179 lines
No EOL
3.5 KiB
Svelte
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> |