@svgrid/grid 2.2.27 → 2.2.29
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 +199 -199
- package/dist/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +179 -179
- package/dist/GridMenus.svelte +782 -734
- package/dist/SvAutoComplete.svelte +165 -165
- package/dist/SvCalendar.svelte +503 -503
- package/dist/SvCarousel.svelte +141 -141
- package/dist/SvCheckBox.svelte +101 -101
- package/dist/SvColorInput.svelte +181 -181
- package/dist/SvComboBox.svelte +275 -275
- package/dist/SvContextMenu.svelte +116 -116
- package/dist/SvCountryInput.svelte +160 -160
- package/dist/SvDrawer.svelte +254 -250
- package/dist/SvDropDownList.svelte +374 -374
- package/dist/SvDurationInput.svelte +126 -126
- package/dist/SvField.svelte +293 -293
- package/dist/SvForm.svelte +437 -437
- package/dist/SvGrid.controller.svelte.d.ts +14 -1
- package/dist/SvGrid.controller.svelte.js +112 -28
- package/dist/SvGrid.css +2704 -2723
- package/dist/SvGrid.svelte +3361 -3333
- package/dist/SvGrid.types.d.ts +20 -5
- package/dist/SvGridChart.svelte +1724 -1724
- package/dist/SvGridChartPanel.svelte +485 -485
- package/dist/SvGridChartView.svelte +70 -70
- package/dist/SvGridDropdown.svelte +696 -696
- package/dist/SvGridSelect.svelte +259 -259
- package/dist/SvGroupCell.svelte +109 -109
- package/dist/SvListBox.svelte +326 -287
- package/dist/SvListBox.svelte.d.ts +10 -1
- package/dist/SvMaskedInput.svelte +122 -122
- package/dist/SvMenu.svelte +124 -124
- package/dist/SvMenuList.svelte +146 -146
- package/dist/SvMultiSelect.svelte +281 -281
- package/dist/SvNumberInput.svelte +168 -168
- package/dist/SvOtpInput.svelte +158 -158
- package/dist/SvPasswordInput.svelte +151 -151
- package/dist/SvPhoneInput.svelte +133 -131
- package/dist/SvPopover.svelte +197 -197
- package/dist/SvRadioGroup.svelte +107 -107
- package/dist/SvRowGroupPanel.svelte +170 -170
- package/dist/SvScrollArea.svelte +61 -61
- package/dist/SvSlider.svelte +200 -200
- package/dist/SvSwitchButton.svelte +107 -107
- package/dist/SvTagsInput.svelte +112 -112
- package/dist/SvTextInput.svelte +147 -147
- package/dist/SvTimePicker.svelte +245 -245
- package/dist/SvToaster.svelte +159 -159
- package/dist/SvToggleButton.svelte +85 -85
- package/dist/SvTooltip.svelte +161 -161
- package/dist/SvTour.svelte +204 -204
- package/dist/SvTree.svelte +437 -437
- package/dist/SvTreeSelect.svelte +233 -233
- package/dist/cdn/GridMenus-Da_p7SKS.js +486 -0
- package/dist/cdn/GridMenus-Ds3OpzZT.js +490 -0
- package/dist/cdn/{SvDateTimePicker-B1sDc9Pz.js → SvDateTimePicker-B8GopjhC.js} +2 -2
- package/dist/cdn/{SvDateTimePicker-Bh0FTs1E.js → SvDateTimePicker-DB0aIEk8.js} +2 -2
- package/dist/cdn/{SvGridChart-Bgk4hDs8.js → SvGridChart-BcLCYvw9.js} +1 -1
- package/dist/cdn/{SvGridChart-BKQsO5fZ.js → SvGridChart-BhnFdLd0.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-DkCc2Z7b.js → SvGridChartPanel-Cmx9DHAS.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-DSkHIQDd.js → SvGridChartPanel-DvjwxLvK.js} +1 -1
- package/dist/cdn/{SvGridChartView-BrbVaELm.js → SvGridChartView-B40DTJSX.js} +1 -1
- package/dist/cdn/{SvGridChartView-i9rWvSKg.js → SvGridChartView-PwqI4s02.js} +1 -1
- package/dist/cdn/{SvGridDropdown-D08sVmAy.js → SvGridDropdown-DjALc_4f.js} +1 -1
- package/dist/cdn/{SvGridDropdown-CGxxopQ4.js → SvGridDropdown-hpnHvOGt.js} +1 -1
- package/dist/cdn/{src-cgs1wcUS.js → src-BLJfdyL0.js} +4034 -3889
- package/dist/cdn/{src-Cw8d9U8j.js → src-DYDCiC0D.js} +7053 -6908
- package/dist/cdn/svgrid.js +9 -9
- package/dist/cdn/svgrid.svelte-external.js +9 -9
- package/dist/chart-export.js +8 -8
- package/dist/column-groups.js +8 -10
- package/dist/column-id.d.ts +9 -0
- package/dist/column-id.js +25 -0
- package/dist/core.js +2 -1
- package/dist/createListbox.svelte.d.ts +15 -2
- package/dist/createListbox.svelte.js +27 -6
- package/dist/editing.d.ts +1 -0
- package/dist/editing.js +45 -10
- package/dist/filtering/excel-filters.d.ts +30 -10
- package/dist/filtering/excel-filters.js +96 -15
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/menus.d.ts +2 -0
- package/dist/menus.js +25 -0
- package/dist/row-resize.js +40 -0
- package/dist/selection.js +1 -1
- package/package.json +10 -10
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +179 -179
- package/src/GridMenus.svelte +782 -734
- package/src/SvAutoComplete.svelte +165 -165
- package/src/SvCalendar.svelte +503 -503
- package/src/SvCalendar.test.ts +226 -226
- package/src/SvCarousel.svelte +141 -141
- package/src/SvCheckBox.svelte +101 -101
- package/src/SvColorInput.svelte +181 -181
- package/src/SvComboBox.svelte +275 -275
- package/src/SvContextMenu.svelte +116 -116
- package/src/SvCountryInput.svelte +160 -160
- package/src/SvDrawer.svelte +254 -250
- package/src/SvDrawer.test.ts +18 -1
- package/src/SvDropDownList.svelte +374 -374
- package/src/SvDurationInput.svelte +126 -126
- package/src/SvField.svelte +293 -293
- package/src/SvForm.svelte +437 -437
- package/src/SvForm.test.ts +411 -411
- package/src/SvGrid.controller.svelte.ts +3335 -3239
- package/src/SvGrid.css +2704 -2723
- package/src/SvGrid.svelte +3361 -3333
- package/src/SvGrid.types.ts +1628 -1613
- package/src/SvGridChart.svelte +1724 -1724
- package/src/SvGridChartPanel.svelte +485 -485
- package/src/SvGridChartView.svelte +70 -70
- package/src/SvGridDropdown.svelte +696 -696
- package/src/SvGridSelect.svelte +259 -259
- package/src/SvGroupCell.svelte +109 -109
- package/src/SvListBox.svelte +326 -287
- package/src/SvMaskedInput.svelte +122 -122
- package/src/SvMenu.svelte +124 -124
- package/src/SvMenu.test.ts +97 -97
- package/src/SvMenuList.svelte +146 -146
- package/src/SvMultiSelect.svelte +281 -281
- package/src/SvNavPane.test.ts +15 -4
- package/src/SvNumberInput.svelte +168 -168
- package/src/SvOtpInput.svelte +158 -158
- package/src/SvPasswordInput.svelte +151 -151
- package/src/SvPhoneInput.svelte +133 -131
- package/src/SvPopover.svelte +197 -197
- package/src/SvRadioGroup.svelte +107 -107
- package/src/SvRowGroupPanel.svelte +170 -170
- package/src/SvScrollArea.svelte +61 -61
- package/src/SvSlider.svelte +200 -200
- package/src/SvSwitchButton.svelte +107 -107
- package/src/SvTagsInput.svelte +112 -112
- package/src/SvTextInput.svelte +147 -147
- package/src/SvTimePicker.svelte +245 -245
- package/src/SvToaster.svelte +159 -159
- package/src/SvToaster.test.ts +95 -95
- package/src/SvToggleButton.svelte +85 -85
- package/src/SvTooltip.svelte +161 -161
- package/src/SvTour.svelte +204 -204
- package/src/SvTree.svelte +437 -437
- package/src/SvTreeSelect.svelte +233 -233
- package/src/a11y/dismissable.test.ts +119 -119
- package/src/a11y/dismissable.ts +114 -114
- package/src/a11y.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +61 -61
- package/src/ai.test.ts +502 -502
- package/src/ai.ts +1391 -1391
- package/src/build-api.coverage.test.ts +633 -633
- package/src/build-api.ts +895 -895
- package/src/builtin-editors.grid.test.ts +85 -85
- package/src/cell-formatting.ts +169 -169
- package/src/cell-render.ts +469 -469
- package/src/chart-export.ts +201 -201
- package/src/chart-view.svelte.ts +36 -36
- package/src/chart.ts +2296 -2296
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/column-groups.test.ts +48 -0
- package/src/column-groups.ts +8 -10
- package/src/column-id.ts +30 -0
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1112 -1111
- package/src/createAutocomplete.svelte.ts +132 -132
- package/src/createCombobox.svelte.ts +191 -191
- package/src/createCountryInput.svelte.ts +157 -157
- package/src/createDropdownList.svelte.ts +168 -168
- package/src/createForm.svelte.ts +386 -386
- package/src/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- package/src/createListbox.svelte.ts +39 -7
- package/src/createMenu.svelte.ts +225 -225
- package/src/createPopoverSelect.svelte.ts +213 -213
- package/src/createSlider.svelte.ts +191 -191
- package/src/createTooltip.svelte.ts +144 -144
- package/src/createTree.svelte.ts +319 -319
- package/src/editing.test.ts +949 -859
- package/src/editing.ts +709 -675
- package/src/editor-contract.ts +157 -157
- 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.ts +157 -157
- package/src/filtering/excel-filters.test.ts +44 -1
- package/src/filtering/excel-filters.ts +91 -14
- package/src/flex-render.ts +3 -3
- package/src/form-field.ts +127 -127
- package/src/index.ts +715 -713
- package/src/js-scroller.svelte.ts +173 -173
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/list-nav.test.ts +49 -49
- package/src/list-nav.ts +29 -29
- package/src/list-option.test.ts +56 -56
- package/src/list-option.ts +172 -172
- package/src/menus.test.ts +736 -700
- package/src/menus.ts +679 -652
- package/src/merge-objects.ts +48 -48
- package/src/overlays.test.ts +90 -90
- package/src/render-component.ts +28 -28
- package/src/row-resize.test.ts +100 -0
- package/src/row-resize.ts +36 -0
- package/src/scheduler-model.test.ts +562 -562
- package/src/scheduler-model.ts +872 -872
- 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 -312
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/svgrid-wrapper.types.ts +524 -524
- package/src/svgrid.api-extensions.test.ts +8 -3
- package/src/svgrid.behavior.test.ts +706 -706
- package/src/svgrid.cell-dom-ids.test.ts +96 -0
- package/src/svgrid.charting.test.ts +534 -534
- package/src/svgrid.conditional-stat-scope.test.ts +111 -0
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.filter-menu-listbox.svelte.test.ts +308 -0
- package/src/svgrid.filter-menu-scroll.test.ts +3 -1
- package/src/svgrid.group-header-ids.test.ts +121 -0
- 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 +215 -215
- package/src/toast-store.svelte.ts +246 -246
- package/src/toast-store.test.ts +147 -147
- package/src/ui-buttons.test.ts +144 -144
- package/src/ui-inputs.test.ts +118 -118
- package/src/ui-range.test.ts +70 -70
- package/src/ui-selection.test.ts +155 -155
- package/src/ui-tier1.test.ts +142 -142
- package/src/virtual.test.ts +88 -88
- 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/themes/ember.css +71 -71
- package/dist/cdn/GridMenus-BzxEYNtW.js +0 -421
- package/dist/cdn/GridMenus-Dr-nGEXn.js +0 -417
|
@@ -17,9 +17,18 @@ import { type ListOption, type RowHeight } from './list-option';
|
|
|
17
17
|
import { type SvEditorProps } from './editor-contract';
|
|
18
18
|
type Props = SvEditorProps & {
|
|
19
19
|
options: ReadonlyArray<ListOption>;
|
|
20
|
-
value?: string | number |
|
|
20
|
+
value?: string | number | ReadonlyArray<string | number> | ReadonlySet<string | number> | null;
|
|
21
21
|
onChange?: (value: any) => void;
|
|
22
22
|
multiple?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Checklist styling: each row draws a checkbox and the selected-row accent
|
|
25
|
+
* highlight is suppressed, because a list that starts fully selected (a
|
|
26
|
+
* filter checklist) would otherwise paint every row. State reads off the
|
|
27
|
+
* box; `aria-selected` still carries it for assistive tech.
|
|
28
|
+
*/
|
|
29
|
+
checkbox?: boolean;
|
|
30
|
+
/** Stretch to the container width instead of the default 220px. */
|
|
31
|
+
block?: boolean;
|
|
23
32
|
/** Visible height in rows before scrolling. */
|
|
24
33
|
rows?: number;
|
|
25
34
|
/** Window the list (render only visible rows) for large option sets. */
|
|
@@ -1,122 +1,122 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SvMaskedInput - a pattern-masked text input (#=digit, A=letter, *=alnum;
|
|
4
|
-
* other chars are literals). Parity: Smart `smart-masked-text-box`. Emits both
|
|
5
|
-
* the masked display value and the raw (unmasked) value.
|
|
6
|
-
*
|
|
7
|
-
* The styled renderer over the headless `createMaskedInput` core; the mask
|
|
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.
|
|
11
|
-
*/
|
|
12
|
-
import type { Snippet } from 'svelte'
|
|
13
|
-
import { unmask } from './datetime/mask'
|
|
14
|
-
import SvField from './SvField.svelte'
|
|
15
|
-
import { nextEditorId, type SvEditorProps } from './editor-contract'
|
|
16
|
-
import { createMaskedInput } from './createMaskedInput.svelte'
|
|
17
|
-
|
|
18
|
-
type Props = SvEditorProps & {
|
|
19
|
-
value?: string
|
|
20
|
-
/** Called with (masked, raw, complete). */
|
|
21
|
-
onChange?: (masked: string, raw: string, complete: boolean) => void
|
|
22
|
-
mask?: string
|
|
23
|
-
placeholder?: string
|
|
24
|
-
/** Show a clear (x) button when there is a value. */
|
|
25
|
-
clearable?: boolean
|
|
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`. */
|
|
35
|
-
prefixIcon?: Snippet
|
|
36
|
-
/** @deprecated Use `trailing`. */
|
|
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
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
let {
|
|
45
|
-
value = $bindable(''),
|
|
46
|
-
onChange,
|
|
47
|
-
mask = '',
|
|
48
|
-
placeholder,
|
|
49
|
-
disabled = false,
|
|
50
|
-
readonly = false,
|
|
51
|
-
name,
|
|
52
|
-
size = 'md',
|
|
53
|
-
ariaLabel,
|
|
54
|
-
invalid = false,
|
|
55
|
-
required = false,
|
|
56
|
-
error,
|
|
57
|
-
label,
|
|
58
|
-
hint,
|
|
59
|
-
dir,
|
|
60
|
-
id,
|
|
61
|
-
loading = false,
|
|
62
|
-
clearable = false,
|
|
63
|
-
leading,
|
|
64
|
-
trailing,
|
|
65
|
-
prefix,
|
|
66
|
-
suffix,
|
|
67
|
-
prefixIcon,
|
|
68
|
-
suffixIcon,
|
|
69
|
-
block = false,
|
|
70
|
-
width = 200,
|
|
71
|
-
}: Props = $props()
|
|
72
|
-
|
|
73
|
-
const autoId = nextEditorId('sv-mask')
|
|
74
|
-
const uid = $derived(id ?? autoId)
|
|
75
|
-
|
|
76
|
-
const mi = createMaskedInput({
|
|
77
|
-
value: () => value,
|
|
78
|
-
// Write the masked `value` (so `bind:value` works) AND fire `onChange`.
|
|
79
|
-
onChange: (m, r, c) => { value = m; onChange?.(m, r, c) },
|
|
80
|
-
mask: () => mask,
|
|
81
|
-
placeholder: () => placeholder,
|
|
82
|
-
disabled: () => disabled,
|
|
83
|
-
readonly: () => readonly,
|
|
84
|
-
id: () => uid,
|
|
85
|
-
invalid: () => invalid,
|
|
86
|
-
required: () => required,
|
|
87
|
-
error: () => error,
|
|
88
|
-
hint: () => hint,
|
|
89
|
-
ariaLabel: () => ariaLabel,
|
|
90
|
-
})
|
|
91
|
-
</script>
|
|
92
|
-
|
|
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}
|
|
118
|
-
</SvField>
|
|
119
|
-
|
|
120
|
-
<style>
|
|
121
|
-
.sv-masked__input { font-variant-numeric: tabular-nums; letter-spacing: 0.02em; }
|
|
122
|
-
</style>
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvMaskedInput - a pattern-masked text input (#=digit, A=letter, *=alnum;
|
|
4
|
+
* other chars are literals). Parity: Smart `smart-masked-text-box`. Emits both
|
|
5
|
+
* the masked display value and the raw (unmasked) value.
|
|
6
|
+
*
|
|
7
|
+
* The styled renderer over the headless `createMaskedInput` core; the mask
|
|
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.
|
|
11
|
+
*/
|
|
12
|
+
import type { Snippet } from 'svelte'
|
|
13
|
+
import { unmask } from './datetime/mask'
|
|
14
|
+
import SvField from './SvField.svelte'
|
|
15
|
+
import { nextEditorId, type SvEditorProps } from './editor-contract'
|
|
16
|
+
import { createMaskedInput } from './createMaskedInput.svelte'
|
|
17
|
+
|
|
18
|
+
type Props = SvEditorProps & {
|
|
19
|
+
value?: string
|
|
20
|
+
/** Called with (masked, raw, complete). */
|
|
21
|
+
onChange?: (masked: string, raw: string, complete: boolean) => void
|
|
22
|
+
mask?: string
|
|
23
|
+
placeholder?: string
|
|
24
|
+
/** Show a clear (x) button when there is a value. */
|
|
25
|
+
clearable?: boolean
|
|
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`. */
|
|
35
|
+
prefixIcon?: Snippet
|
|
36
|
+
/** @deprecated Use `trailing`. */
|
|
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
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let {
|
|
45
|
+
value = $bindable(''),
|
|
46
|
+
onChange,
|
|
47
|
+
mask = '',
|
|
48
|
+
placeholder,
|
|
49
|
+
disabled = false,
|
|
50
|
+
readonly = false,
|
|
51
|
+
name,
|
|
52
|
+
size = 'md',
|
|
53
|
+
ariaLabel,
|
|
54
|
+
invalid = false,
|
|
55
|
+
required = false,
|
|
56
|
+
error,
|
|
57
|
+
label,
|
|
58
|
+
hint,
|
|
59
|
+
dir,
|
|
60
|
+
id,
|
|
61
|
+
loading = false,
|
|
62
|
+
clearable = false,
|
|
63
|
+
leading,
|
|
64
|
+
trailing,
|
|
65
|
+
prefix,
|
|
66
|
+
suffix,
|
|
67
|
+
prefixIcon,
|
|
68
|
+
suffixIcon,
|
|
69
|
+
block = false,
|
|
70
|
+
width = 200,
|
|
71
|
+
}: Props = $props()
|
|
72
|
+
|
|
73
|
+
const autoId = nextEditorId('sv-mask')
|
|
74
|
+
const uid = $derived(id ?? autoId)
|
|
75
|
+
|
|
76
|
+
const mi = createMaskedInput({
|
|
77
|
+
value: () => value,
|
|
78
|
+
// Write the masked `value` (so `bind:value` works) AND fire `onChange`.
|
|
79
|
+
onChange: (m, r, c) => { value = m; onChange?.(m, r, c) },
|
|
80
|
+
mask: () => mask,
|
|
81
|
+
placeholder: () => placeholder,
|
|
82
|
+
disabled: () => disabled,
|
|
83
|
+
readonly: () => readonly,
|
|
84
|
+
id: () => uid,
|
|
85
|
+
invalid: () => invalid,
|
|
86
|
+
required: () => required,
|
|
87
|
+
error: () => error,
|
|
88
|
+
hint: () => hint,
|
|
89
|
+
ariaLabel: () => ariaLabel,
|
|
90
|
+
})
|
|
91
|
+
</script>
|
|
92
|
+
|
|
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}
|
|
118
|
+
</SvField>
|
|
119
|
+
|
|
120
|
+
<style>
|
|
121
|
+
.sv-masked__input { font-variant-numeric: tabular-nums; letter-spacing: 0.02em; }
|
|
122
|
+
</style>
|
package/dist/SvMenu.svelte
CHANGED
|
@@ -1,124 +1,124 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
export type { MenuItem } from './SvMenuList.svelte'
|
|
3
|
-
</script>
|
|
4
|
-
|
|
5
|
-
<script lang="ts">
|
|
6
|
-
/**
|
|
7
|
-
* SvMenu - a dropdown / context menu: a trigger opens a portalled, animated
|
|
8
|
-
* menu surface with submenus, separators, icons, shortcuts and full keyboard
|
|
9
|
-
* (arrows, Enter, Escape, ArrowRight/Left for submenus). Portals to <body> so it
|
|
10
|
-
* escapes any clipping. Parity: Smart menu / context-menu.
|
|
11
|
-
*
|
|
12
|
-
* ```svelte
|
|
13
|
-
* <SvMenu items={items} onSelect={(i) => run(i)}>
|
|
14
|
-
* {#snippet anchor()}<button>Actions</button>{/snippet}
|
|
15
|
-
* </SvMenu>
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
18
|
-
import type { Snippet } from 'svelte'
|
|
19
|
-
import { anchoredRect, portalToBody, popIn, type AnchoredRect } from './popover'
|
|
20
|
-
import { createDismissableLayer } from './a11y/dismissable'
|
|
21
|
-
import { getFocusable } from './a11y/focus-trap'
|
|
22
|
-
import { type EditorDir } from './editor-contract'
|
|
23
|
-
import SvMenuList, { type MenuItem } from './SvMenuList.svelte'
|
|
24
|
-
|
|
25
|
-
let {
|
|
26
|
-
items,
|
|
27
|
-
open = $bindable(false),
|
|
28
|
-
onOpenChange,
|
|
29
|
-
onSelect,
|
|
30
|
-
ariaLabel,
|
|
31
|
-
anchor,
|
|
32
|
-
dir,
|
|
33
|
-
}: {
|
|
34
|
-
items: ReadonlyArray<MenuItem>
|
|
35
|
-
open?: boolean
|
|
36
|
-
onOpenChange?: (open: boolean) => void
|
|
37
|
-
onSelect?: (item: MenuItem) => void
|
|
38
|
-
ariaLabel?: string
|
|
39
|
-
anchor?: Snippet
|
|
40
|
-
/** Text direction (rtl mirrors the submenu chevron + flips ArrowLeft/Right). */
|
|
41
|
-
dir?: EditorDir
|
|
42
|
-
} = $props()
|
|
43
|
-
|
|
44
|
-
const resolvedDir = $derived(dir === 'ltr' || dir === 'rtl' ? dir : undefined)
|
|
45
|
-
|
|
46
|
-
let anchorEl = $state<HTMLSpanElement | null>(null)
|
|
47
|
-
let panelEl = $state<HTMLDivElement | null>(null)
|
|
48
|
-
let rect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false, maxHeight: 0, availHeight: 0 })
|
|
49
|
-
|
|
50
|
-
function setOpen(v: boolean) { if (v !== open) { open = v; onOpenChange?.(v) } }
|
|
51
|
-
function toggle() { setOpen(!open) }
|
|
52
|
-
function updatePos() {
|
|
53
|
-
if (!anchorEl) return
|
|
54
|
-
rect = anchoredRect(anchorEl.getBoundingClientRect(), { estimatedHeight: Math.min(items.length, 10) * 34 + 8, minWidth: 180 })
|
|
55
|
-
}
|
|
56
|
-
/** Return focus to the trigger. Every dismissal path (Escape, item selection,
|
|
57
|
-
* outside-click, Tab) funnels through the effect cleanup below, so this
|
|
58
|
-
* fires uniformly instead of dropping focus to <body> on close. */
|
|
59
|
-
function focusTrigger() {
|
|
60
|
-
if (!anchorEl) return
|
|
61
|
-
;(getFocusable(anchorEl)[0] ?? anchorEl)?.focus?.()
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
$effect(() => {
|
|
65
|
-
if (!open) return
|
|
66
|
-
updatePos()
|
|
67
|
-
queueMicrotask(() => panelEl?.querySelector<HTMLElement>('[role="menuitem"]:not([disabled])')?.focus())
|
|
68
|
-
const rp = () => updatePos()
|
|
69
|
-
window.addEventListener('scroll', rp, true)
|
|
70
|
-
window.addEventListener('resize', rp)
|
|
71
|
-
// Outside-click dismissal via the shared layer stack (Escape stays owned by
|
|
72
|
-
// SvMenuList's keyboard handling, so closeOnEscape is off here).
|
|
73
|
-
const layer = createDismissableLayer({
|
|
74
|
-
element: () => [anchorEl, panelEl],
|
|
75
|
-
onDismiss: () => setOpen(false),
|
|
76
|
-
closeOnEscape: false,
|
|
77
|
-
})
|
|
78
|
-
layer.activate()
|
|
79
|
-
return () => {
|
|
80
|
-
window.removeEventListener('scroll', rp, true)
|
|
81
|
-
window.removeEventListener('resize', rp)
|
|
82
|
-
layer.release()
|
|
83
|
-
// Only on an actual close (not a mid-open rerun from e.g. `items` changing).
|
|
84
|
-
if (!open) focusTrigger()
|
|
85
|
-
}
|
|
86
|
-
})
|
|
87
|
-
</script>
|
|
88
|
-
|
|
89
|
-
<!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
|
|
90
|
-
<span
|
|
91
|
-
bind:this={anchorEl}
|
|
92
|
-
class="sv-menu__anchor"
|
|
93
|
-
aria-haspopup="menu"
|
|
94
|
-
aria-expanded={open}
|
|
95
|
-
onclick={toggle}
|
|
96
|
-
>
|
|
97
|
-
{@render anchor?.()}
|
|
98
|
-
</span>
|
|
99
|
-
|
|
100
|
-
{#if open}
|
|
101
|
-
<div
|
|
102
|
-
bind:this={panelEl}
|
|
103
|
-
class="sv-menu"
|
|
104
|
-
use:portalToBody
|
|
105
|
-
use:popIn={{ up: rect.openUpward }}
|
|
106
|
-
style:position="fixed"
|
|
107
|
-
style:top={`${rect.top}px`}
|
|
108
|
-
style:left={`${rect.left}px`}
|
|
109
|
-
dir={resolvedDir}
|
|
110
|
-
aria-label={ariaLabel}
|
|
111
|
-
>
|
|
112
|
-
<SvMenuList {items} onclose={() => setOpen(false)} onselect={(i) => onSelect?.(i)} dir={resolvedDir} />
|
|
113
|
-
</div>
|
|
114
|
-
{/if}
|
|
115
|
-
|
|
116
|
-
<style>
|
|
117
|
-
.sv-menu__anchor { display: inline-flex; }
|
|
118
|
-
:global(.sv-menu) {
|
|
119
|
-
z-index: 2147483646; min-width: 180px;
|
|
120
|
-
background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
121
|
-
border: 1px solid var(--sg-border, #e2e8f0); border-radius: 10px;
|
|
122
|
-
box-shadow: 0 16px 48px -12px rgba(15, 23, 42, 0.35); font-size: 13px;
|
|
123
|
-
}
|
|
124
|
-
</style>
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export type { MenuItem } from './SvMenuList.svelte'
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
/**
|
|
7
|
+
* SvMenu - a dropdown / context menu: a trigger opens a portalled, animated
|
|
8
|
+
* menu surface with submenus, separators, icons, shortcuts and full keyboard
|
|
9
|
+
* (arrows, Enter, Escape, ArrowRight/Left for submenus). Portals to <body> so it
|
|
10
|
+
* escapes any clipping. Parity: Smart menu / context-menu.
|
|
11
|
+
*
|
|
12
|
+
* ```svelte
|
|
13
|
+
* <SvMenu items={items} onSelect={(i) => run(i)}>
|
|
14
|
+
* {#snippet anchor()}<button>Actions</button>{/snippet}
|
|
15
|
+
* </SvMenu>
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import type { Snippet } from 'svelte'
|
|
19
|
+
import { anchoredRect, portalToBody, popIn, type AnchoredRect } from './popover'
|
|
20
|
+
import { createDismissableLayer } from './a11y/dismissable'
|
|
21
|
+
import { getFocusable } from './a11y/focus-trap'
|
|
22
|
+
import { type EditorDir } from './editor-contract'
|
|
23
|
+
import SvMenuList, { type MenuItem } from './SvMenuList.svelte'
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
items,
|
|
27
|
+
open = $bindable(false),
|
|
28
|
+
onOpenChange,
|
|
29
|
+
onSelect,
|
|
30
|
+
ariaLabel,
|
|
31
|
+
anchor,
|
|
32
|
+
dir,
|
|
33
|
+
}: {
|
|
34
|
+
items: ReadonlyArray<MenuItem>
|
|
35
|
+
open?: boolean
|
|
36
|
+
onOpenChange?: (open: boolean) => void
|
|
37
|
+
onSelect?: (item: MenuItem) => void
|
|
38
|
+
ariaLabel?: string
|
|
39
|
+
anchor?: Snippet
|
|
40
|
+
/** Text direction (rtl mirrors the submenu chevron + flips ArrowLeft/Right). */
|
|
41
|
+
dir?: EditorDir
|
|
42
|
+
} = $props()
|
|
43
|
+
|
|
44
|
+
const resolvedDir = $derived(dir === 'ltr' || dir === 'rtl' ? dir : undefined)
|
|
45
|
+
|
|
46
|
+
let anchorEl = $state<HTMLSpanElement | null>(null)
|
|
47
|
+
let panelEl = $state<HTMLDivElement | null>(null)
|
|
48
|
+
let rect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false, maxHeight: 0, availHeight: 0 })
|
|
49
|
+
|
|
50
|
+
function setOpen(v: boolean) { if (v !== open) { open = v; onOpenChange?.(v) } }
|
|
51
|
+
function toggle() { setOpen(!open) }
|
|
52
|
+
function updatePos() {
|
|
53
|
+
if (!anchorEl) return
|
|
54
|
+
rect = anchoredRect(anchorEl.getBoundingClientRect(), { estimatedHeight: Math.min(items.length, 10) * 34 + 8, minWidth: 180 })
|
|
55
|
+
}
|
|
56
|
+
/** Return focus to the trigger. Every dismissal path (Escape, item selection,
|
|
57
|
+
* outside-click, Tab) funnels through the effect cleanup below, so this
|
|
58
|
+
* fires uniformly instead of dropping focus to <body> on close. */
|
|
59
|
+
function focusTrigger() {
|
|
60
|
+
if (!anchorEl) return
|
|
61
|
+
;(getFocusable(anchorEl)[0] ?? anchorEl)?.focus?.()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
$effect(() => {
|
|
65
|
+
if (!open) return
|
|
66
|
+
updatePos()
|
|
67
|
+
queueMicrotask(() => panelEl?.querySelector<HTMLElement>('[role="menuitem"]:not([disabled])')?.focus())
|
|
68
|
+
const rp = () => updatePos()
|
|
69
|
+
window.addEventListener('scroll', rp, true)
|
|
70
|
+
window.addEventListener('resize', rp)
|
|
71
|
+
// Outside-click dismissal via the shared layer stack (Escape stays owned by
|
|
72
|
+
// SvMenuList's keyboard handling, so closeOnEscape is off here).
|
|
73
|
+
const layer = createDismissableLayer({
|
|
74
|
+
element: () => [anchorEl, panelEl],
|
|
75
|
+
onDismiss: () => setOpen(false),
|
|
76
|
+
closeOnEscape: false,
|
|
77
|
+
})
|
|
78
|
+
layer.activate()
|
|
79
|
+
return () => {
|
|
80
|
+
window.removeEventListener('scroll', rp, true)
|
|
81
|
+
window.removeEventListener('resize', rp)
|
|
82
|
+
layer.release()
|
|
83
|
+
// Only on an actual close (not a mid-open rerun from e.g. `items` changing).
|
|
84
|
+
if (!open) focusTrigger()
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
|
|
90
|
+
<span
|
|
91
|
+
bind:this={anchorEl}
|
|
92
|
+
class="sv-menu__anchor"
|
|
93
|
+
aria-haspopup="menu"
|
|
94
|
+
aria-expanded={open}
|
|
95
|
+
onclick={toggle}
|
|
96
|
+
>
|
|
97
|
+
{@render anchor?.()}
|
|
98
|
+
</span>
|
|
99
|
+
|
|
100
|
+
{#if open}
|
|
101
|
+
<div
|
|
102
|
+
bind:this={panelEl}
|
|
103
|
+
class="sv-menu"
|
|
104
|
+
use:portalToBody
|
|
105
|
+
use:popIn={{ up: rect.openUpward }}
|
|
106
|
+
style:position="fixed"
|
|
107
|
+
style:top={`${rect.top}px`}
|
|
108
|
+
style:left={`${rect.left}px`}
|
|
109
|
+
dir={resolvedDir}
|
|
110
|
+
aria-label={ariaLabel}
|
|
111
|
+
>
|
|
112
|
+
<SvMenuList {items} onclose={() => setOpen(false)} onselect={(i) => onSelect?.(i)} dir={resolvedDir} />
|
|
113
|
+
</div>
|
|
114
|
+
{/if}
|
|
115
|
+
|
|
116
|
+
<style>
|
|
117
|
+
.sv-menu__anchor { display: inline-flex; }
|
|
118
|
+
:global(.sv-menu) {
|
|
119
|
+
z-index: 2147483646; min-width: 180px;
|
|
120
|
+
background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
121
|
+
border: 1px solid var(--sg-border, #e2e8f0); border-radius: 10px;
|
|
122
|
+
box-shadow: 0 16px 48px -12px rgba(15, 23, 42, 0.35); font-size: 13px;
|
|
123
|
+
}
|
|
124
|
+
</style>
|