@ai-matrx/records-ui 0.115.78 → 0.115.79

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 CHANGED
@@ -1,10 +1,22 @@
1
1
  # Changelog — @ai-matrx/records-ui
2
2
 
3
- ## 0.115.78
3
+ ## 0.115.79
4
4
 
5
5
  UNVERIFIED: this release has no Unreleased entry and the source comparison could not be made (the caller supplied no source comparison). Whether source changed is unknown; consumer action is unknown.
6
6
  Check the package's gitHead on npm against this version's gitHead: `git diff <previous gitHead> <this gitHead> -- apps/shared/records-ui/src`.
7
7
 
8
+ ## 0.115.78
9
+
10
+ SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
11
+ so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
12
+ Diff: `git diff 254d4d3f8b c3135c57b9 -- apps/shared/records-ui/src`
13
+
14
+ Changed shipped source files (4):
15
+ - `apps/shared/records-ui/src/Grid.tsx`
16
+ - `apps/shared/records-ui/src/ViewSwitcher.tsx`
17
+ - `apps/shared/records-ui/src/newRowValues.tsx`
18
+ - `apps/shared/records-ui/src/recordMoves.tsx`
19
+
8
20
  ## 0.115.77
9
21
 
10
22
  SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
package/dist/index.cjs CHANGED
@@ -6886,46 +6886,63 @@ function AttachmentChip({ id, onRemove, onPreview }) {
6886
6886
  const words2 = read?.name ?? (read ? FILE_CHIP_UNREAD : "\u2026");
6887
6887
  const href = read?.fileId ? host.hrefForFile?.({ fileId: read.fileId }) : void 0;
6888
6888
  const title = onPreview ? `Preview ${words2}` : href ? `Open ${words2}` : read?.why ?? FILE_CHIP_NO_PAGE;
6889
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_design_system22.Badge, { variant: "secondary", className: "max-w-[14rem] gap-1 text-[11px]", "data-records-file-chip": id, title, children: [
6890
- onPreview ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6891
- "button",
6892
- {
6893
- type: "button",
6894
- className: "truncate underline-offset-2 hover:underline",
6895
- "data-records-file-preview": id,
6889
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
6890
+ import_design_system22.Badge,
6891
+ {
6892
+ variant: "secondary",
6893
+ className: (0, import_design_system22.cn)("max-w-[14rem] gap-1 text-[11px]", onPreview && "cursor-pointer"),
6894
+ "data-records-file-chip": id,
6895
+ title,
6896
+ ...onPreview ? {
6896
6897
  onClick: (event) => {
6897
6898
  event.stopPropagation();
6898
6899
  onPreview();
6899
6900
  },
6900
6901
  onMouseDown: (event) => event.stopPropagation(),
6901
- onDoubleClick: (event) => event.stopPropagation(),
6902
- children: words2
6903
- }
6904
- ) : href ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6905
- import_outbound_react4.OutboundLink,
6906
- {
6907
- href,
6908
- className: "truncate underline-offset-2 hover:underline",
6909
- "data-records-file-link": read?.fileId ?? "",
6910
- onClick: (event) => event.stopPropagation(),
6911
- onMouseDown: (event) => event.stopPropagation(),
6912
- children: words2
6913
- }
6914
- ) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "truncate", children: words2 }),
6915
- onRemove ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6916
- "button",
6917
- {
6918
- type: "button",
6919
- "aria-label": `Remove ${words2}`,
6920
- className: "shrink-0 rounded px-0.5 text-muted-foreground hover:text-foreground",
6921
- onClick: (event) => {
6922
- event.stopPropagation();
6923
- onRemove();
6924
- },
6925
- children: "\xD7"
6926
- }
6927
- ) : null
6928
- ] });
6902
+ onDoubleClick: (event) => event.stopPropagation()
6903
+ } : {},
6904
+ children: [
6905
+ onPreview ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6906
+ "button",
6907
+ {
6908
+ type: "button",
6909
+ className: "truncate underline-offset-2 hover:underline",
6910
+ "data-records-file-preview": id,
6911
+ onClick: (event) => {
6912
+ event.stopPropagation();
6913
+ onPreview();
6914
+ },
6915
+ onMouseDown: (event) => event.stopPropagation(),
6916
+ onDoubleClick: (event) => event.stopPropagation(),
6917
+ children: words2
6918
+ }
6919
+ ) : href ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6920
+ import_outbound_react4.OutboundLink,
6921
+ {
6922
+ href,
6923
+ className: "truncate underline-offset-2 hover:underline",
6924
+ "data-records-file-link": read?.fileId ?? "",
6925
+ onClick: (event) => event.stopPropagation(),
6926
+ onMouseDown: (event) => event.stopPropagation(),
6927
+ children: words2
6928
+ }
6929
+ ) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "truncate", children: words2 }),
6930
+ onRemove ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
6931
+ "button",
6932
+ {
6933
+ type: "button",
6934
+ "aria-label": `Remove ${words2}`,
6935
+ className: "shrink-0 rounded px-0.5 text-muted-foreground hover:text-foreground",
6936
+ onClick: (event) => {
6937
+ event.stopPropagation();
6938
+ onRemove();
6939
+ },
6940
+ children: "\xD7"
6941
+ }
6942
+ ) : null
6943
+ ]
6944
+ }
6945
+ );
6929
6946
  }
