@flytedan/flytebot-design-system 0.28.0 → 0.30.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
@@ -199,6 +199,15 @@ var ROOT_LAYER = 0;
199
199
  var LAYER_STEP = 10;
200
200
  var ROOT_STEP = 100;
201
201
  var OVERLAY_CEILING = 1e4;
202
+ var openEscapeIds = [];
203
+ var registerEscapeLayer = (id) => {
204
+ if (openEscapeIds.includes(id)) return;
205
+ openEscapeIds = [...openEscapeIds, id];
206
+ };
207
+ var unregisterEscapeLayer = (id) => {
208
+ if (!openEscapeIds.includes(id)) return;
209
+ openEscapeIds = openEscapeIds.filter((existing) => existing !== id);
210
+ };
202
211
  var LayerContext = React2.createContext(ROOT_LAYER);
203
212
  var openRootIds = [];
204
213
  var rootListeners = /* @__PURE__ */ new Set();
@@ -276,6 +285,12 @@ function Popover({
276
285
  });
277
286
  const restore = React2.useRef(null);
278
287
  const zIndex = useOverlayLayer(!!open);
288
+ const escapeId = React2.useId();
289
+ React2.useLayoutEffect(() => {
290
+ if (!open) return;
291
+ registerEscapeLayer(escapeId);
292
+ return () => unregisterEscapeLayer(escapeId);
293
+ }, [open, escapeId]);
279
294
  React2.useEffect(() => {
280
295
  if (!open) return;
281
296
  restore.current = typeof document !== "undefined" ? document.activeElement : null;
@@ -300,10 +315,10 @@ function Popover({
300
315
  onClose && onClose();
301
316
  };
302
317
  const key = (e) => {
303
- if (closeOnEscape && e.key === "Escape") {
304
- e.stopPropagation();
305
- onClose && onClose();
306
- }
318
+ if (!closeOnEscape || e.key !== "Escape") return;
319
+ if (openEscapeIds[openEscapeIds.length - 1] !== escapeId) return;
320
+ e.stopPropagation();
321
+ onClose && onClose();
307
322
  };
308
323
  document.addEventListener("pointerdown", away, true);
309
324
  document.addEventListener("keydown", key);
@@ -311,7 +326,7 @@ function Popover({
311
326
  document.removeEventListener("pointerdown", away, true);
312
327
  document.removeEventListener("keydown", key);
313
328
  };
314
- }, [open, closeOnOutside, closeOnEscape, onClose, anchorRef, triggerRef]);
329
+ }, [open, closeOnOutside, closeOnEscape, onClose, anchorRef, triggerRef, escapeId]);
315
330
  if (!open || !pos) return null;
316
331
  return portal(
317
332
  /* @__PURE__ */ jsx3(LayerProvider, { zIndex, children: /* @__PURE__ */ jsxs3(
@@ -1500,11 +1515,13 @@ var BANNER_TONE_STYLE = {
1500
1515
  text: "var(--danger-text)"
1501
1516
  }
1502
1517
  };
1503
- function Banner({ tone = "info", title, detail, actions }) {
1518
+ function Banner({ tone = "info", title, detail, actions, role }) {
1504
1519
  const t = BANNER_TONE_STYLE[tone];
1520
+ const liveRole = role || (tone === "danger" ? "alert" : "status");
1505
1521
  return /* @__PURE__ */ jsxs19(
1506
1522
  "div",
1507
1523
  {
1524
+ role: liveRole,
1508
1525
  style: {
1509
1526
  display: "flex",
1510
1527
  alignItems: "flex-start",
@@ -2714,7 +2731,7 @@ function DataTable({
2714
2731
  }
2715
2732
 
2716
2733
  // src/components/data/FilterBar.tsx
2717
- import * as React23 from "react";
2734
+ import * as React24 from "react";
2718
2735
 
2719
2736
  // src/components/forms/NumberInput.tsx
2720
2737
  import * as React21 from "react";
@@ -2762,21 +2779,36 @@ function NumberInput({
2762
2779
  "aria-label": ariaLabel
2763
2780
  }) {
2764
2781
  const fieldId = useFieldId(id);
2782
+ const errorId = fieldId + "-error";
2765
2783
  const num = (v) => {
2766
- const n = Number(String(v == null ? "" : v).replace(/[^0-9.-]/g, ""));
2784
+ const s = String(v == null ? "" : v).replace(/[^0-9.-]/g, "");
2785
+ if (s === "" || s === "-") return null;
2786
+ const n = Number(s);
2767
2787
  return isNaN(n) ? null : n;
2768
2788
  };
2789
+ const requiresInteger = Number.isInteger(step);
2790
+ const validationMessage = (n) => {
2791
+ if (requiresInteger && !Number.isInteger(n)) return "Enter a whole number";
2792
+ if (n < min) return "Must be \u2265 " + min;
2793
+ if (n > max) return "Must be \u2264 " + max;
2794
+ return null;
2795
+ };
2769
2796
  const [text, setText] = React21.useState(value == null || value === "" ? "" : String(value));
2770
2797
  const [editing, setEditing] = React21.useState(false);
2798
+ const [invalidMessage, setInvalidMessage] = React21.useState(null);
2771
2799
  const timer = React21.useRef(null);
2772
2800
  React21.useEffect(() => {
2773
- if (!editing) setText(value == null || value === "" ? "" : String(value));
2801
+ if (!editing) {
2802
+ setText(value == null || value === "" ? "" : String(value));
2803
+ setInvalidMessage(null);
2804
+ }
2774
2805
  }, [value, editing]);
2775
2806
  const clamp = (n) => Math.min(max, Math.max(min, n));
2776
2807
  const fire = (n) => onChange && onChange({ target: { value: n } });
2777
2808
  const bump = (dir, mult = 1) => {
2778
2809
  const cur = num(text) ?? num(value) ?? (min === -Infinity ? 0 : min);
2779
2810
  const next = clamp(cur + dir * (bigStep && mult > 1 ? bigStep : step) * (bigStep ? 1 : mult));
2811
+ setInvalidMessage(null);
2780
2812
  setText(String(next));
2781
2813
  fire(next);
2782
2814
  };
@@ -2800,7 +2832,8 @@ function NumberInput({
2800
2832
  const n = num(text);
2801
2833
  return n == null ? "" : format ? n.toLocaleString() : String(n);
2802
2834
  })();
2803
- const box = ["fd-input", "fd-input-num", error ? "fd-input-error" : "", disabled ? "fd-input-disabled" : ""].filter(Boolean).join(" ");
2835
+ const shownError = error || invalidMessage || void 0;
2836
+ const box = ["fd-input", "fd-input-num", shownError ? "fd-input-error" : "", disabled ? "fd-input-disabled" : ""].filter(Boolean).join(" ");
2804
2837
  const fieldName = label || ariaLabel;
2805
2838
  const stepperName = (verb) => fieldName ? verb + " " + fieldName : verb;
2806
2839
  return /* @__PURE__ */ jsxs36("div", { className: ["fd-field", className].filter(Boolean).join(" "), style, children: [
@@ -2816,20 +2849,29 @@ function NumberInput({
2816
2849
  disabled,
2817
2850
  placeholder,
2818
2851
  value: shown,
2852
+ "aria-invalid": shownError ? "true" : void 0,
2853
+ "aria-describedby": shownError ? errorId : void 0,
2819
2854
  onFocus: () => setEditing(true),
2820
2855
  onBlur: () => {
2821
2856
  setEditing(false);
2822
- const n = num(text);
2823
- if (n != null) {
2824
- const c = clamp(n);
2825
- setText(String(c));
2826
- fire(c);
2827
- }
2857
+ if (text.trim() === "") fire(void 0);
2828
2858
  },
2829
2859
  onChange: (e) => {
2830
- setText(e.target.value);
2831
- const n = num(e.target.value);
2832
- if (n != null) fire(n);
2860
+ const raw = e.target.value;
2861
+ setText(raw);
2862
+ if (raw.trim() === "") {
2863
+ setInvalidMessage(null);
2864
+ fire(void 0);
2865
+ return;
2866
+ }
2867
+ const n = num(raw);
2868
+ if (n == null) {
2869
+ setInvalidMessage("Enter a number");
2870
+ return;
2871
+ }
2872
+ const message = validationMessage(n);
2873
+ setInvalidMessage(message);
2874
+ if (!message) fire(n);
2833
2875
  },
2834
2876
  onKeyDown: (e) => {
2835
2877
  if (e.key === "ArrowUp") {
@@ -2872,9 +2914,9 @@ function NumberInput({
2872
2914
  )
2873
2915
  ] })
2874
2916
  ] }),
2875
- error ? /* @__PURE__ */ jsxs36("span", { className: "fd-field-error", children: [
2917
+ shownError ? /* @__PURE__ */ jsxs36("span", { id: errorId, className: "fd-field-error", children: [
2876
2918
  /* @__PURE__ */ jsx42("i", { className: "ph ph-warning-circle", "aria-hidden": "true" }),
2877
- error
2919
+ shownError
2878
2920
  ] }) : help ? /* @__PURE__ */ jsx42("span", { className: "fd-field-help", children: help }) : null
2879
2921
  ] });
2880
2922
  }
@@ -3096,16 +3138,48 @@ function Textarea({ label, help, error, required = false, rows = 4, disabled = f
3096
3138
  ] });
3097
3139
  }
3098
3140
 
3141
+ // src/components/forms/listbox.ts
3142
+ import * as React23 from "react";
3143
+ function useListboxNav({ count, onSelect }) {
3144
+ const [active, setActive] = React23.useState(-1);
3145
+ return {
3146
+ active,
3147
+ setActive,
3148
+ move: (dir) => setActive((a) => Math.min(count - 1, Math.max(0, a + dir))),
3149
+ first: () => setActive(0),
3150
+ last: () => setActive(count - 1),
3151
+ activate: () => {
3152
+ if (active >= 0 && active < count) onSelect(active);
3153
+ }
3154
+ };
3155
+ }
3156
+ function useListboxScrollIntoView(active, listRef, enabled) {
3157
+ React23.useEffect(() => {
3158
+ if (!enabled || active < 0 || !listRef.current) return;
3159
+ const list = listRef.current;
3160
+ const el = list.querySelector('[data-i="' + active + '"]');
3161
+ if (!el) return;
3162
+ const top = el.offsetTop - list.offsetTop;
3163
+ if (top < list.scrollTop) list.scrollTop = top - 4;
3164
+ else if (top + el.offsetHeight > list.scrollTop + list.clientHeight) {
3165
+ list.scrollTop = top + el.offsetHeight - list.clientHeight + 4;
3166
+ }
3167
+ }, [active, enabled]);
3168
+ }
3169
+
3099
3170
  // src/components/data/FilterBar.tsx
3100
3171
  import { jsx as jsx45, jsxs as jsxs39 } from "react/jsx-runtime";
3172
+ var PANEL_WIDTH = { multiSelect: 280, range: 240, date: 300 };
3101
3173
  var isSet = (v) => !(v == null || v === "" || v === false);
3174
+ function operatorsFor(field) {
3175
+ return field.operators && field.operators.length ? field.operators : ["in", "notIn"];
3176
+ }
3102
3177
  function isFieldSet(field, value) {
3103
3178
  switch (field.type) {
3104
3179
  case "text":
3105
3180
  case "select":
3106
3181
  return isSet(value);
3107
- case "multiSelect":
3108
- case "pasteList": {
3182
+ case "multiSelect": {
3109
3183
  const v = value;
3110
3184
  return !!v && Array.isArray(v.values) && v.values.length > 0;
3111
3185
  }
@@ -3116,7 +3190,7 @@ function isFieldSet(field, value) {
3116
3190
  case "date": {
3117
3191
  const v = value;
3118
3192
  if (!v) return false;
3119
- return v.kind === "withinDays" ? v.days != null : !!(v.from || v.to);
3193
+ return v.kind === "withinDays" ? v.days != null : !!(v.from && v.to);
3120
3194
  }
3121
3195
  }
3122
3196
  }
@@ -3128,8 +3202,7 @@ function chipLabel(field, value) {
3128
3202
  const chosen = field.options.find((o) => o.value === value);
3129
3203
  return chosen ? chosen.label : String(value ?? "");
3130
3204
  }
3131
- case "multiSelect":
3132
- case "pasteList": {
3205
+ case "multiSelect": {
3133
3206
  const v = value;
3134
3207
  if (!v || !v.values.length) return "";
3135
3208
  const prefix = v.operator === "notIn" ? "Not " : "";
@@ -3147,7 +3220,7 @@ function chipLabel(field, value) {
3147
3220
  const v = value;
3148
3221
  if (!v) return "";
3149
3222
  if (v.kind === "withinDays") return "Last " + v.days + " day" + (v.days === 1 ? "" : "s");
3150
- return (v.from || "\u2026") + " \u2192 " + (v.to || "\u2026");
3223
+ return v.from + " \u2192 " + v.to;
3151
3224
  }
3152
3225
  }
3153
3226
  }
@@ -3169,338 +3242,395 @@ function OperatorToggle({
3169
3242
  }
3170
3243
  );
3171
3244
  }
3172
- function EditPopover({ zIndex, width, children }) {
3173
- return /* @__PURE__ */ jsx45(LayerProvider, { zIndex, children: /* @__PURE__ */ jsx45(
3174
- "span",
3175
- {
3176
- className: "fd-view-enter",
3177
- style: {
3178
- position: "absolute",
3179
- top: "calc(100% + 6px)",
3180
- left: 0,
3181
- zIndex,
3182
- width,
3183
- maxWidth: "calc(100vw - 32px)",
3184
- display: "block",
3185
- background: "var(--surface)",
3186
- border: "1px solid var(--border)",
3187
- borderRadius: 10,
3188
- boxShadow: "0 16px 44px rgba(11,13,17,.18)",
3189
- padding: "var(--s-8)"
3190
- },
3191
- children
3192
- }
3193
- ) });
3194
- }
3195
3245
  function MultiSelectPanel({
3196
3246
  field,
3197
3247
  value,
3248
+ anchorRef,
3198
3249
  onChange,
3250
+ onRemove,
3199
3251
  onClose
3200
3252
  }) {
3201
- const operator = value?.operator ?? "in";
3253
+ const allowedOps = operatorsFor(field);
3254
+ const [draftOperator, setDraftOperator] = React24.useState(value?.operator ?? allowedOps[0]);
3255
+ const operator = allowedOps.length === 1 ? allowedOps[0] : value?.operator ?? draftOperator;
3202
3256
  const selected = value?.values ?? [];
3203
- const [query, setQuery] = React23.useState("");
3204
- const [asyncOptions, setAsyncOptions] = React23.useState(null);
3205
- const [loading, setLoading] = React23.useState(!!field.loadOptions);
3206
- const [active, setActive] = React23.useState(0);
3207
- const inputRef = React23.useRef(null);
3208
- const listId = React23.useId();
3209
- React23.useEffect(() => {
3257
+ const selectedRef = React24.useRef(selected);
3258
+ selectedRef.current = selected;
3259
+ const commit = (nextValues, nextOperator = operator) => {
3260
+ if (nextValues.length === 0) onRemove();
3261
+ else onChange({ operator: nextOperator, values: nextValues });
3262
+ };
3263
+ const setOperator = (op) => {
3264
+ setDraftOperator(op);
3265
+ if (selected.length > 0) commit(selected, op);
3266
+ };
3267
+ const [mode2, setMode] = React24.useState("search");
3268
+ const [query, setQuery] = React24.useState("");
3269
+ const [asyncState, setAsyncState] = React24.useState(
3270
+ { options: null, loading: !!field.loadOptions, error: false }
3271
+ );
3272
+ const [retryTick, setRetryTick] = React24.useState(0);
3273
+ const inputRef = React24.useRef(null);
3274
+ const listRef = React24.useRef(null);
3275
+ const listId = React24.useId();
3276
+ const loadOptionsRef = React24.useRef(field.loadOptions);
3277
+ loadOptionsRef.current = field.loadOptions;
3278
+ React24.useEffect(() => {
3210
3279
  inputRef.current?.focus();
3211
3280
  }, []);
3212
- React23.useEffect(() => {
3213
- if (!field.loadOptions) return;
3214
- let cancelled = false;
3215
- setLoading(true);
3216
- const loadOptions = field.loadOptions;
3281
+ React24.useEffect(() => {
3282
+ const load = loadOptionsRef.current;
3283
+ if (!load) return;
3284
+ const controller = new AbortController();
3285
+ setAsyncState((s) => ({ options: s.options, loading: true, error: false }));
3217
3286
  const t = setTimeout(() => {
3218
- loadOptions(query).then((opts) => {
3219
- if (!cancelled) {
3220
- setAsyncOptions(opts);
3221
- setLoading(false);
3222
- }
3223
- }).catch(() => {
3224
- if (!cancelled) {
3225
- setAsyncOptions([]);
3226
- setLoading(false);
3227
- }
3287
+ load(query, controller.signal).then((opts) => {
3288
+ if (controller.signal.aborted) return;
3289
+ setAsyncState({ options: opts, loading: false, error: false });
3290
+ }).catch((err) => {
3291
+ if (controller.signal.aborted || err && err.name === "AbortError") return;
3292
+ setAsyncState({ options: null, loading: false, error: true });
3228
3293
  });
3229
3294
  }, 300);
3230
3295
  return () => {
3231
- cancelled = true;
3232
3296
  clearTimeout(t);
3297
+ controller.abort();
3233
3298
  };
3234
- }, [query, field.loadOptions]);
3235
- const options = field.loadOptions ? asyncOptions ?? [] : (field.options ?? []).filter((o) => o.label.toLowerCase().includes(query.toLowerCase()));
3236
- React23.useEffect(() => {
3237
- setActive(0);
3238
- }, [query, options.length]);
3299
+ }, [query, retryTick]);
3300
+ const options = field.loadOptions ? asyncState.options ?? [] : (field.options ?? []).filter((o) => o.label.toLowerCase().includes(query.toLowerCase()));
3301
+ const showLoading = !!field.loadOptions && asyncState.loading;
3302
+ const showError = !!field.loadOptions && asyncState.error;
3303
+ const hasListbox = !showLoading && !showError && options.length > 0;
3239
3304
  const isSelected = (o) => selected.some((s) => s.value === o.value);
3240
3305
  const toggle = (o) => {
3241
3306
  const next = isSelected(o) ? selected.filter((s) => s.value !== o.value) : [...selected, o];
3242
- onChange({ operator, values: next });
3307
+ commit(next);
3308
+ };
3309
+ const nav = useListboxNav({ count: options.length, onSelect: (i) => toggle(options[i]) });
3310
+ useListboxScrollIntoView(nav.active, listRef, hasListbox);
3311
+ React24.useEffect(() => {
3312
+ nav.setActive(0);
3313
+ }, [query, options.length]);
3314
+ const activeId = hasListbox && options[nav.active] ? listId + "-opt-" + nav.active : void 0;
3315
+ const liveRole = showError ? "alert" : "status";
3316
+ const liveMessage = showLoading ? "Loading\u2026" : showError ? "Couldn't load options." : options.length === 0 ? "No matches" : "";
3317
+ const [pasteText, setPasteText] = React24.useState("");
3318
+ const [pasteLoading, setPasteLoading] = React24.useState(false);
3319
+ const [unmatchedTokens, setUnmatchedTokens] = React24.useState([]);
3320
+ const [pasteError, setPasteError] = React24.useState(null);
3321
+ const pasteWrapRef = React24.useRef(null);
3322
+ React24.useEffect(() => {
3323
+ if (mode2 === "paste") pasteWrapRef.current?.querySelector("textarea")?.focus();
3324
+ }, [mode2]);
3325
+ const doResolve = () => {
3326
+ if (!field.resolve) return;
3327
+ const tokens = Array.from(new Set(pasteText.split(/[\n,]/).map((t) => t.trim()).filter(Boolean)));
3328
+ if (!tokens.length) return;
3329
+ setPasteLoading(true);
3330
+ setPasteError(null);
3331
+ field.resolve(tokens).then((result) => {
3332
+ setPasteLoading(false);
3333
+ setUnmatchedTokens(result.unmatched);
3334
+ const merged = new Map(selectedRef.current.map((s) => [s.value, s]));
3335
+ for (const m of result.matched) merged.set(m.value, { value: m.value, label: m.label });
3336
+ commit(Array.from(merged.values()));
3337
+ setPasteText("");
3338
+ }).catch((err) => {
3339
+ setPasteLoading(false);
3340
+ setPasteError(err instanceof Error ? err.message : "Could not resolve the pasted list.");
3341
+ });
3243
3342
  };
3244
- const activeId = options[active] ? listId + "-opt-" + active : void 0;
3245
- return /* @__PURE__ */ jsxs39("span", { className: "fd-stack", style: { gap: "var(--s-8)", width: 260 }, children: [
3246
- /* @__PURE__ */ jsx45(OperatorToggle, { operator, onChange: (op) => onChange({ operator: op, values: selected }), label: field.label }),
3343
+ const header = /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", children: [
3344
+ allowedOps.length > 1 ? /* @__PURE__ */ jsx45(OperatorToggle, { operator, onChange: setOperator, label: field.label }) : null,
3345
+ field.resolve ? /* @__PURE__ */ jsx45(
3346
+ SegmentedControl,
3347
+ {
3348
+ "aria-label": field.label + " \u2014 search or paste a list",
3349
+ options: [{ value: "search", label: "Search" }, { value: "paste", label: "Paste a list" }],
3350
+ value: mode2,
3351
+ onChange: (m) => setMode(m)
3352
+ }
3353
+ ) : null,
3354
+ mode2 === "search" ? /* @__PURE__ */ jsxs39("span", { className: "fd-pop-search", children: [
3355
+ /* @__PURE__ */ jsx45("i", { className: "ph ph-magnifying-glass", "aria-hidden": "true" }),
3356
+ /* @__PURE__ */ jsx45(
3357
+ "input",
3358
+ {
3359
+ ref: inputRef,
3360
+ role: "combobox",
3361
+ "aria-expanded": hasListbox,
3362
+ "aria-controls": hasListbox ? listId : void 0,
3363
+ "aria-autocomplete": "list",
3364
+ "aria-activedescendant": activeId,
3365
+ "aria-label": field.label,
3366
+ value: query,
3367
+ placeholder: field.placeholder || "Search\u2026",
3368
+ onChange: (e) => setQuery(e.target.value),
3369
+ onKeyDown: (e) => {
3370
+ if (e.key === "ArrowDown") {
3371
+ e.preventDefault();
3372
+ nav.move(1);
3373
+ } else if (e.key === "ArrowUp") {
3374
+ e.preventDefault();
3375
+ nav.move(-1);
3376
+ } else if (e.key === "Home") {
3377
+ e.preventDefault();
3378
+ nav.first();
3379
+ } else if (e.key === "End") {
3380
+ e.preventDefault();
3381
+ nav.last();
3382
+ } else if (e.key === "Enter") {
3383
+ e.preventDefault();
3384
+ nav.activate();
3385
+ }
3386
+ }
3387
+ }
3388
+ )
3389
+ ] }) : null
3390
+ ] });
3391
+ const body = mode2 === "paste" ? /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", ref: pasteWrapRef, children: [
3247
3392
  /* @__PURE__ */ jsx45(
3248
- "input",
3393
+ Textarea,
3249
3394
  {
3250
- ref: inputRef,
3251
- role: "combobox",
3252
- "aria-expanded": "true",
3253
- "aria-controls": listId,
3254
- "aria-autocomplete": "list",
3255
- "aria-activedescendant": activeId,
3256
- "aria-label": field.label,
3257
- className: "fd-input",
3258
- style: { width: "100%", padding: "6px 10px" },
3259
- value: query,
3260
- placeholder: field.placeholder || "Search\u2026",
3261
- onChange: (e) => setQuery(e.target.value),
3395
+ "aria-label": field.label + " \u2014 paste a list",
3396
+ rows: 4,
3397
+ placeholder: field.pastePlaceholder || field.placeholder || "Paste names, one per line or comma-separated",
3398
+ value: pasteText,
3399
+ disabled: pasteLoading,
3400
+ onChange: (e) => setPasteText(e.target.value),
3262
3401
  onKeyDown: (e) => {
3263
- if (e.key === "ArrowDown") {
3264
- e.preventDefault();
3265
- setActive((a) => Math.min(a + 1, options.length - 1));
3266
- } else if (e.key === "ArrowUp") {
3267
- e.preventDefault();
3268
- setActive((a) => Math.max(a - 1, 0));
3269
- } else if (e.key === "Enter") {
3402
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
3270
3403
  e.preventDefault();
3271
- if (options[active]) toggle(options[active]);
3272
- } else if (e.key === "Escape") {
3273
- e.stopPropagation();
3274
- onClose();
3404
+ doResolve();
3275
3405
  }
3276
3406
  }
3277
3407
  }
3278
3408
  ),
3279
- loading ? /* @__PURE__ */ jsxs39("span", { className: "fd-row fd-body-sm fd-muted", style: { gap: "var(--s-4)" }, children: [
3280
- /* @__PURE__ */ jsx45("span", { className: "fd-spinner", "aria-hidden": "true" }),
3281
- " Loading\u2026"
3282
- ] }) : options.length === 0 ? /* @__PURE__ */ jsx45("span", { className: "fd-body-sm fd-muted", children: "No matches" }) : /* @__PURE__ */ jsx45(
3409
+ pasteError ? /* @__PURE__ */ jsx45(Banner, { tone: "danger", title: "Couldn't resolve the list", detail: pasteError }) : null,
3410
+ unmatchedTokens.length > 0 ? /* @__PURE__ */ jsx45(Banner, { tone: "warn", title: unmatchedTokens.length + " didn't match", detail: unmatchedTokens.join(", ") }) : null,
3411
+ selected.length > 0 ? /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-xs", children: [
3412
+ /* @__PURE__ */ jsxs39("span", { className: "fd-body-sm fd-muted", children: [
3413
+ selected.length,
3414
+ " matched"
3415
+ ] }),
3416
+ /* @__PURE__ */ jsx45("span", { className: "fd-row fd-wrap fd-gap-xs", children: selected.map((v) => /* @__PURE__ */ jsx45(Tag, { onRemove: () => commit(selected.filter((s) => s.value !== v.value)), children: v.label }, v.value)) })
3417
+ ] }) : null
3418
+ ] }) : /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", children: [
3419
+ /* @__PURE__ */ jsxs39(
3283
3420
  "span",
3284
3421
  {
3285
- id: listId,
3286
- role: "listbox",
3287
- "aria-label": field.label,
3288
- "aria-multiselectable": "true",
3289
- className: "fd-stack",
3290
- style: { gap: 2, maxHeight: 220, overflowY: "auto" },
3291
- children: options.map((o, i) => {
3292
- const on = isSelected(o);
3293
- return /* @__PURE__ */ jsxs39(
3294
- "button",
3295
- {
3296
- type: "button",
3297
- id: listId + "-opt-" + i,
3298
- role: "option",
3299
- "aria-selected": on,
3300
- className: "fd-row",
3301
- onMouseEnter: () => setActive(i),
3302
- onClick: () => toggle(o),
3303
- style: {
3304
- gap: "var(--s-8)",
3305
- padding: "4px 8px",
3306
- borderRadius: 7,
3307
- border: 0,
3308
- width: "100%",
3309
- textAlign: "left",
3310
- cursor: "pointer",
3311
- background: i === active ? "var(--surface-brand)" : "transparent"
3312
- },
3313
- children: [
3314
- /* @__PURE__ */ jsx45("i", { className: on ? "ph ph-check-square" : "ph ph-square", "aria-hidden": "true" }),
3315
- /* @__PURE__ */ jsx45("span", { className: "fd-body-sm", style: { flex: 1, color: "var(--text)" }, children: o.label })
3316
- ]
3317
- },
3318
- o.value
3319
- );
3320
- })
3422
+ className: "fd-row fd-gap-xs" + (liveMessage && !showError ? " fd-muted" : ""),
3423
+ role: liveRole,
3424
+ children: [
3425
+ showLoading ? /* @__PURE__ */ jsx45("span", { className: "fd-spinner", "aria-hidden": "true" }) : null,
3426
+ liveMessage ? /* @__PURE__ */ jsx45("span", { className: "fd-body-sm", children: liveMessage }) : null
3427
+ ]
3321
3428
  }
3322
3429
  ),
3323
- selected.length > 0 ? /* @__PURE__ */ jsxs39("span", { className: "fd-body-sm fd-muted", children: [
3324
- selected.length,
3325
- " selected"
3326
- ] }) : null
3430
+ showError ? /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: () => setRetryTick((n) => n + 1), children: "Retry" }) : null,
3431
+ hasListbox ? /* @__PURE__ */ jsx45("span", { id: listId, role: "listbox", "aria-label": field.label, "aria-multiselectable": "true", ref: listRef, className: "fd-pop-list", children: options.map((o, i) => {
3432
+ const on = isSelected(o);
3433
+ return /* @__PURE__ */ jsxs39(
3434
+ "div",
3435
+ {
3436
+ id: listId + "-opt-" + i,
3437
+ "data-i": i,
3438
+ role: "option",
3439
+ tabIndex: -1,
3440
+ "aria-selected": on,
3441
+ className: ["fd-opt", i === nav.active ? "is-active" : "", on ? "is-selected" : ""].filter(Boolean).join(" "),
3442
+ onMouseEnter: () => nav.setActive(i),
3443
+ onClick: () => toggle(o),
3444
+ children: [
3445
+ /* @__PURE__ */ jsx45("i", { className: "fd-filterbar-opt-check ph " + (on ? "ph-check-square is-checked" : "ph-square"), "aria-hidden": "true" }),
3446
+ /* @__PURE__ */ jsx45("span", { className: "fd-opt-label", children: o.label })
3447
+ ]
3448
+ },
3449
+ o.value
3450
+ );
3451
+ }) }) : null
3327
3452
  ] });
3453
+ const footer = mode2 === "paste" ? /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: doResolve, disabled: pasteLoading || !pasteText.trim(), children: pasteLoading ? "Resolving\u2026" : "Resolve" }) : void 0;
3454
+ return /* @__PURE__ */ jsx45(
3455
+ Popover,
3456
+ {
3457
+ open: true,
3458
+ anchorRef,
3459
+ onClose,
3460
+ placement: "bottom-start",
3461
+ offset: 6,
3462
+ width: PANEL_WIDTH.multiSelect,
3463
+ label: field.label,
3464
+ header,
3465
+ footer,
3466
+ children: body
3467
+ }
3468
+ );
3328
3469
  }
3329
3470
  function RangePanel({
3330
3471
  field,
3331
3472
  value,
3473
+ anchorRef,
3332
3474
  onChange,
3475
+ onRemove,
3333
3476
  onClose
3334
3477
  }) {
3335
- const min = value?.min;
3336
- const max = value?.max;
3337
- const rangeError = min != null && max != null && min > max ? "Must be \u2265 " + (field.minLabel || "min").toLowerCase() : void 0;
3338
- return /* @__PURE__ */ jsxs39("span", { className: "fd-stack", style: { gap: "var(--s-8)", width: 240 }, children: [
3339
- /* @__PURE__ */ jsx45(
3340
- NumberInput,
3341
- {
3342
- label: field.minLabel || "At least",
3343
- value: min ?? "",
3344
- min: field.min,
3345
- max: field.max,
3346
- step: field.step,
3347
- placeholder: field.minPlaceholder,
3348
- onChange: (e) => onChange({ min: e.target.value, max })
3349
- }
3350
- ),
3351
- /* @__PURE__ */ jsx45(
3352
- NumberInput,
3353
- {
3354
- label: field.maxLabel || "At most",
3355
- value: max ?? "",
3356
- min: field.min,
3357
- max: field.max,
3358
- step: field.step,
3359
- error: rangeError,
3360
- placeholder: field.maxPlaceholder,
3361
- onChange: (e) => onChange({ min, max: e.target.value })
3362
- }
3363
- ),
3364
- /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: onClose, children: "Done" })
3365
- ] });
3478
+ const [draftMin, setDraftMin] = React24.useState(value?.min);
3479
+ const [draftMax, setDraftMax] = React24.useState(value?.max);
3480
+ const invalid = draftMin != null && draftMax != null && draftMin > draftMax;
3481
+ const apply = (nextMin, nextMax) => {
3482
+ setDraftMin(nextMin);
3483
+ setDraftMax(nextMax);
3484
+ if (nextMin == null && nextMax == null) {
3485
+ onRemove();
3486
+ return;
3487
+ }
3488
+ if (nextMin != null && nextMax != null && nextMin > nextMax) return;
3489
+ onChange({ min: nextMin, max: nextMax });
3490
+ };
3491
+ return /* @__PURE__ */ jsx45(
3492
+ Popover,
3493
+ {
3494
+ open: true,
3495
+ anchorRef,
3496
+ onClose,
3497
+ placement: "bottom-start",
3498
+ offset: 6,
3499
+ width: PANEL_WIDTH.range,
3500
+ label: field.label,
3501
+ footer: /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: onClose, disabled: invalid, children: "Done" }),
3502
+ children: /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", children: [
3503
+ /* @__PURE__ */ jsx45(
3504
+ NumberInput,
3505
+ {
3506
+ label: field.minLabel || "At least",
3507
+ value: draftMin ?? "",
3508
+ min: field.min,
3509
+ max: field.max,
3510
+ step: field.step,
3511
+ placeholder: field.minPlaceholder,
3512
+ onChange: (e) => apply(e.target.value, draftMax)
3513
+ }
3514
+ ),
3515
+ /* @__PURE__ */ jsx45(
3516
+ NumberInput,
3517
+ {
3518
+ label: field.maxLabel || "At most",
3519
+ value: draftMax ?? "",
3520
+ min: field.min,
3521
+ max: field.max,
3522
+ step: field.step,
3523
+ error: invalid ? "Must be \u2265 " + draftMin : void 0,
3524
+ placeholder: field.maxPlaceholder,
3525
+ onChange: (e) => apply(draftMin, e.target.value)
3526
+ }
3527
+ )
3528
+ ] })
3529
+ }
3530
+ );
3366
3531
  }
