@ai-matrx/records-ui 0.115.75 → 0.115.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/pickers.js CHANGED
@@ -2046,7 +2046,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
2046
2046
 
2047
2047
  // src/buttonCell.tsx
2048
2048
  import { useState as useState24 } from "react";
2049
- import { Button as Button16, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Popover as Popover4, PopoverContent as PopoverContent4, PopoverTrigger as PopoverTrigger4, cn as cn21 } from "@ai-matrx/design-system";
2049
+ import { Button as Button16, Dialog as Dialog2, DialogContent as DialogContent2, DialogDescription, DialogHeader as DialogHeader2, DialogTitle as DialogTitle2, Popover as Popover4, PopoverContent as PopoverContent4, PopoverTrigger as PopoverTrigger4, cn as cn21 } from "@ai-matrx/design-system";
2050
2050
  import { CHOICE_CHIP_CLASS as CHOICE_CHIP_CLASS2, NEUTRAL_CHIP_CLASS as NEUTRAL_CHIP_CLASS2, isStyleColor as isStyleColor2 } from "@ai-matrx/design-system/data-table";
2051
2051
  import { buttonNeeds as buttonNeeds2, buttonOf as buttonOf2 } from "@ai-matrx/records";
2052
2052
  import { useOptionalRecordsClient as useOptionalRecordsClient8 } from "@ai-matrx/records/react";
@@ -2584,9 +2584,7 @@ var NOT_TYPED_BY_NAME = /* @__PURE__ */ new Set([
2584
2584
  "fx.lte",
2585
2585
  "fx.gt",
2586
2586
  "fx.gte",
2587
- "fx.autonumber",
2588
- "fx.created_time",
2589
- "fx.modified_time"
2587
+ "fx.autonumber"
2590
2588
  ]);
