@spectral_labs/ui 1.6.1 → 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/dist/components/command/command.svelte.d.ts +30 -0
- package/dist/components/command/command.svelte.d.ts.map +1 -1
- package/dist/components/command/command.svelte.js +85 -4
- package/dist/components/command/components/command-group.svelte +25 -15
- package/dist/components/command/components/command-group.svelte.d.ts.map +1 -1
- package/dist/components/command/components/command-input.svelte +54 -5
- package/dist/components/command/components/command-input.svelte.d.ts +1 -1
- package/dist/components/command/components/command-input.svelte.d.ts.map +1 -1
- package/dist/components/command/components/command-list.svelte +22 -3
- package/dist/components/command/components/command-list.svelte.d.ts.map +1 -1
- package/dist/components/command/components/command-root.svelte +43 -4
- package/dist/components/command/components/command-root.svelte.d.ts.map +1 -1
- package/dist/components/command/index.d.ts +1 -0
- package/dist/components/command/index.d.ts.map +1 -1
- package/dist/components/command/types.d.ts +59 -4
- package/dist/components/command/types.d.ts.map +1 -1
- 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__/CommandTest.svelte +0 -77
- 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,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} />
|
|
@@ -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>
|