@svgrid/grid 2.2.15 → 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,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>
|
package/src/SvMultiSelect.svelte
CHANGED
|
@@ -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)}
|
package/src/SvNumberInput.svelte
CHANGED
|
@@ -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>
|
package/src/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
|
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
*
|
|
6
6
|
* The styled renderer over the headless `createPasswordInput` core: the reveal
|
|
7
7
|
* toggle + strength heuristic come from the core, spread here via prop-getters.
|
|
8
|
+
* The box, size, invalid state and focus ring are owned by SvField's `frame`
|
|
9
|
+
* chrome; the reveal eye lives in the trailing slot and the strength meter sits
|
|
10
|
+
* below the field.
|
|
8
11
|
*/
|
|
12
|
+
import type { Snippet } from 'svelte'
|
|
9
13
|
import SvField from './SvField.svelte'
|
|
10
14
|
import { nextEditorId, resolveMessages, type SvEditorProps } from './editor-contract'
|
|
11
15
|
import { createPasswordInput } from './createPasswordInput.svelte'
|
|
@@ -28,6 +32,12 @@
|
|
|
28
32
|
autocomplete?: string
|
|
29
33
|
/** Override the built-in strings (reveal toggle + strength labels). */
|
|
30
34
|
messages?: Partial<PasswordMessages>
|
|
35
|
+
/** Leading adornment (icon) inside the field. */
|
|
36
|
+
leading?: Snippet
|
|
37
|
+
/** Stretch to the container width. */
|
|
38
|
+
block?: boolean
|
|
39
|
+
/** Control width in px (ignored when `block`). Default 220. */
|
|
40
|
+
width?: number
|
|
31
41
|
}
|
|
32
42
|
|
|
33
43
|
let {
|
|
@@ -48,8 +58,12 @@
|
|
|
48
58
|
hint,
|
|
49
59
|
dir,
|
|
50
60
|
id,
|
|
61
|
+
loading = false,
|
|
51
62
|
autocomplete = 'current-password',
|
|
52
63
|
messages,
|
|
64
|
+
leading,
|
|
65
|
+
block = false,
|
|
66
|
+
width = 220,
|
|
53
67
|
}: Props = $props()
|
|
54
68
|
|
|
55
69
|
const autoId = nextEditorId('sv-pass')
|
|
@@ -76,20 +90,40 @@
|
|
|
76
90
|
})
|
|
77
91
|
</script>
|
|
78
92
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
{#snippet eye()}
|
|
94
|
+
{#if revealable}
|
|
95
|
+
<button class="sv-pw__eye" {...pw.toggleProps()}>
|
|
96
|
+
{#if pw.revealed}
|
|
97
|
+
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" /><path d="M1 1l22 22" /></svg>
|
|
98
|
+
{:else}
|
|
99
|
+
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" /><circle cx="12" cy="12" r="3" /></svg>
|
|
100
|
+
{/if}
|
|
101
|
+
</button>
|
|
102
|
+
{/if}
|
|
103
|
+
{/snippet}
|
|
104
|
+
|
|
105
|
+
<div class="sv-pw" class:sv-pw--block={block}>
|
|
106
|
+
<SvField
|
|
107
|
+
frame
|
|
108
|
+
id={uid}
|
|
109
|
+
{label}
|
|
110
|
+
{hint}
|
|
111
|
+
{error}
|
|
112
|
+
{required}
|
|
113
|
+
{dir}
|
|
114
|
+
{size}
|
|
115
|
+
{invalid}
|
|
116
|
+
{disabled}
|
|
117
|
+
{readonly}
|
|
118
|
+
{loading}
|
|
119
|
+
{block}
|
|
120
|
+
{width}
|
|
121
|
+
{leading}
|
|
122
|
+
trailing={revealable ? eye : undefined}
|
|
123
|
+
>
|
|
82
124
|
<input class="sv-pw__input" {...pw.inputProps()} />
|
|
83
|
-
{#if
|
|
84
|
-
|
|
85
|
-
{#if pw.revealed}
|
|
86
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" /><path d="M1 1l22 22" /></svg>
|
|
87
|
-
{:else}
|
|
88
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" /><circle cx="12" cy="12" r="3" /></svg>
|
|
89
|
-
{/if}
|
|
90
|
-
</button>
|
|
91
|
-
{/if}
|
|
92
|
-
</div>
|
|
125
|
+
{#if name}<input type="hidden" {name} value={value} />{/if}
|
|
126
|
+
</SvField>
|
|
93
127
|
{#if showStrength}
|
|
94
128
|
<div class="sv-pw__meter" aria-hidden="true">
|
|
95
129
|
{#each Array(4) as _, i (i)}
|
|
@@ -98,27 +132,13 @@
|
|
|
98
132
|
</div>
|
|
99
133
|
<span class="sv-pw__strength is-lvl{pw.strength}">{pw.strengthLabel}</span>
|
|
100
134
|
{/if}
|
|
101
|
-
{#if name}<input type="hidden" {name} value={value} />{/if}
|
|
102
135
|
</div>
|
|
103
|
-
</SvField>
|
|
104
136
|
|
|
105
137
|
<style>
|
|
106
|
-
.sv-pw {
|
|
107
|
-
.sv-pw
|
|
108
|
-
.sv-
|
|
109
|
-
|
|
110
|
-
background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
111
|
-
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
112
|
-
}
|
|
113
|
-
.sv-pw__field:focus-within { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
114
|
-
.sv-pw__field.is-invalid { border-color: var(--sg-danger, #dc2626); }
|
|
115
|
-
.sv-pw__field.is-invalid:focus-within { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
|
|
116
|
-
.sv-pw__input { flex: 1; min-width: 0; border: 0; background: none; outline: none; color: inherit; font: inherit; padding: 0 10px; }
|
|
117
|
-
.sv-pw--sm .sv-pw__field { height: 28px; font-size: 12px; }
|
|
118
|
-
.sv-pw--md .sv-pw__field { height: 34px; font-size: 13px; }
|
|
119
|
-
.sv-pw--lg .sv-pw__field { height: 40px; font-size: 15px; }
|
|
120
|
-
.sv-pw__eye { display: grid; place-items: center; width: 32px; align-self: stretch; background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; }
|
|
121
|
-
.sv-pw__eye:hover { color: var(--_accent); }
|
|
138
|
+
.sv-pw { display: inline-flex; flex-direction: column; gap: 5px; }
|
|
139
|
+
.sv-pw--block { display: flex; width: 100%; }
|
|
140
|
+
.sv-pw__eye { display: grid; place-items: center; width: 26px; align-self: center; background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; padding: 0; }
|
|
141
|
+
.sv-pw__eye:hover { color: var(--sg-accent, #2563eb); }
|
|
122
142
|
.sv-pw__meter { display: flex; gap: 4px; }
|
|
123
143
|
.sv-pw__bar { flex: 1; height: 4px; border-radius: 2px; background: var(--sg-border, #e2e8f0); transition: background 0.15s; }
|
|
124
144
|
.sv-pw__bar.on.is-lvl1 { background: var(--sg-danger, #dc2626); }
|