@ai-matrx/records-ui 0.102.15 → 0.102.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/pickers.cjs CHANGED
@@ -741,12 +741,12 @@ function PickOrAdd({
741
741
  children,
742
742
  chips,
743
743
  multi,
744
- addFirst
744
+ onEscape
745
745
  }) {
746
746
  const addRow = add && !foot ? add : null;
747
747
  const count = items.length + (addRow ? 1 : 0);
748
- const addAt = addRow ? addFirst ? 0 : items.length : -1;
749
- const itemAt = (index) => addRow && addFirst ? index - 1 : index;
748
+ const addAt = addRow ? items.length : -1;
749
+ const itemAt = (index) => index;
750
750
  const removable = (chips ?? []).filter((c) => c.onRemove);
751
751
  const keys = useListKeys({
752
752
  count,
@@ -760,7 +760,7 @@ function PickOrAdd({
760
760
  if (item && !item.disabled) onPick(item.id);
761
761
  },
762
762
  onNothing: onEnterWithNoMatch ?? (add && !add.disabled ? add.onAdd : void 0),
763
- onClose: () => onOpenChange(false),
763
+ onClose: () => onEscape ? onEscape() : onOpenChange(false),
764
764
  ...multi ? { onFinish: () => onOpenChange(false) } : {},
765
765
  ...removable.length > 0 ? { onBackspaceEmpty: () => removable[removable.length - 1]?.onRemove?.() } : {},
766
766
  searchEmpty: search === ""
@@ -794,6 +794,11 @@ function PickOrAdd({
794
794
  onKeyDown: (event) => {
795
795
  if (event.key !== "Tab") event.stopPropagation();
796
796
  },
797
+ onEscapeKeyDown: (event) => {
798
+ if (!onEscape) return;
799
+ event.preventDefault();
800
+ onEscape();
801
+ },
797
802
  onCloseAutoFocus: (event) => {
798
803
  if (anchored) event.preventDefault();
799
804
  },
@@ -815,7 +820,6 @@ function PickOrAdd({
815
820
  notice,
816
821
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system3.ScrollArea, { className: "max-h-64", children: [
817
822
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { role: "listbox", "aria-label": triggerLabel, "aria-multiselectable": multi ? true : void 0, children: [
818
- addFirst ? addRowNode : null,
819
823
  items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
820
824
  PickRow,
821
825
  {
@@ -827,13 +831,13 @@ function PickOrAdd({
827
831
  disabled: item.disabled,
828
832
  attrs: item.attrs,
829
833
  person: item.person,
830
- highlighted: (addRow && addFirst ? index + 1 : index) === keys.highlight,
834
+ highlighted: index === keys.highlight,
831
835
  onPick: () => onPick(item.id),
832
- onPoint: () => keys.setHighlight(addRow && addFirst ? index + 1 : index)
836
+ onPoint: () => keys.setHighlight(index)
833
837
  },
834
838
  item.id
835
839
  )),
836
- addFirst ? null : addRowNode
840
+ addRowNode
837
841
  ] }),
838
842
  items.length === 0 && empty ? empty : null
839
843
  ] }),
@@ -1158,7 +1162,7 @@ var import_jsx_runtime5 = (
1158
1162
  require("react/jsx-runtime")
1159
1163
  );
1160
1164
  var NO_STORE_ON_THIS_SCREEN_REASON = "This question points at a record in somebody's own data, and a page answered without an account is never allowed to search that. Leave it blank \u2014 whoever receives your answer will fill it in.";
1161
- function RelationPicker({ field, value, onChange, disabled, id, className, allowCreate = true, onPicked, autoOpen, onClose }) {
1165
+ function RelationPicker({ field, value, onChange, disabled, id, className, allowCreate = true, onPicked, autoOpen, onClose, onCancel }) {
1162
1166
  const client = (0, import_react4.useOptionalRecordsClient)();
1163
1167
  const [rows, setRows] = (0, import_react3.useState)(null);
1164
1168
  const [words, setWords] = (0, import_react3.useState)({});
@@ -1322,6 +1326,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1322
1326
  PickOrAdd,
1323
1327
  {
1324
1328
  anchored,
1329
+ ...anchored && onCancel ? { onEscape: onCancel } : {},
1325
1330
  multi: field.multi,
1326
1331
  chips: picked.map((recordId) => ({ id: recordId, label: titleOf(recordId), onRemove: () => toggle(recordId) })),
1327
1332
  triggerId: id,
@@ -2523,7 +2528,7 @@ async function personRecordForMember(client, personTableId, people, member) {
2523
2528
  });
2524
2529
  return written;
2525
2530
  }
2526
- function PersonPicker({ field, value, onChange, disabled, id, className, autoOpen, onClose, initialSearch }) {
2531
+ function PersonPicker({ field, value, onChange, disabled, id, className, autoOpen, onClose, onCancel, initialSearch }) {
2527
2532
  const client = (0, import_react18.useOptionalRecordsClient)();
2528
2533
  const host = useRecordsUi();
2529
2534
  const [members, setMembers] = (0, import_react17.useState)(null);
@@ -2637,6 +2642,7 @@ function PersonPicker({ field, value, onChange, disabled, id, className, autoOpe
2637
2642
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2638
2643
  PickOrAdd,
2639
2644
  {
2645
+ ...anchored && onCancel ? { onEscape: onCancel } : {},
2640
2646
  anchored,
2641
2647
  multi: field.multi,
2642
2648
  triggerId: id,
@@ -3198,7 +3204,7 @@ function ChoiceAskButtons({
3198
3204
  onCancel,
3199
3205
  busy
3200
3206
  }) {
3201
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { "data-records-choice-ask": "", role: "group", "aria-label": choiceAskSentence(field, words), className: "flex flex-col gap-1", children: [
3207
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { "data-records-choice-ask": "", "data-records-cell-note": "", role: "group", "aria-label": choiceAskSentence(field, words), className: "flex flex-col gap-1", children: [
3202
3208
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-[11px] leading-snug", children: choiceAskSentence(field, words) }),
3203
3209
  /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "flex flex-wrap items-center gap-1", children: [
3204
3210
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_design_system15.Button, { type: "button", size: "sm", className: "h-6 px-2 text-xs", disabled: busy ?? false, onClick: onAdd, children: "Add" }),
@@ -3214,7 +3220,11 @@ function isTypedDraft(value) {
3214
3220
  var SHORT_REFUSAL = {
3215
3221
  email: "Enter a valid email",
3216
3222
  url: "Enter a valid link",
3217
- phone: "Enter a valid phone number"
3223
+ phone: "Enter a valid phone number",
3224
+ time: "Enter a time, like 2:30 PM",
3225
+ date: "Enter a date",
3226
+ number: "Enter a number",
3227
+ value: "Not a valid value"
3218
3228
  };
3219
3229
  function judgeTyped(field, text, choices, nudge) {
3220
3230
  const trimmed = text.trim();
@@ -3223,7 +3233,7 @@ function judgeTyped(field, text, choices, nudge) {
3223
3233
  if (trimmed === "") return { value: null };
3224
3234
  if (format && format.id === "time") {
3225
3235
  const read2 = (0, import_records10.readTypedTime)(trimmed);
3226
- return read2.ok ? { value: read2.value } : { refusal: `${fieldName(field)} holds a time of day, and ${read2.why}` };
3236
+ return read2.ok ? { value: read2.value } : { refusal: SHORT_REFUSAL.time, why: `${fieldName(field)} holds a time of day, and ${read2.why}` };
3227
3237
  }
3228
3238
  if (kind === "select" || kind === "multi_select") {
3229
3239
  if (!choices || nudge === null) return null;
@@ -3261,7 +3271,7 @@ function judgeTyped(field, text, choices, nudge) {
3261
3271
  if (format && format.id === "rating") {
3262
3272
  const n = Number(trimmed);
3263
3273
  const max = format.options?.ratingMax ?? 5;
3264
- return Number.isInteger(n) && n >= 0 && n <= max ? { value: n === 0 ? null : n } : { refusal: `${fieldName(field)} is a rating from 1 to ${max}, and \u201C${text}\u201D is not one.` };
3274
+ return Number.isInteger(n) && n >= 0 && n <= max ? { value: n === 0 ? null : n } : { refusal: `Enter 1 to ${max}`, why: `${fieldName(field)} is a rating from 1 to ${max}.` };
3265
3275
  }
3266
3276
  if (kind === "phone" || format && format.id === "phone") {
3267
3277
  const digits = trimmed.replace(/\D/g, "");
@@ -3270,7 +3280,11 @@ function judgeTyped(field, text, choices, nudge) {
3270
3280
  }
3271
3281
  const showsAsPercent = Boolean(format && (format.id === "percent" || format.id === "progress"));
3272
3282
  const read = (0, import_records10.coerceTypedAnswer)(field, text, { label: fieldName(field), showsAsPercent });
3273
- if ("refusal" in read) return { refusal: kind === "email" || kind === "url" ? SHORT_REFUSAL[kind] : read.refusal };
3283
+ if ("refusal" in read) {
3284
+ if (kind === "email" || kind === "url") return { refusal: SHORT_REFUSAL[kind] };
3285
+ const short = kind === "datetime" ? SHORT_REFUSAL.date : kind === "number" || kind === "currency" || kind === "percent" || showsAsPercent ? SHORT_REFUSAL.number : read.refusal.length <= 32 ? read.refusal : SHORT_REFUSAL.value;
3286
+ return short === read.refusal ? { refusal: short } : { refusal: short, why: read.refusal };
3287
+ }
3274
3288
  if (typeof read.value === "number" && format && (format.id === "percent" || format.id === "progress") && format.options?.percentScale === "fraction") {
3275
3289
  return { value: Math.round(read.value / 100 * 1e9) / 1e9 };
3276
3290
  }
@@ -3340,7 +3354,7 @@ function TypedTextControl({ field, value, onChange, disabled, id, onCommit, onCa
3340
3354
  box,
3341
3355
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TypedDateHelper, { kind: helper, text: value.text, onText: retype, label: fieldName(field) })
3342
3356
  ] }) : box,
3343
- judged === null ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "text-[10px] text-muted-foreground", children: [
3357
+ judged === null ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { "data-records-cell-note": "", className: "text-[10px] text-muted-foreground", children: [
3344
3358
  "Reading ",
3345
3359
  fieldName(field),
3346
3360
  "'s choices\u2026"
@@ -3360,10 +3374,19 @@ function TypedTextControl({ field, value, onChange, disabled, id, onCommit, onCa
3360
3374
  },
3361
3375
  onCancel: () => onCancel?.()
3362
3376
  }
3363
- ) : "refusal" in judged ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "flex flex-col gap-1", children: [
3364
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { "data-records-typed-refusal": "", role: "status", className: "text-[11px] leading-snug text-amber-700 dark:text-amber-400", children: judged.refusal }),
3377
+ ) : "refusal" in judged ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { "data-records-cell-note": "", className: "flex flex-col gap-1", children: [
3378
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3379
+ "span",
3380
+ {
3381
+ "data-records-typed-refusal": "",
3382
+ role: "status",
3383
+ title: "why" in judged ? judged.why : void 0,
3384
+ className: "text-[11px] leading-snug text-amber-700 dark:text-amber-400",
3385
+ children: judged.refusal
3386
+ }
3387
+ ),
3365
3388
  judged.cancelOnly && onCancel ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { "data-records-choice-ask": "", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_design_system15.Button, { type: "button", size: "sm", variant: "ghost", className: "h-6 px-2 text-xs", onClick: () => onCancel(), children: "Cancel" }) }) : null
3366
- ] }) : judged.value === null || judged.value === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { "data-records-typed-reads-as": "", className: "text-[10px] text-muted-foreground", children: [
3389
+ ] }) : judged.value === null || judged.value === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { "data-records-typed-reads-as": "", "data-records-cell-note": "", className: "text-[10px] text-muted-foreground", children: [
3367
3390
  isChoiceAddition(judged.value) ? `Adds ${quoted(judged.value.add)} to the choices for ${fieldName(field)}. ` : null,
3368
3391
  "Reads as",
3369
3392
  " ",
@@ -3457,9 +3480,9 @@ function FieldControl({ field, value, onChange, disabled, id, autoOpen, onClose,
3457
3480
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(CheckboxControl, { field, value, onChange, disabled, id: inputId });
3458
3481
  case "select":
3459
3482
  case "multi_select":
3460
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(OptionControl, { field, value, onChange, disabled, id: inputId, autoOpen, onClose, addsChoices, initialSearch, asForm });
3483
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(OptionControl, { field, value, onChange, disabled, id: inputId, autoOpen, onClose, onCancel, addsChoices, initialSearch, asForm });
3461
3484
  case "member":
3462
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PersonPicker, { field, value, onChange, disabled, id: inputId, autoOpen, onClose, initialSearch });
3485
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PersonPicker, { field, value, onChange, disabled, id: inputId, autoOpen, onClose, onCancel, initialSearch });
3463
3486
  case "entity_reference":
3464
3487
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(EntityReferenceControl, { field, value, onChange, disabled, id: inputId });
3465
3488
  case "attachment":
@@ -3468,7 +3491,7 @@ function FieldControl({ field, value, onChange, disabled, id, autoOpen, onClose,
3468
3491
  }
3469
3492
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(RelationPicker, { field, value, onChange, disabled, id: inputId });
3470
3493
  case "relation":
3471
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(RelationPicker, { field, value, onChange, disabled, id: inputId, onPicked: onCommit, autoOpen, onClose });
3494
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(RelationPicker, { field, value, onChange, disabled, id: inputId, onPicked: onCommit, autoOpen, onClose, onCancel });
3472
3495
  default:
3473
3496
  if (typeof value === "boolean" || field.config?.["kind"] === "boolean") {
3474
3497
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
@@ -3496,6 +3519,7 @@ function FieldControl({ field, value, onChange, disabled, id, autoOpen, onClose,
3496
3519
  function TypedWordsControl({ field, value, onChange, disabled, id }) {
3497
3520
  const [text, setText] = (0, import_react24.useState)(() => openingText(field, value));
3498
3521
  const [refusal2, setRefusal] = (0, import_react24.useState)(null);
3522
+ const [why, setWhy] = (0, import_react24.useState)(void 0);
3499
3523
  const sent = (0, import_react24.useRef)(value);
3500
3524
  (0, import_react24.useEffect)(() => {
3501
3525
  if (value === sent.current) return;
@@ -3513,6 +3537,7 @@ function TypedWordsControl({ field, value, onChange, disabled, id }) {
3513
3537
  onChange(judged.value);
3514
3538
  } else if (judged && "refusal" in judged) {
3515
3539
  setRefusal(judged.refusal);
3540
+ setWhy("why" in judged ? judged.why : void 0);
3516
3541
  }
3517
3542
  };
3518
3543
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "flex flex-col gap-0.5", children: [
@@ -3536,7 +3561,7 @@ function TypedWordsControl({ field, value, onChange, disabled, id }) {
3536
3561
  ),
3537
3562
  helper && !disabled ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TypedDateHelper, { kind: helper, text, onText: retype, label: fieldName(field) }) : null
3538
3563
  ] }),
3539
- refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { "data-records-typed-refusal": "", role: "status", className: "text-[11px] leading-snug text-amber-700 dark:text-amber-400", children: refusal2 }) : null
3564
+ refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { "data-records-typed-refusal": "", "data-records-cell-note": "", role: "status", title: why, className: "text-[11px] leading-snug text-amber-700 dark:text-amber-400", children: refusal2 }) : null
3540
3565
  ] });
3541
3566
  }
3542
3567
  function CheckboxControl({ field, value, onChange, disabled, id }) {
@@ -3606,8 +3631,9 @@ function JsonControl({ field, value, onChange, disabled, id }) {
3606
3631
  }
3607
3632
  ),
3608
3633
  broken ? (
3609
- // The typed text is still in the box: the notice owns the two doors out.
3610
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3634
+ // The typed text is still in the box: the notice owns the two doors out. In a grid cell it
3635
+ // floats over the table (`data-records-cell-note`), so the row never grows.
3636
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { "data-records-cell-note": "", className: "block", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3611
3637
  RefusalNotice,
3612
3638
  {
3613
3639
  error: broken,
@@ -3617,7 +3643,7 @@ function JsonControl({ field, value, onChange, disabled, id }) {
3617
3643
  setBroken(null);
3618
3644
  }
3619
3645
  }
3620
- )
3646
+ ) })
3621
3647
  ) : null
3622
3648
  ] });
3623
3649
  }
@@ -3630,7 +3656,7 @@ function inTheStoresOrder(options) {
3630
3656
  const made = (o) => String(o.created_at ?? "");
3631
3657
  return [...options].sort((a, b) => position(a) - position(b) || made(a).localeCompare(made(b)));
3632
3658
  }
3633
- function OptionControl({ field, value, onChange, disabled, id, autoOpen, onClose, addsChoices, initialSearch, asForm }) {
3659
+ function OptionControl({ field, value, onChange, disabled, id, autoOpen, onClose, onCancel, addsChoices, initialSearch, asForm }) {
3634
3660
  const client = (0, import_react25.useOptionalRecordsClient)();
3635
3661
  const [options, setOptions] = (0, import_react24.useState)(null);
3636
3662
  const [refusal2, setRefusal] = (0, import_react24.useState)(null);
@@ -3698,6 +3724,7 @@ function OptionControl({ field, value, onChange, disabled, id, autoOpen, onClose
3698
3724
  id,
3699
3725
  autoOpen,
3700
3726
  onClose,
3727
+ onCancel,
3701
3728
  initialSearch,
3702
3729
  addsChoices,
3703
3730
  allowOther: (0, import_records10.allowsOtherValues)(field),
@@ -3778,6 +3805,7 @@ function OpenChoicePicker({
3778
3805
  addsChoices,
3779
3806
  autoOpen,
3780
3807
  onClose,
3808
+ onCancel,
3781
3809
  initialSearch,
3782
3810
  allowOther = true,
3783
3811
  onChoicesChanged
@@ -3845,10 +3873,9 @@ function OpenChoicePicker({
3845
3873
  });
3846
3874
  };
3847
3875
  const offList = q !== "" && decided.decision !== "chosen" && decided.decision !== "empty";
3848
- const nothingMatches = q !== "" && visible.length === 0;
3849
3876
  const foot = null;
3850
3877
  const add = offList && (decided.decision === "ask" || decided.decision === "add") ? { label: `Create \u201C${q}\u201D`, onAdd: addTyped, disabled: adding } : offList && decided.decision === "keep_as_typed" ? { label: `Use \u201C${q}\u201D`, onAdd: () => choose(q) } : null;
3851
- const listed = nothingMatches && add ? options : visible;
3878
+ const listed = visible;
3852
3879
  const pickedOptions = current.map((v) => options.find((o) => (0, import_records10.isTheChosen)(o, v)));
3853
3880
  const cellChips = pickedWords.map((word, i) => {
3854
3881
  const chip = chipOf(pickedOptions[i], word);
@@ -3876,6 +3903,7 @@ function OpenChoicePicker({
3876
3903
  PickOrAdd,
3877
3904
  {
3878
3905
  anchored,
3906
+ ...anchored && onCancel ? { onEscape: onCancel } : {},
3879
3907
  chips: headChips,
3880
3908
  multi: field.multi,
3881
3909
  triggerLabel: `Pick for ${fieldName(field)}`,
@@ -3887,7 +3915,6 @@ function OpenChoicePicker({
3887
3915
  setSearch(next);
3888
3916
  setAddRefused(null);
3889
3917
  },
3890
- addFirst: nothingMatches,
3891
3918
  items: listed.map((option) => ({
3892
3919
  id: option.id,
3893
3920
  label: titleOf(option),