@spectral_labs/ui 1.7.0 → 1.8.0-next.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/LICENSE +21 -0
- package/THIRD_PARTY_NOTICES.md +201 -0
- package/package.json +8 -2
- package/dist/components/__tests__/overlay-scrim-source.test.js +0 -134
- package/dist/components/accordion/__tests__/AccordionTest.svelte +0 -31
- package/dist/components/accordion/__tests__/accordion.test.svelte.js +0 -139
- package/dist/components/alert-dialog/__tests__/AlertDialogTest.svelte +0 -28
- package/dist/components/alert-dialog/__tests__/alert-dialog.a11y.test.js +0 -42
- package/dist/components/alert-dialog/__tests__/alert-dialog.integration.test.svelte.js +0 -51
- package/dist/components/alert-dialog/__tests__/alert-dialog.test.svelte.js +0 -113
- package/dist/components/app-bar/__tests__/app-bar.test.svelte.js +0 -30
- package/dist/components/aspect-ratio/__tests__/aspect-ratio.test.svelte.js +0 -22
- package/dist/components/avatar/__tests__/avatar.test.svelte.js +0 -26
- package/dist/components/badge/__tests__/badge.test.svelte.js +0 -30
- package/dist/components/bottom-sheet/__tests__/BottomSheetTest.svelte +0 -22
- package/dist/components/bottom-sheet/__tests__/bottom-sheet.test.svelte.js +0 -83
- package/dist/components/button/__tests__/button.test.svelte.js +0 -63
- package/dist/components/calendar/__tests__/CalendarTest.svelte +0 -56
- package/dist/components/calendar/__tests__/calendar.test.svelte.js +0 -190
- package/dist/components/card/__tests__/card.test.svelte.js +0 -30
- package/dist/components/checkbox/__tests__/checkbox.test.svelte.js +0 -44
- package/dist/components/chips/__tests__/chips.test.svelte.js +0 -26
- package/dist/components/collapsible/__tests__/CollapsibleTest.svelte +0 -18
- package/dist/components/collapsible/__tests__/collapsible.test.svelte.js +0 -125
- package/dist/components/combobox/__tests__/ComboboxTest.svelte +0 -40
- package/dist/components/combobox/__tests__/combobox.test.svelte.js +0 -255
- package/dist/components/command/__tests__/CommandInlineDirectTest.svelte +0 -29
- package/dist/components/command/__tests__/CommandInlineTest.svelte +0 -89
- package/dist/components/command/__tests__/CommandTest.svelte +0 -77
- package/dist/components/command/__tests__/command-inline.test.svelte.js +0 -392
- package/dist/components/command/__tests__/command-item-passthrough.test.svelte.js +0 -284
- package/dist/components/command/__tests__/command.a11y.test.js +0 -30
- package/dist/components/command/__tests__/command.integration.test.svelte.js +0 -68
- package/dist/components/command/__tests__/command.test.svelte.js +0 -230
- package/dist/components/context-menu/__tests__/ContextMenuNonInteractiveTest.svelte +0 -32
- package/dist/components/context-menu/__tests__/ContextMenuTest.svelte +0 -25
- package/dist/components/context-menu/__tests__/context-menu.test.svelte.js +0 -141
- package/dist/components/date-field/__tests__/DateFieldTest.svelte +0 -20
- package/dist/components/date-field/__tests__/date-field.test.svelte.js +0 -139
- package/dist/components/date-picker/__tests__/DatePickerTest.svelte +0 -20
- package/dist/components/date-picker/__tests__/date-picker.test.svelte.js +0 -190
- package/dist/components/date-range-field/__tests__/DateRangeFieldTest.svelte +0 -20
- package/dist/components/date-range-field/__tests__/date-range-field.test.svelte.js +0 -130
- package/dist/components/date-range-picker/__tests__/DateRangePickerTest.svelte +0 -18
- package/dist/components/date-range-picker/__tests__/date-range-picker.test.svelte.js +0 -156
- package/dist/components/dialog/__tests__/DialogTest.svelte +0 -34
- package/dist/components/dialog/__tests__/dialog.test.svelte.js +0 -184
- package/dist/components/drop-zone/__tests__/drop-zone.test.svelte.js +0 -26
- package/dist/components/dropdown-menu/__tests__/DropdownMenuRadioTest.svelte +0 -90
- package/dist/components/dropdown-menu/__tests__/DropdownMenuTest.svelte +0 -71
- package/dist/components/dropdown-menu/__tests__/dropdown-menu-radio-group.test.svelte.js +0 -306
- package/dist/components/dropdown-menu/__tests__/dropdown-menu.test.svelte.js +0 -567
- package/dist/components/empty-state/__tests__/empty-state.test.svelte.js +0 -26
- package/dist/components/fab/__tests__/fab.test.svelte.js +0 -30
- package/dist/components/field/__tests__/FieldTest.svelte +0 -26
- package/dist/components/field/__tests__/field.test.svelte.js +0 -74
- package/dist/components/grid-list/__tests__/GridListTest.svelte +0 -46
- package/dist/components/grid-list/__tests__/grid-list.test.svelte.js +0 -231
- package/dist/components/hover-card/__tests__/HoverCardTest.svelte +0 -30
- package/dist/components/hover-card/__tests__/hover-card.test.svelte.js +0 -236
- package/dist/components/indicator/__tests__/indicator.test.svelte.js +0 -30
- package/dist/components/input/__tests__/input.test.svelte.js +0 -67
- package/dist/components/label/__tests__/label.test.svelte.js +0 -22
- package/dist/components/language-select/__tests__/language-select.test.svelte.js +0 -199
- package/dist/components/language-select/__tests__/languages.test.js +0 -87
- package/dist/components/link-preview/__tests__/LinkPreviewTest.svelte +0 -37
- package/dist/components/link-preview/__tests__/link-preview.a11y.test.js +0 -28
- package/dist/components/link-preview/__tests__/link-preview.integration.test.svelte.js +0 -54
- package/dist/components/link-preview/__tests__/link-preview.test.svelte.js +0 -215
- package/dist/components/list/__tests__/list.test.svelte.js +0 -22
- package/dist/components/menubar/__tests__/MenubarTest.svelte +0 -52
- package/dist/components/menubar/__tests__/menubar.a11y.test.js +0 -28
- package/dist/components/menubar/__tests__/menubar.integration.test.svelte.js +0 -57
- package/dist/components/menubar/__tests__/menubar.test.svelte.js +0 -328
- package/dist/components/meter/__tests__/meter.test.svelte.js +0 -26
- package/dist/components/nav-bar/__tests__/nav-bar.test.svelte.js +0 -23
- package/dist/components/nav-drawer/__tests__/nav-drawer.test.svelte.js +0 -28
- package/dist/components/nav-rail/__tests__/nav-rail.test.svelte.js +0 -28
- package/dist/components/pagination/__tests__/pagination.test.svelte.js +0 -26
- package/dist/components/pin-input/__tests__/pin-input.test.svelte.js +0 -71
- package/dist/components/popover/__tests__/PopoverTest.svelte +0 -27
- package/dist/components/popover/__tests__/popover.test.svelte.js +0 -196
- package/dist/components/progress/__tests__/progress.test.svelte.js +0 -32
- package/dist/components/radio-group/__tests__/radio-group.test.svelte.js +0 -22
- package/dist/components/range-calendar/__tests__/RangeCalendarTest.svelte +0 -23
- package/dist/components/range-calendar/__tests__/range-calendar.test.svelte.js +0 -105
- package/dist/components/rating-group/__tests__/rating-group.test.svelte.js +0 -26
- package/dist/components/scroll-area/__tests__/scroll-area.test.svelte.js +0 -34
- package/dist/components/search-field/__tests__/SearchFieldTest.svelte +0 -34
- package/dist/components/search-field/__tests__/search-field.test.svelte.js +0 -118
- package/dist/components/select/__tests__/SelectLabelPersistenceTest.svelte +0 -31
- package/dist/components/select/__tests__/SelectTest.svelte +0 -33
- package/dist/components/select/__tests__/select.test.svelte.js +0 -221
- package/dist/components/separator/__tests__/separator.test.svelte.js +0 -39
- package/dist/components/sheet/__tests__/SheetTest.svelte +0 -34
- package/dist/components/sheet/__tests__/sheet.test.svelte.js +0 -193
- package/dist/components/side-sheet/__tests__/side-sheet.test.svelte.js +0 -138
- package/dist/components/sidebar/__tests__/SidebarFullTest.svelte +0 -45
- package/dist/components/sidebar/__tests__/SidebarTest.svelte +0 -9
- package/dist/components/sidebar/__tests__/sidebar.test.svelte.js +0 -186
- package/dist/components/simple-dialog/__tests__/SimpleDialogTest.svelte +0 -40
- package/dist/components/simple-dialog/__tests__/simple-dialog.test.svelte.js +0 -50
- package/dist/components/simple-tooltip/__tests__/SimpleTooltipTest.svelte +0 -19
- package/dist/components/simple-tooltip/__tests__/simple-tooltip.test.svelte.js +0 -61
- package/dist/components/slider/__tests__/slider.test.svelte.js +0 -42
- package/dist/components/sortable-list/__tests__/NestedSortableListTest.svelte +0 -49
- package/dist/components/sortable-list/__tests__/SortableListTest.svelte +0 -42
- package/dist/components/sortable-list/__tests__/sortable-list.nested.test.svelte.js +0 -112
- package/dist/components/sortable-list/__tests__/sortable-list.test.svelte.js +0 -175
- package/dist/components/splitter/__tests__/splitter.test.svelte.js +0 -26
- package/dist/components/switch/__tests__/switch.test.svelte.js +0 -44
- package/dist/components/table/__tests__/table.test.svelte.js +0 -22
- package/dist/components/tabs/__tests__/TabsTest.svelte +0 -32
- package/dist/components/tabs/__tests__/tabs.test.svelte.js +0 -227
- package/dist/components/textarea/__tests__/textarea.test.svelte.js +0 -55
- package/dist/components/time-field/__tests__/TimeFieldTest.svelte +0 -20
- package/dist/components/time-field/__tests__/time-field.test.svelte.js +0 -100
- package/dist/components/time-range-field/__tests__/TimeRangeFieldTest.svelte +0 -20
- package/dist/components/time-range-field/__tests__/time-range-field.test.svelte.js +0 -124
- package/dist/components/toast/__tests__/toast.test.svelte.js +0 -46
- package/dist/components/toggle/__tests__/toggle.test.svelte.js +0 -38
- package/dist/components/toggle-group/__tests__/toggle-group.test.svelte.js +0 -22
- package/dist/components/toolbar/__tests__/ToolbarCompositionTest.svelte +0 -18
- package/dist/components/toolbar/__tests__/ToolbarTabstopTest.svelte +0 -10
- package/dist/components/toolbar/__tests__/toolbar.test.svelte.js +0 -63
- package/dist/components/tooltip/__tests__/TooltipTest.svelte +0 -22
- package/dist/components/tooltip/__tests__/tooltip.test.svelte.js +0 -225
- package/dist/components/tree/__tests__/TreeLabelContentTest.svelte +0 -21
- package/dist/components/tree/__tests__/TreeTest.svelte +0 -24
- package/dist/components/tree/__tests__/tree.test.svelte.js +0 -229
- package/dist/components/virtualizer/__tests__/waterfall-layout.test.js +0 -82
- package/dist/internal/announcer/__tests__/announcer.test.js +0 -42
- package/dist/internal/dismissible-layer/__tests__/dismissible-layer.test.js +0 -196
- package/dist/internal/drag-drop/__tests__/auto-scroll.test.js +0 -104
- package/dist/internal/drag-drop/__tests__/drag-session.test.svelte.js +0 -803
- package/dist/internal/drag-drop/__tests__/drag-source.test.svelte.js +0 -130
- package/dist/internal/drag-drop/__tests__/drop-delegate.test.js +0 -223
- package/dist/internal/drag-drop/__tests__/types.test.js +0 -20
- package/dist/internal/escape-layer/__tests__/escape-layer.test.js +0 -113
- package/dist/internal/focus-scope/__tests__/focus-scope.test.js +0 -225
- package/dist/internal/grace-area/__tests__/grace-area.test.js +0 -67
- package/dist/internal/helpers/__tests__/helpers.test.js +0 -123
- package/dist/internal/portal/__tests__/PortalTest.svelte +0 -21
- package/dist/internal/portal/__tests__/portal.test.js +0 -29
- package/dist/internal/portal/__tests__/portal.test.svelte.js +0 -57
- package/dist/internal/scroll-lock/__tests__/scroll-lock.test.js +0 -160
- package/dist/internal/typeahead/__tests__/typeahead.test.js +0 -61
- package/src/themes/__tests__/carbon-dark-contrast.test.ts +0 -170
- package/src/themes/__tests__/generated-css-drift.test.ts +0 -53
- package/src/themes/__tests__/scrim-opaque.test.ts +0 -52
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import { CalendarDate } from '@internationalized/date';
|
|
2
|
-
import { flushSync, mount, unmount } from 'svelte';
|
|
3
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
4
|
-
import DateRangePickerTest from './DateRangePickerTest.svelte';
|
|
5
|
-
// jsdom does not define PointerEvent — polyfill it for tests
|
|
6
|
-
if (typeof globalThis.PointerEvent === 'undefined') {
|
|
7
|
-
// @ts-expect-error — minimal polyfill for testing
|
|
8
|
-
globalThis.PointerEvent = class PointerEvent extends MouseEvent {
|
|
9
|
-
constructor(type, init) {
|
|
10
|
-
super(type, init);
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
describe('dateRangePicker', () => {
|
|
15
|
-
let target;
|
|
16
|
-
let instance;
|
|
17
|
-
function setup(props = {}) {
|
|
18
|
-
target = document.createElement('div');
|
|
19
|
-
document.body.appendChild(target);
|
|
20
|
-
instance = mount(DateRangePickerTest, { target, props: { portalTo: target, ...props } });
|
|
21
|
-
flushSync();
|
|
22
|
-
}
|
|
23
|
-
afterEach(() => {
|
|
24
|
-
if (instance)
|
|
25
|
-
unmount(instance);
|
|
26
|
-
instance = undefined;
|
|
27
|
-
target?.remove();
|
|
28
|
-
});
|
|
29
|
-
/**
|
|
30
|
-
* Finds the calendar-open trigger button.
|
|
31
|
-
* Date-range-picker renders <button class='sp-date-range-picker-trigger'>.
|
|
32
|
-
* The trigger always renders inline (not portalled).
|
|
33
|
-
*/
|
|
34
|
-
function findTrigger() {
|
|
35
|
-
return (target.querySelector('button.sp-date-range-picker-trigger') ??
|
|
36
|
-
target.querySelector('button[aria-label="Open calendar"]') ??
|
|
37
|
-
target.querySelector('button[aria-haspopup]'));
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Finds open content. When setup() injects portalTo: target, Portal renders
|
|
41
|
-
* content inside target, so queries against target still work in most tests.
|
|
42
|
-
*/
|
|
43
|
-
function findOpenContent() {
|
|
44
|
-
return (target.querySelector('[data-state="open"]') ??
|
|
45
|
-
target.querySelector('.sp-date-range-picker-content') ??
|
|
46
|
-
target.querySelector('[role="dialog"]'));
|
|
47
|
-
}
|
|
48
|
-
// ─── Smoke ────────────────────────────────────────────────────────────────
|
|
49
|
-
it('renders a trigger button', () => {
|
|
50
|
-
setup();
|
|
51
|
-
expect(findTrigger()).toBeTruthy();
|
|
52
|
-
});
|
|
53
|
-
it('stays closed by default', () => {
|
|
54
|
-
setup();
|
|
55
|
-
expect(findOpenContent()).toBeNull();
|
|
56
|
-
});
|
|
57
|
-
it('trigger has aria-expanded="false" when closed', () => {
|
|
58
|
-
setup();
|
|
59
|
-
const trigger = findTrigger();
|
|
60
|
-
expect(trigger?.getAttribute('aria-expanded')).toBe('false');
|
|
61
|
-
});
|
|
62
|
-
// ─── Value binding ────────────────────────────────────────────────────────
|
|
63
|
-
it('renders with an initial range value', () => {
|
|
64
|
-
const initial = {
|
|
65
|
-
start: new CalendarDate(2026, 1, 15),
|
|
66
|
-
end: new CalendarDate(2026, 3, 10),
|
|
67
|
-
};
|
|
68
|
-
setup({ value: initial });
|
|
69
|
-
expect(findTrigger()).toBeTruthy();
|
|
70
|
-
});
|
|
71
|
-
it('accepts partial range (start only)', () => {
|
|
72
|
-
const partial = { start: new CalendarDate(2026, 2, 1) };
|
|
73
|
-
setup({ value: partial });
|
|
74
|
-
expect(findTrigger()).toBeTruthy();
|
|
75
|
-
});
|
|
76
|
-
it('accepts empty range value ({})', () => {
|
|
77
|
-
setup({ value: {} });
|
|
78
|
-
expect(findTrigger()).toBeTruthy();
|
|
79
|
-
});
|
|
80
|
-
// ─── Open / Close ─────────────────────────────────────────────────────────
|
|
81
|
-
it('opens on trigger click', () => {
|
|
82
|
-
setup();
|
|
83
|
-
findTrigger()?.click();
|
|
84
|
-
flushSync();
|
|
85
|
-
expect(findOpenContent()).toBeTruthy();
|
|
86
|
-
});
|
|
87
|
-
it('trigger has aria-expanded="true" when open', () => {
|
|
88
|
-
setup();
|
|
89
|
-
findTrigger()?.click();
|
|
90
|
-
flushSync();
|
|
91
|
-
expect(findTrigger()?.getAttribute('aria-expanded')).toBe('true');
|
|
92
|
-
});
|
|
93
|
-
it('trigger has aria-haspopup="dialog"', () => {
|
|
94
|
-
setup();
|
|
95
|
-
expect(findTrigger()?.getAttribute('aria-haspopup')).toBe('dialog');
|
|
96
|
-
});
|
|
97
|
-
it('closes on Escape when open', () => {
|
|
98
|
-
setup();
|
|
99
|
-
findTrigger()?.click();
|
|
100
|
-
flushSync();
|
|
101
|
-
expect(findOpenContent()).toBeTruthy();
|
|
102
|
-
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
103
|
-
flushSync();
|
|
104
|
-
expect(findOpenContent()).toBeNull();
|
|
105
|
-
});
|
|
106
|
-
it('closes on outside pointerdown', () => {
|
|
107
|
-
setup();
|
|
108
|
-
findTrigger()?.click();
|
|
109
|
-
flushSync();
|
|
110
|
-
expect(findOpenContent()).toBeTruthy();
|
|
111
|
-
const outside = document.createElement('button');
|
|
112
|
-
document.body.appendChild(outside);
|
|
113
|
-
outside.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, clientX: 9999, clientY: 9999 }));
|
|
114
|
-
flushSync();
|
|
115
|
-
expect(findOpenContent()).toBeNull();
|
|
116
|
-
outside.remove();
|
|
117
|
-
});
|
|
118
|
-
// ─── Disabled ─────────────────────────────────────────────────────────────
|
|
119
|
-
it('marks trigger as disabled when disabled=true', () => {
|
|
120
|
-
setup({ disabled: true });
|
|
121
|
-
const trigger = findTrigger();
|
|
122
|
-
const isDisabled = trigger?.hasAttribute('disabled') || trigger?.getAttribute('aria-disabled') === 'true';
|
|
123
|
-
expect(isDisabled).toBe(true);
|
|
124
|
-
});
|
|
125
|
-
// ─── Calendar grid ────────────────────────────────────────────────────────
|
|
126
|
-
it('renders a range calendar grid when open', () => {
|
|
127
|
-
setup();
|
|
128
|
-
findTrigger()?.click();
|
|
129
|
-
flushSync();
|
|
130
|
-
const grid = target.querySelector('[role="grid"]') ?? target.querySelector('[role="gridcell"]');
|
|
131
|
-
expect(grid).toBeTruthy();
|
|
132
|
-
});
|
|
133
|
-
// ─── Portal regression detector ───────────────────────────────────────────
|
|
134
|
-
//
|
|
135
|
-
// T-F-2: Portal is now wired into date-range-picker-root.svelte.
|
|
136
|
-
// Content MUST render in document.body by default (not inline in target).
|
|
137
|
-
it('portals content to document.body by default', () => {
|
|
138
|
-
// Re-mount WITHOUT portalTo default injection
|
|
139
|
-
if (instance) {
|
|
140
|
-
unmount(instance);
|
|
141
|
-
instance = undefined;
|
|
142
|
-
}
|
|
143
|
-
target?.remove();
|
|
144
|
-
target = document.createElement('div');
|
|
145
|
-
document.body.appendChild(target);
|
|
146
|
-
instance = mount(DateRangePickerTest, { target });
|
|
147
|
-
flushSync();
|
|
148
|
-
target
|
|
149
|
-
.querySelector('button.sp-date-range-picker-trigger, button[aria-label="Open calendar"]')
|
|
150
|
-
?.click();
|
|
151
|
-
flushSync();
|
|
152
|
-
// Content MUST be in document.body (not inline in target) since Portal is now wired
|
|
153
|
-
expect(target.querySelector('[data-state="open"], [role="dialog"]')).toBeNull();
|
|
154
|
-
expect(document.body.querySelector('[data-state="open"], .sp-date-range-picker-content')).toBeTruthy();
|
|
155
|
-
});
|
|
156
|
-
});
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import DialogClose from '../components/dialog-close.svelte'
|
|
3
|
-
import DialogContent from '../components/dialog-content.svelte'
|
|
4
|
-
import DialogDescription from '../components/dialog-description.svelte'
|
|
5
|
-
import DialogOverlay from '../components/dialog-overlay.svelte'
|
|
6
|
-
import DialogRoot from '../components/dialog-root.svelte'
|
|
7
|
-
import DialogTitle from '../components/dialog-title.svelte'
|
|
8
|
-
import DialogTrigger from '../components/dialog-trigger.svelte'
|
|
9
|
-
|
|
10
|
-
let {
|
|
11
|
-
open = $bindable(false),
|
|
12
|
-
trapFocus = true,
|
|
13
|
-
preventScroll = true,
|
|
14
|
-
portalTo = undefined,
|
|
15
|
-
presentation = undefined,
|
|
16
|
-
}: {
|
|
17
|
-
open?: boolean
|
|
18
|
-
trapFocus?: boolean
|
|
19
|
-
preventScroll?: boolean
|
|
20
|
-
portalTo?: HTMLElement | string
|
|
21
|
-
presentation?: 'dialog' | 'fullscreen-mobile' | 'fullscreen-tablet' | 'fullscreen'
|
|
22
|
-
} = $props()
|
|
23
|
-
</script>
|
|
24
|
-
|
|
25
|
-
<DialogRoot bind:open>
|
|
26
|
-
<DialogTrigger>Open Dialog</DialogTrigger>
|
|
27
|
-
<DialogOverlay />
|
|
28
|
-
<DialogContent {trapFocus} {preventScroll} {portalTo} {presentation}>
|
|
29
|
-
<DialogTitle>Test Title</DialogTitle>
|
|
30
|
-
<DialogDescription>Test Description</DialogDescription>
|
|
31
|
-
<button data-testid="inside-button">Inside</button>
|
|
32
|
-
<DialogClose>Close</DialogClose>
|
|
33
|
-
</DialogContent>
|
|
34
|
-
</DialogRoot>
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import { flushSync, mount, unmount } from 'svelte';
|
|
2
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
-
import DialogRoot from '../components/dialog-root.svelte';
|
|
4
|
-
import DialogTest from './DialogTest.svelte';
|
|
5
|
-
describe('dialog', () => {
|
|
6
|
-
let target;
|
|
7
|
-
let instance;
|
|
8
|
-
function setup(props = {}) {
|
|
9
|
-
target = document.createElement('div');
|
|
10
|
-
document.body.appendChild(target);
|
|
11
|
-
// Pass portalTo=target by default so existing tests keep querying `target`
|
|
12
|
-
instance = mount(DialogTest, { target, props: { portalTo: target, ...props } });
|
|
13
|
-
flushSync();
|
|
14
|
-
return target;
|
|
15
|
-
}
|
|
16
|
-
function setupRoot(props = {}) {
|
|
17
|
-
target = document.createElement('div');
|
|
18
|
-
document.body.appendChild(target);
|
|
19
|
-
instance = mount(DialogRoot, { target, props });
|
|
20
|
-
return target;
|
|
21
|
-
}
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
if (instance)
|
|
24
|
-
unmount(instance);
|
|
25
|
-
target?.remove();
|
|
26
|
-
});
|
|
27
|
-
// --- Basic rendering ---
|
|
28
|
-
it('renders root', () => {
|
|
29
|
-
setupRoot();
|
|
30
|
-
expect(target.querySelector('.sp-dialog-root')).toBeTruthy();
|
|
31
|
-
});
|
|
32
|
-
it('data-state is closed by default', () => {
|
|
33
|
-
setupRoot();
|
|
34
|
-
expect(target.querySelector('.sp-dialog-root')?.getAttribute('data-state')).toBe('closed');
|
|
35
|
-
});
|
|
36
|
-
// --- Trigger ARIA ---
|
|
37
|
-
it('trigger has aria-haspopup="dialog"', () => {
|
|
38
|
-
setup();
|
|
39
|
-
const trigger = target.querySelector('.sp-dialog-trigger');
|
|
40
|
-
expect(trigger?.getAttribute('aria-haspopup')).toBe('dialog');
|
|
41
|
-
});
|
|
42
|
-
it('trigger has aria-expanded=false when closed', () => {
|
|
43
|
-
setup();
|
|
44
|
-
const trigger = target.querySelector('.sp-dialog-trigger');
|
|
45
|
-
expect(trigger?.getAttribute('aria-expanded')).toBe('false');
|
|
46
|
-
});
|
|
47
|
-
it('trigger has aria-controls pointing to content id', () => {
|
|
48
|
-
setup({ open: true });
|
|
49
|
-
const trigger = target.querySelector('.sp-dialog-trigger');
|
|
50
|
-
const content = target.querySelector('[role="dialog"]');
|
|
51
|
-
expect(trigger?.getAttribute('aria-controls')).toBe(content?.id);
|
|
52
|
-
});
|
|
53
|
-
it('trigger toggles open state on click', () => {
|
|
54
|
-
setup();
|
|
55
|
-
const trigger = target.querySelector('.sp-dialog-trigger');
|
|
56
|
-
trigger.click();
|
|
57
|
-
flushSync();
|
|
58
|
-
expect(target.querySelector('.sp-dialog-root')?.getAttribute('data-state')).toBe('open');
|
|
59
|
-
});
|
|
60
|
-
// --- Content ARIA ---
|
|
61
|
-
it('content has role="dialog"', () => {
|
|
62
|
-
setup({ open: true });
|
|
63
|
-
expect(target.querySelector('[role="dialog"]')).toBeTruthy();
|
|
64
|
-
});
|
|
65
|
-
it('content has aria-modal="true"', () => {
|
|
66
|
-
setup({ open: true });
|
|
67
|
-
const content = target.querySelector('[role="dialog"]');
|
|
68
|
-
expect(content?.getAttribute('aria-modal')).toBe('true');
|
|
69
|
-
});
|
|
70
|
-
it('content has aria-labelledby pointing to title', () => {
|
|
71
|
-
setup({ open: true });
|
|
72
|
-
const content = target.querySelector('[role="dialog"]');
|
|
73
|
-
const title = target.querySelector('.sp-dialog-title');
|
|
74
|
-
expect(content?.getAttribute('aria-labelledby')).toBe(title?.id);
|
|
75
|
-
});
|
|
76
|
-
it('content has aria-describedby pointing to description', () => {
|
|
77
|
-
setup({ open: true });
|
|
78
|
-
const content = target.querySelector('[role="dialog"]');
|
|
79
|
-
const desc = target.querySelector('.sp-dialog-description');
|
|
80
|
-
expect(content?.getAttribute('aria-describedby')).toBe(desc?.id);
|
|
81
|
-
});
|
|
82
|
-
it('content has data-state="open" when open', () => {
|
|
83
|
-
setup({ open: true });
|
|
84
|
-
const content = target.querySelector('[role="dialog"]');
|
|
85
|
-
expect(content?.getAttribute('data-state')).toBe('open');
|
|
86
|
-
});
|
|
87
|
-
// --- Content visibility ---
|
|
88
|
-
it('content is not rendered when closed', () => {
|
|
89
|
-
setup({ open: false });
|
|
90
|
-
expect(target.querySelector('[role="dialog"]')).toBeNull();
|
|
91
|
-
});
|
|
92
|
-
it('content is rendered when open', () => {
|
|
93
|
-
setup({ open: true });
|
|
94
|
-
expect(target.querySelector('[role="dialog"]')).toBeTruthy();
|
|
95
|
-
});
|
|
96
|
-
// --- Overlay ---
|
|
97
|
-
it('overlay has aria-hidden="true"', () => {
|
|
98
|
-
setup({ open: true });
|
|
99
|
-
const overlay = target.querySelector('.sp-dialog-overlay');
|
|
100
|
-
expect(overlay?.getAttribute('aria-hidden')).toBe('true');
|
|
101
|
-
});
|
|
102
|
-
// --- Close button ---
|
|
103
|
-
it('close button closes the dialog', () => {
|
|
104
|
-
setup({ open: true });
|
|
105
|
-
const closeBtn = target.querySelector('.sp-dialog-close');
|
|
106
|
-
closeBtn.click();
|
|
107
|
-
flushSync();
|
|
108
|
-
expect(target.querySelector('[role="dialog"]')).toBeNull();
|
|
109
|
-
});
|
|
110
|
-
// --- Escape key ---
|
|
111
|
-
it('escape key closes the dialog', () => {
|
|
112
|
-
setup({ open: true });
|
|
113
|
-
// Flush to ensure $effect that mounts EscapeLayerState has run
|
|
114
|
-
flushSync();
|
|
115
|
-
expect(target.querySelector('[role="dialog"]')).toBeTruthy();
|
|
116
|
-
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true }));
|
|
117
|
-
flushSync();
|
|
118
|
-
expect(target.querySelector('[role="dialog"]')).toBeNull();
|
|
119
|
-
});
|
|
120
|
-
// --- Title ---
|
|
121
|
-
it('title renders as h2 by default', () => {
|
|
122
|
-
setup({ open: true });
|
|
123
|
-
const title = target.querySelector('.sp-dialog-title');
|
|
124
|
-
expect(title?.tagName).toBe('H2');
|
|
125
|
-
});
|
|
126
|
-
// --- Description ---
|
|
127
|
-
it('description renders as <p>', () => {
|
|
128
|
-
setup({ open: true });
|
|
129
|
-
const desc = target.querySelector('.sp-dialog-description');
|
|
130
|
-
expect(desc?.tagName).toBe('P');
|
|
131
|
-
});
|
|
132
|
-
// --- Trigger aria-expanded updates ---
|
|
133
|
-
it('trigger aria-expanded becomes true when dialog opens', () => {
|
|
134
|
-
setup();
|
|
135
|
-
const trigger = target.querySelector('.sp-dialog-trigger');
|
|
136
|
-
expect(trigger.getAttribute('aria-expanded')).toBe('false');
|
|
137
|
-
trigger.click();
|
|
138
|
-
flushSync();
|
|
139
|
-
expect(trigger.getAttribute('aria-expanded')).toBe('true');
|
|
140
|
-
});
|
|
141
|
-
// --- Portal ---
|
|
142
|
-
it('portals content to document.body by default when open', () => {
|
|
143
|
-
const t = document.createElement('div');
|
|
144
|
-
t.id = 'dialog-host';
|
|
145
|
-
document.body.appendChild(t);
|
|
146
|
-
const inst = mount(DialogTest, { target: t, props: { open: true } });
|
|
147
|
-
flushSync();
|
|
148
|
-
try {
|
|
149
|
-
expect(t.querySelector('[role="dialog"]')).toBeNull();
|
|
150
|
-
expect(document.body.querySelector('[role="dialog"]')).toBeTruthy();
|
|
151
|
-
}
|
|
152
|
-
finally {
|
|
153
|
-
unmount(inst);
|
|
154
|
-
t.remove();
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
it('renders content inline when portalTo is explicitly set to the host', () => {
|
|
158
|
-
const t = document.createElement('div');
|
|
159
|
-
document.body.appendChild(t);
|
|
160
|
-
const inst = mount(DialogTest, {
|
|
161
|
-
target: t,
|
|
162
|
-
props: { open: true, portalTo: t },
|
|
163
|
-
});
|
|
164
|
-
flushSync();
|
|
165
|
-
try {
|
|
166
|
-
expect(t.querySelector('[role="dialog"]')).toBeTruthy();
|
|
167
|
-
}
|
|
168
|
-
finally {
|
|
169
|
-
unmount(inst);
|
|
170
|
-
t.remove();
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
// --- Presentation prop ---
|
|
174
|
-
it('sets data-presentation attribute matching prop', () => {
|
|
175
|
-
setup({ open: true, presentation: 'fullscreen-mobile' });
|
|
176
|
-
const content = target.querySelector('[role="dialog"]');
|
|
177
|
-
expect(content?.getAttribute('data-presentation')).toBe('fullscreen-mobile');
|
|
178
|
-
});
|
|
179
|
-
it('defaults data-presentation to "dialog"', () => {
|
|
180
|
-
setup({ open: true });
|
|
181
|
-
const content = target.querySelector('[role="dialog"]');
|
|
182
|
-
expect(content?.getAttribute('data-presentation')).toBe('dialog');
|
|
183
|
-
});
|
|
184
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { mount, unmount } from 'svelte';
|
|
2
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
-
import DropZone from '../drop-zone.svelte';
|
|
4
|
-
describe('drop-zone', () => {
|
|
5
|
-
let target;
|
|
6
|
-
let instance;
|
|
7
|
-
function setup(props = {}) {
|
|
8
|
-
target = document.createElement('div');
|
|
9
|
-
document.body.appendChild(target);
|
|
10
|
-
instance = mount(DropZone, { target, props });
|
|
11
|
-
return target;
|
|
12
|
-
}
|
|
13
|
-
afterEach(() => {
|
|
14
|
-
if (instance)
|
|
15
|
-
unmount(instance);
|
|
16
|
-
target?.remove();
|
|
17
|
-
});
|
|
18
|
-
it('renders with role=button', () => {
|
|
19
|
-
setup();
|
|
20
|
-
expect(target.querySelector('[role="button"]')).toBeTruthy();
|
|
21
|
-
});
|
|
22
|
-
it('has tabindex', () => {
|
|
23
|
-
setup();
|
|
24
|
-
expect(target.querySelector('[tabindex]')).toBeTruthy();
|
|
25
|
-
});
|
|
26
|
-
});
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import DropdownMenuContent from '../components/dropdown-menu-content.svelte'
|
|
3
|
-
import DropdownMenuItem from '../components/dropdown-menu-item.svelte'
|
|
4
|
-
import DropdownMenuRadioGroup from '../components/dropdown-menu-radio-group.svelte'
|
|
5
|
-
import DropdownMenuRadioItem from '../components/dropdown-menu-radio-item.svelte'
|
|
6
|
-
import DropdownMenuRoot from '../components/dropdown-menu-root.svelte'
|
|
7
|
-
import DropdownMenuTrigger from '../components/dropdown-menu-trigger.svelte'
|
|
8
|
-
|
|
9
|
-
let {
|
|
10
|
-
open = $bindable(true),
|
|
11
|
-
value = $bindable('document'),
|
|
12
|
-
portalTo,
|
|
13
|
-
onValueChange,
|
|
14
|
-
closeOnSelect = undefined,
|
|
15
|
-
disabledValue = undefined,
|
|
16
|
-
textValues = undefined,
|
|
17
|
-
itemAttrs = undefined,
|
|
18
|
-
onItemClick = undefined,
|
|
19
|
-
withLeadingItem = false,
|
|
20
|
-
orphan = false,
|
|
21
|
-
groupAttrs = undefined,
|
|
22
|
-
onItemKeydown = undefined,
|
|
23
|
-
onItemSelect = undefined,
|
|
24
|
-
parentTarget = 'bibliographie',
|
|
25
|
-
}: {
|
|
26
|
-
open?: boolean
|
|
27
|
-
value?: string
|
|
28
|
-
portalTo?: HTMLElement | string
|
|
29
|
-
onValueChange?: (value: string) => void
|
|
30
|
-
closeOnSelect?: boolean
|
|
31
|
-
/** Value of the single item rendered disabled. */
|
|
32
|
-
disabledValue?: string
|
|
33
|
-
/** Per-value `textValue` overrides for typeahead. */
|
|
34
|
-
textValues?: Record<string, string>
|
|
35
|
-
/** Arbitrary HTML attributes spread onto the `placeholders` item. */
|
|
36
|
-
itemAttrs?: Record<string, unknown>
|
|
37
|
-
/** Consumer-supplied click handler on the `placeholders` item. */
|
|
38
|
-
onItemClick?: (event: MouseEvent) => void
|
|
39
|
-
/** A plain Item BEFORE the group — proves roving spans both kinds. */
|
|
40
|
-
withLeadingItem?: boolean
|
|
41
|
-
/** Renders a RadioItem with NO enclosing RadioGroup (guard-rail case). */
|
|
42
|
-
orphan?: boolean
|
|
43
|
-
/** Arbitrary HTML attributes spread onto the `role="group"` node. */
|
|
44
|
-
groupAttrs?: Record<string, unknown>
|
|
45
|
-
/** Consumer-supplied keydown handler on the `placeholders` item. */
|
|
46
|
-
onItemKeydown?: (event: KeyboardEvent) => void
|
|
47
|
-
/** Consumer-supplied `onSelect` on the `placeholders` item. */
|
|
48
|
-
onItemSelect?: (event: Event) => void
|
|
49
|
-
/** Value the PARENT-driven button assigns (see `parent-set` below). */
|
|
50
|
-
parentTarget?: string
|
|
51
|
-
} = $props()
|
|
52
|
-
|
|
53
|
-
const OPTIONS = [
|
|
54
|
-
{ value: 'document', label: 'Document' },
|
|
55
|
-
{ value: 'placeholders', label: 'Placeholders' },
|
|
56
|
-
{ value: 'bibliographie', label: 'Bibliographie' },
|
|
57
|
-
]
|
|
58
|
-
</script>
|
|
59
|
-
|
|
60
|
-
<!-- Lives OUTSIDE the menu: clicking it is the PARENT moving `value`, the
|
|
61
|
-
one path that must move the checked row without any notification. -->
|
|
62
|
-
<button data-testid="parent-set" onclick={() => (value = parentTarget)}>set from parent</button>
|
|
63
|
-
|
|
64
|
-
<DropdownMenuRoot bind:open>
|
|
65
|
-
<DropdownMenuTrigger>Affichage</DropdownMenuTrigger>
|
|
66
|
-
<DropdownMenuContent {portalTo}>
|
|
67
|
-
{#if withLeadingItem}
|
|
68
|
-
<DropdownMenuItem>Plan</DropdownMenuItem>
|
|
69
|
-
{/if}
|
|
70
|
-
{#if orphan}
|
|
71
|
-
<DropdownMenuRadioItem value="orphan">Orphan</DropdownMenuRadioItem>
|
|
72
|
-
{/if}
|
|
73
|
-
<DropdownMenuRadioGroup bind:value {onValueChange} {...groupAttrs ?? {}}>
|
|
74
|
-
{#each OPTIONS as option (option.value)}
|
|
75
|
-
<DropdownMenuRadioItem
|
|
76
|
-
value={option.value}
|
|
77
|
-
disabled={option.value === disabledValue}
|
|
78
|
-
textValue={textValues?.[option.value]}
|
|
79
|
-
{closeOnSelect}
|
|
80
|
-
onclick={option.value === 'placeholders' ? onItemClick : undefined}
|
|
81
|
-
onkeydown={option.value === 'placeholders' ? onItemKeydown : undefined}
|
|
82
|
-
onSelect={option.value === 'placeholders' ? onItemSelect : undefined}
|
|
83
|
-
{...option.value === 'placeholders' ? (itemAttrs ?? {}) : {}}
|
|
84
|
-
>
|
|
85
|
-
{option.label}
|
|
86
|
-
</DropdownMenuRadioItem>
|
|
87
|
-
{/each}
|
|
88
|
-
</DropdownMenuRadioGroup>
|
|
89
|
-
</DropdownMenuContent>
|
|
90
|
-
</DropdownMenuRoot>
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import DropdownMenuCheckboxItem from '../components/dropdown-menu-checkbox-item.svelte'
|
|
3
|
-
import DropdownMenuContent from '../components/dropdown-menu-content.svelte'
|
|
4
|
-
import DropdownMenuItem from '../components/dropdown-menu-item.svelte'
|
|
5
|
-
import DropdownMenuRoot from '../components/dropdown-menu-root.svelte'
|
|
6
|
-
import DropdownMenuSubContent from '../components/dropdown-menu-sub-content.svelte'
|
|
7
|
-
import DropdownMenuSubTrigger from '../components/dropdown-menu-sub-trigger.svelte'
|
|
8
|
-
import DropdownMenuSub from '../components/dropdown-menu-sub.svelte'
|
|
9
|
-
import DropdownMenuTrigger from '../components/dropdown-menu-trigger.svelte'
|
|
10
|
-
|
|
11
|
-
let {
|
|
12
|
-
open = $bindable(false),
|
|
13
|
-
portalTo,
|
|
14
|
-
onOpenChange,
|
|
15
|
-
withSub = false,
|
|
16
|
-
subFirst = false,
|
|
17
|
-
subDisabled = false,
|
|
18
|
-
subTextValue = undefined,
|
|
19
|
-
withCheckbox = false,
|
|
20
|
-
withSecondSub = false,
|
|
21
|
-
}: {
|
|
22
|
-
open?: boolean
|
|
23
|
-
portalTo?: HTMLElement | string
|
|
24
|
-
onOpenChange?: (open: boolean) => void
|
|
25
|
-
withSub?: boolean
|
|
26
|
-
subFirst?: boolean
|
|
27
|
-
subDisabled?: boolean
|
|
28
|
-
subTextValue?: string
|
|
29
|
-
withCheckbox?: boolean
|
|
30
|
-
withSecondSub?: boolean
|
|
31
|
-
} = $props()
|
|
32
|
-
</script>
|
|
33
|
-
|
|
34
|
-
{#snippet subBlock()}
|
|
35
|
-
<DropdownMenuSub>
|
|
36
|
-
<DropdownMenuSubTrigger disabled={subDisabled} textValue={subTextValue}
|
|
37
|
-
>More options</DropdownMenuSubTrigger
|
|
38
|
-
>
|
|
39
|
-
<DropdownMenuSubContent>
|
|
40
|
-
<DropdownMenuItem>Sub Item 1</DropdownMenuItem>
|
|
41
|
-
<DropdownMenuItem>Sub Item 2</DropdownMenuItem>
|
|
42
|
-
</DropdownMenuSubContent>
|
|
43
|
-
</DropdownMenuSub>
|
|
44
|
-
{/snippet}
|
|
45
|
-
|
|
46
|
-
<DropdownMenuRoot bind:open {onOpenChange}>
|
|
47
|
-
<DropdownMenuTrigger>Open Menu</DropdownMenuTrigger>
|
|
48
|
-
<DropdownMenuContent {portalTo}>
|
|
49
|
-
{#if withSub && subFirst}
|
|
50
|
-
{@render subBlock()}
|
|
51
|
-
{/if}
|
|
52
|
-
<DropdownMenuItem>Item 1</DropdownMenuItem>
|
|
53
|
-
<DropdownMenuItem>Item 2</DropdownMenuItem>
|
|
54
|
-
<DropdownMenuItem>Item 3</DropdownMenuItem>
|
|
55
|
-
{#if withCheckbox}
|
|
56
|
-
<DropdownMenuCheckboxItem>Notifications</DropdownMenuCheckboxItem>
|
|
57
|
-
{/if}
|
|
58
|
-
{#if withSub && !subFirst}
|
|
59
|
-
{@render subBlock()}
|
|
60
|
-
{/if}
|
|
61
|
-
{#if withSecondSub}
|
|
62
|
-
<DropdownMenuSub>
|
|
63
|
-
<DropdownMenuSubTrigger>Other options</DropdownMenuSubTrigger>
|
|
64
|
-
<DropdownMenuSubContent>
|
|
65
|
-
<DropdownMenuItem>Sub B Item 1</DropdownMenuItem>
|
|
66
|
-
<DropdownMenuItem>Sub B Item 2</DropdownMenuItem>
|
|
67
|
-
</DropdownMenuSubContent>
|
|
68
|
-
</DropdownMenuSub>
|
|
69
|
-
{/if}
|
|
70
|
-
</DropdownMenuContent>
|
|
71
|
-
</DropdownMenuRoot>
|