@svgrid/grid 2.1.22 → 2.2.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.
- package/README.md +9 -2
- package/dist/DockNodeView.svelte +306 -0
- package/dist/DockNodeView.svelte.d.ts +7 -0
- package/dist/DockPopoutHost.svelte +17 -0
- package/dist/DockPopoutHost.svelte.d.ts +14 -0
- package/dist/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +178 -178
- package/dist/GridMenus.svelte +83 -3
- package/dist/SvAutoComplete.svelte +5 -3
- package/dist/SvCarousel.svelte +141 -163
- package/dist/SvColorInput.svelte +187 -186
- package/dist/SvColorInput.svelte.d.ts +1 -1
- package/dist/SvComboBox.svelte +7 -3
- package/dist/SvCommand.svelte +28 -85
- package/dist/SvContextMenu.svelte +116 -116
- package/dist/SvCountryInput.svelte +1 -1
- package/dist/SvDateRangeInput.svelte +1 -1
- package/dist/SvDateTimePicker.svelte +9 -3
- package/dist/SvDateTimePicker.svelte.d.ts +3 -0
- package/dist/SvDockLayout.svelte +244 -0
- package/dist/SvDockLayout.svelte.d.ts +77 -0
- package/dist/SvDockManager.svelte +922 -0
- package/dist/SvDockManager.svelte.d.ts +106 -0
- package/dist/SvDrawer.svelte +250 -228
- package/dist/SvDrawer.svelte.d.ts +4 -0
- package/dist/SvDropDownList.svelte +85 -13
- package/dist/SvDropDownList.svelte.d.ts +3 -0
- package/dist/SvField.svelte +21 -4
- package/dist/SvField.svelte.d.ts +1 -0
- package/dist/SvForm.svelte +201 -91
- package/dist/SvForm.svelte.d.ts +15 -22
- package/dist/SvGrid.controller.svelte.d.ts +32 -1
- package/dist/SvGrid.controller.svelte.js +156 -27
- package/dist/SvGrid.css +193 -5
- package/dist/SvGrid.svelte +3131 -2786
- package/dist/SvGrid.types.d.ts +452 -1
- package/dist/SvGridBoard.svelte +2339 -2339
- package/dist/SvGridChart.svelte +1724 -1724
- package/dist/SvGridChartPanel.svelte +485 -485
- package/dist/SvGridDropdown.svelte +689 -682
- package/dist/SvGridSelect.svelte +1 -0
- package/dist/SvGroupCell.svelte +109 -109
- package/dist/SvListBox.svelte +36 -14
- package/dist/SvMaskedInput.svelte +4 -3
- package/dist/SvMaskedInput.svelte.d.ts +1 -1
- package/dist/SvMenu.svelte +124 -124
- package/dist/SvMenuList.svelte +117 -165
- package/dist/SvMenuList.svelte.d.ts +2 -13
- package/dist/SvModal.svelte +12 -29
- package/dist/SvMultiSelect.svelte +1 -0
- package/dist/SvNumberInput.svelte +131 -130
- package/dist/SvNumberInput.svelte.d.ts +1 -1
- package/dist/SvOtpInput.svelte +158 -158
- package/dist/SvPagination.svelte +18 -18
- package/dist/SvPasswordInput.svelte +3 -2
- package/dist/SvPasswordInput.svelte.d.ts +1 -1
- package/dist/SvPhoneInput.svelte +3 -2
- package/dist/SvPhoneInput.svelte.d.ts +1 -1
- package/dist/SvPopover.svelte +139 -139
- package/dist/SvRowGroupPanel.svelte +170 -170
- package/dist/SvScrollArea.svelte +61 -61
- package/dist/SvStepper.svelte +6 -9
- package/dist/SvTagsInput.svelte +3 -2
- package/dist/SvTagsInput.svelte.d.ts +1 -1
- package/dist/SvTooltip.svelte +127 -138
- package/dist/SvTour.svelte +204 -176
- package/dist/SvTree.svelte +377 -377
- package/dist/SvTreeSelect.svelte +230 -229
- package/dist/build-api.js +76 -54
- package/dist/cdn/svgrid.js +10913 -6450
- package/dist/cdn/svgrid.svelte-external.js +9628 -5481
- package/dist/chart-export.js +8 -8
- package/dist/columns.js +6 -0
- package/dist/conditional-formatting.d.ts +67 -3
- package/dist/conditional-formatting.js +124 -17
- package/dist/createCarousel.svelte.d.ts +72 -0
- package/dist/createCarousel.svelte.js +80 -0
- package/dist/createCommand.svelte.d.ts +62 -0
- package/dist/createCommand.svelte.js +109 -0
- package/dist/createForm.svelte.d.ts +62 -0
- package/dist/createForm.svelte.js +374 -0
- package/dist/createMenu.svelte.d.ts +88 -0
- package/dist/createMenu.svelte.js +153 -0
- package/dist/createOverlay.svelte.d.ts +38 -0
- package/dist/createOverlay.svelte.js +68 -0
- package/dist/createPagination.d.ts +74 -0
- package/dist/createPagination.js +71 -0
- package/dist/createPopoverSelect.svelte.js +6 -12
- package/dist/createStepper.d.ts +44 -0
- package/dist/createStepper.js +26 -0
- package/dist/createTooltip.svelte.d.ts +32 -0
- package/dist/createTooltip.svelte.js +61 -0
- package/dist/datetime/timezone.d.ts +36 -0
- package/dist/datetime/timezone.js +110 -0
- package/dist/dock-context.d.ts +52 -0
- package/dist/dock-context.js +1 -0
- package/dist/dock-manager-model.d.ts +136 -0
- package/dist/dock-manager-model.js +514 -0
- package/dist/dock-model.d.ts +105 -0
- package/dist/dock-model.js +288 -0
- package/dist/dock-popout.d.ts +30 -0
- package/dist/dock-popout.js +81 -0
- package/dist/editor-contract.d.ts +2 -0
- package/dist/filter-operators.d.ts +6 -0
- package/dist/filter-operators.js +35 -2
- package/dist/filtering/excel-filters.d.ts +18 -1
- package/dist/filtering/excel-filters.js +56 -0
- package/dist/form-field.d.ts +73 -0
- package/dist/form-field.js +15 -0
- package/dist/index.d.ts +25 -5
- package/dist/index.js +40 -4
- package/dist/list-nav.d.ts +18 -0
- package/dist/list-nav.js +30 -0
- package/dist/list-option.d.ts +2 -0
- package/dist/menu-item.d.ts +20 -0
- package/dist/menu-item.js +1 -0
- package/dist/menus.d.ts +5 -0
- package/dist/menus.js +68 -16
- package/dist/panel-resize.d.ts +25 -0
- package/dist/panel-resize.js +40 -0
- package/dist/popover.d.ts +29 -3
- package/dist/popover.js +22 -6
- package/dist/recurrence.d.ts +33 -7
- package/dist/recurrence.js +151 -13
- package/dist/row-drag.d.ts +8 -0
- package/dist/row-drag.js +11 -0
- package/dist/scheduler-ical.d.ts +30 -0
- package/dist/scheduler-ical.js +180 -0
- package/dist/scheduler-model.d.ts +269 -0
- package/dist/scheduler-model.js +602 -0
- package/dist/scheduler-view.svelte.d.ts +22 -0
- package/dist/scheduler-view.svelte.js +13 -0
- package/dist/spreadsheet.js +20 -12
- package/dist/ui-app.types.d.ts +3 -0
- package/package.json +14 -10
- package/src/DockNodeView.svelte +306 -0
- package/src/DockPopoutHost.svelte +17 -0
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +178 -178
- package/src/GridMenus.svelte +83 -3
- package/src/SvAutoComplete.svelte +5 -3
- package/src/SvCarousel.svelte +141 -163
- package/src/SvCarousel.test.ts +68 -0
- package/src/SvColorInput.svelte +187 -186
- package/src/SvComboBox.svelte +7 -3
- package/src/SvCommand.svelte +28 -85
- package/src/SvCommand.test.ts +78 -0
- package/src/SvContextMenu.svelte +116 -116
- package/src/SvCountryInput.svelte +1 -1
- package/src/SvDateRangeInput.svelte +1 -1
- package/src/SvDateTimePicker.svelte +9 -3
- package/src/SvDockLayout.svelte +244 -0
- package/src/SvDockManager.svelte +922 -0
- package/src/SvDrawer.svelte +250 -228
- package/src/SvDropDownList.svelte +85 -13
- package/src/SvField.svelte +21 -4
- package/src/SvForm.svelte +201 -91
- package/src/SvForm.test.ts +299 -0
- package/src/SvGrid.controller.svelte.ts +3158 -3034
- package/src/SvGrid.css +193 -5
- package/src/SvGrid.svelte +3131 -2786
- package/src/SvGrid.types.ts +1489 -1027
- package/src/SvGridBoard.svelte +2339 -2339
- package/src/SvGridChart.svelte +1724 -1724
- package/src/SvGridChartPanel.svelte +485 -485
- package/src/SvGridDropdown.svelte +689 -682
- package/src/SvGridSelect.svelte +1 -0
- package/src/SvGroupCell.svelte +109 -109
- package/src/SvListBox.svelte +36 -14
- package/src/SvMaskedInput.svelte +4 -3
- package/src/SvMenu.svelte +124 -124
- package/src/SvMenu.test.ts +97 -73
- package/src/SvMenuList.svelte +117 -165
- package/src/SvModal.svelte +12 -29
- package/src/SvModal.test.ts +82 -0
- package/src/SvMultiSelect.svelte +1 -0
- package/src/SvNumberInput.svelte +131 -130
- package/src/SvOtpInput.svelte +158 -158
- package/src/SvPagination.svelte +18 -18
- package/src/SvPasswordInput.svelte +3 -2
- package/src/SvPhoneInput.svelte +3 -2
- package/src/SvPopover.svelte +139 -139
- package/src/SvRowGroupPanel.svelte +170 -170
- package/src/SvScrollArea.svelte +61 -61
- package/src/SvStepper.svelte +6 -9
- package/src/SvTagsInput.svelte +3 -2
- package/src/SvTooltip.svelte +127 -138
- package/src/SvTooltip.test.ts +73 -0
- package/src/SvTour.svelte +204 -176
- package/src/SvTree.svelte +377 -377
- package/src/SvTreeSelect.svelte +230 -229
- package/src/a11y.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +61 -61
- package/src/build-api.ts +870 -847
- package/src/cell-formatting.ts +169 -169
- package/src/cell-render.ts +469 -469
- package/src/chart-export.ts +201 -201
- package/src/chart.ts +2296 -2296
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/columns.ts +5 -0
- package/src/conditional-formatting.test.ts +104 -0
- package/src/conditional-formatting.ts +176 -19
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1111 -1111
- package/src/createAutocomplete.svelte.ts +132 -132
- package/src/createCarousel.svelte.ts +104 -0
- package/src/createCombobox.svelte.ts +179 -179
- package/src/createCommand.svelte.ts +125 -0
- package/src/createCountryInput.svelte.ts +157 -157
- package/src/createDropdownList.svelte.ts +162 -162
- package/src/createForm.svelte.ts +333 -0
- package/src/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- package/src/createMenu.svelte.ts +202 -0
- package/src/createOverlay.svelte.ts +101 -0
- package/src/createPagination.test.ts +65 -0
- package/src/createPagination.ts +84 -0
- package/src/createPopoverSelect.svelte.ts +206 -210
- package/src/createStepper.test.ts +62 -0
- package/src/createStepper.ts +61 -0
- package/src/createTooltip.svelte.ts +86 -0
- package/src/createTree.svelte.ts +319 -319
- package/src/datetime/timezone.test.ts +69 -0
- package/src/datetime/timezone.ts +134 -0
- package/src/dock-context.ts +47 -0
- package/src/dock-manager-model.test.ts +275 -0
- package/src/dock-manager-model.ts +593 -0
- package/src/dock-model.test.ts +163 -0
- package/src/dock-model.ts +368 -0
- package/src/dock-popout.ts +94 -0
- package/src/editing.test.ts +859 -859
- package/src/editing.ts +675 -675
- package/src/editor-contract.ts +2 -0
- package/src/export-data-api.test.ts +126 -126
- package/src/export-format.test.ts +107 -107
- package/src/export-format.ts +598 -598
- package/src/filter-operators.test.ts +59 -0
- package/src/filter-operators.ts +35 -2
- package/src/filtering/excel-filters.test.ts +86 -1
- package/src/filtering/excel-filters.ts +65 -0
- package/src/flex-render.ts +3 -3
- package/src/form-field.ts +87 -0
- package/src/index.ts +623 -469
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/list-nav.test.ts +49 -0
- package/src/list-nav.ts +29 -0
- package/src/list-option.ts +2 -0
- package/src/menu-item.ts +21 -0
- package/src/menus.test.ts +48 -0
- package/src/menus.ts +639 -582
- package/src/merge-objects.ts +48 -48
- package/src/overlays.test.ts +90 -90
- package/src/panel-resize.ts +52 -0
- package/src/popover.bounds.test.ts +93 -0
- package/src/popover.ts +51 -7
- package/src/recurrence.test.ts +88 -1
- package/src/recurrence.ts +166 -15
- package/src/render-component.ts +28 -28
- package/src/row-drag.ts +10 -0
- package/src/scheduler-ical.test.ts +68 -0
- package/src/scheduler-ical.ts +180 -0
- package/src/scheduler-model.test.ts +546 -0
- package/src/scheduler-model.ts +853 -0
- package/src/scheduler-view.svelte.ts +33 -0
- package/src/selection.test.ts +754 -754
- package/src/selection.ts +600 -600
- package/src/server-data-source.test.ts +289 -289
- package/src/server-data-source.ts +413 -413
- package/src/sparkline.test.ts +68 -68
- package/src/sparkline.ts +169 -169
- package/src/spreadsheet.test.ts +489 -489
- package/src/spreadsheet.ts +312 -304
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/svgrid-wrapper.types.ts +501 -501
- package/src/svgrid.behavior.test.ts +706 -706
- package/src/svgrid.charting.test.ts +527 -527
- package/src/svgrid.effect-driven-filter.svelte.test.ts +116 -0
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.new-features.wrapper.test.ts +251 -251
- package/src/svgrid.wrapper.test.ts +40 -40
- package/src/test-setup.ts +62 -62
- package/src/themes/index.ts +159 -159
- package/src/ui-app.types.ts +3 -0
- package/src/virtual.test.ts +6 -4
- package/src/virtualization/column-virtualizer.test.ts +27 -27
- package/src/virtualization/column-virtualizer.ts +30 -30
- package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
- package/src/virtualization/types.ts +30 -30
- package/src/virtualization/virtualizer.test.ts +47 -47
- package/src/virtualization/virtualizer.ts +296 -296
package/src/SvColorInput.svelte
CHANGED
|
@@ -1,186 +1,187 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SvColorInput - a color swatch that opens a portalled popover with a hex
|
|
4
|
-
* field, a native picker, and a preset palette. Parity: Smart `smart-color-input`.
|
|
5
|
-
* Emits a hex string. Popover escapes the grid scroll container via popover.ts.
|
|
6
|
-
*
|
|
7
|
-
* The styled renderer over the headless `createColorInput` core: hex
|
|
8
|
-
* normalization, palette, draft field and open/close state come from the core;
|
|
9
|
-
* the portal + anchored measurement stay here (render concerns).
|
|
10
|
-
*/
|
|
11
|
-
import { anchoredRect, portalToBody, popIn, type AnchoredRect } from './popover'
|
|
12
|
-
import { createDismissableLayer } from './a11y/dismissable'
|
|
13
|
-
import SvField from './SvField.svelte'
|
|
14
|
-
import { nextEditorId, resolveMessages, type SvEditorProps } from './editor-contract'
|
|
15
|
-
import { createColorInput } from './createColorInput.svelte'
|
|
16
|
-
|
|
17
|
-
/** User-facing strings for the color popover (localizable via `messages`). */
|
|
18
|
-
type ColorMessages = { dialog: string; picker: string; hex: string }
|
|
19
|
-
const DEFAULT_MESSAGES: ColorMessages = { dialog: 'Choose color', picker: 'Color picker', hex: 'Hex value' }
|
|
20
|
-
|
|
21
|
-
type Props = SvEditorProps & {
|
|
22
|
-
value?: string
|
|
23
|
-
onChange?: (hex: string) => void
|
|
24
|
-
/** Preset swatches. */
|
|
25
|
-
palette?: string[]
|
|
26
|
-
autoOpen?: boolean
|
|
27
|
-
/** Override the built-in popover strings. */
|
|
28
|
-
messages?: Partial<ColorMessages>
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let {
|
|
32
|
-
value = '#3b82f6',
|
|
33
|
-
onChange,
|
|
34
|
-
disabled = false,
|
|
35
|
-
readonly = false,
|
|
36
|
-
palette,
|
|
37
|
-
name,
|
|
38
|
-
size = 'md',
|
|
39
|
-
ariaLabel,
|
|
40
|
-
invalid = false,
|
|
41
|
-
required = false,
|
|
42
|
-
error,
|
|
43
|
-
label,
|
|
44
|
-
hint,
|
|
45
|
-
dir,
|
|
46
|
-
id,
|
|
47
|
-
autoOpen = false,
|
|
48
|
-
messages,
|
|
49
|
-
}: Props = $props()
|
|
50
|
-
|
|
51
|
-
const autoId = nextEditorId('sv-color')
|
|
52
|
-
const uid = $derived(id ?? autoId)
|
|
53
|
-
const M = $derived(resolveMessages(DEFAULT_MESSAGES, messages))
|
|
54
|
-
|
|
55
|
-
const col = createColorInput({
|
|
56
|
-
value: () => value,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
let
|
|
71
|
-
let
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
window.addEventListener('
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
window.removeEventListener('
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
class
|
|
107
|
-
class:is-
|
|
108
|
-
{
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
<span class="sv-
|
|
113
|
-
|
|
114
|
-
</
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
use:
|
|
122
|
-
|
|
123
|
-
style:
|
|
124
|
-
style:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
<input
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
class
|
|
145
|
-
|
|
146
|
-
{
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
{
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
.sv-color--
|
|
164
|
-
.sv-color--
|
|
165
|
-
.sv-color
|
|
166
|
-
.sv-color.is-
|
|
167
|
-
.sv-color.is-invalid
|
|
168
|
-
.sv-color:focus-
|
|
169
|
-
.sv-
|
|
170
|
-
.sv-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
:global(.sv-
|
|
179
|
-
:global(.sv-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
:global(.sv-
|
|
185
|
-
:global(.sv-color__chip
|
|
186
|
-
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvColorInput - a color swatch that opens a portalled popover with a hex
|
|
4
|
+
* field, a native picker, and a preset palette. Parity: Smart `smart-color-input`.
|
|
5
|
+
* Emits a hex string. Popover escapes the grid scroll container via popover.ts.
|
|
6
|
+
*
|
|
7
|
+
* The styled renderer over the headless `createColorInput` core: hex
|
|
8
|
+
* normalization, palette, draft field and open/close state come from the core;
|
|
9
|
+
* the portal + anchored measurement stay here (render concerns).
|
|
10
|
+
*/
|
|
11
|
+
import { anchoredRect, portalToBody, popIn, type AnchoredRect } from './popover'
|
|
12
|
+
import { createDismissableLayer } from './a11y/dismissable'
|
|
13
|
+
import SvField from './SvField.svelte'
|
|
14
|
+
import { nextEditorId, resolveMessages, type SvEditorProps } from './editor-contract'
|
|
15
|
+
import { createColorInput } from './createColorInput.svelte'
|
|
16
|
+
|
|
17
|
+
/** User-facing strings for the color popover (localizable via `messages`). */
|
|
18
|
+
type ColorMessages = { dialog: string; picker: string; hex: string }
|
|
19
|
+
const DEFAULT_MESSAGES: ColorMessages = { dialog: 'Choose color', picker: 'Color picker', hex: 'Hex value' }
|
|
20
|
+
|
|
21
|
+
type Props = SvEditorProps & {
|
|
22
|
+
value?: string
|
|
23
|
+
onChange?: (hex: string) => void
|
|
24
|
+
/** Preset swatches. */
|
|
25
|
+
palette?: string[]
|
|
26
|
+
autoOpen?: boolean
|
|
27
|
+
/** Override the built-in popover strings. */
|
|
28
|
+
messages?: Partial<ColorMessages>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let {
|
|
32
|
+
value = $bindable('#3b82f6'),
|
|
33
|
+
onChange,
|
|
34
|
+
disabled = false,
|
|
35
|
+
readonly = false,
|
|
36
|
+
palette,
|
|
37
|
+
name,
|
|
38
|
+
size = 'md',
|
|
39
|
+
ariaLabel,
|
|
40
|
+
invalid = false,
|
|
41
|
+
required = false,
|
|
42
|
+
error,
|
|
43
|
+
label,
|
|
44
|
+
hint,
|
|
45
|
+
dir,
|
|
46
|
+
id,
|
|
47
|
+
autoOpen = false,
|
|
48
|
+
messages,
|
|
49
|
+
}: Props = $props()
|
|
50
|
+
|
|
51
|
+
const autoId = nextEditorId('sv-color')
|
|
52
|
+
const uid = $derived(id ?? autoId)
|
|
53
|
+
const M = $derived(resolveMessages(DEFAULT_MESSAGES, messages))
|
|
54
|
+
|
|
55
|
+
const col = createColorInput({
|
|
56
|
+
value: () => value,
|
|
57
|
+
// Write `value` (so `bind:value` works) AND fire `onChange` (callback users).
|
|
58
|
+
onChange: (hex) => { value = hex; onChange?.(hex) },
|
|
59
|
+
disabled: () => disabled,
|
|
60
|
+
readonly: () => readonly,
|
|
61
|
+
palette: () => palette as string[],
|
|
62
|
+
id: () => uid,
|
|
63
|
+
invalid: () => invalid,
|
|
64
|
+
required: () => required,
|
|
65
|
+
error: () => error,
|
|
66
|
+
hint: () => hint,
|
|
67
|
+
ariaLabel: () => ariaLabel,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
let triggerEl = $state<HTMLButtonElement | null>(null)
|
|
71
|
+
let panelEl = $state<HTMLDivElement | null>(null)
|
|
72
|
+
let panelRect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false, maxHeight: 0, availHeight: 0 })
|
|
73
|
+
|
|
74
|
+
function updatePos() {
|
|
75
|
+
if (!triggerEl) return
|
|
76
|
+
panelRect = anchoredRect(triggerEl.getBoundingClientRect(), { estimatedHeight: 210, minWidth: 200 })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
$effect(() => {
|
|
80
|
+
if (!col.popover.open) return
|
|
81
|
+
updatePos()
|
|
82
|
+
const reposition = () => updatePos()
|
|
83
|
+
window.addEventListener('scroll', reposition, true)
|
|
84
|
+
window.addEventListener('resize', reposition)
|
|
85
|
+
const layer = createDismissableLayer({
|
|
86
|
+
element: () => [triggerEl, panelEl],
|
|
87
|
+
onDismiss: () => col.popover.close(),
|
|
88
|
+
})
|
|
89
|
+
layer.activate()
|
|
90
|
+
return () => {
|
|
91
|
+
window.removeEventListener('scroll', reposition, true)
|
|
92
|
+
window.removeEventListener('resize', reposition)
|
|
93
|
+
layer.release()
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
function focusOpen(node: HTMLButtonElement) {
|
|
98
|
+
if (!autoOpen) return
|
|
99
|
+
node.focus(); col.popover.show()
|
|
100
|
+
}
|
|
101
|
+
</script>
|
|
102
|
+
|
|
103
|
+
<SvField id={uid} {label} {hint} {error} {required} {dir}>
|
|
104
|
+
<button
|
|
105
|
+
bind:this={triggerEl}
|
|
106
|
+
class="sv-color sv-color--{size}"
|
|
107
|
+
class:is-disabled={disabled}
|
|
108
|
+
class:is-invalid={invalid}
|
|
109
|
+
{...col.swatchProps()}
|
|
110
|
+
use:focusOpen
|
|
111
|
+
>
|
|
112
|
+
<span class="sv-color__swatch" style:background={col.normalized}></span>
|
|
113
|
+
<span class="sv-color__hex">{col.normalized}</span>
|
|
114
|
+
</button>
|
|
115
|
+
</SvField>
|
|
116
|
+
|
|
117
|
+
{#if col.popover.open}
|
|
118
|
+
<div
|
|
119
|
+
class="sv-color__panel"
|
|
120
|
+
bind:this={panelEl}
|
|
121
|
+
use:portalToBody
|
|
122
|
+
use:popIn={{ up: panelRect.openUpward }}
|
|
123
|
+
style:position="fixed"
|
|
124
|
+
style:top={`${panelRect.top}px`}
|
|
125
|
+
style:left={`${panelRect.left}px`}
|
|
126
|
+
role="dialog"
|
|
127
|
+
aria-label={M.dialog}
|
|
128
|
+
>
|
|
129
|
+
<div class="sv-color__top">
|
|
130
|
+
<input class="sv-color__native" type="color" value={col.normalized} oninput={(e) => col.pick((e.currentTarget as HTMLInputElement).value)} aria-label={M.picker} />
|
|
131
|
+
<input
|
|
132
|
+
class="sv-color__field"
|
|
133
|
+
type="text"
|
|
134
|
+
bind:value={col.hexDraft}
|
|
135
|
+
spellcheck="false"
|
|
136
|
+
aria-label={M.hex}
|
|
137
|
+
onblur={col.commitHex}
|
|
138
|
+
onkeydown={(e) => { if (e.key === 'Enter') col.commitHex() }}
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="sv-color__palette">
|
|
142
|
+
{#each col.palette as c (c)}
|
|
143
|
+
<button
|
|
144
|
+
class="sv-color__chip"
|
|
145
|
+
class:is-active={col.isActive(c)}
|
|
146
|
+
style:background={c}
|
|
147
|
+
{...col.chipProps(c)}
|
|
148
|
+
></button>
|
|
149
|
+
{/each}
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
{/if}
|
|
153
|
+
{#if name}<input type="hidden" {name} value={col.normalized} />{/if}
|
|
154
|
+
|
|
155
|
+
<style>
|
|
156
|
+
.sv-color {
|
|
157
|
+
--_accent: var(--sg-accent, #2563eb);
|
|
158
|
+
display: inline-flex; align-items: center; gap: 8px;
|
|
159
|
+
background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
160
|
+
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
161
|
+
padding: 0 10px; cursor: pointer; font: inherit;
|
|
162
|
+
}
|
|
163
|
+
.sv-color--sm { height: 28px; font-size: 12px; }
|
|
164
|
+
.sv-color--md { height: 34px; font-size: 13px; }
|
|
165
|
+
.sv-color--lg { height: 40px; font-size: 15px; }
|
|
166
|
+
.sv-color.is-disabled { opacity: 0.6; cursor: not-allowed; }
|
|
167
|
+
.sv-color.is-invalid { border-color: var(--sg-danger, #dc2626); }
|
|
168
|
+
.sv-color.is-invalid:focus-within { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
|
|
169
|
+
.sv-color:focus-visible { outline: 2px solid var(--sg-focus-ring, var(--_accent)); outline-offset: 2px; }
|
|
170
|
+
.sv-color__swatch { width: 18px; height: 18px; border-radius: 4px; border: 1px solid rgba(0,0,0,0.15); flex: none; }
|
|
171
|
+
.sv-color__hex { font-variant-numeric: tabular-nums; text-transform: lowercase; }
|
|
172
|
+
|
|
173
|
+
:global(.sv-color__panel) {
|
|
174
|
+
z-index: 2147483647; width: 208px; padding: 12px;
|
|
175
|
+
background: var(--sg-bg, #fff); border: 1px solid var(--sg-border, #e2e8f0);
|
|
176
|
+
border-radius: 12px; box-shadow: 0 16px 48px -12px rgba(15,23,42,0.35);
|
|
177
|
+
}
|
|
178
|
+
:global(.sv-color__top) { display: flex; gap: 8px; margin-bottom: 10px; }
|
|
179
|
+
:global(.sv-color__native) { width: 36px; height: 34px; padding: 0; border: 1px solid var(--sg-border, #e2e8f0); border-radius: 8px; background: none; cursor: pointer; }
|
|
180
|
+
:global(.sv-color__field) {
|
|
181
|
+
flex: 1; min-width: 0; height: 34px; padding: 0 10px; font: inherit; font-size: 13px;
|
|
182
|
+
border: 1px solid var(--sg-input-border, #cbd5e1); border-radius: 8px; background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a); outline: none;
|
|
183
|
+
}
|
|
184
|
+
:global(.sv-color__palette) { display: grid; grid-template-columns: repeat(10, 1fr); gap: 4px; }
|
|
185
|
+
:global(.sv-color__chip) { width: 100%; aspect-ratio: 1; border-radius: 5px; border: 1px solid rgba(0,0,0,0.12); cursor: pointer; padding: 0; }
|
|
186
|
+
:global(.sv-color__chip.is-active) { outline: 2px solid var(--sg-accent, #2563eb); outline-offset: 1px; }
|
|
187
|
+
</style>
|
package/src/SvComboBox.svelte
CHANGED
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
let inputEl = $state<HTMLInputElement | null>(null)
|
|
66
66
|
let fieldEl = $state<HTMLDivElement | null>(null)
|
|
67
67
|
let panelEl = $state<HTMLDivElement | null>(null)
|
|
68
|
-
let rect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false })
|
|
68
|
+
let rect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false, maxHeight: 0, availHeight: 0 })
|
|
69
69
|
|
|
70
70
|
// Remote data source: fetch options as the query changes (debounced).
|
|
71
71
|
let remoteOptions = $state<ListOption[]>([])
|
|
@@ -111,7 +111,11 @@
|
|
|
111
111
|
|
|
112
112
|
function updatePos() {
|
|
113
113
|
if (!fieldEl) return
|
|
114
|
-
|
|
114
|
+
// estimatedHeight only picks the flip direction (up vs down); the panel's
|
|
115
|
+
// real max-height comes from availHeight below, so it takes its natural
|
|
116
|
+
// content height (options + group labels) and scrolls only when it truly
|
|
117
|
+
// overflows the room - never from an estimate that under-counts a row.
|
|
118
|
+
rect = anchoredRect(fieldEl.getBoundingClientRect(), { estimatedHeight: Math.min(combo.filtered.length, 8) * 34 + 12 })
|
|
115
119
|
}
|
|
116
120
|
|
|
117
121
|
// Position + reposition + outside-click close are render concerns (need the DOM).
|
|
@@ -150,7 +154,7 @@
|
|
|
150
154
|
</SvField>
|
|
151
155
|
|
|
152
156
|
{#if combo.open}
|
|
153
|
-
<div bind:this={panelEl} class="sv-ddl__panel" use:portalToBody use:popIn={{ up: rect.openUpward }} style:position="fixed" style:top={`${rect.top}px`} style:left={`${rect.left}px`} style:min-width={`${rect.width}px`} {...combo.listboxProps()}>
|
|
157
|
+
<div bind:this={panelEl} class="sv-ddl__panel" use:portalToBody use:popIn={{ up: rect.openUpward }} style:position="fixed" style:top={rect.openUpward ? undefined : `${rect.top}px`} style:bottom={rect.openUpward ? `${rect.bottom}px` : undefined} style:left={`${rect.left}px`} style:min-width={`${rect.width}px`} style:max-height={`${Math.min(rect.availHeight, 288)}px`} {...combo.listboxProps()}>
|
|
154
158
|
{#if loading}
|
|
155
159
|
<div class="sv-ddl__empty sv-ddl__loading"><svg class="sv-ddl__spin" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M21 12a9 9 0 1 1-6.2-8.6" /></svg>{M.loading}</div>
|
|
156
160
|
{:else if loadOptions && !searched && combo.query.trim().length < minLength}
|
package/src/SvCommand.svelte
CHANGED
|
@@ -16,10 +16,8 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { CommandItem } from './ui-app.types'
|
|
18
18
|
import { portalToBody, popIn } from './popover'
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import { createDismissableLayer } from './a11y/dismissable'
|
|
22
|
-
import { fuzzyScore } from './fuzzy'
|
|
19
|
+
import { createOverlay } from './createOverlay.svelte'
|
|
20
|
+
import { createCommand } from './createCommand.svelte'
|
|
23
21
|
|
|
24
22
|
type Props = {
|
|
25
23
|
open?: boolean
|
|
@@ -42,72 +40,29 @@
|
|
|
42
40
|
hotkey = 'mod+k',
|
|
43
41
|
}: Props = $props()
|
|
44
42
|
|
|
45
|
-
let query = $state('')
|
|
46
|
-
let active = $state(0)
|
|
47
43
|
let panelEl = $state<HTMLDivElement | null>(null)
|
|
48
44
|
let inputEl = $state<HTMLInputElement | null>(null)
|
|
49
45
|
|
|
50
|
-
const results = $derived.by(() => {
|
|
51
|
-
const q = query.trim()
|
|
52
|
-
const list = commands.filter((c) => !c.disabled)
|
|
53
|
-
if (!q) return list
|
|
54
|
-
return list
|
|
55
|
-
.map((c) => ({ c, s: fuzzyScore(`${c.label} ${c.keywords ?? ''} ${c.group ?? ''}`, q) }))
|
|
56
|
-
.filter((x) => x.s !== null)
|
|
57
|
-
.sort((a, b) => (b.s as number) - (a.s as number))
|
|
58
|
-
.map((x) => x.c)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
46
|
function close() { open = false; onClose?.() }
|
|
62
|
-
function run(cmd: CommandItem | undefined) {
|
|
63
|
-
if (!cmd || cmd.disabled) return
|
|
64
|
-
close()
|
|
65
|
-
onRun?.(cmd)
|
|
66
|
-
cmd.onRun?.()
|
|
67
|
-
}
|
|
68
|
-
function move(delta: number) {
|
|
69
|
-
const n = results.length
|
|
70
|
-
if (!n) return
|
|
71
|
-
active = (active + delta + n) % n
|
|
72
|
-
queueMicrotask(() =>
|
|
73
|
-
panelEl?.querySelector(`[data-idx="${active}"]`)?.scrollIntoView({ block: 'nearest' }),
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
function onKeydown(e: KeyboardEvent) {
|
|
77
|
-
if (e.key === 'ArrowDown') { e.preventDefault(); move(1) }
|
|
78
|
-
else if (e.key === 'ArrowUp') { e.preventDefault(); move(-1) }
|
|
79
|
-
else if (e.key === 'Enter') { e.preventDefault(); run(results[active]) }
|
|
80
|
-
// Escape + outside-click are owned by the dismissable layer.
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Reset active whenever the query changes.
|
|
84
|
-
$effect(() => { void query; active = 0 })
|
|
85
47
|
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
48
|
+
// Fuzzy filter + roving + run + hotkey live in the command core; the overlay
|
|
49
|
+
// lifecycle (focus-trap into the input, scroll-lock, Escape/outside dismissal)
|
|
50
|
+
// is the shared dialog core.
|
|
51
|
+
const cmd = createCommand({
|
|
52
|
+
commands: () => commands,
|
|
53
|
+
open: () => open,
|
|
54
|
+
onRun,
|
|
55
|
+
onClose: close,
|
|
56
|
+
onToggleOpen: () => (open = !open),
|
|
57
|
+
hotkey: () => hotkey,
|
|
58
|
+
scrollActiveIntoView: (i) =>
|
|
59
|
+
queueMicrotask(() => panelEl?.querySelector(`[data-idx="${i}"]`)?.scrollIntoView({ block: 'nearest' })),
|
|
97
60
|
})
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const onKey = (e: KeyboardEvent) => {
|
|
104
|
-
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === key) {
|
|
105
|
-
e.preventDefault()
|
|
106
|
-
open = !open
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
window.addEventListener('keydown', onKey)
|
|
110
|
-
return () => window.removeEventListener('keydown', onKey)
|
|
61
|
+
const overlay = createOverlay({
|
|
62
|
+
open: () => open,
|
|
63
|
+
getDialog: () => panelEl,
|
|
64
|
+
onClose: close,
|
|
65
|
+
initialFocus: () => inputEl,
|
|
111
66
|
})
|
|
112
67
|
</script>
|
|
113
68
|
|
|
@@ -117,11 +72,8 @@
|
|
|
117
72
|
bind:this={panelEl}
|
|
118
73
|
class="sv-cmd"
|
|
119
74
|
use:popIn={{}}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
aria-label="Command palette"
|
|
123
|
-
tabindex="-1"
|
|
124
|
-
onkeydown={onKeydown}
|
|
75
|
+
{...overlay.dialogProps({ label: 'Command palette' })}
|
|
76
|
+
onkeydown={cmd.onKeydown}
|
|
125
77
|
>
|
|
126
78
|
<div class="sv-cmd__search">
|
|
127
79
|
<span class="sv-cmd__searchicon" aria-hidden="true">⌕</span>
|
|
@@ -129,29 +81,20 @@
|
|
|
129
81
|
bind:this={inputEl}
|
|
130
82
|
class="sv-cmd__input"
|
|
131
83
|
type="text"
|
|
132
|
-
|
|
133
|
-
aria-expanded="true"
|
|
134
|
-
aria-controls="sv-cmd-list"
|
|
135
|
-
aria-activedescendant={results[active] ? `sv-cmd-opt-${active}` : undefined}
|
|
84
|
+
aria-label="Command palette search"
|
|
136
85
|
{placeholder}
|
|
137
|
-
|
|
138
|
-
oninput={(e) => (query = e.currentTarget.value)}
|
|
86
|
+
{...cmd.inputProps()}
|
|
139
87
|
/>
|
|
140
88
|
</div>
|
|
141
|
-
<ul class="sv-cmd__list"
|
|
142
|
-
{#each results as c, i (c.id)}
|
|
143
|
-
{#if !query.trim() && c.group && c.group !== results[i - 1]?.group}
|
|
89
|
+
<ul class="sv-cmd__list" aria-label="Commands" {...cmd.listProps()}>
|
|
90
|
+
{#each cmd.results as c, i (c.id)}
|
|
91
|
+
{#if !cmd.query.trim() && c.group && c.group !== cmd.results[i - 1]?.group}
|
|
144
92
|
<li class="sv-cmd__group" role="presentation">{c.group}</li>
|
|
145
93
|
{/if}
|
|
146
94
|
<li
|
|
147
|
-
id={`sv-cmd-opt-${i}`}
|
|
148
|
-
data-idx={i}
|
|
149
95
|
class="sv-cmd__opt"
|
|
150
|
-
class:is-active={i
|
|
151
|
-
|
|
152
|
-
aria-selected={i === active}
|
|
153
|
-
onpointermove={() => (active = i)}
|
|
154
|
-
onclick={() => run(c)}
|
|
96
|
+
class:is-active={cmd.isActive(i)}
|
|
97
|
+
{...cmd.optionProps(i)}
|
|
155
98
|
>
|
|
156
99
|
{#if c.icon}<span class="sv-cmd__icon" aria-hidden="true">{c.icon}</span>{/if}
|
|
157
100
|
<span class="sv-cmd__label">{c.label}</span>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component tests for SvCommand, exercising the shared createCommand core: the
|
|
3
|
+
* global hotkey toggles it open, typing fuzzy-filters the list, arrow+Enter runs
|
|
4
|
+
* the active command, and clicking a command runs it and closes the palette.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, expect, it, vi, afterEach } from 'vitest'
|
|
7
|
+
import { mount, unmount, flushSync } from 'svelte'
|
|
8
|
+
import SvCommand, { type CommandItem } from './SvCommand.svelte'
|
|
9
|
+
|
|
10
|
+
function mountCmd(props: Record<string, unknown>) {
|
|
11
|
+
const target = document.createElement('div')
|
|
12
|
+
document.body.appendChild(target)
|
|
13
|
+
const app = mount(SvCommand, { target, props: props as any })
|
|
14
|
+
flushSync()
|
|
15
|
+
return { destroy: () => { unmount(app); target.remove() } }
|
|
16
|
+
}
|
|
17
|
+
const palette = () => document.querySelector<HTMLElement>('.sv-cmd')
|
|
18
|
+
const options = () => [...document.querySelectorAll<HTMLElement>('.sv-cmd__opt')]
|
|
19
|
+
const input = () => document.querySelector<HTMLInputElement>('.sv-cmd__input')
|
|
20
|
+
afterEach(() => document.querySelectorAll('.sv-cmd__backdrop').forEach((n) => n.remove()))
|
|
21
|
+
|
|
22
|
+
const commands: CommandItem[] = [
|
|
23
|
+
{ id: 'a', label: 'New File' },
|
|
24
|
+
{ id: 'b', label: 'Open Folder' },
|
|
25
|
+
{ id: 'c', label: 'Save All' },
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
describe('SvCommand', () => {
|
|
29
|
+
it('opens on the Cmd/Ctrl+K hotkey', () => {
|
|
30
|
+
const { destroy } = mountCmd({ open: false, commands })
|
|
31
|
+
try {
|
|
32
|
+
expect(palette()).toBeNull()
|
|
33
|
+
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'k', ctrlKey: true, bubbles: true }))
|
|
34
|
+
flushSync()
|
|
35
|
+
expect(palette()).not.toBeNull()
|
|
36
|
+
expect(options().length).toBe(3)
|
|
37
|
+
} finally { destroy() }
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('fuzzy-filters as you type', () => {
|
|
41
|
+
const { destroy } = mountCmd({ open: true, commands })
|
|
42
|
+
try {
|
|
43
|
+
const el = input()!
|
|
44
|
+
el.value = 'save'
|
|
45
|
+
el.dispatchEvent(new Event('input', { bubbles: true }))
|
|
46
|
+
flushSync()
|
|
47
|
+
const labels = options().map((o) => o.textContent)
|
|
48
|
+
expect(labels.length).toBe(1)
|
|
49
|
+
expect(labels[0]).toContain('Save All')
|
|
50
|
+
} finally { destroy() }
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('runs the active command on Enter and closes', () => {
|
|
54
|
+
const onRun = vi.fn()
|
|
55
|
+
const { destroy } = mountCmd({ open: true, commands, onRun })
|
|
56
|
+
try {
|
|
57
|
+
const el = input()!
|
|
58
|
+
el.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }))
|
|
59
|
+
flushSync()
|
|
60
|
+
el.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }))
|
|
61
|
+
flushSync()
|
|
62
|
+
expect(onRun).toHaveBeenCalledTimes(1)
|
|
63
|
+
expect(onRun.mock.calls[0]![0].id).toBe('b') // ArrowDown moved 0 -> 1
|
|
64
|
+
expect(palette()).toBeNull()
|
|
65
|
+
} finally { destroy() }
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('runs a command on click and closes', () => {
|
|
69
|
+
const onRun = vi.fn()
|
|
70
|
+
const { destroy } = mountCmd({ open: true, commands, onRun })
|
|
71
|
+
try {
|
|
72
|
+
options()[2]!.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
73
|
+
flushSync()
|
|
74
|
+
expect(onRun.mock.calls[0]![0].id).toBe('c')
|
|
75
|
+
expect(palette()).toBeNull()
|
|
76
|
+
} finally { destroy() }
|
|
77
|
+
})
|
|
78
|
+
})
|