@svgrid/grid 2.2.16 → 2.2.17
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 +1 -1
- package/dist/GridFooter.svelte +12 -11
- package/dist/SvAnchor.svelte +65 -0
- package/dist/SvAnchor.svelte.d.ts +24 -0
- package/dist/SvAspectRatio.svelte +38 -0
- package/dist/SvAspectRatio.svelte.d.ts +16 -0
- package/dist/SvAutoComplete.svelte +52 -7
- package/dist/SvAutoComplete.svelte.d.ts +4 -1
- package/dist/SvBlockquote.svelte +43 -0
- package/dist/SvBlockquote.svelte.d.ts +16 -0
- package/dist/SvCalendar.svelte +33 -2
- package/dist/SvCalendar.svelte.d.ts +8 -1
- package/dist/SvCheckBox.svelte +6 -2
- package/dist/SvCode.svelte +52 -0
- package/dist/SvCode.svelte.d.ts +19 -0
- package/dist/SvCollapsible.svelte +75 -0
- package/dist/SvCollapsible.svelte.d.ts +27 -0
- package/dist/SvColorInput.svelte +11 -17
- package/dist/SvColorInput.svelte.d.ts +2 -0
- package/dist/SvComboBox.svelte +85 -8
- package/dist/SvComboBox.svelte.d.ts +14 -0
- package/dist/SvCountryInput.svelte +2 -2
- package/dist/SvCountryInput.svelte.d.ts +1 -1
- package/dist/SvDropDownList.svelte +154 -51
- package/dist/SvDropDownList.svelte.d.ts +17 -3
- package/dist/SvDurationInput.svelte +59 -41
- package/dist/SvDurationInput.svelte.d.ts +18 -0
- package/dist/SvField.svelte +203 -12
- package/dist/SvField.svelte.d.ts +45 -8
- package/dist/SvForm.svelte +195 -21
- package/dist/SvForm.svelte.d.ts +23 -0
- package/dist/SvGrid.controller.svelte.d.ts +12 -1
- package/dist/SvGrid.controller.svelte.js +87 -7
- package/dist/SvGrid.svelte +383 -181
- package/dist/SvGrid.types.d.ts +123 -2
- package/dist/SvGridChartView.svelte +70 -0
- package/dist/SvGridChartView.svelte.d.ts +11 -0
- package/dist/SvGridDropdown.svelte +9 -2
- package/dist/SvGridSelect.svelte +5 -2
- package/dist/SvGridSelect.svelte.d.ts +1 -1
- package/dist/SvGroup.svelte +41 -0
- package/dist/SvGroup.svelte.d.ts +27 -0
- package/dist/SvHoverCard.svelte +70 -0
- package/dist/SvHoverCard.svelte.d.ts +41 -0
- package/dist/SvKbd.svelte +51 -0
- package/dist/SvKbd.svelte.d.ts +18 -0
- package/dist/SvList.svelte +55 -0
- package/dist/SvList.svelte.d.ts +23 -0
- package/dist/SvListBox.svelte +166 -77
- package/dist/SvListBox.svelte.d.ts +17 -4
- package/dist/SvLoadingOverlay.svelte +53 -0
- package/dist/SvLoadingOverlay.svelte.d.ts +28 -0
- package/dist/SvMark.svelte +36 -0
- package/dist/SvMark.svelte.d.ts +14 -0
- package/dist/SvMaskedInput.svelte +50 -35
- package/dist/SvMaskedInput.svelte.d.ts +17 -2
- package/dist/SvMenuList.svelte +32 -3
- package/dist/SvMenubar.svelte +200 -0
- package/dist/SvMenubar.svelte.d.ts +18 -0
- package/dist/SvMultiSelect.svelte +7 -4
- package/dist/SvMultiSelect.svelte.d.ts +1 -1
- package/dist/SvNumberInput.svelte +79 -42
- package/dist/SvNumberInput.svelte.d.ts +25 -0
- package/dist/SvOtpInput.svelte +2 -2
- package/dist/SvPasswordInput.svelte +51 -31
- package/dist/SvPasswordInput.svelte.d.ts +17 -0
- package/dist/SvPhoneInput.svelte +39 -28
- package/dist/SvPhoneInput.svelte.d.ts +4 -0
- package/dist/SvPopconfirm.svelte +79 -0
- package/dist/SvPopconfirm.svelte.d.ts +42 -0
- package/dist/SvPopover.svelte +87 -29
- package/dist/SvPopover.svelte.d.ts +18 -4
- package/dist/SvRadioGroup.svelte +5 -1
- package/dist/SvResult.svelte +73 -0
- package/dist/SvResult.svelte.d.ts +28 -0
- package/dist/SvSegmented.svelte +114 -0
- package/dist/SvSegmented.svelte.d.ts +29 -0
- package/dist/SvSimpleGrid.svelte +41 -0
- package/dist/SvSimpleGrid.svelte.d.ts +25 -0
- package/dist/SvSlider.svelte +23 -10
- package/dist/SvSpinner.svelte +43 -0
- package/dist/SvSpinner.svelte.d.ts +20 -0
- package/dist/SvStack.svelte +37 -0
- package/dist/SvStack.svelte.d.ts +24 -0
- package/dist/SvSwitchButton.svelte +6 -1
- package/dist/SvTagsInput.svelte +9 -5
- package/dist/SvText.svelte +76 -0
- package/dist/SvText.svelte.d.ts +31 -0
- package/dist/SvTextInput.svelte +75 -43
- package/dist/SvTextInput.svelte.d.ts +28 -1
- package/dist/SvTimePicker.svelte +32 -2
- package/dist/SvTimePicker.svelte.d.ts +8 -9
- package/dist/SvTitle.svelte +57 -0
- package/dist/SvTitle.svelte.d.ts +24 -0
- package/dist/SvToaster.svelte +41 -6
- package/dist/SvToggleButton.svelte +4 -1
- package/dist/SvTooltip.svelte +68 -34
- package/dist/SvTooltip.svelte.d.ts +14 -2
- package/dist/SvTree.svelte +81 -21
- package/dist/SvTree.svelte.d.ts +3 -2
- package/dist/SvTreeSelect.svelte +5 -2
- package/dist/SvTreeSelect.svelte.d.ts +1 -1
- package/dist/SvVisuallyHidden.svelte +49 -0
- package/dist/SvVisuallyHidden.svelte.d.ts +19 -0
- package/dist/ai.d.ts +289 -0
- package/dist/ai.js +1013 -0
- package/dist/board-view.svelte.d.ts +22 -0
- package/dist/board-view.svelte.js +13 -0
- package/dist/build-api.js +36 -6
- package/dist/cdn/GridMenus-0AK9O7qG.js +420 -0
- package/dist/cdn/GridMenus-BjUoCXoD.js +416 -0
- package/dist/cdn/SvDateTimePicker-CYC7gRER.js +2176 -0
- package/dist/cdn/SvDateTimePicker-DtQKd4Ns.js +2198 -0
- package/dist/cdn/SvGridChart-BcLCYvw9.js +1195 -0
- package/dist/cdn/SvGridChart-BhnFdLd0.js +1203 -0
- package/dist/cdn/SvGridChartPanel-Cmx9DHAS.js +561 -0
- package/dist/cdn/SvGridChartPanel-DvjwxLvK.js +577 -0
- package/dist/cdn/SvGridChartView-B40DTJSX.js +54 -0
- package/dist/cdn/SvGridChartView-PwqI4s02.js +53 -0
- package/dist/cdn/SvGridDropdown-DjALc_4f.js +255 -0
- package/dist/cdn/SvGridDropdown-hpnHvOGt.js +254 -0
- package/dist/cdn/chart-T1usDN0o.js +1212 -0
- package/dist/cdn/disclose-version-DoD9AFD_.js +2958 -0
- package/dist/cdn/export-format-JT5pWVT8.js +561 -0
- package/dist/cdn/popover-P-9P2YYn.js +69 -0
- package/dist/cdn/rolldown-runtime-Dy4uBu1J.js +11 -0
- package/dist/cdn/src-BLhdLmoN.js +25707 -0
- package/dist/cdn/src-BwnUVj3T.js +25623 -0
- package/dist/cdn/svgrid.js +9 -32897
- package/dist/cdn/svgrid.svelte-external.js +9 -30052
- package/dist/cell-formatting.d.ts +2 -2
- package/dist/cell-render.js +4 -4
- package/dist/chart-view.svelte.d.ts +25 -0
- package/dist/chart-view.svelte.js +14 -0
- package/dist/createCombobox.svelte.d.ts +5 -0
- package/dist/createCombobox.svelte.js +21 -5
- package/dist/createDropdownList.svelte.d.ts +3 -0
- package/dist/createDropdownList.svelte.js +8 -2
- package/dist/createForm.svelte.d.ts +18 -0
- package/dist/createForm.svelte.js +51 -6
- package/dist/createMenu.svelte.js +27 -0
- package/dist/createPopoverSelect.svelte.d.ts +3 -0
- package/dist/createPopoverSelect.svelte.js +9 -1
- package/dist/createSlider.svelte.d.ts +8 -21
- package/dist/createSlider.svelte.js +34 -0
- package/dist/createTooltip.svelte.d.ts +12 -0
- package/dist/createTooltip.svelte.js +72 -9
- package/dist/editor-contract.d.ts +15 -0
- package/dist/export-provider.d.ts +27 -0
- package/dist/export-provider.js +9 -0
- package/dist/filter-operators.d.ts +6 -0
- package/dist/filter-operators.js +33 -0
- package/dist/form-field.d.ts +45 -2
- package/dist/grid-messages.d.ts +61 -0
- package/dist/grid-messages.js +72 -0
- package/dist/group-display.d.ts +62 -0
- package/dist/group-display.js +78 -0
- package/dist/index.d.ts +36 -4
- package/dist/index.js +43 -2
- package/dist/js-scroller.svelte.d.ts +35 -0
- package/dist/js-scroller.svelte.js +154 -0
- package/dist/list-option.d.ts +46 -0
- package/dist/list-option.js +53 -0
- package/dist/menus.js +25 -11
- package/dist/pivot-view.svelte.d.ts +72 -0
- package/dist/pivot-view.svelte.js +13 -0
- package/dist/positioning.d.ts +111 -0
- package/dist/positioning.js +191 -0
- package/dist/scheduler-model.d.ts +3 -0
- package/dist/scheduler-model.js +22 -5
- package/dist/svgrid-wrapper.types.d.ts +18 -0
- package/dist/toast-store.svelte.d.ts +54 -0
- package/dist/toast-store.svelte.js +65 -15
- package/package.json +9 -1
- package/src/GridFooter.svelte +12 -11
- package/src/SvAnchor.svelte +65 -0
- package/src/SvAspectRatio.svelte +38 -0
- package/src/SvAutoComplete.svelte +52 -7
- package/src/SvBlockquote.svelte +43 -0
- package/src/SvCalendar.svelte +33 -2
- package/src/SvCalendar.test.ts +15 -0
- package/src/SvCheckBox.svelte +6 -2
- package/src/SvCode.svelte +52 -0
- package/src/SvCollapsible.svelte +75 -0
- package/src/SvColorInput.svelte +11 -17
- package/src/SvComboBox.svelte +85 -8
- package/src/SvCountryInput.svelte +2 -2
- package/src/SvDropDownList.svelte +154 -51
- package/src/SvDurationInput.svelte +59 -41
- package/src/SvField.svelte +203 -12
- package/src/SvForm.svelte +195 -21
- package/src/SvForm.test.ts +112 -0
- package/src/SvGrid.controller.svelte.ts +88 -7
- package/src/SvGrid.svelte +383 -181
- package/src/SvGrid.types.ts +125 -1
- package/src/SvGridChartView.svelte +70 -0
- package/src/SvGridDropdown.svelte +9 -2
- package/src/SvGridSelect.svelte +5 -2
- package/src/SvGroup.svelte +41 -0
- package/src/SvHoverCard.svelte +70 -0
- package/src/SvKbd.svelte +51 -0
- package/src/SvList.svelte +55 -0
- package/src/SvListBox.svelte +166 -77
- package/src/SvLoadingOverlay.svelte +53 -0
- package/src/SvMark.svelte +36 -0
- package/src/SvMaskedInput.svelte +50 -35
- package/src/SvMenuList.svelte +32 -3
- package/src/SvMenubar.svelte +200 -0
- package/src/SvMultiSelect.svelte +7 -4
- package/src/SvNumberInput.svelte +79 -42
- package/src/SvOtpInput.svelte +2 -2
- package/src/SvPasswordInput.svelte +51 -31
- package/src/SvPhoneInput.svelte +39 -28
- package/src/SvPopconfirm.svelte +79 -0
- package/src/SvPopover.svelte +87 -29
- package/src/SvRadioGroup.svelte +5 -1
- package/src/SvResult.svelte +73 -0
- package/src/SvSegmented.svelte +114 -0
- package/src/SvSimpleGrid.svelte +41 -0
- package/src/SvSlider.svelte +23 -10
- package/src/SvSpinner.svelte +43 -0
- package/src/SvStack.svelte +37 -0
- package/src/SvSwitchButton.svelte +6 -1
- package/src/SvTagsInput.svelte +9 -5
- package/src/SvText.svelte +76 -0
- package/src/SvTextInput.svelte +75 -43
- package/src/SvTimePicker.svelte +32 -2
- package/src/SvTitle.svelte +57 -0
- package/src/SvToaster.svelte +41 -6
- package/src/SvToaster.test.ts +32 -0
- package/src/SvToggleButton.svelte +4 -1
- package/src/SvTooltip.svelte +68 -34
- package/src/SvTree.svelte +81 -21
- package/src/SvTreeSelect.svelte +5 -2
- package/src/SvVisuallyHidden.svelte +49 -0
- package/src/ai.test.ts +502 -0
- package/src/ai.ts +1391 -0
- package/src/board-view.svelte.ts +33 -0
- package/src/build-api.coverage.test.ts +101 -0
- package/src/build-api.ts +43 -18
- package/src/builtin-editors.grid.test.ts +1 -1
- package/src/cell-formatting.ts +5 -5
- package/src/cell-render.ts +4 -4
- package/src/chart-view.svelte.ts +36 -0
- package/src/createCombobox.svelte.ts +17 -5
- package/src/createDropdownList.svelte.ts +8 -2
- package/src/createForm.svelte.ts +59 -6
- package/src/createMenu.svelte.ts +23 -0
- package/src/createPopoverSelect.svelte.ts +8 -1
- package/src/createSlider.svelte.ts +20 -0
- package/src/createTooltip.svelte.ts +67 -9
- package/src/editor-contract.ts +16 -0
- package/src/export-provider.ts +40 -0
- package/src/filter-operators.ts +37 -0
- package/src/form-field.ts +42 -2
- package/src/grid-messages.test.ts +24 -0
- package/src/grid-messages.ts +137 -0
- package/src/group-display.test.ts +77 -0
- package/src/group-display.ts +117 -0
- package/src/i18n.grid.test.ts +73 -0
- package/src/index.ts +91 -2
- package/src/js-scroller.svelte.ts +173 -0
- package/src/list-option.test.ts +56 -0
- package/src/list-option.ts +81 -0
- package/src/menus.test.ts +32 -0
- package/src/menus.ts +25 -12
- package/src/pivot-view.svelte.ts +90 -0
- package/src/pivot-view.test.ts +38 -0
- package/src/pivot.grid.test.ts +102 -0
- package/src/positioning.test.ts +141 -0
- package/src/positioning.ts +266 -0
- package/src/scheduler-model.test.ts +16 -0
- package/src/scheduler-model.ts +24 -5
- package/src/svgrid-wrapper.types.ts +23 -0
- package/src/svgrid.charting.test.ts +8 -1
- package/src/svgrid.comments-autocomplete.test.ts +7 -2
- package/src/svgrid.context-menu.test.ts +8 -3
- package/src/toast-store.svelte.ts +98 -0
- package/src/toast-store.test.ts +62 -1
- package/src/ui-breadth.test.ts +138 -0
- package/src/ui-buttons.test.ts +39 -0
- package/src/ui-inputs.test.ts +2 -2
- package/src/ui-range.test.ts +9 -0
- package/src/ui-selection.test.ts +36 -0
- package/src/ui-tier1.test.ts +1 -1
- package/src/virtual.test.ts +20 -6
- package/dist/SvGridBoard.svelte +0 -2339
- package/dist/SvGridBoard.svelte.d.ts +0 -31
- package/src/SvGridBoard.svelte +0 -2339
- package/src/board.test.ts +0 -927
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SvList - a themed ordered / unordered / unstyled list. Pass `items` for the
|
|
3
|
+
* simple case, or a `children` snippet for full control over each `<li>`.
|
|
4
|
+
*
|
|
5
|
+
* <SvList items={['One', 'Two', 'Three']} />
|
|
6
|
+
* <SvList type="ordered" spacing="lg" items={steps} />
|
|
7
|
+
*/
|
|
8
|
+
import type { Snippet } from 'svelte';
|
|
9
|
+
type Props = {
|
|
10
|
+
/** Marker style. `none` drops markers + indentation. */
|
|
11
|
+
type?: 'unordered' | 'ordered' | 'none';
|
|
12
|
+
/** Simple string items (skip the snippet). */
|
|
13
|
+
items?: string[];
|
|
14
|
+
/** Vertical gap between items. */
|
|
15
|
+
spacing?: 'sm' | 'md' | 'lg';
|
|
16
|
+
/** Font size step. */
|
|
17
|
+
size?: 'sm' | 'md' | 'lg';
|
|
18
|
+
/** Custom list content (`<li>` elements). Overrides `items`. */
|
|
19
|
+
children?: Snippet;
|
|
20
|
+
};
|
|
21
|
+
declare const SvList: import("svelte").Component<Props, {}, "">;
|
|
22
|
+
type SvList = ReturnType<typeof SvList>;
|
|
23
|
+
export default SvList;
|
package/dist/SvListBox.svelte
CHANGED
|
@@ -3,11 +3,20 @@
|
|
|
3
3
|
* SvListBox - an inline single/multi-select list (WAI-ARIA listbox) with
|
|
4
4
|
* roving highlight + full keyboard. Parity: Smart `smart-list-box`. Controlled
|
|
5
5
|
* via `value` (scalar or array) + `onChange`. Scales to huge option sets with
|
|
6
|
-
* `virtual`
|
|
6
|
+
* `virtual` - windowing over the shared `createSvelteVirtualizer` (slot-keyed
|
|
7
|
+
* DOM recycling), which also windows GROUPED lists and mixes row heights.
|
|
8
|
+
*
|
|
9
|
+
* Virtual mode uses a JS-DRIVEN scroller (like Smart): the option list does
|
|
10
|
+
* NOT use native overflow scroll - a single `scrollOffset` state positions the
|
|
11
|
+
* rows (`translateY`) AND drives the virtualizer window in the same reactive
|
|
12
|
+
* flush. Native scroll is composited off the main thread, so it paints the
|
|
13
|
+
* empty scrolled position a frame before JS can re-window (the "blank on a
|
|
14
|
+
* fast thumb-drag" flash); driving the offset in JS removes that async gap.
|
|
7
15
|
*/
|
|
8
|
-
import { type Snippet } from 'svelte'
|
|
9
|
-
import { groupOptions,
|
|
10
|
-
import {
|
|
16
|
+
import { untrack, type Snippet } from 'svelte'
|
|
17
|
+
import { groupOptions, normalizeOptions, flattenForVirtual, type ListOption, type IndexedOption, type RowHeight } from './list-option'
|
|
18
|
+
import { createSvelteVirtualizer } from './virtualization/svelte-virtualizer.svelte'
|
|
19
|
+
import { createJsScroller } from './js-scroller.svelte'
|
|
11
20
|
import { createListbox } from './createListbox.svelte'
|
|
12
21
|
import SvField from './SvField.svelte'
|
|
13
22
|
import { nextEditorId, type SvEditorProps } from './editor-contract'
|
|
@@ -21,9 +30,14 @@
|
|
|
21
30
|
rows?: number
|
|
22
31
|
/** Window the list (render only visible rows) for large option sets. */
|
|
23
32
|
virtual?: boolean
|
|
24
|
-
/**
|
|
25
|
-
|
|
33
|
+
/** Row height in px - a number, or a per-option function for variable
|
|
34
|
+
* heights. Must match the rendered row height. Default 32. */
|
|
35
|
+
rowHeight?: RowHeight
|
|
36
|
+
/** Height (px) of a group heading row when virtualized + grouped. Default 28. */
|
|
37
|
+
groupHeaderHeight?: number
|
|
26
38
|
/** Custom per-option content. Receives the option. */
|
|
39
|
+
item?: Snippet<[ListOption]>
|
|
40
|
+
/** @deprecated Use `item`. */
|
|
27
41
|
itemTemplate?: Snippet<[ListOption]>
|
|
28
42
|
}
|
|
29
43
|
|
|
@@ -33,9 +47,12 @@
|
|
|
33
47
|
onChange,
|
|
34
48
|
multiple = false,
|
|
35
49
|
disabled = false,
|
|
50
|
+
size = 'md',
|
|
36
51
|
rows = 7,
|
|
37
52
|
virtual = false,
|
|
38
53
|
rowHeight = 32,
|
|
54
|
+
groupHeaderHeight = 28,
|
|
55
|
+
item,
|
|
39
56
|
itemTemplate,
|
|
40
57
|
ariaLabel,
|
|
41
58
|
name,
|
|
@@ -52,9 +69,13 @@
|
|
|
52
69
|
const autoId = nextEditorId('sv-lb')
|
|
53
70
|
const uid = $derived(id ?? autoId)
|
|
54
71
|
|
|
72
|
+
// Tolerate primitive / partial options (e.g. setOptions([1, 2, 3])): normalize
|
|
73
|
+
// to { value, label } so labels render and keys are never undefined.
|
|
74
|
+
const norm = $derived(normalizeOptions(options))
|
|
75
|
+
|
|
55
76
|
// The styled listbox is just a renderer over the headless core.
|
|
56
77
|
const lb = createListbox({
|
|
57
|
-
options: () =>
|
|
78
|
+
options: () => norm,
|
|
58
79
|
value: () => value,
|
|
59
80
|
onChange: (v) => onChange?.(v),
|
|
60
81
|
multiple: () => multiple,
|
|
@@ -68,26 +89,63 @@
|
|
|
68
89
|
})
|
|
69
90
|
const isSel = (o: ListOption) => lb.isSelected(o.value)
|
|
70
91
|
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
92
|
+
const rowHeightBase = $derived(typeof rowHeight === 'number' ? rowHeight : 32)
|
|
93
|
+
const optHeight = (o: IndexedOption) => (typeof rowHeight === 'function' ? rowHeight(o, o.index) : rowHeight)
|
|
94
|
+
|
|
95
|
+
// --- Virtualization ---------------------------------------------------------
|
|
96
|
+
const useVirtual = $derived(virtual)
|
|
97
|
+
let listEl = $state<HTMLDivElement | null>(null)
|
|
75
98
|
let viewportH = $state(0)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
79
|
-
const
|
|
80
|
-
|
|
99
|
+
|
|
100
|
+
const flat = $derived(flattenForVirtual(norm, { rowHeight, groupHeaderHeight }))
|
|
101
|
+
const uniform = $derived(!flat.hasGroups && typeof rowHeight === 'number')
|
|
102
|
+
const estimateSize = $derived(uniform ? rowHeightBase : (i: number) => flat.sizeAt(i))
|
|
103
|
+
|
|
104
|
+
const OVERSCAN = 6
|
|
105
|
+
// Effective viewport height: measured, or the computed `rows` height until the
|
|
106
|
+
// viewport element is measured. Never 0.
|
|
107
|
+
const effViewport = $derived(viewportH > 0 ? viewportH : Math.max(1, rows * rowHeightBase))
|
|
108
|
+
|
|
109
|
+
const virtualizer = createSvelteVirtualizer(
|
|
110
|
+
untrack(() => ({ count: flat.entries.length, estimateSize, overscan: OVERSCAN, viewportHeight: effViewport })),
|
|
81
111
|
)
|
|
82
112
|
|
|
83
|
-
|
|
84
|
-
|
|
113
|
+
$effect(() => {
|
|
114
|
+
const count = flat.entries.length
|
|
115
|
+
const es = estimateSize
|
|
116
|
+
untrack(() => virtualizer.setOptions({ count, estimateSize: es }))
|
|
117
|
+
})
|
|
118
|
+
$effect(() => {
|
|
119
|
+
const vh = effViewport
|
|
120
|
+
untrack(() => virtualizer.setViewportHeight(vh))
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const vItems = $derived.by(() => {
|
|
124
|
+
virtualizer.version
|
|
125
|
+
return virtualizer.getVirtualItems()
|
|
126
|
+
})
|
|
127
|
+
const totalSize = $derived.by(() => {
|
|
128
|
+
virtualizer.version
|
|
129
|
+
return virtualizer.getTotalSize()
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
// JS-driven scroll: one offset positions the rows AND drives the virtualizer
|
|
133
|
+
// window in the same flush - no native scroll, so no async "blank flash".
|
|
134
|
+
const scroller = createJsScroller({
|
|
135
|
+
virtualizer,
|
|
136
|
+
totalSize: () => totalSize,
|
|
137
|
+
viewport: () => effViewport,
|
|
138
|
+
lineStep: () => rowHeightBase,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// Keep the active option in view - via the scroller (virtual) or scrollIntoView
|
|
142
|
+
// (native non-virtual). Driven by roving keyboard nav.
|
|
85
143
|
$effect(() => {
|
|
86
144
|
const i = lb.activeIndex
|
|
87
145
|
if (useVirtual) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
146
|
+
const flatIdx = flat.optionFlatIndex[i]
|
|
147
|
+
if (flatIdx == null) return
|
|
148
|
+
untrack(() => scroller.ensureIndexVisible(flatIdx))
|
|
91
149
|
} else {
|
|
92
150
|
queueMicrotask(() => listEl?.querySelector<HTMLElement>(`[data-idx="${i}"]`)?.scrollIntoView({ block: 'nearest' }))
|
|
93
151
|
}
|
|
@@ -95,103 +153,134 @@
|
|
|
95
153
|
</script>
|
|
96
154
|
|
|
97
155
|
<SvField id={uid} {label} {hint} {error} {required} {dir}>
|
|
98
|
-
|
|
156
|
+
<!-- Div-based listbox (role comes from `lb.rootProps()`). -->
|
|
157
|
+
<div
|
|
99
158
|
bind:this={listEl}
|
|
100
|
-
class="sv-listbox"
|
|
159
|
+
class="sv-listbox sv-listbox--{size}"
|
|
101
160
|
class:is-invalid={invalid}
|
|
102
161
|
class:is-virtual={useVirtual}
|
|
103
162
|
style:--sv-rows={rows}
|
|
104
|
-
style:--sv-row-h={`${
|
|
105
|
-
|
|
106
|
-
bind:clientHeight={viewportH}
|
|
163
|
+
style:--sv-row-h={`${rowHeightBase}px`}
|
|
164
|
+
onwheel={useVirtual ? scroller.onWheel : undefined}
|
|
107
165
|
{...lb.rootProps()}
|
|
108
166
|
>
|
|
109
167
|
{#if useVirtual}
|
|
110
|
-
<!--
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
168
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
169
|
+
<div
|
|
170
|
+
class="sv-listbox__vp"
|
|
171
|
+
bind:clientHeight={viewportH}
|
|
172
|
+
onpointerdown={scroller.onContentDown}
|
|
173
|
+
onpointermove={scroller.onContentMove}
|
|
174
|
+
onpointerup={scroller.onContentUp}
|
|
175
|
+
onpointercancel={scroller.onContentUp}
|
|
176
|
+
>
|
|
177
|
+
{#each vItems as vi (vi.key)}
|
|
178
|
+
{@const entry = flat.entries[vi.index]}
|
|
179
|
+
{#if entry?.type === 'group'}
|
|
180
|
+
<div class="sv-listbox__group" role="presentation" style:height={`${vi.size}px`} style:transform={`translateY(${vi.start - scroller.scrollOffset}px)`}>{entry.label}</div>
|
|
181
|
+
{:else if entry}
|
|
182
|
+
{@render optionRow(entry.opt, entry.opt.index, vi.size, vi.start - scroller.scrollOffset)}
|
|
183
|
+
{/if}
|
|
184
|
+
{/each}
|
|
185
|
+
</div>
|
|
186
|
+
{#if scroller.maxOffset > 0}
|
|
187
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
188
|
+
<div class="sv-listbox__sb" onpointerdown={scroller.onTrackDown} aria-hidden="true">
|
|
189
|
+
<div
|
|
190
|
+
class="sv-listbox__thumb"
|
|
191
|
+
class:is-dragging={scroller.dragging}
|
|
192
|
+
style:height={`${scroller.thumbH}px`}
|
|
193
|
+
style:transform={`translateY(${scroller.thumbTop}px)`}
|
|
194
|
+
onpointerdown={scroller.onThumbDown}
|
|
195
|
+
onpointermove={scroller.onThumbMove}
|
|
196
|
+
onpointerup={scroller.onThumbUp}
|
|
197
|
+
onpointercancel={scroller.onThumbUp}
|
|
198
|
+
></div>
|
|
199
|
+
</div>
|
|
200
|
+
{/if}
|
|
123
201
|
{:else}
|
|
124
|
-
{#each groupOptions(
|
|
125
|
-
{#if g.group != null}<
|
|
126
|
-
{#each g.options as opt (opt.
|
|
127
|
-
{@render
|
|
202
|
+
{#each groupOptions(norm) as g (g.group ?? ' ')}
|
|
203
|
+
{#if g.group != null}<div class="sv-listbox__group" role="presentation">{g.group}</div>{/if}
|
|
204
|
+
{#each g.options as opt (opt.index)}
|
|
205
|
+
{@render optionRow(opt, opt.index)}
|
|
128
206
|
{/each}
|
|
129
207
|
{/each}
|
|
130
208
|
{/if}
|
|
131
|
-
</
|
|
209
|
+
</div>
|
|
132
210
|
{#if name}{#each lb.selectedValues as v (v)}<input type="hidden" {name} value={v} />{/each}{/if}
|
|
133
211
|
</SvField>
|
|
134
212
|
|
|
135
|
-
{#snippet
|
|
213
|
+
{#snippet optionInner(opt: ListOption)}
|
|
214
|
+
{#if multiple}<span class="sv-listbox__check" aria-hidden="true">{isSel(opt) ? '✓' : ''}</span>{/if}
|
|
215
|
+
{#if item ?? itemTemplate}{@render (item ?? itemTemplate)!(opt)}{:else}<span class="sv-listbox__label">{opt.label}</span>{/if}
|
|
216
|
+
{/snippet}
|
|
217
|
+
|
|
218
|
+
{#snippet optionRow(opt: IndexedOption, index: number, h?: number, top?: number)}
|
|
136
219
|
<!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
|
|
137
|
-
<
|
|
220
|
+
<div
|
|
138
221
|
class="sv-listbox__opt"
|
|
139
222
|
class:is-selected={isSel(opt)}
|
|
140
223
|
class:is-active={lb.isActive(index)}
|
|
141
224
|
class:is-disabled={opt.disabled}
|
|
142
|
-
style:height={`${
|
|
225
|
+
style:height={`${h ?? optHeight(opt)}px`}
|
|
226
|
+
style:transform={top == null ? undefined : `translateY(${top}px)`}
|
|
143
227
|
{...lb.optionProps(index)}
|
|
144
228
|
>
|
|
145
|
-
{
|
|
146
|
-
|
|
147
|
-
</li>
|
|
229
|
+
{@render optionInner(opt)}
|
|
230
|
+
</div>
|
|
148
231
|
{/snippet}
|
|
149
232
|
|
|
150
233
|
<style>
|
|
151
234
|
.sv-listbox {
|
|
152
235
|
--_accent: var(--sg-accent, #2563eb);
|
|
153
|
-
margin: 0;
|
|
154
|
-
max-height: calc(var(--sv-rows, 7) * var(--sv-row-h, 32px) + 8px); overflow-y: auto;
|
|
236
|
+
margin: 0; list-style: none;
|
|
155
237
|
background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
156
238
|
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
157
|
-
width: 220px; outline: none;
|
|
239
|
+
width: 220px; outline: none; --_fs: 13px;
|
|
240
|
+
}
|
|
241
|
+
/* Native scroll for the (fully rendered) non-virtual list. */
|
|
242
|
+
.sv-listbox:not(.is-virtual) {
|
|
243
|
+
padding: 4px; overflow-y: auto;
|
|
244
|
+
max-height: calc(var(--sv-rows, 7) * var(--sv-row-h, 32px) + 8px);
|
|
245
|
+
}
|
|
246
|
+
/* JS-driven scroller for the virtual list: fixed height, no native scroll -
|
|
247
|
+
the rows are absolutely positioned and moved by `scrollOffset`. */
|
|
248
|
+
.sv-listbox.is-virtual {
|
|
249
|
+
padding: 0; overflow: hidden; position: relative;
|
|
250
|
+
height: calc(var(--sv-rows, 7) * var(--sv-row-h, 32px) + 8px);
|
|
158
251
|
}
|
|
252
|
+
.sv-listbox--sm { --_fs: 12px; }
|
|
253
|
+
.sv-listbox--lg { --_fs: 15px; }
|
|
159
254
|
.sv-listbox:focus-visible { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
160
255
|
.sv-listbox.is-invalid { border-color: var(--sg-danger, #dc2626); }
|
|
161
256
|
.sv-listbox.is-invalid:focus-visible { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
.sv-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
transparent calc((var(--sv-row-h, 32px) - 10px) / 2),
|
|
175
|
-
var(--sg-skeleton, color-mix(in srgb, currentColor 9%, transparent)) calc((var(--sv-row-h, 32px) - 10px) / 2),
|
|
176
|
-
var(--sg-skeleton, color-mix(in srgb, currentColor 9%, transparent)) calc((var(--sv-row-h, 32px) + 10px) / 2),
|
|
177
|
-
transparent calc((var(--sv-row-h, 32px) + 10px) / 2),
|
|
178
|
-
transparent var(--sv-row-h, 32px)
|
|
179
|
-
);
|
|
180
|
-
background-size: 62% var(--sv-row-h, 32px);
|
|
181
|
-
background-repeat: repeat-y;
|
|
182
|
-
background-position: 12px 0;
|
|
257
|
+
|
|
258
|
+
/* The clipped viewport that holds the translated rows. */
|
|
259
|
+
.sv-listbox__vp { position: absolute; inset: 4px; overflow: hidden; touch-action: none; }
|
|
260
|
+
.sv-listbox.is-virtual .sv-listbox__opt,
|
|
261
|
+
.sv-listbox.is-virtual .sv-listbox__group { position: absolute; left: 0; right: 0; top: 0; }
|
|
262
|
+
|
|
263
|
+
/* Custom scrollbar (mouse affordance; keyboard nav handles a11y scrolling). */
|
|
264
|
+
.sv-listbox__sb { position: absolute; top: 4px; right: 2px; bottom: 4px; width: 10px; z-index: 2; }
|
|
265
|
+
.sv-listbox__thumb {
|
|
266
|
+
position: absolute; left: 2px; right: 2px; top: 0; min-height: 24px;
|
|
267
|
+
border-radius: 4px; background: color-mix(in srgb, currentColor 26%, transparent);
|
|
268
|
+
cursor: default; touch-action: none;
|
|
183
269
|
}
|
|
270
|
+
.sv-listbox__thumb:hover, .sv-listbox__thumb.is-dragging { background: color-mix(in srgb, currentColor 42%, transparent); }
|
|
271
|
+
|
|
184
272
|
.sv-listbox__opt {
|
|
185
273
|
display: flex; align-items: center; gap: 8px; box-sizing: border-box; flex: none; padding: 0 10px;
|
|
186
|
-
border-radius: 6px; cursor: pointer; font-size: 13px;
|
|
274
|
+
border-radius: 6px; cursor: pointer; font-size: var(--_fs, 13px);
|
|
187
275
|
}
|
|
188
276
|
.sv-listbox__opt.is-active { background: var(--sg-row-hover-bg, #f1f5f9); }
|
|
189
277
|
.sv-listbox__opt.is-selected { background: color-mix(in srgb, var(--_accent) 14%, transparent); color: var(--_accent); font-weight: 600; }
|
|
190
278
|
.sv-listbox__opt.is-selected.is-active { background: color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
191
279
|
.sv-listbox__opt.is-disabled { opacity: 0.4; cursor: not-allowed; }
|
|
192
280
|
.sv-listbox__group {
|
|
193
|
-
padding: 8px 10px 3px; font-size: 10.5px; font-weight: 700;
|
|
194
|
-
color: var(--sg-muted, #94a3b8);
|
|
281
|
+
box-sizing: border-box; padding: 8px 10px 3px; font-size: 10.5px; font-weight: 700;
|
|
282
|
+
text-transform: uppercase; letter-spacing: 0.04em; color: var(--sg-muted, #94a3b8);
|
|
283
|
+
display: flex; align-items: flex-end;
|
|
195
284
|
}
|
|
196
285
|
.sv-listbox__check { width: 14px; text-align: center; color: var(--_accent); }
|
|
197
286
|
.sv-listbox__label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -2,10 +2,18 @@
|
|
|
2
2
|
* SvListBox - an inline single/multi-select list (WAI-ARIA listbox) with
|
|
3
3
|
* roving highlight + full keyboard. Parity: Smart `smart-list-box`. Controlled
|
|
4
4
|
* via `value` (scalar or array) + `onChange`. Scales to huge option sets with
|
|
5
|
-
* `virtual`
|
|
5
|
+
* `virtual` - windowing over the shared `createSvelteVirtualizer` (slot-keyed
|
|
6
|
+
* DOM recycling), which also windows GROUPED lists and mixes row heights.
|
|
7
|
+
*
|
|
8
|
+
* Virtual mode uses a JS-DRIVEN scroller (like Smart): the option list does
|
|
9
|
+
* NOT use native overflow scroll - a single `scrollOffset` state positions the
|
|
10
|
+
* rows (`translateY`) AND drives the virtualizer window in the same reactive
|
|
11
|
+
* flush. Native scroll is composited off the main thread, so it paints the
|
|
12
|
+
* empty scrolled position a frame before JS can re-window (the "blank on a
|
|
13
|
+
* fast thumb-drag" flash); driving the offset in JS removes that async gap.
|
|
6
14
|
*/
|
|
7
15
|
import { type Snippet } from 'svelte';
|
|
8
|
-
import { type ListOption } from './list-option';
|
|
16
|
+
import { type ListOption, type RowHeight } from './list-option';
|
|
9
17
|
import { type SvEditorProps } from './editor-contract';
|
|
10
18
|
type Props = SvEditorProps & {
|
|
11
19
|
options: ReadonlyArray<ListOption>;
|
|
@@ -16,9 +24,14 @@ type Props = SvEditorProps & {
|
|
|
16
24
|
rows?: number;
|
|
17
25
|
/** Window the list (render only visible rows) for large option sets. */
|
|
18
26
|
virtual?: boolean;
|
|
19
|
-
/**
|
|
20
|
-
|
|
27
|
+
/** Row height in px - a number, or a per-option function for variable
|
|
28
|
+
* heights. Must match the rendered row height. Default 32. */
|
|
29
|
+
rowHeight?: RowHeight;
|
|
30
|
+
/** Height (px) of a group heading row when virtualized + grouped. Default 28. */
|
|
31
|
+
groupHeaderHeight?: number;
|
|
21
32
|
/** Custom per-option content. Receives the option. */
|
|
33
|
+
item?: Snippet<[ListOption]>;
|
|
34
|
+
/** @deprecated Use `item`. */
|
|
22
35
|
itemTemplate?: Snippet<[ListOption]>;
|
|
23
36
|
};
|
|
24
37
|
declare const SvListBox: import("svelte").Component<Props, {}, "">;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvLoadingOverlay - covers its container with a translucent scrim + spinner
|
|
4
|
+
* while `visible`, blocking interaction underneath. Wrap any positioned region
|
|
5
|
+
* (a card, a panel, a form) and toggle it during an async load.
|
|
6
|
+
*
|
|
7
|
+
* ```svelte
|
|
8
|
+
* <div style="position:relative">
|
|
9
|
+
* <SvLoadingOverlay visible={loading} label="Saving" />
|
|
10
|
+
* ...content...
|
|
11
|
+
* </div>
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
import type { Snippet } from 'svelte'
|
|
15
|
+
import SvSpinner from './SvSpinner.svelte'
|
|
16
|
+
|
|
17
|
+
type Props = {
|
|
18
|
+
/** Show the overlay. */
|
|
19
|
+
visible?: boolean
|
|
20
|
+
/** Text shown under the spinner (also the a11y label). */
|
|
21
|
+
label?: string
|
|
22
|
+
/** Spinner size. Default `lg`. */
|
|
23
|
+
spinnerSize?: 'sm' | 'md' | 'lg' | number
|
|
24
|
+
/** Blur the content behind the scrim. */
|
|
25
|
+
blur?: boolean
|
|
26
|
+
/** Custom overlay content instead of the default spinner + label. */
|
|
27
|
+
children?: Snippet
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let { visible = false, label, spinnerSize = 'lg', blur = false, children }: Props = $props()
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
{#if visible}
|
|
34
|
+
<div class="sv-loading" class:is-blur={blur} role="status" aria-label={label ?? 'Loading'} aria-live="polite">
|
|
35
|
+
{#if children}
|
|
36
|
+
{@render children()}
|
|
37
|
+
{:else}
|
|
38
|
+
<SvSpinner size={spinnerSize} />
|
|
39
|
+
{#if label}<span class="sv-loading__label">{label}</span>{/if}
|
|
40
|
+
{/if}
|
|
41
|
+
</div>
|
|
42
|
+
{/if}
|
|
43
|
+
|
|
44
|
+
<style>
|
|
45
|
+
.sv-loading {
|
|
46
|
+
position: absolute; inset: 0; z-index: 20;
|
|
47
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
|
|
48
|
+
background: color-mix(in srgb, var(--sg-bg, #fff) 68%, transparent);
|
|
49
|
+
border-radius: inherit; cursor: progress;
|
|
50
|
+
}
|
|
51
|
+
.sv-loading.is-blur { backdrop-filter: blur(2px); }
|
|
52
|
+
.sv-loading__label { font-size: 13px; font-weight: 550; color: var(--sg-muted, #64748b); }
|
|
53
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SvLoadingOverlay - covers its container with a translucent scrim + spinner
|
|
3
|
+
* while `visible`, blocking interaction underneath. Wrap any positioned region
|
|
4
|
+
* (a card, a panel, a form) and toggle it during an async load.
|
|
5
|
+
*
|
|
6
|
+
* ```svelte
|
|
7
|
+
* <div style="position:relative">
|
|
8
|
+
* <SvLoadingOverlay visible={loading} label="Saving" />
|
|
9
|
+
* ...content...
|
|
10
|
+
* </div>
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
import type { Snippet } from 'svelte';
|
|
14
|
+
type Props = {
|
|
15
|
+
/** Show the overlay. */
|
|
16
|
+
visible?: boolean;
|
|
17
|
+
/** Text shown under the spinner (also the a11y label). */
|
|
18
|
+
label?: string;
|
|
19
|
+
/** Spinner size. Default `lg`. */
|
|
20
|
+
spinnerSize?: 'sm' | 'md' | 'lg' | number;
|
|
21
|
+
/** Blur the content behind the scrim. */
|
|
22
|
+
blur?: boolean;
|
|
23
|
+
/** Custom overlay content instead of the default spinner + label. */
|
|
24
|
+
children?: Snippet;
|
|
25
|
+
};
|
|
26
|
+
declare const SvLoadingOverlay: import("svelte").Component<Props, {}, "">;
|
|
27
|
+
type SvLoadingOverlay = ReturnType<typeof SvLoadingOverlay>;
|
|
28
|
+
export default SvLoadingOverlay;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvMark - highlighted inline text (a themed `<mark>`), for search-hit
|
|
4
|
+
* highlighting and call-outs. Colour by `tone`.
|
|
5
|
+
*
|
|
6
|
+
* <SvMark>matched</SvMark> · <SvMark tone="success">passed</SvMark>
|
|
7
|
+
*/
|
|
8
|
+
import type { Snippet } from 'svelte'
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
tone?: 'yellow' | 'accent' | 'success' | 'warning' | 'error'
|
|
12
|
+
children?: Snippet
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let { tone = 'yellow', children }: Props = $props()
|
|
16
|
+
|
|
17
|
+
// Each tone is a soft tint + a readable foreground.
|
|
18
|
+
const bg = {
|
|
19
|
+
yellow: '#fef08a',
|
|
20
|
+
accent: 'color-mix(in srgb, var(--sg-accent, #2563eb) 22%, transparent)',
|
|
21
|
+
success: 'color-mix(in srgb, var(--sg-success, #16a34a) 22%, transparent)',
|
|
22
|
+
warning: 'color-mix(in srgb, var(--sg-warning, #d97706) 24%, transparent)',
|
|
23
|
+
error: 'color-mix(in srgb, var(--sg-danger, #dc2626) 20%, transparent)',
|
|
24
|
+
} as const
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<mark class="sv-mark" style:background={bg[tone]}>{@render children?.()}</mark>
|
|
28
|
+
|
|
29
|
+
<style>
|
|
30
|
+
.sv-mark {
|
|
31
|
+
color: inherit;
|
|
32
|
+
padding: 0 2px;
|
|
33
|
+
border-radius: 3px;
|
|
34
|
+
font-family: inherit;
|
|
35
|
+
}
|
|
36
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SvMark - highlighted inline text (a themed `<mark>`), for search-hit
|
|
3
|
+
* highlighting and call-outs. Colour by `tone`.
|
|
4
|
+
*
|
|
5
|
+
* <SvMark>matched</SvMark> · <SvMark tone="success">passed</SvMark>
|
|
6
|
+
*/
|
|
7
|
+
import type { Snippet } from 'svelte';
|
|
8
|
+
type Props = {
|
|
9
|
+
tone?: 'yellow' | 'accent' | 'success' | 'warning' | 'error';
|
|
10
|
+
children?: Snippet;
|
|
11
|
+
};
|
|
12
|
+
declare const SvMark: import("svelte").Component<Props, {}, "">;
|
|
13
|
+
type SvMark = ReturnType<typeof SvMark>;
|
|
14
|
+
export default SvMark;
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* the masked display value and the raw (unmasked) value.
|
|
6
6
|
*
|
|
7
7
|
* The styled renderer over the headless `createMaskedInput` core; the mask
|
|
8
|
-
* formatting + state live in the core, spread here via prop-getters.
|
|
8
|
+
* formatting + state live in the core, spread here via prop-getters. The box,
|
|
9
|
+
* size, invalid state, clear button and adornments are owned by SvField's
|
|
10
|
+
* `frame` chrome.
|
|
9
11
|
*/
|
|
10
12
|
import type { Snippet } from 'svelte'
|
|
11
13
|
import { unmask } from './datetime/mask'
|
|
@@ -21,9 +23,22 @@
|
|
|
21
23
|
placeholder?: string
|
|
22
24
|
/** Show a clear (x) button when there is a value. */
|
|
23
25
|
clearable?: boolean
|
|
24
|
-
/** Leading /
|
|
26
|
+
/** Leading adornment (icon/button) inside the field. */
|
|
27
|
+
leading?: Snippet
|
|
28
|
+
/** Trailing adornment (icon/button) inside the field. */
|
|
29
|
+
trailing?: Snippet
|
|
30
|
+
/** Plain-text affix at the start. */
|
|
31
|
+
prefix?: string
|
|
32
|
+
/** Plain-text affix at the end. */
|
|
33
|
+
suffix?: string
|
|
34
|
+
/** @deprecated Use `leading`. */
|
|
25
35
|
prefixIcon?: Snippet
|
|
36
|
+
/** @deprecated Use `trailing`. */
|
|
26
37
|
suffixIcon?: Snippet
|
|
38
|
+
/** Stretch to the container width. */
|
|
39
|
+
block?: boolean
|
|
40
|
+
/** Control width in px (ignored when `block`). Default 200. */
|
|
41
|
+
width?: number
|
|
27
42
|
}
|
|
28
43
|
|
|
29
44
|
let {
|
|
@@ -43,9 +58,16 @@
|
|
|
43
58
|
hint,
|
|
44
59
|
dir,
|
|
45
60
|
id,
|
|
61
|
+
loading = false,
|
|
46
62
|
clearable = false,
|
|
63
|
+
leading,
|
|
64
|
+
trailing,
|
|
65
|
+
prefix,
|
|
66
|
+
suffix,
|
|
47
67
|
prefixIcon,
|
|
48
68
|
suffixIcon,
|
|
69
|
+
block = false,
|
|
70
|
+
width = 200,
|
|
49
71
|
}: Props = $props()
|
|
50
72
|
|
|
51
73
|
const autoId = nextEditorId('sv-mask')
|
|
@@ -68,40 +90,33 @@
|
|
|
68
90
|
})
|
|
69
91
|
</script>
|
|
70
92
|
|
|
71
|
-
<SvField
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
93
|
+
<SvField
|
|
94
|
+
frame
|
|
95
|
+
id={uid}
|
|
96
|
+
{label}
|
|
97
|
+
{hint}
|
|
98
|
+
{error}
|
|
99
|
+
{required}
|
|
100
|
+
{dir}
|
|
101
|
+
{size}
|
|
102
|
+
{invalid}
|
|
103
|
+
{disabled}
|
|
104
|
+
{readonly}
|
|
105
|
+
{loading}
|
|
106
|
+
{block}
|
|
107
|
+
{width}
|
|
108
|
+
leading={leading ?? prefixIcon}
|
|
109
|
+
trailing={trailing ?? suffixIcon}
|
|
110
|
+
{prefix}
|
|
111
|
+
{suffix}
|
|
112
|
+
clearable={clearable}
|
|
113
|
+
showClear={!!mi.masked}
|
|
114
|
+
onclear={() => { value = ''; onChange?.('', '', false) }}
|
|
115
|
+
>
|
|
116
|
+
<input class="sv-masked__input" {...mi.inputProps()} />
|
|
117
|
+
{#if name}<input type="hidden" {name} value={mask ? unmask(mi.masked, mask) : mi.masked} />{/if}
|
|
83
118
|
</SvField>
|
|
84
119
|
|
|
85
120
|
<style>
|
|
86
|
-
.sv-
|
|
87
|
-
--_accent: var(--sg-accent, #2563eb);
|
|
88
|
-
box-sizing: border-box; width: 200px; display: inline-flex; align-items: center; gap: 6px;
|
|
89
|
-
background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
90
|
-
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
91
|
-
font: inherit; padding: 0 8px 0 10px;
|
|
92
|
-
}
|
|
93
|
-
.sv-masked--sm { height: 28px; font-size: 12px; }
|
|
94
|
-
.sv-masked--md { height: 34px; font-size: 13px; }
|
|
95
|
-
.sv-masked--lg { height: 40px; font-size: 15px; }
|
|
96
|
-
.sv-masked:focus-within { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
97
|
-
.sv-masked.is-invalid { border-color: var(--sg-danger, #dc2626); }
|
|
98
|
-
.sv-masked.is-invalid:focus-within { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
|
|
99
|
-
.sv-masked.is-disabled { opacity: 0.6; }
|
|
100
|
-
.sv-masked__input {
|
|
101
|
-
flex: 1; min-width: 0; border: 0; background: none; outline: none; color: inherit; font: inherit;
|
|
102
|
-
font-variant-numeric: tabular-nums; letter-spacing: 0.02em;
|
|
103
|
-
}
|
|
104
|
-
.sv-masked__adorn { display: inline-flex; flex: none; color: var(--sg-muted, #64748b); }
|
|
105
|
-
.sv-masked__clear { display: grid; place-items: center; width: 20px; flex: none; background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; border-radius: 4px; }
|
|
106
|
-
.sv-masked__clear:hover { color: var(--sg-danger, #dc2626); }
|
|
121
|
+
.sv-masked__input { font-variant-numeric: tabular-nums; letter-spacing: 0.02em; }
|
|
107
122
|
</style>
|