@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.
Files changed (150) hide show
  1. package/LICENSE +21 -0
  2. package/THIRD_PARTY_NOTICES.md +201 -0
  3. package/package.json +8 -2
  4. package/dist/components/__tests__/overlay-scrim-source.test.js +0 -134
  5. package/dist/components/accordion/__tests__/AccordionTest.svelte +0 -31
  6. package/dist/components/accordion/__tests__/accordion.test.svelte.js +0 -139
  7. package/dist/components/alert-dialog/__tests__/AlertDialogTest.svelte +0 -28
  8. package/dist/components/alert-dialog/__tests__/alert-dialog.a11y.test.js +0 -42
  9. package/dist/components/alert-dialog/__tests__/alert-dialog.integration.test.svelte.js +0 -51
  10. package/dist/components/alert-dialog/__tests__/alert-dialog.test.svelte.js +0 -113
  11. package/dist/components/app-bar/__tests__/app-bar.test.svelte.js +0 -30
  12. package/dist/components/aspect-ratio/__tests__/aspect-ratio.test.svelte.js +0 -22
  13. package/dist/components/avatar/__tests__/avatar.test.svelte.js +0 -26
  14. package/dist/components/badge/__tests__/badge.test.svelte.js +0 -30
  15. package/dist/components/bottom-sheet/__tests__/BottomSheetTest.svelte +0 -22
  16. package/dist/components/bottom-sheet/__tests__/bottom-sheet.test.svelte.js +0 -83
  17. package/dist/components/button/__tests__/button.test.svelte.js +0 -63
  18. package/dist/components/calendar/__tests__/CalendarTest.svelte +0 -56
  19. package/dist/components/calendar/__tests__/calendar.test.svelte.js +0 -190
  20. package/dist/components/card/__tests__/card.test.svelte.js +0 -30
  21. package/dist/components/checkbox/__tests__/checkbox.test.svelte.js +0 -44
  22. package/dist/components/chips/__tests__/chips.test.svelte.js +0 -26
  23. package/dist/components/collapsible/__tests__/CollapsibleTest.svelte +0 -18
  24. package/dist/components/collapsible/__tests__/collapsible.test.svelte.js +0 -125
  25. package/dist/components/combobox/__tests__/ComboboxTest.svelte +0 -40
  26. package/dist/components/combobox/__tests__/combobox.test.svelte.js +0 -255
  27. package/dist/components/command/__tests__/CommandInlineDirectTest.svelte +0 -29
  28. package/dist/components/command/__tests__/CommandInlineTest.svelte +0 -89
  29. package/dist/components/command/__tests__/CommandTest.svelte +0 -77
  30. package/dist/components/command/__tests__/command-inline.test.svelte.js +0 -392
  31. package/dist/components/command/__tests__/command-item-passthrough.test.svelte.js +0 -284
  32. package/dist/components/command/__tests__/command.a11y.test.js +0 -30
  33. package/dist/components/command/__tests__/command.integration.test.svelte.js +0 -68
  34. package/dist/components/command/__tests__/command.test.svelte.js +0 -230
  35. package/dist/components/context-menu/__tests__/ContextMenuNonInteractiveTest.svelte +0 -32
  36. package/dist/components/context-menu/__tests__/ContextMenuTest.svelte +0 -25
  37. package/dist/components/context-menu/__tests__/context-menu.test.svelte.js +0 -141
  38. package/dist/components/date-field/__tests__/DateFieldTest.svelte +0 -20
  39. package/dist/components/date-field/__tests__/date-field.test.svelte.js +0 -139
  40. package/dist/components/date-picker/__tests__/DatePickerTest.svelte +0 -20
  41. package/dist/components/date-picker/__tests__/date-picker.test.svelte.js +0 -190
  42. package/dist/components/date-range-field/__tests__/DateRangeFieldTest.svelte +0 -20
  43. package/dist/components/date-range-field/__tests__/date-range-field.test.svelte.js +0 -130
  44. package/dist/components/date-range-picker/__tests__/DateRangePickerTest.svelte +0 -18
  45. package/dist/components/date-range-picker/__tests__/date-range-picker.test.svelte.js +0 -156
  46. package/dist/components/dialog/__tests__/DialogTest.svelte +0 -34
  47. package/dist/components/dialog/__tests__/dialog.test.svelte.js +0 -184
  48. package/dist/components/drop-zone/__tests__/drop-zone.test.svelte.js +0 -26
  49. package/dist/components/dropdown-menu/__tests__/DropdownMenuRadioTest.svelte +0 -90
  50. package/dist/components/dropdown-menu/__tests__/DropdownMenuTest.svelte +0 -71
  51. package/dist/components/dropdown-menu/__tests__/dropdown-menu-radio-group.test.svelte.js +0 -306
  52. package/dist/components/dropdown-menu/__tests__/dropdown-menu.test.svelte.js +0 -567
  53. package/dist/components/empty-state/__tests__/empty-state.test.svelte.js +0 -26
  54. package/dist/components/fab/__tests__/fab.test.svelte.js +0 -30
  55. package/dist/components/field/__tests__/FieldTest.svelte +0 -26
  56. package/dist/components/field/__tests__/field.test.svelte.js +0 -74
  57. package/dist/components/grid-list/__tests__/GridListTest.svelte +0 -46
  58. package/dist/components/grid-list/__tests__/grid-list.test.svelte.js +0 -231
  59. package/dist/components/hover-card/__tests__/HoverCardTest.svelte +0 -30
  60. package/dist/components/hover-card/__tests__/hover-card.test.svelte.js +0 -236
  61. package/dist/components/indicator/__tests__/indicator.test.svelte.js +0 -30
  62. package/dist/components/input/__tests__/input.test.svelte.js +0 -67
  63. package/dist/components/label/__tests__/label.test.svelte.js +0 -22
  64. package/dist/components/language-select/__tests__/language-select.test.svelte.js +0 -199
  65. package/dist/components/language-select/__tests__/languages.test.js +0 -87
  66. package/dist/components/link-preview/__tests__/LinkPreviewTest.svelte +0 -37
  67. package/dist/components/link-preview/__tests__/link-preview.a11y.test.js +0 -28
  68. package/dist/components/link-preview/__tests__/link-preview.integration.test.svelte.js +0 -54
  69. package/dist/components/link-preview/__tests__/link-preview.test.svelte.js +0 -215
  70. package/dist/components/list/__tests__/list.test.svelte.js +0 -22
  71. package/dist/components/menubar/__tests__/MenubarTest.svelte +0 -52
  72. package/dist/components/menubar/__tests__/menubar.a11y.test.js +0 -28
  73. package/dist/components/menubar/__tests__/menubar.integration.test.svelte.js +0 -57
  74. package/dist/components/menubar/__tests__/menubar.test.svelte.js +0 -328
  75. package/dist/components/meter/__tests__/meter.test.svelte.js +0 -26
  76. package/dist/components/nav-bar/__tests__/nav-bar.test.svelte.js +0 -23
  77. package/dist/components/nav-drawer/__tests__/nav-drawer.test.svelte.js +0 -28
  78. package/dist/components/nav-rail/__tests__/nav-rail.test.svelte.js +0 -28
  79. package/dist/components/pagination/__tests__/pagination.test.svelte.js +0 -26
  80. package/dist/components/pin-input/__tests__/pin-input.test.svelte.js +0 -71
  81. package/dist/components/popover/__tests__/PopoverTest.svelte +0 -27
  82. package/dist/components/popover/__tests__/popover.test.svelte.js +0 -196
  83. package/dist/components/progress/__tests__/progress.test.svelte.js +0 -32
  84. package/dist/components/radio-group/__tests__/radio-group.test.svelte.js +0 -22
  85. package/dist/components/range-calendar/__tests__/RangeCalendarTest.svelte +0 -23
  86. package/dist/components/range-calendar/__tests__/range-calendar.test.svelte.js +0 -105
  87. package/dist/components/rating-group/__tests__/rating-group.test.svelte.js +0 -26
  88. package/dist/components/scroll-area/__tests__/scroll-area.test.svelte.js +0 -34
  89. package/dist/components/search-field/__tests__/SearchFieldTest.svelte +0 -34
  90. package/dist/components/search-field/__tests__/search-field.test.svelte.js +0 -118
  91. package/dist/components/select/__tests__/SelectLabelPersistenceTest.svelte +0 -31
  92. package/dist/components/select/__tests__/SelectTest.svelte +0 -33
  93. package/dist/components/select/__tests__/select.test.svelte.js +0 -221
  94. package/dist/components/separator/__tests__/separator.test.svelte.js +0 -39
  95. package/dist/components/sheet/__tests__/SheetTest.svelte +0 -34
  96. package/dist/components/sheet/__tests__/sheet.test.svelte.js +0 -193
  97. package/dist/components/side-sheet/__tests__/side-sheet.test.svelte.js +0 -138
  98. package/dist/components/sidebar/__tests__/SidebarFullTest.svelte +0 -45
  99. package/dist/components/sidebar/__tests__/SidebarTest.svelte +0 -9
  100. package/dist/components/sidebar/__tests__/sidebar.test.svelte.js +0 -186
  101. package/dist/components/simple-dialog/__tests__/SimpleDialogTest.svelte +0 -40
  102. package/dist/components/simple-dialog/__tests__/simple-dialog.test.svelte.js +0 -50
  103. package/dist/components/simple-tooltip/__tests__/SimpleTooltipTest.svelte +0 -19
  104. package/dist/components/simple-tooltip/__tests__/simple-tooltip.test.svelte.js +0 -61
  105. package/dist/components/slider/__tests__/slider.test.svelte.js +0 -42
  106. package/dist/components/sortable-list/__tests__/NestedSortableListTest.svelte +0 -49
  107. package/dist/components/sortable-list/__tests__/SortableListTest.svelte +0 -42
  108. package/dist/components/sortable-list/__tests__/sortable-list.nested.test.svelte.js +0 -112
  109. package/dist/components/sortable-list/__tests__/sortable-list.test.svelte.js +0 -175
  110. package/dist/components/splitter/__tests__/splitter.test.svelte.js +0 -26
  111. package/dist/components/switch/__tests__/switch.test.svelte.js +0 -44
  112. package/dist/components/table/__tests__/table.test.svelte.js +0 -22
  113. package/dist/components/tabs/__tests__/TabsTest.svelte +0 -32
  114. package/dist/components/tabs/__tests__/tabs.test.svelte.js +0 -227
  115. package/dist/components/textarea/__tests__/textarea.test.svelte.js +0 -55
  116. package/dist/components/time-field/__tests__/TimeFieldTest.svelte +0 -20
  117. package/dist/components/time-field/__tests__/time-field.test.svelte.js +0 -100
  118. package/dist/components/time-range-field/__tests__/TimeRangeFieldTest.svelte +0 -20
  119. package/dist/components/time-range-field/__tests__/time-range-field.test.svelte.js +0 -124
  120. package/dist/components/toast/__tests__/toast.test.svelte.js +0 -46
  121. package/dist/components/toggle/__tests__/toggle.test.svelte.js +0 -38
  122. package/dist/components/toggle-group/__tests__/toggle-group.test.svelte.js +0 -22
  123. package/dist/components/toolbar/__tests__/ToolbarCompositionTest.svelte +0 -18
  124. package/dist/components/toolbar/__tests__/ToolbarTabstopTest.svelte +0 -10
  125. package/dist/components/toolbar/__tests__/toolbar.test.svelte.js +0 -63
  126. package/dist/components/tooltip/__tests__/TooltipTest.svelte +0 -22
  127. package/dist/components/tooltip/__tests__/tooltip.test.svelte.js +0 -225
  128. package/dist/components/tree/__tests__/TreeLabelContentTest.svelte +0 -21
  129. package/dist/components/tree/__tests__/TreeTest.svelte +0 -24
  130. package/dist/components/tree/__tests__/tree.test.svelte.js +0 -229
  131. package/dist/components/virtualizer/__tests__/waterfall-layout.test.js +0 -82
  132. package/dist/internal/announcer/__tests__/announcer.test.js +0 -42
  133. package/dist/internal/dismissible-layer/__tests__/dismissible-layer.test.js +0 -196
  134. package/dist/internal/drag-drop/__tests__/auto-scroll.test.js +0 -104
  135. package/dist/internal/drag-drop/__tests__/drag-session.test.svelte.js +0 -803
  136. package/dist/internal/drag-drop/__tests__/drag-source.test.svelte.js +0 -130
  137. package/dist/internal/drag-drop/__tests__/drop-delegate.test.js +0 -223
  138. package/dist/internal/drag-drop/__tests__/types.test.js +0 -20
  139. package/dist/internal/escape-layer/__tests__/escape-layer.test.js +0 -113
  140. package/dist/internal/focus-scope/__tests__/focus-scope.test.js +0 -225
  141. package/dist/internal/grace-area/__tests__/grace-area.test.js +0 -67
  142. package/dist/internal/helpers/__tests__/helpers.test.js +0 -123
  143. package/dist/internal/portal/__tests__/PortalTest.svelte +0 -21
  144. package/dist/internal/portal/__tests__/portal.test.js +0 -29
  145. package/dist/internal/portal/__tests__/portal.test.svelte.js +0 -57
  146. package/dist/internal/scroll-lock/__tests__/scroll-lock.test.js +0 -160
  147. package/dist/internal/typeahead/__tests__/typeahead.test.js +0 -61
  148. package/src/themes/__tests__/carbon-dark-contrast.test.ts +0 -170
  149. package/src/themes/__tests__/generated-css-drift.test.ts +0 -53
  150. 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} />