@tensor_1/react 2.6.0 → 2.8.0
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/dist/components/EditorialHome.d.ts +183 -0
- package/dist/components/EditorialHome.js +183 -0
- package/dist/components/Field.js +10 -4
- package/dist/components/Popover.d.ts +18 -4
- package/dist/components/Popover.js +31 -2
- package/dist/components/Toast.js +5 -4
- package/dist/components/Tooltip.d.ts +9 -5
- package/dist/components/Tooltip.js +61 -3
- package/dist/components/Tree.d.ts +15 -1
- package/dist/components/Tree.js +247 -35
- package/dist/hooks/useAnchoredPosition.d.ts +22 -0
- package/dist/hooks/useAnchoredPosition.js +44 -0
- package/dist/hooks/useDialog.js +36 -5
- package/dist/index.cjs +852 -100
- package/dist/index.d.cts +343 -11
- package/dist/index.d.ts +4 -0
- package/dist/index.js +35 -0
- package/dist/overlay-position.d.ts +59 -0
- package/dist/overlay-position.js +62 -0
- package/dist/table-engine.js +72 -6
- package/dist/tree-model.d.ts +48 -0
- package/dist/tree-model.js +49 -0
- package/package.json +1 -1
package/dist/table-engine.js
CHANGED
|
@@ -30,8 +30,36 @@ function toggleSort(keys, field, additive = false) {
|
|
|
30
30
|
function asNum(v) {
|
|
31
31
|
return typeof v === "number" ? v : Number(v);
|
|
32
32
|
}
|
|
33
|
+
function asTime(v) {
|
|
34
|
+
if (v == null) return NaN;
|
|
35
|
+
if (v instanceof Date) return v.getTime();
|
|
36
|
+
if (typeof v === "number") return v;
|
|
37
|
+
const s = String(v).trim();
|
|
38
|
+
if (s === "") return NaN;
|
|
39
|
+
const parsed = Date.parse(s);
|
|
40
|
+
return Number.isNaN(parsed) ? Number(s) : parsed;
|
|
41
|
+
}
|
|
42
|
+
function matchDate(cell, f) {
|
|
43
|
+
const c = asTime(cell);
|
|
44
|
+
if (Number.isNaN(c)) return false;
|
|
45
|
+
switch (f.op) {
|
|
46
|
+
case "eq":
|
|
47
|
+
return c === asTime(f.value);
|
|
48
|
+
case "lt":
|
|
49
|
+
return c < asTime(f.value);
|
|
50
|
+
case "gt":
|
|
51
|
+
return c > asTime(f.value);
|
|
52
|
+
case "between": {
|
|
53
|
+
const [lo, hi] = f.value.map(asTime);
|
|
54
|
+
return c >= lo && c <= hi;
|
|
55
|
+
}
|
|
56
|
+
default:
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
33
60
|
function matchFilter(row, f) {
|
|
34
61
|
const cell = row[f.field];
|
|
62
|
+
if (f.kind === "date") return matchDate(cell, f);
|
|
35
63
|
switch (f.op) {
|
|
36
64
|
case "isAnyOf":
|
|
37
65
|
return Array.isArray(f.value) && f.value.includes(cell);
|
|
@@ -90,13 +118,53 @@ function highlightSegments(text, query) {
|
|
|
90
118
|
}
|
|
91
119
|
return out;
|
|
92
120
|
}
|
|
121
|
+
const ARRAY_OPS = /* @__PURE__ */ new Set(["isAnyOf", "isNoneOf", "between"]);
|
|
122
|
+
const escapePart = (s) => encodeURIComponent(s).replace(/~/g, "%7E");
|
|
123
|
+
const unescapePart = (s) => decodeURIComponent(s);
|
|
124
|
+
const scalarToStr = (v) => v == null ? "" : String(v);
|
|
125
|
+
function parseScalar(s, kind) {
|
|
126
|
+
switch (kind) {
|
|
127
|
+
case "number":
|
|
128
|
+
return Number(s);
|
|
129
|
+
case "boolean":
|
|
130
|
+
return s === "true";
|
|
131
|
+
case "date":
|
|
132
|
+
return /^-?\d+$/.test(s) ? Number(s) : s;
|
|
133
|
+
// epoch-ms number, else the ISO/date string
|
|
134
|
+
default:
|
|
135
|
+
return s;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function kindFromOp(op) {
|
|
139
|
+
if (op === "isAnyOf" || op === "isNoneOf") return "enum";
|
|
140
|
+
if (op === "is") return "boolean";
|
|
141
|
+
if (op === "contains" || op === "equals" || op === "startsWith") return "text";
|
|
142
|
+
return "number";
|
|
143
|
+
}
|
|
144
|
+
function serializeFilter(f) {
|
|
145
|
+
const value = Array.isArray(f.value) ? f.value.map((x) => escapePart(scalarToStr(x))).join("|") : escapePart(scalarToStr(f.value));
|
|
146
|
+
return `${escapePart(f.field)}~${f.kind}~${f.op}~${value}`;
|
|
147
|
+
}
|
|
148
|
+
function parseFilter(tok) {
|
|
149
|
+
const parts = tok.split("~");
|
|
150
|
+
let field, kind, op, raw;
|
|
151
|
+
if (parts.length >= 4) {
|
|
152
|
+
[field, kind, op, raw] = [unescapePart(parts[0]), parts[1], parts[2], parts[3] ?? ""];
|
|
153
|
+
} else {
|
|
154
|
+
[field, op, raw] = [unescapePart(parts[0]), parts[1], parts[2] ?? ""];
|
|
155
|
+
kind = kindFromOp(op);
|
|
156
|
+
}
|
|
157
|
+
if (!field || !op) return null;
|
|
158
|
+
const value = ARRAY_OPS.has(op) ? raw.split("|").filter((s) => s !== "").map((s) => parseScalar(unescapePart(s), kind)) : parseScalar(unescapePart(raw), kind);
|
|
159
|
+
return { field, kind, op, value };
|
|
160
|
+
}
|
|
93
161
|
function serializeView(v) {
|
|
94
162
|
const p = new URLSearchParams();
|
|
95
163
|
if (v.sort?.length) p.set("sort", v.sort.map((s) => `${s.field}:${s.dir === "descending" ? "d" : "a"}`).join(","));
|
|
96
164
|
if (v.columns?.length) p.set("cols", v.columns.join(","));
|
|
97
165
|
if (v.density && v.density !== "standard") p.set("density", v.density);
|
|
98
166
|
if (v.q) p.set("q", v.q);
|
|
99
|
-
if (v.filters?.length) p.set("filter", v.filters.map(
|
|
167
|
+
if (v.filters?.length) p.set("filter", v.filters.map(serializeFilter).join(","));
|
|
100
168
|
return p.toString();
|
|
101
169
|
}
|
|
102
170
|
function parseView(qs) {
|
|
@@ -114,11 +182,9 @@ function parseView(qs) {
|
|
|
114
182
|
const q = p.get("q");
|
|
115
183
|
if (q) v.q = q;
|
|
116
184
|
const filter = p.get("filter");
|
|
117
|
-
if (filter)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return { field, kind: "text", op, value };
|
|
121
|
-
});
|
|
185
|
+
if (filter) {
|
|
186
|
+
v.filters = filter.split(",").filter(Boolean).map(parseFilter).filter((f) => f !== null);
|
|
187
|
+
}
|
|
122
188
|
return v;
|
|
123
189
|
}
|
|
124
190
|
function virtualWindow(scrollTop, viewportH, rowH, count, overscan = 6) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* uix tree model — framework-agnostic, dependency-free tree flattening + keyboard nav.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions (no DOM, no React): flatten a nested tree into the linear list of *visible*
|
|
5
|
+
* rows (respecting expansion) so it can be virtualized with the table engine's virtualWindow,
|
|
6
|
+
* and resolve a keyboard key into a navigation decision. Both the plain and the virtualized
|
|
7
|
+
* <Tree> render paths use these, so the two behave identically and keyboard nav works even for
|
|
8
|
+
* rows that are scrolled out of the virtual window (UIX-FIX-05). Unit-tested (tree-model.test.mjs).
|
|
9
|
+
*/
|
|
10
|
+
/** Minimal shape a node must have; the concrete node (label, icon, …) rides along as `T`. */
|
|
11
|
+
interface TreeLike<T> {
|
|
12
|
+
id: string;
|
|
13
|
+
children?: T[];
|
|
14
|
+
}
|
|
15
|
+
interface FlatNode<T> {
|
|
16
|
+
node: T;
|
|
17
|
+
/** 1-based depth, for aria-level. */
|
|
18
|
+
level: number;
|
|
19
|
+
/** number of siblings at this level (this node's parent's child count), for aria-setsize. */
|
|
20
|
+
setsize: number;
|
|
21
|
+
/** 1-based index among those siblings, for aria-posinset. */
|
|
22
|
+
posinset: number;
|
|
23
|
+
hasChildren: boolean;
|
|
24
|
+
isExpanded: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Flatten a nested tree into the visible rows, in DOM/reading order. A node's children follow
|
|
28
|
+
* it immediately and are included only when the node is in `expanded`.
|
|
29
|
+
*/
|
|
30
|
+
declare function flattenTree<T extends TreeLike<T>>(nodes: readonly T[], expanded: ReadonlySet<string>, level?: number): FlatNode<T>[];
|
|
31
|
+
/** A decision produced by a key press — any subset may be set. */
|
|
32
|
+
interface TreeNavAction {
|
|
33
|
+
/** move focus (roving tabindex) to this node id. */
|
|
34
|
+
focusId?: string;
|
|
35
|
+
/** toggle this node's expansion. */
|
|
36
|
+
toggleId?: string;
|
|
37
|
+
/** select this node id. */
|
|
38
|
+
selectId?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resolve a key press against the flattened, visible node list — the single source of truth for
|
|
42
|
+
* both render paths. Mirrors the WAI-ARIA tree pattern: Up/Down move by row, Right expands then
|
|
43
|
+
* descends, Left collapses then ascends to the parent, Home/End jump, Enter/Space select (and
|
|
44
|
+
* toggle a parent). Returns an empty action for unhandled keys or an unknown current id.
|
|
45
|
+
*/
|
|
46
|
+
declare function treeNav<T extends TreeLike<T>>(flat: readonly FlatNode<T>[], currentId: string | undefined, key: string): TreeNavAction;
|
|
47
|
+
|
|
48
|
+
export { type FlatNode, type TreeLike, type TreeNavAction, flattenTree, treeNav };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
function flattenTree(nodes, expanded, level = 1) {
|
|
2
|
+
const out = [];
|
|
3
|
+
const setsize = nodes.length;
|
|
4
|
+
nodes.forEach((node, i) => {
|
|
5
|
+
const hasChildren = Array.isArray(node.children) && node.children.length > 0;
|
|
6
|
+
const isExpanded = hasChildren && expanded.has(node.id);
|
|
7
|
+
out.push({ node, level, setsize, posinset: i + 1, hasChildren, isExpanded });
|
|
8
|
+
if (isExpanded) out.push(...flattenTree(node.children, expanded, level + 1));
|
|
9
|
+
});
|
|
10
|
+
return out;
|
|
11
|
+
}
|
|
12
|
+
function treeNav(flat, currentId, key) {
|
|
13
|
+
const idx = flat.findIndex((f) => f.node.id === currentId);
|
|
14
|
+
if (idx < 0) return {};
|
|
15
|
+
const cur = flat[idx];
|
|
16
|
+
const focusAt = (i) => {
|
|
17
|
+
const clamped = Math.max(0, Math.min(flat.length - 1, i));
|
|
18
|
+
return { focusId: flat[clamped]?.node.id };
|
|
19
|
+
};
|
|
20
|
+
switch (key) {
|
|
21
|
+
case "ArrowDown":
|
|
22
|
+
return focusAt(idx + 1);
|
|
23
|
+
case "ArrowUp":
|
|
24
|
+
return focusAt(idx - 1);
|
|
25
|
+
case "Home":
|
|
26
|
+
return focusAt(0);
|
|
27
|
+
case "End":
|
|
28
|
+
return focusAt(flat.length - 1);
|
|
29
|
+
case "ArrowRight":
|
|
30
|
+
if (cur.hasChildren && !cur.isExpanded) return { toggleId: cur.node.id };
|
|
31
|
+
if (cur.hasChildren && cur.isExpanded) return focusAt(idx + 1);
|
|
32
|
+
return {};
|
|
33
|
+
case "ArrowLeft":
|
|
34
|
+
if (cur.hasChildren && cur.isExpanded) return { toggleId: cur.node.id };
|
|
35
|
+
for (let i = idx - 1; i >= 0; i--) {
|
|
36
|
+
if (flat[i].level === cur.level - 1) return { focusId: flat[i].node.id };
|
|
37
|
+
}
|
|
38
|
+
return {};
|
|
39
|
+
case "Enter":
|
|
40
|
+
case " ":
|
|
41
|
+
return cur.hasChildren ? { toggleId: cur.node.id, selectId: cur.node.id } : { selectId: cur.node.id };
|
|
42
|
+
default:
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
flattenTree,
|
|
48
|
+
treeNav
|
|
49
|
+
};
|