@ai-matrx/records-ui 0.101.11 → 0.101.14

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.d.cts CHANGED
@@ -6699,6 +6699,7 @@ declare const MOST_NEW_COLUMNS = 40;
6699
6699
  * The column NAMES a person typed or pasted, in order, each once. Empty when there are none.
6700
6700
  * A tab means a spreadsheet header row; several lines mean a list; otherwise one line split on
6701
6701
  * commas, semicolons, pipes and a last "and" — after "with" or a colon when it is a sentence.
6702
+ * Only OUTSIDE brackets: "Status (choice: a, b)" is one column.
6702
6703
  */
6703
6704
  declare function columnNamesFrom(text: string): string[];
6704
6705
  /** What a column of this NAME most likely holds. Text when nothing in the name says otherwise. */
package/dist/index.d.ts CHANGED
@@ -6699,6 +6699,7 @@ declare const MOST_NEW_COLUMNS = 40;
6699
6699
  * The column NAMES a person typed or pasted, in order, each once. Empty when there are none.
6700
6700
  * A tab means a spreadsheet header row; several lines mean a list; otherwise one line split on
6701
6701
  * commas, semicolons, pipes and a last "and" — after "with" or a colon when it is a sentence.
6702
+ * Only OUTSIDE brackets: "Status (choice: a, b)" is one column.
6702
6703
  */
6703
6704
  declare function columnNamesFrom(text: string): string[];
6704
6705
  /** What a column of this NAME most likely holds. Text when nothing in the name says otherwise. */
package/dist/index.js CHANGED
@@ -13375,6 +13375,44 @@ function replanPastedBlock(plan, args, change = {}) {
13375
13375
  droppedLines: (plan.droppedLines ?? 0) + dropped
13376
13376
  });
13377
13377
  }
13378
+ function storedValueAgrees(field2, stored, written, options) {
13379
+ const empty = (v) => v === void 0 || v === null || v === "";
13380
+ if (empty(stored) || empty(written)) return empty(stored) && empty(written);
13381
+ if (field2 !== void 0) {
13382
+ if (isDateField(field2)) {
13383
+ if (hasTimePart(written)) {
13384
+ const a = new Date(stored).getTime();
13385
+ const b = new Date(written).getTime();
13386
+ if (!Number.isNaN(a) && !Number.isNaN(b)) return a === b;
13387
+ } else {
13388
+ const dayOf = (v) => typeof v === "string" && /^\d{4}-\d{2}-\d{2}/.test(v.trim()) ? v.trim().slice(0, 10) : calendarDayOf(v);
13389
+ const a = dayOf(stored);
13390
+ const b = dayOf(written);
13391
+ if (a !== null && b !== null) return a === b;
13392
+ }
13393
+ }
13394
+ const kind = editorKindFor(field2);
13395
+ if (kind === "number" || kind === "currency" || kind === "percent") {
13396
+ const a = typeof stored === "number" ? stored : Number(String(stored).trim());
13397
+ const b = typeof written === "number" ? written : Number(String(written).trim());
13398
+ if (Number.isFinite(a) && Number.isFinite(b)) return a === b;
13399
+ }
13400
+ if (options !== void 0 && typeof stored === "string" && typeof written === "string") {
13401
+ const option = (v) => options.byWord.get(v.trim().toLowerCase()) ?? v.trim().toLowerCase();
13402
+ return option(stored) === option(written);
13403
+ }
13404
+ }
13405
+ return JSON.stringify(stored) === JSON.stringify(written);
13406
+ }
13407
+ function columnsTheGridTurnedAway(turnedAway, columnKeys, columnName, already) {
13408
+ const said = /* @__PURE__ */ new Map();
13409
+ for (const refused of turnedAway) {
13410
+ if (!columnKeys.includes(refused.columnId)) continue;
13411
+ if (already.some((r) => r.column === columnName(refused.columnId))) continue;
13412
+ said.set(refused.columnId, refused.reason);
13413
+ }
13414
+ return [...said].map(([columnId, why]) => ({ fromLine: 0, column: columnName(columnId), raw: "", why }));
13415
+ }
13378
13416
 
13379
13417
  // src/gridControls.tsx
13380
13418
  import { Fragment as Fragment14, jsx as jsx48, jsxs as jsxs44 } from "react/jsx-runtime";
