@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,328 +0,0 @@
1
- import { flushSync, mount, unmount } from 'svelte';
2
- import { afterEach, describe, expect, it } from 'vitest';
3
- import MenubarTest from './MenubarTest.svelte';
4
- describe('menubar', () => {
5
- let target;
6
- let instance;
7
- function setup(props = {}) {
8
- target = document.createElement('div');
9
- document.body.appendChild(target);
10
- instance = mount(MenubarTest, { target, props: { portalTo: target, ...props } });
11
- flushSync();
12
- return target;
13
- }
14
- afterEach(() => {
15
- if (instance)
16
- unmount(instance);
17
- target?.remove();
18
- });
19
- // --- Basic rendering ---
20
- it('renders root with role="menubar"', () => {
21
- setup();
22
- const menubar = target.querySelector('[role="menubar"]');
23
- expect(menubar).toBeTruthy();
24
- });
25
- it('renders all trigger buttons', () => {
26
- setup();
27
- const triggers = target.querySelectorAll('[data-menubar-trigger]');
28
- expect(triggers.length).toBe(3);
29
- });
30
- it('triggers have role="menuitem"', () => {
31
- setup();
32
- const triggers = target.querySelectorAll('[data-menubar-trigger]');
33
- triggers.forEach((trigger) => {
34
- expect(trigger.getAttribute('role')).toBe('menuitem');
35
- });
36
- });
37
- // --- Trigger ARIA ---
38
- it('trigger has aria-haspopup="menu"', () => {
39
- setup();
40
- const trigger = target.querySelector('[data-menubar-trigger]');
41
- expect(trigger?.getAttribute('aria-haspopup')).toBe('menu');
42
- });
43
- it('trigger has aria-expanded="false" when closed', () => {
44
- setup();
45
- const trigger = target.querySelector('[data-menubar-trigger]');
46
- expect(trigger?.getAttribute('aria-expanded')).toBe('false');
47
- });
48
- it('trigger has aria-expanded="true" when menu is open', () => {
49
- setup();
50
- const trigger = target.querySelector('[data-menubar-trigger]');
51
- trigger.click();
52
- flushSync();
53
- expect(trigger.getAttribute('aria-expanded')).toBe('true');
54
- });
55
- // --- Content ARIA ---
56
- it('content has role="menu" when open', () => {
57
- setup();
58
- const trigger = target.querySelector('[data-menubar-trigger]');
59
- trigger.click();
60
- flushSync();
61
- const content = target.querySelector('[role="menu"]');
62
- expect(content).toBeTruthy();
63
- });
64
- it('content has aria-orientation="vertical"', () => {
65
- setup();
66
- const trigger = target.querySelector('[data-menubar-trigger]');
67
- trigger.click();
68
- flushSync();
69
- const content = target.querySelector('[role="menu"]');
70
- expect(content?.getAttribute('aria-orientation')).toBe('vertical');
71
- });
72
- it('content has aria-labelledby pointing to trigger', () => {
73
- setup();
74
- const trigger = target.querySelector('[data-menubar-trigger]');
75
- trigger.click();
76
- flushSync();
77
- const content = target.querySelector('[role="menu"]');
78
- expect(content?.getAttribute('aria-labelledby')).toBe(trigger.id);
79
- });
80
- // --- Click to open/close ---
81
- it('clicking trigger opens menu', () => {
82
- setup();
83
- const trigger = target.querySelector('[data-menubar-trigger]');
84
- trigger.click();
85
- flushSync();
86
- expect(target.querySelector('[role="menu"]')).toBeTruthy();
87
- });
88
- it('clicking trigger again closes menu', () => {
89
- setup();
90
- const trigger = target.querySelector('[data-menubar-trigger]');
91
- trigger.click();
92
- flushSync();
93
- expect(target.querySelector('[role="menu"]')).toBeTruthy();
94
- trigger.click();
95
- flushSync();
96
- expect(target.querySelector('[role="menu"]')).toBeNull();
97
- });
98
- it('content not rendered when closed', () => {
99
- setup();
100
- expect(target.querySelector('[role="menu"]')).toBeNull();
101
- });
102
- // --- Menu items ---
103
- it('menu items have role="menuitem"', () => {
104
- setup();
105
- const trigger = target.querySelector('[data-menubar-trigger]');
106
- trigger.click();
107
- flushSync();
108
- const items = target.querySelectorAll('[role="menuitem"]');
109
- // 3 triggers + items in opened menu
110
- expect(items.length).toBeGreaterThan(3);
111
- });
112
- // --- Keyboard: ArrowDown opens menu ---
113
- it('arrowDown on trigger opens its menu', () => {
114
- setup();
115
- const trigger = target.querySelector('[data-menubar-trigger]');
116
- trigger.focus();
117
- // Dispatch on the trigger — event bubbles up to menubar's onkeydown
118
- trigger.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
119
- flushSync();
120
- expect(target.querySelector('[role="menu"]')).toBeTruthy();
121
- });
122
- // --- Keyboard: ArrowRight/Left between triggers ---
123
- it('arrowRight moves focus to next trigger', () => {
124
- setup();
125
- const triggers = target.querySelectorAll('[data-menubar-trigger]');
126
- triggers[0].focus();
127
- triggers[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }));
128
- flushSync();
129
- expect(document.activeElement).toBe(triggers[1]);
130
- });
131
- it('arrowLeft moves focus to previous trigger', () => {
132
- setup();
133
- const triggers = target.querySelectorAll('[data-menubar-trigger]');
134
- triggers[1].focus();
135
- triggers[1].dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true }));
136
- flushSync();
137
- expect(document.activeElement).toBe(triggers[0]);
138
- });
139
- it('arrowRight loops to first trigger when loop=true', () => {
140
- setup({ loop: true });
141
- const triggers = target.querySelectorAll('[data-menubar-trigger]');
142
- triggers[2].focus();
143
- triggers[2].dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }));
144
- flushSync();
145
- expect(document.activeElement).toBe(triggers[0]);
146
- });
147
- // --- Keyboard: Home/End ---
148
- it('home focuses first trigger', () => {
149
- setup();
150
- const triggers = target.querySelectorAll('[data-menubar-trigger]');
151
- triggers[2].focus();
152
- triggers[2].dispatchEvent(new KeyboardEvent('keydown', { key: 'Home', bubbles: true }));
153
- flushSync();
154
- expect(document.activeElement).toBe(triggers[0]);
155
- });
156
- it('end focuses last trigger', () => {
157
- setup();
158
- const triggers = target.querySelectorAll('[data-menubar-trigger]');
159
- triggers[0].focus();
160
- triggers[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'End', bubbles: true }));
161
- flushSync();
162
- expect(document.activeElement).toBe(triggers[2]);
163
- });
164
- // --- Escape closes menu ---
165
- it('escape closes menu and returns focus to trigger', () => {
166
- setup();
167
- const trigger = target.querySelector('[data-menubar-trigger]');
168
- trigger.click();
169
- flushSync();
170
- expect(target.querySelector('[role="menu"]')).toBeTruthy();
171
- document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true }));
172
- flushSync();
173
- expect(target.querySelector('[role="menu"]')).toBeNull();
174
- });
175
- // --- Hover-to-switch ---
176
- it('hovering another trigger switches menu when one is open', () => {
177
- setup();
178
- const triggers = target.querySelectorAll('[data-menubar-trigger]');
179
- // Open first menu
180
- triggers[0].click();
181
- flushSync();
182
- expect(triggers[0].getAttribute('data-state')).toBe('open');
183
- // Hover second trigger
184
- triggers[1].dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
185
- flushSync();
186
- // Second menu should be open, first closed
187
- expect(triggers[0].getAttribute('data-state')).toBe('closed');
188
- expect(triggers[1].getAttribute('data-state')).toBe('open');
189
- });
190
- it('hovering triggers does NOT open menu when none is open', () => {
191
- setup();
192
- const triggers = target.querySelectorAll('[data-menubar-trigger]');
193
- triggers[1].dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
194
- flushSync();
195
- // No menu should open from hover alone
196
- expect(target.querySelector('[role="menu"]')).toBeNull();
197
- });
198
- // --- SubTrigger inside a menubar menu ---
199
- describe('subTrigger', () => {
200
- function pressKey(key) {
201
- const el = document.activeElement;
202
- el.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true, cancelable: true }));
203
- flushSync();
204
- }
205
- it('arrowRight on a plain item moves to the next menubar menu', () => {
206
- setup({ withSub: true });
207
- const triggers = target.querySelectorAll('[data-menubar-trigger]');
208
- triggers[0].click();
209
- flushSync();
210
- // Autofocus lands on the first item of the opened menu
211
- expect(document.activeElement?.textContent?.trim()).toBe('New');
212
- pressKey('ArrowRight');
213
- expect(triggers[0].getAttribute('data-state')).toBe('closed');
214
- expect(triggers[1].getAttribute('data-state')).toBe('open');
215
- });
216
- it('arrowRight on the sub-trigger opens the submenu without switching menubar menus', () => {
217
- setup({ withSub: true });
218
- const triggers = target.querySelectorAll('[data-menubar-trigger]');
219
- triggers[0].click();
220
- flushSync();
221
- const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
222
- expect(subTrigger).toBeTruthy();
223
- subTrigger.focus();
224
- pressKey('ArrowRight');
225
- // Submenu opened…
226
- expect(subTrigger.getAttribute('aria-expanded')).toBe('true');
227
- expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeTruthy();
228
- // …and the menubar neither closed the current menu nor advanced to the next one
229
- expect(triggers[0].getAttribute('data-state')).toBe('open');
230
- expect(triggers[1].getAttribute('data-state')).toBe('closed');
231
- });
232
- it('arrowRight on the sub-trigger moves focus to the first sub item', () => {
233
- setup({ withSub: true });
234
- const triggers = target.querySelectorAll('[data-menubar-trigger]');
235
- triggers[0].click();
236
- flushSync();
237
- const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
238
- subTrigger.focus();
239
- pressKey('ArrowRight');
240
- expect(document.activeElement?.textContent?.trim()).toBe('Email');
241
- });
242
- it('arrowLeft inside the sub closes it, restores focus to the sub-trigger and keeps the menu', () => {
243
- setup({ withSub: true });
244
- const triggers = target.querySelectorAll('[data-menubar-trigger]');
245
- triggers[0].click();
246
- flushSync();
247
- const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
248
- subTrigger.focus();
249
- pressKey('ArrowRight');
250
- expect(document.activeElement?.textContent?.trim()).toBe('Email');
251
- pressKey('ArrowLeft');
252
- // Sub closed, focus back on the trigger row — no dead end on body.
253
- expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeNull();
254
- expect(document.activeElement).toBe(subTrigger);
255
- // The menubar did NOT switch to the previous menu.
256
- expect(triggers[0].getAttribute('data-state')).toBe('open');
257
- expect(triggers[1].getAttribute('data-state')).toBe('closed');
258
- expect(triggers[2].getAttribute('data-state')).toBe('closed');
259
- });
260
- it('hovering a sibling item closes the open sub', () => {
261
- setup({ withSub: true });
262
- const triggers = target.querySelectorAll('[data-menubar-trigger]');
263
- triggers[0].click();
264
- flushSync();
265
- const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
266
- subTrigger.click();
267
- flushSync();
268
- expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeTruthy();
269
- const newItem = Array.from(target.querySelectorAll('.sp-menubar-content .sp-dropdown-menu-item')).find((el) => el.textContent?.trim() === 'New');
270
- newItem.dispatchEvent(new MouseEvent('pointerenter', { clientX: 0, clientY: 0 }));
271
- flushSync();
272
- expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeNull();
273
- // The menubar menu itself stayed open.
274
- expect(triggers[0].getAttribute('data-state')).toBe('open');
275
- });
276
- it('escape with focus in the menu and a hover-opened sub closes the whole menu in ONE keypress and refocuses the menubar trigger', () => {
277
- setup({ withSub: true });
278
- const triggers = target.querySelectorAll('[data-menubar-trigger]');
279
- triggers[0].click();
280
- flushSync();
281
- // Autofocus lands on the first parent row.
282
- expect(document.activeElement?.textContent?.trim()).toBe('New');
283
- // Pointer path: the sub opens without moving focus.
284
- const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
285
- subTrigger.click();
286
- flushSync();
287
- expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeTruthy();
288
- expect(document.activeElement?.textContent?.trim()).toBe('New');
289
- // ONE Escape: the sub layer defers (focus is NOT inside the sub) and the
290
- // menubar layer closes the whole menu — focus returns to the MENUBAR
291
- // trigger, never warped onto the sub trigger.
292
- pressKey('Escape');
293
- expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeNull();
294
- expect(target.querySelector('[role="menu"]')).toBeNull();
295
- expect(document.activeElement).toBe(triggers[0]);
296
- });
297
- it('escape with focus INSIDE the sub closes only the sub (menu stays open)', () => {
298
- setup({ withSub: true });
299
- const triggers = target.querySelectorAll('[data-menubar-trigger]');
300
- triggers[0].click();
301
- flushSync();
302
- const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
303
- subTrigger.focus();
304
- pressKey('ArrowRight');
305
- expect(document.activeElement?.textContent?.trim()).toBe('Email');
306
- pressKey('Escape');
307
- expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeNull();
308
- expect(document.activeElement).toBe(subTrigger);
309
- // The menubar menu itself stayed open.
310
- expect(triggers[0].getAttribute('data-state')).toBe('open');
311
- });
312
- it('parent roving skips sub items while the sub is open', () => {
313
- setup({ withSub: true });
314
- const triggers = target.querySelectorAll('[data-menubar-trigger]');
315
- triggers[0].click();
316
- flushSync();
317
- const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
318
- // Pointer open keeps focus on the trigger row.
319
- subTrigger.click();
320
- flushSync();
321
- subTrigger.focus();
322
- // Share is the last row: ArrowDown loops to the first PARENT item,
323
- // not into the open sub.
324
- pressKey('ArrowDown');
325
- expect(document.activeElement?.textContent?.trim()).toBe('New');
326
- });
327
- });
328
- });
@@ -1,26 +0,0 @@
1
- import { mount, unmount } from 'svelte';
2
- import { afterEach, describe, expect, it } from 'vitest';
3
- import Meter from '../meter.svelte';
4
- describe('meter', () => {
5
- let target;
6
- let instance;
7
- function setup(props = {}) {
8
- target = document.createElement('div');
9
- document.body.appendChild(target);
10
- instance = mount(Meter, { target, props });
11
- return target;
12
- }
13
- afterEach(() => {
14
- if (instance)
15
- unmount(instance);
16
- target?.remove();
17
- });
18
- it('renders with role=meter', () => {
19
- setup();
20
- expect(target.querySelector('[role="meter"]')).toBeTruthy();
21
- });
22
- it('has aria-valuenow', () => {
23
- setup({ value: 50 });
24
- expect(target.querySelector('[role="meter"]')?.getAttribute('aria-valuenow')).toBe('50');
25
- });
26
- });
@@ -1,23 +0,0 @@
1
- import { mount, unmount } from 'svelte';
2
- import { afterEach, describe, expect, it } from 'vitest';
3
- import NavBarRoot from '../components/nav-bar-root.svelte';
4
- describe('nav-bar', () => {
5
- let target;
6
- let instance;
7
- function setup(props = {}) {
8
- target = document.createElement('div');
9
- document.body.appendChild(target);
10
- instance = mount(NavBarRoot, { target, props });
11
- return target;
12
- }
13
- afterEach(() => {
14
- if (instance)
15
- unmount(instance);
16
- target?.remove();
17
- });
18
- it('renders navigation root and tablist', () => {
19
- setup();
20
- expect(target.querySelector('nav.sp-nav-bar')).toBeTruthy();
21
- expect(target.querySelector('[role="tablist"]')).toBeTruthy();
22
- });
23
- });
@@ -1,28 +0,0 @@
1
- import { mount, unmount } from 'svelte';
2
- import { afterEach, describe, expect, it } from 'vitest';
3
- import NavDrawerRoot from '../components/nav-drawer-root.svelte';
4
- describe('nav-drawer', () => {
5
- let target;
6
- let instance;
7
- function setup(props = {}) {
8
- target = document.createElement('div');
9
- document.body.appendChild(target);
10
- instance = mount(NavDrawerRoot, { target, props });
11
- return target;
12
- }
13
- afterEach(() => {
14
- if (instance)
15
- unmount(instance);
16
- target?.remove();
17
- });
18
- it('renders vertical tablist in drawer items', () => {
19
- setup();
20
- const tablist = target.querySelector('.sp-nav-drawer-items[role="tablist"]');
21
- expect(tablist).toBeTruthy();
22
- expect(tablist?.getAttribute('aria-orientation')).toBe('vertical');
23
- });
24
- it('renders overlay in modal mode when open', () => {
25
- setup({ mode: 'modal', open: true });
26
- expect(target.querySelector('.sp-nav-drawer-overlay')).toBeTruthy();
27
- });
28
- });
@@ -1,28 +0,0 @@
1
- import { mount, unmount } from 'svelte';
2
- import { afterEach, describe, expect, it } from 'vitest';
3
- import NavRailRoot from '../components/nav-rail-root.svelte';
4
- describe('nav-rail', () => {
5
- let target;
6
- let instance;
7
- function setup(props = {}) {
8
- target = document.createElement('div');
9
- document.body.appendChild(target);
10
- instance = mount(NavRailRoot, { target, props });
11
- return target;
12
- }
13
- afterEach(() => {
14
- if (instance)
15
- unmount(instance);
16
- target?.remove();
17
- });
18
- it('renders vertical tablist', () => {
19
- setup();
20
- const tablist = target.querySelector('[role="tablist"]');
21
- expect(tablist).toBeTruthy();
22
- expect(tablist?.getAttribute('aria-orientation')).toBe('vertical');
23
- });
24
- it('applies expanded class', () => {
25
- setup({ expanded: true });
26
- expect(target.querySelector('.sp-nav-rail.expanded')).toBeTruthy();
27
- });
28
- });
@@ -1,26 +0,0 @@
1
- import { mount, unmount } from 'svelte';
2
- import { afterEach, describe, expect, it } from 'vitest';
3
- import Pagination from '../pagination.svelte';
4
- describe('pagination', () => {
5
- let target;
6
- let instance;
7
- function setup(props = {}) {
8
- target = document.createElement('div');
9
- document.body.appendChild(target);
10
- instance = mount(Pagination, { target, props });
11
- return target;
12
- }
13
- afterEach(() => {
14
- if (instance)
15
- unmount(instance);
16
- target?.remove();
17
- });
18
- it('renders nav', () => {
19
- setup();
20
- expect(target.querySelector('nav')).toBeTruthy();
21
- });
22
- it('has aria-label', () => {
23
- setup();
24
- expect(target.querySelector('[aria-label="Pagination"]')).toBeTruthy();
25
- });
26
- });
@@ -1,71 +0,0 @@
1
- import { flushSync, mount, unmount } from 'svelte';
2
- import { afterEach, describe, expect, it } from 'vitest';
3
- import PinInput from '../pin-input.svelte';
4
- describe('pin-input', () => {
5
- let target;
6
- let instance;
7
- function setup(props = {}) {
8
- target = document.createElement('div');
9
- document.body.appendChild(target);
10
- instance = mount(PinInput, { target, props });
11
- return target;
12
- }
13
- afterEach(() => {
14
- if (instance)
15
- unmount(instance);
16
- target?.remove();
17
- });
18
- it('renders input slots', () => {
19
- setup();
20
- expect(target.querySelectorAll('input').length).toBe(4);
21
- });
22
- it('renders 6 slots when length=6', () => {
23
- setup({ length: 6 });
24
- expect(target.querySelectorAll('input').length).toBe(6);
25
- });
26
- it('uses the inputmode prop on slots', () => {
27
- setup({ length: 4, inputmode: 'text' });
28
- const input = target.querySelector('input');
29
- expect(input.getAttribute('inputmode')).toBe('text');
30
- });
31
- it('defaults inputmode to numeric (backward compat)', () => {
32
- setup({ length: 4 });
33
- const input = target.querySelector('input');
34
- expect(input.getAttribute('inputmode')).toBe('numeric');
35
- });
36
- it('uses ariaLabelPrefix in slot aria-labels', () => {
37
- setup({ length: 4, ariaLabelPrefix: 'Character' });
38
- const input = target.querySelector('input');
39
- expect(input.getAttribute('aria-label')).toBe('Character 1 of 4');
40
- });
41
- it('pasteTransformer strips separators before distributing', () => {
42
- setup({ length: 6, pasteTransformer: (raw) => raw.replace(/[^0-9A-Z]/gi, '').toUpperCase() });
43
- const input = target.querySelector('input');
44
- const ev = new Event('paste', { bubbles: true });
45
- Object.defineProperty(ev, 'clipboardData', { value: { getData: () => 'ab-cd-ef' } });
46
- input.dispatchEvent(ev);
47
- flushSync();
48
- const joined = Array.from(target.querySelectorAll('input')).map((i) => i.value).join('');
49
- expect(joined).toBe('ABCDEF');
50
- });
51
- it('charTransformer uppercases typed input', () => {
52
- setup({ length: 4, charTransformer: (c) => c.toUpperCase() });
53
- const input = target.querySelector('input');
54
- input.value = 'a';
55
- input.dispatchEvent(new Event('input', { bubbles: true }));
56
- flushSync();
57
- expect(target.querySelectorAll('input')[0].value).toBe('A');
58
- });
59
- it('allowedPattern rejects disallowed typed characters', () => {
60
- setup({ length: 4, allowedPattern: /[2-9A-HJKMNP-Z]/i });
61
- const input = target.querySelector('input');
62
- input.value = '0';
63
- input.dispatchEvent(new Event('input', { bubbles: true }));
64
- flushSync();
65
- expect(target.querySelectorAll('input')[0].value).toBe('');
66
- });
67
- it('renders group separators when groupSize is set', () => {
68
- setup({ length: 8, groupSize: 4 });
69
- expect(target.querySelectorAll('.sp-pin-input-separator').length).toBe(1);
70
- });
71
- });
@@ -1,27 +0,0 @@
1
- <script lang="ts">
2
- import PopoverContent from '../components/popover-content.svelte'
3
- import PopoverRoot from '../components/popover-root.svelte'
4
- import PopoverTrigger from '../components/popover-trigger.svelte'
5
-
6
- let {
7
- open = $bindable(false),
8
- onOpenChange,
9
- portalTo,
10
- disablePortal,
11
- contentClass,
12
- }: {
13
- open?: boolean
14
- onOpenChange?: (open: boolean) => void
15
- portalTo?: HTMLElement | string
16
- disablePortal?: boolean
17
- contentClass?: string
18
- } = $props()
19
- </script>
20
-
21
- <PopoverRoot bind:open {onOpenChange}>
22
- <PopoverTrigger>Open Popover</PopoverTrigger>
23
- <PopoverContent {portalTo} {disablePortal} class={contentClass}>
24
- <p>Popover content</p>
25
- <button data-testid="inside-popover">Inside</button>
26
- </PopoverContent>
27
- </PopoverRoot>