@ai-matrx/records-ui 0.93.3 → 0.93.4
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 +10 -0
- package/dist/index.cjs +396 -227
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +397 -228
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -7712,6 +7712,10 @@ function useMergedGridControls(args) {
|
|
|
7712
7712
|
(columnKey) => enabled && fieldOf2(columnKey) ? headerMenuItems(columnKey, ctx) : void 0,
|
|
7713
7713
|
[enabled, ctx, fieldOf2]
|
|
7714
7714
|
);
|
|
7715
|
+
const columnsControl = useMemo14(
|
|
7716
|
+
() => enabled ? { order, hidden, onChange: keepColumns } : void 0,
|
|
7717
|
+
[enabled, order, hidden, keepColumns]
|
|
7718
|
+
);
|
|
7715
7719
|
const close = () => setDialog(null);
|
|
7716
7720
|
const dialogs = !enabled ? null : /* @__PURE__ */ jsxs24(Fragment8, { children: [
|
|
7717
7721
|
colours.dialogs,
|
|
@@ -7843,7 +7847,7 @@ function useMergedGridControls(args) {
|
|
|
7843
7847
|
headerMenuFor,
|
|
7844
7848
|
filterFor,
|
|
7845
7849
|
contextMenu,
|
|
7846
|
-
columns:
|
|
7850
|
+
columns: columnsControl,
|
|
7847
7851
|
dialogs,
|
|
7848
7852
|
addColumn: () => setDialog({ kind: "insert", anchor: null, side: "right" }),
|
|
7849
7853
|
sheetTools: enabled ? /* @__PURE__ */ jsxs24(Fragment8, { children: [
|
|
@@ -9293,7 +9297,7 @@ function GridLayoutMenu({
|
|
|
9293
9297
|
}
|
|
9294
9298
|
|
|
9295
9299
|
// src/gridUndo.tsx
|
|
9296
|
-
import { useCallback as useCallback13, useEffect as useEffect21, useMemo as useMemo18, useRef as useRef11, useState as useState26 } from "react";
|
|
9300
|
+
import { useCallback as useCallback13, useEffect as useEffect21, useLayoutEffect, useMemo as useMemo18, useRef as useRef11, useState as useState26, useSyncExternalStore } from "react";
|
|
9297
9301
|
import { useRecordsClient as useRecordsClient16 } from "@ai-matrx/records/react";
|
|
9298
9302
|
import { announceRecordChanged } from "@ai-matrx/records/core";
|
|
9299
9303
|
import { Button as Button23 } from "@ai-matrx/design-system";
|
|
@@ -9399,7 +9403,43 @@ function useGridUndo(args) {
|
|
|
9399
9403
|
[push, undo, redo, held, busy, notice, dismiss]
|
|
9400
9404
|
);
|
|
9401
9405
|
}
|
|
9402
|
-
function
|
|
9406
|
+
function useUndoSource(undo) {
|
|
9407
|
+
const box = useRef11({
|
|
9408
|
+
undo,
|
|
9409
|
+
face: { back: undo.depth.back, forward: undo.depth.forward, busy: undo.busy },
|
|
9410
|
+
listeners: /* @__PURE__ */ new Set()
|
|
9411
|
+
});
|
|
9412
|
+
box.current.undo = undo;
|
|
9413
|
+
const source = useMemo18(
|
|
9414
|
+
() => ({
|
|
9415
|
+
face: () => box.current.face,
|
|
9416
|
+
subscribe: (listener) => {
|
|
9417
|
+
box.current.listeners.add(listener);
|
|
9418
|
+
return () => {
|
|
9419
|
+
box.current.listeners.delete(listener);
|
|
9420
|
+
};
|
|
9421
|
+
},
|
|
9422
|
+
undo: () => void box.current.undo.undo(),
|
|
9423
|
+
redo: () => void box.current.undo.redo()
|
|
9424
|
+
}),
|
|
9425
|
+
[]
|
|
9426
|
+
);
|
|
9427
|
+
const { back, forward } = undo.depth;
|
|
9428
|
+
const busy = undo.busy;
|
|
9429
|
+
useLayoutEffect(() => {
|
|
9430
|
+
const held = box.current.face;
|
|
9431
|
+
if (held.back === back && held.forward === forward && held.busy === busy) return;
|
|
9432
|
+
box.current.face = { back, forward, busy };
|
|
9433
|
+
for (const listener of box.current.listeners) listener();
|
|
9434
|
+
}, [back, forward, busy]);
|
|
9435
|
+
return source;
|
|
9436
|
+
}
|
|
9437
|
+
function UndoRedoLive({ source }) {
|
|
9438
|
+
const face = useSyncExternalStore(source.subscribe, source.face, source.face);
|
|
9439
|
+
return /* @__PURE__ */ jsx37(UndoRedoPair, { face, onUndo: source.undo, onRedo: source.redo });
|
|
9440
|
+
}
|
|
9441
|
+
function UndoRedoPair({ face, onUndo, onRedo }) {
|
|
9442
|
+
const undo = { depth: { back: face.back, forward: face.forward }, busy: face.busy };
|
|
9403
9443
|
if (undo.depth.back === 0 && undo.depth.forward === 0) return null;
|
|
9404
9444
|
return /* @__PURE__ */ jsxs32(Fragment13, { children: [
|
|
9405
9445
|
/* @__PURE__ */ jsxs32(
|
|
@@ -9411,7 +9451,7 @@ function UndoRedoButtons({ undo }) {
|
|
|
9411
9451
|
title: undo.depth.back > 0 ? `Undo (Cmd-Z) \xB7 ${undo.depth.back} ${undo.depth.back === 1 ? "step" : "steps"} back` : "Nothing to undo",
|
|
9412
9452
|
"aria-label": `Undo, ${undo.depth.back} ${undo.depth.back === 1 ? "step" : "steps"}`,
|
|
9413
9453
|
disabled: undo.busy || undo.depth.back === 0,
|
|
9414
|
-
onClick:
|
|
9454
|
+
onClick: onUndo,
|
|
9415
9455
|
children: [
|
|
9416
9456
|
"Undo ",
|
|
9417
9457
|
/* @__PURE__ */ jsx37("span", { className: "ml-1 tabular-nums text-muted-foreground", "data-records-undo-count": "", children: undo.depth.back })
|
|
@@ -9427,7 +9467,7 @@ function UndoRedoButtons({ undo }) {
|
|
|
9427
9467
|
title: undo.depth.forward > 0 ? `Redo (Shift-Cmd-Z) \xB7 ${undo.depth.forward} ${undo.depth.forward === 1 ? "step" : "steps"} forward` : "Nothing to redo",
|
|
9428
9468
|
"aria-label": `Redo, ${undo.depth.forward} ${undo.depth.forward === 1 ? "step" : "steps"}`,
|
|
9429
9469
|
disabled: undo.busy || undo.depth.forward === 0,
|
|
9430
|
-
onClick:
|
|
9470
|
+
onClick: onRedo,
|
|
9431
9471
|
children: [
|
|
9432
9472
|
"Redo ",
|
|
9433
9473
|
/* @__PURE__ */ jsx37("span", { className: "ml-1 tabular-nums text-muted-foreground", "data-records-redo-count": "", children: undo.depth.forward })
|
|
@@ -10792,6 +10832,7 @@ function Grid({
|
|
|
10792
10832
|
);
|
|
10793
10833
|
const rowVersion = useCallback16((row) => editingNow.current.rowSignature(row.id), []);
|
|
10794
10834
|
const undo = useGridUndo({ tableId, reload: records.reload, forget: editing.forget });
|
|
10835
|
+
const undoSource = useUndoSource(undo);
|
|
10795
10836
|
pushWritten.current = (write) => {
|
|
10796
10837
|
const field2 = (fields.data ?? []).find((f) => f.key === write.key);
|
|
10797
10838
|
const name = changedRecordName(table.data, rows.find((r) => r.id === write.rowId)?.document ?? {}, write.key, write.after);
|
|
@@ -11343,9 +11384,14 @@ function Grid({
|
|
|
11343
11384
|
);
|
|
11344
11385
|
const footerNow = useRef14({
|
|
11345
11386
|
summaryCell: () => null,
|
|
11346
|
-
addLine: () => null
|
|
11387
|
+
addLine: () => null,
|
|
11388
|
+
choose: () => void 0,
|
|
11389
|
+
add: () => void 0
|
|
11347
11390
|
});
|
|
11348
11391
|
footerNow.current = {
|
|
11392
|
+
// The footer is memoised on what it shows (`footerRows` below), so its buttons act through here.
|
|
11393
|
+
choose: chooseSummary,
|
|
11394
|
+
add: () => void addRowHere(),
|
|
11349
11395
|
summaryCell: (columnId) => {
|
|
11350
11396
|
const field2 = ordered.find((f) => f.key === columnId);
|
|
11351
11397
|
if (!field2) return null;
|
|
@@ -11358,11 +11404,11 @@ function Grid({
|
|
|
11358
11404
|
value: summaries.values[columnId],
|
|
11359
11405
|
rowCount: summaries.rowCount,
|
|
11360
11406
|
summaries,
|
|
11361
|
-
onChoose: (op2) =>
|
|
11407
|
+
onChoose: (op2) => footerNow.current.choose(columnId, op2)
|
|
11362
11408
|
}
|
|
11363
11409
|
);
|
|
11364
11410
|
},
|
|
11365
|
-
addLine: () => /* @__PURE__ */ jsx42(AddRowLine, { onAdd: () =>
|
|
11411
|
+
addLine: () => /* @__PURE__ */ jsx42(AddRowLine, { onAdd: () => footerNow.current.add(), busy: added.busy })
|
|
11366
11412
|
};
|
|
11367
11413
|
const addColumnNow = useRef14(controls.addColumn);
|
|
11368
11414
|
addColumnNow.current = controls.addColumn;
|
|
@@ -11477,13 +11523,24 @@ function Grid({
|
|
|
11477
11523
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- `hasTalk`/`hasAgent` stand for the host ports the JSX reads; handlers run through `rowActionsNow`.
|
|
11478
11524
|
[canWrite, mayWriteRow, tableActions.runnable, tableActions.agent, runner.busy, hasTalk, hasAgent, editable, rights.write]
|
|
11479
11525
|
);
|
|
11480
|
-
const
|
|
11526
|
+
const headerHeld = useRef14(/* @__PURE__ */ new Map());
|
|
11527
|
+
const menuLatest = useRef14(/* @__PURE__ */ new Map());
|
|
11528
|
+
const addColumnDef = useMemo22(() => addColumnColumn(() => addColumnNow.current()), []);
|
|
11529
|
+
const drawnColumns = useMemo22(
|
|
11481
11530
|
() => {
|
|
11482
|
-
const
|
|
11483
|
-
|
|
11531
|
+
const filterFor = controls.filterFor;
|
|
11532
|
+
const drawn = withHeaderMenus(
|
|
11533
|
+
bodyColumns,
|
|
11534
|
+
(key) => holdHeaderMenu(headerHeld.current, menuLatest.current, key, controls.headerMenuFor(key)),
|
|
11535
|
+
(key) => holdByContent(headerHeld.current, `filter:${key}`, filterFor(key))
|
|
11536
|
+
);
|
|
11537
|
+
return merged && rights.structure && editable ? [...drawn, addColumnDef] : drawn;
|
|
11484
11538
|
},
|
|
11485
|
-
[bodyColumns, controls.headerMenuFor, controls.filterFor, merged, rights.structure, editable]
|
|
11539
|
+
[bodyColumns, controls.headerMenuFor, controls.filterFor, merged, rights.structure, editable, addColumnDef]
|
|
11486
11540
|
);
|
|
11541
|
+
const columnsHeld = useRef14(null);
|
|
11542
|
+
const columns = columnsHeld.current !== null && sameColumns(columnsHeld.current, drawnColumns) ? columnsHeld.current : drawnColumns;
|
|
11543
|
+
columnsHeld.current = columns;
|
|
11487
11544
|
const [localColumns, setLocalColumns] = useState31(
|
|
11488
11545
|
null
|
|
11489
11546
|
);
|
|
@@ -11556,6 +11613,274 @@ function Grid({
|
|
|
11556
11613
|
} : {}
|
|
11557
11614
|
};
|
|
11558
11615
|
}, [presentation, onPresentationChange, writeLook, merged, phone]);
|
|
11616
|
+
const queryTotal = records.data?.total ?? rows.length;
|
|
11617
|
+
const tableQuery = useMemo22(
|
|
11618
|
+
() => merged ? {
|
|
11619
|
+
// THE STORE ANSWERS THE QUERY (tranche 6k): `data` is already the page.
|
|
11620
|
+
mode: "controlled",
|
|
11621
|
+
state: query,
|
|
11622
|
+
totalItems: queryTotal,
|
|
11623
|
+
onStateChange: setQuery,
|
|
11624
|
+
sourceProcessing: { search: "source", columnFilters: "source", sort: "source" }
|
|
11625
|
+
} : { mode: "controlled-local", state: query, onStateChange: setQuery },
|
|
11626
|
+
[merged, query, queryTotal]
|
|
11627
|
+
);
|
|
11628
|
+
const showAddLine = mayAddRow && rows.length > 0;
|
|
11629
|
+
const showSummaryLine = rows.length > 0 && (!merged || Object.keys(chosenSummaries).length > 0);
|
|
11630
|
+
const footerFace = JSON.stringify([
|
|
11631
|
+
showAddLine ? added.busy : null,
|
|
11632
|
+
showSummaryLine ? [chosenSummaries, summaries.values, summaries.rowCount, summaries.loading] : null
|
|
11633
|
+
]);
|
|
11634
|
+
const footerRows = useMemo22(
|
|
11635
|
+
() => [...showAddLine ? [ADD_ROW] : [], ...showSummaryLine ? [SUMMARY_ROW] : []],
|
|
11636
|
+
// `footerFace`, `ordered` and the summaries' operations are what the footer's cells draw.
|
|
11637
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
11638
|
+
[showAddLine, showSummaryLine, footerFace, ordered, summaries.operations, summaries.askOperations]
|
|
11639
|
+
);
|
|
11640
|
+
const coverageTotal = typeof records.data?.total === "number" ? records.data.total : null;
|
|
11641
|
+
const tableCoverage = useMemo22(
|
|
11642
|
+
() => ({
|
|
11643
|
+
answeredBy: merged ? "source" : "client",
|
|
11644
|
+
...merged && coverageTotal !== null ? { matched: coverageTotal } : {},
|
|
11645
|
+
noun: "record",
|
|
11646
|
+
...coverageTotal !== null ? { total: coverageTotal } : {}
|
|
11647
|
+
}),
|
|
11648
|
+
[merged, coverageTotal]
|
|
11649
|
+
);
|
|
11650
|
+
const sheetNow = useRef14({ merged, writeRange, editing, fields: fields.data ?? [], mayWriteRow, notify: host.notify });
|
|
11651
|
+
sheetNow.current = { merged, writeRange, editing, fields: fields.data ?? [], mayWriteRow, notify: host.notify };
|
|
11652
|
+
const hasNotify = Boolean(host.notify);
|
|
11653
|
+
const readOnlyReason = !editable || !rights.known || rights.write ? "Editing is off here, so this table can be copied but not changed." : rights.why("write");
|
|
11654
|
+
const spreadsheetConfig = useMemo22(
|
|
11655
|
+
() => canWrite && editable ? {
|
|
11656
|
+
enabled: true,
|
|
11657
|
+
writableColumnIds: pasteableKeys,
|
|
11658
|
+
onPatch: (patches, intent) => {
|
|
11659
|
+
const now = sheetNow.current;
|
|
11660
|
+
if (now.merged) {
|
|
11661
|
+
void now.writeRange(patches, intent);
|
|
11662
|
+
return;
|
|
11663
|
+
}
|
|
11664
|
+
for (const patch of patches) {
|
|
11665
|
+
now.editing.setCell(patch.rowId, patch.columnId, patch.value);
|
|
11666
|
+
}
|
|
11667
|
+
},
|
|
11668
|
+
...merged ? { undoDepth: 0, onSelectionChange: followSelection, controllerRef: sheetController } : {},
|
|
11669
|
+
columnRefusal: (columnId) => {
|
|
11670
|
+
const field2 = sheetNow.current.fields.find((f) => f.key === columnId);
|
|
11671
|
+
if (!field2) return `This table has no column called ${columnId}.`;
|
|
11672
|
+
return `${fieldName(field2)} cannot take a pasted value \u2014 ${fieldTypeLabel(field2)} is worked out or chosen, not typed.`;
|
|
11673
|
+
},
|
|
11674
|
+
validateCell: ({ columnId, value }) => {
|
|
11675
|
+
const field2 = sheetNow.current.fields.find((f) => f.key === columnId);
|
|
11676
|
+
if (!field2) return `This table has no column called ${columnId}.`;
|
|
11677
|
+
const judged = valueFromPastedText(field2, value === null ? "" : String(value));
|
|
11678
|
+
return "refusal" in judged ? judged.refusal : void 0;
|
|
11679
|
+
},
|
|
11680
|
+
onPastePlan: receivePastePlan,
|
|
11681
|
+
/*
|
|
11682
|
+
* TYPE-TO-EDIT (design-system 0.43 `onEditRequest`): the cell a person is on
|
|
11683
|
+
* opens ITS OWN editor — the Field's — holding what they typed (a letter, an
|
|
11684
|
+
* IME composition, an Option/AltGr character, dictation). A worked-out column
|
|
11685
|
+
* or a row this person may not write answers false, so nothing is swallowed.
|
|
11686
|
+
*/
|
|
11687
|
+
onEditRequest: (address, seed) => {
|
|
11688
|
+
const now = sheetNow.current;
|
|
11689
|
+
const field2 = now.fields.find((f) => f.key === address.columnId);
|
|
11690
|
+
if (!field2 || !fieldIsEditable(field2) || !now.mayWriteRow(address.rowId)) return false;
|
|
11691
|
+
if (seed !== null && seed !== "" && typingOpens(field2) === "none") return false;
|
|
11692
|
+
now.editing.begin({ rowId: address.rowId, key: field2.key }, seed);
|
|
11693
|
+
return true;
|
|
11694
|
+
},
|
|
11695
|
+
...hasNotify ? { notify: (message) => sheetNow.current.notify?.success(message) } : {}
|
|
11696
|
+
} : {
|
|
11697
|
+
/*
|
|
11698
|
+
* RANGE SELECTION AND COPY FOR A READER (merge step 3, 0.42 `readOnly`). A person
|
|
11699
|
+
* who may not change this table may still drag out a range, select all and press
|
|
11700
|
+
* Cmd-C for a real Excel/Sheets block — the UX walk graded range selection BROKEN
|
|
11701
|
+
* on a table whose writes were refused. Every write gesture (cut, paste, clear,
|
|
11702
|
+
* fill, undo) says why it cannot, in the store's own words.
|
|
11703
|
+
*/
|
|
11704
|
+
enabled: true,
|
|
11705
|
+
readOnly: true,
|
|
11706
|
+
...merged ? { onSelectionChange: followSelection, controllerRef: sheetController } : {},
|
|
11707
|
+
readOnlyReason
|
|
11708
|
+
},
|
|
11709
|
+
[canWrite, editable, pasteableKeys, merged, followSelection, receivePastePlan, hasNotify, readOnlyReason]
|
|
11710
|
+
);
|
|
11711
|
+
const toolbarFields = fields.data;
|
|
11712
|
+
const hasSeveralRows = rows.length > 1;
|
|
11713
|
+
const editingBusy = editing.busy;
|
|
11714
|
+
const addRecord = editing.addRecord;
|
|
11715
|
+
const phoneSheetTools = merged && phone ? controls.sheetTools : null;
|
|
11716
|
+
const tableToolbar = useMemo22(
|
|
11717
|
+
() => ({
|
|
11718
|
+
search: true,
|
|
11719
|
+
searchPlaceholder: "Search records",
|
|
11720
|
+
/*
|
|
11721
|
+
* A PHONE (tranche 6o): search and the one Tools button sit side by side at the START of
|
|
11722
|
+
* the row, so Tools is never scrolled out of reach; the search is the same query.
|
|
11723
|
+
*/
|
|
11724
|
+
...merged && phone ? {
|
|
11725
|
+
customSearch: /* @__PURE__ */ jsx42("div", { className: "flex min-w-0 flex-1 items-center gap-1.5", "data-records-grid-phone-search": "", children: /* @__PURE__ */ jsx42(
|
|
11726
|
+
"input",
|
|
11727
|
+
{
|
|
11728
|
+
type: "search",
|
|
11729
|
+
value: query.search,
|
|
11730
|
+
placeholder: "Search",
|
|
11731
|
+
"aria-label": "Search records",
|
|
11732
|
+
onChange: (event) => {
|
|
11733
|
+
const search = event.target.value;
|
|
11734
|
+
setQuery((held) => ({ ...held, search, page: 1 }));
|
|
11735
|
+
},
|
|
11736
|
+
className: "h-8 w-full min-w-0 rounded-md border border-input bg-background px-2 text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
11737
|
+
style: { fontSize: "16px" }
|
|
11738
|
+
}
|
|
11739
|
+
) })
|
|
11740
|
+
} : {},
|
|
11741
|
+
/* ONE ROW (lane TABLE-PAGE-CHROME): on a table page the grid's search, columns and
|
|
11742
|
+
sort are drawn INTO the page's toolbar row, never a second bar under it. */
|
|
11743
|
+
...toolbarSlot !== void 0 ? { portalInto: toolbarSlot, singleRow: true } : {},
|
|
11744
|
+
// SCR-2 / SCR-4: PRESENT for an admin, ABSENT for everyone else.
|
|
11745
|
+
// There is no third state: this package ships no disabled variant of
|
|
11746
|
+
// these controls, so a member cannot be shown a dead one by mistake.
|
|
11747
|
+
/*
|
|
11748
|
+
* WHERE THE GRID'S CONTROLS GO (lane C, review 2026-09-26 D6). Inline when the row has
|
|
11749
|
+
* room; behind the row's "…" once the page row is folded that far (Undo / Redo and New
|
|
11750
|
+
* record stay beside it — the primary action is never folded); on a phone EVERY control
|
|
11751
|
+
* — the table's own (clear, copy/export, group, columns), these, the search, the sort and
|
|
11752
|
+
* the page's filter, layout and share — in ONE "Tools" sheet. Never dropped.
|
|
11753
|
+
*/
|
|
11754
|
+
actions: /* @__PURE__ */ jsxs36(Fragment15, { children: [
|
|
11755
|
+
manual ? /* @__PURE__ */ jsx42(
|
|
11756
|
+
SortStateControl,
|
|
11757
|
+
{
|
|
11758
|
+
...handOrderSortState({
|
|
11759
|
+
sortName: columnSort ? columnLabel(toolbarFields ?? [], columnSort.id) : null,
|
|
11760
|
+
direction: columnSort?.direction ?? null,
|
|
11761
|
+
onBack: () => setQuery((held) => ({ ...held, sort: null, page: 1 })),
|
|
11762
|
+
...viewOrder?.onUseSort && columnSort ? {
|
|
11763
|
+
onUseSort: () => {
|
|
11764
|
+
const sort = columnSort;
|
|
11765
|
+
const chosen = { field: sort.id, direction: sort.direction };
|
|
11766
|
+
setHeldOrder({ order: "sorted", sorts: [chosen] });
|
|
11767
|
+
viewOrder.onUseSort?.(chosen);
|
|
11768
|
+
host.notify?.success(
|
|
11769
|
+
`Sorted by ${columnLabel(toolbarFields ?? [], sort.id)} ${sortArrow(sort.direction)}. ${MANUAL_SORT.replacedToast}`
|
|
11770
|
+
);
|
|
11771
|
+
}
|
|
11772
|
+
} : {}
|
|
11773
|
+
})
|
|
11774
|
+
}
|
|
11775
|
+
) : null,
|
|
11776
|
+
viewId && viewOrder?.onPlaced && hasSeveralRows ? /* @__PURE__ */ jsx42(Button27, { size: "sm", variant: "ghost", onClick: () => setReordering(true), "data-records-reorder-open": "", children: "Reorder" }) : null,
|
|
11777
|
+
!merged && Boolean(editable) && rights.write ? /* @__PURE__ */ jsx42(Button27, { size: "sm", disabled: editingBusy, onClick: () => void addRecord(), children: editingBusy ? "Adding\u2026" : "New record" }) : null,
|
|
11778
|
+
rights.structure && onAddField ? /* @__PURE__ */ jsx42(AddFieldButton, { onClick: onAddField }) : null,
|
|
11779
|
+
/* @__PURE__ */ jsx42(
|
|
11780
|
+
GridLayoutMenu,
|
|
11781
|
+
{
|
|
11782
|
+
look,
|
|
11783
|
+
firstColumn: sortedKeys[0] ? {
|
|
11784
|
+
key: sortedKeys[0],
|
|
11785
|
+
name: (() => {
|
|
11786
|
+
const first = (toolbarFields ?? []).find((f) => f.key === sortedKeys[0]);
|
|
11787
|
+
return first ? fieldName(first) : humanize(sortedKeys[0]);
|
|
11788
|
+
})()
|
|
11789
|
+
} : null,
|
|
11790
|
+
onChange: changeLayout,
|
|
11791
|
+
kept: Boolean(onPresentationChange)
|
|
11792
|
+
}
|
|
11793
|
+
)
|
|
11794
|
+
] }),
|
|
11795
|
+
...merged ? {
|
|
11796
|
+
overflow: {
|
|
11797
|
+
mode: phone ? "sheet" : toolbarFold2 >= 3 ? "menu" : "inline",
|
|
11798
|
+
pinned: /* @__PURE__ */ jsxs36(Fragment15, { children: [
|
|
11799
|
+
/* @__PURE__ */ jsx42(UndoRedoLive, { source: undoSource }),
|
|
11800
|
+
Boolean(editable) && rights.write ? /* @__PURE__ */ jsx42(Button27, { size: "sm", className: "shrink-0", disabled: added.busy, onClick: () => addRowRef.current(), children: added.busy ? "Adding\u2026" : "New record" }) : null
|
|
11801
|
+
] }),
|
|
11802
|
+
...phone ? {
|
|
11803
|
+
sheetTop: /* @__PURE__ */ jsxs36("div", { className: "flex flex-col gap-1.5 pb-1", "data-records-grid-sheet-top": "", "data-matrx-table-tools-keep-open": "", children: [
|
|
11804
|
+
/* @__PURE__ */ jsx42(
|
|
11805
|
+
"input",
|
|
11806
|
+
{
|
|
11807
|
+
type: "search",
|
|
11808
|
+
value: query.search,
|
|
11809
|
+
placeholder: "Search",
|
|
11810
|
+
"aria-label": "Search records",
|
|
11811
|
+
onChange: (event) => {
|
|
11812
|
+
const search = event.target.value;
|
|
11813
|
+
setQuery((held) => ({ ...held, search, page: 1 }));
|
|
11814
|
+
},
|
|
11815
|
+
className: "h-10 w-full min-w-0 rounded-md border border-input bg-background px-2 text-foreground placeholder:text-muted-foreground",
|
|
11816
|
+
style: { fontSize: "16px" }
|
|
11817
|
+
}
|
|
11818
|
+
),
|
|
11819
|
+
/* @__PURE__ */ jsxs36("label", { className: "flex items-center gap-2 text-xs text-muted-foreground", "data-records-grid-sheet-sort": "", children: [
|
|
11820
|
+
/* @__PURE__ */ jsx42("span", { className: "shrink-0", children: "Sort by" }),
|
|
11821
|
+
/* @__PURE__ */ jsxs36(
|
|
11822
|
+
"select",
|
|
11823
|
+
{
|
|
11824
|
+
"aria-label": "Sort by",
|
|
11825
|
+
value: query.sort ? `${query.sort.id}:${query.sort.direction}` : "",
|
|
11826
|
+
onChange: (event) => {
|
|
11827
|
+
const [id, direction] = event.target.value.split(":");
|
|
11828
|
+
setQuery((held) => ({
|
|
11829
|
+
...held,
|
|
11830
|
+
page: 1,
|
|
11831
|
+
sort: id ? { id, direction: direction === "desc" ? "desc" : "asc" } : null
|
|
11832
|
+
}));
|
|
11833
|
+
},
|
|
11834
|
+
className: "h-10 min-w-0 flex-1 rounded-md border border-input bg-background px-2 text-foreground",
|
|
11835
|
+
style: { fontSize: "16px" },
|
|
11836
|
+
children: [
|
|
11837
|
+
/* @__PURE__ */ jsx42("option", { value: "", children: "No sort" }),
|
|
11838
|
+
ordered.flatMap((field2) => [
|
|
11839
|
+
/* @__PURE__ */ jsx42("option", { value: `${field2.key}:asc`, children: `${fieldName(field2)} \u2191` }, `${field2.key}:asc`),
|
|
11840
|
+
/* @__PURE__ */ jsx42("option", { value: `${field2.key}:desc`, children: `${fieldName(field2)} \u2193` }, `${field2.key}:desc`)
|
|
11841
|
+
])
|
|
11842
|
+
]
|
|
11843
|
+
}
|
|
11844
|
+
)
|
|
11845
|
+
] }),
|
|
11846
|
+
phoneSheetTools
|
|
11847
|
+
] }),
|
|
11848
|
+
...sheetExtras ? { sheetExtras } : {}
|
|
11849
|
+
} : {}
|
|
11850
|
+
}
|
|
11851
|
+
} : {}
|
|
11852
|
+
}),
|
|
11853
|
+
[
|
|
11854
|
+
merged,
|
|
11855
|
+
phone,
|
|
11856
|
+
query.search,
|
|
11857
|
+
query.sort,
|
|
11858
|
+
toolbarSlot,
|
|
11859
|
+
manual,
|
|
11860
|
+
columnSort,
|
|
11861
|
+
toolbarFields,
|
|
11862
|
+
viewOrder,
|
|
11863
|
+
host,
|
|
11864
|
+
viewId,
|
|
11865
|
+
hasSeveralRows,
|
|
11866
|
+
editable,
|
|
11867
|
+
rights.write,
|
|
11868
|
+
rights.structure,
|
|
11869
|
+
editingBusy,
|
|
11870
|
+
addRecord,
|
|
11871
|
+
onAddField,
|
|
11872
|
+
look,
|
|
11873
|
+
sortedKeys,
|
|
11874
|
+
changeLayout,
|
|
11875
|
+
onPresentationChange,
|
|
11876
|
+
toolbarFold2,
|
|
11877
|
+
undoSource,
|
|
11878
|
+
added.busy,
|
|
11879
|
+
ordered,
|
|
11880
|
+
phoneSheetTools,
|
|
11881
|
+
sheetExtras
|
|
11882
|
+
]
|
|
11883
|
+
);
|
|
11559
11884
|
const labelOfField = useCallback16(
|
|
11560
11885
|
(key) => {
|
|
11561
11886
|
const held = (fields.data ?? []).find((f) => f.key === key);
|
|
@@ -11724,25 +12049,12 @@ function Grid({
|
|
|
11724
12049
|
viewTabs: false,
|
|
11725
12050
|
stickyHeader: true,
|
|
11726
12051
|
pageSize,
|
|
11727
|
-
query:
|
|
11728
|
-
// THE STORE ANSWERS THE QUERY (tranche 6k): `data` is already the page.
|
|
11729
|
-
mode: "controlled",
|
|
11730
|
-
state: query,
|
|
11731
|
-
totalItems: records.data?.total ?? rows.length,
|
|
11732
|
-
onStateChange: setQuery,
|
|
11733
|
-
sourceProcessing: { search: "source", columnFilters: "source", sort: "source" }
|
|
11734
|
-
} : { mode: "controlled-local", state: query, onStateChange: setQuery },
|
|
12052
|
+
query: tableQuery,
|
|
11735
12053
|
tableClassName: cn20(
|
|
11736
12054
|
!look.wrap && "[&_tbody_td]:max-w-[28rem] [&_tbody_td]:overflow-hidden [&_tbody_td]:text-ellipsis [&_tbody_td]:whitespace-nowrap! [&_tbody_td_*]:whitespace-nowrap!",
|
|
11737
12055
|
look.fit === "scroll" && "sm:[&>table]:w-max! sm:[&>table]:max-w-none! sm:[&>table]:min-w-full!"
|
|
11738
12056
|
),
|
|
11739
|
-
|
|
11740
|
-
const footerRows = [
|
|
11741
|
-
...mayAddRow && rows.length > 0 ? [ADD_ROW] : [],
|
|
11742
|
-
...rows.length > 0 && (!merged || Object.keys(chosenSummaries).length > 0) ? [SUMMARY_ROW] : []
|
|
11743
|
-
];
|
|
11744
|
-
return footerRows.length > 0 ? { footerRows } : {};
|
|
11745
|
-
})(),
|
|
12057
|
+
footerRows,
|
|
11746
12058
|
...copyConfig ? { copy: copyConfig } : {},
|
|
11747
12059
|
...offerSelection ? {
|
|
11748
12060
|
selection: {
|
|
@@ -11776,75 +12088,15 @@ function Grid({
|
|
|
11776
12088
|
] })
|
|
11777
12089
|
}
|
|
11778
12090
|
} : {},
|
|
11779
|
-
detail:
|
|
11780
|
-
window:
|
|
12091
|
+
detail: DETAIL_OFF,
|
|
12092
|
+
window: WINDOW_OFF,
|
|
11781
12093
|
...controls.contextMenu ? { contextMenu: controls.contextMenu } : {},
|
|
11782
|
-
|
|
11783
|
-
spreadsheet: {
|
|
11784
|
-
enabled: true,
|
|
11785
|
-
writableColumnIds: pasteableKeys,
|
|
11786
|
-
onPatch: (patches, intent) => {
|
|
11787
|
-
if (merged) {
|
|
11788
|
-
void writeRange(patches, intent);
|
|
11789
|
-
return;
|
|
11790
|
-
}
|
|
11791
|
-
for (const patch of patches) {
|
|
11792
|
-
editing.setCell(patch.rowId, patch.columnId, patch.value);
|
|
11793
|
-
}
|
|
11794
|
-
},
|
|
11795
|
-
...merged ? { undoDepth: 0, onSelectionChange: followSelection, controllerRef: sheetController } : {},
|
|
11796
|
-
columnRefusal: (columnId) => {
|
|
11797
|
-
const field2 = (fields.data ?? []).find((f) => f.key === columnId);
|
|
11798
|
-
if (!field2) return `This table has no column called ${columnId}.`;
|
|
11799
|
-
return `${fieldName(field2)} cannot take a pasted value \u2014 ${fieldTypeLabel(field2)} is worked out or chosen, not typed.`;
|
|
11800
|
-
},
|
|
11801
|
-
validateCell: ({ columnId, value }) => {
|
|
11802
|
-
const field2 = (fields.data ?? []).find((f) => f.key === columnId);
|
|
11803
|
-
if (!field2) return `This table has no column called ${columnId}.`;
|
|
11804
|
-
const judged = valueFromPastedText(field2, value === null ? "" : String(value));
|
|
11805
|
-
return "refusal" in judged ? judged.refusal : void 0;
|
|
11806
|
-
},
|
|
11807
|
-
onPastePlan: receivePastePlan,
|
|
11808
|
-
/*
|
|
11809
|
-
* TYPE-TO-EDIT (design-system 0.43 `onEditRequest`): the cell a person is on
|
|
11810
|
-
* opens ITS OWN editor — the Field's — holding what they typed (a letter, an
|
|
11811
|
-
* IME composition, an Option/AltGr character, dictation). A worked-out column
|
|
11812
|
-
* or a row this person may not write answers false, so nothing is swallowed.
|
|
11813
|
-
*/
|
|
11814
|
-
onEditRequest: (address, seed) => {
|
|
11815
|
-
const field2 = (fields.data ?? []).find((f) => f.key === address.columnId);
|
|
11816
|
-
if (!field2 || !fieldIsEditable(field2) || !mayWriteRow(address.rowId)) return false;
|
|
11817
|
-
if (seed !== null && seed !== "" && typingOpens(field2) === "none") return false;
|
|
11818
|
-
editing.begin({ rowId: address.rowId, key: field2.key }, seed);
|
|
11819
|
-
return true;
|
|
11820
|
-
},
|
|
11821
|
-
...host.notify ? { notify: (message) => host.notify?.success(message) } : {}
|
|
11822
|
-
}
|
|
11823
|
-
} : {
|
|
11824
|
-
/*
|
|
11825
|
-
* RANGE SELECTION AND COPY FOR A READER (merge step 3, 0.42 `readOnly`). A person
|
|
11826
|
-
* who may not change this table may still drag out a range, select all and press
|
|
11827
|
-
* Cmd-C for a real Excel/Sheets block — the UX walk graded range selection BROKEN
|
|
11828
|
-
* on a table whose writes were refused. Every write gesture (cut, paste, clear,
|
|
11829
|
-
* fill, undo) says why it cannot, in the store's own words.
|
|
11830
|
-
*/
|
|
11831
|
-
spreadsheet: {
|
|
11832
|
-
enabled: true,
|
|
11833
|
-
readOnly: true,
|
|
11834
|
-
...merged ? { onSelectionChange: followSelection, controllerRef: sheetController } : {},
|
|
11835
|
-
readOnlyReason: !editable || !rights.known || rights.write ? "Editing is off here, so this table can be copied but not changed." : rights.why("write")
|
|
11836
|
-
}
|
|
11837
|
-
},
|
|
12094
|
+
spreadsheet: spreadsheetConfig,
|
|
11838
12095
|
...groupingConfig ? { grouping: groupingConfig } : {},
|
|
11839
12096
|
columnState,
|
|
11840
12097
|
...look.rowHeight ? { rowHeight: look.rowHeight } : {},
|
|
11841
|
-
virtualize: fills ?
|
|
11842
|
-
coverage:
|
|
11843
|
-
answeredBy: merged ? "source" : "client",
|
|
11844
|
-
...merged && typeof records.data?.total === "number" ? { matched: records.data.total } : {},
|
|
11845
|
-
noun: "record",
|
|
11846
|
-
...typeof records.data?.total === "number" ? { total: records.data.total } : {}
|
|
11847
|
-
},
|
|
12098
|
+
virtualize: fills ? VIRTUALIZE_FILLED : true,
|
|
12099
|
+
coverage: tableCoverage,
|
|
11848
12100
|
...tableStyleProp ? { tableStyle: tableStyleProp } : {},
|
|
11849
12101
|
...onOpenRecord ? { onRowOpen: (row) => onOpenRecord(row.id) } : {},
|
|
11850
12102
|
...offersRowActions ? { rowActions } : {},
|
|
@@ -11856,142 +12108,7 @@ function Grid({
|
|
|
11856
12108
|
...mayAddRow ? { addFirst: /* @__PURE__ */ jsx42(AddFirstRowButton, { onAdd: () => void addRowHere(), busy: added.busy }) } : {},
|
|
11857
12109
|
emptyDescription: rights.write ? "Add the first one and it appears here." : rights.known ? `Nothing has been written to this table. ${rights.why("write")}` : "Nothing has been written to this table."
|
|
11858
12110
|
}),
|
|
11859
|
-
toolbar:
|
|
11860
|
-
search: true,
|
|
11861
|
-
searchPlaceholder: "Search records",
|
|
11862
|
-
/*
|
|
11863
|
-
* A PHONE (tranche 6o): search and the one Tools button sit side by side at the START of
|
|
11864
|
-
* the row, so Tools is never scrolled out of reach; the search is the same query.
|
|
11865
|
-
*/
|
|
11866
|
-
...merged && phone ? {
|
|
11867
|
-
customSearch: /* @__PURE__ */ jsx42("div", { className: "flex min-w-0 flex-1 items-center gap-1.5", "data-records-grid-phone-search": "", children: /* @__PURE__ */ jsx42(
|
|
11868
|
-
"input",
|
|
11869
|
-
{
|
|
11870
|
-
type: "search",
|
|
11871
|
-
value: query.search,
|
|
11872
|
-
placeholder: "Search",
|
|
11873
|
-
"aria-label": "Search records",
|
|
11874
|
-
onChange: (event) => {
|
|
11875
|
-
const search = event.target.value;
|
|
11876
|
-
setQuery((held) => ({ ...held, search, page: 1 }));
|
|
11877
|
-
},
|
|
11878
|
-
className: "h-8 w-full min-w-0 rounded-md border border-input bg-background px-2 text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
11879
|
-
style: { fontSize: "16px" }
|
|
11880
|
-
}
|
|
11881
|
-
) })
|
|
11882
|
-
} : {},
|
|
11883
|
-
/* ONE ROW (lane TABLE-PAGE-CHROME): on a table page the grid's search, columns and
|
|
11884
|
-
sort are drawn INTO the page's toolbar row, never a second bar under it. */
|
|
11885
|
-
...toolbarSlot !== void 0 ? { portalInto: toolbarSlot, singleRow: true } : {},
|
|
11886
|
-
// SCR-2 / SCR-4: PRESENT for an admin, ABSENT for everyone else.
|
|
11887
|
-
// There is no third state: this package ships no disabled variant of
|
|
11888
|
-
// these controls, so a member cannot be shown a dead one by mistake.
|
|
11889
|
-
/*
|
|
11890
|
-
* WHERE THE GRID'S CONTROLS GO (lane C, review 2026-09-26 D6). Inline when the row has
|
|
11891
|
-
* room; behind the row's "…" once the page row is folded that far (Undo / Redo and New
|
|
11892
|
-
* record stay beside it — the primary action is never folded); on a phone EVERY control
|
|
11893
|
-
* — the table's own (clear, copy/export, group, columns), these, the search, the sort and
|
|
11894
|
-
* the page's filter, layout and share — in ONE "Tools" sheet. Never dropped.
|
|
11895
|
-
*/
|
|
11896
|
-
actions: /* @__PURE__ */ jsxs36(Fragment15, { children: [
|
|
11897
|
-
manual ? /* @__PURE__ */ jsx42(
|
|
11898
|
-
SortStateControl,
|
|
11899
|
-
{
|
|
11900
|
-
...handOrderSortState({
|
|
11901
|
-
sortName: columnSort ? columnLabel(fields.data ?? [], columnSort.id) : null,
|
|
11902
|
-
direction: columnSort?.direction ?? null,
|
|
11903
|
-
onBack: () => setQuery((held) => ({ ...held, sort: null, page: 1 })),
|
|
11904
|
-
...viewOrder?.onUseSort && columnSort ? {
|
|
11905
|
-
onUseSort: () => {
|
|
11906
|
-
const sort = columnSort;
|
|
11907
|
-
const chosen = { field: sort.id, direction: sort.direction };
|
|
11908
|
-
setHeldOrder({ order: "sorted", sorts: [chosen] });
|
|
11909
|
-
viewOrder.onUseSort?.(chosen);
|
|
11910
|
-
host.notify?.success(
|
|
11911
|
-
`Sorted by ${columnLabel(fields.data ?? [], sort.id)} ${sortArrow(sort.direction)}. ${MANUAL_SORT.replacedToast}`
|
|
11912
|
-
);
|
|
11913
|
-
}
|
|
11914
|
-
} : {}
|
|
11915
|
-
})
|
|
11916
|
-
}
|
|
11917
|
-
) : null,
|
|
11918
|
-
viewId && viewOrder?.onPlaced && rows.length > 1 ? /* @__PURE__ */ jsx42(Button27, { size: "sm", variant: "ghost", onClick: () => setReordering(true), "data-records-reorder-open": "", children: "Reorder" }) : null,
|
|
11919
|
-
!merged && Boolean(editable) && rights.write ? /* @__PURE__ */ jsx42(Button27, { size: "sm", disabled: editing.busy, onClick: () => void editing.addRecord(), children: editing.busy ? "Adding\u2026" : "New record" }) : null,
|
|
11920
|
-
rights.structure && onAddField ? /* @__PURE__ */ jsx42(AddFieldButton, { onClick: onAddField }) : null,
|
|
11921
|
-
/* @__PURE__ */ jsx42(
|
|
11922
|
-
GridLayoutMenu,
|
|
11923
|
-
{
|
|
11924
|
-
look,
|
|
11925
|
-
firstColumn: sortedKeys[0] ? {
|
|
11926
|
-
key: sortedKeys[0],
|
|
11927
|
-
name: (() => {
|
|
11928
|
-
const first = (fields.data ?? []).find((f) => f.key === sortedKeys[0]);
|
|
11929
|
-
return first ? fieldName(first) : humanize(sortedKeys[0]);
|
|
11930
|
-
})()
|
|
11931
|
-
} : null,
|
|
11932
|
-
onChange: changeLayout,
|
|
11933
|
-
kept: Boolean(onPresentationChange)
|
|
11934
|
-
}
|
|
11935
|
-
)
|
|
11936
|
-
] }),
|
|
11937
|
-
...merged ? {
|
|
11938
|
-
overflow: {
|
|
11939
|
-
mode: phone ? "sheet" : toolbarFold2 >= 3 ? "menu" : "inline",
|
|
11940
|
-
pinned: /* @__PURE__ */ jsxs36(Fragment15, { children: [
|
|
11941
|
-
/* @__PURE__ */ jsx42(UndoRedoButtons, { undo }),
|
|
11942
|
-
Boolean(editable) && rights.write ? /* @__PURE__ */ jsx42(Button27, { size: "sm", className: "shrink-0", disabled: added.busy, onClick: () => void addRowHere(), children: added.busy ? "Adding\u2026" : "New record" }) : null
|
|
11943
|
-
] }),
|
|
11944
|
-
...phone ? {
|
|
11945
|
-
sheetTop: /* @__PURE__ */ jsxs36("div", { className: "flex flex-col gap-1.5 pb-1", "data-records-grid-sheet-top": "", "data-matrx-table-tools-keep-open": "", children: [
|
|
11946
|
-
/* @__PURE__ */ jsx42(
|
|
11947
|
-
"input",
|
|
11948
|
-
{
|
|
11949
|
-
type: "search",
|
|
11950
|
-
value: query.search,
|
|
11951
|
-
placeholder: "Search",
|
|
11952
|
-
"aria-label": "Search records",
|
|
11953
|
-
onChange: (event) => {
|
|
11954
|
-
const search = event.target.value;
|
|
11955
|
-
setQuery((held) => ({ ...held, search, page: 1 }));
|
|
11956
|
-
},
|
|
11957
|
-
className: "h-10 w-full min-w-0 rounded-md border border-input bg-background px-2 text-foreground placeholder:text-muted-foreground",
|
|
11958
|
-
style: { fontSize: "16px" }
|
|
11959
|
-
}
|
|
11960
|
-
),
|
|
11961
|
-
/* @__PURE__ */ jsxs36("label", { className: "flex items-center gap-2 text-xs text-muted-foreground", "data-records-grid-sheet-sort": "", children: [
|
|
11962
|
-
/* @__PURE__ */ jsx42("span", { className: "shrink-0", children: "Sort by" }),
|
|
11963
|
-
/* @__PURE__ */ jsxs36(
|
|
11964
|
-
"select",
|
|
11965
|
-
{
|
|
11966
|
-
"aria-label": "Sort by",
|
|
11967
|
-
value: query.sort ? `${query.sort.id}:${query.sort.direction}` : "",
|
|
11968
|
-
onChange: (event) => {
|
|
11969
|
-
const [id, direction] = event.target.value.split(":");
|
|
11970
|
-
setQuery((held) => ({
|
|
11971
|
-
...held,
|
|
11972
|
-
page: 1,
|
|
11973
|
-
sort: id ? { id, direction: direction === "desc" ? "desc" : "asc" } : null
|
|
11974
|
-
}));
|
|
11975
|
-
},
|
|
11976
|
-
className: "h-10 min-w-0 flex-1 rounded-md border border-input bg-background px-2 text-foreground",
|
|
11977
|
-
style: { fontSize: "16px" },
|
|
11978
|
-
children: [
|
|
11979
|
-
/* @__PURE__ */ jsx42("option", { value: "", children: "No sort" }),
|
|
11980
|
-
ordered.flatMap((field2) => [
|
|
11981
|
-
/* @__PURE__ */ jsx42("option", { value: `${field2.key}:asc`, children: `${fieldName(field2)} \u2191` }, `${field2.key}:asc`),
|
|
11982
|
-
/* @__PURE__ */ jsx42("option", { value: `${field2.key}:desc`, children: `${fieldName(field2)} \u2193` }, `${field2.key}:desc`)
|
|
11983
|
-
])
|
|
11984
|
-
]
|
|
11985
|
-
}
|
|
11986
|
-
)
|
|
11987
|
-
] }),
|
|
11988
|
-
controls.sheetTools
|
|
11989
|
-
] }),
|
|
11990
|
-
...sheetExtras ? { sheetExtras } : {}
|
|
11991
|
-
} : {}
|
|
11992
|
-
}
|
|
11993
|
-
} : {}
|
|
11994
|
-
}
|
|
12111
|
+
toolbar: tableToolbar
|
|
11995
12112
|
},
|
|
11996
12113
|
`records-grid-${tableId}:${openingKey}`
|
|
11997
12114
|
)
|
|
@@ -12093,6 +12210,58 @@ function Grid({
|
|
|
12093
12210
|
}
|
|
12094
12211
|
) });
|
|
12095
12212
|
}
|
|
12213
|
+
var DETAIL_OFF = { enabled: false };
|
|
12214
|
+
var WINDOW_OFF = { enabled: false };
|
|
12215
|
+
var VIRTUALIZE_FILLED = { threshold: 40 };
|
|
12216
|
+
function menuFace(items) {
|
|
12217
|
+
return JSON.stringify(items, (_key, value) => typeof value === "function" ? "\u0192" : value);
|
|
12218
|
+
}
|
|
12219
|
+
function menuThroughLatest(items, at, latest) {
|
|
12220
|
+
return items.map((item) => {
|
|
12221
|
+
const path = `${at}/${item.id}`;
|
|
12222
|
+
return {
|
|
12223
|
+
...item,
|
|
12224
|
+
...item.onSelect ? { onSelect: () => latest.get(path)?.() } : {},
|
|
12225
|
+
...item.submenu ? { submenu: menuThroughLatest(item.submenu, path, latest) } : {}
|
|
12226
|
+
};
|
|
12227
|
+
});
|
|
12228
|
+
}
|
|
12229
|
+
function bindLatest(items, at, latest) {
|
|
12230
|
+
for (const item of items) {
|
|
12231
|
+
const path = `${at}/${item.id}`;
|
|
12232
|
+
if (item.onSelect) latest.set(path, item.onSelect);
|
|
12233
|
+
if (item.submenu) bindLatest(item.submenu, path, latest);
|
|
12234
|
+
}
|
|
12235
|
+
}
|
|
12236
|
+
function holdHeaderMenu(held, latest, key, items) {
|
|
12237
|
+
if (!items) return items;
|
|
12238
|
+
const at = `menu:${key}`;
|
|
12239
|
+
bindLatest(items, at, latest);
|
|
12240
|
+
const sig = menuFace(items);
|
|
12241
|
+
const was = held.get(at);
|
|
12242
|
+
if (was && was.sig === sig) return was.value;
|
|
12243
|
+
const value = menuThroughLatest(items, at, latest);
|
|
12244
|
+
held.set(at, { sig, value });
|
|
12245
|
+
return value;
|
|
12246
|
+
}
|
|
12247
|
+
function holdByContent(held, at, value) {
|
|
12248
|
+
if (value === void 0) return value;
|
|
12249
|
+
const sig = JSON.stringify(value);
|
|
12250
|
+
const was = held.get(at);
|
|
12251
|
+
if (was && was.sig === sig) return was.value;
|
|
12252
|
+
held.set(at, { sig, value });
|
|
12253
|
+
return value;
|
|
12254
|
+
}
|
|
12255
|
+
function sameColumns(a, b) {
|
|
12256
|
+
if (a === b) return true;
|
|
12257
|
+
if (a.length !== b.length) return false;
|
|
12258
|
+
return a.every((column, i) => {
|
|
12259
|
+
const other = b[i];
|
|
12260
|
+
if (column === other) return true;
|
|
12261
|
+
const keys = Object.keys(column);
|
|
12262
|
+
return keys.length === Object.keys(other).length && keys.every((key) => Object.is(column[key], other[key]));
|
|
12263
|
+
});
|
|
12264
|
+
}
|
|
12096
12265
|
function withHeaderMenus(columns, menuFor, filterFor) {
|
|
12097
12266
|
return columns.map((column) => {
|
|
12098
12267
|
const items = menuFor(String(column.id));
|