@svgrid/grid 2.2.16 → 2.2.18
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
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* the masked display value and the raw (unmasked) value.
|
|
5
5
|
*
|
|
6
6
|
* The styled renderer over the headless `createMaskedInput` core; the mask
|
|
7
|
-
* formatting + state live in the core, spread here via prop-getters.
|
|
7
|
+
* formatting + state live in the core, spread here via prop-getters. The box,
|
|
8
|
+
* size, invalid state, clear button and adornments are owned by SvField's
|
|
9
|
+
* `frame` chrome.
|
|
8
10
|
*/
|
|
9
11
|
import type { Snippet } from 'svelte';
|
|
10
12
|
import { type SvEditorProps } from './editor-contract';
|
|
@@ -16,9 +18,22 @@ type Props = SvEditorProps & {
|
|
|
16
18
|
placeholder?: string;
|
|
17
19
|
/** Show a clear (x) button when there is a value. */
|
|
18
20
|
clearable?: boolean;
|
|
19
|
-
/** Leading /
|
|
21
|
+
/** Leading adornment (icon/button) inside the field. */
|
|
22
|
+
leading?: Snippet;
|
|
23
|
+
/** Trailing adornment (icon/button) inside the field. */
|
|
24
|
+
trailing?: Snippet;
|
|
25
|
+
/** Plain-text affix at the start. */
|
|
26
|
+
prefix?: string;
|
|
27
|
+
/** Plain-text affix at the end. */
|
|
28
|
+
suffix?: string;
|
|
29
|
+
/** @deprecated Use `leading`. */
|
|
20
30
|
prefixIcon?: Snippet;
|
|
31
|
+
/** @deprecated Use `trailing`. */
|
|
21
32
|
suffixIcon?: Snippet;
|
|
33
|
+
/** Stretch to the container width. */
|
|
34
|
+
block?: boolean;
|
|
35
|
+
/** Control width in px (ignored when `block`). Default 200. */
|
|
36
|
+
width?: number;
|
|
22
37
|
};
|
|
23
38
|
declare const SvMaskedInput: import("svelte").Component<Props, {}, "value">;
|
|
24
39
|
type SvMaskedInput = ReturnType<typeof SvMaskedInput>;
|
package/dist/SvMenuList.svelte
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { type EditorDir } from './editor-contract'
|
|
16
16
|
import type { MenuItem } from './menu-item'
|
|
17
17
|
import { createMenu } from './createMenu.svelte'
|
|
18
|
+
import { computePosition, autoUpdate, type Placement } from './positioning'
|
|
18
19
|
|
|
19
20
|
let {
|
|
20
21
|
items,
|
|
@@ -53,6 +54,32 @@
|
|
|
53
54
|
dir: () => dir,
|
|
54
55
|
focusItem: (i) => queueMicrotask(() => listEl?.querySelector<HTMLElement>(`[data-mi="${i}"]`)?.focus()),
|
|
55
56
|
})
|
|
57
|
+
|
|
58
|
+
// Position an open submenu flyout with the shared engine: prefer opening to the
|
|
59
|
+
// side away from the reading direction, flip to the opposite side when there is
|
|
60
|
+
// no room, and clamp vertically to stay in view. Kept absolute (relative to the
|
|
61
|
+
// itemwrap) so a nested flyout is never clipped by a scroll container.
|
|
62
|
+
const startSide: Placement = dir === 'rtl' ? 'left-start' : 'right-start'
|
|
63
|
+
function flyout(node: HTMLElement) {
|
|
64
|
+
const wrap = node.parentElement // .sv-menu__itemwrap
|
|
65
|
+
const btn = wrap?.querySelector<HTMLElement>(':scope > .sv-menu__item') ?? null
|
|
66
|
+
const update = () => {
|
|
67
|
+
if (!btn || !wrap) return
|
|
68
|
+
const r = btn.getBoundingClientRect()
|
|
69
|
+
const w = wrap.getBoundingClientRect()
|
|
70
|
+
const f = { width: node.offsetWidth || 200, height: node.offsetHeight || 120 }
|
|
71
|
+
const p = computePosition(
|
|
72
|
+
{ x: r.left, y: r.top, width: r.width, height: r.height },
|
|
73
|
+
f,
|
|
74
|
+
{ placement: startSide, offset: 2, padding: 8 },
|
|
75
|
+
)
|
|
76
|
+
node.style.left = `${p.x - w.left}px`
|
|
77
|
+
node.style.top = `${p.y - w.top}px`
|
|
78
|
+
}
|
|
79
|
+
if (!btn) { update(); return {} }
|
|
80
|
+
const stop = autoUpdate(btn, node, update)
|
|
81
|
+
return { destroy() { stop() } }
|
|
82
|
+
}
|
|
56
83
|
</script>
|
|
57
84
|
|
|
58
85
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
@@ -74,7 +101,7 @@
|
|
|
74
101
|
{#if item.children?.length}<svg class="sv-menu__chev" viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6" /></svg>{/if}
|
|
75
102
|
</button>
|
|
76
103
|
{#if item.children?.length && menu.isSubOpen(i)}
|
|
77
|
-
<div class="sv-menu__flyout">
|
|
104
|
+
<div class="sv-menu__flyout" use:flyout>
|
|
78
105
|
<Self
|
|
79
106
|
items={item.children}
|
|
80
107
|
{onclose}
|
|
@@ -109,9 +136,11 @@
|
|
|
109
136
|
.sv-menu__shortcut { color: var(--sg-muted, #94a3b8); font-size: 11.5px; margin-inline-start: 12px; }
|
|
110
137
|
.sv-menu__chev { color: var(--sg-muted, #94a3b8); flex: none; }
|
|
111
138
|
.sv-menu[dir='rtl'] .sv-menu__chev, .sv-menu__list.is-submenu .sv-menu__chev { transform: scaleX(-1); }
|
|
139
|
+
/* top/left are the pre-JS fallback (open to the right); the `flyout` action
|
|
140
|
+
overrides them with engine-computed offsets (flip + vertical clamp). */
|
|
112
141
|
.sv-menu__flyout {
|
|
113
|
-
position: absolute; top: -5px;
|
|
142
|
+
position: absolute; top: -5px; left: 100%; z-index: 1;
|
|
114
143
|
background: var(--sg-bg, #fff); border: 1px solid var(--sg-border, #e2e8f0);
|
|
115
|
-
border-radius: 10px; box-shadow: 0 16px 40px -12px rgba(15,23,42,0.32);
|
|
144
|
+
border-radius: 10px; box-shadow: 0 16px 40px -12px rgba(15,23,42,0.32);
|
|
116
145
|
}
|
|
117
146
|
</style>
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export type MenubarMenu = {
|
|
3
|
+
/** Trigger label on the bar. */
|
|
4
|
+
label: string
|
|
5
|
+
/** The dropdown's items (submenus, separators, icons, shortcuts all work). */
|
|
6
|
+
items: import('./menu-item').MenuItem[]
|
|
7
|
+
disabled?: boolean
|
|
8
|
+
}
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<script lang="ts">
|
|
12
|
+
/**
|
|
13
|
+
* SvMenubar - an application menu bar: a horizontal row of menu buttons
|
|
14
|
+
* (File / Edit / View ...) that each open a dropdown built on SvMenuList.
|
|
15
|
+
* Roving focus across the bar, ArrowLeft/Right move between menus, ArrowDown /
|
|
16
|
+
* Enter open the active one, hovering another trigger switches while a menu is
|
|
17
|
+
* open, and Escape closes and returns focus to the trigger. The dropdown is
|
|
18
|
+
* portalled and positioned by the shared engine.
|
|
19
|
+
*
|
|
20
|
+
* ```svelte
|
|
21
|
+
* <SvMenubar menus={[{ label: 'File', items: fileItems }, ...]}
|
|
22
|
+
* onSelect={(i) => run(i)} />
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
import { portalToBody, popIn } from './popover'
|
|
26
|
+
import { computePosition, autoUpdate, type ComputePositionResult } from './positioning'
|
|
27
|
+
import { createDismissableLayer } from './a11y/dismissable'
|
|
28
|
+
import { type EditorDir } from './editor-contract'
|
|
29
|
+
import SvMenuList from './SvMenuList.svelte'
|
|
30
|
+
import type { MenuItem } from './menu-item'
|
|
31
|
+
|
|
32
|
+
type Props = {
|
|
33
|
+
menus: ReadonlyArray<MenubarMenu>
|
|
34
|
+
onSelect?: (item: MenuItem) => void
|
|
35
|
+
ariaLabel?: string
|
|
36
|
+
dir?: EditorDir
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let { menus, onSelect, ariaLabel = 'Menu bar', dir }: Props = $props()
|
|
40
|
+
|
|
41
|
+
const resolvedDir = $derived(dir === 'ltr' || dir === 'rtl' ? dir : undefined)
|
|
42
|
+
const rtl = $derived(resolvedDir === 'rtl')
|
|
43
|
+
|
|
44
|
+
let triggerEls = $state<(HTMLButtonElement | null)[]>([])
|
|
45
|
+
let panelEl = $state<HTMLDivElement | null>(null)
|
|
46
|
+
let openIndex = $state(-1)
|
|
47
|
+
let activeIndex = $state(0)
|
|
48
|
+
let pos = $state<ComputePositionResult>({ x: 0, y: 0, placement: 'bottom-start', side: 'bottom', align: 'start', maxWidth: 0, maxHeight: 0 })
|
|
49
|
+
|
|
50
|
+
const enabled = (i: number) => !menus[i]?.disabled
|
|
51
|
+
|
|
52
|
+
function focusTrigger(i: number) { queueMicrotask(() => triggerEls[i]?.focus()) }
|
|
53
|
+
function focusFirstItem() {
|
|
54
|
+
queueMicrotask(() => panelEl?.querySelector<HTMLElement>('[role="menuitem"]:not([disabled])')?.focus())
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function open(i: number) {
|
|
58
|
+
if (!enabled(i)) return
|
|
59
|
+
openIndex = i
|
|
60
|
+
activeIndex = i
|
|
61
|
+
focusFirstItem()
|
|
62
|
+
}
|
|
63
|
+
function close(refocus = true) {
|
|
64
|
+
const i = openIndex
|
|
65
|
+
openIndex = -1
|
|
66
|
+
if (refocus && i >= 0) focusTrigger(i)
|
|
67
|
+
}
|
|
68
|
+
function toggle(i: number) { if (openIndex === i) close(); else open(i) }
|
|
69
|
+
function onTriggerEnter(i: number) {
|
|
70
|
+
// While a menu is open, hovering another trigger switches to it (menubar UX).
|
|
71
|
+
if (openIndex >= 0 && openIndex !== i && enabled(i)) open(i)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function step(from: number, delta: number): number {
|
|
75
|
+
const n = menus.length
|
|
76
|
+
for (let k = 1; k <= n; k++) {
|
|
77
|
+
const i = (from + delta * k + n * k) % n
|
|
78
|
+
if (enabled(i)) return i
|
|
79
|
+
}
|
|
80
|
+
return from
|
|
81
|
+
}
|
|
82
|
+
function moveActive(delta: number) {
|
|
83
|
+
activeIndex = step(activeIndex, delta)
|
|
84
|
+
focusTrigger(activeIndex)
|
|
85
|
+
if (openIndex >= 0) open(activeIndex)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function onBarKeydown(e: KeyboardEvent) {
|
|
89
|
+
const fwd = rtl ? 'ArrowLeft' : 'ArrowRight'
|
|
90
|
+
const back = rtl ? 'ArrowRight' : 'ArrowLeft'
|
|
91
|
+
switch (e.key) {
|
|
92
|
+
case fwd: e.preventDefault(); moveActive(1); break
|
|
93
|
+
case back: e.preventDefault(); moveActive(-1); break
|
|
94
|
+
case 'Home': e.preventDefault(); activeIndex = step(-1, 1); focusTrigger(activeIndex); if (openIndex >= 0) open(activeIndex); break
|
|
95
|
+
case 'End': e.preventDefault(); activeIndex = step(menus.length, -1); focusTrigger(activeIndex); if (openIndex >= 0) open(activeIndex); break
|
|
96
|
+
case 'ArrowDown':
|
|
97
|
+
case 'Enter':
|
|
98
|
+
case ' ': e.preventDefault(); open(activeIndex); break
|
|
99
|
+
case 'Escape': if (openIndex >= 0) { e.preventDefault(); close() } break
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function updatePos() {
|
|
104
|
+
const trigger = triggerEls[openIndex]
|
|
105
|
+
if (!trigger) return
|
|
106
|
+
const r = trigger.getBoundingClientRect()
|
|
107
|
+
const f = { width: panelEl?.offsetWidth || 180, height: panelEl?.offsetHeight || 200 }
|
|
108
|
+
pos = computePosition(
|
|
109
|
+
{ x: r.left, y: r.top, width: r.width, height: r.height },
|
|
110
|
+
f,
|
|
111
|
+
{ placement: rtl ? 'bottom-end' : 'bottom-start', offset: 4, padding: 8, minMainAxis: 96 },
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Position + dismiss the open dropdown, and let ArrowLeft/Right at the root menu
|
|
116
|
+
// level switch to the adjacent menubar menu (WAI-ARIA menubar behavior). An
|
|
117
|
+
// ArrowRight on a submenu parent still opens its submenu; ArrowLeft inside a
|
|
118
|
+
// submenu still collapses - those are left to SvMenuList.
|
|
119
|
+
$effect(() => {
|
|
120
|
+
const trigger = triggerEls[openIndex]
|
|
121
|
+
if (openIndex < 0 || !panelEl || !trigger) return
|
|
122
|
+
const stop = autoUpdate(trigger, panelEl, updatePos)
|
|
123
|
+
const layer = createDismissableLayer({
|
|
124
|
+
element: () => [trigger, panelEl],
|
|
125
|
+
onDismiss: () => close(false),
|
|
126
|
+
closeOnEscape: false, // Escape is owned by SvMenuList -> onclose -> close()
|
|
127
|
+
})
|
|
128
|
+
layer.activate()
|
|
129
|
+
|
|
130
|
+
const onKey = (e: KeyboardEvent) => {
|
|
131
|
+
const el = document.activeElement as HTMLElement | null
|
|
132
|
+
const list = el?.closest('.sv-menu__list')
|
|
133
|
+
const inSubmenu = !!list?.classList.contains('is-submenu')
|
|
134
|
+
const fwd = rtl ? 'ArrowLeft' : 'ArrowRight'
|
|
135
|
+
const back = rtl ? 'ArrowRight' : 'ArrowLeft'
|
|
136
|
+
if (e.key === fwd) {
|
|
137
|
+
if (el?.getAttribute('aria-haspopup') === 'menu') return // opens a submenu
|
|
138
|
+
e.preventDefault(); open(step(openIndex, 1))
|
|
139
|
+
} else if (e.key === back) {
|
|
140
|
+
if (inSubmenu) return // collapse the submenu instead
|
|
141
|
+
e.preventDefault(); open(step(openIndex, -1))
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
document.addEventListener('keydown', onKey, true)
|
|
145
|
+
return () => { stop(); layer.release(); document.removeEventListener('keydown', onKey, true) }
|
|
146
|
+
})
|
|
147
|
+
</script>
|
|
148
|
+
|
|
149
|
+
<div class="sv-menubar" role="menubar" aria-label={ariaLabel} dir={resolvedDir} onkeydown={onBarKeydown}>
|
|
150
|
+
{#each menus as m, i (i)}
|
|
151
|
+
<button
|
|
152
|
+
type="button"
|
|
153
|
+
bind:this={triggerEls[i]}
|
|
154
|
+
class="sv-menubar__item"
|
|
155
|
+
class:is-open={openIndex === i}
|
|
156
|
+
role="menuitem"
|
|
157
|
+
aria-haspopup="menu"
|
|
158
|
+
aria-expanded={openIndex === i}
|
|
159
|
+
tabindex={activeIndex === i ? 0 : -1}
|
|
160
|
+
disabled={m.disabled}
|
|
161
|
+
onclick={() => toggle(i)}
|
|
162
|
+
onpointerenter={() => onTriggerEnter(i)}
|
|
163
|
+
>{m.label}</button>
|
|
164
|
+
{/each}
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
{#if openIndex >= 0}
|
|
168
|
+
<div
|
|
169
|
+
bind:this={panelEl}
|
|
170
|
+
class="sv-menu"
|
|
171
|
+
use:portalToBody
|
|
172
|
+
use:popIn={{ up: pos.side === 'top' }}
|
|
173
|
+
style:position="fixed"
|
|
174
|
+
style:top={`${pos.y}px`}
|
|
175
|
+
style:left={`${pos.x}px`}
|
|
176
|
+
dir={resolvedDir}
|
|
177
|
+
>
|
|
178
|
+
<SvMenuList
|
|
179
|
+
items={menus[openIndex]?.items ?? []}
|
|
180
|
+
onclose={() => close()}
|
|
181
|
+
onselect={(item) => { onSelect?.(item); close() }}
|
|
182
|
+
dir={resolvedDir}
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
185
|
+
{/if}
|
|
186
|
+
|
|
187
|
+
<style>
|
|
188
|
+
.sv-menubar {
|
|
189
|
+
display: inline-flex; align-items: center; gap: 2px; padding: 3px;
|
|
190
|
+
background: var(--sg-bg, #fff); border: 1px solid var(--sg-border, #e2e8f0); border-radius: 9px;
|
|
191
|
+
}
|
|
192
|
+
.sv-menubar__item {
|
|
193
|
+
font: inherit; font-size: 13px; font-weight: 500; color: var(--sg-fg, #0f172a);
|
|
194
|
+
padding: 5px 11px; background: none; border: 0; border-radius: 6px; cursor: pointer;
|
|
195
|
+
transition: background 0.1s;
|
|
196
|
+
}
|
|
197
|
+
.sv-menubar__item:hover:not([disabled]), .sv-menubar__item.is-open { background: var(--sg-row-hover-bg, #f1f5f9); }
|
|
198
|
+
.sv-menubar__item:focus-visible { outline: 2px solid var(--sg-focus-ring, var(--sg-accent, #2563eb)); outline-offset: -2px; }
|
|
199
|
+
.sv-menubar__item[disabled] { opacity: 0.45; cursor: default; }
|
|
200
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type MenubarMenu = {
|
|
2
|
+
/** Trigger label on the bar. */
|
|
3
|
+
label: string;
|
|
4
|
+
/** The dropdown's items (submenus, separators, icons, shortcuts all work). */
|
|
5
|
+
items: import('./menu-item').MenuItem[];
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
};
|
|
8
|
+
import { type EditorDir } from './editor-contract';
|
|
9
|
+
import type { MenuItem } from './menu-item';
|
|
10
|
+
type Props = {
|
|
11
|
+
menus: ReadonlyArray<MenubarMenu>;
|
|
12
|
+
onSelect?: (item: MenuItem) => void;
|
|
13
|
+
ariaLabel?: string;
|
|
14
|
+
dir?: EditorDir;
|
|
15
|
+
};
|
|
16
|
+
declare const SvMenubar: import("svelte").Component<Props, {}, "">;
|
|
17
|
+
type SvMenubar = ReturnType<typeof SvMenubar>;
|
|
18
|
+
export default SvMenubar;
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
type Props = Pick<
|
|
22
22
|
SvEditorProps,
|
|
23
|
-
'disabled' | 'label' | 'hint' | 'error' | 'required' | 'invalid' | 'size' | 'id' | 'name' | 'dir' | 'ariaLabel'
|
|
23
|
+
'disabled' | 'readonly' | 'label' | 'hint' | 'error' | 'required' | 'invalid' | 'size' | 'id' | 'name' | 'dir' | 'ariaLabel' | 'loading'
|
|
24
24
|
> & {
|
|
25
25
|
options: ReadonlyArray<MultiSelectOption>
|
|
26
26
|
value?: ReadonlyArray<string | number>
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
debounceMs = 250,
|
|
54
54
|
loadingText = 'Loading…',
|
|
55
55
|
disabled = false,
|
|
56
|
+
readonly = false,
|
|
56
57
|
label,
|
|
57
58
|
hint,
|
|
58
59
|
error,
|
|
@@ -63,6 +64,7 @@
|
|
|
63
64
|
name,
|
|
64
65
|
dir,
|
|
65
66
|
ariaLabel,
|
|
67
|
+
loading: loadingProp = false,
|
|
66
68
|
}: Props = $props()
|
|
67
69
|
|
|
68
70
|
const autoId = nextEditorId('sv-ms')
|
|
@@ -120,6 +122,7 @@
|
|
|
120
122
|
const ms = createPopoverSelect({
|
|
121
123
|
itemCount: () => filtered.length,
|
|
122
124
|
disabled: (i) => !!filtered[i]?.disabled,
|
|
125
|
+
readonly: () => readonly,
|
|
123
126
|
onSelect: (i) => { const o = filtered[i]; if (o) toggleOption(o) },
|
|
124
127
|
onOpenChange: (o) => { if (o) { search = ''; if (loadOptions) doLoad('') } },
|
|
125
128
|
getTrigger: () => triggerEl,
|
|
@@ -131,7 +134,7 @@
|
|
|
131
134
|
})
|
|
132
135
|
</script>
|
|
133
136
|
|
|
134
|
-
<SvField id={uid} {label} {hint} {error} {required} {dir}>
|
|
137
|
+
<SvField id={uid} {label} {hint} {error} {required} {dir} loading={loadingProp || loading}>
|
|
135
138
|
<div class="sv-ms sv-ms--{size}" class:is-disabled={disabled}>
|
|
136
139
|
<button
|
|
137
140
|
bind:this={triggerEl}
|
|
@@ -147,7 +150,7 @@
|
|
|
147
150
|
<span class="sv-ms__placeholder">{placeholder}</span>
|
|
148
151
|
{:else}
|
|
149
152
|
<span class="sv-ms__chips">
|
|
150
|
-
{#each selectedOptions.slice(0, maxTagCount) as opt (
|
|
153
|
+
{#each selectedOptions.slice(0, maxTagCount) as opt, i (i)}
|
|
151
154
|
<span class="sv-ms__chip">{opt.label}</span>
|
|
152
155
|
{/each}
|
|
153
156
|
{#if selectedOptions.length > maxTagCount}
|
|
@@ -206,7 +209,7 @@
|
|
|
206
209
|
aria-activedescendant={searchable ? undefined : ms.activeDescendant()}
|
|
207
210
|
tabindex={searchable ? -1 : 0}
|
|
208
211
|
>
|
|
209
|
-
{#each filtered as opt, i (
|
|
212
|
+
{#each filtered as opt, i (i)}
|
|
210
213
|
<li
|
|
211
214
|
class="sv-ms__opt"
|
|
212
215
|
class:is-active={ms.isActive(i)}
|
|
@@ -9,7 +9,7 @@ export type { MultiSelectOption } from './ui-app.types';
|
|
|
9
9
|
*/
|
|
10
10
|
import type { MultiSelectOption } from './ui-app.types';
|
|
11
11
|
import { type SvEditorProps } from './editor-contract';
|
|
12
|
-
type Props = Pick<SvEditorProps, 'disabled' | 'label' | 'hint' | 'error' | 'required' | 'invalid' | 'size' | 'id' | 'name' | 'dir' | 'ariaLabel'> & {
|
|
12
|
+
type Props = Pick<SvEditorProps, 'disabled' | 'readonly' | 'label' | 'hint' | 'error' | 'required' | 'invalid' | 'size' | 'id' | 'name' | 'dir' | 'ariaLabel' | 'loading'> & {
|
|
13
13
|
options: ReadonlyArray<MultiSelectOption>;
|
|
14
14
|
value?: ReadonlyArray<string | number>;
|
|
15
15
|
onChange?: (value: Array<string | number>) => void;
|
|
@@ -6,8 +6,12 @@
|
|
|
6
6
|
*
|
|
7
7
|
* This is the styled renderer over the headless `createNumberInput` core - the
|
|
8
8
|
* same split as the grid (`createSvGrid` / `<SvGrid>`): parse/format/clamp,
|
|
9
|
-
* spinner + keyboard and ARIA all come from the core via prop-getters.
|
|
9
|
+
* spinner + keyboard and ARIA all come from the core via prop-getters. The box,
|
|
10
|
+
* size, invalid state and focus ring are owned by SvField's `frame` chrome; the
|
|
11
|
+
* `prefix`/`suffix` are formatted INTO the value (they travel with the
|
|
12
|
+
* right-aligned number), and the spinner + clear live in the trailing slot.
|
|
10
13
|
*/
|
|
14
|
+
import type { Snippet } from 'svelte'
|
|
11
15
|
import SvRepeatButton from './SvRepeatButton.svelte'
|
|
12
16
|
import SvField from './SvField.svelte'
|
|
13
17
|
import { nextEditorId, type SvEditorProps } from './editor-contract'
|
|
@@ -22,12 +26,24 @@
|
|
|
22
26
|
precision?: number
|
|
23
27
|
/** Group thousands (1,234) on display. */
|
|
24
28
|
grouping?: boolean
|
|
29
|
+
/** Text formatted before the number (e.g. `$`); travels with the value. */
|
|
25
30
|
prefix?: string
|
|
31
|
+
/** Text formatted after the number (e.g. `%`); travels with the value. */
|
|
26
32
|
suffix?: string
|
|
27
33
|
placeholder?: string
|
|
28
34
|
spinButtons?: boolean
|
|
29
35
|
/** Show a clear (x) button when there is a value. */
|
|
30
36
|
clearable?: boolean
|
|
37
|
+
/** Select the whole value when the field is focused. */
|
|
38
|
+
selectOnFocus?: boolean
|
|
39
|
+
/** Step the value with the mouse wheel while the field is focused. */
|
|
40
|
+
wheelStep?: boolean
|
|
41
|
+
/** Leading adornment (icon) inside the field. */
|
|
42
|
+
leading?: Snippet
|
|
43
|
+
/** Stretch to the container width. */
|
|
44
|
+
block?: boolean
|
|
45
|
+
/** Control width in px (ignored when `block`). Default 150. */
|
|
46
|
+
width?: number
|
|
31
47
|
}
|
|
32
48
|
|
|
33
49
|
let {
|
|
@@ -45,6 +61,8 @@
|
|
|
45
61
|
readonly = false,
|
|
46
62
|
spinButtons = true,
|
|
47
63
|
clearable = false,
|
|
64
|
+
selectOnFocus = false,
|
|
65
|
+
wheelStep = false,
|
|
48
66
|
name,
|
|
49
67
|
size = 'md',
|
|
50
68
|
ariaLabel,
|
|
@@ -55,6 +73,10 @@
|
|
|
55
73
|
hint,
|
|
56
74
|
dir,
|
|
57
75
|
id,
|
|
76
|
+
loading = false,
|
|
77
|
+
leading,
|
|
78
|
+
block = false,
|
|
79
|
+
width = 150,
|
|
58
80
|
}: Props = $props()
|
|
59
81
|
|
|
60
82
|
const autoId = nextEditorId('sv-num')
|
|
@@ -81,51 +103,66 @@
|
|
|
81
103
|
hint: () => hint,
|
|
82
104
|
ariaLabel: () => ariaLabel,
|
|
83
105
|
})
|
|
106
|
+
|
|
107
|
+
// DOM-bound ergonomics the headless core stays out of: select-all on focus and
|
|
108
|
+
// wheel-to-step (only while focused, so scrolling the page never changes it).
|
|
109
|
+
function numExtras(node: HTMLInputElement) {
|
|
110
|
+
const onFocus = () => { if (selectOnFocus) requestAnimationFrame(() => node.select()) }
|
|
111
|
+
const onWheel = (e: WheelEvent) => {
|
|
112
|
+
if (!wheelStep || document.activeElement !== node || !num.isInteractive) return
|
|
113
|
+
e.preventDefault()
|
|
114
|
+
num.bump(e.deltaY < 0 ? 1 : -1)
|
|
115
|
+
}
|
|
116
|
+
node.addEventListener('focus', onFocus)
|
|
117
|
+
node.addEventListener('wheel', onWheel, { passive: false })
|
|
118
|
+
return { destroy() { node.removeEventListener('focus', onFocus); node.removeEventListener('wheel', onWheel) } }
|
|
119
|
+
}
|
|
84
120
|
</script>
|
|
85
121
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
122
|
+
{#snippet trailing()}
|
|
123
|
+
{#if clearable && value != null && !disabled && !readonly}
|
|
124
|
+
<button type="button" class="sv-num__clear" aria-label="Clear" tabindex="-1" onclick={() => { value = null; onChange?.(null) }}>
|
|
125
|
+
<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18" /></svg>
|
|
126
|
+
</button>
|
|
127
|
+
{/if}
|
|
128
|
+
{#if spinButtons}
|
|
129
|
+
<div class="sv-num__spin">
|
|
130
|
+
<SvRepeatButton size="sm" variant="ghost" ariaLabel="Increment" onclick={num.increment}>
|
|
131
|
+
<svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M6 15l6-6 6 6" /></svg>
|
|
132
|
+
</SvRepeatButton>
|
|
133
|
+
<SvRepeatButton size="sm" variant="ghost" ariaLabel="Decrement" onclick={num.decrement}>
|
|
134
|
+
<svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6" /></svg>
|
|
135
|
+
</SvRepeatButton>
|
|
136
|
+
</div>
|
|
137
|
+
{/if}
|
|
138
|
+
{/snippet}
|
|
139
|
+
|
|
140
|
+
<SvField
|
|
141
|
+
frame
|
|
142
|
+
id={uid}
|
|
143
|
+
{label}
|
|
144
|
+
{hint}
|
|
145
|
+
{error}
|
|
146
|
+
{required}
|
|
147
|
+
{dir}
|
|
148
|
+
{size}
|
|
149
|
+
{invalid}
|
|
150
|
+
{disabled}
|
|
151
|
+
{readonly}
|
|
152
|
+
{loading}
|
|
153
|
+
{block}
|
|
154
|
+
{width}
|
|
155
|
+
{leading}
|
|
156
|
+
{trailing}
|
|
157
|
+
>
|
|
158
|
+
<input class="sv-num__input" use:numExtras {...num.inputProps()} />
|
|
159
|
+
{#if name}<input type="hidden" {name} value={value ?? ''} />{/if}
|
|
106
160
|
</SvField>
|
|
107
161
|
|
|
108
162
|
<style>
|
|
109
|
-
.sv-
|
|
110
|
-
|
|
111
|
-
display: inline-flex; align-items: stretch; width: 150px;
|
|
112
|
-
background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
113
|
-
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
114
|
-
overflow: hidden;
|
|
115
|
-
}
|
|
116
|
-
.sv-num:focus-within { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
117
|
-
.sv-num.is-invalid { border-color: var(--sg-danger, #dc2626); }
|
|
118
|
-
.sv-num.is-invalid:focus-within { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
|
|
119
|
-
.sv-num.is-disabled { opacity: 0.6; }
|
|
120
|
-
.sv-num__input {
|
|
121
|
-
flex: 1; min-width: 0; border: 0; background: none; outline: none; color: inherit; font: inherit;
|
|
122
|
-
text-align: end; padding: 0 8px;
|
|
123
|
-
}
|
|
124
|
-
.sv-num--sm { height: 28px; font-size: 12px; }
|
|
125
|
-
.sv-num--md { height: 34px; font-size: 13px; }
|
|
126
|
-
.sv-num--lg { height: 40px; font-size: 15px; }
|
|
127
|
-
.sv-num__clear { display: grid; place-items: center; width: 22px; align-self: center; flex: none; background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; border-radius: 4px; }
|
|
163
|
+
.sv-num__input { text-align: end; }
|
|
164
|
+
.sv-num__clear { display: grid; place-items: center; width: 20px; align-self: center; flex: none; background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; border-radius: 4px; }
|
|
128
165
|
.sv-num__clear:hover { color: var(--sg-danger, #dc2626); }
|
|
129
|
-
.sv-num__spin { display: flex; flex-direction: column;
|
|
130
|
-
.sv-num__spin :global(.sv-repeat) { flex: 1; padding: 0
|
|
166
|
+
.sv-num__spin { display: flex; flex-direction: column; margin-inline-start: 2px; }
|
|
167
|
+
.sv-num__spin :global(.sv-repeat) { flex: 1; min-height: 0; padding: 0 5px; border-radius: 4px; line-height: 1; }
|
|
131
168
|
</style>
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SvNumberInput - a numeric input with min/max/step, spinner buttons, optional
|
|
3
|
+
* thousands grouping + precision, prefix/suffix. Parity: Smart `smart-number-input`.
|
|
4
|
+
* Emits number | null. The SvGrid number cell editor, standalone too.
|
|
5
|
+
*
|
|
6
|
+
* This is the styled renderer over the headless `createNumberInput` core - the
|
|
7
|
+
* same split as the grid (`createSvGrid` / `<SvGrid>`): parse/format/clamp,
|
|
8
|
+
* spinner + keyboard and ARIA all come from the core via prop-getters. The box,
|
|
9
|
+
* size, invalid state and focus ring are owned by SvField's `frame` chrome; the
|
|
10
|
+
* `prefix`/`suffix` are formatted INTO the value (they travel with the
|
|
11
|
+
* right-aligned number), and the spinner + clear live in the trailing slot.
|
|
12
|
+
*/
|
|
13
|
+
import type { Snippet } from 'svelte';
|
|
1
14
|
import { type SvEditorProps } from './editor-contract';
|
|
2
15
|
type Props = SvEditorProps & {
|
|
3
16
|
value?: number | null;
|
|
@@ -8,12 +21,24 @@ type Props = SvEditorProps & {
|
|
|
8
21
|
precision?: number;
|
|
9
22
|
/** Group thousands (1,234) on display. */
|
|
10
23
|
grouping?: boolean;
|
|
24
|
+
/** Text formatted before the number (e.g. `$`); travels with the value. */
|
|
11
25
|
prefix?: string;
|
|
26
|
+
/** Text formatted after the number (e.g. `%`); travels with the value. */
|
|
12
27
|
suffix?: string;
|
|
13
28
|
placeholder?: string;
|
|
14
29
|
spinButtons?: boolean;
|
|
15
30
|
/** Show a clear (x) button when there is a value. */
|
|
16
31
|
clearable?: boolean;
|
|
32
|
+
/** Select the whole value when the field is focused. */
|
|
33
|
+
selectOnFocus?: boolean;
|
|
34
|
+
/** Step the value with the mouse wheel while the field is focused. */
|
|
35
|
+
wheelStep?: boolean;
|
|
36
|
+
/** Leading adornment (icon) inside the field. */
|
|
37
|
+
leading?: Snippet;
|
|
38
|
+
/** Stretch to the container width. */
|
|
39
|
+
block?: boolean;
|
|
40
|
+
/** Control width in px (ignored when `block`). Default 150. */
|
|
41
|
+
width?: number;
|
|
17
42
|
};
|
|
18
43
|
declare const SvNumberInput: import("svelte").Component<Props, {}, "value">;
|
|
19
44
|
type SvNumberInput = ReturnType<typeof SvNumberInput>;
|
package/dist/SvOtpInput.svelte
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* ```
|
|
11
11
|
*/
|
|
12
12
|
import SvField from './SvField.svelte'
|
|
13
|
-
import { nextEditorId, type SvEditorProps } from './editor-contract'
|
|
13
|
+
import { editorAria, nextEditorId, type SvEditorProps } from './editor-contract'
|
|
14
14
|
import { sanitizeOtp, otpCells, isOtpComplete } from './otp'
|
|
15
15
|
|
|
16
16
|
type Props = Pick<
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
class:is-disabled={disabled}
|
|
119
119
|
class:is-invalid={invalid}
|
|
120
120
|
role="group"
|
|
121
|
-
|
|
121
|
+
{...editorAria({ id: uid, invalid, required, error, hint, ariaLabel: label ?? 'One-time code' })}
|
|
122
122
|
>
|
|
123
123
|
{#each cells as cell, i (i)}
|
|
124
124
|
<input
|