@dryui/ui 1.3.1 → 1.4.1
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 +131 -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
|
@@ -10,11 +10,14 @@
|
|
|
10
10
|
const ctx = getRichTextEditorCtx();
|
|
11
11
|
|
|
12
12
|
let toolbarEl: HTMLDivElement;
|
|
13
|
+
type LinkRequestOrigin = 'toolbar' | 'content';
|
|
13
14
|
|
|
14
15
|
let showLinkInput = $state(false);
|
|
15
16
|
let linkUrl = $state('');
|
|
16
17
|
let linkInputEl = $state<HTMLInputElement>();
|
|
17
18
|
let savedSelection = $state<Range | null>(null);
|
|
19
|
+
let linkReturnFocusEl = $state<HTMLElement | null>(null);
|
|
20
|
+
let linkRequestOrigin = $state<LinkRequestOrigin>('toolbar');
|
|
18
21
|
|
|
19
22
|
const FOCUSABLE_SELECTOR = 'button:not([disabled]), [role="button"]:not([disabled])';
|
|
20
23
|
|
|
@@ -33,6 +36,28 @@
|
|
|
33
36
|
}
|
|
34
37
|
});
|
|
35
38
|
|
|
39
|
+
$effect(() => {
|
|
40
|
+
if (!toolbarEl) return;
|
|
41
|
+
|
|
42
|
+
function handleLinkRequest(event: Event) {
|
|
43
|
+
if (ctx.readonly || !(event.target instanceof Node)) return;
|
|
44
|
+
|
|
45
|
+
const editorRoot = toolbarEl.closest<HTMLElement>('[data-rte-root]');
|
|
46
|
+
if (!editorRoot?.contains(event.target)) return;
|
|
47
|
+
|
|
48
|
+
ctx.updateState();
|
|
49
|
+
openLinkInput(ctx.currentLink, {
|
|
50
|
+
origin: 'content',
|
|
51
|
+
returnFocusTo: ctx.contentEl
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
document.addEventListener('rte-link-request', handleLinkRequest);
|
|
56
|
+
return () => {
|
|
57
|
+
document.removeEventListener('rte-link-request', handleLinkRequest);
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
36
61
|
function handleKeydown(event: KeyboardEvent) {
|
|
37
62
|
if (
|
|
38
63
|
event.key !== 'ArrowLeft' &&
|
|
@@ -66,11 +91,29 @@
|
|
|
66
91
|
items[nextIndex]!.focus();
|
|
67
92
|
}
|
|
68
93
|
|
|
69
|
-
function
|
|
94
|
+
function focusToolbarItem(item: HTMLElement | null) {
|
|
95
|
+
if (!toolbarEl || !item) return;
|
|
96
|
+
|
|
97
|
+
for (const focusableItem of queryFocusable(toolbarEl)) {
|
|
98
|
+
focusableItem.setAttribute('tabindex', focusableItem === item ? '0' : '-1');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
item.focus();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function openLinkInput(
|
|
105
|
+
currentLink: string | null,
|
|
106
|
+
options: {
|
|
107
|
+
origin?: LinkRequestOrigin;
|
|
108
|
+
returnFocusTo?: HTMLElement | null;
|
|
109
|
+
} = {}
|
|
110
|
+
) {
|
|
70
111
|
const sel = window.getSelection();
|
|
71
112
|
if (sel && sel.rangeCount > 0) {
|
|
72
113
|
savedSelection = sel.getRangeAt(0).cloneRange();
|
|
73
114
|
}
|
|
115
|
+
linkRequestOrigin = options.origin ?? 'toolbar';
|
|
116
|
+
linkReturnFocusEl = options.returnFocusTo ?? null;
|
|
74
117
|
linkUrl = currentLink ?? 'https://';
|
|
75
118
|
showLinkInput = true;
|
|
76
119
|
requestAnimationFrame(() => {
|
|
@@ -79,13 +122,7 @@
|
|
|
79
122
|
});
|
|
80
123
|
}
|
|
81
124
|
|
|
82
|
-
function
|
|
83
|
-
showLinkInput = false;
|
|
84
|
-
linkUrl = '';
|
|
85
|
-
savedSelection = null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function restoreSelection() {
|
|
125
|
+
function restoreSavedSelection() {
|
|
89
126
|
if (savedSelection) {
|
|
90
127
|
const sel = window.getSelection();
|
|
91
128
|
if (sel) {
|
|
@@ -94,6 +131,35 @@
|
|
|
94
131
|
}
|
|
95
132
|
}
|
|
96
133
|
}
|
|
134
|
+
|
|
135
|
+
function cancelLinkInput() {
|
|
136
|
+
if (linkRequestOrigin === 'content') {
|
|
137
|
+
restoreSavedSelection();
|
|
138
|
+
ctx.contentEl?.focus();
|
|
139
|
+
} else {
|
|
140
|
+
focusToolbarItem(linkReturnFocusEl);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
closeLinkInput();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function applyLink() {
|
|
147
|
+
if (linkUrl) {
|
|
148
|
+
restoreSavedSelection();
|
|
149
|
+
ctx.insertLink(linkUrl);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
closeLinkInput();
|
|
153
|
+
ctx.contentEl?.focus();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function closeLinkInput() {
|
|
157
|
+
showLinkInput = false;
|
|
158
|
+
linkUrl = '';
|
|
159
|
+
savedSelection = null;
|
|
160
|
+
linkReturnFocusEl = null;
|
|
161
|
+
linkRequestOrigin = 'toolbar';
|
|
162
|
+
}
|
|
97
163
|
</script>
|
|
98
164
|
|
|
99
165
|
<div
|
|
@@ -321,13 +387,11 @@
|
|
|
321
387
|
title="Link (Ctrl+K)"
|
|
322
388
|
tabindex={-1}
|
|
323
389
|
onmousedown={(e) => e.preventDefault()}
|
|
324
|
-
onclick={() =>
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
}}
|
|
390
|
+
onclick={(event) =>
|
|
391
|
+
openLinkInput(ctx.currentLink, {
|
|
392
|
+
origin: 'toolbar',
|
|
393
|
+
returnFocusTo: event.currentTarget as HTMLElement
|
|
394
|
+
})}
|
|
331
395
|
>
|
|
332
396
|
<svg
|
|
333
397
|
width="16"
|
|
@@ -382,19 +446,16 @@
|
|
|
382
446
|
<input
|
|
383
447
|
bind:value={linkUrl}
|
|
384
448
|
bind:this={linkInputEl}
|
|
449
|
+
aria-label="Link URL"
|
|
385
450
|
data-part="linkInput"
|
|
386
451
|
type="url"
|
|
387
452
|
placeholder="https://example.com"
|
|
388
453
|
onkeydown={(e) => {
|
|
389
454
|
if (e.key === 'Enter') {
|
|
390
455
|
e.preventDefault();
|
|
391
|
-
|
|
392
|
-
restoreSelection();
|
|
393
|
-
ctx.insertLink(linkUrl);
|
|
394
|
-
}
|
|
395
|
-
closeLinkInput();
|
|
456
|
+
applyLink();
|
|
396
457
|
} else if (e.key === 'Escape') {
|
|
397
|
-
|
|
458
|
+
cancelLinkInput();
|
|
398
459
|
}
|
|
399
460
|
}}
|
|
400
461
|
/>
|
|
@@ -403,20 +464,14 @@
|
|
|
403
464
|
size="sm"
|
|
404
465
|
type="button"
|
|
405
466
|
data-part="linkApply"
|
|
406
|
-
onclick={() =>
|
|
407
|
-
if (linkUrl) {
|
|
408
|
-
restoreSelection();
|
|
409
|
-
ctx.insertLink(linkUrl);
|
|
410
|
-
}
|
|
411
|
-
closeLinkInput();
|
|
412
|
-
}}>Apply</Button
|
|
467
|
+
onclick={() => applyLink()}>Apply</Button
|
|
413
468
|
>
|
|
414
469
|
<Button
|
|
415
470
|
variant="outline"
|
|
416
471
|
size="sm"
|
|
417
472
|
type="button"
|
|
418
473
|
data-part="linkCancel"
|
|
419
|
-
onclick={() =>
|
|
474
|
+
onclick={() => cancelLinkInput()}>Cancel</Button
|
|
420
475
|
>
|
|
421
476
|
</div>
|
|
422
477
|
{/if}
|
|
@@ -7,16 +7,28 @@
|
|
|
7
7
|
children: Snippet;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
let {
|
|
10
|
+
let {
|
|
11
|
+
orientation = 'vertical',
|
|
12
|
+
role: roleProp,
|
|
13
|
+
tabindex = 0,
|
|
14
|
+
'aria-label': ariaLabel,
|
|
15
|
+
'aria-labelledby': ariaLabelledBy,
|
|
16
|
+
class: className,
|
|
17
|
+
children,
|
|
18
|
+
...rest
|
|
19
|
+
}: Props = $props();
|
|
20
|
+
|
|
21
|
+
const role = $derived(roleProp ?? (ariaLabel || ariaLabelledBy ? 'region' : undefined));
|
|
11
22
|
</script>
|
|
12
23
|
|
|
13
24
|
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
14
25
|
<div
|
|
15
|
-
role
|
|
16
|
-
aria-label=
|
|
26
|
+
{role}
|
|
27
|
+
aria-label={ariaLabel}
|
|
28
|
+
aria-labelledby={ariaLabelledBy}
|
|
17
29
|
data-scroll-area
|
|
18
30
|
data-orientation={orientation}
|
|
19
|
-
tabindex
|
|
31
|
+
{tabindex}
|
|
20
32
|
class={className}
|
|
21
33
|
{...rest}
|
|
22
34
|
>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { fromAction } from 'svelte/attachments';
|
|
2
3
|
import type { Snippet } from 'svelte';
|
|
3
4
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
-
import {
|
|
5
|
-
import type { Placement } from '@dryui/primitives';
|
|
5
|
+
import { createAnchoredPopover, type Placement } from '@dryui/primitives';
|
|
6
6
|
import { getSelectCtx } from './context.svelte.js';
|
|
7
7
|
|
|
8
8
|
const OPTION_SELECTOR = '[role="option"]:not([data-disabled])';
|
|
@@ -24,30 +24,17 @@
|
|
|
24
24
|
|
|
25
25
|
const ctx = getSelectCtx();
|
|
26
26
|
|
|
27
|
-
let el = $state<HTMLDivElement>();
|
|
28
|
-
|
|
29
|
-
const anchor = createAnchorPosition(
|
|
30
|
-
() => ctx.triggerEl,
|
|
31
|
-
() => el ?? null,
|
|
32
|
-
{
|
|
33
|
-
get placement() {
|
|
34
|
-
return placement;
|
|
35
|
-
},
|
|
36
|
-
get offset() {
|
|
37
|
-
return offset;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
);
|
|
27
|
+
let el = $state<HTMLDivElement | null>(null);
|
|
41
28
|
|
|
42
|
-
|
|
43
|
-
|
|
29
|
+
function attachContent(node: HTMLDivElement) {
|
|
30
|
+
el = node;
|
|
44
31
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
}
|
|
32
|
+
return () => {
|
|
33
|
+
if (el === node) {
|
|
34
|
+
el = null;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
51
38
|
|
|
52
39
|
function getOptionItems(container: HTMLElement): HTMLElement[] {
|
|
53
40
|
return Array.from(container.querySelectorAll<HTMLElement>(OPTION_SELECTOR));
|
|
@@ -59,12 +46,14 @@
|
|
|
59
46
|
items[clamped]?.focus();
|
|
60
47
|
}
|
|
61
48
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
49
|
+
const popover = createAnchoredPopover({
|
|
50
|
+
triggerEl: () => ctx.triggerEl,
|
|
51
|
+
contentEl: () => el ?? null,
|
|
52
|
+
open: () => ctx.open,
|
|
53
|
+
placement: () => placement,
|
|
54
|
+
offset: () => offset,
|
|
55
|
+
onAfterShow: () => {
|
|
65
56
|
focusFirstSelectItem();
|
|
66
|
-
} else if (!ctx.open && el?.matches(':popover-open')) {
|
|
67
|
-
el.hidePopover();
|
|
68
57
|
}
|
|
69
58
|
});
|
|
70
59
|
|
|
@@ -127,7 +116,8 @@
|
|
|
127
116
|
</script>
|
|
128
117
|
|
|
129
118
|
<div
|
|
130
|
-
|
|
119
|
+
{@attach attachContent}
|
|
120
|
+
{@attach fromAction(popover.applyPosition, () => style)}
|
|
131
121
|
popover="auto"
|
|
132
122
|
role="listbox"
|
|
133
123
|
id={ctx.contentId}
|
|
@@ -156,7 +146,7 @@
|
|
|
156
146
|
margin: 0;
|
|
157
147
|
|
|
158
148
|
display: grid;
|
|
159
|
-
grid-template-columns: minmax(12rem, max-content);
|
|
149
|
+
grid-template-columns: minmax(max(12rem, anchor-size(inline)), max-content);
|
|
160
150
|
background: var(--dry-overlay-bg, var(--dry-color-bg-overlay));
|
|
161
151
|
border: 1px solid var(--dry-overlay-border, var(--dry-color-stroke-weak));
|
|
162
152
|
border-radius: var(--dry-overlay-radius, var(--dry-radius-md));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
-
import type
|
|
3
|
+
import { type Placement } from '@dryui/primitives';
|
|
4
4
|
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
5
5
|
placement?: Placement;
|
|
6
6
|
offset?: number;
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
const contentId = generateFormId('select-content');
|
|
40
40
|
|
|
41
41
|
let displayText = $state('');
|
|
42
|
+
let triggerEl = $state<HTMLElement | null>(null);
|
|
42
43
|
|
|
43
44
|
setSelectCtx({
|
|
44
45
|
get open() {
|
|
@@ -55,7 +56,12 @@
|
|
|
55
56
|
},
|
|
56
57
|
triggerId,
|
|
57
58
|
contentId,
|
|
58
|
-
triggerEl
|
|
59
|
+
get triggerEl() {
|
|
60
|
+
return triggerEl;
|
|
61
|
+
},
|
|
62
|
+
set triggerEl(element: HTMLElement | null) {
|
|
63
|
+
triggerEl = element;
|
|
64
|
+
},
|
|
59
65
|
show() {
|
|
60
66
|
if (!disabled) open = true;
|
|
61
67
|
},
|
|
@@ -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 { observeInViewport, observePageVisibility } from '@dryui/primitives/internal/motion';
|
|
4
5
|
|
|
5
6
|
interface Props extends Omit<HTMLAttributes<HTMLSpanElement>, 'children'> {
|
|
6
7
|
color?: string;
|
|
@@ -17,27 +18,36 @@
|
|
|
17
18
|
}: Props = $props();
|
|
18
19
|
|
|
19
20
|
function setup(node: HTMLSpanElement) {
|
|
21
|
+
let onScreen = true;
|
|
22
|
+
let tabVisible = true;
|
|
23
|
+
|
|
24
|
+
const apply = () => {
|
|
25
|
+
if (onScreen && tabVisible) node.dataset.active = '';
|
|
26
|
+
else delete node.dataset.active;
|
|
27
|
+
};
|
|
28
|
+
|
|
20
29
|
$effect(() => {
|
|
21
30
|
node.style.setProperty('--dry-shimmer-color', color);
|
|
22
31
|
node.style.setProperty('--dry-shimmer-duration', `${duration}s`);
|
|
23
32
|
});
|
|
24
33
|
|
|
25
34
|
$effect(() => {
|
|
26
|
-
|
|
27
|
-
node
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
(entries) => {
|
|
32
|
-
for (const entry of entries) {
|
|
33
|
-
if (entry.isIntersecting) node.dataset.active = '';
|
|
34
|
-
else delete node.dataset.active;
|
|
35
|
-
}
|
|
35
|
+
const unsubscribeViewport = observeInViewport(
|
|
36
|
+
node,
|
|
37
|
+
(inView) => {
|
|
38
|
+
onScreen = inView;
|
|
39
|
+
apply();
|
|
36
40
|
},
|
|
37
41
|
{ rootMargin: '100px' }
|
|
38
42
|
);
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
const unsubscribeVisibility = observePageVisibility((visible) => {
|
|
44
|
+
tabVisible = visible;
|
|
45
|
+
apply();
|
|
46
|
+
});
|
|
47
|
+
return () => {
|
|
48
|
+
unsubscribeViewport();
|
|
49
|
+
unsubscribeVisibility();
|
|
50
|
+
};
|
|
41
51
|
});
|
|
42
52
|
}
|
|
43
53
|
</script>
|
|
@@ -25,12 +25,9 @@
|
|
|
25
25
|
</script>
|
|
26
26
|
|
|
27
27
|
<label
|
|
28
|
-
role="option"
|
|
29
28
|
data-transfer-item
|
|
30
29
|
data-disabled={disabled ? '' : undefined}
|
|
31
30
|
data-selected={isSelected ? '' : undefined}
|
|
32
|
-
aria-selected={isSelected}
|
|
33
|
-
aria-disabled={disabled}
|
|
34
31
|
{...rest}
|
|
35
32
|
>
|
|
36
33
|
<Checkbox checked={isSelected} {disabled} onchange={toggle} size="sm" />
|
|
@@ -58,9 +58,8 @@
|
|
|
58
58
|
{/if}
|
|
59
59
|
|
|
60
60
|
<div
|
|
61
|
-
role="
|
|
61
|
+
role="group"
|
|
62
62
|
aria-label={title ?? (type === 'source' ? 'Available items' : 'Selected items')}
|
|
63
|
-
aria-multiselectable="true"
|
|
64
63
|
data-transfer-list
|
|
65
64
|
data-type={type}
|
|
66
65
|
{...rest}
|
|
@@ -88,14 +87,10 @@
|
|
|
88
87
|
{@render content({ items: filterItems(items) })}
|
|
89
88
|
{:else}
|
|
90
89
|
{#each filterItems(items) as item (item.key)}
|
|
91
|
-
<!-- svelte-ignore a11y_no_noninteractive_element_to_interactive_role -->
|
|
92
90
|
<label
|
|
93
|
-
role="option"
|
|
94
91
|
data-transfer-item
|
|
95
92
|
data-disabled={item.disabled ? '' : undefined}
|
|
96
93
|
data-selected={selectedSet.has(item.key) ? '' : undefined}
|
|
97
|
-
aria-selected={selectedSet.has(item.key)}
|
|
98
|
-
aria-disabled={item.disabled ?? false}
|
|
99
94
|
>
|
|
100
95
|
<Checkbox
|
|
101
96
|
checked={selectedSet.has(item.key)}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
interface TreeContext {
|
|
2
|
-
readonly expandedItems:
|
|
2
|
+
readonly expandedItems: ReadonlySet<string>;
|
|
3
3
|
readonly selectedItem: string | null;
|
|
4
|
+
readonly focusedItem: string | null;
|
|
4
5
|
toggleItem: (id: string) => void;
|
|
5
6
|
expandItem: (id: string) => void;
|
|
6
7
|
collapseItem: (id: string) => void;
|
|
7
8
|
selectItem: (id: string) => void;
|
|
9
|
+
setFocusedItem: (id: string) => void;
|
|
10
|
+
registerBranch: (id: string) => void;
|
|
11
|
+
unregisterBranch: (id: string) => void;
|
|
8
12
|
isExpanded: (id: string) => boolean;
|
|
9
13
|
isSelected: (id: string) => boolean;
|
|
14
|
+
isFocused: (id: string) => boolean;
|
|
15
|
+
hasChildren: (id: string) => boolean;
|
|
10
16
|
}
|
|
11
17
|
export declare const setTreeCtx: (ctx: TreeContext) => TreeContext, getTreeCtx: () => TreeContext;
|
|
12
18
|
interface TreeItemContext {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { onDestroy } from 'svelte';
|
|
2
3
|
import type { Snippet } from 'svelte';
|
|
3
4
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
5
|
import { getTreeCtx, getTreeItemCtx } from './context.svelte.js';
|
|
@@ -7,18 +8,16 @@
|
|
|
7
8
|
children: Snippet;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
let { class: className, children,
|
|
11
|
+
let { class: className, children, ...rest }: Props = $props();
|
|
11
12
|
|
|
12
13
|
const ctx = getTreeCtx();
|
|
13
14
|
const itemCtx = getTreeItemCtx();
|
|
14
15
|
const open = $derived(ctx.isExpanded(itemCtx.itemId));
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
});
|
|
21
|
-
}
|
|
16
|
+
const branchItemId = itemCtx.itemId;
|
|
17
|
+
ctx.registerBranch(branchItemId);
|
|
18
|
+
onDestroy(() => {
|
|
19
|
+
ctx.unregisterBranch(branchItemId);
|
|
20
|
+
});
|
|
22
21
|
</script>
|
|
23
22
|
|
|
24
23
|
<div
|
|
@@ -28,7 +27,6 @@
|
|
|
28
27
|
data-state={open ? 'open' : 'closed'}
|
|
29
28
|
class={className}
|
|
30
29
|
{...rest}
|
|
31
|
-
{@attach applyStyles}
|
|
32
30
|
>
|
|
33
31
|
<div class="tree-item-inner">
|
|
34
32
|
{@render children()}
|
|
@@ -42,8 +40,12 @@
|
|
|
42
40
|
|
|
43
41
|
[data-part='children'] {
|
|
44
42
|
display: grid;
|
|
45
|
-
grid-template-rows:
|
|
43
|
+
grid-template-rows: 0fr;
|
|
46
44
|
padding-left: var(--dry-tree-indent, var(--dry-space-4));
|
|
47
45
|
transition: grid-template-rows var(--dry-duration-normal) var(--dry-ease-default);
|
|
48
46
|
}
|
|
47
|
+
|
|
48
|
+
[data-part='children'][data-state='open'] {
|
|
49
|
+
grid-template-rows: 1fr;
|
|
50
|
+
}
|
|
49
51
|
</style>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
-
import type {
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
4
|
import { getTreeCtx, getTreeItemCtx } from './context.svelte.js';
|
|
5
5
|
|
|
6
|
-
interface Props extends
|
|
6
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
7
7
|
children: Snippet;
|
|
8
8
|
}
|
|
9
9
|
|
|
@@ -13,24 +13,17 @@
|
|
|
13
13
|
const itemCtx = getTreeItemCtx();
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
|
-
<
|
|
17
|
-
type="button"
|
|
16
|
+
<div
|
|
18
17
|
data-part="label"
|
|
19
|
-
data-tree-label
|
|
20
18
|
data-selected={ctx.isSelected(itemCtx.itemId) || undefined}
|
|
21
19
|
class={className}
|
|
22
|
-
onclick={() => {
|
|
23
|
-
ctx.selectItem(itemCtx.itemId);
|
|
24
|
-
ctx.toggleItem(itemCtx.itemId);
|
|
25
|
-
}}
|
|
26
20
|
{...rest}
|
|
27
21
|
>
|
|
28
22
|
{@render children()}
|
|
29
|
-
</
|
|
23
|
+
</div>
|
|
30
24
|
|
|
31
25
|
<style>
|
|
32
26
|
[data-part='label'] {
|
|
33
|
-
appearance: none;
|
|
34
27
|
display: grid;
|
|
35
28
|
grid-auto-flow: column;
|
|
36
29
|
grid-auto-columns: max-content;
|
|
@@ -44,9 +37,10 @@
|
|
|
44
37
|
font-size: var(--dry-type-small-size, var(--dry-text-sm-size));
|
|
45
38
|
font-family: var(--dry-font-sans);
|
|
46
39
|
color: var(--dry-color-text-strong);
|
|
40
|
+
outline: var(--dry-tree-item-focus-ring, none);
|
|
41
|
+
outline-offset: var(--dry-tree-item-focus-offset, 0px);
|
|
47
42
|
cursor: pointer;
|
|
48
43
|
user-select: none;
|
|
49
|
-
outline: none;
|
|
50
44
|
transition: background var(--dry-duration-fast) var(--dry-ease-default);
|
|
51
45
|
}
|
|
52
46
|
|
|
@@ -54,11 +48,6 @@
|
|
|
54
48
|
background: var(--dry-tree-item-hover-bg, var(--dry-color-fill));
|
|
55
49
|
}
|
|
56
50
|
|
|
57
|
-
[data-part='label']:focus-visible {
|
|
58
|
-
outline: var(--dry-focus-ring);
|
|
59
|
-
outline-offset: 2px;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
51
|
[data-part='label'][data-selected] {
|
|
63
52
|
background: var(--dry-tree-item-selected-bg, var(--dry-color-fill-brand-weak));
|
|
64
53
|
color: var(--dry-tree-item-selected-color, var(--dry-color-text-brand));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
import type {
|
|
3
|
-
interface Props extends
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
4
4
|
children: Snippet;
|
|
5
5
|
}
|
|
6
6
|
declare const TreeItemLabel: import("svelte").Component<Props, {}, "">;
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
const ctx = getTreeCtx();
|
|
14
14
|
const expanded = $derived(ctx.isExpanded(itemId));
|
|
15
|
+
const focused = $derived(ctx.isFocused(itemId));
|
|
16
|
+
const hasChildren = $derived(ctx.hasChildren(itemId));
|
|
15
17
|
const selected = $derived(ctx.isSelected(itemId));
|
|
16
18
|
|
|
17
19
|
setTreeItemCtx({
|
|
@@ -23,14 +25,39 @@
|
|
|
23
25
|
|
|
24
26
|
<div
|
|
25
27
|
role="treeitem"
|
|
26
|
-
|
|
28
|
+
tabindex={focused ? 0 : -1}
|
|
29
|
+
aria-expanded={hasChildren ? expanded : undefined}
|
|
27
30
|
aria-selected={selected}
|
|
31
|
+
data-branch={hasChildren || undefined}
|
|
28
32
|
data-part="item"
|
|
29
33
|
data-expanded={expanded || undefined}
|
|
34
|
+
data-focused={focused || undefined}
|
|
30
35
|
data-selected={selected || undefined}
|
|
31
36
|
data-item-id={itemId}
|
|
32
37
|
class={className}
|
|
38
|
+
onclick={(e) => {
|
|
39
|
+
(e.currentTarget as HTMLElement).focus();
|
|
40
|
+
ctx.selectItem(itemId);
|
|
41
|
+
}}
|
|
42
|
+
ondblclick={() => {
|
|
43
|
+
if (hasChildren) {
|
|
44
|
+
ctx.toggleItem(itemId);
|
|
45
|
+
}
|
|
46
|
+
}}
|
|
33
47
|
{...rest}
|
|
34
48
|
>
|
|
35
49
|
{@render children()}
|
|
36
50
|
</div>
|
|
51
|
+
|
|
52
|
+
<style>
|
|
53
|
+
[data-part='item'] {
|
|
54
|
+
outline: none;
|
|
55
|
+
--dry-tree-item-focus-ring: none;
|
|
56
|
+
--dry-tree-item-focus-offset: 0px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
[data-part='item']:focus-visible {
|
|
60
|
+
--dry-tree-item-focus-ring: var(--dry-focus-ring);
|
|
61
|
+
--dry-tree-item-focus-offset: 2px;
|
|
62
|
+
}
|
|
63
|
+
</style>
|