@flytedan/flytebot-design-system 0.35.0 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3488,16 +3488,12 @@ function MultiSelectPanel({
3488
3488
  const [unmatchedTokens, setUnmatchedTokens] = React25.useState([]);
3489
3489
  const [pasteError, setPasteError] = React25.useState(null);
3490
3490
  const pasteWrapRef = React25.useRef(null);
3491
- const [resolvedAt, setResolvedAt] = React25.useState(0);
3492
3491
  React25.useEffect(() => {
3493
3492
  if (mode2 === "paste") pasteWrapRef.current?.querySelector("textarea")?.focus();
3494
3493
  }, [mode2]);
3495
- React25.useLayoutEffect(() => {
3496
- if (resolvedAt === 0) return;
3497
- pasteWrapRef.current?.querySelector("textarea")?.focus();
3498
- }, [resolvedAt]);
3499
3494
  const doResolve = () => {
3500
3495
  if (!field.resolve) return;
3496
+ if (pasteLoading) return;
3501
3497
  const tokens = Array.from(new Set(pasteText.split(/[\n,]/).map((t) => t.trim()).filter(Boolean)));
3502
3498
  if (!tokens.length) return;
3503
3499
  setPasteLoading(true);
@@ -3518,7 +3514,6 @@ function MultiSelectPanel({
3518
3514
  return;
3519
3515
  }
3520
3516
  setPasteText("");
3521
- setResolvedAt((n) => n + 1);
3522
3517
  }, (err) => {
3523
3518
  if (!mountedRef.current) return;
3524
3519
  setPasteLoading(false);
@@ -3581,7 +3576,8 @@ function MultiSelectPanel({
3581
3576
  rows: 4,
3582
3577
  placeholder: field.pastePlaceholder || field.placeholder || "Paste names, one per line or comma-separated",
3583
3578
  value: pasteText,
3584
- disabled: pasteLoading,
3579
+ readOnly: pasteLoading,
3580
+ "aria-busy": pasteLoading || void 0,
3585
3581
  onChange: (e) => setPasteText(e.target.value),
3586
3582
  onKeyDown: (e) => {
3587
3583
  if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
@@ -3637,7 +3633,28 @@ function MultiSelectPanel({
3637
3633
  );
3638
3634
  }) }) : null
3639
3635
  ] });
3640
- const footer = mode2 === "paste" ? /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: doResolve, disabled: pasteLoading || !pasteText.trim(), children: pasteLoading ? "Resolving\u2026" : "Resolve" }) : void 0;
3636
+ const resolveDisabled = pasteLoading || !pasteText.trim();
3637
+ const footer = mode2 === "paste" ? /* @__PURE__ */ jsx45(
3638
+ Button,
3639
+ {
3640
+ size: "sm",
3641
+ variant: "secondary",
3642
+ "aria-disabled": resolveDisabled,
3643
+ onClick: (e) => {
3644
+ if (resolveDisabled) {
3645
+ e.preventDefault();
3646
+ return;
3647
+ }
3648
+ doResolve();
3649
+ },
3650
+ onKeyDown: (e) => {
3651
+ if (resolveDisabled && (e.key === "Enter" || e.key === " ")) {
3652
+ e.preventDefault();
3653
+ }
3654
+ },
3655
+ children: pasteLoading ? "Resolving\u2026" : "Resolve"
3656
+ }
3657
+ ) : void 0;
3641
3658
  return /* @__PURE__ */ jsx45(
3642
3659
  Popover,
3643
3660
  {
@@ -3706,17 +3723,25 @@ function useReseedFromProps(value, applies, reseed) {
3706
3723
  if (!sameValue(value, applies())) reseed(value);
3707
3724
  }, [value, applies, reseed]);
3708
3725
  }
