@svgrid/grid 2.1.21 → 2.1.23
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 +9 -2
- package/dist/DockNodeView.svelte +306 -0
- package/dist/DockNodeView.svelte.d.ts +7 -0
- package/dist/DockPopoutHost.svelte +17 -0
- package/dist/DockPopoutHost.svelte.d.ts +14 -0
- package/dist/GridMenus.svelte +83 -3
- package/dist/SvAutoComplete.svelte +5 -3
- package/dist/SvCarousel.svelte +20 -42
- package/dist/SvColorInput.svelte +4 -3
- package/dist/SvColorInput.svelte.d.ts +1 -1
- package/dist/SvComboBox.svelte +7 -3
- package/dist/SvCommand.svelte +28 -85
- package/dist/SvCountryInput.svelte +1 -1
- package/dist/SvDateRangeInput.svelte +1 -1
- package/dist/SvDateTimePicker.svelte +9 -3
- package/dist/SvDateTimePicker.svelte.d.ts +3 -0
- package/dist/SvDockLayout.svelte +244 -0
- package/dist/SvDockLayout.svelte.d.ts +77 -0
- package/dist/SvDockManager.svelte +922 -0
- package/dist/SvDockManager.svelte.d.ts +106 -0
- package/dist/SvDrawer.svelte +69 -47
- package/dist/SvDrawer.svelte.d.ts +4 -0
- package/dist/SvDropDownList.svelte +85 -13
- package/dist/SvDropDownList.svelte.d.ts +3 -0
- package/dist/SvField.svelte +21 -4
- package/dist/SvField.svelte.d.ts +1 -0
- package/dist/SvForm.svelte +201 -91
- package/dist/SvForm.svelte.d.ts +15 -22
- package/dist/SvGrid.controller.svelte.d.ts +32 -1
- package/dist/SvGrid.controller.svelte.js +156 -27
- package/dist/SvGrid.css +193 -5
- package/dist/SvGrid.svelte +357 -12
- package/dist/SvGrid.types.d.ts +452 -1
- package/dist/SvGridDropdown.svelte +8 -1
- package/dist/SvGridSelect.svelte +1 -0
- package/dist/SvListBox.svelte +36 -14
- package/dist/SvMaskedInput.svelte +4 -3
- package/dist/SvMaskedInput.svelte.d.ts +1 -1
- package/dist/SvMenu.svelte +1 -1
- package/dist/SvMenuList.svelte +23 -71
- package/dist/SvMenuList.svelte.d.ts +2 -13
- package/dist/SvModal.svelte +12 -29
- package/dist/SvMultiSelect.svelte +1 -0
- package/dist/SvNumberInput.svelte +4 -3
- package/dist/SvNumberInput.svelte.d.ts +1 -1
- package/dist/SvPagination.svelte +18 -18
- package/dist/SvPasswordInput.svelte +3 -2
- package/dist/SvPasswordInput.svelte.d.ts +1 -1
- package/dist/SvPhoneInput.svelte +3 -2
- package/dist/SvPhoneInput.svelte.d.ts +1 -1
- package/dist/SvPopover.svelte +1 -1
- package/dist/SvStepper.svelte +6 -9
- package/dist/SvTagsInput.svelte +3 -2
- package/dist/SvTagsInput.svelte.d.ts +1 -1
- package/dist/SvTooltip.svelte +12 -23
- package/dist/SvTour.svelte +32 -4
- package/dist/SvTreeSelect.svelte +1 -0
- package/dist/build-api.js +76 -54
- package/dist/cdn/svgrid.js +10907 -6444
- package/dist/cdn/svgrid.svelte-external.js +9622 -5475
- package/dist/columns.js +6 -0
- package/dist/conditional-formatting.d.ts +67 -3
- package/dist/conditional-formatting.js +124 -17
- package/dist/createCarousel.svelte.d.ts +72 -0
- package/dist/createCarousel.svelte.js +80 -0
- package/dist/createCommand.svelte.d.ts +62 -0
- package/dist/createCommand.svelte.js +109 -0
- package/dist/createForm.svelte.d.ts +62 -0
- package/dist/createForm.svelte.js +374 -0
- package/dist/createMenu.svelte.d.ts +88 -0
- package/dist/createMenu.svelte.js +153 -0
- package/dist/createOverlay.svelte.d.ts +38 -0
- package/dist/createOverlay.svelte.js +68 -0
- package/dist/createPagination.d.ts +74 -0
- package/dist/createPagination.js +71 -0
- package/dist/createPopoverSelect.svelte.js +6 -12
- package/dist/createStepper.d.ts +44 -0
- package/dist/createStepper.js +26 -0
- package/dist/createTooltip.svelte.d.ts +32 -0
- package/dist/createTooltip.svelte.js +61 -0
- package/dist/datetime/timezone.d.ts +36 -0
- package/dist/datetime/timezone.js +110 -0
- package/dist/dock-context.d.ts +52 -0
- package/dist/dock-context.js +1 -0
- package/dist/dock-manager-model.d.ts +136 -0
- package/dist/dock-manager-model.js +514 -0
- package/dist/dock-model.d.ts +105 -0
- package/dist/dock-model.js +288 -0
- package/dist/dock-popout.d.ts +30 -0
- package/dist/dock-popout.js +81 -0
- package/dist/editor-contract.d.ts +2 -0
- package/dist/filter-operators.d.ts +6 -0
- package/dist/filter-operators.js +35 -2
- package/dist/filtering/excel-filters.d.ts +18 -1
- package/dist/filtering/excel-filters.js +56 -0
- package/dist/form-field.d.ts +73 -0
- package/dist/form-field.js +15 -0
- package/dist/index.d.ts +25 -5
- package/dist/index.js +40 -4
- package/dist/list-nav.d.ts +18 -0
- package/dist/list-nav.js +30 -0
- package/dist/list-option.d.ts +2 -0
- package/dist/menu-item.d.ts +20 -0
- package/dist/menu-item.js +1 -0
- package/dist/menus.d.ts +5 -0
- package/dist/menus.js +68 -16
- package/dist/panel-resize.d.ts +25 -0
- package/dist/panel-resize.js +40 -0
- package/dist/popover.d.ts +29 -3
- package/dist/popover.js +22 -6
- package/dist/recurrence.d.ts +33 -7
- package/dist/recurrence.js +151 -13
- package/dist/row-drag.d.ts +8 -0
- package/dist/row-drag.js +11 -0
- package/dist/scheduler-ical.d.ts +30 -0
- package/dist/scheduler-ical.js +180 -0
- package/dist/scheduler-model.d.ts +269 -0
- package/dist/scheduler-model.js +602 -0
- package/dist/scheduler-view.svelte.d.ts +22 -0
- package/dist/scheduler-view.svelte.js +13 -0
- package/dist/spreadsheet.js +20 -12
- package/dist/ui-app.types.d.ts +3 -0
- package/package.json +5 -1
- package/src/DockNodeView.svelte +306 -0
- package/src/DockPopoutHost.svelte +17 -0
- package/src/GridMenus.svelte +83 -3
- package/src/SvAutoComplete.svelte +5 -3
- package/src/SvCarousel.svelte +20 -42
- package/src/SvCarousel.test.ts +68 -0
- package/src/SvColorInput.svelte +4 -3
- package/src/SvComboBox.svelte +7 -3
- package/src/SvCommand.svelte +28 -85
- package/src/SvCommand.test.ts +78 -0
- package/src/SvCountryInput.svelte +1 -1
- package/src/SvDateRangeInput.svelte +1 -1
- package/src/SvDateTimePicker.svelte +9 -3
- package/src/SvDockLayout.svelte +244 -0
- package/src/SvDockManager.svelte +922 -0
- package/src/SvDrawer.svelte +69 -47
- package/src/SvDropDownList.svelte +85 -13
- package/src/SvField.svelte +21 -4
- package/src/SvForm.svelte +201 -91
- package/src/SvForm.test.ts +299 -0
- package/src/SvGrid.controller.svelte.ts +152 -28
- package/src/SvGrid.css +193 -5
- package/src/SvGrid.svelte +357 -12
- package/src/SvGrid.types.ts +463 -1
- package/src/SvGridDropdown.svelte +8 -1
- package/src/SvGridSelect.svelte +1 -0
- package/src/SvListBox.svelte +36 -14
- package/src/SvMaskedInput.svelte +4 -3
- package/src/SvMenu.svelte +1 -1
- package/src/SvMenu.test.ts +24 -0
- package/src/SvMenuList.svelte +23 -71
- package/src/SvModal.svelte +12 -29
- package/src/SvModal.test.ts +82 -0
- package/src/SvMultiSelect.svelte +1 -0
- package/src/SvNumberInput.svelte +4 -3
- package/src/SvPagination.svelte +18 -18
- package/src/SvPasswordInput.svelte +3 -2
- package/src/SvPhoneInput.svelte +3 -2
- package/src/SvPopover.svelte +1 -1
- package/src/SvStepper.svelte +6 -9
- package/src/SvTagsInput.svelte +3 -2
- package/src/SvTooltip.svelte +12 -23
- package/src/SvTooltip.test.ts +73 -0
- package/src/SvTour.svelte +32 -4
- package/src/SvTreeSelect.svelte +1 -0
- package/src/build-api.ts +74 -51
- package/src/columns.ts +5 -0
- package/src/conditional-formatting.test.ts +104 -0
- package/src/conditional-formatting.ts +176 -19
- package/src/createCarousel.svelte.ts +104 -0
- package/src/createCommand.svelte.ts +125 -0
- package/src/createForm.svelte.ts +333 -0
- package/src/createMenu.svelte.ts +202 -0
- package/src/createOverlay.svelte.ts +101 -0
- package/src/createPagination.test.ts +65 -0
- package/src/createPagination.ts +84 -0
- package/src/createPopoverSelect.svelte.ts +5 -9
- package/src/createStepper.test.ts +62 -0
- package/src/createStepper.ts +61 -0
- package/src/createTooltip.svelte.ts +86 -0
- package/src/datetime/timezone.test.ts +69 -0
- package/src/datetime/timezone.ts +134 -0
- package/src/dock-context.ts +47 -0
- package/src/dock-manager-model.test.ts +275 -0
- package/src/dock-manager-model.ts +593 -0
- package/src/dock-model.test.ts +163 -0
- package/src/dock-model.ts +368 -0
- package/src/dock-popout.ts +94 -0
- package/src/editor-contract.ts +2 -0
- package/src/filter-operators.test.ts +59 -0
- package/src/filter-operators.ts +35 -2
- package/src/filtering/excel-filters.test.ts +86 -1
- package/src/filtering/excel-filters.ts +65 -0
- package/src/form-field.ts +87 -0
- package/src/index.ts +158 -4
- package/src/list-nav.test.ts +49 -0
- package/src/list-nav.ts +29 -0
- package/src/list-option.ts +2 -0
- package/src/menu-item.ts +21 -0
- package/src/menus.test.ts +48 -0
- package/src/menus.ts +73 -16
- package/src/panel-resize.ts +52 -0
- package/src/popover.bounds.test.ts +93 -0
- package/src/popover.ts +51 -7
- package/src/recurrence.test.ts +88 -1
- package/src/recurrence.ts +166 -15
- package/src/row-drag.ts +10 -0
- package/src/scheduler-ical.test.ts +68 -0
- package/src/scheduler-ical.ts +180 -0
- package/src/scheduler-model.test.ts +546 -0
- package/src/scheduler-model.ts +853 -0
- package/src/scheduler-view.svelte.ts +33 -0
- package/src/spreadsheet.ts +20 -12
- package/src/svgrid.effect-driven-filter.svelte.test.ts +116 -0
- package/src/ui-app.types.ts +3 -0
- package/src/virtual.test.ts +6 -4
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dock-model - the pure, immutable tree behind <SvDockLayout>. A docking layout
|
|
3
|
+
* is a tree of two node kinds:
|
|
4
|
+
*
|
|
5
|
+
* group a row/column of children (nested groups or tab-leaves), each holding
|
|
6
|
+
* a normalized `size` weight, with resizable splitters between them.
|
|
7
|
+
* tabs a leaf: one or more panes shown as tabs (only `active` is visible).
|
|
8
|
+
*
|
|
9
|
+
* Every restructuring a user does by dragging - dock a pane to a neighbour's
|
|
10
|
+
* edge (split) or centre (add as a tab), close a pane, drag a splitter - is a
|
|
11
|
+
* pure transform here that returns a NEW tree. The Svelte view owns the DOM and
|
|
12
|
+
* the drag gestures; all the tree surgery lives here so it is unit-testable
|
|
13
|
+
* without a browser. Modelled on Smart's `smart-layout` group/item structure.
|
|
14
|
+
*/
|
|
15
|
+
export type DockPane = {
|
|
16
|
+
id: string;
|
|
17
|
+
title: string;
|
|
18
|
+
/** Show a close (x) affordance on the tab. Default true. */
|
|
19
|
+
closable?: boolean;
|
|
20
|
+
/** Minimum size (px) for this pane's leaf along the split axis. */
|
|
21
|
+
minSize?: number;
|
|
22
|
+
};
|
|
23
|
+
/** A leaf: a stack of panes rendered as tabs (`active` is the visible one). */
|
|
24
|
+
export type DockTabs = {
|
|
25
|
+
type: 'tabs';
|
|
26
|
+
id: string;
|
|
27
|
+
panes: DockPane[];
|
|
28
|
+
active: number;
|
|
29
|
+
};
|
|
30
|
+
/** A row (side-by-side) or column (stacked) of children with per-child sizes. */
|
|
31
|
+
export type DockGroup = {
|
|
32
|
+
type: 'group';
|
|
33
|
+
id: string;
|
|
34
|
+
direction: 'row' | 'column';
|
|
35
|
+
children: DockNode[];
|
|
36
|
+
/** Size weight per child; parallel to `children`, normalized to sum ~1. */
|
|
37
|
+
sizes: number[];
|
|
38
|
+
};
|
|
39
|
+
export type DockNode = DockGroup | DockTabs;
|
|
40
|
+
/** Where a dragged pane lands relative to a target leaf. */
|
|
41
|
+
export type DockZone = 'left' | 'right' | 'top' | 'bottom' | 'center';
|
|
42
|
+
export type IdGen = () => string;
|
|
43
|
+
export declare function pane(id: string, title: string, closable?: boolean): DockPane;
|
|
44
|
+
/** Largest per-pane `minSize` in a leaf (0 if none). Used by splitter clamping. */
|
|
45
|
+
export declare function leafMinSize(node: DockTabs): number;
|
|
46
|
+
export declare function tabs(genId: IdGen, panes: DockPane[], active?: number): DockTabs;
|
|
47
|
+
export declare function group(genId: IdGen, direction: 'row' | 'column', children: DockNode[], sizes?: number[]): DockGroup;
|
|
48
|
+
/** Return sizes summing to 1, padded/trimmed to `count`. Even split when absent. */
|
|
49
|
+
export declare function normalizeSizes(sizes: number[] | undefined, count: number): number[];
|
|
50
|
+
/** The tabs leaf that holds `paneId`, or null. */
|
|
51
|
+
export declare function findTabsWithPane(root: DockNode, paneId: string): DockTabs | null;
|
|
52
|
+
/** Every pane id in the tree, in document order. */
|
|
53
|
+
export declare function allPaneIds(root: DockNode): string[];
|
|
54
|
+
/**
|
|
55
|
+
* Collapse the tree after edits: drop empty tabs and empty groups, unwrap a
|
|
56
|
+
* group that has a single child, and flatten a child group into its parent when
|
|
57
|
+
* they share a direction (so repeated docking never nests infinitely deep).
|
|
58
|
+
* Returns the tidied node, or null when nothing is left.
|
|
59
|
+
*/
|
|
60
|
+
export declare function normalize(node: DockNode): DockNode | null;
|
|
61
|
+
/** Set the active tab index of a tabs leaf. */
|
|
62
|
+
export declare function setActive(root: DockNode, tabsId: string, active: number): DockNode;
|
|
63
|
+
/** Replace a group's size weights (e.g. after a splitter drag). */
|
|
64
|
+
export declare function setSizes(root: DockNode, groupId: string, sizes: number[]): DockNode;
|
|
65
|
+
/**
|
|
66
|
+
* Reorder a pane WITHIN its tabs leaf (dragging a tab along the strip). `active`
|
|
67
|
+
* follows the pane that was showing. No-op if the indices are out of range.
|
|
68
|
+
*/
|
|
69
|
+
export declare function reorderPane(root: DockNode, tabsId: string, from: number, to: number): DockNode;
|
|
70
|
+
/**
|
|
71
|
+
* Remove a pane. Returns the tidied tree (or null if it emptied) plus the pane
|
|
72
|
+
* that was pulled out, so a caller can re-dock it elsewhere.
|
|
73
|
+
*/
|
|
74
|
+
export declare function removePane(root: DockNode, paneId: string): {
|
|
75
|
+
root: DockNode | null;
|
|
76
|
+
pane: DockPane | null;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Dock `movingPane` relative to the leaf `targetTabsId`. `center` adds it as a
|
|
80
|
+
* tab; an edge splits: the target is wrapped in (or extended into) a group whose
|
|
81
|
+
* direction matches the edge, with the new pane on the requested side.
|
|
82
|
+
*/
|
|
83
|
+
export declare function dockInto(root: DockNode, targetTabsId: string, movingPane: DockPane, zone: DockZone, genId: IdGen): DockNode;
|
|
84
|
+
/** Pull a whole tabs leaf out of the tree (for auto-hide). Returns the tidied
|
|
85
|
+
* tree (or null) plus the extracted leaf. */
|
|
86
|
+
export declare function removeLeaf(root: DockNode, tabsId: string): {
|
|
87
|
+
root: DockNode | null;
|
|
88
|
+
leaf: DockTabs | null;
|
|
89
|
+
};
|
|
90
|
+
/** Dock a whole leaf against an EDGE of the entire tree (for pinning an
|
|
91
|
+
* auto-hidden panel back in). Wraps the root in a new group as needed; the
|
|
92
|
+
* pinned leaf takes `fraction` of that axis (clamped 0.1..0.6), the rest keeps
|
|
93
|
+
* the existing content. */
|
|
94
|
+
export declare function dockLeafToEdge(root: DockNode | null, leaf: DockTabs, side: Exclude<DockZone, 'center'>, genId: IdGen, fraction?: number): DockNode;
|
|
95
|
+
/** Move an existing pane to a new dock position (remove then dock). */
|
|
96
|
+
export declare function movePane(root: DockNode, paneId: string, targetTabsId: string, zone: DockZone, genId: IdGen): DockNode;
|
|
97
|
+
/** A pane (tab). Alias of `pane`, named for the public builder set. */
|
|
98
|
+
export declare function dockPane(id: string, title: string, opts?: boolean | {
|
|
99
|
+
closable?: boolean;
|
|
100
|
+
minSize?: number;
|
|
101
|
+
}): DockPane;
|
|
102
|
+
/** A tab-leaf holding one or more panes. */
|
|
103
|
+
export declare function dockTabs(panes: DockPane[], active?: number): DockTabs;
|
|
104
|
+
/** A row (side-by-side) or column (stacked) of children. */
|
|
105
|
+
export declare function dockGroup(direction: 'row' | 'column', children: DockNode[], sizes?: number[]): DockGroup;
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dock-model - the pure, immutable tree behind <SvDockLayout>. A docking layout
|
|
3
|
+
* is a tree of two node kinds:
|
|
4
|
+
*
|
|
5
|
+
* group a row/column of children (nested groups or tab-leaves), each holding
|
|
6
|
+
* a normalized `size` weight, with resizable splitters between them.
|
|
7
|
+
* tabs a leaf: one or more panes shown as tabs (only `active` is visible).
|
|
8
|
+
*
|
|
9
|
+
* Every restructuring a user does by dragging - dock a pane to a neighbour's
|
|
10
|
+
* edge (split) or centre (add as a tab), close a pane, drag a splitter - is a
|
|
11
|
+
* pure transform here that returns a NEW tree. The Svelte view owns the DOM and
|
|
12
|
+
* the drag gestures; all the tree surgery lives here so it is unit-testable
|
|
13
|
+
* without a browser. Modelled on Smart's `smart-layout` group/item structure.
|
|
14
|
+
*/
|
|
15
|
+
// ---- construction helpers -------------------------------------------------
|
|
16
|
+
export function pane(id, title, closable = true) {
|
|
17
|
+
return { id, title, closable };
|
|
18
|
+
}
|
|
19
|
+
/** Largest per-pane `minSize` in a leaf (0 if none). Used by splitter clamping. */
|
|
20
|
+
export function leafMinSize(node) {
|
|
21
|
+
return node.panes.reduce((m, p) => Math.max(m, p.minSize ?? 0), 0);
|
|
22
|
+
}
|
|
23
|
+
export function tabs(genId, panes, active = 0) {
|
|
24
|
+
return { type: 'tabs', id: genId(), panes, active: clampIndex(active, panes.length) };
|
|
25
|
+
}
|
|
26
|
+
export function group(genId, direction, children, sizes) {
|
|
27
|
+
return { type: 'group', id: genId(), direction, children, sizes: normalizeSizes(sizes, children.length) };
|
|
28
|
+
}
|
|
29
|
+
// ---- small utilities ------------------------------------------------------
|
|
30
|
+
function clampIndex(i, len) {
|
|
31
|
+
if (len <= 0)
|
|
32
|
+
return 0;
|
|
33
|
+
return Math.max(0, Math.min(i, len - 1));
|
|
34
|
+
}
|
|
35
|
+
/** Return sizes summing to 1, padded/trimmed to `count`. Even split when absent. */
|
|
36
|
+
export function normalizeSizes(sizes, count) {
|
|
37
|
+
if (count <= 0)
|
|
38
|
+
return [];
|
|
39
|
+
const src = sizes && sizes.length === count ? sizes.slice() : new Array(count).fill(1);
|
|
40
|
+
const total = src.reduce((a, b) => a + (b > 0 ? b : 0), 0);
|
|
41
|
+
if (total <= 0)
|
|
42
|
+
return new Array(count).fill(1 / count);
|
|
43
|
+
return src.map((s) => (s > 0 ? s : 0) / total);
|
|
44
|
+
}
|
|
45
|
+
// ---- queries --------------------------------------------------------------
|
|
46
|
+
/** The tabs leaf that holds `paneId`, or null. */
|
|
47
|
+
export function findTabsWithPane(root, paneId) {
|
|
48
|
+
if (root.type === 'tabs')
|
|
49
|
+
return root.panes.some((p) => p.id === paneId) ? root : null;
|
|
50
|
+
for (const c of root.children) {
|
|
51
|
+
const hit = findTabsWithPane(c, paneId);
|
|
52
|
+
if (hit)
|
|
53
|
+
return hit;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
/** Every pane id in the tree, in document order. */
|
|
58
|
+
export function allPaneIds(root) {
|
|
59
|
+
if (root.type === 'tabs')
|
|
60
|
+
return root.panes.map((p) => p.id);
|
|
61
|
+
return root.children.flatMap(allPaneIds);
|
|
62
|
+
}
|
|
63
|
+
// ---- normalization --------------------------------------------------------
|
|
64
|
+
/**
|
|
65
|
+
* Collapse the tree after edits: drop empty tabs and empty groups, unwrap a
|
|
66
|
+
* group that has a single child, and flatten a child group into its parent when
|
|
67
|
+
* they share a direction (so repeated docking never nests infinitely deep).
|
|
68
|
+
* Returns the tidied node, or null when nothing is left.
|
|
69
|
+
*/
|
|
70
|
+
export function normalize(node) {
|
|
71
|
+
if (node.type === 'tabs') {
|
|
72
|
+
if (node.panes.length === 0)
|
|
73
|
+
return null;
|
|
74
|
+
return { ...node, active: clampIndex(node.active, node.panes.length) };
|
|
75
|
+
}
|
|
76
|
+
// Normalize children first, dropping the empties.
|
|
77
|
+
const kept = [];
|
|
78
|
+
const keptSizes = [];
|
|
79
|
+
node.children.forEach((child, i) => {
|
|
80
|
+
const n = normalize(child);
|
|
81
|
+
if (n) {
|
|
82
|
+
kept.push(n);
|
|
83
|
+
keptSizes.push(node.sizes[i] ?? 1);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
if (kept.length === 0)
|
|
87
|
+
return null;
|
|
88
|
+
if (kept.length === 1)
|
|
89
|
+
return kept[0]; // unwrap single-child group
|
|
90
|
+
// Flatten same-direction child groups into this one.
|
|
91
|
+
const flatChildren = [];
|
|
92
|
+
const flatSizes = [];
|
|
93
|
+
kept.forEach((child, i) => {
|
|
94
|
+
if (child.type === 'group' && child.direction === node.direction) {
|
|
95
|
+
const share = keptSizes[i];
|
|
96
|
+
child.children.forEach((gc, j) => {
|
|
97
|
+
flatChildren.push(gc);
|
|
98
|
+
flatSizes.push(share * (child.sizes[j] ?? 1 / child.children.length));
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
flatChildren.push(child);
|
|
103
|
+
flatSizes.push(keptSizes[i]);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
return { ...node, children: flatChildren, sizes: normalizeSizes(flatSizes, flatChildren.length) };
|
|
107
|
+
}
|
|
108
|
+
// ---- mutations (return a new tree) ----------------------------------------
|
|
109
|
+
/** Set the active tab index of a tabs leaf. */
|
|
110
|
+
export function setActive(root, tabsId, active) {
|
|
111
|
+
return mapNodes(root, (n) => n.type === 'tabs' && n.id === tabsId ? { ...n, active: clampIndex(active, n.panes.length) } : n);
|
|
112
|
+
}
|
|
113
|
+
/** Replace a group's size weights (e.g. after a splitter drag). */
|
|
114
|
+
export function setSizes(root, groupId, sizes) {
|
|
115
|
+
return mapNodes(root, (n) => n.type === 'group' && n.id === groupId ? { ...n, sizes: normalizeSizes(sizes, n.children.length) } : n);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Reorder a pane WITHIN its tabs leaf (dragging a tab along the strip). `active`
|
|
119
|
+
* follows the pane that was showing. No-op if the indices are out of range.
|
|
120
|
+
*/
|
|
121
|
+
export function reorderPane(root, tabsId, from, to) {
|
|
122
|
+
return mapNodes(root, (n) => {
|
|
123
|
+
if (n.type !== 'tabs' || n.id !== tabsId)
|
|
124
|
+
return n;
|
|
125
|
+
if (from < 0 || from >= n.panes.length)
|
|
126
|
+
return n;
|
|
127
|
+
const showing = n.panes[n.active]?.id;
|
|
128
|
+
const panes = n.panes.slice();
|
|
129
|
+
const [moved] = panes.splice(from, 1);
|
|
130
|
+
if (!moved)
|
|
131
|
+
return n;
|
|
132
|
+
// `to` is an insertion point in [0, length] (length = append), not a select
|
|
133
|
+
// index - clampIndex would wrongly cap it at length-1.
|
|
134
|
+
panes.splice(Math.max(0, Math.min(to, panes.length)), 0, moved);
|
|
135
|
+
const active = panes.findIndex((p) => p.id === showing);
|
|
136
|
+
return { ...n, panes, active: active < 0 ? 0 : active };
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Remove a pane. Returns the tidied tree (or null if it emptied) plus the pane
|
|
141
|
+
* that was pulled out, so a caller can re-dock it elsewhere.
|
|
142
|
+
*/
|
|
143
|
+
export function removePane(root, paneId) {
|
|
144
|
+
let removed = null;
|
|
145
|
+
const strip = (node) => {
|
|
146
|
+
if (node.type === 'tabs') {
|
|
147
|
+
const idx = node.panes.findIndex((p) => p.id === paneId);
|
|
148
|
+
if (idx < 0)
|
|
149
|
+
return node;
|
|
150
|
+
removed = node.panes[idx];
|
|
151
|
+
const panes = node.panes.filter((_, i) => i !== idx);
|
|
152
|
+
return { ...node, panes, active: clampIndex(node.active > idx ? node.active - 1 : node.active, panes.length) };
|
|
153
|
+
}
|
|
154
|
+
return { ...node, children: node.children.map(strip) };
|
|
155
|
+
};
|
|
156
|
+
const stripped = strip(root);
|
|
157
|
+
return { root: normalize(stripped), pane: removed };
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Dock `movingPane` relative to the leaf `targetTabsId`. `center` adds it as a
|
|
161
|
+
* tab; an edge splits: the target is wrapped in (or extended into) a group whose
|
|
162
|
+
* direction matches the edge, with the new pane on the requested side.
|
|
163
|
+
*/
|
|
164
|
+
export function dockInto(root, targetTabsId, movingPane, zone, genId) {
|
|
165
|
+
const dir = zone === 'left' || zone === 'right' ? 'row' : 'column';
|
|
166
|
+
const before = zone === 'left' || zone === 'top';
|
|
167
|
+
const transform = (node) => {
|
|
168
|
+
if (node.type === 'tabs') {
|
|
169
|
+
if (node.id !== targetTabsId)
|
|
170
|
+
return node;
|
|
171
|
+
if (zone === 'center') {
|
|
172
|
+
const panes = [...node.panes, movingPane];
|
|
173
|
+
return { ...node, panes, active: panes.length - 1 };
|
|
174
|
+
}
|
|
175
|
+
// Edge on a bare leaf: wrap it in a new group with the new leaf beside it.
|
|
176
|
+
const leaf = tabs(genId, [movingPane], 0);
|
|
177
|
+
const kids = before ? [leaf, node] : [node, leaf];
|
|
178
|
+
return group(genId, dir, kids);
|
|
179
|
+
}
|
|
180
|
+
// Group: if it directly contains the target leaf and matches the edge
|
|
181
|
+
// direction, insert a sibling in place (keeps the tree flat).
|
|
182
|
+
const idx = node.children.findIndex((c) => c.type === 'tabs' && c.id === targetTabsId);
|
|
183
|
+
if (idx >= 0 && zone !== 'center' && node.direction === dir) {
|
|
184
|
+
const leaf = tabs(genId, [movingPane], 0);
|
|
185
|
+
const children = node.children.slice();
|
|
186
|
+
const sizes = node.sizes.slice();
|
|
187
|
+
const half = (node.sizes[idx] ?? 1 / node.children.length) / 2;
|
|
188
|
+
const insertAt = before ? idx : idx + 1;
|
|
189
|
+
children.splice(insertAt, 0, leaf);
|
|
190
|
+
sizes.splice(idx, 1, half, half); // split the target's slot in two
|
|
191
|
+
// splice above replaced 1 with 2 at idx; the new leaf takes `before`/after
|
|
192
|
+
if (before) {
|
|
193
|
+
// children: [..., leaf, target, ...]; sizes already [half(leaf), half(target)]
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
// we inserted leaf AFTER target but sizes are [half, half] at idx/idx+1 - correct
|
|
197
|
+
}
|
|
198
|
+
return { ...node, children, sizes: normalizeSizes(sizes, children.length) };
|
|
199
|
+
}
|
|
200
|
+
return { ...node, children: node.children.map(transform) };
|
|
201
|
+
};
|
|
202
|
+
return normalize(transform(root)) ?? root;
|
|
203
|
+
}
|
|
204
|
+
/** Pull a whole tabs leaf out of the tree (for auto-hide). Returns the tidied
|
|
205
|
+
* tree (or null) plus the extracted leaf. */
|
|
206
|
+
export function removeLeaf(root, tabsId) {
|
|
207
|
+
let taken = null;
|
|
208
|
+
const strip = (node) => {
|
|
209
|
+
if (node.type === 'tabs') {
|
|
210
|
+
if (node.id === tabsId) {
|
|
211
|
+
taken = node;
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
return node;
|
|
215
|
+
}
|
|
216
|
+
const kids = [];
|
|
217
|
+
const sizes = [];
|
|
218
|
+
node.children.forEach((c, i) => {
|
|
219
|
+
const n = strip(c);
|
|
220
|
+
if (n) {
|
|
221
|
+
kids.push(n);
|
|
222
|
+
sizes.push(node.sizes[i] ?? 1);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
if (kids.length === 0)
|
|
226
|
+
return null;
|
|
227
|
+
return { ...node, children: kids, sizes: normalizeSizes(sizes, kids.length) };
|
|
228
|
+
};
|
|
229
|
+
const stripped = strip(root);
|
|
230
|
+
return { root: stripped ? normalize(stripped) : null, leaf: taken };
|
|
231
|
+
}
|
|
232
|
+
/** Dock a whole leaf against an EDGE of the entire tree (for pinning an
|
|
233
|
+
* auto-hidden panel back in). Wraps the root in a new group as needed; the
|
|
234
|
+
* pinned leaf takes `fraction` of that axis (clamped 0.1..0.6), the rest keeps
|
|
235
|
+
* the existing content. */
|
|
236
|
+
export function dockLeafToEdge(root, leaf, side, genId, fraction = 0.25) {
|
|
237
|
+
if (!root)
|
|
238
|
+
return leaf;
|
|
239
|
+
const dir = side === 'left' || side === 'right' ? 'row' : 'column';
|
|
240
|
+
const before = side === 'left' || side === 'top';
|
|
241
|
+
const f = Math.min(0.6, Math.max(0.1, fraction));
|
|
242
|
+
const kids = before ? [leaf, root] : [root, leaf];
|
|
243
|
+
const sizes = before ? [f, 1 - f] : [1 - f, f];
|
|
244
|
+
return normalize(group(genId, dir, kids, sizes)) ?? leaf;
|
|
245
|
+
}
|
|
246
|
+
/** Move an existing pane to a new dock position (remove then dock). */
|
|
247
|
+
export function movePane(root, paneId, targetTabsId, zone, genId) {
|
|
248
|
+
const { root: without, pane: moved } = removePane(root, paneId);
|
|
249
|
+
if (!without || !moved)
|
|
250
|
+
return root;
|
|
251
|
+
// If removing the pane dissolved the target leaf (it was the pane's own
|
|
252
|
+
// single-pane leaf), there is nothing to dock against - leave the tree as-is.
|
|
253
|
+
if (!containsTabs(without, targetTabsId))
|
|
254
|
+
return root;
|
|
255
|
+
return dockInto(without, targetTabsId, moved, zone, genId);
|
|
256
|
+
}
|
|
257
|
+
function containsTabs(node, tabsId) {
|
|
258
|
+
if (node.type === 'tabs')
|
|
259
|
+
return node.id === tabsId;
|
|
260
|
+
return node.children.some((c) => containsTabs(c, tabsId));
|
|
261
|
+
}
|
|
262
|
+
// ---- internal -------------------------------------------------------------
|
|
263
|
+
/** Map over every node, replacing where `fn` returns a new object. */
|
|
264
|
+
function mapNodes(node, fn) {
|
|
265
|
+
const mapped = fn(node);
|
|
266
|
+
if (mapped.type === 'group') {
|
|
267
|
+
return { ...mapped, children: mapped.children.map((c) => mapNodes(c, fn)) };
|
|
268
|
+
}
|
|
269
|
+
return mapped;
|
|
270
|
+
}
|
|
271
|
+
// ---- consumer builders (auto ids) -----------------------------------------
|
|
272
|
+
// Ergonomic constructors for building an initial layout without threading an
|
|
273
|
+
// id generator. Node ids only need to be unique; a running counter suffices.
|
|
274
|
+
let AUTO = 0;
|
|
275
|
+
const autoId = () => `d${AUTO++}`;
|
|
276
|
+
/** A pane (tab). Alias of `pane`, named for the public builder set. */
|
|
277
|
+
export function dockPane(id, title, opts) {
|
|
278
|
+
const o = typeof opts === 'boolean' ? { closable: opts } : (opts ?? {});
|
|
279
|
+
return { id, title, closable: o.closable ?? true, ...(o.minSize != null ? { minSize: o.minSize } : {}) };
|
|
280
|
+
}
|
|
281
|
+
/** A tab-leaf holding one or more panes. */
|
|
282
|
+
export function dockTabs(panes, active = 0) {
|
|
283
|
+
return tabs(autoId, panes, active);
|
|
284
|
+
}
|
|
285
|
+
/** A row (side-by-side) or column (stacked) of children. */
|
|
286
|
+
export function dockGroup(direction, children, sizes) {
|
|
287
|
+
return group(autoId, direction, children, sizes);
|
|
288
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dock-popout - open a real browser window and render a dock pane's content
|
|
3
|
+
* inside it (Golden-Layout-style pop-out). Runtime-only: an OS window handle is
|
|
4
|
+
* not serializable, so the manager tracks popped panes separately and pops them
|
|
5
|
+
* back into the layout when the window closes.
|
|
6
|
+
*
|
|
7
|
+
* The popped nodes are created by the main document and adopted into the popup,
|
|
8
|
+
* so they stay driven by the main window's reactivity - the pop-out shows the
|
|
9
|
+
* same live content as the docked view. Stylesheets + `--sg-*` theme tokens are
|
|
10
|
+
* copied across so it looks identical.
|
|
11
|
+
*/
|
|
12
|
+
import { type Snippet } from 'svelte';
|
|
13
|
+
import type { DockPane } from './dock-model';
|
|
14
|
+
export type Popout = {
|
|
15
|
+
pane: DockPane;
|
|
16
|
+
win: Window;
|
|
17
|
+
destroy: () => void;
|
|
18
|
+
};
|
|
19
|
+
export type OpenPopoutOptions = {
|
|
20
|
+
pane: DockPane;
|
|
21
|
+
paneSnippet: Snippet<[DockPane]>;
|
|
22
|
+
/** Element whose resolved `--sg-*` tokens theme the popup. */
|
|
23
|
+
themeSource: HTMLElement;
|
|
24
|
+
width?: number;
|
|
25
|
+
height?: number;
|
|
26
|
+
/** Called (once) when the popup closes, to pop the pane back in. */
|
|
27
|
+
onClose: (paneId: string) => void;
|
|
28
|
+
};
|
|
29
|
+
/** Open a pop-out window for a pane. Returns null if the browser blocked it. */
|
|
30
|
+
export declare function openPopout(opts: OpenPopoutOptions): Popout | null;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dock-popout - open a real browser window and render a dock pane's content
|
|
3
|
+
* inside it (Golden-Layout-style pop-out). Runtime-only: an OS window handle is
|
|
4
|
+
* not serializable, so the manager tracks popped panes separately and pops them
|
|
5
|
+
* back into the layout when the window closes.
|
|
6
|
+
*
|
|
7
|
+
* The popped nodes are created by the main document and adopted into the popup,
|
|
8
|
+
* so they stay driven by the main window's reactivity - the pop-out shows the
|
|
9
|
+
* same live content as the docked view. Stylesheets + `--sg-*` theme tokens are
|
|
10
|
+
* copied across so it looks identical.
|
|
11
|
+
*/
|
|
12
|
+
import { mount, unmount } from 'svelte';
|
|
13
|
+
import DockPopoutHost from './DockPopoutHost.svelte';
|
|
14
|
+
import { PANEL_THEME_VARS } from './popover';
|
|
15
|
+
/** Open a pop-out window for a pane. Returns null if the browser blocked it. */
|
|
16
|
+
export function openPopout(opts) {
|
|
17
|
+
const win = window.open('', '', `width=${opts.width ?? 480},height=${opts.height ?? 360}`);
|
|
18
|
+
if (!win)
|
|
19
|
+
return null;
|
|
20
|
+
const doc = win.document;
|
|
21
|
+
doc.title = opts.pane.title;
|
|
22
|
+
// Copy every stylesheet so the popped content is styled identically.
|
|
23
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
24
|
+
try {
|
|
25
|
+
const css = Array.from(sheet.cssRules).map((r) => r.cssText).join('\n');
|
|
26
|
+
const style = doc.createElement('style');
|
|
27
|
+
style.textContent = css;
|
|
28
|
+
doc.head.appendChild(style);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Cross-origin sheet: re-link it instead of reading its rules.
|
|
32
|
+
if (sheet.href) {
|
|
33
|
+
const link = doc.createElement('link');
|
|
34
|
+
link.rel = 'stylesheet';
|
|
35
|
+
link.href = sheet.href;
|
|
36
|
+
doc.head.appendChild(link);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Carry the theme tokens + base look across.
|
|
41
|
+
const cs = getComputedStyle(opts.themeSource);
|
|
42
|
+
for (const v of PANEL_THEME_VARS) {
|
|
43
|
+
const val = cs.getPropertyValue(v).trim();
|
|
44
|
+
if (val)
|
|
45
|
+
doc.documentElement.style.setProperty(v, val);
|
|
46
|
+
}
|
|
47
|
+
doc.body.style.margin = '0';
|
|
48
|
+
doc.body.style.background = cs.getPropertyValue('--sg-bg').trim() || '#fff';
|
|
49
|
+
doc.body.style.color = cs.getPropertyValue('--sg-fg').trim() || '#0f172a';
|
|
50
|
+
doc.body.style.font = cs.font;
|
|
51
|
+
const app = mount(DockPopoutHost, {
|
|
52
|
+
target: doc.body,
|
|
53
|
+
props: { pane: opts.paneSnippet, paneData: opts.pane },
|
|
54
|
+
});
|
|
55
|
+
let done = false;
|
|
56
|
+
const finish = (popBackIn) => {
|
|
57
|
+
if (done)
|
|
58
|
+
return;
|
|
59
|
+
done = true;
|
|
60
|
+
clearInterval(poll);
|
|
61
|
+
try {
|
|
62
|
+
unmount(app);
|
|
63
|
+
}
|
|
64
|
+
catch { /* window already tearing down */ }
|
|
65
|
+
if (popBackIn)
|
|
66
|
+
opts.onClose(opts.pane.id);
|
|
67
|
+
};
|
|
68
|
+
// The user closed the OS window -> pop the pane back into the layout.
|
|
69
|
+
win.addEventListener('beforeunload', () => finish(true));
|
|
70
|
+
const poll = setInterval(() => { if (win.closed)
|
|
71
|
+
finish(true); }, 400);
|
|
72
|
+
return {
|
|
73
|
+
pane: opts.pane,
|
|
74
|
+
win,
|
|
75
|
+
// Programmatic teardown (dock-back / manager unmount): don't re-pop-in.
|
|
76
|
+
destroy: () => { finish(false); try {
|
|
77
|
+
win.close();
|
|
78
|
+
}
|
|
79
|
+
catch { /* noop */ } },
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -41,6 +41,8 @@ export type SvEditorProps = {
|
|
|
41
41
|
id?: string;
|
|
42
42
|
/** Accessible name when there is no visible `label`. */
|
|
43
43
|
ariaLabel?: string;
|
|
44
|
+
/** Busy state - shows a spinner (e.g. while an async value/validation resolves). */
|
|
45
|
+
loading?: boolean;
|
|
44
46
|
};
|
|
45
47
|
/**
|
|
46
48
|
* The interaction surface an editor exposes when embedded as a grid cell editor.
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { Column, RowData } from "./index";
|
|
2
2
|
import type { FilterOperator, FilterOption } from "./SvGrid.types";
|
|
3
3
|
export declare const filterOperatorOptions: Array<FilterOption>;
|
|
4
|
+
/** Operators whose predicate is a set-membership test over a token list.
|
|
5
|
+
* The filter row renders a multi-value chip input for these instead of a
|
|
6
|
+
* single text box. */
|
|
7
|
+
export declare const SET_OPERATORS: ReadonlyArray<FilterOperator>;
|
|
8
|
+
/** Operators that need no value input - they act on emptiness alone. */
|
|
9
|
+
export declare const VALUELESS_OPERATORS: ReadonlyArray<FilterOperator>;
|
|
4
10
|
/** Which operators make sense for each column editor type. */
|
|
5
11
|
export declare const TEXT_OPERATORS: Array<FilterOperator>;
|
|
6
12
|
export declare const NUMBER_OPERATORS: Array<FilterOperator>;
|
package/dist/filter-operators.js
CHANGED
|
@@ -1,34 +1,67 @@
|
|
|
1
1
|
export const filterOperatorOptions = [
|
|
2
2
|
{ value: "contains", label: "Contains", iconName: "op-contains" },
|
|
3
|
+
{ value: "notContains", label: "Not contains", iconName: "op-notContains" },
|
|
3
4
|
{ value: "equals", label: "Equals", iconName: "op-equals" },
|
|
5
|
+
{ value: "notEquals", label: "Not equals", iconName: "op-notEquals" },
|
|
4
6
|
{ value: "startsWith", label: "Starts with", iconName: "op-startsWith" },
|
|
7
|
+
{ value: "endsWith", label: "Ends with", iconName: "op-endsWith" },
|
|
8
|
+
{ value: "regex", label: "Regex", iconName: "op-regex" },
|
|
9
|
+
{ value: "in", label: "In", iconName: "op-in" },
|
|
10
|
+
{ value: "notIn", label: "Not in", iconName: "op-notIn" },
|
|
5
11
|
{ value: "greaterThan", label: "Greater than", iconName: "op-greaterThan" },
|
|
6
12
|
{ value: "lessThan", label: "Less than", iconName: "op-lessThan" },
|
|
7
13
|
{ value: "between", label: "Between", iconName: "op-between" },
|
|
8
|
-
{ value: "isBlank", label: "
|
|
14
|
+
{ value: "isBlank", label: "Blank", iconName: "op-isBlank" },
|
|
15
|
+
{ value: "isNotBlank", label: "Not blank", iconName: "op-isNotBlank" },
|
|
16
|
+
];
|
|
17
|
+
/** Operators whose predicate is a set-membership test over a token list.
|
|
18
|
+
* The filter row renders a multi-value chip input for these instead of a
|
|
19
|
+
* single text box. */
|
|
20
|
+
export const SET_OPERATORS = ["in", "notIn"];
|
|
21
|
+
/** Operators that need no value input - they act on emptiness alone. */
|
|
22
|
+
export const VALUELESS_OPERATORS = [
|
|
23
|
+
"isBlank",
|
|
24
|
+
"isNotBlank",
|
|
9
25
|
];
|
|
10
26
|
/** Which operators make sense for each column editor type. */
|
|
11
27
|
export const TEXT_OPERATORS = [
|
|
12
28
|
"contains",
|
|
29
|
+
"notContains",
|
|
13
30
|
"equals",
|
|
31
|
+
"notEquals",
|
|
14
32
|
"startsWith",
|
|
33
|
+
"endsWith",
|
|
34
|
+
"regex",
|
|
35
|
+
"in",
|
|
36
|
+
"notIn",
|
|
15
37
|
"isBlank",
|
|
38
|
+
"isNotBlank",
|
|
16
39
|
];
|
|
17
40
|
export const NUMBER_OPERATORS = [
|
|
18
41
|
"equals",
|
|
42
|
+
"notEquals",
|
|
19
43
|
"greaterThan",
|
|
20
44
|
"lessThan",
|
|
21
45
|
"between",
|
|
46
|
+
"in",
|
|
47
|
+
"notIn",
|
|
22
48
|
"isBlank",
|
|
49
|
+
"isNotBlank",
|
|
23
50
|
];
|
|
24
51
|
export const DATE_OPERATORS = [
|
|
25
52
|
"equals",
|
|
53
|
+
"notEquals",
|
|
26
54
|
"lessThan",
|
|
27
55
|
"greaterThan",
|
|
28
56
|
"between",
|
|
29
57
|
"isBlank",
|
|
58
|
+
"isNotBlank",
|
|
59
|
+
];
|
|
60
|
+
export const CHECKBOX_OPERATORS = [
|
|
61
|
+
"equals",
|
|
62
|
+
"isBlank",
|
|
63
|
+
"isNotBlank",
|
|
30
64
|
];
|
|
31
|
-
export const CHECKBOX_OPERATORS = ["equals", "isBlank"];
|
|
32
65
|
export const fallbackOperatorOption = {
|
|
33
66
|
value: "contains",
|
|
34
67
|
label: "Contains",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ExcelFilterOperator = 'contains' | 'equals' | 'startsWith' | 'greaterThan' | 'lessThan' | 'between' | 'isBlank';
|
|
1
|
+
export type ExcelFilterOperator = 'contains' | 'notContains' | 'equals' | 'notEquals' | 'startsWith' | 'endsWith' | 'regex' | 'in' | 'notIn' | 'greaterThan' | 'lessThan' | 'between' | 'isBlank' | 'isNotBlank';
|
|
2
2
|
export type ExcelFilter = {
|
|
3
3
|
id: string;
|
|
4
4
|
operator: ExcelFilterOperator;
|
|
@@ -23,4 +23,21 @@ export type ExcelFilterOptions = {
|
|
|
23
23
|
* lowercase. The locale-aware lowercasing handles Turkish dotted-I /
|
|
24
24
|
* dotless-i correctly when the consumer threads `"tr"` through. */
|
|
25
25
|
export declare function normalizeForFilter(s: string, locale?: string | ReadonlyArray<string>): string;
|
|
26
|
+
/**
|
|
27
|
+
* Delimiter used to serialise the `in` / `notIn` value list into the single
|
|
28
|
+
* `value` string that the filter model carries. A newline is used because it
|
|
29
|
+
* (unlike a comma) practically never appears inside a typed token, so tokens
|
|
30
|
+
* survive the round-trip unescaped. The chip input in the filter row reads and
|
|
31
|
+
* writes with the same separator via {@link splitInTokens} / {@link joinInTokens}.
|
|
32
|
+
*/
|
|
33
|
+
export declare const IN_TOKEN_SEP = "\n";
|
|
34
|
+
/**
|
|
35
|
+
* Split a serialised `in` / `notIn` value into its individual tokens. Accepts
|
|
36
|
+
* both the newline separator the chip input writes and a plain comma-separated
|
|
37
|
+
* string (what a raw text field, e.g. the tool-panel filter, produces), so the
|
|
38
|
+
* same value round-trips through either UI.
|
|
39
|
+
*/
|
|
40
|
+
export declare function splitInTokens(value: unknown): string[];
|
|
41
|
+
/** Serialise a list of tokens back into the single `in` / `notIn` value. */
|
|
42
|
+
export declare function joinInTokens(tokens: ReadonlyArray<string>): string;
|
|
26
43
|
export declare function applyExcelFilter(cellValue: unknown, filter: ExcelFilter, options?: ExcelFilterOptions): boolean;
|