@ai-matrx/records-ui 0.93.2 → 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/dist/index.cjs CHANGED
@@ -4349,7 +4349,20 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow,
4349
4349
  }
4350
4350
  return null;
4351
4351
  }, [client, tableId, reload, order]);
4352
+ const rowSignature = (0, import_react22.useCallback)(
4353
+ (rowId) => {
4354
+ const out = [];
4355
+ if (editing?.rowId === rowId) out.push("open", editing.key, draft);
4356
+ const prefix = `${rowId}::`;
4357
+ for (const id in optimistic) if (id.startsWith(prefix)) out.push("held", id, optimistic[id]);
4358
+ for (const id in saving) if (id.startsWith(prefix)) out.push("saving", id);
4359
+ for (const id in refusals) if (id.startsWith(prefix)) out.push("refused", id, refusals[id]);
4360
+ return out;
4361
+ },
4362
+ [editing, draft, optimistic, saving, refusals]
4363
+ );
4352
4364
  return {
4365
+ rowSignature,
4353
4366
  valueFor,
4354
4367
  editing,
4355
4368
  draft,
@@ -7965,6 +7978,10 @@ function useMergedGridControls(args) {
7965
7978
  (columnKey) => enabled && fieldOf2(columnKey) ? headerMenuItems(columnKey, ctx) : void 0,
7966
7979
  [enabled, ctx, fieldOf2]
7967
7980
  );
7981
+ const columnsControl = (0, import_react40.useMemo)(
7982
+ () => enabled ? { order, hidden, onChange: keepColumns } : void 0,
7983
+ [enabled, order, hidden, keepColumns]
7984
+ );
7968
7985
  const close = () => setDialog(null);
7969
7986
  const dialogs = !enabled ? null : /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
7970
7987
  colours.dialogs,
@@ -8096,7 +8113,7 @@ function useMergedGridControls(args) {
8096
8113
  headerMenuFor,
8097
8114
  filterFor,
8098
8115
  contextMenu,
8099
- columns: enabled ? { order, hidden, onChange: keepColumns } : void 0,
8116
+ columns: columnsControl,
8100
8117
  dialogs,
8101
8118
  addColumn: () => setDialog({ kind: "insert", anchor: null, side: "right" }),
8102
8119
  sheetTools: enabled ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
@@ -8202,11 +8219,12 @@ function placePinnedRows(pageRows, pinned, anchors) {
8202
8219
  }
8203
8220
  function withAddRowCells(columns, line) {
8204
8221
  if (line === null) return columns;
8222
+ const draw = () => typeof line === "function" ? line() : line;
8205
8223
  return columns.map((column, index) => {
8206
8224
  const own2 = column.cell;
8207
8225
  return {
8208
8226
  ...column,
8209
- cell: (row, rowIndex) => row.id === ADD_ROW_ID ? index === 0 ? line : null : own2 ? own2(row, rowIndex) : null
8227
+ cell: (row, rowIndex) => row.id === ADD_ROW_ID ? index === 0 ? draw() : null : own2 ? own2(row, rowIndex) : null
8210
8228
  };
8211
8229
  });
8212
8230
  }
@@ -8229,6 +8247,7 @@ function AddRowLine({ onAdd, busy }) {
8229
8247
  }
8230
8248
  );
8231
8249
  }
8250
+ var drawNothing = () => null;
8232
8251
  function addColumnColumn(onAdd) {
8233
8252
  return {
8234
8253
  id: ADD_COLUMN_ID,
@@ -8248,7 +8267,8 @@ function addColumnColumn(onAdd) {
8248
8267
  children: "+"
8249
8268
  }
8250
8269
  ),
8251
- cell: () => null,
8270
+ // ONE function for the life of the module: a body cell that draws nothing never changes.
8271
+ cell: drawNothing,
8252
8272
  sortable: false,
8253
8273
  filter: false,
8254
8274
  hideable: false,
@@ -9623,7 +9643,43 @@ function useGridUndo(args) {
9623
9643
  [push, undo, redo, held, busy, notice, dismiss]
9624
9644
  );
9625
9645
  }