2591
2589
  function formulaFunctionNames(kinds) {
2592
2590
  return [...new Set(kinds.map((row) => row.node))].filter((node) => node.startsWith("fx.") && !NOT_TYPED_BY_NAME.has(node)).map((node) => node.slice(3).toUpperCase()).sort();
@@ -3439,13 +3437,38 @@ var CONTEXT_POLICY_LABEL = {
3439
3437
  exclude: "No, keep it away from assistants"
3440
3438
  };
3441
3439
  var SUBSCRIPTION_CADENCES = exhaustiveValuesOf()(["instant", "hourly", "daily", "weekly"]);
3442
- var ROLLUP_AGG_VALUES = ["count", "sum", "avg", "min", "max"];
3440
+ var ROLLUP_AGG_VALUES = [
3441
+ "count",
3442
+ "sum",
3443
+ "avg",
3444
+ "min",
3445
+ "max",
3446
+ // AIRTABLE PARITY (lane AT-FORMULA): the aggregates that read the linked values as they are.
3447
+ "countall",
3448
+ "counta",
3449
+ "and",
3450
+ "or",
3451
+ "xor",
3452
+ "arrayjoin",
3453
+ "concatenate",
3454
+ "arrayunique",
3455
+ "arraycompact"
3456
+ ];
3443
3457
  var ROLLUP_AGG_LABEL = {
3444
3458
  count: "How many there are",
3445
3459
  sum: "The total of",
3446
3460
  avg: "The average of",
3447
3461
  min: "The smallest",
3448
- max: "The largest"
3462
+ max: "The largest",
3463
+ countall: "How many values, empty ones too",
3464
+ counta: "How many values are filled in",
3465
+ and: "Yes when every value is yes",
3466
+ or: "Yes when any value is yes",
3467
+ xor: "Yes when an odd number are yes",
3468
+ arrayjoin: "All the values, joined with commas",
3469
+ concatenate: "All the values, run together",
3470
+ arrayunique: "Each different value once",
3471
+ arraycompact: "The values that are filled in"
3449
3472
  };
3450
3473
  var FORMULA_OP_VALUES = ["concat", "add", "sub", "mul", "div"];
3451
3474
  var FORMULA_OP_LABEL = {
@@ -3917,7 +3940,7 @@ function linkChipText(raw) {
3917
3940
  // src/gridAttachments.tsx
3918
3941
  import { useEffect as useEffect11, useRef as useRef10, useState as useState17 } from "react";
3919
3942
  import { useOptionalRecordsClient as useOptionalRecordsClient5 } from "@ai-matrx/records/react";
3920
- import { Badge as Badge3, Button as Button10 } from "@ai-matrx/design-system";
3943
+ import { Badge as Badge3, Button as Button10, Dialog, DialogContent, DialogHeader, DialogTitle } from "@ai-matrx/design-system";
3921
3944
 
3922
3945
  // src/FilePicker.tsx
3923
3946
  import { useCallback as useCallback4, useId, useRef as useRef9, useState as useState16 } from "react";
@@ -4037,20 +4060,19 @@ function rememberHostFileIds(ids) {
4037
4060
  }
4038
4061
  var FILE_CHIP_UNREAD = "File";
4039
4062
  var FILE_CHIP_NO_PAGE = "This screen has no way to open files yet.";
4040
- function AttachmentChip({ id, onRemove }) {
4063
+ function useFileRead(id) {
4041
4064
  const client = useOptionalRecordsClient5();
4042
- const host = useRecordsUi();
4043
4065
  const [read, setRead] = useState17(null);
4044
4066
  useEffect11(() => {
4045
4067
  let cancelled = false;
4046
- if (!client) {
4047
- setRead({ name: null, fileId: null, why: null });
4048
- return;
4049
- }
4050
4068
  if (handedHostFileIds.has(id)) {
4051
4069
  setRead({ name: null, fileId: id, why: null });
4052
4070
  return;
4053
4071
  }
4072
+ if (!client) {
4073
+ setRead({ name: null, fileId: null, why: null });
4074
+ return;
4075
+ }
4054
4076
  void client.recordRead({ record_id: id }).then((answered) => {
4055
4077
  if (cancelled) return;
4056
4078
  if (!answered.ok) {
@@ -4066,11 +4088,30 @@ function AttachmentChip({ id, onRemove }) {
4066
4088
  cancelled = true;
4067
4089
  };
4068
4090
  }, [client, id]);
4091
+ return read;
4092
+ }
4093
+ function AttachmentChip({ id, onRemove, onPreview }) {
4094
+ const host = useRecordsUi();
4095
+ const read = useFileRead(id);
4069
4096
  const words = read?.name ?? (read ? FILE_CHIP_UNREAD : "\u2026");
4070
4097
  const href = read?.fileId ? host.hrefForFile?.({ fileId: read.fileId }) : void 0;
4071
- const title = href ? `Open ${words}` : read?.why ?? FILE_CHIP_NO_PAGE;
4098
+ const title = onPreview ? `Preview ${words}` : href ? `Open ${words}` : read?.why ?? FILE_CHIP_NO_PAGE;
4072
4099
  return /* @__PURE__ */ jsxs19(Badge3, { variant: "secondary", className: "max-w-[14rem] gap-1 text-[11px]", "data-records-file-chip": id, title, children: [
4073
- href ? /* @__PURE__ */ jsx21(
4100
+ onPreview ? /* @__PURE__ */ jsx21(
4101
+ "button",
4102
+ {
4103
+ type: "button",
4104
+ className: "truncate underline-offset-2 hover:underline",
4105
+ "data-records-file-preview": id,
4106
+ onClick: (event) => {
4107
+ event.stopPropagation();
4108
+ onPreview();
4109
+ },
4110
+ onMouseDown: (event) => event.stopPropagation(),
4111
+ onDoubleClick: (event) => event.stopPropagation(),
4112
+ children: words
4113
+ }
4114
+ ) : href ? /* @__PURE__ */ jsx21(
4074
4115
  OutboundLink4,
4075
4116
  {
4076
4117
  href,
@@ -4097,9 +4138,70 @@ function AttachmentChip({ id, onRemove }) {
4097
4138
  ] });
4098
4139
  }
4099
4140
  function AttachmentChips({ value, onRemove }) {
4141
+ const host = useRecordsUi();
4100
4142
  const ids = attachmentIdsOf(value);
4143
+ const [viewing, setViewing] = useState17(null);
4101
4144
  if (ids.length === 0) return null;
4102
- return /* @__PURE__ */ jsx21("span", { className: "flex flex-wrap gap-1", "data-records-file-chips": "", children: ids.map((id) => /* @__PURE__ */ jsx21(AttachmentChip, { id, ...onRemove ? { onRemove: () => onRemove(id) } : {} }, id)) });
4145
+ const canPreview = Boolean(host.fileImage);
4146
+ return /* @__PURE__ */ jsxs19("span", { className: "flex flex-wrap gap-1", "data-records-file-chips": "", children: [
4147
+ ids.map((id, index) => /* @__PURE__ */ jsx21(
4148
+ AttachmentChip,
4149
+ {
4150
+ id,
4151
+ ...onRemove ? { onRemove: () => onRemove(id) } : {},
4152
+ ...canPreview ? { onPreview: () => setViewing(index) } : {}
4153
+ },
4154
+ id
4155
+ )),
4156
+ viewing !== null && canPreview ? /* @__PURE__ */ jsx21(AttachmentViewer, { ids, index: Math.min(viewing, ids.length - 1), onIndex: setViewing, onClose: () => setViewing(null) }) : null
4157
+ ] });
4158
+ }
4159
+ function stepAttachment(index, total, way) {
4160
+ return Math.max(0, Math.min(total - 1, index + way));
4161
+ }
4162
+ function AttachmentViewer({
4163
+ ids,
4164
+ index,
4165
+ onIndex,
4166
+ onClose
4167
+ }) {
4168
+ const host = useRecordsUi();
4169
+ const id = ids[index];
4170
+ const read = useFileRead(id);
4171
+ const name = read?.name ?? (read ? FILE_CHIP_UNREAD : "\u2026");
4172
+ const href = read?.fileId ? host.hrefForFile?.({ fileId: read.fileId }) : void 0;
4173
+ const go = (way) => onIndex(stepAttachment(index, ids.length, way));
4174
+ return /* @__PURE__ */ jsx21(Dialog, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs19(
4175
+ DialogContent,
4176
+ {
4177
+ className: "sm:max-w-3xl",
4178
+ "data-records-attachment-viewer": id,
4179
+ onKeyDown: (event) => {
4180
+ if (event.key === "ArrowLeft") {
4181
+ event.preventDefault();
4182
+ go(-1);
4183
+ } else if (event.key === "ArrowRight") {
4184
+ event.preventDefault();
4185
+ go(1);
4186
+ }
4187
+ },
4188
+ onClick: (event) => event.stopPropagation(),
4189
+ children: [
4190
+ /* @__PURE__ */ jsx21(DialogHeader, { children: /* @__PURE__ */ jsx21(DialogTitle, { className: "truncate text-sm", children: name }) }),
4191
+ /* @__PURE__ */ jsx21("div", { className: "relative h-[60vh] w-full overflow-hidden rounded-md bg-muted", "data-records-attachment-stage": "", children: read?.fileId ? host.fileImage?.({ fileId: read.fileId, alt: name }) : null }),
4192
+ /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-2", children: [
4193
+ /* @__PURE__ */ jsx21(Button10, { size: "sm", variant: "outline", "aria-label": "Previous file", disabled: index === 0, onClick: () => go(-1), "data-records-attachment-previous": "", children: "\u2190" }),
4194
+ /* @__PURE__ */ jsx21(Button10, { size: "sm", variant: "outline", "aria-label": "Next file", disabled: index >= ids.length - 1, onClick: () => go(1), "data-records-attachment-next": "", children: "\u2192" }),
4195
+ /* @__PURE__ */ jsxs19("span", { className: "text-xs tabular-nums text-muted-foreground", "data-records-attachment-position": "", children: [
4196
+ index + 1,
4197
+ " of ",
4198
+ ids.length
4199
+ ] }),
4200
+ href ? /* @__PURE__ */ jsx21(OutboundLink4, { href, className: "ml-auto text-xs underline-offset-2 hover:underline", children: "Open in new tab" }) : null
4201
+ ] })
4202
+ ]
4203
+ }
4204
+ ) });
4103
4205
  }
4104
4206
  function hostTakesFiles(host) {
4105
4207
  return Boolean(host.pickFiles || host.upload);
@@ -6302,6 +6404,8 @@ function FieldEditor(props) {
6302
6404
  const [statusGroups, setStatusGroups] = useState23({});
6303
6405
  const [statusKeys, setStatusKeys] = useState23({});
6304
6406
  const [symbology, setSymbology] = useState23(String(start?.config?.["symbology"] ?? ""));
6407
+ const descriptionStart = String(start?.config?.["description"] ?? "");
6408
+ const [description, setDescription] = useState23(descriptionStart);
6305
6409
  const buttonStart = useMemo10(() => buttonOf(start) ?? { label: "" }, [start]);
6306
6410
  const [buttonSettings, setButtonSettings] = useState23(buttonStart);
6307
6411
  const prefixStart = autonumberPrefixOf3(start);
@@ -6451,6 +6555,7 @@ function FieldEditor(props) {
6451
6555
  const defaultChanged = takesADefault(field) && JSON.stringify(defaultNow) !== JSON.stringify(defaultToKeep(field.default));
6452
6556
  const patch = {
6453
6557
  ...defaultChanged ? { default: defaultNow } : {},
6558
+ ...description.trim() !== descriptionStart.trim() ? { description: description.trim() === "" ? null : description.trim() } : {},
6454
6559
  // The column's look travels in the same settings patch (`display_format`), so a save is
6455
6560
  // still one store call. `FieldPatch` does not declare the key yet (store gap, reported);
6456
6561
  // the door has carried it since the Sheet's seam started writing it.
@@ -6657,6 +6762,19 @@ function FieldEditor(props) {
6657
6762
  }
6658
6763
  ) : null
6659
6764
  ] }),
6765
+ field ? /* @__PURE__ */ jsxs30("label", { className: "flex flex-col gap-1 sm:col-span-2", children: [
6766
+ /* @__PURE__ */ jsx32(Label8, { className: "text-xs", children: "Help text" }),
6767
+ /* @__PURE__ */ jsx32(
6768
+ BasicInput9,
6769
+ {
6770
+ value: description,
6771
+ maxLength: 300,
6772
+ "aria-label": "Help text",
6773
+ placeholder: "Shown when the column name is hovered",
6774
+ onChange: (e) => setDescription(e.target.value)
6775
+ }
6776
+ )
6777
+ ] }) : null,
6660
6778
  /* @__PURE__ */ jsxs30("div", { className: "flex flex-col gap-1", children: [
6661
6779
  /* @__PURE__ */ jsx32(Label8, { className: "text-xs", children: "What it holds" }),
6662
6780
  /* @__PURE__ */ jsxs30(
@@ -7546,9 +7664,9 @@ function ButtonValue({ field, recordId, inCell }) {
7546
7664
  ] }) : null
7547
7665
  ] })
7548
7666
  ] }),
7549
- settingUp && tableId ? /* @__PURE__ */ jsx33(Dialog, { open: true, onOpenChange: (open) => open ? null : setSettingUp(false), children: /* @__PURE__ */ jsxs31(DialogContent, { className: "max-h-[85vh] overflow-y-auto sm:max-w-2xl", onClick: stop, onPointerDown: stop, children: [
7550
- /* @__PURE__ */ jsxs31(DialogHeader, { children: [
7551
- /* @__PURE__ */ jsx33(DialogTitle, { children: `Column settings \xB7 ${fieldName(field)}` }),
7667
+ settingUp && tableId ? /* @__PURE__ */ jsx33(Dialog2, { open: true, onOpenChange: (open) => open ? null : setSettingUp(false), children: /* @__PURE__ */ jsxs31(DialogContent2, { className: "max-h-[85vh] overflow-y-auto sm:max-w-2xl", onClick: stop, onPointerDown: stop, children: [
7668
+ /* @__PURE__ */ jsxs31(DialogHeader2, { children: [
7669
+ /* @__PURE__ */ jsx33(DialogTitle2, { children: `Column settings \xB7 ${fieldName(field)}` }),
7552
7670
  /* @__PURE__ */ jsx33(DialogDescription, { className: "sr-only", children: "What the button does" })
7553
7671
  ] }),
7554
7672
  /* @__PURE__ */ jsx33(FieldEditor, { tableId, field, onSaved: () => setSettingUp(false), onCancel: () => setSettingUp(false) })