@spectral_labs/ui 1.7.0 → 1.8.0-next.0

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