9626
- function UndoRedoButtons({ undo }) {
9646
+ function useUndoSource(undo) {
9647
+ const box = (0, import_react52.useRef)({
9648
+ undo,
9649
+ face: { back: undo.depth.back, forward: undo.depth.forward, busy: undo.busy },
9650
+ listeners: /* @__PURE__ */ new Set()
9651
+ });
9652
+ box.current.undo = undo;
9653
+ const source = (0, import_react52.useMemo)(
9654
+ () => ({
9655
+ face: () => box.current.face,
9656
+ subscribe: (listener) => {
9657
+ box.current.listeners.add(listener);
9658
+ return () => {
9659
+ box.current.listeners.delete(listener);
9660
+ };
9661
+ },
9662
+ undo: () => void box.current.undo.undo(),
9663
+ redo: () => void box.current.undo.redo()
9664
+ }),
9665
+ []
9666
+ );
9667
+ const { back, forward } = undo.depth;
9668
+ const busy = undo.busy;
9669
+ (0, import_react52.useLayoutEffect)(() => {
9670
+ const held = box.current.face;
9671
+ if (held.back === back && held.forward === forward && held.busy === busy) return;
9672
+ box.current.face = { back, forward, busy };
9673
+ for (const listener of box.current.listeners) listener();
9674
+ }, [back, forward, busy]);
9675
+ return source;
9676
+ }
9677
+ function UndoRedoLive({ source }) {
9678
+ const face = (0, import_react52.useSyncExternalStore)(source.subscribe, source.face, source.face);
9679
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(UndoRedoPair, { face, onUndo: source.undo, onRedo: source.redo });
9680
+ }
9681
+ function UndoRedoPair({ face, onUndo, onRedo }) {
9682
+ const undo = { depth: { back: face.back, forward: face.forward }, busy: face.busy };
9627
9683
  if (undo.depth.back === 0 && undo.depth.forward === 0) return null;
9628
9684
  return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
9629
9685
  /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
@@ -9635,7 +9691,7 @@ function UndoRedoButtons({ undo }) {
9635
9691
  title: undo.depth.back > 0 ? `Undo (Cmd-Z) \xB7 ${undo.depth.back} ${undo.depth.back === 1 ? "step" : "steps"} back` : "Nothing to undo",
9636
9692
  "aria-label": `Undo, ${undo.depth.back} ${undo.depth.back === 1 ? "step" : "steps"}`,
9637
9693
  disabled: undo.busy || undo.depth.back === 0,
9638
- onClick: () => void undo.undo(),
9694
+ onClick: onUndo,
9639
9695
  children: [
9640
9696
  "Undo ",
9641
9697
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "ml-1 tabular-nums text-muted-foreground", "data-records-undo-count": "", children: undo.depth.back })
@@ -9651,7 +9707,7 @@ function UndoRedoButtons({ undo }) {
9651
9707
  title: undo.depth.forward > 0 ? `Redo (Shift-Cmd-Z) \xB7 ${undo.depth.forward} ${undo.depth.forward === 1 ? "step" : "steps"} forward` : "Nothing to redo",
9652
9708
  "aria-label": `Redo, ${undo.depth.forward} ${undo.depth.forward === 1 ? "step" : "steps"}`,
9653
9709
  disabled: undo.busy || undo.depth.forward === 0,
9654
- onClick: () => void undo.redo(),
9710
+ onClick: onRedo,
9655
9711
  children: [
9656
9712
  "Redo ",
9657
9713
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "ml-1 tabular-nums text-muted-foreground", "data-records-redo-count": "", children: undo.depth.forward })
@@ -10836,6 +10892,8 @@ function HandOrderDialog({
10836
10892
  var import_jsx_runtime42 = require("react/jsx-runtime");
10837
10893
  var NO_COLUMNS_YET = "This table has no columns yet";
10838
10894
  var NO_COLUMNS_WHY = "A column is what a record holds \u2014 a name, a date, an amount, a person. Add the first one and the grid appears with it; everything else on this page works the moment it exists.";
10895
+ var NO_SUMMARIES = Object.freeze({});
10896
+ var rowIdOf = (row) => row.id;
10839
10897
  function Grid({
10840
10898
  tableId,
10841
10899
  pageSize = 50,
@@ -10985,7 +11043,17 @@ function Grid({
10985
11043
  onEnded: (exit, from, how) => sheetController.current?.endEdit(exit, { rowId: from.rowId, columnId: from.key }, how)
10986
11044
  } : {}
10987
11045
  });
11046
+ const editingNow = (0, import_react60.useRef)(editing);
11047
+ editingNow.current = editing;
11048
+ const editingHandle = (0, import_react60.useMemo)(
11049
+ () => new Proxy({}, {
11050
+ get: (_target, key) => editingNow.current[key]
11051
+ }),
11052
+ []
11053
+ );
11054
+ const rowVersion = (0, import_react60.useCallback)((row) => editingNow.current.rowSignature(row.id), []);
10988
11055
  const undo = useGridUndo({ tableId, reload: records.reload, forget: editing.forget });
11056
+ const undoSource = useUndoSource(undo);
10989
11057
  pushWritten.current = (write) => {
10990
11058
  const field2 = (fields.data ?? []).find((f) => f.key === write.key);
10991
11059
  const name = changedRecordName(table.data, rows.find((r) => r.id === write.rowId)?.document ?? {}, write.key, write.after);
@@ -11055,7 +11123,7 @@ function Grid({
11055
11123
  const [selectedIds, setSelectedIds] = (0, import_react60.useState)([]);
11056
11124
  const offerSelection = canWrite && (merged || tableActions.runnable.length > 0);
11057
11125
  const [reordering, setReordering] = (0, import_react60.useState)(false);
11058
- const chosenSummaries = localSummaries ?? presentation.summaries ?? {};
11126
+ const chosenSummaries = localSummaries ?? presentation.summaries ?? NO_SUMMARIES;
11059
11127
  const summaries = useColumnSummaries({ tableId, chosen: chosenSummaries, filter: question.filter, version: records.data });
11060
11128
  const [duplicated, setDuplicated] = (0, import_react60.useState)(null);
11061
11129
  const duplicate = (0, import_react60.useCallback)(
@@ -11504,7 +11572,7 @@ function Grid({
11504
11572
  () => ordered.map(
11505
11573
  (field2) => columnForField(
11506
11574
  field2,
11507
- canWrite ? editing : null,
11575
+ canWrite ? editingHandle : null,
11508
11576
  mayWriteRow,
11509
11577
  whyNotRow,
11510
11578
  {
@@ -11523,7 +11591,7 @@ function Grid({
11523
11591
  [
11524
11592
  ordered,
11525
11593
  canWrite,
11526
- editing,
11594
+ editingHandle,
11527
11595
  mayWriteRow,
11528
11596
  whyNotRow,
11529
11597
  presentation.formats,
@@ -11535,9 +11603,39 @@ function Grid({
11535
11603
  pinCell
11536
11604
  ]
11537
11605
  );
11538
- const columns = (0, import_react60.useMemo)(
11539
- () => {
11540
- const drawn = withHeaderMenus(withAddRowCells(withSummaryCells(
11606
+ const footerNow = (0, import_react60.useRef)({
11607
+ summaryCell: () => null,
11608
+ addLine: () => null,
11609
+ choose: () => void 0,
11610
+ add: () => void 0
11611
+ });
11612
+ footerNow.current = {
11613
+ // The footer is memoised on what it shows (`footerRows` below), so its buttons act through here.
11614
+ choose: chooseSummary,
11615
+ add: () => void addRowHere(),
11616
+ summaryCell: (columnId) => {
11617
+ const field2 = ordered.find((f) => f.key === columnId);
11618
+ if (!field2) return null;
11619
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
11620
+ SummaryCell,
11621
+ {
11622
+ field: field2,
11623
+ firstColumn: ordered[0]?.key === columnId,
11624
+ chosen: chosenSummaries[columnId],
11625
+ value: summaries.values[columnId],
11626
+ rowCount: summaries.rowCount,
11627
+ summaries,
11628
+ onChoose: (op2) => footerNow.current.choose(columnId, op2)
11629
+ }
11630
+ );
11631
+ },
11632
+ addLine: () => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(AddRowLine, { onAdd: () => footerNow.current.add(), busy: added.busy })
11633
+ };
11634
+ const addColumnNow = (0, import_react60.useRef)(controls.addColumn);
11635
+ addColumnNow.current = controls.addColumn;
11636
+ const bodyColumns = (0, import_react60.useMemo)(
11637
+ () => withAddRowCells(
11638
+ withSummaryCells(
11541
11639
  // THE ONE WAY A ROW OPENS ITS RECORD (VERIFIER-17): an Open control on the first cell,
11542
11640
  // and Space on any cell. Absent when the host cannot open a record.
11543
11641
  withOpenRecord(
@@ -11547,27 +11645,123 @@ function Grid({
11547
11645
  // The merged grid's handle sits at the START of the row, where Airtable's expand sits.
11548
11646
  merged ? "start" : "end"
11549
11647
  ),
11550
- (columnId) => {
11551
- const field2 = ordered.find((f) => f.key === columnId);
11552
- if (!field2) return null;
11553
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
11554
- SummaryCell,
11555
- {
11556
- field: field2,
11557
- firstColumn: ordered[0]?.key === columnId,
11558
- chosen: chosenSummaries[columnId],
11559
- value: summaries.values[columnId],
11560
- rowCount: summaries.rowCount,
11561
- summaries,
11562
- onChoose: (op2) => chooseSummary(columnId, op2)
11563
- }
11564
- );
11648
+ (columnId) => footerNow.current.summaryCell(columnId)
11649
+ ),
11650
+ mayAddRow ? () => footerNow.current.addLine() : null
11651
+ ),
11652
+ [fieldColumns, onOpenRecord, table.data, merged, mayAddRow]
11653
+ );
11654
+ const isRowSelectable = (0, import_react60.useCallback)((row) => row.id !== SUMMARY_ROW.id && mayWriteRow(row.id), [mayWriteRow]);
11655
+ const copyConfig = (0, import_react60.useMemo)(
11656
+ () => table.data ? gridCopyConfig({
11657
+ tableId,
11658
+ tableName: tableName(table.data),
11659
+ fields: ordered,
11660
+ rowLabel: (row) => recordNameIn(table.data, row.document ?? {})
11661
+ }) : null,
11662
+ [tableId, table.data, ordered]
11663
+ );
11664
+ const tableStyleProp = (0, import_react60.useMemo)(
11665
+ () => (0, import_data_table9.tableStyleIsEmpty)(paint.style) ? null : {
11666
+ style: paint.style,
11667
+ // A choice is read as its OPTION (`paintByOption`): the rule, the value and the
11668
+ // colour a column keeps for it all name the same option, however it is stored.
11669
+ documentOf: (row) => paint.documentOf(row.document ?? {}),
11670
+ // A value its column keeps a colour for wears THAT colour (`choiceLooks.ts`);
11671
+ // any other value still paints from its word.
11672
+ choiceColorFor: paint.choiceColorFor
11673
+ },
11674
+ [paint]
11675
+ );
11676
+ const rowActionsNow = (0, import_react60.useRef)({ runner, handToAgent, host, table: table.data, duplicate });
11677
+ rowActionsNow.current = { runner, handToAgent, host, table: table.data, duplicate };
11678
+ const offersRowActions = canWrite || Boolean(host.talkToRecord) || Boolean(host.runAgentAction && tableActions.agent.length > 0);
11679
+ const hasTalk = Boolean(host.talkToRecord);
11680
+ const hasAgent = Boolean(host.runAgentAction);
11681
+ const rowActions = (0, import_react60.useCallback)(
11682
+ (row) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
11683
+ canWrite && mayWriteRow(row.id) ? tableActions.runnable.map((action) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
11684
+ ActionButton,
11685
+ {
11686
+ action,
11687
+ busy: runner.busy === action.id,
11688
+ onPress: () => rowActionsNow.current.runner.press(action, [row.id])
11689
+ },
11690
+ action.id
11691
+ )) : null,
11692
+ host.runAgentAction ? tableActions.agent.map((action) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
11693
+ ActionButton,
11694
+ {
11695
+ action,
11696
+ busy: false,
11697
+ onPress: () => rowActionsNow.current.handToAgent(action, row)
11698
+ },
11699
+ action.id
11700
+ )) : null,
11701
+ host.talkToRecord ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
11702
+ import_design_system35.Button,
11703
+ {
11704
+ size: "sm",
11705
+ variant: "ghost",
11706
+ onClick: () => rowActionsNow.current.host.talkToRecord?.({
11707
+ tableId,
11708
+ recordId: row.id,
11709
+ // ONE NAMING CALL (`names.ts`), not this file's own join
11710
+ // of a Table's title column to a document.
11711
+ title: recordNameIn(
11712
+ rowActionsNow.current.table,
11713
+ row.document ?? {}
11714
+ )
11715
+ }),
11716
+ children: "Talk"
11565
11717
  }
11566
- ), mayAddRow ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(AddRowLine, { onAdd: () => void addRowHere(), busy: added.busy }) : null), controls.headerMenuFor, controls.filterFor);
11567
- return merged && rights.structure && editable ? [...drawn, addColumnColumn(controls.addColumn)] : drawn;
11718
+ ) : null,
11719
+ Boolean(editable) && rights.write ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
11720
+ import_design_system35.Button,
11721
+ {
11722
+ size: "sm",
11723
+ variant: "ghost",
11724
+ "data-records-duplicate": row.id,
11725
+ title: "Duplicate this record",
11726
+ onClick: (event) => {
11727
+ event.stopPropagation();
11728
+ void rowActionsNow.current.duplicate(row);
11729
+ },
11730
+ children: "Duplicate"
11731
+ }
11732
+ ) : null,
11733
+ canWrite && mayWriteRow(row.id) ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
11734
+ import_design_system35.Button,
11735
+ {
11736
+ size: "sm",
11737
+ variant: "ghost",
11738
+ title: "Delete this record",
11739
+ onClick: () => setDeleting(row),
11740
+ children: "Delete"
11741
+ }
11742
+ ) : null
11743
+ ] }),
11744
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- `hasTalk`/`hasAgent` stand for the host ports the JSX reads; handlers run through `rowActionsNow`.
11745
+ [canWrite, mayWriteRow, tableActions.runnable, tableActions.agent, runner.busy, hasTalk, hasAgent, editable, rights.write]
11746
+ );
11747
+ const headerHeld = (0, import_react60.useRef)(/* @__PURE__ */ new Map());
11748
+ const menuLatest = (0, import_react60.useRef)(/* @__PURE__ */ new Map());
11749
+ const addColumnDef = (0, import_react60.useMemo)(() => addColumnColumn(() => addColumnNow.current()), []);
11750
+ const drawnColumns = (0, import_react60.useMemo)(
11751
+ () => {
11752
+ const filterFor = controls.filterFor;
11753
+ const drawn = withHeaderMenus(
11754
+ bodyColumns,
11755
+ (key) => holdHeaderMenu(headerHeld.current, menuLatest.current, key, controls.headerMenuFor(key)),
11756
+ (key) => holdByContent(headerHeld.current, `filter:${key}`, filterFor(key))
11757
+ );
11758
+ return merged && rights.structure && editable ? [...drawn, addColumnDef] : drawn;
11568
11759
  },
11569
- [fieldColumns, onOpenRecord, table.data, ordered, chosenSummaries, summaries, chooseSummary, controls.headerMenuFor, controls.filterFor, mayAddRow, addRowHere, added.busy, merged, rights.structure, editable, controls.addColumn]
11760
+ [bodyColumns, controls.headerMenuFor, controls.filterFor, merged, rights.structure, editable, addColumnDef]
11570
11761
  );
11762
+ const columnsHeld = (0, import_react60.useRef)(null);
11763
+ const columns = columnsHeld.current !== null && sameColumns(columnsHeld.current, drawnColumns) ? columnsHeld.current : drawnColumns;
11764
+ columnsHeld.current = columns;
11571
11765
  const [localColumns, setLocalColumns] = (0, import_react60.useState)(
11572
11766
  null
11573
11767
  );
@@ -11640,6 +11834,274 @@ function Grid({
11640
11834
  } : {}
11641
11835
  };
11642
11836
  }, [presentation, onPresentationChange, writeLook, merged, phone]);
11837
+ const queryTotal = records.data?.total ?? rows.length;
11838
+ const tableQuery = (0, import_react60.useMemo)(
11839
+ () => merged ? {
11840
+ // THE STORE ANSWERS THE QUERY (tranche 6k): `data` is already the page.
11841
+ mode: "controlled",
11842
+ state: query,
11843
+ totalItems: queryTotal,
11844
+ onStateChange: setQuery,
11845
+ sourceProcessing: { search: "source", columnFilters: "source", sort: "source" }
11846
+ } : { mode: "controlled-local", state: query, onStateChange: setQuery },
11847
+ [merged, query, queryTotal]
11848
+ );
11849
+ const showAddLine = mayAddRow && rows.length > 0;
11850
+ const showSummaryLine = rows.length > 0 && (!merged || Object.keys(chosenSummaries).length > 0);
11851
+ const footerFace = JSON.stringify([
11852
+ showAddLine ? added.busy : null,
11853
+ showSummaryLine ? [chosenSummaries, summaries.values, summaries.rowCount, summaries.loading] : null
11854
+ ]);
11855
+ const footerRows = (0, import_react60.useMemo)(
11856
+ () => [...showAddLine ? [ADD_ROW] : [], ...showSummaryLine ? [SUMMARY_ROW] : []],
11857
+ // `footerFace`, `ordered` and the summaries' operations are what the footer's cells draw.
11858
+ // eslint-disable-next-line react-hooks/exhaustive-deps
11859
+ [showAddLine, showSummaryLine, footerFace, ordered, summaries.operations, summaries.askOperations]
11860
+ );
11861
+ const coverageTotal = typeof records.data?.total === "number" ? records.data.total : null;
11862
+ const tableCoverage = (0, import_react60.useMemo)(
11863
+ () => ({
11864
+ answeredBy: merged ? "source" : "client",
11865
+ ...merged && coverageTotal !== null ? { matched: coverageTotal } : {},
11866
+ noun: "record",
11867
+ ...coverageTotal !== null ? { total: coverageTotal } : {}
11868
+ }),
11869
+ [merged, coverageTotal]
11870
+ );
11871
+ const sheetNow = (0, import_react60.useRef)({ merged, writeRange, editing, fields: fields.data ?? [], mayWriteRow, notify: host.notify });
11872
+ sheetNow.current = { merged, writeRange, editing, fields: fields.data ?? [], mayWriteRow, notify: host.notify };
11873
+ const hasNotify = Boolean(host.notify);
11874
+ const readOnlyReason = !editable || !rights.known || rights.write ? "Editing is off here, so this table can be copied but not changed." : rights.why("write");
11875
+ const spreadsheetConfig = (0, import_react60.useMemo)(
11876
+ () => canWrite && editable ? {
11877
+ enabled: true,
11878
+ writableColumnIds: pasteableKeys,
11879
+ onPatch: (patches, intent) => {
11880
+ const now = sheetNow.current;
11881
+ if (now.merged) {
11882
+ void now.writeRange(patches, intent);
11883
+ return;
11884
+ }
11885
+ for (const patch of patches) {
11886
+ now.editing.setCell(patch.rowId, patch.columnId, patch.value);
11887
+ }
11888
+ },
11889
+ ...merged ? { undoDepth: 0, onSelectionChange: followSelection, controllerRef: sheetController } : {},
11890
+ columnRefusal: (columnId) => {
11891
+ const field2 = sheetNow.current.fields.find((f) => f.key === columnId);
11892
+ if (!field2) return `This table has no column called ${columnId}.`;
11893
+ return `${fieldName(field2)} cannot take a pasted value \u2014 ${fieldTypeLabel(field2)} is worked out or chosen, not typed.`;
11894
+ },
11895
+ validateCell: ({ columnId, value }) => {
11896
+ const field2 = sheetNow.current.fields.find((f) => f.key === columnId);
11897
+ if (!field2) return `This table has no column called ${columnId}.`;
11898
+ const judged = valueFromPastedText(field2, value === null ? "" : String(value));
11899
+ return "refusal" in judged ? judged.refusal : void 0;
11900
+ },
11901
+ onPastePlan: receivePastePlan,
11902
+ /*
11903
+ * TYPE-TO-EDIT (design-system 0.43 `onEditRequest`): the cell a person is on
11904
+ * opens ITS OWN editor — the Field's — holding what they typed (a letter, an
11905
+ * IME composition, an Option/AltGr character, dictation). A worked-out column
11906
+ * or a row this person may not write answers false, so nothing is swallowed.
11907
+ */
11908
+ onEditRequest: (address, seed) => {
11909
+ const now = sheetNow.current;
11910
+ const field2 = now.fields.find((f) => f.key === address.columnId);
11911
+ if (!field2 || !fieldIsEditable(field2) || !now.mayWriteRow(address.rowId)) return false;
11912
+ if (seed !== null && seed !== "" && typingOpens(field2) === "none") return false;
11913
+ now.editing.begin({ rowId: address.rowId, key: field2.key }, seed);
11914
+ return true;
11915
+ },
11916
+ ...hasNotify ? { notify: (message) => sheetNow.current.notify?.success(message) } : {}
11917
+ } : {
11918
+ /*
11919
+ * RANGE SELECTION AND COPY FOR A READER (merge step 3, 0.42 `readOnly`). A person
11920
+ * who may not change this table may still drag out a range, select all and press
11921
+ * Cmd-C for a real Excel/Sheets block — the UX walk graded range selection BROKEN
11922
+ * on a table whose writes were refused. Every write gesture (cut, paste, clear,
11923
+ * fill, undo) says why it cannot, in the store's own words.
11924
+ */
11925
+ enabled: true,
11926
+ readOnly: true,
11927
+ ...merged ? { onSelectionChange: followSelection, controllerRef: sheetController } : {},
11928
+ readOnlyReason
11929
+ },
11930
+ [canWrite, editable, pasteableKeys, merged, followSelection, receivePastePlan, hasNotify, readOnlyReason]
11931
+ );
11932
+ const toolbarFields = fields.data;
11933
+ const hasSeveralRows = rows.length > 1;
11934
+ const editingBusy = editing.busy;
11935
+ const addRecord = editing.addRecord;
11936
+ const phoneSheetTools = merged && phone ? controls.sheetTools : null;
11937
+ const tableToolbar = (0, import_react60.useMemo)(
11938
+ () => ({
11939
+ search: true,
11940
+ searchPlaceholder: "Search records",
11941
+ /*
11942
+ * A PHONE (tranche 6o): search and the one Tools button sit side by side at the START of
11943
+ * the row, so Tools is never scrolled out of reach; the search is the same query.
11944
+ */
11945
+ ...merged && phone ? {
11946
+ customSearch: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "flex min-w-0 flex-1 items-center gap-1.5", "data-records-grid-phone-search": "", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
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-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",
11958
+ style: { fontSize: "16px" }
11959
+ }
11960
+ ) })
11961
+ } : {},
11962
+ /* ONE ROW (lane TABLE-PAGE-CHROME): on a table page the grid's search, columns and
11963
+ sort are drawn INTO the page's toolbar row, never a second bar under it. */
11964
+ ...toolbarSlot !== void 0 ? { portalInto: toolbarSlot, singleRow: true } : {},
11965
+ // SCR-2 / SCR-4: PRESENT for an admin, ABSENT for everyone else.
11966
+ // There is no third state: this package ships no disabled variant of
11967
+ // these controls, so a member cannot be shown a dead one by mistake.
11968
+ /*
11969
+ * WHERE THE GRID'S CONTROLS GO (lane C, review 2026-09-26 D6). Inline when the row has
11970
+ * room; behind the row's "…" once the page row is folded that far (Undo / Redo and New
11971
+ * record stay beside it — the primary action is never folded); on a phone EVERY control
11972
+ * — the table's own (clear, copy/export, group, columns), these, the search, the sort and
11973
+ * the page's filter, layout and share — in ONE "Tools" sheet. Never dropped.
11974
+ */
11975
+ actions: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
11976
+ manual ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
11977
+ SortStateControl,
11978
+ {
11979
+ ...handOrderSortState({
11980
+ sortName: columnSort ? columnLabel(toolbarFields ?? [], columnSort.id) : null,
11981
+ direction: columnSort?.direction ?? null,
11982
+ onBack: () => setQuery((held) => ({ ...held, sort: null, page: 1 })),
11983
+ ...viewOrder?.onUseSort && columnSort ? {
11984
+ onUseSort: () => {
11985
+ const sort = columnSort;
11986
+ const chosen = { field: sort.id, direction: sort.direction };
11987
+ setHeldOrder({ order: "sorted", sorts: [chosen] });
11988
+ viewOrder.onUseSort?.(chosen);
11989
+ host.notify?.success(
11990
+ `Sorted by ${columnLabel(toolbarFields ?? [], sort.id)} ${sortArrow(sort.direction)}. ${MANUAL_SORT.replacedToast}`
11991
+ );
11992
+ }
11993
+ } : {}
11994
+ })
11995
+ }
11996
+ ) : null,
11997
+ viewId && viewOrder?.onPlaced && hasSeveralRows ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_design_system35.Button, { size: "sm", variant: "ghost", onClick: () => setReordering(true), "data-records-reorder-open": "", children: "Reorder" }) : null,
11998
+ !merged && Boolean(editable) && rights.write ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_design_system35.Button, { size: "sm", disabled: editingBusy, onClick: () => void addRecord(), children: editingBusy ? "Adding\u2026" : "New record" }) : null,
11999
+ rights.structure && onAddField ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(AddFieldButton, { onClick: onAddField }) : null,
12000
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12001
+ GridLayoutMenu,
12002
+ {
12003
+ look,
12004
+ firstColumn: sortedKeys[0] ? {
12005
+ key: sortedKeys[0],
12006
+ name: (() => {
12007
+ const first = (toolbarFields ?? []).find((f) => f.key === sortedKeys[0]);
12008
+ return first ? fieldName(first) : humanize(sortedKeys[0]);
12009
+ })()
12010
+ } : null,
12011
+ onChange: changeLayout,
12012
+ kept: Boolean(onPresentationChange)
12013
+ }
12014
+ )
12015
+ ] }),
12016
+ ...merged ? {
12017
+ overflow: {
12018
+ mode: phone ? "sheet" : toolbarFold2 >= 3 ? "menu" : "inline",
12019
+ pinned: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
12020
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(UndoRedoLive, { source: undoSource }),
12021
+ Boolean(editable) && rights.write ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_design_system35.Button, { size: "sm", className: "shrink-0", disabled: added.busy, onClick: () => addRowRef.current(), children: added.busy ? "Adding\u2026" : "New record" }) : null
12022
+ ] }),
12023
+ ...phone ? {
12024
+ sheetTop: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex flex-col gap-1.5 pb-1", "data-records-grid-sheet-top": "", "data-matrx-table-tools-keep-open": "", children: [
12025
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12026
+ "input",
12027
+ {
12028
+ type: "search",
12029
+ value: query.search,
12030
+ placeholder: "Search",
12031
+ "aria-label": "Search records",
12032
+ onChange: (event) => {
12033
+ const search = event.target.value;
12034
+ setQuery((held) => ({ ...held, search, page: 1 }));
12035
+ },
12036
+ className: "h-10 w-full min-w-0 rounded-md border border-input bg-background px-2 text-foreground placeholder:text-muted-foreground",
12037
+ style: { fontSize: "16px" }
12038
+ }
12039
+ ),
12040
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("label", { className: "flex items-center gap-2 text-xs text-muted-foreground", "data-records-grid-sheet-sort": "", children: [
12041
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "shrink-0", children: "Sort by" }),
12042
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
12043
+ "select",
12044
+ {
12045
+ "aria-label": "Sort by",
12046
+ value: query.sort ? `${query.sort.id}:${query.sort.direction}` : "",
12047
+ onChange: (event) => {
12048
+ const [id, direction] = event.target.value.split(":");
12049
+ setQuery((held) => ({
12050
+ ...held,
12051
+ page: 1,
12052
+ sort: id ? { id, direction: direction === "desc" ? "desc" : "asc" } : null
12053
+ }));
12054
+ },
12055
+ className: "h-10 min-w-0 flex-1 rounded-md border border-input bg-background px-2 text-foreground",
12056
+ style: { fontSize: "16px" },
12057
+ children: [
12058
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("option", { value: "", children: "No sort" }),
12059
+ ordered.flatMap((field2) => [
12060
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("option", { value: `${field2.key}:asc`, children: `${fieldName(field2)} \u2191` }, `${field2.key}:asc`),
12061
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("option", { value: `${field2.key}:desc`, children: `${fieldName(field2)} \u2193` }, `${field2.key}:desc`)
12062
+ ])
12063
+ ]
12064
+ }
12065
+ )
12066
+ ] }),
12067
+ phoneSheetTools
12068
+ ] }),
12069
+ ...sheetExtras ? { sheetExtras } : {}
12070
+ } : {}
12071
+ }
12072
+ } : {}
12073
+ }),
12074
+ [
12075
+ merged,
12076
+ phone,
12077
+ query.search,
12078
+ query.sort,
12079
+ toolbarSlot,
12080
+ manual,
12081
+ columnSort,
12082
+ toolbarFields,
12083
+ viewOrder,
12084
+ host,
12085
+ viewId,
12086
+ hasSeveralRows,
12087
+ editable,
12088
+ rights.write,
12089
+ rights.structure,
12090
+ editingBusy,
12091
+ addRecord,
12092
+ onAddField,
12093
+ look,
12094
+ sortedKeys,
12095
+ changeLayout,
12096
+ onPresentationChange,
12097
+ toolbarFold2,
12098
+ undoSource,
12099
+ added.busy,
12100
+ ordered,
12101
+ phoneSheetTools,
12102
+ sheetExtras
12103
+ ]
12104
+ );
11643
12105
  const labelOfField = (0, import_react60.useCallback)(
11644
12106
  (key) => {
11645
12107
  const held = (fields.data ?? []).find((f) => f.key === key);
@@ -11800,46 +12262,27 @@ function Grid({
11800
12262
  ...fills ? { className: "min-h-0 flex-1" } : {},
11801
12263
  data: rows,
11802
12264
  columns,
11803
- getRowId: (row) => row.id,
12265
+ getRowId: rowIdOf,
12266
+ rowVersion,
11804
12267
  isLoading: nothingToShowYet,
11805
12268
  isFetching: refreshing,
11806
12269
  density: host.density ?? "condensed",
11807
12270
  viewTabs: false,
11808
12271
  stickyHeader: true,
11809
12272
  pageSize,
11810
- query: merged ? {
11811
- // THE STORE ANSWERS THE QUERY (tranche 6k): `data` is already the page.
11812
- mode: "controlled",
11813
- state: query,
11814
- totalItems: records.data?.total ?? rows.length,
11815
- onStateChange: setQuery,
11816
- sourceProcessing: { search: "source", columnFilters: "source", sort: "source" }
11817
- } : { mode: "controlled-local", state: query, onStateChange: setQuery },
12273
+ query: tableQuery,
11818
12274
  tableClassName: (0, import_design_system35.cn)(
11819
12275
  !look.wrap && "[&_tbody_td]:max-w-[28rem] [&_tbody_td]:overflow-hidden [&_tbody_td]:text-ellipsis [&_tbody_td]:whitespace-nowrap! [&_tbody_td_*]:whitespace-nowrap!",
11820
12276
  look.fit === "scroll" && "sm:[&>table]:w-max! sm:[&>table]:max-w-none! sm:[&>table]:min-w-full!"
11821
12277
  ),
11822
- ...(() => {
11823
- const footerRows = [
11824
- ...mayAddRow && rows.length > 0 ? [ADD_ROW] : [],
11825
- ...rows.length > 0 && (!merged || Object.keys(chosenSummaries).length > 0) ? [SUMMARY_ROW] : []
11826
- ];
11827
- return footerRows.length > 0 ? { footerRows } : {};
11828
- })(),
11829
- ...table.data ? {
11830
- copy: gridCopyConfig({
11831
- tableId,
11832
- tableName: tableName(table.data),
11833
- fields: ordered,
11834
- rowLabel: (row) => recordNameIn(table.data, row.document ?? {})
11835
- })
11836
- } : {},
12278
+ footerRows,
12279
+ ...copyConfig ? { copy: copyConfig } : {},
11837
12280
  ...offerSelection ? {
11838
12281
  selection: {
11839
12282
  selectedIds,
11840
12283
  onSelectedIdsChange: setSelectedIds,
11841
12284
  noun,
11842
- isRowSelectable: (row) => row.id !== SUMMARY_ROW.id && mayWriteRow(row.id),
12285
+ isRowSelectable,
11843
12286
  actions: (_selected, ids) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
11844
12287
  merged ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
11845
12288
  BulkActions,
@@ -11866,154 +12309,18 @@ function Grid({
11866
12309
  ] })
11867
12310
  }
11868
12311
  } : {},
11869
- detail: { enabled: false },
11870
- window: { enabled: false },
12312
+ detail: DETAIL_OFF,
12313
+ window: WINDOW_OFF,
11871
12314
  ...controls.contextMenu ? { contextMenu: controls.contextMenu } : {},
11872
- ...canWrite && editable ? {
11873
- spreadsheet: {
11874
- enabled: true,
11875
- writableColumnIds: pasteableKeys,
11876
- onPatch: (patches, intent) => {
11877
- if (merged) {
11878
- void writeRange(patches, intent);
11879
- return;
11880
- }
11881
- for (const patch of patches) {
11882
- editing.setCell(patch.rowId, patch.columnId, patch.value);
11883
- }
11884
- },
11885
- ...merged ? { undoDepth: 0, onSelectionChange: followSelection, controllerRef: sheetController } : {},
11886
- columnRefusal: (columnId) => {
11887
- const field2 = (fields.data ?? []).find((f) => f.key === columnId);
11888
- if (!field2) return `This table has no column called ${columnId}.`;
11889
- return `${fieldName(field2)} cannot take a pasted value \u2014 ${fieldTypeLabel(field2)} is worked out or chosen, not typed.`;
11890
- },
11891
- validateCell: ({ columnId, value }) => {
11892
- const field2 = (fields.data ?? []).find((f) => f.key === columnId);
11893
- if (!field2) return `This table has no column called ${columnId}.`;
11894
- const judged = valueFromPastedText(field2, value === null ? "" : String(value));
11895
- return "refusal" in judged ? judged.refusal : void 0;
11896
- },
11897
- onPastePlan: receivePastePlan,
11898
- /*
11899
- * TYPE-TO-EDIT (design-system 0.43 `onEditRequest`): the cell a person is on
11900
- * opens ITS OWN editor — the Field's — holding what they typed (a letter, an
11901
- * IME composition, an Option/AltGr character, dictation). A worked-out column
11902
- * or a row this person may not write answers false, so nothing is swallowed.
11903
- */
11904
- onEditRequest: (address, seed) => {
11905
- const field2 = (fields.data ?? []).find((f) => f.key === address.columnId);
11906
- if (!field2 || !fieldIsEditable(field2) || !mayWriteRow(address.rowId)) return false;
11907
- if (seed !== null && seed !== "" && typingOpens(field2) === "none") return false;
11908
- editing.begin({ rowId: address.rowId, key: field2.key }, seed);
11909
- return true;
11910
- },
11911
- ...host.notify ? { notify: (message) => host.notify?.success(message) } : {}
11912
- }
11913
- } : {
11914
- /*
11915
- * RANGE SELECTION AND COPY FOR A READER (merge step 3, 0.42 `readOnly`). A person
11916
- * who may not change this table may still drag out a range, select all and press
11917
- * Cmd-C for a real Excel/Sheets block — the UX walk graded range selection BROKEN
11918
- * on a table whose writes were refused. Every write gesture (cut, paste, clear,
11919
- * fill, undo) says why it cannot, in the store's own words.
11920
- */
11921
- spreadsheet: {
11922
- enabled: true,
11923
- readOnly: true,
11924
- ...merged ? { onSelectionChange: followSelection, controllerRef: sheetController } : {},
11925
- readOnlyReason: !editable || !rights.known || rights.write ? "Editing is off here, so this table can be copied but not changed." : rights.why("write")
11926
- }
11927
- },
12315
+ spreadsheet: spreadsheetConfig,
11928
12316
  ...groupingConfig ? { grouping: groupingConfig } : {},
11929
12317
  columnState,
11930
12318
  ...look.rowHeight ? { rowHeight: look.rowHeight } : {},
11931
- virtualize: fills ? { threshold: 40 } : true,
11932
- coverage: {
11933
- answeredBy: merged ? "source" : "client",
11934
- ...merged && typeof records.data?.total === "number" ? { matched: records.data.total } : {},
11935
- noun: "record",
11936
- ...typeof records.data?.total === "number" ? { total: records.data.total } : {}
11937
- },
11938
- ...!(0, import_data_table9.tableStyleIsEmpty)(paint.style) ? {
11939
- tableStyle: {
11940
- style: paint.style,
11941
- // A choice is read as its OPTION (`paintByOption`): the rule, the value and the
11942
- // colour a column keeps for it all name the same option, however it is stored.
11943
- documentOf: (row) => paint.documentOf(row.document ?? {}),
11944
- // A value its column keeps a colour for wears THAT colour (`choiceLooks.ts`);
11945
- // any other value still paints from its word.
11946
- choiceColorFor: paint.choiceColorFor
11947
- }
11948
- } : {},
12319
+ virtualize: fills ? VIRTUALIZE_FILLED : true,
12320
+ coverage: tableCoverage,
12321
+ ...tableStyleProp ? { tableStyle: tableStyleProp } : {},
11949
12322
  ...onOpenRecord ? { onRowOpen: (row) => onOpenRecord(row.id) } : {},
11950
- ...canWrite || host.talkToRecord || host.runAgentAction && tableActions.agent.length > 0 ? {
11951
- // ABSENT, NEVER DEAD: a row this person may not delete carries no
11952
- // Delete at all, rather than one that refuses when pressed — and a
11953
- // deployment with no chat surface bound carries no Talk.
11954
- rowActions: (row) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
11955
- canWrite && mayWriteRow(row.id) ? tableActions.runnable.map((action) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
11956
- ActionButton,
11957
- {
11958
- action,
11959
- busy: runner.busy === action.id,
11960
- onPress: () => runner.press(action, [row.id])
11961
- },
11962
- action.id
11963
- )) : null,
11964
- host.runAgentAction ? tableActions.agent.map((action) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
11965
- ActionButton,
11966
- {
11967
- action,
11968
- busy: false,
11969
- onPress: () => handToAgent(action, row)
11970
- },
11971
- action.id
11972
- )) : null,
11973
- host.talkToRecord ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
11974
- import_design_system35.Button,
11975
- {
11976
- size: "sm",
11977
- variant: "ghost",
11978
- onClick: () => host.talkToRecord?.({
11979
- tableId,
11980
- recordId: row.id,
11981
- // ONE NAMING CALL (`names.ts`), not this file's own join
11982
- // of a Table's title column to a document.
11983
- title: recordNameIn(
11984
- table.data,
11985
- row.document ?? {}
11986
- )
11987
- }),
11988
- children: "Talk"
11989
- }
11990
- ) : null,
11991
- Boolean(editable) && rights.write ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
11992
- import_design_system35.Button,
11993
- {
11994
- size: "sm",
11995
- variant: "ghost",
11996
- "data-records-duplicate": row.id,
11997
- title: "Duplicate this record",
11998
- onClick: (event) => {
11999
- event.stopPropagation();
12000
- void duplicate(row);
12001
- },
12002
- children: "Duplicate"
12003
- }
12004
- ) : null,
12005
- canWrite && mayWriteRow(row.id) ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12006
- import_design_system35.Button,
12007
- {
12008
- size: "sm",
12009
- variant: "ghost",
12010
- title: "Delete this record",
12011
- onClick: () => setDeleting(row),
12012
- children: "Delete"
12013
- }
12014
- ) : null
12015
- ] })
12016
- } : {},
12323
+ ...offersRowActions ? { rowActions } : {},
12017
12324
  emptyState: emptyWords({
12018
12325
  error: records.error,
12019
12326
  asked: questionIsAsked({ search: query.search, columnFilters: query.columnFilters, filter }),
@@ -12022,142 +12329,7 @@ function Grid({
12022
12329
  ...mayAddRow ? { addFirst: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(AddFirstRowButton, { onAdd: () => void addRowHere(), busy: added.busy }) } : {},
12023
12330
  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."
12024
12331
  }),
12025
- toolbar: {
12026
- search: true,
12027
- searchPlaceholder: "Search records",
12028
- /*
12029
- * A PHONE (tranche 6o): search and the one Tools button sit side by side at the START of
12030
- * the row, so Tools is never scrolled out of reach; the search is the same query.
12031
- */
12032
- ...merged && phone ? {
12033
- customSearch: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "flex min-w-0 flex-1 items-center gap-1.5", "data-records-grid-phone-search": "", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12034
- "input",
12035
- {
12036
- type: "search",
12037
- value: query.search,
12038
- placeholder: "Search",
12039
- "aria-label": "Search records",
12040
- onChange: (event) => {
12041
- const search = event.target.value;
12042
- setQuery((held) => ({ ...held, search, page: 1 }));
12043
- },
12044
- 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",
12045
- style: { fontSize: "16px" }
12046
- }
12047
- ) })
12048
- } : {},
12049
- /* ONE ROW (lane TABLE-PAGE-CHROME): on a table page the grid's search, columns and
12050
- sort are drawn INTO the page's toolbar row, never a second bar under it. */
12051
- ...toolbarSlot !== void 0 ? { portalInto: toolbarSlot, singleRow: true } : {},
12052
- // SCR-2 / SCR-4: PRESENT for an admin, ABSENT for everyone else.
12053
- // There is no third state: this package ships no disabled variant of
12054
- // these controls, so a member cannot be shown a dead one by mistake.
12055
- /*
12056
- * WHERE THE GRID'S CONTROLS GO (lane C, review 2026-09-26 D6). Inline when the row has
12057
- * room; behind the row's "…" once the page row is folded that far (Undo / Redo and New
12058
- * record stay beside it — the primary action is never folded); on a phone EVERY control
12059
- * — the table's own (clear, copy/export, group, columns), these, the search, the sort and
12060
- * the page's filter, layout and share — in ONE "Tools" sheet. Never dropped.
12061
- */
12062
- actions: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
12063
- manual ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12064
- SortStateControl,
12065
- {
12066
- ...handOrderSortState({
12067
- sortName: columnSort ? columnLabel(fields.data ?? [], columnSort.id) : null,
12068
- direction: columnSort?.direction ?? null,
12069
- onBack: () => setQuery((held) => ({ ...held, sort: null, page: 1 })),
12070
- ...viewOrder?.onUseSort && columnSort ? {
12071
- onUseSort: () => {
12072
- const sort = columnSort;
12073
- const chosen = { field: sort.id, direction: sort.direction };
12074
- setHeldOrder({ order: "sorted", sorts: [chosen] });
12075
- viewOrder.onUseSort?.(chosen);
12076
- host.notify?.success(
12077
- `Sorted by ${columnLabel(fields.data ?? [], sort.id)} ${sortArrow(sort.direction)}. ${MANUAL_SORT.replacedToast}`
12078
- );
12079
- }
12080
- } : {}
12081
- })
12082
- }
12083
- ) : null,
12084
- viewId && viewOrder?.onPlaced && rows.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_design_system35.Button, { size: "sm", variant: "ghost", onClick: () => setReordering(true), "data-records-reorder-open": "", children: "Reorder" }) : null,
12085
- !merged && Boolean(editable) && rights.write ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_design_system35.Button, { size: "sm", disabled: editing.busy, onClick: () => void editing.addRecord(), children: editing.busy ? "Adding\u2026" : "New record" }) : null,
12086
- rights.structure && onAddField ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(AddFieldButton, { onClick: onAddField }) : null,
12087
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12088
- GridLayoutMenu,
12089
- {
12090
- look,
12091
- firstColumn: sortedKeys[0] ? {
12092
- key: sortedKeys[0],
12093
- name: (() => {
12094
- const first = (fields.data ?? []).find((f) => f.key === sortedKeys[0]);
12095
- return first ? fieldName(first) : humanize(sortedKeys[0]);
12096
- })()
12097
- } : null,
12098
- onChange: changeLayout,
12099
- kept: Boolean(onPresentationChange)
12100
- }
12101
- )
12102
- ] }),
12103
- ...merged ? {
12104
- overflow: {
12105
- mode: phone ? "sheet" : toolbarFold2 >= 3 ? "menu" : "inline",
12106
- pinned: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
12107
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(UndoRedoButtons, { undo }),
12108
- Boolean(editable) && rights.write ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_design_system35.Button, { size: "sm", className: "shrink-0", disabled: added.busy, onClick: () => void addRowHere(), children: added.busy ? "Adding\u2026" : "New record" }) : null
12109
- ] }),
12110
- ...phone ? {
12111
- sheetTop: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex flex-col gap-1.5 pb-1", "data-records-grid-sheet-top": "", "data-matrx-table-tools-keep-open": "", children: [
12112
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
12113
- "input",
12114
- {
12115
- type: "search",
12116
- value: query.search,
12117
- placeholder: "Search",
12118
- "aria-label": "Search records",
12119
- onChange: (event) => {
12120
- const search = event.target.value;
12121
- setQuery((held) => ({ ...held, search, page: 1 }));
12122
- },
12123
- className: "h-10 w-full min-w-0 rounded-md border border-input bg-background px-2 text-foreground placeholder:text-muted-foreground",
12124
- style: { fontSize: "16px" }
12125
- }
12126
- ),
12127
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("label", { className: "flex items-center gap-2 text-xs text-muted-foreground", "data-records-grid-sheet-sort": "", children: [
12128
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "shrink-0", children: "Sort by" }),
12129
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
12130
- "select",
12131
- {
12132
- "aria-label": "Sort by",
12133
- value: query.sort ? `${query.sort.id}:${query.sort.direction}` : "",
12134
- onChange: (event) => {
12135
- const [id, direction] = event.target.value.split(":");
12136
- setQuery((held) => ({
12137
- ...held,
12138
- page: 1,
12139
- sort: id ? { id, direction: direction === "desc" ? "desc" : "asc" } : null
12140
- }));
12141
- },
12142
- className: "h-10 min-w-0 flex-1 rounded-md border border-input bg-background px-2 text-foreground",
12143
- style: { fontSize: "16px" },
12144
- children: [
12145
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("option", { value: "", children: "No sort" }),
12146
- ordered.flatMap((field2) => [
12147
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("option", { value: `${field2.key}:asc`, children: `${fieldName(field2)} \u2191` }, `${field2.key}:asc`),
12148
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("option", { value: `${field2.key}:desc`, children: `${fieldName(field2)} \u2193` }, `${field2.key}:desc`)
12149
- ])
12150
- ]
12151
- }
12152
- )
12153
- ] }),
12154
- controls.sheetTools
12155
- ] }),
12156
- ...sheetExtras ? { sheetExtras } : {}
12157
- } : {}
12158
- }
12159
- } : {}
12160
- }
12332
+ toolbar: tableToolbar
12161
12333
  },
