@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.
Files changed (163) hide show
  1. package/LICENSE +21 -0
  2. package/THIRD_PARTY_NOTICES.md +201 -0
  3. package/dist/components/command/command.svelte.d.ts +30 -0
  4. package/dist/components/command/command.svelte.d.ts.map +1 -1
  5. package/dist/components/command/command.svelte.js +85 -4
  6. package/dist/components/command/components/command-group.svelte +25 -15
  7. package/dist/components/command/components/command-group.svelte.d.ts.map +1 -1
  8. package/dist/components/command/components/command-input.svelte +54 -5
  9. package/dist/components/command/components/command-input.svelte.d.ts +1 -1
  10. package/dist/components/command/components/command-input.svelte.d.ts.map +1 -1
  11. package/dist/components/command/components/command-list.svelte +22 -3
  12. package/dist/components/command/components/command-list.svelte.d.ts.map +1 -1
  13. package/dist/components/command/components/command-root.svelte +43 -4
  14. package/dist/components/command/components/command-root.svelte.d.ts.map +1 -1
  15. package/dist/components/command/index.d.ts +1 -0
  16. package/dist/components/command/index.d.ts.map +1 -1
  17. package/dist/components/command/types.d.ts +59 -4
  18. package/dist/components/command/types.d.ts.map +1 -1
  19. package/package.json +8 -2
  20. package/dist/components/__tests__/overlay-scrim-source.test.js +0 -134
  21. package/dist/components/accordion/__tests__/AccordionTest.svelte +0 -31
  22. package/dist/components/accordion/__tests__/accordion.test.svelte.js +0 -139
  23. package/dist/components/alert-dialog/__tests__/AlertDialogTest.svelte +0 -28
  24. package/dist/components/alert-dialog/__tests__/alert-dialog.a11y.test.js +0 -42
  25. package/dist/components/alert-dialog/__tests__/alert-dialog.integration.test.svelte.js +0 -51
  26. package/dist/components/alert-dialog/__tests__/alert-dialog.test.svelte.js +0 -113
  27. package/dist/components/app-bar/__tests__/app-bar.test.svelte.js +0 -30
  28. package/dist/components/aspect-ratio/__tests__/aspect-ratio.test.svelte.js +0 -22
  29. package/dist/components/avatar/__tests__/avatar.test.svelte.js +0 -26
  30. package/dist/components/badge/__tests__/badge.test.svelte.js +0 -30
  31. package/dist/components/bottom-sheet/__tests__/BottomSheetTest.svelte +0 -22
  32. package/dist/components/bottom-sheet/__tests__/bottom-sheet.test.svelte.js +0 -83
  33. package/dist/components/button/__tests__/button.test.svelte.js +0 -63
  34. package/dist/components/calendar/__tests__/CalendarTest.svelte +0 -56
  35. package/dist/components/calendar/__tests__/calendar.test.svelte.js +0 -190
  36. package/dist/components/card/__tests__/card.test.svelte.js +0 -30
  37. package/dist/components/checkbox/__tests__/checkbox.test.svelte.js +0 -44
  38. package/dist/components/chips/__tests__/chips.test.svelte.js +0 -26
  39. package/dist/components/collapsible/__tests__/CollapsibleTest.svelte +0 -18
  40. package/dist/components/collapsible/__tests__/collapsible.test.svelte.js +0 -125
  41. package/dist/components/combobox/__tests__/ComboboxTest.svelte +0 -40
  42. package/dist/components/combobox/__tests__/combobox.test.svelte.js +0 -255
  43. package/dist/components/command/__tests__/CommandTest.svelte +0 -77
  44. package/dist/components/command/__tests__/command-item-passthrough.test.svelte.js +0 -284
  45. package/dist/components/command/__tests__/command.a11y.test.js +0 -30
  46. package/dist/components/command/__tests__/command.integration.test.svelte.js +0 -68
  47. package/dist/components/command/__tests__/command.test.svelte.js +0 -230
  48. package/dist/components/context-menu/__tests__/ContextMenuNonInteractiveTest.svelte +0 -32
  49. package/dist/components/context-menu/__tests__/ContextMenuTest.svelte +0 -25
  50. package/dist/components/context-menu/__tests__/context-menu.test.svelte.js +0 -141
  51. package/dist/components/date-field/__tests__/DateFieldTest.svelte +0 -20
  52. package/dist/components/date-field/__tests__/date-field.test.svelte.js +0 -139
  53. package/dist/components/date-picker/__tests__/DatePickerTest.svelte +0 -20
  54. package/dist/components/date-picker/__tests__/date-picker.test.svelte.js +0 -190
  55. package/dist/components/date-range-field/__tests__/DateRangeFieldTest.svelte +0 -20
  56. package/dist/components/date-range-field/__tests__/date-range-field.test.svelte.js +0 -130
  57. package/dist/components/date-range-picker/__tests__/DateRangePickerTest.svelte +0 -18
  58. package/dist/components/date-range-picker/__tests__/date-range-picker.test.svelte.js +0 -156
  59. package/dist/components/dialog/__tests__/DialogTest.svelte +0 -34
  60. package/dist/components/dialog/__tests__/dialog.test.svelte.js +0 -184
  61. package/dist/components/drop-zone/__tests__/drop-zone.test.svelte.js +0 -26
  62. package/dist/components/dropdown-menu/__tests__/DropdownMenuRadioTest.svelte +0 -90
  63. package/dist/components/dropdown-menu/__tests__/DropdownMenuTest.svelte +0 -71
  64. package/dist/components/dropdown-menu/__tests__/dropdown-menu-radio-group.test.svelte.js +0 -306
  65. package/dist/components/dropdown-menu/__tests__/dropdown-menu.test.svelte.js +0 -567
  66. package/dist/components/empty-state/__tests__/empty-state.test.svelte.js +0 -26
  67. package/dist/components/fab/__tests__/fab.test.svelte.js +0 -30
  68. package/dist/components/field/__tests__/FieldTest.svelte +0 -26
  69. package/dist/components/field/__tests__/field.test.svelte.js +0 -74
  70. package/dist/components/grid-list/__tests__/GridListTest.svelte +0 -46
  71. package/dist/components/grid-list/__tests__/grid-list.test.svelte.js +0 -231
  72. package/dist/components/hover-card/__tests__/HoverCardTest.svelte +0 -30
  73. package/dist/components/hover-card/__tests__/hover-card.test.svelte.js +0 -236
  74. package/dist/components/indicator/__tests__/indicator.test.svelte.js +0 -30
  75. package/dist/components/input/__tests__/input.test.svelte.js +0 -67
  76. package/dist/components/label/__tests__/label.test.svelte.js +0 -22
  77. package/dist/components/language-select/__tests__/language-select.test.svelte.js +0 -199
  78. package/dist/components/language-select/__tests__/languages.test.js +0 -87
  79. package/dist/components/link-preview/__tests__/LinkPreviewTest.svelte +0 -37
  80. package/dist/components/link-preview/__tests__/link-preview.a11y.test.js +0 -28
  81. package/dist/components/link-preview/__tests__/link-preview.integration.test.svelte.js +0 -54
  82. package/dist/components/link-preview/__tests__/link-preview.test.svelte.js +0 -215
  83. package/dist/components/list/__tests__/list.test.svelte.js +0 -22
  84. package/dist/components/menubar/__tests__/MenubarTest.svelte +0 -52
  85. package/dist/components/menubar/__tests__/menubar.a11y.test.js +0 -28
  86. package/dist/components/menubar/__tests__/menubar.integration.test.svelte.js +0 -57
  87. package/dist/components/menubar/__tests__/menubar.test.svelte.js +0 -328
  88. package/dist/components/meter/__tests__/meter.test.svelte.js +0 -26
  89. package/dist/components/nav-bar/__tests__/nav-bar.test.svelte.js +0 -23
  90. package/dist/components/nav-drawer/__tests__/nav-drawer.test.svelte.js +0 -28
  91. package/dist/components/nav-rail/__tests__/nav-rail.test.svelte.js +0 -28
  92. package/dist/components/pagination/__tests__/pagination.test.svelte.js +0 -26
  93. package/dist/components/pin-input/__tests__/pin-input.test.svelte.js +0 -71
  94. package/dist/components/popover/__tests__/PopoverTest.svelte +0 -27
  95. package/dist/components/popover/__tests__/popover.test.svelte.js +0 -196
  96. package/dist/components/progress/__tests__/progress.test.svelte.js +0 -32
  97. package/dist/components/radio-group/__tests__/radio-group.test.svelte.js +0 -22
  98. package/dist/components/range-calendar/__tests__/RangeCalendarTest.svelte +0 -23
  99. package/dist/components/range-calendar/__tests__/range-calendar.test.svelte.js +0 -105
  100. package/dist/components/rating-group/__tests__/rating-group.test.svelte.js +0 -26
  101. package/dist/components/scroll-area/__tests__/scroll-area.test.svelte.js +0 -34
  102. package/dist/components/search-field/__tests__/SearchFieldTest.svelte +0 -34
  103. package/dist/components/search-field/__tests__/search-field.test.svelte.js +0 -118
  104. package/dist/components/select/__tests__/SelectLabelPersistenceTest.svelte +0 -31
  105. package/dist/components/select/__tests__/SelectTest.svelte +0 -33
  106. package/dist/components/select/__tests__/select.test.svelte.js +0 -221
  107. package/dist/components/separator/__tests__/separator.test.svelte.js +0 -39
  108. package/dist/components/sheet/__tests__/SheetTest.svelte +0 -34
  109. package/dist/components/sheet/__tests__/sheet.test.svelte.js +0 -193
  110. package/dist/components/side-sheet/__tests__/side-sheet.test.svelte.js +0 -138
  111. package/dist/components/sidebar/__tests__/SidebarFullTest.svelte +0 -45
  112. package/dist/components/sidebar/__tests__/SidebarTest.svelte +0 -9
  113. package/dist/components/sidebar/__tests__/sidebar.test.svelte.js +0 -186
  114. package/dist/components/simple-dialog/__tests__/SimpleDialogTest.svelte +0 -40
  115. package/dist/components/simple-dialog/__tests__/simple-dialog.test.svelte.js +0 -50
  116. package/dist/components/simple-tooltip/__tests__/SimpleTooltipTest.svelte +0 -19
  117. package/dist/components/simple-tooltip/__tests__/simple-tooltip.test.svelte.js +0 -61
  118. package/dist/components/slider/__tests__/slider.test.svelte.js +0 -42
  119. package/dist/components/sortable-list/__tests__/NestedSortableListTest.svelte +0 -49
  120. package/dist/components/sortable-list/__tests__/SortableListTest.svelte +0 -42
  121. package/dist/components/sortable-list/__tests__/sortable-list.nested.test.svelte.js +0 -112
  122. package/dist/components/sortable-list/__tests__/sortable-list.test.svelte.js +0 -175
  123. package/dist/components/splitter/__tests__/splitter.test.svelte.js +0 -26
  124. package/dist/components/switch/__tests__/switch.test.svelte.js +0 -44
  125. package/dist/components/table/__tests__/table.test.svelte.js +0 -22
  126. package/dist/components/tabs/__tests__/TabsTest.svelte +0 -32
  127. package/dist/components/tabs/__tests__/tabs.test.svelte.js +0 -227
  128. package/dist/components/textarea/__tests__/textarea.test.svelte.js +0 -55
  129. package/dist/components/time-field/__tests__/TimeFieldTest.svelte +0 -20
  130. package/dist/components/time-field/__tests__/time-field.test.svelte.js +0 -100
  131. package/dist/components/time-range-field/__tests__/TimeRangeFieldTest.svelte +0 -20
  132. package/dist/components/time-range-field/__tests__/time-range-field.test.svelte.js +0 -124
  133. package/dist/components/toast/__tests__/toast.test.svelte.js +0 -46
  134. package/dist/components/toggle/__tests__/toggle.test.svelte.js +0 -38
  135. package/dist/components/toggle-group/__tests__/toggle-group.test.svelte.js +0 -22
  136. package/dist/components/toolbar/__tests__/ToolbarCompositionTest.svelte +0 -18
  137. package/dist/components/toolbar/__tests__/ToolbarTabstopTest.svelte +0 -10
  138. package/dist/components/toolbar/__tests__/toolbar.test.svelte.js +0 -63
  139. package/dist/components/tooltip/__tests__/TooltipTest.svelte +0 -22
  140. package/dist/components/tooltip/__tests__/tooltip.test.svelte.js +0 -225
  141. package/dist/components/tree/__tests__/TreeLabelContentTest.svelte +0 -21
  142. package/dist/components/tree/__tests__/TreeTest.svelte +0 -24
  143. package/dist/components/tree/__tests__/tree.test.svelte.js +0 -229
  144. package/dist/components/virtualizer/__tests__/waterfall-layout.test.js +0 -82
  145. package/dist/internal/announcer/__tests__/announcer.test.js +0 -42
  146. package/dist/internal/dismissible-layer/__tests__/dismissible-layer.test.js +0 -196
  147. package/dist/internal/drag-drop/__tests__/auto-scroll.test.js +0 -104
  148. package/dist/internal/drag-drop/__tests__/drag-session.test.svelte.js +0 -803
  149. package/dist/internal/drag-drop/__tests__/drag-source.test.svelte.js +0 -130
  150. package/dist/internal/drag-drop/__tests__/drop-delegate.test.js +0 -223
  151. package/dist/internal/drag-drop/__tests__/types.test.js +0 -20
  152. package/dist/internal/escape-layer/__tests__/escape-layer.test.js +0 -113
  153. package/dist/internal/focus-scope/__tests__/focus-scope.test.js +0 -225
  154. package/dist/internal/grace-area/__tests__/grace-area.test.js +0 -67
  155. package/dist/internal/helpers/__tests__/helpers.test.js +0 -123
  156. package/dist/internal/portal/__tests__/PortalTest.svelte +0 -21
  157. package/dist/internal/portal/__tests__/portal.test.js +0 -29
  158. package/dist/internal/portal/__tests__/portal.test.svelte.js +0 -57
  159. package/dist/internal/scroll-lock/__tests__/scroll-lock.test.js +0 -160
  160. package/dist/internal/typeahead/__tests__/typeahead.test.js +0 -61
  161. package/src/themes/__tests__/carbon-dark-contrast.test.ts +0 -170
  162. package/src/themes/__tests__/generated-css-drift.test.ts +0 -53
  163. package/src/themes/__tests__/scrim-opaque.test.ts +0 -52
