@flytedan/flytebot-design-system 0.32.0 → 0.33.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/README.md CHANGED
@@ -81,7 +81,8 @@ pnpm run docs
81
81
  pnpm run build # tsup — emits dist/{index.js,index.cjs,index.d.ts}
82
82
  pnpm run typecheck # tsc --noEmit
83
83
  pnpm run lint # eslint .
84
- pnpm run test # tsup, then vitest run (some tests check the built dist/)
84
+ pnpm run test # vitest run — needs no build
85
+ pnpm run test:dist # tsup, then the checks of the built dist/ (scripts/vitest.dist.config.ts)
85
86
  ```
86
87
 
87
88
  ## Releasing
@@ -112,9 +113,10 @@ bash scripts/release.sh <new-version> "<summary>"
112
113
  - fetches `origin` and checks out `origin/main`'s exact tip — never unmerged
113
114
  work;
114
115
  - installs with `pnpm install --frozen-lockfile`;
115
- - runs the gates: `tsup` (first, since some tests check the built `dist/`),
116
- `vitest run`, `tsc --noEmit`, `tsc --noEmit -p tsconfig.docs.json`, the docs
117
- `vite build`, and `lint` if the repo has that script;
116
+ - runs the gates: `tsup`, then straight away the checks of that build
117
+ (`vitest run --config scripts/vitest.dist.config.ts`), then `vitest run`,
118
+ `tsc --noEmit`, `tsc --noEmit -p tsconfig.docs.json`, the docs `vite build`,
119
+ and `lint` if the repo has that script;
118
120
  - refuses `<new-version>` unless it is greater than both the working
119
121
  checkout's `package.json` version and npm's currently published `latest`;
120
122
  - bumps `package.json`, commits `chore(release): <new-version> — <summary>`,
package/dist/index.cjs CHANGED
@@ -3217,7 +3217,8 @@ function NumberInput({
3217
3217
  )
3218
3218
  ] })
3219
3219
  ] }),
3220
- shownError ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { id: errorId, className: "fd-field-error", children: [
3220
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { id: errorId, className: "fd-sr", role: "status", children: shownError ?? "" }),
3221
+ shownError ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "fd-field-error", "aria-hidden": "true", children: [
3221
3222
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("i", { className: "ph ph-warning-circle", "aria-hidden": "true" }),
3222
3223
  shownError
3223
3224
  ] }) : help ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "fd-field-help", children: help }) : null
@@ -3658,9 +3659,7 @@ function MultiSelectPanel({
3658
3659
  mountedRef.current = false;
3659
3660
  };
3660
3661
  }, []);
3661
- const commit = (nextValues, nextOperator = operator) => {
3662
- onCommit({ operator: nextOperator, values: nextValues });
3663
- };
3662
+ const commit = (nextValues, nextOperator = operator) => onCommit({ operator: nextOperator, values: nextValues });
3664
3663
  const setOperator = (op) => {
3665
3664
  setDraftOperator(op);
3666
3665
  if (selectedRef.current.length > 0) commit(selectedRef.current, op);
@@ -3741,7 +3740,11 @@ function MultiSelectPanel({
3741
3740
  setPasteError("This filter allows at most " + field.maxValues + " values; adding this list would make " + merged.size + ".");
3742
3741
  return;
3743
3742
  }
3744
- commit(Array.from(merged.values()), operatorRef.current);
3743
+ const applied = commit(Array.from(merged.values()), operatorRef.current);
3744
+ if (!applied.ok) {
3745
+ setPasteError(applied.reason);
3746
+ return;
3747
+ }
3745
3748
  setPasteText("");
3746
3749
  }, (err) => {
3747
3750
  if (!mountedRef.current) return;
@@ -3878,6 +3881,34 @@ function MultiSelectPanel({
3878
3881
  }
3879
3882
  );
3880
3883
  }
3884
+ function useTextValidity(onFlip) {
3885
+ const invalidRef = React24.useRef(/* @__PURE__ */ new Set());
3886
+ const [, rerender] = React24.useReducer((n) => n + 1, 0);
3887
+ const onFlipRef = React24.useRef(onFlip);
3888
+ onFlipRef.current = onFlip;
3889
+ const record = (name, valid) => {
3890
+ const invalid = invalidRef.current;
3891
+ if (valid !== invalid.has(name)) return false;
3892
+ if (valid) invalid.delete(name);
3893
+ else invalid.add(name);
3894
+ rerender();
3895
+ return true;
3896
+ };
3897
+ return {
3898
+ allValid: () => invalidRef.current.size === 0,
3899
+ report: (name) => (valid) => {
3900
+ if (record(name, valid)) onFlipRef.current();
3901
+ },
3902
+ /** For an input that is about to unmount: it has nothing left to show. */
3903
+ forget: (name) => {
3904
+ record(name, true);
3905
+ }
3906
+ };
3907
+ }
3908
+ function applicable(field, candidate, textValid) {
3909
+ if (!textValid || candidate === void 0) return void 0;
3910
+ return validateFilterValue(field, candidate).ok ? candidate : void 0;
3911
+ }
3881
3912
  function RangePanel({
3882
3913
  field,
3883
3914
  value,
@@ -3887,19 +3918,22 @@ function RangePanel({
3887
3918
  }) {
3888
3919
  const [draft, setDraft] = React24.useState({ min: value?.min, max: value?.max });
3889
3920
  const draftRef = React24.useRef(draft);
3890
- const [minTextValid, setMinTextValid] = React24.useState(true);
3891
- const [maxTextValid, setMaxTextValid] = React24.useState(true);
3921
+ const publish = () => {
3922
+ const { min, max } = draftRef.current;
3923
+ onCommit(applicable(field, min == null && max == null ? void 0 : { min, max }, validity.allValid()));
3924
+ };
3925
+ const validity = useTextValidity(publish);
3892
3926
  const draftMin = draft.min;
3893
3927
  const draftMax = draft.max;
3894
3928
  const isEmpty = draftMin == null && draftMax == null;
3895
3929
  const validation = isEmpty ? { ok: true } : validateFilterValue(field, { min: draftMin, max: draftMax });
3896
3930
  const comboError = !isEmpty && !validation.ok ? validation.reason : void 0;
3897
- const doneDisabled = !minTextValid || !maxTextValid || !!comboError;
3931
+ const doneDisabled = !validity.allValid() || !!comboError;
3898
3932
  const apply = (patch) => {
3899
3933
  const next = { ...draftRef.current, ...patch };
3900
3934
  draftRef.current = next;
3901
3935
  setDraft(next);
3902
- onCommit(next.min == null && next.max == null ? void 0 : next);
3936
+ publish();
3903
3937
  };
3904
3938
  return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3905
3939
  Popover,
@@ -3924,7 +3958,7 @@ function RangePanel({
3924
3958
  integer: field.integer,
3925
3959
  placeholder: field.minPlaceholder,
3926
3960
  onChange: (e) => apply({ min: e.target.value }),
3927
- onValidityChange: setMinTextValid
3961
+ onValidityChange: validity.report("min")
3928
3962
  }
3929
3963
  ),
3930
3964
  /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
@@ -3939,7 +3973,7 @@ function RangePanel({
3939
3973
  error: comboError,
3940
3974
  placeholder: field.maxPlaceholder,
3941
3975
  onChange: (e) => apply({ max: e.target.value }),
3942
- onValidityChange: setMaxTextValid
3976
+ onValidityChange: validity.report("max")
3943
3977
  }
3944
3978
  )
3945
3979
  ] })
@@ -3955,34 +3989,47 @@ function DatePanel({
3955
3989
  }) {
3956
3990
  const defaultDays = field.defaultDays ?? 30;
3957
3991
  const maxDays = effectiveMaxDays(field);
3958
- const [daysTextValid, setDaysTextValid] = React24.useState(true);
3959
3992
  const [kind, setKind] = React24.useState(value?.kind ?? "withinDays");
3960
3993
  const [daysDraft, setDaysDraft] = React24.useState(
3961
3994
  value?.kind === "withinDays" ? value.days : defaultDays
3962
3995
  );
3996
+ const daysRef = React24.useRef(daysDraft);
3963
3997
  const [between, setBetween] = React24.useState(
3964
3998
  value?.kind === "between" ? { from: value.from, to: value.to } : { from: "", to: "" }
3965
3999
  );
4000
+ const shownWithinDays = () => {
4001
+ const days = daysRef.current;
4002
+ return applicable(field, days == null ? void 0 : { kind: "withinDays", days }, validity.allValid());
4003
+ };
4004
+ const validity = useTextValidity(() => onCommit(shownWithinDays()));
3966
4005
  const applyWithinDays = (days) => {
4006
+ daysRef.current = days;
3967
4007
  setDaysDraft(days);
3968
- onCommit(days == null ? void 0 : { kind: "withinDays", days });
4008
+ onCommit(shownWithinDays());
3969
4009
  };
3970
4010
  const applyBetween = (from, to) => {
3971
4011
  setBetween({ from, to });
3972
- onCommit(from && to ? { kind: "between", from, to } : void 0);
4012
+ onCommit(applicable(field, from && to ? { kind: "between", from, to } : void 0, true));
3973
4013
  };
3974
4014
  const switchKind = (next) => {
3975
4015
  if (next === kind) return;
3976
4016
  setKind(next);
3977
- if (next === "between") setBetween({ from: "", to: "" });
3978
- else setDaysDraft(defaultDays);
3979
- setDaysTextValid(true);
4017
+ if (next === "between") {
4018
+ setBetween({ from: "", to: "" });
4019
+ validity.forget("days");
4020
+ } else {
4021
+ daysRef.current = defaultDays;
4022
+ setDaysDraft(defaultDays);
4023
+ }
3980
4024
  onCommit(void 0);
3981
4025
  };
3982
- const doneDisabled = kind === "withinDays" && !daysTextValid;
4026
+ const doneDisabled = kind === "withinDays" && !validity.allValid();
3983
4027
  const confirm = () => {
3984
4028
  if (doneDisabled) return;
3985
- if (kind === "withinDays" && daysDraft != null) onCommit({ kind: "withinDays", days: daysDraft });
4029
+ if (kind === "withinDays") {
4030
+ const shown = shownWithinDays();
4031
+ if (shown) onCommit(shown);
4032
+ }
3986
4033
  onClose();
3987
4034
  };
3988
4035
  return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
@@ -4019,7 +4066,7 @@ function DatePanel({
4019
4066
  integer: true,
4020
4067
  value: daysDraft ?? "",
4021
4068
  onChange: (e) => applyWithinDays(e.target.value),
4022
- onValidityChange: setDaysTextValid
4069
+ onValidityChange: validity.report("days")
4023
4070
  }
4024
4071
  ) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
4025
4072
  DatePicker,
@@ -4044,7 +4091,14 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
4044
4091
  for (const f of fields) assertValidFieldDefinition(f);
4045
4092
  }
4046
4093
  const latest = React24.useRef({ fields, filters, onFilterChange, onFilterRemove });
4047
- latest.current = { fields, filters, onFilterChange, onFilterRemove };
4094
+ const filtersProp = React24.useRef(filters);
4095
+ if (filters !== filtersProp.current) {
4096
+ filtersProp.current = filters;
4097
+ latest.current.filters = filters;
4098
+ }
4099
+ latest.current.fields = fields;
4100
+ latest.current.onFilterChange = onFilterChange;
4101
+ latest.current.onFilterRemove = onFilterRemove;
4048
4102
  const commit = (field, candidate) => {
4049
4103
  const current2 = latest.current.fields.find((f) => f.key === field.key);
4050
4104
  if (!current2) return { ok: false, reason: "This filter is no longer available" };