@svgrid/grid 2.2.25 → 2.2.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +199 -145
- package/dist/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +179 -179
- package/dist/GridMenus.svelte +734 -728
- package/dist/SvAutoComplete.svelte +165 -165
- package/dist/SvCalendar.svelte +503 -503
- package/dist/SvCarousel.svelte +141 -141
- package/dist/SvCheckBox.svelte +101 -101
- package/dist/SvColorInput.svelte +181 -181
- package/dist/SvComboBox.svelte +275 -275
- package/dist/SvContextMenu.svelte +116 -116
- package/dist/SvCountryInput.svelte +160 -160
- package/dist/SvDrawer.svelte +250 -250
- package/dist/SvDropDownList.svelte +374 -374
- package/dist/SvDurationInput.svelte +126 -126
- package/dist/SvField.svelte +293 -293
- package/dist/SvForm.svelte +437 -437
- package/dist/SvGrid.css +2723 -2719
- package/dist/SvGrid.svelte +3333 -3333
- package/dist/SvGridChart.svelte +1724 -1724
- package/dist/SvGridChartPanel.svelte +485 -485
- package/dist/SvGridChartView.svelte +70 -70
- package/dist/SvGridDropdown.svelte +696 -696
- package/dist/SvGridSelect.svelte +259 -259
- package/dist/SvGroupCell.svelte +109 -109
- package/dist/SvListBox.svelte +287 -287
- package/dist/SvMaskedInput.svelte +122 -122
- package/dist/SvMenu.svelte +124 -124
- package/dist/SvMenuList.svelte +146 -146
- package/dist/SvMultiSelect.svelte +281 -281
- package/dist/SvNumberInput.svelte +168 -168
- package/dist/SvOtpInput.svelte +158 -158
- package/dist/SvPasswordInput.svelte +151 -151
- package/dist/SvPhoneInput.svelte +131 -131
- package/dist/SvPopover.svelte +197 -197
- package/dist/SvRadioGroup.svelte +107 -107
- package/dist/SvRowGroupPanel.svelte +170 -170
- package/dist/SvScrollArea.svelte +61 -61
- package/dist/SvSlider.svelte +200 -200
- package/dist/SvSwitchButton.svelte +107 -107
- package/dist/SvTagsInput.svelte +112 -112
- package/dist/SvTextInput.svelte +147 -147
- package/dist/SvTimePicker.svelte +245 -245
- package/dist/SvToaster.svelte +159 -159
- package/dist/SvToggleButton.svelte +85 -85
- package/dist/SvTooltip.svelte +161 -161
- package/dist/SvTour.svelte +204 -204
- package/dist/SvTree.svelte +437 -437
- package/dist/SvTreeSelect.svelte +233 -233
- package/dist/a11y/dismissable.d.ts +11 -0
- package/dist/a11y/dismissable.js +21 -0
- package/dist/ai.js +3 -3
- package/dist/cdn/GridMenus-BzxEYNtW.js +421 -0
- package/dist/cdn/GridMenus-Dr-nGEXn.js +417 -0
- package/dist/cdn/{SvDateTimePicker-CYC7gRER.js → SvDateTimePicker-B1sDc9Pz.js} +451 -444
- package/dist/cdn/{SvDateTimePicker-DtQKd4Ns.js → SvDateTimePicker-Bh0FTs1E.js} +114 -107
- package/dist/cdn/{SvGridChart-BhnFdLd0.js → SvGridChart-BKQsO5fZ.js} +1 -1
- package/dist/cdn/{SvGridChart-BcLCYvw9.js → SvGridChart-Bgk4hDs8.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-DvjwxLvK.js → SvGridChartPanel-DSkHIQDd.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-Cmx9DHAS.js → SvGridChartPanel-DkCc2Z7b.js} +1 -1
- package/dist/cdn/{SvGridChartView-B40DTJSX.js → SvGridChartView-BrbVaELm.js} +1 -1
- package/dist/cdn/{SvGridChartView-PwqI4s02.js → SvGridChartView-i9rWvSKg.js} +1 -1
- package/dist/cdn/{SvGridDropdown-hpnHvOGt.js → SvGridDropdown-CGxxopQ4.js} +1 -1
- package/dist/cdn/{SvGridDropdown-DjALc_4f.js → SvGridDropdown-D08sVmAy.js} +1 -1
- package/dist/cdn/{src-DysW9qbk.js → src-Cw8d9U8j.js} +5753 -5753
- package/dist/cdn/{src-bYfr_7aR.js → src-cgs1wcUS.js} +8102 -8102
- package/dist/cdn/svgrid.js +9 -9
- package/dist/cdn/svgrid.svelte-external.js +9 -9
- package/dist/chart-export.js +8 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +30 -13
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +179 -179
- package/src/GridMenus.svelte +734 -728
- package/src/SvAutoComplete.svelte +165 -165
- package/src/SvCalendar.svelte +503 -503
- package/src/SvCalendar.test.ts +226 -226
- package/src/SvCarousel.svelte +141 -141
- package/src/SvCheckBox.svelte +101 -101
- package/src/SvColorInput.svelte +181 -181
- package/src/SvComboBox.svelte +275 -275
- package/src/SvContextMenu.svelte +116 -116
- package/src/SvCountryInput.svelte +160 -160
- package/src/SvDrawer.svelte +250 -250
- package/src/SvDropDownList.svelte +374 -374
- package/src/SvDurationInput.svelte +126 -126
- package/src/SvField.svelte +293 -293
- package/src/SvForm.svelte +437 -437
- package/src/SvForm.test.ts +411 -411
- package/src/SvGrid.controller.svelte.ts +3239 -3239
- package/src/SvGrid.css +2723 -2719
- package/src/SvGrid.svelte +3333 -3333
- package/src/SvGrid.types.ts +1613 -1613
- package/src/SvGridChart.svelte +1724 -1724
- package/src/SvGridChartPanel.svelte +485 -485
- package/src/SvGridChartView.svelte +70 -70
- package/src/SvGridDropdown.svelte +696 -696
- package/src/SvGridSelect.svelte +259 -259
- package/src/SvGroupCell.svelte +109 -109
- package/src/SvListBox.svelte +287 -287
- package/src/SvMaskedInput.svelte +122 -122
- package/src/SvMenu.svelte +124 -124
- package/src/SvMenu.test.ts +97 -97
- package/src/SvMenuList.svelte +146 -146
- package/src/SvMultiSelect.svelte +281 -281
- package/src/SvNumberInput.svelte +168 -168
- package/src/SvOtpInput.svelte +158 -158
- package/src/SvPasswordInput.svelte +151 -151
- package/src/SvPhoneInput.svelte +131 -131
- package/src/SvPopover.svelte +197 -197
- package/src/SvRadioGroup.svelte +107 -107
- package/src/SvRowGroupPanel.svelte +170 -170
- package/src/SvScrollArea.svelte +61 -61
- package/src/SvSlider.svelte +200 -200
- package/src/SvSwitchButton.svelte +107 -107
- package/src/SvTagsInput.svelte +112 -112
- package/src/SvTextInput.svelte +147 -147
- package/src/SvTimePicker.svelte +245 -245
- package/src/SvToaster.svelte +159 -159
- package/src/SvToaster.test.ts +95 -95
- package/src/SvToggleButton.svelte +85 -85
- package/src/SvTooltip.svelte +161 -161
- package/src/SvTour.svelte +204 -204
- package/src/SvTree.svelte +437 -437
- package/src/SvTreeSelect.svelte +233 -233
- package/src/a11y/dismissable.test.ts +119 -95
- package/src/a11y/dismissable.ts +114 -94
- package/src/a11y.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +61 -61
- package/src/ai.test.ts +502 -502
- package/src/ai.ts +1391 -1391
- package/src/build-api.coverage.test.ts +633 -633
- package/src/build-api.ts +895 -895
- package/src/builtin-editors.grid.test.ts +85 -85
- package/src/cell-formatting.ts +169 -169
- package/src/cell-render.ts +469 -469
- package/src/chart-export.ts +201 -201
- package/src/chart-view.svelte.ts +36 -36
- package/src/chart.ts +2296 -2296
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1111 -1111
- package/src/createAutocomplete.svelte.ts +132 -132
- package/src/createCombobox.svelte.ts +191 -191
- package/src/createCountryInput.svelte.ts +157 -157
- package/src/createDropdownList.svelte.ts +168 -168
- package/src/createForm.svelte.ts +386 -386
- package/src/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- package/src/createMenu.svelte.ts +225 -225
- package/src/createPopoverSelect.svelte.ts +213 -213
- package/src/createSlider.svelte.ts +191 -191
- package/src/createTooltip.svelte.ts +144 -144
- package/src/createTree.svelte.ts +319 -319
- package/src/editing.test.ts +859 -859
- package/src/editing.ts +675 -675
- package/src/editor-contract.ts +157 -157
- package/src/export-data-api.test.ts +126 -126
- package/src/export-format.test.ts +107 -107
- package/src/export-format.ts +598 -598
- package/src/filter-operators.ts +157 -157
- package/src/flex-render.ts +3 -3
- package/src/form-field.ts +127 -127
- package/src/index.ts +713 -712
- package/src/js-scroller.svelte.ts +173 -173
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/list-nav.test.ts +49 -49
- package/src/list-nav.ts +29 -29
- package/src/list-option.test.ts +56 -56
- package/src/list-option.ts +172 -172
- package/src/menus.test.ts +700 -700
- package/src/menus.ts +652 -652
- package/src/merge-objects.ts +48 -48
- package/src/overlays.test.ts +90 -90
- package/src/render-component.ts +28 -28
- package/src/scheduler-model.test.ts +562 -562
- package/src/scheduler-model.ts +872 -872
- package/src/selection.test.ts +754 -754
- package/src/selection.ts +600 -600
- package/src/server-data-source.test.ts +289 -289
- package/src/server-data-source.ts +413 -413
- package/src/sparkline.test.ts +68 -68
- package/src/sparkline.ts +169 -169
- package/src/spreadsheet.test.ts +489 -489
- package/src/spreadsheet.ts +312 -312
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/svgrid-wrapper.types.ts +524 -524
- package/src/svgrid.behavior.test.ts +706 -706
- package/src/svgrid.charting.test.ts +534 -534
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.filter-menu-scroll.test.ts +102 -0
- package/src/svgrid.new-features.wrapper.test.ts +251 -251
- package/src/svgrid.wrapper.test.ts +40 -40
- package/src/test-setup.ts +62 -62
- package/src/themes/index.ts +215 -215
- package/src/toast-store.svelte.ts +246 -246
- package/src/toast-store.test.ts +147 -147
- package/src/ui-buttons.test.ts +144 -144
- package/src/ui-inputs.test.ts +118 -118
- package/src/ui-range.test.ts +70 -70
- package/src/ui-selection.test.ts +155 -155
- package/src/ui-tier1.test.ts +142 -142
- package/src/virtual.test.ts +88 -88
- package/src/virtualization/column-virtualizer.test.ts +27 -27
- package/src/virtualization/column-virtualizer.ts +30 -30
- package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
- package/src/virtualization/types.ts +30 -30
- package/src/virtualization/virtualizer.test.ts +47 -47
- package/src/virtualization/virtualizer.ts +296 -296
- package/themes/ember.css +71 -71
- package/dist/cdn/GridMenus-GfCAHgkZ.js +0 -420
- package/dist/cdn/GridMenus-GiVNSeDU.js +0 -416
|
@@ -1,144 +1,144 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* createTooltip - the HEADLESS core behind <SvTooltip>: the hover/focus open
|
|
3
|
-
* state machine with a show delay, an optional close delay, a shared delay-group
|
|
4
|
-
* (so moving between grouped tooltips skips the re-delay), Escape-to-hide, and the
|
|
5
|
-
* `aria-describedby` / `role="tooltip"` wiring - as prop-getters you spread onto
|
|
6
|
-
* your own anchor and tooltip elements. No styles, no positioning (that is a DOM
|
|
7
|
-
* concern the renderer owns), like `createListbox`.
|
|
8
|
-
*
|
|
9
|
-
* ```svelte
|
|
10
|
-
* <script lang="ts">
|
|
11
|
-
* import { createTooltip } from '@svgrid/grid'
|
|
12
|
-
* const tip = createTooltip({ text: () => label })
|
|
13
|
-
* </script>
|
|
14
|
-
* <span {...tip.anchorProps()}>{@render children?.()}</span>
|
|
15
|
-
* {#if tip.open}
|
|
16
|
-
* <div {...tip.tooltipProps()}>{label}</div>
|
|
17
|
-
* {/if}
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
import { nextEditorId } from './editor-contract'
|
|
21
|
-
|
|
22
|
-
export type TooltipConfig = {
|
|
23
|
-
/** The tooltip text; the tooltip never opens when it is empty. */
|
|
24
|
-
text?: () => string | undefined
|
|
25
|
-
/** Suppress the tooltip entirely. */
|
|
26
|
-
disabled?: () => boolean
|
|
27
|
-
/** Show delay in ms after hover/focus. Default 300. */
|
|
28
|
-
delay?: () => number
|
|
29
|
-
/** Hide delay in ms after leave/blur. Default 0. Give it room for interactive tips. */
|
|
30
|
-
closeDelay?: () => number
|
|
31
|
-
/**
|
|
32
|
-
* Delay-group id. Tooltips sharing a group open INSTANTLY while any group
|
|
33
|
-
* member is open or was open within a short grace window - so scanning a
|
|
34
|
-
* toolbar of icon buttons does not re-wait the show delay each time.
|
|
35
|
-
*/
|
|
36
|
-
group?: () => string | undefined
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export type TooltipAnchorProps = {
|
|
40
|
-
'aria-describedby': string | undefined
|
|
41
|
-
onpointerenter: () => void
|
|
42
|
-
onpointerleave: () => void
|
|
43
|
-
onfocusin: () => void
|
|
44
|
-
onfocusout: () => void
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export type TooltipProps = {
|
|
48
|
-
id: string
|
|
49
|
-
role: 'tooltip'
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Grace window (ms) after a group tooltip closes during which the next opens instantly. */
|
|
53
|
-
const GROUP_SKIP_WINDOW = 300
|
|
54
|
-
type GroupEntry = { openCount: number; lastCloseAt: number }
|
|
55
|
-
const groupRegistry = new Map<string, GroupEntry>()
|
|
56
|
-
function groupEntry(id: string): GroupEntry {
|
|
57
|
-
let e = groupRegistry.get(id)
|
|
58
|
-
if (!e) { e = { openCount: 0, lastCloseAt: 0 }; groupRegistry.set(id, e) }
|
|
59
|
-
return e
|
|
60
|
-
}
|
|
61
|
-
const now = () => (typeof Date !== 'undefined' ? Date.now() : 0)
|
|
62
|
-
|
|
63
|
-
export function createTooltip(config: TooltipConfig = {}) {
|
|
64
|
-
const id = nextEditorId('sv-tip')
|
|
65
|
-
let open = $state(false)
|
|
66
|
-
let showTimer: ReturnType<typeof setTimeout> | undefined
|
|
67
|
-
let hideTimer: ReturnType<typeof setTimeout> | undefined
|
|
68
|
-
let counted = false // whether this instance is currently counted in its group
|
|
69
|
-
|
|
70
|
-
function groupId() { return config.group?.() }
|
|
71
|
-
|
|
72
|
-
/** Effective show delay: 0 when the group is warm (a member open / just closed). */
|
|
73
|
-
function effectiveDelay(): number {
|
|
74
|
-
const base = config.delay?.() ?? 300
|
|
75
|
-
const g = groupId()
|
|
76
|
-
if (!g) return base
|
|
77
|
-
const e = groupEntry(g)
|
|
78
|
-
if (e.openCount > 0 || now() - e.lastCloseAt < GROUP_SKIP_WINDOW) return 0
|
|
79
|
-
return base
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function markOpen() {
|
|
83
|
-
open = true
|
|
84
|
-
const g = groupId()
|
|
85
|
-
if (g && !counted) { counted = true; groupEntry(g).openCount++ }
|
|
86
|
-
}
|
|
87
|
-
function reallyHide() {
|
|
88
|
-
open = false
|
|
89
|
-
clearTimeout(showTimer)
|
|
90
|
-
clearTimeout(hideTimer)
|
|
91
|
-
const g = groupId()
|
|
92
|
-
if (g && counted) { counted = false; const e = groupEntry(g); e.openCount--; e.lastCloseAt = now() }
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function show() {
|
|
96
|
-
if ((config.disabled?.() ?? false) || !(config.text?.() ?? '')) return
|
|
97
|
-
clearTimeout(hideTimer)
|
|
98
|
-
if (open) return
|
|
99
|
-
clearTimeout(showTimer)
|
|
100
|
-
showTimer = setTimeout(markOpen, effectiveDelay())
|
|
101
|
-
}
|
|
102
|
-
/** Delayed hide (honors `closeDelay`) - used by pointer/focus leave. */
|
|
103
|
-
function scheduleHide() {
|
|
104
|
-
clearTimeout(showTimer)
|
|
105
|
-
const cd = config.closeDelay?.() ?? 0
|
|
106
|
-
if (cd > 0 && open) { clearTimeout(hideTimer); hideTimer = setTimeout(reallyHide, cd) }
|
|
107
|
-
else reallyHide()
|
|
108
|
-
}
|
|
109
|
-
/** Immediate hide - used by Escape and imperative callers. */
|
|
110
|
-
function hide() { reallyHide() }
|
|
111
|
-
|
|
112
|
-
// Escape hides the tooltip while it is open (WAI-ARIA tooltip pattern).
|
|
113
|
-
$effect(() => {
|
|
114
|
-
if (!open) return
|
|
115
|
-
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') hide() }
|
|
116
|
-
document.addEventListener('keydown', onKey)
|
|
117
|
-
return () => document.removeEventListener('keydown', onKey)
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
return {
|
|
121
|
-
get open() { return open },
|
|
122
|
-
/** Stable id of the tooltip element (for `aria-describedby` + the tip's id). */
|
|
123
|
-
tipId: id,
|
|
124
|
-
show,
|
|
125
|
-
hide,
|
|
126
|
-
/** Keep an interactive tip open while the pointer is over it. */
|
|
127
|
-
keepOpen: show,
|
|
128
|
-
/** Delayed hide honoring `closeDelay` (pointer/focus leave). */
|
|
129
|
-
scheduleHide,
|
|
130
|
-
/** Spread onto the anchor/trigger wrapper. Also mirror `tipId` onto the real
|
|
131
|
-
* focusable child's `aria-describedby` so AT announces it on focus. */
|
|
132
|
-
anchorProps: (): TooltipAnchorProps => ({
|
|
133
|
-
'aria-describedby': open ? id : undefined,
|
|
134
|
-
onpointerenter: show,
|
|
135
|
-
onpointerleave: scheduleHide,
|
|
136
|
-
onfocusin: show,
|
|
137
|
-
onfocusout: scheduleHide,
|
|
138
|
-
}),
|
|
139
|
-
/** Spread onto the tooltip element. */
|
|
140
|
-
tooltipProps: (): TooltipProps => ({ id, role: 'tooltip' }),
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export type Tooltip = ReturnType<typeof createTooltip>
|
|
1
|
+
/**
|
|
2
|
+
* createTooltip - the HEADLESS core behind <SvTooltip>: the hover/focus open
|
|
3
|
+
* state machine with a show delay, an optional close delay, a shared delay-group
|
|
4
|
+
* (so moving between grouped tooltips skips the re-delay), Escape-to-hide, and the
|
|
5
|
+
* `aria-describedby` / `role="tooltip"` wiring - as prop-getters you spread onto
|
|
6
|
+
* your own anchor and tooltip elements. No styles, no positioning (that is a DOM
|
|
7
|
+
* concern the renderer owns), like `createListbox`.
|
|
8
|
+
*
|
|
9
|
+
* ```svelte
|
|
10
|
+
* <script lang="ts">
|
|
11
|
+
* import { createTooltip } from '@svgrid/grid'
|
|
12
|
+
* const tip = createTooltip({ text: () => label })
|
|
13
|
+
* </script>
|
|
14
|
+
* <span {...tip.anchorProps()}>{@render children?.()}</span>
|
|
15
|
+
* {#if tip.open}
|
|
16
|
+
* <div {...tip.tooltipProps()}>{label}</div>
|
|
17
|
+
* {/if}
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
import { nextEditorId } from './editor-contract'
|
|
21
|
+
|
|
22
|
+
export type TooltipConfig = {
|
|
23
|
+
/** The tooltip text; the tooltip never opens when it is empty. */
|
|
24
|
+
text?: () => string | undefined
|
|
25
|
+
/** Suppress the tooltip entirely. */
|
|
26
|
+
disabled?: () => boolean
|
|
27
|
+
/** Show delay in ms after hover/focus. Default 300. */
|
|
28
|
+
delay?: () => number
|
|
29
|
+
/** Hide delay in ms after leave/blur. Default 0. Give it room for interactive tips. */
|
|
30
|
+
closeDelay?: () => number
|
|
31
|
+
/**
|
|
32
|
+
* Delay-group id. Tooltips sharing a group open INSTANTLY while any group
|
|
33
|
+
* member is open or was open within a short grace window - so scanning a
|
|
34
|
+
* toolbar of icon buttons does not re-wait the show delay each time.
|
|
35
|
+
*/
|
|
36
|
+
group?: () => string | undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type TooltipAnchorProps = {
|
|
40
|
+
'aria-describedby': string | undefined
|
|
41
|
+
onpointerenter: () => void
|
|
42
|
+
onpointerleave: () => void
|
|
43
|
+
onfocusin: () => void
|
|
44
|
+
onfocusout: () => void
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type TooltipProps = {
|
|
48
|
+
id: string
|
|
49
|
+
role: 'tooltip'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Grace window (ms) after a group tooltip closes during which the next opens instantly. */
|
|
53
|
+
const GROUP_SKIP_WINDOW = 300
|
|
54
|
+
type GroupEntry = { openCount: number; lastCloseAt: number }
|
|
55
|
+
const groupRegistry = new Map<string, GroupEntry>()
|
|
56
|
+
function groupEntry(id: string): GroupEntry {
|
|
57
|
+
let e = groupRegistry.get(id)
|
|
58
|
+
if (!e) { e = { openCount: 0, lastCloseAt: 0 }; groupRegistry.set(id, e) }
|
|
59
|
+
return e
|
|
60
|
+
}
|
|
61
|
+
const now = () => (typeof Date !== 'undefined' ? Date.now() : 0)
|
|
62
|
+
|
|
63
|
+
export function createTooltip(config: TooltipConfig = {}) {
|
|
64
|
+
const id = nextEditorId('sv-tip')
|
|
65
|
+
let open = $state(false)
|
|
66
|
+
let showTimer: ReturnType<typeof setTimeout> | undefined
|
|
67
|
+
let hideTimer: ReturnType<typeof setTimeout> | undefined
|
|
68
|
+
let counted = false // whether this instance is currently counted in its group
|
|
69
|
+
|
|
70
|
+
function groupId() { return config.group?.() }
|
|
71
|
+
|
|
72
|
+
/** Effective show delay: 0 when the group is warm (a member open / just closed). */
|
|
73
|
+
function effectiveDelay(): number {
|
|
74
|
+
const base = config.delay?.() ?? 300
|
|
75
|
+
const g = groupId()
|
|
76
|
+
if (!g) return base
|
|
77
|
+
const e = groupEntry(g)
|
|
78
|
+
if (e.openCount > 0 || now() - e.lastCloseAt < GROUP_SKIP_WINDOW) return 0
|
|
79
|
+
return base
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function markOpen() {
|
|
83
|
+
open = true
|
|
84
|
+
const g = groupId()
|
|
85
|
+
if (g && !counted) { counted = true; groupEntry(g).openCount++ }
|
|
86
|
+
}
|
|
87
|
+
function reallyHide() {
|
|
88
|
+
open = false
|
|
89
|
+
clearTimeout(showTimer)
|
|
90
|
+
clearTimeout(hideTimer)
|
|
91
|
+
const g = groupId()
|
|
92
|
+
if (g && counted) { counted = false; const e = groupEntry(g); e.openCount--; e.lastCloseAt = now() }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function show() {
|
|
96
|
+
if ((config.disabled?.() ?? false) || !(config.text?.() ?? '')) return
|
|
97
|
+
clearTimeout(hideTimer)
|
|
98
|
+
if (open) return
|
|
99
|
+
clearTimeout(showTimer)
|
|
100
|
+
showTimer = setTimeout(markOpen, effectiveDelay())
|
|
101
|
+
}
|
|
102
|
+
/** Delayed hide (honors `closeDelay`) - used by pointer/focus leave. */
|
|
103
|
+
function scheduleHide() {
|
|
104
|
+
clearTimeout(showTimer)
|
|
105
|
+
const cd = config.closeDelay?.() ?? 0
|
|
106
|
+
if (cd > 0 && open) { clearTimeout(hideTimer); hideTimer = setTimeout(reallyHide, cd) }
|
|
107
|
+
else reallyHide()
|
|
108
|
+
}
|
|
109
|
+
/** Immediate hide - used by Escape and imperative callers. */
|
|
110
|
+
function hide() { reallyHide() }
|
|
111
|
+
|
|
112
|
+
// Escape hides the tooltip while it is open (WAI-ARIA tooltip pattern).
|
|
113
|
+
$effect(() => {
|
|
114
|
+
if (!open) return
|
|
115
|
+
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') hide() }
|
|
116
|
+
document.addEventListener('keydown', onKey)
|
|
117
|
+
return () => document.removeEventListener('keydown', onKey)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
get open() { return open },
|
|
122
|
+
/** Stable id of the tooltip element (for `aria-describedby` + the tip's id). */
|
|
123
|
+
tipId: id,
|
|
124
|
+
show,
|
|
125
|
+
hide,
|
|
126
|
+
/** Keep an interactive tip open while the pointer is over it. */
|
|
127
|
+
keepOpen: show,
|
|
128
|
+
/** Delayed hide honoring `closeDelay` (pointer/focus leave). */
|
|
129
|
+
scheduleHide,
|
|
130
|
+
/** Spread onto the anchor/trigger wrapper. Also mirror `tipId` onto the real
|
|
131
|
+
* focusable child's `aria-describedby` so AT announces it on focus. */
|
|
132
|
+
anchorProps: (): TooltipAnchorProps => ({
|
|
133
|
+
'aria-describedby': open ? id : undefined,
|
|
134
|
+
onpointerenter: show,
|
|
135
|
+
onpointerleave: scheduleHide,
|
|
136
|
+
onfocusin: show,
|
|
137
|
+
onfocusout: scheduleHide,
|
|
138
|
+
}),
|
|
139
|
+
/** Spread onto the tooltip element. */
|
|
140
|
+
tooltipProps: (): TooltipProps => ({ id, role: 'tooltip' }),
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export type Tooltip = ReturnType<typeof createTooltip>
|