@svgrid/grid 2.6.20 → 2.6.22
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/CHANGELOG.md +62 -0
- package/README.md +22 -0
- package/dist/GridMenus.svelte +17 -12
- package/dist/SvGrid.controller.svelte.d.ts +14 -8
- package/dist/SvGrid.controller.svelte.js +214 -74
- package/dist/SvGrid.css +1 -1
- package/dist/SvGrid.svelte +112 -57
- package/dist/SvGrid.types.d.ts +191 -1
- package/dist/ai.d.ts +28 -0
- package/dist/ai.js +6 -0
- package/dist/cdn/{GridMenus-B0F9iBrG.js → GridMenus-BuoBPqxx.js} +137 -132
- package/dist/cdn/GridMenus-n4llxoOI.js +494 -0
- package/dist/cdn/column-resize-DsfNXMom.js +102 -0
- package/dist/cdn/row-resize-BRcimkUT.js +95 -0
- package/dist/cdn/{src-Cd0tearp.js → src-C9Hihx1W.js} +3497 -3487
- package/dist/cdn/{src-B1TdiyS8.js → src-D1lXwq1l.js} +8258 -8248
- package/dist/cdn/svgrid.js +10 -8
- package/dist/cdn/svgrid.svelte-external.js +10 -8
- package/dist/cdn/validate-_CDJzgIo.js +75 -0
- package/dist/cell-formatting.d.ts +2 -0
- package/dist/cell-formatting.js +2 -0
- package/dist/chart-export.d.ts +1 -0
- package/dist/chart.d.ts +31 -5
- package/dist/chart.js +9 -3
- package/dist/column-groups.js +1 -1
- package/dist/column-resize.d.ts +46 -0
- package/dist/column-resize.js +205 -0
- package/dist/columns.d.ts +0 -3
- package/dist/columns.js +0 -57
- package/dist/core.d.ts +216 -4
- package/dist/core.js +532 -119
- package/dist/createTree.svelte.d.ts +3 -0
- package/dist/createTree.svelte.js +1 -0
- package/dist/datetime/date-core.d.ts +2 -0
- package/dist/datetime/date-restrict.d.ts +1 -0
- package/dist/datetime/timezone.d.ts +1 -0
- package/dist/dock-manager-model.d.ts +3 -0
- package/dist/dock-manager-model.js +1 -0
- package/dist/dock-model.d.ts +6 -0
- package/dist/dock-model.js +3 -0
- package/dist/editor-contract.d.ts +1 -0
- package/dist/filtering/excel-filters.js +28 -0
- package/dist/group-display.d.ts +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -0
- package/dist/list-option.d.ts +1 -0
- package/dist/menus.js +1 -1
- package/dist/positioning.d.ts +2 -0
- package/dist/row-resize.d.ts +11 -0
- package/dist/row-resize.js +7 -1
- package/dist/scheduler-ical.d.ts +1 -0
- package/dist/scheduler-model.d.ts +1 -0
- package/dist/selection.js +9 -0
- package/dist/spreadsheet.d.ts +1 -1
- package/dist/spreadsheet.js +1 -1
- package/dist/summaries.js +22 -0
- package/dist/svgrid-wrapper.types.d.ts +5 -0
- package/dist/toast-store.svelte.d.ts +4 -0
- package/dist/validate.d.ts +50 -0
- package/dist/validate.js +187 -0
- package/package.json +4 -1
- package/src/GridMenus.svelte +17 -12
- package/src/SvGrid.controller.svelte.ts +223 -76
- package/src/SvGrid.css +1 -1
- package/src/SvGrid.svelte +112 -57
- package/src/SvGrid.types.ts +191 -1
- package/src/ai.ts +28 -0
- package/src/cell-formatting.ts +2 -0
- package/src/chart-export.ts +1 -0
- package/src/chart.ts +31 -5
- package/src/column-groups.ts +1 -1
- package/src/column-resize.test.ts +381 -0
- package/src/column-resize.ts +227 -0
- package/src/columns.test.ts +0 -103
- package/src/columns.ts +0 -58
- package/src/core.aggregate.test.ts +134 -0
- package/src/core.filter.test.ts +156 -0
- package/src/core.grouping.test.ts +146 -0
- package/src/core.row-shape.test.ts +119 -0
- package/src/core.rowmodel-cache.test.ts +121 -0
- package/src/core.sort.test.ts +293 -0
- package/src/core.ts +723 -119
- package/src/createTree.svelte.ts +3 -0
- package/src/datetime/date-core.ts +2 -0
- package/src/datetime/date-restrict.ts +1 -0
- package/src/datetime/timezone.ts +1 -0
- package/src/dock-manager-model.ts +3 -0
- package/src/dock-model.ts +6 -0
- package/src/editor-contract.ts +1 -0
- package/src/filtering/excel-filters.ts +30 -0
- package/src/filtering/normalize-fast-path.test.ts +104 -0
- package/src/group-display.ts +1 -1
- package/src/index.ts +12 -1
- package/src/list-option.ts +1 -0
- package/src/menus.ts +1 -1
- package/src/positioning.ts +2 -0
- package/src/resize-props.test.ts +361 -0
- package/src/row-resize.test.ts +31 -0
- package/src/row-resize.ts +21 -3
- package/src/scheduler-ical.ts +1 -0
- package/src/scheduler-model.ts +1 -0
- package/src/selection.ts +9 -0
- package/src/spreadsheet.ts +1 -1
- package/src/summaries.ts +21 -0
- package/src/svgrid-wrapper.types.ts +5 -0
- package/src/svgrid.summaries.test.ts +217 -0
- package/src/toast-store.svelte.ts +4 -0
- package/src/validate.test.ts +207 -0
- package/src/validate.ts +269 -0
- package/dist/cdn/GridMenus-IHK_l7m6.js +0 -489
package/src/createTree.svelte.ts
CHANGED
|
@@ -31,6 +31,7 @@ export type TreeNode = {
|
|
|
31
31
|
lazy?: boolean
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/** A node's checkbox state. `'indeterminate'` means some but not all descendants are checked. */
|
|
34
35
|
export type CheckState = 'checked' | 'indeterminate' | 'unchecked'
|
|
35
36
|
|
|
36
37
|
/** A single visible (flattened) tree row. */
|
|
@@ -145,6 +146,7 @@ export type TreeConfig = {
|
|
|
145
146
|
filter?: () => string | undefined
|
|
146
147
|
}
|
|
147
148
|
|
|
149
|
+
/** Build the headless tree model: expansion, selection and checkbox cascading, with no markup. */
|
|
148
150
|
export function createTree(config: TreeConfig) {
|
|
149
151
|
const nodes = () => config.nodes()
|
|
150
152
|
const checkable = () => config.checkable?.() ?? false
|
|
@@ -316,4 +318,5 @@ export function createTree(config: TreeConfig) {
|
|
|
316
318
|
}
|
|
317
319
|
}
|
|
318
320
|
|
|
321
|
+
/** The headless tree instance returned by {@link createTree}. */
|
|
319
322
|
export type Tree = ReturnType<typeof createTree>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* `firstDayOfWeek`.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
/** Anything the date helpers accept: a `Date`, epoch milliseconds, or a parseable string. */
|
|
13
14
|
export type DateLike = Date | number | string
|
|
14
15
|
|
|
15
16
|
/** Coerce a Date | epoch-ms | parseable string to a Date, or null if invalid. */
|
|
@@ -137,6 +138,7 @@ export function centuryRange(year: number): { start: number; end: number } {
|
|
|
137
138
|
return { start, end: start + 99 }
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
/** One cell of a month grid, including the leading and trailing days from adjacent months. */
|
|
140
142
|
export type MonthMatrixCell = {
|
|
141
143
|
date: Date
|
|
142
144
|
/** In the displayed month (vs. leading/trailing days of adjacent months). */
|
package/src/datetime/timezone.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
type IdGen,
|
|
32
32
|
} from './dock-model'
|
|
33
33
|
|
|
34
|
+
/** Which edge a pane docks against when dropped. */
|
|
34
35
|
export type DockSide = Exclude<DockZone, 'center'>
|
|
35
36
|
|
|
36
37
|
/** A floating window: one tabs leaf shown in a movable/resizable frame. */
|
|
@@ -58,6 +59,7 @@ export type AutoHideEntry = {
|
|
|
58
59
|
size: number
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
/** The whole dock manager: its layout tree plus floating and pinned panes. */
|
|
61
63
|
export type DockManagerState = {
|
|
62
64
|
main: DockNode | null
|
|
63
65
|
floating: FloatWindow[]
|
|
@@ -74,6 +76,7 @@ export type PaneLocation =
|
|
|
74
76
|
| { kind: 'floating'; windowId: string }
|
|
75
77
|
| { kind: 'autoHide'; entryId: string }
|
|
76
78
|
|
|
79
|
+
/** Find a pane in the layout tree by id, returning it with its parent for mutation. */
|
|
77
80
|
export function locatePane(state: DockManagerState, paneId: string): PaneLocation | null {
|
|
78
81
|
if (state.main && findTabsWithPane(state.main, paneId)) return { kind: 'main' }
|
|
79
82
|
for (const w of state.floating) {
|
package/src/dock-model.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* without a browser. Modelled on Smart's `smart-layout` group/item structure.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
/** One dockable pane: its id, title, and the state a tab needs to render. */
|
|
16
17
|
export type DockPane = {
|
|
17
18
|
id: string
|
|
18
19
|
title: string
|
|
@@ -40,15 +41,18 @@ export type DockGroup = {
|
|
|
40
41
|
sizes: number[]
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
/** A node in the layout tree - either a split group or a tabbed leaf. */
|
|
43
45
|
export type DockNode = DockGroup | DockTabs
|
|
44
46
|
|
|
45
47
|
/** Where a dragged pane lands relative to a target leaf. */
|
|
46
48
|
export type DockZone = 'left' | 'right' | 'top' | 'bottom' | 'center'
|
|
47
49
|
|
|
50
|
+
/** Supplies ids for newly created nodes, so layouts stay deterministic in tests. */
|
|
48
51
|
export type IdGen = () => string
|
|
49
52
|
|
|
50
53
|
// ---- construction helpers -------------------------------------------------
|
|
51
54
|
|
|
55
|
+
/** Build a {@link DockPane}. */
|
|
52
56
|
export function pane(id: string, title: string, closable = true): DockPane {
|
|
53
57
|
return { id, title, closable }
|
|
54
58
|
}
|
|
@@ -58,10 +62,12 @@ export function leafMinSize(node: DockTabs): number {
|
|
|
58
62
|
return node.panes.reduce((m, p) => Math.max(m, p.minSize ?? 0), 0)
|
|
59
63
|
}
|
|
60
64
|
|
|
65
|
+
/** Build a tabbed leaf holding the given panes. */
|
|
61
66
|
export function tabs(genId: IdGen, panes: DockPane[], active = 0): DockTabs {
|
|
62
67
|
return { type: 'tabs', id: genId(), panes, active: clampIndex(active, panes.length) }
|
|
63
68
|
}
|
|
64
69
|
|
|
70
|
+
/** Build a split group: panes or nested groups laid out in a row or column. */
|
|
65
71
|
export function group(
|
|
66
72
|
genId: IdGen,
|
|
67
73
|
direction: 'row' | 'column',
|
package/src/editor-contract.ts
CHANGED
|
@@ -49,12 +49,42 @@ export function normalizeForFilter(
|
|
|
49
49
|
locale?: string | ReadonlyArray<string>,
|
|
50
50
|
): string {
|
|
51
51
|
if (!s) return ''
|
|
52
|
+
|
|
53
|
+
// Fast path for pure-ASCII input, which is the overwhelming majority of what
|
|
54
|
+
// a grid filters. Both of the expensive steps are provably no-ops there:
|
|
55
|
+
// ASCII is already NFD-normalised, and the combining-marks block this strips
|
|
56
|
+
// (U+0300..U+036F) contains no ASCII. So lowercasing alone is the same
|
|
57
|
+
// answer, and `normalize-fast-path.test.ts` checks that exhaustively over
|
|
58
|
+
// every ASCII code point rather than taking the argument on trust.
|
|
59
|
+
//
|
|
60
|
+
// Worth the branch: this runs once per cell per filter - 100,000 times for a
|
|
61
|
+
// one-column filter over a 100k grid - and a CPU profile of a browser filter
|
|
62
|
+
// put more time in this function than in any other.
|
|
63
|
+
//
|
|
64
|
+
// Note the locale still routes through toLocaleLowerCase even on the fast
|
|
65
|
+
// path: 'I'.toLocaleLowerCase('tr') is 'ı', so ASCII does NOT imply the
|
|
66
|
+
// locale can be ignored.
|
|
67
|
+
if (isAsciiOnly(s)) {
|
|
68
|
+
return locale ? s.toLocaleLowerCase(locale as string | string[]) : s.toLowerCase()
|
|
69
|
+
}
|
|
70
|
+
|
|
52
71
|
const stripped = s.normalize('NFD').replace(DIACRITIC_RE, '')
|
|
53
72
|
return locale
|
|
54
73
|
? stripped.toLocaleLowerCase(locale as string | string[])
|
|
55
74
|
: stripped.toLowerCase()
|
|
56
75
|
}
|
|
57
76
|
|
|
77
|
+
/**
|
|
78
|
+
* True when every code unit is ASCII. A plain char-code scan, which is far
|
|
79
|
+
* cheaper than the `normalize()` + regex pass it lets us skip.
|
|
80
|
+
*/
|
|
81
|
+
function isAsciiOnly(s: string): boolean {
|
|
82
|
+
for (let i = 0; i < s.length; i++) {
|
|
83
|
+
if (s.charCodeAt(i) > 127) return false
|
|
84
|
+
}
|
|
85
|
+
return true
|
|
86
|
+
}
|
|
87
|
+
|
|
58
88
|
/**
|
|
59
89
|
* Delimiter used to serialise the `in` / `notIn` value list into the single
|
|
60
90
|
* `value` string that the filter model carries.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Equivalence tests for `normalizeForFilter`'s ASCII fast path.
|
|
3
|
+
*
|
|
4
|
+
* The function NFD-decomposes, strips combining marks, then lowercases. It runs
|
|
5
|
+
* once per cell per filter - 100,000 times for a one-column filter on a 100k
|
|
6
|
+
* grid - and a CPU profile of a browser filter put 15.5% of the whole operation
|
|
7
|
+
* inside it, more than any other single function.
|
|
8
|
+
*
|
|
9
|
+
* For a pure-ASCII string the first two steps are provably no-ops: ASCII is
|
|
10
|
+
* already in NFD, and the combining-marks block (U+0300..U+036F) contains no
|
|
11
|
+
* ASCII. So the fast path returns `lowercase(s)` directly.
|
|
12
|
+
*
|
|
13
|
+
* "Provably" is doing a lot of work in that sentence, so the first test checks
|
|
14
|
+
* it exhaustively over every ASCII code point rather than trusting the argument.
|
|
15
|
+
*/
|
|
16
|
+
import { describe, expect, it } from 'vitest'
|
|
17
|
+
import { normalizeForFilter } from './excel-filters'
|
|
18
|
+
|
|
19
|
+
/** The original implementation, verbatim, as the oracle. */
|
|
20
|
+
const DIACRITIC_RE = /[̀-ͯ]/g
|
|
21
|
+
function reference(s: string, locale?: string | ReadonlyArray<string>): string {
|
|
22
|
+
if (!s) return ''
|
|
23
|
+
const stripped = s.normalize('NFD').replace(DIACRITIC_RE, '')
|
|
24
|
+
return locale
|
|
25
|
+
? stripped.toLocaleLowerCase(locale as string | string[])
|
|
26
|
+
: stripped.toLowerCase()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('normalizeForFilter - ASCII fast path equivalence', () => {
|
|
30
|
+
it('matches on every single ASCII code point', () => {
|
|
31
|
+
for (let code = 0; code < 128; code++) {
|
|
32
|
+
const ch = String.fromCharCode(code)
|
|
33
|
+
expect(normalizeForFilter(ch), `code point ${code}`).toBe(reference(ch))
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('matches on every ASCII code point under a Turkish locale', () => {
|
|
38
|
+
// Turkish is the locale that makes ASCII lowercasing locale-dependent:
|
|
39
|
+
// 'I'.toLocaleLowerCase('tr') is 'ı', not 'i'. The fast path must still
|
|
40
|
+
// route through toLocaleLowerCase rather than assuming ASCII is safe.
|
|
41
|
+
for (let code = 0; code < 128; code++) {
|
|
42
|
+
const ch = String.fromCharCode(code)
|
|
43
|
+
expect(normalizeForFilter(ch, 'tr'), `code point ${code}`).toBe(reference(ch, 'tr'))
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const CASES: Array<[string, string]> = [
|
|
48
|
+
['empty', ''],
|
|
49
|
+
['plain ascii', 'Hello World'],
|
|
50
|
+
['ascii with digits and punctuation', 'ABC-123_x.y,z!'],
|
|
51
|
+
['precomposed accents', 'café CAFÉ Café'],
|
|
52
|
+
['decomposed accents', 'café'],
|
|
53
|
+
['mixed ascii and accents', 'naive naïve NAÏVE'],
|
|
54
|
+
['german sharp s', 'straße STRASSE'],
|
|
55
|
+
['greek', 'ΣΊΣΥΦΟΣ σίσυφος'],
|
|
56
|
+
['cyrillic', 'Привет МИР'],
|
|
57
|
+
['cjk', '日本語テキスト'],
|
|
58
|
+
['emoji', 'grid 🚀 test'],
|
|
59
|
+
['combining marks alone', '̀́̂'],
|
|
60
|
+
['ascii then combining mark', 'áb'],
|
|
61
|
+
['turkish dotted capital', 'İstanbul ISTANBUL'],
|
|
62
|
+
['ligature', 'file flow'],
|
|
63
|
+
['fullwidth', 'ABC'],
|
|
64
|
+
['nbsp and control chars', 'a b\tc\nd'],
|
|
65
|
+
['long ascii', 'x'.repeat(500)],
|
|
66
|
+
['long with one non-ascii at the end', 'x'.repeat(500) + 'é'],
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
for (const [name, input] of CASES) {
|
|
70
|
+
it(`matches: ${name}`, () => {
|
|
71
|
+
expect(normalizeForFilter(input)).toBe(reference(input))
|
|
72
|
+
})
|
|
73
|
+
it(`matches with locale: ${name}`, () => {
|
|
74
|
+
expect(normalizeForFilter(input, 'tr')).toBe(reference(input, 'tr'))
|
|
75
|
+
expect(normalizeForFilter(input, 'de')).toBe(reference(input, 'de'))
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
it('matches across randomised mixed-script strings', () => {
|
|
80
|
+
let seed = 7
|
|
81
|
+
const rand = () => {
|
|
82
|
+
seed = (seed * 1103515245 + 12345) & 0x7fffffff
|
|
83
|
+
return seed / 0x7fffffff
|
|
84
|
+
}
|
|
85
|
+
const pools = [
|
|
86
|
+
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 -_.',
|
|
87
|
+
'áéíóúàèìòùâêîôûäëïöüñçÁÉÍÓÚÑÇ',
|
|
88
|
+
'̀́̂̃̈',
|
|
89
|
+
'ΑΒΓΔαβγδ',
|
|
90
|
+
'абвгдАБВГД',
|
|
91
|
+
'日本語中文한국어',
|
|
92
|
+
]
|
|
93
|
+
for (let trial = 0; trial < 300; trial++) {
|
|
94
|
+
let s = ''
|
|
95
|
+
const len = 1 + Math.floor(rand() * 20)
|
|
96
|
+
for (let i = 0; i < len; i++) {
|
|
97
|
+
const pool = pools[Math.floor(rand() * pools.length)]!
|
|
98
|
+
s += pool[Math.floor(rand() * pool.length)]
|
|
99
|
+
}
|
|
100
|
+
expect(normalizeForFilter(s), s).toBe(reference(s))
|
|
101
|
+
expect(normalizeForFilter(s, 'tr'), s).toBe(reference(s, 'tr'))
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
})
|
package/src/group-display.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* they are trivially unit-testable and carry no dependency on the grid runtime.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
/**
|
|
17
|
+
/** Grouping display variants. */
|
|
18
18
|
export type GroupDisplayType = 'groupRows' | 'singleColumn' | 'multipleColumns'
|
|
19
19
|
|
|
20
20
|
export type GroupFooterOptions<R> = {
|
package/src/index.ts
CHANGED
|
@@ -588,7 +588,13 @@ export {
|
|
|
588
588
|
type CellBorderSpec,
|
|
589
589
|
type BorderSpec,
|
|
590
590
|
} from './spreadsheet'
|
|
591
|
+
// The two resize actions, exported as a matched pair. `<SvGrid>` drives both
|
|
592
|
+
// through the `rowResize` / `columnResize` props - both default OFF, both are
|
|
593
|
+
// turned on by setting them true, and the grid `import()`s the module itself so
|
|
594
|
+
// a grid that leaves them off carries neither. Import them directly only for a
|
|
595
|
+
// headless table, which renders its own rows and headers.
|
|
591
596
|
export { rowResize, type RowResizeOptions } from './row-resize'
|
|
597
|
+
export { columnResize, type ColumnResizeOptions } from './column-resize'
|
|
592
598
|
export { rowDropZone, type RowDragEndEvent, type RowDropZoneOptions } from './row-drag'
|
|
593
599
|
export { resolveColumnTypes, inferCellDataType, type CellDataType } from './column-types'
|
|
594
600
|
export {
|
|
@@ -690,7 +696,12 @@ export { createVirtualizer } from './virtualization/virtualizer'
|
|
|
690
696
|
export { createSvelteVirtualizer } from './virtualization/svelte-virtualizer.svelte'
|
|
691
697
|
export { createColumnVirtualizer } from './virtualization/column-virtualizer'
|
|
692
698
|
export type { VirtualItem, VirtualizerOptions, VirtualizerState } from './virtualization/types'
|
|
693
|
-
export type {
|
|
699
|
+
export type {
|
|
700
|
+
SvGridApi,
|
|
701
|
+
SvGridFilterOperator,
|
|
702
|
+
SvGridViewState,
|
|
703
|
+
SvGridWrapperProps,
|
|
704
|
+
} from './svgrid-wrapper.types'
|
|
694
705
|
export {
|
|
695
706
|
parseEditorValue,
|
|
696
707
|
normalizeEditorOptions,
|
package/src/list-option.ts
CHANGED
|
@@ -71,6 +71,7 @@ export type VirtualListRow =
|
|
|
71
71
|
| { type: 'group'; label: string; size: number }
|
|
72
72
|
| { type: 'option'; opt: IndexedOption; size: number }
|
|
73
73
|
|
|
74
|
+
/** A flattened option list plus its measurements, for virtualizing long dropdowns. */
|
|
74
75
|
export type FlatVirtualModel = {
|
|
75
76
|
/** Group headings + options in render order, each with its px height. */
|
|
76
77
|
entries: VirtualListRow[]
|
package/src/menus.ts
CHANGED
|
@@ -151,7 +151,7 @@ export function createMenus<
|
|
|
151
151
|
function openChooseColumns(event: MouseEvent) {
|
|
152
152
|
event.stopPropagation();
|
|
153
153
|
// Submenu behavior: keep the parent operations menu open and float the
|
|
154
|
-
// Choose Columns panel out to the right of it
|
|
154
|
+
// Choose Columns panel out to the right of it, submenu-style. Falls
|
|
155
155
|
// back to below the item if the popover would clip the viewport.
|
|
156
156
|
const trigger = event.currentTarget as HTMLElement;
|
|
157
157
|
const menuEl = trigger.closest(".sv-grid-menu") as HTMLElement | null;
|
package/src/positioning.ts
CHANGED
|
@@ -35,6 +35,7 @@ export type Rect = { x: number; y: number; width: number; height: number }
|
|
|
35
35
|
/** The available viewport (defaults to `window` in the browser). */
|
|
36
36
|
export type Viewport = { width: number; height: number }
|
|
37
37
|
|
|
38
|
+
/** Where to put a floating element: its preferred side, offsets, and collision behaviour. */
|
|
38
39
|
export type ComputePositionOptions = {
|
|
39
40
|
/** Preferred placement. Default `'bottom-start'`. */
|
|
40
41
|
placement?: Placement
|
|
@@ -58,6 +59,7 @@ export type ComputePositionOptions = {
|
|
|
58
59
|
minMainAxis?: number
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
/** The resolved position, including the side actually used after collision handling. */
|
|
61
63
|
export type ComputePositionResult = {
|
|
62
64
|
/** Floating left, in viewport (fixed-position) coordinates. */
|
|
63
65
|
x: number
|