@ai-matrx/records-ui 0.113.36 → 0.113.38

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/pickers.cjs CHANGED
@@ -1025,13 +1025,20 @@ var NOT_ANSWERED_YET = whatYouMayDo(null, false);
1025
1025
  var KERNEL_REASON = "This is one of the tables the platform ships, so its columns are not changed from a screen. Make your own table to change columns.";
1026
1026
  function whatYouMayDoWithTable(table, level, known) {
1027
1027
  const base = whatYouMayDo(level, known);
1028
- if (!table?.is_kernel) return base;
1028
+ if (!table?.is_kernel) return table && tableIsLocked(table) ? lockedStructure(base) : base;
1029
1029
  return {
1030
1030
  ...base,
1031
1031
  structure: false,
1032
1032
  why: (what) => what === "structure" ? KERNEL_REASON : base.why(what)
1033
1033
  };
1034
1034
  }
1035
+ function tableIsLocked(table) {
1036
+ return table?.metadata?.["locked"] === true;
1037
+ }
1038
+ var DATABASE_LOCKED_REASON = "This database is locked, so its columns and settings are read-only. Unlock it to change them.";
1039
+ function lockedStructure(base) {
1040
+ return { ...base, structure: false, why: (what) => what === "structure" ? DATABASE_LOCKED_REASON : base.why(what) };
1041
+ }
1035
1042
 
1036
1043
  // src/host.tsx
1037
1044
  var import_jsx_runtime4 = require("react/jsx-runtime");
@@ -4225,10 +4232,46 @@ function SourceArchivedNotice({ fieldId, tableId }) {
4225
4232
  }
4226
4233
 
4227
4234
  // src/Condition.tsx
4228
- var import_react31 = require("react");
4235
+ var import_react32 = require("react");
4229
4236
  var import_design_system21 = require("@ai-matrx/design-system");
4230
4237
  var import_records16 = require("@ai-matrx/records");
4231
- var import_react32 = require("@ai-matrx/records/react");
4238
+ var import_react33 = require("@ai-matrx/records/react");
4239
+
4240
+ // src/personTime.ts
4241
+ var import_react31 = require("react");
4242
+ var import_table_style2 = require("@ai-matrx/design-system/data-table/table-style");
4243
+ var zone = null;
4244
+ var listeners = /* @__PURE__ */ new Set();
4245
+ function getPersonTimeZone() {
4246
+ return zone;
4247
+ }
4248
+ function usePersonTimeZone() {
4249
+ return (0, import_react31.useSyncExternalStore)(
4250
+ (cb) => {
4251
+ listeners.add(cb);
4252
+ return () => listeners.delete(cb);
4253
+ },
4254
+ () => zone,
4255
+ () => null
4256
+ );
4257
+ }
4258
+ function personWallNow(at = /* @__PURE__ */ new Date()) {
4259
+ if (!zone) return at;
4260
+ const parts = new Intl.DateTimeFormat("en-US", {
4261
+ timeZone: zone,
4262
+ year: "numeric",
4263
+ month: "2-digit",
4264
+ day: "2-digit",
4265
+ hour: "2-digit",
4266
+ minute: "2-digit",
4267
+ second: "2-digit",
4268
+ hourCycle: "h23"
4269
+ }).formatToParts(at);
4270
+ const n = (t) => Number(parts.find((p) => p.type === t)?.value ?? 0);
4271
+ return new Date(n("year"), n("month") - 1, n("day"), n("hour"), n("minute"), n("second"));
4272
+ }
4273
+
4274
+ // src/Condition.tsx
4232
4275
  var import_jsx_runtime23 = require("react/jsx-runtime");