@@ -15169,12 +15207,7 @@ function PastePreview({
15169
15207
  plan.refusals.length > 0 ? /* @__PURE__ */ jsxs47("div", { "data-matrx-paste-refusals": true, className: "flex flex-col gap-1", children: [
15170
15208
  /* @__PURE__ */ jsx51("p", { className: "text-xs font-medium", children: plan.refusals.length === 1 ? "One cell will be left alone:" : `${plan.refusals.length} cells will be left alone:` }),
15171
15209
  /* @__PURE__ */ jsx51("ul", { className: "flex flex-col gap-1", children: plan.refusals.slice(0, 12).map((refusal3, index) => /* @__PURE__ */ jsxs47("li", { className: "text-xs text-muted-foreground", children: [
15172
- /* @__PURE__ */ jsxs47("span", { className: "font-medium", children: [
15173
- "Row ",
15174
- refusal3.fromLine,
15175
- ", ",
15176
- refusal3.column
15177
- ] }),
15210
+ /* @__PURE__ */ jsx51("span", { className: "font-medium", children: refusal3.fromLine > 0 ? `Row ${refusal3.fromLine}, ${refusal3.column}` : refusal3.column }),
15178
15211
  " \u2014 ",
15179
15212
  refusal3.why
15180
15213
  ] }, index)) }),
@@ -21006,19 +21039,9 @@ function GridBody({
21006
21039
  pastRightEdge: sheetPlan.pastRightEdge,
21007
21040
  ...headed ? { header: { unmatched: headed.unmatched } } : {}
21008
21041
  });
21009
- const notWritable = /* @__PURE__ */ new Map();
21010
- for (const refused of sheetPlan.refusals) {
21011
- if (built.refusals.some((r) => r.column === columnLabel(fieldList, refused.columnId))) continue;
21012
- notWritable.set(refused.columnId, refused.reason);
21013
- }
21014
- for (const [columnId, why] of notWritable) {
21015
- built.refusals.push({
21016
- fromLine: 0,
21017
- column: columnLabel(fieldList, columnId),
21018
- raw: "",
21019
- why
21020
- });
21021
- }
21042
+ built.refusals.push(
21043
+ ...columnsTheGridTurnedAway(sheetPlan.refusals, columnKeys, (id) => columnLabel(fieldList, id), built.refusals)
21044
+ );
21022
21045
  if (merged && built.cells > 0 && built.creates === 0 && built.refusals.length === 0 && built.pastRightEdge === 0 && (built.header?.unmatched.length ?? 0) === 0 && built.unmatchedOptions.length === 0) {
21023
21046
  const outcome = await pasteAtOnce.current(built);
21024
21047
  if (outcome.refusals.length > 0 || outcome.missing > 0) {
@@ -21119,20 +21142,21 @@ function GridBody({
21119
21142
  const reread = await client.listByIds({ table_id: tableId, ids: expected.map((e) => e.recordId) });
21120
21143
  let readBack = 0;
21121
21144
  let missing = 0;
21145
+ const fieldOf2 = new Map((fields.data ?? []).map((f) => [f.key, f]));
21122
21146
  const refusals = [];
21123
21147
  if (!reread.ok) refusals.push(`The rows were written and this could not read them back to check: ${refusalSentence(reread.error)}`);
21124
21148
  const seen = new Map((reread.ok ? reread.data : []).map((r) => [r.id, r.document ?? {}]));
21125
21149
  for (const entry of expected) {
21126
21150
  const stored = seen.get(entry.recordId);
21127
21151
  for (const [key, value] of Object.entries(entry.document)) {
21128
- if (stored !== void 0 && JSON.stringify(stored[key]) === JSON.stringify(value)) readBack += 1;
21152
+ if (stored !== void 0 && storedValueAgrees(fieldOf2.get(key), stored[key], value, choiceOptions.current.get(key))) readBack += 1;
21129
21153
  else missing += 1;
21130
21154
  }
21131
21155
  }
21132
21156
  records.reload();
21133
21157
  return { written: lines, readBack, missing, refusals };
21134
21158
  },
21135
- [rows, client, tableId, editing, undo, records, versions]
21159
+ [rows, client, tableId, editing, undo, records, versions, fields.data]
21136
21160
  );
21137
21161
  const pasteAtOnce = useRef28(applyPasteAsOne);
21138
21162
  pasteAtOnce.current = applyPasteAsOne;
@@ -21207,6 +21231,7 @@ function GridBody({
21207
21231
  }
21208
21232
  let readBack = 0;
21209
21233
  let missing = 0;
21234
+ const fieldOf2 = new Map((fields.data ?? []).map((f) => [f.key, f]));
21210
21235
  const seen = /* @__PURE__ */ new Map();
21211
21236
  const READ_BACK_PAGE = 500;
21212
21237
  for (let offset = 0; ; offset += READ_BACK_PAGE) {
@@ -21226,7 +21251,7 @@ function GridBody({
21226
21251
  for (const entry of expected) {
21227
21252
  const stored = seen.get(entry.recordId);
21228
21253
  for (const [key, value] of Object.entries(entry.document)) {
21229
- if (stored !== void 0 && JSON.stringify(stored[key]) === JSON.stringify(value)) {
21254
+ if (stored !== void 0 && storedValueAgrees(fieldOf2.get(key), stored[key], value, choiceOptions.current.get(key))) {
21230
21255
  readBack += 1;
21231
21256
  } else {
21232
21257
  missing += 1;
@@ -21235,7 +21260,7 @@ function GridBody({
21235
21260
  }
21236
21261
  records.reload();
21237
21262
  return { written, readBack, missing, refusals };
21238
- }, [pastePlan, client, tableId, records, merged, applyPasteAsOne, versions]);
21263
+ }, [pastePlan, client, tableId, records, merged, applyPasteAsOne, versions, fields.data]);
21239
21264
  const columnIds = useMemo35(() => ordered.map((field2) => field2.key), [ordered]);
21240
21265
  const lookWrites = useRef28(null);
21241
21266
  const writeLook = useCallback24(
@@ -22869,6 +22894,7 @@ import { withTablePlacement as withTablePlacement2 } from "@ai-matrx/records";
22869
22894
  import { BasicInput as BasicInput17, Button as Button49, Select as Select10, SelectContent as SelectContent10, SelectItem as SelectItem10, SelectTrigger as SelectTrigger10, SelectValue as SelectValue10, Skeleton as Skeleton6, Textarea as Textarea2, cn as cn39 } from "@ai-matrx/design-system";
22870
22895
 
22871
22896
  // src/newTableColumns.ts
22897
+ import { RESOLVABLE_FIELD_TYPE_WORDS } from "@ai-matrx/records/templates";
22872
22898
  var NEW_COLUMN_TYPES = [
22873
22899
  { type: "text", words: "Text" },
22874
22900
  { type: "rich_text", words: "Formatted text" },
@@ -22886,7 +22912,80 @@ var NEW_COLUMN_TYPES = [
22886
22912
  ];
22887
22913
  var MOST_NEW_COLUMNS = 40;
22888
22914
  var BULLET = /^\s*(?:[-*•]|\d+[.)])\s+/;
22889
- function columnNamesFrom(text) {
22915
+ var TYPE_WORD_ALIASES = {
22916
+ choice: "select",
22917
+ dropdown: "select",
22918
+ status: "select",
22919
+ list: "select",
22920
+ choices: "multi_select",
22921
+ tags: "multi_select",
22922
+ money: "currency",
22923
+ boolean: "checkbox",
22924
+ bool: "checkbox",
22925
+ "yes/no": "checkbox",
22926
+ "yes / no": "checkbox",
22927
+ link: "url",
22928
+ integer: "number",
22929
+ decimal: "number",
22930
+ float: "number",
22931
+ string: "text",
22932
+ long_text: "rich_text",
22933
+ "formatted text": "rich_text",
22934
+ "date and time": "datetime",
22935
+ "date time": "datetime"
22936
+ };
22937
+ function typeFromWord(raw) {
22938
+ const word = raw.trim().toLowerCase();
22939
+ if (word === "") return void 0;
22940
+ const type = TYPE_WORD_ALIASES[word] ?? NEW_COLUMN_TYPES.find((t) => t.type === word.replace(/[\s-]+/g, "_"))?.type;
22941
+ if (!type) return void 0;
22942
+ return RESOLVABLE_FIELD_TYPE_WORDS.includes(type === "date" ? "datetime" : type) ? type : void 0;
22943
+ }
22944
+ function maskBrackets(text) {
22945
+ let depth = 0;
22946
+ let out = "";
22947
+ for (const ch of text) {
22948
+ if (ch === "(" || ch === "[") {
22949
+ depth++;
22950
+ out += depth === 1 ? ch : "\0";
22951
+ } else if ((ch === ")" || ch === "]") && depth > 0) {
22952
+ depth--;
22953
+ out += depth === 0 ? ch : "\0";
22954
+ } else out += depth > 0 ? "\0" : ch;
22955
+ }
22956
+ return out;
22957
+ }
22958
+ function splitOutsideBrackets(text, sep) {
22959
+ const masked = maskBrackets(text);
22960
+ const parts = [];
22961
+ let from = 0;
22962
+ const re = new RegExp(sep.source, sep.flags.includes("g") ? sep.flags : sep.flags + "g");
22963
+ for (const m of masked.matchAll(re)) {
22964
+ if (m[0] === "") continue;
22965
+ parts.push(text.slice(from, m.index));
22966
+ from = m.index + m[0].length;
22967
+ }
22968
+ parts.push(text.slice(from));
22969
+ return parts;
22970
+ }
22971
+ function specFromPart(part) {
22972
+ const clean = (t) => t.replace(/^["'\s]+|["'.\s]+$/g, "").replace(/\s+/g, " ");
22973
+ const m = /^(.*?)\s*[(\[]([^()\[\]]*)[)\]]\s*$/.exec(part.trim());
22974
+ if (m) {
22975
+ const inner2 = m[2];
22976
+ const colon = inner2.indexOf(":");
22977
+ const type = typeFromWord(colon >= 0 ? inner2.slice(0, colon) : inner2);
22978
+ const name = clean(m[1]);
22979
+ if (type && name !== "") {
22980
+ const options = colon >= 0 ? [...new Set(inner2.slice(colon + 1).split(/\s*[,;|]\s*/).map(clean).filter((o) => o !== ""))] : void 0;
22981
+ return { label: name, type, ...options && options.length > 0 && (type === "select" || type === "multi_select") ? { options } : {} };
22982
+ }
22983
+ }
22984
+ const withoutPairs = part.replace(/\([^()\[\]]*\)|\[[^()\[\]]*\]/g, "");
22985
+ const label = /[()\[\]]/.test(withoutPairs) ? part.replace(/[()\[\]]/g, " ") : part;
22986
+ return { label: clean(label) };
22987
+ }
22988
+ function specsFrom(text) {
22890
22989
  const trimmed = text.trim();
22891
22990
  if (trimmed === "") return [];
22892
22991
  let parts;
@@ -22897,23 +22996,28 @@ function columnNamesFrom(text) {
22897
22996
  parts = trimmed.split(/\r?\n/).map((line) => line.replace(BULLET, ""));
22898
22997
  } else {
22899
22998
  let line = trimmed;
22900
- const colon = line.indexOf(":");
22999
+ const masked = maskBrackets(line);
23000
+ const colon = masked.indexOf(":");
22901
23001
  if (colon >= 0) line = line.slice(colon + 1);
22902
23002
  else {
22903
- const withAt = line.toLowerCase().lastIndexOf(" with ");
23003
+ const withAt = masked.toLowerCase().lastIndexOf(" with ");
22904
23004
  if (withAt >= 0) line = line.slice(withAt + " with ".length);
22905
23005
  }
22906
- parts = line.split(/\s*[,;|]\s*|\s+and\s+|\s*&\s*/i);
23006
+ parts = splitOutsideBrackets(line, /\s*[,;|]\s*|\s+and\s+|\s*&\s*/i);
22907
23007
  }
22908
- const names = parts.map((p) => p.replace(/^["'\s]+|["'.\s]+$/g, "").replace(/\s+/g, " ")).filter((p) => p !== "").map((p) => p === p.toLowerCase() ? p[0].toUpperCase() + p.slice(1) : p);
23008
+ const specs = parts.map(specFromPart).filter((c) => c.label !== "").map((c) => c.label === c.label.toLowerCase() ? { ...c, label: c.label[0].toUpperCase() + c.label.slice(1) } : c);
22909
23009
  const seen = /* @__PURE__ */ new Set();
22910
- const once = names.filter((n) => {
22911
- const k = n.toLowerCase();
23010
+ const once = specs.filter((c) => {
23011
+ const k = c.label.toLowerCase();
22912
23012
  if (seen.has(k)) return false;
22913
23013
  seen.add(k);
22914
23014
  return true;
22915
23015
  });
22916
- return uniqueLabels(once).slice(0, MOST_NEW_COLUMNS);
23016
+ const labels = uniqueLabels(once.map((c) => c.label));
23017
+ return once.map((c, i) => ({ ...c, label: labels[i] })).slice(0, MOST_NEW_COLUMNS);
23018
+ }
23019
+ function columnNamesFrom(text) {
23020
+ return specsFrom(text).map((c) => c.label);
22917
23021
  }
22918
23022
  var STATUS_CHOICES = ["Not started", "In progress", "Done"];
22919
23023
  var PRIORITY_CHOICES = ["Low", "Medium", "High"];
@@ -22938,7 +23042,9 @@ function columnFromName(label) {
22938
23042
  return { label, type: "text" };
22939
23043
  }
22940
23044
  function columnsFrom(text) {
22941
- return columnNamesFrom(text).map(columnFromName);
23045
+ return specsFrom(text).map(
23046
+ (c) => c.type ? { label: c.label, type: c.type, ...c.options && c.options.length > 0 ? { options: c.options } : {} } : columnFromName(c.label)
23047
+ );
22942
23048
  }
22943
23049
  function fieldsForNewColumns(columns) {
22944
23050
  const keys = /* @__PURE__ */ new Set();