6930
6947
  function AttachmentChips({ value, onRemove }) {
6931
6948
  const host = useRecordsUi();
@@ -6966,6 +6983,18 @@ function AttachmentViewer({
6966
6983
  {
6967
6984
  className: "sm:max-w-3xl",
6968
6985
  "data-records-attachment-viewer": id,
6986
+ tabIndex: -1,
6987
+ onOpenAutoFocus: (event) => {
6988
+ event.preventDefault();
6989
+ event.currentTarget.focus();
6990
+ },
6991
+ onCloseAutoFocus: (event) => {
6992
+ const chip = document.querySelector(`[data-records-file-preview="${ids[index]}"]`);
6993
+ if (chip) {
6994
+ event.preventDefault();
6995
+ chip.focus();
6996
+ }
6997
+ },
6969
6998
  onKeyDown: (event) => {
6970
6999
  if (event.key === "ArrowLeft") {
6971
7000
  event.preventDefault();
@@ -20700,6 +20729,8 @@ function columnForField(field2, editing, mayWriteRow, whyNotRow, look, onAskWhoC
20700
20729
  ] }) : name
20701
20730
  ),
20702
20731
  label: name,
20732
+ // The field's own description, one hover away on every header (not only the type mark's glyph).
20733
+ ...fieldHelpText(field2) ? { helpText: fieldHelpText(field2) } : {},
20703
20734
  accessorFn: (row) => row.document?.[field2.key],
20704
20735
  // A column the store keeps no value for is not offered a sort it would refuse (`sortRefusal`).
20705
20736
  sortable: sortRefusal(field2) === null,
@@ -21853,9 +21884,9 @@ var import_records43 = require("@ai-matrx/records");
21853
21884
 
21854
21885
  // src/importRuleCheck.ts
21855
21886
  var import_core13 = require("@ai-matrx/records/core");
21856
- function checkImportRules(fields, rows, mapping, choiceOptions, relationNames) {
21887
+ function refusedCellsOf(fields, rows, mapping, choiceOptions, relationNames) {
21857
21888
  const byKey = new Map(fields.map((f) => [f.key, f]));
21858
- const verdicts = [];
21889
+ const cells = [];
21859
21890
  for (const [header, key] of Object.entries(mapping)) {
21860
21891
  const field2 = byKey.get(key);
21861
21892
  if (!field2) continue;
@@ -21865,9 +21896,8 @@ function checkImportRules(fields, rows, mapping, choiceOptions, relationNames) {
21865
21896
  if (isChoice && !options) continue;
21866
21897
  const named = relationNames?.get(key);
21867
21898
  if (kind2 === "relation" && !named) continue;
21868
- let refused = 0;
21869
21899
  let judged = 0;
21870
- let first = null;
21900
+ const mine = [];
21871
21901
  rows.forEach((row, i) => {
21872
21902
  const raw = String(row[header] ?? "").trim();
21873
21903
  if (raw === "") return;
@@ -21883,17 +21913,20 @@ function checkImportRules(fields, rows, mapping, choiceOptions, relationNames) {
21883
21913
  );
21884
21914
  if (predicted.length > 0) why = predicted[0].message;
21885
21915
  }
21886
- if (why !== null) {
21887
- refused += 1;
21888
- if (!first) first = { why, line: i + 1, raw };
21889
- }
21916
+ if (why !== null) mine.push({ header, line: i + 1, raw, why });
21890
21917
  });
21891
- if (refused > 0 && first) {
21892
- const f = first;
21893
- verdicts.push({ header, field: field2, refused, judged, firstWhy: f.why, firstLine: f.line, firstRaw: f.raw });
21894
- }
21918
+ for (const c of mine) cells.push({ ...c, judged, field: field2 });
21919
+ }
21920
+ return cells;
21921
+ }
21922
+ function checkImportRules(fields, rows, mapping, choiceOptions, relationNames) {
21923
+ const verdicts = /* @__PURE__ */ new Map();
21924
+ for (const c of refusedCellsOf(fields, rows, mapping, choiceOptions, relationNames)) {
21925
+ const have = verdicts.get(c.header);
21926
+ if (have) have.refused += 1;
21927
+ else verdicts.set(c.header, { header: c.header, field: c.field, refused: 1, judged: c.judged, firstWhy: c.why, firstLine: c.line, firstRaw: c.raw });
21895
21928
  }
21896
- return verdicts;
21929
+ return [...verdicts.values()];
21897
21930
  }
21898
21931
 
21899
21932
  // src/ImportWizard.tsx
@@ -22305,7 +22338,7 @@ function ImportWizard({ tableId, source, onDone, onWrote, autoRun, className })
22305
22338
  },
22306
22339
  [client, openRun]
22307
22340
  );
22308
- const declared2 = fields.data ?? [];
22341
+ const declared2 = (0, import_react122.useMemo)(() => fields.data ?? [], [fields.data]);
22309
22342
  const titleKey = table.data?.title_field ?? null;
22310
22343
  const titleHeader = titleFrom ?? (titleKey ? Object.entries(mapping).find(([h, key]) => key === titleKey && h !== TITLE_COPY)?.[0] ?? null : null);
22311
22344
  const distinctRows = (0, import_react122.useMemo)(() => {
@@ -22599,6 +22632,12 @@ function ImportWizard({ tableId, source, onDone, onWrote, autoRun, className })
22599
22632
  () => exactDuplicateRows(effectiveRows, (plan?.columns ?? []).map((c) => c.header).filter((h) => !leftOut.has(h))),
22600
22633
  [effectiveRows, plan, leftOut]
22601
22634
  );
22635
+ const [goAheadAnyway, setGoAheadAnyway] = (0, import_react122.useState)(false);
22636
+ const refusedCells = (0, import_react122.useMemo)(
22637
+ () => goAheadAnyway ? refusedCellsOf(declared2, effectiveRows, mapping, choiceIndex, relationIndex) : [],
22638
+ [goAheadAnyway, declared2, effectiveRows, mapping, choiceIndex, relationIndex]
22639
+ );
22640
+ const [keptAside, setKeptAside] = (0, import_react122.useState)([]);
22602
22641
  const rowsToSend = (0, import_react122.useCallback)(
22603
22642
  (index) => {
22604
22643
  const repeated = new Set(skipRepeats ? repeats.map((r) => r.row) : []);
@@ -22608,6 +22647,7 @@ function ImportWizard({ tableId, source, onDone, onWrote, autoRun, className })
22608
22647
  if (repeated.has(at)) return;
22609
22648
  const out = {};
22610
22649
  for (const [header, value] of Object.entries(row)) if (!leftOut.has(header)) out[header] = value;
22650
+ for (const c of refusedCells) if (c.line === at + 1 && c.header in out) out[c.header] = "";
22611
22651
  if (titleFrom) out[TITLE_COPY] = String(row[titleFrom] ?? "").trim();
22612
22652
  for (const { header, field: field2 } of mappedRelations) {
22613
22653
  const named = index.get(field2.key);
@@ -22637,7 +22677,7 @@ function ImportWizard({ tableId, source, onDone, onWrote, autoRun, className })
22637
22677
  return { rows: sent, lines };
22638
22678
  },
22639
22679
  // eslint-disable-next-line react-hooks/exhaustive-deps -- choiceFor reads unlinkedChoice
22640
- [effectiveRows, combined, titleFrom, leftOut, mappedRelations, unlinkedChoice, dateAsks, dateMiss, repeats, skipRepeats]
22680
+ [effectiveRows, combined, titleFrom, leftOut, mappedRelations, unlinkedChoice, dateAsks, dateMiss, repeats, skipRepeats, refusedCells]
22641
22681
  );
22642
22682
  const run = (0, import_react122.useCallback)(async () => {
22643
22683
  if (!plan) return;
@@ -22705,6 +22745,7 @@ function ImportWizard({ tableId, source, onDone, onWrote, autoRun, className })
22705
22745
  index = reread;
22706
22746
  }
22707
22747
  const { rows: sendRows, lines: sendLines } = rowsToSend(index);
22748
+ setKeptAside(refusedCells.map(({ header, line, raw, why }) => ({ header, line, raw, why })));
22708
22749
  setSentLines(sendLines);
22709
22750
  setRepeatsSkipped(effectiveRows.length - sendRows.length);
22710
22751
  let sendMapping = mapping;
@@ -22838,7 +22879,7 @@ function ImportWizard({ tableId, source, onDone, onWrote, autoRun, className })
22838
22879
  setPhase("done");
22839
22880
  onWrote?.(finished.data.rows_written);
22840
22881
  void loadRuns();
22841
- }, [newColumns, makesColumns, kindChoice, guessedKind, addChoiceWords, client, dedupeKey, effectiveRows, fileBytes, fileHash, fileName, format, leftOut, loadRuns, mappedRelations, mapping, newChoiceWords, nudge, onDuplicate, onWrote, plan, readRelationNames, relationAsks, relationIndex, rights.admin, rowsToSend, tableId, unmapped, unlinkedChoice]);
22882
+ }, [newColumns, makesColumns, kindChoice, guessedKind, addChoiceWords, client, dedupeKey, effectiveRows, fileBytes, fileHash, fileName, format, leftOut, loadRuns, mappedRelations, mapping, newChoiceWords, nudge, onDuplicate, onWrote, plan, readRelationNames, refusedCells, relationAsks, relationIndex, rights.admin, rowsToSend, tableId, unmapped, unlinkedChoice]);
22842
22883
  const counts = (0, import_react122.useMemo)(() => {
22843
22884
  let landed = 0;
22844
22885
  let duplicate = 0;
@@ -22867,8 +22908,8 @@ function ImportWizard({ tableId, source, onDone, onWrote, autoRun, className })
22867
22908
  );
22868
22909
  const ruleVerdictOf = (0, import_react122.useMemo)(() => new Map(ruleVerdicts.map((v) => [v.header, v])), [ruleVerdicts]);
22869
22910
  const refusedByRules = ruleVerdicts.reduce((n, v) => n + v.refused, 0);
22870
- const [goAheadAnyway, setGoAheadAnyway] = (0, import_react122.useState)(false);
22871
- (0, import_react122.useEffect)(() => setGoAheadAnyway(false), [ruleVerdicts]);
22911
+ const verdictKey = ruleVerdicts.map((v) => `${v.header}:${v.refused}/${v.judged}`).join("|");
22912
+ (0, import_react122.useEffect)(() => setGoAheadAnyway(false), [verdictKey]);
22872
22913
  const [autoRan, setAutoRan] = (0, import_react122.useState)(false);
22873
22914
  (0, import_react122.useEffect)(() => {
22874
22915
  if (!autoRun || autoRan || !source || phase !== "ready" || !plan) return;
@@ -23458,6 +23499,23 @@ function ImportWizard({ tableId, source, onDone, onWrote, autoRun, className })
23458
23499
  ] })
23459
23500
  }
