@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,225 +0,0 @@
1
- import { tabbable } from 'tabbable';
2
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
- import { FocusScopeState } from '../focus-scope.svelte.js';
4
- // Mock the tabbable package
5
- vi.mock('tabbable', () => ({
6
- tabbable: vi.fn(() => []),
7
- }));
8
- const mockedTabbable = vi.mocked(tabbable);
9
- function createContainer() {
10
- const container = document.createElement('div');
11
- document.body.appendChild(container);
12
- return container;
13
- }
14
- function createTabbableElements(container, count) {
15
- const elements = [];
16
- for (let i = 0; i < count; i++) {
17
- const btn = document.createElement('button');
18
- btn.textContent = `Button ${i}`;
19
- container.appendChild(btn);
20
- elements.push(btn);
21
- }
22
- return elements;
23
- }
24
- describe('focusScopeState', () => {
25
- let container;
26
- beforeEach(() => {
27
- container = createContainer();
28
- });
29
- afterEach(() => {
30
- container.remove();
31
- vi.restoreAllMocks();
32
- });
33
- describe('mount()', () => {
34
- it('stores previously focused element and focuses first tabbable', () => {
35
- const outsideButton = document.createElement('button');
36
- document.body.appendChild(outsideButton);
37
- outsideButton.focus();
38
- expect(document.activeElement).toBe(outsideButton);
39
- const tabbableElements = createTabbableElements(container, 3);
40
- mockedTabbable.mockReturnValue(tabbableElements);
41
- const focusSpy = vi.spyOn(tabbableElements[0], 'focus');
42
- const props = { element: container, enabled: true };
43
- const scope = new FocusScopeState(props);
44
- scope.mount();
45
- expect(focusSpy).toHaveBeenCalledWith({ preventScroll: false });
46
- // Unmount should restore to the outsideButton
47
- scope.unmount();
48
- expect(outsideButton).toBe(document.activeElement);
49
- outsideButton.remove();
50
- });
51
- it('focuses the container itself when no tabbable children exist', () => {
52
- mockedTabbable.mockReturnValue([]);
53
- const focusSpy = vi.spyOn(container, 'focus');
54
- const props = { element: container, enabled: true };
55
- const scope = new FocusScopeState(props);
56
- scope.mount();
57
- expect(focusSpy).toHaveBeenCalledWith({ preventScroll: false });
58
- });
59
- it('does nothing when enabled is false', () => {
60
- const tabbableElements = createTabbableElements(container, 2);
61
- mockedTabbable.mockReturnValue(tabbableElements);
62
- const focusSpy = vi.spyOn(tabbableElements[0], 'focus');
63
- const props = { element: container, enabled: false };
64
- const scope = new FocusScopeState(props);
65
- scope.mount();
66
- expect(focusSpy).not.toHaveBeenCalled();
67
- });
68
- });
69
- describe('unmount()', () => {
70
- it('restores previously focused element', () => {
71
- const outsideButton = document.createElement('button');
72
- document.body.appendChild(outsideButton);
73
- outsideButton.focus();
74
- const tabbableElements = createTabbableElements(container, 2);
75
- mockedTabbable.mockReturnValue(tabbableElements);
76
- const props = { element: container, enabled: true };
77
- const scope = new FocusScopeState(props);
78
- scope.mount();
79
- scope.unmount();
80
- expect(document.activeElement).toBe(outsideButton);
81
- outsideButton.remove();
82
- });
83
- });
84
- describe('handleKeydown()', () => {
85
- it('wraps Tab from last to first tabbable', () => {
86
- const tabbableElements = createTabbableElements(container, 3);
87
- mockedTabbable.mockReturnValue(tabbableElements);
88
- // Focus the last element
89
- tabbableElements[2].focus();
90
- const props = { element: container, enabled: true, loop: true };
91
- const scope = new FocusScopeState(props);
92
- const focusSpy = vi.spyOn(tabbableElements[0], 'focus');
93
- const event = new KeyboardEvent('keydown', { key: 'Tab', bubbles: true });
94
- const preventSpy = vi.spyOn(event, 'preventDefault');
95
- scope.handleKeydown(event);
96
- expect(preventSpy).toHaveBeenCalled();
97
- expect(focusSpy).toHaveBeenCalled();
98
- });
99
- it('wraps Shift+Tab from first to last tabbable', () => {
100
- const tabbableElements = createTabbableElements(container, 3);
101
- mockedTabbable.mockReturnValue(tabbableElements);
102
- // Focus the first element
103
- tabbableElements[0].focus();
104
- const props = { element: container, enabled: true, loop: true };
105
- const scope = new FocusScopeState(props);
106
- const focusSpy = vi.spyOn(tabbableElements[2], 'focus');
107
- const event = new KeyboardEvent('keydown', { key: 'Tab', shiftKey: true, bubbles: true });
108
- const preventSpy = vi.spyOn(event, 'preventDefault');
109
- scope.handleKeydown(event);
110
- expect(preventSpy).toHaveBeenCalled();
111
- expect(focusSpy).toHaveBeenCalled();
112
- });
113
- it('does nothing for non-Tab keys', () => {
114
- const tabbableElements = createTabbableElements(container, 2);
115
- mockedTabbable.mockReturnValue(tabbableElements);
116
- const props = { element: container, enabled: true };
117
- const scope = new FocusScopeState(props);
118
- const event = new KeyboardEvent('keydown', { key: 'Enter', bubbles: true });
119
- const preventSpy = vi.spyOn(event, 'preventDefault');
120
- scope.handleKeydown(event);
121
- expect(preventSpy).not.toHaveBeenCalled();
122
- });
123
- });
124
- describe('sSR safety', () => {
125
- it('mount() does nothing when disabled (SSR scenario)', () => {
126
- const tabbableElements = createTabbableElements(container, 2);
127
- mockedTabbable.mockReturnValue(tabbableElements);
128
- const focusSpy = vi.spyOn(tabbableElements[0], 'focus');
129
- const onOpen = vi.fn();
130
- const props = {
131
- element: container,
132
- enabled: false,
133
- onOpenAutoFocus: onOpen,
134
- };
135
- const scope = new FocusScopeState(props);
136
- scope.mount();
137
- expect(focusSpy).not.toHaveBeenCalled();
138
- expect(onOpen).not.toHaveBeenCalled();
139
- });
140
- it('mount() with null element does not throw and does not focus', () => {
141
- mockedTabbable.mockReturnValue([]);
142
- const onOpen = vi.fn();
143
- const props = { element: null, enabled: true, onOpenAutoFocus: onOpen };
144
- const scope = new FocusScopeState(props);
145
- // Should not throw even with null element
146
- scope.mount();
147
- // onOpenAutoFocus is NOT called because element guard skips early
148
- expect(onOpen).not.toHaveBeenCalled();
149
- });
150
- });
151
- describe('onOpenAutoFocus', () => {
152
- it('callback is called on mount', () => {
153
- const tabbableElements = createTabbableElements(container, 2);
154
- mockedTabbable.mockReturnValue(tabbableElements);
155
- const onOpenAutoFocus = vi.fn();
156
- const props = {
157
- element: container,
158
- enabled: true,
159
- onOpenAutoFocus,
160
- };
161
- const scope = new FocusScopeState(props);
162
- scope.mount();
163
- expect(onOpenAutoFocus).toHaveBeenCalledOnce();
164
- expect(onOpenAutoFocus).toHaveBeenCalledWith(expect.any(Event));
165
- });
166
- it('can be prevented to skip auto-focus', () => {
167
- const tabbableElements = createTabbableElements(container, 2);
168
- mockedTabbable.mockReturnValue(tabbableElements);
169
- const focusSpy = vi.spyOn(tabbableElements[0], 'focus');
170
- const onOpenAutoFocus = vi.fn((event) => {
171
- event.preventDefault();
172
- });
173
- const props = {
174
- element: container,
175
- enabled: true,
176
- onOpenAutoFocus,
177
- };
178
- const scope = new FocusScopeState(props);
179
- scope.mount();
180
- expect(onOpenAutoFocus).toHaveBeenCalledOnce();
181
- expect(focusSpy).not.toHaveBeenCalled();
182
- });
183
- });
184
- describe('onCloseAutoFocus', () => {
185
- it('callback is called on unmount', () => {
186
- const tabbableElements = createTabbableElements(container, 2);
187
- mockedTabbable.mockReturnValue(tabbableElements);
188
- const onCloseAutoFocus = vi.fn();
189
- const props = {
190
- element: container,
191
- enabled: true,
192
- onCloseAutoFocus,
193
- };
194
- const scope = new FocusScopeState(props);
195
- scope.mount();
196
- scope.unmount();
197
- expect(onCloseAutoFocus).toHaveBeenCalledOnce();
198
- expect(onCloseAutoFocus).toHaveBeenCalledWith(expect.any(Event));
199
- });
200
- it('can be prevented to skip focus restore', () => {
201
- const outsideButton = document.createElement('button');
202
- document.body.appendChild(outsideButton);
203
- outsideButton.focus();
204
- const tabbableElements = createTabbableElements(container, 2);
205
- mockedTabbable.mockReturnValue(tabbableElements);
206
- const restoreSpy = vi.spyOn(outsideButton, 'focus');
207
- const onCloseAutoFocus = vi.fn((event) => {
208
- event.preventDefault();
209
- });
210
- const props = {
211
- element: container,
212
- enabled: true,
213
- onCloseAutoFocus,
214
- };
215
- const scope = new FocusScopeState(props);
216
- scope.mount();
217
- restoreSpy.mockClear(); // clear any calls from mount phase
218
- scope.unmount();
219
- expect(onCloseAutoFocus).toHaveBeenCalledOnce();
220
- // Focus should NOT have been restored because we prevented it
221
- expect(restoreSpy).not.toHaveBeenCalled();
222
- outsideButton.remove();
223
- });
224
- });
225
- });
@@ -1,67 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { GraceAreaState } from '../grace-area.svelte.js';
3
- describe('graceAreaState', () => {
4
- function createMockElement(rect) {
5
- const el = document.createElement('div');
6
- el.getBoundingClientRect = () => ({
7
- ...rect,
8
- width: rect.right - rect.left,
9
- height: rect.bottom - rect.top,
10
- x: rect.left,
11
- y: rect.top,
12
- toJSON: () => { },
13
- });
14
- return el;
15
- }
16
- it('returns false when not enabled', () => {
17
- const state = new GraceAreaState({
18
- triggerElement: document.createElement('div'),
19
- contentElement: document.createElement('div'),
20
- enabled: false,
21
- });
22
- state.setPointerExit({ x: 0, y: 0 });
23
- expect(state.isPointInGraceArea({ x: 50, y: 50 })).toBe(false);
24
- });
25
- it('returns false when no pointer exit is set', () => {
26
- const state = new GraceAreaState({
27
- triggerElement: document.createElement('div'),
28
- contentElement: document.createElement('div'),
29
- enabled: true,
30
- });
31
- expect(state.isPointInGraceArea({ x: 50, y: 50 })).toBe(false);
32
- });
33
- it('detects point inside grace area polygon', () => {
34
- const content = createMockElement({ left: 100, top: 100, right: 200, bottom: 200 });
35
- const state = new GraceAreaState({
36
- triggerElement: document.createElement('div'),
37
- contentElement: content,
38
- enabled: true,
39
- });
40
- // Pointer exits trigger at (50, 150) — to the left of content
41
- state.setPointerExit({ x: 50, y: 150 });
42
- // Point between trigger exit and content should be in grace area
43
- expect(state.isPointInGraceArea({ x: 75, y: 150 })).toBe(true);
44
- });
45
- it('detects point outside grace area', () => {
46
- const content = createMockElement({ left: 100, top: 100, right: 200, bottom: 200 });
47
- const state = new GraceAreaState({
48
- triggerElement: document.createElement('div'),
49
- contentElement: content,
50
- enabled: true,
51
- });
52
- state.setPointerExit({ x: 50, y: 150 });
53
- // Point far away from the grace area
54
- expect(state.isPointInGraceArea({ x: 300, y: 300 })).toBe(false);
55
- });
56
- it('reset clears pointer exit', () => {
57
- const content = createMockElement({ left: 100, top: 100, right: 200, bottom: 200 });
58
- const state = new GraceAreaState({
59
- triggerElement: document.createElement('div'),
60
- contentElement: content,
61
- enabled: true,
62
- });
63
- state.setPointerExit({ x: 50, y: 150 });
64
- state.reset();
65
- expect(state.isPointInGraceArea({ x: 75, y: 150 })).toBe(false);
66
- });
67
- });
@@ -1,123 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { arraysAreEqual, boolToStr, getDataOpenClosed, getDirectionalKeys, isBrowser, isFunction, isHTMLElement, isNull, isNullish, isNumber, isString, isUndefined, kbd, mergeProps, next, prev, resetIdCounter, useId, wrapArray, } from '../index.js';
3
- describe('kbd', () => {
4
- it('has standard key constants', () => {
5
- expect(kbd.ESCAPE).toBe('Escape');
6
- expect(kbd.ENTER).toBe('Enter');
7
- expect(kbd.SPACE).toBe(' ');
8
- expect(kbd.TAB).toBe('Tab');
9
- expect(kbd.ARROW_DOWN).toBe('ArrowDown');
10
- });
11
- });
12
- describe('getDirectionalKeys', () => {
13
- it('returns down/up for vertical orientation', () => {
14
- const keys = getDirectionalKeys('ltr', 'vertical');
15
- expect(keys.nextKey).toBe('ArrowDown');
16
- expect(keys.prevKey).toBe('ArrowUp');
17
- });
18
- it('returns right/left for horizontal LTR', () => {
19
- const keys = getDirectionalKeys('ltr', 'horizontal');
20
- expect(keys.nextKey).toBe('ArrowRight');
21
- expect(keys.prevKey).toBe('ArrowLeft');
22
- });
23
- it('returns left/right for horizontal RTL', () => {
24
- const keys = getDirectionalKeys('rtl', 'horizontal');
25
- expect(keys.nextKey).toBe('ArrowLeft');
26
- expect(keys.prevKey).toBe('ArrowRight');
27
- });
28
- });
29
- describe('dom helpers', () => {
30
- it('isBrowser returns true in jsdom', () => {
31
- expect(isBrowser).toBe(true);
32
- });
33
- it('isHTMLElement checks correctly', () => {
34
- expect(isHTMLElement(document.createElement('div'))).toBe(true);
35
- expect(isHTMLElement('string')).toBe(false);
36
- expect(isHTMLElement(null)).toBe(false);
37
- });
38
- it('getDataOpenClosed returns correct value', () => {
39
- expect(getDataOpenClosed(true)).toBe('open');
40
- expect(getDataOpenClosed(false)).toBe('closed');
41
- });
42
- it('boolToStr returns string boolean', () => {
43
- expect(boolToStr(true)).toBe('true');
44
- expect(boolToStr(false)).toBe('false');
45
- });
46
- });
47
- describe('array helpers', () => {
48
- it('wrapArray starts at the given index', () => {
49
- expect(wrapArray([1, 2, 3, 4], 2)).toEqual([3, 4, 1, 2]);
50
- });
51
- it('next returns next item', () => {
52
- expect(next([1, 2, 3], 0)).toBe(2);
53
- expect(next([1, 2, 3], 2, true)).toBe(1);
54
- expect(next([1, 2, 3], 2, false)).toBeUndefined();
55
- });
56
- it('prev returns previous item', () => {
57
- expect(prev([1, 2, 3], 1)).toBe(1);
58
- expect(prev([1, 2, 3], 0, true)).toBe(3);
59
- expect(prev([1, 2, 3], 0, false)).toBeUndefined();
60
- });
61
- it('arraysAreEqual handles NaN', () => {
62
- expect(arraysAreEqual([1, 2, 3], [1, 2, 3])).toBe(true);
63
- expect(arraysAreEqual([1, 2], [1, 2, 3])).toBe(false);
64
- expect(arraysAreEqual([Number.NaN], [Number.NaN])).toBe(true);
65
- });
66
- });
67
- describe('is helpers', () => {
68
- it('isString', () => {
69
- expect(isString('hello')).toBe(true);
70
- expect(isString(123)).toBe(false);
71
- });
72
- it('isNumber', () => {
73
- expect(isNumber(42)).toBe(true);
74
- expect(isNumber(Number.NaN)).toBe(false);
75
- });
76
- it('isFunction', () => {
77
- expect(isFunction(() => { })).toBe(true);
78
- expect(isFunction('fn')).toBe(false);
79
- });
80
- it('isNull / isUndefined / isNullish', () => {
81
- expect(isNull(null)).toBe(true);
82
- expect(isNull(undefined)).toBe(false);
83
- expect(isUndefined(undefined)).toBe(true);
84
- expect(isNullish(null)).toBe(true);
85
- expect(isNullish(undefined)).toBe(true);
86
- expect(isNullish(0)).toBe(false);
87
- });
88
- });
89
- describe('useId', () => {
90
- it('generates unique IDs', () => {
91
- resetIdCounter();
92
- const id1 = useId();
93
- const id2 = useId();
94
- expect(id1).toMatch(/^sp-\d+$/);
95
- expect(id2).toMatch(/^sp-\d+$/);
96
- expect(id1).not.toBe(id2);
97
- });
98
- it('supports custom prefix', () => {
99
- resetIdCounter();
100
- const id = useId('dialog');
101
- expect(id).toMatch(/^dialog-\d+$/);
102
- });
103
- });
104
- describe('mergeProps', () => {
105
- it('merges simple props (last wins)', () => {
106
- const result = mergeProps({ a: 1 }, { a: 2, b: 3 });
107
- expect(result).toEqual({ a: 2, b: 3 });
108
- });
109
- it('combines event handlers', () => {
110
- const calls = [];
111
- const result = mergeProps({ onclick: () => calls.push('first') }, { onclick: () => calls.push('second') });
112
- result.onclick();
113
- expect(calls).toEqual(['first', 'second']);
114
- });
115
- it('combines class strings', () => {
116
- const result = mergeProps({ class: 'a' }, { class: 'b' });
117
- expect(result.class).toBe('a b');
118
- });
119
- it('combines style strings', () => {
120
- const result = mergeProps({ style: 'color: red' }, { style: 'font-size: 12px' });
121
- expect(result.style).toBe('color: red; font-size: 12px');
122
- });
123
- });
@@ -1,21 +0,0 @@
1
- <script lang="ts">
2
- import { Portal } from '../index.js'
3
-
4
- const {
5
- target = undefined,
6
- disabled = false,
7
- show = true,
8
- }: {
9
- target?: HTMLElement | string
10
- disabled?: boolean
11
- show?: boolean
12
- } = $props()
13
- </script>
14
-
15
- <div data-testid="origin">
16
- {#if show}
17
- <Portal to={target} {disabled}>
18
- <div data-testid="portalled">hello</div>
19
- </Portal>
20
- {/if}
21
- </div>
@@ -1,29 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { PortalState } from '../portal.svelte.js';
3
- describe('portalState', () => {
4
- it('resolves to document.body by default', () => {
5
- const portal = new PortalState({});
6
- expect(portal.resolveTarget()).toBe(document.body);
7
- });
8
- it('resolves a CSS selector', () => {
9
- const target = document.createElement('div');
10
- target.id = 'portal-target';
11
- document.body.appendChild(target);
12
- const portal = new PortalState({ target: '#portal-target' });
13
- expect(portal.resolveTarget()).toBe(target);
14
- target.remove();
15
- });
16
- it('falls back to body for missing selector', () => {
17
- const portal = new PortalState({ target: '#nonexistent' });
18
- expect(portal.resolveTarget()).toBe(document.body);
19
- });
20
- it('resolves a direct HTMLElement reference', () => {
21
- const target = document.createElement('div');
22
- const portal = new PortalState({ target });
23
- expect(portal.resolveTarget()).toBe(target);
24
- });
25
- it('returns null when disabled', () => {
26
- const portal = new PortalState({ disabled: true });
27
- expect(portal.resolveTarget()).toBeNull();
28
- });
29
- });
@@ -1,57 +0,0 @@
1
- import { flushSync, mount, unmount } from 'svelte';
2
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
3
- import PortalTest from './PortalTest.svelte';
4
- describe('portal component', () => {
5
- let host;
6
- let instance;
7
- beforeEach(() => {
8
- host = document.createElement('div');
9
- document.body.appendChild(host);
10
- });
11
- afterEach(() => {
12
- if (instance)
13
- unmount(instance);
14
- instance = undefined;
15
- host.remove();
16
- });
17
- it('renders children into document.body by default', () => {
18
- instance = mount(PortalTest, { target: host });
19
- flushSync();
20
- const origin = host.querySelector('[data-testid="origin"]');
21
- expect(origin?.querySelector('[data-testid="portalled"]')).toBeNull();
22
- expect(document.body.querySelector('[data-testid="portalled"]')).toBeTruthy();
23
- });
24
- it('renders children into a specific HTMLElement target', () => {
25
- const target = document.createElement('div');
26
- target.id = 'custom-target';
27
- document.body.appendChild(target);
28
- instance = mount(PortalTest, { target: host, props: { target } });
29
- flushSync();
30
- expect(target.querySelector('[data-testid="portalled"]')).toBeTruthy();
31
- target.remove();
32
- });
33
- it('resolves a CSS selector target', () => {
34
- const target = document.createElement('div');
35
- target.id = 'selector-target';
36
- document.body.appendChild(target);
37
- instance = mount(PortalTest, { target: host, props: { target: '#selector-target' } });
38
- flushSync();
39
- expect(target.querySelector('[data-testid="portalled"]')).toBeTruthy();
40
- target.remove();
41
- });
42
- it('renders inline when disabled', () => {
43
- instance = mount(PortalTest, { target: host, props: { disabled: true } });
44
- flushSync();
45
- const origin = host.querySelector('[data-testid="origin"]');
46
- expect(origin?.querySelector('[data-testid="portalled"]')).toBeTruthy();
47
- });
48
- it('unmounts portalled content when parent removes Portal', () => {
49
- instance = mount(PortalTest, { target: host, props: { show: true } });
50
- flushSync();
51
- expect(document.body.querySelector('[data-testid="portalled"]')).toBeTruthy();
52
- unmount(instance);
53
- instance = undefined;
54
- flushSync();
55
- expect(document.body.querySelector('[data-testid="portalled"]')).toBeNull();
56
- });
57
- });