@ai-matrx/records-ui 0.93.33 → 0.93.37

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.js CHANGED
@@ -5458,16 +5458,26 @@ import { tableShapeChanged, useRecordsClient as useRecordsClient6 } from "@ai-ma
5458
5458
  import { Button as Button12, Select as Select3, SelectContent as SelectContent3, SelectItem as SelectItem3, SelectTrigger as SelectTrigger3, SelectValue as SelectValue3 } from "@ai-matrx/design-system";
5459
5459
  import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
5460
5460
  var RETYPE_TARGETS = [
5461
- { behaviour: "text", label: "Text", holds: "words" },
5462
- { behaviour: "range", label: "Number", holds: "a number" },
5463
- { behaviour: "boolean", label: "Yes / no", holds: "a yes or a no" }
5461
+ { id: "text", behaviour: "text", kind: null, label: "Text", holds: "words" },
5462
+ { id: "number", behaviour: "range", kind: "number", label: "Number", holds: "a number" },
5463
+ // A DATE is a `range` of kind date: `migrate_retype` alone cannot say "date" (it keeps the old
5464
+ // kind, so every date would be kept aside), so this one goes through `custom.field_update`'s own
5465
+ // retype arm (`parity_type: datetime`), which converts on the SAME trigger and keeps what does not
5466
+ // fit in `_retired` with the reason — measured on the live store 2026-09-28.
5467
+ { id: "date", behaviour: "range", kind: "date", label: "Date", holds: "a date" },
5468
+ { id: "checkbox", behaviour: "boolean", kind: null, label: "Yes / no", holds: "a yes or a no" }
5464
5469
  ];
5465
5470
  var PREVIEW_GROUPS = 1e3;
5466
5471
  function retypeableFrom(field2) {
5467
5472
  const behaviour = String(field2.type);
5468
- if (behaviour !== "text" && behaviour !== "range" && behaviour !== "boolean") return false;
5473
+ return behaviour === "text" || behaviour === "range" || behaviour === "boolean";
5474
+ }
5475
+ function currentTarget(field2) {
5469
5476
  const kind = String(editorKindFor(field2));
5470
- return kind !== "datetime" && kind !== "date";
5477
+ if (String(field2.type) === "boolean") return "checkbox";
5478
+ if (kind === "datetime" || kind === "date") return "date";
5479
+ if (String(field2.type) === "range") return "number";
5480
+ return "text";
5471
5481
  }
