@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,186 +0,0 @@
1
- import { flushSync, mount, unmount } from 'svelte';
2
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
- import SidebarFullTest from './SidebarFullTest.svelte';
4
- import SidebarTest from './SidebarTest.svelte';
5
- // jsdom does not implement window.matchMedia — provide a minimal stub.
6
- // eslint-disable-next-line svelte/prefer-svelte-reactivity -- test-only non-reactive listener registry
7
- const listenerRegistry = new Map();
8
- function setupMatchMedia() {
9
- listenerRegistry.clear();
10
- const mql = {
11
- matches: false,
12
- media: '',
13
- onchange: null,
14
- addListener: vi.fn(),
15
- removeListener: vi.fn(),
16
- addEventListener: vi.fn((type, handler) => {
17
- if (!listenerRegistry.has(type))
18
- listenerRegistry.set(type, new Set());
19
- listenerRegistry.get(type).add(handler);
20
- }),
21
- removeEventListener: vi.fn((type, handler) => {
22
- listenerRegistry.get(type)?.delete(handler);
23
- }),
24
- dispatchEvent: vi.fn((event) => {
25
- listenerRegistry.get(event.type)?.forEach((h) => h(event));
26
- return true;
27
- }),
28
- };
29
- Object.defineProperty(window, 'matchMedia', {
30
- writable: true,
31
- value: vi.fn().mockReturnValue(mql),
32
- });
33
- return { mql, listeners: listenerRegistry };
34
- }
35
- describe('sidebar', () => {
36
- let target;
37
- let instance;
38
- let matchMediaStub;
39
- beforeEach(() => {
40
- matchMediaStub = setupMatchMedia();
41
- });
42
- afterEach(() => {
43
- if (instance)
44
- unmount(instance);
45
- target?.remove();
46
- vi.restoreAllMocks();
47
- });
48
- it('renders children', () => {
49
- target = document.createElement('div');
50
- document.body.appendChild(target);
51
- instance = mount(SidebarTest, { target });
52
- expect(target.querySelector('[data-testid="child"]')).toBeTruthy();
53
- });
54
- it('removes MediaQueryList listeners on unmount', () => {
55
- target = document.createElement('div');
56
- document.body.appendChild(target);
57
- instance = mount(SidebarTest, { target });
58
- const addCallsBefore = matchMediaStub.mql.addEventListener.mock
59
- .calls.length;
60
- unmount(instance);
61
- instance = undefined;
62
- const removeCalls = matchMediaStub.mql.removeEventListener.mock
63
- .calls.length;
64
- expect(removeCalls).toBeGreaterThanOrEqual(addCallsBefore);
65
- });
66
- });
67
- describe('sidebar full', () => {
68
- // NOTE: SidebarRoot and SidebarTrigger do NOT forward extra HTML attributes (their prop types
69
- // only declare known props: class + children). So data-testid is silently ignored when placed
70
- // on <Sidebar.Root> or <Sidebar.Trigger> in the fixture. We use CSS class selectors instead:
71
- // - aside.sp-sidebar → the sidebar root element
72
- // - button.sp-sidebar-trigger → the trigger button
73
- // - [data-testid="..."] → only for inner content elements that use standard HTML
74
- let target;
75
- let instance;
76
- let matchMediaStub;
77
- beforeEach(() => {
78
- matchMediaStub = setupMatchMedia();
79
- });
80
- function setup(props = {}) {
81
- target = document.createElement('div');
82
- document.body.appendChild(target);
83
- instance = mount(SidebarFullTest, { target, props });
84
- flushSync();
85
- }
86
- afterEach(() => {
87
- if (instance)
88
- unmount(instance);
89
- instance = undefined;
90
- target?.remove();
91
- vi.restoreAllMocks();
92
- });
93
- it('renders provider + root + trigger + inset', () => {
94
- setup();
95
- // Root is <aside class="sp-sidebar ...">; trigger is <button class="sp-sidebar-trigger ...">
96
- expect(target.querySelector('aside.sp-sidebar')).toBeTruthy();
97
- expect(target.querySelector('button.sp-sidebar-trigger')).toBeTruthy();
98
- // Inset renders its children directly — inner element has data-testid
99
- expect(target.querySelector('[data-testid="inset-content"]')).toBeTruthy();
100
- });
101
- it('renders header, content, footer', () => {
102
- setup();
103
- // Header/Footer contain our inner elements which carry data-testid
104
- expect(target.querySelector('[data-testid="sidebar-header"]')).toBeTruthy();
105
- expect(target.querySelector('[data-testid="sidebar-footer"]')).toBeTruthy();
106
- });
107
- it('renders menu items inside groups', () => {
108
- setup();
109
- // MenuButton forwards data attributes (extends HTMLButtonAttributes)
110
- expect(target.querySelector('[data-testid="menu-home"]')).toBeTruthy();
111
- expect(target.querySelector('[data-testid="menu-settings"]')).toBeTruthy();
112
- });
113
- it('initial state expanded when open=true', () => {
114
- setup({ open: true });
115
- expect(target.querySelector('aside.sp-sidebar')?.getAttribute('data-state')).toBe('expanded');
116
- });
117
- it('initial state collapsed when open=false', () => {
118
- setup({ open: false });
119
- expect(target.querySelector('aside.sp-sidebar')?.getAttribute('data-state')).toBe('collapsed');
120
- });
121
- it('clicking trigger toggles open state', () => {
122
- setup({ open: true });
123
- const trigger = target.querySelector('button.sp-sidebar-trigger');
124
- trigger?.click();
125
- flushSync();
126
- expect(target.querySelector('aside.sp-sidebar')?.getAttribute('data-state')).toBe('collapsed');
127
- trigger?.click();
128
- flushSync();
129
- expect(target.querySelector('aside.sp-sidebar')?.getAttribute('data-state')).toBe('expanded');
130
- });
131
- it('collapsible="icon" sets data-collapsible', () => {
132
- setup({ collapsible: 'icon' });
133
- expect(target.querySelector('[data-collapsible="icon"]')).toBeTruthy();
134
- });
135
- it('collapsible="offcanvas" sets data-collapsible', () => {
136
- setup({ collapsible: 'offcanvas' });
137
- expect(target.querySelector('[data-collapsible="offcanvas"]')).toBeTruthy();
138
- });
139
- it('collapsible="none" renders aside without data-collapsible (static sidebar)', () => {
140
- // When collapsible="none", sidebar-root renders a bare <aside> without data-collapsible or data-state.
141
- // This is by design — non-collapsible sidebars have no dynamic state attribute.
142
- setup({ collapsible: 'none' });
143
- const root = target.querySelector('aside.sp-sidebar');
144
- expect(root).toBeTruthy();
145
- expect(root?.getAttribute('data-collapsible')).toBeNull();
146
- });
147
- it('isMobile — matchMedia stub does not crash the component (SSR-safe path)', () => {
148
- // isMobile is an internal context value; it is NOT reflected as a DOM attribute.
149
- // This test verifies the component mounts without error regardless of matchMedia.matches value.
150
- matchMediaStub.mql.matches = true;
151
- expect(() => setup()).not.toThrow();
152
- });
153
- it('menu button is a button element', () => {
154
- setup();
155
- // MenuButton extends HTMLButtonAttributes so data-testid is forwarded
156
- expect(target.querySelector('[data-testid="menu-home"]')?.tagName.toLowerCase()).toBe('button');
157
- });
158
- it('menu buttons render with sp-sidebar-menu-button class', () => {
159
- setup();
160
- expect(target.querySelector('[data-testid="menu-home"]')?.className).toMatch(/sp-sidebar-menu-button/);
161
- });
162
- it('renders separator as hr with sp-sidebar-separator class', () => {
163
- setup();
164
- expect(target.querySelector('hr.sp-sidebar-separator')).toBeTruthy();
165
- });
166
- it('renders group label text', () => {
167
- setup();
168
- expect((target.textContent ?? '').includes('Main')).toBe(true);
169
- });
170
- it('sidebar root is an aside element', () => {
171
- setup();
172
- const root = target.querySelector('aside.sp-sidebar');
173
- expect(root?.tagName.toLowerCase()).toBe('aside');
174
- });
175
- it('cleans up MediaQueryList listeners on unmount (P0 regression check)', () => {
176
- setup();
177
- const addedBefore = matchMediaStub.mql.addEventListener.mock.calls
178
- .length;
179
- if (instance)
180
- unmount(instance);
181
- instance = undefined;
182
- const removeCalls = matchMediaStub.mql.removeEventListener.mock
183
- .calls.length;
184
- expect(removeCalls).toBeGreaterThanOrEqual(addedBefore);
185
- });
186
- });
@@ -1,40 +0,0 @@
1
- <script lang="ts">
2
- import { SimpleDialog } from '../index.js'
3
-
4
- let {
5
- open = $bindable(false),
6
- title = 'Dialog Title',
7
- description = 'Description',
8
- confirmText = 'Confirm',
9
- cancelText = 'Cancel',
10
- onConfirm,
11
- onCancel,
12
- presentation = undefined,
13
- }: {
14
- open?: boolean
15
- title?: string
16
- description?: string
17
- confirmText?: string
18
- cancelText?: string
19
- onConfirm?: () => void
20
- onCancel?: () => void
21
- presentation?: 'dialog' | 'fullscreen-mobile' | 'fullscreen-tablet' | 'fullscreen'
22
- } = $props()
23
- </script>
24
-
25
- <div data-testid="harness">
26
- <button type="button" data-testid="toggle" onclick={() => (open = !open)}>Toggle</button>
27
-
28
- <SimpleDialog
29
- {open}
30
- {title}
31
- {description}
32
- {confirmText}
33
- {cancelText}
34
- {onConfirm}
35
- {onCancel}
36
- {presentation}
37
- >
38
- <p data-testid="body">dialog body content</p>
39
- </SimpleDialog>
40
- </div>
@@ -1,50 +0,0 @@
1
- import { flushSync, mount, unmount } from 'svelte';
2
- import { afterEach, describe, expect, it } from 'vitest';
3
- import SimpleDialogTest from './SimpleDialogTest.svelte';
4
- describe('simpleDialog', () => {
5
- let target;
6
- let instance;
7
- function setup(props = {}) {
8
- target = document.createElement('div');
9
- document.body.appendChild(target);
10
- instance = mount(SimpleDialogTest, { target, props });
11
- flushSync();
12
- }
13
- afterEach(() => {
14
- if (instance)
15
- unmount(instance);
16
- instance = undefined;
17
- target?.remove();
18
- });
19
- it('component mounts without errors (composition smoke test)', () => {
20
- setup();
21
- expect(instance).toBeTruthy();
22
- });
23
- it('closes by default (open=false bindable)', () => {
24
- setup();
25
- expect(document.body.querySelector('[role="dialog"]')).toBeNull();
26
- });
27
- it('can be opened via bindable open prop', () => {
28
- setup({ open: true });
29
- const dialog = document.body.querySelector('[role="dialog"]');
30
- expect(dialog).toBeTruthy();
31
- });
32
- it('passes title and description props through to Dialog components', () => {
33
- setup({ open: true, title: 'Dialog Title', description: 'Dialog Description' });
34
- const dialog = document.body.querySelector('[role="dialog"]');
35
- const text = dialog?.textContent ?? '';
36
- expect(text).toContain('Dialog Title');
37
- expect(text).toContain('Dialog Description');
38
- });
39
- it('renders body children snippet when open', () => {
40
- setup({ open: true });
41
- const body = document.body.querySelector('[data-testid="body"]');
42
- expect(body).toBeTruthy();
43
- expect(body?.textContent).toContain('dialog body content');
44
- });
45
- it('forwards presentation to Dialog.Content', () => {
46
- setup({ open: true, presentation: 'fullscreen' });
47
- const dialog = document.body.querySelector('[role="dialog"]');
48
- expect(dialog?.getAttribute('data-presentation')).toBe('fullscreen');
49
- });
50
- });
@@ -1,19 +0,0 @@
1
- <script lang="ts">
2
- import { SimpleTooltip } from '../index.js'
3
-
4
- const {
5
- text = 'Tooltip text',
6
- side = 'top',
7
- openDelay = 0,
8
- closeDelay = 0,
9
- }: {
10
- text?: string
11
- side?: 'top' | 'right' | 'bottom' | 'left'
12
- openDelay?: number
13
- closeDelay?: number
14
- } = $props()
15
- </script>
16
-
17
- <SimpleTooltip {text} {side} {openDelay} {closeDelay}>
18
- <button type="button" data-testid="trigger">hover me</button>
19
- </SimpleTooltip>
@@ -1,61 +0,0 @@
1
- import { flushSync, mount, unmount } from 'svelte';
2
- import { afterEach, describe, expect, it } from 'vitest';
3
- import SimpleTooltipTest from './SimpleTooltipTest.svelte';
4
- if (typeof PointerEvent === 'undefined') {
5
- ;
6
- globalThis.PointerEvent = class PointerEvent extends MouseEvent {
7
- constructor(type, init = {}) {
8
- super(type, init);
9
- }
10
- };
11
- }
12
- describe('simpleTooltip', () => {
13
- let target;
14
- let instance;
15
- function setup(props = {}) {
16
- target = document.createElement('div');
17
- document.body.appendChild(target);
18
- instance = mount(SimpleTooltipTest, { target, props });
19
- flushSync();
20
- }
21
- afterEach(() => {
22
- if (instance)
23
- unmount(instance);
24
- instance = undefined;
25
- target?.remove();
26
- });
27
- it('renders trigger element (composition smoke test)', () => {
28
- setup();
29
- const trigger = target.querySelector('[data-testid="trigger"]');
30
- expect(trigger).toBeTruthy();
31
- expect(trigger?.textContent).toContain('hover me');
32
- });
33
- it('does not render tooltip content by default (before hover)', () => {
34
- setup();
35
- const tooltip = target.querySelector('[role="tooltip"]');
36
- expect(tooltip).toBeNull();
37
- });
38
- it('text prop is wired to tooltip content (pass-through smoke test)', async () => {
39
- setup({ text: 'Custom message', openDelay: 0, closeDelay: 0 });
40
- const trigger = target.querySelector('[data-testid="trigger"]');
41
- // Trigger hover
42
- trigger?.dispatchEvent(new PointerEvent('pointerenter', { bubbles: true }));
43
- flushSync();
44
- // Wait for openDelay to complete
45
- await new Promise((resolve) => {
46
- setTimeout(() => {
47
- flushSync();
48
- const tooltip = target.querySelector('[role="tooltip"]');
49
- if (tooltip) {
50
- expect(tooltip.textContent ?? '').toContain('Custom message');
51
- }
52
- else {
53
- // If tooltip didn't open on first try, it's still a pass (prop is wired)
54
- // This is acceptable for smoke test - we're verifying composition
55
- expect(trigger).toBeTruthy();
56
- }
57
- resolve();
58
- }, 100);
59
- });
60
- });
61
- });
@@ -1,42 +0,0 @@
1
- import { mount, unmount } from 'svelte';
2
- import { afterEach, describe, expect, it } from 'vitest';
3
- import Slider from '../slider.svelte';
4
- describe('slider', () => {
5
- let target;
6
- let instance;
7
- function setup(props = {}) {
8
- target = document.createElement('div');
9
- document.body.appendChild(target);
10
- instance = mount(Slider, { 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-valuenow matching value', () => {
23
- setup({ value: 42, min: 0, max: 100 });
24
- expect(target.querySelector('[role="slider"]')?.getAttribute('aria-valuenow')).toBe('42');
25
- });
26
- it('has aria-valuemin and aria-valuemax', () => {
27
- setup({ min: 10, max: 200 });
28
- const s = target.querySelector('[role="slider"]');
29
- expect(s.getAttribute('aria-valuemin')).toBe('10');
30
- expect(s.getAttribute('aria-valuemax')).toBe('200');
31
- });
32
- it('has aria-orientation', () => {
33
- setup({ orientation: 'vertical' });
34
- expect(target.querySelector('[role="slider"]')?.getAttribute('aria-orientation')).toBe('vertical');
35
- });
36
- it('generates hidden input when name is provided', () => {
37
- setup({ name: 'volume', value: 75 });
38
- const hidden = target.querySelector('input[type="hidden"]');
39
- expect(hidden).toBeTruthy();
40
- expect(hidden.name).toBe('volume');
41
- });
42
- });
@@ -1,49 +0,0 @@
1
- <script lang="ts">
2
- import * as SortableList from '../index.js'
3
-
4
- interface Block {
5
- id: string
6
- label: string
7
- }
8
-
9
- interface OuterBlock extends Block {
10
- children?: Block[]
11
- }
12
-
13
- let {
14
- items = $bindable<OuterBlock[]>([
15
- { id: 'title', label: 'Title' },
16
- {
17
- id: 'container',
18
- label: 'Container',
19
- children: [
20
- { id: 'inner-1', label: 'Inner 1' },
21
- { id: 'inner-2', label: 'Inner 2' },
22
- ],
23
- },
24
- { id: 'author', label: 'Author' },
25
- { id: 'place', label: 'Place' },
26
- ]),
27
- }: {
28
- items?: OuterBlock[]
29
- } = $props()
30
- </script>
31
-
32
- <SortableList.Root bind:items group="nested-blocks" aria-label="Outer list">
33
- {#snippet item(data: OuterBlock)}
34
- <SortableList.Item id={data.id}>
35
- {data.label}
36
- {#if data.children}
37
- <SortableList.Root
38
- items={data.children}
39
- group="nested-blocks"
40
- aria-label="Inner list"
41
- >
42
- {#snippet item(child: Block)}
43
- <SortableList.Item id={child.id}>{child.label}</SortableList.Item>
44
- {/snippet}
45
- </SortableList.Root>
46
- {/if}
47
- </SortableList.Item>
48
- {/snippet}
49
- </SortableList.Root>
@@ -1,42 +0,0 @@
1
- <script lang="ts">
2
- import * as SortableList from '../index.js'
3
-
4
- interface Item {
5
- id: string
6
- label: string
7
- }
8
-
9
- let {
10
- items = $bindable<Item[]>([
11
- { id: 'a', label: 'A' },
12
- { id: 'b', label: 'B' },
13
- { id: 'c', label: 'C' },
14
- ]),
15
- orientation = 'vertical' as 'vertical' | 'horizontal',
16
- disabled = false,
17
- group = undefined as string | undefined,
18
- onReorder = undefined as ((keys: string[], target: unknown) => void) | undefined,
19
- }: {
20
- items?: Item[]
21
- orientation?: 'vertical' | 'horizontal'
22
- disabled?: boolean
23
- group?: string
24
- onReorder?: (keys: string[], target: unknown) => void
25
- } = $props()
26
- </script>
27
-
28
- <SortableList.Root
29
- bind:items
30
- {orientation}
31
- {disabled}
32
- {group}
33
- {onReorder}
34
- aria-label="Sortable test"
35
- >
36
- {#snippet item(data)}
37
- <SortableList.Item id={data.id}>
38
- <SortableList.Handle>&#x22EE;&#x22EE;</SortableList.Handle>
39
- {data.label}
40
- </SortableList.Item>
41
- {/snippet}
42
- </SortableList.Root>
@@ -1,112 +0,0 @@
1
- /**
2
- * SortableList nested lists — regression lock for #190.
3
- *
4
- * A Root nested inside an Item (same group) used to freeze the ghost:
5
- * the pointerdown bubbled from the inner item to the outer item (both drag
6
- * sources), and the second beginPointerDrag clobbered the pending session
7
- * before the movement threshold was met.
8
- *
9
- * These tests drive the pointer path far enough in jsdom to lock the claim
10
- * semantics (innermost source wins) and the clean end of the session.
11
- * Full spatial behavior (drop positions, indicators) stays in Playwright.
12
- */
13
- import { flushSync, mount, unmount } from 'svelte';
14
- import { afterEach, beforeAll, describe, expect, it } from 'vitest';
15
- import { dragSession } from '../../../internal/drag-drop/index.js';
16
- import NestedSortableListTest from './NestedSortableListTest.svelte';
17
- // Polyfill browser APIs absent from jsdom
18
- if (typeof PointerEvent === 'undefined') {
19
- ;
20
- globalThis.PointerEvent = class PointerEvent extends (MouseEvent) {
21
- pointerId;
22
- pointerType;
23
- constructor(type, init = {}) {
24
- super(type, init);
25
- this.pointerId = init.pointerId ?? 0;
26
- this.pointerType = init.pointerType ?? '';
27
- }
28
- };
29
- }
30
- function createMousePointerEvent(type, x, y) {
31
- return new PointerEvent(type, {
32
- clientX: x,
33
- clientY: y,
34
- pointerId: 1,
35
- pointerType: 'mouse',
36
- bubbles: true,
37
- });
38
- }
39
- describe('sortableList — nested Root inside an Item (#190)', () => {
40
- let target;
41
- let instance;
42
- beforeAll(() => {
43
- if (!HTMLElement.prototype.setPointerCapture) {
44
- HTMLElement.prototype.setPointerCapture = () => { };
45
- HTMLElement.prototype.releasePointerCapture = () => { };
46
- }
47
- });
48
- function setup() {
49
- dragSession._resetForTests();
50
- target = document.createElement('div');
51
- document.body.appendChild(target);
52
- instance = mount(NestedSortableListTest, { target, props: {} });
53
- flushSync();
54
- }
55
- afterEach(() => {
56
- if (instance) {
57
- unmount(instance);
58
- }
59
- instance = undefined;
60
- target?.remove();
61
- dragSession._resetForTests();
62
- });
63
- function getItem(id) {
64
- const el = target.querySelector(`[data-dnd-key="${id}"]`);
65
- if (!el)
66
- throw new Error(`item ${id} not found`);
67
- return el;
68
- }
69
- it('renders both lists (outer 4 items, inner 2 items)', () => {
70
- setup();
71
- const lists = target.querySelectorAll('.sp-sortable-list');
72
- expect(lists).toHaveLength(2);
73
- expect(target.querySelectorAll('[data-dnd-key]')).toHaveLength(6);
74
- });
75
- it('pointerdown on an inner item claims the inner source, not the outer item', () => {
76
- setup();
77
- const inner = getItem('inner-1');
78
- inner.dispatchEvent(createMousePointerEvent('pointerdown', 100, 100));
79
- expect(dragSession.sourceId).not.toBeNull();
80
- expect(dragSession.sourceId.endsWith('-item-inner-1')).toBe(true);
81
- dragSession.cancel();
82
- });
83
- it('dragging an inner item past the threshold starts and ends a clean session', () => {
84
- setup();
85
- const inner = getItem('inner-1');
86
- inner.dispatchEvent(createMousePointerEvent('pointerdown', 100, 100));
87
- inner.dispatchEvent(createMousePointerEvent('pointermove', 112, 112));
88
- expect(dragSession.isDragging).toBe(true);
89
- expect(dragSession.sourceId.endsWith('-item-inner-1')).toBe(true);
90
- expect(dragSession.draggingKeys.has('inner-1')).toBe(true);
91
- expect(document.querySelector('.sp-drag-ghost')).not.toBeNull();
92
- // Releasing the pointer must end the session — this is where the ghost
93
- // used to stay frozen on screen before the fix.
94
- inner.dispatchEvent(createMousePointerEvent('pointerup', 112, 112));
95
- expect(dragSession.isDragging).toBe(false);
96
- expect(dragSession.sourceId).toBeNull();
97
- expect(document.querySelector('.sp-drag-ghost')).toBeNull();
98
- });
99
- it('dragging an outer item (the container itself) still works', () => {
100
- setup();
101
- const container = getItem('container');
102
- // pointerdown directly on the container item, outside the inner list
103
- container.dispatchEvent(createMousePointerEvent('pointerdown', 100, 100));
104
- expect(dragSession.sourceId.endsWith('-item-container')).toBe(true);
105
- container.dispatchEvent(createMousePointerEvent('pointermove', 112, 112));
106
- expect(dragSession.isDragging).toBe(true);
107
- expect(dragSession.draggingKeys.has('container')).toBe(true);
108
- container.dispatchEvent(createMousePointerEvent('pointerup', 112, 112));
109
- expect(dragSession.isDragging).toBe(false);
110
- expect(document.querySelector('.sp-drag-ghost')).toBeNull();
111
- });
112
- });