@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,139 +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 DateFieldTest from './DateFieldTest.svelte';
|
|
5
|
-
describe('date-field', () => {
|
|
6
|
-
let target;
|
|
7
|
-
let instance;
|
|
8
|
-
function setup(props = {}) {
|
|
9
|
-
target = document.createElement('div');
|
|
10
|
-
document.body.appendChild(target);
|
|
11
|
-
instance = mount(DateFieldTest, { target, props });
|
|
12
|
-
flushSync();
|
|
13
|
-
return target;
|
|
14
|
-
}
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
if (instance)
|
|
17
|
-
unmount(instance);
|
|
18
|
-
target?.remove();
|
|
19
|
-
});
|
|
20
|
-
// 1. Renders group role container
|
|
21
|
-
it('renders group role container', () => {
|
|
22
|
-
setup();
|
|
23
|
-
const group = target.querySelector('[role="group"]');
|
|
24
|
-
expect(group).toBeTruthy();
|
|
25
|
-
expect(group?.classList.contains('sp-date-field')).toBe(true);
|
|
26
|
-
});
|
|
27
|
-
// 2. Renders day/month/year segments (role="spinbutton")
|
|
28
|
-
it('renders day, month, and year segments with role="spinbutton"', () => {
|
|
29
|
-
setup();
|
|
30
|
-
const spinbuttons = target.querySelectorAll('[role="spinbutton"]');
|
|
31
|
-
expect(spinbuttons.length).toBe(3);
|
|
32
|
-
});
|
|
33
|
-
// 3. Segments have aria-label identifying part (day/month/year or jour/mois/année)
|
|
34
|
-
it('segments have aria-label identifying day, month, year', () => {
|
|
35
|
-
setup();
|
|
36
|
-
const spinbuttons = target.querySelectorAll('[role="spinbutton"]');
|
|
37
|
-
const labels = Array.from(spinbuttons).map((s) => s.getAttribute('aria-label')?.toLowerCase() ?? '');
|
|
38
|
-
// Each label should match day/month/year (en) or jour/mois/année (fr) etc.
|
|
39
|
-
const hasDayLabel = labels.some((l) => /day|jour|giorno|día|tag/i.test(l));
|
|
40
|
-
const hasMonthLabel = labels.some((l) => /month|mois|mese|mes|monat/i.test(l));
|
|
41
|
-
const hasYearLabel = labels.some((l) => /year|année|anno|año|jahr/i.test(l));
|
|
42
|
-
expect(hasDayLabel).toBe(true);
|
|
43
|
-
expect(hasMonthLabel).toBe(true);
|
|
44
|
-
expect(hasYearLabel).toBe(true);
|
|
45
|
-
});
|
|
46
|
-
// 4. Renders value when provided
|
|
47
|
-
it('renders provided value in segments', () => {
|
|
48
|
-
const date = new CalendarDate(2024, 6, 15);
|
|
49
|
-
setup({ value: date });
|
|
50
|
-
const spinbuttons = target.querySelectorAll('[role="spinbutton"]');
|
|
51
|
-
const texts = Array.from(spinbuttons).map((s) => s.textContent?.trim() ?? '');
|
|
52
|
-
// 2024, 06, 15 should appear (order may vary by locale)
|
|
53
|
-
expect(texts.includes('2024')).toBe(true);
|
|
54
|
-
// Month may render as "06" or "6"
|
|
55
|
-
expect(texts.includes('06') || texts.includes('6')).toBe(true);
|
|
56
|
-
expect(texts.includes('15')).toBe(true);
|
|
57
|
-
});
|
|
58
|
-
// 5. Typing a digit updates segment
|
|
59
|
-
it('typing a digit on the day segment updates its value', () => {
|
|
60
|
-
setup();
|
|
61
|
-
// Find segment with aria-label "day"
|
|
62
|
-
const daySegment = target.querySelector('[role="spinbutton"][aria-label="day"]');
|
|
63
|
-
expect(daySegment).toBeTruthy();
|
|
64
|
-
daySegment.focus();
|
|
65
|
-
flushSync();
|
|
66
|
-
daySegment.dispatchEvent(new KeyboardEvent('keydown', { key: '5', bubbles: true }));
|
|
67
|
-
flushSync();
|
|
68
|
-
// The segment content should reflect digit 5 (may be padded as "05")
|
|
69
|
-
const text = daySegment.textContent?.trim() ?? '';
|
|
70
|
-
expect(Number.parseInt(text, 10)).toBe(5);
|
|
71
|
-
});
|
|
72
|
-
// 6. ArrowUp increments segment
|
|
73
|
-
it('arrowUp increments the month segment', () => {
|
|
74
|
-
const date = new CalendarDate(2024, 6, 15);
|
|
75
|
-
setup({ value: date });
|
|
76
|
-
const monthSegment = target.querySelector('[role="spinbutton"][aria-label="month"]');
|
|
77
|
-
expect(monthSegment).toBeTruthy();
|
|
78
|
-
monthSegment.focus();
|
|
79
|
-
flushSync();
|
|
80
|
-
monthSegment.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true }));
|
|
81
|
-
flushSync();
|
|
82
|
-
expect(monthSegment.textContent?.trim()).toBe('07');
|
|
83
|
-
});
|
|
84
|
-
// 7. ArrowDown decrements segment
|
|
85
|
-
it('arrowDown decrements the month segment', () => {
|
|
86
|
-
const date = new CalendarDate(2024, 6, 15);
|
|
87
|
-
setup({ value: date });
|
|
88
|
-
const monthSegment = target.querySelector('[role="spinbutton"][aria-label="month"]');
|
|
89
|
-
expect(monthSegment).toBeTruthy();
|
|
90
|
-
monthSegment.focus();
|
|
91
|
-
flushSync();
|
|
92
|
-
monthSegment.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
|
|
93
|
-
flushSync();
|
|
94
|
-
expect(monthSegment.textContent?.trim()).toBe('05');
|
|
95
|
-
});
|
|
96
|
-
// 8. Tab moves between segments (no error thrown)
|
|
97
|
-
it('tab key dispatched on segment does not throw', () => {
|
|
98
|
-
setup();
|
|
99
|
-
const spinbuttons = target.querySelectorAll('[role="spinbutton"]');
|
|
100
|
-
expect(spinbuttons.length).toBe(3);
|
|
101
|
-
expect(() => {
|
|
102
|
-
spinbuttons[0].focus();
|
|
103
|
-
flushSync();
|
|
104
|
-
spinbuttons[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }));
|
|
105
|
-
flushSync();
|
|
106
|
-
}).not.toThrow();
|
|
107
|
-
});
|
|
108
|
-
// 9. Disabled → group has data-disabled, segments have tabindex=-1
|
|
109
|
-
it('disabled field has data-disabled on group and tabindex=-1 on segments', () => {
|
|
110
|
-
setup({ disabled: true });
|
|
111
|
-
const group = target.querySelector('[role="group"]');
|
|
112
|
-
expect(group?.hasAttribute('data-disabled')).toBe(true);
|
|
113
|
-
const spinbuttons = target.querySelectorAll('[role="spinbutton"]');
|
|
114
|
-
spinbuttons.forEach((s) => {
|
|
115
|
-
expect(s.getAttribute('tabindex')).toBe('-1');
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
// 10. Readonly → segments have aria-readonly="true"
|
|
119
|
-
it('readonly field has aria-readonly on segments', () => {
|
|
120
|
-
setup({ readonly: true });
|
|
121
|
-
const spinbuttons = target.querySelectorAll('[role="spinbutton"]');
|
|
122
|
-
expect(spinbuttons.length).toBeGreaterThan(0);
|
|
123
|
-
spinbuttons.forEach((s) => {
|
|
124
|
-
expect(s.getAttribute('aria-readonly')).toBe('true');
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
// 11. locale="fr-FR" renders segments (smoke — no crash, still 3 spinbuttons)
|
|
128
|
-
it('locale="fr-FR" renders three spinbutton segments without error', () => {
|
|
129
|
-
setup({ locale: 'fr-FR' });
|
|
130
|
-
const spinbuttons = target.querySelectorAll('[role="spinbutton"]');
|
|
131
|
-
expect(spinbuttons.length).toBe(3);
|
|
132
|
-
});
|
|
133
|
-
// 12. Group has aria-label
|
|
134
|
-
it('group has an aria-label', () => {
|
|
135
|
-
setup({ ariaLabel: 'Date de naissance' });
|
|
136
|
-
const group = target.querySelector('[role="group"]');
|
|
137
|
-
expect(group?.getAttribute('aria-label')).toBe('Date de naissance');
|
|
138
|
-
});
|
|
139
|
-
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { DateValue } from '../../../internal/date-time/types.js'
|
|
3
|
-
import DatePickerRoot from '../components/date-picker-root.svelte'
|
|
4
|
-
|
|
5
|
-
let {
|
|
6
|
-
value = $bindable<DateValue | undefined>(undefined),
|
|
7
|
-
disabled = false,
|
|
8
|
-
readonly: isReadonly = false,
|
|
9
|
-
closeOnDateSelect = true,
|
|
10
|
-
portalTo = undefined,
|
|
11
|
-
}: {
|
|
12
|
-
value?: DateValue
|
|
13
|
-
disabled?: boolean
|
|
14
|
-
readonly?: boolean
|
|
15
|
-
closeOnDateSelect?: boolean
|
|
16
|
-
portalTo?: HTMLElement | string
|
|
17
|
-
} = $props()
|
|
18
|
-
</script>
|
|
19
|
-
|
|
20
|
-
<DatePickerRoot bind:value {disabled} readonly={isReadonly} {closeOnDateSelect} {portalTo} />
|
|
@@ -1,190 +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 DatePickerTest from './DatePickerTest.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('datePicker', () => {
|
|
15
|
-
let target;
|
|
16
|
-
let instance;
|
|
17
|
-
function setup(props = {}) {
|
|
18
|
-
target = document.createElement('div');
|
|
19
|
-
document.body.appendChild(target);
|
|
20
|
-
instance = mount(DatePickerTest, {
|
|
21
|
-
target,
|
|
22
|
-
props: { portalTo: target, ...props },
|
|
23
|
-
});
|
|
24
|
-
flushSync();
|
|
25
|
-
}
|
|
26
|
-
afterEach(() => {
|
|
27
|
-
if (instance)
|
|
28
|
-
unmount(instance);
|
|
29
|
-
instance = undefined;
|
|
30
|
-
target?.remove();
|
|
31
|
-
});
|
|
32
|
-
/**
|
|
33
|
-
* Finds the calendar-open trigger button.
|
|
34
|
-
* The trigger is a <button class='sp-date-picker-trigger'> with aria-label='Open calendar'.
|
|
35
|
-
* Also accepts aria-haspopup in case the API evolves.
|
|
36
|
-
*/
|
|
37
|
-
function findTrigger() {
|
|
38
|
-
return (target.querySelector('button.sp-date-picker-trigger') ??
|
|
39
|
-
target.querySelector('button[aria-label="Open calendar"]') ??
|
|
40
|
-
target.querySelector('button[aria-haspopup]'));
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Finds the open popup content inside the portal target.
|
|
44
|
-
* The root renders <div data-state="open" class="sp-date-picker-content">.
|
|
45
|
-
*/
|
|
46
|
-
function findOpenContent() {
|
|
47
|
-
return (target.querySelector('[data-state="open"]') ??
|
|
48
|
-
target.querySelector('.sp-date-picker-content') ??
|
|
49
|
-
target.querySelector('[role="dialog"]'));
|
|
50
|
-
}
|
|
51
|
-
// ─── Smoke ────────────────────────────────────────────────────────────────
|
|
52
|
-
it('renders a trigger button', () => {
|
|
53
|
-
setup();
|
|
54
|
-
expect(findTrigger()).toBeTruthy();
|
|
55
|
-
});
|
|
56
|
-
it('stays closed by default', () => {
|
|
57
|
-
setup();
|
|
58
|
-
expect(findOpenContent()).toBeNull();
|
|
59
|
-
});
|
|
60
|
-
it('renders a date field alongside the trigger', () => {
|
|
61
|
-
setup();
|
|
62
|
-
expect(target.querySelector('.sp-date-picker-field-group')).toBeTruthy();
|
|
63
|
-
});
|
|
64
|
-
// ─── Value binding ────────────────────────────────────────────────────────
|
|
65
|
-
it('renders with an initial date value without error', () => {
|
|
66
|
-
const initial = new CalendarDate(2026, 4, 13);
|
|
67
|
-
setup({ value: initial });
|
|
68
|
-
expect(findTrigger()).toBeTruthy();
|
|
69
|
-
});
|
|
70
|
-
it('renders with a different year/month without error', () => {
|
|
71
|
-
const val = new CalendarDate(2024, 12, 31);
|
|
72
|
-
setup({ value: val });
|
|
73
|
-
expect(findTrigger()).toBeTruthy();
|
|
74
|
-
});
|
|
75
|
-
// ─── Open / Close ─────────────────────────────────────────────────────────
|
|
76
|
-
it('opens popup on trigger click', () => {
|
|
77
|
-
setup();
|
|
78
|
-
findTrigger()?.click();
|
|
79
|
-
flushSync();
|
|
80
|
-
expect(findOpenContent()).toBeTruthy();
|
|
81
|
-
});
|
|
82
|
-
it('trigger has aria-expanded=true when open', () => {
|
|
83
|
-
setup();
|
|
84
|
-
findTrigger()?.click();
|
|
85
|
-
flushSync();
|
|
86
|
-
const trigger = findTrigger();
|
|
87
|
-
expect(trigger?.getAttribute('aria-expanded')).toBe('true');
|
|
88
|
-
});
|
|
89
|
-
it('trigger has aria-expanded=false when closed', () => {
|
|
90
|
-
setup();
|
|
91
|
-
const trigger = findTrigger();
|
|
92
|
-
expect(trigger?.getAttribute('aria-expanded')).toBe('false');
|
|
93
|
-
});
|
|
94
|
-
it('trigger has aria-haspopup="dialog"', () => {
|
|
95
|
-
setup();
|
|
96
|
-
expect(findTrigger()?.getAttribute('aria-haspopup')).toBe('dialog');
|
|
97
|
-
});
|
|
98
|
-
it('closes via EscapeLayer when Escape is pressed', () => {
|
|
99
|
-
setup();
|
|
100
|
-
findTrigger()?.click();
|
|
101
|
-
flushSync();
|
|
102
|
-
expect(findOpenContent()).toBeTruthy();
|
|
103
|
-
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
104
|
-
flushSync();
|
|
105
|
-
expect(findOpenContent()).toBeNull();
|
|
106
|
-
});
|
|
107
|
-
it('closes via DismissibleLayer on outside pointerdown', () => {
|
|
108
|
-
setup();
|
|
109
|
-
findTrigger()?.click();
|
|
110
|
-
flushSync();
|
|
111
|
-
expect(findOpenContent()).toBeTruthy();
|
|
112
|
-
// Click somewhere outside the component entirely
|
|
113
|
-
const outside = document.createElement('button');
|
|
114
|
-
outside.style.cssText = 'position:fixed;top:9999px;left:9999px';
|
|
115
|
-
document.body.appendChild(outside);
|
|
116
|
-
outside.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, clientX: 9999, clientY: 9999 }));
|
|
117
|
-
flushSync();
|
|
118
|
-
expect(findOpenContent()).toBeNull();
|
|
119
|
-
outside.remove();
|
|
120
|
-
});
|
|
121
|
-
it('closes again when trigger is clicked while open (toggle)', () => {
|
|
122
|
-
setup();
|
|
123
|
-
findTrigger()?.click();
|
|
124
|
-
flushSync();
|
|
125
|
-
expect(findOpenContent()).toBeTruthy();
|
|
126
|
-
findTrigger()?.click();
|
|
127
|
-
flushSync();
|
|
128
|
-
expect(findOpenContent()).toBeNull();
|
|
129
|
-
});
|
|
130
|
-
// ─── Disabled ─────────────────────────────────────────────────────────────
|
|
131
|
-
it('marks the trigger button as disabled when disabled=true', () => {
|
|
132
|
-
setup({ disabled: true });
|
|
133
|
-
const trigger = findTrigger();
|
|
134
|
-
const isDisabled = trigger?.hasAttribute('disabled') || trigger?.getAttribute('aria-disabled') === 'true';
|
|
135
|
-
expect(isDisabled).toBe(true);
|
|
136
|
-
});
|
|
137
|
-
it('does not open when disabled=true and trigger is clicked', () => {
|
|
138
|
-
setup({ disabled: true });
|
|
139
|
-
findTrigger()?.click();
|
|
140
|
-
flushSync();
|
|
141
|
-
expect(findOpenContent()).toBeNull();
|
|
142
|
-
});
|
|
143
|
-
// ─── Calendar grid ────────────────────────────────────────────────────────
|
|
144
|
-
it('renders a calendar grid (role=grid) when open', () => {
|
|
145
|
-
setup();
|
|
146
|
-
findTrigger()?.click();
|
|
147
|
-
flushSync();
|
|
148
|
-
expect(target.querySelector('[role="grid"]')).toBeTruthy();
|
|
149
|
-
});
|
|
150
|
-
it('renders calendar day cells (role=gridcell) when open', () => {
|
|
151
|
-
setup();
|
|
152
|
-
findTrigger()?.click();
|
|
153
|
-
flushSync();
|
|
154
|
-
const cells = target.querySelectorAll('[role="gridcell"]');
|
|
155
|
-
expect(cells.length).toBeGreaterThan(0);
|
|
156
|
-
});
|
|
157
|
-
// ─── Portal ───────────────────────────────────────────────────────────────
|
|
158
|
-
it('portals content to document.body by default (not inside host element)', () => {
|
|
159
|
-
// Create a fresh instance WITHOUT portalTo so it defaults to document.body
|
|
160
|
-
const bodyTarget = document.createElement('div');
|
|
161
|
-
document.body.appendChild(bodyTarget);
|
|
162
|
-
const bodyInstance = mount(DatePickerTest, {
|
|
163
|
-
target: bodyTarget,
|
|
164
|
-
});
|
|
165
|
-
flushSync();
|
|
166
|
-
const triggerEl = bodyTarget.querySelector('button.sp-date-picker-trigger, button[aria-label="Open calendar"]');
|
|
167
|
-
triggerEl?.click();
|
|
168
|
-
flushSync();
|
|
169
|
-
// Content must NOT be inside the host element
|
|
170
|
-
const insideHost = bodyTarget.querySelector('[data-state="open"], .sp-date-picker-content');
|
|
171
|
-
// Content MUST be somewhere in document.body
|
|
172
|
-
const inBody = document.body.querySelector('[data-state="open"], .sp-date-picker-content');
|
|
173
|
-
unmount(bodyInstance);
|
|
174
|
-
bodyTarget.remove();
|
|
175
|
-
expect(insideHost).toBeNull();
|
|
176
|
-
expect(inBody).toBeTruthy();
|
|
177
|
-
});
|
|
178
|
-
// ─── closeOnDateSelect ────────────────────────────────────────────────────
|
|
179
|
-
it('stays open when closeOnDateSelect=false after selecting a date', () => {
|
|
180
|
-
setup({ closeOnDateSelect: false });
|
|
181
|
-
findTrigger()?.click();
|
|
182
|
-
flushSync();
|
|
183
|
-
expect(findOpenContent()).toBeTruthy();
|
|
184
|
-
// Click the first available (non-disabled) day cell button
|
|
185
|
-
const firstCell = target.querySelector('[role="gridcell"] button:not([disabled]):not([aria-disabled="true"])');
|
|
186
|
-
firstCell?.click();
|
|
187
|
-
flushSync();
|
|
188
|
-
expect(findOpenContent()).toBeTruthy();
|
|
189
|
-
});
|
|
190
|
-
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { DateRange } from '../../../internal/date-time/types.js'
|
|
3
|
-
import DateRangeFieldRoot from '../components/date-range-field-root.svelte'
|
|
4
|
-
|
|
5
|
-
let {
|
|
6
|
-
value = $bindable<DateRange>({}),
|
|
7
|
-
disabled = false,
|
|
8
|
-
readonly: isReadonly = false,
|
|
9
|
-
required = false,
|
|
10
|
-
onValueChange,
|
|
11
|
-
}: {
|
|
12
|
-
value?: DateRange
|
|
13
|
-
disabled?: boolean
|
|
14
|
-
readonly?: boolean
|
|
15
|
-
required?: boolean
|
|
16
|
-
onValueChange?: (v: DateRange) => void
|
|
17
|
-
} = $props()
|
|
18
|
-
</script>
|
|
19
|
-
|
|
20
|
-
<DateRangeFieldRoot bind:value {disabled} readonly={isReadonly} {required} {onValueChange} />
|
|
@@ -1,130 +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 DateRangeFieldTest from './DateRangeFieldTest.svelte';
|
|
5
|
-
describe('dateRangeField', () => {
|
|
6
|
-
let target;
|
|
7
|
-
let instance;
|
|
8
|
-
function setup(props = {}) {
|
|
9
|
-
target = document.createElement('div');
|
|
10
|
-
document.body.appendChild(target);
|
|
11
|
-
instance = mount(DateRangeFieldTest, { target, props });
|
|
12
|
-
flushSync();
|
|
13
|
-
}
|
|
14
|
-
afterEach(() => {
|
|
15
|
-
if (instance)
|
|
16
|
-
unmount(instance);
|
|
17
|
-
instance = undefined;
|
|
18
|
-
target?.remove();
|
|
19
|
-
});
|
|
20
|
-
// T-C-1-1: smoke — renders two inner DateField groups (start + end)
|
|
21
|
-
it('renders two date field groups (start + end)', () => {
|
|
22
|
-
setup();
|
|
23
|
-
// The outer wrapper is role=group (aria-label="Date range")
|
|
24
|
-
// Each DateField.Root also renders role=group — 2 inner groups expected
|
|
25
|
-
const allGroups = target.querySelectorAll('[role="group"]');
|
|
26
|
-
// At least 3: outer date-range-field + 2 inner DateField groups
|
|
27
|
-
expect(allGroups.length).toBeGreaterThanOrEqual(3);
|
|
28
|
-
// Inner groups have aria-label "Start date" and "End date"
|
|
29
|
-
const startGroup = target.querySelector('[aria-label="Start date"]');
|
|
30
|
-
const endGroup = target.querySelector('[aria-label="End date"]');
|
|
31
|
-
expect(startGroup).not.toBeNull();
|
|
32
|
-
expect(endGroup).not.toBeNull();
|
|
33
|
-
});
|
|
34
|
-
// T-C-1-2: accepts an initial DateRange value and renders segments
|
|
35
|
-
it('accepts an initial DateRange value and renders spinbutton segments', () => {
|
|
36
|
-
const initialRange = {
|
|
37
|
-
start: new CalendarDate(2026, 1, 15),
|
|
38
|
-
end: new CalendarDate(2026, 3, 10),
|
|
39
|
-
};
|
|
40
|
-
setup({ value: initialRange });
|
|
41
|
-
// Each DateField renders 3 spinbutton segments (year, month, day)
|
|
42
|
-
const segments = target.querySelectorAll('[role="spinbutton"]');
|
|
43
|
-
// 2 fields × 3 segments = 6 spinbuttons
|
|
44
|
-
expect(segments.length).toBe(6);
|
|
45
|
-
// Verify year values are present in aria-valuenow attributes
|
|
46
|
-
const yearSegments = Array.from(segments).filter((s) => s.getAttribute('aria-label') === 'year');
|
|
47
|
-
expect(yearSegments.length).toBe(2);
|
|
48
|
-
expect(yearSegments[0].getAttribute('aria-valuenow')).toBe('2026');
|
|
49
|
-
expect(yearSegments[1].getAttribute('aria-valuenow')).toBe('2026');
|
|
50
|
-
});
|
|
51
|
-
// T-C-1-3: applies data-disabled to inner DateField groups when disabled=true
|
|
52
|
-
it('applies data-disabled to inner DateField groups when disabled=true', () => {
|
|
53
|
-
setup({ disabled: true });
|
|
54
|
-
// DateField.Root sets data-disabled on its own group element
|
|
55
|
-
const disabledGroups = target.querySelectorAll('[role="group"][data-disabled]');
|
|
56
|
-
expect(disabledGroups.length).toBeGreaterThanOrEqual(2);
|
|
57
|
-
});
|
|
58
|
-
// T-C-1-4: applies aria-readonly to segments when readonly=true
|
|
59
|
-
it('applies aria-readonly to segments when readonly=true', () => {
|
|
60
|
-
setup({ readonly: true });
|
|
61
|
-
// DateField renders aria-readonly on each spinbutton segment
|
|
62
|
-
const readonlySegments = target.querySelectorAll('[role="spinbutton"][aria-readonly="true"]');
|
|
63
|
-
expect(readonlySegments.length).toBeGreaterThanOrEqual(2);
|
|
64
|
-
});
|
|
65
|
-
// T-C-1-5: renders a visible en-dash separator between start and end
|
|
66
|
-
it('renders a visible en-dash separator between start and end fields', () => {
|
|
67
|
-
setup();
|
|
68
|
-
const separator = target.querySelector('.sp-date-range-separator');
|
|
69
|
-
expect(separator).not.toBeNull();
|
|
70
|
-
expect(separator?.textContent).toBe('–');
|
|
71
|
-
});
|
|
72
|
-
// T-C-1-extra-1: onStartChange fires when start DateField segment value changes via ArrowUp
|
|
73
|
-
it('fires onValueChange when start date segment is incremented via ArrowUp', () => {
|
|
74
|
-
const changes = [];
|
|
75
|
-
setup({
|
|
76
|
-
value: { start: new CalendarDate(2026, 3, 10), end: new CalendarDate(2026, 6, 20) },
|
|
77
|
-
onValueChange: (v) => changes.push(v),
|
|
78
|
-
});
|
|
79
|
-
// Get the first spinbutton in the start DateField (month segment — first editable)
|
|
80
|
-
const startGroup = target.querySelector('[aria-label="Start date"]');
|
|
81
|
-
expect(startGroup).not.toBeNull();
|
|
82
|
-
const segments = startGroup.querySelectorAll('[role="spinbutton"]');
|
|
83
|
-
expect(segments.length).toBeGreaterThan(0);
|
|
84
|
-
// Fire ArrowUp to increment — triggers onStartChange → onValueChange
|
|
85
|
-
const monthSeg = segments[0];
|
|
86
|
-
monthSeg.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true }));
|
|
87
|
-
flushSync();
|
|
88
|
-
expect(changes.length).toBeGreaterThan(0);
|
|
89
|
-
});
|
|
90
|
-
// T-C-1-extra-2: onEndChange fires when end DateField segment value changes via ArrowUp
|
|
91
|
-
it('fires onValueChange when end date segment is incremented via ArrowUp', () => {
|
|
92
|
-
const changes = [];
|
|
93
|
-
setup({
|
|
94
|
-
value: { start: new CalendarDate(2026, 3, 10), end: new CalendarDate(2026, 6, 20) },
|
|
95
|
-
onValueChange: (v) => changes.push(v),
|
|
96
|
-
});
|
|
97
|
-
// Get the first spinbutton in the end DateField
|
|
98
|
-
const endGroup = target.querySelector('[aria-label="End date"]');
|
|
99
|
-
expect(endGroup).not.toBeNull();
|
|
100
|
-
const segments = endGroup.querySelectorAll('[role="spinbutton"]');
|
|
101
|
-
expect(segments.length).toBeGreaterThan(0);
|
|
102
|
-
// Fire ArrowUp to increment — triggers onEndChange → onValueChange
|
|
103
|
-
const monthSeg = segments[0];
|
|
104
|
-
monthSeg.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true }));
|
|
105
|
-
flushSync();
|
|
106
|
-
expect(changes.length).toBeGreaterThan(0);
|
|
107
|
-
});
|
|
108
|
-
// T-C-1-6: re-mounting with a new value reflects updated bindable prop
|
|
109
|
-
it('updates rendered segments when remounted with new DateRange value', () => {
|
|
110
|
-
setup();
|
|
111
|
-
// Verify initial state: segments have no aria-valuenow (empty DateRange {})
|
|
112
|
-
const initialYearSegments = Array.from(target.querySelectorAll('[role="spinbutton"]')).filter((s) => s.getAttribute('aria-label') === 'year');
|
|
113
|
-
// With value={} (no start/end), segments should show placeholder (no valuenow)
|
|
114
|
-
expect(initialYearSegments.length).toBe(2);
|
|
115
|
-
expect(initialYearSegments[0].getAttribute('aria-valuenow')).toBeNull();
|
|
116
|
-
// Remount with a concrete value
|
|
117
|
-
if (instance)
|
|
118
|
-
unmount(instance);
|
|
119
|
-
const newRange = {
|
|
120
|
-
start: new CalendarDate(2027, 6, 1),
|
|
121
|
-
end: new CalendarDate(2027, 6, 15),
|
|
122
|
-
};
|
|
123
|
-
instance = mount(DateRangeFieldTest, { target, props: { value: newRange } });
|
|
124
|
-
flushSync();
|
|
125
|
-
const updatedYearSegments = Array.from(target.querySelectorAll('[role="spinbutton"]')).filter((s) => s.getAttribute('aria-label') === 'year');
|
|
126
|
-
expect(updatedYearSegments.length).toBe(2);
|
|
127
|
-
expect(updatedYearSegments[0].getAttribute('aria-valuenow')).toBe('2027');
|
|
128
|
-
expect(updatedYearSegments[1].getAttribute('aria-valuenow')).toBe('2027');
|
|
129
|
-
});
|
|
130
|
-
});
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { DateRange } from '../../../internal/date-time/types.js'
|
|
3
|
-
import DateRangePickerRoot from '../components/date-range-picker-root.svelte'
|
|
4
|
-
|
|
5
|
-
let {
|
|
6
|
-
value = $bindable<DateRange>({}),
|
|
7
|
-
disabled = false,
|
|
8
|
-
readonly: isReadonly = false,
|
|
9
|
-
portalTo = undefined,
|
|
10
|
-
}: {
|
|
11
|
-
value?: DateRange
|
|
12
|
-
disabled?: boolean
|
|
13
|
-
readonly?: boolean
|
|
14
|
-
portalTo?: HTMLElement | string
|
|
15
|
-
} = $props()
|
|
16
|
-
</script>
|
|
17
|
-
|
|
18
|
-
<DateRangePickerRoot bind:value {disabled} readonly={isReadonly} {portalTo} />
|