5472
5482
  function FieldRetype({ field: field2, tableId, onRetyped }) {
5473
5483
  const client = useRecordsClient6();
@@ -5478,7 +5488,7 @@ function FieldRetype({ field: field2, tableId, onRetyped }) {
5478
5488
  const [busy, setBusy] = useState17(false);
5479
5489
  const [said, setSaid] = useState17(null);
5480
5490
  const name = fieldName(field2);
5481
- const targets = RETYPE_TARGETS.filter((t) => t.behaviour !== String(field2.type));
5491
+ const targets = RETYPE_TARGETS.filter((t) => t.id !== currentTarget(field2));
5482
5492
  useEffect14(() => {
5483
5493
  if (!to) return;
5484
5494
  let cancelled = false;
@@ -5493,7 +5503,7 @@ function FieldRetype({ field: field2, tableId, onRetyped }) {
5493
5503
  return;
5494
5504
  }
5495
5505
  const groups = answer.data.map((row) => ({ value: row.groups[field2.key] ?? null, count: row.row_count }));
5496
- setPreview(previewRetype({ type: to.behaviour }, groups, answer.data.length >= PREVIEW_GROUPS));
5506
+ setPreview(previewRetype({ type: to.behaviour, kind: to.kind }, groups, answer.data.length >= PREVIEW_GROUPS));
5497
5507
  });
5498
5508
  return () => {
5499
5509
  cancelled = true;
@@ -5503,22 +5513,35 @@ function FieldRetype({ field: field2, tableId, onRetyped }) {
5503
5513
  return /* @__PURE__ */ jsxs18("p", { className: "text-[11px] text-muted-foreground", "data-records-retype": "not-here", children: [
5504
5514
  name,
5505
5515
  " holds ",
5506
- String(editorKindFor(field2)) === "datetime" ? "dates" : "records picked from a list or another table",
5507
- ", so what it holds is not changed here \u2014 add a new column and move the values across."
5516
+ String(field2.type) === "formula" ? "a value the table works out" : "records picked from a list or another table",
5517
+ ", and the store keeps those as record ids \u2014 turned into words or numbers they would read as ids, so it is not changed here."
5508
5518
  ] });
5509
5519
  }
5510
5520
  const change = async () => {
5511
5521
  if (!to) return;
5512
5522
  setBusy(true);
5513
5523
  setRefusal(null);
5514
- const done = await client.migrateRetype({ field_id: field2.id, to: to.behaviour });
5515
- setBusy(false);
5516
- if (!done.ok) {
5517
- setRefusal(done.error);
5518
- return;
5524
+ let holding;
5525
+ let aside;
5526
+ if (to.id === "date") {
5527
+ const done = await client.fieldUpdate({ field_id: field2.id, patch: { parity_type: "datetime" } });
5528
+ setBusy(false);
5529
+ if (!done.ok) {
5530
+ setRefusal(done.error);
5531
+ return;
5532
+ }
5533
+ holding = preview?.converts ?? 0;
5534
+ aside = preview?.keptAside ?? 0;
5535
+ } else {
5536
+ const done = await client.migrateRetype({ field_id: field2.id, to: to.behaviour });
5537
+ setBusy(false);
5538
+ if (!done.ok) {
5539
+ setRefusal(done.error);
5540
+ return;
5541
+ }
5542
+ holding = done.data.records_still_holding_a_value ?? 0;
5543
+ aside = done.data.values_in_retired_for_this_field ?? 0;
5519
5544
  }
5520
- const holding = done.data.records_still_holding_a_value ?? 0;
5521
- const aside = done.data.values_in_retired_for_this_field ?? 0;
5522
5545
  setSaid(
5523
5546
  `${name} now holds ${to.holds}. ${holding} ${holding === 1 ? "record holds" : "records hold"} a value` + (aside > 0 ? `; ${aside} ${aside === 1 ? "value that did not fit was" : "values that did not fit were"} kept with ${aside === 1 ? "its record" : "their records"}, with the reason.` : ".")
5524
5547
  );
@@ -5529,9 +5552,9 @@ function FieldRetype({ field: field2, tableId, onRetyped }) {
5529
5552
  };
5530
5553
  return /* @__PURE__ */ jsxs18("div", { className: "flex flex-col gap-1.5", "data-records-retype": "", children: [
5531
5554
  /* @__PURE__ */ jsxs18("div", { className: "flex flex-wrap items-center gap-2", children: [
5532
- /* @__PURE__ */ jsxs18(Select3, { value: to?.behaviour ?? "", onValueChange: (next) => setTo(targets.find((t) => t.behaviour === next) ?? null), children: [
5555
+ /* @__PURE__ */ jsxs18(Select3, { value: to?.id ?? "", onValueChange: (next) => setTo(targets.find((t) => t.id === next) ?? null), children: [
5533
5556
  /* @__PURE__ */ jsx22(SelectTrigger3, { size: "sm", "aria-label": "Change what this column holds", className: "w-auto min-w-[12rem]", children: /* @__PURE__ */ jsx22(SelectValue3, { placeholder: "Change what this column holds" }) }),
5534
- /* @__PURE__ */ jsx22(SelectContent3, { children: targets.map((t) => /* @__PURE__ */ jsx22(SelectItem3, { value: t.behaviour, children: t.label }, t.behaviour)) })
5557
+ /* @__PURE__ */ jsx22(SelectContent3, { children: targets.map((t) => /* @__PURE__ */ jsx22(SelectItem3, { value: t.id, children: t.label }, t.id)) })
5535
5558
  ] }),
5536
5559
  to ? /* @__PURE__ */ jsx22(Button12, { size: "sm", disabled: busy || asking || preview === null, onClick: () => void change(), "data-records-retype-go": "", children: busy ? "Changing\u2026" : `Change to ${to.label}` }) : null,
5537
5560
  to ? /* @__PURE__ */ jsx22(Button12, { size: "sm", variant: "ghost", onClick: () => setTo(null), disabled: busy, children: "Cancel" }) : null
@@ -9525,7 +9548,10 @@ function columnForField(field2, editing, mayWriteRow, whyNotRow, look, onAskWhoC
9525
9548
  // The type is in the header's tooltip rather than a second header row: one
9526
9549
  // row of headers, always — and it is the parity type's own WORD, not a
9527
9550
  // token (`Long text`, never `long_text`).
9528
- className: "align-top",
9551
+ // Top-aligned where a value may wrap to several lines; on a phone a cell never wraps (one line,
9552
+ // scrolled sideways) and the row is as tall as its 44 px tick box, so the value sits in the
9553
+ // middle beside the box — top-aligned it sat ~10 px above it (merged-grid review 2, 390 px).
9554
+ className: "sm:align-top max-sm:align-middle",
9529
9555
  ...{ title: fieldTypeLabel(field2) }
9530
9556
  };
9531
9557
  }
@@ -9555,6 +9581,12 @@ function PastePreview({
9555
9581
  "aria-label": "What this paste will write",
9556
9582
  "data-matrx-paste-preview": true,
9557
9583
  className: "fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4",
9584
+ onKeyDown: (event) => {
9585
+ if (event.key !== "Escape") return;
9586
+ event.preventDefault();
9587
+ event.stopPropagation();
9588
+ if (!busy) onCancel();
9589
+ },
9558
9590
  children: /* @__PURE__ */ jsxs30("div", { className: "flex max-h-[80vh] w-full max-w-2xl flex-col gap-3 overflow-auto rounded-lg border bg-background p-4 shadow-lg", children: [
9559
9591
  /* @__PURE__ */ jsxs30("div", { className: "flex items-baseline justify-between gap-3", children: [
9560
9592
  /* @__PURE__ */ jsx34("h2", { className: "text-sm font-medium", children: outcome ? "What was written" : "Before this is written" }),
@@ -9630,11 +9662,13 @@ function PastePreview({
9630
9662
  ". Add the columns first and paste again."
9631
9663
  ] }) : null,
9632
9664
  /* @__PURE__ */ jsxs30("div", { className: "flex items-center justify-end gap-2 pt-1", children: [
9633
- /* @__PURE__ */ jsx34(Button22, { size: "sm", variant: "ghost", onClick: onCancel, disabled: busy, children: "Cancel" }),
9665
+ /* @__PURE__ */ jsx34(Button22, { size: "sm", variant: "ghost", onClick: onCancel, disabled: busy, autoFocus: nothingToDo, children: "Cancel" }),
9634
9666
  /* @__PURE__ */ jsx34(
9635
9667
  Button22,
9636
9668
  {
9637
9669
  size: "sm",
9670
+ autoFocus: !nothingToDo,
9671
+ "data-matrx-paste-confirm": "",
9638
9672
  disabled: busy || nothingToDo,
9639
9673
  onClick: () => {
9640
9674
  setBusy(true);
@@ -9654,7 +9688,7 @@ function Outcome({ outcome, onClose }) {
9654
9688
  return /* @__PURE__ */ jsxs30(Fragment11, { children: [
9655
9689
  /* @__PURE__ */ jsx34("p", { className: "text-sm", "data-matrx-paste-outcome": whole ? "whole" : "partial", children: whole ? `All ${outcome.readBack} ${outcome.readBack === 1 ? "value is" : "values are"} in the table \u2014 read back out of it, not just accepted.` : `${outcome.readBack} of ${outcome.readBack + outcome.missing} values are in the table. The rest are not, and nothing was lost from what you copied \u2014 it is still on your clipboard.` }),
9656
9690
  outcome.refusals.length > 0 ? /* @__PURE__ */ jsx34("ul", { className: "flex flex-col gap-1", children: outcome.refusals.slice(0, 8).map((why, index) => /* @__PURE__ */ jsx34("li", { className: "text-xs text-muted-foreground", children: why }, index)) }) : null,
9657
- /* @__PURE__ */ jsx34("div", { className: "flex justify-end pt-1", children: /* @__PURE__ */ jsx34(Button22, { size: "sm", onClick: onClose, children: "Close" }) })
9691
+ /* @__PURE__ */ jsx34("div", { className: "flex justify-end pt-1", children: /* @__PURE__ */ jsx34(Button22, { size: "sm", onClick: onClose, autoFocus: true, children: "Close" }) })
9658
9692
  ] });
9659
9693
  }
9660
9694
  function PlanTable({ plan }) {