3367
3532
  function DatePanel({
3368
3533
  field,
3369
3534
  value,
3535
+ anchorRef,
3370
3536
  onChange,
3537
+ onRemove,
3371
3538
  onClose
3372
3539
  }) {
3373
- const kind = value?.kind ?? "withinDays";
3374
- return /* @__PURE__ */ jsxs39("span", { className: "fd-stack", style: { gap: "var(--s-8)", width: 300 }, children: [
3375
- /* @__PURE__ */ jsx45(
3376
- SegmentedControl,
3377
- {
3378
- "aria-label": field.label + " \u2014 relative or between dates",
3379
- options: [
3380
- { value: "withinDays", label: "Within last N days" },
3381
- { value: "between", label: "Between dates" }
3382
- ],
3383
- value: kind,
3384
- onChange: (k) => {
3385
- if (k === "withinDays") {
3386
- onChange({ kind: "withinDays", days: value?.kind === "withinDays" ? value.days : field.defaultDays ?? 30 });
3387
- } else {
3388
- onChange({ kind: "between", from: value?.kind === "between" ? value.from : "", to: value?.kind === "between" ? value.to : "" });
3389
- }
3390
- }
3391
- }
3392
- ),
3393
- kind === "withinDays" ? /* @__PURE__ */ jsx45(
3394
- NumberInput,
3395
- {
3396
- label: "Days",
3397
- min: 1,
3398
- value: value?.kind === "withinDays" ? value.days : field.defaultDays ?? 30,
3399
- onChange: (e) => onChange({ kind: "withinDays", days: e.target.value })
3400
- }
3401
- ) : /* @__PURE__ */ jsx45(
3402
- DatePicker,
3403
- {
3404
- range: true,
3405
- label: "Date range",
3406
- value: value?.kind === "between" ? { start: value.from, end: value.to } : void 0,
3407
- onChange: (e) => onChange({ kind: "between", from: e.target.value.start || "", to: e.target.value.end || "" })
3540
+ const defaultDays = field.defaultDays ?? 30;
3541
+ const maxDays = field.maxDays ?? 3650;
3542
+ const [kind, setKind] = React24.useState(value?.kind ?? "withinDays");
3543
+ const [daysDraft, setDaysDraft] = React24.useState(
3544
+ value?.kind === "withinDays" ? value.days : defaultDays
3545
+ );
3546
+ const [between, setBetween] = React24.useState(
3547
+ value?.kind === "between" ? { from: value.from, to: value.to } : { from: "", to: "" }
3548
+ );
3549
+ const committedRef = React24.useRef(!!value);
3550
+ const applyWithinDays = (days) => {
3551
+ setDaysDraft(days);
3552
+ if (days != null) {
3553
+ committedRef.current = true;
3554
+ onChange({ kind: "withinDays", days });
3555
+ } else if (committedRef.current) {
3556
+ committedRef.current = false;
3557
+ onRemove();
3558
+ }
3559
+ };
3560
+ const applyBetween = (from, to) => {
3561
+ setBetween({ from, to });
3562
+ if (from && to) {
3563
+ committedRef.current = true;
3564
+ onChange({ kind: "between", from, to });
3565
+ } else if (committedRef.current) {
3566
+ committedRef.current = false;
3567
+ onRemove();
3568
+ }
3569
+ };
3570
+ const switchKind = (next) => {
3571
+ setKind(next);
3572
+ if (next === "between") {
3573
+ setBetween({ from: "", to: "" });
3574
+ if (committedRef.current) {
3575
+ committedRef.current = false;
3576
+ onRemove();
3408
3577
  }
3409
- ),
3410
- /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: onClose, children: "Done" })
3411
- ] });
3412
- }
3413
- function PasteListPanel({
3414
- field,
3415
- value,
3416
- onChange,
3417
- onClose
3418
- }) {
3419
- const operator = value?.operator ?? "in";
3420
- const selected = value?.values ?? [];
3421
- const [text, setText] = React23.useState("");
3422
- const [loading, setLoading] = React23.useState(false);
3423
- const [unmatched, setUnmatched] = React23.useState([]);
3424
- const [error, setError] = React23.useState(null);
3425
- const wrapRef = React23.useRef(null);
3426
- React23.useEffect(() => {
3427
- wrapRef.current?.querySelector("textarea")?.focus();
3428
- }, []);
3429
- const doResolve = () => {
3430
- const tokens = text.split(/[\n,]/).map((t) => t.trim()).filter(Boolean);
3431
- if (!tokens.length) return;
3432
- setLoading(true);
3433
- setError(null);
3434
- field.resolve(tokens).then((result) => {
3435
- setLoading(false);
3436
- setUnmatched(result.unmatched);
3437
- const merged = new Map(selected.map((s) => [s.value, s]));
3438
- for (const m of result.matched) merged.set(m.value, { value: m.value, label: m.label });
3439
- onChange({ operator, values: Array.from(merged.values()) });
3440
- setText("");
3441
- }).catch((err) => {
3442
- setLoading(false);
3443
- setError(err instanceof Error ? err.message : "Could not resolve the pasted list.");
3444
- });
3578
+ }
3445
3579
  };
3446
- return /* @__PURE__ */ jsxs39("span", { className: "fd-stack", style: { gap: "var(--s-8)", width: 320 }, children: [
3447
- /* @__PURE__ */ jsx45(OperatorToggle, { operator, onChange: (op) => onChange({ operator: op, values: selected }), label: field.label }),
3448
- /* @__PURE__ */ jsx45("span", { ref: wrapRef, children: /* @__PURE__ */ jsx45(
3449
- Textarea,
3450
- {
3451
- "aria-label": field.label + " \u2014 paste a list",
3452
- rows: 4,
3453
- placeholder: field.placeholder || "Paste names, one per line or comma-separated",
3454
- value: text,
3455
- disabled: loading,
3456
- onChange: (e) => setText(e.target.value),
3457
- onKeyDown: (e) => {
3458
- if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
3459
- e.preventDefault();
3460
- doResolve();
3580
+ const confirm = () => {
3581
+ if (kind === "withinDays" && daysDraft != null) {
3582
+ committedRef.current = true;
3583
+ onChange({ kind: "withinDays", days: daysDraft });
3584
+ }
3585
+ onClose();
3586
+ };
3587
+ return /* @__PURE__ */ jsx45(
3588
+ Popover,
3589
+ {
3590
+ open: true,
3591
+ anchorRef,
3592
+ onClose,
3593
+ placement: "bottom-start",
3594
+ offset: 6,
3595
+ width: PANEL_WIDTH.date,
3596
+ label: field.label,
3597
+ footer: /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: confirm, children: "Done" }),
3598
+ children: /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", children: [
3599
+ /* @__PURE__ */ jsx45(
3600
+ SegmentedControl,
3601
+ {
3602
+ "aria-label": field.label + " \u2014 relative or between dates",
3603
+ options: [
3604
+ { value: "withinDays", label: "Within last N days" },
3605
+ { value: "between", label: "Between dates" }
3606
+ ],
3607
+ value: kind,
3608
+ onChange: (k) => switchKind(k)
3461
3609
  }
3462
- }
3463
- }
3464
- ) }),
3465
- /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: doResolve, disabled: loading || !text.trim(), children: loading ? "Resolving\u2026" : "Resolve" }),
3466
- error ? /* @__PURE__ */ jsx45(Banner, { tone: "danger", title: "Couldn't resolve the list", detail: error }) : null,
3467
- unmatched.length > 0 ? /* @__PURE__ */ jsx45(Banner, { tone: "warn", title: unmatched.length + " didn't match", detail: unmatched.join(", ") }) : null,
3468
- selected.length > 0 ? /* @__PURE__ */ jsxs39("span", { className: "fd-stack", style: { gap: "var(--s-4)" }, children: [
3469
- /* @__PURE__ */ jsxs39("span", { className: "fd-body-sm fd-muted", children: [
3470
- selected.length,
3471
- " matched"
3472
- ] }),
3473
- /* @__PURE__ */ jsx45("span", { className: "fd-row", style: { flexWrap: "wrap", gap: "var(--s-4)" }, children: selected.map((v) => /* @__PURE__ */ jsx45(Tag, { onRemove: () => onChange({ operator, values: selected.filter((s) => s.value !== v.value) }), children: v.label }, v.value)) })
3474
- ] }) : null,
3475
- /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: onClose, children: "Done" })
3476
- ] });
3610
+ ),
3611
+ kind === "withinDays" ? /* @__PURE__ */ jsx45(NumberInput, { label: "Days", min: 1, max: maxDays, step: 1, value: daysDraft ?? "", onChange: (e) => applyWithinDays(e.target.value) }) : /* @__PURE__ */ jsx45(
3612
+ DatePicker,
3613
+ {
3614
+ range: true,
3615
+ label: "Date range",
3616
+ value: { start: between.from, end: between.to },
3617
+ onChange: (e) => applyBetween(e.target.value.start || "", e.target.value.end || "")
3618
+ }
3619
+ )
3620
+ ] })
3621
+ }
3622
+ );
3477
3623
  }
