@ai-matrx/records-ui 0.90.2 → 0.92.1
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 +51 -0
- package/dist/index.cjs +1409 -826
- 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 +2594 -2007
- 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,124 @@ 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-label": fieldName(field2),
|
|
2302
|
+
"aria-invalid": judged !== null && "refusal" in judged ? true : void 0,
|
|
2303
|
+
onChange: (e) => onChange({ typedDraft: true, text: e.target.value, judged: judgeTyped(field2, e.target.value, needsChoices ? choices : void 0) })
|
|
2304
|
+
}
|
|
2305
|
+
),
|
|
2306
|
+
judged === null ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "text-[10px] text-muted-foreground", children: [
|
|
2307
|
+
"Reading ",
|
|
2308
|
+
fieldName(field2),
|
|
2309
|
+
"'s choices\u2026"
|
|
2310
|
+
] }) : "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: [
|
|
2311
|
+
"Reads as",
|
|
2312
|
+
" ",
|
|
2313
|
+
needsChoices ? String(
|
|
2314
|
+
(Array.isArray(judged.value) ? judged.value : [judged.value]).map((k) => {
|
|
2315
|
+
const option = choices?.find((o) => (0, import_records5.optionKey)(o) === k);
|
|
2316
|
+
return option ? recordName(option.data, null, String(k)) : String(k);
|
|
2317
|
+
}).join(", ")
|
|
2318
|
+
) : (() => {
|
|
2319
|
+
const shown3 = format ?? (kind === "datetime" ? { id: carriesTimeOfDay(field2, judged.value) ? "datetime" : "date" } : void 0);
|
|
2320
|
+
return shown3 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(FormattedValue, { value: judged.value, format: shown3 }) : String(judged.value);
|
|
2321
|
+
})()
|
|
2322
|
+
] })
|
|
2323
|
+
] });
|
|
2324
|
+
}
|
|
2325
|
+
function FieldControl({ field: field2, value, onChange, disabled, id, autoOpen, onClose }) {
|
|
2190
2326
|
const kind = editorKindFor(field2);
|
|
2191
2327
|
const inputId = id ?? `field-${field2.key}`;
|
|
2328
|
+
if (isTypedDraft(value)) return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(TypedTextControl, { field: field2, value, onChange, disabled, id: inputId });
|
|
2192
2329
|
const format = formatForField(field2);
|
|
2193
2330
|
if (format && !FORMAT_BLIND_KINDS.has(String(kind))) {
|
|
2194
2331
|
const own2 = formatControl({ format, value, onChange, disabled, id: inputId, label: fieldName(field2) });
|
|
@@ -2261,9 +2398,9 @@ function FieldControl({ field: field2, value, onChange, disabled, id }) {
|
|
|
2261
2398
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(CheckboxControl, { field: field2, value, onChange, disabled, id: inputId });
|
|
2262
2399
|
case "select":
|
|
2263
2400
|
case "multi_select":
|
|
2264
|
-
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(OptionControl, { field: field2, value, onChange, disabled, id: inputId });
|
|
2401
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(OptionControl, { field: field2, value, onChange, disabled, id: inputId, autoOpen, onClose });
|
|
2265
2402
|
case "member":
|
|
2266
|
-
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PersonPicker, { field: field2, value, onChange, disabled, id: inputId });
|
|
2403
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PersonPicker, { field: field2, value, onChange, disabled, id: inputId, autoOpen, onClose });
|
|
2267
2404
|
case "entity_reference":
|
|
2268
2405
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(EntityReferenceControl, { field: field2, value, onChange, disabled, id: inputId });
|
|
2269
2406
|
case "relation":
|
|
@@ -2383,7 +2520,7 @@ function inTheStoresOrder(options) {
|
|
|
2383
2520
|
const made = (o) => String(o.created_at ?? "");
|
|
2384
2521
|
return [...options].sort((a, b) => position(a) - position(b) || made(a).localeCompare(made(b)));
|
|
2385
2522
|
}
|
|
2386
|
-
function OptionControl({ field: field2, value, onChange, disabled, id }) {
|
|
2523
|
+
function OptionControl({ field: field2, value, onChange, disabled, id, autoOpen, onClose }) {
|
|
2387
2524
|
const client = (0, import_react11.useOptionalRecordsClient)();
|
|
2388
2525
|
const [options, setOptions] = (0, import_react10.useState)(null);
|
|
2389
2526
|
const [refusal2, setRefusal] = (0, import_react10.useState)(null);
|
|
@@ -2426,7 +2563,7 @@ function OptionControl({ field: field2, value, onChange, disabled, id }) {
|
|
|
2426
2563
|
if (field2.multi) {
|
|
2427
2564
|
const chosen = options.filter((option) => (0, import_records5.choiceValuesOf)(value).some((v) => (0, import_records5.isTheChosen)(option, v)));
|
|
2428
2565
|
const picked = new Set(chosen.map((option) => (0, import_records5.optionKey)(option)));
|
|
2429
|
-
|
|
2566
|
+
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
2567
|
import_design_system8.Button,
|
|
2431
2568
|
{
|
|
2432
2569
|
type: "button",
|
|
@@ -2443,6 +2580,20 @@ function OptionControl({ field: field2, value, onChange, disabled, id }) {
|
|
|
2443
2580
|
},
|
|
2444
2581
|
option.id
|
|
2445
2582
|
)) });
|
|
2583
|
+
if (!autoOpen) return toggles;
|
|
2584
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
2585
|
+
import_design_system8.Popover,
|
|
2586
|
+
{
|
|
2587
|
+
defaultOpen: true,
|
|
2588
|
+
onOpenChange: (open) => {
|
|
2589
|
+
if (!open) onClose?.();
|
|
2590
|
+
},
|
|
2591
|
+
children: [
|
|
2592
|
+
/* @__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(", ") }) }),
|
|
2593
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_design_system8.PopoverContent, { sizing: "content", className: "max-w-[20rem] p-2", align: "start", children: toggles })
|
|
2594
|
+
]
|
|
2595
|
+
}
|
|
2596
|
+
);
|
|
2446
2597
|
}
|
|
2447
2598
|
const current = options.find((option) => (0, import_records5.isTheChosen)(option, value));
|
|
2448
2599
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
@@ -2451,6 +2602,13 @@ function OptionControl({ field: field2, value, onChange, disabled, id }) {
|
|
|
2451
2602
|
value: current ? (0, import_records5.optionKey)(current) : "",
|
|
2452
2603
|
disabled: disabled ?? false,
|
|
2453
2604
|
onValueChange: (next) => onChange(next === "" ? null : next),
|
|
2605
|
+
...autoOpen ? {
|
|
2606
|
+
// IN A GRID CELL the list opens at once and a pick is the commit (Airtable).
|
|
2607
|
+
defaultOpen: true,
|
|
2608
|
+
onOpenChange: (open) => {
|
|
2609
|
+
if (!open) onClose?.();
|
|
2610
|
+
}
|
|
2611
|
+
} : {},
|
|
2454
2612
|
children: [
|
|
2455
2613
|
/* @__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
2614
|
/* @__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 +2728,20 @@ async function personRecordForMember(client, personTableId, people, member) {
|
|
|
2570
2728
|
});
|
|
2571
2729
|
return written;
|
|
2572
2730
|
}
|
|
2573
|
-
function PersonPicker({ field: field2, value, onChange, disabled, id, className }) {
|
|
2731
|
+
function PersonPicker({ field: field2, value, onChange, disabled, id, className, autoOpen, onClose }) {
|
|
2574
2732
|
const client = (0, import_react13.useOptionalRecordsClient)();
|
|
2575
2733
|
const host = useRecordsUi();
|
|
2576
2734
|
const [members, setMembers] = (0, import_react12.useState)(null);
|
|
2577
2735
|
const [people, setPeople] = (0, import_react12.useState)([]);
|
|
2578
2736
|
const [error, setError] = (0, import_react12.useState)(null);
|
|
2579
2737
|
const [search, setSearch] = (0, import_react12.useState)("");
|
|
2580
|
-
const [open,
|
|
2738
|
+
const [open, setOpenState] = (0, import_react12.useState)(autoOpen === true);
|
|
2739
|
+
const onCloseRef = (0, import_react12.useRef)(onClose);
|
|
2740
|
+
onCloseRef.current = onClose;
|
|
2741
|
+
const setOpen = (0, import_react12.useCallback)((next) => {
|
|
2742
|
+
setOpenState(next);
|
|
2743
|
+
if (!next) onCloseRef.current?.();
|
|
2744
|
+
}, []);
|
|
2581
2745
|
const [busy, setBusy] = (0, import_react12.useState)(false);
|
|
2582
2746
|
const membersPort = host.members;
|
|
2583
2747
|
const personTable = field2.relation_target;
|
|
@@ -2726,6 +2890,7 @@ var import_react19 = require("react");
|
|
|
2726
2890
|
var import_core3 = require("@ai-matrx/records/core");
|
|
2727
2891
|
var import_react20 = require("@ai-matrx/records/react");
|
|
2728
2892
|
var import_design_system12 = require("@ai-matrx/design-system");
|
|
2893
|
+
var import_records6 = require("@ai-matrx/records");
|
|
2729
2894
|
|
|
2730
2895
|
// src/whoChangedSource.tsx
|
|
2731
2896
|
var import_react14 = require("react");
|
|
@@ -2843,7 +3008,7 @@ function useWhoChanged(fieldKey, recordId) {
|
|
|
2843
3008
|
|
|
2844
3009
|
// src/values.tsx
|
|
2845
3010
|
var import_data_table4 = require("@ai-matrx/design-system/data-table");
|
|
2846
|
-
var
|
|
3011
|
+
var import_field_formats6 = require("@ai-matrx/design-system/field-formats");
|
|
2847
3012
|
|
|
2848
3013
|
// src/withheld.tsx
|
|
2849
3014
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
@@ -3373,32 +3538,61 @@ function renderValue(field2, value, document2, labels, stillWaitingFor) {
|
|
|
3373
3538
|
if (whole) return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(WholeValueCell, { words: value, pointer: whole });
|
|
3374
3539
|
const kind = editorKindFor(field2);
|
|
3375
3540
|
const chosen = kind === "select" || kind === "multi_select";
|
|
3376
|
-
const
|
|
3377
|
-
if (!chosen) return void 0;
|
|
3541
|
+
const chipOf = (item) => {
|
|
3378
3542
|
const color = storedChoiceColor(field2, item) ?? storedChoiceColor(field2, scalarText(field2, item, labels));
|
|
3379
|
-
return color ? import_data_table4.
|
|
3543
|
+
return color ? { className: import_data_table4.CHOICE_CHIP_CLASS[color], tinted: true } : { className: import_data_table4.NEUTRAL_CHIP_CLASS, tinted: false };
|
|
3380
3544
|
};
|
|
3381
|
-
if (
|
|
3382
|
-
|
|
3383
|
-
|
|
3545
|
+
if (chosen) {
|
|
3546
|
+
const items = Array.isArray(value) ? value : [value];
|
|
3547
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "flex flex-wrap gap-1", "data-records-choice-chips": "", children: items.map((item, i) => {
|
|
3548
|
+
const chip = chipOf(item);
|
|
3384
3549
|
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3385
3550
|
import_design_system11.Badge,
|
|
3386
3551
|
{
|
|
3387
|
-
variant: "
|
|
3388
|
-
className: (0, import_design_system11.cn)("text-[11px]",
|
|
3389
|
-
|
|
3390
|
-
|
|
3552
|
+
variant: "outline",
|
|
3553
|
+
className: (0, import_design_system11.cn)("text-[11px]", chip.className),
|
|
3554
|
+
"data-records-choice-chip": "",
|
|
3555
|
+
...chip.tinted ? { "data-records-choice-tint": "" } : {},
|
|
3556
|
+
children: scalarText(field2, item, labels, Array.isArray(value) ? void 0 : stillWaitingFor)
|
|
3391
3557
|
},
|
|
3392
3558
|
`${String(item)}-${i}`
|
|
3393
3559
|
);
|
|
3394
3560
|
}) });
|
|
3395
3561
|
}
|
|
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) });
|
|
3562
|
+
if (Array.isArray(value)) {
|
|
3563
|
+
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
3564
|
}
|
|
3400
3565
|
if (kind === "url" && typeof value === "string") {
|
|
3401
|
-
|
|
3566
|
+
const href = value.includes("://") ? value : `https://${value}`;
|
|
3567
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
3568
|
+
"a",
|
|
3569
|
+
{
|
|
3570
|
+
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",
|
|
3571
|
+
href,
|
|
3572
|
+
target: "_blank",
|
|
3573
|
+
rel: "noreferrer",
|
|
3574
|
+
title: value,
|
|
3575
|
+
"data-records-link-chip": "",
|
|
3576
|
+
onClick: (event) => event.stopPropagation(),
|
|
3577
|
+
children: [
|
|
3578
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { "aria-hidden": "true", className: "text-muted-foreground", children: "\u2197" }),
|
|
3579
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "truncate", children: linkChipText(value) })
|
|
3580
|
+
]
|
|
3581
|
+
}
|
|
3582
|
+
);
|
|
3583
|
+
}
|
|
3584
|
+
if (kind === "phone" && typeof value === "string" && value.trim() !== "") {
|
|
3585
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3586
|
+
"a",
|
|
3587
|
+
{
|
|
3588
|
+
className: "truncate tabular-nums text-primary underline-offset-2 hover:underline",
|
|
3589
|
+
href: `tel:${value.replace(/[^\d+]/g, "")}`,
|
|
3590
|
+
title: value,
|
|
3591
|
+
"data-records-phone": "",
|
|
3592
|
+
onClick: (event) => event.stopPropagation(),
|
|
3593
|
+
children: formatPhone(value)
|
|
3594
|
+
}
|
|
3595
|
+
);
|
|
3402
3596
|
}
|
|
3403
3597
|
if (kind === "email" && typeof value === "string") {
|
|
3404
3598
|
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("a", { className: "truncate text-primary underline-offset-2 hover:underline", href: `mailto:${value}`, children: value });
|
|
@@ -3417,7 +3611,7 @@ function scalarText(field2, value, labels, stillWaitingFor) {
|
|
|
3417
3611
|
}
|
|
3418
3612
|
const shownAs = shownAsOf(field2);
|
|
3419
3613
|
if (shownAs) {
|
|
3420
|
-
const said = (0,
|
|
3614
|
+
const said = (0, import_field_formats6.formatFieldValue)(value, shownAs);
|
|
3421
3615
|
if (said.ok && !said.empty) return said.text;
|
|
3422
3616
|
}
|
|
3423
3617
|
if (typeof value === "number") {
|
|
@@ -3478,7 +3672,6 @@ function cardWords(record, field2, labels) {
|
|
|
3478
3672
|
|
|
3479
3673
|
// src/gridEditing.tsx
|
|
3480
3674
|
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
3675
|
function asksWhoChanged(event) {
|
|
3483
3676
|
return event.altKey && event.shiftKey && !event.metaKey && !event.ctrlKey && (event.code === "KeyH" || event.key.toLowerCase() === "h");
|
|
3484
3677
|
}
|
|
@@ -3515,7 +3708,9 @@ function rememberVersion(cache, recordId, version) {
|
|
|
3515
3708
|
const held = cache.get(recordId);
|
|
3516
3709
|
if (held === void 0 || version > held) cache.set(recordId, version);
|
|
3517
3710
|
}
|
|
3518
|
-
function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow, onWritten }) {
|
|
3711
|
+
function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow, onWritten, onEnded }) {
|
|
3712
|
+
const endedRef = (0, import_react19.useRef)(onEnded);
|
|
3713
|
+
endedRef.current = onEnded;
|
|
3519
3714
|
const client = (0, import_react20.useRecordsClient)();
|
|
3520
3715
|
const [editing, setEditing] = (0, import_react19.useState)(null);
|
|
3521
3716
|
const [draft, setDraft] = (0, import_react19.useState)(null);
|
|
@@ -3561,6 +3756,24 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow,
|
|
|
3561
3756
|
(rowId, key) => refusals[cellKey(rowId, key)] ?? null,
|
|
3562
3757
|
[refusals]
|
|
3563
3758
|
);
|
|
3759
|
+
const settling = (0, import_react19.useRef)(/* @__PURE__ */ new Map());
|
|
3760
|
+
(0, import_react19.useEffect)(() => {
|
|
3761
|
+
if (settling.current.size === 0) return;
|
|
3762
|
+
const released = [];
|
|
3763
|
+
for (const [id, before] of settling.current) {
|
|
3764
|
+
const [rowId, key] = id.split("::");
|
|
3765
|
+
const row = rows.find((r) => r.id === rowId);
|
|
3766
|
+
if (!row) continue;
|
|
3767
|
+
if (!sameValue(row.document?.[key], before)) released.push(id);
|
|
3768
|
+
}
|
|
3769
|
+
if (released.length === 0) return;
|
|
3770
|
+
for (const id of released) settling.current.delete(id);
|
|
3771
|
+
setOptimistic((held) => {
|
|
3772
|
+
const next = { ...held };
|
|
3773
|
+
for (const id of released) delete next[id];
|
|
3774
|
+
return next;
|
|
3775
|
+
});
|
|
3776
|
+
}, [rows]);
|
|
3564
3777
|
const shownValue2 = (0, import_react19.useCallback)(
|
|
3565
3778
|
(rowId, key) => {
|
|
3566
3779
|
const held = optimistic[cellKey(rowId, key)];
|
|
@@ -3579,9 +3792,8 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow,
|
|
|
3579
3792
|
if (editing && editing.rowId === address.rowId && editing.key === address.key) return;
|
|
3580
3793
|
const current = shownValue2(address.rowId, address.key);
|
|
3581
3794
|
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;
|
|
3795
|
+
const seeded = seedField ? draftFromTyping(seedField, seed) : void 0;
|
|
3796
|
+
if (seedField && seeded === void 0) return;
|
|
3585
3797
|
if (seeded !== void 0) {
|
|
3586
3798
|
startedFrom.current = current;
|
|
3587
3799
|
startedStored.current = current;
|
|
@@ -3632,7 +3844,11 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow,
|
|
|
3632
3844
|
},
|
|
3633
3845
|
[writable, client, editing, shownValue2, optimistic, rows, host, fields]
|
|
3634
3846
|
);
|
|
3635
|
-
const
|
|
3847
|
+
const openRef = (0, import_react19.useRef)(null);
|
|
3848
|
+
openRef.current = editing;
|
|
3849
|
+
const cancel = (0, import_react19.useCallback)((via = "key") => {
|
|
3850
|
+
const open = openRef.current;
|
|
3851
|
+
if (open && via !== "blur") endedRef.current?.("stay", open, via === "key" ? "keyboard" : "pointer");
|
|
3636
3852
|
setEditing(null);
|
|
3637
3853
|
setDraft(null);
|
|
3638
3854
|
typed.current = null;
|
|
@@ -3674,6 +3890,7 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow,
|
|
|
3674
3890
|
});
|
|
3675
3891
|
if (written.ok) {
|
|
3676
3892
|
rememberVersion(versions.current, rowId, written.data);
|
|
3893
|
+
settling.current.set(id, base);
|
|
3677
3894
|
writtenRef.current?.({ rowId, key, before: base, after: value });
|
|
3678
3895
|
reload();
|
|
3679
3896
|
return;
|
|
@@ -3721,11 +3938,25 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow,
|
|
|
3721
3938
|
[client, reload]
|
|
3722
3939
|
);
|
|
3723
3940
|
const commit = (0, import_react19.useCallback)(
|
|
3724
|
-
(move = null) => {
|
|
3941
|
+
(move = null, via = "key") => {
|
|
3725
3942
|
const open = editing;
|
|
3726
3943
|
if (!open) return;
|
|
3727
|
-
|
|
3944
|
+
let value = typed.current;
|
|
3945
|
+
if (isTypedDraft(value)) {
|
|
3946
|
+
if (value.judged === null || "refusal" in value.judged) return false;
|
|
3947
|
+
value = value.judged.value;
|
|
3948
|
+
}
|
|
3728
3949
|
if (!sameValue(startedFrom.current, value)) void writeCell(open.rowId, open.key, value, startedStored.current);
|
|
3950
|
+
const ended = endedRef.current;
|
|
3951
|
+
if (ended && via !== "blur") {
|
|
3952
|
+
setEditing(null);
|
|
3953
|
+
setDraft(null);
|
|
3954
|
+
typed.current = null;
|
|
3955
|
+
startedFrom.current = null;
|
|
3956
|
+
const exit = move === "next" ? "right" : move === "previous" ? "left" : via === "pick" ? "stay" : "down";
|
|
3957
|
+
ended(exit, open, via === "pick" ? "pointer" : "keyboard");
|
|
3958
|
+
return;
|
|
3959
|
+
}
|
|
3729
3960
|
if (!move) {
|
|
3730
3961
|
setEditing(null);
|
|
3731
3962
|
setDraft(null);
|
|
@@ -3937,6 +4168,7 @@ function GridCell({
|
|
|
3937
4168
|
const open = Boolean(
|
|
3938
4169
|
editing && editing.editing?.rowId === row.id && editing.editing.key === field2.key
|
|
3939
4170
|
);
|
|
4171
|
+
const wasActive = (0, import_react19.useRef)(false);
|
|
3940
4172
|
const value = editing ? editing.valueFor(row, field2) : row.document?.[field2.key];
|
|
3941
4173
|
const state = editing ? editing.stateOf(row.id, field2.key) : null;
|
|
3942
4174
|
const refusal2 = editing ? editing.refusalOf(row.id, field2.key) : null;
|
|
@@ -3948,8 +4180,8 @@ function GridCell({
|
|
|
3948
4180
|
field: field2,
|
|
3949
4181
|
draft: editing.draft,
|
|
3950
4182
|
onType: editing.type,
|
|
3951
|
-
onCommit: (move) => editing.commit(move),
|
|
3952
|
-
onCancel: editing.cancel,
|
|
4183
|
+
onCommit: (move, via) => editing.commit(move, via),
|
|
4184
|
+
onCancel: (via) => editing.cancel(via),
|
|
3953
4185
|
below: (
|
|
3954
4186
|
// KEPT EDITING: the refusal stays beside the value it is about, so the
|
|
3955
4187
|
// person reads what to change while changing it. Keep editing is the
|
|
@@ -3998,6 +4230,17 @@ function GridCell({
|
|
|
3998
4230
|
event.stopPropagation();
|
|
3999
4231
|
editing?.begin({ rowId: row.id, key: field2.key });
|
|
4000
4232
|
};
|
|
4233
|
+
const onPointerDown = (event) => {
|
|
4234
|
+
wasActive.current = event.currentTarget.closest("td")?.hasAttribute("data-matrx-cell-active") === true;
|
|
4235
|
+
};
|
|
4236
|
+
const onCellClick = (event) => {
|
|
4237
|
+
if (!editable) return;
|
|
4238
|
+
if (event.detail === 0) {
|
|
4239
|
+
openThisCell(event);
|
|
4240
|
+
return;
|
|
4241
|
+
}
|
|
4242
|
+
if (wasActive.current && opensAsAList(field2)) openThisCell(event);
|
|
4243
|
+
};
|
|
4001
4244
|
return (
|
|
4002
4245
|
// BOTH MARKERS, ON PURPOSE. `data-matrx-cell-control` is the data table's
|
|
4003
4246
|
// opt-out from whole-row click, so opening a cell never also opens the
|
|
@@ -4012,62 +4255,65 @@ function GridCell({
|
|
|
4012
4255
|
"data-matrx-cell-fill": "",
|
|
4013
4256
|
className: "group/cell relative flex w-full min-w-0 flex-col gap-0.5",
|
|
4014
4257
|
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)) {
|
|
4258
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "flex w-full min-w-0 items-center gap-1", children: [
|
|
4259
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4260
|
+
"span",
|
|
4261
|
+
{
|
|
4262
|
+
role: editable ? "button" : void 0,
|
|
4263
|
+
"aria-label": editable ? `Edit ${fieldName(field2)}` : void 0,
|
|
4264
|
+
title: editable ? `${fieldName(field2)} \u2014 double-click or press Enter to edit` : fieldIsEditable(field2) ? (
|
|
4265
|
+
// NOT A DEAD CONTROL AND NOT A SILENT ONE: the cell is plain
|
|
4266
|
+
// text, and hovering it says exactly why, in the level's own
|
|
4267
|
+
// words. The editor was never offered, so nothing is refused
|
|
4268
|
+
// after it has been typed into.
|
|
4269
|
+
`${fieldName(field2)} \u2014 ${whyNot ?? "you cannot change this record"}`
|
|
4270
|
+
) : `${fieldName(field2)} is worked out by the system, so it cannot be typed in`,
|
|
4271
|
+
className: (0, import_design_system12.cn)(
|
|
4272
|
+
// `w-full` so the whole cell is the control. A gap beside it is bare
|
|
4273
|
+
// table cell, and a click there is a row click.
|
|
4274
|
+
"block w-full min-w-0 flex-1 truncate rounded px-1 py-0.5",
|
|
4275
|
+
editable ? "cursor-text hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" : "",
|
|
4276
|
+
state === "saving" ? "opacity-60" : "",
|
|
4277
|
+
state === "refused" ? "ring-1 ring-destructive" : ""
|
|
4278
|
+
),
|
|
4279
|
+
onMouseDown: onPointerDown,
|
|
4280
|
+
onClick: editable ? onCellClick : void 0,
|
|
4281
|
+
onDoubleClick: (event) => {
|
|
4053
4282
|
event.preventDefault();
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4283
|
+
event.stopPropagation();
|
|
4284
|
+
if (editable) editing?.begin({ rowId: row.id, key: field2.key });
|
|
4285
|
+
},
|
|
4286
|
+
onKeyDown: (event) => {
|
|
4287
|
+
if (editable && (event.key === "Enter" || event.key === "F2")) {
|
|
4288
|
+
event.preventDefault();
|
|
4289
|
+
editing?.begin({ rowId: row.id, key: field2.key });
|
|
4290
|
+
return;
|
|
4291
|
+
}
|
|
4292
|
+
if (onAskWhoChanged && asksWhoChanged(event)) {
|
|
4293
|
+
event.preventDefault();
|
|
4294
|
+
onAskWhoChanged(field2.key, row.id);
|
|
4295
|
+
return;
|
|
4296
|
+
}
|
|
4297
|
+
if (editable && isTypedCharacter(event) && typingOpens(field2) !== "none") {
|
|
4298
|
+
event.preventDefault();
|
|
4299
|
+
editing?.begin({ rowId: row.id, key: field2.key }, event.key);
|
|
4300
|
+
}
|
|
4301
|
+
},
|
|
4302
|
+
tabIndex: editable || onAskWhoChanged ? 0 : void 0,
|
|
4303
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4304
|
+
RecordValue,
|
|
4305
|
+
{
|
|
4306
|
+
field: field2,
|
|
4307
|
+
value,
|
|
4308
|
+
document: row.document,
|
|
4309
|
+
withheld: withheldNoticeOf(row.hidden, field2.key),
|
|
4310
|
+
...format ? { format } : {}
|
|
4311
|
+
}
|
|
4312
|
+
)
|
|
4313
|
+
}
|
|
4314
|
+
),
|
|
4315
|
+
onAskWhoChanged ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(WhoBadge, { field: field2, row, onAskWhoChanged }) : null
|
|
4316
|
+
] }),
|
|
4071
4317
|
state === "saving" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "px-1 text-[10px] text-muted-foreground", children: "Saving\u2026" }) : null,
|
|
4072
4318
|
refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(CellRefusalNotice, { refusal: refusal2, field: field2, row, editing, typed: true }) : null
|
|
4073
4319
|
]
|
|
@@ -4122,9 +4368,10 @@ function WhoBadge({
|
|
|
4122
4368
|
"button",
|
|
4123
4369
|
{
|
|
4124
4370
|
type: "button",
|
|
4371
|
+
"data-records-who-badge": "",
|
|
4125
4372
|
"aria-label": `Who changed ${fieldName(field2)}?`,
|
|
4126
4373
|
title: who.sentence ?? `Who changed ${fieldName(field2)}? (press H)`,
|
|
4127
|
-
className: "
|
|
4374
|
+
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
4375
|
onMouseEnter: ask,
|
|
4129
4376
|
onFocus: ask,
|
|
4130
4377
|
onClick: (event) => {
|
|
@@ -4155,6 +4402,12 @@ function closesOnBlur(field2) {
|
|
|
4155
4402
|
return true;
|
|
4156
4403
|
}
|
|
4157
4404
|
}
|
|
4405
|
+
function opensAsAList(field2) {
|
|
4406
|
+
const kind = editorKindFor(field2);
|
|
4407
|
+
if (kind === "member") return true;
|
|
4408
|
+
if (kind === "select" || kind === "multi_select") return !(CHOICE_EDITOR_ADDS_WHAT_IS_TYPED && (0, import_records6.allowsOtherValues)(field2));
|
|
4409
|
+
return false;
|
|
4410
|
+
}
|
|
4158
4411
|
function takesANewline(field2) {
|
|
4159
4412
|
const kind = editorKindFor(field2);
|
|
4160
4413
|
return kind === "long_text" || kind === "json";
|
|
@@ -4168,12 +4421,13 @@ function OpenCell({
|
|
|
4168
4421
|
below
|
|
4169
4422
|
}) {
|
|
4170
4423
|
const multiline = takesANewline(field2);
|
|
4171
|
-
const
|
|
4424
|
+
const picker = opensAsAList(field2);
|
|
4425
|
+
const blurCommits = !picker && closesOnBlur(field2);
|
|
4172
4426
|
const done = (0, import_react19.useRef)(false);
|
|
4173
4427
|
const once = (run) => {
|
|
4174
4428
|
if (done.current) return;
|
|
4175
4429
|
done.current = true;
|
|
4176
|
-
run();
|
|
4430
|
+
if (run() === false) done.current = false;
|
|
4177
4431
|
};
|
|
4178
4432
|
const host = (0, import_react19.useRef)(null);
|
|
4179
4433
|
(0, import_react19.useEffect)(() => {
|
|
@@ -4190,7 +4444,7 @@ function OpenCell({
|
|
|
4190
4444
|
const blur = blurCommits ? {
|
|
4191
4445
|
onBlur: (event) => {
|
|
4192
4446
|
if (event.currentTarget.contains(event.relatedTarget)) return;
|
|
4193
|
-
once(() => onCommit(null));
|
|
4447
|
+
once(() => onCommit(null, "blur"));
|
|
4194
4448
|
}
|
|
4195
4449
|
} : {};
|
|
4196
4450
|
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
@@ -4205,28 +4459,37 @@ function OpenCell({
|
|
|
4205
4459
|
if (event.key === "Escape") {
|
|
4206
4460
|
event.preventDefault();
|
|
4207
4461
|
event.stopPropagation();
|
|
4208
|
-
once(onCancel);
|
|
4462
|
+
once(() => onCancel("key"));
|
|
4209
4463
|
return;
|
|
4210
4464
|
}
|
|
4211
4465
|
if (event.key === "Tab") {
|
|
4212
4466
|
event.preventDefault();
|
|
4213
4467
|
event.stopPropagation();
|
|
4214
|
-
once(() => onCommit(event.shiftKey ? "previous" : "next"));
|
|
4468
|
+
once(() => onCommit(event.shiftKey ? "previous" : "next", "key"));
|
|
4215
4469
|
return;
|
|
4216
4470
|
}
|
|
4217
4471
|
if (event.key === "Enter") {
|
|
4218
4472
|
if (multiline && event.shiftKey && !event.metaKey && !event.ctrlKey) return;
|
|
4219
4473
|
event.preventDefault();
|
|
4220
4474
|
event.stopPropagation();
|
|
4221
|
-
once(() => onCommit(null));
|
|
4475
|
+
once(() => onCommit(null, "key"));
|
|
4222
4476
|
}
|
|
4223
4477
|
},
|
|
4224
4478
|
...blur,
|
|
4225
4479
|
children: [
|
|
4226
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4480
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4481
|
+
FieldControl,
|
|
4482
|
+
{
|
|
4483
|
+
field: field2,
|
|
4484
|
+
value: draft,
|
|
4485
|
+
onChange: onType,
|
|
4486
|
+
id: `cell-${field2.id}`,
|
|
4487
|
+
...picker ? { autoOpen: true, onClose: () => once(() => onCommit(null, "pick")) } : {}
|
|
4488
|
+
}
|
|
4489
|
+
),
|
|
4490
|
+
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: [
|
|
4491
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system12.Button, { size: "sm", onClick: () => once(() => onCommit(null, "pick")), children: "Save" }),
|
|
4492
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system12.Button, { size: "sm", variant: "ghost", onClick: () => once(() => onCancel("pick")), children: "Cancel" })
|
|
4230
4493
|
] }),
|
|
4231
4494
|
below
|
|
4232
4495
|
]
|
|
@@ -4237,8 +4500,9 @@ function OpenCell({
|
|
|
4237
4500
|
// src/Grid.tsx
|
|
4238
4501
|
var import_react57 = require("react");
|
|
4239
4502
|
var import_react58 = require("@ai-matrx/records/react");
|
|
4240
|
-
var
|
|
4503
|
+
var import_records15 = require("@ai-matrx/records");
|
|
4241
4504
|
var import_data_table9 = require("@ai-matrx/design-system/data-table");
|
|
4505
|
+
var import_core8 = require("@ai-matrx/records/core");
|
|
4242
4506
|
|
|
4243
4507
|
// src/archiveWords.ts
|
|
4244
4508
|
function archiveConfirmSentence(what) {
|
|
@@ -4284,10 +4548,10 @@ var import_design_system17 = require("@ai-matrx/design-system");
|
|
|
4284
4548
|
// src/FormulaTextEditor.tsx
|
|
4285
4549
|
var import_react23 = require("react");
|
|
4286
4550
|
var import_react24 = require("@ai-matrx/records/react");
|
|
4287
|
-
var
|
|
4551
|
+
var import_records7 = require("@ai-matrx/records");
|
|
4288
4552
|
var import_design_system13 = require("@ai-matrx/design-system");
|
|
4289
4553
|
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
4290
|
-
var FORMULA_FUNCTIONS =
|
|
4554
|
+
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
4555
|
var RESULT_WORD = {
|
|
4292
4556
|
number: "a number",
|
|
4293
4557
|
text: "text",
|
|
@@ -4620,7 +4884,7 @@ function ChoiceColorsEditor({
|
|
|
4620
4884
|
|
|
4621
4885
|
// src/FieldFormatPicker.tsx
|
|
4622
4886
|
var import_design_system16 = require("@ai-matrx/design-system");
|
|
4623
|
-
var
|
|
4887
|
+
var import_field_formats7 = require("@ai-matrx/design-system/field-formats");
|
|
4624
4888
|
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
4625
4889
|
var KIND_WORD = {
|
|
4626
4890
|
choice: "select",
|
|
@@ -4683,7 +4947,7 @@ function formatMenu(field2) {
|
|
|
4683
4947
|
if (base === null) return { groups: [], otherKinds: [] };
|
|
4684
4948
|
if (base === "formula") {
|
|
4685
4949
|
const groups2 = [];
|
|
4686
|
-
for (const def of
|
|
4950
|
+
for (const def of import_field_formats7.FIELD_FORMAT_LIST) {
|
|
4687
4951
|
if (neverALook(def) || KIND_WORD[def.id] || def.id === "boolean") continue;
|
|
4688
4952
|
const bucket = groups2.find((g) => g.group === def.group);
|
|
4689
4953
|
if (bucket) bucket.formats.push(def);
|
|
@@ -4691,10 +4955,10 @@ function formatMenu(field2) {
|
|
|
4691
4955
|
}
|
|
4692
4956
|
return { groups: groups2, otherKinds: [] };
|
|
4693
4957
|
}
|
|
4694
|
-
const groups = (0,
|
|
4958
|
+
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
4959
|
const own2 = new Set(groups.flatMap((g) => g.formats.map((d) => d.id)));
|
|
4696
4960
|
const otherKinds = [];
|
|
4697
|
-
for (const def of
|
|
4961
|
+
for (const def of import_field_formats7.FIELD_FORMAT_LIST) {
|
|
4698
4962
|
if (own2.has(def.id) || neverALook(def)) continue;
|
|
4699
4963
|
const word = KIND_WORD[def.id] ?? PLAIN_WORD[def.base];
|
|
4700
4964
|
if (word) otherKinds.push({ def, word });
|
|
@@ -4703,7 +4967,7 @@ function formatMenu(field2) {
|
|
|
4703
4967
|
}
|
|
4704
4968
|
function startingChoice(field2) {
|
|
4705
4969
|
const held = displayFormatOf(field2);
|
|
4706
|
-
if (!held || !(0,
|
|
4970
|
+
if (!held || !(0, import_field_formats7.getFieldFormat)(held.id) || held.id === "formula") return { format: null, retype: null };
|
|
4707
4971
|
return {
|
|
4708
4972
|
format: Object.keys(held.options).length > 0 ? { id: held.id, options: held.options } : { id: held.id },
|
|
4709
4973
|
retype: null
|
|
@@ -4733,7 +4997,7 @@ function FieldFormatPicker({
|
|
|
4733
4997
|
}) {
|
|
4734
4998
|
const { groups, otherKinds } = formatMenu(field2);
|
|
4735
4999
|
if (groups.length === 0 && otherKinds.length === 0) return null;
|
|
4736
|
-
const active = value.format ? (0,
|
|
5000
|
+
const active = value.format ? (0, import_field_formats7.getFieldFormat)(value.format.id) : null;
|
|
4737
5001
|
const options = value.format?.options ?? {};
|
|
4738
5002
|
const optionKeys = active?.optionKeys ?? [];
|
|
4739
5003
|
const retypedTo = value.retype ? otherKinds.find((k) => k.def.id === value.format?.id) : void 0;
|
|
@@ -4897,124 +5161,473 @@ function FieldFormatPicker({
|
|
|
4897
5161
|
] });
|
|
4898
5162
|
}
|
|
4899
5163
|
|
|
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}`;
|
|
5164
|
+
// src/gridPage.ts
|
|
5165
|
+
var import_records8 = require("@ai-matrx/records");
|
|
5166
|
+
var import_data_table6 = require("@ai-matrx/design-system/data-table");
|
|
5167
|
+
function sortRefusal(f) {
|
|
5168
|
+
if (!computedOnRead(f)) return null;
|
|
5169
|
+
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
5170
|
}
|
|
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
|
-
|
|
5171
|
+
function filterRefusal(f) {
|
|
5172
|
+
if (!computedOnRead(f)) return null;
|
|
5173
|
+
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.`;
|
|
5174
|
+
}
|
|
5175
|
+
function computedOnRead(f) {
|
|
5176
|
+
const kind = String(editorKindFor(f));
|
|
5177
|
+
return (f.type === "formula" || kind === "formula" || kind === "lookup" || kind === "rollup") && String(f.compute_on ?? "read") === "read";
|
|
5178
|
+
}
|
|
5179
|
+
function sortAsFor(field2) {
|
|
5180
|
+
const kind = String(editorKindFor(field2));
|
|
5181
|
+
if (kind === "number" || kind === "currency" || kind === "percent") return "number";
|
|
5182
|
+
if (kind === "datetime") return "date";
|
|
5183
|
+
return "text";
|
|
5184
|
+
}
|
|
5185
|
+
function pageSortFor(fields, sort) {
|
|
5186
|
+
if (!sort) return [];
|
|
5187
|
+
const field2 = fields.find((f) => f.key === sort.id);
|
|
5188
|
+
if (!field2) return [];
|
|
5189
|
+
return [{ field: field2.key, direction: sort.direction === "desc" ? "desc" : "asc", as: sortAsFor(field2) }];
|
|
5190
|
+
}
|
|
5191
|
+
function containsPattern(text) {
|
|
5192
|
+
return `(?i)${text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`;
|
|
5193
|
+
}
|
|
5194
|
+
var field = (f) => ({ field: f.id });
|
|
5195
|
+
var constant = (value) => ({ const: value });
|
|
5196
|
+
var op = (name, ...args) => ({ op: name, args });
|
|
5197
|
+
function clauseFor(f, value, now = /* @__PURE__ */ new Date()) {
|
|
5198
|
+
const name = fieldName(f);
|
|
5199
|
+
const refused = filterRefusal(f);
|
|
5200
|
+
if (refused) return refused;
|
|
5201
|
+
const not = (rule) => op("not", rule);
|
|
5202
|
+
switch (value.kind) {
|
|
5203
|
+
case "text": {
|
|
5204
|
+
const mode = value.mode ?? "contains";
|
|
5205
|
+
if (mode === "empty" || mode === "null") return value.negated ? { rule: op("present", field(f)) } : { rule: not(op("present", field(f))) };
|
|
5206
|
+
if (mode === "not_empty" || mode === "not_null") return value.negated ? { rule: not(op("present", field(f))) } : { rule: op("present", field(f)) };
|
|
5207
|
+
const text = value.value.trim();
|
|
5208
|
+
if (text === "") return null;
|
|
5209
|
+
const kind = String(editorKindFor(f));
|
|
5210
|
+
if (kind === "number" || kind === "currency" || kind === "percent" || kind === "checkbox") {
|
|
5211
|
+
return `${name} holds numbers, so it is filtered by a range, not by words.`;
|
|
5212
|
+
}
|
|
5213
|
+
const matches2 = op("matches", field(f), constant(containsPattern(text)));
|
|
5214
|
+
return { rule: value.negated ? not(matches2) : matches2 };
|
|
5215
|
+
}
|
|
5216
|
+
case "select": {
|
|
5217
|
+
const words2 = value.values ?? (value.value === "" ? [] : [value.value]);
|
|
5218
|
+
if (words2.length === 0) return value.negated ? null : { rule: constant(false) };
|
|
5219
|
+
if (words2.length === 1 && !value.negated) return { flat: { [f.key]: words2[0] } };
|
|
5220
|
+
const any = words2.length === 1 ? op("eq", field(f), constant(words2[0])) : op("or", ...words2.map((w) => op("eq", field(f), constant(w))));
|
|
5221
|
+
return { rule: value.negated ? not(any) : any };
|
|
5222
|
+
}
|
|
5223
|
+
case "boolean":
|
|
5224
|
+
return value.negated ? { rule: op("ne", field(f), constant(value.value)) } : { flat: { [f.key]: value.value } };
|
|
5225
|
+
case "number": {
|
|
5226
|
+
if (value.op === "empty") return { rule: not(op("present", field(f))) };
|
|
5227
|
+
if (value.op === "not_empty") return { rule: op("present", field(f)) };
|
|
5228
|
+
if (value.op === "eq" || value.op === "ne") {
|
|
5229
|
+
if (typeof value.min !== "number") return null;
|
|
5230
|
+
return { rule: op(value.op, field(f), constant(value.min)) };
|
|
5231
|
+
}
|
|
5232
|
+
if (value.op === "lt") return typeof value.max === "number" ? { rule: op("lt", field(f), constant(value.max)) } : null;
|
|
5233
|
+
if (value.op === "gt") return typeof value.min === "number" ? { rule: op("gt", field(f), constant(value.min)) } : null;
|
|
5234
|
+
const parts = [];
|
|
5235
|
+
if (typeof value.min === "number") parts.push(op("gte", field(f), constant(value.min)));
|
|
5236
|
+
if (typeof value.max === "number") parts.push(op("lte", field(f), constant(value.max)));
|
|
5237
|
+
if (parts.length === 0) return null;
|
|
5238
|
+
const range = parts.length === 1 ? parts[0] : op("and", ...parts);
|
|
5239
|
+
return { rule: value.negated ? not(range) : range };
|
|
5240
|
+
}
|
|
5241
|
+
case "date": {
|
|
5242
|
+
const bounds = (0, import_data_table6.dateFilterBounds)(value, now);
|
|
5243
|
+
if (bounds.since === void 0 && bounds.until === void 0) return null;
|
|
5244
|
+
return {
|
|
5245
|
+
flat: {
|
|
5246
|
+
[f.key]: {
|
|
5247
|
+
...bounds.since === void 0 ? {} : { from: bounds.since },
|
|
5248
|
+
...bounds.until === void 0 ? {} : { to: bounds.until }
|
|
5249
|
+
}
|
|
5250
|
+
}
|
|
5251
|
+
};
|
|
5252
|
+
}
|
|
5253
|
+
default:
|
|
5254
|
+
return `${name} cannot be filtered that way by the store.`;
|
|
5255
|
+
}
|
|
5256
|
+
}
|
|
5257
|
+
function storeQuestion(fields, base, columnFilters, now = /* @__PURE__ */ new Date()) {
|
|
5258
|
+
const refused = [];
|
|
5259
|
+
const flat = {};
|
|
5260
|
+
const rules = [];
|
|
5261
|
+
if (base && (0, import_records8.isRuleFilter)(base)) rules.push(base);
|
|
5262
|
+
else if (base) Object.assign(flat, base);
|
|
5263
|
+
for (const [key, value] of Object.entries(columnFilters)) {
|
|
5264
|
+
if (!value) continue;
|
|
5265
|
+
const f = fields.find((candidate) => candidate.key === key);
|
|
5266
|
+
if (!f) continue;
|
|
5267
|
+
const clause = clauseFor(f, value, now);
|
|
5268
|
+
if (clause === null) continue;
|
|
5269
|
+
if (typeof clause === "string") {
|
|
5270
|
+
refused.push(clause);
|
|
5271
|
+
continue;
|
|
5272
|
+
}
|
|
5273
|
+
if ("flat" in clause) Object.assign(flat, clause.flat);
|
|
5274
|
+
else rules.push(clause.rule);
|
|
5275
|
+
}
|
|
5276
|
+
const flatKeys = Object.keys(flat);
|
|
5277
|
+
if (rules.length === 0) return { filter: flatKeys.length > 0 ? flat : void 0, refused };
|
|
5278
|
+
for (const key of flatKeys) {
|
|
5279
|
+
const f = fields.find((candidate) => candidate.key === key);
|
|
5280
|
+
const value = flat[key];
|
|
5281
|
+
if (!f) continue;
|
|
5282
|
+
if (value !== null && typeof value === "object") {
|
|
5283
|
+
refused.push(`${fieldName(f)}'s date range cannot be asked together with the other filters yet, so it is left off.`);
|
|
5284
|
+
continue;
|
|
5285
|
+
}
|
|
5286
|
+
rules.push(value === null ? op("not", op("present", field(f))) : op("eq", field(f), constant(value)));
|
|
5287
|
+
}
|
|
5288
|
+
return { filter: rules.length === 1 ? rules[0] : op("and", ...rules), refused };
|
|
5289
|
+
}
|
|
5290
|
+
function inColumnWords(error, fields) {
|
|
5291
|
+
const say2 = (text) => {
|
|
5292
|
+
if (!text) return text;
|
|
5293
|
+
let out = text;
|
|
5294
|
+
for (const f of [...fields].sort((a, b) => b.key.length - a.key.length)) {
|
|
5295
|
+
const key = f.key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5296
|
+
out = out.replace(new RegExp(`(["\u201C'])${key}(["\u201D'])`, "g"), `$1${fieldName(f)}$2`);
|
|
5297
|
+
}
|
|
5298
|
+
return out;
|
|
5299
|
+
};
|
|
5300
|
+
return { ...error, message: say2(error.message) ?? error.message, ...error.hint ? { hint: say2(error.hint) ?? error.hint } : {} };
|
|
5301
|
+
}
|
|
5302
|
+
|
|
5303
|
+
// src/gridMenus.ts
|
|
5304
|
+
function fieldOf(ctx, columnId) {
|
|
5305
|
+
return ctx.fields.find((f) => f.key === columnId);
|
|
5306
|
+
}
|
|
5307
|
+
function columnWords(ctx, columnId) {
|
|
5308
|
+
const field2 = fieldOf(ctx, columnId);
|
|
5309
|
+
return field2 ? fieldName(field2) : columnId;
|
|
5310
|
+
}
|
|
5311
|
+
function rowLabelRefusal(field2) {
|
|
5312
|
+
if (!field2) return "This column is not one of the table's own.";
|
|
5313
|
+
const kind = String(field2.type);
|
|
5314
|
+
if (kind === "formula" || kind === "lookup" || kind === "rollup") {
|
|
5315
|
+
return `${fieldName(field2)} is worked out by the table, and a record is named by a column people fill in.`;
|
|
5316
|
+
}
|
|
5317
|
+
if (kind === "relation") return `${fieldName(field2)} points at other records; a record is named by its own words.`;
|
|
5318
|
+
return void 0;
|
|
5319
|
+
}
|
|
5320
|
+
function headerMenuItems(columnId, ctx) {
|
|
5321
|
+
const field2 = fieldOf(ctx, columnId);
|
|
5322
|
+
const shape = ctx.mayStructure ? void 0 : ctx.whyNotStructure;
|
|
5323
|
+
const isLabel = ctx.titleField === columnId;
|
|
5324
|
+
const labelRefusal = shape ?? (isLabel ? "Records are already named by this column." : rowLabelRefusal(field2));
|
|
5325
|
+
const a = ctx.actions;
|
|
5326
|
+
return [
|
|
5327
|
+
{ id: "col-rename", label: "Rename column", icon: "pencil", disabledReason: shape, onSelect: () => a.renameColumn(columnId) },
|
|
5328
|
+
{ id: "col-settings", label: "Column settings\u2026", icon: "settings", disabledReason: shape, onSelect: () => a.columnSettings(columnId) },
|
|
5329
|
+
{ id: "col-insert-left", label: "Insert column left\u2026", icon: "panel-left", disabledReason: shape, onSelect: () => a.insertColumn(columnId, "left") },
|
|
5330
|
+
{ id: "col-insert-right", label: "Insert column right\u2026", icon: "panel-right", disabledReason: shape, onSelect: () => a.insertColumn(columnId, "right") },
|
|
5331
|
+
{
|
|
5332
|
+
id: "col-row-label",
|
|
5333
|
+
label: isLabel ? "Names each record" : "Use as row label",
|
|
5334
|
+
icon: "tag",
|
|
5335
|
+
disabledReason: labelRefusal,
|
|
5336
|
+
onSelect: () => a.useAsRowLabel(columnId)
|
|
5337
|
+
},
|
|
5338
|
+
...ctx.extras?.header?.(columnId) ?? [],
|
|
5339
|
+
{ id: "col-hide", label: "Hide column", icon: "eye-off", onSelect: () => a.hideColumn(columnId) },
|
|
5340
|
+
{
|
|
5341
|
+
id: "col-delete",
|
|
5342
|
+
label: "Delete column\u2026",
|
|
5343
|
+
icon: "trash",
|
|
5344
|
+
destructive: true,
|
|
5345
|
+
disabledReason: shape ?? (isLabel ? "Records are named by this column; pick another row label first." : void 0),
|
|
5346
|
+
onSelect: () => a.deleteColumn(columnId)
|
|
5347
|
+
}
|
|
5348
|
+
];
|
|
5349
|
+
}
|
|
5350
|
+
function cellSection(payload, ctx) {
|
|
5351
|
+
const a = ctx.actions;
|
|
5352
|
+
const { rowId, columnId } = payload;
|
|
5353
|
+
const mayWrite = ctx.mayWriteRow(rowId);
|
|
5354
|
+
const typeable = ctx.typeable(columnId);
|
|
5355
|
+
const why = !mayWrite ? ctx.whyNotWrite(rowId) : !typeable ? `${columnWords(ctx, columnId)} is worked out by the table; nobody types it.` : void 0;
|
|
5356
|
+
const items = [
|
|
5357
|
+
{ id: "cell-copy", label: "Copy cell", icon: "copy", shortcut: "\u2318C", onSelect: () => a.copyText(payload.text, "The cell") },
|
|
5358
|
+
{ id: "cell-paste", label: "Paste", icon: "clipboard-paste", shortcut: "\u2318V", disabledReason: why, onSelect: () => a.pasteInto(rowId, columnId) },
|
|
5359
|
+
{ id: "cell-clear", label: "Clear cell", icon: "eraser", destructive: true, disabledReason: why, onSelect: () => a.clearCells([{ rowId, columnId }]) }
|
|
5360
|
+
];
|
|
5361
|
+
if (a.askWhoChanged) {
|
|
5362
|
+
items.push({ id: "cell-history", label: "Who changed this?", icon: "history", shortcut: "\u2325\u21E7H", onSelect: () => a.askWhoChanged?.(columnId, rowId) });
|
|
5363
|
+
}
|
|
5364
|
+
const empty = payload.value === null || payload.value === void 0 || payload.value === "";
|
|
5365
|
+
items.push({
|
|
5366
|
+
id: "cell-filter",
|
|
5367
|
+
label: empty ? "Show rows where this is empty" : "Show rows with this value",
|
|
5368
|
+
icon: "filter",
|
|
5369
|
+
onSelect: () => a.filterBy(columnId, payload.value)
|
|
5370
|
+
});
|
|
5371
|
+
items.push(...ctx.extras?.cell?.(rowId, columnId) ?? []);
|
|
5372
|
+
return { id: "cell", title: payload.columnLabel || "Cell", items };
|
|
5373
|
+
}
|
|
5374
|
+
function rowSection(rowId, ctx, rowText) {
|
|
5375
|
+
const a = ctx.actions;
|
|
5376
|
+
const mayWrite = ctx.mayWriteRow(rowId);
|
|
5377
|
+
const items = [];
|
|
5378
|
+
if (a.openRecord) {
|
|
5379
|
+
items.push({ id: "row-open", label: "Open record", icon: "panel-right", shortcut: "Space", onSelect: () => a.openRecord?.(rowId) });
|
|
5380
|
+
items.push({ id: "row-history", label: "Row history", icon: "history", onSelect: () => a.openRecord?.(rowId) });
|
|
5381
|
+
}
|
|
5382
|
+
if (rowText !== null) {
|
|
5383
|
+
items.push({ id: "row-copy", label: "Copy row", icon: "copy", onSelect: () => a.copyText(rowText, "The row") });
|
|
5384
|
+
}
|
|
5385
|
+
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.";
|
|
5386
|
+
const insertWhy = !ctx.mayAddRecord ? ctx.whyNotAdd : a.insertRow ? void 0 : noPlace;
|
|
5387
|
+
items.push(
|
|
5388
|
+
{ id: "row-insert-above", label: "Insert row above", icon: "plus", disabledReason: insertWhy, onSelect: () => a.insertRow?.(rowId, "above") },
|
|
5389
|
+
{ id: "row-insert-below", label: "Insert row below", icon: "plus", disabledReason: insertWhy, onSelect: () => a.insertRow?.(rowId, "below") },
|
|
5390
|
+
{
|
|
5391
|
+
id: "row-duplicate",
|
|
5392
|
+
label: "Duplicate row",
|
|
5393
|
+
icon: "copy-plus",
|
|
5394
|
+
disabledReason: ctx.mayAddRecord ? void 0 : ctx.whyNotAdd,
|
|
5395
|
+
onSelect: () => a.duplicateRow(rowId)
|
|
5396
|
+
},
|
|
5397
|
+
{
|
|
5398
|
+
id: "row-archive",
|
|
5399
|
+
label: "Delete row\u2026",
|
|
5400
|
+
icon: "trash",
|
|
5401
|
+
destructive: true,
|
|
5402
|
+
disabledReason: mayWrite ? void 0 : ctx.whyNotWrite(rowId),
|
|
5403
|
+
onSelect: () => a.archiveRow(rowId)
|
|
5404
|
+
}
|
|
5405
|
+
);
|
|
5406
|
+
items.push(...ctx.extras?.row?.(rowId) ?? []);
|
|
5407
|
+
return { id: "row", title: ctx.extras?.rowTitle?.(rowId) || "Row", items };
|
|
5408
|
+
}
|
|
5409
|
+
function columnSection(columnId, columnLabel2, ctx) {
|
|
5410
|
+
const a = ctx.actions;
|
|
5411
|
+
const sorted = ctx.sortedBy?.id === columnId ? ctx.sortedBy.direction : null;
|
|
5412
|
+
const field2 = fieldOf(ctx, columnId);
|
|
5413
|
+
const refused = field2 ? sortRefusal(field2) ?? void 0 : void 0;
|
|
5414
|
+
return {
|
|
5415
|
+
id: "column",
|
|
5416
|
+
title: columnLabel2 || columnWords(ctx, columnId),
|
|
5417
|
+
items: [
|
|
5418
|
+
{ 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") },
|
|
5419
|
+
{ 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") },
|
|
5420
|
+
...sorted ? [{ id: "col-sort-clear", label: "Clear sort", icon: "arrow-up-down", onSelect: () => a.sortBy(columnId, null) }] : [],
|
|
5421
|
+
...headerMenuItems(columnId, ctx)
|
|
5422
|
+
]
|
|
5423
|
+
};
|
|
5424
|
+
}
|
|
5425
|
+
function tableSection(ctx) {
|
|
5426
|
+
const a = ctx.actions;
|
|
5427
|
+
const items = [
|
|
5428
|
+
{ id: "table-new-record", label: "New record", icon: "plus", disabledReason: ctx.mayAddRecord ? void 0 : ctx.whyNotAdd, onSelect: () => a.newRecord() }
|
|
5429
|
+
];
|
|
5430
|
+
if (a.addColumn) {
|
|
5431
|
+
items.push({
|
|
5432
|
+
id: "table-add-column",
|
|
5433
|
+
label: "Add column\u2026",
|
|
5434
|
+
icon: "plus",
|
|
5435
|
+
disabledReason: ctx.mayStructure ? void 0 : ctx.whyNotStructure,
|
|
5436
|
+
onSelect: () => a.addColumn?.()
|
|
5437
|
+
});
|
|
5438
|
+
}
|
|
5439
|
+
items.push(...ctx.extras?.table?.() ?? []);
|
|
5440
|
+
return { id: "table", title: "Table", items };
|
|
5441
|
+
}
|
|
5442
|
+
function gridMenuSections(payload, ctx, rowText = () => null) {
|
|
5443
|
+
switch (payload.level) {
|
|
5444
|
+
case "cell":
|
|
5445
|
+
return [
|
|
5446
|
+
{ ...cellSection(payload, ctx), primary: true },
|
|
5447
|
+
rowSection(payload.rowId, ctx, rowText(payload.rowId)),
|
|
5448
|
+
columnSection(payload.columnId, payload.columnLabel, ctx),
|
|
5449
|
+
tableSection(ctx)
|
|
5450
|
+
];
|
|
5451
|
+
case "row":
|
|
5452
|
+
return [{ ...rowSection(payload.rowId, ctx, rowText(payload.rowId)), primary: true }, tableSection(ctx)];
|
|
5453
|
+
case "column":
|
|
5454
|
+
return [{ ...columnSection(payload.columnId, payload.columnLabel, ctx), primary: true }, tableSection(ctx)];
|
|
5455
|
+
case "selection": {
|
|
5456
|
+
const n = payload.cellCount;
|
|
5457
|
+
const blocked = payload.rowIds.find((id) => !ctx.mayWriteRow(id));
|
|
5458
|
+
const untypeable = payload.columnIds.find((id) => !ctx.typeable(id));
|
|
5459
|
+
const why = blocked ? ctx.whyNotWrite(blocked) : untypeable ? `${columnWords(ctx, untypeable)} is worked out by the table; nobody types it.` : void 0;
|
|
5460
|
+
const cells = payload.rowIds.flatMap((rowId) => payload.columnIds.map((columnId) => ({ rowId, columnId })));
|
|
5461
|
+
return [
|
|
5462
|
+
{
|
|
5463
|
+
id: "selection",
|
|
5464
|
+
title: `${n} ${n === 1 ? "cell" : "cells"}`,
|
|
5465
|
+
primary: true,
|
|
5466
|
+
items: [
|
|
5467
|
+
{ id: "sel-copy", label: `Copy ${n} cells`, icon: "copy", shortcut: "\u2318C", onSelect: () => ctx.actions.copyText(payload.tsv, `${n} cells`) },
|
|
5468
|
+
{ id: "sel-clear", label: `Clear ${n} cells`, icon: "eraser", destructive: true, disabledReason: why, onSelect: () => ctx.actions.clearCells(cells) },
|
|
5469
|
+
{
|
|
5470
|
+
id: "sel-fill-down",
|
|
5471
|
+
label: "Fill down",
|
|
5472
|
+
icon: "arrow-down-to-line",
|
|
5473
|
+
shortcut: "\u2318D",
|
|
5474
|
+
disabledReason: why ?? (payload.rowIds.length < 2 ? "Select at least two rows to fill down." : void 0),
|
|
5475
|
+
onSelect: () => ctx.actions.fillDown(payload.rowIds, payload.columnIds)
|
|
5476
|
+
}
|
|
5477
|
+
]
|
|
5478
|
+
},
|
|
5479
|
+
tableSection(ctx)
|
|
5480
|
+
];
|
|
5481
|
+
}
|
|
5482
|
+
case "table":
|
|
5483
|
+
return [{ ...tableSection(ctx), primary: true }];
|
|
5484
|
+
}
|
|
5485
|
+
}
|
|
5486
|
+
function placeColumn(order, newKey, anchor, side) {
|
|
5487
|
+
const rest = order.filter((k) => k !== newKey);
|
|
5488
|
+
const at = rest.indexOf(anchor);
|
|
5489
|
+
if (at < 0) return [...rest, newKey];
|
|
5490
|
+
const index = side === "left" ? at : at + 1;
|
|
5491
|
+
return [...rest.slice(0, index), newKey, ...rest.slice(index)];
|
|
5492
|
+
}
|
|
5493
|
+
function columnsInKeptOrder(fieldKeys, kept) {
|
|
5494
|
+
if (!kept || kept.length === 0) return [...fieldKeys];
|
|
5495
|
+
const known = new Set(fieldKeys);
|
|
5496
|
+
const named = kept.filter((k, i) => known.has(k) && kept.indexOf(k) === i);
|
|
5497
|
+
const namedSet = new Set(named);
|
|
5498
|
+
return [...named, ...fieldKeys.filter((k) => !namedSet.has(k))];
|
|
5499
|
+
}
|
|
5500
|
+
function compareFieldOrder(a, b) {
|
|
5501
|
+
const bySort = (a.sort ?? 100) - (b.sort ?? 100);
|
|
5502
|
+
if (bySort !== 0) return bySort;
|
|
5503
|
+
const made = String(a.created_at ?? "").localeCompare(String(b.created_at ?? ""));
|
|
5504
|
+
if (made !== 0) return made;
|
|
5505
|
+
return a.key.localeCompare(b.key);
|
|
5506
|
+
}
|
|
5507
|
+
function nextFieldSort(fields) {
|
|
5508
|
+
let last = 0;
|
|
5509
|
+
for (const f of fields ?? []) last = Math.max(last, f.sort ?? 100);
|
|
5510
|
+
return last + 1;
|
|
5511
|
+
}
|
|
5512
|
+
|
|
5513
|
+
// src/FieldEditor.tsx
|
|
5514
|
+
var import_records9 = require("@ai-matrx/records");
|
|
5515
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
5516
|
+
var RULE_KINDS = FIELD_RULE_KIND_VALUES.map((kind) => ({ kind, label: FIELD_RULE_KIND_LABEL[kind] }));
|
|
5517
|
+
var SENSITIVITIES = import_records9.FIELD_SENSITIVITIES.map((value) => ({ value, label: FIELD_SENSITIVITY_LABEL[value] }));
|
|
5518
|
+
var AGENT_VISIBILITY = import_records9.CONTEXT_POLICIES.map((value) => ({ value, label: CONTEXT_POLICY_LABEL[value] }));
|
|
5519
|
+
var ROLLUP_OPERATIONS = ROLLUP_AGG_VALUES.map((value) => ({
|
|
5520
|
+
value,
|
|
5521
|
+
label: ROLLUP_AGG_LABEL[value]
|
|
5522
|
+
}));
|
|
5523
|
+
var FORMULA_SEPARATOR_LABEL = "Joined by";
|
|
5524
|
+
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.";
|
|
5525
|
+
var FORMULA_DEFAULT_SEPARATOR = " \u2014 ";
|
|
5526
|
+
var FORMULA_OPERATIONS = FORMULA_OP_VALUES.map((value) => ({
|
|
5527
|
+
value,
|
|
5528
|
+
label: FORMULA_OP_LABEL[value],
|
|
5529
|
+
joins: FORMULA_OP_JOINS[value]
|
|
5530
|
+
}));
|
|
5531
|
+
function keyFor(label) {
|
|
5532
|
+
const token = label.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48);
|
|
5533
|
+
if (token === "") return "";
|
|
5534
|
+
return /^[a-z]/.test(token) ? token : `f_${token}`;
|
|
5535
|
+
}
|
|
5536
|
+
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.";
|
|
5537
|
+
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.";
|
|
5538
|
+
var COLUMN_DID_NOT_LAND = `${COLUMN_DID_NOT_LAND_SENTENCE} ${COLUMN_DID_NOT_LAND_REMEDY}`;
|
|
5539
|
+
function FieldEditor({ tableId, field: field2, onSaved, onCancel, onRemoved, className }) {
|
|
5540
|
+
const table = (0, import_react30.useTable)(tableId);
|
|
5541
|
+
const fields = (0, import_react30.useFields)(tableId);
|
|
5542
|
+
const rights = useTableRights(table.data);
|
|
5543
|
+
const mutation = (0, import_react30.useFieldMutation)();
|
|
5544
|
+
const client = (0, import_react30.useRecordsClient)();
|
|
5545
|
+
const [label, setLabel] = (0, import_react29.useState)(field2?.label ?? "");
|
|
5546
|
+
const [type, setType] = (0, import_react29.useState)(() => startingType(field2));
|
|
5547
|
+
const [required, setRequired] = (0, import_react29.useState)(field2?.required ?? false);
|
|
5548
|
+
const [multi, setMulti] = (0, import_react29.useState)(field2?.multi ?? false);
|
|
5549
|
+
const [dated, setDated] = (0, import_react29.useState)(field2?.dated ?? false);
|
|
5550
|
+
const [sensitivity, setSensitivity] = (0, import_react29.useState)(String(field2?.sensitivity ?? "internal"));
|
|
5551
|
+
const [contextPolicy, setContextPolicy] = (0, import_react29.useState)(String(field2?.context_policy ?? "include"));
|
|
5552
|
+
const [rules, setRules] = (0, import_react29.useState)(field2?.rules ?? []);
|
|
5553
|
+
const [allowOther, setAllowOther] = (0, import_react29.useState)(field2?.config?.["allow_other"] === true);
|
|
5554
|
+
const [optionText, setOptionText] = (0, import_react29.useState)("");
|
|
5555
|
+
const [unit, setUnit] = (0, import_react29.useState)(field2?.unit ?? "$");
|
|
5556
|
+
const [withTime, setWithTime] = (0, import_react29.useState)(String(field2?.config?.["kind"] ?? "date") === "datetime");
|
|
5557
|
+
const [via, setVia] = (0, import_react29.useState)(String(field2?.config?.["via"] ?? ""));
|
|
5558
|
+
const [pick, setPick] = (0, import_react29.useState)(String(field2?.config?.["pick"] ?? ""));
|
|
5559
|
+
const [agg, setAgg] = (0, import_react29.useState)(
|
|
5560
|
+
field2?.config?.["agg"] ?? "count"
|
|
5561
|
+
);
|
|
5562
|
+
const [of, setOf] = (0, import_react29.useState)(String(field2?.config?.["of"] ?? ""));
|
|
5563
|
+
const [relationTarget, setRelationTarget] = (0, import_react29.useState)(String(field2?.relation_target ?? ""));
|
|
5564
|
+
const [display, setDisplay] = (0, import_react29.useState)(field2?.display ?? null);
|
|
5565
|
+
const [formulaOp, setFormulaOp] = (0, import_react29.useState)(() => formulaOpOf(field2));
|
|
5566
|
+
const [formulaFields, setFormulaFields] = (0, import_react29.useState)(() => formulaFieldIdsOf(field2));
|
|
5567
|
+
const [formulaSeparator, setFormulaSeparator] = (0, import_react29.useState)(() => formulaSeparatorOf(field2));
|
|
5568
|
+
const [allowedTypes, setAllowedTypes] = (0, import_react29.useState)(() => entityReferenceKindsOf(field2));
|
|
5569
|
+
const startingFormulaText = String(field2?.config?.["formula_text"] ?? "");
|
|
5570
|
+
const [formulaText, setFormulaText] = (0, import_react29.useState)(startingFormulaText);
|
|
5571
|
+
const [formulaWritten, setFormulaWritten] = (0, import_react29.useState)(() => startingFormulaText.trim() !== "" || Boolean(field2));
|
|
5572
|
+
const [formulaOk, setFormulaOk] = (0, import_react29.useState)(null);
|
|
5573
|
+
const [askingToRemove, setAskingToRemove] = (0, import_react29.useState)(false);
|
|
5574
|
+
const [choiceRows, setChoiceRows] = (0, import_react29.useState)(null);
|
|
5575
|
+
const [showsAs, setShowsAs] = (0, import_react29.useState)(() => field2 ? startingChoice(field2) : { format: null, retype: null });
|
|
5576
|
+
const [backfillError, setBackfillError] = (0, import_react29.useState)(null);
|
|
5577
|
+
const [notLanded, setNotLanded] = (0, import_react29.useState)(false);
|
|
5578
|
+
const others = (0, import_react29.useMemo)(
|
|
5579
|
+
() => (fields.data ?? []).filter((f) => f.id !== field2?.id),
|
|
5580
|
+
[fields.data, field2?.id]
|
|
5581
|
+
);
|
|
5582
|
+
const relations = (0, import_react29.useMemo)(() => others.filter((f) => f.type === "relation"), [others]);
|
|
5583
|
+
const choice = fieldTypeChoice(type);
|
|
5584
|
+
const editing = Boolean(field2);
|
|
5585
|
+
if (!rights.structure) {
|
|
5586
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: (0, import_design_system17.cn)("text-xs text-muted-foreground", className), children: rights.why("structure") });
|
|
5587
|
+
}
|
|
5588
|
+
const options = optionText.split("\n").map((word) => word.trim()).filter((word) => word !== "");
|
|
5589
|
+
const formulaChanged = formulaText.trim() !== startingFormulaText.trim();
|
|
5590
|
+
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;
|
|
5591
|
+
const missing = editing ? formulaMissing : formulaMissing ?? (type === "formula" && formulaWritten ? null : whatIsMissing({ type, label, options, via, pick, agg, of, relations, formulaFields, relationTarget, allowedTypes }));
|
|
5592
|
+
const save = async () => {
|
|
5593
|
+
if (missing) return;
|
|
5594
|
+
if (editing && field2) {
|
|
5595
|
+
const chooses = type === "select" || type === "multi_select";
|
|
5596
|
+
const look = chooses ? choiceRows ? choiceDisplayPatch(field2, choiceRows) : null : null;
|
|
5597
|
+
const shownAs = chooses ? void 0 : formatPatch(field2, showsAs);
|
|
5598
|
+
const patch = {
|
|
5599
|
+
// The column's look travels in the same settings patch (`display_format`), so a save is
|
|
5600
|
+
// still one store call. `FieldPatch` does not declare the key yet (store gap, reported);
|
|
5601
|
+
// the door has carried it since the Sheet's seam started writing it.
|
|
5602
|
+
...chooses ? look ? { display_format: look } : {} : shownAs ?? {},
|
|
5603
|
+
label,
|
|
5604
|
+
required,
|
|
5605
|
+
dated,
|
|
5606
|
+
sensitivity,
|
|
5607
|
+
context_policy: contextPolicy,
|
|
5608
|
+
rules,
|
|
5609
|
+
// FLD-2. THE SHAPE OF THE VALUE, WHICH IS A SETTING LIKE ANY OTHER. This
|
|
5610
|
+
// patch never carried it, so the tick beside "Can hold more than one"
|
|
5611
|
+
// was decoration on an existing column — and the store's own roll-up
|
|
5612
|
+
// refusal tells a person to use exactly that tick. It is sent ALONE:
|
|
5613
|
+
// `custom.field_update` honours a `multi`-only patch since FIX-7B-FIELD's
|
|
5614
|
+
// migration (before it, the door read `multi` only inside its behaviour
|
|
5615
|
+
// arm, answered "saved" and changed nothing), so there is no behaviour
|
|
5616
|
+
// word to send beside it and sending one would retype a column nobody
|
|
5617
|
+
// asked to retype.
|
|
5618
|
+
...multiApplies(type) ? { multi } : {},
|
|
5619
|
+
...unitApplies(type) ? { unit } : {},
|
|
5620
|
+
// An empty box means "leave the choices alone"; a list replaces them.
|
|
5621
|
+
...optionText.trim() === "" ? {} : { options },
|
|
5622
|
+
// REL-DISP. Which of the other record's columns this one shows. Sent for
|
|
5623
|
+
// every relation column, `null` included, because `null` is how the door
|
|
5624
|
+
// REMOVES a spec and the column goes back to the target table's own
|
|
5625
|
+
// reference column — a panel that only sent the key when it was set
|
|
5626
|
+
// could turn the override on and never off.
|
|
5627
|
+
...field2.type === "relation" ? { display } : {},
|
|
5628
|
+
// STORE-RULE-GAPS: a choice list's own setting, sent for every list so
|
|
5629
|
+
// turning it off is as possible as turning it on.
|
|
5630
|
+
...field2.type === "list" ? { allow_other: allowOther } : {},
|
|
5018
5631
|
// K2: a re-aimed formula goes WITH its type word, the arm `field_update` reads it in.
|
|
5019
5632
|
...type === "formula" && formulaWritten && formulaChanged && formulaText.trim() !== "" ? { type: "formula", formula_text: formulaText.trim() } : {}
|
|
5020
5633
|
};
|
|
@@ -5024,6 +5637,10 @@ function FieldEditor({ tableId, field: field2, onSaved, onCancel, onRemoved, cla
|
|
|
5024
5637
|
}
|
|
5025
5638
|
const spec = {
|
|
5026
5639
|
label,
|
|
5640
|
+
// A NEW COLUMN LANDS AT THE END (lane C, review 2026-09-26 C5): declared one past the last
|
|
5641
|
+
// column's `sort`, so every view and every client draws it last — never in the alphabetical
|
|
5642
|
+
// slot of its internal key.
|
|
5643
|
+
sort: nextFieldSort(fields.data),
|
|
5027
5644
|
...isRelationFieldType(type) ? {
|
|
5028
5645
|
type: "relation",
|
|
5029
5646
|
relation_target: relationTarget,
|
|
@@ -5642,10 +6259,10 @@ function entityReferenceKindsOf(field2) {
|
|
|
5642
6259
|
|
|
5643
6260
|
// src/gridColors.tsx
|
|
5644
6261
|
var import_react31 = require("react");
|
|
5645
|
-
var
|
|
6262
|
+
var import_records10 = require("@ai-matrx/records");
|
|
5646
6263
|
var import_react32 = require("@ai-matrx/records/react");
|
|
5647
6264
|
var import_design_system18 = require("@ai-matrx/design-system");
|
|
5648
|
-
var
|
|
6265
|
+
var import_data_table7 = require("@ai-matrx/design-system/data-table");
|
|
5649
6266
|
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
5650
6267
|
function isChoiceField(field2) {
|
|
5651
6268
|
const kind = String(editorKindFor(field2));
|
|
@@ -5701,7 +6318,7 @@ function useChoiceOptions(fields, keys) {
|
|
|
5701
6318
|
return index;
|
|
5702
6319
|
}
|
|
5703
6320
|
function lookOf(option) {
|
|
5704
|
-
return { id: String(option.id), key: (0,
|
|
6321
|
+
return { id: String(option.id), key: (0, import_records10.optionKey)(option), label: (0, import_records10.optionLabel)(option) };
|
|
5705
6322
|
}
|
|
5706
6323
|
function paintByOption(style, fields, index, lookup) {
|
|
5707
6324
|
if (index.size === 0) return { style, documentOf: (doc) => doc, choiceColorFor: lookup };
|
|
@@ -5761,7 +6378,7 @@ function rulesLeaf(drafts, fields) {
|
|
|
5761
6378
|
...d.id ? { id: d.id } : {},
|
|
5762
6379
|
field: field2.id,
|
|
5763
6380
|
op: d.op,
|
|
5764
|
-
...
|
|
6381
|
+
...import_data_table7.OPS_WITH_VALUE.includes(d.op) ? { value: d.value } : {},
|
|
5765
6382
|
color: d.color,
|
|
5766
6383
|
target: d.target
|
|
5767
6384
|
});
|
|
@@ -5770,9 +6387,9 @@ function rulesLeaf(drafts, fields) {
|
|
|
5770
6387
|
}
|
|
5771
6388
|
function swatches(prefix, current, write) {
|
|
5772
6389
|
return [
|
|
5773
|
-
...
|
|
6390
|
+
...import_data_table7.STYLE_COLORS.map((color) => ({
|
|
5774
6391
|
id: `${prefix}-${color}`,
|
|
5775
|
-
label: color === current ? `${
|
|
6392
|
+
label: color === current ? `${import_data_table7.STYLE_COLOR_LABELS[color]} (now)` : import_data_table7.STYLE_COLOR_LABELS[color],
|
|
5776
6393
|
onSelect: () => write(color)
|
|
5777
6394
|
})),
|
|
5778
6395
|
...current ? [{ id: `${prefix}-none`, label: "No highlight", onSelect: () => write(null) }] : []
|
|
@@ -5838,11 +6455,11 @@ function useGridColours(args) {
|
|
|
5838
6455
|
},
|
|
5839
6456
|
{
|
|
5840
6457
|
id: "col-highlight",
|
|
5841
|
-
label: column ? `Highlight column (${
|
|
6458
|
+
label: column ? `Highlight column (${import_data_table7.STYLE_COLOR_LABELS[column]})` : "Highlight column",
|
|
5842
6459
|
submenu: swatches(
|
|
5843
6460
|
"col-highlight",
|
|
5844
6461
|
column,
|
|
5845
|
-
(color) => void write(["columns", field2.id], color, color ? `${name} is highlighted ${
|
|
6462
|
+
(color) => void write(["columns", field2.id], color, color ? `${name} is highlighted ${import_data_table7.STYLE_COLOR_LABELS[color].toLowerCase()}.` : `${name} is no longer highlighted.`)
|
|
5846
6463
|
)
|
|
5847
6464
|
}
|
|
5848
6465
|
]
|
|
@@ -5907,7 +6524,7 @@ function useGridColours(args) {
|
|
|
5907
6524
|
}
|
|
5908
6525
|
var SELECT2 = "h-8 rounded border bg-background px-1 text-xs";
|
|
5909
6526
|
var CHOICE_OPS = ["is", "is_not", "is_empty", "not_empty"].filter(
|
|
5910
|
-
(op2) =>
|
|
6527
|
+
(op2) => import_data_table7.COLOR_RULE_OPS.includes(op2)
|
|
5911
6528
|
);
|
|
5912
6529
|
function ColourRulesDialog({
|
|
5913
6530
|
open,
|
|
@@ -5957,10 +6574,10 @@ function ColourRulesDialog({
|
|
|
5957
6574
|
className: SELECT2,
|
|
5958
6575
|
value: rule.op,
|
|
5959
6576
|
onChange: (e) => set(i, { op: e.target.value }),
|
|
5960
|
-
children: (choicesOf(rule.field) ?
|
|
6577
|
+
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))
|
|
5961
6578
|
}
|
|
5962
6579
|
),
|
|
5963
|
-
|
|
6580
|
+
import_data_table7.OPS_WITH_VALUE.includes(rule.op) && choicesOf(rule.field) ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
5964
6581
|
"select",
|
|
5965
6582
|
{
|
|
5966
6583
|
"aria-label": "Choice",
|
|
@@ -5973,7 +6590,7 @@ function ColourRulesDialog({
|
|
|
5973
6590
|
choicesOf(rule.field).map((option) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("option", { value: option.id, children: option.label }, option.id))
|
|
5974
6591
|
]
|
|
5975
6592
|
}
|
|
5976
|
-
) :
|
|
6593
|
+
) : import_data_table7.OPS_WITH_VALUE.includes(rule.op) ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5977
6594
|
"input",
|
|
5978
6595
|
{
|
|
5979
6596
|
"aria-label": "Value",
|
|
@@ -5996,7 +6613,7 @@ function ColourRulesDialog({
|
|
|
5996
6613
|
className: SELECT2,
|
|
5997
6614
|
value: rule.color,
|
|
5998
6615
|
onChange: (e) => set(i, { color: e.target.value }),
|
|
5999
|
-
children:
|
|
6616
|
+
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))
|
|
6000
6617
|
}
|
|
6001
6618
|
),
|
|
6002
6619
|
/* @__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" })
|
|
@@ -6039,207 +6656,9 @@ function ColourRulesDialog({
|
|
|
6039
6656
|
var import_react33 = require("react");
|
|
6040
6657
|
var import_react34 = require("@ai-matrx/records/react");
|
|
6041
6658
|
var import_design_system19 = require("@ai-matrx/design-system");
|
|
6042
|
-
|
|
6043
|
-
// src/gridMenus.ts
|
|
6044
|
-
function fieldOf(ctx, columnId) {
|
|
6045
|
-
return ctx.fields.find((f) => f.key === columnId);
|
|
6046
|
-
}
|
|
6047
|
-
function columnWords(ctx, columnId) {
|
|
6048
|
-
const field2 = fieldOf(ctx, columnId);
|
|
6049
|
-
return field2 ? fieldName(field2) : columnId;
|
|
6050
|
-
}
|
|
6051
|
-
function rowLabelRefusal(field2) {
|
|
6052
|
-
if (!field2) return "This column is not one of the table's own.";
|
|
6053
|
-
const kind = String(field2.type);
|
|
6054
|
-
if (kind === "formula" || kind === "lookup" || kind === "rollup") {
|
|
6055
|
-
return `${fieldName(field2)} is worked out by the table, and a record is named by a column people fill in.`;
|
|
6056
|
-
}
|
|
6057
|
-
if (kind === "relation") return `${fieldName(field2)} points at other records; a record is named by its own words.`;
|
|
6058
|
-
return void 0;
|
|
6059
|
-
}
|
|
6060
|
-
function headerMenuItems(columnId, ctx) {
|
|
6061
|
-
const field2 = fieldOf(ctx, columnId);
|
|
6062
|
-
const shape = ctx.mayStructure ? void 0 : ctx.whyNotStructure;
|
|
6063
|
-
const isLabel = ctx.titleField === columnId;
|
|
6064
|
-
const labelRefusal = shape ?? (isLabel ? "Records are already named by this column." : rowLabelRefusal(field2));
|
|
6065
|
-
const a = ctx.actions;
|
|
6066
|
-
return [
|
|
6067
|
-
{ id: "col-rename", label: "Rename column", icon: "pencil", disabledReason: shape, onSelect: () => a.renameColumn(columnId) },
|
|
6068
|
-
{ id: "col-settings", label: "Column settings\u2026", icon: "settings", disabledReason: shape, onSelect: () => a.columnSettings(columnId) },
|
|
6069
|
-
{ id: "col-insert-left", label: "Insert column left\u2026", icon: "panel-left", disabledReason: shape, onSelect: () => a.insertColumn(columnId, "left") },
|
|
6070
|
-
{ id: "col-insert-right", label: "Insert column right\u2026", icon: "panel-right", disabledReason: shape, onSelect: () => a.insertColumn(columnId, "right") },
|
|
6071
|
-
{
|
|
6072
|
-
id: "col-row-label",
|
|
6073
|
-
label: isLabel ? "Names each record" : "Use as row label",
|
|
6074
|
-
icon: "tag",
|
|
6075
|
-
disabledReason: labelRefusal,
|
|
6076
|
-
onSelect: () => a.useAsRowLabel(columnId)
|
|
6077
|
-
},
|
|
6078
|
-
...ctx.extras?.header?.(columnId) ?? [],
|
|
6079
|
-
{ id: "col-hide", label: "Hide column", icon: "eye-off", onSelect: () => a.hideColumn(columnId) },
|
|
6080
|
-
{
|
|
6081
|
-
id: "col-delete",
|
|
6082
|
-
label: "Delete column\u2026",
|
|
6083
|
-
icon: "trash",
|
|
6084
|
-
destructive: true,
|
|
6085
|
-
disabledReason: shape ?? (isLabel ? "Records are named by this column; pick another row label first." : void 0),
|
|
6086
|
-
onSelect: () => a.deleteColumn(columnId)
|
|
6087
|
-
}
|
|
6088
|
-
];
|
|
6089
|
-
}
|
|
6090
|
-
function cellSection(payload, ctx) {
|
|
6091
|
-
const a = ctx.actions;
|
|
6092
|
-
const { rowId, columnId } = payload;
|
|
6093
|
-
const mayWrite = ctx.mayWriteRow(rowId);
|
|
6094
|
-
const typeable = ctx.typeable(columnId);
|
|
6095
|
-
const why = !mayWrite ? ctx.whyNotWrite(rowId) : !typeable ? `${columnWords(ctx, columnId)} is worked out by the table; nobody types it.` : void 0;
|
|
6096
|
-
const items = [
|
|
6097
|
-
{ id: "cell-copy", label: "Copy cell", icon: "copy", shortcut: "\u2318C", onSelect: () => a.copyText(payload.text, "The cell") },
|
|
6098
|
-
{ id: "cell-paste", label: "Paste", icon: "clipboard-paste", shortcut: "\u2318V", disabledReason: why, onSelect: () => a.pasteInto(rowId, columnId) },
|
|
6099
|
-
{ id: "cell-clear", label: "Clear cell", icon: "eraser", destructive: true, disabledReason: why, onSelect: () => a.clearCells([{ rowId, columnId }]) }
|
|
6100
|
-
];
|
|
6101
|
-
if (a.askWhoChanged) {
|
|
6102
|
-
items.push({ id: "cell-history", label: "Who changed this?", icon: "history", shortcut: "\u2325\u21E7H", onSelect: () => a.askWhoChanged?.(columnId, rowId) });
|
|
6103
|
-
}
|
|
6104
|
-
const empty = payload.value === null || payload.value === void 0 || payload.value === "";
|
|
6105
|
-
items.push({
|
|
6106
|
-
id: "cell-filter",
|
|
6107
|
-
label: empty ? "Show rows where this is empty" : "Show rows with this value",
|
|
6108
|
-
icon: "filter",
|
|
6109
|
-
onSelect: () => a.filterBy(columnId, payload.value)
|
|
6110
|
-
});
|
|
6111
|
-
items.push(...ctx.extras?.cell?.(rowId, columnId) ?? []);
|
|
6112
|
-
return { id: "cell", title: payload.columnLabel || "Cell", items };
|
|
6113
|
-
}
|
|
6114
|
-
function rowSection(rowId, ctx, rowText) {
|
|
6115
|
-
const a = ctx.actions;
|
|
6116
|
-
const mayWrite = ctx.mayWriteRow(rowId);
|
|
6117
|
-
const items = [];
|
|
6118
|
-
if (a.openRecord) {
|
|
6119
|
-
items.push({ id: "row-open", label: "Open record", icon: "panel-right", shortcut: "Space", onSelect: () => a.openRecord?.(rowId) });
|
|
6120
|
-
items.push({ id: "row-history", label: "Row history", icon: "history", onSelect: () => a.openRecord?.(rowId) });
|
|
6121
|
-
}
|
|
6122
|
-
if (rowText !== null) {
|
|
6123
|
-
items.push({ id: "row-copy", label: "Copy row", icon: "copy", onSelect: () => a.copyText(rowText, "The row") });
|
|
6124
|
-
}
|
|
6125
|
-
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.";
|
|
6126
|
-
const insertWhy = !ctx.mayAddRecord ? ctx.whyNotAdd : a.insertRow ? void 0 : noPlace;
|
|
6127
|
-
items.push(
|
|
6128
|
-
{ id: "row-insert-above", label: "Insert row above", icon: "plus", disabledReason: insertWhy, onSelect: () => a.insertRow?.(rowId, "above") },
|
|
6129
|
-
{ id: "row-insert-below", label: "Insert row below", icon: "plus", disabledReason: insertWhy, onSelect: () => a.insertRow?.(rowId, "below") },
|
|
6130
|
-
{
|
|
6131
|
-
id: "row-duplicate",
|
|
6132
|
-
label: "Duplicate row",
|
|
6133
|
-
icon: "copy-plus",
|
|
6134
|
-
disabledReason: ctx.mayAddRecord ? void 0 : ctx.whyNotAdd,
|
|
6135
|
-
onSelect: () => a.duplicateRow(rowId)
|
|
6136
|
-
},
|
|
6137
|
-
{
|
|
6138
|
-
id: "row-archive",
|
|
6139
|
-
label: "Delete row\u2026",
|
|
6140
|
-
icon: "trash",
|
|
6141
|
-
destructive: true,
|
|
6142
|
-
disabledReason: mayWrite ? void 0 : ctx.whyNotWrite(rowId),
|
|
6143
|
-
onSelect: () => a.archiveRow(rowId)
|
|
6144
|
-
}
|
|
6145
|
-
);
|
|
6146
|
-
items.push(...ctx.extras?.row?.(rowId) ?? []);
|
|
6147
|
-
return { id: "row", title: ctx.extras?.rowTitle?.(rowId) || "Row", items };
|
|
6148
|
-
}
|
|
6149
|
-
function columnSection(columnId, columnLabel2, ctx) {
|
|
6150
|
-
const a = ctx.actions;
|
|
6151
|
-
const sorted = ctx.sortedBy?.id === columnId ? ctx.sortedBy.direction : null;
|
|
6152
|
-
return {
|
|
6153
|
-
id: "column",
|
|
6154
|
-
title: columnLabel2 || columnWords(ctx, columnId),
|
|
6155
|
-
items: [
|
|
6156
|
-
{ 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") },
|
|
6157
|
-
{ 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") },
|
|
6158
|
-
...sorted ? [{ id: "col-sort-clear", label: "Clear sort", icon: "arrow-up-down", onSelect: () => a.sortBy(columnId, null) }] : [],
|
|
6159
|
-
...headerMenuItems(columnId, ctx)
|
|
6160
|
-
]
|
|
6161
|
-
};
|
|
6162
|
-
}
|
|
6163
|
-
function tableSection(ctx) {
|
|
6164
|
-
const a = ctx.actions;
|
|
6165
|
-
const items = [
|
|
6166
|
-
{ id: "table-new-record", label: "New record", icon: "plus", disabledReason: ctx.mayAddRecord ? void 0 : ctx.whyNotAdd, onSelect: () => a.newRecord() }
|
|
6167
|
-
];
|
|
6168
|
-
if (a.addColumn) {
|
|
6169
|
-
items.push({
|
|
6170
|
-
id: "table-add-column",
|
|
6171
|
-
label: "Add column\u2026",
|
|
6172
|
-
icon: "plus",
|
|
6173
|
-
disabledReason: ctx.mayStructure ? void 0 : ctx.whyNotStructure,
|
|
6174
|
-
onSelect: () => a.addColumn?.()
|
|
6175
|
-
});
|
|
6176
|
-
}
|
|
6177
|
-
items.push(...ctx.extras?.table?.() ?? []);
|
|
6178
|
-
return { id: "table", title: "Table", items };
|
|
6179
|
-
}
|
|
6180
|
-
function gridMenuSections(payload, ctx, rowText = () => null) {
|
|
6181
|
-
switch (payload.level) {
|
|
6182
|
-
case "cell":
|
|
6183
|
-
return [
|
|
6184
|
-
{ ...cellSection(payload, ctx), primary: true },
|
|
6185
|
-
rowSection(payload.rowId, ctx, rowText(payload.rowId)),
|
|
6186
|
-
columnSection(payload.columnId, payload.columnLabel, ctx),
|
|
6187
|
-
tableSection(ctx)
|
|
6188
|
-
];
|
|
6189
|
-
case "row":
|
|
6190
|
-
return [{ ...rowSection(payload.rowId, ctx, rowText(payload.rowId)), primary: true }, tableSection(ctx)];
|
|
6191
|
-
case "column":
|
|
6192
|
-
return [{ ...columnSection(payload.columnId, payload.columnLabel, ctx), primary: true }, tableSection(ctx)];
|
|
6193
|
-
case "selection": {
|
|
6194
|
-
const n = payload.cellCount;
|
|
6195
|
-
const blocked = payload.rowIds.find((id) => !ctx.mayWriteRow(id));
|
|
6196
|
-
const untypeable = payload.columnIds.find((id) => !ctx.typeable(id));
|
|
6197
|
-
const why = blocked ? ctx.whyNotWrite(blocked) : untypeable ? `${columnWords(ctx, untypeable)} is worked out by the table; nobody types it.` : void 0;
|
|
6198
|
-
const cells = payload.rowIds.flatMap((rowId) => payload.columnIds.map((columnId) => ({ rowId, columnId })));
|
|
6199
|
-
return [
|
|
6200
|
-
{
|
|
6201
|
-
id: "selection",
|
|
6202
|
-
title: `${n} ${n === 1 ? "cell" : "cells"}`,
|
|
6203
|
-
primary: true,
|
|
6204
|
-
items: [
|
|
6205
|
-
{ id: "sel-copy", label: `Copy ${n} cells`, icon: "copy", shortcut: "\u2318C", onSelect: () => ctx.actions.copyText(payload.tsv, `${n} cells`) },
|
|
6206
|
-
{ id: "sel-clear", label: `Clear ${n} cells`, icon: "eraser", destructive: true, disabledReason: why, onSelect: () => ctx.actions.clearCells(cells) },
|
|
6207
|
-
{
|
|
6208
|
-
id: "sel-fill-down",
|
|
6209
|
-
label: "Fill down",
|
|
6210
|
-
icon: "arrow-down-to-line",
|
|
6211
|
-
shortcut: "\u2318D",
|
|
6212
|
-
disabledReason: why ?? (payload.rowIds.length < 2 ? "Select at least two rows to fill down." : void 0),
|
|
6213
|
-
onSelect: () => ctx.actions.fillDown(payload.rowIds, payload.columnIds)
|
|
6214
|
-
}
|
|
6215
|
-
]
|
|
6216
|
-
},
|
|
6217
|
-
tableSection(ctx)
|
|
6218
|
-
];
|
|
6219
|
-
}
|
|
6220
|
-
case "table":
|
|
6221
|
-
return [{ ...tableSection(ctx), primary: true }];
|
|
6222
|
-
}
|
|
6223
|
-
}
|
|
6224
|
-
function placeColumn(order, newKey, anchor, side) {
|
|
6225
|
-
const rest = order.filter((k) => k !== newKey);
|
|
6226
|
-
const at = rest.indexOf(anchor);
|
|
6227
|
-
if (at < 0) return [...rest, newKey];
|
|
6228
|
-
const index = side === "left" ? at : at + 1;
|
|
6229
|
-
return [...rest.slice(0, index), newKey, ...rest.slice(index)];
|
|
6230
|
-
}
|
|
6231
|
-
function columnsInKeptOrder(fieldKeys, kept) {
|
|
6232
|
-
if (!kept || kept.length === 0) return [...fieldKeys];
|
|
6233
|
-
const known = new Set(fieldKeys);
|
|
6234
|
-
const named = kept.filter((k, i) => known.has(k) && kept.indexOf(k) === i);
|
|
6235
|
-
const namedSet = new Set(named);
|
|
6236
|
-
return [...named, ...fieldKeys.filter((k) => !namedSet.has(k))];
|
|
6237
|
-
}
|
|
6238
|
-
|
|
6239
|
-
// src/gridRowLabel.tsx
|
|
6240
6659
|
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
6241
6660
|
function labelColumns(fields) {
|
|
6242
|
-
return [...fields].filter((f) => rowLabelRefusal(f) === void 0).sort(
|
|
6661
|
+
return [...fields].filter((f) => rowLabelRefusal(f) === void 0).sort(compareFieldOrder);
|
|
6243
6662
|
}
|
|
6244
6663
|
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.";
|
|
6245
6664
|
function RowLabelPicker({
|
|
@@ -6333,7 +6752,7 @@ function RowLabelPicker({
|
|
|
6333
6752
|
// src/gridSummaries.tsx
|
|
6334
6753
|
var import_react35 = require("react");
|
|
6335
6754
|
var import_react36 = require("@ai-matrx/records/react");
|
|
6336
|
-
var
|
|
6755
|
+
var import_records11 = require("@ai-matrx/records");
|
|
6337
6756
|
var import_design_system20 = require("@ai-matrx/design-system");
|
|
6338
6757
|
|
|
6339
6758
|
// src/gridLook.ts
|
|
@@ -6533,7 +6952,7 @@ function useColumnSummaries(args) {
|
|
|
6533
6952
|
const out = {};
|
|
6534
6953
|
for (const [key, op2] of entries) {
|
|
6535
6954
|
const measure = op2 === "count" ? { op: "count" } : { op: op2, key };
|
|
6536
|
-
out[key] = row?.measures?.[(0,
|
|
6955
|
+
out[key] = row?.measures?.[(0, import_records11.measureKey)(measure)] ?? null;
|
|
6537
6956
|
}
|
|
6538
6957
|
setValues(out);
|
|
6539
6958
|
setRowCount(row?.row_count ?? 0);
|
|
@@ -6584,7 +7003,10 @@ function SummaryCell({
|
|
|
6584
7003
|
"data-records-summary": field2.key,
|
|
6585
7004
|
className: (0, import_design_system20.cn)(
|
|
6586
7005
|
"h-6 max-w-full justify-start gap-1 px-1 text-xs font-normal",
|
|
6587
|
-
|
|
7006
|
+
// AN UNCHOSEN SUMMARY IS A HOVER, NOT A WORD IN EVERY FOOTER CELL (lane C, review
|
|
7007
|
+
// 2026-09-26 E1 — Airtable's footer): it shows under the pointer, on keyboard focus and
|
|
7008
|
+
// while its menu is open; the header menu's "Summarize column" is the other door.
|
|
7009
|
+
chosen ? "text-foreground" : "text-muted-foreground opacity-0 hover:opacity-100 focus-visible:opacity-100 data-[state=open]:opacity-100"
|
|
6588
7010
|
),
|
|
6589
7011
|
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)}`,
|
|
6590
7012
|
children: chosen ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
|
|
@@ -6633,7 +7055,7 @@ function summaryMenuItem(field2, firstColumn, chosen, operations, onChoose) {
|
|
|
6633
7055
|
|
|
6634
7056
|
// src/pasteValues.ts
|
|
6635
7057
|
var import_core4 = require("@ai-matrx/records/core");
|
|
6636
|
-
var
|
|
7058
|
+
var import_records12 = require("@ai-matrx/records");
|
|
6637
7059
|
function wordForStoredChoice(options, value) {
|
|
6638
7060
|
if (options === void 0 || typeof value !== "string" || value === "") return null;
|
|
6639
7061
|
for (const label of options.labels) {
|
|
@@ -6646,7 +7068,7 @@ function choiceOptionsOf(options) {
|
|
|
6646
7068
|
const labels = [];
|
|
6647
7069
|
for (const option of options) {
|
|
6648
7070
|
const id = option.id;
|
|
6649
|
-
const key = (0,
|
|
7071
|
+
const key = (0, import_records12.optionKey)(option);
|
|
6650
7072
|
const label = recordName(option.data, null, "");
|
|
6651
7073
|
if (label) {
|
|
6652
7074
|
labels.push(label);
|
|
@@ -6658,7 +7080,7 @@ function choiceOptionsOf(options) {
|
|
|
6658
7080
|
return { byWord, labels };
|
|
6659
7081
|
}
|
|
6660
7082
|
function valueFromPastedText(field2, raw, options) {
|
|
6661
|
-
return (0,
|
|
7083
|
+
return (0, import_records12.coerceTypedAnswer)(field2, raw, {
|
|
6662
7084
|
label: fieldName(field2),
|
|
6663
7085
|
...options ? { options } : {}
|
|
6664
7086
|
});
|
|
@@ -6849,8 +7271,31 @@ function useMergedGridControls(args) {
|
|
|
6849
7271
|
if (!made) return;
|
|
6850
7272
|
pendingInsert.current = null;
|
|
6851
7273
|
if (pending.anchor) keepColumns({ order: placeColumn(order, made, pending.anchor, pending.side), hidden });
|
|
6852
|
-
|
|
7274
|
+
else if (keptOrder && keptOrder.length > 0) keepColumns({ order: [...order.filter((k) => k !== made), made], hidden });
|
|
7275
|
+
}, [fieldKeys, order, hidden, keepColumns, keptOrder]);
|
|
6853
7276
|
const fieldOf2 = (0, import_react37.useCallback)((key) => fields.find((f) => f.key === key), [fields]);
|
|
7277
|
+
const choiceKeys = (0, import_react37.useMemo)(() => enabled ? fields.filter(isChoiceField).map((f) => f.key) : [], [enabled, fields]);
|
|
7278
|
+
const choiceOptions = useChoiceOptions(fields, choiceKeys);
|
|
7279
|
+
const filterFor = (0, import_react37.useCallback)(
|
|
7280
|
+
(key) => {
|
|
7281
|
+
if (!enabled) return void 0;
|
|
7282
|
+
const field2 = fieldOf2(key);
|
|
7283
|
+
if (!field2) return void 0;
|
|
7284
|
+
const kind = String(editorKindFor(field2));
|
|
7285
|
+
if (kind === "number" || kind === "currency" || kind === "percent" || kind === "rating") return { filter: "number" };
|
|
7286
|
+
if (kind === "datetime" || kind === "date") return { filter: "date" };
|
|
7287
|
+
if (kind === "checkbox") return { filter: "boolean" };
|
|
7288
|
+
if (!isChoiceField(field2)) return void 0;
|
|
7289
|
+
const options = choiceOptions.get(key);
|
|
7290
|
+
if (!options || options.length === 0) return { filter: "select" };
|
|
7291
|
+
const holdsKeys = rows.some((row) => {
|
|
7292
|
+
const v = (row.document ?? {})[key];
|
|
7293
|
+
return (Array.isArray(v) ? v : [v]).some((x) => options.some((o) => o.key === x && o.id !== x));
|
|
7294
|
+
});
|
|
7295
|
+
return { filter: "select", filterOptions: options.map((o) => ({ value: holdsKeys ? o.key : o.id, label: o.label })) };
|
|
7296
|
+
},
|
|
7297
|
+
[enabled, fieldOf2, choiceOptions, rows]
|
|
7298
|
+
);
|
|
6854
7299
|
const copyText = (0, import_react37.useCallback)(
|
|
6855
7300
|
(text, what) => {
|
|
6856
7301
|
const clip = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
|
|
@@ -7136,7 +7581,7 @@ function useMergedGridControls(args) {
|
|
|
7136
7581
|
},
|
|
7137
7582
|
children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_design_system21.DialogContent, { className: "max-h-[85vh] overflow-y-auto sm:max-w-2xl", children: [
|
|
7138
7583
|
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_design_system21.DialogHeader, { children: [
|
|
7139
|
-
/* @__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 ? `
|
|
7584
|
+
/* @__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 }),
|
|
7140
7585
|
/* @__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." })
|
|
7141
7586
|
] }),
|
|
7142
7587
|
dialog?.kind === "settings" ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
@@ -7198,6 +7643,7 @@ function useMergedGridControls(args) {
|
|
|
7198
7643
|
] });
|
|
7199
7644
|
return {
|
|
7200
7645
|
headerMenuFor,
|
|
7646
|
+
filterFor,
|
|
7201
7647
|
contextMenu,
|
|
7202
7648
|
columns: enabled ? { order, hidden, onChange: keepColumns } : void 0,
|
|
7203
7649
|
dialogs,
|
|
@@ -7225,9 +7671,11 @@ function useAddedRows(args) {
|
|
|
7225
7671
|
const { tableId, enabled, pageRows, pageKey } = args;
|
|
7226
7672
|
const client = (0, import_react40.useRecordsClient)();
|
|
7227
7673
|
const [added, setAdded] = (0, import_react39.useState)([]);
|
|
7674
|
+
const [anchors, setAnchors] = (0, import_react39.useState)(() => /* @__PURE__ */ new Map());
|
|
7228
7675
|
const [busy, setBusy] = (0, import_react39.useState)(false);
|
|
7229
7676
|
(0, import_react39.useEffect)(() => {
|
|
7230
7677
|
setAdded([]);
|
|
7678
|
+
setAnchors(/* @__PURE__ */ new Map());
|
|
7231
7679
|
}, [pageKey, tableId]);
|
|
7232
7680
|
const pinnedKey = added.map((r) => r.id).join(",");
|
|
7233
7681
|
const asked = (0, import_react39.useRef)(0);
|
|
@@ -7249,11 +7697,11 @@ function useAddedRows(args) {
|
|
|
7249
7697
|
);
|
|
7250
7698
|
});
|
|
7251
7699
|
}, [enabled, pageRows, pinnedKey, client, tableId]);
|
|
7252
|
-
const rows = (0, import_react39.useMemo)(() =>
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
}, [
|
|
7700
|
+
const rows = (0, import_react39.useMemo)(() => enabled ? placePinnedRows(pageRows, added, anchors) : pageRows, [enabled, pageRows, added, anchors]);
|
|
7701
|
+
const pin = (0, import_react39.useCallback)((row, after) => {
|
|
7702
|
+
setAdded((held) => held.some((r) => r.id === row.id) ? held : [...held, row]);
|
|
7703
|
+
if (after) setAnchors((held) => new Map(held).set(row.id, after));
|
|
7704
|
+
}, []);
|
|
7257
7705
|
const add = (0, import_react39.useCallback)(async () => {
|
|
7258
7706
|
setBusy(true);
|
|
7259
7707
|
const written = await client.recordWrite({ table_id: tableId, data: {} });
|
|
@@ -7263,7 +7711,29 @@ function useAddedRows(args) {
|
|
|
7263
7711
|
setAdded((held) => held.some((r) => r.id === id) ? held : [...held, { id, document: {}, level: "editor", hidden: {} }]);
|
|
7264
7712
|
return { id };
|
|
7265
7713
|
}, [client, tableId]);
|
|
7266
|
-
return { rows, add, busy };
|
|
7714
|
+
return { rows, add, pin, busy };
|
|
7715
|
+
}
|
|
7716
|
+
function placePinnedRows(pageRows, pinned, anchors) {
|
|
7717
|
+
if (pinned.length === 0) return [...pageRows];
|
|
7718
|
+
const pageIds = new Set(pageRows.map((r) => r.id));
|
|
7719
|
+
const fresh = new Map(pageRows.map((r) => [r.id, r]));
|
|
7720
|
+
const below = /* @__PURE__ */ new Map();
|
|
7721
|
+
const placed = /* @__PURE__ */ new Set();
|
|
7722
|
+
for (const row of pinned) {
|
|
7723
|
+
const anchor = anchors.get(row.id);
|
|
7724
|
+
if (anchor && anchor !== row.id && pageIds.has(anchor)) {
|
|
7725
|
+
below.set(anchor, [...below.get(anchor) ?? [], fresh.get(row.id) ?? row]);
|
|
7726
|
+
placed.add(row.id);
|
|
7727
|
+
}
|
|
7728
|
+
}
|
|
7729
|
+
const out = [];
|
|
7730
|
+
const walk = (row) => {
|
|
7731
|
+
out.push(row);
|
|
7732
|
+
for (const child of below.get(row.id) ?? []) walk(child);
|
|
7733
|
+
};
|
|
7734
|
+
for (const row of pageRows) if (!placed.has(row.id)) walk(row);
|
|
7735
|
+
for (const row of pinned) if (!pageIds.has(row.id) && !placed.has(row.id)) out.push(row);
|
|
7736
|
+
return out;
|
|
7267
7737
|
}
|
|
7268
7738
|
function withAddRowCells(columns, line) {
|
|
7269
7739
|
if (line === null) return columns;
|
|
@@ -7785,6 +8255,10 @@ function reorderItems(rows, sort, label) {
|
|
|
7785
8255
|
}
|
|
7786
8256
|
return list.map((row) => ({ id: row.id, label: label(row) }));
|
|
7787
8257
|
}
|
|
8258
|
+
function fieldColumnIds(ids, fields) {
|
|
8259
|
+
const known = new Set(fields.map((f) => f.key));
|
|
8260
|
+
return ids.filter((id) => known.has(id));
|
|
8261
|
+
}
|
|
7788
8262
|
function columnLabel(fields, columnId) {
|
|
7789
8263
|
const field2 = fields.find((f) => f.key === columnId);
|
|
7790
8264
|
return field2 ? fieldName(field2) : humanize(columnId);
|
|
@@ -7796,8 +8270,8 @@ function AddFieldButton({ onClick }) {
|
|
|
7796
8270
|
size: "sm",
|
|
7797
8271
|
variant: "outline",
|
|
7798
8272
|
onClick,
|
|
7799
|
-
"aria-label": "Add a
|
|
7800
|
-
title: "Add a
|
|
8273
|
+
"aria-label": "Add a column",
|
|
8274
|
+
title: "Add a column to this table",
|
|
7801
8275
|
children: "+"
|
|
7802
8276
|
}
|
|
7803
8277
|
);
|
|
@@ -7864,7 +8338,8 @@ function columnForField(field2, editing, mayWriteRow, whyNotRow, look, onAskWhoC
|
|
|
7864
8338
|
] }) : name,
|
|
7865
8339
|
label: name,
|
|
7866
8340
|
accessorFn: (row) => row.document?.[field2.key],
|
|
7867
|
-
|
|
8341
|
+
// A column the store keeps no value for is not offered a sort it would refuse (`sortRefusal`).
|
|
8342
|
+
sortable: sortRefusal(field2) === null,
|
|
7868
8343
|
...format ? { format } : {},
|
|
7869
8344
|
/*
|
|
7870
8345
|
* A frozen column MUST declare a width or the table declines to freeze at
|
|
@@ -7872,7 +8347,7 @@ function columnForField(field2, editing, mayWriteRow, whyNotRow, look, onAskWhoC
|
|
|
7872
8347
|
* frozen column gets one here.
|
|
7873
8348
|
*/
|
|
7874
8349
|
...look?.frozen ? { frozen: true, width: FROZEN_COLUMN_WIDTH } : {},
|
|
7875
|
-
filter: editorKindFor(field2) === "select" || editorKindFor(field2) === "multi_select" ? "select" : "auto",
|
|
8350
|
+
filter: filterRefusal(field2) !== null ? false : editorKindFor(field2) === "select" || editorKindFor(field2) === "multi_select" ? "select" : "auto",
|
|
7876
8351
|
cell: (row) => (
|
|
7877
8352
|
/*
|
|
7878
8353
|
* 🚨 A READING QUESTION IS NOT AN EDITING RIGHT. This used to read
|
|
@@ -8115,7 +8590,7 @@ function confirmLabel(plan, noun, nounPlural) {
|
|
|
8115
8590
|
// src/gridActions.tsx
|
|
8116
8591
|
var import_react44 = require("react");
|
|
8117
8592
|
var import_react45 = require("@ai-matrx/records/react");
|
|
8118
|
-
var
|
|
8593
|
+
var import_records13 = require("@ai-matrx/records");
|
|
8119
8594
|
var import_design_system26 = require("@ai-matrx/design-system");
|
|
8120
8595
|
var import_jsx_runtime30 = require("react/jsx-runtime");
|
|
8121
8596
|
var actionListeners = /* @__PURE__ */ new Map();
|
|
@@ -8208,7 +8683,7 @@ function useActionRunner(args) {
|
|
|
8208
8683
|
const answered = await client.actionRun({ action_id: action.id, record_ids: ids });
|
|
8209
8684
|
setBusy(null);
|
|
8210
8685
|
if (!answered.ok) {
|
|
8211
|
-
const refusals = (0,
|
|
8686
|
+
const refusals = (0, import_records13.actionRefusals)(answered.error);
|
|
8212
8687
|
setOutcome({
|
|
8213
8688
|
kind: "refused",
|
|
8214
8689
|
action,
|
|
@@ -8458,6 +8933,14 @@ function InTableToolbar({ children }) {
|
|
|
8458
8933
|
if (slot === null) return null;
|
|
8459
8934
|
return (0, import_react_dom.createPortal)(children, slot);
|
|
8460
8935
|
}
|
|
8936
|
+
var TableToolbarFoldContext = (0, import_react46.createContext)(0);
|
|
8937
|
+
function useTableToolbarFold() {
|
|
8938
|
+
return (0, import_react46.useContext)(TableToolbarFoldContext);
|
|
8939
|
+
}
|
|
8940
|
+
var TableToolbarSheetExtrasContext = (0, import_react46.createContext)(void 0);
|
|
8941
|
+
function useTableToolbarSheetExtras() {
|
|
8942
|
+
return (0, import_react46.useContext)(TableToolbarSheetExtrasContext);
|
|
8943
|
+
}
|
|
8461
8944
|
|
|
8462
8945
|
// src/GridLayoutMenu.tsx
|
|
8463
8946
|
var import_react47 = require("react");
|
|
@@ -8567,114 +9050,10 @@ function GridLayoutMenu({
|
|
|
8567
9050
|
] });
|
|
8568
9051
|
}
|
|
8569
9052
|
|
|
8570
|
-
// src/gridPage.ts
|
|
8571
|
-
var import_records12 = require("@ai-matrx/records");
|
|
8572
|
-
var import_data_table7 = require("@ai-matrx/design-system/data-table");
|
|
8573
|
-
function sortAsFor(field2) {
|
|
8574
|
-
const kind = String(editorKindFor(field2));
|
|
8575
|
-
if (kind === "number" || kind === "currency" || kind === "percent") return "number";
|
|
8576
|
-
if (kind === "datetime") return "date";
|
|
8577
|
-
return "text";
|
|
8578
|
-
}
|
|
8579
|
-
function pageSortFor(fields, sort) {
|
|
8580
|
-
if (!sort) return [];
|
|
8581
|
-
const field2 = fields.find((f) => f.key === sort.id);
|
|
8582
|
-
if (!field2) return [];
|
|
8583
|
-
return [{ field: field2.key, direction: sort.direction === "desc" ? "desc" : "asc", as: sortAsFor(field2) }];
|
|
8584
|
-
}
|
|
8585
|
-
function containsPattern(text) {
|
|
8586
|
-
return `(?i)${text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`;
|
|
8587
|
-
}
|
|
8588
|
-
var field = (f) => ({ field: f.id });
|
|
8589
|
-
var constant = (value) => ({ const: value });
|
|
8590
|
-
var op = (name, ...args) => ({ op: name, args });
|
|
8591
|
-
function clauseFor(f, value, now = /* @__PURE__ */ new Date()) {
|
|
8592
|
-
const name = fieldName(f);
|
|
8593
|
-
const not = (rule) => op("not", rule);
|
|
8594
|
-
switch (value.kind) {
|
|
8595
|
-
case "text": {
|
|
8596
|
-
const mode = value.mode ?? "contains";
|
|
8597
|
-
if (mode === "empty" || mode === "null") return value.negated ? { rule: op("present", field(f)) } : { rule: not(op("present", field(f))) };
|
|
8598
|
-
if (mode === "not_empty" || mode === "not_null") return value.negated ? { rule: not(op("present", field(f))) } : { rule: op("present", field(f)) };
|
|
8599
|
-
const text = value.value.trim();
|
|
8600
|
-
if (text === "") return null;
|
|
8601
|
-
const kind = String(editorKindFor(f));
|
|
8602
|
-
if (kind === "number" || kind === "currency" || kind === "percent" || kind === "checkbox") {
|
|
8603
|
-
return `${name} holds numbers, so it is filtered by a range, not by words.`;
|
|
8604
|
-
}
|
|
8605
|
-
const matches2 = op("matches", field(f), constant(containsPattern(text)));
|
|
8606
|
-
return { rule: value.negated ? not(matches2) : matches2 };
|
|
8607
|
-
}
|
|
8608
|
-
case "select": {
|
|
8609
|
-
const words2 = value.values ?? (value.value === "" ? [] : [value.value]);
|
|
8610
|
-
if (words2.length === 0) return value.negated ? null : { rule: constant(false) };
|
|
8611
|
-
if (words2.length === 1 && !value.negated) return { flat: { [f.key]: words2[0] } };
|
|
8612
|
-
const any = words2.length === 1 ? op("eq", field(f), constant(words2[0])) : op("or", ...words2.map((w) => op("eq", field(f), constant(w))));
|
|
8613
|
-
return { rule: value.negated ? not(any) : any };
|
|
8614
|
-
}
|
|
8615
|
-
case "boolean":
|
|
8616
|
-
return value.negated ? { rule: op("ne", field(f), constant(value.value)) } : { flat: { [f.key]: value.value } };
|
|
8617
|
-
case "number": {
|
|
8618
|
-
const parts = [];
|
|
8619
|
-
if (typeof value.min === "number") parts.push(op("gte", field(f), constant(value.min)));
|
|
8620
|
-
if (typeof value.max === "number") parts.push(op("lte", field(f), constant(value.max)));
|
|
8621
|
-
if (parts.length === 0) return null;
|
|
8622
|
-
const range = parts.length === 1 ? parts[0] : op("and", ...parts);
|
|
8623
|
-
return { rule: value.negated ? not(range) : range };
|
|
8624
|
-
}
|
|
8625
|
-
case "date": {
|
|
8626
|
-
const bounds = (0, import_data_table7.dateFilterBounds)(value, now);
|
|
8627
|
-
if (bounds.since === void 0 && bounds.until === void 0) return null;
|
|
8628
|
-
return {
|
|
8629
|
-
flat: {
|
|
8630
|
-
[f.key]: {
|
|
8631
|
-
...bounds.since === void 0 ? {} : { from: bounds.since },
|
|
8632
|
-
...bounds.until === void 0 ? {} : { to: bounds.until }
|
|
8633
|
-
}
|
|
8634
|
-
}
|
|
8635
|
-
};
|
|
8636
|
-
}
|
|
8637
|
-
default:
|
|
8638
|
-
return `${name} cannot be filtered that way by the store.`;
|
|
8639
|
-
}
|
|
8640
|
-
}
|
|
8641
|
-
function storeQuestion(fields, base, columnFilters, now = /* @__PURE__ */ new Date()) {
|
|
8642
|
-
const refused = [];
|
|
8643
|
-
const flat = {};
|
|
8644
|
-
const rules = [];
|
|
8645
|
-
if (base && (0, import_records12.isRuleFilter)(base)) rules.push(base);
|
|
8646
|
-
else if (base) Object.assign(flat, base);
|
|
8647
|
-
for (const [key, value] of Object.entries(columnFilters)) {
|
|
8648
|
-
if (!value) continue;
|
|
8649
|
-
const f = fields.find((candidate) => candidate.key === key);
|
|
8650
|
-
if (!f) continue;
|
|
8651
|
-
const clause = clauseFor(f, value, now);
|
|
8652
|
-
if (clause === null) continue;
|
|
8653
|
-
if (typeof clause === "string") {
|
|
8654
|
-
refused.push(clause);
|
|
8655
|
-
continue;
|
|
8656
|
-
}
|
|
8657
|
-
if ("flat" in clause) Object.assign(flat, clause.flat);
|
|
8658
|
-
else rules.push(clause.rule);
|
|
8659
|
-
}
|
|
8660
|
-
const flatKeys = Object.keys(flat);
|
|
8661
|
-
if (rules.length === 0) return { filter: flatKeys.length > 0 ? flat : void 0, refused };
|
|
8662
|
-
for (const key of flatKeys) {
|
|
8663
|
-
const f = fields.find((candidate) => candidate.key === key);
|
|
8664
|
-
const value = flat[key];
|
|
8665
|
-
if (!f) continue;
|
|
8666
|
-
if (value !== null && typeof value === "object") {
|
|
8667
|
-
refused.push(`${fieldName(f)}'s date range cannot be asked together with the other filters yet, so it is left off.`);
|
|
8668
|
-
continue;
|
|
8669
|
-
}
|
|
8670
|
-
rules.push(value === null ? op("not", op("present", field(f))) : op("eq", field(f), constant(value)));
|
|
8671
|
-
}
|
|
8672
|
-
return { filter: rules.length === 1 ? rules[0] : op("and", ...rules), refused };
|
|
8673
|
-
}
|
|
8674
|
-
|
|
8675
9053
|
// src/gridUndo.tsx
|
|
8676
9054
|
var import_react49 = require("react");
|
|
8677
9055
|
var import_react50 = require("@ai-matrx/records/react");
|
|
9056
|
+
var import_core5 = require("@ai-matrx/records/core");
|
|
8678
9057
|
var import_design_system28 = require("@ai-matrx/design-system");
|
|
8679
9058
|
var import_data_table8 = require("@ai-matrx/design-system/data-table");
|
|
8680
9059
|
var import_jsx_runtime34 = require("react/jsx-runtime");
|
|
@@ -8701,6 +9080,7 @@ function useGridUndo(args) {
|
|
|
8701
9080
|
const [busy, setBusy] = (0, import_react49.useState)(false);
|
|
8702
9081
|
const [notice, setNotice] = (0, import_react49.useState)(null);
|
|
8703
9082
|
const counter = (0, import_react49.useRef)(0);
|
|
9083
|
+
const dismiss = (0, import_react49.useCallback)(() => setNotice(null), []);
|
|
8704
9084
|
const push = (0, import_react49.useCallback)((step2) => {
|
|
8705
9085
|
if (step2.cells.length === 0 && !step2.created?.length && !step2.archived?.length) return;
|
|
8706
9086
|
counter.current += 1;
|
|
@@ -8740,6 +9120,9 @@ function useGridUndo(args) {
|
|
|
8740
9120
|
return;
|
|
8741
9121
|
}
|
|
8742
9122
|
forget(step2.cells.map((c) => ({ rowId: c.rowId, key: c.key })));
|
|
9123
|
+
for (const id of /* @__PURE__ */ new Set([...step2.cells.map((c) => c.rowId), ...step2.created ?? [], ...step2.archived ?? []])) {
|
|
9124
|
+
(0, import_core5.announceRecordChanged)(id);
|
|
9125
|
+
}
|
|
8743
9126
|
setHeld(() => {
|
|
8744
9127
|
let stack = taken.stack;
|
|
8745
9128
|
if (direction === "undo" && ((step2.created?.length ?? 0) > 0 || step2.noRedo)) {
|
|
@@ -8768,49 +9151,64 @@ function useGridUndo(args) {
|
|
|
8768
9151
|
depth: { back: held.stack.past.length, forward: held.stack.future.length },
|
|
8769
9152
|
busy,
|
|
8770
9153
|
notice,
|
|
8771
|
-
dismiss
|
|
9154
|
+
dismiss,
|
|
9155
|
+
say: (says, sticky = false) => setNotice({ says, refusal: sticky ? { code: "internal", message: says } : null, canUndo: false })
|
|
8772
9156
|
}),
|
|
8773
|
-
[push, undo, redo, held, busy, notice]
|
|
9157
|
+
[push, undo, redo, held, busy, notice, dismiss]
|
|
8774
9158
|
);
|
|
8775
9159
|
}
|
|
8776
9160
|
function UndoRedoButtons({ undo }) {
|
|
8777
9161
|
if (undo.depth.back === 0 && undo.depth.forward === 0) return null;
|
|
8778
9162
|
return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
|
|
8779
|
-
|
|
9163
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
|
|
8780
9164
|
import_design_system28.Button,
|
|
8781
9165
|
{
|
|
8782
9166
|
size: "sm",
|
|
8783
9167
|
variant: "ghost",
|
|
8784
9168
|
"data-records-undo": "",
|
|
8785
|
-
title: `Undo (Cmd-Z) \xB7 ${undo.depth.back} ${undo.depth.back === 1 ? "step" : "steps"} back
|
|
8786
|
-
|
|
9169
|
+
title: undo.depth.back > 0 ? `Undo (Cmd-Z) \xB7 ${undo.depth.back} ${undo.depth.back === 1 ? "step" : "steps"} back` : "Nothing to undo",
|
|
9170
|
+
"aria-label": `Undo, ${undo.depth.back} ${undo.depth.back === 1 ? "step" : "steps"}`,
|
|
9171
|
+
disabled: undo.busy || undo.depth.back === 0,
|
|
8787
9172
|
onClick: () => void undo.undo(),
|
|
8788
|
-
children:
|
|
9173
|
+
children: [
|
|
9174
|
+
"Undo ",
|
|
9175
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "ml-1 tabular-nums text-muted-foreground", "data-records-undo-count": "", children: undo.depth.back })
|
|
9176
|
+
]
|
|
8789
9177
|
}
|
|
8790
|
-
)
|
|
8791
|
-
|
|
9178
|
+
),
|
|
9179
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
|
|
8792
9180
|
import_design_system28.Button,
|
|
8793
9181
|
{
|
|
8794
9182
|
size: "sm",
|
|
8795
9183
|
variant: "ghost",
|
|
8796
9184
|
"data-records-redo": "",
|
|
8797
|
-
title: `Redo (Shift-Cmd-Z) \xB7 ${undo.depth.forward} ${undo.depth.forward === 1 ? "step" : "steps"} forward
|
|
8798
|
-
|
|
9185
|
+
title: undo.depth.forward > 0 ? `Redo (Shift-Cmd-Z) \xB7 ${undo.depth.forward} ${undo.depth.forward === 1 ? "step" : "steps"} forward` : "Nothing to redo",
|
|
9186
|
+
"aria-label": `Redo, ${undo.depth.forward} ${undo.depth.forward === 1 ? "step" : "steps"}`,
|
|
9187
|
+
disabled: undo.busy || undo.depth.forward === 0,
|
|
8799
9188
|
onClick: () => void undo.redo(),
|
|
8800
|
-
children:
|
|
9189
|
+
children: [
|
|
9190
|
+
"Redo ",
|
|
9191
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "ml-1 tabular-nums text-muted-foreground", "data-records-redo-count": "", children: undo.depth.forward })
|
|
9192
|
+
]
|
|
8801
9193
|
}
|
|
8802
|
-
)
|
|
9194
|
+
)
|
|
8803
9195
|
] });
|
|
8804
9196
|
}
|
|
8805
9197
|
function UndoNotice({ undo }) {
|
|
8806
9198
|
const notice = undo.notice;
|
|
9199
|
+
const dismiss = undo.dismiss;
|
|
9200
|
+
(0, import_react49.useEffect)(() => {
|
|
9201
|
+
if (!notice || notice.refusal) return;
|
|
9202
|
+
const timer = setTimeout(dismiss, 8e3);
|
|
9203
|
+
return () => clearTimeout(timer);
|
|
9204
|
+
}, [notice, dismiss]);
|
|
8807
9205
|
if (!notice) return null;
|
|
8808
9206
|
return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
|
|
8809
9207
|
"p",
|
|
8810
9208
|
{
|
|
8811
9209
|
role: "status",
|
|
8812
9210
|
"data-records-undo-notice": notice.refusal ? "refused" : "done",
|
|
8813
|
-
className: "flex items-center gap-2 rounded border border-border bg-
|
|
9211
|
+
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",
|
|
8814
9212
|
children: [
|
|
8815
9213
|
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { children: notice.says }),
|
|
8816
9214
|
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,
|
|
@@ -8832,7 +9230,7 @@ function undoKey(event) {
|
|
|
8832
9230
|
|
|
8833
9231
|
// src/gridAssist.tsx
|
|
8834
9232
|
var import_react52 = require("react");
|
|
8835
|
-
var
|
|
9233
|
+
var import_core6 = require("@ai-matrx/records/core");
|
|
8836
9234
|
var import_content_transfer = require("@ai-matrx/design-system/content-transfer");
|
|
8837
9235
|
|
|
8838
9236
|
// src/gridAgentContext.ts
|
|
@@ -9102,7 +9500,7 @@ function useGridAssist(args) {
|
|
|
9102
9500
|
if (!row) return;
|
|
9103
9501
|
const document2 = row.document ?? {};
|
|
9104
9502
|
const value = editing.valueFor(row, fieldOf2(key));
|
|
9105
|
-
const pointer = typeof document2[key] === "string" ? (0,
|
|
9503
|
+
const pointer = typeof document2[key] === "string" ? (0, import_core6.wholeValueOf)(document2, key) : null;
|
|
9106
9504
|
setWhole({ rowId, key, rowTitle: titleOf(row), value, pointer });
|
|
9107
9505
|
},
|
|
9108
9506
|
[rowOf, fieldOf2, titleOf, editing]
|
|
@@ -9195,6 +9593,7 @@ function useGridAssist(args) {
|
|
|
9195
9593
|
// src/gridBulk.tsx
|
|
9196
9594
|
var import_react53 = require("react");
|
|
9197
9595
|
var import_react54 = require("@ai-matrx/records/react");
|
|
9596
|
+
var import_core7 = require("@ai-matrx/records/core");
|
|
9198
9597
|
var import_design_system30 = require("@ai-matrx/design-system");
|
|
9199
9598
|
var import_jsx_runtime37 = require("react/jsx-runtime");
|
|
9200
9599
|
function countOf(n, one, many) {
|
|
@@ -9251,6 +9650,7 @@ function useGridBulk(args) {
|
|
|
9251
9650
|
setRefusal(done.error);
|
|
9252
9651
|
return null;
|
|
9253
9652
|
}
|
|
9653
|
+
for (const change of changes) if ("record_id" in change && change.record_id) (0, import_core7.announceRecordChanged)(change.record_id);
|
|
9254
9654
|
return done.data;
|
|
9255
9655
|
},
|
|
9256
9656
|
[client, tableId]
|
|
@@ -9411,7 +9811,7 @@ function BulkActions({
|
|
|
9411
9811
|
// src/handOrder.tsx
|
|
9412
9812
|
var import_react55 = require("react");
|
|
9413
9813
|
var import_react56 = require("@ai-matrx/records/react");
|
|
9414
|
-
var
|
|
9814
|
+
var import_records14 = require("@ai-matrx/records");
|
|
9415
9815
|
var import_design_system31 = require("@ai-matrx/design-system");
|
|
9416
9816
|
|
|
9417
9817
|
// src/viewKeys.ts
|
|
@@ -9628,8 +10028,8 @@ function unknownViewLine(raw) {
|
|
|
9628
10028
|
}
|
|
9629
10029
|
var LAYOUT_NO_FIELD = {
|
|
9630
10030
|
grid: "",
|
|
9631
|
-
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
|
|
9632
|
-
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
|
|
10031
|
+
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.",
|
|
10032
|
+
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.",
|
|
9633
10033
|
gallery: ""
|
|
9634
10034
|
};
|
|
9635
10035
|
function viewDocument(spec) {
|
|
@@ -9839,7 +10239,7 @@ function useHandOrderRows(args) {
|
|
|
9839
10239
|
const client = (0, import_react56.useRecordsClient)();
|
|
9840
10240
|
const { viewId, active, page, pageSize } = args;
|
|
9841
10241
|
const filter = args.filter && Object.keys(args.filter).length > 0 ? args.filter : null;
|
|
9842
|
-
const filterKey = filter ? (0,
|
|
10242
|
+
const filterKey = filter ? (0, import_records14.stableFilterKey)(filter) : "";
|
|
9843
10243
|
const filterNow = (0, import_react55.useRef)(filter);
|
|
9844
10244
|
filterNow.current = filter;
|
|
9845
10245
|
const [answer, setAnswer] = (0, import_react55.useState)(null);
|
|
@@ -9966,6 +10366,7 @@ function Grid({
|
|
|
9966
10366
|
pageSize = 50,
|
|
9967
10367
|
filter,
|
|
9968
10368
|
onOpenRecord,
|
|
10369
|
+
openRecordId = null,
|
|
9969
10370
|
onAddField,
|
|
9970
10371
|
onNewRecordForm,
|
|
9971
10372
|
toolbarActions,
|
|
@@ -9995,7 +10396,8 @@ function Grid({
|
|
|
9995
10396
|
);
|
|
9996
10397
|
const merged = host.grid === "merged";
|
|
9997
10398
|
const phone = (0, import_design_system32.useIsMobile)();
|
|
9998
|
-
const
|
|
10399
|
+
const toolbarFold2 = useTableToolbarFold();
|
|
10400
|
+
const sheetExtras = useTableToolbarSheetExtras();
|
|
9999
10401
|
const rights = useTableRights(table.data);
|
|
10000
10402
|
const [heldOrder, setHeldOrder] = (0, import_react57.useState)(null);
|
|
10001
10403
|
const sortsKey = JSON.stringify(viewOrder?.sorts ?? []);
|
|
@@ -10080,6 +10482,7 @@ function Grid({
|
|
|
10080
10482
|
);
|
|
10081
10483
|
const canWrite = Boolean(editable) && (rights.write || askPerRow && rowIds.some(mayWriteRow));
|
|
10082
10484
|
const pushWritten = (0, import_react57.useRef)(null);
|
|
10485
|
+
const sheetController = (0, import_react57.useRef)(null);
|
|
10083
10486
|
const editing = useGridEditing({
|
|
10084
10487
|
tableId,
|
|
10085
10488
|
fields: fields.data ?? [],
|
|
@@ -10087,7 +10490,15 @@ function Grid({
|
|
|
10087
10490
|
reload: records.reload,
|
|
10088
10491
|
canWrite,
|
|
10089
10492
|
mayWriteRow,
|
|
10090
|
-
...merged ? { onWritten: (write) => pushWritten.current?.(write) } : {}
|
|
10493
|
+
...merged ? { onWritten: (write) => pushWritten.current?.(write) } : {},
|
|
10494
|
+
/*
|
|
10495
|
+
* THE KEYBOARD COMES BACK (merged-grid review 2026-09-26, A1): Enter moves the cursor down, Tab
|
|
10496
|
+
* right, Escape stays, and the grid — not the page body — has the keys, so the next arrow,
|
|
10497
|
+
* Enter or Cmd-Z works without a click. Sheets and Airtable.
|
|
10498
|
+
*/
|
|
10499
|
+
...merged ? {
|
|
10500
|
+
onEnded: (exit, from, how) => sheetController.current?.endEdit(exit, { rowId: from.rowId, columnId: from.key }, how)
|
|
10501
|
+
} : {}
|
|
10091
10502
|
});
|
|
10092
10503
|
const undo = useGridUndo({ tableId, reload: records.reload, forget: editing.forget });
|
|
10093
10504
|
pushWritten.current = (write) => {
|
|
@@ -10102,7 +10513,7 @@ function Grid({
|
|
|
10102
10513
|
const [localLook, setLocalLook] = (0, import_react57.useState)(null);
|
|
10103
10514
|
const [localSummaries, setLocalSummaries] = (0, import_react57.useState)(null);
|
|
10104
10515
|
const sortedKeys = (0, import_react57.useMemo)(
|
|
10105
|
-
() => [...fields.data ?? []].sort(
|
|
10516
|
+
() => [...fields.data ?? []].sort(compareFieldOrder).map((f) => f.key),
|
|
10106
10517
|
[fields.data]
|
|
10107
10518
|
);
|
|
10108
10519
|
const look = (0, import_react57.useMemo)(() => {
|
|
@@ -10119,16 +10530,16 @@ function Grid({
|
|
|
10119
10530
|
const frozenList = look.frozen;
|
|
10120
10531
|
const frozenKeys = (0, import_react57.useMemo)(() => new Set(frozenList), [frozenList]);
|
|
10121
10532
|
const ordered = (0, import_react57.useMemo)(() => {
|
|
10122
|
-
const all = [...fields.data ?? []].sort(
|
|
10123
|
-
(a, b) => (a.sort ?? 100) - (b.sort ?? 100) || a.key.localeCompare(b.key)
|
|
10124
|
-
);
|
|
10533
|
+
const all = [...fields.data ?? []].sort(compareFieldOrder);
|
|
10125
10534
|
if (frozenKeys.size === 0) return all;
|
|
10126
10535
|
const pinned = frozenList.map((key) => all.find((f) => f.key === key)).filter((f) => f !== void 0);
|
|
10127
10536
|
return [...pinned, ...all.filter((f) => !frozenKeys.has(f.key))];
|
|
10128
10537
|
}, [fields.data, frozenKeys, frozenList]);
|
|
10129
10538
|
const tableActions = useTableActions(tableId, canWrite || Boolean(host.runAgentAction));
|
|
10130
|
-
const
|
|
10131
|
-
const
|
|
10539
|
+
const ownWord = (table.data?.label_singular ?? "").trim();
|
|
10540
|
+
const sameAsName = ownWord === "" || (table.data ? ownWord.toLowerCase() === tableName(table.data).toLowerCase() : true);
|
|
10541
|
+
const noun = merged && sameAsName ? "row" : (table.data?.label_singular ?? "record").toLowerCase();
|
|
10542
|
+
const nounPlural = merged && sameAsName ? "rows" : pluralNoun(table.data ?? null).toLowerCase();
|
|
10132
10543
|
const runner = useActionRunner({
|
|
10133
10544
|
rows,
|
|
10134
10545
|
fields: fields.data ?? [],
|
|
@@ -10176,10 +10587,29 @@ function Grid({
|
|
|
10176
10587
|
setDuplicated(`The copy was not made: ${made.error.message}`);
|
|
10177
10588
|
return;
|
|
10178
10589
|
}
|
|
10179
|
-
|
|
10590
|
+
const copyId = made.data;
|
|
10591
|
+
if (merged) added.pin({ id: copyId, document: data, level: row.level, hidden: {} }, row.id);
|
|
10592
|
+
if (manual && viewId) {
|
|
10593
|
+
const ids = rows.map((r) => r.id).filter((id) => id !== copyId);
|
|
10594
|
+
const at = ids.indexOf(row.id);
|
|
10595
|
+
const next = [...ids.slice(0, at + 1), copyId, ...ids.slice(at + 1)];
|
|
10596
|
+
const placed = await client.viewRecordOrderSet({ view_id: viewId, record_ids: next });
|
|
10597
|
+
if (!placed.ok) {
|
|
10598
|
+
setDuplicated(`A copy of ${recordNameIn(table.data, document2)} was made and could not be placed beside it: ${placed.error.message}`);
|
|
10599
|
+
records.reload();
|
|
10600
|
+
return;
|
|
10601
|
+
}
|
|
10602
|
+
viewOrder?.onPlaced?.();
|
|
10603
|
+
hand.reload();
|
|
10604
|
+
}
|
|
10605
|
+
if (merged) {
|
|
10606
|
+
undo.push({ label: `Duplicated ${recordNameIn(table.data, document2)}`, cells: [], created: [copyId] });
|
|
10607
|
+
} else {
|
|
10608
|
+
setDuplicated(`A copy of ${recordNameIn(table.data, document2)} is in the table, right below it.`);
|
|
10609
|
+
}
|
|
10180
10610
|
records.reload();
|
|
10181
10611
|
},
|
|
10182
|
-
[client, fields.data, host, records, table.data, tableId]
|
|
10612
|
+
[client, fields.data, host, records, table.data, tableId, merged, added, manual, viewId, rows, viewOrder, hand, undo]
|
|
10183
10613
|
);
|
|
10184
10614
|
const recordIds = (0, import_react57.useMemo)(() => rows.map((r) => r.id), [rows]);
|
|
10185
10615
|
const { cells: enrichCells, refresh: refreshEnrich } = useEnrichCells(tableId, recordIds);
|
|
@@ -10271,9 +10701,20 @@ function Grid({
|
|
|
10271
10701
|
why
|
|
10272
10702
|
});
|
|
10273
10703
|
}
|
|
10704
|
+
if (merged && built.cells > 0 && built.creates === 0 && built.refusals.length === 0 && built.pastRightEdge === 0 && built.unmatchedOptions.length === 0) {
|
|
10705
|
+
const outcome = await pasteAtOnce.current(built);
|
|
10706
|
+
if (outcome.refusals.length > 0 || outcome.missing > 0) {
|
|
10707
|
+
undo.say(
|
|
10708
|
+
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.`,
|
|
10709
|
+
true
|
|
10710
|
+
);
|
|
10711
|
+
}
|
|
10712
|
+
sheetController.current?.focusGrid();
|
|
10713
|
+
return;
|
|
10714
|
+
}
|
|
10274
10715
|
setPastePlan({ plan: built });
|
|
10275
10716
|
},
|
|
10276
|
-
[fields.data, loadChoiceOptions, rows]
|
|
10717
|
+
[fields.data, loadChoiceOptions, rows, merged, undo]
|
|
10277
10718
|
);
|
|
10278
10719
|
const addPastedOptions = (0, import_react57.useCallback)(
|
|
10279
10720
|
async (fieldKey, words2) => {
|
|
@@ -10329,6 +10770,7 @@ function Grid({
|
|
|
10329
10770
|
}))
|
|
10330
10771
|
);
|
|
10331
10772
|
editing.forget(cells);
|
|
10773
|
+
for (const u of updates) (0, import_core8.announceRecordChanged)(u.row.recordId);
|
|
10332
10774
|
const lines = updates.length + creates.length;
|
|
10333
10775
|
undo.push({ label: `Pasted ${lines} ${lines === 1 ? "row" : "rows"}`, cells, created });
|
|
10334
10776
|
const expected = [
|
|
@@ -10353,6 +10795,8 @@ function Grid({
|
|
|
10353
10795
|
},
|
|
10354
10796
|
[rows, client, tableId, editing, undo, records]
|
|
10355
10797
|
);
|
|
10798
|
+
const pasteAtOnce = (0, import_react57.useRef)(applyPasteAsOne);
|
|
10799
|
+
pasteAtOnce.current = applyPasteAsOne;
|
|
10356
10800
|
const writeRange = (0, import_react57.useCallback)(
|
|
10357
10801
|
async (patches, intent) => {
|
|
10358
10802
|
if (patches.length === 0) return;
|
|
@@ -10377,7 +10821,14 @@ function Grid({
|
|
|
10377
10821
|
const applyPaste = (0, import_react57.useCallback)(async () => {
|
|
10378
10822
|
const held = pastePlan;
|
|
10379
10823
|
if (!held) return { written: 0, readBack: 0, missing: 0, refusals: [] };
|
|
10380
|
-
if (merged)
|
|
10824
|
+
if (merged) {
|
|
10825
|
+
const outcome = await applyPasteAsOne(held.plan);
|
|
10826
|
+
if (outcome.missing === 0 && outcome.refusals.length === 0) {
|
|
10827
|
+
setPastePlan(null);
|
|
10828
|
+
sheetController.current?.focusGrid();
|
|
10829
|
+
}
|
|
10830
|
+
return outcome;
|
|
10831
|
+
}
|
|
10381
10832
|
const refusals = [];
|
|
10382
10833
|
let written = 0;
|
|
10383
10834
|
const expected = [];
|
|
@@ -10472,7 +10923,7 @@ function Grid({
|
|
|
10472
10923
|
tableName: table.data ? tableName(table.data) : "",
|
|
10473
10924
|
fields: fields.data ?? [],
|
|
10474
10925
|
document: row.document ?? {},
|
|
10475
|
-
level: (0,
|
|
10926
|
+
level: (0, import_records15.asPermissionLevel)(row.level)
|
|
10476
10927
|
}),
|
|
10477
10928
|
[host, tableId, table.data, fields.data]
|
|
10478
10929
|
);
|
|
@@ -10500,6 +10951,18 @@ function Grid({
|
|
|
10500
10951
|
total: typeof records.data?.total === "number" ? records.data.total : null,
|
|
10501
10952
|
search: searchTerm
|
|
10502
10953
|
});
|
|
10954
|
+
const followRef = (0, import_react57.useRef)({ openRecordId, onOpenRecord });
|
|
10955
|
+
followRef.current = { openRecordId, onOpenRecord };
|
|
10956
|
+
const followSelection = (0, import_react57.useCallback)(
|
|
10957
|
+
(selection) => {
|
|
10958
|
+
assist.onSelectionChange(selection);
|
|
10959
|
+
const { openRecordId: open, onOpenRecord: openIt } = followRef.current;
|
|
10960
|
+
if (!open || !openIt || !selection) return;
|
|
10961
|
+
const rowId = selection.active.rowId;
|
|
10962
|
+
if (rowId !== open && rowId !== ADD_ROW.id && rowId !== SUMMARY_ROW.id) openIt(rowId);
|
|
10963
|
+
},
|
|
10964
|
+
[assist.onSelectionChange]
|
|
10965
|
+
);
|
|
10503
10966
|
const addRowRef = (0, import_react57.useRef)(() => void 0);
|
|
10504
10967
|
const controls = useMergedGridControls({
|
|
10505
10968
|
enabled: merged,
|
|
@@ -10614,10 +11077,10 @@ function Grid({
|
|
|
10614
11077
|
}
|
|
10615
11078
|
);
|
|
10616
11079
|
}
|
|
10617
|
-
), mayAddRow ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(AddRowLine, { onAdd: () => void addRowHere(), busy: added.busy }) : null), controls.headerMenuFor);
|
|
11080
|
+
), mayAddRow ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(AddRowLine, { onAdd: () => void addRowHere(), busy: added.busy }) : null), controls.headerMenuFor, controls.filterFor);
|
|
10618
11081
|
return merged && rights.structure && editable ? [...drawn, addColumnColumn(controls.addColumn)] : drawn;
|
|
10619
11082
|
},
|
|
10620
|
-
[fieldColumns, onOpenRecord, table.data, ordered, chosenSummaries, summaries, chooseSummary, controls.headerMenuFor, mayAddRow, addRowHere, added.busy, merged, rights.structure, editable, controls.addColumn]
|
|
11083
|
+
[fieldColumns, onOpenRecord, table.data, ordered, chosenSummaries, summaries, chooseSummary, controls.headerMenuFor, controls.filterFor, mayAddRow, addRowHere, added.busy, merged, rights.structure, editable, controls.addColumn]
|
|
10621
11084
|
);
|
|
10622
11085
|
const [localColumns, setLocalColumns] = (0, import_react57.useState)(
|
|
10623
11086
|
null
|
|
@@ -10640,7 +11103,11 @@ function Grid({
|
|
|
10640
11103
|
() => ({
|
|
10641
11104
|
order: controls.columns?.order ?? localColumns?.order ?? columnIds,
|
|
10642
11105
|
hidden: controls.columns?.hidden ?? localColumns?.hidden ?? [],
|
|
10643
|
-
onChange: (
|
|
11106
|
+
onChange: (drawn) => {
|
|
11107
|
+
const next = { order: fieldColumnIds(drawn.order, fields.data ?? []), hidden: fieldColumnIds(drawn.hidden, fields.data ?? []) };
|
|
11108
|
+
if (controls.columns) controls.columns.onChange(next);
|
|
11109
|
+
else setLocalColumns(next);
|
|
11110
|
+
},
|
|
10644
11111
|
...widths ? { widths } : {},
|
|
10645
11112
|
// A drag that changed nothing would be the silent half of a control that
|
|
10646
11113
|
// looks alive, so the width always lands somewhere — the session at
|
|
@@ -10650,13 +11117,12 @@ function Grid({
|
|
|
10650
11117
|
writeLook({ ...presentation, widths: next });
|
|
10651
11118
|
}
|
|
10652
11119
|
}),
|
|
10653
|
-
[columnIds, localColumns, widths, presentation, writeLook, controls.columns]
|
|
11120
|
+
[columnIds, localColumns, widths, presentation, writeLook, controls.columns, fields.data]
|
|
10654
11121
|
);
|
|
10655
11122
|
const groupingConfig = (0, import_react57.useMemo)(() => {
|
|
10656
11123
|
const view = presentation.grouping;
|
|
10657
11124
|
const field2 = view?.field ?? null;
|
|
10658
11125
|
if (field2 === null && !onPresentationChange) return null;
|
|
10659
|
-
if (field2 === null && merged && phone) return null;
|
|
10660
11126
|
const aggregates = view && Object.keys(view.aggregates ?? {}).length > 0 ? view.aggregates : field2 ? { [field2]: { kind: "count" } } : void 0;
|
|
10661
11127
|
return {
|
|
10662
11128
|
columnId: field2,
|
|
@@ -10703,7 +11169,7 @@ function Grid({
|
|
|
10703
11169
|
return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(WhoChangedSource, { tableId, labelOf: labelOfField, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
|
|
10704
11170
|
"div",
|
|
10705
11171
|
{
|
|
10706
|
-
className: (0, import_design_system32.cn)("flex min-h-0 flex-col gap-2", fills && "flex-1", className),
|
|
11172
|
+
className: (0, import_design_system32.cn)("relative flex min-h-0 flex-col gap-2", fills && "flex-1", className),
|
|
10707
11173
|
"data-records-grid-footer": footer ?? "natural",
|
|
10708
11174
|
...merged ? {
|
|
10709
11175
|
// ONE Cmd-Z (tranche 6d): the grid's own undo, never a text box's.
|
|
@@ -10718,7 +11184,7 @@ function Grid({
|
|
|
10718
11184
|
} : {},
|
|
10719
11185
|
children: [
|
|
10720
11186
|
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,
|
|
10721
|
-
records.error ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(RefusalNotice, { error: records.error }) : null,
|
|
11187
|
+
records.error ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(RefusalNotice, { error: inColumnWords(records.error, fields.data ?? []) }) : null,
|
|
10722
11188
|
manual && hand.error ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(RefusalNotice, { error: hand.error }) : null,
|
|
10723
11189
|
archived ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
|
|
10724
11190
|
"p",
|
|
@@ -10930,7 +11396,7 @@ function Grid({
|
|
|
10930
11396
|
editing.setCell(patch.rowId, patch.columnId, patch.value);
|
|
10931
11397
|
}
|
|
10932
11398
|
},
|
|
10933
|
-
...merged ? { undoDepth: 0, onSelectionChange:
|
|
11399
|
+
...merged ? { undoDepth: 0, onSelectionChange: followSelection, controllerRef: sheetController } : {},
|
|
10934
11400
|
columnRefusal: (columnId) => {
|
|
10935
11401
|
const field2 = (fields.data ?? []).find((f) => f.key === columnId);
|
|
10936
11402
|
if (!field2) return `This table has no column called ${columnId}.`;
|
|
@@ -10952,6 +11418,7 @@ function Grid({
|
|
|
10952
11418
|
onEditRequest: (address, seed) => {
|
|
10953
11419
|
const field2 = (fields.data ?? []).find((f) => f.key === address.columnId);
|
|
10954
11420
|
if (!field2 || !fieldIsEditable(field2) || !mayWriteRow(address.rowId)) return false;
|
|
11421
|
+
if (seed !== null && seed !== "" && typingOpens(field2) === "none") return false;
|
|
10955
11422
|
editing.begin({ rowId: address.rowId, key: field2.key }, seed);
|
|
10956
11423
|
return true;
|
|
10957
11424
|
},
|
|
@@ -10968,7 +11435,7 @@ function Grid({
|
|
|
10968
11435
|
spreadsheet: {
|
|
10969
11436
|
enabled: true,
|
|
10970
11437
|
readOnly: true,
|
|
10971
|
-
...merged ? { onSelectionChange:
|
|
11438
|
+
...merged ? { onSelectionChange: followSelection, controllerRef: sheetController } : {},
|
|
10972
11439
|
readOnlyReason: !editable || !rights.known || rights.write ? "Editing is off here, so this table can be copied but not changed." : rights.why("write")
|
|
10973
11440
|
}
|
|
10974
11441
|
},
|
|
@@ -11061,7 +11528,12 @@ function Grid({
|
|
|
11061
11528
|
) : null
|
|
11062
11529
|
] })
|
|
11063
11530
|
} : {},
|
|
11064
|
-
emptyState: {
|
|
11531
|
+
emptyState: records.error ? {
|
|
11532
|
+
// A REFUSED READ IS NOT AN EMPTY TABLE (merged-grid review 2026-09-26): the store's own
|
|
11533
|
+
// sentence is above the grid, and this never says "No records yet" over it.
|
|
11534
|
+
title: "These records could not be read",
|
|
11535
|
+
description: inColumnWords(records.error, fields.data ?? []).message
|
|
11536
|
+
} : {
|
|
11065
11537
|
...mayAddRow ? { action: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(AddFirstRowButton, { onAdd: () => void addRowHere(), busy: added.busy }) } : {},
|
|
11066
11538
|
title: "No records yet",
|
|
11067
11539
|
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."
|
|
@@ -11074,35 +11546,21 @@ function Grid({
|
|
|
11074
11546
|
* the row, so Tools is never scrolled out of reach; the search is the same query.
|
|
11075
11547
|
*/
|
|
11076
11548
|
...merged && phone ? {
|
|
11077
|
-
customSearch: /* @__PURE__ */ (0, import_jsx_runtime39.
|
|
11078
|
-
|
|
11079
|
-
|
|
11080
|
-
|
|
11081
|
-
|
|
11082
|
-
|
|
11083
|
-
|
|
11084
|
-
|
|
11085
|
-
|
|
11086
|
-
|
|
11087
|
-
|
|
11088
|
-
|
|
11089
|
-
|
|
11090
|
-
|
|
11091
|
-
|
|
11092
|
-
),
|
|
11093
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
11094
|
-
import_design_system32.Button,
|
|
11095
|
-
{
|
|
11096
|
-
size: "sm",
|
|
11097
|
-
variant: "outline",
|
|
11098
|
-
className: "h-8 shrink-0",
|
|
11099
|
-
"data-records-grid-tools": "",
|
|
11100
|
-
"aria-haspopup": "dialog",
|
|
11101
|
-
onClick: () => setToolsOpen(true),
|
|
11102
|
-
children: "Tools"
|
|
11103
|
-
}
|
|
11104
|
-
)
|
|
11105
|
-
] })
|
|
11549
|
+
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)(
|
|
11550
|
+
"input",
|
|
11551
|
+
{
|
|
11552
|
+
type: "search",
|
|
11553
|
+
value: query.search,
|
|
11554
|
+
placeholder: "Search",
|
|
11555
|
+
"aria-label": "Search records",
|
|
11556
|
+
onChange: (event) => {
|
|
11557
|
+
const search = event.target.value;
|
|
11558
|
+
setQuery((held) => ({ ...held, search, page: 1 }));
|
|
11559
|
+
},
|
|
11560
|
+
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",
|
|
11561
|
+
style: { fontSize: "16px" }
|
|
11562
|
+
}
|
|
11563
|
+
) })
|
|
11106
11564
|
} : {},
|
|
11107
11565
|
/* ONE ROW (lane TABLE-PAGE-CHROME): on a table page the grid's search, columns and
|
|
11108
11566
|
sort are drawn INTO the page's toolbar row, never a second bar under it. */
|
|
@@ -11110,63 +11568,110 @@ function Grid({
|
|
|
11110
11568
|
// SCR-2 / SCR-4: PRESENT for an admin, ABSENT for everyone else.
|
|
11111
11569
|
// There is no third state: this package ships no disabled variant of
|
|
11112
11570
|
// these controls, so a member cannot be shown a dead one by mistake.
|
|
11113
|
-
|
|
11114
|
-
|
|
11115
|
-
|
|
11116
|
-
|
|
11117
|
-
|
|
11118
|
-
|
|
11119
|
-
|
|
11120
|
-
|
|
11121
|
-
|
|
11122
|
-
|
|
11123
|
-
onUseSort: () => {
|
|
11124
|
-
const sort = columnSort;
|
|
11125
|
-
const chosen = { field: sort.id, direction: sort.direction };
|
|
11126
|
-
setHeldOrder({ order: "sorted", sorts: [chosen] });
|
|
11127
|
-
viewOrder.onUseSort?.(chosen);
|
|
11128
|
-
host.notify?.success(
|
|
11129
|
-
`Sorted by ${columnLabel(fields.data ?? [], sort.id)} ${sortArrow(sort.direction)}. ${MANUAL_SORT.replacedToast}`
|
|
11130
|
-
);
|
|
11131
|
-
}
|
|
11132
|
-
} : {}
|
|
11133
|
-
})
|
|
11134
|
-
}
|
|
11135
|
-
) : null,
|
|
11136
|
-
merged ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(UndoRedoButtons, { undo }) : null,
|
|
11137
|
-
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,
|
|
11138
|
-
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,
|
|
11139
|
-
rights.structure && onAddField ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(AddFieldButton, { onClick: onAddField }) : null,
|
|
11140
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
11141
|
-
GridLayoutMenu,
|
|
11142
|
-
{
|
|
11143
|
-
look,
|
|
11144
|
-
firstColumn: sortedKeys[0] ? {
|
|
11145
|
-
key: sortedKeys[0],
|
|
11146
|
-
name: (() => {
|
|
11147
|
-
const first = (fields.data ?? []).find((f) => f.key === sortedKeys[0]);
|
|
11148
|
-
return first ? fieldName(first) : humanize(sortedKeys[0]);
|
|
11149
|
-
})()
|
|
11150
|
-
} : null,
|
|
11151
|
-
onChange: changeLayout,
|
|
11152
|
-
kept: Boolean(onPresentationChange)
|
|
11153
|
-
}
|
|
11154
|
-
)
|
|
11155
|
-
] });
|
|
11156
|
-
if (!merged || !phone) return all;
|
|
11157
|
-
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)(
|
|
11158
|
-
"div",
|
|
11571
|
+
/*
|
|
11572
|
+
* WHERE THE GRID'S CONTROLS GO (lane C, review 2026-09-26 D6). Inline when the row has
|
|
11573
|
+
* room; behind the row's "…" once the page row is folded that far (Undo / Redo and New
|
|
11574
|
+
* record stay beside it — the primary action is never folded); on a phone EVERY control
|
|
11575
|
+
* — the table's own (clear, copy/export, group, columns), these, the search, the sort and
|
|
11576
|
+
* the page's filter, layout and share — in ONE "Tools" sheet. Never dropped.
|
|
11577
|
+
*/
|
|
11578
|
+
actions: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
|
|
11579
|
+
manual ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
11580
|
+
SortStateControl,
|
|
11159
11581
|
{
|
|
11160
|
-
|
|
11161
|
-
|
|
11162
|
-
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
|
|
11166
|
-
|
|
11582
|
+
...handOrderSortState({
|
|
11583
|
+
sortName: columnSort ? columnLabel(fields.data ?? [], columnSort.id) : null,
|
|
11584
|
+
direction: columnSort?.direction ?? null,
|
|
11585
|
+
onBack: () => setQuery((held) => ({ ...held, sort: null, page: 1 })),
|
|
11586
|
+
...viewOrder?.onUseSort && columnSort ? {
|
|
11587
|
+
onUseSort: () => {
|
|
11588
|
+
const sort = columnSort;
|
|
11589
|
+
const chosen = { field: sort.id, direction: sort.direction };
|
|
11590
|
+
setHeldOrder({ order: "sorted", sorts: [chosen] });
|
|
11591
|
+
viewOrder.onUseSort?.(chosen);
|
|
11592
|
+
host.notify?.success(
|
|
11593
|
+
`Sorted by ${columnLabel(fields.data ?? [], sort.id)} ${sortArrow(sort.direction)}. ${MANUAL_SORT.replacedToast}`
|
|
11594
|
+
);
|
|
11595
|
+
}
|
|
11596
|
+
} : {}
|
|
11597
|
+
})
|
|
11598
|
+
}
|
|
11599
|
+
) : null,
|
|
11600
|
+
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,
|
|
11601
|
+
!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,
|
|
11602
|
+
rights.structure && onAddField ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(AddFieldButton, { onClick: onAddField }) : null,
|
|
11603
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
11604
|
+
GridLayoutMenu,
|
|
11605
|
+
{
|
|
11606
|
+
look,
|
|
11607
|
+
firstColumn: sortedKeys[0] ? {
|
|
11608
|
+
key: sortedKeys[0],
|
|
11609
|
+
name: (() => {
|
|
11610
|
+
const first = (fields.data ?? []).find((f) => f.key === sortedKeys[0]);
|
|
11611
|
+
return first ? fieldName(first) : humanize(sortedKeys[0]);
|
|
11612
|
+
})()
|
|
11613
|
+
} : null,
|
|
11614
|
+
onChange: changeLayout,
|
|
11615
|
+
kept: Boolean(onPresentationChange)
|
|
11167
11616
|
}
|
|
11168
|
-
)
|
|
11169
|
-
})
|
|
11617
|
+
)
|
|
11618
|
+
] }),
|
|
11619
|
+
...merged ? {
|
|
11620
|
+
overflow: {
|
|
11621
|
+
mode: phone ? "sheet" : toolbarFold2 >= 3 ? "menu" : "inline",
|
|
11622
|
+
pinned: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
|
|
11623
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(UndoRedoButtons, { undo }),
|
|
11624
|
+
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
|
|
11625
|
+
] }),
|
|
11626
|
+
...phone ? {
|
|
11627
|
+
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: [
|
|
11628
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
11629
|
+
"input",
|
|
11630
|
+
{
|
|
11631
|
+
type: "search",
|
|
11632
|
+
value: query.search,
|
|
11633
|
+
placeholder: "Search",
|
|
11634
|
+
"aria-label": "Search records",
|
|
11635
|
+
onChange: (event) => {
|
|
11636
|
+
const search = event.target.value;
|
|
11637
|
+
setQuery((held) => ({ ...held, search, page: 1 }));
|
|
11638
|
+
},
|
|
11639
|
+
className: "h-10 w-full min-w-0 rounded-md border border-input bg-background px-2 text-foreground placeholder:text-muted-foreground",
|
|
11640
|
+
style: { fontSize: "16px" }
|
|
11641
|
+
}
|
|
11642
|
+
),
|
|
11643
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("label", { className: "flex items-center gap-2 text-xs text-muted-foreground", "data-records-grid-sheet-sort": "", children: [
|
|
11644
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "shrink-0", children: "Sort by" }),
|
|
11645
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
|
|
11646
|
+
"select",
|
|
11647
|
+
{
|
|
11648
|
+
"aria-label": "Sort by",
|
|
11649
|
+
value: query.sort ? `${query.sort.id}:${query.sort.direction}` : "",
|
|
11650
|
+
onChange: (event) => {
|
|
11651
|
+
const [id, direction] = event.target.value.split(":");
|
|
11652
|
+
setQuery((held) => ({
|
|
11653
|
+
...held,
|
|
11654
|
+
page: 1,
|
|
11655
|
+
sort: id ? { id, direction: direction === "desc" ? "desc" : "asc" } : null
|
|
11656
|
+
}));
|
|
11657
|
+
},
|
|
11658
|
+
className: "h-10 min-w-0 flex-1 rounded-md border border-input bg-background px-2 text-foreground",
|
|
11659
|
+
style: { fontSize: "16px" },
|
|
11660
|
+
children: [
|
|
11661
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("option", { value: "", children: "No sort" }),
|
|
11662
|
+
ordered.flatMap((field2) => [
|
|
11663
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("option", { value: `${field2.key}:asc`, children: `${fieldName(field2)} \u2191` }, `${field2.key}:asc`),
|
|
11664
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("option", { value: `${field2.key}:desc`, children: `${fieldName(field2)} \u2193` }, `${field2.key}:desc`)
|
|
11665
|
+
])
|
|
11666
|
+
]
|
|
11667
|
+
}
|
|
11668
|
+
)
|
|
11669
|
+
] })
|
|
11670
|
+
] }),
|
|
11671
|
+
...sheetExtras ? { sheetExtras } : {}
|
|
11672
|
+
} : {}
|
|
11673
|
+
}
|
|
11674
|
+
} : {}
|
|
11170
11675
|
}
|
|
11171
11676
|
},
|
|
11172
11677
|
`records-grid-${tableId}:${openingKey}`
|
|
@@ -11178,11 +11683,14 @@ function Grid({
|
|
|
11178
11683
|
PastePreview,
|
|
11179
11684
|
{
|
|
11180
11685
|
plan: pastePlan.plan,
|
|
11181
|
-
noun: table.data?.label_singular ?? "record",
|
|
11182
|
-
nounPlural: pluralNoun(table.data ?? null),
|
|
11686
|
+
noun: merged ? noun : table.data?.label_singular ?? "record",
|
|
11687
|
+
nounPlural: merged ? nounPlural : pluralNoun(table.data ?? null),
|
|
11183
11688
|
onConfirm: applyPaste,
|
|
11184
11689
|
onAddOptions: addPastedOptions,
|
|
11185
|
-
onCancel: () =>
|
|
11690
|
+
onCancel: () => {
|
|
11691
|
+
setPastePlan(null);
|
|
11692
|
+
sheetController.current?.focusGrid();
|
|
11693
|
+
}
|
|
11186
11694
|
}
|
|
11187
11695
|
) : null,
|
|
11188
11696
|
merged ? null : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
@@ -11248,7 +11756,17 @@ function Grid({
|
|
|
11248
11756
|
const failed = await editing.remove(row.id);
|
|
11249
11757
|
setDeleteBusy(false);
|
|
11250
11758
|
setDeleting(null);
|
|
11251
|
-
if (
|
|
11759
|
+
if (failed) return;
|
|
11760
|
+
if (merged) {
|
|
11761
|
+
undo.push({
|
|
11762
|
+
label: `Deleted ${name} \u2014 it is in this table's Archived items`,
|
|
11763
|
+
cells: [],
|
|
11764
|
+
archived: [row.id]
|
|
11765
|
+
});
|
|
11766
|
+
sheetController.current?.focusGrid();
|
|
11767
|
+
} else {
|
|
11768
|
+
setArchived(name);
|
|
11769
|
+
}
|
|
11252
11770
|
}
|
|
11253
11771
|
}
|
|
11254
11772
|
)
|
|
@@ -11256,17 +11774,19 @@ function Grid({
|
|
|
11256
11774
|
}
|
|
11257
11775
|
) });
|
|
11258
11776
|
}
|
|
11259
|
-
function withHeaderMenus(columns, menuFor) {
|
|
11777
|
+
function withHeaderMenus(columns, menuFor, filterFor) {
|
|
11260
11778
|
return columns.map((column) => {
|
|
11261
11779
|
const items = menuFor(String(column.id));
|
|
11262
|
-
|
|
11780
|
+
const filter = filterFor?.(String(column.id));
|
|
11781
|
+
const withMenu = items && items.length > 0 ? { ...column, headerMenu: items } : column;
|
|
11782
|
+
return filter ? { ...withMenu, ...filter } : withMenu;
|
|
11263
11783
|
});
|
|
11264
11784
|
}
|
|
11265
11785
|
|
|
11266
11786
|
// src/ExportMenu.tsx
|
|
11267
11787
|
var import_react59 = require("react");
|
|
11268
11788
|
var import_react60 = require("@ai-matrx/records/react");
|
|
11269
|
-
var
|
|
11789
|
+
var import_core9 = require("@ai-matrx/records/core");
|
|
11270
11790
|
var import_react61 = require("@ai-matrx/alchemy/react");
|
|
11271
11791
|
var import_design_system33 = require("@ai-matrx/design-system");
|
|
11272
11792
|
var import_jsx_runtime40 = require("react/jsx-runtime");
|
|
@@ -11285,7 +11805,7 @@ function toRows(fields, records, labels) {
|
|
|
11285
11805
|
absent_reason: envelope?.absent ?? null,
|
|
11286
11806
|
// A VALUE KEPT AS A FILE carries its pointer, so the export writes the whole text
|
|
11287
11807
|
// (`withWholeValues`), never the first words passed off as the value.
|
|
11288
|
-
source: (0,
|
|
11808
|
+
source: (0, import_core9.wholeValueOf)(document2, field2.key)
|
|
11289
11809
|
};
|
|
11290
11810
|
}
|
|
11291
11811
|
return row;
|
|
@@ -11339,7 +11859,7 @@ function ExportMenu({ tableId, rows, label, className }) {
|
|
|
11339
11859
|
);
|
|
11340
11860
|
const lookup = (field2, id) => labels(field2, id) ?? learned.get(`${field2.id}:${id}`) ?? null;
|
|
11341
11861
|
const read = host.readFileText;
|
|
11342
|
-
return (0,
|
|
11862
|
+
return (0, import_core9.withWholeValues)(toRows(all, records, lookup), read ? (fileId) => read({ fileId }) : null);
|
|
11343
11863
|
};
|
|
11344
11864
|
return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: (0, import_design_system33.cn)("flex items-center gap-1", className), children: [
|
|
11345
11865
|
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
@@ -11347,7 +11867,7 @@ function ExportMenu({ tableId, rows, label, className }) {
|
|
|
11347
11867
|
{
|
|
11348
11868
|
size: "sm",
|
|
11349
11869
|
variant: "outline",
|
|
11350
|
-
onClick: () => void exported().then((rows2) => download(`${name}.csv`, "text/csv;charset=utf-8", (0,
|
|
11870
|
+
onClick: () => void exported().then((rows2) => download(`${name}.csv`, "text/csv;charset=utf-8", (0, import_core9.exportCsv)(fields.data ?? [], rows2))).catch(
|
|
11351
11871
|
(thrown) => setFailure(refusalFromThrown(thrown, "The CSV could not be written.", "Try again in a moment."))
|
|
11352
11872
|
),
|
|
11353
11873
|
children: "CSV"
|
|
@@ -11368,7 +11888,7 @@ function ExportMenu({ tableId, rows, label, className }) {
|
|
|
11368
11888
|
);
|
|
11369
11889
|
setFailure(null);
|
|
11370
11890
|
try {
|
|
11371
|
-
exported().then((rows2) => (0,
|
|
11891
|
+
exported().then((rows2) => (0, import_core9.exportXlsx)(fields.data ?? [], rows2, name)).then(
|
|
11372
11892
|
(bytes) => download(
|
|
11373
11893
|
`${name}.xlsx`,
|
|
11374
11894
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
@@ -11389,7 +11909,7 @@ function ExportMenu({ tableId, rows, label, className }) {
|
|
|
11389
11909
|
sourceId: `records:${tableId}`,
|
|
11390
11910
|
size: "xs",
|
|
11391
11911
|
json: () => records.map((r) => r.document),
|
|
11392
|
-
human: () => (0,
|
|
11912
|
+
human: () => (0, import_core9.exportCsv)(fields.data ?? [], toRows(fields.data ?? [], records, labels))
|
|
11393
11913
|
}
|
|
11394
11914
|
),
|
|
11395
11915
|
failure ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(RefusalLine, { error: failure }) : null
|
|
@@ -11493,11 +12013,11 @@ function FilePicker({
|
|
|
11493
12013
|
// src/ImportWizard.tsx
|
|
11494
12014
|
var import_react63 = require("react");
|
|
11495
12015
|
var import_react64 = require("@ai-matrx/records/react");
|
|
11496
|
-
var
|
|
12016
|
+
var import_core11 = require("@ai-matrx/records/core");
|
|
11497
12017
|
var import_design_system35 = require("@ai-matrx/design-system");
|
|
11498
12018
|
|
|
11499
12019
|
// src/importRuleCheck.ts
|
|
11500
|
-
var
|
|
12020
|
+
var import_core10 = require("@ai-matrx/records/core");
|
|
11501
12021
|
function checkImportRules(fields, rows, mapping) {
|
|
11502
12022
|
const byKey = new Map(fields.map((f) => [f.key, f]));
|
|
11503
12023
|
const verdicts = [];
|
|
@@ -11515,7 +12035,7 @@ function checkImportRules(fields, rows, mapping) {
|
|
|
11515
12035
|
let why = null;
|
|
11516
12036
|
if ("refusal" in meant) why = meant.refusal;
|
|
11517
12037
|
else {
|
|
11518
|
-
const predicted = (0,
|
|
12038
|
+
const predicted = (0, import_core10.predictValueRefusals)([field2], { [key]: meant.value }).filter(
|
|
11519
12039
|
(p) => p.field_key === void 0 || p.field_key === key
|
|
11520
12040
|
);
|
|
11521
12041
|
if (predicted.length > 0) why = predicted[0].message;
|
|
@@ -11643,7 +12163,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
|
|
|
11643
12163
|
try {
|
|
11644
12164
|
const xlsx = file.name.toLowerCase().endsWith(".xlsx");
|
|
11645
12165
|
const bytes = await file.arrayBuffer();
|
|
11646
|
-
const result = xlsx ? await (0,
|
|
12166
|
+
const result = xlsx ? await (0, import_core11.importXlsx)(bytes) : (0, import_core11.importCsv)(new TextDecoder().decode(bytes));
|
|
11647
12167
|
setFormat(xlsx ? "xlsx" : "csv");
|
|
11648
12168
|
setFileName(file.name);
|
|
11649
12169
|
setFileBytes(file.size);
|
|
@@ -14003,10 +14523,10 @@ var import_design_system45 = require("@ai-matrx/design-system");
|
|
|
14003
14523
|
// src/RecordForm.tsx
|
|
14004
14524
|
var import_react80 = require("react");
|
|
14005
14525
|
var import_react81 = require("@ai-matrx/records/react");
|
|
14006
|
-
var
|
|
14526
|
+
var import_core13 = require("@ai-matrx/records/core");
|
|
14007
14527
|
|
|
14008
14528
|
// src/writeOnTheWinningVersion.ts
|
|
14009
|
-
var
|
|
14529
|
+
var import_core12 = require("@ai-matrx/records/core");
|
|
14010
14530
|
function sameStoredValue(left, right) {
|
|
14011
14531
|
if (left === right) return true;
|
|
14012
14532
|
const empty = (v) => v === null || v === void 0 || v === "";
|
|
@@ -14028,7 +14548,7 @@ async function updateOnTheWinningVersion(client, recordId, patch, expectedVersio
|
|
|
14028
14548
|
});
|
|
14029
14549
|
const first = await send(expectedVersion);
|
|
14030
14550
|
if (first.ok) return first;
|
|
14031
|
-
const conflict = (0,
|
|
14551
|
+
const conflict = (0, import_core12.asWriteConflict)(first.error, recordId);
|
|
14032
14552
|
if (!conflict) return first;
|
|
14033
14553
|
const untouched = base === "always" || Object.keys(patch).every((key) => sameStoredValue(conflict.contested_fields[key], base[key]));
|
|
14034
14554
|
if (!untouched) return first;
|
|
@@ -14265,7 +14785,7 @@ function RecordForm({
|
|
|
14265
14785
|
return out;
|
|
14266
14786
|
}, [draft]);
|
|
14267
14787
|
const predicted = (0, import_react80.useMemo)(
|
|
14268
|
-
() => fields.data ? (0,
|
|
14788
|
+
() => fields.data ? (0, import_core13.predictWriteRefusals)({ fields: fields.data, document: document2, ...recordType ? { recordType } : {} }) : [],
|
|
14269
14789
|
[fields.data, document2, recordType]
|
|
14270
14790
|
);
|
|
14271
14791
|
if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(RefusalNotice, { error: fields.error, className });
|
|
@@ -14314,7 +14834,7 @@ function RecordForm({
|
|
|
14314
14834
|
setEdit({
|
|
14315
14835
|
saving: false,
|
|
14316
14836
|
error: answered2.ok ? null : answered2.error,
|
|
14317
|
-
conflict: answered2.ok ? null : (0,
|
|
14837
|
+
conflict: answered2.ok ? null : (0, import_core13.asWriteConflict)(answered2.error, recordId)
|
|
14318
14838
|
});
|
|
14319
14839
|
const moved = answered2.ok ? answered2.data : null;
|
|
14320
14840
|
if (moved !== null) {
|
|
@@ -14420,7 +14940,7 @@ function FieldRow({
|
|
|
14420
14940
|
}) {
|
|
14421
14941
|
const id = `field-${field2.id}`;
|
|
14422
14942
|
const storedWords = document2?.[field2.key];
|
|
14423
|
-
const whole = typeof storedWords === "string" ? (0,
|
|
14943
|
+
const whole = typeof storedWords === "string" ? (0, import_core13.wholeValueOf)(document2, field2.key) : null;
|
|
14424
14944
|
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: [
|
|
14425
14945
|
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)(FieldLabel, { field: field2, htmlFor: id, children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(ProvenanceBadge, { document: document2, fieldKey: field2.key }) }),
|
|
14426
14946
|
whole ? (
|
|
@@ -14517,8 +15037,8 @@ function Peek({ tableId, recordId, onClose, className }) {
|
|
|
14517
15037
|
const title = document2 ? recordNameIn(table.data, document2) : "";
|
|
14518
15038
|
const href = host.hrefForRecord && table.data ? host.hrefForRecord({ table: table.data, recordId }) : void 0;
|
|
14519
15039
|
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: [
|
|
14520
|
-
/* @__PURE__ */ (0, import_jsx_runtime52.
|
|
14521
|
-
|
|
15040
|
+
/* @__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 }),
|
|
15041
|
+
/* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
14522
15042
|
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
|
|
14523
15043
|
import_react85.AlchemyMenu,
|
|
14524
15044
|
{
|
|
@@ -14556,7 +15076,7 @@ function Peek({ tableId, recordId, onClose, className }) {
|
|
|
14556
15076
|
// it would take and who can give it.
|
|
14557
15077
|
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)("span", { className: "max-w-[18rem] text-[11px] text-muted-foreground", children: may.why("write") })
|
|
14558
15078
|
) : null,
|
|
14559
|
-
onClose ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_design_system45.Button, { size: "sm", variant: "ghost", onClick: onClose, "aria-label": "Close", children: "\xD7" }) : null
|
|
15079
|
+
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
|
|
14560
15080
|
] }),
|
|
14561
15081
|
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_design_system45.Separator, {}),
|
|
14562
15082
|
talking && host.chat ? /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(import_jsx_runtime52.Fragment, { children: [
|
|
@@ -14687,14 +15207,14 @@ var LOOK_WORDS = {
|
|
|
14687
15207
|
// src/ViewSwitcher.tsx
|
|
14688
15208
|
var import_react88 = require("react");
|
|
14689
15209
|
var import_react89 = require("@ai-matrx/records/react");
|
|
14690
|
-
var
|
|
15210
|
+
var import_records17 = require("@ai-matrx/records");
|
|
14691
15211
|
var import_design_system47 = require("@ai-matrx/design-system");
|
|
14692
15212
|
var import_data_table11 = require("@ai-matrx/design-system/data-table");
|
|
14693
15213
|
|
|
14694
15214
|
// src/Pipeline.tsx
|
|
14695
15215
|
var import_react86 = require("react");
|
|
14696
15216
|
var import_react87 = require("@ai-matrx/records/react");
|
|
14697
|
-
var
|
|
15217
|
+
var import_records16 = require("@ai-matrx/records");
|
|
14698
15218
|
var import_design_system46 = require("@ai-matrx/design-system");
|
|
14699
15219
|
var import_data_table10 = require("@ai-matrx/design-system/data-table");
|
|
14700
15220
|
|
|
@@ -14801,7 +15321,7 @@ function PipelineBoard({
|
|
|
14801
15321
|
alive.current = false;
|
|
14802
15322
|
};
|
|
14803
15323
|
}, []);
|
|
14804
|
-
const filterKey = (0,
|
|
15324
|
+
const filterKey = (0, import_records16.stableFilterKey)(filter);
|
|
14805
15325
|
const reload = (0, import_react86.useCallback)(async () => {
|
|
14806
15326
|
setLoading(true);
|
|
14807
15327
|
const [read, board, held] = await Promise.all([
|
|
@@ -15448,8 +15968,8 @@ function useViewRecords(view, pageSize = 200, filter, viewId) {
|
|
|
15448
15968
|
cancelled = true;
|
|
15449
15969
|
};
|
|
15450
15970
|
}, [client, ruleId, pageSize]);
|
|
15451
|
-
const asked = (0,
|
|
15452
|
-
const fromView = asked !== "{}" && (asked === (0,
|
|
15971
|
+
const asked = (0, import_records17.stableFilterKey)(filter ?? null);
|
|
15972
|
+
const fromView = asked !== "{}" && (asked === (0, import_records17.stableFilterKey)(view.where ?? null) || asked === (0, import_records17.stableFilterKey)(view.filters ?? null));
|
|
15453
15973
|
const tableRows = (0, import_react88.useMemo)(() => table.data?.rows ?? [], [table.data]);
|
|
15454
15974
|
const manual = view.order === "manual" && Boolean(viewId) && !ruleId;
|
|
15455
15975
|
const hand = useHandOrderRows({
|
|
@@ -15499,6 +16019,7 @@ function ViewSwitcher({
|
|
|
15499
16019
|
onLayoutChange,
|
|
15500
16020
|
onViewChange,
|
|
15501
16021
|
onOpenRecord,
|
|
16022
|
+
openRecordId,
|
|
15502
16023
|
onMoved,
|
|
15503
16024
|
onNewRecordForm,
|
|
15504
16025
|
onAskWhoChanged,
|
|
@@ -15577,6 +16098,7 @@ function ViewSwitcher({
|
|
|
15577
16098
|
...local.presentation ?? view.presentation ? { presentation: local.presentation ?? view.presentation } : {},
|
|
15578
16099
|
...onViewChange ? { onPresentationChange: (next) => change({ presentation: next }) } : {},
|
|
15579
16100
|
...onOpenRecord ? { onOpenRecord } : {},
|
|
16101
|
+
...openRecordId ? { openRecordId } : {},
|
|
15580
16102
|
...onNewRecordForm ? { onNewRecordForm } : {},
|
|
15581
16103
|
...onAskWhoChanged ? { onAskWhoChanged } : {},
|
|
15582
16104
|
...viewId ? {
|
|
@@ -16025,7 +16547,7 @@ function Card2({
|
|
|
16025
16547
|
|
|
16026
16548
|
// src/ArchivedView.tsx
|
|
16027
16549
|
var import_react90 = require("react");
|
|
16028
|
-
var
|
|
16550
|
+
var import_records18 = require("@ai-matrx/records");
|
|
16029
16551
|
var import_react91 = require("@ai-matrx/records/react");
|
|
16030
16552
|
var import_design_system48 = require("@ai-matrx/design-system");
|
|
16031
16553
|
var import_jsx_runtime56 = require("react/jsx-runtime");
|
|
@@ -16035,7 +16557,7 @@ function mayNotRestoreLine(why) {
|
|
|
16035
16557
|
return said === "" ? "Bringing a record back needs permission to edit this table. Ask somebody who has it." : said;
|
|
16036
16558
|
}
|
|
16037
16559
|
function archivedRowLine(row, titleKey) {
|
|
16038
|
-
return `${recordName(row.document, titleKey)} \u2014 ${(0,
|
|
16560
|
+
return `${recordName(row.document, titleKey)} \u2014 ${(0, import_records18.archivedByLine)(row)}`;
|
|
16039
16561
|
}
|
|
16040
16562
|
function ArchivedView({
|
|
16041
16563
|
tableId,
|
|
@@ -16049,7 +16571,7 @@ function ArchivedView({
|
|
|
16049
16571
|
const table = (0, import_react91.useTable)(tableId);
|
|
16050
16572
|
const rights = useTableRights(table.data);
|
|
16051
16573
|
const fields = (0, import_react91.useFields)(tableId);
|
|
16052
|
-
const [lane, setLane] = (0, import_react90.useState)(laneFromHost ??
|
|
16574
|
+
const [lane, setLane] = (0, import_react90.useState)(laneFromHost ?? import_records18.DEFAULT_ARCHIVE_LANE);
|
|
16053
16575
|
const [rows, setRows] = (0, import_react90.useState)([]);
|
|
16054
16576
|
const [loading, setLoading] = (0, import_react90.useState)(true);
|
|
16055
16577
|
const [error, setError] = (0, import_react90.useState)(null);
|
|
@@ -16096,15 +16618,15 @@ function ArchivedView({
|
|
|
16096
16618
|
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: [
|
|
16097
16619
|
/* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
16098
16620
|
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("span", { className: "text-sm font-medium", children: "Archived" }),
|
|
16099
|
-
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("div", { className: "ml-auto flex items-center gap-0.5", role: "group", "aria-label": "Archived by", children:
|
|
16621
|
+
/* @__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)(
|
|
16100
16622
|
import_design_system48.Button,
|
|
16101
16623
|
{
|
|
16102
16624
|
size: "sm",
|
|
16103
16625
|
variant: option === lane ? "secondary" : "ghost",
|
|
16104
16626
|
"aria-pressed": option === lane,
|
|
16105
|
-
title:
|
|
16627
|
+
title: import_records18.ARCHIVE_LANE_MEANS[option],
|
|
16106
16628
|
onClick: () => pick(option),
|
|
16107
|
-
children:
|
|
16629
|
+
children: import_records18.ARCHIVE_LANE_LABEL[option]
|
|
16108
16630
|
},
|
|
16109
16631
|
option
|
|
16110
16632
|
)) })
|
|
@@ -16120,7 +16642,7 @@ function ArchivedView({
|
|
|
16120
16642
|
),
|
|
16121
16643
|
error ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(RefusalNotice, { error }) : null,
|
|
16122
16644
|
loading && !error ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_design_system48.Skeleton, { className: "h-24 w-full" }) : null,
|
|
16123
|
-
!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,
|
|
16645
|
+
!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,
|
|
16124
16646
|
!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)(
|
|
16125
16647
|
"li",
|
|
16126
16648
|
{
|
|
@@ -16130,7 +16652,7 @@ function ArchivedView({
|
|
|
16130
16652
|
children: [
|
|
16131
16653
|
/* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
16132
16654
|
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("span", { className: "truncate text-sm", children: recordName(row.document, titleKey) }),
|
|
16133
|
-
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("span", { className: "truncate text-xs text-muted-foreground", "data-testid": "archived-who", children: (0,
|
|
16655
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)("span", { className: "truncate text-xs text-muted-foreground", "data-testid": "archived-who", children: (0, import_records18.archivedByLine)(row) }),
|
|
16134
16656
|
refusal2 && refusal2.recordId === row.id ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(RefusalNotice, { error: refusal2.error, className: "mt-2" }) : null
|
|
16135
16657
|
] }),
|
|
16136
16658
|
mayRestore ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
|
|
@@ -16153,7 +16675,7 @@ function ArchivedView({
|
|
|
16153
16675
|
|
|
16154
16676
|
// src/ArchivedDisclosure.tsx
|
|
16155
16677
|
var import_react92 = require("react");
|
|
16156
|
-
var
|
|
16678
|
+
var import_records19 = require("@ai-matrx/records");
|
|
16157
16679
|
var import_react93 = require("@ai-matrx/records/react");
|
|
16158
16680
|
var import_design_system49 = require("@ai-matrx/design-system");
|
|
16159
16681
|
var import_jsx_runtime57 = require("react/jsx-runtime");
|
|
@@ -16253,10 +16775,10 @@ function ArchivedPortals({
|
|
|
16253
16775
|
error ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(RefusalNotice, { error }) : null,
|
|
16254
16776
|
rows === null && !error ? /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(import_design_system49.Skeleton, { className: "h-16 w-full" }) : null,
|
|
16255
16777
|
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,
|
|
16256
|
-
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,
|
|
16778
|
+
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,
|
|
16257
16779
|
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) => {
|
|
16258
16780
|
const asking = confirming === portal.portal_id;
|
|
16259
|
-
const matches2 = (0,
|
|
16781
|
+
const matches2 = (0, import_records19.portalTitleTypedBack)(typed, portal.title);
|
|
16260
16782
|
return /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(
|
|
16261
16783
|
"li",
|
|
16262
16784
|
{
|
|
@@ -16295,7 +16817,7 @@ function ArchivedPortals({
|
|
|
16295
16817
|
] }),
|
|
16296
16818
|
asking ? /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
|
|
16297
16819
|
/* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
|
|
16298
|
-
(0,
|
|
16820
|
+
(0, import_records19.portalConfirmLine)(portal.title),
|
|
16299
16821
|
" Restoring it puts it back exactly as it was: everyone who was invited can sign in again, and it shows the same Tables."
|
|
16300
16822
|
] }),
|
|
16301
16823
|
/* @__PURE__ */ (0, import_jsx_runtime57.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5", children: [
|
|
@@ -16442,7 +16964,7 @@ function ViewBar({ tableId, activeViewId, onActiveView, seed, settings, onViews,
|
|
|
16442
16964
|
);
|
|
16443
16965
|
}
|
|
16444
16966
|
if (error) return /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(RefusalNotice, { error, className, actions: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(import_design_system50.Button, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" }) });
|
|
16445
|
-
if (views === null) return /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(import_design_system50.Skeleton, { className: (0, import_design_system50.cn)("h-
|
|
16967
|
+
if (views === null) return /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(import_design_system50.Skeleton, { className: (0, import_design_system50.cn)("h-8 w-28 shrink-0", className) });
|
|
16446
16968
|
const current = views.find((v) => v.id === active) ?? null;
|
|
16447
16969
|
return /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(import_design_system50.Popover, { open, onOpenChange: setOpen, children: [
|
|
16448
16970
|
/* @__PURE__ */ (0, import_jsx_runtime58.jsx)(import_design_system50.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(
|
|
@@ -16450,7 +16972,7 @@ function ViewBar({ tableId, activeViewId, onActiveView, seed, settings, onViews,
|
|
|
16450
16972
|
{
|
|
16451
16973
|
size: "sm",
|
|
16452
16974
|
variant: "subtle",
|
|
16453
|
-
className: (0, import_design_system50.cn)("h-
|
|
16975
|
+
className: (0, import_design_system50.cn)("h-8 max-w-[14rem] shrink-0 gap-1 px-2 text-xs", className),
|
|
16454
16976
|
"aria-label": `View: ${current?.name ?? "none yet"}. Change view`,
|
|
16455
16977
|
"data-view-picker": "",
|
|
16456
16978
|
children: [
|
|
@@ -19253,7 +19775,7 @@ function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
|
|
|
19253
19775
|
// src/PortalsPanel.tsx
|
|
19254
19776
|
var import_react114 = require("react");
|
|
19255
19777
|
var import_react115 = require("@ai-matrx/records/react");
|
|
19256
|
-
var
|
|
19778
|
+
var import_records20 = require("@ai-matrx/records");
|
|
19257
19779
|
var import_design_system60 = require("@ai-matrx/design-system");
|
|
19258
19780
|
|
|
19259
19781
|
// src/BuildOrAsk.tsx
|
|
@@ -19717,7 +20239,7 @@ function PortalsPanel({ tableId, activePortalId, className }) {
|
|
|
19717
20239
|
] }) : null,
|
|
19718
20240
|
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,
|
|
19719
20241
|
/* @__PURE__ */ (0, import_jsx_runtime68.jsx)("ul", { className: "flex flex-col gap-2", children: shown3.map((portal) => {
|
|
19720
|
-
const url = `${origin}${(0,
|
|
20242
|
+
const url = `${origin}${(0, import_records20.portalPath)(portal.slug)}`;
|
|
19721
20243
|
const open = openId === portal.portal_id;
|
|
19722
20244
|
const linked = portal.portal_id === activePortalId;
|
|
19723
20245
|
return /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)(
|
|
@@ -19832,8 +20354,8 @@ function ArchivePortalControl({
|
|
|
19832
20354
|
}
|
|
19833
20355
|
),
|
|
19834
20356
|
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: [
|
|
19835
|
-
/* @__PURE__ */ (0, import_jsx_runtime68.jsx)("p", { className: "text-xs text-muted-foreground", "data-testid": "portal-archive-consequence", children: (0,
|
|
19836
|
-
/* @__PURE__ */ (0, import_jsx_runtime68.jsx)("p", { className: "text-xs text-muted-foreground", children: (0,
|
|
20357
|
+
/* @__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) }),
|
|
20358
|
+
/* @__PURE__ */ (0, import_jsx_runtime68.jsx)("p", { className: "text-xs text-muted-foreground", children: (0, import_records20.portalConfirmLine)(portal.title) }),
|
|
19837
20359
|
/* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5", children: [
|
|
19838
20360
|
/* @__PURE__ */ (0, import_jsx_runtime68.jsx)(
|
|
19839
20361
|
import_design_system60.BasicInput,
|
|
@@ -19851,7 +20373,7 @@ function ArchivePortalControl({
|
|
|
19851
20373
|
size: "sm",
|
|
19852
20374
|
variant: "destructive",
|
|
19853
20375
|
"data-testid": "portal-archive-do",
|
|
19854
|
-
disabled: busy || !(0,
|
|
20376
|
+
disabled: busy || !(0, import_records20.portalTitleTypedBack)(typed, portal.title),
|
|
19855
20377
|
onClick: () => void go(),
|
|
19856
20378
|
children: busy ? "Archiving\u2026" : "Archive it"
|
|
19857
20379
|
}
|
|
@@ -21010,7 +21532,7 @@ function usePublishGate() {
|
|
|
21010
21532
|
}
|
|
21011
21533
|
|
|
21012
21534
|
// src/FormBuilder.tsx
|
|
21013
|
-
var
|
|
21535
|
+
var import_records21 = require("@ai-matrx/records");
|
|
21014
21536
|
var import_design_system66 = require("@ai-matrx/design-system");
|
|
21015
21537
|
|
|
21016
21538
|
// src/FormRunner.tsx
|
|
@@ -21886,7 +22408,7 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, className }) {
|
|
|
21886
22408
|
/* @__PURE__ */ (0, import_jsx_runtime74.jsx)(
|
|
21887
22409
|
PublishRow,
|
|
21888
22410
|
{
|
|
21889
|
-
url: `${publicOrigin}${(0,
|
|
22411
|
+
url: `${publicOrigin}${(0, import_records21.publicFormPath)(draft.id)}`,
|
|
21890
22412
|
state: draft.state,
|
|
21891
22413
|
mayPublish: rights.structure && !publishGate.blocked,
|
|
21892
22414
|
why: publishGate.why ?? rights.why("structure"),
|
|
@@ -22904,8 +23426,8 @@ function SignBlock({ tableId, recordId, render, className }) {
|
|
|
22904
23426
|
// src/ChartBlock.tsx
|
|
22905
23427
|
var import_react138 = require("react");
|
|
22906
23428
|
var import_recharts = require("recharts");
|
|
22907
|
-
var
|
|
22908
|
-
var
|
|
23429
|
+
var import_records22 = require("@ai-matrx/records");
|
|
23430
|
+
var import_core14 = require("@ai-matrx/records/core");
|
|
22909
23431
|
var import_design_system71 = require("@ai-matrx/design-system");
|
|
22910
23432
|
var import_jsx_runtime79 = require("react/jsx-runtime");
|
|
22911
23433
|
var priorKey = (key) => `prior__${key}`;
|
|
@@ -22962,7 +23484,7 @@ function pretty(n) {
|
|
|
22962
23484
|
return Number.isInteger(n) ? n.toLocaleString() : n.toLocaleString(void 0, { maximumFractionDigits: 2 });
|
|
22963
23485
|
}
|
|
22964
23486
|
function asRefusal2(block) {
|
|
22965
|
-
return (0,
|
|
23487
|
+
return (0, import_core14.mapPgError)(
|
|
22966
23488
|
{
|
|
22967
23489
|
message: block.refused ?? "",
|
|
22968
23490
|
...block.sqlstate ? { code: block.sqlstate } : {}
|
|
@@ -22975,7 +23497,7 @@ function ChartBlock({ block, subject, className }) {
|
|
|
22975
23497
|
const kind = block.kind;
|
|
22976
23498
|
const tableId = block.table_id ?? subject;
|
|
22977
23499
|
const measures = block.measures && block.measures.length > 0 ? block.measures : [{ op: "count" }];
|
|
22978
|
-
const primary = (0,
|
|
23500
|
+
const primary = (0, import_records22.measureKey)(measures[0]);
|
|
22979
23501
|
const windows = kind === "stuck" ? null : block.compare ?? null;
|
|
22980
23502
|
const compared = !!windows;
|
|
22981
23503
|
const grain = block.bucket?.by ?? null;
|
|
@@ -23907,7 +24429,7 @@ function TargetControl({
|
|
|
23907
24429
|
// src/FormsPanel.tsx
|
|
23908
24430
|
var import_react141 = require("react");
|
|
23909
24431
|
var import_react142 = require("@ai-matrx/records/react");
|
|
23910
|
-
var
|
|
24432
|
+
var import_records23 = require("@ai-matrx/records");
|
|
23911
24433
|
var import_design_system73 = require("@ai-matrx/design-system");
|
|
23912
24434
|
var import_jsx_runtime81 = require("react/jsx-runtime");
|
|
23913
24435
|
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.";
|
|
@@ -24011,7 +24533,7 @@ function FormsPanel({ tableId, activeFormId, className }) {
|
|
|
24011
24533
|
}
|
|
24012
24534
|
) : null,
|
|
24013
24535
|
/* @__PURE__ */ (0, import_jsx_runtime81.jsx)("ul", { className: "flex flex-col gap-2", children: forms.map((form) => {
|
|
24014
|
-
const url = `${origin}${(0,
|
|
24536
|
+
const url = `${origin}${(0, import_records23.publicFormPath)(form.form_id)}`;
|
|
24015
24537
|
return /* @__PURE__ */ (0, import_jsx_runtime81.jsxs)(
|
|
24016
24538
|
"li",
|
|
24017
24539
|
{
|
|
@@ -24076,7 +24598,7 @@ function FormsPanel({ tableId, activeFormId, className }) {
|
|
|
24076
24598
|
// src/BookingBuilder.tsx
|
|
24077
24599
|
var import_react143 = require("react");
|
|
24078
24600
|
var import_react144 = require("@ai-matrx/records/react");
|
|
24079
|
-
var
|
|
24601
|
+
var import_records24 = require("@ai-matrx/records");
|
|
24080
24602
|
var import_design_system74 = require("@ai-matrx/design-system");
|
|
24081
24603
|
var import_jsx_runtime82 = require("react/jsx-runtime");
|
|
24082
24604
|
var STORE_ANSWERS_THESE = ["slot", "status", "booked_with"];
|
|
@@ -24220,7 +24742,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
|
|
|
24220
24742
|
if (!rights.structure) {
|
|
24221
24743
|
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." });
|
|
24222
24744
|
}
|
|
24223
|
-
const url = formId ? `${publicOrigin}${(0,
|
|
24745
|
+
const url = formId ? `${publicOrigin}${(0, import_records24.bookingPath)(formId)}` : null;
|
|
24224
24746
|
const shown3 = offer ?? null;
|
|
24225
24747
|
return (
|
|
24226
24748
|
// THE RAIL IS 384px WIDE AND THE VIEWPORT IS NOT (walk 1, 2026-09-21):
|
|
@@ -24422,7 +24944,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
|
|
|
24422
24944
|
// src/BookingSlots.tsx
|
|
24423
24945
|
var import_react145 = require("react");
|
|
24424
24946
|
var import_react146 = require("@ai-matrx/records/react");
|
|
24425
|
-
var
|
|
24947
|
+
var import_records25 = require("@ai-matrx/records");
|
|
24426
24948
|
var import_design_system75 = require("@ai-matrx/design-system");
|
|
24427
24949
|
var import_jsx_runtime83 = require("react/jsx-runtime");
|
|
24428
24950
|
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.";
|
|
@@ -24539,7 +25061,7 @@ function BookingSlots({ tableId, activeBookingId, className }) {
|
|
|
24539
25061
|
" Open the table the appointments should land in to make one."
|
|
24540
25062
|
] }) : null,
|
|
24541
25063
|
/* @__PURE__ */ (0, import_jsx_runtime83.jsx)("ul", { className: "flex flex-col gap-2", children: pages.map((page) => {
|
|
24542
|
-
const url = `${origin}${(0,
|
|
25064
|
+
const url = `${origin}${(0, import_records25.bookingPath)(page.form_id)}`;
|
|
24543
25065
|
const mayOpen = levels.data?.[page.table_id] === "admin";
|
|
24544
25066
|
const open = page.published_at !== null && page.closed_at === null;
|
|
24545
25067
|
return /* @__PURE__ */ (0, import_jsx_runtime83.jsxs)(
|
|
@@ -24641,7 +25163,7 @@ var import_design_system77 = require("@ai-matrx/design-system");
|
|
|
24641
25163
|
|
|
24642
25164
|
// src/CaptureRun.tsx
|
|
24643
25165
|
var import_react147 = require("react");
|
|
24644
|
-
var
|
|
25166
|
+
var import_records26 = require("@ai-matrx/records");
|
|
24645
25167
|
var import_react148 = require("@ai-matrx/records/react");
|
|
24646
25168
|
var import_design_system76 = require("@ai-matrx/design-system");
|
|
24647
25169
|
var import_jsx_runtime84 = require("react/jsx-runtime");
|
|
@@ -24696,7 +25218,7 @@ function CaptureRun({ sheetId, face: given, className }) {
|
|
|
24696
25218
|
const [sending, setSending] = (0, import_react147.useState)(false);
|
|
24697
25219
|
const queueRef = (0, import_react147.useRef)(null);
|
|
24698
25220
|
(0, import_react147.useEffect)(() => {
|
|
24699
|
-
const q2 = (0,
|
|
25221
|
+
const q2 = (0, import_records26.openCaptureQueue)({
|
|
24700
25222
|
onChange: (c, all) => {
|
|
24701
25223
|
setCounts(c);
|
|
24702
25224
|
setItems(all);
|
|
@@ -24963,7 +25485,7 @@ function CaptureRun({ sheetId, face: given, className }) {
|
|
|
24963
25485
|
value: String(answers[q.field] ?? ""),
|
|
24964
25486
|
onChange: (e) => {
|
|
24965
25487
|
const raw = e.target.value;
|
|
24966
|
-
const answered2 = field2 ? (0,
|
|
25488
|
+
const answered2 = field2 ? (0, import_records26.coerceTypedAnswer)(field2, raw, { label: q.ask ?? (0, import_records26.coercionLabel)(field2) }) : { value: raw };
|
|
24967
25489
|
setAnswers((held) => ({
|
|
24968
25490
|
...held,
|
|
24969
25491
|
[q.field]: raw === "" ? "" : "refusal" in answered2 ? raw : answered2.value
|
|
@@ -25619,8 +26141,8 @@ function recordsDataSource(client, fallbackSchema = "custom") {
|
|
|
25619
26141
|
// src/TablesHome.tsx
|
|
25620
26142
|
var import_react163 = require("react");
|
|
25621
26143
|
var import_react164 = require("@ai-matrx/records/react");
|
|
25622
|
-
var
|
|
25623
|
-
var
|
|
26144
|
+
var import_core16 = require("@ai-matrx/records/core");
|
|
26145
|
+
var import_records28 = require("@ai-matrx/records");
|
|
25624
26146
|
var import_design_system83 = require("@ai-matrx/design-system");
|
|
25625
26147
|
|
|
25626
26148
|
// src/ExampleTables.tsx
|
|
@@ -25714,11 +26236,11 @@ function ExampleTables({
|
|
|
25714
26236
|
}
|
|
25715
26237
|
|
|
25716
26238
|
// src/createTable.ts
|
|
25717
|
-
var
|
|
26239
|
+
var import_core15 = require("@ai-matrx/records/core");
|
|
25718
26240
|
|
|
25719
26241
|
// src/WhereItLives.tsx
|
|
25720
26242
|
var import_react161 = require("react");
|
|
25721
|
-
var
|
|
26243
|
+
var import_records27 = require("@ai-matrx/records");
|
|
25722
26244
|
var import_react162 = require("@ai-matrx/records/react");
|
|
25723
26245
|
var import_design_system82 = require("@ai-matrx/design-system");
|
|
25724
26246
|
var import_jsx_runtime91 = require("react/jsx-runtime");
|
|
@@ -25844,7 +26366,7 @@ function WhereItLives({ tableId, knownOrganizationName, onMoved, variant = "head
|
|
|
25844
26366
|
/* @__PURE__ */ (0, import_jsx_runtime91.jsx)(SharedNameAddress, { to: choosing, all: answer.home.destinations }),
|
|
25845
26367
|
"?"
|
|
25846
26368
|
] }),
|
|
25847
|
-
/* @__PURE__ */ (0, import_jsx_runtime91.jsx)("p", { className: "text-muted-foreground", children: (0,
|
|
26369
|
+
/* @__PURE__ */ (0, import_jsx_runtime91.jsx)("p", { className: "text-muted-foreground", children: (0, import_records27.moveConsequence)(answer.home, choosing) }),
|
|
25848
26370
|
said?.tone === "refused" ? /* @__PURE__ */ (0, import_jsx_runtime91.jsx)(
|
|
25849
26371
|
import_design_system82.ErrorBox,
|
|
25850
26372
|
{
|
|
@@ -25992,8 +26514,8 @@ function TablesHome({ onOpenTable, organizationName, makingOnly = false, classNa
|
|
|
25992
26514
|
alive = false;
|
|
25993
26515
|
};
|
|
25994
26516
|
}, [client]);
|
|
25995
|
-
const listed = facts.phase === "read" ? (0,
|
|
25996
|
-
(0,
|
|
26517
|
+
const listed = facts.phase === "read" ? (0, import_records28.withTablePlacement)(
|
|
26518
|
+
(0, import_core16.withTableFacts)(tables.data ?? [], facts.rows, me),
|
|
25997
26519
|
facts.rows
|
|
25998
26520
|
) : tables.data ?? [];
|
|
25999
26521
|
const [showEverything, setShowEverything] = (0, import_react163.useState)(false);
|
|
@@ -26037,7 +26559,7 @@ function TablesHome({ onOpenTable, organizationName, makingOnly = false, classNa
|
|
|
26037
26559
|
if (!trimmed) return;
|
|
26038
26560
|
setBusy(true);
|
|
26039
26561
|
setError(null);
|
|
26040
|
-
const result = await (0,
|
|
26562
|
+
const result = await (0, import_core15.declareTable)(client, { name: trimmed, slug: (0, import_core15.tokenFor)(trimmed) });
|
|
26041
26563
|
setBusy(false);
|
|
26042
26564
|
if (!result.ok) {
|
|
26043
26565
|
setError(result.error);
|
|
@@ -26197,7 +26719,7 @@ function TablesHome({ onOpenTable, organizationName, makingOnly = false, classNa
|
|
|
26197
26719
|
|
|
26198
26720
|
// src/TablePage.tsx
|
|
26199
26721
|
var import_react168 = require("react");
|
|
26200
|
-
var
|
|
26722
|
+
var import_records29 = require("@ai-matrx/records");
|
|
26201
26723
|
var import_react169 = require("@ai-matrx/records/react");
|
|
26202
26724
|
var import_design_system85 = require("@ai-matrx/design-system");
|
|
26203
26725
|
|
|
@@ -26304,25 +26826,27 @@ function ViewFilter({ tableId, view, onApply, className }) {
|
|
|
26304
26826
|
|
|
26305
26827
|
// src/toolbarFit.ts
|
|
26306
26828
|
var import_react167 = require("react");
|
|
26307
|
-
var
|
|
26829
|
+
var FOLDED_GUESS = { look: 104, chooser: 96, tools: 120 };
|
|
26308
26830
|
function toolbarFold(input) {
|
|
26309
|
-
const { available, needsNow, gap, folded
|
|
26310
|
-
const
|
|
26311
|
-
|
|
26312
|
-
|
|
26313
|
-
|
|
26314
|
-
|
|
26315
|
-
const
|
|
26316
|
-
if (
|
|
26317
|
-
return
|
|
26831
|
+
const { available, needsNow, gap, folded } = input;
|
|
26832
|
+
const pieces = [
|
|
26833
|
+
{ piece: input.look, stage: 1, guess: FOLDED_GUESS.look },
|
|
26834
|
+
{ piece: input.chooser, stage: 2, guess: FOLDED_GUESS.chooser },
|
|
26835
|
+
...input.tools ? [{ piece: input.tools, stage: 3, guess: FOLDED_GUESS.tools }] : []
|
|
26836
|
+
];
|
|
26837
|
+
const widthAt = ({ piece, stage, guess }, fold) => {
|
|
26838
|
+
if (piece.now === 0 && piece.full === null) return 0;
|
|
26839
|
+
if (fold >= stage) return piece.folded ?? (folded >= stage ? piece.now : Math.min(guess, piece.full ?? piece.now));
|
|
26840
|
+
return piece.full ?? piece.now;
|
|
26318
26841
|
};
|
|
26319
26842
|
const withGap = (width) => width > 0 ? width + gap : 0;
|
|
26320
|
-
const base =
|
|
26321
|
-
const
|
|
26843
|
+
const base = pieces.reduce((rest, p) => rest - withGap(p.piece.now), needsNow);
|
|
26844
|
+
const stages = input.tools ? [0, 1, 2, 3] : [0, 1, 2];
|
|
26845
|
+
const needs = stages.map((fold) => pieces.reduce((sum, p) => sum + withGap(widthAt(p, fold)), base));
|
|
26322
26846
|
const fits = needs.findIndex((need) => need <= available + 0.5);
|
|
26323
|
-
if (fits >= 0) return fits;
|
|
26847
|
+
if (fits >= 0) return stages[fits];
|
|
26324
26848
|
const least = Math.min(...needs);
|
|
26325
|
-
return needs.findIndex((need) => need <= least + 0.5);
|
|
26849
|
+
return stages[needs.findIndex((need) => need <= least + 0.5)];
|
|
26326
26850
|
}
|
|
26327
26851
|
function naturalWidth(el) {
|
|
26328
26852
|
const html = el;
|
|
@@ -26355,7 +26879,12 @@ function useToolbarFit(row) {
|
|
|
26355
26879
|
const [fold, setFold] = (0, import_react167.useState)(0);
|
|
26356
26880
|
(0, import_react167.useEffect)(() => {
|
|
26357
26881
|
if (!row) return;
|
|
26358
|
-
const widths = {
|
|
26882
|
+
const widths = {
|
|
26883
|
+
look: { full: null, folded: null },
|
|
26884
|
+
chooser: { full: null, folded: null },
|
|
26885
|
+
tools: { full: null, folded: null }
|
|
26886
|
+
};
|
|
26887
|
+
const stageOf = { look: 1, chooser: 2, tools: 3 };
|
|
26359
26888
|
let current = 0;
|
|
26360
26889
|
let pending = 0;
|
|
26361
26890
|
let stopped = false;
|
|
@@ -26364,23 +26893,22 @@ function useToolbarFit(row) {
|
|
|
26364
26893
|
if (stopped) return;
|
|
26365
26894
|
const available = row.clientWidth;
|
|
26366
26895
|
if (available <= 0) return;
|
|
26367
|
-
const
|
|
26368
|
-
|
|
26369
|
-
|
|
26370
|
-
|
|
26371
|
-
|
|
26372
|
-
|
|
26373
|
-
|
|
26374
|
-
|
|
26896
|
+
const read = (name) => {
|
|
26897
|
+
const el = name === "tools" ? row.querySelector(":scope > [data-toolbar-fit-slot]") : row.querySelector(`:scope > [data-toolbar-fit="${name}"]`);
|
|
26898
|
+
const now = !el ? 0 : name === "tools" ? slotNeeds(el) : naturalWidth(el);
|
|
26899
|
+
const w = widths[name];
|
|
26900
|
+
if (el && now > 0) {
|
|
26901
|
+
if (current >= stageOf[name]) w.folded = now;
|
|
26902
|
+
else w.full = now;
|
|
26903
|
+
}
|
|
26904
|
+
if (!el && current < stageOf[name]) w.full = null;
|
|
26905
|
+
return { now, full: w.full, folded: w.folded };
|
|
26906
|
+
};
|
|
26907
|
+
const look = read("look");
|
|
26908
|
+
const chooser = read("chooser");
|
|
26909
|
+
const tools = read("tools");
|
|
26375
26910
|
const { needs, gap } = rowNeeds(row);
|
|
26376
|
-
const next = toolbarFold({
|
|
26377
|
-
available,
|
|
26378
|
-
needsNow: needs,
|
|
26379
|
-
gap,
|
|
26380
|
-
folded: current,
|
|
26381
|
-
look: { now: lookNow, full: widths.lookFull, folded: widths.lookFolded },
|
|
26382
|
-
chooser: { now: chooserNow, full: widths.chooserFull }
|
|
26383
|
-
});
|
|
26911
|
+
const next = toolbarFold({ available, needsNow: needs, gap, folded: current, look, chooser, tools });
|
|
26384
26912
|
if (next !== current) {
|
|
26385
26913
|
current = next;
|
|
26386
26914
|
setFold(next);
|
|
@@ -26417,6 +26945,12 @@ function useToolbarFit(row) {
|
|
|
26417
26945
|
}, [row]);
|
|
26418
26946
|
return fold;
|
|
26419
26947
|
}
|
|
26948
|
+
function slotNeeds(slot) {
|
|
26949
|
+
const inner = shownChildren(slot);
|
|
26950
|
+
const style = typeof getComputedStyle === "undefined" ? null : getComputedStyle(slot);
|
|
26951
|
+
const gap = style ? parseFloat(style.columnGap || "0") || 0 : 0;
|
|
26952
|
+
return inner.reduce((sum, c) => sum + naturalWidth(c), 0) + gap * Math.max(0, inner.length - 1);
|
|
26953
|
+
}
|
|
26420
26954
|
function clippedEnds(scrollLeft, clientWidth, scrollWidth) {
|
|
26421
26955
|
const start = scrollLeft > 1;
|
|
26422
26956
|
const end = scrollLeft + clientWidth < scrollWidth - 1;
|
|
@@ -26560,7 +27094,7 @@ function TableNotHere({
|
|
|
26560
27094
|
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: [
|
|
26561
27095
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("p", { className: "text-sm font-medium", children: "This table is archived" }),
|
|
26562
27096
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("p", { className: "max-w-prose text-xs text-muted-foreground", children: TABLE_IS_ARCHIVED }),
|
|
26563
|
-
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("p", { className: "text-xs text-muted-foreground", children: (0,
|
|
27097
|
+
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("p", { className: "text-xs text-muted-foreground", children: (0, import_records29.archivedByLine)(row) }),
|
|
26564
27098
|
refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(RefusalNotice, { error: refusal2 }) : null,
|
|
26565
27099
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsxs)("div", { className: "flex gap-2", children: [
|
|
26566
27100
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
|
|
@@ -26776,6 +27310,8 @@ function TablePage({
|
|
|
26776
27310
|
const face = tableOpening(view, hostLayouts.map((h) => h.id));
|
|
26777
27311
|
const designated = face.kind === "designated";
|
|
26778
27312
|
const chooserInline = !designated && !phone && toolbarFold2 < 2;
|
|
27313
|
+
const chooserCompact = !designated && !phone && toolbarFold2 >= 2;
|
|
27314
|
+
const controlsInSheet = phone && !designated;
|
|
26779
27315
|
(0, import_react168.useEffect)(() => {
|
|
26780
27316
|
if (face.kind !== "designated") return;
|
|
26781
27317
|
if (hostFromLink || opened.layout || layoutChosen) return;
|
|
@@ -26955,6 +27491,50 @@ function TablePage({
|
|
|
26955
27491
|
};
|
|
26956
27492
|
const footer = footerOf(shown3?.presentation);
|
|
26957
27493
|
const setFooter = (next) => void patchView({ presentation: { ...shown3?.presentation ?? {}, footer: next } });
|
|
27494
|
+
const layoutRadio = /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(
|
|
27495
|
+
import_design_system85.DropdownMenuRadioGroup,
|
|
27496
|
+
{
|
|
27497
|
+
value: layoutShowing ?? "",
|
|
27498
|
+
onValueChange: (value) => {
|
|
27499
|
+
if (VIEW_LAYOUTS.includes(value)) pickLayout(value);
|
|
27500
|
+
else pickHostLayout(value);
|
|
27501
|
+
},
|
|
27502
|
+
children: [
|
|
27503
|
+
hostLayouts.map((option) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuRadioItem, { value: option.id, children: option.label }, `host:${option.id}`)),
|
|
27504
|
+
VIEW_LAYOUTS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuRadioItem, { value: option, children: LAYOUT_LABEL[option] }, option))
|
|
27505
|
+
]
|
|
27506
|
+
}
|
|
27507
|
+
);
|
|
27508
|
+
const sheetExtras = controlsInSheet && main === "records" && !hostLayout && shownLayout === "grid" ? /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_jsx_runtime94.Fragment, { children: [
|
|
27509
|
+
/* @__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 }) }) }),
|
|
27510
|
+
/* @__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: [
|
|
27511
|
+
VIEW_LAYOUTS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
|
|
27512
|
+
import_design_system85.Button,
|
|
27513
|
+
{
|
|
27514
|
+
size: "sm",
|
|
27515
|
+
variant: layoutShowing === option ? "secondary" : "ghost",
|
|
27516
|
+
"aria-pressed": layoutShowing === option,
|
|
27517
|
+
className: "h-8 !w-auto",
|
|
27518
|
+
onClick: () => pickLayout(option),
|
|
27519
|
+
children: LAYOUT_LABEL[option]
|
|
27520
|
+
},
|
|
27521
|
+
option
|
|
27522
|
+
)),
|
|
27523
|
+
hostLayouts.map((option) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
|
|
27524
|
+
import_design_system85.Button,
|
|
27525
|
+
{
|
|
27526
|
+
size: "sm",
|
|
27527
|
+
variant: layoutShowing === option.id ? "secondary" : "ghost",
|
|
27528
|
+
"aria-pressed": layoutShowing === option.id,
|
|
27529
|
+
className: "h-8 !w-auto",
|
|
27530
|
+
onClick: () => pickHostLayout(option.id),
|
|
27531
|
+
children: option.label
|
|
27532
|
+
},
|
|
27533
|
+
`host:${option.id}`
|
|
27534
|
+
))
|
|
27535
|
+
] }),
|
|
27536
|
+
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
|
|
27537
|
+
] }) : void 0;
|
|
26958
27538
|
const pageActions = /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_jsx_runtime94.Fragment, { children: [
|
|
26959
27539
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
|
|
26960
27540
|
ShareControl,
|
|
@@ -26976,22 +27556,9 @@ function TablePage({
|
|
|
26976
27556
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("circle", { cx: "19", cy: "12", r: "1.75" })
|
|
26977
27557
|
] }) }) }),
|
|
26978
27558
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_design_system85.DropdownMenuContent, { align: "end", className: "w-56", "data-table-menu-content": "", children: [
|
|
26979
|
-
!chooserInline ? /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_jsx_runtime94.Fragment, { children: [
|
|
27559
|
+
!chooserInline && !chooserCompact ? /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_jsx_runtime94.Fragment, { children: [
|
|
26980
27560
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuLabel, { className: "text-xs text-muted-foreground", children: "Layout" }),
|
|
26981
|
-
|
|
26982
|
-
import_design_system85.DropdownMenuRadioGroup,
|
|
26983
|
-
{
|
|
26984
|
-
value: layoutShowing ?? "",
|
|
26985
|
-
onValueChange: (value) => {
|
|
26986
|
-
if (VIEW_LAYOUTS.includes(value)) pickLayout(value);
|
|
26987
|
-
else pickHostLayout(value);
|
|
26988
|
-
},
|
|
26989
|
-
children: [
|
|
26990
|
-
hostLayouts.map((option) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuRadioItem, { value: option.id, children: option.label }, `host:${option.id}`)),
|
|
26991
|
-
VIEW_LAYOUTS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuRadioItem, { value: option, children: LAYOUT_LABEL[option] }, option))
|
|
26992
|
-
]
|
|
26993
|
-
}
|
|
26994
|
-
),
|
|
27561
|
+
layoutRadio,
|
|
26995
27562
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuSeparator, {})
|
|
26996
27563
|
] }) : null,
|
|
26997
27564
|
MENU.filter((entry) => entry.shown(rights)).map((entry, index, shownEntries) => /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
|
|
@@ -27111,11 +27678,26 @@ function TablePage({
|
|
|
27111
27678
|
`host:${option.id}`
|
|
27112
27679
|
))
|
|
27113
27680
|
] }) : null,
|
|
27114
|
-
|
|
27681
|
+
chooserCompact ? (
|
|
27682
|
+
/* THE ROW HAD NO ROOM (lane C, review 2026-09-26 D6: the switcher vanished at 1280 px and
|
|
27683
|
+
with the record panel open): the chooser folds to ONE button naming the layout that is
|
|
27684
|
+
showing — it never leaves the row. */
|
|
27685
|
+
/* @__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: [
|
|
27686
|
+
/* @__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: [
|
|
27687
|
+
layoutShowingLabel ?? "Layout",
|
|
27688
|
+
/* @__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" }) })
|
|
27689
|
+
] }) }),
|
|
27690
|
+
/* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_design_system85.DropdownMenuContent, { align: "start", className: "w-44", "data-layout-chooser-menu": "", children: [
|
|
27691
|
+
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuLabel, { className: "text-xs text-muted-foreground", children: "Layout" }),
|
|
27692
|
+
layoutRadio
|
|
27693
|
+
] })
|
|
27694
|
+
] }) })
|
|
27695
|
+
) : null,
|
|
27696
|
+
main === "records" && !hostLayout && !(controlsInSheet && shownLayout === "grid") ? /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(ViewFilter, { tableId, view: shown3, onApply: (where) => void patchView({ where }) }) : null,
|
|
27115
27697
|
lookShowing && main === "records" && toolbarFold2 >= 1 && !namingLook ? (
|
|
27116
27698
|
/* FOLDED: the row did not fit, so the three doors are one menu (same doors, same words). */
|
|
27117
27699
|
/* @__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: [
|
|
27118
|
-
/* @__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-
|
|
27700
|
+
/* @__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: [
|
|
27119
27701
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("span", { "aria-hidden": "true", className: "h-1.5 w-1.5 shrink-0 rounded-full bg-primary" }),
|
|
27120
27702
|
LOOK_WORDS.folded,
|
|
27121
27703
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsxs)("span", { className: "sr-only", children: [
|
|
@@ -27133,7 +27715,7 @@ function TablePage({
|
|
|
27133
27715
|
) : 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: [
|
|
27134
27716
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("span", { className: "sr-only", children: LOOK_WORDS.yours }),
|
|
27135
27717
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)("span", { "aria-hidden": "true", className: "h-1.5 w-1.5 shrink-0 rounded-full bg-primary" }),
|
|
27136
|
-
lookSavable ? /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", variant: "outline", className: "h-
|
|
27718
|
+
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,
|
|
27137
27719
|
rights.write ? namingLook ? /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(
|
|
27138
27720
|
"form",
|
|
27139
27721
|
{
|
|
@@ -27150,15 +27732,15 @@ function TablePage({
|
|
|
27150
27732
|
"aria-label": LOOK_WORDS.newName,
|
|
27151
27733
|
value: lookName,
|
|
27152
27734
|
onChange: (e) => setLookName(e.target.value),
|
|
27153
|
-
className: "h-
|
|
27735
|
+
className: "h-8 w-36 text-xs",
|
|
27154
27736
|
placeholder: "View name"
|
|
27155
27737
|
}
|
|
27156
27738
|
),
|
|
27157
|
-
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", type: "submit", className: "h-
|
|
27739
|
+
/* @__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" })
|
|
27158
27740
|
]
|
|
27159
27741
|
}
|
|
27160
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", variant: "ghost", className: "h-
|
|
27161
|
-
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", variant: "ghost", className: "h-
|
|
27742
|
+
) : /* @__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,
|
|
27743
|
+
/* @__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 })
|
|
27162
27744
|
] }) : null,
|
|
27163
27745
|
/* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
|
|
27164
27746
|
"div",
|
|
@@ -27239,7 +27821,7 @@ function TablePage({
|
|
|
27239
27821
|
}
|
|
27240
27822
|
}
|
|
27241
27823
|
)
|
|
27242
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(TableToolbarSlotContext.Provider, { value: toolbarSlot, children: /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(
|
|
27824
|
+
) : /* @__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)(
|
|
27243
27825
|
"div",
|
|
27244
27826
|
{
|
|
27245
27827
|
className: (0, import_design_system85.cn)(
|
|
@@ -27301,6 +27883,7 @@ function TablePage({
|
|
|
27301
27883
|
setOpenRecord(recordId);
|
|
27302
27884
|
setRail("record");
|
|
27303
27885
|
},
|
|
27886
|
+
openRecordId: rail === "record" ? openRecord : null,
|
|
27304
27887
|
onAskWhoChanged: (fieldKey, recordId) => {
|
|
27305
27888
|
setAsking({ fieldKey, recordId });
|
|
27306
27889
|
setRail("who-changed");
|
|
@@ -27310,7 +27893,7 @@ function TablePage({
|
|
|
27310
27893
|
view || faceUnknown ? null : /* @__PURE__ */ (0, import_jsx_runtime94.jsx)("p", { className: "text-xs text-muted-foreground", children: VIEW_NOT_SAVED_YET })
|
|
27311
27894
|
]
|
|
27312
27895
|
}
|
|
27313
|
-
) })
|
|
27896
|
+
) }) }) })
|
|
27314
27897
|
] }),
|
|
27315
27898
|
rail === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(RailFrame, { phone, title: RAIL_TITLE[rail], onClose: () => setRail("none"), children: [
|
|
27316
27899
|
rail === "settings" ? /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
|