@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
package/dist/SvTooltip.svelte
CHANGED
|
@@ -1,64 +1,90 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
/**
|
|
3
3
|
* SvTooltip - a small hover/focus tooltip anchored to its child, portalled to
|
|
4
|
-
* <body> so it is never clipped.
|
|
4
|
+
* <body> so it is never clipped. Positioned by the shared engine (full placement
|
|
5
|
+
* matrix + flip + shift + arrow), shows after a short delay, hides on leave /
|
|
5
6
|
* blur / Escape, and is wired via `aria-describedby` (role="tooltip") onto the
|
|
6
7
|
* focusable child itself, so AT users get it announced on focus, not just
|
|
7
8
|
* sighted users on hover.
|
|
8
9
|
*
|
|
10
|
+
* Set `group` on several tooltips so scanning between them skips the re-delay,
|
|
11
|
+
* `closeDelay` + `interactive` for a hoverable tip (e.g. one with a link).
|
|
12
|
+
*
|
|
9
13
|
* ```svelte
|
|
10
14
|
* <SvTooltip text="Delete row"><button aria-label="Delete">🗑</button></SvTooltip>
|
|
11
15
|
* ```
|
|
12
16
|
*/
|
|
13
17
|
import type { Snippet } from 'svelte'
|
|
14
18
|
import { portalToBody, popIn } from './popover'
|
|
19
|
+
import { computePosition, autoUpdate, type Placement, type ComputePositionResult } from './positioning'
|
|
15
20
|
import { getFocusable } from './a11y/focus-trap'
|
|
16
21
|
import { createTooltip } from './createTooltip.svelte'
|
|
17
22
|
|
|
18
23
|
type Props = {
|
|
19
24
|
text: string
|
|
20
|
-
placement
|
|
25
|
+
/** Preferred placement; flips when there is no room. Default `top`. */
|
|
26
|
+
placement?: Placement
|
|
21
27
|
/** Show delay in ms. Default 300. */
|
|
22
28
|
delay?: number
|
|
29
|
+
/** Hide delay in ms after leave (give it room for an interactive tip). Default 0. */
|
|
30
|
+
closeDelay?: number
|
|
31
|
+
/** Delay-group id: grouped tooltips open instantly while the group is warm. */
|
|
32
|
+
group?: string
|
|
33
|
+
/** Let the pointer move onto the tip without closing it (e.g. a tip with a link). */
|
|
34
|
+
interactive?: boolean
|
|
23
35
|
disabled?: boolean
|
|
24
36
|
children?: Snippet
|
|
25
37
|
}
|
|
26
38
|
|
|
27
|
-
let {
|
|
39
|
+
let {
|
|
40
|
+
text,
|
|
41
|
+
placement = 'top',
|
|
42
|
+
delay = 300,
|
|
43
|
+
closeDelay = 0,
|
|
44
|
+
group,
|
|
45
|
+
interactive = false,
|
|
46
|
+
disabled = false,
|
|
47
|
+
children,
|
|
48
|
+
}: Props = $props()
|
|
49
|
+
|
|
50
|
+
const ARROW = 8
|
|
28
51
|
|
|
29
52
|
let anchorEl = $state<HTMLSpanElement | null>(null)
|
|
30
53
|
let tipEl = $state<HTMLDivElement | null>(null)
|
|
31
|
-
let pos = $state({
|
|
54
|
+
let pos = $state<ComputePositionResult>({
|
|
55
|
+
x: 0, y: 0, placement, side: 'top', align: 'center', maxWidth: 0, maxHeight: 0, arrow: { x: 12 },
|
|
56
|
+
})
|
|
32
57
|
|
|
33
|
-
// Open state, show
|
|
34
|
-
// this component owns only the DOM concerns (positioning
|
|
35
|
-
|
|
58
|
+
// Open state, show/close delay, delay-group and Escape-to-hide live in the
|
|
59
|
+
// shared headless core; this component owns only the DOM concerns (positioning
|
|
60
|
+
// + describedby merge). Interactive tips get a small closeDelay by default so
|
|
61
|
+
// the pointer can travel from anchor to tip.
|
|
62
|
+
const tip = createTooltip({
|
|
63
|
+
text: () => text,
|
|
64
|
+
disabled: () => disabled,
|
|
65
|
+
delay: () => delay,
|
|
66
|
+
closeDelay: () => (closeDelay || (interactive ? 120 : 0)),
|
|
67
|
+
group: () => group,
|
|
68
|
+
})
|
|
36
69
|
const tid = tip.tipId
|
|
37
70
|
|
|
38
71
|
function place() {
|
|
39
72
|
if (!anchorEl) return
|
|
40
73
|
const r = anchorEl.getBoundingClientRect()
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const arrowX = Math.max(10, Math.min(tipW - 10, r.left + r.width / 2 - left))
|
|
48
|
-
pos = { top, left, arrowX }
|
|
74
|
+
const floating = { width: tipEl?.offsetWidth || 0, height: tipEl?.offsetHeight || 30 }
|
|
75
|
+
pos = computePosition(
|
|
76
|
+
{ x: r.left, y: r.top, width: r.width, height: r.height },
|
|
77
|
+
floating,
|
|
78
|
+
{ placement, offset: 8, padding: 4, arrow: { size: ARROW } },
|
|
79
|
+
)
|
|
49
80
|
}
|
|
50
81
|
|
|
51
|
-
// Reposition while open (
|
|
82
|
+
// Reposition while open (measure the tip after it mounts, then keep in sync).
|
|
52
83
|
$effect(() => {
|
|
53
|
-
if (!tip.open) return
|
|
84
|
+
if (!tip.open || !anchorEl || !tipEl) return
|
|
54
85
|
queueMicrotask(place)
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
window.addEventListener('resize', rp)
|
|
58
|
-
return () => {
|
|
59
|
-
window.removeEventListener('scroll', rp, true)
|
|
60
|
-
window.removeEventListener('resize', rp)
|
|
61
|
-
}
|
|
86
|
+
const stop = autoUpdate(anchorEl, tipEl, place)
|
|
87
|
+
return () => stop()
|
|
62
88
|
})
|
|
63
89
|
|
|
64
90
|
// The wrapper span above is never itself Tab-focusable, so putting
|
|
@@ -98,15 +124,18 @@
|
|
|
98
124
|
<div
|
|
99
125
|
bind:this={tipEl}
|
|
100
126
|
id={tid}
|
|
101
|
-
class="sv-tip"
|
|
127
|
+
class="sv-tip sv-tip--{pos.side}"
|
|
128
|
+
class:sv-tip--interactive={interactive}
|
|
102
129
|
role="tooltip"
|
|
103
130
|
use:portalToBody
|
|
104
|
-
use:popIn={{ up:
|
|
105
|
-
class:sv-tip--bottom={placement === 'bottom'}
|
|
131
|
+
use:popIn={{ up: pos.side === 'top', duration: 110, scale: 0.9 }}
|
|
106
132
|
style:position="fixed"
|
|
107
|
-
style:top={`${pos.
|
|
108
|
-
style:left={`${pos.
|
|
109
|
-
style:--tip-arrow-x={`${pos.
|
|
133
|
+
style:top={`${pos.y}px`}
|
|
134
|
+
style:left={`${pos.x}px`}
|
|
135
|
+
style:--tip-arrow-x={pos.arrow?.x != null ? `${pos.arrow.x}px` : undefined}
|
|
136
|
+
style:--tip-arrow-y={pos.arrow?.y != null ? `${pos.arrow.y}px` : undefined}
|
|
137
|
+
onpointerenter={interactive ? tip.keepOpen : undefined}
|
|
138
|
+
onpointerleave={interactive ? tip.scheduleHide : undefined}
|
|
110
139
|
>{text}<span class="sv-tip__arrow" aria-hidden="true"></span></div>
|
|
111
140
|
{/if}
|
|
112
141
|
|
|
@@ -118,10 +147,15 @@
|
|
|
118
147
|
background: var(--sg-fg, #0f172a); color: var(--sg-bg, #fff);
|
|
119
148
|
border-radius: 6px; box-shadow: 0 6px 20px -6px rgba(15, 23, 42, 0.5); white-space: normal;
|
|
120
149
|
}
|
|
150
|
+
:global(.sv-tip--interactive) { pointer-events: auto; }
|
|
121
151
|
:global(.sv-tip__arrow) {
|
|
122
|
-
position: absolute;
|
|
123
|
-
background: var(--sg-fg, #0f172a); transform:
|
|
152
|
+
position: absolute; width: 8px; height: 8px;
|
|
153
|
+
background: var(--sg-fg, #0f172a); transform: rotate(45deg);
|
|
124
154
|
}
|
|
125
|
-
|
|
126
|
-
|
|
155
|
+
/* Arrow sits on the edge facing the anchor. top/bottom use --tip-arrow-x,
|
|
156
|
+
left/right use --tip-arrow-y. */
|
|
157
|
+
:global(.sv-tip--top .sv-tip__arrow) { bottom: -3px; inset-inline-start: var(--tip-arrow-x, 12px); }
|
|
158
|
+
:global(.sv-tip--bottom .sv-tip__arrow) { top: -3px; inset-inline-start: var(--tip-arrow-x, 12px); }
|
|
159
|
+
:global(.sv-tip--left .sv-tip__arrow) { right: -3px; top: var(--tip-arrow-y, 12px); }
|
|
160
|
+
:global(.sv-tip--right .sv-tip__arrow) { left: -3px; top: var(--tip-arrow-y, 12px); }
|
|
127
161
|
</style>
|
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SvTooltip - a small hover/focus tooltip anchored to its child, portalled to
|
|
3
|
-
* <body> so it is never clipped.
|
|
3
|
+
* <body> so it is never clipped. Positioned by the shared engine (full placement
|
|
4
|
+
* matrix + flip + shift + arrow), shows after a short delay, hides on leave /
|
|
4
5
|
* blur / Escape, and is wired via `aria-describedby` (role="tooltip") onto the
|
|
5
6
|
* focusable child itself, so AT users get it announced on focus, not just
|
|
6
7
|
* sighted users on hover.
|
|
7
8
|
*
|
|
9
|
+
* Set `group` on several tooltips so scanning between them skips the re-delay,
|
|
10
|
+
* `closeDelay` + `interactive` for a hoverable tip (e.g. one with a link).
|
|
11
|
+
*
|
|
8
12
|
* ```svelte
|
|
9
13
|
* <SvTooltip text="Delete row"><button aria-label="Delete">🗑</button></SvTooltip>
|
|
10
14
|
* ```
|
|
11
15
|
*/
|
|
12
16
|
import type { Snippet } from 'svelte';
|
|
17
|
+
import { type Placement } from './positioning';
|
|
13
18
|
type Props = {
|
|
14
19
|
text: string;
|
|
15
|
-
placement
|
|
20
|
+
/** Preferred placement; flips when there is no room. Default `top`. */
|
|
21
|
+
placement?: Placement;
|
|
16
22
|
/** Show delay in ms. Default 300. */
|
|
17
23
|
delay?: number;
|
|
24
|
+
/** Hide delay in ms after leave (give it room for an interactive tip). Default 0. */
|
|
25
|
+
closeDelay?: number;
|
|
26
|
+
/** Delay-group id: grouped tooltips open instantly while the group is warm. */
|
|
27
|
+
group?: string;
|
|
28
|
+
/** Let the pointer move onto the tip without closing it (e.g. a tip with a link). */
|
|
29
|
+
interactive?: boolean;
|
|
18
30
|
disabled?: boolean;
|
|
19
31
|
children?: Snippet;
|
|
20
32
|
};
|
package/dist/SvTree.svelte
CHANGED
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
* `createTree` core; this component renders it, and owns the DOM concerns
|
|
14
14
|
* (windowing, focus, and merging lazily-loaded children into the node tree).
|
|
15
15
|
*/
|
|
16
|
-
import { tick,
|
|
16
|
+
import { tick, untrack } from 'svelte'
|
|
17
17
|
import { type EditorDir } from './editor-contract'
|
|
18
|
-
import {
|
|
18
|
+
import { createSvelteVirtualizer } from './virtualization/svelte-virtualizer.svelte'
|
|
19
|
+
import { createJsScroller } from './js-scroller.svelte'
|
|
19
20
|
import { createTree, sortTreeNodes, type TreeNode, type TreeDropPosition } from './createTree.svelte'
|
|
20
21
|
|
|
21
22
|
type Props = {
|
|
@@ -35,8 +36,9 @@
|
|
|
35
36
|
dir?: EditorDir
|
|
36
37
|
/** Window rows (render only visible) - for large trees. Needs `height`. */
|
|
37
38
|
virtual?: boolean
|
|
38
|
-
/**
|
|
39
|
-
|
|
39
|
+
/** Row height in px - a number, or a per-node function for variable
|
|
40
|
+
* heights. Must match the rendered row height. Default 30. */
|
|
41
|
+
rowHeight?: number | ((node: TreeNode, index: number) => number)
|
|
40
42
|
/** Scroll-viewport height in px. Enables scrolling (required for `virtual`). */
|
|
41
43
|
height?: number
|
|
42
44
|
/** Load a lazy node's children on first expand (node has `lazy: true`). */
|
|
@@ -197,14 +199,58 @@
|
|
|
197
199
|
})
|
|
198
200
|
|
|
199
201
|
// --- Virtualization ---------------------------------------------------------
|
|
202
|
+
// Windowing runs on the shared `createSvelteVirtualizer` (cumulative offsets +
|
|
203
|
+
// binary search + variable-size support). Rows stay keyed by node id (NOT slot
|
|
204
|
+
// recycling): a tree row carries per-row identity - an in-progress inline
|
|
205
|
+
// rename, an active drag, roving focus - that must stay pinned to its node
|
|
206
|
+
// while scrolling. The engine only supplies the window + each row's offset.
|
|
200
207
|
const scrollable = $derived(virtual || height != null)
|
|
201
208
|
let treeEl = $state<HTMLDivElement | null>(null)
|
|
202
|
-
let scrollTop = $state(0)
|
|
203
209
|
let viewportH = $state(0)
|
|
204
|
-
const
|
|
205
|
-
|
|
210
|
+
const rowHeightBase = $derived(typeof rowHeight === 'number' ? rowHeight : 30)
|
|
211
|
+
const uniform = $derived(typeof rowHeight === 'number')
|
|
212
|
+
const rowSize = (i: number) => {
|
|
213
|
+
const r = tree.rows[i]
|
|
214
|
+
return r ? (typeof rowHeight === 'function' ? rowHeight(r.node, i) : rowHeight) : rowHeightBase
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const estimateSize = $derived(uniform ? rowHeightBase : (i: number) => rowSize(i))
|
|
218
|
+
// Generous overscan buffers fast flings; effective viewport never 0 (see SvListBox).
|
|
219
|
+
const OVERSCAN = 20
|
|
220
|
+
const effViewport = $derived(viewportH > 0 ? viewportH : Math.max(1, height ?? 320))
|
|
221
|
+
// Seed with the initial (untracked) values so the first paint renders a full window.
|
|
222
|
+
const virtualizer = createSvelteVirtualizer(
|
|
223
|
+
untrack(() => ({ count: tree.rows.length, estimateSize, overscan: OVERSCAN, viewportHeight: effViewport })),
|
|
206
224
|
)
|
|
207
|
-
|
|
225
|
+
// untrack keeps the virtualizer's internal `version` write out of these
|
|
226
|
+
// effects' dependencies, so they re-run only on their real inputs.
|
|
227
|
+
$effect(() => {
|
|
228
|
+
const count = tree.rows.length
|
|
229
|
+
const es = estimateSize
|
|
230
|
+
untrack(() => virtualizer.setOptions({ count, estimateSize: es }))
|
|
231
|
+
})
|
|
232
|
+
$effect(() => {
|
|
233
|
+
const vh = effViewport
|
|
234
|
+
untrack(() => virtualizer.setViewportHeight(vh))
|
|
235
|
+
})
|
|
236
|
+
const totalSize = $derived.by(() => { virtualizer.version; return virtualizer.getTotalSize() })
|
|
237
|
+
const visibleRows = $derived.by(() => {
|
|
238
|
+
if (!virtual) return tree.rows
|
|
239
|
+
virtualizer.version
|
|
240
|
+
const st = virtualizer.getState()
|
|
241
|
+
return tree.rows.slice(st.startIndex, st.endIndex + 1)
|
|
242
|
+
})
|
|
243
|
+
const rowTop = (i: number) => virtualizer.getOffsetForIndex(i)
|
|
244
|
+
const rowHeightPx = (i: number) => (virtual ? virtualizer.getSizeForIndex(i) : rowSize(i))
|
|
245
|
+
|
|
246
|
+
// JS-driven scroll for virtual mode (see js-scroller): no native scroll, so a
|
|
247
|
+
// fast thumb-drag never flashes an empty tree.
|
|
248
|
+
const scroller = createJsScroller({
|
|
249
|
+
virtualizer,
|
|
250
|
+
totalSize: () => totalSize,
|
|
251
|
+
viewport: () => effViewport,
|
|
252
|
+
lineStep: () => rowHeightBase,
|
|
253
|
+
})
|
|
208
254
|
|
|
209
255
|
// Follow the core's roving focus row; scroll it into view first when windowed.
|
|
210
256
|
let lastFocusTick = 0
|
|
@@ -212,10 +258,7 @@
|
|
|
212
258
|
if (tree.focusTick === lastFocusTick) return
|
|
213
259
|
lastFocusTick = tree.focusTick
|
|
214
260
|
const i = tree.activeIndex
|
|
215
|
-
if (virtual
|
|
216
|
-
const next = scrollToIndex(i, scrollTop, treeEl.clientHeight, rowHeight)
|
|
217
|
-
if (next !== scrollTop) { treeEl.scrollTop = next; scrollTop = next }
|
|
218
|
-
}
|
|
261
|
+
if (virtual) untrack(() => scroller.ensureIndexVisible(i))
|
|
219
262
|
tick().then(() => treeEl?.querySelector<HTMLElement>(`[data-row="${i}"]`)?.focus())
|
|
220
263
|
})
|
|
221
264
|
</script>
|
|
@@ -228,25 +271,35 @@
|
|
|
228
271
|
{#if query}<button type="button" class="sv-tree__search-clear" aria-label="Clear search" onclick={() => (query = '')}>×</button>{/if}
|
|
229
272
|
</div>
|
|
230
273
|
{/if}
|
|
274
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
231
275
|
<div
|
|
232
276
|
bind:this={treeEl}
|
|
233
277
|
class="sv-tree"
|
|
234
|
-
class:sv-tree--scroll={scrollable}
|
|
278
|
+
class:sv-tree--scroll={scrollable && !virtual}
|
|
235
279
|
class:is-virtual={virtual}
|
|
236
280
|
dir={resolvedDir}
|
|
237
281
|
style:height={height != null ? `${height}px` : undefined}
|
|
238
|
-
|
|
282
|
+
onwheel={virtual ? scroller.onWheel : undefined}
|
|
283
|
+
onpointerdown={virtual ? scroller.onContentDown : undefined}
|
|
284
|
+
onpointermove={virtual ? scroller.onContentMove : undefined}
|
|
285
|
+
onpointerup={virtual ? scroller.onContentUp : undefined}
|
|
286
|
+
onpointercancel={virtual ? scroller.onContentUp : undefined}
|
|
239
287
|
bind:clientHeight={viewportH}
|
|
240
288
|
{...tree.treeProps()}
|
|
241
289
|
>
|
|
242
|
-
{#if virtual}<div class="sv-tree__sizer" aria-hidden="true" style:height={`${vr.totalHeight}px`}></div>{/if}
|
|
243
290
|
{#each visibleRows as item (item.node.id)}
|
|
244
291
|
{@render treeRow(item)}
|
|
245
292
|
{/each}
|
|
293
|
+
{#if virtual && scroller.maxOffset > 0}
|
|
294
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
295
|
+
<div class="sv-tree__sb" onpointerdown={scroller.onTrackDown} aria-hidden="true">
|
|
296
|
+
<div class="sv-tree__thumb" class:is-dragging={scroller.dragging} style:height={`${scroller.thumbH}px`} style:transform={`translateY(${scroller.thumbTop}px)`} onpointerdown={scroller.onThumbDown} onpointermove={scroller.onThumbMove} onpointerup={scroller.onThumbUp} onpointercancel={scroller.onThumbUp}></div>
|
|
297
|
+
</div>
|
|
298
|
+
{/if}
|
|
246
299
|
</div>
|
|
247
300
|
</div>
|
|
248
301
|
|
|
249
|
-
{#snippet treeRow(item)}
|
|
302
|
+
{#snippet treeRow(item: (typeof tree.rows)[number])}
|
|
250
303
|
{@const cs = checkable ? tree.checkStateOf(item.node) : 'unchecked'}
|
|
251
304
|
{@const isLoading = loadingIds.has(item.node.id)}
|
|
252
305
|
{@const rowProps = tree.itemProps(item)}
|
|
@@ -258,9 +311,9 @@
|
|
|
258
311
|
class:drop-before={dropId === item.node.id && dropPos === 'before'}
|
|
259
312
|
class:drop-after={dropId === item.node.id && dropPos === 'after'}
|
|
260
313
|
class:drop-inside={dropId === item.node.id && dropPos === 'inside'}
|
|
261
|
-
style:height={`${
|
|
314
|
+
style:height={`${rowHeightPx(item.index)}px`}
|
|
262
315
|
style:padding-inline-start={`${item.depth * 18 + 6}px`}
|
|
263
|
-
style:transform={virtual ? `translateY(${item.index
|
|
316
|
+
style:transform={virtual ? `translateY(${rowTop(item.index) - scroller.scrollOffset}px)` : undefined}
|
|
264
317
|
draggable={reorderable && editingId !== item.node.id && !item.node.disabled}
|
|
265
318
|
ondragstart={reorderable ? (e) => onRowDragStart(e, item.node) : undefined}
|
|
266
319
|
ondragover={reorderable ? (e) => onRowDragOver(e, item.node) : undefined}
|
|
@@ -329,10 +382,17 @@
|
|
|
329
382
|
border: 1px solid var(--sg-border, #e2e8f0); border-radius: var(--sg-radius, 8px);
|
|
330
383
|
font-size: 13px;
|
|
331
384
|
}
|
|
332
|
-
|
|
333
|
-
.sv-tree
|
|
334
|
-
|
|
385
|
+
/* Non-virtual (height-capped) tree uses native scroll. */
|
|
386
|
+
.sv-tree--scroll { overflow-y: auto; overscroll-behavior: contain; overflow-anchor: none; }
|
|
387
|
+
/* Virtual tree: JS-driven, no native scroll - rows are moved by `scrollOffset`. */
|
|
388
|
+
.sv-tree.is-virtual { position: relative; overflow: hidden; touch-action: none; }
|
|
335
389
|
.sv-tree.is-virtual .sv-tree__row { position: absolute; inset-inline: 4px; top: 4px; }
|
|
390
|
+
.sv-tree__sb { position: absolute; top: 4px; right: 2px; bottom: 4px; width: 10px; z-index: 2; }
|
|
391
|
+
.sv-tree__thumb {
|
|
392
|
+
position: absolute; left: 2px; right: 2px; top: 0; min-height: 24px; border-radius: 4px;
|
|
393
|
+
background: color-mix(in srgb, currentColor 26%, transparent); cursor: default; touch-action: none;
|
|
394
|
+
}
|
|
395
|
+
.sv-tree__thumb:hover, .sv-tree__thumb.is-dragging { background: color-mix(in srgb, currentColor 42%, transparent); }
|
|
336
396
|
.sv-tree__row {
|
|
337
397
|
display: flex; align-items: center; gap: 4px; box-sizing: border-box; padding-inline-end: 8px;
|
|
338
398
|
border-radius: 6px; cursor: pointer; outline: none;
|
package/dist/SvTree.svelte.d.ts
CHANGED
|
@@ -18,8 +18,9 @@ type Props = {
|
|
|
18
18
|
dir?: EditorDir;
|
|
19
19
|
/** Window rows (render only visible) - for large trees. Needs `height`. */
|
|
20
20
|
virtual?: boolean;
|
|
21
|
-
/**
|
|
22
|
-
|
|
21
|
+
/** Row height in px - a number, or a per-node function for variable
|
|
22
|
+
* heights. Must match the rendered row height. Default 30. */
|
|
23
|
+
rowHeight?: number | ((node: TreeNode, index: number) => number);
|
|
23
24
|
/** Scroll-viewport height in px. Enables scrolling (required for `virtual`). */
|
|
24
25
|
height?: number;
|
|
25
26
|
/** Load a lazy node's children on first expand (node has `lazy: true`). */
|
package/dist/SvTreeSelect.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
|
nodes: ReadonlyArray<TreeSelectNode>
|
|
26
26
|
value?: string | number | null
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
showPath = false,
|
|
43
43
|
expandedIds = [],
|
|
44
44
|
disabled = false,
|
|
45
|
+
readonly = false,
|
|
45
46
|
label,
|
|
46
47
|
hint,
|
|
47
48
|
error,
|
|
@@ -52,6 +53,7 @@
|
|
|
52
53
|
name,
|
|
53
54
|
dir,
|
|
54
55
|
ariaLabel,
|
|
56
|
+
loading = false,
|
|
55
57
|
}: Props = $props()
|
|
56
58
|
|
|
57
59
|
const autoId = nextEditorId('sv-ts')
|
|
@@ -94,6 +96,7 @@
|
|
|
94
96
|
const ts = createPopoverSelect({
|
|
95
97
|
itemCount: () => rows.length,
|
|
96
98
|
disabled: (i) => !!rows[i]?.node.disabled,
|
|
99
|
+
readonly: () => readonly,
|
|
97
100
|
onSelect: (i) => { const row = rows[i]; if (row) onRowClick(row) },
|
|
98
101
|
getTrigger: () => triggerEl,
|
|
99
102
|
getPanel: () => panelEl,
|
|
@@ -127,7 +130,7 @@
|
|
|
127
130
|
})
|
|
128
131
|
</script>
|
|
129
132
|
|
|
130
|
-
<SvField id={uid} {label} {hint} {error} {required} {dir}>
|
|
133
|
+
<SvField id={uid} {label} {hint} {error} {required} {dir} {loading}>
|
|
131
134
|
<div class="sv-ts sv-ts--{size}" class:is-disabled={disabled}>
|
|
132
135
|
<button
|
|
133
136
|
bind:this={triggerEl}
|
|
@@ -9,7 +9,7 @@ export type { TreeSelectNode } from './ui-app.types';
|
|
|
9
9
|
*/
|
|
10
10
|
import type { TreeSelectNode } 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
|
nodes: ReadonlyArray<TreeSelectNode>;
|
|
14
14
|
value?: string | number | null;
|
|
15
15
|
onChange?: (value: string | number) => void;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvVisuallyHidden - content available to screen readers but visually hidden
|
|
4
|
+
* (the accessible "sr-only" pattern). Use for extra context on icon-only
|
|
5
|
+
* controls, live-region text, and skip links (which become visible on focus
|
|
6
|
+
* via `focusable`).
|
|
7
|
+
*
|
|
8
|
+
* <button><Icon /><SvVisuallyHidden>Delete row</SvVisuallyHidden></button>
|
|
9
|
+
*/
|
|
10
|
+
import type { Snippet } from 'svelte'
|
|
11
|
+
|
|
12
|
+
type Props = {
|
|
13
|
+
/** Reveal the content when it (or a child) receives focus - for skip links. */
|
|
14
|
+
focusable?: boolean
|
|
15
|
+
/** Element/tag to render. */
|
|
16
|
+
as?: string
|
|
17
|
+
children?: Snippet
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let { focusable = false, as = 'span', children }: Props = $props()
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<svelte:element this={as} class="sv-vh" class:is-focusable={focusable}>
|
|
24
|
+
{@render children?.()}
|
|
25
|
+
</svelte:element>
|
|
26
|
+
|
|
27
|
+
<style>
|
|
28
|
+
.sv-vh {
|
|
29
|
+
position: absolute;
|
|
30
|
+
width: 1px;
|
|
31
|
+
height: 1px;
|
|
32
|
+
padding: 0;
|
|
33
|
+
margin: -1px;
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
clip: rect(0 0 0 0);
|
|
36
|
+
white-space: nowrap;
|
|
37
|
+
border: 0;
|
|
38
|
+
}
|
|
39
|
+
.sv-vh.is-focusable:focus,
|
|
40
|
+
.sv-vh.is-focusable:focus-within {
|
|
41
|
+
position: static;
|
|
42
|
+
width: auto;
|
|
43
|
+
height: auto;
|
|
44
|
+
margin: 0;
|
|
45
|
+
overflow: visible;
|
|
46
|
+
clip: auto;
|
|
47
|
+
white-space: normal;
|
|
48
|
+
}
|
|
49
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SvVisuallyHidden - content available to screen readers but visually hidden
|
|
3
|
+
* (the accessible "sr-only" pattern). Use for extra context on icon-only
|
|
4
|
+
* controls, live-region text, and skip links (which become visible on focus
|
|
5
|
+
* via `focusable`).
|
|
6
|
+
*
|
|
7
|
+
* <button><Icon /><SvVisuallyHidden>Delete row</SvVisuallyHidden></button>
|
|
8
|
+
*/
|
|
9
|
+
import type { Snippet } from 'svelte';
|
|
10
|
+
type Props = {
|
|
11
|
+
/** Reveal the content when it (or a child) receives focus - for skip links. */
|
|
12
|
+
focusable?: boolean;
|
|
13
|
+
/** Element/tag to render. */
|
|
14
|
+
as?: string;
|
|
15
|
+
children?: Snippet;
|
|
16
|
+
};
|
|
17
|
+
declare const SvVisuallyHidden: import("svelte").Component<Props, {}, "">;
|
|
18
|
+
type SvVisuallyHidden = ReturnType<typeof SvVisuallyHidden>;
|
|
19
|
+
export default SvVisuallyHidden;
|