3478
3624
  function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align = "left", className = "", ...rest }) {
3479
- const [panel, setPanel] = React23.useState(null);
3480
- const zIndex = useOverlayLayer(!!panel);
3481
- const rootRef = React23.useRef(null);
3482
- const textInputRef = React23.useRef(null);
3483
- const optionsRef = React23.useRef(null);
3625
+ const [panel, setPanel] = React24.useState(null);
3626
+ const editAnchorRef = React24.useRef(null);
3627
+ const addAnchorRef = React24.useRef(null);
3628
+ const textInputRef = React24.useRef(null);
3629
+ const optionsRef = React24.useRef(null);
3484
3630
  const editingField = panel && panel.kind === "edit" ? fields.find((f) => f.key === panel.key) : void 0;
3485
- const dismissible = !!panel && (panel.kind === "add" || panel.kind === "edit" && editingField?.type !== "text");
3486
- React23.useEffect(() => {
3487
- if (!dismissible) return;
3488
- const away = (e) => {
3489
- const target = e.target;
3490
- if (target.closest && target.closest(".fd-pop")) return;
3491
- if (rootRef.current && !rootRef.current.contains(e.target)) setPanel(null);
3492
- };
3493
- const esc = (e) => {
3494
- if (e.key === "Escape") setPanel(null);
3495
- };
3496
- document.addEventListener("pointerdown", away);
3497
- document.addEventListener("keydown", esc);
3498
- return () => {
3499
- document.removeEventListener("pointerdown", away);
3500
- document.removeEventListener("keydown", esc);
3501
- };
3502
- }, [dismissible]);
3503
- React23.useEffect(() => {
3631
+ const startEditing = (key) => setPanel({ kind: "edit", key });
3632
+ const closeEditing = () => setPanel(null);
3633
+ React24.useEffect(() => {
3504
3634
  if (!panel || panel.kind !== "edit") return;
3505
3635
  if (editingField?.type === "text") {
3506
3636
  textInputRef.current?.focus();
@@ -3510,72 +3640,77 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
3510
3640
  }
3511
3641
  }, [panel]);
3512
3642
  const addableFields = fields.filter((f) => !isFieldSet(f, filters[f.key]));
3513
- const startEditing = (key) => setPanel({ kind: "edit", key });
3514
- return /* @__PURE__ */ jsxs39("span", { className, style: { position: "relative", display: "inline-flex", flexWrap: "wrap", alignItems: "center", gap: 8 }, ref: rootRef, ...rest, children: [
3643
+ const addMenuItems = [
3644
+ { kind: "section", label: "Add filter" },
3645
+ ...addableFields.map((f) => ({ id: f.key, label: f.label, onSelect: () => startEditing(f.key) }))
3646
+ ];
3647
+ return /* @__PURE__ */ jsxs39("span", { className: ["fd-filterbar", className].filter(Boolean).join(" "), ...rest, children: [
3515
3648
  fields.map((field) => {
3516
3649
  const value = filters[field.key];
3517
3650
  const editing = panel?.kind === "edit" && panel.key === field.key;
3518
3651
  if (editing && field.type === "text") {
3519
- return /* @__PURE__ */ jsxs39(
3520
- "span",
3521
- {
3522
- className: "fd-tag",
3523
- style: { background: "var(--surface-brand)", borderColor: "var(--border-brand)", cursor: "text" },
3524
- children: [
3525
- /* @__PURE__ */ jsx45(
3526
- "input",
3527
- {
3528
- ref: textInputRef,
3529
- value: value ?? "",
3530
- placeholder: field.placeholder || field.label,
3531
- "aria-label": field.label,
3532
- onChange: (e) => onFilterChange(field.key, e.target.value),
3533
- onKeyDown: (e) => {
3534
- if (e.key === "Enter") setPanel(null);
3535
- else if (e.key === "Escape") {
3536
- e.stopPropagation();
3537
- setPanel(null);
3538
- }
3539
- },
3540
- onBlur: () => setPanel(null),
3541
- style: { all: "unset", minWidth: 90, color: "var(--text-on-surface-brand)" }
3542
- }
3543
- ),
3544
- isSet(value) ? /* @__PURE__ */ jsx45(
3545
- "span",
3546
- {
3547
- className: "fd-tag-remove",
3548
- role: "button",
3549
- "aria-label": "Remove " + field.label,
3550
- onClick: (e) => {
3551
- e.stopPropagation();
3552
- onFilterRemove(field.key);
3553
- setPanel(null);
3554
- },
3555
- children: /* @__PURE__ */ jsx45("i", { className: "ph ph-x", "aria-hidden": "true" })
3652
+ return /* @__PURE__ */ jsxs39("span", { className: ["fd-tag", "fd-filterbar-text"].join(" "), children: [
3653
+ /* @__PURE__ */ jsx45(
3654
+ "input",
3655
+ {
3656
+ ref: textInputRef,
3657
+ className: "fd-filterbar-text-input",
3658
+ value: value ?? "",
3659
+ placeholder: field.placeholder || field.label,
3660
+ "aria-label": field.label,
3661
+ onChange: (e) => onFilterChange(field.key, e.target.value),
3662
+ onKeyDown: (e) => {
3663
+ if (e.key === "Enter") setPanel(null);
3664
+ else if (e.key === "Escape") {
3665
+ e.stopPropagation();
3666
+ setPanel(null);
3556
3667
  }
3557
- ) : null
3558
- ]
3559
- },
3560
- field.key
3561
- );
3562
- }
3563
- if (editing && field.type === "select") {
3564
- const chosen = field.options.find((o) => o.value === value);
3565
- return /* @__PURE__ */ jsxs39("span", { style: { position: "relative", display: "inline-flex" }, children: [
3566
- /* @__PURE__ */ jsxs39(Tag, { selected: true, onClick: () => setPanel(null), onRemove: () => {
3567
- if (isSet(value)) onFilterRemove(field.key);
3568
- setPanel(null);
3569
- }, children: [
3570
- field.label,
3571
- chosen ? ": " + chosen.label : ""
3572
- ] }),
3573
- /* @__PURE__ */ jsx45(LayerProvider, { zIndex, children: /* @__PURE__ */ jsx45(
3668
+ },
3669
+ onBlur: () => setPanel(null)
3670
+ }
3671
+ ),
3672
+ isSet(value) ? /* @__PURE__ */ jsx45(
3574
3673
  "span",
3575
3674
  {
3576
- className: "fd-view-enter",
3577
- style: { position: "absolute", top: "calc(100% + 6px)", left: 0, zIndex, minWidth: 200, maxWidth: "calc(100vw - 32px)", display: "block", background: "var(--surface)", border: "1px solid var(--border)", borderRadius: 10, boxShadow: "0 16px 44px rgba(11,13,17,.18)", padding: 8 },
3578
- children: /* @__PURE__ */ jsx45("span", { className: "fd-stack", role: "listbox", "aria-label": field.label, ref: optionsRef, style: { gap: 2 }, children: field.options.map((o) => {
3675
+ className: "fd-tag-remove",
3676
+ role: "button",
3677
+ "aria-label": "Remove " + field.label,
3678
+ onClick: (e) => {
3679
+ e.stopPropagation();
3680
+ onFilterRemove(field.key);
3681
+ setPanel(null);
3682
+ },
3683
+ children: /* @__PURE__ */ jsx45("i", { className: "ph ph-x", "aria-hidden": "true" })
3684
+ }
3685
+ ) : null
3686
+ ] }, field.key);
3687
+ }
3688
+ const set = isFieldSet(field, value);
3689
+ if (!editing && !set) return null;
3690
+ const chipRef = editing ? editAnchorRef : void 0;
3691
+ const toggleOrClose = () => editing ? closeEditing() : startEditing(field.key);
3692
+ const remove = () => {
3693
+ if (set) onFilterRemove(field.key);
3694
+ closeEditing();
3695
+ };
3696
+ const chipText = field.label + (set ? ": " + chipLabel(field, value) : "");
3697
+ if (field.type === "text") {
3698
+ return /* @__PURE__ */ jsx45(Tag, { selected: true, onClick: toggleOrClose, onRemove: remove, children: chipText }, field.key);
3699
+ }
3700
+ if (field.type === "select") {
3701
+ return /* @__PURE__ */ jsxs39("span", { children: [
3702
+ /* @__PURE__ */ jsx45("span", { ref: chipRef, children: /* @__PURE__ */ jsx45(Tag, { selected: true, onClick: toggleOrClose, onRemove: remove, children: chipText }) }),
3703
+ editing ? /* @__PURE__ */ jsx45(
3704
+ Popover,
3705
+ {
3706
+ open: true,
3707
+ anchorRef: editAnchorRef,
3708
+ onClose: closeEditing,
3709
+ placement: align === "right" ? "bottom-end" : "bottom-start",
3710
+ offset: 6,
3711
+ minWidth: 200,
3712
+ label: field.label,
3713
+ children: /* @__PURE__ */ jsx45("span", { role: "listbox", "aria-label": field.label, ref: optionsRef, className: "fd-pop-list", children: field.options.map((o) => {
3579
3714
  const on = o.value === value;
3580
3715
  return /* @__PURE__ */ jsx45(
3581
3716
  "button",
@@ -3583,84 +3718,72 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
3583
3718
  type: "button",
3584
3719
  role: "option",
3585
3720
  "aria-selected": on,
3586
- className: "fd-row",
3721
+ className: ["fd-opt", on ? "is-selected" : ""].filter(Boolean).join(" "),
3587
3722
  onClick: () => {
3588
3723
  onFilterChange(field.key, o.value);
3589
3724
  setPanel(null);
3590
3725
  },
3591
- style: { gap: 8, padding: "4px 8px", borderRadius: 7, border: 0, width: "100%", textAlign: "left", cursor: "pointer", background: on ? "var(--surface-brand)" : "transparent" },
3592
- children: /* @__PURE__ */ jsx45("span", { className: "fd-body-sm", style: { flex: 1, fontWeight: on ? 700 : 500, color: on ? "var(--text-on-surface-brand)" : "var(--text)" }, children: o.label })
3726
+ children: /* @__PURE__ */ jsx45("span", { className: "fd-opt-label", children: o.label })
3593
3727
  },
3594
3728
  o.value
3595
3729
  );
3596
3730
  }) })
3597
3731
  }
3598
- ) })
3599
- ] }, field.key);
3600
- }
3601
- if (editing && field.type === "multiSelect") {
3602
- const mv = value;
3603
- return /* @__PURE__ */ jsxs39("span", { style: { position: "relative", display: "inline-flex" }, children: [
3604
- /* @__PURE__ */ jsxs39(Tag, { selected: true, onClick: () => setPanel(null), onRemove: () => {
3605
- if (isFieldSet(field, value)) onFilterRemove(field.key);
3606
- setPanel(null);
3607
- }, children: [
3608
- field.label,
3609
- isFieldSet(field, value) ? ": " + chipLabel(field, value) : ""
3610
- ] }),
3611
- /* @__PURE__ */ jsx45(EditPopover, { zIndex, width: 260, children: /* @__PURE__ */ jsx45(MultiSelectPanel, { field, value: mv, onChange: (v) => onFilterChange(field.key, v), onClose: () => setPanel(null) }) })
3612
- ] }, field.key);
3613
- }
3614
- if (editing && field.type === "range") {
3615
- const rv = value;
3616
- return /* @__PURE__ */ jsxs39("span", { style: { position: "relative", display: "inline-flex" }, children: [
3617
- /* @__PURE__ */ jsxs39(Tag, { selected: true, onClick: () => setPanel(null), onRemove: () => {
3618
- if (isFieldSet(field, value)) onFilterRemove(field.key);
3619
- setPanel(null);
3620
- }, children: [
3621
- field.label,
3622
- isFieldSet(field, value) ? ": " + chipLabel(field, value) : ""
3623
- ] }),
3624
- /* @__PURE__ */ jsx45(EditPopover, { zIndex, width: 240, children: /* @__PURE__ */ jsx45(RangePanel, { field, value: rv, onChange: (v) => onFilterChange(field.key, v), onClose: () => setPanel(null) }) })
3732
+ ) : null
3625
3733
  ] }, field.key);
3626
3734
  }
3627
- if (editing && field.type === "date") {
3628
- const dv = value;
3629
- return /* @__PURE__ */ jsxs39("span", { style: { position: "relative", display: "inline-flex" }, children: [
3630
- /* @__PURE__ */ jsxs39(Tag, { selected: true, onClick: () => setPanel(null), onRemove: () => {
3631
- if (isFieldSet(field, value)) onFilterRemove(field.key);
3632
- setPanel(null);
3633
- }, children: [
3634
- field.label,
3635
- isFieldSet(field, value) ? ": " + chipLabel(field, value) : ""
3636
- ] }),
3637
- /* @__PURE__ */ jsx45(EditPopover, { zIndex, width: 300, children: /* @__PURE__ */ jsx45(DatePanel, { field, value: dv, onChange: (v) => onFilterChange(field.key, v), onClose: () => setPanel(null) }) })
3735
+ if (field.type === "multiSelect") {
3736
+ return /* @__PURE__ */ jsxs39("span", { children: [
3737
+ /* @__PURE__ */ jsx45("span", { ref: chipRef, children: /* @__PURE__ */ jsx45(Tag, { selected: true, onClick: toggleOrClose, onRemove: remove, children: chipText }) }),
3738
+ editing ? /* @__PURE__ */ jsx45(
3739
+ MultiSelectPanel,
3740
+ {
3741
+ field,
3742
+ value,
3743
+ anchorRef: editAnchorRef,
3744
+ onChange: (v) => onFilterChange(field.key, v),
3745
+ onRemove: () => onFilterRemove(field.key),
3746
+ onClose: closeEditing
3747
+ }
3748
+ ) : null
3638
3749
  ] }, field.key);
3639
3750
  }
3640
- if (editing && field.type === "pasteList") {
3641
- const pv = value;
3642
- return /* @__PURE__ */ jsxs39("span", { style: { position: "relative", display: "inline-flex" }, children: [
3643
- /* @__PURE__ */ jsxs39(Tag, { selected: true, onClick: () => setPanel(null), onRemove: () => {
3644
- if (isFieldSet(field, value)) onFilterRemove(field.key);
3645
- setPanel(null);
3646
- }, children: [
3647
- field.label,
3648
- isFieldSet(field, value) ? ": " + chipLabel(field, value) : ""
3649
- ] }),
3650
- /* @__PURE__ */ jsx45(EditPopover, { zIndex, width: 320, children: /* @__PURE__ */ jsx45(PasteListPanel, { field, value: pv, onChange: (v) => onFilterChange(field.key, v), onClose: () => setPanel(null) }) })
3751
+ if (field.type === "range") {
3752
+ return /* @__PURE__ */ jsxs39("span", { children: [
3753
+ /* @__PURE__ */ jsx45("span", { ref: chipRef, children: /* @__PURE__ */ jsx45(Tag, { selected: true, onClick: toggleOrClose, onRemove: remove, children: chipText }) }),
3754
+ editing ? /* @__PURE__ */ jsx45(
3755
+ RangePanel,
3756
+ {
3757
+ field,
3758
+ value,
3759
+ anchorRef: editAnchorRef,
3760
+ onChange: (v) => onFilterChange(field.key, v),
3761
+ onRemove: () => onFilterRemove(field.key),
3762
+ onClose: closeEditing
3763
+ }
3764
+ ) : null
3651
3765
  ] }, field.key);
3652
3766
  }
3653
- if (isFieldSet(field, value)) {
3654
- return /* @__PURE__ */ jsxs39(Tag, { selected: true, onClick: () => startEditing(field.key), onRemove: () => onFilterRemove(field.key), children: [
3655
- field.label,
3656
- ": ",
3657
- chipLabel(field, value)
3767
+ if (field.type === "date") {
3768
+ return /* @__PURE__ */ jsxs39("span", { children: [
3769
+ /* @__PURE__ */ jsx45("span", { ref: chipRef, children: /* @__PURE__ */ jsx45(Tag, { selected: true, onClick: toggleOrClose, onRemove: remove, children: chipText }) }),
3770
+ editing ? /* @__PURE__ */ jsx45(
3771
+ DatePanel,
3772
+ {
3773
+ field,
3774
+ value,
3775
+ anchorRef: editAnchorRef,
3776
+ onChange: (v) => onFilterChange(field.key, v),
3777
+ onRemove: () => onFilterRemove(field.key),
3778
+ onClose: closeEditing
3779
+ }
3780
+ ) : null
3658
3781
  ] }, field.key);
3659
3782
  }
3660
3783
  return null;
3661
3784
  }),
3662
- addableFields.length > 0 ? /* @__PURE__ */ jsxs39("span", { style: { position: "relative", display: "inline-flex" }, children: [
3663
- /* @__PURE__ */ jsx45(
3785
+ addableFields.length > 0 ? /* @__PURE__ */ jsxs39("span", { children: [
3786
+ /* @__PURE__ */ jsx45("span", { ref: addAnchorRef, children: /* @__PURE__ */ jsx45(
3664
3787
  Button,
3665
3788
  {
3666
3789
  size: "sm",
@@ -3671,29 +3794,20 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
3671
3794
  onClick: () => setPanel(panel?.kind === "add" ? null : { kind: "add" }),
3672
3795
  children: "Filter"
3673
3796
  }
3674
- ),
3675
- panel?.kind === "add" ? /* @__PURE__ */ jsx45(LayerProvider, { zIndex, children: /* @__PURE__ */ jsx45(
3676
- "span",
3797
+ ) }),
3798
+ /* @__PURE__ */ jsx45(
3799
+ Popover,
3677
3800
  {
3678
- className: "fd-view-enter",
3679
- style: { position: "absolute", top: "calc(100% + 6px)", [align]: 0, zIndex, width: 220, maxWidth: "calc(100vw - 32px)", display: "block", background: "var(--surface)", border: "1px solid var(--border)", borderRadius: 10, boxShadow: "0 16px 44px rgba(11,13,17,.18)", padding: 8 },
3680
- children: /* @__PURE__ */ jsxs39("span", { className: "fd-stack", role: "menu", "aria-label": "Add filter", style: { gap: 2 }, children: [
3681
- /* @__PURE__ */ jsx45("span", { className: "fd-overline fd-muted", style: { padding: "4px 8px 6px" }, children: "Add filter" }),
3682
- addableFields.map((f) => /* @__PURE__ */ jsx45(
3683
- "button",
3684
- {
3685
- type: "button",
3686
- role: "menuitem",
3687
- className: "fd-row",
3688
- onClick: () => startEditing(f.key),
3689
- style: { gap: 8, padding: "4px 8px", borderRadius: 7, border: 0, width: "100%", textAlign: "left", cursor: "pointer", background: "transparent" },
3690
- children: /* @__PURE__ */ jsx45("span", { className: "fd-body-sm", style: { flex: 1, color: "var(--text)" }, children: f.label })
3691
- },
3692
- f.key
3693
- ))
3694
- ] })
3801
+ open: panel?.kind === "add",
3802
+ anchorRef: addAnchorRef,
3803
+ onClose: closeEditing,
3804
+ placement: align === "right" ? "bottom-end" : "bottom-start",
3805
+ minWidth: 200,
3806
+ role: "menu",
3807
+ label: "Add filter",
3808
+ children: /* @__PURE__ */ jsx45(Menu, { items: addMenuItems })
3695
3809
  }
3696
- ) }) : null
3810
+ )
3697
3811
  ] }) : null
3698
3812
  ] });
3699
3813
  }
@@ -3725,7 +3839,7 @@ function StatTile({ label, value, sub, delta, invert = false, loading = false, c
3725
3839
  }
3726
3840
 
3727
3841
  // src/components/data/CodeBlock.tsx
3728
- import * as React24 from "react";
3842
+ import * as React25 from "react";
3729
3843
  import { Fragment as Fragment6, jsx as jsx47, jsxs as jsxs41 } from "react/jsx-runtime";
3730
3844
  var JS_KW = /^(?:const|let|var|function|return|if|else|for|while|do|new|class|extends|import|from|export|default|await|async|try|catch|finally|throw|typeof|instanceof|of|in|switch|case|break|continue|delete|void|yield|static|get|set|this|super|interface|type|enum|implements|public|private|protected|readonly|abstract|as|satisfies|declare|namespace)$/;
3731
3845
  var RULES = {
@@ -3881,13 +3995,13 @@ function tokenize(src, lang) {
3881
3995
  return out;
3882
3996
  }
3883
3997
  function Highlighted({ code, lang }) {
3884
- const toks = React24.useMemo(() => tokenize(code, lang), [code, lang]);
3998
+ const toks = React25.useMemo(() => tokenize(code, lang), [code, lang]);
3885
3999
  return /* @__PURE__ */ jsx47(Fragment6, { children: toks.map((tk, i) => tk.c ? /* @__PURE__ */ jsx47("span", { className: "tok-" + tk.c, children: tk.t }, i) : tk.t) });
3886
4000
  }
3887
4001
  function useCopy() {
3888
- const [done, setDone] = React24.useState(false);
3889
- const timer = React24.useRef(null);
3890
- React24.useEffect(() => () => {
4002
+ const [done, setDone] = React25.useState(false);
4003
+ const timer = React25.useRef(null);
4004
+ React25.useEffect(() => () => {
3891
4005
  if (timer.current) clearTimeout(timer.current);
3892
4006
  }, []);
3893
4007
  const copy = (text) => {
@@ -3923,8 +4037,8 @@ function CodeBlock({
3923
4037
  actions,
3924
4038
  className = ""
3925
4039
  }) {
3926
- const [wrap, setWrap] = React24.useState(!!wrapDefault);
3927
- const [open, setOpen] = React24.useState(false);
4040
+ const [wrap, setWrap] = React25.useState(!!wrapDefault);
4041
+ const [open, setOpen] = React25.useState(false);
3928
4042
  const [copied, copy] = useCopy();
3929
4043
  const body = String(code).replace(/\n$/, "");
3930
4044
  const lines = body.split("\n");
@@ -3953,7 +4067,7 @@ function CodeBlock({
3953
4067
  ] });
3954
4068
  }
3955
4069
  function DiffBlock({ diff = "", filename, collapseAfter = 34, className = "" }) {
3956
- const [open, setOpen] = React24.useState(false);
4070
+ const [open, setOpen] = React25.useState(false);
3957
4071
  const [copied, copy] = useCopy();
3958
4072
  const raw = String(diff).replace(/\n$/, "");
3959
4073
  const all = raw.split("\n").map((line) => {
@@ -3999,7 +4113,7 @@ function DiffBlock({ diff = "", filename, collapseAfter = 34, className = "" })
3999
4113
  }
4000
4114
 
4001
4115
  // src/components/data/Markdown.tsx
4002
- import * as React25 from "react";
4116
+ import * as React26 from "react";
4003
4117
  import { jsx as jsx48, jsxs as jsxs42 } from "react/jsx-runtime";
4004
4118
  var SAFE = /^(?:https?:|mailto:|tel:|#|\/|\.{1,2}\/)/i;
4005
4119
  var safeHref = (h) => SAFE.test(String(h || "").trim()) ? String(h).trim() : null;
@@ -4038,7 +4152,7 @@ function inlineNodes(src, ctx, keyBase) {
4038
4152
  const href = safeHref(g(2));
4039
4153
  out.push(href ? /* @__PURE__ */ jsx48("a", { href, title: g(3) || void 0, target: /^https?:/i.test(href) ? "_blank" : void 0, rel: "noopener noreferrer", onClick: ctx.onLinkClick, children: inlineNodes(g(1), ctx, key()) }, key()) : /* @__PURE__ */ jsx48("span", { children: g(1) }, key()));
4040
4154
  } else if (kind === "cite") {
4041
- out.push(ctx.renderCitation ? /* @__PURE__ */ jsx48(React25.Fragment, { children: ctx.renderCitation(g(1)) }, key()) : /* @__PURE__ */ jsx48("sup", { className: "fd-md-cite", children: g(1) }, key()));
4155
+ out.push(ctx.renderCitation ? /* @__PURE__ */ jsx48(React26.Fragment, { children: ctx.renderCitation(g(1)) }, key()) : /* @__PURE__ */ jsx48("sup", { className: "fd-md-cite", children: g(1) }, key()));
4042
4156
  } else if (kind === "strongem") out.push(/* @__PURE__ */ jsx48("strong", { children: /* @__PURE__ */ jsx48("em", { children: inlineNodes(g(1), ctx, key()) }) }, key()));
4043
4157
  else if (kind === "strong") out.push(/* @__PURE__ */ jsx48("strong", { children: inlineNodes(g(1) || g(2), ctx, key()) }, key()));
4044
4158
  else if (kind === "em") out.push(/* @__PURE__ */ jsx48("em", { children: inlineNodes(g(1) || g(2), ctx, key()) }, key()));
@@ -4203,7 +4317,7 @@ function Markdown({
4203
4317
  onLinkClick
4204
4318
  }) {
4205
4319
  const ctx = { allowImages, headingOffset, renderCitation, onLinkClick, codeProps };
4206
- const blocks = React25.useMemo(() => parseBlocks(source), [source]);
4320
+ const blocks = React26.useMemo(() => parseBlocks(source), [source]);
4207
4321
  return /* @__PURE__ */ jsx48("div", { className: ["fd-md", className].filter(Boolean).join(" "), children: renderBlocks(blocks, ctx, "b") });
4208
4322
  }
4209
4323
  function MarkdownInline({ source = "", className = "", onLinkClick }) {
@@ -4317,7 +4431,7 @@ function Pagination({
4317
4431
  }
4318
4432
 
4319
4433
  // src/components/data/RelativeTime.tsx
4320
- import * as React26 from "react";
4434
+ import * as React27 from "react";
4321
4435
  import { jsxs as jsxs45 } from "react/jsx-runtime";
4322
4436
  var MIN = 6e4;
4323
4437
  var HOUR = 36e5;
@@ -4369,9 +4483,9 @@ function formatDuration(ms) {
4369
4483
  return h + "h" + (m % 60 ? " " + m % 60 + "m" : "");
4370
4484
  }
4371
4485
  function RelativeTime({ value, prefix, className = "", refresh: refresh2 = true, ...rest }) {
4372
- const [, force] = React26.useState(0);
4486
+ const [, force] = React27.useState(0);
4373
4487
  const t = value instanceof Date ? value.getTime() : Date.parse(value);
4374
- React26.useEffect(() => {
4488
+ React27.useEffect(() => {
4375
4489
  if (!refresh2 || !t || isNaN(t)) return;
4376
4490
  const abs = Math.abs(t - Date.now());
4377
4491
  if (abs > DAY) return;
@@ -4387,7 +4501,7 @@ function RelativeTime({ value, prefix, className = "", refresh: refresh2 = true,
4387
4501
  }
4388
4502
 
4389
4503
  // src/components/data/ScoreMeter.tsx
4390
- import * as React27 from "react";
4504
+ import * as React28 from "react";
4391
4505
  import { jsx as jsx51, jsxs as jsxs46 } from "react/jsx-runtime";
4392
4506
  function ScoreMeter({
4393
4507
  score,
@@ -4402,10 +4516,10 @@ function ScoreMeter({
4402
4516
  breakdownWidth = 280,
4403
4517
  className = ""
4404
4518
  }) {
4405
- const [open, setOpen] = React27.useState(false);
4406
- const ref = React27.useRef(null);
4519
+ const [open, setOpen] = React28.useState(false);
4520
+ const ref = React28.useRef(null);
4407
4521
  const value = Math.max(0, Math.min(100, Math.round(Number(score) || 0)));
4408
- const body = /* @__PURE__ */ jsxs46(React27.Fragment, { children: [
4522
+ const body = /* @__PURE__ */ jsxs46(React28.Fragment, { children: [
4409
4523
  /* @__PURE__ */ jsx51("span", { className: "fd-scoremeter-rail", style: { width }, "aria-hidden": "true", children: /* @__PURE__ */ jsx51(SegmentedMeter, { total: 100, segments, height, showTotal: false, showPercent: false, legend: false }) }),
4410
4524
  showScore ? /* @__PURE__ */ jsx51("span", { className: "fd-scoremeter-value fd-tabular", "aria-hidden": "true", children: value }) : null
4411
4525
  ] });
@@ -4424,7 +4538,7 @@ function ScoreMeter({
4424
4538
  }
4425
4539
  );
4426
4540
  }
4427
- return /* @__PURE__ */ jsxs46(React27.Fragment, { children: [
4541
+ return /* @__PURE__ */ jsxs46(React28.Fragment, { children: [
4428
4542
  /* @__PURE__ */ jsx51(
4429
4543
  "button",
4430
4544
  {
@@ -4455,13 +4569,13 @@ function ScoreMeter({
4455
4569
  }
4456
4570
 
4457
4571
  // src/components/data/SortMenu.tsx
4458
- import * as React28 from "react";
4572
+ import * as React29 from "react";
4459
4573
  import { jsx as jsx52, jsxs as jsxs47 } from "react/jsx-runtime";
4460
4574
  function SortMenu({ fields = [], sort, onSort, align = "right", size = "md", className = "", ...rest }) {
4461
- const [open, setOpen] = React28.useState(false);
4575
+ const [open, setOpen] = React29.useState(false);
4462
4576
  const zIndex = useOverlayLayer(open);
4463
- const ref = React28.useRef(null);
4464
- React28.useEffect(() => {
4577
+ const ref = React29.useRef(null);
4578
+ React29.useEffect(() => {
4465
4579
  if (!open) return;
4466
4580
  const away = (e) => {
4467
4581
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -4523,7 +4637,7 @@ function SortMenu({ fields = [], sort, onSort, align = "right", size = "md", cla
4523
4637
  }
4524
4638
 
4525
4639
  // src/components/data/StepList.tsx
4526
- import * as React29 from "react";
4640
+ import * as React30 from "react";
4527
4641
  import { jsx as jsx53, jsxs as jsxs48 } from "react/jsx-runtime";
4528
4642
  var KIND_ICON = { tool: "wrench", search: "magnifying-glass", read: "file-text", write: "pencil-simple", reasoning: "brain", run: "terminal", fetch: "cloud-arrow-down" };
4529
4643
  function StepList({
@@ -4536,9 +4650,9 @@ function StepList({
4536
4650
  dense = false,
4537
4651
  className = ""
4538
4652
  }) {
4539
- const [openState, setOpenState] = React29.useState(defaultOpen);
4653
+ const [openState, setOpenState] = React30.useState(defaultOpen);
4540
4654
  const open = openProp == null ? openState : openProp;
4541
- const [expanded, setExpanded] = React29.useState({});
4655
+ const [expanded, setExpanded] = React30.useState({});
4542
4656
  if (!steps.length) return null;
4543
4657
  const running = steps.some((s) => s.status === "running");
4544
4658
  const failed = steps.filter((s) => s.status === "error").length;
@@ -4596,7 +4710,7 @@ function StepList({
4596
4710
  }
4597
4711
 
4598
4712
  // src/components/data/VirtualList.tsx
4599
- import * as React30 from "react";
4713
+ import * as React31 from "react";
4600
4714
  import { jsx as jsx54, jsxs as jsxs49 } from "react/jsx-runtime";
4601
4715
  var VIRTUAL_LIST_BUFFER_ROWS = 20;
4602
4716
  var VIRTUAL_LIST_ROW_GAP = 6;
@@ -4648,35 +4762,35 @@ function VirtualList({
4648
4762
  className = "",
4649
4763
  style
4650
4764
  }) {
4651
- const [scrollTop, setScrollTop] = React30.useState(0);
4652
- const loaded = React30.useMemo(
4765
+ const [scrollTop, setScrollTop] = React31.useState(0);
4766
+ const loaded = React31.useMemo(
4653
4767
  () => ({ firstItemIndex, count: items.length }),
4654
4768
  [firstItemIndex, items.length]
4655
4769
  );
4656
4770
  const total = Math.max(totalCount == null ? 0 : totalCount, firstItemIndex + items.length);
4657
4771
  const bandKey = firstItemIndex + ":" + items.length;
4658
- const win = React30.useMemo(
4772
+ const win = React31.useMemo(
4659
4773
  () => computeVirtualWindow({ scrollTop, viewportHeight: height, itemHeight, itemCount: total, loaded }),
4660
4774
  [scrollTop, height, itemHeight, total, loaded]
4661
4775
  );
4662
- const need = React30.useMemo(() => computeNeedMore(win, loaded), [win, loaded]);
4776
+ const need = React31.useMemo(() => computeNeedMore(win, loaded), [win, loaded]);
4663
4777
  const wantStart = need.start && hasMore?.start !== false;
4664
4778
  const wantEnd = need.end && hasMore?.end !== false;
4665
4779
  const bucket2 = Math.floor(win.visibleStart / VIRTUAL_LIST_BUFFER_ROWS);
4666
4780
  const askKey = bandKey + "@" + bucket2;
4667
- const asked = React30.useRef({ start: null, end: null });
4668
- React30.useEffect(() => {
4781
+ const asked = React31.useRef({ start: null, end: null });
4782
+ React31.useEffect(() => {
4669
4783
  if (loading || !wantStart || asked.current.start === askKey) return;
4670
4784
  asked.current.start = askKey;
4671
4785
  onNeedMore("start", win);
4672
4786
  }, [wantStart, loading, askKey, onNeedMore, win]);
4673
- React30.useEffect(() => {
4787
+ React31.useEffect(() => {
4674
4788
  if (loading || !wantEnd || asked.current.end === askKey) return;
4675
4789
  asked.current.end = askKey;
4676
4790
  onNeedMore("end", win);
4677
4791
  }, [wantEnd, loading, askKey, onNeedMore, win]);
4678
- const lastKeysRef = React30.useRef(null);
4679
- const newKeys = React30.useMemo(() => {
4792
+ const lastKeysRef = React31.useRef(null);
4793
+ const newKeys = React31.useMemo(() => {
4680
4794
  const prev = lastKeysRef.current;
4681
4795
  if (!prev) return /* @__PURE__ */ new Set();
4682
4796
  const fresh = /* @__PURE__ */ new Set();
@@ -4686,7 +4800,7 @@ function VirtualList({
4686
4800
  }
4687
4801
  return fresh;
4688
4802
  }, [items]);
4689
- React30.useEffect(() => {
4803
+ React31.useEffect(() => {
4690
4804
  lastKeysRef.current = new Set(items.map(keyOf));
4691
4805
  }, [items]);
4692
4806
  const gapStyle = { "--fd-vlist-gap": rowGap + "px" };
@@ -4731,7 +4845,7 @@ function VirtualList({
4731
4845
  }
4732
4846
 
4733
4847
  // src/components/data/EntityRow.tsx
4734
- import * as React31 from "react";
4848
+ import * as React32 from "react";
4735
4849
  import { jsx as jsx55, jsxs as jsxs50 } from "react/jsx-runtime";
4736
4850
  var ENTITY_ROW_METRIC_WIDTH = 74;
4737
4851
  var METRIC_TONE_COLOR = {
@@ -4744,7 +4858,7 @@ function EntityRowMetrics({ metrics, className = "", style }) {
4744
4858
  const key = m.id || m.label || i;
4745
4859
  const className2 = ["fd-erow-metric", m.tone && m.tone !== "default" ? "is-" + m.tone : "", m.tooltip != null ? "is-described" : ""].filter(Boolean).join(" ");
4746
4860
  const tone = { color: METRIC_TONE_COLOR[m.tone || "default"] };
4747
- const content = /* @__PURE__ */ jsxs50(React31.Fragment, { children: [
4861
+ const content = /* @__PURE__ */ jsxs50(React32.Fragment, { children: [
4748
4862
  m.icon ? /* @__PURE__ */ jsx55("i", { className: "ph ph-" + m.icon, "aria-hidden": "true" }) : null,
4749
4863
  /* @__PURE__ */ jsx55("span", { className: "fd-erow-metric-value fd-tabular", children: m.value }),
4750
4864
  /* @__PURE__ */ jsx55("span", { className: "fd-sr", children: " " + m.label })
@@ -4836,7 +4950,7 @@ function EntityRow({
4836
4950
  }
4837
4951
 
4838
4952
  // src/components/data/TransferList.tsx
4839
- import * as React32 from "react";
4953
+ import * as React33 from "react";
4840
4954
  import { createPortal as createPortal4 } from "react-dom";
4841
4955
 
4842
4956
  // src/components/forms/Input.tsx
@@ -4942,12 +5056,12 @@ function TransferList({
4942
5056
  listHeight = 440,
4943
5057
  className = ""
4944
5058
  }) {
4945
- const [drag, setDrag] = React32.useState(null);
4946
- const [dragOverSide, setDragOverSide] = React32.useState(null);
4947
- const dragRef = React32.useRef(null);
4948
- const armedRef = React32.useRef(null);
4949
- const sideRefs = React32.useRef({ left: null, right: null });
4950
- const hitTestSide = React32.useCallback((x, y) => {
5059
+ const [drag, setDrag] = React33.useState(null);
5060
+ const [dragOverSide, setDragOverSide] = React33.useState(null);
5061
+ const dragRef = React33.useRef(null);
5062
+ const armedRef = React33.useRef(null);
5063
+ const sideRefs = React33.useRef({ left: null, right: null });
5064
+ const hitTestSide = React33.useCallback((x, y) => {
4951
5065
  for (const side of ["left", "right"]) {
4952
5066
  const el = sideRefs.current[side];
4953
5067
  if (!el) continue;
@@ -4956,7 +5070,7 @@ function TransferList({
4956
5070
  }
4957
5071
  return null;
4958
5072
  }, []);
4959
- const findItem = React32.useCallback(
5073
+ const findItem = React33.useCallback(
4960
5074
  (side, key) => (side === "left" ? left.items : right.items).find((it) => keyOf(it) === key),
4961
5075
  [left.items, right.items, keyOf]
4962
5076
  );
@@ -4978,7 +5092,7 @@ function TransferList({
4978
5092
  height: rect.height
4979
5093
  };
4980
5094
  };
4981
- React32.useEffect(() => {
5095
+ React33.useEffect(() => {
4982
5096
  const onMovePointer = (e) => {
4983
5097
  const armed = armedRef.current;
4984
5098
  if (!armed || e.pointerId !== armed.pointerId) return;
@@ -5031,7 +5145,7 @@ function TransferList({
5031
5145
  window.removeEventListener("pointercancel", endDrag);
5032
5146
  };
5033
5147
  }, [hitTestSide, findItem, onMove]);
5034
- React32.useEffect(() => {
5148
+ React33.useEffect(() => {
5035
5149
  if (!drag || typeof document === "undefined") return;
5036
5150
  const prev = document.body.style.userSelect;
5037
5151
  document.body.style.userSelect = "none";
@@ -5139,11 +5253,11 @@ function TransferList({
5139
5253
  }
5140
5254
 
5141
5255
  // src/components/forms/Checkbox.tsx
5142
- import * as React33 from "react";
5256
+ import * as React34 from "react";
5143
5257
  import { jsx as jsx59, jsxs as jsxs53 } from "react/jsx-runtime";
5144
5258
  function Checkbox({ label, description, card = false, indeterminate = false, className = "", ...rest }) {
5145
- const ref = React33.useRef(null);
5146
- React33.useEffect(() => {
5259
+ const ref = React34.useRef(null);
5260
+ React34.useEffect(() => {
5147
5261
  if (ref.current) ref.current.indeterminate = indeterminate;
5148
5262
  }, [indeterminate]);
5149
5263
  return /* @__PURE__ */ jsxs53("label", { className: ["fd-choice", card ? "fd-choice-card" : "", className].filter(Boolean).join(" "), children: [
@@ -5187,7 +5301,7 @@ function Switch({ label, description, className = "", ...rest }) {
5187
5301
  }
5188
5302
 
5189
5303
  // src/components/forms/Select.tsx
5190
- import * as React34 from "react";
5304
+ import * as React35 from "react";
5191
5305
  import { jsx as jsx62, jsxs as jsxs56 } from "react/jsx-runtime";
5192
5306
  var norm = (o) => typeof o === "string" ? { value: o, label: o } : o;
5193
5307
  function Select({
@@ -5214,13 +5328,12 @@ function Select({
5214
5328
  const isOn = (v) => multiple ? vals.includes(v) : v === value;
5215
5329
  const hasSearch = searchable === void 0 ? opts.length > 8 : searchable;
5216
5330
  const { fieldId, labelId, ariaLabelledBy } = useTriggerLabelling(label, id);
5217
- const [open, setOpen] = React34.useState(false);
5218
- const [q, setQ] = React34.useState("");
5219
- const [active, setActive] = React34.useState(-1);
5220
- const rootRef = React34.useRef(null);
5221
- const boxRef = React34.useRef(null);
5222
- const listRef = React34.useRef(null);
5223
- const typeBuf = React34.useRef({ s: "", t: 0 });
5331
+ const [open, setOpen] = React35.useState(false);
5332
+ const [q, setQ] = React35.useState("");
5333
+ const rootRef = React35.useRef(null);
5334
+ const boxRef = React35.useRef(null);
5335
+ const listRef = React35.useRef(null);
5336
+ const typeBuf = React35.useRef({ s: "", t: 0 });
5224
5337
  const selected = multiple ? null : opts.find((o) => o.value === value);
5225
5338
  const chosen = multiple ? opts.filter((o) => vals.includes(o.value)) : [];
5226
5339
  const boxText = multiple ? chosen.length === 0 ? placeholder : chosen.length === 1 ? chosen[0].label : summary ? summary(chosen.map((o) => o.value)) : chosen.length + " selected" : selected ? selected.label : placeholder;
@@ -5237,23 +5350,16 @@ function Select({
5237
5350
  fire(o.value);
5238
5351
  setOpen(false);
5239
5352
  };
5353
+ const nav = useListboxNav({ count: visible.length, onSelect: (i) => pick(visible[i]) });
5354
+ useListboxScrollIntoView(nav.active, listRef, open);
5240
5355
  const toggle = () => {
5241
5356
  if (disabled || loading) return;
5242
5357
  if (!open) {
5243
5358
  setQ("");
5244
- setActive(Math.max(0, opts.findIndex((o) => isOn(o.value))));
5359
+ nav.setActive(Math.max(0, opts.findIndex((o) => isOn(o.value))));
5245
5360
  }
5246
5361
  setOpen(!open);
5247
5362
  };
5248
- React34.useEffect(() => {
5249
- if (!open || active < 0 || !listRef.current) return;
5250
- const el = listRef.current.querySelector('[data-i="' + active + '"]');
5251
- if (el) {
5252
- const L = listRef.current, top = el.offsetTop - L.offsetTop;
5253
- if (top < L.scrollTop) L.scrollTop = top - 4;
5254
- else if (top + el.offsetHeight > L.scrollTop + L.clientHeight) L.scrollTop = top + el.offsetHeight - L.clientHeight + 4;
5255
- }
5256
- }, [active, open]);
5257
5363
  const onKey = (e) => {
5258
5364
  if (disabled) return;
5259
5365
  if (!open && (e.key === "Enter" || e.key === " " || e.key === "ArrowDown" || e.key === "ArrowUp")) {
@@ -5275,19 +5381,19 @@ function Select({
5275
5381
  setOpen(false);
5276
5382
  } else if (e.key === "ArrowDown") {
5277
5383
  e.preventDefault();
5278
- setActive((a) => Math.min(visible.length - 1, a + 1));
5384
+ nav.move(1);
5279
5385
  } else if (e.key === "ArrowUp") {
5280
5386
  e.preventDefault();
5281
- setActive((a) => Math.max(0, a - 1));
5387
+ nav.move(-1);
5282
5388
  } else if (e.key === "Home") {
5283
5389
  e.preventDefault();
5284
- setActive(0);
5390
+ nav.first();
5285
5391
  } else if (e.key === "End") {
5286
5392
  e.preventDefault();
5287
- setActive(visible.length - 1);
5393
+ nav.last();
5288
5394
  } else if (e.key === "Enter") {
5289
5395
  e.preventDefault();
5290
- if (visible[active]) pick(visible[active]);
5396
+ nav.activate();
5291
5397
  } else if (e.key === "Tab") setOpen(false);
5292
5398
  };
5293
5399
  const groups = [];
@@ -5360,13 +5466,13 @@ function Select({
5360
5466
  onKeyDown: onKey,
5361
5467
  onChange: (e) => {
5362
5468
  setQ(e.target.value);
5363
- setActive(0);
5469
+ nav.setActive(0);
5364
5470
  }
5365
5471
  }
5366
5472
  ),
5367
5473
  q ? /* @__PURE__ */ jsx62("span", { className: "fd-body-sm fd-muted", children: visible.length }) : null
5368
5474
  ] }) : void 0,
5369
- footer: multiple ? /* @__PURE__ */ jsxs56(React34.Fragment, { children: [
5475
+ footer: multiple ? /* @__PURE__ */ jsxs56(React35.Fragment, { children: [
5370
5476
  /* @__PURE__ */ jsx62(
5371
5477
  "button",
5372
5478
  {
@@ -5397,7 +5503,7 @@ function Select({
5397
5503
  'Nothing matches "',
5398
5504
  q,
5399
5505
  '".'
5400
- ] }) : groups.map((grp) => /* @__PURE__ */ jsxs56(React34.Fragment, { children: [
5506
+ ] }) : groups.map((grp) => /* @__PURE__ */ jsxs56(React35.Fragment, { children: [
5401
5507
  grp.g ? /* @__PURE__ */ jsx62("div", { className: "fd-pop-group", children: grp.g }) : null,
5402
5508
  grp.items.map(({ o, i }) => /* @__PURE__ */ jsxs56(
5403
5509
  "button",
@@ -5407,8 +5513,8 @@ function Select({
5407
5513
  role: "option",
5408
5514
  "aria-selected": isOn(o.value),
5409
5515
  "aria-disabled": o.disabled || void 0,
5410
- className: ["fd-opt", i === active ? "is-active" : "", isOn(o.value) ? "is-selected" : ""].filter(Boolean).join(" "),
5411
- onMouseEnter: () => setActive(i),
5516
+ className: ["fd-opt", i === nav.active ? "is-active" : "", isOn(o.value) ? "is-selected" : ""].filter(Boolean).join(" "),
5517
+ onMouseEnter: () => nav.setActive(i),
5412
5518
  onClick: () => pick(o),
5413
5519
  children: [
5414
5520
  multiple ? /* @__PURE__ */ jsx62("span", { "aria-hidden": "true", style: { flex: "none", display: "grid", placeItems: "center", width: 16, height: 16, borderRadius: 4, border: "1.5px solid " + (isOn(o.value) ? "var(--brand)" : "var(--border-strong, var(--border))"), background: isOn(o.value) ? "var(--brand)" : "var(--surface)", color: "#fff" }, children: isOn(o.value) ? /* @__PURE__ */ jsx62("i", { className: "ph ph-check", style: { fontSize: 11 } }) : null }) : null,
@@ -5434,7 +5540,7 @@ function Select({
5434
5540
  }
5435
5541
 
5436
5542
  // src/components/forms/TimePicker.tsx
5437
- import * as React35 from "react";
5543
+ import * as React36 from "react";
5438
5544
  import { jsx as jsx63, jsxs as jsxs57 } from "react/jsx-runtime";
5439
5545
  var pad = (n) => String(n).padStart(2, "0");
5440
5546
  function ClockFace({ value = "09:00", onChange }) {
@@ -5443,9 +5549,9 @@ function ClockFace({ value = "09:00", onChange }) {
5443
5549
  if (isNaN(m)) m = 0;
5444
5550
  const pm = h24 >= 12;
5445
5551
  const h12 = h24 % 12 === 0 ? 12 : h24 % 12;
5446
- const [mode2, setMode] = React35.useState("h");
5447
- const faceRef = React35.useRef(null);
5448
- const dragging = React35.useRef(false);
5552
+ const [mode2, setMode] = React36.useState("h");
5553
+ const faceRef = React36.useRef(null);
5554
+ const dragging = React36.useRef(false);
5449
5555
  const set = (h, mm, isPm) => onChange((isPm ? h % 12 + 12 : h % 12) + ":" + pad(mm));
5450
5556
  const R = 108, NR = 80;
5451
5557
  const nums = mode2 === "h" ? Array.from({ length: 12 }, (_, i) => i + 1) : Array.from({ length: 12 }, (_, i) => i * 5);
@@ -5518,9 +5624,9 @@ function ClockFace({ value = "09:00", onChange }) {
5518
5624
  }
5519
5625
  function TimePicker({ label, help, error, required = false, disabled = false, value = "", onChange, placeholder = "Pick a time", id, className = "", style }) {
5520
5626
  const { fieldId, labelId, ariaLabelledBy } = useTriggerLabelling(label, id);
5521
- const [open, setOpen] = React35.useState(false);
5522
- const rootRef = React35.useRef(null);
5523
- const boxRef = React35.useRef(null);
5627
+ const [open, setOpen] = React36.useState(false);
5628
+ const rootRef = React36.useRef(null);
5629
+ const boxRef = React36.useRef(null);
5524
5630
  const disp = () => {
5525
5631
  if (!value) return "";
5526
5632
  const [h, m] = value.split(":").map(Number);
@@ -5563,7 +5669,7 @@ function TimePicker({ label, help, error, required = false, disabled = false, va
5563
5669
  offset: 6,
5564
5670
  width: 262,
5565
5671
  role: "presentation",
5566
- footer: /* @__PURE__ */ jsxs57(React35.Fragment, { children: [
5672
+ footer: /* @__PURE__ */ jsxs57(React36.Fragment, { children: [
5567
5673
  /* @__PURE__ */ jsx63(
5568
5674
  "button",
5569
5675
  {
@@ -5591,7 +5697,7 @@ function TimePicker({ label, help, error, required = false, disabled = false, va
5591
5697
  }
5592
5698
 
5593
5699
  // src/components/forms/Slider.tsx
5594
- import * as React36 from "react";
5700
+ import * as React37 from "react";
5595
5701
  import { jsx as jsx64, jsxs as jsxs58 } from "react/jsx-runtime";
5596
5702
  function Slider({
5597
5703
  label,
@@ -5609,7 +5715,7 @@ function Slider({
5609
5715
  ...rest
5610
5716
  }) {
5611
5717
  const fieldId = useFieldId(id);
5612
- const [dragging, setDragging] = React36.useState(false);
5718
+ const [dragging, setDragging] = React37.useState(false);
5613
5719
  const v = value === void 0 ? min : Number(value);
5614
5720
  const pct = max === min ? 0 : (v - min) / (max - min) * 100;
5615
5721
  return /* @__PURE__ */ jsxs58("div", { className: ["fd-field", className].filter(Boolean).join(" "), children: [
@@ -5643,7 +5749,7 @@ function Slider({
5643
5749
  }
5644
5750
 
5645
5751
  // src/components/forms/RangeSlider.tsx
5646
- import * as React37 from "react";
5752
+ import * as React38 from "react";
5647
5753
  import { jsx as jsx65, jsxs as jsxs59 } from "react/jsx-runtime";
5648
5754
  function RangeSlider({
5649
5755
  label,
@@ -5663,9 +5769,9 @@ function RangeSlider({
5663
5769
  }) {
5664
5770
  const fmt2 = format || ((v) => String(v));
5665
5771
  const [a, b] = value || [min, max];
5666
- const [drag, setDrag] = React37.useState(null);
5667
- const [focus, setFocus] = React37.useState(null);
5668
- const railRef = React37.useRef(null);
5772
+ const [drag, setDrag] = React38.useState(null);
5773
+ const [focus, setFocus] = React38.useState(null);
5774
+ const railRef = React38.useRef(null);
5669
5775
  const pct = (v) => max === min ? 0 : (v - min) / (max - min) * 100;
5670
5776
  const clampPair = (i, v) => {
5671
5777
  v = Math.min(max, Math.max(min, Math.round(v / step) * step));
@@ -5680,7 +5786,7 @@ function RangeSlider({
5680
5786
  const r = railRef.current.getBoundingClientRect();
5681
5787
  return min + Math.min(1, Math.max(0, (e.clientX - r.left) / r.width)) * (max - min);
5682
5788
  };
5683
- React37.useEffect(() => {
5789
+ React38.useEffect(() => {
5684
5790
  if (drag === null) return;
5685
5791
  const mv = (e) => onChange && onChange(clampPair(drag, fromEvent(e)));
5686
5792
  const upH = () => setDrag(null);
@@ -5761,7 +5867,7 @@ function RangeSlider({
5761
5867
  /* @__PURE__ */ jsxs59("div", { className: "fd-range" + (hasLabels ? " has-labels" : ""), onPointerDown: onRailDown, children: [
5762
5868
  /* @__PURE__ */ jsx65("span", { className: "fd-range-rail", ref: railRef }),
5763
5869
  /* @__PURE__ */ jsx65("span", { className: "fd-range-fill", style: { left: pct(a) + "%", width: pct(b) - pct(a) + "%" } }),
5764
- marks.map((m) => /* @__PURE__ */ jsxs59(React37.Fragment, { children: [
5870
+ marks.map((m) => /* @__PURE__ */ jsxs59(React38.Fragment, { children: [
5765
5871
  /* @__PURE__ */ jsx65("span", { className: "fd-range-mark" + (m.value >= a && m.value <= b ? " is-in" : ""), style: { left: pct(m.value) + "%" } }),
5766
5872
  m.label ? /* @__PURE__ */ jsx65("span", { className: "fd-range-mark-label", style: { left: pct(m.value) + "%" }, children: m.label }) : null
5767
5873
  ] }, m.value)),
@@ -5790,7 +5896,7 @@ function RangeSlider({
5790
5896
  }
5791
5897
 
5792
5898
  // src/components/forms/Dropzone.tsx
5793
- import * as React38 from "react";
5899
+ import * as React39 from "react";
5794
5900
  import { jsx as jsx66, jsxs as jsxs60 } from "react/jsx-runtime";
5795
5901
  function Dropzone({
5796
5902
  onFiles,
@@ -5805,8 +5911,8 @@ function Dropzone({
5805
5911
  className = "",
5806
5912
  style
5807
5913
  }) {
5808
- const [over, setOver] = React38.useState(false);
5809
- const depth = React38.useRef(0);
5914
+ const [over, setOver] = React39.useState(false);
5915
+ const depth = React39.useRef(0);
5810
5916
  const has = (e) => {
5811
5917
  const dt = e.dataTransfer;
5812
5918
  if (!dt) return false;
@@ -5868,8 +5974,8 @@ function FilePickButton({
5868
5974
  className = "",
5869
5975
  children
5870
5976
  }) {
5871
- const ref = React38.useRef(null);
5872
- return /* @__PURE__ */ jsxs60(React38.Fragment, { children: [
5977
+ const ref = React39.useRef(null);
5978
+ return /* @__PURE__ */ jsxs60(React39.Fragment, { children: [
5873
5979
  /* @__PURE__ */ jsx66(
5874
5980
  "button",
5875
5981
  {
@@ -5902,10 +6008,10 @@ function FilePickButton({
5902
6008
  }
5903
6009
  function useStagedFiles(upload, opts) {
5904
6010
  const o = opts || {};
5905
- const [items, setItems] = React38.useState([]);
5906
- const controllers = React38.useRef({});
6011
+ const [items, setItems] = React39.useState([]);
6012
+ const controllers = React39.useRef({});
5907
6013
  const patch = (id, next) => setItems((list) => list.map((f) => f.id === id ? Object.assign({}, f, next) : f));
5908
- const run = React38.useCallback((att) => {
6014
+ const run = React39.useCallback((att) => {
5909
6015
  if (!upload) return;
5910
6016
  const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
5911
6017
  controllers.current[att.id] = ac;
@@ -5922,14 +6028,14 @@ function useStagedFiles(upload, opts) {
5922
6028
  delete controllers.current[att.id];
5923
6029
  });
5924
6030
  }, [upload]);
5925
- const add = React38.useCallback((files) => {
6031
+ const add = React39.useCallback((files) => {
5926
6032
  if (!upload) return [];
5927
6033
  const atts = Array.from(files).map((f) => toAttachment(f));
5928
6034
  setItems((list) => list.concat(atts));
5929
6035
  atts.forEach(run);
5930
6036
  return atts;
5931
6037
  }, [upload, run]);
5932
- const remove = React38.useCallback((att) => {
6038
+ const remove = React39.useCallback((att) => {
5933
6039
  const ac = controllers.current[att.id];
5934
6040
  if (ac) {
5935
6041
  try {
@@ -5940,10 +6046,10 @@ function useStagedFiles(upload, opts) {
5940
6046
  }
5941
6047
  setItems((list) => list.filter((f) => f.id !== att.id));
5942
6048
  }, []);
5943
- const retry = React38.useCallback((att) => {
6049
+ const retry = React39.useCallback((att) => {
5944
6050
  run(att);
5945
6051
  }, [run]);
5946
- const clear = React38.useCallback(() => {
6052
+ const clear = React39.useCallback(() => {
5947
6053
  Object.values(controllers.current).forEach((ac) => {
5948
6054
  try {
5949
6055
  ac && ac.abort();
@@ -6067,7 +6173,7 @@ function FileGrid({ files = [], onOpen, onRemove, onRetry, tiles = true, maxHeig
6067
6173
  }
6068
6174
 
6069
6175
  // src/components/forms/MarkdownEditor.tsx
6070
- import * as React39 from "react";
6176
+ import * as React40 from "react";
6071
6177
  import { jsx as jsx68, jsxs as jsxs62 } from "react/jsx-runtime";
6072
6178
  var isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent || "");
6073
6179
  var INLINE_RE = /(\*\*\*[^*\n]+\*\*\*|\*\*[^*\n]+\*\*|__[^_\n]+__|~~[^~\n]+~~|`[^`\n]+`|\*[^*\s][^*\n]*\*|(?<![A-Za-z0-9_])_[^_\s][^_\n]*_|\[[^\]\n]*\]\([^)\s\n]*\)|https?:\/\/\S+)/g;
@@ -6281,7 +6387,7 @@ function syncDom(root, value) {
6281
6387
  while (root.children.length > lines.length) root.removeChild(root.lastChild);
6282
6388
  }
6283
6389
  var LIST_CONT = RE_LI;
6284
- var MarkdownEditor = React39.forwardRef(function MarkdownEditor2({
6390
+ var MarkdownEditor = React40.forwardRef(function MarkdownEditor2({
6285
6391
  value = "",
6286
6392
  onChange,
6287
6393
  onSubmit,
@@ -6300,10 +6406,10 @@ var MarkdownEditor = React39.forwardRef(function MarkdownEditor2({
6300
6406
  className = "",
6301
6407
  id
6302
6408
  }, ref) {
6303
- const boxRef = React39.useRef(null);
6304
- const composing = React39.useRef(false);
6305
- const pendingCaret = React39.useRef(null);
6306
- React39.useLayoutEffect(() => {
6409
+ const boxRef = React40.useRef(null);
6410
+ const composing = React40.useRef(false);
6411
+ const pendingCaret = React40.useRef(null);
6412
+ React40.useLayoutEffect(() => {
6307
6413
  const root = boxRef.current;
6308
6414
  if (!root || composing.current) return;
6309
6415
  const active = document.activeElement === root || root.contains(document.activeElement);
@@ -6312,7 +6418,7 @@ var MarkdownEditor = React39.forwardRef(function MarkdownEditor2({
6312
6418
  pendingCaret.current = null;
6313
6419
  if (active && caret != null) placeCaret(root, caret);
6314
6420
  }, [value]);
6315
- React39.useEffect(() => {
6421
+ React40.useEffect(() => {
6316
6422
  if (autoFocus && boxRef.current) boxRef.current.focus();
6317
6423
  }, [autoFocus]);
6318
6424
  const caretNow = () => {
@@ -6379,7 +6485,7 @@ var MarkdownEditor = React39.forwardRef(function MarkdownEditor2({
6379
6485
  api.replaceRange(from, to, next, from + next.length);
6380
6486
  }
6381
6487
  };
6382
- React39.useImperativeHandle(ref, () => api);
6488
+ React40.useImperativeHandle(ref, () => api);
6383
6489
  function detect(text, caret) {
6384
6490
  if (!onTrigger) return;
6385
6491
  const upto = text.slice(0, caret);
@@ -6531,16 +6637,16 @@ var MarkdownEditor = React39.forwardRef(function MarkdownEditor2({
6531
6637
  });
6532
6638
 
6533
6639
  // src/components/platform/AccountMenu.tsx
6534
- import * as React41 from "react";
6640
+ import * as React42 from "react";
6535
6641
 
6536
6642
  // src/kits/sessionContext.tsx
6537
- import * as React40 from "react";
6538
- var SessionContext = React40.createContext(null);
6643
+ import * as React41 from "react";
6644
+ var SessionContext = React41.createContext(null);
6539
6645
  function SessionProvider({ value, children }) {
6540
- return React40.createElement(SessionContext.Provider, { value }, children);
6646
+ return React41.createElement(SessionContext.Provider, { value }, children);
6541
6647
  }
6542
6648
  function useSessionContext() {
6543
- const ctx = React40.useContext(SessionContext);
6649
+ const ctx = React41.useContext(SessionContext);
6544
6650
  if (!ctx) {
6545
6651
  throw new Error(
6546
6652
  "useSessionContext() was called outside a <SessionProvider>. Wrap your app root in <SessionProvider value={...}> with your real session, or \u2014 in the docs site or a demo \u2014 in <DemoSessionProvider>, before rendering AccountMenu, Gate, ProfilePage or AppSwitcher."
@@ -6580,11 +6686,11 @@ function AccountMenu({
6580
6686
  ...rest
6581
6687
  }) {
6582
6688
  const { user, roles, can: can2, signOut: ctxSignOut, demo } = useSessionContext();
6583
- const [open, setOpen] = React41.useState(false);
6584
- const [switching, setSwitching] = React41.useState(false);
6585
- const [signingOut, setSigningOut] = React41.useState(false);
6586
- const [signOutError, setSignOutError] = React41.useState(null);
6587
- const ref = React41.useRef(null);
6689
+ const [open, setOpen] = React42.useState(false);
6690
+ const [switching, setSwitching] = React42.useState(false);
6691
+ const [signingOut, setSigningOut] = React42.useState(false);
6692
+ const [signOutError, setSignOutError] = React42.useState(null);
6693
+ const ref = React42.useRef(null);
6588
6694
  const visibleLinks = links.filter((l) => !l.perm || can2(l.perm));
6589
6695
  const visibleGroups = groups.map((g) => ({ label: g.label, links: g.links.filter((l) => !l.perm || can2(l.perm)) })).filter((g) => g.links.length);
6590
6696
  const closeMenu = () => {
@@ -6709,13 +6815,13 @@ function AccountMenu({
6709
6815
  }
6710
6816
 
6711
6817
  // src/components/platform/AppSwitcher.tsx
6712
- import * as React42 from "react";
6818
+ import * as React43 from "react";
6713
6819
  import { jsx as jsx70, jsxs as jsxs64 } from "react/jsx-runtime";
6714
6820
  function AppSwitcher({ apps: appsProp, onNavigate, className = "" }) {
6715
6821
  const { apps: contextApps } = useSessionContext();
6716
6822
  const apps = appsProp ?? contextApps ?? [];
6717
- const [open, setOpen] = React42.useState(false);
6718
- const anchorRef = React42.useRef(null);
6823
+ const [open, setOpen] = React43.useState(false);
6824
+ const anchorRef = React43.useRef(null);
6719
6825
  if (!apps.length) return null;
6720
6826
  const items = apps.map((app) => ({
6721
6827
  id: app.key,
@@ -6729,7 +6835,7 @@ function AppSwitcher({ apps: appsProp, onNavigate, className = "" }) {
6729
6835
  window.location.href = app.url;
6730
6836
  }
6731
6837
  }));
6732
- return /* @__PURE__ */ jsxs64(React42.Fragment, { children: [
6838
+ return /* @__PURE__ */ jsxs64(React43.Fragment, { children: [
6733
6839
  /* @__PURE__ */ jsx70("span", { ref: anchorRef, className: ["fd-appswitch-anchor", className].filter(Boolean).join(" "), style: { display: "inline-flex" }, children: /* @__PURE__ */ jsx70(
6734
6840
  IconButton,
6735
6841
  {
@@ -6758,10 +6864,10 @@ function AppSwitcher({ apps: appsProp, onNavigate, className = "" }) {
6758
6864
  }
6759
6865
 
6760
6866
  // src/components/platform/AppearanceMenu.tsx
6761
- import * as React44 from "react";
6867
+ import * as React45 from "react";
6762
6868
 
6763
6869
  // src/kits/appearance.ts
6764
- import * as React43 from "react";
6870
+ import * as React44 from "react";
6765
6871
  var DEFAULT_STORAGE_KEY = "fd-appearance";
6766
6872
  var DEFAULT_PREFS = {
6767
6873
  theme: "system",
@@ -6856,11 +6962,11 @@ function applyPrefs(prefs, root = document.documentElement) {
6856
6962
  else root.removeAttribute("data-motion");
6857
6963
  }
6858
6964
  function useAppearancePrefs(store) {
6859
- const prefs = React43.useSyncExternalStore(store.subscribe, store.getSnapshot);
6860
- React43.useEffect(() => {
6965
+ const prefs = React44.useSyncExternalStore(store.subscribe, store.getSnapshot);
6966
+ React44.useEffect(() => {
6861
6967
  applyPrefs(prefs);
6862
6968
  }, [prefs]);
6863
- React43.useEffect(() => {
6969
+ React44.useEffect(() => {
6864
6970
  if (prefs.theme !== "system" || typeof window === "undefined" || typeof window.matchMedia !== "function") {
6865
6971
  return;
6866
6972
  }
@@ -6869,7 +6975,7 @@ function useAppearancePrefs(store) {
6869
6975
  mq.addEventListener("change", onChange);
6870
6976
  return () => mq.removeEventListener("change", onChange);
6871
6977
  }, [prefs]);
6872
- const update = React43.useCallback(
6978
+ const update = React44.useCallback(
6873
6979
  (patch) => {
6874
6980
  store.write({ ...store.getSnapshot(), ...patch });
6875
6981
  },
@@ -6892,15 +6998,15 @@ var TEXT_SCALE_OPTIONS = [
6892
6998
  { value: "xl", label: "XL" }
6893
6999
  ];
6894
7000
  function AppearanceMenu({ className = "", store: storeProp, storageKey, storage, defaults }) {
6895
- const store = React44.useMemo(
7001
+ const store = React45.useMemo(
6896
7002
  () => storeProp ?? createLocalStorageAppearanceStore({ storageKey, storage, defaults }),
6897
7003
  [storeProp, storageKey, storage, defaults]
6898
7004
  );
6899
7005
  const [prefs, update] = useAppearancePrefs(store);
6900
- const [open, setOpen] = React44.useState(false);
6901
- const anchorRef = React44.useRef(null);
7006
+ const [open, setOpen] = React45.useState(false);
7007
+ const anchorRef = React45.useRef(null);
6902
7008
  const patch = (p) => update(p);
6903
- return /* @__PURE__ */ jsxs65(React44.Fragment, { children: [
7009
+ return /* @__PURE__ */ jsxs65(React45.Fragment, { children: [
6904
7010
  /* @__PURE__ */ jsx71("span", { ref: anchorRef, className: ["fd-appearance-anchor", className].filter(Boolean).join(" "), style: { display: "inline-flex" }, children: /* @__PURE__ */ jsx71(
6905
7011
  IconButton,
6906
7012
  {
@@ -6969,14 +7075,14 @@ function AppearanceMenu({ className = "", store: storeProp, storageKey, storage,
6969
7075
  }
6970
7076
 
6971
7077
  // src/components/platform/ApiSpecBrowser.tsx
6972
- import * as React45 from "react";
7078
+ import * as React46 from "react";
6973
7079
  import { jsx as jsx72, jsxs as jsxs66 } from "react/jsx-runtime";
6974
7080
  var METHOD_TONE = { GET: "success", POST: "info", PATCH: "warning", PUT: "warning", DELETE: "danger" };
6975
7081
  function Json({ obj }) {
6976
7082
  return /* @__PURE__ */ jsx72("pre", { className: "fd-json", children: JSON.stringify(obj, null, 2) });
6977
7083
  }
6978
7084
  function Endpoint({ s, onRequest }) {
6979
- const [tried, setTried] = React45.useState(null);
7085
+ const [tried, setTried] = React46.useState(null);
6980
7086
  const run = async () => {
6981
7087
  setTried("busy");
6982
7088
  const t0 = (window.performance || Date).now();
@@ -7049,10 +7155,10 @@ function ApiSpecBrowser({
7049
7155
  className = "",
7050
7156
  ...rest
7051
7157
  }) {
7052
- const [q, setQ] = React45.useState("");
7053
- const [method, setMethod] = React45.useState(null);
7054
- const [mod, setMod] = React45.useState(null);
7055
- const [listOnly, setListOnly] = React45.useState(false);
7158
+ const [q, setQ] = React46.useState("");
7159
+ const [method, setMethod] = React46.useState(null);
7160
+ const [mod, setMod] = React46.useState(null);
7161
+ const [listOnly, setListOnly] = React46.useState(false);
7056
7162
  const activeModule = modules && modules.find((m) => m.id === mod);
7057
7163
  const hits = spec.filter((s) => (!method || s.method === method) && (!listOnly || s.isList) && (!activeModule || activeModule.endpoints.indexOf(s.id) >= 0) && (!q || (s.path + " " + s.title + " " + s.purpose + " " + (s.usedBy || []).join(" ")).toLowerCase().includes(q.toLowerCase())));
7058
7164
  const effGroups = groups && groups.length ? groups : [["All endpoints", spec.map((s) => s.id)]];
@@ -7131,7 +7237,7 @@ function ApiSpecBrowser({
7131
7237
  }
7132
7238
 
7133
7239
  // src/components/platform/ProfilePage.tsx
7134
- import * as React46 from "react";
7240
+ import * as React47 from "react";
7135
7241
  import { jsx as jsx73, jsxs as jsxs67 } from "react/jsx-runtime";
7136
7242
  function fieldInitialValue(field, user) {
7137
7243
  if (field.value) return field.value(user);
@@ -7167,21 +7273,21 @@ function ProfilePage({
7167
7273
  }) {
7168
7274
  const { user: sessionUser, roles, permissions, saveProfile: saveProfile2 } = useSessionContext();
7169
7275
  const user = userProp || sessionUser;
7170
- const initialFieldValues = React46.useMemo(() => {
7276
+ const initialFieldValues = React47.useMemo(() => {
7171
7277
  const values2 = {};
7172
7278
  for (const field of fields) values2[field.key] = fieldInitialValue(field, user);
7173
7279
  return values2;
7174
7280
  }, [user.id, fields]);
7175
- const initialNotify = React46.useMemo(() => {
7281
+ const initialNotify = React47.useMemo(() => {
7176
7282
  const values2 = {};
7177
7283
  for (const n of notifications) values2[n.key] = !!user.notify?.[n.key];
7178
7284
  return values2;
7179
7285
  }, [user.id, notifications]);
7180
- const [values, setValues] = React46.useState(initialFieldValues);
7181
- const [notify, setNotify] = React46.useState(initialNotify);
7182
- const [saving, setSaving] = React46.useState(false);
7183
- const [saved, setSaved] = React46.useState(false);
7184
- const [prevUserId, setPrevUserId] = React46.useState(user.id);
7286
+ const [values, setValues] = React47.useState(initialFieldValues);
7287
+ const [notify, setNotify] = React47.useState(initialNotify);
7288
+ const [saving, setSaving] = React47.useState(false);
7289
+ const [saved, setSaved] = React47.useState(false);
7290
+ const [prevUserId, setPrevUserId] = React47.useState(user.id);
7185
7291
  if (user.id !== prevUserId) {
7186
7292
  setPrevUserId(user.id);
7187
7293
  setValues(initialFieldValues);
@@ -7369,11 +7475,11 @@ function ProfilePage({
7369
7475
  }
7370
7476
 
7371
7477
  // src/components/platform/SignInPage.tsx
7372
- import * as React47 from "react";
7478
+ import * as React48 from "react";
7373
7479
  import { Fragment as Fragment18, jsx as jsx74, jsxs as jsxs68 } from "react/jsx-runtime";
7374
7480
  function SignInPage({ productName, onSignIn, continueAs, error }) {
7375
- const [signingIn, setSigningIn] = React47.useState(false);
7376
- const [resuming, setResuming] = React47.useState(false);
7481
+ const [signingIn, setSigningIn] = React48.useState(false);
7482
+ const [resuming, setResuming] = React48.useState(false);
7377
7483
  function handleSignIn() {
7378
7484
  setSigningIn(true);
7379
7485
  onSignIn();
@@ -7562,10 +7668,10 @@ function GoogleGlyph() {
7562
7668
  }
7563
7669
 
7564
7670
  // src/components/platform/RoadmapTimeline.tsx
7565
- import * as React50 from "react";
7671
+ import * as React51 from "react";
7566
7672
 
7567
7673
  // src/kits/session.ts
7568
- import * as React48 from "react";
7674
+ import * as React49 from "react";
7569
7675
  var PERMISSION_CATALOG = [
7570
7676
  { group: "Plans", items: [
7571
7677
  { key: "plan.view", label: "View plans", detail: "Read any plan in the workspace." },
@@ -9132,8 +9238,8 @@ function roadmap(overrides) {
9132
9238
  };
9133
9239
  }
9134
9240
  function useSession() {
9135
- const [s, setS] = React48.useState(getSession);
9136
- React48.useEffect(() => subscribe(setS), []);
9241
+ const [s, setS] = React49.useState(getSession);
9242
+ React49.useEffect(() => subscribe(setS), []);
9137
9243
  return s;
9138
9244
  }
9139
9245
  var SessionKit = {
@@ -9166,7 +9272,7 @@ var SessionKit = {
9166
9272
  };
9167
9273
  function DemoSessionProvider({ children }) {
9168
9274
  const session = useSession();
9169
- const value = React48.useMemo(
9275
+ const value = React49.useMemo(
9170
9276
  () => ({
9171
9277
  user: session.user,
9172
9278
  roles: session.roles,
@@ -9182,11 +9288,11 @@ function DemoSessionProvider({ children }) {
9182
9288
  }),
9183
9289
  [session]
9184
9290
  );
9185
- return React48.createElement(SessionProvider, { value }, children);
9291
+ return React49.createElement(SessionProvider, { value }, children);
9186
9292
  }
9187
9293
 
9188
9294
  // src/kits/runtime.ts
9189
- import * as React49 from "react";
9295
+ import * as React50 from "react";
9190
9296
  var WIRED_ENDPOINTS = [
9191
9297
  // Nothing yet. Every id below would come from a real service:
9192
9298
  // "plan.get", "placements.list", …
@@ -9357,8 +9463,8 @@ var RuntimeKit = {
9357
9463
  };
9358
9464
  RuntimeKit.declare(FEATURE_NEEDS);
9359
9465
  function useRuntimeMode() {
9360
- const [m, setM] = React49.useState(RuntimeKit.getMode());
9361
- React49.useEffect(() => RuntimeKit.subscribe(setM), []);
9466
+ const [m, setM] = React50.useState(RuntimeKit.getMode());
9467
+ React50.useEffect(() => RuntimeKit.subscribe(setM), []);
9362
9468
  return m;
9363
9469
  }
9364
9470
  function useFeatureStatus(key) {
@@ -9441,12 +9547,12 @@ function RoadmapCard({ item, byKey, onOpenFlag }) {
9441
9547
  }
9442
9548
  function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
9443
9549
  const session = SessionKit.useSession();
9444
- const [project, setProject] = React50.useState("");
9445
- const [q, setQ] = React50.useState("");
9446
- const scrollRef = React50.useRef(null);
9447
- const nowRef = React50.useRef(null);
9448
- const rm = React50.useMemo(() => SessionKit.roadmap({ isComplete: RuntimeKit.isComplete }), [session]);
9449
- const byKey = React50.useMemo(() => {
9550
+ const [project, setProject] = React51.useState("");
9551
+ const [q, setQ] = React51.useState("");
9552
+ const scrollRef = React51.useRef(null);
9553
+ const nowRef = React51.useRef(null);
9554
+ const rm = React51.useMemo(() => SessionKit.roadmap({ isComplete: RuntimeKit.isComplete }), [session]);
9555
+ const byKey = React51.useMemo(() => {
9450
9556
  const m = {};
9451
9557
  rm.items.forEach((i) => {
9452
9558
  m[i.key] = i;
@@ -9455,7 +9561,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
9455
9561
  }, [rm]);
9456
9562
  const items = rm.items.filter((i) => (!project || i.project === project) && (!q || (i.label + " " + i.description + " " + (i.backend || "") + " " + i.key).toLowerCase().includes(q.toLowerCase())));
9457
9563
  const projects = [...new Set(rm.items.map((i) => i.project))];
9458
- React50.useEffect(() => {
9564
+ React51.useEffect(() => {
9459
9565
  let raf1 = 0, raf2 = 0;
9460
9566
  const place = () => {
9461
9567
  const box = scrollRef.current, mark = nowRef.current;
@@ -9529,7 +9635,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
9529
9635
  lastPhase = item.phase;
9530
9636
  const inPhase = items.filter((i) => i.phase === item.phase).length;
9531
9637
  const isBoundary = n === firstPending;
9532
- return /* @__PURE__ */ jsxs69(React50.Fragment, { children: [
9638
+ return /* @__PURE__ */ jsxs69(React51.Fragment, { children: [
9533
9639
  showPhase ? /* @__PURE__ */ jsxs69("div", { className: "fd-rm-era", children: [
9534
9640
  /* @__PURE__ */ jsxs69("span", { className: "fd-row", style: { gap: 8, flexWrap: "wrap", alignItems: "baseline" }, children: [
9535
9641
  /* @__PURE__ */ jsx75("span", { style: { fontWeight: 700 }, children: item.phase === 0 ? "Shipped" : "Phase " + item.phase + " \u2014 " + item.phaseName }),
@@ -9573,7 +9679,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
9573
9679
  }
9574
9680
 
9575
9681
  // src/components/platform/ComingSoon.tsx
9576
- import * as React51 from "react";
9682
+ import * as React52 from "react";
9577
9683
  import { createPortal as createPortal5 } from "react-dom";
9578
9684
  import { Fragment as Fragment20, jsx as jsx76, jsxs as jsxs70 } from "react/jsx-runtime";
9579
9685
  var BYPASS_STORE = "fd.soon.bypass.v1";
@@ -9591,8 +9697,8 @@ function writeBypassed(list) {
9591
9697
  }
9592
9698
  }
9593
9699
  function useBypass(key) {
9594
- const [on, setOn] = React51.useState(() => !!key && readBypassed().indexOf(key) >= 0);
9595
- React51.useEffect(() => {
9700
+ const [on, setOn] = React52.useState(() => !!key && readBypassed().indexOf(key) >= 0);
9701
+ React52.useEffect(() => {
9596
9702
  setOn(!!key && readBypassed().indexOf(key) >= 0);
9597
9703
  }, [key]);
9598
9704
  const set = (next) => {
@@ -9639,9 +9745,9 @@ function SoonCard({ label, detail, backend, eta, effort, onRoadmap, allowed, onB
9639
9745
  ] });
9640
9746
  }
9641
9747
  function useHoverCard(open) {
9642
- const anchor = React51.useRef(null);
9643
- const [pos, setPos] = React51.useState(null);
9644
- React51.useLayoutEffect(() => {
9748
+ const anchor = React52.useRef(null);
9749
+ const [pos, setPos] = React52.useState(null);
9750
+ React52.useLayoutEffect(() => {
9645
9751
  if (!open || !anchor.current) {
9646
9752
  setPos(null);
9647
9753
  return;
@@ -9768,10 +9874,10 @@ function ComingSoon({
9768
9874
  ] });
9769
9875
  }
9770
9876
  function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, onBypass, blur, tip, className, rest, children }) {
9771
- const [open, setOpen] = React51.useState(false);
9877
+ const [open, setOpen] = React52.useState(false);
9772
9878
  const [anchor, pos] = useHoverCard(open);
9773
9879
  const zIndex = useOverlayLayer(open);
9774
- const close = React51.useRef(null);
9880
+ const close = React52.useRef(null);
9775
9881
  const show = () => {
9776
9882
  if (close.current) {
9777
9883
  clearTimeout(close.current);
@@ -9787,7 +9893,7 @@ function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, o
9787
9893
  setOpen(false);
9788
9894
  }, 140);
9789
9895
  };
9790
- React51.useEffect(() => () => {
9896
+ React52.useEffect(() => () => {
9791
9897
  if (close.current) clearTimeout(close.current);
9792
9898
  }, []);
9793
9899
  return /* @__PURE__ */ jsxs70(
@@ -9958,14 +10064,14 @@ function PermissionHint({ perm, children }) {
9958
10064
  }
9959
10065
 
9960
10066
  // src/components/platform/ModeSwitch.tsx
9961
- import * as React52 from "react";
10067
+ import * as React53 from "react";
9962
10068
  import { Fragment as Fragment21, jsx as jsx78, jsxs as jsxs72 } from "react/jsx-runtime";
9963
10069
  function ModeSwitch({ canToggle = false, requestCount = 0, onClearLog, renderLog, onOpenSpec, summary }) {
9964
10070
  const mode2 = useRuntimeMode();
9965
- const [open, setOpen] = React52.useState(false);
10071
+ const [open, setOpen] = React53.useState(false);
9966
10072
  const zIndex = useOverlayLayer(open);
9967
- const ref = React52.useRef(null);
9968
- React52.useEffect(() => {
10073
+ const ref = React53.useRef(null);
10074
+ React53.useEffect(() => {
9969
10075
  if (!open) return;
9970
10076
  const away = (e) => {
9971
10077
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -10221,7 +10327,7 @@ function SaturationDistribution({ campuses = [], floorBand, floorScore, onSelect
10221
10327
  }
10222
10328
 
10223
10329
  // src/components/planner/MixGap.tsx
10224
- import * as React53 from "react";
10330
+ import * as React54 from "react";
10225
10331
  import { Fragment as Fragment22, jsx as jsx81, jsxs as jsxs75 } from "react/jsx-runtime";
10226
10332
  var MIX_GAP_LAYOUT = {
10227
10333
  trackHeight: 30,
@@ -10236,7 +10342,7 @@ function mixGapOffset(value, max) {
10236
10342
  }
10237
10343
  function MixGap({ rows = [], loading = false, className = "" }) {
10238
10344
  const max = Math.max(1, ...rows.flatMap((r) => [r.target, r.realized]));
10239
- const [hover, setHover] = React53.useState(null);
10345
+ const [hover, setHover] = React54.useState(null);
10240
10346
  const toneOf = (gap) => gap >= -1 ? "ok" : gap >= -4 ? "warn" : "danger";
10241
10347
  const TONE = { ok: "var(--ok-solid)", warn: "var(--warn-solid)", danger: "var(--danger-solid)" };
10242
10348
  const { trackHeight, barHeight, targetWidth, labelGap } = MIX_GAP_LAYOUT;
@@ -10379,7 +10485,7 @@ function ChannelContribution({
10379
10485
  }
10380
10486
 
10381
10487
  // src/components/planner/BudgetReallocator.tsx
10382
- import * as React54 from "react";
10488
+ import * as React55 from "react";
10383
10489
  import { Fragment as Fragment23, jsx as jsx83, jsxs as jsxs77 } from "react/jsx-runtime";
10384
10490
  var bandFor = (crp) => crp >= 200 ? "dominant" : crp >= 100 ? "strong" : crp >= 50 ? "adequate" : "weak";
10385
10491
  function BudgetReallocator({
@@ -10395,8 +10501,8 @@ function BudgetReallocator({
10395
10501
  onCancel,
10396
10502
  className = ""
10397
10503
  }) {
10398
- const [draft, setDraft] = React54.useState(spend);
10399
- React54.useEffect(() => setDraft(spend), [spend]);
10504
+ const [draft, setDraft] = React55.useState(spend);
10505
+ React55.useEffect(() => setDraft(spend), [spend]);
10400
10506
  const dirty = draft !== spend;
10401
10507
  const nextCrp = scoreFor ? scoreFor(draft) : crp;
10402
10508
  const nextBand = bandFor(nextCrp);
@@ -10411,10 +10517,10 @@ function BudgetReallocator({
10411
10517
  setDraft(spend);
10412
10518
  if (onCancel) onCancel();
10413
10519
  };
10414
- const trackRef = React54.useRef(null);
10415
- const chipRef = React54.useRef(null);
10416
- const [chipLeft, setChipLeft] = React54.useState(null);
10417
- React54.useLayoutEffect(() => {
10520
+ const trackRef = React55.useRef(null);
10521
+ const chipRef = React55.useRef(null);
10522
+ const [chipLeft, setChipLeft] = React55.useState(null);
10523
+ React55.useLayoutEffect(() => {
10418
10524
  const track = trackRef.current;
10419
10525
  if (!dirty || !track) {
10420
10526
  setChipLeft(null);
@@ -10601,7 +10707,7 @@ function ImportanceTag({ level, showLabel = true, size = "sm", className = "" })
10601
10707
  }
10602
10708
 
10603
10709
  // src/components/planner/PreferenceMeter.tsx
10604
- import * as React55 from "react";
10710
+ import * as React56 from "react";
10605
10711
  import { jsx as jsx86, jsxs as jsxs80 } from "react/jsx-runtime";
10606
10712
  function preferenceScore(criteria) {
10607
10713
  const earned = criteria.reduce((n, c) => n + (Number(c.earned) || 0), 0);
@@ -10631,7 +10737,7 @@ function PreferenceMeter({
10631
10737
  className = ""
10632
10738
  }) {
10633
10739
  const value = score == null ? preferenceScore(criteria) : score;
10634
- const segments = React55.useMemo(() => preferenceSegments(criteria), [criteria]);
10740
+ const segments = React56.useMemo(() => preferenceSegments(criteria), [criteria]);
10635
10741
  return /* @__PURE__ */ jsx86(
10636
10742
  ScoreMeter,
10637
10743
  {
@@ -10670,10 +10776,10 @@ function PreferenceMeter({
10670
10776
  }
10671
10777
 
10672
10778
  // src/components/chat/AgentChatPanel.tsx
10673
- import * as React61 from "react";
10779
+ import * as React62 from "react";
10674
10780
 
10675
10781
  // src/components/chat/chatEngine.ts
10676
- import * as React56 from "react";
10782
+ import * as React57 from "react";
10677
10783
  var CHAT_UNAVAILABLE = "chat_unavailable";
10678
10784
  var JOB_PENDING = ["queued", "running"];
10679
10785
  var JOB_SUCCESS = ["completed", "recovered"];
@@ -10727,22 +10833,22 @@ function useChatEngine(opts) {
10727
10833
  onClear,
10728
10834
  onFeedback
10729
10835
  } = opts || {};
10730
- const [status, setStatus] = React56.useState("idle");
10731
- const [threadId, setThreadId] = React56.useState(null);
10732
- const [messages, setMessages] = React56.useState([]);
10733
- const [queue, setQueue] = React56.useState([]);
10734
- const [fatal, setFatal] = React56.useState(null);
10735
- const [busy, setBusy] = React56.useState(false);
10736
- const [turnStartedAt, setTurnStartedAt] = React56.useState(null);
10737
- const listRef = React56.useRef([]);
10738
- const queueRef = React56.useRef([]);
10739
- const busyRef = React56.useRef(false);
10740
- const stoppedRef = React56.useRef(false);
10741
- const abortRef = React56.useRef(null);
10742
- const serverCount = React56.useRef(0);
10743
- const threadRef = React56.useRef(null);
10744
- const mounted = React56.useRef(true);
10745
- React56.useEffect(() => {
10836
+ const [status, setStatus] = React57.useState("idle");
10837
+ const [threadId, setThreadId] = React57.useState(null);
10838
+ const [messages, setMessages] = React57.useState([]);
10839
+ const [queue, setQueue] = React57.useState([]);
10840
+ const [fatal, setFatal] = React57.useState(null);
10841
+ const [busy, setBusy] = React57.useState(false);
10842
+ const [turnStartedAt, setTurnStartedAt] = React57.useState(null);
10843
+ const listRef = React57.useRef([]);
10844
+ const queueRef = React57.useRef([]);
10845
+ const busyRef = React57.useRef(false);
10846
+ const stoppedRef = React57.useRef(false);
10847
+ const abortRef = React57.useRef(null);
10848
+ const serverCount = React57.useRef(0);
10849
+ const threadRef = React57.useRef(null);
10850
+ const mounted = React57.useRef(true);
10851
+ React57.useEffect(() => {
10746
10852
  mounted.current = true;
10747
10853
  return () => {
10748
10854
  mounted.current = false;
@@ -10764,14 +10870,14 @@ function useChatEngine(opts) {
10764
10870
  }
10765
10871
  return false;
10766
10872
  };
10767
- const loadThread = React56.useCallback(async (id) => {
10873
+ const loadThread = React57.useCallback(async (id) => {
10768
10874
  const data = await apiAdapter.getThread(id);
10769
10875
  const list = [...data && data.messages || []];
10770
10876
  serverCount.current = list.length;
10771
10877
  commit(list);
10772
10878
  return list;
10773
10879
  }, [apiAdapter]);
10774
- React56.useEffect(() => {
10880
+ React57.useEffect(() => {
10775
10881
  if (!apiAdapter) {
10776
10882
  setStatus("idle");
10777
10883
  setFatal(null);
@@ -10984,7 +11090,7 @@ function useChatEngine(opts) {
10984
11090
  await dispatchTurn(turn);
10985
11091
  }
10986
11092
  }
10987
- const send = React56.useCallback((text, attachments) => {
11093
+ const send = React57.useCallback((text, attachments) => {
10988
11094
  const body = (text || "").trim();
10989
11095
  if (!body && !(attachments && attachments.length)) return;
10990
11096
  if (status === "disconnected") return;
@@ -10994,7 +11100,7 @@ function useChatEngine(opts) {
10994
11100
  stoppedRef.current = false;
10995
11101
  drain();
10996
11102
  }, [status]);
10997
- const stop = React56.useCallback(() => {
11103
+ const stop = React57.useCallback(() => {
10998
11104
  stoppedRef.current = true;
10999
11105
  const ac = abortRef.current;
11000
11106
  if (ac) {
@@ -11013,11 +11119,11 @@ function useChatEngine(opts) {
11013
11119
  store.del(STORAGE_PREFIX + threadRef.current);
11014
11120
  }
11015
11121
  }, [apiAdapter]);
11016
- const removeQueued = React56.useCallback((id) => {
11122
+ const removeQueued = React57.useCallback((id) => {
11017
11123
  queueRef.current = queueRef.current.filter((t) => t.id !== id);
11018
11124
  setQueue(queueRef.current.slice());
11019
11125
  }, []);
11020
- const retry = React56.useCallback(() => {
11126
+ const retry = React57.useCallback(() => {
11021
11127
  const list = listRef.current;
11022
11128
  let at = -1;
11023
11129
  for (let i = list.length - 1; i >= 0; i--) if (list[i].role === "user") {
@@ -11033,19 +11139,19 @@ function useChatEngine(opts) {
11033
11139
  setQueue(queueRef.current.slice());
11034
11140
  drain();
11035
11141
  }, []);
11036
- const clear = React56.useCallback(() => {
11142
+ const clear = React57.useCallback(() => {
11037
11143
  commit([]);
11038
11144
  serverCount.current = 0;
11039
11145
  queueRef.current = [];
11040
11146
  setQueue([]);
11041
11147
  onClear && onClear();
11042
11148
  }, [onClear]);
11043
- const setFeedback = React56.useCallback((id, value) => {
11149
+ const setFeedback = React57.useCallback((id, value) => {
11044
11150
  patch(id, (m) => ({ feedback: m.feedback === value ? null : value }));
11045
11151
  const msg = listRef.current.find((m) => m.id === id);
11046
11152
  onFeedback && onFeedback({ message: msg, feedback: msg ? msg.feedback : value });
11047
11153
  }, [onFeedback]);
11048
- const reload = React56.useCallback(async () => {
11154
+ const reload = React57.useCallback(async () => {
11049
11155
  if (!threadRef.current) return;
11050
11156
  setStatus("loading");
11051
11157
  try {
@@ -11083,10 +11189,10 @@ var ChatKit = {
11083
11189
  };
11084
11190
 
11085
11191
  // src/components/chat/ChatTranscript.tsx
11086
- import * as React58 from "react";
11192
+ import * as React59 from "react";
11087
11193
 
11088
11194
  // src/components/chat/ChatTurn.tsx
11089
- import * as React57 from "react";
11195
+ import * as React58 from "react";
11090
11196
  import { jsx as jsx87, jsxs as jsxs81 } from "react/jsx-runtime";
11091
11197
  function JsonView({ value }) {
11092
11198
  let text;
@@ -11123,7 +11229,7 @@ function PacketCard({ packet, schema, render, onApply, applied }) {
11123
11229
  ] });
11124
11230
  }
11125
11231
  function ThinkingBlock({ text, durationMs, streaming, defaultOpen = false }) {
11126
- const [open, setOpen] = React57.useState(defaultOpen);
11232
+ const [open, setOpen] = React58.useState(defaultOpen);
11127
11233
  if (!text) return null;
11128
11234
  return /* @__PURE__ */ jsxs81("div", { className: "fdc-think" + (open ? " is-open" : ""), children: [
11129
11235
  /* @__PURE__ */ jsxs81("button", { type: "button", className: "fdc-think-head", onClick: () => setOpen((v) => !v), "aria-expanded": open, children: [
@@ -11140,7 +11246,7 @@ function ThinkingBlock({ text, durationMs, streaming, defaultOpen = false }) {
11140
11246
  ] });
11141
11247
  }
11142
11248
  function Citations({ items = [], onOpen }) {
11143
- const [open, setOpen] = React57.useState(false);
11249
+ const [open, setOpen] = React58.useState(false);
11144
11250
  if (!items.length) return null;
11145
11251
  return /* @__PURE__ */ jsxs81("div", { className: "fdc-cites", children: [
11146
11252
  /* @__PURE__ */ jsxs81("button", { type: "button", className: "fdc-cites-head", onClick: () => setOpen((v) => !v), "aria-expanded": open, children: [
@@ -11166,7 +11272,7 @@ function clampText(text, max) {
11166
11272
  return (at > max * 0.6 ? cut.slice(0, at) : cut).trimEnd() + "\u2026";
11167
11273
  }
11168
11274
  function MessageBody({ message: m, ctx }) {
11169
- const [expanded, setExpanded] = React57.useState(false);
11275
+ const [expanded, setExpanded] = React58.useState(false);
11170
11276
  const isUser = m.role === "user";
11171
11277
  const raw = m.text || "";
11172
11278
  const clamped = !expanded && !m.streaming ? clampText(raw, ctx.maxVisibleChars) : null;
@@ -11233,9 +11339,9 @@ function MessageBody({ message: m, ctx }) {
11233
11339
  ] });
11234
11340
  }
11235
11341
  function useCopyRun() {
11236
- const [done, setDone] = React57.useState(false);
11237
- const t = React57.useRef(null);
11238
- React57.useEffect(() => () => {
11342
+ const [done, setDone] = React58.useState(false);
11343
+ const t = React58.useRef(null);
11344
+ React58.useEffect(() => () => {
11239
11345
  if (t.current) clearTimeout(t.current);
11240
11346
  }, []);
11241
11347
  return [done, (text) => {
@@ -11262,7 +11368,7 @@ function RunActions({ group, ctx }) {
11262
11368
  ] }),
11263
11369
  isAssistant && ctx.onRetry ? /* @__PURE__ */ jsx87("button", { type: "button", className: "fdc-act", onClick: ctx.onRetry, "aria-label": "Retry this turn", children: /* @__PURE__ */ jsx87("i", { className: "ph ph-arrow-clockwise", "aria-hidden": "true" }) }) : null,
11264
11370
  group.role === "user" && ctx.onEdit ? /* @__PURE__ */ jsx87("button", { type: "button", className: "fdc-act", onClick: () => ctx.onEdit(last), "aria-label": "Edit and resend", children: /* @__PURE__ */ jsx87("i", { className: "ph ph-pencil-simple", "aria-hidden": "true" }) }) : null,
11265
- isAssistant && ctx.onFeedback ? /* @__PURE__ */ jsxs81(React57.Fragment, { children: [
11371
+ isAssistant && ctx.onFeedback ? /* @__PURE__ */ jsxs81(React58.Fragment, { children: [
11266
11372
  /* @__PURE__ */ jsx87("button", { type: "button", className: "fdc-act" + (fb === "up" ? " is-on" : ""), onClick: () => ctx.onFeedback(last.id, "up"), "aria-pressed": fb === "up", "aria-label": "Good response", children: /* @__PURE__ */ jsx87("i", { className: "ph ph-thumbs-up", "aria-hidden": "true" }) }),
11267
11373
  /* @__PURE__ */ jsx87("button", { type: "button", className: "fdc-act" + (fb === "down" ? " is-on" : ""), onClick: () => ctx.onFeedback(last.id, "down"), "aria-pressed": fb === "down", "aria-label": "Bad response", children: /* @__PURE__ */ jsx87("i", { className: "ph ph-thumbs-down", "aria-hidden": "true" }) })
11268
11374
  ] }) : null,
@@ -11357,10 +11463,10 @@ function ChatTranscript({
11357
11463
  renderEmpty,
11358
11464
  className = ""
11359
11465
  }) {
11360
- const scroller = React58.useRef(null);
11361
- const stick = React58.useRef(true);
11362
- const [pill, setPill] = React58.useState(0);
11363
- const seen = React58.useRef(0);
11466
+ const scroller = React59.useRef(null);
11467
+ const stick = React59.useRef(true);
11468
+ const [pill, setPill] = React59.useState(0);
11469
+ const seen = React59.useRef(0);
11364
11470
  const toBottom = (smooth) => {
11365
11471
  const el = scroller.current;
11366
11472
  if (!el) return;
@@ -11379,7 +11485,7 @@ function ChatTranscript({
11379
11485
  seen.current = messages.length;
11380
11486
  }
11381
11487
  };
11382
- React58.useLayoutEffect(() => {
11488
+ React59.useLayoutEffect(() => {
11383
11489
  const el = scroller.current;
11384
11490
  if (!el) return;
11385
11491
  if (stick.current) {
@@ -11387,7 +11493,7 @@ function ChatTranscript({
11387
11493
  seen.current = messages.length;
11388
11494
  } else setPill(Math.max(0, messages.length - seen.current));
11389
11495
  }, [messages]);
11390
- React58.useEffect(() => {
11496
+ React59.useEffect(() => {
11391
11497
  const el = scroller.current;
11392
11498
  const inner = el && el.firstChild;
11393
11499
  if (!el || !inner || typeof ResizeObserver === "undefined") return;
@@ -11397,7 +11503,7 @@ function ChatTranscript({
11397
11503
  ro.observe(inner);
11398
11504
  return () => ro.disconnect();
11399
11505
  }, []);
11400
- const groups = React58.useMemo(() => groupMessages(messages), [messages]);
11506
+ const groups = React59.useMemo(() => groupMessages(messages), [messages]);
11401
11507
  const empty = !messages.length && status === "ready";
11402
11508
  return /* @__PURE__ */ jsxs82("div", { className: ["fdc-scroll", className].filter(Boolean).join(" "), ref: scroller, onScroll, children: [
11403
11509
  /* @__PURE__ */ jsxs82("div", { className: "fdc-log", role: "log", "aria-label": "Conversation", children: [
@@ -11421,7 +11527,7 @@ function ChatTranscript({
11421
11527
  const prev = groups[i - 1];
11422
11528
  const k = dayKey(g.messages[0].timestamp);
11423
11529
  const showDay = !!k && (!prev || dayKey(prev.messages[0].timestamp) !== k);
11424
- return /* @__PURE__ */ jsxs82(React58.Fragment, { children: [
11530
+ return /* @__PURE__ */ jsxs82(React59.Fragment, { children: [
11425
11531
  showDay ? /* @__PURE__ */ jsx88("div", { className: "fdc-day", children: /* @__PURE__ */ jsx88("span", { children: dayLabel(k) }) }) : null,
11426
11532
  /* @__PURE__ */ jsx88(ChatTurn, { group: g, ctx })
11427
11533
  ] }, g.key || i);
@@ -11438,7 +11544,7 @@ function ChatTranscript({
11438
11544
  var TranscriptKit = { groupMessages };
11439
11545
 
11440
11546
  // src/components/chat/ChatComposer.tsx
11441
- import * as React59 from "react";
11547
+ import * as React60 from "react";
11442
11548
  import { jsx as jsx89, jsxs as jsxs83 } from "react/jsx-runtime";
11443
11549
  function ChatComposer({
11444
11550
  onSubmit,
@@ -11466,17 +11572,17 @@ function ChatComposer({
11466
11572
  onReject,
11467
11573
  onOpenAttachment
11468
11574
  }) {
11469
- const [text, setText] = React59.useState(draft || "");
11470
- const [trigger, setTrigger] = React59.useState(null);
11471
- const [mentionItems, setMentionItems] = React59.useState([]);
11472
- const [listening, setListening] = React59.useState(false);
11473
- const [notice, setNotice] = React59.useState(null);
11474
- const editor = React59.useRef(null);
11475
- const wrap = React59.useRef(null);
11476
- const stopVoice = React59.useRef(null);
11575
+ const [text, setText] = React60.useState(draft || "");
11576
+ const [trigger, setTrigger] = React60.useState(null);
11577
+ const [mentionItems, setMentionItems] = React60.useState([]);
11578
+ const [listening, setListening] = React60.useState(false);
11579
+ const [notice, setNotice] = React60.useState(null);
11580
+ const editor = React60.useRef(null);
11581
+ const wrap = React60.useRef(null);
11582
+ const stopVoice = React60.useRef(null);
11477
11583
  const staged = useStagedFiles(fileUploadHandler, { onError: () => {
11478
11584
  } });
11479
- React59.useEffect(() => {
11585
+ React60.useEffect(() => {
11480
11586
  if (draft != null && draft !== text) setText(draft);
11481
11587
  }, [draft]);
11482
11588
  const change = (v) => {
@@ -11512,7 +11618,7 @@ function ChatComposer({
11512
11618
  e.preventDefault();
11513
11619
  staged.add(found.files);
11514
11620
  };
11515
- React59.useEffect(() => {
11621
+ React60.useEffect(() => {
11516
11622
  if (!trigger || trigger.type !== "mention" || !mentionSources) {
11517
11623
  setMentionItems([]);
11518
11624
  return;
@@ -11530,7 +11636,7 @@ function ChatComposer({
11530
11636
  const q = (trigger.query || "").toLowerCase();
11531
11637
  setMentionItems(mentionSources.filter((m) => !q || (m.label + " " + (m.description || "")).toLowerCase().includes(q)));
11532
11638
  }, [trigger, mentionSources]);
11533
- const slashItems = React59.useMemo(() => {
11639
+ const slashItems = React60.useMemo(() => {
11534
11640
  if (!trigger || trigger.type !== "slash" || !slashCommands) return [];
11535
11641
  const q = (trigger.query || "").toLowerCase();
11536
11642
  return slashCommands.filter((c) => !q || (c.id + " " + c.label + " " + (c.description || "")).toLowerCase().includes(q));
@@ -11704,12 +11810,12 @@ function ChatComposer({
11704
11810
  }
11705
11811
 
11706
11812
  // src/components/chat/ChatSessionBar.tsx
11707
- import * as React60 from "react";
11813
+ import * as React61 from "react";
11708
11814
  import { jsx as jsx90, jsxs as jsxs84 } from "react/jsx-runtime";
11709
11815
  var compact2 = meterFormats.compact;
11710
11816
  function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extras }) {
11711
- const [open, setOpen] = React60.useState(false);
11712
- const anchor = React60.useRef(null);
11817
+ const [open, setOpen] = React61.useState(false);
11818
+ const anchor = React61.useRef(null);
11713
11819
  const stats = sessionStats || null;
11714
11820
  const cu = contextUsage || null;
11715
11821
  const limits = usageLimits || null;
@@ -11724,7 +11830,7 @@ function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extr
11724
11830
  if (stats && stats.runningTasks) bits.push(stats.runningTasks + " running task" + (stats.runningTasks === 1 ? "" : "s"));
11725
11831
  if (cu) bits.push(pct + "% context");
11726
11832
  const expandable = !!(cu || limits);
11727
- return /* @__PURE__ */ jsxs84(React60.Fragment, { children: [
11833
+ return /* @__PURE__ */ jsxs84(React61.Fragment, { children: [
11728
11834
  /* @__PURE__ */ jsxs84("div", { className: "fdc-bar", children: [
11729
11835
  /* @__PURE__ */ jsxs84(
11730
11836
  "button",
@@ -11865,7 +11971,7 @@ function ModelControls({
11865
11971
  ] })
11866
11972
  });
11867
11973
  }
11868
- return /* @__PURE__ */ jsxs84(React60.Fragment, { children: [
11974
+ return /* @__PURE__ */ jsxs84(React61.Fragment, { children: [
11869
11975
  /* @__PURE__ */ jsx90(
11870
11976
  MenuButton,
11871
11977
  {
@@ -11982,11 +12088,11 @@ function AgentChatPanel({
11982
12088
  onFeedback,
11983
12089
  onEditMessage
11984
12090
  }) {
11985
- const [panelWidth, setPanelWidth] = React61.useState(width);
11986
- const [applied, setApplied] = React61.useState({});
11987
- const [draft, setDraft] = React61.useState("");
11988
- const dragging = React61.useRef(null);
11989
- React61.useEffect(() => setPanelWidth(width), [width]);
12091
+ const [panelWidth, setPanelWidth] = React62.useState(width);
12092
+ const [applied, setApplied] = React62.useState({});
12093
+ const [draft, setDraft] = React62.useState("");
12094
+ const dragging = React62.useRef(null);
12095
+ React62.useEffect(() => setPanelWidth(width), [width]);
11990
12096
  const engine = useChatEngine({
11991
12097
  contextType,
11992
12098
  contextId,
@@ -12190,7 +12296,7 @@ function AgentChatPanel({
12190
12296
  }
12191
12297
 
12192
12298
  // src/components/files/FileViewer.tsx
12193
- import * as React62 from "react";
12299
+ import * as React63 from "react";
12194
12300
  import { jsx as jsx92, jsxs as jsxs86 } from "react/jsx-runtime";
12195
12301
  function previewKindOf(file) {
12196
12302
  const mime = (file.mime || "").toLowerCase();
@@ -12247,13 +12353,13 @@ function parseDelimited(text, delimiter) {
12247
12353
  return rows;
12248
12354
  }
12249
12355
  function usePreview(file, kind, resolveUrl) {
12250
- const [state, setState] = React62.useState({ status: "loading" });
12251
- const [attempt, setAttempt] = React62.useState(0);
12252
- const resolveRef = React62.useRef(resolveUrl);
12253
- React62.useEffect(() => {
12356
+ const [state, setState] = React63.useState({ status: "loading" });
12357
+ const [attempt, setAttempt] = React63.useState(0);
12358
+ const resolveRef = React63.useRef(resolveUrl);
12359
+ React63.useEffect(() => {
12254
12360
  resolveRef.current = resolveUrl;
12255
12361
  }, [resolveUrl]);
12256
- React62.useEffect(() => {
12362
+ React63.useEffect(() => {
12257
12363
  if (kind === "none") {
12258
12364
  setState({ status: "ready" });
12259
12365
  return;
@@ -12283,7 +12389,7 @@ function usePreview(file, kind, resolveUrl) {
12283
12389
  if (ac) ac.abort();
12284
12390
  };
12285
12391
  }, [file.id, kind, attempt]);
12286
- return { ...state, retry: React62.useCallback(() => setAttempt((a) => a + 1), []) };
12392
+ return { ...state, retry: React63.useCallback(() => setAttempt((a) => a + 1), []) };
12287
12393
  }
12288
12394
  function PreviewBody({ file, kind, state }) {
12289
12395
  if (kind === "image") return /* @__PURE__ */ jsx92("img", { className: "fd-fv-image", src: state.url, alt: file.name || "Preview" });
@@ -12328,13 +12434,13 @@ function PreviewBody({ file, kind, state }) {
12328
12434
  ] });
12329
12435
  }
12330
12436
  function FileViewer({ file, resolveUrl, sidePanel, sidePanelLabel = "Details", onClose, actions, className = "" }) {
12331
- const kind = React62.useMemo(() => previewKindOf(file), [file]);
12437
+ const kind = React63.useMemo(() => previewKindOf(file), [file]);
12332
12438
  const preview = usePreview(file, kind, resolveUrl);
12333
- const [tab, setTab] = React62.useState("preview");
12334
- const [downloading, setDownloading] = React62.useState(false);
12335
- const [downloadError, setDownloadError] = React62.useState(null);
12439
+ const [tab, setTab] = React63.useState("preview");
12440
+ const [downloading, setDownloading] = React63.useState(false);
12441
+ const [downloadError, setDownloadError] = React63.useState(null);
12336
12442
  const hasSide = sidePanel != null;
12337
- React62.useEffect(() => {
12443
+ React63.useEffect(() => {
12338
12444
  setTab("preview");
12339
12445
  }, [file.id]);
12340
12446
  const download = () => {
@@ -12401,7 +12507,7 @@ function FileViewer({ file, resolveUrl, sidePanel, sidePanelLabel = "Details", o
12401
12507
  }
12402
12508
 
12403
12509
  // src/components/files/FileBrowser.tsx
12404
- import * as React63 from "react";
12510
+ import * as React64 from "react";
12405
12511
  import { Fragment as Fragment27, jsx as jsx93, jsxs as jsxs87 } from "react/jsx-runtime";
12406
12512
  function childrenOf(folders, parentId) {
12407
12513
  return folders.filter((f) => f.parentId === parentId).sort((a, b) => a.name.localeCompare(b.name));
@@ -12472,10 +12578,10 @@ function FolderPickerMenu({
12472
12578
  destinations,
12473
12579
  onPick
12474
12580
  }) {
12475
- const [open, setOpen] = React63.useState(false);
12581
+ const [open, setOpen] = React64.useState(false);
12476
12582
  const zIndex = useOverlayLayer(open);
12477
- const ref = React63.useRef(null);
12478
- React63.useEffect(() => {
12583
+ const ref = React64.useRef(null);
12584
+ React64.useEffect(() => {
12479
12585
  if (!open) return;
12480
12586
  const away = (e) => {
12481
12587
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -12527,8 +12633,8 @@ function FolderPickerMenu({
12527
12633
  ] });
12528
12634
  }
12529
12635
  function RowShell({ icon, name, meta, onOpen, draggableId, isDropTarget, onDropPayload, actions, className = "" }) {
12530
- const [over, setOver] = React63.useState(false);
12531
- const dragCounter = React63.useRef(0);
12636
+ const [over, setOver] = React64.useState(false);
12637
+ const dragCounter = React64.useRef(0);
12532
12638
  const dropHandlers = isDropTarget ? {
12533
12639
  onDragEnter: (e) => {
12534
12640
  if (!hasInternalPayload(e)) return;
@@ -12576,8 +12682,8 @@ function RowShell({ icon, name, meta, onOpen, draggableId, isDropTarget, onDropP
12576
12682
  }
12577
12683
  function TreeNode({ folder, depth, folders, currentFolderId, onNavigate, onMoveInto, canMove }) {
12578
12684
  const kids = childrenOf(folders, folder.id);
12579
- const [expanded, setExpanded] = React63.useState(() => currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId));
12580
- React63.useEffect(() => {
12685
+ const [expanded, setExpanded] = React64.useState(() => currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId));
12686
+ React64.useEffect(() => {
12581
12687
  if (currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId)) setExpanded(true);
12582
12688
  }, [currentFolderId, folder.id, folders]);
12583
12689
  const isCurrent = folder.id === currentFolderId;
@@ -12643,15 +12749,15 @@ function FileBrowser({
12643
12749
  const canUpload = perm.canUpload !== false && !!onUpload;
12644
12750
  const canRemoveFile = perm.canRemoveFile !== false && !!onRemoveFile;
12645
12751
  const canDeleteFolder = perm.canDeleteFolder !== false && !!onDeleteFolder;
12646
- const [createOpen, setCreateOpen] = React63.useState(false);
12647
- const [createName, setCreateName] = React63.useState("");
12648
- const [renameTarget, setRenameTarget] = React63.useState(null);
12649
- const [renameValue, setRenameValue] = React63.useState("");
12650
- const [deleteTarget, setDeleteTarget] = React63.useState(null);
12651
- const uploadInputRef = React63.useRef(null);
12652
- const crumbFolders = React63.useMemo(() => ancestorsOf(folders, currentFolderId), [folders, currentFolderId]);
12653
- const subfolders = React63.useMemo(() => childrenOf(folders, currentFolderId), [folders, currentFolderId]);
12654
- const currentFiles = React63.useMemo(
12752
+ const [createOpen, setCreateOpen] = React64.useState(false);
12753
+ const [createName, setCreateName] = React64.useState("");
12754
+ const [renameTarget, setRenameTarget] = React64.useState(null);
12755
+ const [renameValue, setRenameValue] = React64.useState("");
12756
+ const [deleteTarget, setDeleteTarget] = React64.useState(null);
12757
+ const uploadInputRef = React64.useRef(null);
12758
+ const crumbFolders = React64.useMemo(() => ancestorsOf(folders, currentFolderId), [folders, currentFolderId]);
12759
+ const subfolders = React64.useMemo(() => childrenOf(folders, currentFolderId), [folders, currentFolderId]);
12760
+ const currentFiles = React64.useMemo(
12655
12761
  () => files.filter((f) => f.folderId === currentFolderId).sort((a, b) => a.name.localeCompare(b.name)),
12656
12762
  [files, currentFolderId]
12657
12763
  );
@@ -12883,8 +12989,8 @@ function BreadcrumbCrumb({
12883
12989
  isDropTarget,
12884
12990
  onDropPayload
12885
12991
  }) {
12886
- const [over, setOver] = React63.useState(false);
12887
- const dragCounter = React63.useRef(0);
12992
+ const [over, setOver] = React64.useState(false);
12993
+ const dragCounter = React64.useRef(0);
12888
12994
  const dropHandlers = isDropTarget ? {
12889
12995
  onDragEnter: (e) => {
12890
12996
  if (!hasInternalPayload(e)) return;
@@ -12913,9 +13019,27 @@ function BreadcrumbCrumb({
12913
13019
  }
12914
13020
 
12915
13021
  // src/kits/query.ts
12916
- import * as React64 from "react";
13022
+ import * as React65 from "react";
13023
+ var isMultiSelectShaped = (v) => !!v && typeof v === "object" && Array.isArray(v.values);
13024
+ var isRangeShaped = (v) => !!v && typeof v === "object" && ("min" in v || "max" in v);
13025
+ var isDateShaped = (v) => !!v && typeof v === "object" && "kind" in v;
13026
+ function isActiveFilterValue(v) {
13027
+ if (v == null || v === "" || v === false) return false;
13028
+ if (Array.isArray(v)) return v.length > 0;
13029
+ if (isMultiSelectShaped(v)) return v.values.length > 0;
13030
+ if (isRangeShaped(v)) return v.min != null || v.max != null;
13031
+ if (isDateShaped(v)) return v.kind === "withinDays" ? v.days != null : !!(v.from && v.to);
13032
+ return true;
13033
+ }
12917
13034
  function eqFilter(get2) {
12918
- return (row, value) => Array.isArray(value) ? value.includes(get2(row)) : get2(row) === value;
13035
+ return (row, value) => {
13036
+ if (Array.isArray(value)) return value.includes(get2(row));
13037
+ if (isMultiSelectShaped(value)) {
13038
+ const hit = value.values.some((o) => o.value === get2(row));
13039
+ return value.operator === "notIn" ? !hit : hit;
13040
+ }
13041
+ return get2(row) === value;
13042
+ };
12919
13043
  }
12920
13044
  function anyOfFilter(get2) {
12921
13045
  return (row, value) => {
@@ -12926,10 +13050,15 @@ function anyOfFilter(get2) {
12926
13050
  function rangeFilter(get2) {
12927
13051
  return (row, value) => {
12928
13052
  const n = get2(row);
12929
- const bounds = Array.isArray(value) ? value : [0, Number.POSITIVE_INFINITY];
12930
- const lo = Number(bounds[0] ?? 0);
12931
- const hiRaw = bounds[1];
12932
- const hi = hiRaw == null ? Number.POSITIVE_INFINITY : Number(hiRaw);
13053
+ let lo = 0;
13054
+ let hi = Number.POSITIVE_INFINITY;
13055
+ if (Array.isArray(value)) {
13056
+ lo = Number(value[0] ?? 0);
13057
+ hi = value[1] == null ? Number.POSITIVE_INFINITY : Number(value[1]);
13058
+ } else if (isRangeShaped(value)) {
13059
+ lo = value.min ?? 0;
13060
+ hi = value.max ?? Number.POSITIVE_INFINITY;
13061
+ }
12933
13062
  return n >= lo && (hi === Number.POSITIVE_INFINITY || n <= hi);
12934
13063
  };
12935
13064
  }
@@ -12945,22 +13074,22 @@ function compare(a, b, dir) {
12945
13074
  var API = null;
12946
13075
  var PREFS = null;
12947
13076
  function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
12948
- const [query, setQuery] = React64.useState(() => {
13077
+ const [query, setQuery] = React65.useState(() => {
12949
13078
  const store = PREFS || window.PlannerPrefs;
12950
13079
  const saved = prefsKey && store ? store.getTable(prefsKey) : {};
12951
13080
  return { ...DEFAULTS, ...defaults || {}, ...saved.pageSize ? { pageSize: saved.pageSize } : {}, ...saved.sort ? { sort: saved.sort, dir: saved.dir || "desc" } : {} };
12952
13081
  });
12953
- const savePref = React64.useCallback((patch2) => {
13082
+ const savePref = React65.useCallback((patch2) => {
12954
13083
  const store = PREFS || window.PlannerPrefs;
12955
13084
  if (prefsKey && store) store.setTable(prefsKey, patch2);
12956
13085
  }, [prefsKey]);
12957
- const [res, setRes] = React64.useState(null);
12958
- const [loading, setLoading] = React64.useState(true);
12959
- const seq2 = React64.useRef(0);
13086
+ const [res, setRes] = React65.useState(null);
13087
+ const [loading, setLoading] = React65.useState(true);
13088
+ const seq2 = React65.useRef(0);
12960
13089
  const depKey = (deps || []).join("|");
12961
13090
  const paramKey = JSON.stringify(params || {});
12962
13091
  const queryKey = JSON.stringify(query);
12963
- React64.useEffect(() => {
13092
+ React65.useEffect(() => {
12964
13093
  const id = ++seq2.current;
12965
13094
  setLoading(true);
12966
13095
  const t = setTimeout(() => {
@@ -12996,7 +13125,7 @@ function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
12996
13125
  loading,
12997
13126
  /** True only on the very first load — later loads keep showing stale rows. */
12998
13127
  initialLoading: loading && !res,
12999
- activeFilterCount: Object.values(query.filters).filter((v) => !(v == null || v === "" || v === false)).length,
13128
+ activeFilterCount: Object.values(query.filters).filter(isActiveFilterValue).length,
13000
13129
  setPage: (page) => patch({ page }, false),
13001
13130
  setPageSize: (pageSize) => {
13002
13131
  savePref({ pageSize });
@@ -13059,7 +13188,6 @@ var QueryKit = {
13059
13188
  const Q = { ...DEFAULTS, ...query || {} };
13060
13189
  const c = cfg;
13061
13190
  const filters = Q.filters || {};
13062
- const active = (v) => !(v == null || v === "" || v === false || Array.isArray(v) && !v.length);
13063
13191
  let out = rows;
13064
13192
  const term = (Q.q || "").trim().toLowerCase();
13065
13193
  if (term && c.searchKeys) {
@@ -13069,7 +13197,7 @@ var QueryKit = {
13069
13197
  const searched = out;
13070
13198
  Object.entries(filters).forEach(([id, val]) => {
13071
13199
  const fn = c.filters && c.filters[id];
13072
- if (!active(val) || !fn) return;
13200
+ if (!isActiveFilterValue(val) || !fn) return;
13073
13201
  out = out.filter((r) => fn(r, val));
13074
13202
  });
13075
13203
  const matched = out;
@@ -13116,9 +13244,30 @@ var QueryKit = {
13116
13244
  if (Q.sort) parts.push("sort=" + Q.sort + "&dir=" + Q.dir);
13117
13245
  if (Q.q) parts.push("q=" + encodeURIComponent(Q.q));
13118
13246
  Object.entries(Q.filters || {}).forEach(([k, v]) => {
13119
- if (v == null || v === "" || v === false || Array.isArray(v) && !v.length) return;
13120
- if (Array.isArray(v)) v.forEach((x) => parts.push(k + "=" + encodeURIComponent(x === Infinity ? "*" : x)));
13121
- else parts.push(k + "=" + encodeURIComponent(v));
13247
+ if (!isActiveFilterValue(v)) return;
13248
+ if (Array.isArray(v)) {
13249
+ v.forEach((x) => parts.push(k + "=" + encodeURIComponent(x === Infinity ? "*" : x)));
13250
+ return;
13251
+ }
13252
+ if (isMultiSelectShaped(v)) {
13253
+ const prefix = v.operator === "notIn" ? "notIn:" : "";
13254
+ v.values.forEach((o) => parts.push(k + "=" + encodeURIComponent(prefix + String(o.value))));
13255
+ return;
13256
+ }
13257
+ if (isRangeShaped(v)) {
13258
+ if (v.min != null) parts.push(k + ".min=" + encodeURIComponent(String(v.min)));
13259
+ if (v.max != null) parts.push(k + ".max=" + encodeURIComponent(String(v.max)));
13260
+ return;
13261
+ }
13262
+ if (isDateShaped(v)) {
13263
+ if (v.kind === "withinDays") parts.push(k + "=withinDays:" + encodeURIComponent(String(v.days)));
13264
+ else {
13265
+ parts.push(k + ".from=" + encodeURIComponent(String(v.from)));
13266
+ parts.push(k + ".to=" + encodeURIComponent(String(v.to)));
13267
+ }
13268
+ return;
13269
+ }
13270
+ parts.push(k + "=" + encodeURIComponent(v));
13122
13271
  });
13123
13272
  return parts.join("&");
13124
13273
  },
@@ -13259,12 +13408,12 @@ function createVersionStore(initialState, options) {
13259
13408
  }
13260
13409
 
13261
13410
  // src/kits/documentTitle.ts
13262
- import * as React65 from "react";
13411
+ import * as React66 from "react";
13263
13412
  function formatDocumentTitle(routeTitle, appName) {
13264
13413
  return [routeTitle, appName, "Flytedesk"].filter((part) => !!part && part.length > 0).join(" \xB7 ");
13265
13414
  }
13266
13415
  function useDocumentTitle(routeTitle, appName) {
13267
- React65.useEffect(() => {
13416
+ React66.useEffect(() => {
13268
13417
  if (typeof document === "undefined") return;
13269
13418
  document.title = formatDocumentTitle(routeTitle, appName);
13270
13419
  }, [routeTitle, appName]);