@webamoki/web-svelte 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ui/context-menu/ContextMenu.svelte +57 -0
- package/dist/components/ui/context-menu/ContextMenu.svelte.d.ts +15 -0
- package/dist/components/ui/context-menu/ContextMenuContent.svelte +99 -0
- package/dist/components/ui/context-menu/ContextMenuContent.svelte.d.ts +8 -0
- package/dist/components/ui/context-menu/ContextMenuItem.svelte +33 -0
- package/dist/components/ui/context-menu/ContextMenuItem.svelte.d.ts +8 -0
- package/dist/components/ui/context-menu/ContextMenuSeparator.svelte +1 -0
- package/dist/components/ui/context-menu/ContextMenuSeparator.svelte.d.ts +26 -0
- package/dist/components/ui/context-menu/ContextMenuTrigger.svelte +23 -0
- package/dist/components/ui/context-menu/ContextMenuTrigger.svelte.d.ts +7 -0
- package/dist/components/ui/context-menu/context-menu-state.svelte.d.ts +7 -0
- package/dist/components/ui/context-menu/context-menu-state.svelte.js +31 -0
- package/dist/components/ui/drag-drop/Draggable.svelte +80 -0
- package/dist/components/ui/drag-drop/Draggable.svelte.d.ts +31 -0
- package/dist/components/ui/drag-drop/Dropzone.svelte +71 -0
- package/dist/components/ui/drag-drop/Dropzone.svelte.d.ts +32 -0
- package/dist/components/ui/drag-drop/drag-manager.d.ts +8 -0
- package/dist/components/ui/drag-drop/drag-manager.js +28 -0
- package/dist/components/ui/index.d.ts +9 -1
- package/dist/components/ui/index.js +15 -1
- package/dist/utils/form/index.d.ts +1 -0
- package/dist/utils/form/index.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export type ContextMenuState = {
|
|
3
|
+
position: { x: number; y: number };
|
|
4
|
+
open: boolean;
|
|
5
|
+
};
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<script lang="ts">
|
|
9
|
+
import { setContext, onDestroy, type Snippet } from 'svelte';
|
|
10
|
+
import { contextMenuState } from './context-menu-state.svelte';
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
children: Snippet<[]>;
|
|
14
|
+
onOpenChange?: (open: boolean) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let { children, onOpenChange }: Props = $props();
|
|
18
|
+
|
|
19
|
+
const menuId = Symbol('context-menu');
|
|
20
|
+
|
|
21
|
+
let menuState = $state<ContextMenuState>({
|
|
22
|
+
position: { x: 0, y: 0 },
|
|
23
|
+
open: false
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Watch for changes to menuState.open and sync with global store
|
|
27
|
+
$effect(() => {
|
|
28
|
+
if (menuState.open) {
|
|
29
|
+
contextMenuState.openMenu(menuId);
|
|
30
|
+
} else if (contextMenuState.isThisMenuOpen(menuId)) {
|
|
31
|
+
contextMenuState.closeMenu();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Watch for global store changes and close this menu if another opens
|
|
36
|
+
$effect(() => {
|
|
37
|
+
if (contextMenuState.open && !contextMenuState.isThisMenuOpen(menuId)) {
|
|
38
|
+
menuState.open = false;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Call onOpenChange callback when open state changes
|
|
43
|
+
$effect(() => {
|
|
44
|
+
onOpenChange?.(menuState.open);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Cleanup scroll lock when component is destroyed
|
|
48
|
+
onDestroy(() => {
|
|
49
|
+
if (contextMenuState.isThisMenuOpen(menuId)) {
|
|
50
|
+
contextMenuState.closeMenu();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
setContext('context-menu', menuState);
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
{@render children()}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type ContextMenuState = {
|
|
2
|
+
position: {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
};
|
|
6
|
+
open: boolean;
|
|
7
|
+
};
|
|
8
|
+
import { type Snippet } from 'svelte';
|
|
9
|
+
interface Props {
|
|
10
|
+
children: Snippet<[]>;
|
|
11
|
+
onOpenChange?: (open: boolean) => void;
|
|
12
|
+
}
|
|
13
|
+
declare const ContextMenu: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type ContextMenu = ReturnType<typeof ContextMenu>;
|
|
15
|
+
export default ContextMenu;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getContext, onMount, type Snippet } from 'svelte';
|
|
3
|
+
import { scale } from 'svelte/transition';
|
|
4
|
+
import type { ContextMenuState } from './ContextMenu.svelte';
|
|
5
|
+
import { cn } from '../../../shadcn/utils.js';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
children: Snippet<[]>;
|
|
9
|
+
class?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let { children, class: className }: Props = $props();
|
|
13
|
+
|
|
14
|
+
const menuState: ContextMenuState = getContext('context-menu');
|
|
15
|
+
|
|
16
|
+
let menuElement: HTMLDivElement | null = $state(null);
|
|
17
|
+
let adjustedPosition = $state({ x: 0, y: 0 });
|
|
18
|
+
|
|
19
|
+
function handleDocumentClick(event: MouseEvent) {
|
|
20
|
+
if (menuElement && !menuElement.contains(event.target as Node)) {
|
|
21
|
+
menuState.open = false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function adjustPositionToFitScreen() {
|
|
26
|
+
if (!menuElement) return;
|
|
27
|
+
|
|
28
|
+
const rect = menuElement.getBoundingClientRect();
|
|
29
|
+
const viewportWidth = window.innerWidth;
|
|
30
|
+
const viewportHeight = window.innerHeight;
|
|
31
|
+
|
|
32
|
+
let x = menuState.position.x;
|
|
33
|
+
let y = menuState.position.y;
|
|
34
|
+
|
|
35
|
+
// Adjust horizontal position if menu goes off right edge
|
|
36
|
+
if (x + rect.width > viewportWidth) {
|
|
37
|
+
x = viewportWidth - rect.width - 8; // 8px padding from edge
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Adjust horizontal position if menu goes off left edge
|
|
41
|
+
if (x < 0) {
|
|
42
|
+
x = 8; // 8px padding from edge
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Adjust vertical position if menu goes off bottom edge
|
|
46
|
+
if (y + rect.height > viewportHeight) {
|
|
47
|
+
y = viewportHeight - rect.height - 8; // 8px padding from edge
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Adjust vertical position if menu goes off top edge
|
|
51
|
+
if (y < 0) {
|
|
52
|
+
y = 8; // 8px padding from edge
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
adjustedPosition = { x, y };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
$effect(() => {
|
|
59
|
+
if (menuState.open && menuElement) {
|
|
60
|
+
// Use requestAnimationFrame to ensure DOM has updated
|
|
61
|
+
requestAnimationFrame(() => {
|
|
62
|
+
adjustPositionToFitScreen();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
onMount(() => {
|
|
68
|
+
document.addEventListener('click', handleDocumentClick);
|
|
69
|
+
|
|
70
|
+
return () => {
|
|
71
|
+
document.removeEventListener('click', handleDocumentClick);
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
{#if menuState.open}
|
|
77
|
+
<div
|
|
78
|
+
bind:this={menuElement}
|
|
79
|
+
class={cn('context-menu-content', className)}
|
|
80
|
+
style="left: {adjustedPosition.x}px; top: {adjustedPosition.y}px;"
|
|
81
|
+
transition:scale={{ duration: 150, start: 0.95 }}
|
|
82
|
+
>
|
|
83
|
+
{@render children()}
|
|
84
|
+
</div>
|
|
85
|
+
{/if}
|
|
86
|
+
|
|
87
|
+
<style>
|
|
88
|
+
.context-menu-content {
|
|
89
|
+
position: fixed;
|
|
90
|
+
background-color: white;
|
|
91
|
+
border: 1px solid #ddd;
|
|
92
|
+
border-radius: 8px;
|
|
93
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
94
|
+
padding: 4px;
|
|
95
|
+
min-width: 150px;
|
|
96
|
+
z-index: 50;
|
|
97
|
+
transform-origin: top left;
|
|
98
|
+
}
|
|
99
|
+
</style>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
children: Snippet<[]>;
|
|
4
|
+
class?: string;
|
|
5
|
+
}
|
|
6
|
+
declare const ContextMenuContent: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type ContextMenuContent = ReturnType<typeof ContextMenuContent>;
|
|
8
|
+
export default ContextMenuContent;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { cn } from '../../../shadcn/utils.js';
|
|
4
|
+
|
|
5
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
variant?: 'default' | 'destructive';
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
class: className,
|
|
12
|
+
variant = 'default',
|
|
13
|
+
disabled = false,
|
|
14
|
+
onclick,
|
|
15
|
+
children,
|
|
16
|
+
...restProps
|
|
17
|
+
}: Props = $props();
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<div
|
|
21
|
+
role="menuitem"
|
|
22
|
+
tabindex={disabled ? -1 : 0}
|
|
23
|
+
class={cn(
|
|
24
|
+
'flex cursor-pointer items-center gap-2 rounded px-3 py-2 text-sm hover:bg-gray-100',
|
|
25
|
+
variant === 'destructive' && 'text-red-600 hover:bg-red-50',
|
|
26
|
+
disabled && 'pointer-events-none opacity-50',
|
|
27
|
+
className
|
|
28
|
+
)}
|
|
29
|
+
{onclick}
|
|
30
|
+
{...restProps}
|
|
31
|
+
>
|
|
32
|
+
{@render children?.()}
|
|
33
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
variant?: 'default' | 'destructive';
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
}
|
|
6
|
+
declare const ContextMenuItem: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type ContextMenuItem = ReturnType<typeof ContextMenuItem>;
|
|
8
|
+
export default ContextMenuItem;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<div class="my-1 border-b border-gray-200"></div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export default ContextMenuSeparator;
|
|
2
|
+
type ContextMenuSeparator = SvelteComponent<{
|
|
3
|
+
[x: string]: never;
|
|
4
|
+
}, {
|
|
5
|
+
[evt: string]: CustomEvent<any>;
|
|
6
|
+
}, {}> & {
|
|
7
|
+
$$bindings?: string | undefined;
|
|
8
|
+
};
|
|
9
|
+
declare const ContextMenuSeparator: $$__sveltets_2_IsomorphicComponent<{
|
|
10
|
+
[x: string]: never;
|
|
11
|
+
}, {
|
|
12
|
+
[evt: string]: CustomEvent<any>;
|
|
13
|
+
}, {}, {}, string>;
|
|
14
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
15
|
+
new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
|
|
16
|
+
$$bindings?: Bindings;
|
|
17
|
+
} & Exports;
|
|
18
|
+
(internal: unknown, props: {
|
|
19
|
+
$$events?: Events;
|
|
20
|
+
$$slots?: Slots;
|
|
21
|
+
}): Exports & {
|
|
22
|
+
$set?: any;
|
|
23
|
+
$on?: any;
|
|
24
|
+
};
|
|
25
|
+
z_$$bindings?: Bindings;
|
|
26
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getContext, type Snippet } from 'svelte';
|
|
3
|
+
import type { ContextMenuState } from './ContextMenu.svelte';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
children: Snippet<[]>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let { children }: Props = $props();
|
|
10
|
+
|
|
11
|
+
const menuState: ContextMenuState = getContext('context-menu');
|
|
12
|
+
|
|
13
|
+
function handleContextMenu(event: MouseEvent) {
|
|
14
|
+
event.preventDefault();
|
|
15
|
+
menuState.position.x = event.clientX;
|
|
16
|
+
menuState.position.y = event.clientY;
|
|
17
|
+
menuState.open = true;
|
|
18
|
+
}
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<div role="button" tabindex="-1" oncontextmenu={handleContextMenu}>
|
|
22
|
+
{@render children()}
|
|
23
|
+
</div>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
children: Snippet<[]>;
|
|
4
|
+
}
|
|
5
|
+
declare const ContextMenuTrigger: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type ContextMenuTrigger = ReturnType<typeof ContextMenuTrigger>;
|
|
7
|
+
export default ContextMenuTrigger;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const globalMenuState = $state({
|
|
2
|
+
open: false,
|
|
3
|
+
id: null
|
|
4
|
+
});
|
|
5
|
+
export const contextMenuState = {
|
|
6
|
+
get open() {
|
|
7
|
+
return globalMenuState.open;
|
|
8
|
+
},
|
|
9
|
+
get currentId() {
|
|
10
|
+
return globalMenuState.id;
|
|
11
|
+
},
|
|
12
|
+
openMenu(id) {
|
|
13
|
+
globalMenuState.open = true;
|
|
14
|
+
globalMenuState.id = id;
|
|
15
|
+
// Prevent scrolling when menu opens
|
|
16
|
+
if (typeof document !== 'undefined') {
|
|
17
|
+
document.body.style.overflow = 'hidden';
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
closeMenu() {
|
|
21
|
+
globalMenuState.open = false;
|
|
22
|
+
globalMenuState.id = null;
|
|
23
|
+
// Restore scrolling when menu closes
|
|
24
|
+
if (typeof document !== 'undefined') {
|
|
25
|
+
document.body.style.overflow = '';
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
isThisMenuOpen(id) {
|
|
29
|
+
return globalMenuState.open && globalMenuState.id === id;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<script lang="ts" generics="T">
|
|
2
|
+
import { onDestroy, type Snippet } from 'svelte';
|
|
3
|
+
import type { DragManager } from './drag-manager.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
data: T;
|
|
7
|
+
manager: DragManager<T>;
|
|
8
|
+
children: Snippet<[]>;
|
|
9
|
+
class?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let { manager, children, class: className, data }: Props = $props();
|
|
13
|
+
|
|
14
|
+
let isDragging = $state(false);
|
|
15
|
+
let mouseX = $state(0);
|
|
16
|
+
let mouseY = $state(0);
|
|
17
|
+
|
|
18
|
+
function startDrag(e: MouseEvent) {
|
|
19
|
+
if (e.button !== 0) return;
|
|
20
|
+
|
|
21
|
+
e.preventDefault();
|
|
22
|
+
isDragging = true;
|
|
23
|
+
manager.drag(data);
|
|
24
|
+
|
|
25
|
+
mouseX = e.clientX;
|
|
26
|
+
mouseY = e.clientY;
|
|
27
|
+
|
|
28
|
+
// Force grabbing cursor on the body
|
|
29
|
+
document.body.style.cursor = 'grabbing';
|
|
30
|
+
|
|
31
|
+
window.addEventListener('mousemove', moveDrag);
|
|
32
|
+
window.addEventListener('mouseup', endDrag);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function moveDrag(e: MouseEvent) {
|
|
36
|
+
mouseX = e.clientX;
|
|
37
|
+
mouseY = e.clientY;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function endDrag() {
|
|
41
|
+
isDragging = false;
|
|
42
|
+
manager.stop();
|
|
43
|
+
|
|
44
|
+
// Reset cursor
|
|
45
|
+
document.body.style.cursor = '';
|
|
46
|
+
|
|
47
|
+
window.removeEventListener('mousemove', moveDrag);
|
|
48
|
+
window.removeEventListener('mouseup', endDrag);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
onDestroy(() => {
|
|
52
|
+
if (isDragging) {
|
|
53
|
+
manager.stop();
|
|
54
|
+
}
|
|
55
|
+
if (typeof window !== 'undefined') {
|
|
56
|
+
window.removeEventListener('mousemove', moveDrag);
|
|
57
|
+
window.removeEventListener('mouseup', endDrag);
|
|
58
|
+
document.body.style.cursor = ''; // safe in browser only
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<div
|
|
64
|
+
onmousedown={startDrag}
|
|
65
|
+
class={`relative select-none ${isDragging ? 'opacity-30 grayscale' : 'cursor-grab'} ${className ?? ''}`}
|
|
66
|
+
role="button"
|
|
67
|
+
tabindex="0"
|
|
68
|
+
aria-label="Draggable item"
|
|
69
|
+
>
|
|
70
|
+
{@render children()}
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
{#if isDragging}
|
|
74
|
+
<div
|
|
75
|
+
class="pointer-events-none fixed z-9999 -translate-x-1/2 -translate-y-1/2 scale-115 transform opacity-95 shadow-xl"
|
|
76
|
+
style={`top:${mouseY}px; left:${mouseX}px;`}
|
|
77
|
+
>
|
|
78
|
+
{@render children()}
|
|
79
|
+
</div>
|
|
80
|
+
{/if}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
|
+
import type { DragManager } from './drag-manager.js';
|
|
3
|
+
declare function $$render<T>(): {
|
|
4
|
+
props: {
|
|
5
|
+
data: T;
|
|
6
|
+
manager: DragManager<T>;
|
|
7
|
+
children: Snippet<[]>;
|
|
8
|
+
class?: string;
|
|
9
|
+
};
|
|
10
|
+
exports: {};
|
|
11
|
+
bindings: "";
|
|
12
|
+
slots: {};
|
|
13
|
+
events: {};
|
|
14
|
+
};
|
|
15
|
+
declare class __sveltets_Render<T> {
|
|
16
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
17
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
18
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
19
|
+
bindings(): "";
|
|
20
|
+
exports(): {};
|
|
21
|
+
}
|
|
22
|
+
interface $$IsomorphicComponent {
|
|
23
|
+
new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
24
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
25
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
26
|
+
<T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
27
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
28
|
+
}
|
|
29
|
+
declare const Draggable: $$IsomorphicComponent;
|
|
30
|
+
type Draggable<T> = InstanceType<typeof Draggable<T>>;
|
|
31
|
+
export default Draggable;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script lang="ts" generics="T">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { DragManager } from './drag-manager.js';
|
|
4
|
+
import { LucideArrowDown } from '@lucide/svelte';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
isDroppable: (data: T) => boolean;
|
|
8
|
+
onDrop: (data: T) => void;
|
|
9
|
+
manager: DragManager<T>;
|
|
10
|
+
children: Snippet<[]>;
|
|
11
|
+
class?: string;
|
|
12
|
+
}
|
|
13
|
+
let { isDroppable, onDrop, manager, children, class: className = '' }: Props = $props();
|
|
14
|
+
|
|
15
|
+
let isOver = $state(false);
|
|
16
|
+
|
|
17
|
+
let canDrop = $derived(manager.isDragging && isDroppable(manager.dragData!));
|
|
18
|
+
|
|
19
|
+
// --- Event Handlers ---
|
|
20
|
+
|
|
21
|
+
function handleMouseEnter() {
|
|
22
|
+
// Only track 'isOver' if a drag is active
|
|
23
|
+
if (manager.isDragging) {
|
|
24
|
+
isOver = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function handleMouseLeave() {
|
|
29
|
+
isOver = false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function handleMouseUp() {
|
|
33
|
+
if (isOver && canDrop) {
|
|
34
|
+
onDrop(manager.dragData as T);
|
|
35
|
+
manager.stop();
|
|
36
|
+
}
|
|
37
|
+
isOver = false;
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
42
|
+
<div
|
|
43
|
+
onmouseenter={handleMouseEnter}
|
|
44
|
+
onmouseleave={handleMouseLeave}
|
|
45
|
+
onmouseup={handleMouseUp}
|
|
46
|
+
class={`relative p-4 transition-colors duration-150 ${className}
|
|
47
|
+
${
|
|
48
|
+
// Zero Layout Shift: always reserve the same border width to prevent layout flicker.
|
|
49
|
+
// Use transparent border when not droppable so sizing remains constant.
|
|
50
|
+
!manager.isDragging
|
|
51
|
+
? 'border-2 border-dashed border-transparent'
|
|
52
|
+
: canDrop
|
|
53
|
+
? 'border-2 border-dashed border-gray-400'
|
|
54
|
+
: 'border-2 border-dashed border-transparent'
|
|
55
|
+
}`}
|
|
56
|
+
>
|
|
57
|
+
{@render children()}
|
|
58
|
+
|
|
59
|
+
{#if isOver && canDrop}
|
|
60
|
+
<div
|
|
61
|
+
class="absolute inset-0 z-10 flex flex-col items-center justify-center
|
|
62
|
+
bg-gray-200/50 p-4 backdrop-blur-none"
|
|
63
|
+
>
|
|
64
|
+
<LucideArrowDown class="h-16 w-16 animate-bounce text-gray-700" />
|
|
65
|
+
</div>
|
|
66
|
+
{/if}
|
|
67
|
+
|
|
68
|
+
{#if isOver && !canDrop}
|
|
69
|
+
<div class="absolute inset-0 z-10" aria-label="Cannot drop here"></div>
|
|
70
|
+
{/if}
|
|
71
|
+
</div>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { DragManager } from './drag-manager.js';
|
|
3
|
+
declare function $$render<T>(): {
|
|
4
|
+
props: {
|
|
5
|
+
isDroppable: (data: T) => boolean;
|
|
6
|
+
onDrop: (data: T) => void;
|
|
7
|
+
manager: DragManager<T>;
|
|
8
|
+
children: Snippet<[]>;
|
|
9
|
+
class?: string;
|
|
10
|
+
};
|
|
11
|
+
exports: {};
|
|
12
|
+
bindings: "";
|
|
13
|
+
slots: {};
|
|
14
|
+
events: {};
|
|
15
|
+
};
|
|
16
|
+
declare class __sveltets_Render<T> {
|
|
17
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
18
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
19
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
20
|
+
bindings(): "";
|
|
21
|
+
exports(): {};
|
|
22
|
+
}
|
|
23
|
+
interface $$IsomorphicComponent {
|
|
24
|
+
new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
25
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
26
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
27
|
+
<T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
28
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
29
|
+
}
|
|
30
|
+
declare const Dropzone: $$IsomorphicComponent;
|
|
31
|
+
type Dropzone<T> = InstanceType<typeof Dropzone<T>>;
|
|
32
|
+
export default Dropzone;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createSubscriber } from 'svelte/reactivity';
|
|
2
|
+
export class DragManager {
|
|
3
|
+
#dragData = null;
|
|
4
|
+
#subscribe;
|
|
5
|
+
#update = () => { };
|
|
6
|
+
constructor() {
|
|
7
|
+
this.#subscribe = createSubscriber((update) => {
|
|
8
|
+
this.#update = update;
|
|
9
|
+
return () => { };
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
drag(dragData) {
|
|
13
|
+
this.#dragData = dragData;
|
|
14
|
+
this.#update();
|
|
15
|
+
}
|
|
16
|
+
stop() {
|
|
17
|
+
this.#dragData = null;
|
|
18
|
+
this.#update();
|
|
19
|
+
}
|
|
20
|
+
get isDragging() {
|
|
21
|
+
this.#subscribe();
|
|
22
|
+
return this.#dragData !== null;
|
|
23
|
+
}
|
|
24
|
+
get dragData() {
|
|
25
|
+
this.#subscribe();
|
|
26
|
+
return this.#dragData;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -3,4 +3,12 @@ import ChoiceMulti from './choice/ChoiceMulti.svelte';
|
|
|
3
3
|
import WeekdayChoice from './choice/WeekdayChoice.svelte';
|
|
4
4
|
import WeekdayChoiceMulti from './choice/WeekdayChoiceMulti.svelte';
|
|
5
5
|
import SearchBar from './search/SearchBar.svelte';
|
|
6
|
-
|
|
6
|
+
import ContextMenu from './context-menu/ContextMenu.svelte';
|
|
7
|
+
import ContextMenuContent from './context-menu/ContextMenuContent.svelte';
|
|
8
|
+
import ContextMenuItem from './context-menu/ContextMenuItem.svelte';
|
|
9
|
+
import ContextMenuSeparator from './context-menu/ContextMenuSeparator.svelte';
|
|
10
|
+
import ContextMenuTrigger from './context-menu/ContextMenuTrigger.svelte';
|
|
11
|
+
import { DragManager } from './drag-drop/drag-manager.js';
|
|
12
|
+
import Draggable from './drag-drop/Draggable.svelte';
|
|
13
|
+
import Dropzone from './drag-drop/Dropzone.svelte';
|
|
14
|
+
export { Choice, ChoiceMulti, SearchBar, WeekdayChoice, WeekdayChoiceMulti, DragManager, Draggable, Dropzone, ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger };
|
|
@@ -3,4 +3,18 @@ import ChoiceMulti from './choice/ChoiceMulti.svelte';
|
|
|
3
3
|
import WeekdayChoice from './choice/WeekdayChoice.svelte';
|
|
4
4
|
import WeekdayChoiceMulti from './choice/WeekdayChoiceMulti.svelte';
|
|
5
5
|
import SearchBar from './search/SearchBar.svelte';
|
|
6
|
-
|
|
6
|
+
// context menu
|
|
7
|
+
import ContextMenu from './context-menu/ContextMenu.svelte';
|
|
8
|
+
import ContextMenuContent from './context-menu/ContextMenuContent.svelte';
|
|
9
|
+
import ContextMenuItem from './context-menu/ContextMenuItem.svelte';
|
|
10
|
+
import ContextMenuSeparator from './context-menu/ContextMenuSeparator.svelte';
|
|
11
|
+
import ContextMenuTrigger from './context-menu/ContextMenuTrigger.svelte';
|
|
12
|
+
// drag drop
|
|
13
|
+
import { DragManager } from './drag-drop/drag-manager.js';
|
|
14
|
+
import Draggable from './drag-drop/Draggable.svelte';
|
|
15
|
+
import Dropzone from './drag-drop/Dropzone.svelte';
|
|
16
|
+
export { Choice, ChoiceMulti, SearchBar, WeekdayChoice, WeekdayChoiceMulti,
|
|
17
|
+
// drag drop
|
|
18
|
+
DragManager, Draggable, Dropzone,
|
|
19
|
+
// context menu
|
|
20
|
+
ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type } from 'arktype';
|
|
2
2
|
import { type SuperValidated } from 'sveltekit-superforms';
|
|
3
|
+
export * from './virtual-form.js';
|
|
3
4
|
export declare function prepareForm<S extends type.Any<Record<string, unknown>>>(validated: SuperValidated<S['infer']> | S['infer'], schema: S, options?: Partial<{
|
|
4
5
|
invalidateAll: boolean;
|
|
5
6
|
resetForm: boolean;
|
package/dist/utils/form/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { toast } from 'svelte-sonner';
|
|
|
3
3
|
import { defaults, superForm } from 'sveltekit-superforms';
|
|
4
4
|
import { arktype, arktypeClient } from 'sveltekit-superforms/adapters';
|
|
5
5
|
import { dateTransport } from '../datetime/index.js';
|
|
6
|
+
export * from './virtual-form.js';
|
|
6
7
|
export function prepareForm(validated, schema, options) {
|
|
7
8
|
const form = superForm(validated, {
|
|
8
9
|
validators: arktypeClient(schema),
|