4233
4276
  var CONDITION_OPS = [
4234
4277
  { op: "eq", label: "is" },
@@ -4239,6 +4282,48 @@ var CONDITION_OPS = [
4239
4282
  { op: "gte", label: "is at least" },
4240
4283
  { op: "lte", label: "is at most" }
4241
4284
  ];
4285
+ var FilterTermsContext = (0, import_react32.createContext)(false);
4286
+ var RELATIVE_DAY_TERMS = [
4287
+ { kind: "today", label: "is today" },
4288
+ { kind: "tomorrow", label: "is tomorrow" },
4289
+ { kind: "yesterday", label: "is yesterday" },
4290
+ { kind: "this_week", label: "is this week" },
4291
+ { kind: "next_week", label: "is next week" },
4292
+ { kind: "last_week", label: "is last week" },
4293
+ { kind: "this_month", label: "is this month" },
4294
+ { kind: "next_month", label: "is next month" },
4295
+ { kind: "last_month", label: "is last month" },
4296
+ { kind: "before_today", label: "is before today" },
4297
+ { kind: "next_days", label: "is in the next \u2026 days", takesDays: true },
4298
+ { kind: "past_days", label: "is in the past \u2026 days", takesDays: true }
4299
+ ];
4300
+ function relativeDayNode(fieldId, kind, days) {
4301
+ const zone2 = getPersonTimeZone();
4302
+ const window2 = { relative: kind };
4303
+ if (RELATIVE_DAY_TERMS.find((t) => t.kind === kind)?.takesDays) window2["n"] = days ?? 7;
4304
+ if (zone2) window2["tz"] = zone2;
4305
+ return { op: "within", args: [{ field: fieldId }, { const: window2 }] };
4306
+ }
4307
+ function relativeDayOf(expr) {
4308
+ const node = asNode(expr);
4309
+ if (!node || node["op"] !== "within") return null;
4310
+ const [a, b] = argsOf(node);
4311
+ const window2 = asNode(b?.["const"]);
4312
+ if (!a || typeof a["field"] !== "string" || !window2 || typeof window2["relative"] !== "string") return null;
4313
+ const days = typeof window2["n"] === "number" ? window2["n"] : null;
4314
+ return { kind: window2["relative"], days, field: a["field"] };
4315
+ }
4316
+ function meNode(fieldId, negate) {
4317
+ return { op: negate ? "ne" : "eq", args: [{ field: fieldId }, { const: { me: true } }] };
4318
+ }
4319
+ function meOf(expr) {
4320
+ const node = asNode(expr);
4321
+ if (!node || node["op"] !== "eq" && node["op"] !== "ne") return null;
4322
+ const [a, b] = argsOf(node);
4323
+ const value = asNode(b?.["const"]);
4324
+ if (!a || typeof a["field"] !== "string" || !value || value["me"] !== true) return null;
4325
+ return { which: node["op"] === "eq" ? "me" : "not_me", field: a["field"] };
4326
+ }
4242
4327
  function pointsAtARecord(f) {
4243
4328
  return !!f?.field && f.field.type === "relation" && !!f.field.relation_target;
4244
4329
  }
@@ -4248,11 +4333,11 @@ function isChoiceColumn(f) {
4248
4333
  return kind === "select" || kind === "multi_select";
4249
4334
  }
4250
4335
  function ChoiceValue({ field, value, onChange }) {
4251
- const client = (0, import_react32.useOptionalRecordsClient)();
4252
- const [choices, setChoices] = (0, import_react31.useState)(null);
4253
- const [open, setOpen] = (0, import_react31.useState)(false);
4254
- const [search, setSearch] = (0, import_react31.useState)("");
4255
- (0, import_react31.useEffect)(() => {
4336
+ const client = (0, import_react33.useOptionalRecordsClient)();
4337
+ const [choices, setChoices] = (0, import_react32.useState)(null);
4338
+ const [open, setOpen] = (0, import_react32.useState)(false);
4339
+ const [search, setSearch] = (0, import_react32.useState)("");
4340
+ (0, import_react32.useEffect)(() => {
4256
4341
  if (!client) return;
4257
4342
  let cancelled = false;
4258
4343
  void (0, askOnce_exports.fieldOptionsOnce)(client, field.id).then((got) => {
@@ -4309,6 +4394,7 @@ function ChoiceValue({ field, value, onChange }) {
4309
4394
  }
4310
4395
  function conditionIsSimple(expr) {
4311
4396
  if (expr === null || expr === void 0) return true;
4397
+ if (relativeDayOf(expr) !== null || meOf(expr) !== null) return true;
4312
4398
  const node = expr;
4313
4399
  if (typeof node.op !== "string" || !CONDITION_OPS.some((c) => c.op === node.op)) return false;
4314
4400
  if (!Array.isArray(node.args)) return false;
@@ -4353,6 +4439,14 @@ function conditionInWords(expr, fields) {
4353
4439
  const args = Array.isArray(n["args"]) ? n["args"] : [];
4354
4440
  if (op === "not") return `not ${walk(args[0])}`;
4355
4441
  if (op === "present") return `${walk(args[0])} has been answered`;
4442
+ const dayRange = relativeDayOf(n);
4443
+ if (dayRange) {
4444
+ const term = RELATIVE_DAY_TERMS.find((t) => t.kind === dayRange.kind);
4445
+ const said3 = term ? term.label.replace("\u2026", String(dayRange.days ?? 7)) : "is in a day range";
4446
+ return `${name(dayRange.field)} ${said3}`;
4447
+ }
4448
+ const reader = meOf(n);
4449
+ if (reader) return `${name(reader.field)} ${reader.which === "me" ? "is me" : "is not me"}`;
4356
4450
  const word = said2[op] ?? op;
4357
4451
  if (op === "and" || op === "or") return args.map(walk).join(` ${word} `);
4358
4452
  if (args.length === 2) return `${walk(args[0])} ${word} ${walk(args[1])}`;
@@ -4374,11 +4468,31 @@ function conditionValue(typed) {
4374
4468
  }
4375
4469
  function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", className }) {
4376
4470
  const node = expr ?? null;
4377
- const op = node?.op ?? "";
4471
+ const filterTerms = (0, import_react32.useContext)(FilterTermsContext);
4472
+ const relative = relativeDayOf(expr);
4473
+ const reader = meOf(expr);
4474
+ const op = relative ? `rel:${relative.kind}` : reader ? reader.which : node?.op ?? "";
4378
4475
  const onField = node?.args?.[0]?.field ?? "";
4379
- const value = node?.args?.[1]?.const ?? "";
4476
+ const value = relative || reader ? "" : node?.args?.[1]?.const ?? "";
4380
4477
  const chosen = fields.find((f) => f.id === onField);
4478
+ const chosenKind = chosen?.field ? String(editorKindFor(chosen.field)) : "";
4479
+ const extraTerms = !filterTerms ? [] : chosenKind === "datetime" ? RELATIVE_DAY_TERMS.map((t) => ({ op: `rel:${t.kind}`, label: t.label })) : chosenKind === "member" ? [{ op: "me", label: "is me" }, { op: "not_me", label: "is not me" }] : [];
4480
+ const relativeTerm = relative ? RELATIVE_DAY_TERMS.find((t) => t.kind === relative.kind) : void 0;
4381
4481
  function write(next) {
4482
+ const picked = next.op;
4483
+ const fieldNow = next.field ?? onField;
4484
+ if (fieldNow && picked !== void 0 && (picked.startsWith("rel:") || picked === "me" || picked === "not_me")) {
4485
+ onChange(picked === "me" || picked === "not_me" ? meNode(fieldNow, picked === "not_me") : relativeDayNode(fieldNow, picked.slice(4), relative?.days ?? void 0));
4486
+ return;
4487
+ }
4488
+ if (fieldNow && next.days !== void 0 && relative) {
4489
+ onChange(relativeDayNode(fieldNow, relative.kind, next.days));
4490
+ return;
4491
+ }
4492
+ if (next.field !== void 0 && (relative || reader)) {
4493
+ onChange(next.field ? { op: CONDITION_OPS[0].op, args: [{ field: next.field }, { const: "" }] } : null);
4494
+ return;
4495
+ }
4382
4496
  const chosenOp = next.op ?? (op || (next.field ? CONDITION_OPS[0].op : ""));
4383
4497
  const chosenField = next.field ?? onField;
4384
4498
  const chosenValue = next.value ?? value;
@@ -4420,11 +4534,21 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
4420
4534
  className: "h-8 w-auto min-w-[6rem] text-xs",
4421
4535
  value: op,
4422
4536
  onChange: (next) => write({ op: next }),
4423
- options: CONDITION_OPS.map((c) => ({ value: c.op, label: c.label })),
4537
+ options: [...CONDITION_OPS, ...extraTerms].map((c) => ({ value: c.op, label: c.label })),
4424
4538
  attrs: { "data-condition": "comparison" }
4425
4539
  }
4426
4540
  ),
4427
- op === "present" ? null : pointsAtARecord(chosen) ? (
4541
+ relative ? relativeTerm?.takesDays ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
4542
+ import_design_system21.BasicInput,
4543
+ {
4544
+ className: "h-8 w-16 text-xs",
4545
+ type: "number",
4546
+ min: 0,
4547
+ "aria-label": "Number of days",
4548
+ value: String(relative.days ?? 7),
4549
+ onChange: (e) => write({ days: Math.max(0, Math.floor(Number(e.target.value) || 0)) })
4550
+ }
4551
+ ) : null : reader || op === "present" ? null : pointsAtARecord(chosen) ? (
4428
4552
  // A RECORD IS PICKED BY ITS WORDS, NEVER TYPED AS AN ID. The same picker the
4429
4553
  // cell uses, on the same `relation_words_many` the cell reads, so a filter and
4430
4554
  // the column it filters can never disagree about what a record is called. It
@@ -4519,7 +4643,7 @@ function ConditionGroup({
4519
4643
  className
4520
4644
  }) {
4521
4645
  const { joiner, args } = clausesOf(expr);
4522
- const [drafting, setDrafting] = (0, import_react31.useState)(null);
4646
+ const [drafting, setDrafting] = (0, import_react32.useState)(null);
4523
4647
  const nested = depth > 0;
4524
4648
  const replaceAt = (index, next) => {
4525
4649
  const nextArgs = args.slice();
@@ -4646,9 +4770,9 @@ function ConditionGroup({
4646
4770
  }
4647
4771
 
4648
4772
  // src/FieldRetype.tsx
4649
- var import_react33 = require("react");
4773
+ var import_react34 = require("react");
4650
4774
  var import_records17 = require("@ai-matrx/records");
4651
- var import_react34 = require("@ai-matrx/records/react");
4775
+ var import_react35 = require("@ai-matrx/records/react");
4652
4776
  var import_design_system22 = require("@ai-matrx/design-system");
4653
4777
  var import_jsx_runtime24 = require("react/jsx-runtime");
4654
4778
  var RETYPE_TARGETS = [
@@ -4685,17 +4809,17 @@ function currentTarget(field) {
4685
4809
  return "text";
4686
4810
  }
4687
4811
  function FieldRetype({ field, tableId, onRetyped }) {
4688
- const client = (0, import_react34.useRecordsClient)();
4812
+ const client = (0, import_react35.useRecordsClient)();
4689
4813
  const notify = useRecordsUi().notify;
4690
- const [to, setTo] = (0, import_react33.useState)(null);
4691
- const [preview, setPreview] = (0, import_react33.useState)(null);
4692
- const [asking, setAsking] = (0, import_react33.useState)(false);
4693
- const [refusal, setRefusal] = (0, import_react33.useState)(null);
4694
- const [busy, setBusy] = (0, import_react33.useState)(false);
4695
- const [said2, setSaid] = (0, import_react33.useState)(null);
4814
+ const [to, setTo] = (0, import_react34.useState)(null);
4815
+ const [preview, setPreview] = (0, import_react34.useState)(null);
4816
+ const [asking, setAsking] = (0, import_react34.useState)(false);
4817
+ const [refusal, setRefusal] = (0, import_react34.useState)(null);
4818
+ const [busy, setBusy] = (0, import_react34.useState)(false);
4819
+ const [said2, setSaid] = (0, import_react34.useState)(null);
4696
4820
  const name = fieldName(field);
4697
4821
  const targets = RETYPE_TARGETS.filter((t) => t.id !== currentTarget(field));
4698
- (0, import_react33.useEffect)(() => {
4822
+ (0, import_react34.useEffect)(() => {
4699
4823
  if (!to) return;
4700
4824
  let cancelled = false;
4701
4825
  setPreview(null);
@@ -4751,7 +4875,7 @@ function FieldRetype({ field, tableId, onRetyped }) {
4751
4875
  undo: async () => {
4752
4876
  const back = await client.migrateUndo({ migration_id: migration });
4753
4877
  if (!back.ok) throw new Error(refusalSentence(back.error));
4754
- (0, import_react34.tableShapeChanged)(tableId);
4878
+ (0, import_react35.tableShapeChanged)(tableId);
4755
4879
  onRetyped?.();
4756
4880
  }
4757
4881
  });
@@ -4762,7 +4886,7 @@ function FieldRetype({ field, tableId, onRetyped }) {
4762
4886
  );
4763
4887
  setTo(null);
4764
4888
  setPreview(null);
4765
- (0, import_react34.tableShapeChanged)(tableId);
4889
+ (0, import_react35.tableShapeChanged)(tableId);
4766
4890
  onRetyped?.();
4767
4891
  };
4768
4892
  return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex flex-col gap-1.5", "data-records-retype": "", children: [
@@ -4794,9 +4918,9 @@ function FieldRetype({ field, tableId, onRetyped }) {
4794
4918
  }
4795
4919
 
4796
4920
  // src/FieldRulePreview.tsx
4797
- var import_react35 = require("react");
4921
+ var import_react36 = require("react");
4798
4922
  var import_records18 = require("@ai-matrx/records");
4799
- var import_react36 = require("@ai-matrx/records/react");
4923
+ var import_react37 = require("@ai-matrx/records/react");
4800
4924
  var import_jsx_runtime25 = require("react/jsx-runtime");
4801
4925
  var PREVIEW_GROUPS2 = 1e3;
4802
4926
  function ruleWords(rules) {
@@ -4814,14 +4938,14 @@ function groupValue(value, numbers) {
4814
4938
  return /^-?(\d+\.?\d*|\.\d+)([eE][-+]?\d+)?$/.test(text) ? Number(text) : value;
4815
4939
  }
4816
4940
  function FieldRulePreview({ field, tableId, rules }) {
4817
- const client = (0, import_react36.useRecordsClient)();
4818
- const [preview, setPreview] = (0, import_react35.useState)(null);
4819
- const [refusal, setRefusal] = (0, import_react35.useState)(null);
4820
- const key = (0, import_react35.useMemo)(() => ruleWords(rules), [rules]);
4821
- const latest = (0, import_react35.useRef)(rules);
4941
+ const client = (0, import_react37.useRecordsClient)();
4942
+ const [preview, setPreview] = (0, import_react36.useState)(null);
4943
+ const [refusal, setRefusal] = (0, import_react36.useState)(null);
4944
+ const key = (0, import_react36.useMemo)(() => ruleWords(rules), [rules]);
4945
+ const latest = (0, import_react36.useRef)(rules);
4822
4946
  latest.current = rules;
4823
4947
  const name = fieldName(field);
4824
- (0, import_react35.useEffect)(() => {
4948
+ (0, import_react36.useEffect)(() => {
4825
4949
  let cancelled = false;
4826
4950
  setPreview(null);
4827
4951
  setRefusal(null);
@@ -4942,9 +5066,9 @@ function choiceLookForNewList(multi, rows) {
4942
5066
  }
4943
5067
 
4944
5068
  // src/ChoiceListEditor.tsx
4945
- var import_react37 = require("react");
5069
+ var import_react38 = require("react");
4946
5070
  var import_records19 = require("@ai-matrx/records");
4947
- var import_react38 = require("@ai-matrx/records/react");
5071
+ var import_react39 = require("@ai-matrx/records/react");
4948
5072
  var import_design_system23 = require("@ai-matrx/design-system");
4949
5073
  var import_data_table5 = require("@ai-matrx/design-system/data-table");
4950
5074
  var import_jsx_runtime26 = require("react/jsx-runtime");
@@ -4989,15 +5113,15 @@ function ChoiceListEditor({
4989
5113
  rehome = {},
4990
5114
  onRehome
4991
5115
  }) {
4992
- const client = (0, import_react38.useRecordsClient)();
4993
- const [refused, setRefused] = (0, import_react37.useState)(null);
4994
- const [usage, setUsage] = (0, import_react37.useState)(null);
4995
- const [asking, setAsking] = (0, import_react37.useState)(null);
4996
- const [moveTo, setMoveTo] = (0, import_react37.useState)("");
4997
- const focusRow = (0, import_react37.useRef)(null);
4998
- const [removed, setRemoved] = (0, import_react37.useState)({});
5116
+ const client = (0, import_react39.useRecordsClient)();
5117
+ const [refused, setRefused] = (0, import_react38.useState)(null);
5118
+ const [usage, setUsage] = (0, import_react38.useState)(null);
5119
+ const [asking, setAsking] = (0, import_react38.useState)(null);
5120
+ const [moveTo, setMoveTo] = (0, import_react38.useState)("");
5121
+ const focusRow = (0, import_react38.useRef)(null);
5122
+ const [removed, setRemoved] = (0, import_react38.useState)({});
4999
5123
  const fieldId = field?.id ?? null;
5000
- (0, import_react37.useEffect)(() => {
5124
+ (0, import_react38.useEffect)(() => {
5001
5125
  if (rows !== null || !fieldId || !field) return;
5002
5126
  let live = true;
5003
5127
  void client.fieldOptions({ field_id: fieldId }).then((got) => {
@@ -5014,7 +5138,7 @@ function ChoiceListEditor({
5014
5138
  live = false;
5015
5139
  };
5016
5140
  }, [client, fieldId, rows, onRows, onStart]);
5017
- (0, import_react37.useEffect)(() => {
5141
+ (0, import_react38.useEffect)(() => {
5018
5142
  if (!onRehome || !fieldId) return;
5019
5143
  let live = true;
5020
5144
  void client.fieldChoiceUsage({ field_id: fieldId }).then(
@@ -5154,7 +5278,7 @@ function ChoiceListEditor({
5154
5278
  ] });
5155
5279
  }
5156
5280
  function ChoiceSwatch({ words, color, onColor }) {
5157
- const [open, setOpen] = (0, import_react37.useState)(false);
5281
+ const [open, setOpen] = (0, import_react38.useState)(false);
5158
5282
  const pick = (next) => {
5159
5283
  onColor(next);
5160
5284
  setOpen(false);
@@ -5595,36 +5719,6 @@ function readersSentence(count, tableName2) {
5595
5719
  return `${rollups} on ${tableName2 ?? "this table"} read through this.`;
5596
5720
  }
5597
5721
 
5598
- // src/personTime.ts
5599
- var import_react39 = require("react");
5600
- var zone = null;
5601
- var listeners = /* @__PURE__ */ new Set();
5602
- function usePersonTimeZone() {
5603
- return (0, import_react39.useSyncExternalStore)(
5604
- (cb) => {
5605
- listeners.add(cb);
5606
- return () => listeners.delete(cb);
5607
- },
5608
- () => zone,
5609
- () => null
5610
- );
5611
- }
5612
- function personWallNow(at = /* @__PURE__ */ new Date()) {
5613
- if (!zone) return at;
5614
- const parts = new Intl.DateTimeFormat("en-US", {
5615
- timeZone: zone,
5616
- year: "numeric",
5617
- month: "2-digit",
5618
- day: "2-digit",
5619
- hour: "2-digit",
5620
- minute: "2-digit",
5621
- second: "2-digit",
5622
- hourCycle: "h23"
5623
- }).formatToParts(at);
5624
- const n = (t) => Number(parts.find((p) => p.type === t)?.value ?? 0);
5625
- return new Date(n("year"), n("month") - 1, n("day"), n("hour"), n("minute"), n("second"));
5626
- }
5627
-
5628
5722
  // src/gridMenus.ts
5629
5723
  function nextFieldSort(fields) {
5630
5724
  let last = 0;