12162
12334
  `records-grid-${tableId}:${openingKey}`
12163
12335
  )
@@ -12259,6 +12431,58 @@ function Grid({
12259
12431
  }
12260
12432
  ) });
12261
12433
  }
12434
+ var DETAIL_OFF = { enabled: false };
12435
+ var WINDOW_OFF = { enabled: false };
12436
+ var VIRTUALIZE_FILLED = { threshold: 40 };
12437
+ function menuFace(items) {
12438
+ return JSON.stringify(items, (_key, value) => typeof value === "function" ? "\u0192" : value);
12439
+ }
12440
+ function menuThroughLatest(items, at, latest) {
12441
+ return items.map((item) => {
12442
+ const path = `${at}/${item.id}`;
12443
+ return {
12444
+ ...item,
12445
+ ...item.onSelect ? { onSelect: () => latest.get(path)?.() } : {},
12446
+ ...item.submenu ? { submenu: menuThroughLatest(item.submenu, path, latest) } : {}
12447
+ };
12448
+ });
12449
+ }
12450
+ function bindLatest(items, at, latest) {
12451
+ for (const item of items) {
12452
+ const path = `${at}/${item.id}`;
12453
+ if (item.onSelect) latest.set(path, item.onSelect);
12454
+ if (item.submenu) bindLatest(item.submenu, path, latest);
12455
+ }
12456
+ }
12457
+ function holdHeaderMenu(held, latest, key, items) {
12458
+ if (!items) return items;
12459
+ const at = `menu:${key}`;
12460
+ bindLatest(items, at, latest);
12461
+ const sig = menuFace(items);
12462
+ const was = held.get(at);
12463
+ if (was && was.sig === sig) return was.value;
12464
+ const value = menuThroughLatest(items, at, latest);
12465
+ held.set(at, { sig, value });
12466
+ return value;
12467
+ }
12468
+ function holdByContent(held, at, value) {
12469
+ if (value === void 0) return value;
12470
+ const sig = JSON.stringify(value);
12471
+ const was = held.get(at);
12472
+ if (was && was.sig === sig) return was.value;
12473
+ held.set(at, { sig, value });
12474
+ return value;
12475
+ }
12476
+ function sameColumns(a, b) {
12477
+ if (a === b) return true;
12478
+ if (a.length !== b.length) return false;
12479
+ return a.every((column, i) => {
12480
+ const other = b[i];
12481
+ if (column === other) return true;
12482
+ const keys = Object.keys(column);
12483
+ return keys.length === Object.keys(other).length && keys.every((key) => Object.is(column[key], other[key]));
12484
+ });
12485
+ }
12262
12486
  function withHeaderMenus(columns, menuFor, filterFor) {
12263
12487
  return columns.map((column) => {
12264
12488
  const items = menuFor(String(column.id));
@@ -26512,7 +26736,8 @@ function useEmbedHandshake(args) {
26512
26736
  }
26513
26737
 
26514
26738
  // src/RecordsMount.tsx
26515
- var import_react160 = require("@ai-matrx/records/react");
26739
+ var import_react160 = require("react");
26740
+ var import_react161 = require("@ai-matrx/records/react");
26516
26741
  var import_jsx_runtime91 = require("react/jsx-runtime");
26517
26742
  var STORE_DECIDES_REASON = "This host offers exactly what the record store says this person may do: the level comes back with the table on the read door, so a control they cannot use is never drawn in the first place.";
26518
26743
  function storeDecidesRights(_table) {
@@ -26524,9 +26749,25 @@ function RecordsMount({
26524
26749
  letTheStoreDecideRights,
26525
26750
  children
26526
26751
  }) {
26527
- const bound = { ...host ?? {} };
26752
+ const bound = useShallowStableHost(host);
26528
26753
  void letTheStoreDecideRights;
26529
- return /* @__PURE__ */ (0, import_jsx_runtime91.jsx)(import_react160.RecordsProvider, { config, children: /* @__PURE__ */ (0, import_jsx_runtime91.jsx)(RecordsUiProvider, { value: bound, children: /* @__PURE__ */ (0, import_jsx_runtime91.jsx)(RecordLabelProvider, { children }) }) });
26754
+ return /* @__PURE__ */ (0, import_jsx_runtime91.jsx)(import_react161.RecordsProvider, { config, children: /* @__PURE__ */ (0, import_jsx_runtime91.jsx)(RecordsUiProvider, { value: bound, children: /* @__PURE__ */ (0, import_jsx_runtime91.jsx)(RecordLabelProvider, { children }) }) });
26755
+ }
26756
+ function useShallowStableHost(host) {
26757
+ const held = (0, import_react160.useRef)(null);
26758
+ const next = host ?? {};
26759
+ const previous = held.current;
26760
+ if (previous !== null && sameParts(previous, next)) return previous;
26761
+ const bound = { ...next };
26762
+ held.current = bound;
26763
+ return bound;
26764
+ }
26765
+ function sameParts(a, b) {
26766
+ const aKeys = Object.keys(a);
26767
+ const bKeys = Object.keys(b);
26768
+ if (aKeys.length !== bKeys.length) return false;
26769
+ for (const key of aKeys) if (a[key] !== b[key]) return false;
26770
+ return true;
26530
26771
  }
26531
26772
  function personActor(userId) {
26532
26773
  return userId ? { actor: "user", user_id: userId } : { actor: "user" };
@@ -26545,15 +26786,15 @@ function recordsDataSource(client, fallbackSchema = "custom") {
26545
26786
  }
26546
26787
 
26547
26788
  // src/TablesHome.tsx
26548
- var import_react165 = require("react");
26549
- var import_react166 = require("@ai-matrx/records/react");
26789
+ var import_react166 = require("react");
26790
+ var import_react167 = require("@ai-matrx/records/react");
26550
26791
  var import_core16 = require("@ai-matrx/records/core");
26551
26792
  var import_records29 = require("@ai-matrx/records");
26552
26793
  var import_design_system85 = require("@ai-matrx/design-system");
26553
26794
 
26554
26795
  // src/ExampleTables.tsx
26555
- var import_react161 = require("react");
26556
- var import_react162 = require("@ai-matrx/records/react");
26796
+ var import_react162 = require("react");
26797
+ var import_react163 = require("@ai-matrx/records/react");
26557
26798
  var import_use_cases = require("@ai-matrx/records/use-cases");
26558
26799
  var import_design_system83 = require("@ai-matrx/design-system");
26559
26800
  var import_jsx_runtime92 = require("react/jsx-runtime");
@@ -26562,12 +26803,12 @@ function ExampleTables({
26562
26803
  onClose,
26563
26804
  className
26564
26805
  }) {
26565
- const client = (0, import_react162.useRecordsClient)();
26566
- const [summaries, setSummaries] = (0, import_react161.useState)(null);
26567
- const [busy, setBusy] = (0, import_react161.useState)(null);
26568
- const [error, setError] = (0, import_react161.useState)(null);
26569
- const [built, setBuilt] = (0, import_react161.useState)(null);
26570
- (0, import_react161.useEffect)(() => {
26806
+ const client = (0, import_react163.useRecordsClient)();
26807
+ const [summaries, setSummaries] = (0, import_react162.useState)(null);
26808
+ const [busy, setBusy] = (0, import_react162.useState)(null);
26809
+ const [error, setError] = (0, import_react162.useState)(null);
26810
+ const [built, setBuilt] = (0, import_react162.useState)(null);
26811
+ (0, import_react162.useEffect)(() => {
26571
26812
  let alive = true;
26572
26813
  void Promise.all((0, import_use_cases.useCaseIds)().map(async (id) => (0, import_use_cases.summarize)(await (0, import_use_cases.loadUseCase)(id)))).then((rows) => {
26573
26814
  if (alive) setSummaries(rows);
@@ -26645,22 +26886,22 @@ function ExampleTables({
26645
26886
  var import_core15 = require("@ai-matrx/records/core");
26646
26887
 
26647
26888
  // src/WhereItLives.tsx
26648
- var import_react163 = require("react");
26889
+ var import_react164 = require("react");
26649
26890
  var import_records28 = require("@ai-matrx/records");
26650
- var import_react164 = require("@ai-matrx/records/react");
26891
+ var import_react165 = require("@ai-matrx/records/react");
26651
26892
  var import_design_system84 = require("@ai-matrx/design-system");
26652
26893
  var import_jsx_runtime93 = require("react/jsx-runtime");
26653
26894
  function WhereItLives({ tableId, knownOrganizationName, onMoved, variant = "header" }) {
26654
- const client = (0, import_react164.useRecordsClient)();
26895
+ const client = (0, import_react165.useRecordsClient)();
26655
26896
  const host = useRecordsUi();
26656
- const [answer, setAnswer] = (0, import_react163.useState)(null);
26657
- const [attempt, setAttempt] = (0, import_react163.useState)(0);
26658
- const [open, setOpen] = (0, import_react163.useState)(false);
26659
- const [armed, setArmed] = (0, import_react163.useState)(variant !== "row" || !knownOrganizationName);
26660
- const [choosing, setChoosing] = (0, import_react163.useState)(null);
26661
- const [moving, setMoving] = (0, import_react163.useState)(false);
26662
- const [said, setSaid] = (0, import_react163.useState)(null);
26663
- (0, import_react163.useEffect)(() => {
26897
+ const [answer, setAnswer] = (0, import_react164.useState)(null);
26898
+ const [attempt, setAttempt] = (0, import_react164.useState)(0);
26899
+ const [open, setOpen] = (0, import_react164.useState)(false);
26900
+ const [armed, setArmed] = (0, import_react164.useState)(variant !== "row" || !knownOrganizationName);
26901
+ const [choosing, setChoosing] = (0, import_react164.useState)(null);
26902
+ const [moving, setMoving] = (0, import_react164.useState)(false);
26903
+ const [said, setSaid] = (0, import_react164.useState)(null);
26904
+ (0, import_react164.useEffect)(() => {
26664
26905
  if (!armed) return;
26665
26906
  let alive = true;
26666
26907
  setAnswer(null);
@@ -26906,11 +27147,11 @@ function visibilityLaneFor(table) {
26906
27147
  return "organization";
26907
27148
  }
26908
27149
  function TablesHome({ onOpenTable, organizationName, makingOnly = false, className }) {
26909
- const client = (0, import_react166.useRecordsClient)();
26910
- const tables = (0, import_react166.useTables)();
27150
+ const client = (0, import_react167.useRecordsClient)();
27151
+ const tables = (0, import_react167.useTables)();
26911
27152
  const me = client.config.actor.user_id ?? null;
26912
- const [facts, setFacts] = (0, import_react165.useState)({ phase: "reading" });
26913
- (0, import_react165.useEffect)(() => {
27153
+ const [facts, setFacts] = (0, import_react166.useState)({ phase: "reading" });
27154
+ (0, import_react166.useEffect)(() => {
26914
27155
  let alive = true;
26915
27156
  void client.tableFacts().then((answered) => {
26916
27157
  if (!alive) return;
@@ -26924,11 +27165,11 @@ function TablesHome({ onOpenTable, organizationName, makingOnly = false, classNa
26924
27165
  (0, import_core16.withTableFacts)(tables.data ?? [], facts.rows, me),
26925
27166
  facts.rows
26926
27167
  ) : tables.data ?? [];
26927
- const [showEverything, setShowEverything] = (0, import_react165.useState)(false);
27168
+ const [showEverything, setShowEverything] = (0, import_react166.useState)(false);
26928
27169
  const keptCount = listed.filter((t) => laneFor(t) === "app").length;
26929
- const [askedName, setAskedName] = (0, import_react165.useState)(null);
27170
+ const [askedName, setAskedName] = (0, import_react166.useState)(null);
26930
27171
  const firstTableId = listed[0]?.id ?? null;
26931
- (0, import_react165.useEffect)(() => {
27172
+ (0, import_react166.useEffect)(() => {
26932
27173
  if (organizationName || !firstTableId) return;
26933
27174
  let alive = true;
26934
27175
  void client.tableHome({ table_id: firstTableId }).then((answered) => {
@@ -26941,14 +27182,14 @@ function TablesHome({ onOpenTable, organizationName, makingOnly = false, classNa
26941
27182
  const livesIn = organizationName || askedName;
26942
27183
  const isMine = (t) => Boolean(me) && laneFor(t) !== "system" && laneFor(t) !== "app" && t.created_by === me;
26943
27184
  const sectionsKnown = facts.phase !== "failed";
26944
- const [creating, setCreating] = (0, import_react165.useState)(false);
26945
- const [name, setName] = (0, import_react165.useState)("");
26946
- const [busy, setBusy] = (0, import_react165.useState)(false);
26947
- const [error, setError] = (0, import_react165.useState)(null);
26948
- const [importInto, setImportInto] = (0, import_react165.useState)(null);
26949
- const [examples, setExamples] = (0, import_react165.useState)(false);
26950
- const [boards, setBoards] = (0, import_react165.useState)(null);
26951
- (0, import_react165.useEffect)(() => {
27185
+ const [creating, setCreating] = (0, import_react166.useState)(false);
27186
+ const [name, setName] = (0, import_react166.useState)("");
27187
+ const [busy, setBusy] = (0, import_react166.useState)(false);
27188
+ const [error, setError] = (0, import_react166.useState)(null);
27189
+ const [importInto, setImportInto] = (0, import_react166.useState)(null);
27190
+ const [examples, setExamples] = (0, import_react166.useState)(false);
27191
+ const [boards, setBoards] = (0, import_react166.useState)(null);
27192
+ (0, import_react166.useEffect)(() => {
26952
27193
  if (makingOnly) return;
26953
27194
  let cancelled = false;
26954
27195
  void client.dashboards({}).then((result) => {
@@ -26959,7 +27200,7 @@ function TablesHome({ onOpenTable, organizationName, makingOnly = false, classNa
26959
27200
  cancelled = true;
26960
27201
  };
26961
27202
  }, [client, makingOnly]);
26962
- const create = (0, import_react165.useCallback)(
27203
+ const create = (0, import_react166.useCallback)(
26963
27204
  async (mode) => {
26964
27205
  const trimmed = name.trim();
26965
27206
  if (!trimmed) return;
@@ -27124,15 +27365,15 @@ function TablesHome({ onOpenTable, organizationName, makingOnly = false, classNa
27124
27365
  }
27125
27366
 
27126
27367
  // src/TablePage.tsx
27127
- var import_react170 = require("react");
27368
+ var import_react171 = require("react");
27128
27369
  var import_records30 = require("@ai-matrx/records");
27129
- var import_react171 = require("@ai-matrx/records/react");
27370
+ var import_react172 = require("@ai-matrx/records/react");
27130
27371
  var import_design_system87 = require("@ai-matrx/design-system");
27131
27372
 
27132
27373
  // src/ViewFilter.tsx
27133
- var import_react167 = require("react");
27374
+ var import_react168 = require("react");
27134
27375
  var import_design_system86 = require("@ai-matrx/design-system");
27135
- var import_react168 = require("@ai-matrx/records/react");
27376
+ var import_react169 = require("@ai-matrx/records/react");
27136
27377
  var import_jsx_runtime95 = require("react/jsx-runtime");
27137
27378
  function conditionCount(expr) {
27138
27379
  if (!expr || typeof expr !== "object" || Array.isArray(expr)) return 0;
@@ -27143,15 +27384,15 @@ function conditionCount(expr) {
27143
27384
  return 1;
27144
27385
  }
27145
27386
  function ViewFilter({ tableId, view, onApply, className }) {
27146
- const fields = (0, import_react168.useFields)(tableId);
27147
- const [open, setOpen] = (0, import_react167.useState)(false);
27387
+ const fields = (0, import_react169.useFields)(tableId);
27388
+ const [open, setOpen] = (0, import_react168.useState)(false);
27148
27389
  const saved = view?.where ?? null;
27149
- const [draft, setDraft] = (0, import_react167.useState)(saved);
27390
+ const [draft, setDraft] = (0, import_react168.useState)(saved);
27150
27391
  const savedKey = JSON.stringify(saved);
27151
- (0, import_react167.useEffect)(() => {
27392
+ (0, import_react168.useEffect)(() => {
27152
27393
  setDraft(saved);
27153
27394
  }, [view?.id, savedKey]);
27154
- const conditionFields = (0, import_react167.useMemo)(
27395
+ const conditionFields = (0, import_react168.useMemo)(
27155
27396
  () => (fields.data ?? []).map((f) => ({ id: f.id, key: f.key, label: f.label || f.key, field: f })),
27156
27397
  [fields.data]
27157
27398
  );
@@ -27231,7 +27472,7 @@ function ViewFilter({ tableId, view, onApply, className }) {
27231
27472
  }
27232
27473
 
27233
27474
  // src/toolbarFit.ts
27234
- var import_react169 = require("react");
27475
+ var import_react170 = require("react");
27235
27476
  var FOLDED_GUESS = { look: 104, chooser: 96, tools: 120 };
27236
27477
  function toolbarFold(input) {
27237
27478
  const { available, needsNow, gap, folded } = input;
@@ -27282,8 +27523,8 @@ function rowNeeds(row) {
27282
27523
  return { needs: needs + gap * Math.max(0, pieces.length - 1), gap };
27283
27524
  }
27284
27525
  function useToolbarFit(row) {
27285
- const [fold, setFold] = (0, import_react169.useState)(0);
27286
- (0, import_react169.useEffect)(() => {
27526
+ const [fold, setFold] = (0, import_react170.useState)(0);
27527
+ (0, import_react170.useEffect)(() => {
27287
27528
  if (!row) return;
27288
27529
  const widths = {
27289
27530
  look: { full: null, folded: null },
@@ -27363,8 +27604,8 @@ function clippedEnds(scrollLeft, clientWidth, scrollWidth) {
27363
27604
  return start && end ? "both" : start ? "start" : end ? "end" : "none";
27364
27605
  }
27365
27606
  function useSlotScroll(slot) {
27366
- const [ends, setEnds] = (0, import_react169.useState)("none");
27367
- (0, import_react169.useEffect)(() => {
27607
+ const [ends, setEnds] = (0, import_react170.useState)("none");
27608
+ (0, import_react170.useEffect)(() => {
27368
27609
  if (!slot) return;
27369
27610
  let pending = 0;
27370
27611
  const read = () => {
@@ -27480,11 +27721,11 @@ function TableNotHere({
27480
27721
  leaveLabel,
27481
27722
  className
27482
27723
  }) {
27483
- const client = (0, import_react171.useRecordsClient)();
27484
- const [archived, setArchived] = (0, import_react170.useState)({ phase: "asking" });
27485
- const [restoring, setRestoring] = (0, import_react170.useState)(false);
27486
- const [refusal2, setRefusal] = (0, import_react170.useState)(null);
27487
- (0, import_react170.useEffect)(() => {
27724
+ const client = (0, import_react172.useRecordsClient)();
27725
+ const [archived, setArchived] = (0, import_react171.useState)({ phase: "asking" });
27726
+ const [restoring, setRestoring] = (0, import_react171.useState)(false);
27727
+ const [refusal2, setRefusal] = (0, import_react171.useState)(null);
27728
+ (0, import_react171.useEffect)(() => {
27488
27729
  let alive = true;
27489
27730
  void client.tableArchived({ table_id: tableId }).then((answered) => {
27490
27731
  if (!alive) return;
@@ -27661,15 +27902,15 @@ function TablePage({
27661
27902
  onShownViewChange,
27662
27903
  className
27663
27904
  }) {
27664
- const client = (0, import_react171.useRecordsClient)();
27665
- const table = (0, import_react171.useTable)(tableId);
27905
+ const client = (0, import_react172.useRecordsClient)();
27906
+ const table = (0, import_react172.useTable)(tableId);
27666
27907
  const rights = useTableRights(table.data);
27667
- const pageFields = (0, import_react171.useFields)(filter ? tableId : null);
27908
+ const pageFields = (0, import_react172.useFields)(filter ? tableId : null);
27668
27909
  const askedInWords = filter ? filterInWords(filter, pageFields.data ?? []) : null;
27669
- const organizationId = (0, import_react171.useRecordsClient)().config.organizationId;
27670
- const [view, setView] = (0, import_react170.useState)(null);
27671
- const [looks, setLooks] = (0, import_react170.useState)(void 0);
27672
- (0, import_react170.useEffect)(() => {
27910
+ const organizationId = (0, import_react172.useRecordsClient)().config.organizationId;
27911
+ const [view, setView] = (0, import_react171.useState)(null);
27912
+ const [looks, setLooks] = (0, import_react171.useState)(void 0);
27913
+ (0, import_react171.useEffect)(() => {
27673
27914
  let live = true;
27674
27915
  setLooks(void 0);
27675
27916
  void readLooks(client, tableId).then((answered) => {
@@ -27682,35 +27923,35 @@ function TablePage({
27682
27923
  }, [client, tableId]);
27683
27924
  const look = view && looks ? looks[view.id] : void 0;
27684
27925
  const shown3 = view ? applyLook(view, look) : null;
27685
- const [viewsEpoch, setViewsEpoch] = (0, import_react170.useState)(0);
27926
+ const [viewsEpoch, setViewsEpoch] = (0, import_react171.useState)(0);
27686
27927
  const opening = openingRail(activeRecordId, activeRail);
27687
27928
  const hostLayouts = useRecordsUi().layouts ?? [];
27688
27929
  const hostLayoutFor = (asked) => openingHostLayout(asked, hostLayouts);
27689
27930
  const hostFromLink = hostLayoutFor(activeView);
27690
27931
  const openedByPackage = openingView(hostFromLink ? null : activeView, activeDashboardId);
27691
27932
  const opened = hostFromLink ? { ...openedByPackage, main: "records" } : openedByPackage;
27692
- const [hostLayout, setHostLayout] = (0, import_react170.useState)(hostFromLink);
27693
- const [views, setViews] = (0, import_react170.useState)(void 0);
27694
- const [pickedViewId, setPickedViewId] = (0, import_react170.useState)(null);
27695
- const [layoutChosen, setLayoutChosen] = (0, import_react170.useState)(false);
27933
+ const [hostLayout, setHostLayout] = (0, import_react171.useState)(hostFromLink);
27934
+ const [views, setViews] = (0, import_react171.useState)(void 0);
27935
+ const [pickedViewId, setPickedViewId] = (0, import_react171.useState)(null);
27936
+ const [layoutChosen, setLayoutChosen] = (0, import_react171.useState)(false);
27696
27937
  const railAsked = typeof activeRail === "string" ? activeRail.trim() : "";
27697
27938
  const railUnknown = railAsked !== "" && railFromParam(railAsked) === null ? railAsked : null;
27698
27939
  const shareFromLink = railFromParam(activeRail) === "share";
27699
27940
  const canShare = useCanShare();
27700
27941
  const itemFor = (which) => railFromParam(activeRail) === which ? activeItemId ?? null : null;
27701
- const [asking, setAsking] = (0, import_react170.useState)(null);
27702
- const [layoutFromLink, setLayoutFromLink] = (0, import_react170.useState)(opened.layout);
27703
- const [surface, setSurface] = (0, import_react170.useState)({
27942
+ const [asking, setAsking] = (0, import_react171.useState)(null);
27943
+ const [layoutFromLink, setLayoutFromLink] = (0, import_react171.useState)(opened.layout);
27944
+ const [surface, setSurface] = (0, import_react171.useState)({
27704
27945
  main: opened.main,
27705
27946
  rail: opening.rail
27706
27947
  });
27707
27948
  const { main, rail } = surface;
27708
27949
  const setRail = (next) => setSurface((now) => ({ ...now, rail: next }));
27709
- const [openRecord, setOpenRecord] = (0, import_react170.useState)(opening.record);
27950
+ const [openRecord, setOpenRecord] = (0, import_react171.useState)(opening.record);
27710
27951
  const phone = (0, import_design_system87.useIsMobile)();
27711
- const [shareAsked, setShareAsked] = (0, import_react170.useState)(0);
27712
- const [toolbarSlot, setToolbarSlot] = (0, import_react170.useState)(null);
27713
- const [toolbarRow, setToolbarRow] = (0, import_react170.useState)(null);
27952
+ const [shareAsked, setShareAsked] = (0, import_react171.useState)(0);
27953
+ const [toolbarSlot, setToolbarSlot] = (0, import_react171.useState)(null);
27954
+ const [toolbarRow, setToolbarRow] = (0, import_react171.useState)(null);
27714
27955
  const toolbarFold2 = useToolbarFit(toolbarRow);
27715
27956
  const slotClipped = useSlotScroll(toolbarSlot);
27716
27957
  const face = tableOpening(view, hostLayouts.map((h) => h.id));
@@ -27718,7 +27959,7 @@ function TablePage({
27718
27959
  const chooserInline = !designated && !phone && toolbarFold2 < 2;
27719
27960
  const chooserCompact = !designated && !phone && toolbarFold2 >= 2;
27720
27961
  const controlsInSheet = phone && !designated;
27721
- (0, import_react170.useEffect)(() => {
27962
+ (0, import_react171.useEffect)(() => {
27722
27963
  if (face.kind !== "designated") return;
27723
27964
  if (hostFromLink || opened.layout || layoutChosen) return;
27724
27965
  setHostLayout(face.hostLayout);
@@ -27749,9 +27990,9 @@ function TablePage({
27749
27990
  lookDiffers: view ? lookDiffers(view, look) : false
27750
27991
  };
27751
27992
  const shownSignature = JSON.stringify(shownReport);
27752
- const reportRef = (0, import_react170.useRef)(onShownViewChange);
27993
+ const reportRef = (0, import_react171.useRef)(onShownViewChange);
27753
27994
  reportRef.current = onShownViewChange;
27754
- (0, import_react170.useEffect)(() => {
27995
+ (0, import_react171.useEffect)(() => {
27755
27996
  reportRef.current?.(JSON.parse(shownSignature));
27756
27997
  }, [shownSignature]);
27757
27998
  const pickLayout = (next) => {
@@ -27769,18 +28010,18 @@ function TablePage({
27769
28010
  onViewChanged?.(id);
27770
28011
  };
27771
28012
  const openedFromMenu = MENU.find((entry) => entry.press.main !== void 0 && entry.press.main === main) ?? MENU.find((entry) => entry.press.rail !== void 0 && entry.press.rail === rail) ?? null;
27772
- (0, import_react170.useEffect)(() => {
28013
+ (0, import_react171.useEffect)(() => {
27773
28014
  if (!activeRecordId) return;
27774
28015
  setOpenRecord(activeRecordId);
27775
28016
  setSurface((now) => ({ ...now, rail: "record" }));
27776
28017
  }, [activeRecordId]);
27777
- (0, import_react170.useEffect)(() => {
28018
+ (0, import_react171.useEffect)(() => {
27778
28019
  if (activeRecordId) return;
27779
28020
  const named = railFromParam(activeRail);
27780
28021
  if (named === null || named === "share") return;
27781
28022
  setSurface((now) => ({ ...now, rail: named }));
27782
28023
  }, [activeRail, activeRecordId]);
27783
- (0, import_react170.useEffect)(() => {
28024
+ (0, import_react171.useEffect)(() => {
27784
28025
  const host = hostLayoutFor(activeView);
27785
28026
  if (host) {
27786
28027
  setHostLayout(host);
@@ -27794,8 +28035,8 @@ function TablePage({
27794
28035
  setLayoutFromLink(isLayout ? named : null);
27795
28036
  setSurface((now) => ({ ...now, main: isLayout ? "records" : named }));
27796
28037
  }, [activeView]);
27797
- const [viewSaveRefused, setViewSaveRefused] = (0, import_react170.useState)(null);
27798
- const patchView = (0, import_react170.useCallback)(
28038
+ const [viewSaveRefused, setViewSaveRefused] = (0, import_react171.useState)(null);
28039
+ const patchView = (0, import_react171.useCallback)(
27799
28040
  async (asked) => {
27800
28041
  const current = view;
27801
28042
  if (!current) return;
@@ -27812,9 +28053,9 @@ function TablePage({
27812
28053
  [client, tableId, view, looks]
27813
28054
  );
27814
28055
  const lookShowing = Boolean(view && looks && lookDiffers(view, looks[view.id]));
27815
- const [lookRefused, setLookRefused] = (0, import_react170.useState)(null);
27816
- const [namingLook, setNamingLook] = (0, import_react170.useState)(false);
27817
- const [lookName, setLookName] = (0, import_react170.useState)("");
28056
+ const [lookRefused, setLookRefused] = (0, import_react171.useState)(null);
28057
+ const [namingLook, setNamingLook] = (0, import_react171.useState)(false);
28058
+ const [lookName, setLookName] = (0, import_react171.useState)("");
27818
28059
  const resetLook = async () => {
27819
28060
  const current = view;
27820
28061
  if (!current) return;
@@ -27872,7 +28113,7 @@ function TablePage({
27872
28113
  setPickedViewId(written.data);
27873
28114
  setViewsEpoch((n) => n + 1);
27874
28115
  };
27875
- const [designateRefused, setDesignateRefused] = (0, import_react170.useState)(null);
28116
+ const [designateRefused, setDesignateRefused] = (0, import_react171.useState)(null);
27876
28117
  const opensAs = view?.isDefault ? openingLayoutOf(view) : null;
27877
28118
  const canDesignate = Boolean(rights.write && view && layoutShowing && layoutShowing !== opensAs);
27878
28119
  const layoutShowingLabel = layoutShowing ? hostLayouts.find((h) => h.id === layoutShowing)?.label ?? (VIEW_LAYOUTS.includes(layoutShowing) ? LAYOUT_LABEL[layoutShowing] : layoutShowing) : "";