23460
23501
  ) }) : null,
23502
+ afterWrite && keptAside.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime77.jsxs)("div", { "data-import-kept-aside": "", className: "flex flex-col gap-0.5", children: [
23503
+ /* @__PURE__ */ (0, import_jsx_runtime77.jsxs)("p", { children: [
23504
+ keptAside.length,
23505
+ " cell",
23506
+ keptAside.length === 1 ? " was" : "s were",
23507
+ " kept aside and left empty; every other value in those rows was written."
23508
+ ] }),
23509
+ /* @__PURE__ */ (0, import_jsx_runtime77.jsx)("ul", { className: "flex flex-col gap-0.5 text-muted-foreground", children: keptAside.map((c) => /* @__PURE__ */ (0, import_jsx_runtime77.jsxs)("li", { children: [
23510
+ c.header,
23511
+ ", ",
23512
+ source ? `row ${c.line}` : `line ${lineInYourFile(c.line)}`,
23513
+ ": ",
23514
+ c.raw,
23515
+ " \u2014 ",
23516
+ c.why
23517
+ ] }, `${c.header}:${c.line}`)) })
23518
+ ] }) : null,
23461
23519
  repeatsSkipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime77.jsxs)("p", { className: "text-muted-foreground", "data-import-repeats-skipped": "", children: [
23462
23520
  repeatsSkipped,
23463
23521
  " repeated row",
@@ -39885,6 +39943,9 @@ function settingsToCopy(source, target) {
39885
39943
  const same3 = source.layout === target.layout;
39886
39944
  return {
39887
39945
  where: source.where ?? null,
39946
+ // The flat filter map a view may carry instead of (or beside) `where`: every stored filter shape travels.
39947
+ filters: source.filters ?? null,
39948
+ // saveViewPatch sends null to the door, which clears it
39888
39949
  sorts: source.sorts ?? [],
39889
39950
  groupField: source.groupField ?? null,
39890
39951
  presentation: look,
@@ -39919,7 +39980,7 @@ function SortableRow({ id, disabled, children }) {
39919
39980
  }
39920
39981
  );
39921
39982
  }
39922
- function ViewBar({ tableId, activeViewId, onActiveView, seed, settings, onViews, initialViews, className }) {
39983
+ function ViewBar({ tableId, activeViewId, onActiveView, seed, settings, onViews, onSettingsCopied, initialViews, className }) {
39923
39984
  const client = (0, import_react233.useRecordsClient)();
39924
39985
  const claimSeed = useSeedGuard();
39925
39986
  const table = (0, import_react234.useTable)(tableId);
@@ -40092,6 +40153,7 @@ function ViewBar({ tableId, activeViewId, onActiveView, seed, settings, onViews,
40092
40153
  return;
40093
40154
  }
40094
40155
  setCopying(false);
40156
+ onSettingsCopied?.(into.id);
40095
40157
  await load();
40096
40158
  notify?.success(`Copied settings from \u201C${source.name}\u201D`);
40097
40159
  }
@@ -54534,6 +54596,14 @@ function TablePage({
54534
54596
  }
54535
54597
  },
54536
54598
  onViews: setViews,
54599
+ onSettingsCopied: (copiedId) => {
54600
+ setAddressResetEpoch((n) => n + 1);
54601
+ setLooks((all) => {
54602
+ const { [copiedId]: _gone, ...rest } = all ?? {};
54603
+ return rest;
54604
+ });
54605
+ void keepLook(client, tableId, copiedId, null);
54606
+ },
54537
54607
  ...seededViews && viewsEpoch === 0 ? { initialViews: seededViews } : {},
54538
54608
  seed: seedViews ?? [defaultView(tableId)],
54539
54609
  settings: view && main === "records" && shownLayout === "grid" ? /* @__PURE__ */ (0, import_jsx_runtime178.jsxs)("div", { className: "flex flex-col gap-0.5 px-1 py-1", role: "radiogroup", "aria-label": FOOTER_WORDS.label, children: [