@dryui/ui 1.3.1 → 1.4.0
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/accordion/accordion-content.svelte +1 -1
- package/dist/alert/alert.svelte +1 -1
- package/dist/app-frame/app-frame.svelte +125 -0
- package/dist/app-frame/app-frame.svelte.d.ts +10 -0
- package/dist/app-frame/index.d.ts +8 -0
- package/dist/app-frame/index.js +1 -0
- package/dist/aurora/aurora.svelte +22 -59
- package/dist/beam/beam.svelte +28 -9
- package/dist/carousel/carousel-button-dots.svelte +25 -8
- package/dist/carousel/carousel-button-thumbnails.svelte +25 -8
- package/dist/carousel/carousel-root.svelte +115 -4
- package/dist/carousel/carousel-slide.svelte +5 -1
- package/dist/carousel/carousel-viewport.svelte +2 -0
- package/dist/carousel/context.svelte.d.ts +5 -0
- package/dist/chart/chart-bars.svelte +25 -16
- package/dist/chart/chart-donut.svelte +25 -16
- package/dist/chart/chart-root.svelte +134 -30
- package/dist/chart/chart-root.svelte.d.ts +1 -0
- package/dist/chart/context.svelte.d.ts +3 -1
- package/dist/chart/context.svelte.js +1 -0
- package/dist/chart/index.d.ts +1 -0
- package/dist/chromatic-shift/chromatic-shift.svelte +36 -9
- package/dist/collapsible/collapsible-content.svelte +2 -1
- package/dist/combobox/combobox-content.svelte +26 -44
- package/dist/combobox/combobox-content.svelte.d.ts +1 -1
- package/dist/combobox/combobox-input-root.svelte +7 -1
- package/dist/combobox/combobox-input.svelte +21 -8
- package/dist/country-select/country-select-button-input.svelte +124 -260
- package/dist/date-picker/datepicker-content.svelte +18 -26
- package/dist/date-picker/datepicker-content.svelte.d.ts +2 -1
- package/dist/date-picker/datepicker-input-root.svelte +7 -1
- package/dist/date-range-picker/date-range-picker-content.svelte +18 -14
- package/dist/date-range-picker/date-range-picker-content.svelte.d.ts +2 -1
- package/dist/date-range-picker/date-range-picker-root.svelte +7 -1
- package/dist/displacement/displacement.svelte +16 -22
- package/dist/drag-and-drop/context.svelte.d.ts +2 -0
- package/dist/drag-and-drop/drag-and-drop-handle.svelte +34 -5
- package/dist/drag-and-drop/drag-and-drop-item.svelte +23 -14
- package/dist/drag-and-drop/drag-and-drop-root.svelte +60 -16
- package/dist/god-rays/god-rays.svelte +11 -0
- package/dist/gradient-mesh/gradient-mesh.svelte +27 -5
- package/dist/hover-card/context.svelte.d.ts +1 -10
- package/dist/hover-card/context.svelte.js +1 -2
- package/dist/hover-card/hover-card-content.svelte +41 -3
- package/dist/hover-card/hover-card-root.svelte +7 -55
- package/dist/hover-card/hover-card-trigger.svelte +79 -40
- package/dist/hover-card/hover-card-trigger.svelte.d.ts +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/internal/motion.d.ts +1 -1
- package/dist/internal/motion.js +1 -1
- package/dist/marquee/marquee.svelte +42 -5
- package/dist/mega-menu/context.svelte.d.ts +2 -1
- package/dist/mega-menu/mega-menu-button-trigger.svelte +2 -14
- package/dist/mega-menu/mega-menu-item.svelte +3 -1
- package/dist/mega-menu/mega-menu-panel.svelte +35 -3
- package/dist/mega-menu/mega-menu-root.svelte +28 -13
- package/dist/menubar/context.svelte.d.ts +2 -2
- package/dist/menubar/menubar-button-trigger.svelte +5 -3
- package/dist/menubar/menubar-content.svelte +20 -12
- package/dist/menubar/menubar-root.svelte +4 -4
- package/dist/multi-select-combobox/multi-select-combobox-content.svelte +18 -55
- package/dist/multi-select-combobox/multi-select-combobox-content.svelte.d.ts +1 -1
- package/dist/noise/noise.svelte +38 -6
- package/dist/notification-center/context.svelte.d.ts +0 -1
- package/dist/notification-center/notification-center-panel.svelte +54 -35
- package/dist/notification-center/notification-center-root.svelte +0 -1
- package/dist/notification-center/notification-center-trigger-button.svelte +1 -8
- package/dist/option-picker/option-picker-description.svelte +2 -2
- package/dist/option-picker/option-picker-item.svelte +10 -3
- package/dist/option-picker/option-picker-label.svelte +2 -2
- package/dist/option-picker/option-picker-preview.svelte +18 -13
- package/dist/phone-input/phone-input-select.svelte +2 -152
- package/dist/phone-input/phone-input-select.svelte.d.ts +1 -7
- package/dist/rich-text-editor/rich-text-editor-toolbar-button-input.svelte +84 -29
- package/dist/scroll-area/scroll-area.svelte +16 -4
- package/dist/select/select-content.svelte +21 -31
- package/dist/select/select-content.svelte.d.ts +1 -1
- package/dist/select/select-root-input.svelte +7 -1
- package/dist/shimmer/shimmer.svelte +22 -12
- package/dist/transfer/transfer-item.svelte +0 -3
- package/dist/transfer/transfer-list-input.svelte +1 -6
- package/dist/tree/context.svelte.d.ts +7 -1
- package/dist/tree/tree-item-children.svelte +12 -10
- package/dist/tree/tree-item-label.svelte +6 -17
- package/dist/tree/tree-item-label.svelte.d.ts +2 -2
- package/dist/tree/tree-item.svelte +28 -1
- package/dist/tree/tree-root.svelte +135 -59
- package/package.json +8 -2
- package/skills/dryui/SKILL.md +1 -0
- package/dist/hover-card/hover-card-root.svelte.d.ts +0 -9
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
import { onMount } from 'svelte';
|
|
3
3
|
import type { Snippet } from 'svelte';
|
|
4
4
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
5
|
+
import {
|
|
6
|
+
observeInViewport,
|
|
7
|
+
observePageVisibility,
|
|
8
|
+
observeReducedMotionPreference
|
|
9
|
+
} from '@dryui/primitives/internal/motion';
|
|
5
10
|
|
|
6
11
|
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
7
12
|
scale?: number;
|
|
@@ -48,17 +53,12 @@
|
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
onMount(() => {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
documentVisible = !document.hidden;
|
|
58
|
-
const handleVisibilityChange = () => {
|
|
59
|
-
documentVisible = !document.hidden;
|
|
60
|
-
};
|
|
61
|
-
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
56
|
+
const stopMotion = observeReducedMotionPreference((matches) => {
|
|
57
|
+
prefersReducedMotion = matches;
|
|
58
|
+
});
|
|
59
|
+
const stopVisibility = observePageVisibility((visible) => {
|
|
60
|
+
documentVisible = visible;
|
|
61
|
+
});
|
|
62
62
|
|
|
63
63
|
let frameId: number | undefined;
|
|
64
64
|
let lastTime = 0;
|
|
@@ -86,21 +86,15 @@
|
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
$effect(() => {
|
|
89
|
-
if (!element
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const entry = entries[0];
|
|
93
|
-
inViewport = entry?.isIntersecting ?? true;
|
|
89
|
+
if (!element) return;
|
|
90
|
+
return observeInViewport(element, (inView) => {
|
|
91
|
+
inViewport = inView;
|
|
94
92
|
});
|
|
95
|
-
|
|
96
|
-
observer.observe(element);
|
|
97
|
-
|
|
98
|
-
return () => observer.disconnect();
|
|
99
93
|
});
|
|
100
94
|
|
|
101
95
|
return () => {
|
|
102
|
-
|
|
103
|
-
|
|
96
|
+
stopMotion();
|
|
97
|
+
stopVisibility();
|
|
104
98
|
};
|
|
105
99
|
});
|
|
106
100
|
|
|
@@ -5,6 +5,8 @@ export interface DragAndDropContext {
|
|
|
5
5
|
readonly orientation: 'vertical' | 'horizontal';
|
|
6
6
|
readonly hasHandle: boolean;
|
|
7
7
|
readonly foreignOverIndex: number | null;
|
|
8
|
+
readonly instructionsId: string;
|
|
9
|
+
readonly itemCount: number;
|
|
8
10
|
registerHandle: () => void;
|
|
9
11
|
startDrag: (index: number, event: PointerEvent) => void;
|
|
10
12
|
handleDragOver: (index: number) => void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { mergeIds } from '@dryui/primitives';
|
|
4
5
|
import { getDragAndDropCtx } from './context.svelte.js';
|
|
5
6
|
|
|
6
7
|
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
@@ -8,11 +9,17 @@
|
|
|
8
9
|
children?: Snippet | undefined;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
let {
|
|
12
|
+
let {
|
|
13
|
+
index,
|
|
14
|
+
children,
|
|
15
|
+
'aria-describedby': ariaDescribedBy,
|
|
16
|
+
'aria-label': ariaLabel,
|
|
17
|
+
class: className,
|
|
18
|
+
...rest
|
|
19
|
+
}: Props = $props();
|
|
12
20
|
|
|
13
21
|
const ctx = getDragAndDropCtx();
|
|
14
22
|
|
|
15
|
-
// Register that a handle exists so items don't initiate drag themselves
|
|
16
23
|
ctx.registerHandle();
|
|
17
24
|
|
|
18
25
|
function handlePointerDown(e: PointerEvent) {
|
|
@@ -28,18 +35,40 @@
|
|
|
28
35
|
ctx.endDrag();
|
|
29
36
|
}
|
|
30
37
|
}
|
|
38
|
+
|
|
39
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
40
|
+
const upKey = ctx.orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft';
|
|
41
|
+
const downKey = ctx.orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight';
|
|
42
|
+
|
|
43
|
+
if (e.key === upKey) {
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
ctx.moveItem(index, 'up');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (e.key === downKey) {
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
ctx.moveItem(index, 'down');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (e.key === 'Escape' && ctx.isDragging) {
|
|
54
|
+
e.preventDefault();
|
|
55
|
+
ctx.cancelDrag();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
31
58
|
</script>
|
|
32
59
|
|
|
33
60
|
<div
|
|
34
61
|
role="button"
|
|
35
62
|
tabindex="0"
|
|
36
|
-
aria-roledescription="
|
|
37
|
-
aria-label=
|
|
38
|
-
aria-
|
|
63
|
+
aria-roledescription="reorder handle"
|
|
64
|
+
aria-label={ariaLabel ?? `Reorder item ${index + 1}`}
|
|
65
|
+
aria-describedby={mergeIds(ariaDescribedBy, ctx.instructionsId)}
|
|
66
|
+
aria-keyshortcuts={ctx.orientation === 'vertical' ? 'ArrowUp ArrowDown' : 'ArrowLeft ArrowRight'}
|
|
39
67
|
data-dnd-handle
|
|
40
68
|
data-dragging={ctx.isDragging && ctx.draggedIndex === index ? '' : undefined}
|
|
41
69
|
onpointerdown={handlePointerDown}
|
|
42
70
|
onpointerup={handlePointerUp}
|
|
71
|
+
onkeydown={handleKeydown}
|
|
43
72
|
{...rest}
|
|
44
73
|
class={className}
|
|
45
74
|
>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { mergeIds } from '@dryui/primitives';
|
|
4
5
|
import { getDragAndDropCtx } from './context.svelte.js';
|
|
5
6
|
|
|
6
7
|
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
@@ -8,7 +9,13 @@
|
|
|
8
9
|
children: Snippet<[{ isDragging: boolean; isOver: boolean }]>;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
let {
|
|
12
|
+
let {
|
|
13
|
+
index,
|
|
14
|
+
children,
|
|
15
|
+
'aria-describedby': ariaDescribedBy,
|
|
16
|
+
class: className,
|
|
17
|
+
...rest
|
|
18
|
+
}: Props = $props();
|
|
12
19
|
|
|
13
20
|
const ctx = getDragAndDropCtx();
|
|
14
21
|
|
|
@@ -18,22 +25,17 @@
|
|
|
18
25
|
ctx.foreignOverIndex === index
|
|
19
26
|
);
|
|
20
27
|
|
|
21
|
-
let grabbing = $state(false);
|
|
22
|
-
|
|
23
28
|
function handlePointerDown(e: PointerEvent) {
|
|
24
29
|
// Only start drag if there's no handle registered (drag from whole item)
|
|
25
30
|
if (!ctx.hasHandle) {
|
|
26
31
|
e.preventDefault();
|
|
27
32
|
ctx.startDrag(index, e);
|
|
28
|
-
grabbing = true;
|
|
29
33
|
}
|
|
30
34
|
}
|
|
31
35
|
|
|
32
|
-
function handlePointerUp() {
|
|
33
|
-
grabbing = false;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
36
|
function handleKeydown(e: KeyboardEvent) {
|
|
37
|
+
if (ctx.hasHandle) return;
|
|
38
|
+
|
|
37
39
|
if (e.key === ' ' || e.key === 'Enter') {
|
|
38
40
|
e.preventDefault();
|
|
39
41
|
if (ctx.isDragging && ctx.draggedIndex === index) {
|
|
@@ -54,25 +56,32 @@
|
|
|
54
56
|
ctx.moveItem(index, 'down');
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
if (e.key === 'Escape') {
|
|
59
|
+
if (e.key === 'Escape' && ctx.isDragging) {
|
|
58
60
|
e.preventDefault();
|
|
59
61
|
ctx.cancelDrag();
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
</script>
|
|
63
65
|
|
|
66
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex (keyboard reordering keeps the item itself focusable when there is no separate handle) -->
|
|
64
67
|
<div
|
|
65
|
-
role="
|
|
66
|
-
tabindex=
|
|
67
|
-
aria-roledescription="sortable"
|
|
68
|
-
aria-
|
|
68
|
+
role="listitem"
|
|
69
|
+
tabindex={ctx.hasHandle ? undefined : 0}
|
|
70
|
+
aria-roledescription="sortable item"
|
|
71
|
+
aria-posinset={index + 1}
|
|
72
|
+
aria-setsize={ctx.itemCount}
|
|
73
|
+
aria-describedby={mergeIds(ariaDescribedBy, !ctx.hasHandle ? ctx.instructionsId : undefined)}
|
|
74
|
+
aria-keyshortcuts={!ctx.hasHandle
|
|
75
|
+
? ctx.orientation === 'vertical'
|
|
76
|
+
? 'ArrowUp ArrowDown'
|
|
77
|
+
: 'ArrowLeft ArrowRight'
|
|
78
|
+
: undefined}
|
|
69
79
|
data-dnd-item
|
|
70
80
|
data-index={index}
|
|
71
81
|
data-dragging={itemIsDragging ? '' : undefined}
|
|
72
82
|
data-drag-active={ctx.isDragging ? '' : undefined}
|
|
73
83
|
data-over={isOver ? '' : undefined}
|
|
74
84
|
onpointerdown={handlePointerDown}
|
|
75
|
-
onpointerup={handlePointerUp}
|
|
76
85
|
onkeydown={handleKeydown}
|
|
77
86
|
{...rest}
|
|
78
87
|
class={className}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { flushSync } from 'svelte';
|
|
3
3
|
import type { Snippet } from 'svelte';
|
|
4
4
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
5
|
+
import { createId, mergeIds } from '@dryui/primitives';
|
|
5
6
|
import { setDragAndDropCtx } from './context.svelte.js';
|
|
6
7
|
import { getGroupCtx } from './group-context.svelte.js';
|
|
7
8
|
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
orientation = 'vertical',
|
|
20
21
|
listId,
|
|
21
22
|
children,
|
|
23
|
+
'aria-describedby': ariaDescribedBy,
|
|
22
24
|
class: className,
|
|
23
25
|
...rest
|
|
24
26
|
}: Props = $props();
|
|
@@ -46,6 +48,16 @@
|
|
|
46
48
|
let crossListIndex: number | null = null;
|
|
47
49
|
|
|
48
50
|
const groupCtx = getGroupCtx();
|
|
51
|
+
const instructionsId = createId('dry-dnd-instructions');
|
|
52
|
+
|
|
53
|
+
let keyboardInstructions = $derived.by(() => {
|
|
54
|
+
const arrowKeys =
|
|
55
|
+
orientation === 'vertical' ? 'Arrow Up and Arrow Down' : 'Arrow Left and Arrow Right';
|
|
56
|
+
if (hasHandle) {
|
|
57
|
+
return `Use the reorder handle and ${arrowKeys} to move items. Drag with a pointer to reorder, and press Escape to cancel an active drag.`;
|
|
58
|
+
}
|
|
59
|
+
return `Focus an item and use ${arrowKeys} to move it. Drag with a pointer to reorder, and press Escape to cancel an active drag.`;
|
|
60
|
+
});
|
|
49
61
|
|
|
50
62
|
// Register with group when inside one
|
|
51
63
|
$effect(() => {
|
|
@@ -71,6 +83,30 @@
|
|
|
71
83
|
const FLIP_DURATION = 200;
|
|
72
84
|
const FLIP_EASING = 'cubic-bezier(0.2, 0, 0, 1)';
|
|
73
85
|
|
|
86
|
+
function getInsertAt(from: number, to: number) {
|
|
87
|
+
return to > from ? to - 1 : to;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function announceReorder(from: number, to: number) {
|
|
91
|
+
if (to === from || to === from + 1) return;
|
|
92
|
+
const insertAt = getInsertAt(from, to);
|
|
93
|
+
announce(`Item moved to position ${insertAt + 1} of ${items.length}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function focusReorderedControl(index: number) {
|
|
97
|
+
const targetItem = rootElement?.querySelector<HTMLElement>(
|
|
98
|
+
`[data-dnd-item][data-index="${index}"]`
|
|
99
|
+
);
|
|
100
|
+
if (!targetItem) return;
|
|
101
|
+
|
|
102
|
+
if (hasHandle) {
|
|
103
|
+
targetItem.querySelector<HTMLElement>('[data-dnd-handle]')?.focus();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
targetItem.focus();
|
|
108
|
+
}
|
|
109
|
+
|
|
74
110
|
function captureRects(): Map<number, DOMRect> {
|
|
75
111
|
const rects = new Map<number, DOMRect>();
|
|
76
112
|
if (!rootElement) return rects;
|
|
@@ -85,7 +121,7 @@
|
|
|
85
121
|
if (to === from || to === from + 1) return;
|
|
86
122
|
|
|
87
123
|
const firstRects = captureRects();
|
|
88
|
-
const insertAt =
|
|
124
|
+
const insertAt = getInsertAt(from, to);
|
|
89
125
|
|
|
90
126
|
const newItems = [...items];
|
|
91
127
|
const removed = newItems.splice(from, 1);
|
|
@@ -337,7 +373,9 @@
|
|
|
337
373
|
isPending = false;
|
|
338
374
|
isDragging = true;
|
|
339
375
|
createPreview();
|
|
340
|
-
announce(
|
|
376
|
+
announce(
|
|
377
|
+
`Grabbed item at position ${(draggedIndex ?? 0) + 1} of ${items.length}. Drag to move and release to drop.`
|
|
378
|
+
);
|
|
341
379
|
}
|
|
342
380
|
|
|
343
381
|
if (isDragging) {
|
|
@@ -360,7 +398,7 @@
|
|
|
360
398
|
) {
|
|
361
399
|
resetState();
|
|
362
400
|
groupCtx.move(listId, from, toListId, toIdx);
|
|
363
|
-
announce(
|
|
401
|
+
announce(`Item moved to position ${toIdx + 1} in another list`);
|
|
364
402
|
return;
|
|
365
403
|
}
|
|
366
404
|
animateCrossListDrop(from, toListId, toIdx);
|
|
@@ -379,6 +417,7 @@
|
|
|
379
417
|
) {
|
|
380
418
|
resetState();
|
|
381
419
|
applyReorder(from, to);
|
|
420
|
+
announceReorder(from, to);
|
|
382
421
|
return;
|
|
383
422
|
}
|
|
384
423
|
animateDrop(from, to);
|
|
@@ -490,6 +529,7 @@
|
|
|
490
529
|
isAnimating = false;
|
|
491
530
|
resetState();
|
|
492
531
|
applyReorder(from, to);
|
|
532
|
+
announceReorder(from, to);
|
|
493
533
|
})
|
|
494
534
|
.catch(() => {
|
|
495
535
|
if (placeholderEl) placeholderEl.style.removeProperty('display');
|
|
@@ -607,7 +647,7 @@
|
|
|
607
647
|
isAnimating = false;
|
|
608
648
|
resetState();
|
|
609
649
|
flushSync(() => groupCtx!.move(listId!, from, toListId, toIdx));
|
|
610
|
-
announce(
|
|
650
|
+
announce(`Item moved to position ${toIdx + 1} in another list`);
|
|
611
651
|
})
|
|
612
652
|
.catch(() => {
|
|
613
653
|
if (placeholderEl) placeholderEl.style.removeProperty('display');
|
|
@@ -645,14 +685,6 @@
|
|
|
645
685
|
isPending = false;
|
|
646
686
|
}
|
|
647
687
|
|
|
648
|
-
function handleKeydown(e: KeyboardEvent) {
|
|
649
|
-
if (e.key === 'Escape' && isDragging && !isAnimating) {
|
|
650
|
-
e.preventDefault();
|
|
651
|
-
announce('Reorder cancelled');
|
|
652
|
-
resetState();
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
688
|
setDragAndDropCtx({
|
|
657
689
|
get draggedIndex() {
|
|
658
690
|
return draggedIndex;
|
|
@@ -672,6 +704,12 @@
|
|
|
672
704
|
get foreignOverIndex() {
|
|
673
705
|
return foreignOverIndex;
|
|
674
706
|
},
|
|
707
|
+
get instructionsId() {
|
|
708
|
+
return instructionsId;
|
|
709
|
+
},
|
|
710
|
+
get itemCount() {
|
|
711
|
+
return items.length;
|
|
712
|
+
},
|
|
675
713
|
registerHandle() {
|
|
676
714
|
hasHandle = true;
|
|
677
715
|
},
|
|
@@ -711,6 +749,7 @@
|
|
|
711
749
|
if (toGap !== fromIndex && toGap !== fromIndex + 1) {
|
|
712
750
|
reorder(fromIndex, toGap);
|
|
713
751
|
const insertAt = toGap > fromIndex ? toGap - 1 : toGap;
|
|
752
|
+
focusReorderedControl(insertAt);
|
|
714
753
|
announce(`Item moved to position ${insertAt + 1} of ${items.length}`);
|
|
715
754
|
}
|
|
716
755
|
},
|
|
@@ -729,10 +768,13 @@
|
|
|
729
768
|
|
|
730
769
|
<div
|
|
731
770
|
bind:this={rootElement}
|
|
732
|
-
role="
|
|
771
|
+
role="list"
|
|
772
|
+
aria-roledescription="sortable list"
|
|
773
|
+
aria-describedby={mergeIds(ariaDescribedBy, instructionsId)}
|
|
733
774
|
data-dnd-root
|
|
734
775
|
data-orientation={orientation}
|
|
735
776
|
data-dragging={isDragging ? '' : undefined}
|
|
777
|
+
data-handle={hasHandle ? '' : undefined}
|
|
736
778
|
data-over-end={(isDragging && overIndex !== null && overIndex >= items.length) ||
|
|
737
779
|
(foreignOverIndex !== null && foreignOverIndex >= items.length)
|
|
738
780
|
? ''
|
|
@@ -740,18 +782,20 @@
|
|
|
740
782
|
onpointermove={handlePointerMove}
|
|
741
783
|
onpointerup={handlePointerUp}
|
|
742
784
|
onpointercancel={handlePointerCancel}
|
|
743
|
-
onkeydown={handleKeydown}
|
|
744
785
|
{...rest}
|
|
745
786
|
class={className}
|
|
746
787
|
>
|
|
747
788
|
{@render children()}
|
|
748
|
-
<div
|
|
789
|
+
<div id={instructionsId} data-dnd-instructions class="assistiveText">
|
|
790
|
+
{keyboardInstructions}
|
|
791
|
+
</div>
|
|
792
|
+
<div data-dnd-live-region aria-live="assertive" aria-atomic="true" class="assistiveText">
|
|
749
793
|
{liveRegionMessage}
|
|
750
794
|
</div>
|
|
751
795
|
</div>
|
|
752
796
|
|
|
753
797
|
<style>
|
|
754
|
-
.
|
|
798
|
+
.assistiveText {
|
|
755
799
|
position: absolute;
|
|
756
800
|
aspect-ratio: 1;
|
|
757
801
|
height: 1px;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { observeOffscreenState } from '@dryui/primitives/internal/motion';
|
|
4
5
|
|
|
5
6
|
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
6
7
|
color?: string | undefined;
|
|
@@ -55,6 +56,8 @@
|
|
|
55
56
|
node.style.setProperty('--dry-rays-blend', blendMode);
|
|
56
57
|
node.style.setProperty('--dry-rays-speed', speedValue);
|
|
57
58
|
});
|
|
59
|
+
|
|
60
|
+
$effect(() => observeOffscreenState(node, { rootMargin: '200px' }));
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
// Flash-on-load: background is a computed conic-gradient with a dynamic number of stops
|
|
@@ -107,6 +110,14 @@
|
|
|
107
110
|
animation: god-rays-rotate var(--dry-rays-speed, 30s) linear infinite;
|
|
108
111
|
}
|
|
109
112
|
|
|
113
|
+
[data-god-rays][data-animated]:not([data-offscreen]) [data-god-rays-layer] {
|
|
114
|
+
will-change: transform, filter;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
[data-god-rays][data-offscreen] [data-god-rays-layer] {
|
|
118
|
+
animation-play-state: paused;
|
|
119
|
+
}
|
|
120
|
+
|
|
110
121
|
@keyframes god-rays-rotate {
|
|
111
122
|
to {
|
|
112
123
|
transform: rotate(360deg);
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
5
5
|
import {
|
|
6
6
|
getReducedMotionPreference,
|
|
7
|
+
observeInViewport,
|
|
8
|
+
observePageVisibility,
|
|
7
9
|
observeReducedMotionPreference,
|
|
8
10
|
registerPropertyOnce,
|
|
9
11
|
supportsPropertyRegistration,
|
|
@@ -30,6 +32,9 @@
|
|
|
30
32
|
let element = $state<HTMLDivElement | null>(null);
|
|
31
33
|
let prefersReducedMotion = $state(false);
|
|
32
34
|
let animated = $state(false);
|
|
35
|
+
let onScreen = $state(true);
|
|
36
|
+
let tabVisible = $state(true);
|
|
37
|
+
const paused = $derived(!onScreen || !tabVisible);
|
|
33
38
|
let pointerX = $state('50%');
|
|
34
39
|
let pointerY = $state('50%');
|
|
35
40
|
let pointerFrame = $state<number | null>(null);
|
|
@@ -126,19 +131,31 @@
|
|
|
126
131
|
};
|
|
127
132
|
|
|
128
133
|
const stopMotionObserver = observeReducedMotionPreference(updateAnimatedState);
|
|
134
|
+
const stopVisibility = observePageVisibility((visible) => {
|
|
135
|
+
tabVisible = visible;
|
|
136
|
+
});
|
|
137
|
+
let stopViewport = () => {};
|
|
138
|
+
if (element) {
|
|
139
|
+
stopViewport = observeInViewport(
|
|
140
|
+
element,
|
|
141
|
+
(inView) => {
|
|
142
|
+
onScreen = inView;
|
|
143
|
+
},
|
|
144
|
+
{ rootMargin: '200px' }
|
|
145
|
+
);
|
|
146
|
+
}
|
|
129
147
|
|
|
130
148
|
if (!supportsPropertyRegistration() || getReducedMotionPreference()) {
|
|
131
149
|
animated = false;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
stopMotionObserver();
|
|
135
|
-
};
|
|
150
|
+
} else {
|
|
151
|
+
animated = true;
|
|
136
152
|
}
|
|
137
153
|
|
|
138
|
-
animated = true;
|
|
139
154
|
return () => {
|
|
140
155
|
cancelQueuedPointerPosition();
|
|
141
156
|
stopMotionObserver();
|
|
157
|
+
stopVisibility();
|
|
158
|
+
stopViewport();
|
|
142
159
|
};
|
|
143
160
|
});
|
|
144
161
|
|
|
@@ -164,6 +181,7 @@
|
|
|
164
181
|
data-animated={animated || undefined}
|
|
165
182
|
data-interactive={interactive || undefined}
|
|
166
183
|
data-reduced-motion={prefersReducedMotion || undefined}
|
|
184
|
+
data-paused={paused || undefined}
|
|
167
185
|
onpointermove={interactive ? handlePointerMove : undefined}
|
|
168
186
|
onpointerleave={interactive ? handlePointerLeave : undefined}
|
|
169
187
|
{...rest}
|
|
@@ -213,6 +231,10 @@
|
|
|
213
231
|
animation: mesh-cycle var(--dry-mesh-duration) ease-in-out infinite alternate;
|
|
214
232
|
}
|
|
215
233
|
|
|
234
|
+
[data-gradient-mesh][data-animated][data-paused] {
|
|
235
|
+
animation-play-state: paused;
|
|
236
|
+
}
|
|
237
|
+
|
|
216
238
|
[data-gradient-mesh][data-interactive] {
|
|
217
239
|
background:
|
|
218
240
|
radial-gradient(
|
|
@@ -1,10 +1 @@
|
|
|
1
|
-
|
|
2
|
-
readonly open: boolean;
|
|
3
|
-
readonly triggerId: string;
|
|
4
|
-
readonly contentId: string;
|
|
5
|
-
triggerEl: HTMLElement | null;
|
|
6
|
-
show: () => void;
|
|
7
|
-
close: () => void;
|
|
8
|
-
}
|
|
9
|
-
export declare const setHoverCardCtx: (ctx: HoverCardContext) => HoverCardContext, getHoverCardCtx: () => HoverCardContext;
|
|
10
|
-
export {};
|
|
1
|
+
export { setHoverCardCtx, getHoverCardCtx, type HoverCardContext } from '@dryui/primitives';
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export const [setHoverCardCtx, getHoverCardCtx] = createContext('hover-card');
|
|
1
|
+
export { setHoverCardCtx, getHoverCardCtx } from '@dryui/primitives';
|
|
@@ -24,6 +24,18 @@
|
|
|
24
24
|
|
|
25
25
|
let contentEl = $state<HTMLDivElement>();
|
|
26
26
|
|
|
27
|
+
$effect(() => {
|
|
28
|
+
if (contentEl) {
|
|
29
|
+
ctx.contentEl = contentEl;
|
|
30
|
+
|
|
31
|
+
return () => {
|
|
32
|
+
if (ctx.contentEl === contentEl) {
|
|
33
|
+
ctx.contentEl = null;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
27
39
|
const popover = createAnchoredPopover({
|
|
28
40
|
triggerEl: () => ctx.triggerEl,
|
|
29
41
|
contentEl: () => contentEl ?? null,
|
|
@@ -35,20 +47,46 @@
|
|
|
35
47
|
function handleKeydown(e: KeyboardEvent) {
|
|
36
48
|
if (e.key === 'Escape') {
|
|
37
49
|
e.preventDefault();
|
|
38
|
-
ctx.
|
|
50
|
+
ctx.forceClose();
|
|
51
|
+
ctx.ignoreNextTriggerFocusOpen = true;
|
|
52
|
+
ctx.triggerEl?.focus();
|
|
39
53
|
}
|
|
40
54
|
}
|
|
55
|
+
|
|
56
|
+
function handlePointerEnter() {
|
|
57
|
+
ctx.contentHovered = true;
|
|
58
|
+
ctx.showImmediate();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function handlePointerLeave() {
|
|
62
|
+
ctx.contentHovered = false;
|
|
63
|
+
ctx.close();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function handleFocusIn() {
|
|
67
|
+
ctx.contentFocused = true;
|
|
68
|
+
ctx.showImmediate();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function handleFocusOut() {
|
|
72
|
+
ctx.contentFocused = false;
|
|
73
|
+
ctx.close();
|
|
74
|
+
}
|
|
41
75
|
</script>
|
|
42
76
|
|
|
43
77
|
<div
|
|
44
78
|
bind:this={contentEl}
|
|
45
79
|
id={ctx.contentId}
|
|
46
80
|
popover="manual"
|
|
81
|
+
role="dialog"
|
|
82
|
+
aria-labelledby={ctx.triggerId}
|
|
47
83
|
data-hover-card-content
|
|
48
84
|
data-state={ctx.open ? 'open' : 'closed'}
|
|
49
85
|
use:popover.applyPosition={style}
|
|
50
|
-
onpointerenter={
|
|
51
|
-
onpointerleave={
|
|
86
|
+
onpointerenter={handlePointerEnter}
|
|
87
|
+
onpointerleave={handlePointerLeave}
|
|
88
|
+
onfocusin={handleFocusIn}
|
|
89
|
+
onfocusout={handleFocusOut}
|
|
52
90
|
onkeydown={handleKeydown}
|
|
53
91
|
class={className}
|
|
54
92
|
{...rest}
|
|
@@ -1,59 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type {
|
|
3
|
-
import {
|
|
4
|
-
import { setHoverCardCtx } from './context.svelte.js';
|
|
2
|
+
import type { ComponentProps } from 'svelte';
|
|
3
|
+
import { HoverCard as PrimitiveHoverCard } from '@dryui/primitives';
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
let { openDelay = 700, closeDelay = 300, children }: Props = $props();
|
|
13
|
-
|
|
14
|
-
let open = $state(false);
|
|
15
|
-
|
|
16
|
-
const triggerId = generateFormId('hovercard-trigger');
|
|
17
|
-
const contentId = generateFormId('hovercard-content');
|
|
18
|
-
|
|
19
|
-
let openTimeout: ReturnType<typeof setTimeout>;
|
|
20
|
-
let closeTimeout: ReturnType<typeof setTimeout>;
|
|
21
|
-
|
|
22
|
-
function show() {
|
|
23
|
-
clearTimeout(closeTimeout);
|
|
24
|
-
openTimeout = setTimeout(() => {
|
|
25
|
-
open = true;
|
|
26
|
-
}, openDelay);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function close() {
|
|
30
|
-
clearTimeout(openTimeout);
|
|
31
|
-
closeTimeout = setTimeout(() => {
|
|
32
|
-
open = false;
|
|
33
|
-
}, closeDelay);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
setHoverCardCtx({
|
|
37
|
-
get open() {
|
|
38
|
-
return open;
|
|
39
|
-
},
|
|
40
|
-
get triggerId() {
|
|
41
|
-
return triggerId;
|
|
42
|
-
},
|
|
43
|
-
get contentId() {
|
|
44
|
-
return contentId;
|
|
45
|
-
},
|
|
46
|
-
triggerEl: null,
|
|
47
|
-
show,
|
|
48
|
-
close
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
$effect(() => {
|
|
52
|
-
return () => {
|
|
53
|
-
clearTimeout(openTimeout);
|
|
54
|
-
clearTimeout(closeTimeout);
|
|
55
|
-
};
|
|
56
|
-
});
|
|
5
|
+
// The styled hover-card only adds theme tokens to trigger/content, so root
|
|
6
|
+
// is a pure re-export of the primitives implementation. The ctx set here is
|
|
7
|
+
// consumed by the ui trigger/content via the shared primitives ctx key.
|
|
8
|
+
let props: ComponentProps<typeof PrimitiveHoverCard.Root> = $props();
|
|
57
9
|
</script>
|
|
58
10
|
|
|
59
|
-
{
|
|
11
|
+
<PrimitiveHoverCard.Root {...props} />
|