@@ -1,196 +0,0 @@
1
- import { flushSync, mount, unmount } from 'svelte';
2
- import { afterEach, describe, expect, it } from 'vitest';
3
- import PopoverRoot from '../components/popover-root.svelte';
4
- import PopoverTest from './PopoverTest.svelte';
5
- describe('popover', () => {
6
- let target;
7
- let instance;
8
- function setup(props = {}) {
9
- target = document.createElement('div');
10
- document.body.appendChild(target);
11
- instance = mount(PopoverTest, { target, props: { portalTo: target, ...props } });
12
- flushSync();
13
- return target;
14
- }
15
- function setupRoot(props = {}) {
16
- target = document.createElement('div');
17
- document.body.appendChild(target);
18
- instance = mount(PopoverRoot, { target, props });
19
- return target;
20
- }
21
- afterEach(() => {
22
- if (instance)
23
- unmount(instance);
24
- target?.remove();
25
- });
26
- // --- Basic rendering ---
27
- it('renders root', () => {
28
- setupRoot();
29
- expect(target.querySelector('.sp-popover-root')).toBeTruthy();
30
- });
31
- it('root has data-state="closed" by default', () => {
32
- setup();
33
- const root = target.querySelector('.sp-popover-root');
34
- expect(root?.getAttribute('data-state')).toBe('closed');
35
- });
36
- // --- Trigger ARIA ---
37
- it('trigger has aria-haspopup="dialog"', () => {
38
- setup();
39
- const trigger = target.querySelector('.sp-popover-trigger');
40
- expect(trigger?.getAttribute('aria-haspopup')).toBe('dialog');
41
- });
42
- it('trigger has aria-expanded="false" when closed', () => {
43
- setup();
44
- const trigger = target.querySelector('.sp-popover-trigger');
45
- expect(trigger?.getAttribute('aria-expanded')).toBe('false');
46
- });
47
- it('trigger has aria-expanded="true" when open', () => {
48
- setup({ open: true });
49
- const trigger = target.querySelector('.sp-popover-trigger');
50
- expect(trigger?.getAttribute('aria-expanded')).toBe('true');
51
- });
52
- it('trigger has type="button"', () => {
53
- setup();
54
- const trigger = target.querySelector('.sp-popover-trigger');
55
- expect(trigger?.type).toBe('button');
56
- });
57
- it('trigger has aria-controls when open', () => {
58
- setup({ open: true });
59
- flushSync();
60
- const trigger = target.querySelector('.sp-popover-trigger');
61
- const content = target.querySelector('[role="dialog"]');
62
- if (content) {
63
- expect(trigger?.getAttribute('aria-controls')).toBe(content.id);
64
- }
65
- });
66
- it('trigger has no aria-controls when closed', () => {
67
- setup({ open: false });
68
- const trigger = target.querySelector('.sp-popover-trigger');
69
- expect(trigger?.getAttribute('aria-controls')).toBeNull();
70
- });
71
- // --- Content ARIA ---
72
- it('content has role="dialog"', () => {
73
- setup({ open: true });
74
- const content = target.querySelector('[role="dialog"]');
75
- expect(content).toBeTruthy();
76
- });
77
- it('content has data-state="open" when open', () => {
78
- setup({ open: true });
79
- const content = target.querySelector('[role="dialog"]');
80
- expect(content?.getAttribute('data-state')).toBe('open');
81
- });
82
- it('content is not rendered when closed', () => {
83
- setup({ open: false });
84
- expect(target.querySelector('[role="dialog"]')).toBeNull();
85
- });
86
- // --- Toggle behavior ---
87
- it('clicking trigger opens popover', () => {
88
- setup({ open: false });
89
- const trigger = target.querySelector('.sp-popover-trigger');
90
- trigger.click();
91
- flushSync();
92
- expect(target.querySelector('.sp-popover-root')?.getAttribute('data-state')).toBe('open');
93
- });
94
- it('clicking trigger again closes popover', () => {
95
- setup({ open: true });
96
- const trigger = target.querySelector('.sp-popover-trigger');
97
- trigger.click();
98
- flushSync();
99
- expect(target.querySelector('.sp-popover-root')?.getAttribute('data-state')).toBe('closed');
100
- });
101
- it('trigger aria-expanded updates on toggle', () => {
102
- setup({ open: false });
103
- const trigger = target.querySelector('.sp-popover-trigger');
104
- expect(trigger.getAttribute('aria-expanded')).toBe('false');
105
- trigger.click();
106
- flushSync();
107
- expect(trigger.getAttribute('aria-expanded')).toBe('true');
108
- });
109
- // --- Escape key ---
110
- it('escape key closes popover', () => {
111
- setup({ open: true });
112
- flushSync();
113
- expect(target.querySelector('[role="dialog"]')).toBeTruthy();
114
- document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true }));
115
- flushSync();
116
- expect(target.querySelector('[role="dialog"]')).toBeNull();
117
- });
118
- it('calls onOpenChange with new value on each open transition', () => {
119
- const calls = [];
120
- const localTarget = document.createElement('div');
121
- document.body.appendChild(localTarget);
122
- const localInstance = mount(PopoverTest, {
123
- target: localTarget,
124
- props: {
125
- open: false,
126
- onOpenChange: (open) => calls.push(open),
127
- },
128
- });
129
- flushSync();
130
- try {
131
- const trigger = localTarget.querySelector('.sp-popover-trigger');
132
- trigger.click();
133
- flushSync();
134
- expect(calls).toEqual([true]);
135
- trigger.click();
136
- flushSync();
137
- expect(calls).toEqual([true, false]);
138
- }
139
- finally {
140
- unmount(localInstance);
141
- localTarget.remove();
142
- }
143
- });
144
- describe('portal opt-out (disablePortal)', () => {
145
- it('portals to document.body by default', () => {
146
- const localTarget = document.createElement('div');
147
- document.body.appendChild(localTarget);
148
- const localInstance = mount(PopoverTest, {
149
- target: localTarget,
150
- props: { open: true },
151
- });
152
- flushSync();
153
- try {
154
- expect(localTarget.querySelector('.sp-popover-content')).toBeNull();
155
- expect(document.body.querySelector('.sp-popover-content')).toBeTruthy();
156
- }
157
- finally {
158
- unmount(localInstance);
159
- localTarget.remove();
160
- }
161
- });
162
- it('renders inline when disablePortal={true}', () => {
163
- const localTarget = document.createElement('div');
164
- document.body.appendChild(localTarget);
165
- const localInstance = mount(PopoverTest, {
166
- target: localTarget,
167
- props: { open: true, disablePortal: true },
168
- });
169
- flushSync();
170
- try {
171
- expect(localTarget.querySelector('.sp-popover-content')).toBeTruthy();
172
- }
173
- finally {
174
- unmount(localInstance);
175
- localTarget.remove();
176
- }
177
- });
178
- it('preserves consumer class on the content element when inline', () => {
179
- const localTarget = document.createElement('div');
180
- document.body.appendChild(localTarget);
181
- const localInstance = mount(PopoverTest, {
182
- target: localTarget,
183
- props: { open: true, disablePortal: true, contentClass: 'my-consumer-class' },
184
- });
185
- flushSync();
186
- try {
187
- const el = localTarget.querySelector('.sp-popover-content.my-consumer-class');
188
- expect(el).toBeTruthy();
189
- }
190
- finally {
191
- unmount(localInstance);
192
- localTarget.remove();
193
- }
194
- });
195
- });
196
- });
@@ -1,32 +0,0 @@
1
- import { mount, unmount } from 'svelte';
2
- import { afterEach, describe, expect, it } from 'vitest';
3
- import Progress from '../progress.svelte';
4
- describe('progress', () => {
5
- let target;
6
- let instance;
7
- function setup(props = {}) {
8
- target = document.createElement('div');
9
- document.body.appendChild(target);
10
- instance = mount(Progress, { target, props });
11
- return target;
12
- }
13
- afterEach(() => {
14
- if (instance)
15
- unmount(instance);
16
- target?.remove();
17
- });
18
- it('renders with role="progressbar"', () => {
19
- setup();
20
- expect(target.querySelector('[role="progressbar"]')).toBeTruthy();
21
- });
22
- it('has aria-valuenow when value is set', () => {
23
- setup({ value: 60, max: 100 });
24
- const bar = target.querySelector('[role="progressbar"]');
25
- expect(bar.getAttribute('aria-valuenow')).toBe('60');
26
- expect(bar.getAttribute('aria-valuemax')).toBe('100');
27
- });
28
- it('applies color class', () => {
29
- setup({ color: 'success' });
30
- expect(target.querySelector('[role="progressbar"]')?.className).toContain('success');
31
- });
32
- });
@@ -1,22 +0,0 @@
1
- import { mount, unmount } from 'svelte';
2
- import { afterEach, describe, expect, it } from 'vitest';
3
- import RadioGroupRoot from '../components/radio-group-root.svelte';
4
- describe('radio-group', () => {
5
- let target;
6
- let instance;
7
- function setup(props = {}) {
8
- target = document.createElement('div');
9
- document.body.appendChild(target);
10
- instance = mount(RadioGroupRoot, { target, props });
11
- return target;
12
- }
13
- afterEach(() => {
14
- if (instance)
15
- unmount(instance);
16
- target?.remove();
17
- });
18
- it('renders with role=radiogroup', () => {
19
- setup();
20
- expect(target.querySelector('[role="radiogroup"]')).toBeTruthy();
21
- });
22
- });
@@ -1,23 +0,0 @@
1
- <script lang="ts">
2
- import type { DateRange, DateValue } from '../../../internal/date-time/types.js'
3
- import * as RangeCalendar from '../index.js'
4
-
5
- let {
6
- value = $bindable<DateRange>({}),
7
- placeholder = $bindable<DateValue | undefined>(undefined),
8
- disabled = false,
9
- readonly: isReadonly = false,
10
- locale = 'en-US',
11
- }: {
12
- value?: DateRange
13
- placeholder?: DateValue
14
- disabled?: boolean
15
- readonly?: boolean
16
- locale?: string
17
- } = $props()
18
- </script>
19
-
20
- <RangeCalendar.Root bind:value bind:placeholder {disabled} readonly={isReadonly} {locale}>
21
- <RangeCalendar.Header />
22
- <RangeCalendar.Grid />
23
- </RangeCalendar.Root>
@@ -1,105 +0,0 @@
1
- import { CalendarDate } from '@internationalized/date';
2
- import { flushSync, mount, unmount } from 'svelte';
3
- import { afterEach, beforeAll, describe, expect, it } from 'vitest';
4
- import RangeCalendarTest from './RangeCalendarTest.svelte';
5
- beforeAll(() => {
6
- if (typeof PointerEvent === 'undefined') {
7
- ;
8
- globalThis.PointerEvent = class PointerEvent extends MouseEvent {
9
- constructor(type, init = {}) {
10
- super(type, init);
11
- }
12
- };
13
- }
14
- });
15
- describe('rangeCalendar', () => {
16
- let target;
17
- let instance;
18
- function setup(props = {}) {
19
- target = document.createElement('div');
20
- document.body.appendChild(target);
21
- instance = mount(RangeCalendarTest, { target, props });
22
- flushSync();
23
- }
24
- afterEach(() => {
25
- if (instance)
26
- unmount(instance);
27
- instance = undefined;
28
- target?.remove();
29
- });
30
- it('renders grid', () => {
31
- setup({ placeholder: new CalendarDate(2026, 4, 13) });
32
- expect(target.querySelector('[role="grid"], [role="group"]')).toBeTruthy();
33
- });
34
- it('renders day cells', () => {
35
- setup({ placeholder: new CalendarDate(2026, 4, 13) });
36
- expect(target.querySelectorAll('[role="gridcell"], button[data-value]').length).toBeGreaterThan(20);
37
- });
38
- it('heading shows current month', () => {
39
- setup({ placeholder: new CalendarDate(2026, 4, 13) });
40
- expect(target.textContent ?? '').toMatch(/April|Avr|Apr|2026/i);
41
- });
42
- it('initial range marks start and end as selected', () => {
43
- setup({ value: { start: new CalendarDate(2026, 4, 10), end: new CalendarDate(2026, 4, 20) } });
44
- // data-range-start and data-range-end mark the endpoints
45
- const starts = target.querySelectorAll('[data-range-start], [data-selected]');
46
- const ends = target.querySelectorAll('[data-range-end], [aria-selected="true"]');
47
- expect(starts.length + ends.length).toBeGreaterThanOrEqual(2);
48
- });
49
- it('initial range highlights cells in-between', () => {
50
- setup({ value: { start: new CalendarDate(2026, 4, 10), end: new CalendarDate(2026, 4, 20) } });
51
- expect(target.querySelector('[data-in-range], [data-range-middle], [data-highlighted]')).toBeTruthy();
52
- });
53
- it('first click sets the range start', () => {
54
- setup({ placeholder: new CalendarDate(2026, 4, 13) });
55
- const cell = target.querySelector('[role="gridcell"] button:not([disabled]), button[data-value]:not([disabled])');
56
- cell?.click();
57
- flushSync();
58
- expect(target.querySelector('[data-selected], [aria-selected="true"], [data-range-start]')).toBeTruthy();
59
- });
60
- it('second click completes the range', () => {
61
- setup({ placeholder: new CalendarDate(2026, 4, 13) });
62
- const cells = target.querySelectorAll('[role="gridcell"] button:not([disabled]), button[data-value]:not([disabled])');
63
- cells[5]?.click();
64
- flushSync();
65
- cells[15]?.click();
66
- flushSync();
67
- // At least start + end = 2 selected markers
68
- expect(target.querySelectorAll('[data-selected], [aria-selected="true"], [data-range-start], [data-range-end]').length).toBeGreaterThanOrEqual(2);
69
- });
70
- it('hovering after first click shows preview', () => {
71
- setup({ placeholder: new CalendarDate(2026, 4, 13) });
72
- const cells = target.querySelectorAll('[role="gridcell"] button:not([disabled]), button[data-value]:not([disabled])');
73
- cells[5]?.click();
74
- flushSync();
75
- cells[10]?.dispatchEvent(new PointerEvent('pointerenter', { bubbles: true }));
76
- flushSync();
77
- // After hover, in-range preview should appear
78
- expect(target.querySelector('[data-in-range], [data-highlighted], [data-hovered], [data-preview]')).toBeTruthy();
79
- });
80
- it('disabled ignores clicks', () => {
81
- setup({ disabled: true, placeholder: new CalendarDate(2026, 4, 13) });
82
- target.querySelector('[role="gridcell"] button, button[data-value]')?.click();
83
- flushSync();
84
- expect(target.querySelector('[data-selected], [aria-selected="true"], [data-range-start]')).toBeNull();
85
- });
86
- it('readonly ignores clicks', () => {
87
- setup({ readonly: true, placeholder: new CalendarDate(2026, 4, 13) });
88
- target.querySelector('[role="gridcell"] button, button[data-value]')?.click();
89
- flushSync();
90
- expect(target.querySelector('[data-selected], [aria-selected="true"], [data-range-start]')).toBeNull();
91
- });
92
- it('next-month advances heading', () => {
93
- setup({ placeholder: new CalendarDate(2026, 4, 13) });
94
- const before = target.textContent ?? '';
95
- target
96
- .querySelector('button[aria-label*="next" i], button[aria-label*="Next" i], button[data-calendar-next]')
97
- ?.click();
98
- flushSync();
99
- expect(target.textContent ?? '').not.toBe(before);
100
- });
101
- it('grid has aria-label', () => {
102
- setup({ placeholder: new CalendarDate(2026, 4, 13) });
103
- expect(target.querySelector('[role="grid"]')?.getAttribute('aria-label')).toBeTruthy();
104
- });
105
- });
@@ -1,26 +0,0 @@
1
- import { mount, unmount } from 'svelte';
2
- import { afterEach, describe, expect, it } from 'vitest';
3
- import RatingGroup from '../rating-group.svelte';
4
- describe('rating-group', () => {
5
- let target;
6
- let instance;
7
- function setup(props = {}) {
8
- target = document.createElement('div');
9
- document.body.appendChild(target);
10
- instance = mount(RatingGroup, { target, props });
11
- return target;
12
- }
13
- afterEach(() => {
14
- if (instance)
15
- unmount(instance);
16
- target?.remove();
17
- });
18
- it('renders with role=slider', () => {
19
- setup();
20
- expect(target.querySelector('[role="slider"]')).toBeTruthy();
21
- });
22
- it('has aria-valuemax', () => {
23
- setup();
24
- expect(target.querySelector('[role="slider"]')?.getAttribute('aria-valuemax')).toBe('5');
25
- });
26
- });
@@ -1,34 +0,0 @@
1
- import { mount, unmount } from 'svelte';
2
- import { afterEach, describe, expect, it } from 'vitest';
3
- import ScrollArea from '../scroll-area.svelte';
4
- describe('scroll-area', () => {
5
- let target;
6
- let instance;
7
- function setup(props = {}) {
8
- target = document.createElement('div');
9
- document.body.appendChild(target);
10
- instance = mount(ScrollArea, { target, props });
11
- return target;
12
- }
13
- afterEach(() => {
14
- if (instance)
15
- unmount(instance);
16
- target?.remove();
17
- });
18
- it('uses hover mode by default', () => {
19
- setup();
20
- const root = target.querySelector('.sp-scroll-area');
21
- expect(root?.getAttribute('data-type')).toBe('hover');
22
- });
23
- it('accepts explicit mode', () => {
24
- setup({ type: 'always' });
25
- const root = target.querySelector('.sp-scroll-area');
26
- expect(root?.getAttribute('data-type')).toBe('always');
27
- });
28
- it('renders shell, viewport and content wrappers', () => {
29
- setup();
30
- expect(target.querySelector('.sp-scroll-area-shell')).toBeTruthy();
31
- expect(target.querySelector('.sp-scroll-area-viewport')).toBeTruthy();
32
- expect(target.querySelector('.sp-scroll-area-content')).toBeTruthy();
33
- });
34
- });
@@ -1,34 +0,0 @@
1
- <script lang="ts">
2
- import SearchField from '../search-field.svelte'
3
-
4
- let value = $state('')
5
- const disabled = false
6
- const placeholder = 'Search...'
7
- const size = 'md' as const
8
- let clearCount = $state(0)
9
- let inputCount = $state(0)
10
-
11
- function handleClear() {
12
- clearCount++
13
- }
14
-
15
- function handleInput(_v: string) {
16
- inputCount++
17
- }
18
- </script>
19
-
20
- <SearchField
21
- bind:value
22
- {placeholder}
23
- {size}
24
- {disabled}
25
- onClear={handleClear}
26
- onInput={handleInput}
27
- aria-label="Test search"
28
- />
29
-
30
- <div style="margin-top: 1rem; font-size: 0.85rem;">
31
- <p>Value: {value}</p>
32
- <p>Clear count: {clearCount}</p>
33
- <p>Input count: {inputCount}</p>
34
- </div>
@@ -1,118 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import { dirname, resolve } from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
- import { mount, unmount } from 'svelte';
5
- import { afterEach, describe, expect, it } from 'vitest';
6
- import SearchField from '../search-field.svelte';
7
- const componentSource = readFileSync(resolve(dirname(fileURLToPath(import.meta.url)), '../search-field.svelte'), 'utf8');
8
- describe('searchField', () => {
9
- let target;
10
- let instance;
11
- function setup(props = {}) {
12
- target = document.createElement('div');
13
- document.body.appendChild(target);
14
- instance = mount(SearchField, { target, props });
15
- return target;
16
- }
17
- afterEach(() => {
18
- if (instance)
19
- unmount(instance);
20
- target?.remove();
21
- });
22
- it('renders input element', () => {
23
- setup();
24
- const input = target.querySelector('input[type="search"]');
25
- expect(input).toBeTruthy();
26
- });
27
- it('renders placeholder text', () => {
28
- setup({ placeholder: 'Find something...' });
29
- const input = target.querySelector('input');
30
- expect(input.placeholder).toBe('Find something...');
31
- });
32
- it('accepts initial value', () => {
33
- setup({ value: 'hello' });
34
- const input = target.querySelector('input');
35
- expect(input.value).toBe('hello');
36
- });
37
- it('updates bindable value on input event', async () => {
38
- setup({ value: '' });
39
- const input = target.querySelector('input');
40
- input.value = 'typed text';
41
- input.dispatchEvent(new Event('input', { bubbles: true }));
42
- // The component updates its internal value through the event handler
43
- expect(input.value).toBe('typed text');
44
- });
45
- it('does not show clear button when value is empty', () => {
46
- setup({ value: '' });
47
- const clearBtn = target.querySelector('button[aria-label="Clear search"]');
48
- expect(clearBtn).toBeFalsy();
49
- });
50
- it('shows clear button when value is set', () => {
51
- setup({ value: 'search term' });
52
- const clearBtn = target.querySelector('button[aria-label="Clear search"]');
53
- expect(clearBtn).toBeTruthy();
54
- });
55
- it('clears value when clear button is clicked', async () => {
56
- let onClearCalled = false;
57
- setup({
58
- value: 'initial text',
59
- onClear: () => {
60
- onClearCalled = true;
61
- },
62
- });
63
- const clearBtn = target.querySelector('button[aria-label="Clear search"]');
64
- expect(clearBtn).toBeTruthy();
65
- clearBtn.click();
66
- // Verify the onClear callback was called
67
- expect(onClearCalled).toBe(true);
68
- });
69
- it('clears value when Escape key is pressed', async () => {
70
- let onClearCalled = false;
71
- setup({
72
- value: 'search text',
73
- onClear: () => {
74
- onClearCalled = true;
75
- },
76
- });
77
- const input = target.querySelector('input');
78
- input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
79
- expect(onClearCalled).toBe(true);
80
- });
81
- it('applies disabled state to input and clear button', () => {
82
- setup({ value: 'text', disabled: true });
83
- const input = target.querySelector('input');
84
- const clearBtn = target.querySelector('button[aria-label="Clear search"]');
85
- expect(input.disabled).toBe(true);
86
- expect(clearBtn?.disabled).toBe(true);
87
- });
88
- it('renders search icon', () => {
89
- setup();
90
- const icon = target.querySelector('.sp-search-icon');
91
- expect(icon).toBeTruthy();
92
- expect(icon?.tagName).toBe('svg');
93
- });
94
- it('size="sm" : exposes the input inside the .sm wrapper', () => {
95
- setup({ size: 'sm' });
96
- const wrapper = target.querySelector('.sp-search-field.sm');
97
- expect(wrapper).toBeTruthy();
98
- expect(wrapper?.querySelector('input.sp-search-input')).toBeTruthy();
99
- });
100
- // ── iOS Safari focus-zoom contract ────────────────────────────────
101
- // Real WebKit focus-zoom cannot be reproduced in jsdom. These tests
102
- // lock the CSS contract that prevents the regression : (a) no
103
- // hardcoded magic value bypassing the token system, (b) typography
104
- // is token-driven for fine pointers, (c) a clamp to >= 1rem is in
105
- // place on coarse pointers. Visual / real-device validation is
106
- // tracked by the issue and done manually on iPhone Safari.
107
- describe('iOS Safari focus-zoom contract (size="sm")', () => {
108
- it('does not hardcode 0.85rem on the input', () => {
109
- expect(componentSource).not.toMatch(/font-size:\s*0\.85rem/);
110
- });
111
- it('drives the sm input typography from the body-medium token', () => {
112
- expect(componentSource).toMatch(/\.sp-search-field\.sm\s+\.sp-search-input\s*\{[^}]*font-size:\s*var\(--sp-sys-typescale-body-medium-size[^}]*\}/);
113
- });
114
- it('clamps the sm input to at least 1rem on coarse pointers', () => {
115
- expect(componentSource).toMatch(/@media\s*\(pointer:\s*coarse\)\s*\{[\s\S]*?\.sp-search-field\.sm\s+\.sp-search-input\s*\{[^}]*font-size:\s*max\(1rem,[^)]*\)[\s\S]*?\}/);
116
- });
117
- });
118
- });
@@ -1,31 +0,0 @@
1
- <script lang="ts">
2
- import * as Select from '../index.js'
3
-
4
- const {
5
- portalTo,
6
- }: {
7
- portalTo?: HTMLElement | string
8
- } = $props()
9
-
10
- let value = $state('')
11
- let showContent = $state(true)
12
- </script>
13
-
14
- <button
15
- type="button"
16
- data-testid="toggle-content"
17
- onclick={() => {
18
- showContent = !showContent
19
- }}
20
- >
21
- Toggle content
22
- </button>
23
-
24
- <Select.Root bind:value name="color">
25
- <Select.Trigger data-testid="trigger" placeholder="Pick a color..." />
26
- {#if showContent}
27
- <Select.Content {portalTo}>
28
- <Select.Item value="blue" label="Blue">Blue</Select.Item>
29
- </Select.Content>
30
- {/if}
31
- </Select.Root>
@@ -1,33 +0,0 @@
1
- <script lang="ts">
2
- import SelectContent from '../components/select-content.svelte'
3
- import SelectItem from '../components/select-item.svelte'
4
- import SelectRoot from '../components/select-root.svelte'
5
- import SelectTrigger from '../components/select-trigger.svelte'
6
-
7
- let {
8
- open = $bindable(false),
9
- value = $bindable(''),
10
- name = '',
11
- portalTo,
12
- onOpenChange,
13
- disablePortal,
14
- contentClass,
15
- }: {
16
- open?: boolean
17
- value?: string
18
- name?: string
19
- portalTo?: HTMLElement | string
20
- onOpenChange?: (open: boolean) => void
21
- disablePortal?: boolean
22
- contentClass?: string
23
- } = $props()
24
- </script>
25
-
26
- <SelectRoot bind:open bind:value {name} {onOpenChange}>
27
- <SelectTrigger>Select option</SelectTrigger>
28
- <SelectContent {portalTo} {disablePortal} class={contentClass}>
29
- {#each Array.from({ length: 20 }, (_, i) => `item-${i + 1}`) as item (item)}
30
- <SelectItem value={item}>{item}</SelectItem>
31
- {/each}
32
- </SelectContent>
33
- </SelectRoot>