@ai-matrx/records-ui 0.102.15 → 0.102.17

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
@@ -739,12 +739,12 @@ function PickOrAdd({
739
739
  children,
740
740
  chips,
741
741
  multi,
742
- addFirst
742
+ onEscape
743
743
  }) {
744
744
  const addRow = add && !foot ? add : null;
745
745
  const count = items.length + (addRow ? 1 : 0);
746
- const addAt = addRow ? addFirst ? 0 : items.length : -1;
747
- const itemAt = (index) => addRow && addFirst ? index - 1 : index;
746
+ const addAt = addRow ? items.length : -1;
747
+ const itemAt = (index) => index;
748
748
  const removable = (chips ?? []).filter((c) => c.onRemove);
749
749
  const keys = useListKeys({
750
750
  count,
@@ -758,7 +758,7 @@ function PickOrAdd({
758
758
  if (item && !item.disabled) onPick(item.id);
759
759
  },
760
760
  onNothing: onEnterWithNoMatch ?? (add && !add.disabled ? add.onAdd : void 0),
761
- onClose: () => onOpenChange(false),
761
+ onClose: () => onEscape ? onEscape() : onOpenChange(false),
762
762
  ...multi ? { onFinish: () => onOpenChange(false) } : {},
763
763
  ...removable.length > 0 ? { onBackspaceEmpty: () => removable[removable.length - 1]?.onRemove?.() } : {},
764
764
  searchEmpty: search === ""
@@ -792,6 +792,11 @@ function PickOrAdd({
792
792
  onKeyDown: (event) => {
793
793
  if (event.key !== "Tab") event.stopPropagation();
794
794
  },
795
+ onEscapeKeyDown: (event) => {
796
+ if (!onEscape) return;
797
+ event.preventDefault();
798
+ onEscape();
799
+ },
795
800
  onCloseAutoFocus: (event) => {
796
801
  if (anchored) event.preventDefault();
797
802
  },
@@ -813,7 +818,6 @@ function PickOrAdd({
813
818
  notice,
814
819
  /* @__PURE__ */ jsxs3(ScrollArea, { className: "max-h-64", children: [
815
820
  /* @__PURE__ */ jsxs3("div", { role: "listbox", "aria-label": triggerLabel, "aria-multiselectable": multi ? true : void 0, children: [
816
- addFirst ? addRowNode : null,
817
821
  items.map((item, index) => /* @__PURE__ */ jsx3(
818
822
  PickRow,
819
823
  {
@@ -825,13 +829,13 @@ function PickOrAdd({
825
829
  disabled: item.disabled,
826
830
  attrs: item.attrs,
827
831
  person: item.person,
828
- highlighted: (addRow && addFirst ? index + 1 : index) === keys.highlight,
832
+ highlighted: index === keys.highlight,
829
833
  onPick: () => onPick(item.id),
830
- onPoint: () => keys.setHighlight(addRow && addFirst ? index + 1 : index)
834
+ onPoint: () => keys.setHighlight(index)
831
835
  },
832
836
  item.id
833
837
  )),
834
- addFirst ? null : addRowNode
838
+ addRowNode
835
839
  ] }),
836
840
  items.length === 0 && empty ? empty : null
837
841
  ] }),
@@ -1152,7 +1156,7 @@ function tellApart(rows, nameOf2) {
1152
1156
  // src/RelationPicker.tsx
1153
1157
  import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1154
1158
  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.";
1155
- function RelationPicker({ field, value, onChange, disabled, id, className, allowCreate = true, onPicked, autoOpen, onClose }) {
1159
+ function RelationPicker({ field, value, onChange, disabled, id, className, allowCreate = true, onPicked, autoOpen, onClose, onCancel }) {
1156
1160
  const client = useOptionalRecordsClient();
1157
1161
  const [rows, setRows] = useState3(null);
1158
1162
  const [words, setWords] = useState3({});
@@ -1320,6 +1324,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1320
1324
  PickOrAdd,
1321
1325
  {
1322
1326
  anchored,
1327
+ ...anchored && onCancel ? { onEscape: onCancel } : {},
1323
1328
  multi: field.multi,
1324
1329
  chips: picked.map((recordId) => ({ id: recordId, label: titleOf(recordId), onRemove: () => toggle(recordId) })),
1325
1330
  triggerId: id,
@@ -2521,7 +2526,7 @@ async function personRecordForMember(client, personTableId, people, member) {
2521
2526
  });
2522
2527
  return written;
2523
2528
  }
2524
- function PersonPicker({ field, value, onChange, disabled, id, className, autoOpen, onClose, initialSearch }) {
2529
+ function PersonPicker({ field, value, onChange, disabled, id, className, autoOpen, onClose, onCancel, initialSearch }) {
2525
2530
  const client = useOptionalRecordsClient5();
2526
2531
  const host = useRecordsUi();
2527
2532
  const [members, setMembers] = useState10(null);
@@ -2635,6 +2640,7 @@ function PersonPicker({ field, value, onChange, disabled, id, className, autoOpe
2635
2640
  /* @__PURE__ */ jsx13(
2636
2641
  PickOrAdd,
2637
2642
  {
2643
+ ...anchored && onCancel ? { onEscape: onCancel } : {},
2638
2644
  anchored,
2639
2645
  multi: field.multi,
2640
2646
  triggerId: id,
@@ -3200,7 +3206,7 @@ function ChoiceAskButtons({
3200
3206
  onCancel,
3201
3207
  busy
3202
3208
  }) {
3203
- return /* @__PURE__ */ jsxs15("span", { "data-records-choice-ask": "", role: "group", "aria-label": choiceAskSentence(field, words), className: "flex flex-col gap-1", children: [
3209
+ return /* @__PURE__ */ jsxs15("span", { "data-records-choice-ask": "", "data-records-cell-note": "", role: "group", "aria-label": choiceAskSentence(field, words), className: "flex flex-col gap-1", children: [
3204
3210
  /* @__PURE__ */ jsx17("span", { className: "text-[11px] leading-snug", children: choiceAskSentence(field, words) }),
3205
3211
  /* @__PURE__ */ jsxs15("span", { className: "flex flex-wrap items-center gap-1", children: [
3206
3212
  /* @__PURE__ */ jsx17(Button7, { type: "button", size: "sm", className: "h-6 px-2 text-xs", disabled: busy ?? false, onClick: onAdd, children: "Add" }),
@@ -3216,7 +3222,11 @@ function isTypedDraft(value) {
3216
3222
  var SHORT_REFUSAL = {
3217
3223
  email: "Enter a valid email",
3218
3224
  url: "Enter a valid link",
3219
- phone: "Enter a valid phone number"
3225
+ phone: "Enter a valid phone number",
3226
+ time: "Enter a time, like 2:30 PM",
3227
+ date: "Enter a date",
3228
+ number: "Enter a number",
3229
+ value: "Not a valid value"
3220
3230
  };
3221
3231
  function judgeTyped(field, text, choices, nudge) {
3222
3232
  const trimmed = text.trim();
@@ -3225,7 +3235,7 @@ function judgeTyped(field, text, choices, nudge) {
3225
3235
  if (trimmed === "") return { value: null };
3226
3236
  if (format && format.id === "time") {
3227
3237
  const read2 = readTypedTime2(trimmed);
3228
- return read2.ok ? { value: read2.value } : { refusal: `${fieldName(field)} holds a time of day, and ${read2.why}` };
3238
+ return read2.ok ? { value: read2.value } : { refusal: SHORT_REFUSAL.time, why: `${fieldName(field)} holds a time of day, and ${read2.why}` };
3229
3239
  }
3230
3240
  if (kind === "select" || kind === "multi_select") {
3231
3241
  if (!choices || nudge === null) return null;
@@ -3263,7 +3273,7 @@ function judgeTyped(field, text, choices, nudge) {
3263
3273
  if (format && format.id === "rating") {
3264
3274
  const n = Number(trimmed);
3265
3275
  const max = format.options?.ratingMax ?? 5;
3266
- 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.` };
3276
+ 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}.` };
3267
3277
  }
3268
3278
  if (kind === "phone" || format && format.id === "phone") {
3269
3279
  const digits = trimmed.replace(/\D/g, "");
@@ -3272,7 +3282,11 @@ function judgeTyped(field, text, choices, nudge) {
3272
3282
  }
3273
3283
  const showsAsPercent = Boolean(format && (format.id === "percent" || format.id === "progress"));
3274
3284
  const read = coerceTypedAnswer(field, text, { label: fieldName(field), showsAsPercent });
3275
- if ("refusal" in read) return { refusal: kind === "email" || kind === "url" ? SHORT_REFUSAL[kind] : read.refusal };
3285
+ if ("refusal" in read) {
3286
+ if (kind === "email" || kind === "url") return { refusal: SHORT_REFUSAL[kind] };
3287
+ 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;
3288
+ return short === read.refusal ? { refusal: short } : { refusal: short, why: read.refusal };
3289
+ }
3276
3290
  if (typeof read.value === "number" && format && (format.id === "percent" || format.id === "progress") && format.options?.percentScale === "fraction") {
3277
3291
  return { value: Math.round(read.value / 100 * 1e9) / 1e9 };
3278
3292
  }
@@ -3342,7 +3356,7 @@ function TypedTextControl({ field, value, onChange, disabled, id, onCommit, onCa
3342
3356
  box,
3343
3357
  /* @__PURE__ */ jsx17(TypedDateHelper, { kind: helper, text: value.text, onText: retype, label: fieldName(field) })
3344
3358
  ] }) : box,
3345
- judged === null ? /* @__PURE__ */ jsxs15("span", { className: "text-[10px] text-muted-foreground", children: [
3359
+ judged === null ? /* @__PURE__ */ jsxs15("span", { "data-records-cell-note": "", className: "text-[10px] text-muted-foreground", children: [
3346
3360
  "Reading ",
3347
3361
  fieldName(field),
3348
3362
  "'s choices\u2026"
@@ -3362,10 +3376,19 @@ function TypedTextControl({ field, value, onChange, disabled, id, onCommit, onCa
3362
3376
  },
3363
3377
  onCancel: () => onCancel?.()
3364
3378
  }
3365
- ) : "refusal" in judged ? /* @__PURE__ */ jsxs15("span", { className: "flex flex-col gap-1", children: [
3366
- /* @__PURE__ */ jsx17("span", { "data-records-typed-refusal": "", role: "status", className: "text-[11px] leading-snug text-amber-700 dark:text-amber-400", children: judged.refusal }),
3379
+ ) : "refusal" in judged ? /* @__PURE__ */ jsxs15("span", { "data-records-cell-note": "", className: "flex flex-col gap-1", children: [
3380
+ /* @__PURE__ */ jsx17(
3381
+ "span",
3382
+ {
3383
+ "data-records-typed-refusal": "",
3384
+ role: "status",
3385
+ title: "why" in judged ? judged.why : void 0,
3386
+ className: "text-[11px] leading-snug text-amber-700 dark:text-amber-400",
3387
+ children: judged.refusal
3388
+ }
3389
+ ),
3367
3390
  judged.cancelOnly && onCancel ? /* @__PURE__ */ jsx17("span", { "data-records-choice-ask": "", children: /* @__PURE__ */ jsx17(Button7, { type: "button", size: "sm", variant: "ghost", className: "h-6 px-2 text-xs", onClick: () => onCancel(), children: "Cancel" }) }) : null
3368
- ] }) : judged.value === null || judged.value === void 0 ? null : /* @__PURE__ */ jsxs15("span", { "data-records-typed-reads-as": "", className: "text-[10px] text-muted-foreground", children: [
3391
+ ] }) : judged.value === null || judged.value === void 0 ? null : /* @__PURE__ */ jsxs15("span", { "data-records-typed-reads-as": "", "data-records-cell-note": "", className: "text-[10px] text-muted-foreground", children: [
3369
3392
  isChoiceAddition(judged.value) ? `Adds ${quoted(judged.value.add)} to the choices for ${fieldName(field)}. ` : null,
3370
3393
  "Reads as",
3371
3394
  " ",
@@ -3459,9 +3482,9 @@ function FieldControl({ field, value, onChange, disabled, id, autoOpen, onClose,
3459
3482
  return /* @__PURE__ */ jsx17(CheckboxControl, { field, value, onChange, disabled, id: inputId });
3460
3483
  case "select":
3461
3484
  case "multi_select":
3462
- return /* @__PURE__ */ jsx17(OptionControl, { field, value, onChange, disabled, id: inputId, autoOpen, onClose, addsChoices, initialSearch, asForm });
3485
+ return /* @__PURE__ */ jsx17(OptionControl, { field, value, onChange, disabled, id: inputId, autoOpen, onClose, onCancel, addsChoices, initialSearch, asForm });
3463
3486
  case "member":
3464
- return /* @__PURE__ */ jsx17(PersonPicker, { field, value, onChange, disabled, id: inputId, autoOpen, onClose, initialSearch });
3487
+ return /* @__PURE__ */ jsx17(PersonPicker, { field, value, onChange, disabled, id: inputId, autoOpen, onClose, onCancel, initialSearch });
3465
3488
  case "entity_reference":
3466
3489
  return /* @__PURE__ */ jsx17(EntityReferenceControl, { field, value, onChange, disabled, id: inputId });
3467
3490
  case "attachment":
@@ -3470,7 +3493,7 @@ function FieldControl({ field, value, onChange, disabled, id, autoOpen, onClose,
3470
3493
  }
3471
3494
  return /* @__PURE__ */ jsx17(RelationPicker, { field, value, onChange, disabled, id: inputId });
3472
3495
  case "relation":
3473
- return /* @__PURE__ */ jsx17(RelationPicker, { field, value, onChange, disabled, id: inputId, onPicked: onCommit, autoOpen, onClose });
3496
+ return /* @__PURE__ */ jsx17(RelationPicker, { field, value, onChange, disabled, id: inputId, onPicked: onCommit, autoOpen, onClose, onCancel });
3474
3497
  default:
3475
3498
  if (typeof value === "boolean" || field.config?.["kind"] === "boolean") {
3476
3499
  return /* @__PURE__ */ jsx17(
@@ -3498,6 +3521,7 @@ function FieldControl({ field, value, onChange, disabled, id, autoOpen, onClose,
3498
3521
  function TypedWordsControl({ field, value, onChange, disabled, id }) {
3499
3522
  const [text, setText] = useState14(() => openingText(field, value));
3500
3523
  const [refusal2, setRefusal] = useState14(null);
3524
+ const [why, setWhy] = useState14(void 0);
3501
3525
  const sent = useRef11(value);
3502
3526
  useEffect10(() => {
3503
3527
  if (value === sent.current) return;
@@ -3515,6 +3539,7 @@ function TypedWordsControl({ field, value, onChange, disabled, id }) {
3515
3539
  onChange(judged.value);
3516
3540
  } else if (judged && "refusal" in judged) {
3517
3541
  setRefusal(judged.refusal);
3542
+ setWhy("why" in judged ? judged.why : void 0);
3518
3543
  }
3519
3544
  };
3520
3545
  return /* @__PURE__ */ jsxs15("span", { className: "flex flex-col gap-0.5", children: [
@@ -3538,7 +3563,7 @@ function TypedWordsControl({ field, value, onChange, disabled, id }) {
3538
3563
  ),
3539
3564
  helper && !disabled ? /* @__PURE__ */ jsx17(TypedDateHelper, { kind: helper, text, onText: retype, label: fieldName(field) }) : null
3540
3565
  ] }),
3541
- refusal2 ? /* @__PURE__ */ jsx17("span", { "data-records-typed-refusal": "", role: "status", className: "text-[11px] leading-snug text-amber-700 dark:text-amber-400", children: refusal2 }) : null
3566
+ refusal2 ? /* @__PURE__ */ jsx17("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
3542
3567
  ] });
3543
3568
  }
3544
3569
  function CheckboxControl({ field, value, onChange, disabled, id }) {
@@ -3608,8 +3633,9 @@ function JsonControl({ field, value, onChange, disabled, id }) {
3608
3633
  }
3609
3634
  ),
3610
3635
  broken ? (
3611
- // The typed text is still in the box: the notice owns the two doors out.
3612
- /* @__PURE__ */ jsx17(
3636
+ // The typed text is still in the box: the notice owns the two doors out. In a grid cell it
3637
+ // floats over the table (`data-records-cell-note`), so the row never grows.
3638
+ /* @__PURE__ */ jsx17("span", { "data-records-cell-note": "", className: "block", children: /* @__PURE__ */ jsx17(
3613
3639
  RefusalNotice,
3614
3640
  {
3615
3641
  error: broken,
@@ -3619,7 +3645,7 @@ function JsonControl({ field, value, onChange, disabled, id }) {
3619
3645
  setBroken(null);
3620
3646
  }
3621
3647
  }
3622
- )
3648
+ ) })
3623
3649
  ) : null
3624
3650
  ] });
3625
3651
  }
@@ -3632,7 +3658,7 @@ function inTheStoresOrder(options) {
3632
3658
  const made = (o) => String(o.created_at ?? "");
3633
3659
  return [...options].sort((a, b) => position(a) - position(b) || made(a).localeCompare(made(b)));
3634
3660
  }
3635
- function OptionControl({ field, value, onChange, disabled, id, autoOpen, onClose, addsChoices, initialSearch, asForm }) {
3661
+ function OptionControl({ field, value, onChange, disabled, id, autoOpen, onClose, onCancel, addsChoices, initialSearch, asForm }) {
3636
3662
  const client = useOptionalRecordsClient7();
3637
3663
  const [options, setOptions] = useState14(null);
3638
3664
  const [refusal2, setRefusal] = useState14(null);
@@ -3700,6 +3726,7 @@ function OptionControl({ field, value, onChange, disabled, id, autoOpen, onClose
3700
3726
  id,
3701
3727
  autoOpen,
3702
3728
  onClose,
3729
+ onCancel,
3703
3730
  initialSearch,
3704
3731
  addsChoices,
3705
3732
  allowOther: allowsOtherValues(field),
@@ -3780,6 +3807,7 @@ function OpenChoicePicker({
3780
3807
  addsChoices,
3781
3808
  autoOpen,
3782
3809
  onClose,
3810
+ onCancel,
3783
3811
  initialSearch,
3784
3812
  allowOther = true,
3785
3813
  onChoicesChanged
@@ -3847,10 +3875,9 @@ function OpenChoicePicker({
3847
3875
  });
3848
3876
  };
3849
3877
  const offList = q !== "" && decided.decision !== "chosen" && decided.decision !== "empty";
3850
- const nothingMatches = q !== "" && visible.length === 0;
3851
3878
  const foot = null;
3852
3879
  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;
3853
- const listed = nothingMatches && add ? options : visible;
3880
+ const listed = visible;
3854
3881
  const pickedOptions = current.map((v) => options.find((o) => isTheChosen(o, v)));
3855
3882
  const cellChips = pickedWords.map((word, i) => {
3856
3883
  const chip = chipOf(pickedOptions[i], word);
@@ -3878,6 +3905,7 @@ function OpenChoicePicker({
3878
3905
  PickOrAdd,
3879
3906
  {
3880
3907
  anchored,
3908
+ ...anchored && onCancel ? { onEscape: onCancel } : {},
3881
3909
  chips: headChips,
3882
3910
  multi: field.multi,
3883
3911
  triggerLabel: `Pick for ${fieldName(field)}`,
@@ -3889,7 +3917,6 @@ function OpenChoicePicker({
3889
3917
  setSearch(next);
3890
3918
  setAddRefused(null);
3891
3919
  },
3892
- addFirst: nothingMatches,
3893
3920
  items: listed.map((option) => ({
3894
3921
  id: option.id,
3895
3922
  label: titleOf(option),