3709
- function usePreserveFocusAcrossRemount(seed) {
3710
- const pendingId = React25.useRef(null);
3711
- const capture = (ids) => {
3726
+ function usePreserveFocusAcrossRemount(seed, fields) {
3727
+ const pendingName = React25.useRef(null);
3728
+ const fieldsRef = useLatest(fields);
3729
+ const capture = () => {
3712
3730
  const active = document.activeElement;
3713
- pendingId.current = active instanceof HTMLElement && ids.includes(active.id) ? active.id : null;
3731
+ pendingName.current = null;
3732
+ if (!(active instanceof HTMLElement)) return;
3733
+ for (const [name, ref] of Object.entries(fieldsRef.current)) {
3734
+ if (ref.current?.contains(active)) {
3735
+ pendingName.current = name;
3736
+ break;
3737
+ }
3738
+ }
3714
3739
  };
3715
3740
  React25.useLayoutEffect(() => {
3716
- const id = pendingId.current;
3717
- if (!id) return;
3718
- pendingId.current = null;
3719
- document.getElementById(id)?.focus();
3741
+ const name = pendingName.current;
3742
+ if (!name) return;
3743
+ pendingName.current = null;
3744
+ fieldsRef.current[name]?.current?.querySelector("input,textarea,select,button")?.focus();
3720
3745
  }, [seed]);
3721
3746
  return capture;
3722
3747
  }
@@ -3730,9 +3755,11 @@ function RangePanel({
3730
3755
  }) {
3731
3756
  const [draft, setDraft] = React25.useState(() => rangeDraft(value));
3732
3757
  const [seed, setSeed] = React25.useState(0);
3733
- const preserveFocus = usePreserveFocusAcrossRemount(seed);
3734
- const minId = field.key + "-min";
3735
- const maxId = field.key + "-max";
3758
+ const minId = React25.useId();
3759
+ const maxId = React25.useId();
3760
+ const minWrapRef = React25.useRef(null);
3761
+ const maxWrapRef = React25.useRef(null);
3762
+ const preserveFocus = usePreserveFocusAcrossRemount(seed, { min: minWrapRef, max: maxWrapRef });
3736
3763
  const draftRef = React25.useRef(draft);
3737
3764
  const shown = () => {
3738
3765
  const { min, max } = draftRef.current;
@@ -3743,7 +3770,7 @@ function RangePanel({
3743
3770
  };
3744
3771
  const validity = useTextValidity(publish);
3745
3772
  useReseedFromProps(value, shown, (next) => {
3746
- preserveFocus([minId, maxId]);
3773
+ preserveFocus();
3747
3774
  draftRef.current = rangeDraft(next);
3748
3775
  setDraft(draftRef.current);
3749
3776
  validity.reset();
@@ -3761,6 +3788,16 @@ function RangePanel({
3761
3788
  setDraft(next);
3762
3789
  publish();
3763
3790
  };
3791
+ const doneClick = (e) => {
3792
+ if (doneDisabled) {
3793
+ e.preventDefault();
3794
+ return;
3795
+ }
3796
+ onClose();
3797
+ };
3798
+ const doneKeyDown = (e) => {
3799
+ if (doneDisabled && (e.key === "Enter" || e.key === " ")) e.preventDefault();
3800
+ };
3764
3801
  return /* @__PURE__ */ jsx45(
3765
3802
  Popover,
3766
3803
  {
@@ -3771,9 +3808,9 @@ function RangePanel({
3771
3808
  offset: 6,
3772
3809
  width: PANEL_WIDTH.range,
3773
3810
  label: field.label,
3774
- footer: /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: onClose, disabled: doneDisabled, children: "Done" }),
3811
+ footer: /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", "aria-disabled": doneDisabled, onClick: doneClick, onKeyDown: doneKeyDown, children: "Done" }),
3775
3812
  children: /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", children: [
3776
- /* @__PURE__ */ jsx45(
3813
+ /* @__PURE__ */ jsx45("span", { ref: minWrapRef, children: /* @__PURE__ */ jsx45(
3777
3814
  NumberInput,
3778
3815
  {
3779
3816
  id: minId,
@@ -3788,8 +3825,8 @@ function RangePanel({
3788
3825
  onValidityChange: validity.report("min")
3789
3826
  },
3790
3827
  "min-" + seed
3791
- ),
3792
- /* @__PURE__ */ jsx45(
3828
+ ) }),
3829
+ /* @__PURE__ */ jsx45("span", { ref: maxWrapRef, children: /* @__PURE__ */ jsx45(
3793
3830
  NumberInput,
3794
3831
  {
3795
3832
  id: maxId,
@@ -3805,7 +3842,7 @@ function RangePanel({
3805
3842
  onValidityChange: validity.report("max")
3806
3843
  },
3807
3844
  "max-" + seed
3808
- )
3845
+ ) })
3809
3846
  ] })
3810
3847
  }
3811
3848
  );
@@ -3832,8 +3869,9 @@ function DatePanel({
3832
3869
  const daysConfirmed = React25.useRef(value?.kind === "withinDays");
3833
3870
  const [between, setBetween] = React25.useState(initial.between);
3834
3871
  const [seed, setSeed] = React25.useState(0);
3835
- const preserveFocus = usePreserveFocusAcrossRemount(seed);
3836
- const daysId = field.key + "-days";
3872
+ const daysId = React25.useId();
3873
+ const daysWrapRef = React25.useRef(null);
3874
+ const preserveFocus = usePreserveFocusAcrossRemount(seed, { days: daysWrapRef });
3837
3875
  const shownWithinDays = () => {
3838
3876
  const days = daysRef.current;
3839
3877
  return applicable(field, days == null ? void 0 : { kind: "withinDays", days }, validity.allValid());
@@ -3849,7 +3887,7 @@ function DatePanel({
3849
3887
  return daysConfirmed.current ? shownWithinDays() : void 0;
3850
3888
  };
3851
3889
  useReseedFromProps(value, applies, (next) => {
3852
- preserveFocus([daysId]);
3890
+ preserveFocus();
3853
3891
  const d = dateDraft(field, next);
3854
3892
  setKind(d.kind);
3855
3893
  daysRef.current = d.days;
@@ -3892,6 +3930,9 @@ function DatePanel({
3892
3930
  }
3893
3931
  onClose();
3894
3932
  };
3933
+ const doneKeyDown = (e) => {
3934
+ if (doneDisabled && (e.key === "Enter" || e.key === " ")) e.preventDefault();
3935
+ };
3895
3936
  return /* @__PURE__ */ jsx45(
3896
3937
  Popover,
3897
3938
  {
@@ -3902,7 +3943,7 @@ function DatePanel({
3902
3943
  offset: 6,
3903
3944
  width: PANEL_WIDTH.date,
3904
3945
  label: field.label,
3905
- footer: /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: confirm, disabled: doneDisabled, children: "Done" }),
3946
+ footer: /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", "aria-disabled": doneDisabled, onClick: confirm, onKeyDown: doneKeyDown, children: "Done" }),
3906
3947
  children: /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", children: [
3907
3948
  /* @__PURE__ */ jsx45(
3908
3949
  SegmentedControl,
@@ -3916,7 +3957,7 @@ function DatePanel({
3916
3957
  onChange: (k) => switchKind(k)
3917
3958
  }
3918
3959
  ),
3919
- kind === "withinDays" ? /* @__PURE__ */ jsx45(
3960
+ kind === "withinDays" ? /* @__PURE__ */ jsx45("span", { ref: daysWrapRef, children: /* @__PURE__ */ jsx45(
3920
3961
  NumberInput,
3921
3962
  {
3922
3963
  id: daysId,
@@ -3930,7 +3971,7 @@ function DatePanel({
3930
3971
  onValidityChange: validity.report("days")
3931
3972
  },
3932
3973
  "days-" + seed
3933
- ) : /* @__PURE__ */ jsx45(
3974
+ ) }) : /* @__PURE__ */ jsx45(
3934
3975
  DatePicker,
3935
3976
  {
3936
3977
  range: true,