@ai-matrx/records-ui 0.90.1 → 0.92.0
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 +58 -0
- package/dist/index.cjs +1426 -834
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -8
- package/dist/index.d.ts +40 -8
- package/dist/index.js +2608 -2012
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -67,7 +67,7 @@ __export(src_exports, {
|
|
|
67
67
|
ConditionGroup: () => ConditionGroup,
|
|
68
68
|
ConditionRow: () => ConditionRow,
|
|
69
69
|
CustomFieldsSection: () => CustomFieldsSection,
|
|
70
|
-
DEFAULT_FIELDS: () =>
|
|
70
|
+
DEFAULT_FIELDS: () => import_core15.DEFAULT_FIELDS,
|
|
71
71
|
DEFAULT_FOOTER: () => DEFAULT_FOOTER,
|
|
72
72
|
DEFAULT_VIEW_NAME: () => DEFAULT_VIEW_NAME,
|
|
73
73
|
DUE_STATE_LABEL: () => DUE_STATE_LABEL,
|
|
@@ -221,7 +221,7 @@ __export(src_exports, {
|
|
|
221
221
|
WithheldValue: () => WithheldValue,
|
|
222
222
|
actorBadge: () => actorBadge,
|
|
223
223
|
actorWords: () => actorWords,
|
|
224
|
-
addFields: () =>
|
|
224
|
+
addFields: () => import_core15.addFields,
|
|
225
225
|
applyLook: () => applyLook,
|
|
226
226
|
archiveConfirmSentence: () => archiveConfirmSentence,
|
|
227
227
|
archivedRowLine: () => archivedRowLine,
|
|
@@ -250,7 +250,7 @@ __export(src_exports, {
|
|
|
250
250
|
dashboardFromSummary: () => dashboardFromSummary,
|
|
251
251
|
dateFormatFor: () => dateFormatFor,
|
|
252
252
|
dateText: () => dateText,
|
|
253
|
-
declareTable: () =>
|
|
253
|
+
declareTable: () => import_core15.declareTable,
|
|
254
254
|
declareView: () => declareView,
|
|
255
255
|
defaultViewOf: () => defaultViewOf,
|
|
256
256
|
designatedLayoutOf: () => designatedLayoutOf,
|
|
@@ -258,7 +258,7 @@ __export(src_exports, {
|
|
|
258
258
|
ensureSystemTable: () => ensureSystemTable,
|
|
259
259
|
entityReferenceSpec: () => entityReferenceSpec,
|
|
260
260
|
envelopeFor: () => envelopeFor,
|
|
261
|
-
fieldDeclarationFor: () =>
|
|
261
|
+
fieldDeclarationFor: () => import_core15.fieldDeclarationFor,
|
|
262
262
|
fieldIsEditable: () => fieldIsEditable,
|
|
263
263
|
fieldName: () => fieldName,
|
|
264
264
|
fieldToken: () => fieldToken,
|
|
@@ -346,7 +346,7 @@ __export(src_exports, {
|
|
|
346
346
|
tableName: () => tableName,
|
|
347
347
|
tableOpening: () => tableOpening,
|
|
348
348
|
tableRightsAt: () => tableRightsAt,
|
|
349
|
-
tokenFor: () =>
|
|
349
|
+
tokenFor: () => import_core15.tokenFor,
|
|
350
350
|
unknownRailLine: () => unknownRailLine,
|
|
351
351
|
unknownViewLine: () => unknownViewLine,
|
|
352
352
|
useCanShare: () => useCanShare,
|
|
@@ -677,6 +677,7 @@ function humanize(token) {
|
|
|
677
677
|
}
|
|
678
678
|
function fieldName(field2) {
|
|
679
679
|
const label = (field2.label ?? "").trim();
|
|
680
|
+
if (label !== "" && label === field2.key && /^[a-z0-9]+(?:[_.-][a-z0-9]+)+$/.test(label)) return humanize(label);
|
|
680
681
|
if (label !== "") return label;
|
|
681
682
|
const name = (field2.name ?? "").trim();
|
|
682
683
|
if (name !== "") return name;
|
|
@@ -1090,9 +1091,9 @@ function parityTypesWithNoExplanation() {
|
|
|
1090
1091
|
function kindsWithNoChoice() {
|
|
1091
1092
|
return import_records2.DECLARABLE_FIELD_KINDS.map((row) => row.kind).filter((kind) => !BY_ID.has(kind));
|
|
1092
1093
|
}
|
|
1093
|
-
var ADD_FIELD_OPEN_LABEL = "Add
|
|
1094
|
-
var ADD_FIELD_SAVE_LABEL = "Create
|
|
1095
|
-
var EDIT_FIELD_SAVE_LABEL = "Save
|
|
1094
|
+
var ADD_FIELD_OPEN_LABEL = "Add column";
|
|
1095
|
+
var ADD_FIELD_SAVE_LABEL = "Create column";
|
|
1096
|
+
var EDIT_FIELD_SAVE_LABEL = "Save column";
|
|
1096
1097
|
|
|
1097
1098
|
// src/parity.ts
|
|
1098
1099
|
var kernelId = (name) => import_records3.KERNEL_TABLES.find((t) => t.name === name)?.id ?? "";
|
|
@@ -2016,7 +2017,7 @@ function rich(id, text, raw, format) {
|
|
|
2016
2017
|
case "url":
|
|
2017
2018
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("a", { href: text.includes("://") ? text : `https://${text}`, target: "_blank", rel: "noopener noreferrer", className: LINK, onClick: own, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate", children: text }) });
|
|
2018
2019
|
case "phone":
|
|
2019
|
-
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("a", { href: `tel:${text.replace(/[^\d+]/g, "")}`, className: LINK, onClick: own, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate", children: text }) });
|
|
2020
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("a", { href: `tel:${text.replace(/[^\d+]/g, "")}`, className: LINK, onClick: own, title: text, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate tabular-nums", children: formatPhone(text) }) });
|
|
2020
2021
|
case "address":
|
|
2021
2022
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2022
2023
|
"a",
|
|
@@ -2081,6 +2082,26 @@ function FormattedValue({
|
|
|
2081
2082
|
if (!result.ok || result.empty || !def?.rich) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_data_table2.FormattedCell, { result });
|
|
2082
2083
|
return rich(def.id, result.text, value, format) ?? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_data_table2.FormattedCell, { result });
|
|
2083
2084
|
}
|
|
2085
|
+
function formatPhone(raw) {
|
|
2086
|
+
const text = raw.trim();
|
|
2087
|
+
const digits = text.replace(/\D/g, "");
|
|
2088
|
+
if (/[a-z]/i.test(text.replace(/ext\.?|x/gi, ""))) return text;
|
|
2089
|
+
if (digits.length === 10 && !text.startsWith("+")) return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`;
|
|
2090
|
+
if (digits.length === 11 && digits.startsWith("1") && (text.startsWith("+1") || text.startsWith("1") || !text.startsWith("+"))) {
|
|
2091
|
+
return `+1 (${digits.slice(1, 4)}) ${digits.slice(4, 7)}-${digits.slice(7)}`;
|
|
2092
|
+
}
|
|
2093
|
+
return text;
|
|
2094
|
+
}
|
|
2095
|
+
function linkChipText(raw) {
|
|
2096
|
+
try {
|
|
2097
|
+
const url = new URL(raw.includes("://") ? raw : `https://${raw}`);
|
|
2098
|
+
const host = url.hostname.replace(/^www\./, "");
|
|
2099
|
+
const last = url.pathname.split("/").filter(Boolean).pop();
|
|
2100
|
+
return last ? `${host} \xB7 ${decodeURIComponent(last)}` : host;
|
|
2101
|
+
} catch {
|
|
2102
|
+
return raw;
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2084
2105
|
|
|
2085
2106
|
// src/formatControls.tsx
|
|
2086
2107
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
@@ -2175,6 +2196,7 @@ function formatControl(args) {
|
|
|
2175
2196
|
}
|
|
2176
2197
|
|
|
2177
2198
|
// src/editors.tsx
|
|
2199
|
+
var import_field_formats5 = require("@ai-matrx/design-system/field-formats");
|
|
2178
2200
|
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
2179
2201
|
var CHOICE_EDITOR_ADDS_WHAT_IS_TYPED = true;
|
|
2180
2202
|
var FORMAT_BLIND_KINDS = /* @__PURE__ */ new Set(["formula", "lookup", "rollup", "json", "checkbox", "select", "multi_select", "member", "entity_reference", "relation", "attachment", "datetime"]);
|
|
@@ -2186,9 +2208,123 @@ function FieldLabel({ field: field2, htmlFor, children }) {
|
|
|
2186
2208
|
children
|
|
2187
2209
|
] });
|
|
2188
2210
|
}
|
|
2189
|
-
function
|
|
2211
|
+
function isTypedDraft(value) {
|
|
2212
|
+
return typeof value === "object" && value !== null && value.typedDraft === true;
|
|
2213
|
+
}
|
|
2214
|
+
var TYPED_AS_WORDS = /* @__PURE__ */ new Set(["text", "long_text", "email", "url", "phone"]);
|
|
2215
|
+
var TYPED_AND_READ = /* @__PURE__ */ new Set(["number", "currency", "percent", "datetime", "select", "multi_select"]);
|
|
2216
|
+
function typingOpens(field2) {
|
|
2217
|
+
const kind = String(editorKindFor(field2));
|
|
2218
|
+
if (kind === "formula" || kind === "lookup" || kind === "rollup") return "none";
|
|
2219
|
+
const format = formatForField(field2);
|
|
2220
|
+
if (format && format.id === "time") return "read";
|
|
2221
|
+
if (format && format.id === "rating") return "read";
|
|
2222
|
+
if (TYPED_AS_WORDS.has(kind)) return "words";
|
|
2223
|
+
if (TYPED_AND_READ.has(kind)) return "read";
|
|
2224
|
+
return "none";
|
|
2225
|
+
}
|
|
2226
|
+
function judgeTyped(field2, text, choices) {
|
|
2227
|
+
const trimmed = text.trim();
|
|
2228
|
+
const kind = String(editorKindFor(field2));
|
|
2229
|
+
const format = formatForField(field2);
|
|
2230
|
+
if (trimmed === "") return { value: null };
|
|
2231
|
+
if (format && format.id === "time") {
|
|
2232
|
+
const kept = (0, import_field_formats5.parseFieldInput)(trimmed, format);
|
|
2233
|
+
return typeof kept === "string" && /^\d{2}:\d{2}(:\d{2})?$/.test(kept) ? { value: kept } : { refusal: `${fieldName(field2)} holds a time of day, and \u201C${text}\u201D is not one. Write it as 14:30 or 2:30 PM.` };
|
|
2234
|
+
}
|
|
2235
|
+
if (kind === "select" || kind === "multi_select") {
|
|
2236
|
+
if (!choices) return null;
|
|
2237
|
+
const title = (option) => recordName(option.data, null, "").toLowerCase();
|
|
2238
|
+
const words2 = field2.multi || kind === "multi_select" ? trimmed.split(/\s*[,;\n]\s*/).filter(Boolean) : [trimmed];
|
|
2239
|
+
const keys = [];
|
|
2240
|
+
const unknown = [];
|
|
2241
|
+
for (const word of words2) {
|
|
2242
|
+
const low = word.toLowerCase();
|
|
2243
|
+
const option = choices.find((o) => title(o) === low || (0, import_records5.optionKey)(o).toLowerCase() === low);
|
|
2244
|
+
if (option) keys.push((0, import_records5.optionKey)(option));
|
|
2245
|
+
else if ((0, import_records5.allowsOtherValues)(field2)) keys.push(word);
|
|
2246
|
+
else unknown.push(word);
|
|
2247
|
+
}
|
|
2248
|
+
if (unknown.length > 0) {
|
|
2249
|
+
return {
|
|
2250
|
+
refusal: `${fieldName(field2)} has no choice called \u201C${unknown.join("\u201D, \u201C")}\u201D. Type one of its choices, or press Escape and pick from the list.`
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
2253
|
+
return { value: field2.multi || kind === "multi_select" ? keys : keys[0] ?? null };
|
|
2254
|
+
}
|
|
2255
|
+
if (format && format.id === "rating") {
|
|
2256
|
+
const n = Number(trimmed);
|
|
2257
|
+
const max = format.options?.ratingMax ?? 5;
|
|
2258
|
+
return Number.isInteger(n) && n >= 0 && n <= max ? { value: n === 0 ? null : n } : { refusal: `${fieldName(field2)} is a rating from 1 to ${max}, and \u201C${text}\u201D is not one.` };
|
|
2259
|
+
}
|
|
2260
|
+
const read = (0, import_records5.coerceTypedAnswer)(field2, text, { label: fieldName(field2) });
|
|
2261
|
+
if ("refusal" in read) return { refusal: read.refusal };
|
|
2262
|
+
if (typeof read.value === "number" && format && (format.id === "percent" || format.id === "progress") && format.options?.percentScale === "fraction") {
|
|
2263
|
+
return { value: Math.round(read.value / 100 * 1e9) / 1e9 };
|
|
2264
|
+
}
|
|
2265
|
+
return { value: read.value };
|
|
2266
|
+
}
|
|
2267
|
+
function draftFromTyping(field2, seed) {
|
|
2268
|
+
const opens = typingOpens(field2);
|
|
2269
|
+
if (opens === "none") return void 0;
|
|
2270
|
+
if (opens === "words") return seed;
|
|
2271
|
+
return { typedDraft: true, text: seed, judged: judgeTyped(field2, seed) };
|
|
2272
|
+
}
|
|
2273
|
+
function TypedTextControl({ field: field2, value, onChange, disabled, id }) {
|
|
2274
|
+
const client = (0, import_react11.useOptionalRecordsClient)();
|
|
2275
|
+
const kind = String(editorKindFor(field2));
|
|
2276
|
+
const needsChoices = kind === "select" || kind === "multi_select";
|
|
2277
|
+
const [choices, setChoices] = (0, import_react10.useState)(null);
|
|
2278
|
+
(0, import_react10.useEffect)(() => {
|
|
2279
|
+
if (!needsChoices || !client) return;
|
|
2280
|
+
let cancelled = false;
|
|
2281
|
+
void client.fieldOptions({ field_id: field2.id }).then((result) => {
|
|
2282
|
+
if (!cancelled) setChoices(result.ok ? inTheStoresOrder(result.data ?? []) : []);
|
|
2283
|
+
});
|
|
2284
|
+
return () => {
|
|
2285
|
+
cancelled = true;
|
|
2286
|
+
};
|
|
2287
|
+
}, [client, field2.id, needsChoices]);
|
|
2288
|
+
(0, import_react10.useEffect)(() => {
|
|
2289
|
+
if (choices && value.judged === null) onChange({ ...value, judged: judgeTyped(field2, value.text, choices) });
|
|
2290
|
+
}, [choices, value, field2, onChange]);
|
|
2291
|
+
const judged = value.judged;
|
|
2292
|
+
const format = formatForField(field2);
|
|
2293
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "flex flex-col gap-0.5", children: [
|
|
2294
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2295
|
+
import_design_system8.BasicInput,
|
|
2296
|
+
{
|
|
2297
|
+
id,
|
|
2298
|
+
value: value.text,
|
|
2299
|
+
disabled,
|
|
2300
|
+
autoComplete: "off",
|
|
2301
|
+
"aria-invalid": judged !== null && "refusal" in judged ? true : void 0,
|
|
2302
|
+
onChange: (e) => onChange({ typedDraft: true, text: e.target.value, judged: judgeTyped(field2, e.target.value, needsChoices ? choices : void 0) })
|
|
2303
|
+
}
|
|
2304
|
+
),
|
|
2305
|
+
judged === null ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "text-[10px] text-muted-foreground", children: [
|
|
2306
|
+
"Reading ",
|
|
2307
|
+
fieldName(field2),
|
|
2308
|
+
"'s choices\u2026"
|
|
2309
|
+
] }) : "refusal" in judged ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { "data-records-typed-refusal": "", role: "status", className: "text-[11px] leading-snug text-amber-700 dark:text-amber-400", children: judged.refusal }) : judged.value === null || judged.value === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { "data-records-typed-reads-as": "", className: "text-[10px] text-muted-foreground", children: [
|
|
2310
|
+
"Reads as",
|
|
2311
|
+
" ",
|
|
2312
|
+
needsChoices ? String(
|
|
2313
|
+
(Array.isArray(judged.value) ? judged.value : [judged.value]).map((k) => {
|
|
2314
|
+
const option = choices?.find((o) => (0, import_records5.optionKey)(o) === k);
|
|
2315
|
+
return option ? recordName(option.data, null, String(k)) : String(k);
|
|
2316
|
+
}).join(", ")
|
|
2317
|
+
) : (() => {
|
|
2318
|
+
const shown3 = format ?? (kind === "datetime" ? { id: carriesTimeOfDay(field2, judged.value) ? "datetime" : "date" } : void 0);
|
|
2319
|
+
return shown3 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(FormattedValue, { value: judged.value, format: shown3 }) : String(judged.value);
|
|
2320
|
+
})()
|
|
2321
|
+
] })
|
|
2322
|
+
] });
|
|
2323
|
+
}
|
|
2324
|
+
function FieldControl({ field: field2, value, onChange, disabled, id, autoOpen, onClose }) {
|
|
2190
2325
|
const kind = editorKindFor(field2);
|
|
2191
2326
|
const inputId = id ?? `field-${field2.key}`;
|
|
2327
|
+
if (isTypedDraft(value)) return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(TypedTextControl, { field: field2, value, onChange, disabled, id: inputId });
|
|
2192
2328
|
const format = formatForField(field2);
|
|
2193
2329
|
if (format && !FORMAT_BLIND_KINDS.has(String(kind))) {
|
|
2194
2330
|
const own2 = formatControl({ format, value, onChange, disabled, id: inputId, label: fieldName(field2) });
|
|
@@ -2261,9 +2397,9 @@ function FieldControl({ field: field2, value, onChange, disabled, id }) {
|
|
|
2261
2397
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(CheckboxControl, { field: field2, value, onChange, disabled, id: inputId });
|
|
2262
2398
|
case "select":
|
|
2263
2399
|
case "multi_select":
|
|
2264
|
-
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(OptionControl, { field: field2, value, onChange, disabled, id: inputId });
|
|
2400
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(OptionControl, { field: field2, value, onChange, disabled, id: inputId, autoOpen, onClose });
|
|
2265
2401
|
case "member":
|
|
2266
|
-
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PersonPicker, { field: field2, value, onChange, disabled, id: inputId });
|
|
2402
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PersonPicker, { field: field2, value, onChange, disabled, id: inputId, autoOpen, onClose });
|
|
2267
2403
|
case "entity_reference":
|
|
2268
2404
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(EntityReferenceControl, { field: field2, value, onChange, disabled, id: inputId });
|
|
2269
2405
|
case "relation":
|
|
@@ -2383,7 +2519,7 @@ function inTheStoresOrder(options) {
|
|
|
2383
2519
|
const made = (o) => String(o.created_at ?? "");
|
|
2384
2520
|
return [...options].sort((a, b) => position(a) - position(b) || made(a).localeCompare(made(b)));
|
|
2385
2521
|
}
|
|
2386
|
-
function OptionControl({ field: field2, value, onChange, disabled, id }) {
|
|
2522
|
+
function OptionControl({ field: field2, value, onChange, disabled, id, autoOpen, onClose }) {
|
|
2387
2523
|
const client = (0, import_react11.useOptionalRecordsClient)();
|
|
2388
2524
|
const [options, setOptions] = (0, import_react10.useState)(null);
|
|
2389
2525
|
const [refusal2, setRefusal] = (0, import_react10.useState)(null);
|
|
@@ -2426,7 +2562,7 @@ function OptionControl({ field: field2, value, onChange, disabled, id }) {
|
|
|
2426
2562
|
if (field2.multi) {
|
|
2427
2563
|
const chosen = options.filter((option) => (0, import_records5.choiceValuesOf)(value).some((v) => (0, import_records5.isTheChosen)(option, v)));
|
|
2428
2564
|
const picked = new Set(chosen.map((option) => (0, import_records5.optionKey)(option)));
|
|
2429
|
-
|
|
2565
|
+
const toggles = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { id: autoOpen ? void 0 : id, className: "flex flex-wrap gap-1", children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2430
2566
|
import_design_system8.Button,
|
|
2431
2567
|
{
|
|
2432
2568
|
type: "button",
|
|
@@ -2443,6 +2579,20 @@ function OptionControl({ field: field2, value, onChange, disabled, id }) {
|
|
|
2443
2579
|
},
|
|
2444
2580
|
option.id
|
|
2445
2581
|
)) });
|
|
2582
|
+
if (!autoOpen) return toggles;
|
|
2583
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
2584
|
+
import_design_system8.Popover,
|
|
2585
|
+
{
|
|
2586
|
+
defaultOpen: true,
|
|
2587
|
+
onOpenChange: (open) => {
|
|
2588
|
+
if (!open) onClose?.();
|
|
2589
|
+
},
|
|
2590
|
+
children: [
|
|
2591
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_design_system8.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_design_system8.Button, { id, type: "button", size: "sm", variant: "outline", className: "h-6 max-w-full truncate", children: chosen.length === 0 ? "Choose" : chosen.map(titleOf).join(", ") }) }),
|
|
2592
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_design_system8.PopoverContent, { sizing: "content", className: "max-w-[20rem] p-2", align: "start", children: toggles })
|
|
2593
|
+
]
|
|
2594
|
+
}
|
|
2595
|
+
);
|
|
2446
2596
|
}
|
|
2447
2597
|
const current = options.find((option) => (0, import_records5.isTheChosen)(option, value));
|
|
2448
2598
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
@@ -2451,6 +2601,13 @@ function OptionControl({ field: field2, value, onChange, disabled, id }) {
|
|
|
2451
2601
|
value: current ? (0, import_records5.optionKey)(current) : "",
|
|
2452
2602
|
disabled: disabled ?? false,
|
|
2453
2603
|
onValueChange: (next) => onChange(next === "" ? null : next),
|
|
2604
|
+
...autoOpen ? {
|
|
2605
|
+
// IN A GRID CELL the list opens at once and a pick is the commit (Airtable).
|
|
2606
|
+
defaultOpen: true,
|
|
2607
|
+
onOpenChange: (open) => {
|
|
2608
|
+
if (!open) onClose?.();
|
|
2609
|
+
}
|
|
2610
|
+
} : {},
|
|
2454
2611
|
children: [
|
|
2455
2612
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_design_system8.SelectTrigger, { id, size: "sm", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_design_system8.SelectValue, { placeholder: "Choose" }) }),
|
|
2456
2613
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_design_system8.SelectContent, { children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_design_system8.SelectItem, { value: (0, import_records5.optionKey)(option), children: titleOf(option) }, option.id)) })
|
|
@@ -2570,14 +2727,20 @@ async function personRecordForMember(client, personTableId, people, member) {
|
|
|
2570
2727
|
});
|
|
2571
2728
|
return written;
|
|
2572
2729
|
}
|
|
2573
|
-
function PersonPicker({ field: field2, value, onChange, disabled, id, className }) {
|
|
2730
|
+
function PersonPicker({ field: field2, value, onChange, disabled, id, className, autoOpen, onClose }) {
|
|
2574
2731
|
const client = (0, import_react13.useOptionalRecordsClient)();
|
|
2575
2732
|
const host = useRecordsUi();
|
|
2576
2733
|
const [members, setMembers] = (0, import_react12.useState)(null);
|
|
2577
2734
|
const [people, setPeople] = (0, import_react12.useState)([]);
|
|
2578
2735
|
const [error, setError] = (0, import_react12.useState)(null);
|
|
2579
2736
|
const [search, setSearch] = (0, import_react12.useState)("");
|
|
2580
|
-
const [open,
|
|
2737
|
+
const [open, setOpenState] = (0, import_react12.useState)(autoOpen === true);
|
|
2738
|
+
const onCloseRef = (0, import_react12.useRef)(onClose);
|
|
2739
|
+
onCloseRef.current = onClose;
|
|
2740
|
+
const setOpen = (0, import_react12.useCallback)((next) => {
|
|
2741
|
+
setOpenState(next);
|
|
2742
|
+
if (!next) onCloseRef.current?.();
|
|
2743
|
+
}, []);
|
|
2581
2744
|
const [busy, setBusy] = (0, import_react12.useState)(false);
|
|
2582
2745
|
const membersPort = host.members;
|
|
2583
2746
|
const personTable = field2.relation_target;
|
|
@@ -2726,6 +2889,7 @@ var import_react19 = require("react");
|
|
|
2726
2889
|
var import_core3 = require("@ai-matrx/records/core");
|
|
2727
2890
|
var import_react20 = require("@ai-matrx/records/react");
|
|
2728
2891
|
var import_design_system12 = require("@ai-matrx/design-system");
|
|
2892
|
+
var import_records6 = require("@ai-matrx/records");
|
|
2729
2893
|
|
|
2730
2894
|
// src/whoChangedSource.tsx
|
|
2731
2895
|
var import_react14 = require("react");
|
|
@@ -2843,7 +3007,7 @@ function useWhoChanged(fieldKey, recordId) {
|
|
|
2843
3007
|
|
|
2844
3008
|
// src/values.tsx
|
|
2845
3009
|
var import_data_table4 = require("@ai-matrx/design-system/data-table");
|
|
2846
|
-
var
|
|
3010
|
+
var import_field_formats6 = require("@ai-matrx/design-system/field-formats");
|
|
2847
3011
|
|
|
2848
3012
|
// src/withheld.tsx
|
|
2849
3013
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
@@ -3373,32 +3537,61 @@ function renderValue(field2, value, document2, labels, stillWaitingFor) {
|
|
|
3373
3537
|
if (whole) return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(WholeValueCell, { words: value, pointer: whole });
|
|
3374
3538
|
const kind = editorKindFor(field2);
|
|
3375
3539
|
const chosen = kind === "select" || kind === "multi_select";
|
|
3376
|
-
const
|
|
3377
|
-
if (!chosen) return void 0;
|
|
3540
|
+
const chipOf = (item) => {
|
|
3378
3541
|
const color = storedChoiceColor(field2, item) ?? storedChoiceColor(field2, scalarText(field2, item, labels));
|
|
3379
|
-
return color ? import_data_table4.
|
|
3542
|
+
return color ? { className: import_data_table4.CHOICE_CHIP_CLASS[color], tinted: true } : { className: import_data_table4.NEUTRAL_CHIP_CLASS, tinted: false };
|
|
3380
3543
|
};
|
|
3381
|
-
if (
|
|
3382
|
-
|
|
3383
|
-
|
|
3544
|
+
if (chosen) {
|
|
3545
|
+
const items = Array.isArray(value) ? value : [value];
|
|
3546
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "flex flex-wrap gap-1", "data-records-choice-chips": "", children: items.map((item, i) => {
|
|
3547
|
+
const chip = chipOf(item);
|
|
3384
3548
|
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3385
3549
|
import_design_system11.Badge,
|
|
3386
3550
|
{
|
|
3387
|
-
variant: "
|
|
3388
|
-
className: (0, import_design_system11.cn)("text-[11px]",
|
|
3389
|
-
|
|
3390
|
-
|
|
3551
|
+
variant: "outline",
|
|
3552
|
+
className: (0, import_design_system11.cn)("text-[11px]", chip.className),
|
|
3553
|
+
"data-records-choice-chip": "",
|
|
3554
|
+
...chip.tinted ? { "data-records-choice-tint": "" } : {},
|
|
3555
|
+
children: scalarText(field2, item, labels, Array.isArray(value) ? void 0 : stillWaitingFor)
|
|
3391
3556
|
},
|
|
3392
3557
|
`${String(item)}-${i}`
|
|
3393
3558
|
);
|
|
3394
3559
|
}) });
|
|
3395
3560
|
}
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_design_system11.Badge, { variant: "secondary", className: (0, import_design_system11.cn)("text-[11px]", tint), "data-records-choice-tint": "", children: scalarText(field2, value, labels, stillWaitingFor) });
|
|
3561
|
+
if (Array.isArray(value)) {
|
|
3562
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "flex flex-wrap gap-1", children: value.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_design_system11.Badge, { variant: "secondary", className: "text-[11px]", children: scalarText(field2, item, labels) }, `${String(item)}-${i}`)) });
|
|
3399
3563
|
}
|
|
3400
3564
|
if (kind === "url" && typeof value === "string") {
|
|
3401
|
-
|
|
3565
|
+
const href = value.includes("://") ? value : `https://${value}`;
|
|
3566
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
3567
|
+
"a",
|
|
3568
|
+
{
|
|
3569
|
+
className: "inline-flex min-w-0 max-w-full items-center gap-1 rounded-md border border-border bg-muted/60 px-1.5 py-0.5 text-[11px] text-foreground hover:bg-accent",
|
|
3570
|
+
href,
|
|
3571
|
+
target: "_blank",
|
|
3572
|
+
rel: "noreferrer",
|
|
3573
|
+
title: value,
|
|
3574
|
+
"data-records-link-chip": "",
|
|
3575
|
+
onClick: (event) => event.stopPropagation(),
|
|
3576
|
+
children: [
|
|
3577
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { "aria-hidden": "true", className: "text-muted-foreground", children: "\u2197" }),
|
|
3578
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "truncate", children: linkChipText(value) })
|
|
3579
|
+
]
|
|
3580
|
+
}
|
|
3581
|
+
);
|
|
3582
|
+
}
|
|
3583
|
+
if (kind === "phone" && typeof value === "string" && value.trim() !== "") {
|
|
3584
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3585
|
+
"a",
|
|
3586
|
+
{
|
|
3587
|
+
className: "truncate tabular-nums text-primary underline-offset-2 hover:underline",
|
|
3588
|
+
href: `tel:${value.replace(/[^\d+]/g, "")}`,
|
|
3589
|
+
title: value,
|
|
3590
|
+
"data-records-phone": "",
|
|
3591
|
+
onClick: (event) => event.stopPropagation(),
|
|
3592
|
+
children: formatPhone(value)
|
|
3593
|
+
}
|
|
3594
|
+
);
|
|
3402
3595
|
}
|
|
3403
3596
|
if (kind === "email" && typeof value === "string") {
|
|
3404
3597
|
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("a", { className: "truncate text-primary underline-offset-2 hover:underline", href: `mailto:${value}`, children: value });
|
|
@@ -3417,7 +3610,7 @@ function scalarText(field2, value, labels, stillWaitingFor) {
|
|
|
3417
3610
|
}
|
|
3418
3611
|
const shownAs = shownAsOf(field2);
|
|
3419
3612
|
if (shownAs) {
|
|
3420
|
-
const said = (0,
|
|
3613
|
+
const said = (0, import_field_formats6.formatFieldValue)(value, shownAs);
|
|
3421
3614
|
if (said.ok && !said.empty) return said.text;
|
|
3422
3615
|
}
|
|
3423
3616
|
if (typeof value === "number") {
|
|
@@ -3478,7 +3671,6 @@ function cardWords(record, field2, labels) {
|
|
|
3478
3671
|
|
|
3479
3672
|
// src/gridEditing.tsx
|
|
3480
3673
|
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
3481
|
-
var TYPED_KINDS = /* @__PURE__ */ new Set(["text", "long_text", "email", "url", "phone", "number", "currency", "percent"]);
|
|
3482
3674
|
function asksWhoChanged(event) {
|
|
3483
3675
|
return event.altKey && event.shiftKey && !event.metaKey && !event.ctrlKey && (event.code === "KeyH" || event.key.toLowerCase() === "h");
|
|
3484
3676
|
}
|
|
@@ -3515,7 +3707,9 @@ function rememberVersion(cache, recordId, version) {
|
|
|
3515
3707
|
const held = cache.get(recordId);
|
|
3516
3708
|
if (held === void 0 || version > held) cache.set(recordId, version);
|
|
3517
3709
|
}
|
|
3518
|
-
function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow, onWritten }) {
|
|
3710
|
+
function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow, onWritten, onEnded }) {
|
|
3711
|
+
const endedRef = (0, import_react19.useRef)(onEnded);
|
|
3712
|
+
endedRef.current = onEnded;
|
|
3519
3713
|
const client = (0, import_react20.useRecordsClient)();
|
|
3520
3714
|
const [editing, setEditing] = (0, import_react19.useState)(null);
|
|
3521
3715
|
const [draft, setDraft] = (0, import_react19.useState)(null);
|
|
@@ -3561,6 +3755,24 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow,
|
|
|
3561
3755
|
(rowId, key) => refusals[cellKey(rowId, key)] ?? null,
|
|
3562
3756
|
[refusals]
|
|
3563
3757
|
);
|
|
3758
|
+
const settling = (0, import_react19.useRef)(/* @__PURE__ */ new Map());
|
|
3759
|
+
(0, import_react19.useEffect)(() => {
|
|
3760
|
+
if (settling.current.size === 0) return;
|
|
3761
|
+
const released = [];
|
|
3762
|
+
for (const [id, before] of settling.current) {
|
|
3763
|
+
const [rowId, key] = id.split("::");
|
|
3764
|
+
const row = rows.find((r) => r.id === rowId);
|
|
3765
|
+
if (!row) continue;
|
|
3766
|
+
if (!sameValue(row.document?.[key], before)) released.push(id);
|
|
3767
|
+
}
|
|
3768
|
+
if (released.length === 0) return;
|
|
3769
|
+
for (const id of released) settling.current.delete(id);
|
|
3770
|
+
setOptimistic((held) => {
|
|
3771
|
+
const next = { ...held };
|
|
3772
|
+
for (const id of released) delete next[id];
|
|
3773
|
+
return next;
|
|
3774
|
+
});
|
|
3775
|
+
}, [rows]);
|
|
3564
3776
|
const shownValue2 = (0, import_react19.useCallback)(
|
|
3565
3777
|
(rowId, key) => {
|
|
3566
3778
|
const held = optimistic[cellKey(rowId, key)];
|
|
@@ -3579,9 +3791,8 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow,
|
|
|
3579
3791
|
if (editing && editing.rowId === address.rowId && editing.key === address.key) return;
|
|
3580
3792
|
const current = shownValue2(address.rowId, address.key);
|
|
3581
3793
|
const seedField = typeof seed === "string" && seed !== "" ? fields.find((f) => f.key === address.key) : void 0;
|
|
3582
|
-
const
|
|
3583
|
-
|
|
3584
|
-
const seeded = seedField && TYPED_KINDS.has(seedKind) ? numeric ? Number.isFinite(Number(seed)) ? Number(seed) : void 0 : seed : void 0;
|
|
3794
|
+
const seeded = seedField ? draftFromTyping(seedField, seed) : void 0;
|
|
3795
|
+
if (seedField && seeded === void 0) return;
|
|
3585
3796
|
if (seeded !== void 0) {
|
|
3586
3797
|
startedFrom.current = current;
|
|
3587
3798
|
startedStored.current = current;
|
|
@@ -3632,7 +3843,11 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow,
|
|
|
3632
3843
|
},
|
|
3633
3844
|
[writable, client, editing, shownValue2, optimistic, rows, host, fields]
|
|
3634
3845
|
);
|
|
3635
|
-
const
|
|
3846
|
+
const openRef = (0, import_react19.useRef)(null);
|
|
3847
|
+
openRef.current = editing;
|
|
3848
|
+
const cancel = (0, import_react19.useCallback)((via = "key") => {
|
|
3849
|
+
const open = openRef.current;
|
|
3850
|
+
if (open && via !== "blur") endedRef.current?.("stay", open, via === "key" ? "keyboard" : "pointer");
|
|
3636
3851
|
setEditing(null);
|
|
3637
3852
|
setDraft(null);
|
|
3638
3853
|
typed.current = null;
|
|
@@ -3674,6 +3889,7 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow,
|
|
|
3674
3889
|
});
|
|
3675
3890
|
if (written.ok) {
|
|
3676
3891
|
rememberVersion(versions.current, rowId, written.data);
|
|
3892
|
+
settling.current.set(id, base);
|
|
3677
3893
|
writtenRef.current?.({ rowId, key, before: base, after: value });
|
|
3678
3894
|
reload();
|
|
3679
3895
|
return;
|
|
@@ -3721,11 +3937,25 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow,
|
|
|
3721
3937
|
[client, reload]
|
|
3722
3938
|
);
|
|
3723
3939
|
const commit = (0, import_react19.useCallback)(
|
|
3724
|
-
(move = null) => {
|
|
3940
|
+
(move = null, via = "key") => {
|
|
3725
3941
|
const open = editing;
|
|
3726
3942
|
if (!open) return;
|
|
3727
|
-
|
|
3943
|
+
let value = typed.current;
|
|
3944
|
+
if (isTypedDraft(value)) {
|
|
3945
|
+
if (value.judged === null || "refusal" in value.judged) return false;
|
|
3946
|
+
value = value.judged.value;
|
|
3947
|
+
}
|
|
3728
3948
|
if (!sameValue(startedFrom.current, value)) void writeCell(open.rowId, open.key, value, startedStored.current);
|
|
3949
|
+
const ended = endedRef.current;
|
|
3950
|
+
if (ended && via !== "blur") {
|
|
3951
|
+
setEditing(null);
|
|
3952
|
+
setDraft(null);
|
|
3953
|
+
typed.current = null;
|
|
3954
|
+
startedFrom.current = null;
|
|
3955
|
+
const exit = move === "next" ? "right" : move === "previous" ? "left" : via === "pick" ? "stay" : "down";
|
|
3956
|
+
ended(exit, open, via === "pick" ? "pointer" : "keyboard");
|
|
3957
|
+
return;
|
|
3958
|
+
}
|
|
3729
3959
|
if (!move) {
|
|
3730
3960
|
setEditing(null);
|
|
3731
3961
|
setDraft(null);
|
|
@@ -3937,6 +4167,7 @@ function GridCell({
|
|
|
3937
4167
|
const open = Boolean(
|
|
3938
4168
|
editing && editing.editing?.rowId === row.id && editing.editing.key === field2.key
|
|
3939
4169
|
);
|
|
4170
|
+
const wasActive = (0, import_react19.useRef)(false);
|
|
3940
4171
|
const value = editing ? editing.valueFor(row, field2) : row.document?.[field2.key];
|
|
3941
4172
|
const state = editing ? editing.stateOf(row.id, field2.key) : null;
|
|
3942
4173
|
const refusal2 = editing ? editing.refusalOf(row.id, field2.key) : null;
|
|
@@ -3948,8 +4179,8 @@ function GridCell({
|
|
|
3948
4179
|
field: field2,
|
|
3949
4180
|
draft: editing.draft,
|
|
3950
4181
|
onType: editing.type,
|
|
3951
|
-
onCommit: (move) => editing.commit(move),
|
|
3952
|
-
onCancel: editing.cancel,
|
|
4182
|
+
onCommit: (move, via) => editing.commit(move, via),
|
|
4183
|
+
onCancel: (via) => editing.cancel(via),
|
|
3953
4184
|
below: (
|
|
3954
4185
|
// KEPT EDITING: the refusal stays beside the value it is about, so the
|
|
3955
4186
|
// person reads what to change while changing it. Keep editing is the
|
|
@@ -3998,6 +4229,17 @@ function GridCell({
|
|
|
3998
4229
|
event.stopPropagation();
|
|
3999
4230
|
editing?.begin({ rowId: row.id, key: field2.key });
|
|
4000
4231
|
};
|
|
4232
|
+
const onPointerDown = (event) => {
|
|
4233
|
+
wasActive.current = event.currentTarget.closest("td")?.hasAttribute("data-matrx-cell-active") === true;
|
|
4234
|
+
};
|
|
4235
|
+
const onCellClick = (event) => {
|
|
4236
|
+
if (!editable) return;
|
|
4237
|
+
if (event.detail === 0) {
|
|
4238
|
+
openThisCell(event);
|
|
4239
|
+
return;
|
|
4240
|
+
}
|
|
4241
|
+
if (wasActive.current && opensAsAList(field2)) openThisCell(event);
|
|
4242
|
+
};
|
|
4001
4243
|
return (
|
|
4002
4244
|
// BOTH MARKERS, ON PURPOSE. `data-matrx-cell-control` is the data table's
|
|
4003
4245
|
// opt-out from whole-row click, so opening a cell never also opens the
|
|
@@ -4012,62 +4254,65 @@ function GridCell({
|
|
|
4012
4254
|
"data-matrx-cell-fill": "",
|
|
4013
4255
|
className: "group/cell relative flex w-full min-w-0 flex-col gap-0.5",
|
|
4014
4256
|
children: [
|
|
4015
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
event
|
|
4039
|
-
if (editable) editing?.begin({ rowId: row.id, key: field2.key });
|
|
4040
|
-
},
|
|
4041
|
-
onKeyDown: (event) => {
|
|
4042
|
-
if (editable && (event.key === "Enter" || event.key === "F2")) {
|
|
4043
|
-
event.preventDefault();
|
|
4044
|
-
editing?.begin({ rowId: row.id, key: field2.key });
|
|
4045
|
-
return;
|
|
4046
|
-
}
|
|
4047
|
-
if (onAskWhoChanged && asksWhoChanged(event)) {
|
|
4048
|
-
event.preventDefault();
|
|
4049
|
-
onAskWhoChanged(field2.key, row.id);
|
|
4050
|
-
return;
|
|
4051
|
-
}
|
|
4052
|
-
if (editable && isTypedCharacter(event)) {
|
|
4257
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "flex w-full min-w-0 items-center gap-1", children: [
|
|
4258
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4259
|
+
"span",
|
|
4260
|
+
{
|
|
4261
|
+
role: editable ? "button" : void 0,
|
|
4262
|
+
"aria-label": editable ? `Edit ${fieldName(field2)}` : void 0,
|
|
4263
|
+
title: editable ? `${fieldName(field2)} \u2014 double-click or press Enter to edit` : fieldIsEditable(field2) ? (
|
|
4264
|
+
// NOT A DEAD CONTROL AND NOT A SILENT ONE: the cell is plain
|
|
4265
|
+
// text, and hovering it says exactly why, in the level's own
|
|
4266
|
+
// words. The editor was never offered, so nothing is refused
|
|
4267
|
+
// after it has been typed into.
|
|
4268
|
+
`${fieldName(field2)} \u2014 ${whyNot ?? "you cannot change this record"}`
|
|
4269
|
+
) : `${fieldName(field2)} is worked out by the system, so it cannot be typed in`,
|
|
4270
|
+
className: (0, import_design_system12.cn)(
|
|
4271
|
+
// `w-full` so the whole cell is the control. A gap beside it is bare
|
|
4272
|
+
// table cell, and a click there is a row click.
|
|
4273
|
+
"block w-full min-w-0 flex-1 truncate rounded px-1 py-0.5",
|
|
4274
|
+
editable ? "cursor-text hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" : "",
|
|
4275
|
+
state === "saving" ? "opacity-60" : "",
|
|
4276
|
+
state === "refused" ? "ring-1 ring-destructive" : ""
|
|
4277
|
+
),
|
|
4278
|
+
onMouseDown: onPointerDown,
|
|
4279
|
+
onClick: editable ? onCellClick : void 0,
|
|
4280
|
+
onDoubleClick: (event) => {
|
|
4053
4281
|
event.preventDefault();
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4282
|
+
event.stopPropagation();
|
|
4283
|
+
if (editable) editing?.begin({ rowId: row.id, key: field2.key });
|
|
4284
|
+
},
|
|
4285
|
+
onKeyDown: (event) => {
|
|
4286
|
+
if (editable && (event.key === "Enter" || event.key === "F2")) {
|
|
4287
|
+
event.preventDefault();
|
|
4288
|
+
editing?.begin({ rowId: row.id, key: field2.key });
|
|
4289
|
+
return;
|
|
4290
|
+
}
|
|
4291
|
+
if (onAskWhoChanged && asksWhoChanged(event)) {
|
|
4292
|
+
event.preventDefault();
|
|
4293
|
+
onAskWhoChanged(field2.key, row.id);
|
|
4294
|
+
return;
|
|
4295
|
+
}
|
|
4296
|
+
if (editable && isTypedCharacter(event) && typingOpens(field2) !== "none") {
|
|
4297
|
+
event.preventDefault();
|
|
4298
|
+
editing?.begin({ rowId: row.id, key: field2.key }, event.key);
|
|
4299
|
+
}
|
|
4300
|
+
},
|
|
4301
|
+
tabIndex: editable || onAskWhoChanged ? 0 : void 0,
|
|
4302
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4303
|
+
RecordValue,
|
|
4304
|
+
{
|
|
4305
|
+
field: field2,
|
|
4306
|
+
value,
|
|
4307
|
+
document: row.document,
|
|
4308
|
+
withheld: withheldNoticeOf(row.hidden, field2.key),
|
|
4309
|
+
...format ? { format } : {}
|
|
4310
|
+
}
|
|
4311
|
+
)
|
|
4312
|
+
}
|
|
4313
|
+
),
|
|
4314
|
+
onAskWhoChanged ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(WhoBadge, { field: field2, row, onAskWhoChanged }) : null
|
|
4315
|
+
] }),
|
|
4071
4316
|
state === "saving" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "px-1 text-[10px] text-muted-foreground", children: "Saving\u2026" }) : null,
|
|
4072
4317
|
refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(CellRefusalNotice, { refusal: refusal2, field: field2, row, editing, typed: true }) : null
|
|
4073
4318
|
]
|
|
@@ -4122,9 +4367,10 @@ function WhoBadge({
|
|
|
4122
4367
|
"button",
|
|
4123
4368
|
{
|
|
4124
4369
|
type: "button",
|
|
4370
|
+
"data-records-who-badge": "",
|
|
4125
4371
|
"aria-label": `Who changed ${fieldName(field2)}?`,
|
|
4126
4372
|
title: who.sentence ?? `Who changed ${fieldName(field2)}? (press H)`,
|
|
4127
|
-
className: "
|
|
4373
|
+
className: "hidden max-w-[9rem] shrink-0 select-none truncate rounded px-1 text-[10px] leading-4 text-muted-foreground hover:bg-muted focus-visible:inline-flex in-data-[matrx-cell-active=true]:inline-flex",
|
|
4128
4374
|
onMouseEnter: ask,
|
|
4129
4375
|
onFocus: ask,
|
|
4130
4376
|
onClick: (event) => {
|
|
@@ -4155,6 +4401,12 @@ function closesOnBlur(field2) {
|
|
|
4155
4401
|
return true;
|
|
4156
4402
|
}
|
|
4157
4403
|
}
|
|
4404
|
+
function opensAsAList(field2) {
|
|
4405
|
+
const kind = editorKindFor(field2);
|
|
4406
|
+
if (kind === "member") return true;
|
|
4407
|
+
if (kind === "select" || kind === "multi_select") return !(CHOICE_EDITOR_ADDS_WHAT_IS_TYPED && (0, import_records6.allowsOtherValues)(field2));
|
|
4408
|
+
return false;
|
|
4409
|
+
}
|
|
4158
4410
|
function takesANewline(field2) {
|
|
4159
4411
|
const kind = editorKindFor(field2);
|
|
4160
4412
|
return kind === "long_text" || kind === "json";
|
|
@@ -4168,12 +4420,13 @@ function OpenCell({
|
|
|
4168
4420
|
below
|
|
4169
4421
|
}) {
|
|
4170
4422
|
const multiline = takesANewline(field2);
|
|
4171
|
-
const
|
|
4423
|
+
const picker = opensAsAList(field2);
|
|
4424
|
+
const blurCommits = !picker && closesOnBlur(field2);
|
|
4172
4425
|
const done = (0, import_react19.useRef)(false);
|
|
4173
4426
|
const once = (run) => {
|
|
4174
4427
|
if (done.current) return;
|
|
4175
4428
|
done.current = true;
|
|
4176
|
-
run();
|
|
4429
|
+
if (run() === false) done.current = false;
|
|
4177
4430
|
};
|
|
4178
4431
|
const host = (0, import_react19.useRef)(null);
|
|
4179
4432
|
(0, import_react19.useEffect)(() => {
|
|
@@ -4190,7 +4443,7 @@ function OpenCell({
|
|
|
4190
4443
|
const blur = blurCommits ? {
|
|
4191
4444
|
onBlur: (event) => {
|
|
4192
4445
|
if (event.currentTarget.contains(event.relatedTarget)) return;
|
|
4193
|
-
once(() => onCommit(null));
|
|
4446
|
+
once(() => onCommit(null, "blur"));
|
|
4194
4447
|
}
|
|
4195
4448
|
} : {};
|
|
4196
4449
|
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
@@ -4205,28 +4458,37 @@ function OpenCell({
|
|
|
4205
4458
|
if (event.key === "Escape") {
|
|
4206
4459
|
event.preventDefault();
|
|
4207
4460
|
event.stopPropagation();
|
|
4208
|
-
once(onCancel);
|
|
4461
|
+
once(() => onCancel("key"));
|
|
4209
4462
|
return;
|
|
4210
4463
|
}
|
|
4211
4464
|
if (event.key === "Tab") {
|
|
4212
4465
|
event.preventDefault();
|
|
4213
4466
|
event.stopPropagation();
|
|
4214
|
-
once(() => onCommit(event.shiftKey ? "previous" : "next"));
|
|
4467
|
+
once(() => onCommit(event.shiftKey ? "previous" : "next", "key"));
|
|
4215
4468
|
return;
|
|
4216
4469
|
}
|
|
4217
4470
|
if (event.key === "Enter") {
|
|
4218
4471
|
if (multiline && event.shiftKey && !event.metaKey && !event.ctrlKey) return;
|
|
4219
4472
|
event.preventDefault();
|
|
4220
4473
|
event.stopPropagation();
|
|
4221
|
-
once(() => onCommit(null));
|
|
4474
|
+
once(() => onCommit(null, "key"));
|
|
4222
4475
|
}
|
|
4223
4476
|
},
|
|
4224
4477
|
...blur,
|
|
4225
4478
|
children: [
|
|
4226
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4479
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4480
|
+
FieldControl,
|
|
4481
|
+
{
|
|
4482
|
+
field: field2,
|
|
4483
|
+
value: draft,
|
|
4484
|
+
onChange: onType,
|
|
4485
|
+
id: `cell-${field2.id}`,
|
|
4486
|
+
...picker ? { autoOpen: true, onClose: () => once(() => onCommit(null, "pick")) } : {}
|
|
4487
|
+
}
|
|
4488
|
+
),
|
|
4489
|
+
picker ? null : blurCommits ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[10px] text-muted-foreground", children: multiline ? "\u23CE saves \xB7 \u21E7\u23CE new line \xB7 Esc cancels \xB7 Tab next" : "\u23CE saves \xB7 Esc cancels \xB7 Tab next" }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "flex items-center gap-1", children: [
|
|
4490
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system12.Button, { size: "sm", onClick: () => once(() => onCommit(null, "pick")), children: "Save" }),
|
|
4491
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system12.Button, { size: "sm", variant: "ghost", onClick: () => once(() => onCancel("pick")), children: "Cancel" })
|
|
4230
4492
|
] }),
|
|
4231
4493
|
below
|
|
4232
4494
|
]
|
|
@@ -4237,8 +4499,9 @@ function OpenCell({
|
|
|
4237
4499
|
// src/Grid.tsx
|
|
4238
4500
|
var import_react57 = require("react");
|
|
4239
4501
|
var import_react58 = require("@ai-matrx/records/react");
|
|
4240
|
-
var
|
|
4502
|
+
var import_records15 = require("@ai-matrx/records");
|
|
4241
4503
|
var import_data_table9 = require("@ai-matrx/design-system/data-table");
|
|
4504
|
+
var import_core8 = require("@ai-matrx/records/core");
|
|
4242
4505
|
|
|
4243
4506
|
// src/archiveWords.ts
|
|
4244
4507
|
function archiveConfirmSentence(what) {
|
|
@@ -4284,10 +4547,10 @@ var import_design_system17 = require("@ai-matrx/design-system");
|
|
|
4284
4547
|
// src/FormulaTextEditor.tsx
|
|
4285
4548
|
var import_react23 = require("react");
|
|
4286
4549
|
var import_react24 = require("@ai-matrx/records/react");
|
|
4287
|
-
var
|
|
4550
|
+
var import_records7 = require("@ai-matrx/records");
|
|
4288
4551
|
var import_design_system13 = require("@ai-matrx/design-system");
|
|
4289
4552
|
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
4290
|
-
var FORMULA_FUNCTIONS =
|
|
4553
|
+
var FORMULA_FUNCTIONS = import_records7.RULE_NODE_KINDS.map((row) => row.node).filter((node) => node.startsWith("fx.")).map((node) => node.slice(3).toUpperCase()).filter((name) => !["ADD", "SUB", "MUL", "DIV", "EQ", "NEQ", "GT", "GTE", "LT", "LTE", "NEG", "FIELD", "CONST"].includes(name)).sort();
|
|
4291
4554
|
var RESULT_WORD = {
|
|
4292
4555
|
number: "a number",
|
|
4293
4556
|
text: "text",
|
|
@@ -4620,7 +4883,7 @@ function ChoiceColorsEditor({
|
|
|
4620
4883
|
|
|
4621
4884
|
// src/FieldFormatPicker.tsx
|
|
4622
4885
|
var import_design_system16 = require("@ai-matrx/design-system");
|
|
4623
|
-
var
|
|
4886
|
+
var import_field_formats7 = require("@ai-matrx/design-system/field-formats");
|
|
4624
4887
|
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
4625
4888
|
var KIND_WORD = {
|
|
4626
4889
|
choice: "select",
|
|
@@ -4683,7 +4946,7 @@ function formatMenu(field2) {
|
|
|
4683
4946
|
if (base === null) return { groups: [], otherKinds: [] };
|
|
4684
4947
|
if (base === "formula") {
|
|
4685
4948
|
const groups2 = [];
|
|
4686
|
-
for (const def of
|
|
4949
|
+
for (const def of import_field_formats7.FIELD_FORMAT_LIST) {
|
|
4687
4950
|
if (neverALook(def) || KIND_WORD[def.id] || def.id === "boolean") continue;
|
|
4688
4951
|
const bucket = groups2.find((g) => g.group === def.group);
|
|
4689
4952
|
if (bucket) bucket.formats.push(def);
|
|
@@ -4691,10 +4954,10 @@ function formatMenu(field2) {
|
|
|
4691
4954
|
}
|
|
4692
4955
|
return { groups: groups2, otherKinds: [] };
|
|
4693
4956
|
}
|
|
4694
|
-
const groups = (0,
|
|
4957
|
+
const groups = (0, import_field_formats7.groupedFormatsForBase)(base).map((g) => ({ group: g.group, formats: g.formats.filter((d) => !neverALook(d) && !KIND_WORD[d.id]) })).filter((g) => g.formats.length > 0);
|
|
4695
4958
|
const own2 = new Set(groups.flatMap((g) => g.formats.map((d) => d.id)));
|
|
4696
4959
|
const otherKinds = [];
|
|
4697
|
-
for (const def of
|
|
4960
|
+
for (const def of import_field_formats7.FIELD_FORMAT_LIST) {
|
|
4698
4961
|
if (own2.has(def.id) || neverALook(def)) continue;
|
|
4699
4962
|
const word = KIND_WORD[def.id] ?? PLAIN_WORD[def.base];
|
|
4700
4963
|
if (word) otherKinds.push({ def, word });
|
|
@@ -4703,7 +4966,7 @@ function formatMenu(field2) {
|
|
|
4703
4966
|
}
|
|
4704
4967
|
function startingChoice(field2) {
|
|
4705
4968
|
const held = displayFormatOf(field2);
|
|
4706
|
-
if (!held || !(0,
|
|
4969
|
+
if (!held || !(0, import_field_formats7.getFieldFormat)(held.id) || held.id === "formula") return { format: null, retype: null };
|
|
4707
4970
|
return {
|
|
4708
4971
|
format: Object.keys(held.options).length > 0 ? { id: held.id, options: held.options } : { id: held.id },
|
|
4709
4972
|
retype: null
|
|
@@ -4733,7 +4996,7 @@ function FieldFormatPicker({
|
|
|
4733
4996
|
}) {
|
|
4734
4997
|
const { groups, otherKinds } = formatMenu(field2);
|
|
4735
4998
|
if (groups.length === 0 && otherKinds.length === 0) return null;
|
|
4736
|
-
const active = value.format ? (0,
|
|
4999
|
+
const active = value.format ? (0, import_field_formats7.getFieldFormat)(value.format.id) : null;
|
|
4737
5000
|
const options = value.format?.options ?? {};
|
|
4738
5001
|
const optionKeys = active?.optionKeys ?? [];
|
|
4739
5002
|
const retypedTo = value.retype ? otherKinds.find((k) => k.def.id === value.format?.id) : void 0;
|
|
@@ -4897,125 +5160,474 @@ function FieldFormatPicker({
|
|
|
4897
5160
|
] });
|
|
4898
5161
|
}
|
|
4899
5162
|
|
|
4900
|
-
// src/
|
|
4901
|
-
var
|
|
4902
|
-
var
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
var ROLLUP_OPERATIONS = ROLLUP_AGG_VALUES.map((value) => ({
|
|
4907
|
-
value,
|
|
4908
|
-
label: ROLLUP_AGG_LABEL[value]
|
|
4909
|
-
}));
|
|
4910
|
-
var FORMULA_SEPARATOR_LABEL = "Joined by";
|
|
4911
|
-
var EDITING_A_WORKED_OUT_COLUMN = "This column works out its answer from the columns picked when it was made. To change what it works out, write it as a formula below \u2014 the store checks it as you type, and a rename alone leaves it exactly as it is.";
|
|
4912
|
-
var FORMULA_DEFAULT_SEPARATOR = " \u2014 ";
|
|
4913
|
-
var FORMULA_OPERATIONS = FORMULA_OP_VALUES.map((value) => ({
|
|
4914
|
-
value,
|
|
4915
|
-
label: FORMULA_OP_LABEL[value],
|
|
4916
|
-
joins: FORMULA_OP_JOINS[value]
|
|
4917
|
-
}));
|
|
4918
|
-
function keyFor(label) {
|
|
4919
|
-
const token = label.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48);
|
|
4920
|
-
if (token === "") return "";
|
|
4921
|
-
return /^[a-z]/.test(token) ? token : `f_${token}`;
|
|
5163
|
+
// src/gridPage.ts
|
|
5164
|
+
var import_records8 = require("@ai-matrx/records");
|
|
5165
|
+
var import_data_table6 = require("@ai-matrx/design-system/data-table");
|
|
5166
|
+
function sortRefusal(f) {
|
|
5167
|
+
if (!computedOnRead(f)) return null;
|
|
5168
|
+
return `${fieldName(f)} is worked out each time it is read, so the store keeps no value to sort the whole table by. Sort by a column it is worked out from.`;
|
|
4922
5169
|
}
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
const
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
const
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
const
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
const
|
|
4951
|
-
const
|
|
4952
|
-
|
|
4953
|
-
const
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5170
|
+
function filterRefusal(f) {
|
|
5171
|
+
if (!computedOnRead(f)) return null;
|
|
5172
|
+
return `${fieldName(f)} is worked out each time it is read, so the store keeps no value to filter the whole table by. Filter by a column it is worked out from.`;
|
|
5173
|
+
}
|
|
5174
|
+
function computedOnRead(f) {
|
|
5175
|
+
const kind = String(editorKindFor(f));
|
|
5176
|
+
return (f.type === "formula" || kind === "formula" || kind === "lookup" || kind === "rollup") && String(f.compute_on ?? "read") === "read";
|
|
5177
|
+
}
|
|
5178
|
+
function sortAsFor(field2) {
|
|
5179
|
+
const kind = String(editorKindFor(field2));
|
|
5180
|
+
if (kind === "number" || kind === "currency" || kind === "percent") return "number";
|
|
5181
|
+
if (kind === "datetime") return "date";
|
|
5182
|
+
return "text";
|
|
5183
|
+
}
|
|
5184
|
+
function pageSortFor(fields, sort) {
|
|
5185
|
+
if (!sort) return [];
|
|
5186
|
+
const field2 = fields.find((f) => f.key === sort.id);
|
|
5187
|
+
if (!field2) return [];
|
|
5188
|
+
return [{ field: field2.key, direction: sort.direction === "desc" ? "desc" : "asc", as: sortAsFor(field2) }];
|
|
5189
|
+
}
|
|
5190
|
+
function containsPattern(text) {
|
|
5191
|
+
return `(?i)${text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`;
|
|
5192
|
+
}
|
|
5193
|
+
var field = (f) => ({ field: f.id });
|
|
5194
|
+
var constant = (value) => ({ const: value });
|
|
5195
|
+
var op = (name, ...args) => ({ op: name, args });
|
|
5196
|
+
function clauseFor(f, value, now = /* @__PURE__ */ new Date()) {
|
|
5197
|
+
const name = fieldName(f);
|
|
5198
|
+
const refused = filterRefusal(f);
|
|
5199
|
+
if (refused) return refused;
|
|
5200
|
+
const not = (rule) => op("not", rule);
|
|
5201
|
+
switch (value.kind) {
|
|
5202
|
+
case "text": {
|
|
5203
|
+
const mode = value.mode ?? "contains";
|
|
5204
|
+
if (mode === "empty" || mode === "null") return value.negated ? { rule: op("present", field(f)) } : { rule: not(op("present", field(f))) };
|
|
5205
|
+
if (mode === "not_empty" || mode === "not_null") return value.negated ? { rule: not(op("present", field(f))) } : { rule: op("present", field(f)) };
|
|
5206
|
+
const text = value.value.trim();
|
|
5207
|
+
if (text === "") return null;
|
|
5208
|
+
const kind = String(editorKindFor(f));
|
|
5209
|
+
if (kind === "number" || kind === "currency" || kind === "percent" || kind === "checkbox") {
|
|
5210
|
+
return `${name} holds numbers, so it is filtered by a range, not by words.`;
|
|
5211
|
+
}
|
|
5212
|
+
const matches2 = op("matches", field(f), constant(containsPattern(text)));
|
|
5213
|
+
return { rule: value.negated ? not(matches2) : matches2 };
|
|
5214
|
+
}
|
|
5215
|
+
case "select": {
|
|
5216
|
+
const words2 = value.values ?? (value.value === "" ? [] : [value.value]);
|
|
5217
|
+
if (words2.length === 0) return value.negated ? null : { rule: constant(false) };
|
|
5218
|
+
if (words2.length === 1 && !value.negated) return { flat: { [f.key]: words2[0] } };
|
|
5219
|
+
const any = words2.length === 1 ? op("eq", field(f), constant(words2[0])) : op("or", ...words2.map((w) => op("eq", field(f), constant(w))));
|
|
5220
|
+
return { rule: value.negated ? not(any) : any };
|
|
5221
|
+
}
|
|
5222
|
+
case "boolean":
|
|
5223
|
+
return value.negated ? { rule: op("ne", field(f), constant(value.value)) } : { flat: { [f.key]: value.value } };
|
|
5224
|
+
case "number": {
|
|
5225
|
+
if (value.op === "empty") return { rule: not(op("present", field(f))) };
|
|
5226
|
+
if (value.op === "not_empty") return { rule: op("present", field(f)) };
|
|
5227
|
+
if (value.op === "eq" || value.op === "ne") {
|
|
5228
|
+
if (typeof value.min !== "number") return null;
|
|
5229
|
+
return { rule: op(value.op, field(f), constant(value.min)) };
|
|
5230
|
+
}
|
|
5231
|
+
if (value.op === "lt") return typeof value.max === "number" ? { rule: op("lt", field(f), constant(value.max)) } : null;
|
|
5232
|
+
if (value.op === "gt") return typeof value.min === "number" ? { rule: op("gt", field(f), constant(value.min)) } : null;
|
|
5233
|
+
const parts = [];
|
|
5234
|
+
if (typeof value.min === "number") parts.push(op("gte", field(f), constant(value.min)));
|
|
5235
|
+
if (typeof value.max === "number") parts.push(op("lte", field(f), constant(value.max)));
|
|
5236
|
+
if (parts.length === 0) return null;
|
|
5237
|
+
const range = parts.length === 1 ? parts[0] : op("and", ...parts);
|
|
5238
|
+
return { rule: value.negated ? not(range) : range };
|
|
5239
|
+
}
|
|
5240
|
+
case "date": {
|
|
5241
|
+
const bounds = (0, import_data_table6.dateFilterBounds)(value, now);
|
|
5242
|
+
if (bounds.since === void 0 && bounds.until === void 0) return null;
|
|
5243
|
+
return {
|
|
5244
|
+
flat: {
|
|
5245
|
+
[f.key]: {
|
|
5246
|
+
...bounds.since === void 0 ? {} : { from: bounds.since },
|
|
5247
|
+
...bounds.until === void 0 ? {} : { to: bounds.until }
|
|
5248
|
+
}
|
|
5249
|
+
}
|
|
5250
|
+
};
|
|
5251
|
+
}
|
|
5252
|
+
default:
|
|
5253
|
+
return `${name} cannot be filtered that way by the store.`;
|
|
5254
|
+
}
|
|
5255
|
+
}
|
|
5256
|
+
function storeQuestion(fields, base, columnFilters, now = /* @__PURE__ */ new Date()) {
|
|
5257
|
+
const refused = [];
|
|
5258
|
+
const flat = {};
|
|
5259
|
+
const rules = [];
|
|
5260
|
+
if (base && (0, import_records8.isRuleFilter)(base)) rules.push(base);
|
|
5261
|
+
else if (base) Object.assign(flat, base);
|
|
5262
|
+
for (const [key, value] of Object.entries(columnFilters)) {
|
|
5263
|
+
if (!value) continue;
|
|
5264
|
+
const f = fields.find((candidate) => candidate.key === key);
|
|
5265
|
+
if (!f) continue;
|
|
5266
|
+
const clause = clauseFor(f, value, now);
|
|
5267
|
+
if (clause === null) continue;
|
|
5268
|
+
if (typeof clause === "string") {
|
|
5269
|
+
refused.push(clause);
|
|
5270
|
+
continue;
|
|
5271
|
+
}
|
|
5272
|
+
if ("flat" in clause) Object.assign(flat, clause.flat);
|
|
5273
|
+
else rules.push(clause.rule);
|
|
5274
|
+
}
|
|
5275
|
+
const flatKeys = Object.keys(flat);
|
|
5276
|
+
if (rules.length === 0) return { filter: flatKeys.length > 0 ? flat : void 0, refused };
|
|
5277
|
+
for (const key of flatKeys) {
|
|
5278
|
+
const f = fields.find((candidate) => candidate.key === key);
|
|
5279
|
+
const value = flat[key];
|
|
5280
|
+
if (!f) continue;
|
|
5281
|
+
if (value !== null && typeof value === "object") {
|
|
5282
|
+
refused.push(`${fieldName(f)}'s date range cannot be asked together with the other filters yet, so it is left off.`);
|
|
5283
|
+
continue;
|
|
5284
|
+
}
|
|
5285
|
+
rules.push(value === null ? op("not", op("present", field(f))) : op("eq", field(f), constant(value)));
|
|
5286
|
+
}
|
|
5287
|
+
return { filter: rules.length === 1 ? rules[0] : op("and", ...rules), refused };
|
|
5288
|
+
}
|
|
5289
|
+
function inColumnWords(error, fields) {
|
|
5290
|
+
const say2 = (text) => {
|
|
5291
|
+
if (!text) return text;
|
|
5292
|
+
let out = text;
|
|
5293
|
+
for (const f of [...fields].sort((a, b) => b.key.length - a.key.length)) {
|
|
5294
|
+
const key = f.key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5295
|
+
out = out.replace(new RegExp(`(["\u201C'])${key}(["\u201D'])`, "g"), `$1${fieldName(f)}$2`);
|
|
5296
|
+
}
|
|
5297
|
+
return out;
|
|
5298
|
+
};
|
|
5299
|
+
return { ...error, message: say2(error.message) ?? error.message, ...error.hint ? { hint: say2(error.hint) ?? error.hint } : {} };
|
|
5300
|
+
}
|
|
5301
|
+
|
|
5302
|
+
// src/gridMenus.ts
|
|
5303
|
+
function fieldOf(ctx, columnId) {
|
|
5304
|
+
return ctx.fields.find((f) => f.key === columnId);
|
|
5305
|
+
}
|
|
5306
|
+
function columnWords(ctx, columnId) {
|
|
5307
|
+
const field2 = fieldOf(ctx, columnId);
|
|
5308
|
+
return field2 ? fieldName(field2) : columnId;
|
|
5309
|
+
}
|
|
5310
|
+
function rowLabelRefusal(field2) {
|
|
5311
|
+
if (!field2) return "This column is not one of the table's own.";
|
|
5312
|
+
const kind = String(field2.type);
|
|
5313
|
+
if (kind === "formula" || kind === "lookup" || kind === "rollup") {
|
|
5314
|
+
return `${fieldName(field2)} is worked out by the table, and a record is named by a column people fill in.`;
|
|
5315
|
+
}
|
|
5316
|
+
if (kind === "relation") return `${fieldName(field2)} points at other records; a record is named by its own words.`;
|
|
5317
|
+
return void 0;
|
|
5318
|
+
}
|
|
5319
|
+
function headerMenuItems(columnId, ctx) {
|
|
5320
|
+
const field2 = fieldOf(ctx, columnId);
|
|
5321
|
+
const shape = ctx.mayStructure ? void 0 : ctx.whyNotStructure;
|
|
5322
|
+
const isLabel = ctx.titleField === columnId;
|
|
5323
|
+
const labelRefusal = shape ?? (isLabel ? "Records are already named by this column." : rowLabelRefusal(field2));
|
|
5324
|
+
const a = ctx.actions;
|
|
5325
|
+
return [
|
|
5326
|
+
{ id: "col-rename", label: "Rename column", icon: "pencil", disabledReason: shape, onSelect: () => a.renameColumn(columnId) },
|
|
5327
|
+
{ id: "col-settings", label: "Column settings\u2026", icon: "settings", disabledReason: shape, onSelect: () => a.columnSettings(columnId) },
|
|
5328
|
+
{ id: "col-insert-left", label: "Insert column left\u2026", icon: "panel-left", disabledReason: shape, onSelect: () => a.insertColumn(columnId, "left") },
|
|
5329
|
+
{ id: "col-insert-right", label: "Insert column right\u2026", icon: "panel-right", disabledReason: shape, onSelect: () => a.insertColumn(columnId, "right") },
|
|
5330
|
+
{
|
|
5331
|
+
id: "col-row-label",
|
|
5332
|
+
label: isLabel ? "Names each record" : "Use as row label",
|
|
5333
|
+
icon: "tag",
|
|
5334
|
+
disabledReason: labelRefusal,
|
|
5335
|
+
onSelect: () => a.useAsRowLabel(columnId)
|
|
5336
|
+
},
|
|
5337
|
+
...ctx.extras?.header?.(columnId) ?? [],
|
|
5338
|
+
{ id: "col-hide", label: "Hide column", icon: "eye-off", onSelect: () => a.hideColumn(columnId) },
|
|
5339
|
+
{
|
|
5340
|
+
id: "col-delete",
|
|
5341
|
+
label: "Delete column\u2026",
|
|
5342
|
+
icon: "trash",
|
|
5343
|
+
destructive: true,
|
|
5344
|
+
disabledReason: shape ?? (isLabel ? "Records are named by this column; pick another row label first." : void 0),
|
|
5345
|
+
onSelect: () => a.deleteColumn(columnId)
|
|
5346
|
+
}
|
|
5347
|
+
];
|
|
5348
|
+
}
|
|
5349
|
+
function cellSection(payload, ctx) {
|
|
5350
|
+
const a = ctx.actions;
|
|
5351
|
+
const { rowId, columnId } = payload;
|
|
5352
|
+
const mayWrite = ctx.mayWriteRow(rowId);
|
|
5353
|
+
const typeable = ctx.typeable(columnId);
|
|
5354
|
+
const why = !mayWrite ? ctx.whyNotWrite(rowId) : !typeable ? `${columnWords(ctx, columnId)} is worked out by the table; nobody types it.` : void 0;
|
|
5355
|
+
const items = [
|
|
5356
|
+
{ id: "cell-copy", label: "Copy cell", icon: "copy", shortcut: "\u2318C", onSelect: () => a.copyText(payload.text, "The cell") },
|
|
5357
|
+
{ id: "cell-paste", label: "Paste", icon: "clipboard-paste", shortcut: "\u2318V", disabledReason: why, onSelect: () => a.pasteInto(rowId, columnId) },
|
|
5358
|
+
{ id: "cell-clear", label: "Clear cell", icon: "eraser", destructive: true, disabledReason: why, onSelect: () => a.clearCells([{ rowId, columnId }]) }
|
|
5359
|
+
];
|
|
5360
|
+
if (a.askWhoChanged) {
|
|
5361
|
+
items.push({ id: "cell-history", label: "Who changed this?", icon: "history", shortcut: "\u2325\u21E7H", onSelect: () => a.askWhoChanged?.(columnId, rowId) });
|
|
5362
|
+
}
|
|
5363
|
+
const empty = payload.value === null || payload.value === void 0 || payload.value === "";
|
|
5364
|
+
items.push({
|
|
5365
|
+
id: "cell-filter",
|
|
5366
|
+
label: empty ? "Show rows where this is empty" : "Show rows with this value",
|
|
5367
|
+
icon: "filter",
|
|
5368
|
+
onSelect: () => a.filterBy(columnId, payload.value)
|
|
5369
|
+
});
|
|
5370
|
+
items.push(...ctx.extras?.cell?.(rowId, columnId) ?? []);
|
|
5371
|
+
return { id: "cell", title: payload.columnLabel || "Cell", items };
|
|
5372
|
+
}
|
|
5373
|
+
function rowSection(rowId, ctx, rowText) {
|
|
5374
|
+
const a = ctx.actions;
|
|
5375
|
+
const mayWrite = ctx.mayWriteRow(rowId);
|
|
5376
|
+
const items = [];
|
|
5377
|
+
if (a.openRecord) {
|
|
5378
|
+
items.push({ id: "row-open", label: "Open record", icon: "panel-right", shortcut: "Space", onSelect: () => a.openRecord?.(rowId) });
|
|
5379
|
+
items.push({ id: "row-history", label: "Row history", icon: "history", onSelect: () => a.openRecord?.(rowId) });
|
|
5380
|
+
}
|
|
5381
|
+
if (rowText !== null) {
|
|
5382
|
+
items.push({ id: "row-copy", label: "Copy row", icon: "copy", onSelect: () => a.copyText(rowText, "The row") });
|
|
5383
|
+
}
|
|
5384
|
+
const noPlace = "This view is sorted, so a new record lands where its sort puts it. Reorder places rows by hand; New record adds one.";
|
|
5385
|
+
const insertWhy = !ctx.mayAddRecord ? ctx.whyNotAdd : a.insertRow ? void 0 : noPlace;
|
|
5386
|
+
items.push(
|
|
5387
|
+
{ id: "row-insert-above", label: "Insert row above", icon: "plus", disabledReason: insertWhy, onSelect: () => a.insertRow?.(rowId, "above") },
|
|
5388
|
+
{ id: "row-insert-below", label: "Insert row below", icon: "plus", disabledReason: insertWhy, onSelect: () => a.insertRow?.(rowId, "below") },
|
|
5389
|
+
{
|
|
5390
|
+
id: "row-duplicate",
|
|
5391
|
+
label: "Duplicate row",
|
|
5392
|
+
icon: "copy-plus",
|
|
5393
|
+
disabledReason: ctx.mayAddRecord ? void 0 : ctx.whyNotAdd,
|
|
5394
|
+
onSelect: () => a.duplicateRow(rowId)
|
|
5395
|
+
},
|
|
5396
|
+
{
|
|
5397
|
+
id: "row-archive",
|
|
5398
|
+
label: "Delete row\u2026",
|
|
5399
|
+
icon: "trash",
|
|
5400
|
+
destructive: true,
|
|
5401
|
+
disabledReason: mayWrite ? void 0 : ctx.whyNotWrite(rowId),
|
|
5402
|
+
onSelect: () => a.archiveRow(rowId)
|
|
5403
|
+
}
|
|
5404
|
+
);
|
|
5405
|
+
items.push(...ctx.extras?.row?.(rowId) ?? []);
|
|
5406
|
+
return { id: "row", title: ctx.extras?.rowTitle?.(rowId) || "Row", items };
|
|
5407
|
+
}
|
|
5408
|
+
function columnSection(columnId, columnLabel2, ctx) {
|
|
5409
|
+
const a = ctx.actions;
|
|
5410
|
+
const sorted = ctx.sortedBy?.id === columnId ? ctx.sortedBy.direction : null;
|
|
5411
|
+
const field2 = fieldOf(ctx, columnId);
|
|
5412
|
+
const refused = field2 ? sortRefusal(field2) ?? void 0 : void 0;
|
|
5413
|
+
return {
|
|
5414
|
+
id: "column",
|
|
5415
|
+
title: columnLabel2 || columnWords(ctx, columnId),
|
|
5416
|
+
items: [
|
|
5417
|
+
{ id: "col-sort-asc", label: "Sort A\u2192Z", icon: "arrow-up-a-z", disabledReason: refused ?? (sorted === "asc" ? "Already sorted A\u2192Z." : void 0), onSelect: () => a.sortBy(columnId, "asc") },
|
|
5418
|
+
{ id: "col-sort-desc", label: "Sort Z\u2192A", icon: "arrow-down-a-z", disabledReason: refused ?? (sorted === "desc" ? "Already sorted Z\u2192A." : void 0), onSelect: () => a.sortBy(columnId, "desc") },
|
|
5419
|
+
...sorted ? [{ id: "col-sort-clear", label: "Clear sort", icon: "arrow-up-down", onSelect: () => a.sortBy(columnId, null) }] : [],
|
|
5420
|
+
...headerMenuItems(columnId, ctx)
|
|
5421
|
+
]
|
|
5422
|
+
};
|
|
5423
|
+
}
|
|
5424
|
+
function tableSection(ctx) {
|
|
5425
|
+
const a = ctx.actions;
|
|
5426
|
+
const items = [
|
|
5427
|
+
{ id: "table-new-record", label: "New record", icon: "plus", disabledReason: ctx.mayAddRecord ? void 0 : ctx.whyNotAdd, onSelect: () => a.newRecord() }
|
|
5428
|
+
];
|
|
5429
|
+
if (a.addColumn) {
|
|
5430
|
+
items.push({
|
|
5431
|
+
id: "table-add-column",
|
|
5432
|
+
label: "Add column\u2026",
|
|
5433
|
+
icon: "plus",
|
|
5434
|
+
disabledReason: ctx.mayStructure ? void 0 : ctx.whyNotStructure,
|
|
5435
|
+
onSelect: () => a.addColumn?.()
|
|
5436
|
+
});
|
|
5437
|
+
}
|
|
5438
|
+
items.push(...ctx.extras?.table?.() ?? []);
|
|
5439
|
+
return { id: "table", title: "Table", items };
|
|
5440
|
+
}
|
|
5441
|
+
function gridMenuSections(payload, ctx, rowText = () => null) {
|
|
5442
|
+
switch (payload.level) {
|
|
5443
|
+
case "cell":
|
|
5444
|
+
return [
|
|
5445
|
+
{ ...cellSection(payload, ctx), primary: true },
|
|
5446
|
+
rowSection(payload.rowId, ctx, rowText(payload.rowId)),
|
|
5447
|
+
columnSection(payload.columnId, payload.columnLabel, ctx),
|
|
5448
|
+
tableSection(ctx)
|
|
5449
|
+
];
|
|
5450
|
+
case "row":
|
|
5451
|
+
return [{ ...rowSection(payload.rowId, ctx, rowText(payload.rowId)), primary: true }, tableSection(ctx)];
|
|
5452
|
+
case "column":
|
|
5453
|
+
return [{ ...columnSection(payload.columnId, payload.columnLabel, ctx), primary: true }, tableSection(ctx)];
|
|
5454
|
+
case "selection": {
|
|
5455
|
+
const n = payload.cellCount;
|
|
5456
|
+
const blocked = payload.rowIds.find((id) => !ctx.mayWriteRow(id));
|
|
5457
|
+
const untypeable = payload.columnIds.find((id) => !ctx.typeable(id));
|
|
5458
|
+
const why = blocked ? ctx.whyNotWrite(blocked) : untypeable ? `${columnWords(ctx, untypeable)} is worked out by the table; nobody types it.` : void 0;
|
|
5459
|
+
const cells = payload.rowIds.flatMap((rowId) => payload.columnIds.map((columnId) => ({ rowId, columnId })));
|
|
5460
|
+
return [
|
|
5461
|
+
{
|
|
5462
|
+
id: "selection",
|
|
5463
|
+
title: `${n} ${n === 1 ? "cell" : "cells"}`,
|
|
5464
|
+
primary: true,
|
|
5465
|
+
items: [
|
|
5466
|
+
{ id: "sel-copy", label: `Copy ${n} cells`, icon: "copy", shortcut: "\u2318C", onSelect: () => ctx.actions.copyText(payload.tsv, `${n} cells`) },
|
|
5467
|
+
{ id: "sel-clear", label: `Clear ${n} cells`, icon: "eraser", destructive: true, disabledReason: why, onSelect: () => ctx.actions.clearCells(cells) },
|
|
5468
|
+
{
|
|
5469
|
+
id: "sel-fill-down",
|
|
5470
|
+
label: "Fill down",
|
|
5471
|
+
icon: "arrow-down-to-line",
|
|
5472
|
+
shortcut: "\u2318D",
|
|
5473
|
+
disabledReason: why ?? (payload.rowIds.length < 2 ? "Select at least two rows to fill down." : void 0),
|
|
5474
|
+
onSelect: () => ctx.actions.fillDown(payload.rowIds, payload.columnIds)
|
|
5475
|
+
}
|
|
5476
|
+
]
|
|
5477
|
+
},
|
|
5478
|
+
tableSection(ctx)
|
|
5479
|
+
];
|
|
5480
|
+
}
|
|
5481
|
+
case "table":
|
|
5482
|
+
return [{ ...tableSection(ctx), primary: true }];
|
|
5483
|
+
}
|
|
5484
|
+
}
|
|
5485
|
+
function placeColumn(order, newKey, anchor, side) {
|
|
5486
|
+
const rest = order.filter((k) => k !== newKey);
|
|
5487
|
+
const at = rest.indexOf(anchor);
|
|
5488
|
+
if (at < 0) return [...rest, newKey];
|
|
5489
|
+
const index = side === "left" ? at : at + 1;
|
|
5490
|
+
return [...rest.slice(0, index), newKey, ...rest.slice(index)];
|
|
5491
|
+
}
|
|
5492
|
+
function columnsInKeptOrder(fieldKeys, kept) {
|
|
5493
|
+
if (!kept || kept.length === 0) return [...fieldKeys];
|
|
5494
|
+
const known = new Set(fieldKeys);
|
|
5495
|
+
const named = kept.filter((k, i) => known.has(k) && kept.indexOf(k) === i);
|
|
5496
|
+
const namedSet = new Set(named);
|
|
5497
|
+
return [...named, ...fieldKeys.filter((k) => !namedSet.has(k))];
|
|
5498
|
+
}
|
|
5499
|
+
function compareFieldOrder(a, b) {
|
|
5500
|
+
const bySort = (a.sort ?? 100) - (b.sort ?? 100);
|
|
5501
|
+
if (bySort !== 0) return bySort;
|
|
5502
|
+
const made = String(a.created_at ?? "").localeCompare(String(b.created_at ?? ""));
|
|
5503
|
+
if (made !== 0) return made;
|
|
5504
|
+
return a.key.localeCompare(b.key);
|
|
5505
|
+
}
|
|
5506
|
+
function nextFieldSort(fields) {
|
|
5507
|
+
let last = 0;
|
|
5508
|
+
for (const f of fields ?? []) last = Math.max(last, f.sort ?? 100);
|
|
5509
|
+
return last + 1;
|
|
5510
|
+
}
|
|
5511
|
+
|
|
5512
|
+
// src/FieldEditor.tsx
|
|
5513
|
+
var import_records9 = require("@ai-matrx/records");
|
|
5514
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
5515
|
+
var RULE_KINDS = FIELD_RULE_KIND_VALUES.map((kind) => ({ kind, label: FIELD_RULE_KIND_LABEL[kind] }));
|
|
5516
|
+
var SENSITIVITIES = import_records9.FIELD_SENSITIVITIES.map((value) => ({ value, label: FIELD_SENSITIVITY_LABEL[value] }));
|
|
5517
|
+
var AGENT_VISIBILITY = import_records9.CONTEXT_POLICIES.map((value) => ({ value, label: CONTEXT_POLICY_LABEL[value] }));
|
|
5518
|
+
var ROLLUP_OPERATIONS = ROLLUP_AGG_VALUES.map((value) => ({
|
|
5519
|
+
value,
|
|
5520
|
+
label: ROLLUP_AGG_LABEL[value]
|
|
5521
|
+
}));
|
|
5522
|
+
var FORMULA_SEPARATOR_LABEL = "Joined by";
|
|
5523
|
+
var EDITING_A_WORKED_OUT_COLUMN = "This column works out its answer from the columns picked when it was made. To change what it works out, write it as a formula below \u2014 the store checks it as you type, and a rename alone leaves it exactly as it is.";
|
|
5524
|
+
var FORMULA_DEFAULT_SEPARATOR = " \u2014 ";
|
|
5525
|
+
var FORMULA_OPERATIONS = FORMULA_OP_VALUES.map((value) => ({
|
|
5526
|
+
value,
|
|
5527
|
+
label: FORMULA_OP_LABEL[value],
|
|
5528
|
+
joins: FORMULA_OP_JOINS[value]
|
|
5529
|
+
}));
|
|
5530
|
+
function keyFor(label) {
|
|
5531
|
+
const token = label.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48);
|
|
5532
|
+
if (token === "") return "";
|
|
5533
|
+
return /^[a-z]/.test(token) ? token : `f_${token}`;
|
|
5534
|
+
}
|
|
5535
|
+
var COLUMN_DID_NOT_LAND_SENTENCE = "The store accepted this column and the table does not have it yet. Nothing was lost \u2014 your record values are untouched \u2014 but the column is not there, so this panel is staying open.";
|
|
5536
|
+
var COLUMN_DID_NOT_LAND_REMEDY = "Press Save again; if it keeps happening, tell us, because the store and the table disagree and that is ours to fix.";
|
|
5537
|
+
var COLUMN_DID_NOT_LAND = `${COLUMN_DID_NOT_LAND_SENTENCE} ${COLUMN_DID_NOT_LAND_REMEDY}`;
|
|
5538
|
+
function FieldEditor({ tableId, field: field2, onSaved, onCancel, onRemoved, className }) {
|
|
5539
|
+
const table = (0, import_react30.useTable)(tableId);
|
|
5540
|
+
const fields = (0, import_react30.useFields)(tableId);
|
|
5541
|
+
const rights = useTableRights(table.data);
|
|
5542
|
+
const mutation = (0, import_react30.useFieldMutation)();
|
|
5543
|
+
const client = (0, import_react30.useRecordsClient)();
|
|
5544
|
+
const [label, setLabel] = (0, import_react29.useState)(field2?.label ?? "");
|
|
5545
|
+
const [type, setType] = (0, import_react29.useState)(() => startingType(field2));
|
|
5546
|
+
const [required, setRequired] = (0, import_react29.useState)(field2?.required ?? false);
|
|
5547
|
+
const [multi, setMulti] = (0, import_react29.useState)(field2?.multi ?? false);
|
|
5548
|
+
const [dated, setDated] = (0, import_react29.useState)(field2?.dated ?? false);
|
|
5549
|
+
const [sensitivity, setSensitivity] = (0, import_react29.useState)(String(field2?.sensitivity ?? "internal"));
|
|
5550
|
+
const [contextPolicy, setContextPolicy] = (0, import_react29.useState)(String(field2?.context_policy ?? "include"));
|
|
5551
|
+
const [rules, setRules] = (0, import_react29.useState)(field2?.rules ?? []);
|
|
5552
|
+
const [allowOther, setAllowOther] = (0, import_react29.useState)(field2?.config?.["allow_other"] === true);
|
|
5553
|
+
const [optionText, setOptionText] = (0, import_react29.useState)("");
|
|
5554
|
+
const [unit, setUnit] = (0, import_react29.useState)(field2?.unit ?? "$");
|
|
5555
|
+
const [withTime, setWithTime] = (0, import_react29.useState)(String(field2?.config?.["kind"] ?? "date") === "datetime");
|
|
5556
|
+
const [via, setVia] = (0, import_react29.useState)(String(field2?.config?.["via"] ?? ""));
|
|
5557
|
+
const [pick, setPick] = (0, import_react29.useState)(String(field2?.config?.["pick"] ?? ""));
|
|
5558
|
+
const [agg, setAgg] = (0, import_react29.useState)(
|
|
5559
|
+
field2?.config?.["agg"] ?? "count"
|
|
5560
|
+
);
|
|
5561
|
+
const [of, setOf] = (0, import_react29.useState)(String(field2?.config?.["of"] ?? ""));
|
|
5562
|
+
const [relationTarget, setRelationTarget] = (0, import_react29.useState)(String(field2?.relation_target ?? ""));
|
|
5563
|
+
const [display, setDisplay] = (0, import_react29.useState)(field2?.display ?? null);
|
|
5564
|
+
const [formulaOp, setFormulaOp] = (0, import_react29.useState)(() => formulaOpOf(field2));
|
|
5565
|
+
const [formulaFields, setFormulaFields] = (0, import_react29.useState)(() => formulaFieldIdsOf(field2));
|
|
5566
|
+
const [formulaSeparator, setFormulaSeparator] = (0, import_react29.useState)(() => formulaSeparatorOf(field2));
|
|
5567
|
+
const [allowedTypes, setAllowedTypes] = (0, import_react29.useState)(() => entityReferenceKindsOf(field2));
|
|
5568
|
+
const startingFormulaText = String(field2?.config?.["formula_text"] ?? "");
|
|
5569
|
+
const [formulaText, setFormulaText] = (0, import_react29.useState)(startingFormulaText);
|
|
5570
|
+
const [formulaWritten, setFormulaWritten] = (0, import_react29.useState)(() => startingFormulaText.trim() !== "" || Boolean(field2));
|
|
5571
|
+
const [formulaOk, setFormulaOk] = (0, import_react29.useState)(null);
|
|
5572
|
+
const [askingToRemove, setAskingToRemove] = (0, import_react29.useState)(false);
|
|
5573
|
+
const [choiceRows, setChoiceRows] = (0, import_react29.useState)(null);
|
|
5574
|
+
const [showsAs, setShowsAs] = (0, import_react29.useState)(() => field2 ? startingChoice(field2) : { format: null, retype: null });
|
|
5575
|
+
const [backfillError, setBackfillError] = (0, import_react29.useState)(null);
|
|
5576
|
+
const [notLanded, setNotLanded] = (0, import_react29.useState)(false);
|
|
5577
|
+
const others = (0, import_react29.useMemo)(
|
|
5578
|
+
() => (fields.data ?? []).filter((f) => f.id !== field2?.id),
|
|
5579
|
+
[fields.data, field2?.id]
|
|
5580
|
+
);
|
|
5581
|
+
const relations = (0, import_react29.useMemo)(() => others.filter((f) => f.type === "relation"), [others]);
|
|
5582
|
+
const choice = fieldTypeChoice(type);
|
|
5583
|
+
const editing = Boolean(field2);
|
|
5584
|
+
if (!rights.structure) {
|
|
5585
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: (0, import_design_system17.cn)("text-xs text-muted-foreground", className), children: rights.why("structure") });
|
|
5586
|
+
}
|
|
5587
|
+
const options = optionText.split("\n").map((word) => word.trim()).filter((word) => word !== "");
|
|
5588
|
+
const formulaChanged = formulaText.trim() !== startingFormulaText.trim();
|
|
5589
|
+
const formulaMissing = type !== "formula" || !formulaWritten ? null : editing && !formulaChanged ? null : formulaText.trim() === "" ? "Write the formula this column works out." : formulaOk === false ? "The formula has a mistake \u2014 it is marked above." : formulaOk === null ? "Checking the formula\u2026" : null;
|
|
5590
|
+
const missing = editing ? formulaMissing : formulaMissing ?? (type === "formula" && formulaWritten ? null : whatIsMissing({ type, label, options, via, pick, agg, of, relations, formulaFields, relationTarget, allowedTypes }));
|
|
5591
|
+
const save = async () => {
|
|
5592
|
+
if (missing) return;
|
|
5593
|
+
if (editing && field2) {
|
|
5594
|
+
const chooses = type === "select" || type === "multi_select";
|
|
5595
|
+
const look = chooses ? choiceRows ? choiceDisplayPatch(field2, choiceRows) : null : null;
|
|
5596
|
+
const shownAs = chooses ? void 0 : formatPatch(field2, showsAs);
|
|
5597
|
+
const patch = {
|
|
5598
|
+
// The column's look travels in the same settings patch (`display_format`), so a save is
|
|
5599
|
+
// still one store call. `FieldPatch` does not declare the key yet (store gap, reported);
|
|
5600
|
+
// the door has carried it since the Sheet's seam started writing it.
|
|
5601
|
+
...chooses ? look ? { display_format: look } : {} : shownAs ?? {},
|
|
5602
|
+
label,
|
|
5603
|
+
required,
|
|
5604
|
+
dated,
|
|
5605
|
+
sensitivity,
|
|
5606
|
+
context_policy: contextPolicy,
|
|
5607
|
+
rules,
|
|
5608
|
+
// FLD-2. THE SHAPE OF THE VALUE, WHICH IS A SETTING LIKE ANY OTHER. This
|
|
5609
|
+
// patch never carried it, so the tick beside "Can hold more than one"
|
|
5610
|
+
// was decoration on an existing column — and the store's own roll-up
|
|
5611
|
+
// refusal tells a person to use exactly that tick. It is sent ALONE:
|
|
5612
|
+
// `custom.field_update` honours a `multi`-only patch since FIX-7B-FIELD's
|
|
5613
|
+
// migration (before it, the door read `multi` only inside its behaviour
|
|
5614
|
+
// arm, answered "saved" and changed nothing), so there is no behaviour
|
|
5615
|
+
// word to send beside it and sending one would retype a column nobody
|
|
5616
|
+
// asked to retype.
|
|
5617
|
+
...multiApplies(type) ? { multi } : {},
|
|
5618
|
+
...unitApplies(type) ? { unit } : {},
|
|
5619
|
+
// An empty box means "leave the choices alone"; a list replaces them.
|
|
5620
|
+
...optionText.trim() === "" ? {} : { options },
|
|
5621
|
+
// REL-DISP. Which of the other record's columns this one shows. Sent for
|
|
5622
|
+
// every relation column, `null` included, because `null` is how the door
|
|
5623
|
+
// REMOVES a spec and the column goes back to the target table's own
|
|
5624
|
+
// reference column — a panel that only sent the key when it was set
|
|
5625
|
+
// could turn the override on and never off.
|
|
5626
|
+
...field2.type === "relation" ? { display } : {},
|
|
5627
|
+
// STORE-RULE-GAPS: a choice list's own setting, sent for every list so
|
|
5628
|
+
// turning it off is as possible as turning it on.
|
|
5629
|
+
...field2.type === "list" ? { allow_other: allowOther } : {},
|
|
5630
|
+
// K2: a re-aimed formula goes WITH its type word, the arm `field_update` reads it in.
|
|
5019
5631
|
...type === "formula" && formulaWritten && formulaChanged && formulaText.trim() !== "" ? { type: "formula", formula_text: formulaText.trim() } : {}
|
|
5020
5632
|
};
|
|
5021
5633
|
const written2 = await mutation.update({ field_id: field2.id, patch });
|
|
@@ -5024,6 +5636,10 @@ function FieldEditor({ tableId, field: field2, onSaved, onCancel, onRemoved, cla
|
|
|
5024
5636
|
}
|
|
5025
5637
|
const spec = {
|
|
5026
5638
|
label,
|
|
5639
|
+
// A NEW COLUMN LANDS AT THE END (lane C, review 2026-09-26 C5): declared one past the last
|
|
5640
|
+
// column's `sort`, so every view and every client draws it last — never in the alphabetical
|
|
5641
|
+
// slot of its internal key.
|
|
5642
|
+
sort: nextFieldSort(fields.data),
|
|
5027
5643
|
...isRelationFieldType(type) ? {
|
|
5028
5644
|
type: "relation",
|
|
5029
5645
|
relation_target: relationTarget,
|
|
@@ -5351,9 +5967,8 @@ function FieldEditor({ tableId, field: field2, onSaved, onCancel, onRemoved, cla
|
|
|
5351
5967
|
onCancel ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system17.Button, { size: "sm", variant: "ghost", onClick: onCancel, children: "Cancel" }) : null,
|
|
5352
5968
|
field2 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "ml-auto flex items-center gap-2", children: askingToRemove ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
|
|
5353
5969
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "text-destructive", children: [
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
" takes it off every record of this table. The values stay in the record's history and nothing else is deleted."
|
|
5970
|
+
archiveConfirmSentence(`the field \u201C${fieldName(field2)}\u201D`),
|
|
5971
|
+
" Its values stay in every record and come back with it."
|
|
5357
5972
|
] }),
|
|
5358
5973
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system17.Button, { size: "sm", variant: "destructive", disabled: mutation.saving, onClick: () => void remove(), children: "Remove it" }),
|
|
5359
5974
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system17.Button, { size: "sm", variant: "ghost", onClick: () => setAskingToRemove(false), children: "Keep it" })
|
|
@@ -5643,10 +6258,10 @@ function entityReferenceKindsOf(field2) {
|
|
|
5643
6258
|
|
|
5644
6259
|
// src/gridColors.tsx
|
|
5645
6260
|
var import_react31 = require("react");
|
|
5646
|
-
var
|
|
6261
|
+
var import_records10 = require("@ai-matrx/records");
|
|
5647
6262
|
var import_react32 = require("@ai-matrx/records/react");
|
|
5648
6263
|
var import_design_system18 = require("@ai-matrx/design-system");
|
|
5649
|
-
var
|
|
6264
|
+
var import_data_table7 = require("@ai-matrx/design-system/data-table");
|
|
5650
6265
|
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
5651
6266
|
function isChoiceField(field2) {
|
|
5652
6267
|
const kind = String(editorKindFor(field2));
|
|
@@ -5702,7 +6317,7 @@ function useChoiceOptions(fields, keys) {
|
|
|
5702
6317
|
return index;
|
|
5703
6318
|
}
|
|
5704
6319
|
function lookOf(option) {
|
|
5705
|
-
return { id: String(option.id), key: (0,
|
|
6320
|
+
return { id: String(option.id), key: (0, import_records10.optionKey)(option), label: (0, import_records10.optionLabel)(option) };
|
|
5706
6321
|
}
|
|
5707
6322
|
function paintByOption(style, fields, index, lookup) {
|
|
5708
6323
|
if (index.size === 0) return { style, documentOf: (doc) => doc, choiceColorFor: lookup };
|
|
@@ -5762,7 +6377,7 @@ function rulesLeaf(drafts, fields) {
|
|
|
5762
6377
|
...d.id ? { id: d.id } : {},
|
|
5763
6378
|
field: field2.id,
|
|
5764
6379
|
op: d.op,
|
|
5765
|
-
...
|
|
6380
|
+
...import_data_table7.OPS_WITH_VALUE.includes(d.op) ? { value: d.value } : {},
|
|
5766
6381
|
color: d.color,
|
|
5767
6382
|
target: d.target
|
|
5768
6383
|
});
|
|
@@ -5771,9 +6386,9 @@ function rulesLeaf(drafts, fields) {
|
|
|
5771
6386
|
}
|
|
5772
6387
|
function swatches(prefix, current, write) {
|
|
5773
6388
|
return [
|
|
5774
|
-
...
|
|
6389
|
+
...import_data_table7.STYLE_COLORS.map((color) => ({
|
|
5775
6390
|
id: `${prefix}-${color}`,
|
|
5776
|
-
label: color === current ? `${
|
|
6391
|
+
label: color === current ? `${import_data_table7.STYLE_COLOR_LABELS[color]} (now)` : import_data_table7.STYLE_COLOR_LABELS[color],
|
|
5777
6392
|
onSelect: () => write(color)
|
|
5778
6393
|
})),
|
|
5779
6394
|
...current ? [{ id: `${prefix}-none`, label: "No highlight", onSelect: () => write(null) }] : []
|
|
@@ -5839,11 +6454,11 @@ function useGridColours(args) {
|
|
|
5839
6454
|
},
|
|
5840
6455
|
{
|
|
5841
6456
|
id: "col-highlight",
|
|
5842
|
-
label: column ? `Highlight column (${
|
|
6457
|
+
label: column ? `Highlight column (${import_data_table7.STYLE_COLOR_LABELS[column]})` : "Highlight column",
|
|
5843
6458
|
submenu: swatches(
|
|
5844
6459
|
"col-highlight",
|
|
5845
6460
|
column,
|
|
5846
|
-
(color) => void write(["columns", field2.id], color, color ? `${name} is highlighted ${
|
|
6461
|
+
(color) => void write(["columns", field2.id], color, color ? `${name} is highlighted ${import_data_table7.STYLE_COLOR_LABELS[color].toLowerCase()}.` : `${name} is no longer highlighted.`)
|
|
5847
6462
|
)
|
|
5848
6463
|
}
|
|
5849
6464
|
]
|
|
@@ -5908,7 +6523,7 @@ function useGridColours(args) {
|
|
|
5908
6523
|
}
|
|
5909
6524
|
var SELECT2 = "h-8 rounded border bg-background px-1 text-xs";
|
|
5910
6525
|
var CHOICE_OPS = ["is", "is_not", "is_empty", "not_empty"].filter(
|
|
5911
|
-
(op2) =>
|
|
6526
|
+
(op2) => import_data_table7.COLOR_RULE_OPS.includes(op2)
|
|
5912
6527
|
);
|
|
5913
6528
|
function ColourRulesDialog({
|
|
5914
6529
|
open,
|
|
@@ -5958,10 +6573,10 @@ function ColourRulesDialog({
|
|
|
5958
6573
|
className: SELECT2,
|
|
5959
6574
|
value: rule.op,
|
|
5960
6575
|
onChange: (e) => set(i, { op: e.target.value }),
|
|
5961
|
-
children: (choicesOf(rule.field) ?
|
|
6576
|
+
children: (choicesOf(rule.field) ? import_data_table7.COLOR_RULE_OPS.filter((op2) => CHOICE_OPS.includes(op2)) : import_data_table7.COLOR_RULE_OPS).map((op2) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("option", { value: op2, children: import_data_table7.COLOR_RULE_OP_LABELS[op2] }, op2))
|
|
5962
6577
|
}
|
|
5963
6578
|
),
|
|
5964
|
-
|
|
6579
|
+
import_data_table7.OPS_WITH_VALUE.includes(rule.op) && choicesOf(rule.field) ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
5965
6580
|
"select",
|
|
5966
6581
|
{
|
|
5967
6582
|
"aria-label": "Choice",
|
|
@@ -5974,7 +6589,7 @@ function ColourRulesDialog({
|
|
|
5974
6589
|
choicesOf(rule.field).map((option) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("option", { value: option.id, children: option.label }, option.id))
|
|
5975
6590
|
]
|
|
5976
6591
|
}
|
|
5977
|
-
) :
|
|
6592
|
+
) : import_data_table7.OPS_WITH_VALUE.includes(rule.op) ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5978
6593
|
"input",
|
|
5979
6594
|
{
|
|
5980
6595
|
"aria-label": "Value",
|
|
@@ -5997,7 +6612,7 @@ function ColourRulesDialog({
|
|
|
5997
6612
|
className: SELECT2,
|
|
5998
6613
|
value: rule.color,
|
|
5999
6614
|
onChange: (e) => set(i, { color: e.target.value }),
|
|
6000
|
-
children:
|
|
6615
|
+
children: import_data_table7.STYLE_COLORS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("option", { value: c, children: import_data_table7.STYLE_COLOR_LABELS[c] }, c))
|
|
6001
6616
|
}
|
|
6002
6617
|
),
|
|
6003
6618
|
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_design_system18.Button, { type: "button", size: "sm", variant: "ghost", onClick: () => setDrafts(rows.filter((_, j) => j !== i)), children: "Remove" })
|
|
@@ -6040,207 +6655,9 @@ function ColourRulesDialog({
|
|
|
6040
6655
|
var import_react33 = require("react");
|
|
6041
6656
|
var import_react34 = require("@ai-matrx/records/react");
|
|
6042
6657
|
var import_design_system19 = require("@ai-matrx/design-system");
|
|
6043
|
-
|
|
6044
|
-
// src/gridMenus.ts
|
|
6045
|
-
function fieldOf(ctx, columnId) {
|
|
6046
|
-
return ctx.fields.find((f) => f.key === columnId);
|
|
6047
|
-
}
|
|
6048
|
-
function columnWords(ctx, columnId) {
|
|
6049
|
-
const field2 = fieldOf(ctx, columnId);
|
|
6050
|
-
return field2 ? fieldName(field2) : columnId;
|
|
6051
|
-
}
|
|
6052
|
-
function rowLabelRefusal(field2) {
|
|
6053
|
-
if (!field2) return "This column is not one of the table's own.";
|
|
6054
|
-
const kind = String(field2.type);
|
|
6055
|
-
if (kind === "formula" || kind === "lookup" || kind === "rollup") {
|
|
6056
|
-
return `${fieldName(field2)} is worked out by the table, and a record is named by a column people fill in.`;
|
|
6057
|
-
}
|
|
6058
|
-
if (kind === "relation") return `${fieldName(field2)} points at other records; a record is named by its own words.`;
|
|
6059
|
-
return void 0;
|
|
6060
|
-
}
|
|
6061
|
-
function headerMenuItems(columnId, ctx) {
|
|
6062
|
-
const field2 = fieldOf(ctx, columnId);
|
|
6063
|
-
const shape = ctx.mayStructure ? void 0 : ctx.whyNotStructure;
|
|
6064
|
-
const isLabel = ctx.titleField === columnId;
|
|
6065
|
-
const labelRefusal = shape ?? (isLabel ? "Records are already named by this column." : rowLabelRefusal(field2));
|
|
6066
|
-
const a = ctx.actions;
|
|
6067
|
-
return [
|
|
6068
|
-
{ id: "col-rename", label: "Rename column", icon: "pencil", disabledReason: shape, onSelect: () => a.renameColumn(columnId) },
|
|
6069
|
-
{ id: "col-settings", label: "Column settings\u2026", icon: "settings", disabledReason: shape, onSelect: () => a.columnSettings(columnId) },
|
|
6070
|
-
{ id: "col-insert-left", label: "Insert column left\u2026", icon: "panel-left", disabledReason: shape, onSelect: () => a.insertColumn(columnId, "left") },
|
|
6071
|
-
{ id: "col-insert-right", label: "Insert column right\u2026", icon: "panel-right", disabledReason: shape, onSelect: () => a.insertColumn(columnId, "right") },
|
|
6072
|
-
{
|
|
6073
|
-
id: "col-row-label",
|
|
6074
|
-
label: isLabel ? "Names each record" : "Use as row label",
|
|
6075
|
-
icon: "tag",
|
|
6076
|
-
disabledReason: labelRefusal,
|
|
6077
|
-
onSelect: () => a.useAsRowLabel(columnId)
|
|
6078
|
-
},
|
|
6079
|
-
...ctx.extras?.header?.(columnId) ?? [],
|
|
6080
|
-
{ id: "col-hide", label: "Hide column", icon: "eye-off", onSelect: () => a.hideColumn(columnId) },
|
|
6081
|
-
{
|
|
6082
|
-
id: "col-delete",
|
|
6083
|
-
label: "Delete column\u2026",
|
|
6084
|
-
icon: "trash",
|
|
6085
|
-
destructive: true,
|
|
6086
|
-
disabledReason: shape ?? (isLabel ? "Records are named by this column; pick another row label first." : void 0),
|
|
6087
|
-
onSelect: () => a.deleteColumn(columnId)
|
|
6088
|
-
}
|
|
6089
|
-
];
|
|
6090
|
-
}
|
|
6091
|
-
function cellSection(payload, ctx) {
|
|
6092
|
-
const a = ctx.actions;
|
|
6093
|
-
const { rowId, columnId } = payload;
|
|
6094
|
-
const mayWrite = ctx.mayWriteRow(rowId);
|
|
6095
|
-
const typeable = ctx.typeable(columnId);
|
|
6096
|
-
const why = !mayWrite ? ctx.whyNotWrite(rowId) : !typeable ? `${columnWords(ctx, columnId)} is worked out by the table; nobody types it.` : void 0;
|
|
6097
|
-
const items = [
|
|
6098
|
-
{ id: "cell-copy", label: "Copy cell", icon: "copy", shortcut: "\u2318C", onSelect: () => a.copyText(payload.text, "The cell") },
|
|
6099
|
-
{ id: "cell-paste", label: "Paste", icon: "clipboard-paste", shortcut: "\u2318V", disabledReason: why, onSelect: () => a.pasteInto(rowId, columnId) },
|
|
6100
|
-
{ id: "cell-clear", label: "Clear cell", icon: "eraser", destructive: true, disabledReason: why, onSelect: () => a.clearCells([{ rowId, columnId }]) }
|
|
6101
|
-
];
|
|
6102
|
-
if (a.askWhoChanged) {
|
|
6103
|
-
items.push({ id: "cell-history", label: "Who changed this?", icon: "history", shortcut: "\u2325\u21E7H", onSelect: () => a.askWhoChanged?.(columnId, rowId) });
|
|
6104
|
-
}
|
|
6105
|
-
const empty = payload.value === null || payload.value === void 0 || payload.value === "";
|
|
6106
|
-
items.push({
|
|
6107
|
-
id: "cell-filter",
|
|
6108
|
-
label: empty ? "Show rows where this is empty" : "Show rows with this value",
|
|
6109
|
-
icon: "filter",
|
|
6110
|
-
onSelect: () => a.filterBy(columnId, payload.value)
|
|
6111
|
-
});
|
|
6112
|
-
items.push(...ctx.extras?.cell?.(rowId, columnId) ?? []);
|
|
6113
|
-
return { id: "cell", title: payload.columnLabel || "Cell", items };
|
|
6114
|
-
}
|
|
6115
|
-
function rowSection(rowId, ctx, rowText) {
|
|
6116
|
-
const a = ctx.actions;
|
|
6117
|
-
const mayWrite = ctx.mayWriteRow(rowId);
|
|
6118
|
-
const items = [];
|
|
6119
|
-
if (a.openRecord) {
|
|
6120
|
-
items.push({ id: "row-open", label: "Open record", icon: "panel-right", shortcut: "Space", onSelect: () => a.openRecord?.(rowId) });
|
|
6121
|
-
items.push({ id: "row-history", label: "Row history", icon: "history", onSelect: () => a.openRecord?.(rowId) });
|
|
6122
|
-
}
|
|
6123
|
-
if (rowText !== null) {
|
|
6124
|
-
items.push({ id: "row-copy", label: "Copy row", icon: "copy", onSelect: () => a.copyText(rowText, "The row") });
|
|
6125
|
-
}
|
|
6126
|
-
const noPlace = "This view is sorted, so a new record lands where its sort puts it. Reorder places rows by hand; New record adds one.";
|
|
6127
|
-
const insertWhy = !ctx.mayAddRecord ? ctx.whyNotAdd : a.insertRow ? void 0 : noPlace;
|
|
6128
|
-
items.push(
|
|
6129
|
-
{ id: "row-insert-above", label: "Insert row above", icon: "plus", disabledReason: insertWhy, onSelect: () => a.insertRow?.(rowId, "above") },
|
|
6130
|
-
{ id: "row-insert-below", label: "Insert row below", icon: "plus", disabledReason: insertWhy, onSelect: () => a.insertRow?.(rowId, "below") },
|
|
6131
|
-
{
|
|
6132
|
-
id: "row-duplicate",
|
|
6133
|
-
label: "Duplicate row",
|
|
6134
|
-
icon: "copy-plus",
|
|
6135
|
-
disabledReason: ctx.mayAddRecord ? void 0 : ctx.whyNotAdd,
|
|
6136
|
-
onSelect: () => a.duplicateRow(rowId)
|
|
6137
|
-
},
|
|
6138
|
-
{
|
|
6139
|
-
id: "row-archive",
|
|
6140
|
-
label: "Delete row\u2026",
|
|
6141
|
-
icon: "trash",
|
|
6142
|
-
destructive: true,
|
|
6143
|
-
disabledReason: mayWrite ? void 0 : ctx.whyNotWrite(rowId),
|
|
6144
|
-
onSelect: () => a.archiveRow(rowId)
|
|
6145
|
-
}
|
|
6146
|
-
);
|
|
6147
|
-
items.push(...ctx.extras?.row?.(rowId) ?? []);
|
|
6148
|
-
return { id: "row", title: ctx.extras?.rowTitle?.(rowId) || "Row", items };
|
|
6149
|
-
}
|
|
6150
|
-
function columnSection(columnId, columnLabel2, ctx) {
|
|
6151
|
-
const a = ctx.actions;
|
|
6152
|
-
const sorted = ctx.sortedBy?.id === columnId ? ctx.sortedBy.direction : null;
|
|
6153
|
-
return {
|
|
6154
|
-
id: "column",
|
|
6155
|
-
title: columnLabel2 || columnWords(ctx, columnId),
|
|
6156
|
-
items: [
|
|
6157
|
-
{ id: "col-sort-asc", label: "Sort A\u2192Z", icon: "arrow-up-a-z", disabledReason: sorted === "asc" ? "Already sorted A\u2192Z." : void 0, onSelect: () => a.sortBy(columnId, "asc") },
|
|
6158
|
-
{ id: "col-sort-desc", label: "Sort Z\u2192A", icon: "arrow-down-a-z", disabledReason: sorted === "desc" ? "Already sorted Z\u2192A." : void 0, onSelect: () => a.sortBy(columnId, "desc") },
|
|
6159
|
-
...sorted ? [{ id: "col-sort-clear", label: "Clear sort", icon: "arrow-up-down", onSelect: () => a.sortBy(columnId, null) }] : [],
|
|
6160
|
-
...headerMenuItems(columnId, ctx)
|
|
6161
|
-
]
|
|
6162
|
-
};
|
|
6163
|
-
}
|
|
6164
|
-
function tableSection(ctx) {
|
|
6165
|
-
const a = ctx.actions;
|
|
6166
|
-
const items = [
|
|
6167
|
-
{ id: "table-new-record", label: "New record", icon: "plus", disabledReason: ctx.mayAddRecord ? void 0 : ctx.whyNotAdd, onSelect: () => a.newRecord() }
|
|
6168
|
-
];
|
|
6169
|
-
if (a.addColumn) {
|
|
6170
|
-
items.push({
|
|
6171
|
-
id: "table-add-column",
|
|
6172
|
-
label: "Add column\u2026",
|
|
6173
|
-
icon: "plus",
|
|
6174
|
-
disabledReason: ctx.mayStructure ? void 0 : ctx.whyNotStructure,
|
|
6175
|
-
onSelect: () => a.addColumn?.()
|
|
6176
|
-
});
|
|
6177
|
-
}
|
|
6178
|
-
items.push(...ctx.extras?.table?.() ?? []);
|
|
6179
|
-
return { id: "table", title: "Table", items };
|
|
6180
|
-
}
|
|
6181
|
-
function gridMenuSections(payload, ctx, rowText = () => null) {
|
|
6182
|
-
switch (payload.level) {
|
|
6183
|
-
case "cell":
|
|
6184
|
-
return [
|
|
6185
|
-
{ ...cellSection(payload, ctx), primary: true },
|
|
6186
|
-
rowSection(payload.rowId, ctx, rowText(payload.rowId)),
|
|
6187
|
-
columnSection(payload.columnId, payload.columnLabel, ctx),
|
|
6188
|
-
tableSection(ctx)
|
|
6189
|
-
];
|
|
6190
|
-
case "row":
|
|
6191
|
-
return [{ ...rowSection(payload.rowId, ctx, rowText(payload.rowId)), primary: true }, tableSection(ctx)];
|
|
6192
|
-
case "column":
|
|
6193
|
-
return [{ ...columnSection(payload.columnId, payload.columnLabel, ctx), primary: true }, tableSection(ctx)];
|
|
6194
|
-
case "selection": {
|
|
6195
|
-
const n = payload.cellCount;
|
|
6196
|
-
const blocked = payload.rowIds.find((id) => !ctx.mayWriteRow(id));
|
|
6197
|
-
const untypeable = payload.columnIds.find((id) => !ctx.typeable(id));
|
|
6198
|
-
const why = blocked ? ctx.whyNotWrite(blocked) : untypeable ? `${columnWords(ctx, untypeable)} is worked out by the table; nobody types it.` : void 0;
|
|
6199
|
-
const cells = payload.rowIds.flatMap((rowId) => payload.columnIds.map((columnId) => ({ rowId, columnId })));
|
|
6200
|
-
return [
|
|
6201
|
-
{
|
|
6202
|
-
id: "selection",
|
|
6203
|
-
title: `${n} ${n === 1 ? "cell" : "cells"}`,
|
|
6204
|
-
primary: true,
|
|
6205
|
-
items: [
|
|
6206
|
-
{ id: "sel-copy", label: `Copy ${n} cells`, icon: "copy", shortcut: "\u2318C", onSelect: () => ctx.actions.copyText(payload.tsv, `${n} cells`) },
|
|
6207
|
-
{ id: "sel-clear", label: `Clear ${n} cells`, icon: "eraser", destructive: true, disabledReason: why, onSelect: () => ctx.actions.clearCells(cells) },
|
|
6208
|
-
{
|
|
6209
|
-
id: "sel-fill-down",
|
|
6210
|
-
label: "Fill down",
|
|
6211
|
-
icon: "arrow-down-to-line",
|
|
6212
|
-
shortcut: "\u2318D",
|
|
6213
|
-
disabledReason: why ?? (payload.rowIds.length < 2 ? "Select at least two rows to fill down." : void 0),
|
|
6214
|
-
onSelect: () => ctx.actions.fillDown(payload.rowIds, payload.columnIds)
|
|
6215
|
-
}
|
|
6216
|
-
]
|
|
6217
|
-
},
|
|
6218
|
-
tableSection(ctx)
|
|
6219
|
-
];
|
|
6220
|
-
}
|
|
6221
|
-
case "table":
|
|
6222
|
-
return [{ ...tableSection(ctx), primary: true }];
|
|
6223
|
-
}
|
|
6224
|
-
}
|
|
6225
|
-
function placeColumn(order, newKey, anchor, side) {
|
|
6226
|
-
const rest = order.filter((k) => k !== newKey);
|
|
6227
|
-
const at = rest.indexOf(anchor);
|
|
6228
|
-
if (at < 0) return [...rest, newKey];
|
|
6229
|
-
const index = side === "left" ? at : at + 1;
|
|
6230
|
-
return [...rest.slice(0, index), newKey, ...rest.slice(index)];
|
|
6231
|
-
}
|
|
6232
|
-
function columnsInKeptOrder(fieldKeys, kept) {
|
|
6233
|
-
if (!kept || kept.length === 0) return [...fieldKeys];
|
|
6234
|
-
const known = new Set(fieldKeys);
|
|
6235
|
-
const named = kept.filter((k, i) => known.has(k) && kept.indexOf(k) === i);
|
|
6236
|
-
const namedSet = new Set(named);
|
|
6237
|
-
return [...named, ...fieldKeys.filter((k) => !namedSet.has(k))];
|
|
6238
|
-
}
|
|
6239
|
-
|
|
6240
|
-
// src/gridRowLabel.tsx
|
|
6241
6658
|
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
6242
6659
|
function labelColumns(fields) {
|
|
6243
|
-
return [...fields].filter((f) => rowLabelRefusal(f) === void 0).sort(
|
|
6660
|
+
return [...fields].filter((f) => rowLabelRefusal(f) === void 0).sort(compareFieldOrder);
|
|
6244
6661
|
}
|
|
6245
6662
|
var JOINED_LABEL_LIMIT = "The record store names each record by the words one column holds, so a name joined from several columns (First and Last) is not something it keeps yet. Add a column that holds the whole name and pick it here, or pick the one column that names a record best.";
|
|
6246
6663
|
function RowLabelPicker({
|
|
@@ -6334,7 +6751,7 @@ function RowLabelPicker({
|
|
|
6334
6751
|
// src/gridSummaries.tsx
|
|
6335
6752
|
var import_react35 = require("react");
|
|
6336
6753
|
var import_react36 = require("@ai-matrx/records/react");
|
|
6337
|
-
var
|
|
6754
|
+
var import_records11 = require("@ai-matrx/records");
|
|
6338
6755
|
var import_design_system20 = require("@ai-matrx/design-system");
|
|
6339
6756
|
|
|
6340
6757
|
// src/gridLook.ts
|
|
@@ -6534,7 +6951,7 @@ function useColumnSummaries(args) {
|
|
|
6534
6951
|
const out = {};
|
|
6535
6952
|
for (const [key, op2] of entries) {
|
|
6536
6953
|
const measure = op2 === "count" ? { op: "count" } : { op: op2, key };
|
|
6537
|
-
out[key] = row?.measures?.[(0,
|
|
6954
|
+
out[key] = row?.measures?.[(0, import_records11.measureKey)(measure)] ?? null;
|
|
6538
6955
|
}
|
|
6539
6956
|
setValues(out);
|
|
6540
6957
|
setRowCount(row?.row_count ?? 0);
|
|
@@ -6585,7 +7002,10 @@ function SummaryCell({
|
|
|
6585
7002
|
"data-records-summary": field2.key,
|
|
6586
7003
|
className: (0, import_design_system20.cn)(
|
|
6587
7004
|
"h-6 max-w-full justify-start gap-1 px-1 text-xs font-normal",
|
|
6588
|
-
|
|
7005
|
+
// AN UNCHOSEN SUMMARY IS A HOVER, NOT A WORD IN EVERY FOOTER CELL (lane C, review
|
|
7006
|
+
// 2026-09-26 E1 — Airtable's footer): it shows under the pointer, on keyboard focus and
|
|
7007
|
+
// while its menu is open; the header menu's "Summarize column" is the other door.
|
|
7008
|
+
chosen ? "text-foreground" : "text-muted-foreground opacity-0 hover:opacity-100 focus-visible:opacity-100 data-[state=open]:opacity-100"
|
|
6589
7009
|
),
|
|
6590
7010
|
title: chosen && rowCount !== null ? `${label} of ${fieldName(field2)}, worked out by the store over all ${rowCount} record${rowCount === 1 ? "" : "s"} \u2014 not only this page.` : `Summarize ${fieldName(field2)}`,
|
|
6591
7011
|
children: chosen ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
|
|
@@ -6634,7 +7054,7 @@ function summaryMenuItem(field2, firstColumn, chosen, operations, onChoose) {
|
|
|
6634
7054
|
|
|
6635
7055
|
// src/pasteValues.ts
|
|
6636
7056
|
var import_core4 = require("@ai-matrx/records/core");
|
|
6637
|
-
var
|
|
7057
|
+
var import_records12 = require("@ai-matrx/records");
|
|
6638
7058
|
function wordForStoredChoice(options, value) {
|
|
6639
7059
|
if (options === void 0 || typeof value !== "string" || value === "") return null;
|
|
6640
7060
|
for (const label of options.labels) {
|
|
@@ -6647,7 +7067,7 @@ function choiceOptionsOf(options) {
|
|
|
6647
7067
|
const labels = [];
|
|
6648
7068
|
for (const option of options) {
|
|
6649
7069
|
const id = option.id;
|
|
6650
|
-
const key = (0,
|
|
7070
|
+
const key = (0, import_records12.optionKey)(option);
|
|
6651
7071
|
const label = recordName(option.data, null, "");
|
|
6652
7072
|
if (label) {
|
|
6653
7073
|
labels.push(label);
|
|
@@ -6659,7 +7079,7 @@ function choiceOptionsOf(options) {
|
|
|
6659
7079
|
return { byWord, labels };
|
|
6660
7080
|
}
|
|
6661
7081
|
function valueFromPastedText(field2, raw, options) {
|
|
6662
|
-
return (0,
|
|
7082
|
+
return (0, import_records12.coerceTypedAnswer)(field2, raw, {
|
|
6663
7083
|
label: fieldName(field2),
|
|
6664
7084
|
...options ? { options } : {}
|
|
6665
7085
|
});
|
|
@@ -6850,8 +7270,31 @@ function useMergedGridControls(args) {
|
|
|
6850
7270
|
if (!made) return;
|
|
6851
7271
|
pendingInsert.current = null;
|
|
6852
7272
|
if (pending.anchor) keepColumns({ order: placeColumn(order, made, pending.anchor, pending.side), hidden });
|
|
6853
|
-
|
|
7273
|
+
else if (keptOrder && keptOrder.length > 0) keepColumns({ order: [...order.filter((k) => k !== made), made], hidden });
|
|
7274
|
+
}, [fieldKeys, order, hidden, keepColumns, keptOrder]);
|
|
6854
7275
|
const fieldOf2 = (0, import_react37.useCallback)((key) => fields.find((f) => f.key === key), [fields]);
|
|
7276
|
+
const choiceKeys = (0, import_react37.useMemo)(() => enabled ? fields.filter(isChoiceField).map((f) => f.key) : [], [enabled, fields]);
|
|
7277
|
+
const choiceOptions = useChoiceOptions(fields, choiceKeys);
|
|
7278
|
+
const filterFor = (0, import_react37.useCallback)(
|
|
7279
|
+
(key) => {
|
|
7280
|
+
if (!enabled) return void 0;
|
|
7281
|
+
const field2 = fieldOf2(key);
|
|
7282
|
+
if (!field2) return void 0;
|
|
7283
|
+
const kind = String(editorKindFor(field2));
|
|
7284
|
+
if (kind === "number" || kind === "currency" || kind === "percent" || kind === "rating") return { filter: "number" };
|
|
7285
|
+
if (kind === "datetime" || kind === "date") return { filter: "date" };
|
|
7286
|
+
if (kind === "checkbox") return { filter: "boolean" };
|
|
7287
|
+
if (!isChoiceField(field2)) return void 0;
|
|
7288
|
+
const options = choiceOptions.get(key);
|
|
7289
|
+
if (!options || options.length === 0) return { filter: "select" };
|
|
7290
|
+
const holdsKeys = rows.some((row) => {
|
|
7291
|
+
const v = (row.document ?? {})[key];
|
|
7292
|
+
return (Array.isArray(v) ? v : [v]).some((x) => options.some((o) => o.key === x && o.id !== x));
|
|
7293
|
+
});
|
|
7294
|
+
return { filter: "select", filterOptions: options.map((o) => ({ value: holdsKeys ? o.key : o.id, label: o.label })) };
|
|
7295
|
+
},
|
|
7296
|
+
[enabled, fieldOf2, choiceOptions, rows]
|
|
7297
|
+
);
|
|
6855
7298
|
const copyText = (0, import_react37.useCallback)(
|
|
6856
7299
|
(text, what) => {
|
|
6857
7300
|
const clip = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
|
|
@@ -7137,7 +7580,7 @@ function useMergedGridControls(args) {
|
|
|
7137
7580
|
},
|
|
7138
7581
|
children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_design_system21.DialogContent, { className: "max-h-[85vh] overflow-y-auto sm:max-w-2xl", children: [
|
|
7139
7582
|
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_design_system21.DialogHeader, { children: [
|
|
7140
|
-
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_design_system21.DialogTitle, { children: dialog?.kind === "settings" ? `Column settings \xB7 ${fieldName(dialog.field)}` : dialog?.kind === "insert" && dialog.anchor ? `
|
|
7583
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_design_system21.DialogTitle, { children: dialog?.kind === "settings" ? `Column settings \xB7 ${fieldName(dialog.field)}` : dialog?.kind === "insert" && dialog.anchor ? `Add column ${dialog.side === "left" ? "left" : "right"} of ${fieldOf2(dialog.anchor) ? fieldName(fieldOf2(dialog.anchor)) : dialog.anchor}` : ADD_FIELD_OPEN_LABEL }),
|
|
7141
7584
|
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_design_system21.DialogDescription, { className: "sr-only", children: "The column\u2019s name, what it holds and how it looks." })
|
|
7142
7585
|
] }),
|
|
7143
7586
|
dialog?.kind === "settings" ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
@@ -7199,6 +7642,7 @@ function useMergedGridControls(args) {
|
|
|
7199
7642
|
] });
|
|
7200
7643
|
return {
|
|
7201
7644
|
headerMenuFor,
|
|
7645
|
+
filterFor,
|
|
7202
7646
|
contextMenu,
|
|
7203
7647
|
columns: enabled ? { order, hidden, onChange: keepColumns } : void 0,
|
|
7204
7648
|
dialogs,
|
|
@@ -7226,9 +7670,11 @@ function useAddedRows(args) {
|
|
|
7226
7670
|
const { tableId, enabled, pageRows, pageKey } = args;
|
|
7227
7671
|
const client = (0, import_react40.useRecordsClient)();
|
|
7228
7672
|
const [added, setAdded] = (0, import_react39.useState)([]);
|
|
7673
|
+
const [anchors, setAnchors] = (0, import_react39.useState)(() => /* @__PURE__ */ new Map());
|
|
7229
7674
|
const [busy, setBusy] = (0, import_react39.useState)(false);
|
|
7230
7675
|
(0, import_react39.useEffect)(() => {
|
|
7231
7676
|
setAdded([]);
|
|
7677
|
+
setAnchors(/* @__PURE__ */ new Map());
|
|
7232
7678
|
}, [pageKey, tableId]);
|
|
7233
7679
|
const pinnedKey = added.map((r) => r.id).join(",");
|
|
7234
7680
|
const asked = (0, import_react39.useRef)(0);
|
|
@@ -7250,11 +7696,11 @@ function useAddedRows(args) {
|
|
|
7250
7696
|
);
|
|
7251
7697
|
});
|
|
7252
7698
|
}, [enabled, pageRows, pinnedKey, client, tableId]);
|
|
7253
|
-
const rows = (0, import_react39.useMemo)(() =>
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
}, [
|
|
7699
|
+
const rows = (0, import_react39.useMemo)(() => enabled ? placePinnedRows(pageRows, added, anchors) : pageRows, [enabled, pageRows, added, anchors]);
|
|
7700
|
+
const pin = (0, import_react39.useCallback)((row, after) => {
|
|
7701
|
+
setAdded((held) => held.some((r) => r.id === row.id) ? held : [...held, row]);
|
|
7702
|
+
if (after) setAnchors((held) => new Map(held).set(row.id, after));
|
|
7703
|
+
}, []);
|
|
7258
7704
|
const add = (0, import_react39.useCallback)(async () => {
|
|
7259
7705
|
setBusy(true);
|
|
7260
7706
|
const written = await client.recordWrite({ table_id: tableId, data: {} });
|
|
@@ -7264,7 +7710,29 @@ function useAddedRows(args) {
|
|
|
7264
7710
|
setAdded((held) => held.some((r) => r.id === id) ? held : [...held, { id, document: {}, level: "editor", hidden: {} }]);
|
|
7265
7711
|
return { id };
|
|
7266
7712
|
}, [client, tableId]);
|
|
7267
|
-
return { rows, add, busy };
|
|
7713
|
+
return { rows, add, pin, busy };
|
|
7714
|
+
}
|
|
7715
|
+
function placePinnedRows(pageRows, pinned, anchors) {
|
|
7716
|
+
if (pinned.length === 0) return [...pageRows];
|
|
7717
|
+
const pageIds = new Set(pageRows.map((r) => r.id));
|
|
7718
|
+
const fresh = new Map(pageRows.map((r) => [r.id, r]));
|
|
7719
|
+
const below = /* @__PURE__ */ new Map();
|
|
7720
|
+
const placed = /* @__PURE__ */ new Set();
|
|
7721
|
+
for (const row of pinned) {
|
|
7722
|
+
const anchor = anchors.get(row.id);
|
|
7723
|
+
if (anchor && anchor !== row.id && pageIds.has(anchor)) {
|
|
7724
|
+
below.set(anchor, [...below.get(anchor) ?? [], fresh.get(row.id) ?? row]);
|
|
7725
|
+
placed.add(row.id);
|
|
7726
|
+
}
|
|
7727
|
+
}
|
|
7728
|
+
const out = [];
|
|
7729
|
+
const walk = (row) => {
|
|
7730
|
+
out.push(row);
|
|
7731
|
+
for (const child of below.get(row.id) ?? []) walk(child);
|
|
7732
|
+
};
|
|
7733
|
+
for (const row of pageRows) if (!placed.has(row.id)) walk(row);
|
|
7734
|
+
for (const row of pinned) if (!pageIds.has(row.id) && !placed.has(row.id)) out.push(row);
|
|
7735
|
+
return out;
|
|
7268
7736
|
}
|
|
7269
7737
|
function withAddRowCells(columns, line) {
|
|
7270
7738
|
if (line === null) return columns;
|
|
@@ -7786,6 +8254,10 @@ function reorderItems(rows, sort, label) {
|
|
|
7786
8254
|
}
|
|
7787
8255
|
return list.map((row) => ({ id: row.id, label: label(row) }));
|
|
7788
8256
|
}
|
|
8257
|
+
function fieldColumnIds(ids, fields) {
|
|
8258
|
+
const known = new Set(fields.map((f) => f.key));
|
|
8259
|
+
return ids.filter((id) => known.has(id));
|
|
8260
|
+
}
|
|
7789
8261
|
function columnLabel(fields, columnId) {
|
|
7790
8262
|
const field2 = fields.find((f) => f.key === columnId);
|
|
7791
8263
|
return field2 ? fieldName(field2) : humanize(columnId);
|
|
@@ -7797,8 +8269,8 @@ function AddFieldButton({ onClick }) {
|
|
|
7797
8269
|
size: "sm",
|
|
7798
8270
|
variant: "outline",
|
|
7799
8271
|
onClick,
|
|
7800
|
-
"aria-label": "Add a
|
|
7801
|
-
title: "Add a
|
|
8272
|
+
"aria-label": "Add a column",
|
|
8273
|
+
title: "Add a column to this table",
|
|
7802
8274
|
children: "+"
|
|
7803
8275
|
}
|
|
7804
8276
|
);
|
|
@@ -7865,7 +8337,8 @@ function columnForField(field2, editing, mayWriteRow, whyNotRow, look, onAskWhoC
|
|
|
7865
8337
|
] }) : name,
|
|
7866
8338
|
label: name,
|
|
7867
8339
|
accessorFn: (row) => row.document?.[field2.key],
|
|
7868
|
-
|
|
8340
|
+
// A column the store keeps no value for is not offered a sort it would refuse (`sortRefusal`).
|
|
8341
|
+
sortable: sortRefusal(field2) === null,
|
|
7869
8342
|
...format ? { format } : {},
|
|
7870
8343
|
/*
|
|
7871
8344
|
* A frozen column MUST declare a width or the table declines to freeze at
|
|
@@ -7873,7 +8346,7 @@ function columnForField(field2, editing, mayWriteRow, whyNotRow, look, onAskWhoC
|
|
|
7873
8346
|
* frozen column gets one here.
|
|
7874
8347
|
*/
|
|
7875
8348
|
...look?.frozen ? { frozen: true, width: FROZEN_COLUMN_WIDTH } : {},
|
|
7876
|
-
filter: editorKindFor(field2) === "select" || editorKindFor(field2) === "multi_select" ? "select" : "auto",
|
|
8349
|
+
filter: filterRefusal(field2) !== null ? false : editorKindFor(field2) === "select" || editorKindFor(field2) === "multi_select" ? "select" : "auto",
|
|
7877
8350
|
cell: (row) => (
|
|
7878
8351
|
/*
|
|
7879
8352
|
* 🚨 A READING QUESTION IS NOT AN EDITING RIGHT. This used to read
|
|
@@ -8116,7 +8589,7 @@ function confirmLabel(plan, noun, nounPlural) {
|
|
|
8116
8589
|
// src/gridActions.tsx
|
|
8117
8590
|
var import_react44 = require("react");
|
|
8118
8591
|
var import_react45 = require("@ai-matrx/records/react");
|
|
8119
|
-
var
|
|
8592
|
+
var import_records13 = require("@ai-matrx/records");
|
|
8120
8593
|
var import_design_system26 = require("@ai-matrx/design-system");
|
|
8121
8594
|
var import_jsx_runtime30 = require("react/jsx-runtime");
|
|
8122
8595
|
var actionListeners = /* @__PURE__ */ new Map();
|
|
@@ -8209,7 +8682,7 @@ function useActionRunner(args) {
|
|
|
8209
8682
|
const answered = await client.actionRun({ action_id: action.id, record_ids: ids });
|
|
8210
8683
|
setBusy(null);
|
|
8211
8684
|
if (!answered.ok) {
|
|
8212
|
-
const refusals = (0,
|
|
8685
|
+
const refusals = (0, import_records13.actionRefusals)(answered.error);
|
|
8213
8686
|
setOutcome({
|
|
8214
8687
|
kind: "refused",
|
|
8215
8688
|
action,
|
|
@@ -8459,6 +8932,14 @@ function InTableToolbar({ children }) {
|
|
|
8459
8932
|
if (slot === null) return null;
|
|
8460
8933
|
return (0, import_react_dom.createPortal)(children, slot);
|
|
8461
8934
|
}
|
|
8935
|
+
var TableToolbarFoldContext = (0, import_react46.createContext)(0);
|
|
8936
|
+
function useTableToolbarFold() {
|
|
8937
|
+
return (0, import_react46.useContext)(TableToolbarFoldContext);
|
|
8938
|
+
}
|
|
8939
|
+
var TableToolbarSheetExtrasContext = (0, import_react46.createContext)(void 0);
|
|
8940
|
+
function useTableToolbarSheetExtras() {
|
|
8941
|
+
return (0, import_react46.useContext)(TableToolbarSheetExtrasContext);
|
|
8942
|
+
}
|
|
8462
8943
|
|
|
8463
8944
|
// src/GridLayoutMenu.tsx
|
|
8464
8945
|
var import_react47 = require("react");
|
|
@@ -8568,114 +9049,10 @@ function GridLayoutMenu({
|
|
|
8568
9049
|
] });
|
|
8569
9050
|
}
|
|
8570
9051
|
|
|
8571
|
-
// src/gridPage.ts
|
|
8572
|
-
var import_records12 = require("@ai-matrx/records");
|
|
8573
|
-
var import_data_table7 = require("@ai-matrx/design-system/data-table");
|
|
8574
|
-
function sortAsFor(field2) {
|
|
8575
|
-
const kind = String(editorKindFor(field2));
|
|
8576
|
-
if (kind === "number" || kind === "currency" || kind === "percent") return "number";
|
|
8577
|
-
if (kind === "datetime") return "date";
|
|
8578
|
-
return "text";
|
|
8579
|
-
}
|
|
8580
|
-
function pageSortFor(fields, sort) {
|
|
8581
|
-
if (!sort) return [];
|
|
8582
|
-
const field2 = fields.find((f) => f.key === sort.id);
|
|
8583
|
-
if (!field2) return [];
|
|
8584
|
-
return [{ field: field2.key, direction: sort.direction === "desc" ? "desc" : "asc", as: sortAsFor(field2) }];
|
|
8585
|
-
}
|
|
8586
|
-
function containsPattern(text) {
|
|
8587
|
-
return `(?i)${text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`;
|
|
8588
|
-
}
|
|
8589
|
-
var field = (f) => ({ field: f.id });
|
|
8590
|
-
var constant = (value) => ({ const: value });
|
|
8591
|
-
var op = (name, ...args) => ({ op: name, args });
|
|
8592
|
-
function clauseFor(f, value, now = /* @__PURE__ */ new Date()) {
|
|
8593
|
-
const name = fieldName(f);
|
|
8594
|
-
const not = (rule) => op("not", rule);
|
|
8595
|
-
switch (value.kind) {
|
|
8596
|
-
case "text": {
|
|
8597
|
-
const mode = value.mode ?? "contains";
|
|
8598
|
-
if (mode === "empty" || mode === "null") return value.negated ? { rule: op("present", field(f)) } : { rule: not(op("present", field(f))) };
|
|
8599
|
-
if (mode === "not_empty" || mode === "not_null") return value.negated ? { rule: not(op("present", field(f))) } : { rule: op("present", field(f)) };
|
|
8600
|
-
const text = value.value.trim();
|
|
8601
|
-
if (text === "") return null;
|
|
8602
|
-
const kind = String(editorKindFor(f));
|
|
8603
|
-
if (kind === "number" || kind === "currency" || kind === "percent" || kind === "checkbox") {
|
|
8604
|
-
return `${name} holds numbers, so it is filtered by a range, not by words.`;
|
|
8605
|
-
}
|
|
8606
|
-
const matches2 = op("matches", field(f), constant(containsPattern(text)));
|
|
8607
|
-
return { rule: value.negated ? not(matches2) : matches2 };
|
|
8608
|
-
}
|
|
8609
|
-
case "select": {
|
|
8610
|
-
const words2 = value.values ?? (value.value === "" ? [] : [value.value]);
|
|
8611
|
-
if (words2.length === 0) return value.negated ? null : { rule: constant(false) };
|
|
8612
|
-
if (words2.length === 1 && !value.negated) return { flat: { [f.key]: words2[0] } };
|
|
8613
|
-
const any = words2.length === 1 ? op("eq", field(f), constant(words2[0])) : op("or", ...words2.map((w) => op("eq", field(f), constant(w))));
|
|
8614
|
-
return { rule: value.negated ? not(any) : any };
|
|
8615
|
-
}
|
|
8616
|
-
case "boolean":
|
|
8617
|
-
return value.negated ? { rule: op("ne", field(f), constant(value.value)) } : { flat: { [f.key]: value.value } };
|
|
8618
|
-
case "number": {
|
|
8619
|
-
const parts = [];
|
|
8620
|
-
if (typeof value.min === "number") parts.push(op("gte", field(f), constant(value.min)));
|
|
8621
|
-
if (typeof value.max === "number") parts.push(op("lte", field(f), constant(value.max)));
|
|
8622
|
-
if (parts.length === 0) return null;
|
|
8623
|
-
const range = parts.length === 1 ? parts[0] : op("and", ...parts);
|
|
8624
|
-
return { rule: value.negated ? not(range) : range };
|
|
8625
|
-
}
|
|
8626
|
-
case "date": {
|
|
8627
|
-
const bounds = (0, import_data_table7.dateFilterBounds)(value, now);
|
|
8628
|
-
if (bounds.since === void 0 && bounds.until === void 0) return null;
|
|
8629
|
-
return {
|
|
8630
|
-
flat: {
|
|
8631
|
-
[f.key]: {
|
|
8632
|
-
...bounds.since === void 0 ? {} : { from: bounds.since },
|
|
8633
|
-
...bounds.until === void 0 ? {} : { to: bounds.until }
|
|
8634
|
-
}
|
|
8635
|
-
}
|
|
8636
|
-
};
|
|
8637
|
-
}
|
|
8638
|
-
default:
|
|
8639
|
-
return `${name} cannot be filtered that way by the store.`;
|
|
8640
|
-
}
|
|
8641
|
-
}
|
|
8642
|
-
function storeQuestion(fields, base, columnFilters, now = /* @__PURE__ */ new Date()) {
|
|
8643
|
-
const refused = [];
|
|
8644
|
-
const flat = {};
|
|
8645
|
-
const rules = [];
|
|
8646
|
-
if (base && (0, import_records12.isRuleFilter)(base)) rules.push(base);
|
|
8647
|
-
else if (base) Object.assign(flat, base);
|
|
8648
|
-
for (const [key, value] of Object.entries(columnFilters)) {
|
|
8649
|
-
if (!value) continue;
|
|
8650
|
-
const f = fields.find((candidate) => candidate.key === key);
|
|
8651
|
-
if (!f) continue;
|
|
8652
|
-
const clause = clauseFor(f, value, now);
|
|
8653
|
-
if (clause === null) continue;
|
|
8654
|
-
if (typeof clause === "string") {
|
|
8655
|
-
refused.push(clause);
|
|
8656
|
-
continue;
|
|
8657
|
-
}
|
|
8658
|
-
if ("flat" in clause) Object.assign(flat, clause.flat);
|
|
8659
|
-
else rules.push(clause.rule);
|
|
8660
|
-
}
|
|
8661
|
-
const flatKeys = Object.keys(flat);
|
|
8662
|
-
if (rules.length === 0) return { filter: flatKeys.length > 0 ? flat : void 0, refused };
|
|
8663
|
-
for (const key of flatKeys) {
|
|
8664
|
-
const f = fields.find((candidate) => candidate.key === key);
|
|
8665
|
-
const value = flat[key];
|
|
8666
|
-
if (!f) continue;
|
|
8667
|
-
if (value !== null && typeof value === "object") {
|
|
8668
|
-
refused.push(`${fieldName(f)}'s date range cannot be asked together with the other filters yet, so it is left off.`);
|
|
8669
|
-
continue;
|
|
8670
|
-
}
|
|
8671
|
-
rules.push(value === null ? op("not", op("present", field(f))) : op("eq", field(f), constant(value)));
|
|
8672
|
-
}
|
|
8673
|
-
return { filter: rules.length === 1 ? rules[0] : op("and", ...rules), refused };
|
|
8674
|
-
}
|
|
8675
|
-
|
|
8676
9052
|
// src/gridUndo.tsx
|
|
8677
9053
|
var import_react49 = require("react");
|
|
8678
9054
|
var import_react50 = require("@ai-matrx/records/react");
|
|
9055
|
+
var import_core5 = require("@ai-matrx/records/core");
|
|
8679
9056
|
var import_design_system28 = require("@ai-matrx/design-system");
|
|
8680
9057
|
var import_data_table8 = require("@ai-matrx/design-system/data-table");
|
|
8681
9058
|
var import_jsx_runtime34 = require("react/jsx-runtime");
|
|
@@ -8702,6 +9079,7 @@ function useGridUndo(args) {
|
|
|
8702
9079
|
const [busy, setBusy] = (0, import_react49.useState)(false);
|
|
8703
9080
|
const [notice, setNotice] = (0, import_react49.useState)(null);
|
|
8704
9081
|
const counter = (0, import_react49.useRef)(0);
|
|
9082
|
+
const dismiss = (0, import_react49.useCallback)(() => setNotice(null), []);
|
|
8705
9083
|
const push = (0, import_react49.useCallback)((step2) => {
|
|
8706
9084
|
if (step2.cells.length === 0 && !step2.created?.length && !step2.archived?.length) return;
|
|
8707
9085
|
counter.current += 1;
|
|
@@ -8741,6 +9119,9 @@ function useGridUndo(args) {
|
|
|
8741
9119
|
return;
|
|
8742
9120
|
}
|
|
8743
9121
|
forget(step2.cells.map((c) => ({ rowId: c.rowId, key: c.key })));
|
|
9122
|
+
for (const id of /* @__PURE__ */ new Set([...step2.cells.map((c) => c.rowId), ...step2.created ?? [], ...step2.archived ?? []])) {
|
|
9123
|
+
(0, import_core5.announceRecordChanged)(id);
|
|
9124
|
+
}
|
|
8744
9125
|
setHeld(() => {
|
|
8745
9126
|
let stack = taken.stack;
|
|
8746
9127
|
if (direction === "undo" && ((step2.created?.length ?? 0) > 0 || step2.noRedo)) {
|
|
@@ -8769,49 +9150,64 @@ function useGridUndo(args) {
|
|
|
8769
9150
|
depth: { back: held.stack.past.length, forward: held.stack.future.length },
|
|
8770
9151
|
busy,
|
|
8771
9152
|
notice,
|
|
8772
|
-
dismiss
|
|
9153
|
+
dismiss,
|
|
9154
|
+
say: (says, sticky = false) => setNotice({ says, refusal: sticky ? { code: "internal", message: says } : null, canUndo: false })
|
|
8773
9155
|
}),
|
|
8774
|
-
[push, undo, redo, held, busy, notice]
|
|
9156
|
+
[push, undo, redo, held, busy, notice, dismiss]
|
|
8775
9157
|
);
|
|
8776
9158
|
}
|
|
8777
9159
|
function UndoRedoButtons({ undo }) {
|
|
8778
9160
|
if (undo.depth.back === 0 && undo.depth.forward === 0) return null;
|
|
8779
9161
|
return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
|
|
8780
|
-
|
|
9162
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
|
|
8781
9163
|
import_design_system28.Button,
|
|
8782
9164
|
{
|
|
8783
9165
|
size: "sm",
|
|
8784
9166
|
variant: "ghost",
|
|
8785
9167
|
"data-records-undo": "",
|
|
8786
|
-
title: `Undo (Cmd-Z) \xB7 ${undo.depth.back} ${undo.depth.back === 1 ? "step" : "steps"} back
|
|
8787
|
-
|
|
9168
|
+
title: undo.depth.back > 0 ? `Undo (Cmd-Z) \xB7 ${undo.depth.back} ${undo.depth.back === 1 ? "step" : "steps"} back` : "Nothing to undo",
|
|
9169
|
+
"aria-label": `Undo, ${undo.depth.back} ${undo.depth.back === 1 ? "step" : "steps"}`,
|
|
9170
|
+
disabled: undo.busy || undo.depth.back === 0,
|
|
8788
9171
|
onClick: () => void undo.undo(),
|
|
8789
|
-
children:
|
|
9172
|
+
children: [
|
|
9173
|
+
"Undo ",
|
|
9174
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "ml-1 tabular-nums text-muted-foreground", "data-records-undo-count": "", children: undo.depth.back })
|
|
9175
|
+
]
|
|
8790
9176
|
}
|
|
8791
|
-
)
|
|
8792
|
-
|
|
9177
|
+
),
|
|
9178
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
|
|
8793
9179
|
import_design_system28.Button,
|
|
8794
9180
|
{
|
|
8795
9181
|
size: "sm",
|
|
8796
9182
|
variant: "ghost",
|
|
8797
9183
|
"data-records-redo": "",
|
|
8798
|
-
title: `Redo (Shift-Cmd-Z) \xB7 ${undo.depth.forward} ${undo.depth.forward === 1 ? "step" : "steps"} forward
|
|
8799
|
-
|
|
9184
|
+
title: undo.depth.forward > 0 ? `Redo (Shift-Cmd-Z) \xB7 ${undo.depth.forward} ${undo.depth.forward === 1 ? "step" : "steps"} forward` : "Nothing to redo",
|
|
9185
|
+
"aria-label": `Redo, ${undo.depth.forward} ${undo.depth.forward === 1 ? "step" : "steps"}`,
|
|
9186
|
+
disabled: undo.busy || undo.depth.forward === 0,
|
|
8800
9187
|
onClick: () => void undo.redo(),
|
|
8801
|
-
children:
|
|
9188
|
+
children: [
|
|
9189
|
+
"Redo ",
|
|
9190
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "ml-1 tabular-nums text-muted-foreground", "data-records-redo-count": "", children: undo.depth.forward })
|
|
9191
|
+
]
|
|
8802
9192
|
}
|
|
8803
|
-
)
|
|
9193
|
+
)
|
|
8804
9194
|
] });
|
|
8805
9195
|
}
|
|
8806
9196
|
function UndoNotice({ undo }) {
|
|
8807
9197
|
const notice = undo.notice;
|
|
9198
|
+
const dismiss = undo.dismiss;
|
|
9199
|
+
(0, import_react49.useEffect)(() => {
|
|
9200
|
+
if (!notice || notice.refusal) return;
|
|
9201
|
+
const timer = setTimeout(dismiss, 8e3);
|
|
9202
|
+
return () => clearTimeout(timer);
|
|
9203
|
+
}, [notice, dismiss]);
|
|
8808
9204
|
if (!notice) return null;
|
|
8809
9205
|
return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
|
|
8810
9206
|
"p",
|
|
8811
9207
|
{
|
|
8812
9208
|
role: "status",
|
|
8813
9209
|
"data-records-undo-notice": notice.refusal ? "refused" : "done",
|
|
8814
|
-
className: "flex items-center gap-2 rounded border border-border bg-
|
|
9210
|
+
className: "pointer-events-auto absolute bottom-3 left-1/2 z-30 flex max-w-[min(36rem,calc(100%-2rem))] -translate-x-1/2 items-center gap-2 rounded-md border border-border bg-popover px-3 py-1.5 text-xs text-popover-foreground shadow-lg",
|
|
8815
9211
|
children: [
|
|
8816
9212
|
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { children: notice.says }),
|
|
8817
9213
|
notice.canUndo && undo.canUndo ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_design_system28.Button, { size: "sm", variant: "outline", className: "ml-auto h-6", "data-records-undo-now": "", onClick: () => void undo.undo(), children: "Undo" }) : null,
|
|
@@ -8833,7 +9229,7 @@ function undoKey(event) {
|
|
|
8833
9229
|
|
|
8834
9230
|
// src/gridAssist.tsx
|
|
8835
9231
|
var import_react52 = require("react");
|
|
8836
|
-
var
|
|
9232
|
+
var import_core6 = require("@ai-matrx/records/core");
|
|
8837
9233
|
var import_content_transfer = require("@ai-matrx/design-system/content-transfer");
|
|
8838
9234
|
|
|
8839
9235
|
// src/gridAgentContext.ts
|
|
@@ -9103,7 +9499,7 @@ function useGridAssist(args) {
|
|
|
9103
9499
|
if (!row) return;
|
|
9104
9500
|
const document2 = row.document ?? {};
|
|
9105
9501
|
const value = editing.valueFor(row, fieldOf2(key));
|
|
9106
|
-
const pointer = typeof document2[key] === "string" ? (0,
|
|
9502
|
+
const pointer = typeof document2[key] === "string" ? (0, import_core6.wholeValueOf)(document2, key) : null;
|
|
9107
9503
|
setWhole({ rowId, key, rowTitle: titleOf(row), value, pointer });
|
|
9108
9504
|
},
|
|
9109
9505
|
[rowOf, fieldOf2, titleOf, editing]
|
|
@@ -9196,6 +9592,7 @@ function useGridAssist(args) {
|
|
|
9196
9592
|
// src/gridBulk.tsx
|
|
9197
9593
|
var import_react53 = require("react");
|
|
9198
9594
|
var import_react54 = require("@ai-matrx/records/react");
|
|
9595
|
+
var import_core7 = require("@ai-matrx/records/core");
|
|
9199
9596
|
var import_design_system30 = require("@ai-matrx/design-system");
|
|
9200
9597
|
var import_jsx_runtime37 = require("react/jsx-runtime");
|
|
9201
9598
|
function countOf(n, one, many) {
|
|
@@ -9252,6 +9649,7 @@ function useGridBulk(args) {
|
|
|
9252
9649
|
setRefusal(done.error);
|
|
9253
9650
|
return null;
|
|
9254
9651
|
}
|
|
9652
|
+
for (const change of changes) if ("record_id" in change && change.record_id) (0, import_core7.announceRecordChanged)(change.record_id);
|
|
9255
9653
|
return done.data;
|
|
9256
9654
|
},
|
|
9257
9655
|
[client, tableId]
|
|
@@ -9412,7 +9810,7 @@ function BulkActions({
|
|
|
9412
9810
|
// src/handOrder.tsx
|
|
9413
9811
|
var import_react55 = require("react");
|
|
9414
9812
|
var import_react56 = require("@ai-matrx/records/react");
|
|
9415
|
-
var
|
|
9813
|
+
var import_records14 = require("@ai-matrx/records");
|
|
9416
9814
|
var import_design_system31 = require("@ai-matrx/design-system");
|
|
9417
9815
|
|
|
9418
9816
|
// src/viewKeys.ts
|
|
@@ -9629,8 +10027,8 @@ function unknownViewLine(raw) {
|
|
|
9629
10027
|
}
|
|
9630
10028
|
var LAYOUT_NO_FIELD = {
|
|
9631
10029
|
grid: "",
|
|
9632
|
-
kanban: "This table has no choice field yet, so there is nothing to make columns from. Add a field that holds one of a set of choices \u2014 Add
|
|
9633
|
-
calendar: "This table has no date field yet, so there is nothing to place records on. Add a field that holds a date \u2014 Add
|
|
10030
|
+
kanban: "This table has no choice field yet, so there is nothing to make columns from. Add a field that holds one of a set of choices \u2014 Add column, then Select \u2014 and it appears here.",
|
|
10031
|
+
calendar: "This table has no date field yet, so there is nothing to place records on. Add a field that holds a date \u2014 Add column, then Date \u2014 and it appears here.",
|
|
9634
10032
|
gallery: ""
|
|
9635
10033
|
};
|
|
9636
10034
|
function viewDocument(spec) {
|
|
@@ -9840,7 +10238,7 @@ function useHandOrderRows(args) {
|
|
|
9840
10238
|
const client = (0, import_react56.useRecordsClient)();
|
|
9841
10239
|
const { viewId, active, page, pageSize } = args;
|
|
9842
10240
|
const filter = args.filter && Object.keys(args.filter).length > 0 ? args.filter : null;
|
|
9843
|
-
const filterKey = filter ? (0,
|
|
10241
|
+
const filterKey = filter ? (0, import_records14.stableFilterKey)(filter) : "";
|
|
9844
10242
|
const filterNow = (0, import_react55.useRef)(filter);
|
|
9845
10243
|
filterNow.current = filter;
|
|
9846
10244
|
const [answer, setAnswer] = (0, import_react55.useState)(null);
|
|
@@ -9967,6 +10365,7 @@ function Grid({
|
|
|
9967
10365
|
pageSize = 50,
|
|
9968
10366
|
filter,
|
|
9969
10367
|
onOpenRecord,
|
|
10368
|
+
openRecordId = null,
|
|
9970
10369
|
onAddField,
|
|
9971
10370
|
onNewRecordForm,
|
|
9972
10371
|
toolbarActions,
|
|
@@ -9996,7 +10395,8 @@ function Grid({
|
|
|
9996
10395
|
);
|
|
9997
10396
|
const merged = host.grid === "merged";
|
|
9998
10397
|
const phone = (0, import_design_system32.useIsMobile)();
|
|
9999
|
-
const
|
|
10398
|
+
const toolbarFold2 = useTableToolbarFold();
|
|
10399
|
+
const sheetExtras = useTableToolbarSheetExtras();
|
|
10000
10400
|
const rights = useTableRights(table.data);
|
|
10001
10401
|
const [heldOrder, setHeldOrder] = (0, import_react57.useState)(null);
|
|
10002
10402
|
const sortsKey = JSON.stringify(viewOrder?.sorts ?? []);
|
|
@@ -10081,6 +10481,7 @@ function Grid({
|
|
|
10081
10481
|
);
|
|
10082
10482
|
const canWrite = Boolean(editable) && (rights.write || askPerRow && rowIds.some(mayWriteRow));
|
|
10083
10483
|
const pushWritten = (0, import_react57.useRef)(null);
|
|
10484
|
+
const sheetController = (0, import_react57.useRef)(null);
|
|
10084
10485
|
const editing = useGridEditing({
|
|
10085
10486
|
tableId,
|
|
10086
10487
|
fields: fields.data ?? [],
|
|
@@ -10088,7 +10489,15 @@ function Grid({
|
|
|
10088
10489
|
reload: records.reload,
|
|
10089
10490
|
canWrite,
|
|
10090
10491
|
mayWriteRow,
|
|
10091
|
-
...merged ? { onWritten: (write) => pushWritten.current?.(write) } : {}
|
|
10492
|
+
...merged ? { onWritten: (write) => pushWritten.current?.(write) } : {},
|
|
10493
|
+
/*
|
|
10494
|
+
* THE KEYBOARD COMES BACK (merged-grid review 2026-09-26, A1): Enter moves the cursor down, Tab
|
|
10495
|
+
* right, Escape stays, and the grid — not the page body — has the keys, so the next arrow,
|
|
10496
|
+
* Enter or Cmd-Z works without a click. Sheets and Airtable.
|
|
10497
|
+
*/
|
|
10498
|
+
...merged ? {
|
|
10499
|
+
onEnded: (exit, from, how) => sheetController.current?.endEdit(exit, { rowId: from.rowId, columnId: from.key }, how)
|
|
10500
|
+
} : {}
|
|
10092
10501
|
});
|
|
10093
10502
|
const undo = useGridUndo({ tableId, reload: records.reload, forget: editing.forget });
|
|
10094
10503
|
pushWritten.current = (write) => {
|
|
@@ -10103,7 +10512,7 @@ function Grid({
|
|
|
10103
10512
|
const [localLook, setLocalLook] = (0, import_react57.useState)(null);
|
|
10104
10513
|
const [localSummaries, setLocalSummaries] = (0, import_react57.useState)(null);
|
|
10105
10514
|
const sortedKeys = (0, import_react57.useMemo)(
|
|
10106
|
-
() => [...fields.data ?? []].sort(
|
|
10515
|
+
() => [...fields.data ?? []].sort(compareFieldOrder).map((f) => f.key),
|
|
10107
10516
|
[fields.data]
|
|
10108
10517
|
);
|
|
10109
10518
|
const look = (0, import_react57.useMemo)(() => {
|
|
@@ -10120,16 +10529,16 @@ function Grid({
|
|
|
10120
10529
|
const frozenList = look.frozen;
|
|
10121
10530
|
const frozenKeys = (0, import_react57.useMemo)(() => new Set(frozenList), [frozenList]);
|
|
10122
10531
|
const ordered = (0, import_react57.useMemo)(() => {
|
|
10123
|
-
const all = [...fields.data ?? []].sort(
|
|
10124
|
-
(a, b) => (a.sort ?? 100) - (b.sort ?? 100) || a.key.localeCompare(b.key)
|
|
10125
|
-
);
|
|
10532
|
+
const all = [...fields.data ?? []].sort(compareFieldOrder);
|
|
10126
10533
|
if (frozenKeys.size === 0) return all;
|
|
10127
10534
|
const pinned = frozenList.map((key) => all.find((f) => f.key === key)).filter((f) => f !== void 0);
|
|
10128
10535
|
return [...pinned, ...all.filter((f) => !frozenKeys.has(f.key))];
|
|
10129
10536
|
}, [fields.data, frozenKeys, frozenList]);
|
|
10130
10537
|
const tableActions = useTableActions(tableId, canWrite || Boolean(host.runAgentAction));
|
|
10131
|
-
const
|
|
10132
|
-
const
|
|
10538
|
+
const ownWord = (table.data?.label_singular ?? "").trim();
|
|
10539
|
+
const sameAsName = ownWord === "" || (table.data ? ownWord.toLowerCase() === tableName(table.data).toLowerCase() : true);
|
|
10540
|
+
const noun = merged && sameAsName ? "row" : (table.data?.label_singular ?? "record").toLowerCase();
|
|
10541
|
+
const nounPlural = merged && sameAsName ? "rows" : pluralNoun(table.data ?? null).toLowerCase();
|
|
10133
10542
|
const runner = useActionRunner({
|
|
10134
10543
|
rows,
|
|
10135
10544
|
fields: fields.data ?? [],
|
|
@@ -10177,10 +10586,29 @@ function Grid({
|
|
|
10177
10586
|
setDuplicated(`The copy was not made: ${made.error.message}`);
|
|
10178
10587
|
return;
|
|
10179
10588
|
}
|
|
10180
|
-
|
|
10589
|
+
const copyId = made.data;
|
|
10590
|
+
if (merged) added.pin({ id: copyId, document: data, level: row.level, hidden: {} }, row.id);
|
|
10591
|
+
if (manual && viewId) {
|
|
10592
|
+
const ids = rows.map((r) => r.id).filter((id) => id !== copyId);
|
|
10593
|
+
const at = ids.indexOf(row.id);
|
|
10594
|
+
const next = [...ids.slice(0, at + 1), copyId, ...ids.slice(at + 1)];
|
|
10595
|
+
const placed = await client.viewRecordOrderSet({ view_id: viewId, record_ids: next });
|
|
10596
|
+
if (!placed.ok) {
|
|
10597
|
+
setDuplicated(`A copy of ${recordNameIn(table.data, document2)} was made and could not be placed beside it: ${placed.error.message}`);
|
|
10598
|
+
records.reload();
|
|
10599
|
+
return;
|
|
10600
|
+
}
|
|
10601
|
+
viewOrder?.onPlaced?.();
|
|
10602
|
+
hand.reload();
|
|
10603
|
+
}
|
|
10604
|
+
if (merged) {
|
|
10605
|
+
undo.push({ label: `Duplicated ${recordNameIn(table.data, document2)}`, cells: [], created: [copyId] });
|
|
10606
|
+
} else {
|
|
10607
|
+
setDuplicated(`A copy of ${recordNameIn(table.data, document2)} is in the table, right below it.`);
|
|
10608
|
+
}
|
|
10181
10609
|
records.reload();
|
|
10182
10610
|
},
|
|
10183
|
-
[client, fields.data, host, records, table.data, tableId]
|
|
10611
|
+
[client, fields.data, host, records, table.data, tableId, merged, added, manual, viewId, rows, viewOrder, hand, undo]
|
|
10184
10612
|
);
|
|
10185
10613
|
const recordIds = (0, import_react57.useMemo)(() => rows.map((r) => r.id), [rows]);
|
|
10186
10614
|
const { cells: enrichCells, refresh: refreshEnrich } = useEnrichCells(tableId, recordIds);
|
|
@@ -10272,9 +10700,20 @@ function Grid({
|
|
|
10272
10700
|
why
|
|
10273
10701
|
});
|
|
10274
10702
|
}
|
|
10703
|
+
if (merged && built.cells > 0 && built.creates === 0 && built.refusals.length === 0 && built.pastRightEdge === 0 && built.unmatchedOptions.length === 0) {
|
|
10704
|
+
const outcome = await pasteAtOnce.current(built);
|
|
10705
|
+
if (outcome.refusals.length > 0 || outcome.missing > 0) {
|
|
10706
|
+
undo.say(
|
|
10707
|
+
outcome.refusals.length > 0 ? outcome.refusals.join(" ") : `${outcome.missing} pasted ${outcome.missing === 1 ? "value was" : "values were"} not found in the table when it was read back.`,
|
|
10708
|
+
true
|
|
10709
|
+
);
|
|
10710
|
+
}
|
|
10711
|
+
sheetController.current?.focusGrid();
|
|
10712
|
+
return;
|
|
10713
|
+
}
|
|
10275
10714
|
setPastePlan({ plan: built });
|
|
10276
10715
|
},
|
|
10277
|
-
[fields.data, loadChoiceOptions, rows]
|
|
10716
|
+
[fields.data, loadChoiceOptions, rows, merged, undo]
|
|
10278
10717
|
);
|
|
10279
10718
|
const addPastedOptions = (0, import_react57.useCallback)(
|
|
10280
10719
|
async (fieldKey, words2) => {
|
|
@@ -10330,6 +10769,7 @@ function Grid({
|
|
|
10330
10769
|
}))
|
|
10331
10770
|
);
|
|
10332
10771
|
editing.forget(cells);
|
|
10772
|
+
for (const u of updates) (0, import_core8.announceRecordChanged)(u.row.recordId);
|
|
10333
10773
|
const lines = updates.length + creates.length;
|
|
10334
10774
|
undo.push({ label: `Pasted ${lines} ${lines === 1 ? "row" : "rows"}`, cells, created });
|
|
10335
10775
|
const expected = [
|
|
@@ -10354,6 +10794,8 @@ function Grid({
|
|
|
10354
10794
|
},
|
|
10355
10795
|
[rows, client, tableId, editing, undo, records]
|
|
10356
10796
|
);
|
|
10797
|
+
const pasteAtOnce = (0, import_react57.useRef)(applyPasteAsOne);
|
|
10798
|
+
pasteAtOnce.current = applyPasteAsOne;
|
|
10357
10799
|
const writeRange = (0, import_react57.useCallback)(
|
|
10358
10800
|
async (patches, intent) => {
|
|
10359
10801
|
if (patches.length === 0) return;
|
|
@@ -10378,7 +10820,14 @@ function Grid({
|
|
|
10378
10820
|
const applyPaste = (0, import_react57.useCallback)(async () => {
|
|
10379
10821
|
const held = pastePlan;
|
|
10380
10822
|
if (!held) return { written: 0, readBack: 0, missing: 0, refusals: [] };
|
|
10381
|
-
if (merged)
|
|
10823
|
+
if (merged) {
|
|
10824
|
+
const outcome = await applyPasteAsOne(held.plan);
|
|
10825
|
+
if (outcome.missing === 0 && outcome.refusals.length === 0) {
|
|
10826
|
+
setPastePlan(null);
|
|
10827
|
+
sheetController.current?.focusGrid();
|
|
10828
|
+
}
|
|
10829
|
+
return outcome;
|
|
10830
|
+
}
|
|
10382
10831
|
const refusals = [];
|
|
10383
10832
|
let written = 0;
|
|
10384
10833
|
const expected = [];
|
|
@@ -10473,7 +10922,7 @@ function Grid({
|
|
|
10473
10922
|
tableName: table.data ? tableName(table.data) : "",
|
|
10474
10923
|
fields: fields.data ?? [],
|
|
10475
10924
|
document: row.document ?? {},
|
|
10476
|
-
level: (0,
|
|
10925
|
+
level: (0, import_records15.asPermissionLevel)(row.level)
|
|
10477
10926
|
}),
|
|
10478
10927
|
[host, tableId, table.data, fields.data]
|
|
10479
10928
|
);
|
|
@@ -10501,6 +10950,18 @@ function Grid({
|
|
|
10501
10950
|
total: typeof records.data?.total === "number" ? records.data.total : null,
|
|
10502
10951
|
search: searchTerm
|
|
10503
10952
|
});
|
|
10953
|
+
const followRef = (0, import_react57.useRef)({ openRecordId, onOpenRecord });
|
|
10954
|
+
followRef.current = { openRecordId, onOpenRecord };
|
|
10955
|
+
const followSelection = (0, import_react57.useCallback)(
|
|
10956
|
+
(selection) => {
|
|
10957
|
+
assist.onSelectionChange(selection);
|
|
10958
|
+
const { openRecordId: open, onOpenRecord: openIt } = followRef.current;
|
|
10959
|
+
if (!open || !openIt || !selection) return;
|
|
10960
|
+
const rowId = selection.active.rowId;
|
|
10961
|
+
if (rowId !== open && rowId !== ADD_ROW.id && rowId !== SUMMARY_ROW.id) openIt(rowId);
|
|
10962
|
+
},
|
|
10963
|
+
[assist.onSelectionChange]
|
|
10964
|
+
);
|
|
10504
10965
|
const addRowRef = (0, import_react57.useRef)(() => void 0);
|
|
10505
10966
|
const controls = useMergedGridControls({
|
|
10506
10967
|
enabled: merged,
|
|
@@ -10615,10 +11076,10 @@ function Grid({
|
|
|
10615
11076
|
}
|
|
10616
11077
|
);
|
|
10617
11078
|
}
|
|
10618
|
-
), mayAddRow ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(AddRowLine, { onAdd: () => void addRowHere(), busy: added.busy }) : null), controls.headerMenuFor);
|
|
11079
|
+
), mayAddRow ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(AddRowLine, { onAdd: () => void addRowHere(), busy: added.busy }) : null), controls.headerMenuFor, controls.filterFor);
|
|
10619
11080
|
return merged && rights.structure && editable ? [...drawn, addColumnColumn(controls.addColumn)] : drawn;
|
|
10620
11081
|
},
|
|
10621
|
-
[fieldColumns, onOpenRecord, table.data, ordered, chosenSummaries, summaries, chooseSummary, controls.headerMenuFor, mayAddRow, addRowHere, added.busy, merged, rights.structure, editable, controls.addColumn]
|
|
11082
|
+
[fieldColumns, onOpenRecord, table.data, ordered, chosenSummaries, summaries, chooseSummary, controls.headerMenuFor, controls.filterFor, mayAddRow, addRowHere, added.busy, merged, rights.structure, editable, controls.addColumn]
|
|
10622
11083
|
);
|
|
10623
11084
|
const [localColumns, setLocalColumns] = (0, import_react57.useState)(
|
|
10624
11085
|
null
|
|
@@ -10641,7 +11102,11 @@ function Grid({
|
|
|
10641
11102
|
() => ({
|
|
10642
11103
|
order: controls.columns?.order ?? localColumns?.order ?? columnIds,
|
|
10643
11104
|
hidden: controls.columns?.hidden ?? localColumns?.hidden ?? [],
|
|
10644
|
-
onChange: (
|
|
11105
|
+
onChange: (drawn) => {
|
|
11106
|
+
const next = { order: fieldColumnIds(drawn.order, fields.data ?? []), hidden: fieldColumnIds(drawn.hidden, fields.data ?? []) };
|
|
11107
|
+
if (controls.columns) controls.columns.onChange(next);
|
|
11108
|
+
else setLocalColumns(next);
|
|
11109
|
+
},
|
|
10645
11110
|
...widths ? { widths } : {},
|
|
10646
11111
|
// A drag that changed nothing would be the silent half of a control that
|
|
10647
11112
|
// looks alive, so the width always lands somewhere — the session at
|
|
@@ -10651,13 +11116,12 @@ function Grid({
|
|
|
10651
11116
|
writeLook({ ...presentation, widths: next });
|
|
10652
11117
|
}
|
|
10653
11118
|
}),
|
|
10654
|
-
[columnIds, localColumns, widths, presentation, writeLook, controls.columns]
|
|
11119
|
+
[columnIds, localColumns, widths, presentation, writeLook, controls.columns, fields.data]
|
|
10655
11120
|
);
|
|
10656
11121
|
const groupingConfig = (0, import_react57.useMemo)(() => {
|
|
10657
11122
|
const view = presentation.grouping;
|
|
10658
11123
|
const field2 = view?.field ?? null;
|
|
10659
11124
|
if (field2 === null && !onPresentationChange) return null;
|
|
10660
|
-
if (field2 === null && merged && phone) return null;
|
|
10661
11125
|
const aggregates = view && Object.keys(view.aggregates ?? {}).length > 0 ? view.aggregates : field2 ? { [field2]: { kind: "count" } } : void 0;
|
|
10662
11126
|
return {
|
|
10663
11127
|
columnId: field2,
|
|
@@ -10704,7 +11168,7 @@ function Grid({
|
|
|
10704
11168
|
return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(WhoChangedSource, { tableId, labelOf: labelOfField, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
|
|
10705
11169
|
"div",
|
|
10706
11170
|
{
|
|
10707
|
-
className: (0, import_design_system32.cn)("flex min-h-0 flex-col gap-2", fills && "flex-1", className),
|
|
11171
|
+
className: (0, import_design_system32.cn)("relative flex min-h-0 flex-col gap-2", fills && "flex-1", className),
|
|
10708
11172
|
"data-records-grid-footer": footer ?? "natural",
|
|
10709
11173
|
...merged ? {
|
|
10710
11174
|
// ONE Cmd-Z (tranche 6d): the grid's own undo, never a text box's.
|
|
@@ -10719,7 +11183,7 @@ function Grid({
|
|
|
10719
11183
|
} : {},
|
|
10720
11184
|
children: [
|
|
10721
11185
|
toolbarActions ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "flex items-center gap-2", "data-records-grid-actions": "", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "ml-auto flex items-center gap-1", children: toolbarActions }) }) : null,
|
|
10722
|
-
records.error ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(RefusalNotice, { error: records.error }) : null,
|
|
11186
|
+
records.error ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(RefusalNotice, { error: inColumnWords(records.error, fields.data ?? []) }) : null,
|
|
10723
11187
|
manual && hand.error ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(RefusalNotice, { error: hand.error }) : null,
|
|
10724
11188
|
archived ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
|
|
10725
11189
|
"p",
|
|
@@ -10931,7 +11395,7 @@ function Grid({
|
|
|
10931
11395
|
editing.setCell(patch.rowId, patch.columnId, patch.value);
|
|
10932
11396
|
}
|
|
10933
11397
|
},
|
|
10934
|
-
...merged ? { undoDepth: 0, onSelectionChange:
|
|
11398
|
+
...merged ? { undoDepth: 0, onSelectionChange: followSelection, controllerRef: sheetController } : {},
|
|
10935
11399
|
columnRefusal: (columnId) => {
|
|
10936
11400
|
const field2 = (fields.data ?? []).find((f) => f.key === columnId);
|
|
10937
11401
|
if (!field2) return `This table has no column called ${columnId}.`;
|
|
@@ -10953,6 +11417,7 @@ function Grid({
|
|
|
10953
11417
|
onEditRequest: (address, seed) => {
|
|
10954
11418
|
const field2 = (fields.data ?? []).find((f) => f.key === address.columnId);
|
|
10955
11419
|
if (!field2 || !fieldIsEditable(field2) || !mayWriteRow(address.rowId)) return false;
|
|
11420
|
+
if (seed !== null && seed !== "" && typingOpens(field2) === "none") return false;
|
|
10956
11421
|
editing.begin({ rowId: address.rowId, key: field2.key }, seed);
|
|
10957
11422
|
return true;
|
|
10958
11423
|
},
|
|
@@ -10969,7 +11434,7 @@ function Grid({
|
|
|
10969
11434
|
spreadsheet: {
|
|
10970
11435
|
enabled: true,
|
|
10971
11436
|
readOnly: true,
|
|
10972
|
-
...merged ? { onSelectionChange:
|
|
11437
|
+
...merged ? { onSelectionChange: followSelection, controllerRef: sheetController } : {},
|
|
10973
11438
|
readOnlyReason: !editable || !rights.known || rights.write ? "Editing is off here, so this table can be copied but not changed." : rights.why("write")
|
|
10974
11439
|
}
|
|
10975
11440
|
},
|
|
@@ -11062,7 +11527,12 @@ function Grid({
|
|
|
11062
11527
|
) : null
|
|
11063
11528
|
] })
|
|
11064
11529
|
} : {},
|
|
11065
|
-
emptyState: {
|
|
11530
|
+
emptyState: records.error ? {
|
|
11531
|
+
// A REFUSED READ IS NOT AN EMPTY TABLE (merged-grid review 2026-09-26): the store's own
|
|
11532
|
+
// sentence is above the grid, and this never says "No records yet" over it.
|
|
11533
|
+
title: "These records could not be read",
|
|
11534
|
+
description: inColumnWords(records.error, fields.data ?? []).message
|
|
11535
|
+
} : {
|
|
11066
11536
|
...mayAddRow ? { action: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(AddFirstRowButton, { onAdd: () => void addRowHere(), busy: added.busy }) } : {},
|
|
11067
11537
|
title: "No records yet",
|
|
11068
11538
|
description: 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."
|
|
@@ -11075,35 +11545,21 @@ function Grid({
|
|
|
11075
11545
|
* the row, so Tools is never scrolled out of reach; the search is the same query.
|
|
11076
11546
|
*/
|
|
11077
11547
|
...merged && phone ? {
|
|
11078
|
-
customSearch: /* @__PURE__ */ (0, import_jsx_runtime39.
|
|
11079
|
-
|
|
11080
|
-
|
|
11081
|
-
|
|
11082
|
-
|
|
11083
|
-
|
|
11084
|
-
|
|
11085
|
-
|
|
11086
|
-
|
|
11087
|
-
|
|
11088
|
-
|
|
11089
|
-
|
|
11090
|
-
|
|
11091
|
-
|
|
11092
|
-
|
|
11093
|
-
),
|
|
11094
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
11095
|
-
import_design_system32.Button,
|
|
11096
|
-
{
|
|
11097
|
-
size: "sm",
|
|
11098
|
-
variant: "outline",
|
|
11099
|
-
className: "h-8 shrink-0",
|
|
11100
|
-
"data-records-grid-tools": "",
|
|
11101
|
-
"aria-haspopup": "dialog",
|
|
11102
|
-
onClick: () => setToolsOpen(true),
|
|
11103
|
-
children: "Tools"
|
|
11104
|
-
}
|
|
11105
|
-
)
|
|
11106
|
-
] })
|
|
11548
|
+
customSearch: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "flex min-w-0 flex-1 items-center gap-1.5", "data-records-grid-phone-search": "", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
11549
|
+
"input",
|
|
11550
|
+
{
|
|
11551
|
+
type: "search",
|
|
11552
|
+
value: query.search,
|
|
11553
|
+
placeholder: "Search",
|
|
11554
|
+
"aria-label": "Search records",
|
|
11555
|
+
onChange: (event) => {
|
|
11556
|
+
const search = event.target.value;
|
|
11557
|
+
setQuery((held) => ({ ...held, search, page: 1 }));
|
|
11558
|
+
},
|
|
11559
|
+
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",
|
|
11560
|
+
style: { fontSize: "16px" }
|
|
11561
|
+
}
|
|
11562
|
+
) })
|
|
11107
11563
|
} : {},
|
|
11108
11564
|
/* ONE ROW (lane TABLE-PAGE-CHROME): on a table page the grid's search, columns and
|
|
11109
11565
|
sort are drawn INTO the page's toolbar row, never a second bar under it. */
|
|
@@ -11111,63 +11567,110 @@ function Grid({
|
|
|
11111
11567
|
// SCR-2 / SCR-4: PRESENT for an admin, ABSENT for everyone else.
|
|
11112
11568
|
// There is no third state: this package ships no disabled variant of
|
|
11113
11569
|
// these controls, so a member cannot be shown a dead one by mistake.
|
|
11114
|
-
|
|
11115
|
-
|
|
11116
|
-
|
|
11117
|
-
|
|
11118
|
-
|
|
11119
|
-
|
|
11120
|
-
|
|
11121
|
-
|
|
11122
|
-
|
|
11123
|
-
|
|
11124
|
-
onUseSort: () => {
|
|
11125
|
-
const sort = columnSort;
|
|
11126
|
-
const chosen = { field: sort.id, direction: sort.direction };
|
|
11127
|
-
setHeldOrder({ order: "sorted", sorts: [chosen] });
|
|
11128
|
-
viewOrder.onUseSort?.(chosen);
|
|
11129
|
-
host.notify?.success(
|
|
11130
|
-
`Sorted by ${columnLabel(fields.data ?? [], sort.id)} ${sortArrow(sort.direction)}. ${MANUAL_SORT.replacedToast}`
|
|
11131
|
-
);
|
|
11132
|
-
}
|
|
11133
|
-
} : {}
|
|
11134
|
-
})
|
|
11135
|
-
}
|
|
11136
|
-
) : null,
|
|
11137
|
-
merged ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(UndoRedoButtons, { undo }) : null,
|
|
11138
|
-
viewId && viewOrder?.onPlaced && rows.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system32.Button, { size: "sm", variant: "ghost", onClick: () => setReordering(true), "data-records-reorder-open": "", children: "Reorder" }) : null,
|
|
11139
|
-
Boolean(editable) && rights.write ? merged ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system32.Button, { size: "sm", disabled: added.busy, onClick: () => void addRowHere(), children: added.busy ? "Adding\u2026" : "New record" }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system32.Button, { size: "sm", disabled: editing.busy, onClick: () => void editing.addRecord(), children: editing.busy ? "Adding\u2026" : "New record" }) : null,
|
|
11140
|
-
rights.structure && onAddField ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(AddFieldButton, { onClick: onAddField }) : null,
|
|
11141
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
11142
|
-
GridLayoutMenu,
|
|
11143
|
-
{
|
|
11144
|
-
look,
|
|
11145
|
-
firstColumn: sortedKeys[0] ? {
|
|
11146
|
-
key: sortedKeys[0],
|
|
11147
|
-
name: (() => {
|
|
11148
|
-
const first = (fields.data ?? []).find((f) => f.key === sortedKeys[0]);
|
|
11149
|
-
return first ? fieldName(first) : humanize(sortedKeys[0]);
|
|
11150
|
-
})()
|
|
11151
|
-
} : null,
|
|
11152
|
-
onChange: changeLayout,
|
|
11153
|
-
kept: Boolean(onPresentationChange)
|
|
11154
|
-
}
|
|
11155
|
-
)
|
|
11156
|
-
] });
|
|
11157
|
-
if (!merged || !phone) return all;
|
|
11158
|
-
return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_jsx_runtime39.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system32.BottomSheet, { open: toolsOpen, onOpenChange: setToolsOpen, title: "Table tools", surface: "solid", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
11159
|
-
"div",
|
|
11570
|
+
/*
|
|
11571
|
+
* WHERE THE GRID'S CONTROLS GO (lane C, review 2026-09-26 D6). Inline when the row has
|
|
11572
|
+
* room; behind the row's "…" once the page row is folded that far (Undo / Redo and New
|
|
11573
|
+
* record stay beside it — the primary action is never folded); on a phone EVERY control
|
|
11574
|
+
* — the table's own (clear, copy/export, group, columns), these, the search, the sort and
|
|
11575
|
+
* the page's filter, layout and share — in ONE "Tools" sheet. Never dropped.
|
|
11576
|
+
*/
|
|
11577
|
+
actions: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
|
|
11578
|
+
manual ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
11579
|
+
SortStateControl,
|
|
11160
11580
|
{
|
|
11161
|
-
|
|
11162
|
-
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
|
|
11166
|
-
|
|
11167
|
-
|
|
11581
|
+
...handOrderSortState({
|
|
11582
|
+
sortName: columnSort ? columnLabel(fields.data ?? [], columnSort.id) : null,
|
|
11583
|
+
direction: columnSort?.direction ?? null,
|
|
11584
|
+
onBack: () => setQuery((held) => ({ ...held, sort: null, page: 1 })),
|
|
11585
|
+
...viewOrder?.onUseSort && columnSort ? {
|
|
11586
|
+
onUseSort: () => {
|
|
11587
|
+
const sort = columnSort;
|
|
11588
|
+
const chosen = { field: sort.id, direction: sort.direction };
|
|
11589
|
+
setHeldOrder({ order: "sorted", sorts: [chosen] });
|
|
11590
|
+
viewOrder.onUseSort?.(chosen);
|
|
11591
|
+
host.notify?.success(
|
|
11592
|
+
`Sorted by ${columnLabel(fields.data ?? [], sort.id)} ${sortArrow(sort.direction)}. ${MANUAL_SORT.replacedToast}`
|
|
11593
|
+
);
|
|
11594
|
+
}
|
|
11595
|
+
} : {}
|
|
11596
|
+
})
|
|
11168
11597
|
}
|
|
11169
|
-
)
|
|
11170
|
-
|
|
11598
|
+
) : null,
|
|
11599
|
+
viewId && viewOrder?.onPlaced && rows.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system32.Button, { size: "sm", variant: "ghost", onClick: () => setReordering(true), "data-records-reorder-open": "", children: "Reorder" }) : null,
|
|
11600
|
+
!merged && Boolean(editable) && rights.write ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system32.Button, { size: "sm", disabled: editing.busy, onClick: () => void editing.addRecord(), children: editing.busy ? "Adding\u2026" : "New record" }) : null,
|
|
11601
|
+
rights.structure && onAddField ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(AddFieldButton, { onClick: onAddField }) : null,
|
|
11602
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
11603
|
+
GridLayoutMenu,
|
|
11604
|
+
{
|
|
11605
|
+
look,
|
|
11606
|
+
firstColumn: sortedKeys[0] ? {
|
|
11607
|
+
key: sortedKeys[0],
|
|
11608
|
+
name: (() => {
|
|
11609
|
+
const first = (fields.data ?? []).find((f) => f.key === sortedKeys[0]);
|
|
11610
|
+
return first ? fieldName(first) : humanize(sortedKeys[0]);
|
|
11611
|
+
})()
|
|
11612
|
+
} : null,
|
|
11613
|
+
onChange: changeLayout,
|
|
11614
|
+
kept: Boolean(onPresentationChange)
|
|
11615
|
+
}
|
|
11616
|
+
)
|
|
11617
|
+
] }),
|
|
11618
|
+
...merged ? {
|
|
11619
|
+
overflow: {
|
|
11620
|
+
mode: phone ? "sheet" : toolbarFold2 >= 3 ? "menu" : "inline",
|
|
11621
|
+
pinned: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
|
|
11622
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(UndoRedoButtons, { undo }),
|
|
11623
|
+
Boolean(editable) && rights.write ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system32.Button, { size: "sm", className: "shrink-0", disabled: added.busy, onClick: () => void addRowHere(), children: added.busy ? "Adding\u2026" : "New record" }) : null
|
|
11624
|
+
] }),
|
|
11625
|
+
...phone ? {
|
|
11626
|
+
sheetTop: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex flex-col gap-1.5 pb-1", "data-records-grid-sheet-top": "", "data-matrx-table-tools-keep-open": "", children: [
|
|
11627
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
11628
|
+
"input",
|
|
11629
|
+
{
|
|
11630
|
+
type: "search",
|
|
11631
|
+
value: query.search,
|
|
11632
|
+
placeholder: "Search",
|
|
11633
|
+
"aria-label": "Search records",
|
|
11634
|
+
onChange: (event) => {
|
|
11635
|
+
const search = event.target.value;
|
|
11636
|
+
setQuery((held) => ({ ...held, search, page: 1 }));
|
|
11637
|
+
},
|
|
11638
|
+
className: "h-10 w-full min-w-0 rounded-md border border-input bg-background px-2 text-foreground placeholder:text-muted-foreground",
|
|
11639
|
+
style: { fontSize: "16px" }
|
|
11640
|
+
}
|
|
11641
|
+
),
|
|
11642
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("label", { className: "flex items-center gap-2 text-xs text-muted-foreground", "data-records-grid-sheet-sort": "", children: [
|
|
11643
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "shrink-0", children: "Sort by" }),
|
|
11644
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
|
|
11645
|
+
"select",
|
|
11646
|
+
{
|
|
11647
|
+
"aria-label": "Sort by",
|
|
11648
|
+
value: query.sort ? `${query.sort.id}:${query.sort.direction}` : "",
|
|
11649
|
+
onChange: (event) => {
|
|
11650
|
+
const [id, direction] = event.target.value.split(":");
|
|
11651
|
+
setQuery((held) => ({
|
|
11652
|
+
...held,
|
|
11653
|
+
page: 1,
|
|
11654
|
+
sort: id ? { id, direction: direction === "desc" ? "desc" : "asc" } : null
|
|
11655
|
+
}));
|
|
11656
|
+
},
|
|
11657
|
+
className: "h-10 min-w-0 flex-1 rounded-md border border-input bg-background px-2 text-foreground",
|
|
11658
|
+
style: { fontSize: "16px" },
|
|
11659
|
+
children: [
|
|
11660
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("option", { value: "", children: "No sort" }),
|
|
11661
|
+
ordered.flatMap((field2) => [
|
|
11662
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("option", { value: `${field2.key}:asc`, children: `${fieldName(field2)} \u2191` }, `${field2.key}:asc`),
|
|
11663
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("option", { value: `${field2.key}:desc`, children: `${fieldName(field2)} \u2193` }, `${field2.key}:desc`)
|
|
11664
|
+
])
|
|
11665
|
+
]
|
|
11666
|
+
}
|
|
11667
|
+
)
|
|
11668
|
+
] })
|
|
11669
|
+
] }),
|
|
11670
|
+
...sheetExtras ? { sheetExtras } : {}
|
|
11671
|
+
} : {}
|
|
11672
|
+
}
|
|
11673
|
+
} : {}
|
|
11171
11674
|
}
|
|
11172
11675
|
},
|
|
11173
11676
|
`records-grid-${tableId}:${openingKey}`
|
|
@@ -11179,11 +11682,14 @@ function Grid({
|
|
|
11179
11682
|
PastePreview,
|
|
11180
11683
|
{
|
|
11181
11684
|
plan: pastePlan.plan,
|
|
11182
|
-
noun: table.data?.label_singular ?? "record",
|
|
11183
|
-
nounPlural: pluralNoun(table.data ?? null),
|
|
11685
|
+
noun: merged ? noun : table.data?.label_singular ?? "record",
|
|
11686
|
+
nounPlural: merged ? nounPlural : pluralNoun(table.data ?? null),
|
|
11184
11687
|
onConfirm: applyPaste,
|
|
11185
11688
|
onAddOptions: addPastedOptions,
|
|
11186
|
-
onCancel: () =>
|
|
11689
|
+
onCancel: () => {
|
|
11690
|
+
setPastePlan(null);
|
|
11691
|
+
sheetController.current?.focusGrid();
|
|
11692
|
+
}
|
|
11187
11693
|
}
|
|
11188
11694
|
) : null,
|
|
11189
11695
|
merged ? null : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
@@ -11249,7 +11755,17 @@ function Grid({
|
|
|
11249
11755
|
const failed = await editing.remove(row.id);
|
|
11250
11756
|
setDeleteBusy(false);
|
|
11251
11757
|
setDeleting(null);
|
|
11252
|
-
if (
|
|
11758
|
+
if (failed) return;
|
|
11759
|
+
if (merged) {
|
|
11760
|
+
undo.push({
|
|
11761
|
+
label: `Deleted ${name} \u2014 it is in this table's Archived items`,
|
|
11762
|
+
cells: [],
|
|
11763
|
+
archived: [row.id]
|
|
11764
|
+
});
|
|
11765
|
+
sheetController.current?.focusGrid();
|
|
11766
|
+
} else {
|
|
11767
|
+
setArchived(name);
|
|
11768
|
+
}
|
|
11253
11769
|
}
|
|
11254
11770
|
}
|
|
11255
11771
|
)
|
|
@@ -11257,17 +11773,19 @@ function Grid({
|
|
|
11257
11773
|
}
|
|
11258
11774
|
) });
|
|
11259
11775
|
}
|
|
11260
|
-
function withHeaderMenus(columns, menuFor) {
|
|
11776
|
+
function withHeaderMenus(columns, menuFor, filterFor) {
|
|
11261
11777
|
return columns.map((column) => {
|
|
11262
11778
|
const items = menuFor(String(column.id));
|
|
11263
|
-
|
|
11779
|
+
const filter = filterFor?.(String(column.id));
|
|
11780
|
+
const withMenu = items && items.length > 0 ? { ...column, headerMenu: items } : column;
|
|
11781
|
+
return filter ? { ...withMenu, ...filter } : withMenu;
|
|
11264
11782
|
});
|
|
11265
11783
|
}
|
|
11266
11784
|
|
|
11267
11785
|
// src/ExportMenu.tsx
|
|
11268
11786
|
var import_react59 = require("react");
|
|
11269
11787
|
var import_react60 = require("@ai-matrx/records/react");
|
|
11270
|
-
var
|
|
11788
|
+
var import_core9 = require("@ai-matrx/records/core");
|
|
11271
11789
|
var import_react61 = require("@ai-matrx/alchemy/react");
|
|
11272
11790
|
var import_design_system33 = require("@ai-matrx/design-system");
|
|
11273
11791
|
var import_jsx_runtime40 = require("react/jsx-runtime");
|
|
@@ -11286,7 +11804,7 @@ function toRows(fields, records, labels) {
|
|
|
11286
11804
|
absent_reason: envelope?.absent ?? null,
|
|
11287
11805
|
// A VALUE KEPT AS A FILE carries its pointer, so the export writes the whole text
|
|
11288
11806
|
// (`withWholeValues`), never the first words passed off as the value.
|
|
11289
|
-
source: (0,
|
|
11807
|
+
source: (0, import_core9.wholeValueOf)(document2, field2.key)
|
|
11290
11808
|
};
|
|
11291
11809
|
}
|
|
11292
11810
|
return row;
|
|
@@ -11340,7 +11858,7 @@ function ExportMenu({ tableId, rows, label, className }) {
|
|
|
11340
11858
|
);
|
|
11341
11859
|
const lookup = (field2, id) => labels(field2, id) ?? learned.get(`${field2.id}:${id}`) ?? null;
|
|
11342
11860
|
const read = host.readFileText;
|
|
11343
|
-
return (0,
|
|
11861
|
+
return (0, import_core9.withWholeValues)(toRows(all, records, lookup), read ? (fileId) => read({ fileId }) : null);
|
|
11344
11862
|
};
|
|
11345
11863
|
return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: (0, import_design_system33.cn)("flex items-center gap-1", className), children: [
|
|
11346
11864
|
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
@@ -11348,7 +11866,7 @@ function ExportMenu({ tableId, rows, label, className }) {
|
|
|
11348
11866
|
{
|
|
11349
11867
|
size: "sm",
|
|
11350
11868
|
variant: "outline",
|
|
11351
|
-
onClick: () => void exported().then((rows2) => download(`${name}.csv`, "text/csv;charset=utf-8", (0,
|
|
11869
|
+
onClick: () => void exported().then((rows2) => download(`${name}.csv`, "text/csv;charset=utf-8", (0, import_core9.exportCsv)(fields.data ?? [], rows2))).catch(
|
|
11352
11870
|
(thrown) => setFailure(refusalFromThrown(thrown, "The CSV could not be written.", "Try again in a moment."))
|
|
11353
11871
|
),
|
|
11354
11872
|
children: "CSV"
|
|
@@ -11369,7 +11887,7 @@ function ExportMenu({ tableId, rows, label, className }) {
|
|
|
11369
11887
|
);
|
|
11370
11888
|
setFailure(null);
|
|
11371
11889
|
try {
|
|
11372
|
-
exported().then((rows2) => (0,
|
|
11890
|
+
exported().then((rows2) => (0, import_core9.exportXlsx)(fields.data ?? [], rows2, name)).then(
|
|
11373
11891
|
(bytes) => download(
|
|
11374
11892
|
`${name}.xlsx`,
|
|
11375
11893
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
@@ -11390,7 +11908,7 @@ function ExportMenu({ tableId, rows, label, className }) {
|
|
|
11390
11908
|
sourceId: `records:${tableId}`,
|
|
11391
11909
|
size: "xs",
|
|
11392
11910
|
json: () => records.map((r) => r.document),
|
|
11393
|
-
human: () => (0,
|
|
11911
|
+
human: () => (0, import_core9.exportCsv)(fields.data ?? [], toRows(fields.data ?? [], records, labels))
|
|
11394
11912
|
}
|
|
11395
11913
|
),
|
|
11396
11914
|
failure ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(RefusalLine, { error: failure }) : null
|
|
@@ -11494,11 +12012,11 @@ function FilePicker({
|
|
|
11494
12012
|
// src/ImportWizard.tsx
|
|
11495
12013
|
var import_react63 = require("react");
|
|
11496
12014
|
var import_react64 = require("@ai-matrx/records/react");
|
|
11497
|
-
var
|
|
12015
|
+
var import_core11 = require("@ai-matrx/records/core");
|
|
11498
12016
|
var import_design_system35 = require("@ai-matrx/design-system");
|
|
11499
12017
|
|
|
11500
12018
|
// src/importRuleCheck.ts
|
|
11501
|
-
var
|
|
12019
|
+
var import_core10 = require("@ai-matrx/records/core");
|
|
11502
12020
|
function checkImportRules(fields, rows, mapping) {
|
|
11503
12021
|
const byKey = new Map(fields.map((f) => [f.key, f]));
|
|
11504
12022
|
const verdicts = [];
|
|
@@ -11516,7 +12034,7 @@ function checkImportRules(fields, rows, mapping) {
|
|
|
11516
12034
|
let why = null;
|
|
11517
12035
|
if ("refusal" in meant) why = meant.refusal;
|
|
11518
12036
|
else {
|
|
11519
|
-
const predicted = (0,
|
|
12037
|
+
const predicted = (0, import_core10.predictValueRefusals)([field2], { [key]: meant.value }).filter(
|
|
11520
12038
|
(p) => p.field_key === void 0 || p.field_key === key
|
|
11521
12039
|
);
|
|
11522
12040
|
if (predicted.length > 0) why = predicted[0].message;
|
|
@@ -11644,7 +12162,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
|
|
|
11644
12162
|
try {
|
|
11645
12163
|
const xlsx = file.name.toLowerCase().endsWith(".xlsx");
|
|
11646
12164
|
const bytes = await file.arrayBuffer();
|
|
11647
|
-
const result = xlsx ? await (0,
|
|
12165
|
+
const result = xlsx ? await (0, import_core11.importXlsx)(bytes) : (0, import_core11.importCsv)(new TextDecoder().decode(bytes));
|
|
11648
12166
|
setFormat(xlsx ? "xlsx" : "csv");
|
|
11649
12167
|
setFileName(file.name);
|
|
11650
12168
|
setFileBytes(file.size);
|
|
@@ -13749,9 +14267,8 @@ function TableSettings({
|
|
|
13749
14267
|
shape.error ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(RefusalNotice, { error: shape.error }) : null,
|
|
13750
14268
|
askingToRemove ? /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex flex-wrap items-center gap-2 rounded border border-destructive/40 px-2 py-1.5", children: [
|
|
13751
14269
|
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("span", { className: "text-destructive", children: [
|
|
13752
|
-
|
|
13753
|
-
|
|
13754
|
-
" takes it off every record of this table. The values stay in each record's history and nothing else is deleted."
|
|
14270
|
+
archiveConfirmSentence(`the field \u201C${fieldName(askingToRemove)}\u201D`),
|
|
14271
|
+
" Its values stay in every record and come back with it."
|
|
13755
14272
|
] }),
|
|
13756
14273
|
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
13757
14274
|
import_design_system41.Button,
|
|
@@ -14005,10 +14522,10 @@ var import_design_system45 = require("@ai-matrx/design-system");
|
|
|
14005
14522
|
// src/RecordForm.tsx
|
|
14006
14523
|
var import_react80 = require("react");
|
|
14007
14524
|
var import_react81 = require("@ai-matrx/records/react");
|
|
14008
|
-
var
|
|
14525
|
+
var import_core13 = require("@ai-matrx/records/core");
|
|
14009
14526
|
|
|
14010
14527
|
// src/writeOnTheWinningVersion.ts
|
|
14011
|
-
var
|
|
14528
|
+
var import_core12 = require("@ai-matrx/records/core");
|
|
14012
14529
|
function sameStoredValue(left, right) {
|
|
14013
14530
|
if (left === right) return true;
|
|
14014
14531
|
const empty = (v) => v === null || v === void 0 || v === "";
|
|
@@ -14030,7 +14547,7 @@ async function updateOnTheWinningVersion(client, recordId, patch, expectedVersio
|
|
|
14030
14547
|
});
|
|
14031
14548
|
const first = await send(expectedVersion);
|
|
14032
14549
|
if (first.ok) return first;
|
|
14033
|
-
const conflict = (0,
|
|
14550
|
+
const conflict = (0, import_core12.asWriteConflict)(first.error, recordId);
|
|
14034
14551
|
if (!conflict) return first;
|
|
14035
14552
|
const untouched = base === "always" || Object.keys(patch).every((key) => sameStoredValue(conflict.contested_fields[key], base[key]));
|
|
14036
14553
|
if (!untouched) return first;
|
|
@@ -14267,7 +14784,7 @@ function RecordForm({
|
|
|
14267
14784
|
return out;
|
|
14268
14785
|
}, [draft]);
|
|
14269
14786
|
const predicted = (0, import_react80.useMemo)(
|
|
14270
|
-
() => fields.data ? (0,
|
|
14787
|
+
() => fields.data ? (0, import_core13.predictWriteRefusals)({ fields: fields.data, document: document2, ...recordType ? { recordType } : {} }) : [],
|
|
14271
14788
|
[fields.data, document2, recordType]
|
|
14272
14789
|
);
|
|
14273
14790
|
if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(RefusalNotice, { error: fields.error, className });
|
|
@@ -14316,7 +14833,7 @@ function RecordForm({
|
|
|
14316
14833
|
setEdit({
|
|
14317
14834
|
saving: false,
|
|
14318
14835
|
error: answered2.ok ? null : answered2.error,
|
|
14319
|
-
conflict: answered2.ok ? null : (0,
|
|
14836
|
+
conflict: answered2.ok ? null : (0, import_core13.asWriteConflict)(answered2.error, recordId)
|
|
14320
14837
|
});
|
|
14321
14838
|
const moved = answered2.ok ? answered2.data : null;
|
|
14322
14839
|
if (moved !== null) {
|
|
@@ -14422,7 +14939,7 @@ function FieldRow({
|
|
|
14422
14939
|
}) {
|
|
14423
14940
|
const id = `field-${field2.id}`;
|
|
14424
14941
|
const storedWords = document2?.[field2.key];
|
|
14425
|
-
const whole = typeof storedWords === "string" ? (0,
|
|
14942
|
+
const whole = typeof storedWords === "string" ? (0, import_core13.wholeValueOf)(document2, field2.key) : null;
|
|
14426
14943
|
return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: (0, import_design_system43.cn)("flex min-w-0 flex-col gap-1", whole && "sm:col-span-2"), children: [
|
|
14427
14944
|
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)(FieldLabel, { field: field2, htmlFor: id, children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(ProvenanceBadge, { document: document2, fieldKey: field2.key }) }),
|
|
14428
14945
|
whole ? (
|
|
@@ -14519,8 +15036,8 @@ function Peek({ tableId, recordId, onClose, className }) {
|
|
|
14519
15036
|
const title = document2 ? recordNameIn(table.data, document2) : "";
|
|
14520
15037
|
const href = host.hrefForRecord && table.data ? host.hrefForRecord({ table: table.data, recordId }) : void 0;
|
|
14521
15038
|
return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("div", { className: (0, import_design_system45.cn)("flex min-w-0 flex-col gap-2 text-xs", className), children: [
|
|
14522
|
-
/* @__PURE__ */ (0, import_jsx_runtime52.
|
|
14523
|
-
|
|
15039
|
+
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)("h3", { className: "break-words text-sm font-medium leading-snug", "data-records-peek-title": "", title, children: record.loading && !document2 ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_design_system45.Skeleton, { className: "h-4 w-40" }) : title }),
|
|
15040
|
+
/* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
14524
15041
|
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
|
|
14525
15042
|
import_react85.AlchemyMenu,
|
|
14526
15043
|
{
|
|
@@ -14558,7 +15075,7 @@ function Peek({ tableId, recordId, onClose, className }) {
|
|
|
14558
15075
|
// it would take and who can give it.
|
|
14559
15076
|
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)("span", { className: "max-w-[18rem] text-[11px] text-muted-foreground", children: may.why("write") })
|
|
14560
15077
|
) : null,
|
|
14561
|
-
onClose ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_design_system45.Button, { size: "sm", variant: "ghost", onClick: onClose, "aria-label": "Close", children: "\xD7" }) : null
|
|
15078
|
+
onClose ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_design_system45.Button, { size: "sm", variant: "ghost", className: "ml-auto", onClick: onClose, "aria-label": "Close", children: "\xD7" }) : null
|
|
14562
15079
|
] }),
|
|
14563
15080
|
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_design_system45.Separator, {}),
|
|
14564
15081
|
talking && host.chat ? /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(import_jsx_runtime52.Fragment, { children: [
|
|
@@ -14689,14 +15206,14 @@ var LOOK_WORDS = {
|
|
|
14689
15206
|
// src/ViewSwitcher.tsx
|
|
14690
15207
|
var import_react88 = require("react");
|
|
14691
15208
|
var import_react89 = require("@ai-matrx/records/react");
|
|
14692
|
-
var
|
|
15209
|
+
var import_records17 = require("@ai-matrx/records");
|
|
14693
15210
|
var import_design_system47 = require("@ai-matrx/design-system");
|
|
14694
15211
|
var import_data_table11 = require("@ai-matrx/design-system/data-table");
|
|
14695
15212
|
|
|
14696
15213
|
// src/Pipeline.tsx
|
|
14697
15214
|
var import_react86 = require("react");
|
|
14698
15215
|
var import_react87 = require("@ai-matrx/records/react");
|
|
14699
|
-
var
|
|
15216
|
+
var import_records16 = require("@ai-matrx/records");
|
|
14700
15217
|
var import_design_system46 = require("@ai-matrx/design-system");
|
|
14701
15218
|
var import_data_table10 = require("@ai-matrx/design-system/data-table");
|
|
14702
15219
|
|
|
@@ -14803,7 +15320,7 @@ function PipelineBoard({
|
|
|
14803
15320
|
alive.current = false;
|
|
14804
15321
|
};
|
|
14805
15322
|
}, []);
|
|
14806
|
-
const filterKey = (0,
|
|
15323
|
+
const filterKey = (0, import_records16.stableFilterKey)(filter);
|
|
14807
15324
|
const reload = (0, import_react86.useCallback)(async () => {
|
|
14808
15325
|
setLoading(true);
|
|
14809
15326
|
const [read, board, held] = await Promise.all([
|
|
@@ -15450,8 +15967,8 @@ function useViewRecords(view, pageSize = 200, filter, viewId) {
|
|
|
15450
15967
|
cancelled = true;
|
|
15451
15968
|
};
|
|
15452
15969
|
}, [client, ruleId, pageSize]);
|
|
15453
|
-
const asked = (0,
|
|
15454
|
-
const fromView = asked !== "{}" && (asked === (0,
|
|
15970
|
+
const asked = (0, import_records17.stableFilterKey)(filter ?? null);
|
|
15971
|
+
const fromView = asked !== "{}" && (asked === (0, import_records17.stableFilterKey)(view.where ?? null) || asked === (0, import_records17.stableFilterKey)(view.filters ?? null));
|
|
15455
15972
|
const tableRows = (0, import_react88.useMemo)(() => table.data?.rows ?? [], [table.data]);
|
|
15456
15973
|
const manual = view.order === "manual" && Boolean(viewId) && !ruleId;
|
|
15457
15974
|
const hand = useHandOrderRows({
|
|
@@ -15501,6 +16018,7 @@ function ViewSwitcher({
|
|
|
15501
16018
|
onLayoutChange,
|
|
15502
16019
|
onViewChange,
|
|
15503
16020
|
onOpenRecord,
|
|
16021
|
+
openRecordId,
|
|
15504
16022
|
onMoved,
|
|
15505
16023
|
onNewRecordForm,
|
|
15506
16024
|
onAskWhoChanged,
|
|
@@ -15579,6 +16097,7 @@ function ViewSwitcher({
|
|
|
15579
16097
|
...local.presentation ?? view.presentation ? { presentation: local.presentation ?? view.presentation } : {},
|
|
15580
16098
|
...onViewChange ? { onPresentationChange: (next) => change({ presentation: next }) } : {},
|
|
15581
16099
|
...onOpenRecord ? { onOpenRecord } : {},
|
|
16100
|
+
...openRecordId ? { openRecordId } : {},
|
|
15582
16101
|
...onNewRecordForm ? { onNewRecordForm } : {},
|
|
15583
16102
|
...onAskWhoChanged ? { onAskWhoChanged } : {},
|
|
15584
16103
|
...viewId ? {
|
|
@@ -16027,7 +16546,7 @@ function Card2({
|
|
|
16027
16546
|
|
|
16028
16547
|
// src/ArchivedView.tsx
|
|
16029
16548
|
var import_react90 = require("react");
|
|
16030
|
-
var
|
|
16549
|
+
var import_records18 = require("@ai-matrx/records");
|
|
16031
16550
|
var import_react91 = require("@ai-matrx/records/react");
|
|
16032
16551
|
var import_design_system48 = require("@ai-matrx/design-system");
|
|
16033
16552
|
var import_jsx_runtime56 = require("react/jsx-runtime");
|
|
@@ -16037,7 +16556,7 @@ function mayNotRestoreLine(why) {
|
|
|
16037
16556
|
return said === "" ? "Bringing a record back needs permission to edit this table. Ask somebody who has it." : said;
|
|
16038
16557
|
}
|
|
16039
16558
|
function archivedRowLine(row, titleKey) {
|
|
16040
|
-
return `${recordName(row.document, titleKey)} \u2014 ${(0,
|
|
16559
|
+
return `${recordName(row.document, titleKey)} \u2014 ${(0, import_records18.archivedByLine)(row)}`;
|
|
16041
16560
|
}
|
|
16042
16561
|
function ArchivedView({
|
|
16043
16562
|
tableId,
|
|
@@ -16051,7 +16570,7 @@ function ArchivedView({
|
|
|
16051
16570
|
const table = (0, import_react91.useTable)(tableId);
|
|
16052
16571
|
const rights = useTableRights(table.data);
|
|
16053
16572
|
const fields = (0, import_react91.useFields)(tableId);
|
|
16054
|
-
const [lane, setLane] = (0, import_react90.useState)(laneFromHost ??
|
|
16573
|
+
const [lane, setLane] = (0, import_react90.useState)(laneFromHost ?? import_records18.DEFAULT_ARCHIVE_LANE);
|
|
16055
16574
|
const [rows, setRows] = (0, import_react90.useState)([]);
|
|
16056
16575
|
const [loading, setLoading] = (0, import_react90.useState)(true);
|
|
16057
16576
|
const [error, setError] = (0, import_react90.useState)(null);
|
|
@@ -16098,15 +16617,15 @@ function ArchivedView({
|
|
|
16098
16617
|
return /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: (0, import_design_system48.cn)("flex min-h-0 flex-col gap-2", className), "data-testid": "archived-view", children: [
|
|
16099
16618
|
/* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
16100
16619
|
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("span", { className: "text-sm font-medium", children: "Archived" }),
|
|
16101
|
-
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("div", { className: "ml-auto flex items-center gap-0.5", role: "group", "aria-label": "Archived by", children:
|
|
16620
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("div", { className: "ml-auto flex items-center gap-0.5", role: "group", "aria-label": "Archived by", children: import_records18.ARCHIVE_LANES.map((option) => /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
|
|
16102
16621
|
import_design_system48.Button,
|
|
16103
16622
|
{
|
|
16104
16623
|
size: "sm",
|
|
16105
16624
|
variant: option === lane ? "secondary" : "ghost",
|
|
16106
16625
|
"aria-pressed": option === lane,
|
|
16107
|
-
title:
|
|
16626
|
+
title: import_records18.ARCHIVE_LANE_MEANS[option],
|
|
16108
16627
|
onClick: () => pick(option),
|
|
16109
|
-
children:
|
|
16628
|
+
children: import_records18.ARCHIVE_LANE_LABEL[option]
|
|
16110
16629
|
},
|
|
16111
16630
|
option
|
|
16112
16631
|
)) })
|
|
@@ -16122,7 +16641,7 @@ function ArchivedView({
|
|
|
16122
16641
|
),
|
|
16123
16642
|
error ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(RefusalNotice, { error }) : null,
|
|
16124
16643
|
loading && !error ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_design_system48.Skeleton, { className: "h-24 w-full" }) : null,
|
|
16125
|
-
!loading && !error && rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("p", { "data-testid": "archived-empty", className: "rounded-md border border-dashed px-3 py-6 text-xs text-muted-foreground", children: (0,
|
|
16644
|
+
!loading && !error && rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("p", { "data-testid": "archived-empty", className: "rounded-md border border-dashed px-3 py-6 text-xs text-muted-foreground", children: (0, import_records18.emptyArchiveLine)(lane) }) : null,
|
|
16126
16645
|
!loading && !error && rows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("ul", { className: "flex flex-col gap-1", "data-testid": "archived-rows", children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(
|
|
16127
16646
|
"li",
|
|
16128
16647
|
{
|
|
@@ -16132,7 +16651,7 @@ function ArchivedView({
|
|
|
16132
16651
|
children: [
|
|
16133
16652
|
/* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
16134
16653
|
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("span", { className: "truncate text-sm", children: recordName(row.document, titleKey) }),
|
|
16135
|
-
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("span", { className: "truncate text-xs text-muted-foreground", "data-testid": "archived-who", children: (0,
|
|
16654
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("span", { className: "truncate text-xs text-muted-foreground", "data-testid": "archived-who", children: (0, import_records18.archivedByLine)(row) }),
|
|
16136
16655
|
refusal2 && refusal2.recordId === row.id ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(RefusalNotice, { error: refusal2.error, className: "mt-2" }) : null
|
|
16137
16656
|
] }),
|
|
16138
16657
|
mayRestore ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
|
|
@@ -16155,7 +16674,7 @@ function ArchivedView({
|
|
|
16155
16674
|
|
|
16156
16675
|
// src/ArchivedDisclosure.tsx
|
|
16157
16676
|
var import_react92 = require("react");
|
|
16158
|
-
var
|
|
16677
|
+
var import_records19 = require("@ai-matrx/records");
|
|
16159
16678
|
var import_react93 = require("@ai-matrx/records/react");
|
|
16160
16679
|
var import_design_system49 = require("@ai-matrx/design-system");
|
|
16161
16680
|
var import_jsx_runtime57 = require("react/jsx-runtime");
|
|
@@ -16255,10 +16774,10 @@ function ArchivedPortals({
|
|
|
16255
16774
|
error ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(RefusalNotice, { error }) : null,
|
|
16256
16775
|
rows === null && !error ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(import_design_system49.Skeleton, { className: "h-16 w-full" }) : null,
|
|
16257
16776
|
said ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("p", { className: "rounded-md border border-border px-3 py-2 text-xs", "data-testid": "archived-portal-said", children: said }) : null,
|
|
16258
|
-
rows !== null && !error && rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("p", { "data-testid": "archived-portals-empty", className: "text-xs text-muted-foreground", children: (0,
|
|
16777
|
+
rows !== null && !error && rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("p", { "data-testid": "archived-portals-empty", className: "text-xs text-muted-foreground", children: (0, import_records19.emptyPortalArchiveLine)() }) : null,
|
|
16259
16778
|
rows !== null && rows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("ul", { className: "flex flex-col gap-1.5", "data-testid": "archived-portals", children: rows.map((portal) => {
|
|
16260
16779
|
const asking = confirming === portal.portal_id;
|
|
16261
|
-
const matches2 = (0,
|
|
16780
|
+
const matches2 = (0, import_records19.portalTitleTypedBack)(typed, portal.title);
|
|
16262
16781
|
return /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(
|
|
16263
16782
|
"li",
|
|
16264
16783
|
{
|
|
@@ -16297,7 +16816,7 @@ function ArchivedPortals({
|
|
|
16297
16816
|
] }),
|
|
16298
16817
|
asking ? /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
|
|
16299
16818
|
/* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
|
|
16300
|
-
(0,
|
|
16819
|
+
(0, import_records19.portalConfirmLine)(portal.title),
|
|
16301
16820
|
" Restoring it puts it back exactly as it was: everyone who was invited can sign in again, and it shows the same Tables."
|
|
16302
16821
|
] }),
|
|
16303
16822
|
/* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5", children: [
|
|
@@ -19255,7 +19774,7 @@ function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
|
|
|
19255
19774
|
// src/PortalsPanel.tsx
|
|
19256
19775
|
var import_react114 = require("react");
|
|
19257
19776
|
var import_react115 = require("@ai-matrx/records/react");
|
|
19258
|
-
var
|
|
19777
|
+
var import_records20 = require("@ai-matrx/records");
|
|
19259
19778
|
var import_design_system60 = require("@ai-matrx/design-system");
|
|
19260
19779
|
|
|
19261
19780
|
// src/BuildOrAsk.tsx
|
|
@@ -19719,7 +20238,7 @@ function PortalsPanel({ tableId, activePortalId, className }) {
|
|
|
19719
20238
|
] }) : null,
|
|
19720
20239
|
activePortalId && !shown3.some((p) => p.portal_id === activePortalId) ? /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("p", { className: "rounded-md border border-amber-600/40 bg-amber-500/5 px-3 py-2 text-xs leading-relaxed text-amber-700 dark:border-amber-400/40 dark:text-amber-300", children: PORTAL_NOT_HERE_LINE }) : null,
|
|
19721
20240
|
/* @__PURE__ */ (0, import_jsx_runtime68.jsx)("ul", { className: "flex flex-col gap-2", children: shown3.map((portal) => {
|
|
19722
|
-
const url = `${origin}${(0,
|
|
20241
|
+
const url = `${origin}${(0, import_records20.portalPath)(portal.slug)}`;
|
|
19723
20242
|
const open = openId === portal.portal_id;
|
|
19724
20243
|
const linked = portal.portal_id === activePortalId;
|
|
19725
20244
|
return /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)(
|
|
@@ -19834,8 +20353,8 @@ function ArchivePortalControl({
|
|
|
19834
20353
|
}
|
|
19835
20354
|
),
|
|
19836
20355
|
asking ? /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("div", { className: "mt-1.5 flex w-full flex-col gap-1.5 rounded-md border border-dashed border-border p-2.5", children: [
|
|
19837
|
-
/* @__PURE__ */ (0, import_jsx_runtime68.jsx)("p", { className: "text-xs text-muted-foreground", "data-testid": "portal-archive-consequence", children: (0,
|
|
19838
|
-
/* @__PURE__ */ (0, import_jsx_runtime68.jsx)("p", { className: "text-xs text-muted-foreground", children: (0,
|
|
20356
|
+
/* @__PURE__ */ (0, import_jsx_runtime68.jsx)("p", { className: "text-xs text-muted-foreground", "data-testid": "portal-archive-consequence", children: (0, import_records20.portalArchiveConsequence)(portal.title, portal.signed_in, portal.invited) }),
|
|
20357
|
+
/* @__PURE__ */ (0, import_jsx_runtime68.jsx)("p", { className: "text-xs text-muted-foreground", children: (0, import_records20.portalConfirmLine)(portal.title) }),
|
|
19839
20358
|
/* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5", children: [
|
|
19840
20359
|
/* @__PURE__ */ (0, import_jsx_runtime68.jsx)(
|
|
19841
20360
|
import_design_system60.BasicInput,
|
|
@@ -19853,7 +20372,7 @@ function ArchivePortalControl({
|
|
|
19853
20372
|
size: "sm",
|
|
19854
20373
|
variant: "destructive",
|
|
19855
20374
|
"data-testid": "portal-archive-do",
|
|
19856
|
-
disabled: busy || !(0,
|
|
20375
|
+
disabled: busy || !(0, import_records20.portalTitleTypedBack)(typed, portal.title),
|
|
19857
20376
|
onClick: () => void go(),
|
|
19858
20377
|
children: busy ? "Archiving\u2026" : "Archive it"
|
|
19859
20378
|
}
|
|
@@ -21012,7 +21531,7 @@ function usePublishGate() {
|
|
|
21012
21531
|
}
|
|
21013
21532
|
|
|
21014
21533
|
// src/FormBuilder.tsx
|
|
21015
|
-
var
|
|
21534
|
+
var import_records21 = require("@ai-matrx/records");
|
|
21016
21535
|
var import_design_system66 = require("@ai-matrx/design-system");
|
|
21017
21536
|
|
|
21018
21537
|
// src/FormRunner.tsx
|
|
@@ -21439,6 +21958,7 @@ function NotifyRuleEditor({ tableId, seed, chosenRuleId, onChoose, className })
|
|
|
21439
21958
|
const [views, setViews] = (0, import_react126.useState)(null);
|
|
21440
21959
|
const [error, setError] = (0, import_react126.useState)(null);
|
|
21441
21960
|
const [busy, setBusy] = (0, import_react126.useState)(false);
|
|
21961
|
+
const [stopping, setStopping] = (0, import_react126.useState)(null);
|
|
21442
21962
|
const load = (0, import_react126.useCallback)(async () => {
|
|
21443
21963
|
const [held, offered] = await Promise.all([
|
|
21444
21964
|
// THE PERSON'S OWN DOOR, not the notifier's. It answers what is addressed
|
|
@@ -21535,6 +22055,7 @@ function NotifyRuleEditor({ tableId, seed, chosenRuleId, onChoose, className })
|
|
|
21535
22055
|
await load();
|
|
21536
22056
|
}
|
|
21537
22057
|
async function unsubscribe(ruleId) {
|
|
22058
|
+
setStopping(null);
|
|
21538
22059
|
setBusy(true);
|
|
21539
22060
|
const deleted = await client.recordDelete({ record_id: ruleId });
|
|
21540
22061
|
setBusy(false);
|
|
@@ -21596,14 +22117,18 @@ function NotifyRuleEditor({ tableId, seed, chosenRuleId, onChoose, className })
|
|
|
21596
22117
|
children: "Use for this form"
|
|
21597
22118
|
}
|
|
21598
22119
|
) : null,
|
|
21599
|
-
rights.write && !(onChoose && held.rule_id === chosenRuleId) ? /* @__PURE__ */ (0, import_jsx_runtime73.
|
|
22120
|
+
rights.write && !(onChoose && held.rule_id === chosenRuleId) && stopping === held.rule_id ? /* @__PURE__ */ (0, import_jsx_runtime73.jsxs)("span", { className: (0, import_design_system65.cn)("flex flex-wrap items-center gap-2", onChoose ? void 0 : "ml-auto"), children: [
|
|
22121
|
+
/* @__PURE__ */ (0, import_jsx_runtime73.jsx)("span", { className: "text-xs text-destructive", children: archiveConfirmSentence("this subscription") }),
|
|
22122
|
+
/* @__PURE__ */ (0, import_jsx_runtime73.jsx)(import_design_system65.Button, { size: "sm", variant: "destructive", disabled: busy, onClick: () => void unsubscribe(held.rule_id), children: "Stop telling me" }),
|
|
22123
|
+
/* @__PURE__ */ (0, import_jsx_runtime73.jsx)(import_design_system65.Button, { size: "sm", variant: "ghost", onClick: () => setStopping(null), children: "Keep it" })
|
|
22124
|
+
] }) : rights.write && !(onChoose && held.rule_id === chosenRuleId) ? /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(
|
|
21600
22125
|
import_design_system65.Button,
|
|
21601
22126
|
{
|
|
21602
22127
|
size: "sm",
|
|
21603
22128
|
variant: "ghost",
|
|
21604
22129
|
className: onChoose ? void 0 : "ml-auto",
|
|
21605
22130
|
disabled: busy,
|
|
21606
|
-
onClick: () =>
|
|
22131
|
+
onClick: () => setStopping(held.rule_id),
|
|
21607
22132
|
children: "Stop telling me"
|
|
21608
22133
|
}
|
|
21609
22134
|
) : null
|
|
@@ -21882,7 +22407,7 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, className }) {
|
|
|
21882
22407
|
/* @__PURE__ */ (0, import_jsx_runtime74.jsx)(
|
|
21883
22408
|
PublishRow,
|
|
21884
22409
|
{
|
|
21885
|
-
url: `${publicOrigin}${(0,
|
|
22410
|
+
url: `${publicOrigin}${(0, import_records21.publicFormPath)(draft.id)}`,
|
|
21886
22411
|
state: draft.state,
|
|
21887
22412
|
mayPublish: rights.structure && !publishGate.blocked,
|
|
21888
22413
|
why: publishGate.why ?? rights.why("structure"),
|
|
@@ -22900,8 +23425,8 @@ function SignBlock({ tableId, recordId, render, className }) {
|
|
|
22900
23425
|
// src/ChartBlock.tsx
|
|
22901
23426
|
var import_react138 = require("react");
|
|
22902
23427
|
var import_recharts = require("recharts");
|
|
22903
|
-
var
|
|
22904
|
-
var
|
|
23428
|
+
var import_records22 = require("@ai-matrx/records");
|
|
23429
|
+
var import_core14 = require("@ai-matrx/records/core");
|
|
22905
23430
|
var import_design_system71 = require("@ai-matrx/design-system");
|
|
22906
23431
|
var import_jsx_runtime79 = require("react/jsx-runtime");
|
|
22907
23432
|
var priorKey = (key) => `prior__${key}`;
|
|
@@ -22958,7 +23483,7 @@ function pretty(n) {
|
|
|
22958
23483
|
return Number.isInteger(n) ? n.toLocaleString() : n.toLocaleString(void 0, { maximumFractionDigits: 2 });
|
|
22959
23484
|
}
|
|
22960
23485
|
function asRefusal2(block) {
|
|
22961
|
-
return (0,
|
|
23486
|
+
return (0, import_core14.mapPgError)(
|
|
22962
23487
|
{
|
|
22963
23488
|
message: block.refused ?? "",
|
|
22964
23489
|
...block.sqlstate ? { code: block.sqlstate } : {}
|
|
@@ -22971,7 +23496,7 @@ function ChartBlock({ block, subject, className }) {
|
|
|
22971
23496
|
const kind = block.kind;
|
|
22972
23497
|
const tableId = block.table_id ?? subject;
|
|
22973
23498
|
const measures = block.measures && block.measures.length > 0 ? block.measures : [{ op: "count" }];
|
|
22974
|
-
const primary = (0,
|
|
23499
|
+
const primary = (0, import_records22.measureKey)(measures[0]);
|
|
22975
23500
|
const windows = kind === "stuck" ? null : block.compare ?? null;
|
|
22976
23501
|
const compared = !!windows;
|
|
22977
23502
|
const grain = block.bucket?.by ?? null;
|
|
@@ -23339,6 +23864,7 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
|
|
|
23339
23864
|
const [boards, setBoards] = (0, import_react139.useState)(null);
|
|
23340
23865
|
const [error, setError] = (0, import_react139.useState)(null);
|
|
23341
23866
|
const [activeId, setActiveId] = (0, import_react139.useState)(activeDashboardId ?? null);
|
|
23867
|
+
const [askingToRemove, setAskingToRemove] = (0, import_react139.useState)(null);
|
|
23342
23868
|
const [run, setRun] = (0, import_react139.useState)(null);
|
|
23343
23869
|
const [running, setRunning] = (0, import_react139.useState)(false);
|
|
23344
23870
|
const [question, setQuestion] = (0, import_react139.useState)("");
|
|
@@ -23449,6 +23975,7 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
|
|
|
23449
23975
|
else setError(again.error);
|
|
23450
23976
|
}, [client, activeId, filter, grain]);
|
|
23451
23977
|
async function remove(target) {
|
|
23978
|
+
setAskingToRemove(null);
|
|
23452
23979
|
const gone = await client.dashboardDelete({ dashboard_id: target.id });
|
|
23453
23980
|
if (!gone.ok) {
|
|
23454
23981
|
setError(gone.error);
|
|
@@ -23537,7 +24064,11 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
|
|
|
23537
24064
|
children: scheduling ? "Done" : "Send on a schedule"
|
|
23538
24065
|
}
|
|
23539
24066
|
),
|
|
23540
|
-
rights.admin ? /* @__PURE__ */ (0, import_jsx_runtime80.
|
|
24067
|
+
rights.admin && askingToRemove === board.id ? /* @__PURE__ */ (0, import_jsx_runtime80.jsxs)(import_jsx_runtime80.Fragment, { children: [
|
|
24068
|
+
/* @__PURE__ */ (0, import_jsx_runtime80.jsx)("span", { className: "text-xs text-destructive", children: archiveConfirmSentence(`the dashboard \u201C${board.name}\u201D`) }),
|
|
24069
|
+
/* @__PURE__ */ (0, import_jsx_runtime80.jsx)(import_design_system72.Button, { size: "sm", variant: "destructive", onClick: () => void remove(board), children: "Remove it" }),
|
|
24070
|
+
/* @__PURE__ */ (0, import_jsx_runtime80.jsx)(import_design_system72.Button, { size: "sm", variant: "ghost", onClick: () => setAskingToRemove(null), children: "Keep it" })
|
|
24071
|
+
] }) : rights.admin ? /* @__PURE__ */ (0, import_jsx_runtime80.jsx)(import_design_system72.Button, { size: "sm", variant: "ghost", onClick: () => setAskingToRemove(board.id), children: "Remove" }) : null
|
|
23541
24072
|
] }) : null
|
|
23542
24073
|
] }),
|
|
23543
24074
|
board && scheduling ? (
|
|
@@ -23897,7 +24428,7 @@ function TargetControl({
|
|
|
23897
24428
|
// src/FormsPanel.tsx
|
|
23898
24429
|
var import_react141 = require("react");
|
|
23899
24430
|
var import_react142 = require("@ai-matrx/records/react");
|
|
23900
|
-
var
|
|
24431
|
+
var import_records23 = require("@ai-matrx/records");
|
|
23901
24432
|
var import_design_system73 = require("@ai-matrx/design-system");
|
|
23902
24433
|
var import_jsx_runtime81 = require("react/jsx-runtime");
|
|
23903
24434
|
var FORM_NOT_HERE_LINE = "The link named a form this table does not have \u2014 it may have been removed, or it belongs to another table. The forms this table does have are listed here.";
|
|
@@ -24001,7 +24532,7 @@ function FormsPanel({ tableId, activeFormId, className }) {
|
|
|
24001
24532
|
}
|
|
24002
24533
|
) : null,
|
|
24003
24534
|
/* @__PURE__ */ (0, import_jsx_runtime81.jsx)("ul", { className: "flex flex-col gap-2", children: forms.map((form) => {
|
|
24004
|
-
const url = `${origin}${(0,
|
|
24535
|
+
const url = `${origin}${(0, import_records23.publicFormPath)(form.form_id)}`;
|
|
24005
24536
|
return /* @__PURE__ */ (0, import_jsx_runtime81.jsxs)(
|
|
24006
24537
|
"li",
|
|
24007
24538
|
{
|
|
@@ -24066,7 +24597,7 @@ function FormsPanel({ tableId, activeFormId, className }) {
|
|
|
24066
24597
|
// src/BookingBuilder.tsx
|
|
24067
24598
|
var import_react143 = require("react");
|
|
24068
24599
|
var import_react144 = require("@ai-matrx/records/react");
|
|
24069
|
-
var
|
|
24600
|
+
var import_records24 = require("@ai-matrx/records");
|
|
24070
24601
|
var import_design_system74 = require("@ai-matrx/design-system");
|
|
24071
24602
|
var import_jsx_runtime82 = require("react/jsx-runtime");
|
|
24072
24603
|
var STORE_ANSWERS_THESE = ["slot", "status", "booked_with"];
|
|
@@ -24210,7 +24741,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
|
|
|
24210
24741
|
if (!rights.structure) {
|
|
24211
24742
|
return /* @__PURE__ */ (0, import_jsx_runtime82.jsx)("p", { className: (0, import_design_system74.cn)("text-xs text-muted-foreground", className), children: rights.why("structure") ?? "Making a booking page needs the admin level on this table, because it lets people with no account take time in your calendar." });
|
|
24212
24743
|
}
|
|
24213
|
-
const url = formId ? `${publicOrigin}${(0,
|
|
24744
|
+
const url = formId ? `${publicOrigin}${(0, import_records24.bookingPath)(formId)}` : null;
|
|
24214
24745
|
const shown3 = offer ?? null;
|
|
24215
24746
|
return (
|
|
24216
24747
|
// THE RAIL IS 384px WIDE AND THE VIEWPORT IS NOT (walk 1, 2026-09-21):
|
|
@@ -24412,7 +24943,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
|
|
|
24412
24943
|
// src/BookingSlots.tsx
|
|
24413
24944
|
var import_react145 = require("react");
|
|
24414
24945
|
var import_react146 = require("@ai-matrx/records/react");
|
|
24415
|
-
var
|
|
24946
|
+
var import_records25 = require("@ai-matrx/records");
|
|
24416
24947
|
var import_design_system75 = require("@ai-matrx/design-system");
|
|
24417
24948
|
var import_jsx_runtime83 = require("react/jsx-runtime");
|
|
24418
24949
|
var BOOKING_NOT_HERE_LINE = "The link named a booking page this table does not have \u2014 it may have been closed for good, or it writes into another table. The booking pages this table does have are listed here.";
|
|
@@ -24529,7 +25060,7 @@ function BookingSlots({ tableId, activeBookingId, className }) {
|
|
|
24529
25060
|
" Open the table the appointments should land in to make one."
|
|
24530
25061
|
] }) : null,
|
|
24531
25062
|
/* @__PURE__ */ (0, import_jsx_runtime83.jsx)("ul", { className: "flex flex-col gap-2", children: pages.map((page) => {
|
|
24532
|
-
const url = `${origin}${(0,
|
|
25063
|
+
const url = `${origin}${(0, import_records25.bookingPath)(page.form_id)}`;
|
|
24533
25064
|
const mayOpen = levels.data?.[page.table_id] === "admin";
|
|
24534
25065
|
const open = page.published_at !== null && page.closed_at === null;
|
|
24535
25066
|
return /* @__PURE__ */ (0, import_jsx_runtime83.jsxs)(
|
|
@@ -24631,7 +25162,7 @@ var import_design_system77 = require("@ai-matrx/design-system");
|
|
|
24631
25162
|
|
|
24632
25163
|
// src/CaptureRun.tsx
|
|
24633
25164
|
var import_react147 = require("react");
|
|
24634
|
-
var
|
|
25165
|
+
var import_records26 = require("@ai-matrx/records");
|
|
24635
25166
|
var import_react148 = require("@ai-matrx/records/react");
|
|
24636
25167
|
var import_design_system76 = require("@ai-matrx/design-system");
|
|
24637
25168
|
var import_jsx_runtime84 = require("react/jsx-runtime");
|
|
@@ -24686,7 +25217,7 @@ function CaptureRun({ sheetId, face: given, className }) {
|
|
|
24686
25217
|
const [sending, setSending] = (0, import_react147.useState)(false);
|
|
24687
25218
|
const queueRef = (0, import_react147.useRef)(null);
|
|
24688
25219
|
(0, import_react147.useEffect)(() => {
|
|
24689
|
-
const q2 = (0,
|
|
25220
|
+
const q2 = (0, import_records26.openCaptureQueue)({
|
|
24690
25221
|
onChange: (c, all) => {
|
|
24691
25222
|
setCounts(c);
|
|
24692
25223
|
setItems(all);
|
|
@@ -24953,7 +25484,7 @@ function CaptureRun({ sheetId, face: given, className }) {
|
|
|
24953
25484
|
value: String(answers[q.field] ?? ""),
|
|
24954
25485
|
onChange: (e) => {
|
|
24955
25486
|
const raw = e.target.value;
|
|
24956
|
-
const answered2 = field2 ? (0,
|
|
25487
|
+
const answered2 = field2 ? (0, import_records26.coerceTypedAnswer)(field2, raw, { label: q.ask ?? (0, import_records26.coercionLabel)(field2) }) : { value: raw };
|
|
24957
25488
|
setAnswers((held) => ({
|
|
24958
25489
|
...held,
|
|
24959
25490
|
[q.field]: raw === "" ? "" : "refusal" in answered2 ? raw : answered2.value
|
|
@@ -25609,8 +26140,8 @@ function recordsDataSource(client, fallbackSchema = "custom") {
|
|
|
25609
26140
|
// src/TablesHome.tsx
|
|
25610
26141
|
var import_react163 = require("react");
|
|
25611
26142
|
var import_react164 = require("@ai-matrx/records/react");
|
|
25612
|
-
var
|
|
25613
|
-
var
|
|
26143
|
+
var import_core16 = require("@ai-matrx/records/core");
|
|
26144
|
+
var import_records28 = require("@ai-matrx/records");
|
|
25614
26145
|
var import_design_system83 = require("@ai-matrx/design-system");
|
|
25615
26146
|
|
|
25616
26147
|
// src/ExampleTables.tsx
|
|
@@ -25704,11 +26235,11 @@ function ExampleTables({
|
|
|
25704
26235
|
}
|
|
25705
26236
|
|
|
25706
26237
|
// src/createTable.ts
|
|
25707
|
-
var
|
|
26238
|
+
var import_core15 = require("@ai-matrx/records/core");
|
|
25708
26239
|
|
|
25709
26240
|
// src/WhereItLives.tsx
|
|
25710
26241
|
var import_react161 = require("react");
|
|
25711
|
-
var
|
|
26242
|
+
var import_records27 = require("@ai-matrx/records");
|
|
25712
26243
|
var import_react162 = require("@ai-matrx/records/react");
|
|
25713
26244
|
var import_design_system82 = require("@ai-matrx/design-system");
|
|
25714
26245
|
var import_jsx_runtime91 = require("react/jsx-runtime");
|
|
@@ -25834,7 +26365,7 @@ function WhereItLives({ tableId, knownOrganizationName, onMoved, variant = "head
|
|
|
25834
26365
|
/* @__PURE__ */ (0, import_jsx_runtime91.jsx)(SharedNameAddress, { to: choosing, all: answer.home.destinations }),
|
|
25835
26366
|
"?"
|
|
25836
26367
|
] }),
|
|
25837
|
-
/* @__PURE__ */ (0, import_jsx_runtime91.jsx)("p", { className: "text-muted-foreground", children: (0,
|
|
26368
|
+
/* @__PURE__ */ (0, import_jsx_runtime91.jsx)("p", { className: "text-muted-foreground", children: (0, import_records27.moveConsequence)(answer.home, choosing) }),
|
|
25838
26369
|
said?.tone === "refused" ? /* @__PURE__ */ (0, import_jsx_runtime91.jsx)(
|
|
25839
26370
|
import_design_system82.ErrorBox,
|
|
25840
26371
|
{
|
|
@@ -25982,8 +26513,8 @@ function TablesHome({ onOpenTable, organizationName, makingOnly = false, classNa
|
|
|
25982
26513
|
alive = false;
|
|
25983
26514
|
};
|
|
25984
26515
|
}, [client]);
|
|
25985
|
-
const listed = facts.phase === "read" ? (0,
|
|
25986
|
-
(0,
|
|
26516
|
+
const listed = facts.phase === "read" ? (0, import_records28.withTablePlacement)(
|
|
26517
|
+
(0, import_core16.withTableFacts)(tables.data ?? [], facts.rows, me),
|
|
25987
26518
|
facts.rows
|
|
25988
26519
|
) : tables.data ?? [];
|
|
25989
26520
|
const [showEverything, setShowEverything] = (0, import_react163.useState)(false);
|
|
@@ -26027,7 +26558,7 @@ function TablesHome({ onOpenTable, organizationName, makingOnly = false, classNa
|
|
|
26027
26558
|
if (!trimmed) return;
|
|
26028
26559
|
setBusy(true);
|
|
26029
26560
|
setError(null);
|
|
26030
|
-
const result = await (0,
|
|
26561
|
+
const result = await (0, import_core15.declareTable)(client, { name: trimmed, slug: (0, import_core15.tokenFor)(trimmed) });
|
|
26031
26562
|
setBusy(false);
|
|
26032
26563
|
if (!result.ok) {
|
|
26033
26564
|
setError(result.error);
|
|
@@ -26187,7 +26718,7 @@ function TablesHome({ onOpenTable, organizationName, makingOnly = false, classNa
|
|
|
26187
26718
|
|
|
26188
26719
|
// src/TablePage.tsx
|
|
26189
26720
|
var import_react168 = require("react");
|
|
26190
|
-
var
|
|
26721
|
+
var import_records29 = require("@ai-matrx/records");
|
|
26191
26722
|
var import_react169 = require("@ai-matrx/records/react");
|
|
26192
26723
|
var import_design_system85 = require("@ai-matrx/design-system");
|
|
26193
26724
|
|
|
@@ -26294,25 +26825,27 @@ function ViewFilter({ tableId, view, onApply, className }) {
|
|
|
26294
26825
|
|
|
26295
26826
|
// src/toolbarFit.ts
|
|
26296
26827
|
var import_react167 = require("react");
|
|
26297
|
-
var
|
|
26828
|
+
var FOLDED_GUESS = { look: 104, chooser: 96, tools: 120 };
|
|
26298
26829
|
function toolbarFold(input) {
|
|
26299
|
-
const { available, needsNow, gap, folded
|
|
26300
|
-
const
|
|
26301
|
-
|
|
26302
|
-
|
|
26303
|
-
|
|
26304
|
-
|
|
26305
|
-
const
|
|
26306
|
-
if (
|
|
26307
|
-
return
|
|
26830
|
+
const { available, needsNow, gap, folded } = input;
|
|
26831
|
+
const pieces = [
|
|
26832
|
+
{ piece: input.look, stage: 1, guess: FOLDED_GUESS.look },
|
|
26833
|
+
{ piece: input.chooser, stage: 2, guess: FOLDED_GUESS.chooser },
|
|
26834
|
+
...input.tools ? [{ piece: input.tools, stage: 3, guess: FOLDED_GUESS.tools }] : []
|
|
26835
|
+
];
|
|
26836
|
+
const widthAt = ({ piece, stage, guess }, fold) => {
|
|
26837
|
+
if (piece.now === 0 && piece.full === null) return 0;
|
|
26838
|
+
if (fold >= stage) return piece.folded ?? (folded >= stage ? piece.now : Math.min(guess, piece.full ?? piece.now));
|
|
26839
|
+
return piece.full ?? piece.now;
|
|
26308
26840
|
};
|
|
26309
26841
|
const withGap = (width) => width > 0 ? width + gap : 0;
|
|
26310
|
-
const base =
|
|
26311
|
-
const
|
|
26842
|
+
const base = pieces.reduce((rest, p) => rest - withGap(p.piece.now), needsNow);
|
|
26843
|
+
const stages = input.tools ? [0, 1, 2, 3] : [0, 1, 2];
|
|
26844
|
+
const needs = stages.map((fold) => pieces.reduce((sum, p) => sum + withGap(widthAt(p, fold)), base));
|
|
26312
26845
|
const fits = needs.findIndex((need) => need <= available + 0.5);
|
|
26313
|
-
if (fits >= 0) return fits;
|
|
26846
|
+
if (fits >= 0) return stages[fits];
|
|
26314
26847
|
const least = Math.min(...needs);
|
|
26315
|
-
return needs.findIndex((need) => need <= least + 0.5);
|
|
26848
|
+
return stages[needs.findIndex((need) => need <= least + 0.5)];
|
|
26316
26849
|
}
|
|
26317
26850
|
function naturalWidth(el) {
|
|
26318
26851
|
const html = el;
|
|
@@ -26345,7 +26878,12 @@ function useToolbarFit(row) {
|
|
|
26345
26878
|
const [fold, setFold] = (0, import_react167.useState)(0);
|
|
26346
26879
|
(0, import_react167.useEffect)(() => {
|
|
26347
26880
|
if (!row) return;
|
|
26348
|
-
const widths = {
|
|
26881
|
+
const widths = {
|
|
26882
|
+
look: { full: null, folded: null },
|
|
26883
|
+
chooser: { full: null, folded: null },
|
|
26884
|
+
tools: { full: null, folded: null }
|
|
26885
|
+
};
|
|
26886
|
+
const stageOf = { look: 1, chooser: 2, tools: 3 };
|
|
26349
26887
|
let current = 0;
|
|
26350
26888
|
let pending = 0;
|
|
26351
26889
|
let stopped = false;
|
|
@@ -26354,23 +26892,22 @@ function useToolbarFit(row) {
|
|
|
26354
26892
|
if (stopped) return;
|
|
26355
26893
|
const available = row.clientWidth;
|
|
26356
26894
|
if (available <= 0) return;
|
|
26357
|
-
const
|
|
26358
|
-
|
|
26359
|
-
|
|
26360
|
-
|
|
26361
|
-
|
|
26362
|
-
|
|
26363
|
-
|
|
26364
|
-
|
|
26895
|
+
const read = (name) => {
|
|
26896
|
+
const el = name === "tools" ? row.querySelector(":scope > [data-toolbar-fit-slot]") : row.querySelector(`:scope > [data-toolbar-fit="${name}"]`);
|
|
26897
|
+
const now = !el ? 0 : name === "tools" ? slotNeeds(el) : naturalWidth(el);
|
|
26898
|
+
const w = widths[name];
|
|
26899
|
+
if (el && now > 0) {
|
|
26900
|
+
if (current >= stageOf[name]) w.folded = now;
|
|
26901
|
+
else w.full = now;
|
|
26902
|
+
}
|
|
26903
|
+
if (!el && current < stageOf[name]) w.full = null;
|
|
26904
|
+
return { now, full: w.full, folded: w.folded };
|
|
26905
|
+
};
|
|
26906
|
+
const look = read("look");
|
|
26907
|
+
const chooser = read("chooser");
|
|
26908
|
+
const tools = read("tools");
|
|
26365
26909
|
const { needs, gap } = rowNeeds(row);
|
|
26366
|
-
const next = toolbarFold({
|
|
26367
|
-
available,
|
|
26368
|
-
needsNow: needs,
|
|
26369
|
-
gap,
|
|
26370
|
-
folded: current,
|
|
26371
|
-
look: { now: lookNow, full: widths.lookFull, folded: widths.lookFolded },
|
|
26372
|
-
chooser: { now: chooserNow, full: widths.chooserFull }
|
|
26373
|
-
});
|
|
26910
|
+
const next = toolbarFold({ available, needsNow: needs, gap, folded: current, look, chooser, tools });
|
|
26374
26911
|
if (next !== current) {
|
|
26375
26912
|
current = next;
|
|
26376
26913
|
setFold(next);
|
|
@@ -26407,6 +26944,12 @@ function useToolbarFit(row) {
|
|
|
26407
26944
|
}, [row]);
|
|
26408
26945
|
return fold;
|
|
26409
26946
|
}
|
|
26947
|
+
function slotNeeds(slot) {
|
|
26948
|
+
const inner = shownChildren(slot);
|
|
26949
|
+
const style = typeof getComputedStyle === "undefined" ? null : getComputedStyle(slot);
|
|
26950
|
+
const gap = style ? parseFloat(style.columnGap || "0") || 0 : 0;
|
|
26951
|
+
return inner.reduce((sum, c) => sum + naturalWidth(c), 0) + gap * Math.max(0, inner.length - 1);
|
|
26952
|
+
}
|
|
26410
26953
|
function clippedEnds(scrollLeft, clientWidth, scrollWidth) {
|
|
26411
26954
|
const start = scrollLeft > 1;
|
|
26412
26955
|
const end = scrollLeft + clientWidth < scrollWidth - 1;
|
|
@@ -26550,7 +27093,7 @@ function TableNotHere({
|
|
|
26550
27093
|
return /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)("div", { className: (0, import_design_system85.cn)("flex flex-col items-start gap-2 rounded-md border border-dashed p-6", className), children: [
|
|
26551
27094
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("p", { className: "text-sm font-medium", children: "This table is archived" }),
|
|
26552
27095
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("p", { className: "max-w-prose text-xs text-muted-foreground", children: TABLE_IS_ARCHIVED }),
|
|
26553
|
-
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("p", { className: "text-xs text-muted-foreground", children: (0,
|
|
27096
|
+
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("p", { className: "text-xs text-muted-foreground", children: (0, import_records29.archivedByLine)(row) }),
|
|
26554
27097
|
refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(RefusalNotice, { error: refusal2 }) : null,
|
|
26555
27098
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsxs)("div", { className: "flex gap-2", children: [
|
|
26556
27099
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
|
|
@@ -26766,6 +27309,8 @@ function TablePage({
|
|
|
26766
27309
|
const face = tableOpening(view, hostLayouts.map((h) => h.id));
|
|
26767
27310
|
const designated = face.kind === "designated";
|
|
26768
27311
|
const chooserInline = !designated && !phone && toolbarFold2 < 2;
|
|
27312
|
+
const chooserCompact = !designated && !phone && toolbarFold2 >= 2;
|
|
27313
|
+
const controlsInSheet = phone && !designated;
|
|
26769
27314
|
(0, import_react168.useEffect)(() => {
|
|
26770
27315
|
if (face.kind !== "designated") return;
|
|
26771
27316
|
if (hostFromLink || opened.layout || layoutChosen) return;
|
|
@@ -26945,6 +27490,50 @@ function TablePage({
|
|
|
26945
27490
|
};
|
|
26946
27491
|
const footer = footerOf(shown3?.presentation);
|
|
26947
27492
|
const setFooter = (next) => void patchView({ presentation: { ...shown3?.presentation ?? {}, footer: next } });
|
|
27493
|
+
const layoutRadio = /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(
|
|
27494
|
+
import_design_system85.DropdownMenuRadioGroup,
|
|
27495
|
+
{
|
|
27496
|
+
value: layoutShowing ?? "",
|
|
27497
|
+
onValueChange: (value) => {
|
|
27498
|
+
if (VIEW_LAYOUTS.includes(value)) pickLayout(value);
|
|
27499
|
+
else pickHostLayout(value);
|
|
27500
|
+
},
|
|
27501
|
+
children: [
|
|
27502
|
+
hostLayouts.map((option) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuRadioItem, { value: option.id, children: option.label }, `host:${option.id}`)),
|
|
27503
|
+
VIEW_LAYOUTS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuRadioItem, { value: option, children: LAYOUT_LABEL[option] }, option))
|
|
27504
|
+
]
|
|
27505
|
+
}
|
|
27506
|
+
);
|
|
27507
|
+
const sheetExtras = controlsInSheet && main === "records" && !hostLayout && shownLayout === "grid" ? /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_jsx_runtime94.Fragment, { children: [
|
|
27508
|
+
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("div", { className: "flex items-center gap-2 text-xs", "data-table-sheet-filter": "", "data-matrx-table-tools-keep-open": "", children: /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(ViewFilter, { tableId, view: shown3, onApply: (where) => void patchView({ where }) }) }),
|
|
27509
|
+
/* @__PURE__ */ (0, import_jsx_runtime94.jsxs)("div", { role: "group", "aria-label": "Layout", className: "flex flex-wrap gap-1 pt-1", "data-table-sheet-layout": "", children: [
|
|
27510
|
+
VIEW_LAYOUTS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
|
|
27511
|
+
import_design_system85.Button,
|
|
27512
|
+
{
|
|
27513
|
+
size: "sm",
|
|
27514
|
+
variant: layoutShowing === option ? "secondary" : "ghost",
|
|
27515
|
+
"aria-pressed": layoutShowing === option,
|
|
27516
|
+
className: "h-8 !w-auto",
|
|
27517
|
+
onClick: () => pickLayout(option),
|
|
27518
|
+
children: LAYOUT_LABEL[option]
|
|
27519
|
+
},
|
|
27520
|
+
option
|
|
27521
|
+
)),
|
|
27522
|
+
hostLayouts.map((option) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
|
|
27523
|
+
import_design_system85.Button,
|
|
27524
|
+
{
|
|
27525
|
+
size: "sm",
|
|
27526
|
+
variant: layoutShowing === option.id ? "secondary" : "ghost",
|
|
27527
|
+
"aria-pressed": layoutShowing === option.id,
|
|
27528
|
+
className: "h-8 !w-auto",
|
|
27529
|
+
onClick: () => pickHostLayout(option.id),
|
|
27530
|
+
children: option.label
|
|
27531
|
+
},
|
|
27532
|
+
`host:${option.id}`
|
|
27533
|
+
))
|
|
27534
|
+
] }),
|
|
27535
|
+
rights.share && canShare ? /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", variant: "ghost", className: "h-8", onClick: () => setShareAsked((n) => n + 1), "data-table-sheet-share": "", children: "Share\u2026" }) : null
|
|
27536
|
+
] }) : void 0;
|
|
26948
27537
|
const pageActions = /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_jsx_runtime94.Fragment, { children: [
|
|
26949
27538
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
|
|
26950
27539
|
ShareControl,
|
|
@@ -26966,22 +27555,9 @@ function TablePage({
|
|
|
26966
27555
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("circle", { cx: "19", cy: "12", r: "1.75" })
|
|
26967
27556
|
] }) }) }),
|
|
26968
27557
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_design_system85.DropdownMenuContent, { align: "end", className: "w-56", "data-table-menu-content": "", children: [
|
|
26969
|
-
!chooserInline ? /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_jsx_runtime94.Fragment, { children: [
|
|
27558
|
+
!chooserInline && !chooserCompact ? /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_jsx_runtime94.Fragment, { children: [
|
|
26970
27559
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuLabel, { className: "text-xs text-muted-foreground", children: "Layout" }),
|
|
26971
|
-
|
|
26972
|
-
import_design_system85.DropdownMenuRadioGroup,
|
|
26973
|
-
{
|
|
26974
|
-
value: layoutShowing ?? "",
|
|
26975
|
-
onValueChange: (value) => {
|
|
26976
|
-
if (VIEW_LAYOUTS.includes(value)) pickLayout(value);
|
|
26977
|
-
else pickHostLayout(value);
|
|
26978
|
-
},
|
|
26979
|
-
children: [
|
|
26980
|
-
hostLayouts.map((option) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuRadioItem, { value: option.id, children: option.label }, `host:${option.id}`)),
|
|
26981
|
-
VIEW_LAYOUTS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuRadioItem, { value: option, children: LAYOUT_LABEL[option] }, option))
|
|
26982
|
-
]
|
|
26983
|
-
}
|
|
26984
|
-
),
|
|
27560
|
+
layoutRadio,
|
|
26985
27561
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuSeparator, {})
|
|
26986
27562
|
] }) : null,
|
|
26987
27563
|
MENU.filter((entry) => entry.shown(rights)).map((entry, index, shownEntries) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
|
|
@@ -27101,11 +27677,26 @@ function TablePage({
|
|
|
27101
27677
|
`host:${option.id}`
|
|
27102
27678
|
))
|
|
27103
27679
|
] }) : null,
|
|
27104
|
-
|
|
27680
|
+
chooserCompact ? (
|
|
27681
|
+
/* THE ROW HAD NO ROOM (lane C, review 2026-09-26 D6: the switcher vanished at 1280 px and
|
|
27682
|
+
with the record panel open): the chooser folds to ONE button naming the layout that is
|
|
27683
|
+
showing — it never leaves the row. */
|
|
27684
|
+
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("span", { className: "flex shrink-0 items-center", "data-toolbar-fit": "chooser", "data-layout-chooser-compact": "", children: /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_design_system85.DropdownMenu, { children: [
|
|
27685
|
+
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_design_system85.Button, { size: "sm", variant: "outline", className: "h-8 gap-1 px-2 text-xs", "aria-label": `Layout: ${layoutShowingLabel ?? "none"}`, children: [
|
|
27686
|
+
layoutShowingLabel ?? "Layout",
|
|
27687
|
+
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("svg", { viewBox: "0 0 24 24", className: "h-3.5 w-3.5 opacity-70", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime94.jsx)("path", { d: "m6 9 6 6 6-6" }) })
|
|
27688
|
+
] }) }),
|
|
27689
|
+
/* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_design_system85.DropdownMenuContent, { align: "start", className: "w-44", "data-layout-chooser-menu": "", children: [
|
|
27690
|
+
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuLabel, { className: "text-xs text-muted-foreground", children: "Layout" }),
|
|
27691
|
+
layoutRadio
|
|
27692
|
+
] })
|
|
27693
|
+
] }) })
|
|
27694
|
+
) : null,
|
|
27695
|
+
main === "records" && !hostLayout && !(controlsInSheet && shownLayout === "grid") ? /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(ViewFilter, { tableId, view: shown3, onApply: (where) => void patchView({ where }) }) : null,
|
|
27105
27696
|
lookShowing && main === "records" && toolbarFold2 >= 1 && !namingLook ? (
|
|
27106
27697
|
/* FOLDED: the row did not fit, so the three doors are one menu (same doors, same words). */
|
|
27107
27698
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("span", { className: "flex shrink-0 items-center", "data-view-look": "", "data-view-look-folded": "", "data-toolbar-fit": "look", children: /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_design_system85.DropdownMenu, { children: [
|
|
27108
|
-
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_design_system85.Button, { size: "sm", variant: "outline", className: "h-
|
|
27699
|
+
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_design_system85.Button, { size: "sm", variant: "outline", className: "h-8 gap-1.5 px-2 text-xs", title: LOOK_WORDS.yours, "data-view-look-menu": "", children: [
|
|
27109
27700
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("span", { "aria-hidden": "true", className: "h-1.5 w-1.5 shrink-0 rounded-full bg-primary" }),
|
|
27110
27701
|
LOOK_WORDS.folded,
|
|
27111
27702
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsxs)("span", { className: "sr-only", children: [
|
|
@@ -27123,7 +27714,7 @@ function TablePage({
|
|
|
27123
27714
|
) : lookShowing && main === "records" ? /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)("span", { className: "flex shrink-0 items-center gap-1", "data-view-look": "", "data-toolbar-fit": "look", title: LOOK_WORDS.yours, children: [
|
|
27124
27715
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("span", { className: "sr-only", children: LOOK_WORDS.yours }),
|
|
27125
27716
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("span", { "aria-hidden": "true", className: "h-1.5 w-1.5 shrink-0 rounded-full bg-primary" }),
|
|
27126
|
-
lookSavable ? /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", variant: "outline", className: "h-
|
|
27717
|
+
lookSavable ? /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", variant: "outline", className: "h-8 px-2 text-xs", onClick: () => void saveLookToView(), "data-view-look-save": "", children: LOOK_WORDS.saveToView }) : null,
|
|
27127
27718
|
rights.write ? namingLook ? /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(
|
|
27128
27719
|
"form",
|
|
27129
27720
|
{
|
|
@@ -27140,15 +27731,15 @@ function TablePage({
|
|
|
27140
27731
|
"aria-label": LOOK_WORDS.newName,
|
|
27141
27732
|
value: lookName,
|
|
27142
27733
|
onChange: (e) => setLookName(e.target.value),
|
|
27143
|
-
className: "h-
|
|
27734
|
+
className: "h-8 w-36 text-xs",
|
|
27144
27735
|
placeholder: "View name"
|
|
27145
27736
|
}
|
|
27146
27737
|
),
|
|
27147
|
-
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", type: "submit", className: "h-
|
|
27738
|
+
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", type: "submit", className: "h-8 px-2 text-xs", disabled: lookName.trim() === "", children: "Save" })
|
|
27148
27739
|
]
|
|
27149
27740
|
}
|
|
27150
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", variant: "ghost", className: "h-
|
|
27151
|
-
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", variant: "ghost", className: "h-
|
|
27741
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", variant: "ghost", className: "h-8 px-2 text-xs", onClick: () => setNamingLook(true), "data-view-look-save-new": "", children: LOOK_WORDS.saveAsNew }) : null,
|
|
27742
|
+
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", variant: "ghost", className: "h-8 px-2 text-xs", onClick: () => void resetLook(), "data-view-look-reset": "", children: LOOK_WORDS.reset })
|
|
27152
27743
|
] }) : null,
|
|
27153
27744
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
|
|
27154
27745
|
"div",
|
|
@@ -27229,7 +27820,7 @@ function TablePage({
|
|
|
27229
27820
|
}
|
|
27230
27821
|
}
|
|
27231
27822
|
)
|
|
27232
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(TableToolbarSlotContext.Provider, { value: toolbarSlot, children: /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(
|
|
27823
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(TableToolbarSlotContext.Provider, { value: toolbarSlot, children: /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(TableToolbarFoldContext.Provider, { value: toolbarFold2, children: /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(TableToolbarSheetExtrasContext.Provider, { value: sheetExtras, children: /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(
|
|
27233
27824
|
"div",
|
|
27234
27825
|
{
|
|
27235
27826
|
className: (0, import_design_system85.cn)(
|
|
@@ -27291,6 +27882,7 @@ function TablePage({
|
|
|
27291
27882
|
setOpenRecord(recordId);
|
|
27292
27883
|
setRail("record");
|
|
27293
27884
|
},
|
|
27885
|
+
openRecordId: rail === "record" ? openRecord : null,
|
|
27294
27886
|
onAskWhoChanged: (fieldKey, recordId) => {
|
|
27295
27887
|
setAsking({ fieldKey, recordId });
|
|
27296
27888
|
setRail("who-changed");
|
|
@@ -27300,7 +27892,7 @@ function TablePage({
|
|
|
27300
27892
|
view || faceUnknown ? null : /* @__PURE__ */ (0, import_jsx_runtime94.jsx)("p", { className: "text-xs text-muted-foreground", children: VIEW_NOT_SAVED_YET })
|
|
27301
27893
|
]
|
|
27302
27894
|
}
|
|
27303
|
-
) })
|
|
27895
|
+
) }) }) })
|
|
27304
27896
|
] }),
|
|
27305
27897
|
rail === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(RailFrame, { phone, title: RAIL_TITLE[rail], onClose: () => setRail("none"), children: [
|
|
27306
27898
|
rail === "settings" ? /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
|