@flytedan/flytebot-design-system 0.28.0 → 0.29.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" || tone === "warn" ? "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,8 +2779,11 @@ 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
  };
2769
2789
  const [text, setText] = React21.useState(value == null || value === "" ? "" : String(value));
@@ -2816,9 +2836,15 @@ function NumberInput({
2816
2836
  disabled,
2817
2837
  placeholder,
2818
2838
  value: shown,
2839
+ "aria-invalid": error ? "true" : void 0,
2840
+ "aria-describedby": error ? errorId : void 0,
2819
2841
  onFocus: () => setEditing(true),
2820
2842
  onBlur: () => {
2821
2843
  setEditing(false);
2844
+ if (text.trim() === "") {
2845
+ fire(void 0);
2846
+ return;
2847
+ }
2822
2848
  const n = num(text);
2823
2849
  if (n != null) {
2824
2850
  const c = clamp(n);
@@ -2828,6 +2854,10 @@ function NumberInput({
2828
2854
  },
2829
2855
  onChange: (e) => {
2830
2856
  setText(e.target.value);
2857
+ if (e.target.value.trim() === "") {
2858
+ fire(void 0);
2859
+ return;
2860
+ }
2831
2861
  const n = num(e.target.value);
2832
2862
  if (n != null) fire(n);
2833
2863
  },
@@ -2872,7 +2902,7 @@ function NumberInput({
2872
2902
  )
2873
2903
  ] })
2874
2904
  ] }),
2875
- error ? /* @__PURE__ */ jsxs36("span", { className: "fd-field-error", children: [
2905
+ error ? /* @__PURE__ */ jsxs36("span", { id: errorId, className: "fd-field-error", children: [
2876
2906
  /* @__PURE__ */ jsx42("i", { className: "ph ph-warning-circle", "aria-hidden": "true" }),
2877
2907
  error
2878
2908
  ] }) : help ? /* @__PURE__ */ jsx42("span", { className: "fd-field-help", children: help }) : null
@@ -3096,16 +3126,48 @@ function Textarea({ label, help, error, required = false, rows = 4, disabled = f
3096
3126
  ] });
3097
3127
  }
3098
3128
 
3129
+ // src/components/forms/listbox.ts
3130
+ import * as React23 from "react";
3131
+ function useListboxNav({ count, onSelect }) {
3132
+ const [active, setActive] = React23.useState(-1);
3133
+ return {
3134
+ active,
3135
+ setActive,
3136
+ move: (dir) => setActive((a) => Math.min(count - 1, Math.max(0, a + dir))),
3137
+ first: () => setActive(0),
3138
+ last: () => setActive(count - 1),
3139
+ activate: () => {
3140
+ if (active >= 0 && active < count) onSelect(active);
3141
+ }
3142
+ };
3143
+ }
3144
+ function useListboxScrollIntoView(active, listRef, enabled) {
3145
+ React23.useEffect(() => {
3146
+ if (!enabled || active < 0 || !listRef.current) return;
3147
+ const list = listRef.current;
3148
+ const el = list.querySelector('[data-i="' + active + '"]');
3149
+ if (!el) return;
3150
+ const top = el.offsetTop - list.offsetTop;
3151
+ if (top < list.scrollTop) list.scrollTop = top - 4;
3152
+ else if (top + el.offsetHeight > list.scrollTop + list.clientHeight) {
3153
+ list.scrollTop = top + el.offsetHeight - list.clientHeight + 4;
3154
+ }
3155
+ }, [active, enabled]);
3156
+ }
3157
+
3099
3158
  // src/components/data/FilterBar.tsx
3100
3159
  import { jsx as jsx45, jsxs as jsxs39 } from "react/jsx-runtime";
3160
+ var PANEL_WIDTH = { multiSelect: 280, range: 240, date: 300 };
3101
3161
  var isSet = (v) => !(v == null || v === "" || v === false);
3162
+ function operatorsFor(field) {
3163
+ return field.operators && field.operators.length ? field.operators : ["in", "notIn"];
3164
+ }
3102
3165
  function isFieldSet(field, value) {
3103
3166
  switch (field.type) {
3104
3167
  case "text":
3105
3168
  case "select":
3106
3169
  return isSet(value);
3107
- case "multiSelect":
3108
- case "pasteList": {
3170
+ case "multiSelect": {
3109
3171
  const v = value;
3110
3172
  return !!v && Array.isArray(v.values) && v.values.length > 0;
3111
3173
  }
@@ -3116,7 +3178,7 @@ function isFieldSet(field, value) {
3116
3178
  case "date": {
3117
3179
  const v = value;
3118
3180
  if (!v) return false;
3119
- return v.kind === "withinDays" ? v.days != null : !!(v.from || v.to);
3181
+ return v.kind === "withinDays" ? v.days != null : !!(v.from && v.to);
3120
3182
  }
3121
3183
  }
3122
3184
  }
@@ -3128,8 +3190,7 @@ function chipLabel(field, value) {
3128
3190
  const chosen = field.options.find((o) => o.value === value);
3129
3191
  return chosen ? chosen.label : String(value ?? "");
3130
3192
  }
3131
- case "multiSelect":
3132
- case "pasteList": {
3193
+ case "multiSelect": {
3133
3194
  const v = value;
3134
3195
  if (!v || !v.values.length) return "";
3135
3196
  const prefix = v.operator === "notIn" ? "Not " : "";
@@ -3147,7 +3208,7 @@ function chipLabel(field, value) {
3147
3208
  const v = value;
3148
3209
  if (!v) return "";
3149
3210
  if (v.kind === "withinDays") return "Last " + v.days + " day" + (v.days === 1 ? "" : "s");
3150
- return (v.from || "\u2026") + " \u2192 " + (v.to || "\u2026");
3211
+ return v.from + " \u2192 " + v.to;
3151
3212
  }
3152
3213
  }
3153
3214
  }
@@ -3169,338 +3230,374 @@ function OperatorToggle({
3169
3230
  }
3170
3231
  );
3171
3232
  }
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
3233
  function MultiSelectPanel({
3196
3234
  field,
3197
3235
  value,
3236
+ anchorRef,
3198
3237
  onChange,
3238
+ onRemove,
3199
3239
  onClose
3200
3240
  }) {
3201
- const operator = value?.operator ?? "in";
3241
+ const allowedOps = operatorsFor(field);
3242
+ const operator = allowedOps.length === 1 ? allowedOps[0] : value?.operator ?? allowedOps[0];
3202
3243
  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(() => {
3244
+ const selectedRef = React24.useRef(selected);
3245
+ selectedRef.current = selected;
3246
+ const commit = (nextValues, nextOperator = operator) => {
3247
+ if (nextValues.length === 0) onRemove();
3248
+ else onChange({ operator: nextOperator, values: nextValues });
3249
+ };
3250
+ const [mode2, setMode] = React24.useState("search");
3251
+ const [query, setQuery] = React24.useState("");
3252
+ const [asyncState, setAsyncState] = React24.useState(
3253
+ { options: null, loading: !!field.loadOptions, error: false }
3254
+ );
3255
+ const [retryTick, setRetryTick] = React24.useState(0);
3256
+ const inputRef = React24.useRef(null);
3257
+ const listRef = React24.useRef(null);
3258
+ const listId = React24.useId();
3259
+ const loadOptionsRef = React24.useRef(field.loadOptions);
3260
+ loadOptionsRef.current = field.loadOptions;
3261
+ React24.useEffect(() => {
3210
3262
  inputRef.current?.focus();
3211
3263
  }, []);
3212
- React23.useEffect(() => {
3213
- if (!field.loadOptions) return;
3214
- let cancelled = false;
3215
- setLoading(true);
3216
- const loadOptions = field.loadOptions;
3264
+ React24.useEffect(() => {
3265
+ const load = loadOptionsRef.current;
3266
+ if (!load) return;
3267
+ const controller = new AbortController();
3268
+ setAsyncState((s) => ({ options: s.options, loading: true, error: false }));
3217
3269
  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
- }
3270
+ load(query, controller.signal).then((opts) => {
3271
+ if (controller.signal.aborted) return;
3272
+ setAsyncState({ options: opts, loading: false, error: false });
3273
+ }).catch((err) => {
3274
+ if (controller.signal.aborted || err && err.name === "AbortError") return;
3275
+ setAsyncState({ options: null, loading: false, error: true });
3228
3276
  });
3229
3277
  }, 300);
3230
3278
  return () => {
3231
- cancelled = true;
3232
3279
  clearTimeout(t);
3280
+ controller.abort();
3233
3281
  };
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]);
3282
+ }, [query, retryTick]);
3283
+ const options = field.loadOptions ? asyncState.options ?? [] : (field.options ?? []).filter((o) => o.label.toLowerCase().includes(query.toLowerCase()));
3284
+ const showLoading = !!field.loadOptions && asyncState.loading;
3285
+ const showError = !!field.loadOptions && asyncState.error;
3286
+ const showListbox = !showLoading && !showError;
3239
3287
  const isSelected = (o) => selected.some((s) => s.value === o.value);
3240
3288
  const toggle = (o) => {
3241
3289
  const next = isSelected(o) ? selected.filter((s) => s.value !== o.value) : [...selected, o];
3242
- onChange({ operator, values: next });
3290
+ commit(next);
3291
+ };
3292
+ const nav = useListboxNav({ count: options.length, onSelect: (i) => toggle(options[i]) });
3293
+ useListboxScrollIntoView(nav.active, listRef, showListbox);
3294
+ React24.useEffect(() => {
3295
+ nav.setActive(0);
3296
+ }, [query, options.length]);
3297
+ const activeId = showListbox && options[nav.active] ? listId + "-opt-" + nav.active : void 0;
3298
+ const [pasteText, setPasteText] = React24.useState("");
3299
+ const [pasteLoading, setPasteLoading] = React24.useState(false);
3300
+ const [unmatchedTokens, setUnmatchedTokens] = React24.useState([]);
3301
+ const [pasteError, setPasteError] = React24.useState(null);
3302
+ const pasteWrapRef = React24.useRef(null);
3303
+ React24.useEffect(() => {
3304
+ if (mode2 === "paste") pasteWrapRef.current?.querySelector("textarea")?.focus();
3305
+ }, [mode2]);
3306
+ const doResolve = () => {
3307
+ if (!field.resolve) return;
3308
+ const tokens = Array.from(new Set(pasteText.split(/[\n,]/).map((t) => t.trim()).filter(Boolean)));
3309
+ if (!tokens.length) return;
3310
+ setPasteLoading(true);
3311
+ setPasteError(null);
3312
+ field.resolve(tokens).then((result) => {
3313
+ setPasteLoading(false);
3314
+ setUnmatchedTokens(result.unmatched);
3315
+ const merged = new Map(selectedRef.current.map((s) => [s.value, s]));
3316
+ for (const m of result.matched) merged.set(m.value, { value: m.value, label: m.label });
3317
+ commit(Array.from(merged.values()));
3318
+ setPasteText("");
3319
+ }).catch((err) => {
3320
+ setPasteLoading(false);
3321
+ setPasteError(err instanceof Error ? err.message : "Could not resolve the pasted list.");
3322
+ });
3243
3323
  };
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 }),
3324
+ const header = /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", children: [
3325
+ allowedOps.length > 1 ? /* @__PURE__ */ jsx45(OperatorToggle, { operator, onChange: (op) => commit(selected, op), label: field.label }) : null,
3326
+ field.resolve ? /* @__PURE__ */ jsx45(
3327
+ SegmentedControl,
3328
+ {
3329
+ "aria-label": field.label + " \u2014 search or paste a list",
3330
+ options: [{ value: "search", label: "Search" }, { value: "paste", label: "Paste a list" }],
3331
+ value: mode2,
3332
+ onChange: (m) => setMode(m)
3333
+ }
3334
+ ) : null,
3335
+ mode2 === "search" ? /* @__PURE__ */ jsxs39("span", { className: "fd-pop-search", children: [
3336
+ /* @__PURE__ */ jsx45("i", { className: "ph ph-magnifying-glass", "aria-hidden": "true" }),
3337
+ /* @__PURE__ */ jsx45(
3338
+ "input",
3339
+ {
3340
+ ref: inputRef,
3341
+ role: "combobox",
3342
+ "aria-expanded": showListbox,
3343
+ "aria-controls": showListbox ? listId : void 0,
3344
+ "aria-autocomplete": "list",
3345
+ "aria-activedescendant": activeId,
3346
+ "aria-label": field.label,
3347
+ value: query,
3348
+ placeholder: field.placeholder || "Search\u2026",
3349
+ onChange: (e) => setQuery(e.target.value),
3350
+ onKeyDown: (e) => {
3351
+ if (e.key === "ArrowDown") {
3352
+ e.preventDefault();
3353
+ nav.move(1);
3354
+ } else if (e.key === "ArrowUp") {
3355
+ e.preventDefault();
3356
+ nav.move(-1);
3357
+ } else if (e.key === "Home") {
3358
+ e.preventDefault();
3359
+ nav.first();
3360
+ } else if (e.key === "End") {
3361
+ e.preventDefault();
3362
+ nav.last();
3363
+ } else if (e.key === "Enter") {
3364
+ e.preventDefault();
3365
+ nav.activate();
3366
+ }
3367
+ }
3368
+ }
3369
+ )
3370
+ ] }) : null
3371
+ ] });
3372
+ const body = mode2 === "paste" ? /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", ref: pasteWrapRef, children: [
3247
3373
  /* @__PURE__ */ jsx45(
3248
- "input",
3374
+ Textarea,
3249
3375
  {
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),
3376
+ "aria-label": field.label + " \u2014 paste a list",
3377
+ rows: 4,
3378
+ placeholder: field.pastePlaceholder || field.placeholder || "Paste names, one per line or comma-separated",
3379
+ value: pasteText,
3380
+ disabled: pasteLoading,
3381
+ onChange: (e) => setPasteText(e.target.value),
3262
3382
  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") {
3383
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
3270
3384
  e.preventDefault();
3271
- if (options[active]) toggle(options[active]);
3272
- } else if (e.key === "Escape") {
3273
- e.stopPropagation();
3274
- onClose();
3385
+ doResolve();
3275
3386
  }
3276
3387
  }
3277
3388
  }
3278
3389
  ),
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(
3283
- "span",
3284
- {
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
- })
3321
- }
3322
- ),
3323
- selected.length > 0 ? /* @__PURE__ */ jsxs39("span", { className: "fd-body-sm fd-muted", children: [
3324
- selected.length,
3325
- " selected"
3390
+ pasteError ? /* @__PURE__ */ jsx45(Banner, { tone: "danger", title: "Couldn't resolve the list", detail: pasteError }) : null,
3391
+ unmatchedTokens.length > 0 ? /* @__PURE__ */ jsx45(Banner, { tone: "warn", title: unmatchedTokens.length + " didn't match", detail: unmatchedTokens.join(", ") }) : null,
3392
+ selected.length > 0 ? /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-xs", children: [
3393
+ /* @__PURE__ */ jsxs39("span", { className: "fd-body-sm fd-muted", children: [
3394
+ selected.length,
3395
+ " matched"
3396
+ ] }),
3397
+ /* @__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)) })
3326
3398
  ] }) : null
3327
- ] });
3399
+ ] }) : showLoading ? /* @__PURE__ */ jsxs39("span", { className: "fd-row fd-gap-xs", role: "status", children: [
3400
+ /* @__PURE__ */ jsx45("span", { className: "fd-spinner", "aria-hidden": "true" }),
3401
+ " Loading\u2026"
3402
+ ] }) : showError ? /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", children: [
3403
+ /* @__PURE__ */ jsx45("span", { className: "fd-body-sm", role: "alert", children: "Couldn't load options." }),
3404
+ /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: () => setRetryTick((n) => n + 1), children: "Retry" })
3405
+ ] }) : options.length === 0 ? /* @__PURE__ */ jsx45("span", { className: "fd-body-sm fd-muted", role: "status", children: "No matches" }) : /* @__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) => {
3406
+ const on = isSelected(o);
3407
+ return /* @__PURE__ */ jsxs39(
3408
+ "div",
3409
+ {
3410
+ id: listId + "-opt-" + i,
3411
+ "data-i": i,
3412
+ role: "option",
3413
+ tabIndex: -1,
3414
+ "aria-selected": on,
3415
+ className: ["fd-opt", i === nav.active ? "is-active" : "", on ? "is-selected" : ""].filter(Boolean).join(" "),
3416
+ onMouseEnter: () => nav.setActive(i),
3417
+ onClick: () => toggle(o),
3418
+ children: [
3419
+ /* @__PURE__ */ jsx45("i", { className: "fd-filterbar-opt-check ph " + (on ? "ph-check-square is-checked" : "ph-square"), "aria-hidden": "true" }),
3420
+ /* @__PURE__ */ jsx45("span", { className: "fd-opt-label", children: o.label })
3421
+ ]
3422
+ },
3423
+ o.value
3424
+ );
3425
+ }) });
3426
+ const footer = mode2 === "paste" ? /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: doResolve, disabled: pasteLoading || !pasteText.trim(), children: pasteLoading ? "Resolving\u2026" : "Resolve" }) : void 0;
3427
+ return /* @__PURE__ */ jsx45(
3428
+ Popover,
3429
+ {
3430
+ open: true,
3431
+ anchorRef,
3432
+ onClose,
3433
+ placement: "bottom-start",
3434
+ offset: 6,
3435
+ width: PANEL_WIDTH.multiSelect,
3436
+ label: field.label,
3437
+ header,
3438
+ footer,
3439
+ children: body
3440
+ }
3441
+ );
3328
3442
  }
3329
3443
  function RangePanel({
3330
3444
  field,
3331
3445
  value,
3446
+ anchorRef,
3332
3447
  onChange,
3448
+ onRemove,
3333
3449
  onClose
3334
3450
  }) {
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
- ] });
3451
+ const [draftMin, setDraftMin] = React24.useState(value?.min);
3452
+ const [draftMax, setDraftMax] = React24.useState(value?.max);
3453
+ const invalid = draftMin != null && draftMax != null && draftMin > draftMax;
3454
+ const apply = (nextMin, nextMax) => {
3455
+ setDraftMin(nextMin);
3456
+ setDraftMax(nextMax);
3457
+ if (nextMin == null && nextMax == null) {
3458
+ onRemove();
3459
+ return;
3460
+ }
3461
+ if (nextMin != null && nextMax != null && nextMin > nextMax) return;
3462
+ onChange({ min: nextMin, max: nextMax });
3463
+ };
3464
+ return /* @__PURE__ */ jsx45(
3465
+ Popover,
3466
+ {
3467
+ open: true,
3468
+ anchorRef,
3469
+ onClose,
3470
+ placement: "bottom-start",
3471
+ offset: 6,
3472
+ width: PANEL_WIDTH.range,
3473
+ label: field.label,
3474
+ footer: /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: onClose, children: "Done" }),
3475
+ children: /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", children: [
3476
+ /* @__PURE__ */ jsx45(
3477
+ NumberInput,
3478
+ {
3479
+ label: field.minLabel || "At least",
3480
+ value: draftMin ?? "",
3481
+ min: field.min,
3482
+ max: field.max,
3483
+ step: field.step,
3484
+ placeholder: field.minPlaceholder,
3485
+ onChange: (e) => apply(e.target.value, draftMax)
3486
+ }
3487
+ ),
3488
+ /* @__PURE__ */ jsx45(
3489
+ NumberInput,
3490
+ {
3491
+ label: field.maxLabel || "At most",
3492
+ value: draftMax ?? "",
3493
+ min: field.min,
3494
+ max: field.max,
3495
+ step: field.step,
3496
+ error: invalid ? "Must be \u2265 " + draftMin : void 0,
3497
+ placeholder: field.maxPlaceholder,
3498
+ onChange: (e) => apply(draftMin, e.target.value)
3499
+ }
3500
+ )
3501
+ ] })
3502
+ }
3503
+ );
3366
3504
  }
3367
3505
  function DatePanel({
3368
3506
  field,
3369
3507
  value,
3508
+ anchorRef,
3370
3509
  onChange,
3510
+ onRemove,
3371
3511
  onClose
3372
3512
  }) {
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 || "" })
3513
+ const defaultDays = field.defaultDays ?? 30;
3514
+ const [draft, setDraft] = React24.useState(value ?? { kind: "withinDays", days: defaultDays });
3515
+ const committedRef = React24.useRef(!!value);
3516
+ const applyWithinDays = (days) => {
3517
+ const next = { kind: "withinDays", days: days ?? defaultDays };
3518
+ setDraft(next);
3519
+ if (days != null) {
3520
+ committedRef.current = true;
3521
+ onChange(next);
3522
+ }
3523
+ };
3524
+ const applyBetween = (from, to) => {
3525
+ const next = { kind: "between", from, to };
3526
+ setDraft(next);
3527
+ if (from && to) {
3528
+ committedRef.current = true;
3529
+ onChange(next);
3530
+ } else if (committedRef.current) {
3531
+ committedRef.current = false;
3532
+ onRemove();
3533
+ }
3534
+ };
3535
+ const switchKind = (kind) => {
3536
+ if (kind === "withinDays") {
3537
+ const days = draft.kind === "withinDays" ? draft.days : defaultDays;
3538
+ setDraft({ kind: "withinDays", days });
3539
+ } else {
3540
+ setDraft({ kind: "between", from: "", to: "" });
3541
+ if (committedRef.current) {
3542
+ committedRef.current = false;
3543
+ onRemove();
3408
3544
  }
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
- });
3545
+ }
3445
3546
  };
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();
3547
+ const confirm = () => {
3548
+ if (draft.kind === "withinDays") {
3549
+ committedRef.current = true;
3550
+ onChange(draft);
3551
+ }
3552
+ onClose();
3553
+ };
3554
+ return /* @__PURE__ */ jsx45(
3555
+ Popover,
3556
+ {
3557
+ open: true,
3558
+ anchorRef,
3559
+ onClose,
3560
+ placement: "bottom-start",
3561
+ offset: 6,
3562
+ width: PANEL_WIDTH.date,
3563
+ label: field.label,
3564
+ footer: /* @__PURE__ */ jsx45(Button, { size: "sm", variant: "secondary", onClick: confirm, children: "Done" }),
3565
+ children: /* @__PURE__ */ jsxs39("span", { className: "fd-stack fd-gap-sm", children: [
3566
+ /* @__PURE__ */ jsx45(
3567
+ SegmentedControl,
3568
+ {
3569
+ "aria-label": field.label + " \u2014 relative or between dates",
3570
+ options: [
3571
+ { value: "withinDays", label: "Within last N days" },
3572
+ { value: "between", label: "Between dates" }
3573
+ ],
3574
+ value: draft.kind,
3575
+ onChange: (k) => switchKind(k)
3461
3576
  }
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
- ] });
3577
+ ),
3578
+ draft.kind === "withinDays" ? /* @__PURE__ */ jsx45(NumberInput, { label: "Days", min: 1, value: draft.days, onChange: (e) => applyWithinDays(e.target.value) }) : /* @__PURE__ */ jsx45(
3579
+ DatePicker,
3580
+ {
3581
+ range: true,
3582
+ label: "Date range",
3583
+ value: { start: draft.from, end: draft.to },
3584
+ onChange: (e) => applyBetween(e.target.value.start || "", e.target.value.end || "")
3585
+ }
3586
+ )
3587
+ ] })
3588
+ }
3589
+ );
3477
3590
  }
3478
3591
  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);
3592
+ const [panel, setPanel] = React24.useState(null);
3593
+ const editAnchorRef = React24.useRef(null);
3594
+ const addAnchorRef = React24.useRef(null);
3595
+ const textInputRef = React24.useRef(null);
3596
+ const optionsRef = React24.useRef(null);
3484
3597
  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(() => {
3598
+ const startEditing = (key) => setPanel({ kind: "edit", key });
3599
+ const closeEditing = () => setPanel(null);
3600
+ React24.useEffect(() => {
3504
3601
  if (!panel || panel.kind !== "edit") return;
3505
3602
  if (editingField?.type === "text") {
3506
3603
  textInputRef.current?.focus();
@@ -3510,72 +3607,77 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
3510
3607
  }
3511
3608
  }, [panel]);
3512
3609
  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: [
3610
+ const addMenuItems = [
3611
+ { kind: "section", label: "Add filter" },
3612
+ ...addableFields.map((f) => ({ id: f.key, label: f.label, onSelect: () => startEditing(f.key) }))
3613
+ ];
3614
+ return /* @__PURE__ */ jsxs39("span", { className: ["fd-filterbar", className].filter(Boolean).join(" "), ...rest, children: [
3515
3615
  fields.map((field) => {
3516
3616
  const value = filters[field.key];
3517
3617
  const editing = panel?.kind === "edit" && panel.key === field.key;
3518
3618
  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" })
3619
+ return /* @__PURE__ */ jsxs39("span", { className: ["fd-tag", "fd-filterbar-text"].join(" "), children: [
3620
+ /* @__PURE__ */ jsx45(
3621
+ "input",
3622
+ {
3623
+ ref: textInputRef,
3624
+ className: "fd-filterbar-text-input",
3625
+ value: value ?? "",
3626
+ placeholder: field.placeholder || field.label,
3627
+ "aria-label": field.label,
3628
+ onChange: (e) => onFilterChange(field.key, e.target.value),
3629
+ onKeyDown: (e) => {
3630
+ if (e.key === "Enter") setPanel(null);
3631
+ else if (e.key === "Escape") {
3632
+ e.stopPropagation();
3633
+ setPanel(null);
3556
3634
  }
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(
3635
+ },
3636
+ onBlur: () => setPanel(null)
3637
+ }
3638
+ ),
3639
+ isSet(value) ? /* @__PURE__ */ jsx45(
3574
3640
  "span",
3575
3641
  {
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) => {
3642
+ className: "fd-tag-remove",
3643
+ role: "button",
3644
+ "aria-label": "Remove " + field.label,
3645
+ onClick: (e) => {
3646
+ e.stopPropagation();
3647
+ onFilterRemove(field.key);
3648
+ setPanel(null);
3649
+ },
3650
+ children: /* @__PURE__ */ jsx45("i", { className: "ph ph-x", "aria-hidden": "true" })
3651
+ }
3652
+ ) : null
3653
+ ] }, field.key);
3654
+ }
3655
+ const set = isFieldSet(field, value);
3656
+ if (!editing && !set) return null;
3657
+ const chipRef = editing ? editAnchorRef : void 0;
3658
+ const toggleOrClose = () => editing ? closeEditing() : startEditing(field.key);
3659
+ const remove = () => {
3660
+ if (set) onFilterRemove(field.key);
3661
+ closeEditing();
3662
+ };
3663
+ const chipText = field.label + (set ? ": " + chipLabel(field, value) : "");
3664
+ if (field.type === "text") {
3665
+ return /* @__PURE__ */ jsx45(Tag, { selected: true, onClick: toggleOrClose, onRemove: remove, children: chipText }, field.key);
3666
+ }
3667
+ if (field.type === "select") {
3668
+ return /* @__PURE__ */ jsxs39("span", { children: [
3669
+ /* @__PURE__ */ jsx45("span", { ref: chipRef, children: /* @__PURE__ */ jsx45(Tag, { selected: true, onClick: toggleOrClose, onRemove: remove, children: chipText }) }),
3670
+ editing ? /* @__PURE__ */ jsx45(
3671
+ Popover,
3672
+ {
3673
+ open: true,
3674
+ anchorRef: editAnchorRef,
3675
+ onClose: closeEditing,
3676
+ placement: align === "right" ? "bottom-end" : "bottom-start",
3677
+ offset: 6,
3678
+ minWidth: 200,
3679
+ label: field.label,
3680
+ children: /* @__PURE__ */ jsx45("span", { role: "listbox", "aria-label": field.label, ref: optionsRef, className: "fd-pop-list", children: field.options.map((o) => {
3579
3681
  const on = o.value === value;
3580
3682
  return /* @__PURE__ */ jsx45(
3581
3683
  "button",
@@ -3583,84 +3685,72 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
3583
3685
  type: "button",
3584
3686
  role: "option",
3585
3687
  "aria-selected": on,
3586
- className: "fd-row",
3688
+ className: ["fd-opt", on ? "is-selected" : ""].filter(Boolean).join(" "),
3587
3689
  onClick: () => {
3588
3690
  onFilterChange(field.key, o.value);
3589
3691
  setPanel(null);
3590
3692
  },
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 })
3693
+ children: /* @__PURE__ */ jsx45("span", { className: "fd-opt-label", children: o.label })
3593
3694
  },
3594
3695
  o.value
3595
3696
  );
3596
3697
  }) })
3597
3698
  }
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) }) })
3699
+ ) : null
3612
3700
  ] }, field.key);
3613
3701
  }
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) }) })
3625
- ] }, field.key);
3626
- }
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) }) })
3702
+ if (field.type === "multiSelect") {
3703
+ return /* @__PURE__ */ jsxs39("span", { children: [
3704
+ /* @__PURE__ */ jsx45("span", { ref: chipRef, children: /* @__PURE__ */ jsx45(Tag, { selected: true, onClick: toggleOrClose, onRemove: remove, children: chipText }) }),
3705
+ editing ? /* @__PURE__ */ jsx45(
3706
+ MultiSelectPanel,
3707
+ {
3708
+ field,
3709
+ value,
3710
+ anchorRef: editAnchorRef,
3711
+ onChange: (v) => onFilterChange(field.key, v),
3712
+ onRemove: () => onFilterRemove(field.key),
3713
+ onClose: closeEditing
3714
+ }
3715
+ ) : null
3638
3716
  ] }, field.key);
3639
3717
  }
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) }) })
3718
+ if (field.type === "range") {
3719
+ return /* @__PURE__ */ jsxs39("span", { children: [
3720
+ /* @__PURE__ */ jsx45("span", { ref: chipRef, children: /* @__PURE__ */ jsx45(Tag, { selected: true, onClick: toggleOrClose, onRemove: remove, children: chipText }) }),
3721
+ editing ? /* @__PURE__ */ jsx45(
3722
+ RangePanel,
3723
+ {
3724
+ field,
3725
+ value,
3726
+ anchorRef: editAnchorRef,
3727
+ onChange: (v) => onFilterChange(field.key, v),
3728
+ onRemove: () => onFilterRemove(field.key),
3729
+ onClose: closeEditing
3730
+ }
3731
+ ) : null
3651
3732
  ] }, field.key);
3652
3733
  }
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)
3734
+ if (field.type === "date") {
3735
+ return /* @__PURE__ */ jsxs39("span", { children: [
3736
+ /* @__PURE__ */ jsx45("span", { ref: chipRef, children: /* @__PURE__ */ jsx45(Tag, { selected: true, onClick: toggleOrClose, onRemove: remove, children: chipText }) }),
3737
+ editing ? /* @__PURE__ */ jsx45(
3738
+ DatePanel,
3739
+ {
3740
+ field,
3741
+ value,
3742
+ anchorRef: editAnchorRef,
3743
+ onChange: (v) => onFilterChange(field.key, v),
3744
+ onRemove: () => onFilterRemove(field.key),
3745
+ onClose: closeEditing
3746
+ }
3747
+ ) : null
3658
3748
  ] }, field.key);
3659
3749
  }
3660
3750
  return null;
3661
3751
  }),
3662
- addableFields.length > 0 ? /* @__PURE__ */ jsxs39("span", { style: { position: "relative", display: "inline-flex" }, children: [
3663
- /* @__PURE__ */ jsx45(
3752
+ addableFields.length > 0 ? /* @__PURE__ */ jsxs39("span", { children: [
3753
+ /* @__PURE__ */ jsx45("span", { ref: addAnchorRef, children: /* @__PURE__ */ jsx45(
3664
3754
  Button,
3665
3755
  {
3666
3756
  size: "sm",
@@ -3671,29 +3761,20 @@ function FilterBar({ fields = [], filters, onFilterChange, onFilterRemove, align
3671
3761
  onClick: () => setPanel(panel?.kind === "add" ? null : { kind: "add" }),
3672
3762
  children: "Filter"
3673
3763
  }
3674
- ),
3675
- panel?.kind === "add" ? /* @__PURE__ */ jsx45(LayerProvider, { zIndex, children: /* @__PURE__ */ jsx45(
3676
- "span",
3764
+ ) }),
3765
+ /* @__PURE__ */ jsx45(
3766
+ Popover,
3677
3767
  {
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
- ] })
3768
+ open: panel?.kind === "add",
3769
+ anchorRef: addAnchorRef,
3770
+ onClose: closeEditing,
3771
+ placement: align === "right" ? "bottom-end" : "bottom-start",
3772
+ minWidth: 200,
3773
+ role: "menu",
3774
+ label: "Add filter",
3775
+ children: /* @__PURE__ */ jsx45(Menu, { items: addMenuItems })
3695
3776
  }
3696
- ) }) : null
3777
+ )
3697
3778
  ] }) : null
3698
3779
  ] });
3699
3780
  }
@@ -3725,7 +3806,7 @@ function StatTile({ label, value, sub, delta, invert = false, loading = false, c
3725
3806
  }
3726
3807
 
3727
3808
  // src/components/data/CodeBlock.tsx
3728
- import * as React24 from "react";
3809
+ import * as React25 from "react";
3729
3810
  import { Fragment as Fragment6, jsx as jsx47, jsxs as jsxs41 } from "react/jsx-runtime";
3730
3811
  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
3812
  var RULES = {
@@ -3881,13 +3962,13 @@ function tokenize(src, lang) {
3881
3962
  return out;
3882
3963
  }
3883
3964
  function Highlighted({ code, lang }) {
3884
- const toks = React24.useMemo(() => tokenize(code, lang), [code, lang]);
3965
+ const toks = React25.useMemo(() => tokenize(code, lang), [code, lang]);
3885
3966
  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
3967
  }
3887
3968
  function useCopy() {
3888
- const [done, setDone] = React24.useState(false);
3889
- const timer = React24.useRef(null);
3890
- React24.useEffect(() => () => {
3969
+ const [done, setDone] = React25.useState(false);
3970
+ const timer = React25.useRef(null);
3971
+ React25.useEffect(() => () => {
3891
3972
  if (timer.current) clearTimeout(timer.current);
3892
3973
  }, []);
3893
3974
  const copy = (text) => {
@@ -3923,8 +4004,8 @@ function CodeBlock({
3923
4004
  actions,
3924
4005
  className = ""
3925
4006
  }) {
3926
- const [wrap, setWrap] = React24.useState(!!wrapDefault);
3927
- const [open, setOpen] = React24.useState(false);
4007
+ const [wrap, setWrap] = React25.useState(!!wrapDefault);
4008
+ const [open, setOpen] = React25.useState(false);
3928
4009
  const [copied, copy] = useCopy();
3929
4010
  const body = String(code).replace(/\n$/, "");
3930
4011
  const lines = body.split("\n");
@@ -3953,7 +4034,7 @@ function CodeBlock({
3953
4034
  ] });
3954
4035
  }
3955
4036
  function DiffBlock({ diff = "", filename, collapseAfter = 34, className = "" }) {
3956
- const [open, setOpen] = React24.useState(false);
4037
+ const [open, setOpen] = React25.useState(false);
3957
4038
  const [copied, copy] = useCopy();
3958
4039
  const raw = String(diff).replace(/\n$/, "");
3959
4040
  const all = raw.split("\n").map((line) => {
@@ -3999,7 +4080,7 @@ function DiffBlock({ diff = "", filename, collapseAfter = 34, className = "" })
3999
4080
  }
4000
4081
 
4001
4082
  // src/components/data/Markdown.tsx
4002
- import * as React25 from "react";
4083
+ import * as React26 from "react";
4003
4084
  import { jsx as jsx48, jsxs as jsxs42 } from "react/jsx-runtime";
4004
4085
  var SAFE = /^(?:https?:|mailto:|tel:|#|\/|\.{1,2}\/)/i;
4005
4086
  var safeHref = (h) => SAFE.test(String(h || "").trim()) ? String(h).trim() : null;
@@ -4038,7 +4119,7 @@ function inlineNodes(src, ctx, keyBase) {
4038
4119
  const href = safeHref(g(2));
4039
4120
  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
4121
  } 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()));
4122
+ 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
4123
  } else if (kind === "strongem") out.push(/* @__PURE__ */ jsx48("strong", { children: /* @__PURE__ */ jsx48("em", { children: inlineNodes(g(1), ctx, key()) }) }, key()));
4043
4124
  else if (kind === "strong") out.push(/* @__PURE__ */ jsx48("strong", { children: inlineNodes(g(1) || g(2), ctx, key()) }, key()));
4044
4125
  else if (kind === "em") out.push(/* @__PURE__ */ jsx48("em", { children: inlineNodes(g(1) || g(2), ctx, key()) }, key()));
@@ -4203,7 +4284,7 @@ function Markdown({
4203
4284
  onLinkClick
4204
4285
  }) {
4205
4286
  const ctx = { allowImages, headingOffset, renderCitation, onLinkClick, codeProps };
4206
- const blocks = React25.useMemo(() => parseBlocks(source), [source]);
4287
+ const blocks = React26.useMemo(() => parseBlocks(source), [source]);
4207
4288
  return /* @__PURE__ */ jsx48("div", { className: ["fd-md", className].filter(Boolean).join(" "), children: renderBlocks(blocks, ctx, "b") });
4208
4289
  }
4209
4290
  function MarkdownInline({ source = "", className = "", onLinkClick }) {
@@ -4317,7 +4398,7 @@ function Pagination({
4317
4398
  }
4318
4399
 
4319
4400
  // src/components/data/RelativeTime.tsx
4320
- import * as React26 from "react";
4401
+ import * as React27 from "react";
4321
4402
  import { jsxs as jsxs45 } from "react/jsx-runtime";
4322
4403
  var MIN = 6e4;
4323
4404
  var HOUR = 36e5;
@@ -4369,9 +4450,9 @@ function formatDuration(ms) {
4369
4450
  return h + "h" + (m % 60 ? " " + m % 60 + "m" : "");
4370
4451
  }
4371
4452
  function RelativeTime({ value, prefix, className = "", refresh: refresh2 = true, ...rest }) {
4372
- const [, force] = React26.useState(0);
4453
+ const [, force] = React27.useState(0);
4373
4454
  const t = value instanceof Date ? value.getTime() : Date.parse(value);
4374
- React26.useEffect(() => {
4455
+ React27.useEffect(() => {
4375
4456
  if (!refresh2 || !t || isNaN(t)) return;
4376
4457
  const abs = Math.abs(t - Date.now());
4377
4458
  if (abs > DAY) return;
@@ -4387,7 +4468,7 @@ function RelativeTime({ value, prefix, className = "", refresh: refresh2 = true,
4387
4468
  }
4388
4469
 
4389
4470
  // src/components/data/ScoreMeter.tsx
4390
- import * as React27 from "react";
4471
+ import * as React28 from "react";
4391
4472
  import { jsx as jsx51, jsxs as jsxs46 } from "react/jsx-runtime";
4392
4473
  function ScoreMeter({
4393
4474
  score,
@@ -4402,10 +4483,10 @@ function ScoreMeter({
4402
4483
  breakdownWidth = 280,
4403
4484
  className = ""
4404
4485
  }) {
4405
- const [open, setOpen] = React27.useState(false);
4406
- const ref = React27.useRef(null);
4486
+ const [open, setOpen] = React28.useState(false);
4487
+ const ref = React28.useRef(null);
4407
4488
  const value = Math.max(0, Math.min(100, Math.round(Number(score) || 0)));
4408
- const body = /* @__PURE__ */ jsxs46(React27.Fragment, { children: [
4489
+ const body = /* @__PURE__ */ jsxs46(React28.Fragment, { children: [
4409
4490
  /* @__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
4491
  showScore ? /* @__PURE__ */ jsx51("span", { className: "fd-scoremeter-value fd-tabular", "aria-hidden": "true", children: value }) : null
4411
4492
  ] });
@@ -4424,7 +4505,7 @@ function ScoreMeter({
4424
4505
  }
4425
4506
  );
4426
4507
  }
4427
- return /* @__PURE__ */ jsxs46(React27.Fragment, { children: [
4508
+ return /* @__PURE__ */ jsxs46(React28.Fragment, { children: [
4428
4509
  /* @__PURE__ */ jsx51(
4429
4510
  "button",
4430
4511
  {
@@ -4455,13 +4536,13 @@ function ScoreMeter({
4455
4536
  }
4456
4537
 
4457
4538
  // src/components/data/SortMenu.tsx
4458
- import * as React28 from "react";
4539
+ import * as React29 from "react";
4459
4540
  import { jsx as jsx52, jsxs as jsxs47 } from "react/jsx-runtime";
4460
4541
  function SortMenu({ fields = [], sort, onSort, align = "right", size = "md", className = "", ...rest }) {
4461
- const [open, setOpen] = React28.useState(false);
4542
+ const [open, setOpen] = React29.useState(false);
4462
4543
  const zIndex = useOverlayLayer(open);
4463
- const ref = React28.useRef(null);
4464
- React28.useEffect(() => {
4544
+ const ref = React29.useRef(null);
4545
+ React29.useEffect(() => {
4465
4546
  if (!open) return;
4466
4547
  const away = (e) => {
4467
4548
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -4523,7 +4604,7 @@ function SortMenu({ fields = [], sort, onSort, align = "right", size = "md", cla
4523
4604
  }
4524
4605
 
4525
4606
  // src/components/data/StepList.tsx
4526
- import * as React29 from "react";
4607
+ import * as React30 from "react";
4527
4608
  import { jsx as jsx53, jsxs as jsxs48 } from "react/jsx-runtime";
4528
4609
  var KIND_ICON = { tool: "wrench", search: "magnifying-glass", read: "file-text", write: "pencil-simple", reasoning: "brain", run: "terminal", fetch: "cloud-arrow-down" };
4529
4610
  function StepList({
@@ -4536,9 +4617,9 @@ function StepList({
4536
4617
  dense = false,
4537
4618
  className = ""
4538
4619
  }) {
4539
- const [openState, setOpenState] = React29.useState(defaultOpen);
4620
+ const [openState, setOpenState] = React30.useState(defaultOpen);
4540
4621
  const open = openProp == null ? openState : openProp;
4541
- const [expanded, setExpanded] = React29.useState({});
4622
+ const [expanded, setExpanded] = React30.useState({});
4542
4623
  if (!steps.length) return null;
4543
4624
  const running = steps.some((s) => s.status === "running");
4544
4625
  const failed = steps.filter((s) => s.status === "error").length;
@@ -4596,7 +4677,7 @@ function StepList({
4596
4677
  }
4597
4678
 
4598
4679
  // src/components/data/VirtualList.tsx
4599
- import * as React30 from "react";
4680
+ import * as React31 from "react";
4600
4681
  import { jsx as jsx54, jsxs as jsxs49 } from "react/jsx-runtime";
4601
4682
  var VIRTUAL_LIST_BUFFER_ROWS = 20;
4602
4683
  var VIRTUAL_LIST_ROW_GAP = 6;
@@ -4648,35 +4729,35 @@ function VirtualList({
4648
4729
  className = "",
4649
4730
  style
4650
4731
  }) {
4651
- const [scrollTop, setScrollTop] = React30.useState(0);
4652
- const loaded = React30.useMemo(
4732
+ const [scrollTop, setScrollTop] = React31.useState(0);
4733
+ const loaded = React31.useMemo(
4653
4734
  () => ({ firstItemIndex, count: items.length }),
4654
4735
  [firstItemIndex, items.length]
4655
4736
  );
4656
4737
  const total = Math.max(totalCount == null ? 0 : totalCount, firstItemIndex + items.length);
4657
4738
  const bandKey = firstItemIndex + ":" + items.length;
4658
- const win = React30.useMemo(
4739
+ const win = React31.useMemo(
4659
4740
  () => computeVirtualWindow({ scrollTop, viewportHeight: height, itemHeight, itemCount: total, loaded }),
4660
4741
  [scrollTop, height, itemHeight, total, loaded]
4661
4742
  );
4662
- const need = React30.useMemo(() => computeNeedMore(win, loaded), [win, loaded]);
4743
+ const need = React31.useMemo(() => computeNeedMore(win, loaded), [win, loaded]);
4663
4744
  const wantStart = need.start && hasMore?.start !== false;
4664
4745
  const wantEnd = need.end && hasMore?.end !== false;
4665
4746
  const bucket2 = Math.floor(win.visibleStart / VIRTUAL_LIST_BUFFER_ROWS);
4666
4747
  const askKey = bandKey + "@" + bucket2;
4667
- const asked = React30.useRef({ start: null, end: null });
4668
- React30.useEffect(() => {
4748
+ const asked = React31.useRef({ start: null, end: null });
4749
+ React31.useEffect(() => {
4669
4750
  if (loading || !wantStart || asked.current.start === askKey) return;
4670
4751
  asked.current.start = askKey;
4671
4752
  onNeedMore("start", win);
4672
4753
  }, [wantStart, loading, askKey, onNeedMore, win]);
4673
- React30.useEffect(() => {
4754
+ React31.useEffect(() => {
4674
4755
  if (loading || !wantEnd || asked.current.end === askKey) return;
4675
4756
  asked.current.end = askKey;
4676
4757
  onNeedMore("end", win);
4677
4758
  }, [wantEnd, loading, askKey, onNeedMore, win]);
4678
- const lastKeysRef = React30.useRef(null);
4679
- const newKeys = React30.useMemo(() => {
4759
+ const lastKeysRef = React31.useRef(null);
4760
+ const newKeys = React31.useMemo(() => {
4680
4761
  const prev = lastKeysRef.current;
4681
4762
  if (!prev) return /* @__PURE__ */ new Set();
4682
4763
  const fresh = /* @__PURE__ */ new Set();
@@ -4686,7 +4767,7 @@ function VirtualList({
4686
4767
  }
4687
4768
  return fresh;
4688
4769
  }, [items]);
4689
- React30.useEffect(() => {
4770
+ React31.useEffect(() => {
4690
4771
  lastKeysRef.current = new Set(items.map(keyOf));
4691
4772
  }, [items]);
4692
4773
  const gapStyle = { "--fd-vlist-gap": rowGap + "px" };
@@ -4731,7 +4812,7 @@ function VirtualList({
4731
4812
  }
4732
4813
 
4733
4814
  // src/components/data/EntityRow.tsx
4734
- import * as React31 from "react";
4815
+ import * as React32 from "react";
4735
4816
  import { jsx as jsx55, jsxs as jsxs50 } from "react/jsx-runtime";
4736
4817
  var ENTITY_ROW_METRIC_WIDTH = 74;
4737
4818
  var METRIC_TONE_COLOR = {
@@ -4744,7 +4825,7 @@ function EntityRowMetrics({ metrics, className = "", style }) {
4744
4825
  const key = m.id || m.label || i;
4745
4826
  const className2 = ["fd-erow-metric", m.tone && m.tone !== "default" ? "is-" + m.tone : "", m.tooltip != null ? "is-described" : ""].filter(Boolean).join(" ");
4746
4827
  const tone = { color: METRIC_TONE_COLOR[m.tone || "default"] };
4747
- const content = /* @__PURE__ */ jsxs50(React31.Fragment, { children: [
4828
+ const content = /* @__PURE__ */ jsxs50(React32.Fragment, { children: [
4748
4829
  m.icon ? /* @__PURE__ */ jsx55("i", { className: "ph ph-" + m.icon, "aria-hidden": "true" }) : null,
4749
4830
  /* @__PURE__ */ jsx55("span", { className: "fd-erow-metric-value fd-tabular", children: m.value }),
4750
4831
  /* @__PURE__ */ jsx55("span", { className: "fd-sr", children: " " + m.label })
@@ -4836,7 +4917,7 @@ function EntityRow({
4836
4917
  }
4837
4918
 
4838
4919
  // src/components/data/TransferList.tsx
4839
- import * as React32 from "react";
4920
+ import * as React33 from "react";
4840
4921
  import { createPortal as createPortal4 } from "react-dom";
4841
4922
 
4842
4923
  // src/components/forms/Input.tsx
@@ -4942,12 +5023,12 @@ function TransferList({
4942
5023
  listHeight = 440,
4943
5024
  className = ""
4944
5025
  }) {
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) => {
5026
+ const [drag, setDrag] = React33.useState(null);
5027
+ const [dragOverSide, setDragOverSide] = React33.useState(null);
5028
+ const dragRef = React33.useRef(null);
5029
+ const armedRef = React33.useRef(null);
5030
+ const sideRefs = React33.useRef({ left: null, right: null });
5031
+ const hitTestSide = React33.useCallback((x, y) => {
4951
5032
  for (const side of ["left", "right"]) {
4952
5033
  const el = sideRefs.current[side];
4953
5034
  if (!el) continue;
@@ -4956,7 +5037,7 @@ function TransferList({
4956
5037
  }
4957
5038
  return null;
4958
5039
  }, []);
4959
- const findItem = React32.useCallback(
5040
+ const findItem = React33.useCallback(
4960
5041
  (side, key) => (side === "left" ? left.items : right.items).find((it) => keyOf(it) === key),
4961
5042
  [left.items, right.items, keyOf]
4962
5043
  );
@@ -4978,7 +5059,7 @@ function TransferList({
4978
5059
  height: rect.height
4979
5060
  };
4980
5061
  };
4981
- React32.useEffect(() => {
5062
+ React33.useEffect(() => {
4982
5063
  const onMovePointer = (e) => {
4983
5064
  const armed = armedRef.current;
4984
5065
  if (!armed || e.pointerId !== armed.pointerId) return;
@@ -5031,7 +5112,7 @@ function TransferList({
5031
5112
  window.removeEventListener("pointercancel", endDrag);
5032
5113
  };
5033
5114
  }, [hitTestSide, findItem, onMove]);
5034
- React32.useEffect(() => {
5115
+ React33.useEffect(() => {
5035
5116
  if (!drag || typeof document === "undefined") return;
5036
5117
  const prev = document.body.style.userSelect;
5037
5118
  document.body.style.userSelect = "none";
@@ -5139,11 +5220,11 @@ function TransferList({
5139
5220
  }
5140
5221
 
5141
5222
  // src/components/forms/Checkbox.tsx
5142
- import * as React33 from "react";
5223
+ import * as React34 from "react";
5143
5224
  import { jsx as jsx59, jsxs as jsxs53 } from "react/jsx-runtime";
5144
5225
  function Checkbox({ label, description, card = false, indeterminate = false, className = "", ...rest }) {
5145
- const ref = React33.useRef(null);
5146
- React33.useEffect(() => {
5226
+ const ref = React34.useRef(null);
5227
+ React34.useEffect(() => {
5147
5228
  if (ref.current) ref.current.indeterminate = indeterminate;
5148
5229
  }, [indeterminate]);
5149
5230
  return /* @__PURE__ */ jsxs53("label", { className: ["fd-choice", card ? "fd-choice-card" : "", className].filter(Boolean).join(" "), children: [
@@ -5187,7 +5268,7 @@ function Switch({ label, description, className = "", ...rest }) {
5187
5268
  }
5188
5269
 
5189
5270
  // src/components/forms/Select.tsx
5190
- import * as React34 from "react";
5271
+ import * as React35 from "react";
5191
5272
  import { jsx as jsx62, jsxs as jsxs56 } from "react/jsx-runtime";
5192
5273
  var norm = (o) => typeof o === "string" ? { value: o, label: o } : o;
5193
5274
  function Select({
@@ -5214,13 +5295,12 @@ function Select({
5214
5295
  const isOn = (v) => multiple ? vals.includes(v) : v === value;
5215
5296
  const hasSearch = searchable === void 0 ? opts.length > 8 : searchable;
5216
5297
  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 });
5298
+ const [open, setOpen] = React35.useState(false);
5299
+ const [q, setQ] = React35.useState("");
5300
+ const rootRef = React35.useRef(null);
5301
+ const boxRef = React35.useRef(null);
5302
+ const listRef = React35.useRef(null);
5303
+ const typeBuf = React35.useRef({ s: "", t: 0 });
5224
5304
  const selected = multiple ? null : opts.find((o) => o.value === value);
5225
5305
  const chosen = multiple ? opts.filter((o) => vals.includes(o.value)) : [];
5226
5306
  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 +5317,16 @@ function Select({
5237
5317
  fire(o.value);
5238
5318
  setOpen(false);
5239
5319
  };
5320
+ const nav = useListboxNav({ count: visible.length, onSelect: (i) => pick(visible[i]) });
5321
+ useListboxScrollIntoView(nav.active, listRef, open);
5240
5322
  const toggle = () => {
5241
5323
  if (disabled || loading) return;
5242
5324
  if (!open) {
5243
5325
  setQ("");
5244
- setActive(Math.max(0, opts.findIndex((o) => isOn(o.value))));
5326
+ nav.setActive(Math.max(0, opts.findIndex((o) => isOn(o.value))));
5245
5327
  }
5246
5328
  setOpen(!open);
5247
5329
  };
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
5330
  const onKey = (e) => {
5258
5331
  if (disabled) return;
5259
5332
  if (!open && (e.key === "Enter" || e.key === " " || e.key === "ArrowDown" || e.key === "ArrowUp")) {
@@ -5275,19 +5348,19 @@ function Select({
5275
5348
  setOpen(false);
5276
5349
  } else if (e.key === "ArrowDown") {
5277
5350
  e.preventDefault();
5278
- setActive((a) => Math.min(visible.length - 1, a + 1));
5351
+ nav.move(1);
5279
5352
  } else if (e.key === "ArrowUp") {
5280
5353
  e.preventDefault();
5281
- setActive((a) => Math.max(0, a - 1));
5354
+ nav.move(-1);
5282
5355
  } else if (e.key === "Home") {
5283
5356
  e.preventDefault();
5284
- setActive(0);
5357
+ nav.first();
5285
5358
  } else if (e.key === "End") {
5286
5359
  e.preventDefault();
5287
- setActive(visible.length - 1);
5360
+ nav.last();
5288
5361
  } else if (e.key === "Enter") {
5289
5362
  e.preventDefault();
5290
- if (visible[active]) pick(visible[active]);
5363
+ nav.activate();
5291
5364
  } else if (e.key === "Tab") setOpen(false);
5292
5365
  };
5293
5366
  const groups = [];
@@ -5360,13 +5433,13 @@ function Select({
5360
5433
  onKeyDown: onKey,
5361
5434
  onChange: (e) => {
5362
5435
  setQ(e.target.value);
5363
- setActive(0);
5436
+ nav.setActive(0);
5364
5437
  }
5365
5438
  }
5366
5439
  ),
5367
5440
  q ? /* @__PURE__ */ jsx62("span", { className: "fd-body-sm fd-muted", children: visible.length }) : null
5368
5441
  ] }) : void 0,
5369
- footer: multiple ? /* @__PURE__ */ jsxs56(React34.Fragment, { children: [
5442
+ footer: multiple ? /* @__PURE__ */ jsxs56(React35.Fragment, { children: [
5370
5443
  /* @__PURE__ */ jsx62(
5371
5444
  "button",
5372
5445
  {
@@ -5397,7 +5470,7 @@ function Select({
5397
5470
  'Nothing matches "',
5398
5471
  q,
5399
5472
  '".'
5400
- ] }) : groups.map((grp) => /* @__PURE__ */ jsxs56(React34.Fragment, { children: [
5473
+ ] }) : groups.map((grp) => /* @__PURE__ */ jsxs56(React35.Fragment, { children: [
5401
5474
  grp.g ? /* @__PURE__ */ jsx62("div", { className: "fd-pop-group", children: grp.g }) : null,
5402
5475
  grp.items.map(({ o, i }) => /* @__PURE__ */ jsxs56(
5403
5476
  "button",
@@ -5407,8 +5480,8 @@ function Select({
5407
5480
  role: "option",
5408
5481
  "aria-selected": isOn(o.value),
5409
5482
  "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),
5483
+ className: ["fd-opt", i === nav.active ? "is-active" : "", isOn(o.value) ? "is-selected" : ""].filter(Boolean).join(" "),
5484
+ onMouseEnter: () => nav.setActive(i),
5412
5485
  onClick: () => pick(o),
5413
5486
  children: [
5414
5487
  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 +5507,7 @@ function Select({
5434
5507
  }
5435
5508
 
5436
5509
  // src/components/forms/TimePicker.tsx
5437
- import * as React35 from "react";
5510
+ import * as React36 from "react";
5438
5511
  import { jsx as jsx63, jsxs as jsxs57 } from "react/jsx-runtime";
5439
5512
  var pad = (n) => String(n).padStart(2, "0");
5440
5513
  function ClockFace({ value = "09:00", onChange }) {
@@ -5443,9 +5516,9 @@ function ClockFace({ value = "09:00", onChange }) {
5443
5516
  if (isNaN(m)) m = 0;
5444
5517
  const pm = h24 >= 12;
5445
5518
  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);
5519
+ const [mode2, setMode] = React36.useState("h");
5520
+ const faceRef = React36.useRef(null);
5521
+ const dragging = React36.useRef(false);
5449
5522
  const set = (h, mm, isPm) => onChange((isPm ? h % 12 + 12 : h % 12) + ":" + pad(mm));
5450
5523
  const R = 108, NR = 80;
5451
5524
  const nums = mode2 === "h" ? Array.from({ length: 12 }, (_, i) => i + 1) : Array.from({ length: 12 }, (_, i) => i * 5);
@@ -5518,9 +5591,9 @@ function ClockFace({ value = "09:00", onChange }) {
5518
5591
  }
5519
5592
  function TimePicker({ label, help, error, required = false, disabled = false, value = "", onChange, placeholder = "Pick a time", id, className = "", style }) {
5520
5593
  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);
5594
+ const [open, setOpen] = React36.useState(false);
5595
+ const rootRef = React36.useRef(null);
5596
+ const boxRef = React36.useRef(null);
5524
5597
  const disp = () => {
5525
5598
  if (!value) return "";
5526
5599
  const [h, m] = value.split(":").map(Number);
@@ -5563,7 +5636,7 @@ function TimePicker({ label, help, error, required = false, disabled = false, va
5563
5636
  offset: 6,
5564
5637
  width: 262,
5565
5638
  role: "presentation",
5566
- footer: /* @__PURE__ */ jsxs57(React35.Fragment, { children: [
5639
+ footer: /* @__PURE__ */ jsxs57(React36.Fragment, { children: [
5567
5640
  /* @__PURE__ */ jsx63(
5568
5641
  "button",
5569
5642
  {
@@ -5591,7 +5664,7 @@ function TimePicker({ label, help, error, required = false, disabled = false, va
5591
5664
  }
5592
5665
 
5593
5666
  // src/components/forms/Slider.tsx
5594
- import * as React36 from "react";
5667
+ import * as React37 from "react";
5595
5668
  import { jsx as jsx64, jsxs as jsxs58 } from "react/jsx-runtime";
5596
5669
  function Slider({
5597
5670
  label,
@@ -5609,7 +5682,7 @@ function Slider({
5609
5682
  ...rest
5610
5683
  }) {
5611
5684
  const fieldId = useFieldId(id);
5612
- const [dragging, setDragging] = React36.useState(false);
5685
+ const [dragging, setDragging] = React37.useState(false);
5613
5686
  const v = value === void 0 ? min : Number(value);
5614
5687
  const pct = max === min ? 0 : (v - min) / (max - min) * 100;
5615
5688
  return /* @__PURE__ */ jsxs58("div", { className: ["fd-field", className].filter(Boolean).join(" "), children: [
@@ -5643,7 +5716,7 @@ function Slider({
5643
5716
  }
5644
5717
 
5645
5718
  // src/components/forms/RangeSlider.tsx
5646
- import * as React37 from "react";
5719
+ import * as React38 from "react";
5647
5720
  import { jsx as jsx65, jsxs as jsxs59 } from "react/jsx-runtime";
5648
5721
  function RangeSlider({
5649
5722
  label,
@@ -5663,9 +5736,9 @@ function RangeSlider({
5663
5736
  }) {
5664
5737
  const fmt2 = format || ((v) => String(v));
5665
5738
  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);
5739
+ const [drag, setDrag] = React38.useState(null);
5740
+ const [focus, setFocus] = React38.useState(null);
5741
+ const railRef = React38.useRef(null);
5669
5742
  const pct = (v) => max === min ? 0 : (v - min) / (max - min) * 100;
5670
5743
  const clampPair = (i, v) => {
5671
5744
  v = Math.min(max, Math.max(min, Math.round(v / step) * step));
@@ -5680,7 +5753,7 @@ function RangeSlider({
5680
5753
  const r = railRef.current.getBoundingClientRect();
5681
5754
  return min + Math.min(1, Math.max(0, (e.clientX - r.left) / r.width)) * (max - min);
5682
5755
  };
5683
- React37.useEffect(() => {
5756
+ React38.useEffect(() => {
5684
5757
  if (drag === null) return;
5685
5758
  const mv = (e) => onChange && onChange(clampPair(drag, fromEvent(e)));
5686
5759
  const upH = () => setDrag(null);
@@ -5761,7 +5834,7 @@ function RangeSlider({
5761
5834
  /* @__PURE__ */ jsxs59("div", { className: "fd-range" + (hasLabels ? " has-labels" : ""), onPointerDown: onRailDown, children: [
5762
5835
  /* @__PURE__ */ jsx65("span", { className: "fd-range-rail", ref: railRef }),
5763
5836
  /* @__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: [
5837
+ marks.map((m) => /* @__PURE__ */ jsxs59(React38.Fragment, { children: [
5765
5838
  /* @__PURE__ */ jsx65("span", { className: "fd-range-mark" + (m.value >= a && m.value <= b ? " is-in" : ""), style: { left: pct(m.value) + "%" } }),
5766
5839
  m.label ? /* @__PURE__ */ jsx65("span", { className: "fd-range-mark-label", style: { left: pct(m.value) + "%" }, children: m.label }) : null
5767
5840
  ] }, m.value)),
@@ -5790,7 +5863,7 @@ function RangeSlider({
5790
5863
  }
5791
5864
 
5792
5865
  // src/components/forms/Dropzone.tsx
5793
- import * as React38 from "react";
5866
+ import * as React39 from "react";
5794
5867
  import { jsx as jsx66, jsxs as jsxs60 } from "react/jsx-runtime";
5795
5868
  function Dropzone({
5796
5869
  onFiles,
@@ -5805,8 +5878,8 @@ function Dropzone({
5805
5878
  className = "",
5806
5879
  style
5807
5880
  }) {
5808
- const [over, setOver] = React38.useState(false);
5809
- const depth = React38.useRef(0);
5881
+ const [over, setOver] = React39.useState(false);
5882
+ const depth = React39.useRef(0);
5810
5883
  const has = (e) => {
5811
5884
  const dt = e.dataTransfer;
5812
5885
  if (!dt) return false;
@@ -5868,8 +5941,8 @@ function FilePickButton({
5868
5941
  className = "",
5869
5942
  children
5870
5943
  }) {
5871
- const ref = React38.useRef(null);
5872
- return /* @__PURE__ */ jsxs60(React38.Fragment, { children: [
5944
+ const ref = React39.useRef(null);
5945
+ return /* @__PURE__ */ jsxs60(React39.Fragment, { children: [
5873
5946
  /* @__PURE__ */ jsx66(
5874
5947
  "button",
5875
5948
  {
@@ -5902,10 +5975,10 @@ function FilePickButton({
5902
5975
  }
5903
5976
  function useStagedFiles(upload, opts) {
5904
5977
  const o = opts || {};
5905
- const [items, setItems] = React38.useState([]);
5906
- const controllers = React38.useRef({});
5978
+ const [items, setItems] = React39.useState([]);
5979
+ const controllers = React39.useRef({});
5907
5980
  const patch = (id, next) => setItems((list) => list.map((f) => f.id === id ? Object.assign({}, f, next) : f));
5908
- const run = React38.useCallback((att) => {
5981
+ const run = React39.useCallback((att) => {
5909
5982
  if (!upload) return;
5910
5983
  const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
5911
5984
  controllers.current[att.id] = ac;
@@ -5922,14 +5995,14 @@ function useStagedFiles(upload, opts) {
5922
5995
  delete controllers.current[att.id];
5923
5996
  });
5924
5997
  }, [upload]);
5925
- const add = React38.useCallback((files) => {
5998
+ const add = React39.useCallback((files) => {
5926
5999
  if (!upload) return [];
5927
6000
  const atts = Array.from(files).map((f) => toAttachment(f));
5928
6001
  setItems((list) => list.concat(atts));
5929
6002
  atts.forEach(run);
5930
6003
  return atts;
5931
6004
  }, [upload, run]);
5932
- const remove = React38.useCallback((att) => {
6005
+ const remove = React39.useCallback((att) => {
5933
6006
  const ac = controllers.current[att.id];
5934
6007
  if (ac) {
5935
6008
  try {
@@ -5940,10 +6013,10 @@ function useStagedFiles(upload, opts) {
5940
6013
  }
5941
6014
  setItems((list) => list.filter((f) => f.id !== att.id));
5942
6015
  }, []);
5943
- const retry = React38.useCallback((att) => {
6016
+ const retry = React39.useCallback((att) => {
5944
6017
  run(att);
5945
6018
  }, [run]);
5946
- const clear = React38.useCallback(() => {
6019
+ const clear = React39.useCallback(() => {
5947
6020
  Object.values(controllers.current).forEach((ac) => {
5948
6021
  try {
5949
6022
  ac && ac.abort();
@@ -6067,7 +6140,7 @@ function FileGrid({ files = [], onOpen, onRemove, onRetry, tiles = true, maxHeig
6067
6140
  }
6068
6141
 
6069
6142
  // src/components/forms/MarkdownEditor.tsx
6070
- import * as React39 from "react";
6143
+ import * as React40 from "react";
6071
6144
  import { jsx as jsx68, jsxs as jsxs62 } from "react/jsx-runtime";
6072
6145
  var isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent || "");
6073
6146
  var INLINE_RE = /(\*\*\*[^*\n]+\*\*\*|\*\*[^*\n]+\*\*|__[^_\n]+__|~~[^~\n]+~~|`[^`\n]+`|\*[^*\s][^*\n]*\*|(?<![A-Za-z0-9_])_[^_\s][^_\n]*_|\[[^\]\n]*\]\([^)\s\n]*\)|https?:\/\/\S+)/g;
@@ -6281,7 +6354,7 @@ function syncDom(root, value) {
6281
6354
  while (root.children.length > lines.length) root.removeChild(root.lastChild);
6282
6355
  }
6283
6356
  var LIST_CONT = RE_LI;
6284
- var MarkdownEditor = React39.forwardRef(function MarkdownEditor2({
6357
+ var MarkdownEditor = React40.forwardRef(function MarkdownEditor2({
6285
6358
  value = "",
6286
6359
  onChange,
6287
6360
  onSubmit,
@@ -6300,10 +6373,10 @@ var MarkdownEditor = React39.forwardRef(function MarkdownEditor2({
6300
6373
  className = "",
6301
6374
  id
6302
6375
  }, ref) {
6303
- const boxRef = React39.useRef(null);
6304
- const composing = React39.useRef(false);
6305
- const pendingCaret = React39.useRef(null);
6306
- React39.useLayoutEffect(() => {
6376
+ const boxRef = React40.useRef(null);
6377
+ const composing = React40.useRef(false);
6378
+ const pendingCaret = React40.useRef(null);
6379
+ React40.useLayoutEffect(() => {
6307
6380
  const root = boxRef.current;
6308
6381
  if (!root || composing.current) return;
6309
6382
  const active = document.activeElement === root || root.contains(document.activeElement);
@@ -6312,7 +6385,7 @@ var MarkdownEditor = React39.forwardRef(function MarkdownEditor2({
6312
6385
  pendingCaret.current = null;
6313
6386
  if (active && caret != null) placeCaret(root, caret);
6314
6387
  }, [value]);
6315
- React39.useEffect(() => {
6388
+ React40.useEffect(() => {
6316
6389
  if (autoFocus && boxRef.current) boxRef.current.focus();
6317
6390
  }, [autoFocus]);
6318
6391
  const caretNow = () => {
@@ -6379,7 +6452,7 @@ var MarkdownEditor = React39.forwardRef(function MarkdownEditor2({
6379
6452
  api.replaceRange(from, to, next, from + next.length);
6380
6453
  }
6381
6454
  };
6382
- React39.useImperativeHandle(ref, () => api);
6455
+ React40.useImperativeHandle(ref, () => api);
6383
6456
  function detect(text, caret) {
6384
6457
  if (!onTrigger) return;
6385
6458
  const upto = text.slice(0, caret);
@@ -6531,16 +6604,16 @@ var MarkdownEditor = React39.forwardRef(function MarkdownEditor2({
6531
6604
  });
6532
6605
 
6533
6606
  // src/components/platform/AccountMenu.tsx
6534
- import * as React41 from "react";
6607
+ import * as React42 from "react";
6535
6608
 
6536
6609
  // src/kits/sessionContext.tsx
6537
- import * as React40 from "react";
6538
- var SessionContext = React40.createContext(null);
6610
+ import * as React41 from "react";
6611
+ var SessionContext = React41.createContext(null);
6539
6612
  function SessionProvider({ value, children }) {
6540
- return React40.createElement(SessionContext.Provider, { value }, children);
6613
+ return React41.createElement(SessionContext.Provider, { value }, children);
6541
6614
  }
6542
6615
  function useSessionContext() {
6543
- const ctx = React40.useContext(SessionContext);
6616
+ const ctx = React41.useContext(SessionContext);
6544
6617
  if (!ctx) {
6545
6618
  throw new Error(
6546
6619
  "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 +6653,11 @@ function AccountMenu({
6580
6653
  ...rest
6581
6654
  }) {
6582
6655
  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);
6656
+ const [open, setOpen] = React42.useState(false);
6657
+ const [switching, setSwitching] = React42.useState(false);
6658
+ const [signingOut, setSigningOut] = React42.useState(false);
6659
+ const [signOutError, setSignOutError] = React42.useState(null);
6660
+ const ref = React42.useRef(null);
6588
6661
  const visibleLinks = links.filter((l) => !l.perm || can2(l.perm));
6589
6662
  const visibleGroups = groups.map((g) => ({ label: g.label, links: g.links.filter((l) => !l.perm || can2(l.perm)) })).filter((g) => g.links.length);
6590
6663
  const closeMenu = () => {
@@ -6709,13 +6782,13 @@ function AccountMenu({
6709
6782
  }
6710
6783
 
6711
6784
  // src/components/platform/AppSwitcher.tsx
6712
- import * as React42 from "react";
6785
+ import * as React43 from "react";
6713
6786
  import { jsx as jsx70, jsxs as jsxs64 } from "react/jsx-runtime";
6714
6787
  function AppSwitcher({ apps: appsProp, onNavigate, className = "" }) {
6715
6788
  const { apps: contextApps } = useSessionContext();
6716
6789
  const apps = appsProp ?? contextApps ?? [];
6717
- const [open, setOpen] = React42.useState(false);
6718
- const anchorRef = React42.useRef(null);
6790
+ const [open, setOpen] = React43.useState(false);
6791
+ const anchorRef = React43.useRef(null);
6719
6792
  if (!apps.length) return null;
6720
6793
  const items = apps.map((app) => ({
6721
6794
  id: app.key,
@@ -6729,7 +6802,7 @@ function AppSwitcher({ apps: appsProp, onNavigate, className = "" }) {
6729
6802
  window.location.href = app.url;
6730
6803
  }
6731
6804
  }));
6732
- return /* @__PURE__ */ jsxs64(React42.Fragment, { children: [
6805
+ return /* @__PURE__ */ jsxs64(React43.Fragment, { children: [
6733
6806
  /* @__PURE__ */ jsx70("span", { ref: anchorRef, className: ["fd-appswitch-anchor", className].filter(Boolean).join(" "), style: { display: "inline-flex" }, children: /* @__PURE__ */ jsx70(
6734
6807
  IconButton,
6735
6808
  {
@@ -6758,10 +6831,10 @@ function AppSwitcher({ apps: appsProp, onNavigate, className = "" }) {
6758
6831
  }
6759
6832
 
6760
6833
  // src/components/platform/AppearanceMenu.tsx
6761
- import * as React44 from "react";
6834
+ import * as React45 from "react";
6762
6835
 
6763
6836
  // src/kits/appearance.ts
6764
- import * as React43 from "react";
6837
+ import * as React44 from "react";
6765
6838
  var DEFAULT_STORAGE_KEY = "fd-appearance";
6766
6839
  var DEFAULT_PREFS = {
6767
6840
  theme: "system",
@@ -6856,11 +6929,11 @@ function applyPrefs(prefs, root = document.documentElement) {
6856
6929
  else root.removeAttribute("data-motion");
6857
6930
  }
6858
6931
  function useAppearancePrefs(store) {
6859
- const prefs = React43.useSyncExternalStore(store.subscribe, store.getSnapshot);
6860
- React43.useEffect(() => {
6932
+ const prefs = React44.useSyncExternalStore(store.subscribe, store.getSnapshot);
6933
+ React44.useEffect(() => {
6861
6934
  applyPrefs(prefs);
6862
6935
  }, [prefs]);
6863
- React43.useEffect(() => {
6936
+ React44.useEffect(() => {
6864
6937
  if (prefs.theme !== "system" || typeof window === "undefined" || typeof window.matchMedia !== "function") {
6865
6938
  return;
6866
6939
  }
@@ -6869,7 +6942,7 @@ function useAppearancePrefs(store) {
6869
6942
  mq.addEventListener("change", onChange);
6870
6943
  return () => mq.removeEventListener("change", onChange);
6871
6944
  }, [prefs]);
6872
- const update = React43.useCallback(
6945
+ const update = React44.useCallback(
6873
6946
  (patch) => {
6874
6947
  store.write({ ...store.getSnapshot(), ...patch });
6875
6948
  },
@@ -6892,15 +6965,15 @@ var TEXT_SCALE_OPTIONS = [
6892
6965
  { value: "xl", label: "XL" }
6893
6966
  ];
6894
6967
  function AppearanceMenu({ className = "", store: storeProp, storageKey, storage, defaults }) {
6895
- const store = React44.useMemo(
6968
+ const store = React45.useMemo(
6896
6969
  () => storeProp ?? createLocalStorageAppearanceStore({ storageKey, storage, defaults }),
6897
6970
  [storeProp, storageKey, storage, defaults]
6898
6971
  );
6899
6972
  const [prefs, update] = useAppearancePrefs(store);
6900
- const [open, setOpen] = React44.useState(false);
6901
- const anchorRef = React44.useRef(null);
6973
+ const [open, setOpen] = React45.useState(false);
6974
+ const anchorRef = React45.useRef(null);
6902
6975
  const patch = (p) => update(p);
6903
- return /* @__PURE__ */ jsxs65(React44.Fragment, { children: [
6976
+ return /* @__PURE__ */ jsxs65(React45.Fragment, { children: [
6904
6977
  /* @__PURE__ */ jsx71("span", { ref: anchorRef, className: ["fd-appearance-anchor", className].filter(Boolean).join(" "), style: { display: "inline-flex" }, children: /* @__PURE__ */ jsx71(
6905
6978
  IconButton,
6906
6979
  {
@@ -6969,14 +7042,14 @@ function AppearanceMenu({ className = "", store: storeProp, storageKey, storage,
6969
7042
  }
6970
7043
 
6971
7044
  // src/components/platform/ApiSpecBrowser.tsx
6972
- import * as React45 from "react";
7045
+ import * as React46 from "react";
6973
7046
  import { jsx as jsx72, jsxs as jsxs66 } from "react/jsx-runtime";
6974
7047
  var METHOD_TONE = { GET: "success", POST: "info", PATCH: "warning", PUT: "warning", DELETE: "danger" };
6975
7048
  function Json({ obj }) {
6976
7049
  return /* @__PURE__ */ jsx72("pre", { className: "fd-json", children: JSON.stringify(obj, null, 2) });
6977
7050
  }
6978
7051
  function Endpoint({ s, onRequest }) {
6979
- const [tried, setTried] = React45.useState(null);
7052
+ const [tried, setTried] = React46.useState(null);
6980
7053
  const run = async () => {
6981
7054
  setTried("busy");
6982
7055
  const t0 = (window.performance || Date).now();
@@ -7049,10 +7122,10 @@ function ApiSpecBrowser({
7049
7122
  className = "",
7050
7123
  ...rest
7051
7124
  }) {
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);
7125
+ const [q, setQ] = React46.useState("");
7126
+ const [method, setMethod] = React46.useState(null);
7127
+ const [mod, setMod] = React46.useState(null);
7128
+ const [listOnly, setListOnly] = React46.useState(false);
7056
7129
  const activeModule = modules && modules.find((m) => m.id === mod);
7057
7130
  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
7131
  const effGroups = groups && groups.length ? groups : [["All endpoints", spec.map((s) => s.id)]];
@@ -7131,7 +7204,7 @@ function ApiSpecBrowser({
7131
7204
  }
7132
7205
 
7133
7206
  // src/components/platform/ProfilePage.tsx
7134
- import * as React46 from "react";
7207
+ import * as React47 from "react";
7135
7208
  import { jsx as jsx73, jsxs as jsxs67 } from "react/jsx-runtime";
7136
7209
  function fieldInitialValue(field, user) {
7137
7210
  if (field.value) return field.value(user);
@@ -7167,21 +7240,21 @@ function ProfilePage({
7167
7240
  }) {
7168
7241
  const { user: sessionUser, roles, permissions, saveProfile: saveProfile2 } = useSessionContext();
7169
7242
  const user = userProp || sessionUser;
7170
- const initialFieldValues = React46.useMemo(() => {
7243
+ const initialFieldValues = React47.useMemo(() => {
7171
7244
  const values2 = {};
7172
7245
  for (const field of fields) values2[field.key] = fieldInitialValue(field, user);
7173
7246
  return values2;
7174
7247
  }, [user.id, fields]);
7175
- const initialNotify = React46.useMemo(() => {
7248
+ const initialNotify = React47.useMemo(() => {
7176
7249
  const values2 = {};
7177
7250
  for (const n of notifications) values2[n.key] = !!user.notify?.[n.key];
7178
7251
  return values2;
7179
7252
  }, [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);
7253
+ const [values, setValues] = React47.useState(initialFieldValues);
7254
+ const [notify, setNotify] = React47.useState(initialNotify);
7255
+ const [saving, setSaving] = React47.useState(false);
7256
+ const [saved, setSaved] = React47.useState(false);
7257
+ const [prevUserId, setPrevUserId] = React47.useState(user.id);
7185
7258
  if (user.id !== prevUserId) {
7186
7259
  setPrevUserId(user.id);
7187
7260
  setValues(initialFieldValues);
@@ -7369,11 +7442,11 @@ function ProfilePage({
7369
7442
  }
7370
7443
 
7371
7444
  // src/components/platform/SignInPage.tsx
7372
- import * as React47 from "react";
7445
+ import * as React48 from "react";
7373
7446
  import { Fragment as Fragment18, jsx as jsx74, jsxs as jsxs68 } from "react/jsx-runtime";
7374
7447
  function SignInPage({ productName, onSignIn, continueAs, error }) {
7375
- const [signingIn, setSigningIn] = React47.useState(false);
7376
- const [resuming, setResuming] = React47.useState(false);
7448
+ const [signingIn, setSigningIn] = React48.useState(false);
7449
+ const [resuming, setResuming] = React48.useState(false);
7377
7450
  function handleSignIn() {
7378
7451
  setSigningIn(true);
7379
7452
  onSignIn();
@@ -7562,10 +7635,10 @@ function GoogleGlyph() {
7562
7635
  }
7563
7636
 
7564
7637
  // src/components/platform/RoadmapTimeline.tsx
7565
- import * as React50 from "react";
7638
+ import * as React51 from "react";
7566
7639
 
7567
7640
  // src/kits/session.ts
7568
- import * as React48 from "react";
7641
+ import * as React49 from "react";
7569
7642
  var PERMISSION_CATALOG = [
7570
7643
  { group: "Plans", items: [
7571
7644
  { key: "plan.view", label: "View plans", detail: "Read any plan in the workspace." },
@@ -9132,8 +9205,8 @@ function roadmap(overrides) {
9132
9205
  };
9133
9206
  }
9134
9207
  function useSession() {
9135
- const [s, setS] = React48.useState(getSession);
9136
- React48.useEffect(() => subscribe(setS), []);
9208
+ const [s, setS] = React49.useState(getSession);
9209
+ React49.useEffect(() => subscribe(setS), []);
9137
9210
  return s;
9138
9211
  }
9139
9212
  var SessionKit = {
@@ -9166,7 +9239,7 @@ var SessionKit = {
9166
9239
  };
9167
9240
  function DemoSessionProvider({ children }) {
9168
9241
  const session = useSession();
9169
- const value = React48.useMemo(
9242
+ const value = React49.useMemo(
9170
9243
  () => ({
9171
9244
  user: session.user,
9172
9245
  roles: session.roles,
@@ -9182,11 +9255,11 @@ function DemoSessionProvider({ children }) {
9182
9255
  }),
9183
9256
  [session]
9184
9257
  );
9185
- return React48.createElement(SessionProvider, { value }, children);
9258
+ return React49.createElement(SessionProvider, { value }, children);
9186
9259
  }
9187
9260
 
9188
9261
  // src/kits/runtime.ts
9189
- import * as React49 from "react";
9262
+ import * as React50 from "react";
9190
9263
  var WIRED_ENDPOINTS = [
9191
9264
  // Nothing yet. Every id below would come from a real service:
9192
9265
  // "plan.get", "placements.list", …
@@ -9357,8 +9430,8 @@ var RuntimeKit = {
9357
9430
  };
9358
9431
  RuntimeKit.declare(FEATURE_NEEDS);
9359
9432
  function useRuntimeMode() {
9360
- const [m, setM] = React49.useState(RuntimeKit.getMode());
9361
- React49.useEffect(() => RuntimeKit.subscribe(setM), []);
9433
+ const [m, setM] = React50.useState(RuntimeKit.getMode());
9434
+ React50.useEffect(() => RuntimeKit.subscribe(setM), []);
9362
9435
  return m;
9363
9436
  }
9364
9437
  function useFeatureStatus(key) {
@@ -9441,12 +9514,12 @@ function RoadmapCard({ item, byKey, onOpenFlag }) {
9441
9514
  }
9442
9515
  function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
9443
9516
  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(() => {
9517
+ const [project, setProject] = React51.useState("");
9518
+ const [q, setQ] = React51.useState("");
9519
+ const scrollRef = React51.useRef(null);
9520
+ const nowRef = React51.useRef(null);
9521
+ const rm = React51.useMemo(() => SessionKit.roadmap({ isComplete: RuntimeKit.isComplete }), [session]);
9522
+ const byKey = React51.useMemo(() => {
9450
9523
  const m = {};
9451
9524
  rm.items.forEach((i) => {
9452
9525
  m[i.key] = i;
@@ -9455,7 +9528,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
9455
9528
  }, [rm]);
9456
9529
  const items = rm.items.filter((i) => (!project || i.project === project) && (!q || (i.label + " " + i.description + " " + (i.backend || "") + " " + i.key).toLowerCase().includes(q.toLowerCase())));
9457
9530
  const projects = [...new Set(rm.items.map((i) => i.project))];
9458
- React50.useEffect(() => {
9531
+ React51.useEffect(() => {
9459
9532
  let raf1 = 0, raf2 = 0;
9460
9533
  const place = () => {
9461
9534
  const box = scrollRef.current, mark = nowRef.current;
@@ -9529,7 +9602,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
9529
9602
  lastPhase = item.phase;
9530
9603
  const inPhase = items.filter((i) => i.phase === item.phase).length;
9531
9604
  const isBoundary = n === firstPending;
9532
- return /* @__PURE__ */ jsxs69(React50.Fragment, { children: [
9605
+ return /* @__PURE__ */ jsxs69(React51.Fragment, { children: [
9533
9606
  showPhase ? /* @__PURE__ */ jsxs69("div", { className: "fd-rm-era", children: [
9534
9607
  /* @__PURE__ */ jsxs69("span", { className: "fd-row", style: { gap: 8, flexWrap: "wrap", alignItems: "baseline" }, children: [
9535
9608
  /* @__PURE__ */ jsx75("span", { style: { fontWeight: 700 }, children: item.phase === 0 ? "Shipped" : "Phase " + item.phase + " \u2014 " + item.phaseName }),
@@ -9573,7 +9646,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
9573
9646
  }
9574
9647
 
9575
9648
  // src/components/platform/ComingSoon.tsx
9576
- import * as React51 from "react";
9649
+ import * as React52 from "react";
9577
9650
  import { createPortal as createPortal5 } from "react-dom";
9578
9651
  import { Fragment as Fragment20, jsx as jsx76, jsxs as jsxs70 } from "react/jsx-runtime";
9579
9652
  var BYPASS_STORE = "fd.soon.bypass.v1";
@@ -9591,8 +9664,8 @@ function writeBypassed(list) {
9591
9664
  }
9592
9665
  }
9593
9666
  function useBypass(key) {
9594
- const [on, setOn] = React51.useState(() => !!key && readBypassed().indexOf(key) >= 0);
9595
- React51.useEffect(() => {
9667
+ const [on, setOn] = React52.useState(() => !!key && readBypassed().indexOf(key) >= 0);
9668
+ React52.useEffect(() => {
9596
9669
  setOn(!!key && readBypassed().indexOf(key) >= 0);
9597
9670
  }, [key]);
9598
9671
  const set = (next) => {
@@ -9639,9 +9712,9 @@ function SoonCard({ label, detail, backend, eta, effort, onRoadmap, allowed, onB
9639
9712
  ] });
9640
9713
  }
9641
9714
  function useHoverCard(open) {
9642
- const anchor = React51.useRef(null);
9643
- const [pos, setPos] = React51.useState(null);
9644
- React51.useLayoutEffect(() => {
9715
+ const anchor = React52.useRef(null);
9716
+ const [pos, setPos] = React52.useState(null);
9717
+ React52.useLayoutEffect(() => {
9645
9718
  if (!open || !anchor.current) {
9646
9719
  setPos(null);
9647
9720
  return;
@@ -9768,10 +9841,10 @@ function ComingSoon({
9768
9841
  ] });
9769
9842
  }
9770
9843
  function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, onBypass, blur, tip, className, rest, children }) {
9771
- const [open, setOpen] = React51.useState(false);
9844
+ const [open, setOpen] = React52.useState(false);
9772
9845
  const [anchor, pos] = useHoverCard(open);
9773
9846
  const zIndex = useOverlayLayer(open);
9774
- const close = React51.useRef(null);
9847
+ const close = React52.useRef(null);
9775
9848
  const show = () => {
9776
9849
  if (close.current) {
9777
9850
  clearTimeout(close.current);
@@ -9787,7 +9860,7 @@ function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, o
9787
9860
  setOpen(false);
9788
9861
  }, 140);
9789
9862
  };
9790
- React51.useEffect(() => () => {
9863
+ React52.useEffect(() => () => {
9791
9864
  if (close.current) clearTimeout(close.current);
9792
9865
  }, []);
9793
9866
  return /* @__PURE__ */ jsxs70(
@@ -9958,14 +10031,14 @@ function PermissionHint({ perm, children }) {
9958
10031
  }
9959
10032
 
9960
10033
  // src/components/platform/ModeSwitch.tsx
9961
- import * as React52 from "react";
10034
+ import * as React53 from "react";
9962
10035
  import { Fragment as Fragment21, jsx as jsx78, jsxs as jsxs72 } from "react/jsx-runtime";
9963
10036
  function ModeSwitch({ canToggle = false, requestCount = 0, onClearLog, renderLog, onOpenSpec, summary }) {
9964
10037
  const mode2 = useRuntimeMode();
9965
- const [open, setOpen] = React52.useState(false);
10038
+ const [open, setOpen] = React53.useState(false);
9966
10039
  const zIndex = useOverlayLayer(open);
9967
- const ref = React52.useRef(null);
9968
- React52.useEffect(() => {
10040
+ const ref = React53.useRef(null);
10041
+ React53.useEffect(() => {
9969
10042
  if (!open) return;
9970
10043
  const away = (e) => {
9971
10044
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -10221,7 +10294,7 @@ function SaturationDistribution({ campuses = [], floorBand, floorScore, onSelect
10221
10294
  }
10222
10295
 
10223
10296
  // src/components/planner/MixGap.tsx
10224
- import * as React53 from "react";
10297
+ import * as React54 from "react";
10225
10298
  import { Fragment as Fragment22, jsx as jsx81, jsxs as jsxs75 } from "react/jsx-runtime";
10226
10299
  var MIX_GAP_LAYOUT = {
10227
10300
  trackHeight: 30,
@@ -10236,7 +10309,7 @@ function mixGapOffset(value, max) {
10236
10309
  }
10237
10310
  function MixGap({ rows = [], loading = false, className = "" }) {
10238
10311
  const max = Math.max(1, ...rows.flatMap((r) => [r.target, r.realized]));
10239
- const [hover, setHover] = React53.useState(null);
10312
+ const [hover, setHover] = React54.useState(null);
10240
10313
  const toneOf = (gap) => gap >= -1 ? "ok" : gap >= -4 ? "warn" : "danger";
10241
10314
  const TONE = { ok: "var(--ok-solid)", warn: "var(--warn-solid)", danger: "var(--danger-solid)" };
10242
10315
  const { trackHeight, barHeight, targetWidth, labelGap } = MIX_GAP_LAYOUT;
@@ -10379,7 +10452,7 @@ function ChannelContribution({
10379
10452
  }
10380
10453
 
10381
10454
  // src/components/planner/BudgetReallocator.tsx
10382
- import * as React54 from "react";
10455
+ import * as React55 from "react";
10383
10456
  import { Fragment as Fragment23, jsx as jsx83, jsxs as jsxs77 } from "react/jsx-runtime";
10384
10457
  var bandFor = (crp) => crp >= 200 ? "dominant" : crp >= 100 ? "strong" : crp >= 50 ? "adequate" : "weak";
10385
10458
  function BudgetReallocator({
@@ -10395,8 +10468,8 @@ function BudgetReallocator({
10395
10468
  onCancel,
10396
10469
  className = ""
10397
10470
  }) {
10398
- const [draft, setDraft] = React54.useState(spend);
10399
- React54.useEffect(() => setDraft(spend), [spend]);
10471
+ const [draft, setDraft] = React55.useState(spend);
10472
+ React55.useEffect(() => setDraft(spend), [spend]);
10400
10473
  const dirty = draft !== spend;
10401
10474
  const nextCrp = scoreFor ? scoreFor(draft) : crp;
10402
10475
  const nextBand = bandFor(nextCrp);
@@ -10411,10 +10484,10 @@ function BudgetReallocator({
10411
10484
  setDraft(spend);
10412
10485
  if (onCancel) onCancel();
10413
10486
  };
10414
- const trackRef = React54.useRef(null);
10415
- const chipRef = React54.useRef(null);
10416
- const [chipLeft, setChipLeft] = React54.useState(null);
10417
- React54.useLayoutEffect(() => {
10487
+ const trackRef = React55.useRef(null);
10488
+ const chipRef = React55.useRef(null);
10489
+ const [chipLeft, setChipLeft] = React55.useState(null);
10490
+ React55.useLayoutEffect(() => {
10418
10491
  const track = trackRef.current;
10419
10492
  if (!dirty || !track) {
10420
10493
  setChipLeft(null);
@@ -10601,7 +10674,7 @@ function ImportanceTag({ level, showLabel = true, size = "sm", className = "" })
10601
10674
  }
10602
10675
 
10603
10676
  // src/components/planner/PreferenceMeter.tsx
10604
- import * as React55 from "react";
10677
+ import * as React56 from "react";
10605
10678
  import { jsx as jsx86, jsxs as jsxs80 } from "react/jsx-runtime";
10606
10679
  function preferenceScore(criteria) {
10607
10680
  const earned = criteria.reduce((n, c) => n + (Number(c.earned) || 0), 0);
@@ -10631,7 +10704,7 @@ function PreferenceMeter({
10631
10704
  className = ""
10632
10705
  }) {
10633
10706
  const value = score == null ? preferenceScore(criteria) : score;
10634
- const segments = React55.useMemo(() => preferenceSegments(criteria), [criteria]);
10707
+ const segments = React56.useMemo(() => preferenceSegments(criteria), [criteria]);
10635
10708
  return /* @__PURE__ */ jsx86(
10636
10709
  ScoreMeter,
10637
10710
  {
@@ -10670,10 +10743,10 @@ function PreferenceMeter({
10670
10743
  }
10671
10744
 
10672
10745
  // src/components/chat/AgentChatPanel.tsx
10673
- import * as React61 from "react";
10746
+ import * as React62 from "react";
10674
10747
 
10675
10748
  // src/components/chat/chatEngine.ts
10676
- import * as React56 from "react";
10749
+ import * as React57 from "react";
10677
10750
  var CHAT_UNAVAILABLE = "chat_unavailable";
10678
10751
  var JOB_PENDING = ["queued", "running"];
10679
10752
  var JOB_SUCCESS = ["completed", "recovered"];
@@ -10727,22 +10800,22 @@ function useChatEngine(opts) {
10727
10800
  onClear,
10728
10801
  onFeedback
10729
10802
  } = 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(() => {
10803
+ const [status, setStatus] = React57.useState("idle");
10804
+ const [threadId, setThreadId] = React57.useState(null);
10805
+ const [messages, setMessages] = React57.useState([]);
10806
+ const [queue, setQueue] = React57.useState([]);
10807
+ const [fatal, setFatal] = React57.useState(null);
10808
+ const [busy, setBusy] = React57.useState(false);
10809
+ const [turnStartedAt, setTurnStartedAt] = React57.useState(null);
10810
+ const listRef = React57.useRef([]);
10811
+ const queueRef = React57.useRef([]);
10812
+ const busyRef = React57.useRef(false);
10813
+ const stoppedRef = React57.useRef(false);
10814
+ const abortRef = React57.useRef(null);
10815
+ const serverCount = React57.useRef(0);
10816
+ const threadRef = React57.useRef(null);
10817
+ const mounted = React57.useRef(true);
10818
+ React57.useEffect(() => {
10746
10819
  mounted.current = true;
10747
10820
  return () => {
10748
10821
  mounted.current = false;
@@ -10764,14 +10837,14 @@ function useChatEngine(opts) {
10764
10837
  }
10765
10838
  return false;
10766
10839
  };
10767
- const loadThread = React56.useCallback(async (id) => {
10840
+ const loadThread = React57.useCallback(async (id) => {
10768
10841
  const data = await apiAdapter.getThread(id);
10769
10842
  const list = [...data && data.messages || []];
10770
10843
  serverCount.current = list.length;
10771
10844
  commit(list);
10772
10845
  return list;
10773
10846
  }, [apiAdapter]);
10774
- React56.useEffect(() => {
10847
+ React57.useEffect(() => {
10775
10848
  if (!apiAdapter) {
10776
10849
  setStatus("idle");
10777
10850
  setFatal(null);
@@ -10984,7 +11057,7 @@ function useChatEngine(opts) {
10984
11057
  await dispatchTurn(turn);
10985
11058
  }
10986
11059
  }
10987
- const send = React56.useCallback((text, attachments) => {
11060
+ const send = React57.useCallback((text, attachments) => {
10988
11061
  const body = (text || "").trim();
10989
11062
  if (!body && !(attachments && attachments.length)) return;
10990
11063
  if (status === "disconnected") return;
@@ -10994,7 +11067,7 @@ function useChatEngine(opts) {
10994
11067
  stoppedRef.current = false;
10995
11068
  drain();
10996
11069
  }, [status]);
10997
- const stop = React56.useCallback(() => {
11070
+ const stop = React57.useCallback(() => {
10998
11071
  stoppedRef.current = true;
10999
11072
  const ac = abortRef.current;
11000
11073
  if (ac) {
@@ -11013,11 +11086,11 @@ function useChatEngine(opts) {
11013
11086
  store.del(STORAGE_PREFIX + threadRef.current);
11014
11087
  }
11015
11088
  }, [apiAdapter]);
11016
- const removeQueued = React56.useCallback((id) => {
11089
+ const removeQueued = React57.useCallback((id) => {
11017
11090
  queueRef.current = queueRef.current.filter((t) => t.id !== id);
11018
11091
  setQueue(queueRef.current.slice());
11019
11092
  }, []);
11020
- const retry = React56.useCallback(() => {
11093
+ const retry = React57.useCallback(() => {
11021
11094
  const list = listRef.current;
11022
11095
  let at = -1;
11023
11096
  for (let i = list.length - 1; i >= 0; i--) if (list[i].role === "user") {
@@ -11033,19 +11106,19 @@ function useChatEngine(opts) {
11033
11106
  setQueue(queueRef.current.slice());
11034
11107
  drain();
11035
11108
  }, []);
11036
- const clear = React56.useCallback(() => {
11109
+ const clear = React57.useCallback(() => {
11037
11110
  commit([]);
11038
11111
  serverCount.current = 0;
11039
11112
  queueRef.current = [];
11040
11113
  setQueue([]);
11041
11114
  onClear && onClear();
11042
11115
  }, [onClear]);
11043
- const setFeedback = React56.useCallback((id, value) => {
11116
+ const setFeedback = React57.useCallback((id, value) => {
11044
11117
  patch(id, (m) => ({ feedback: m.feedback === value ? null : value }));
11045
11118
  const msg = listRef.current.find((m) => m.id === id);
11046
11119
  onFeedback && onFeedback({ message: msg, feedback: msg ? msg.feedback : value });
11047
11120
  }, [onFeedback]);
11048
- const reload = React56.useCallback(async () => {
11121
+ const reload = React57.useCallback(async () => {
11049
11122
  if (!threadRef.current) return;
11050
11123
  setStatus("loading");
11051
11124
  try {
@@ -11083,10 +11156,10 @@ var ChatKit = {
11083
11156
  };
11084
11157
 
11085
11158
  // src/components/chat/ChatTranscript.tsx
11086
- import * as React58 from "react";
11159
+ import * as React59 from "react";
11087
11160
 
11088
11161
  // src/components/chat/ChatTurn.tsx
11089
- import * as React57 from "react";
11162
+ import * as React58 from "react";
11090
11163
  import { jsx as jsx87, jsxs as jsxs81 } from "react/jsx-runtime";
11091
11164
  function JsonView({ value }) {
11092
11165
  let text;
@@ -11123,7 +11196,7 @@ function PacketCard({ packet, schema, render, onApply, applied }) {
11123
11196
  ] });
11124
11197
  }
11125
11198
  function ThinkingBlock({ text, durationMs, streaming, defaultOpen = false }) {
11126
- const [open, setOpen] = React57.useState(defaultOpen);
11199
+ const [open, setOpen] = React58.useState(defaultOpen);
11127
11200
  if (!text) return null;
11128
11201
  return /* @__PURE__ */ jsxs81("div", { className: "fdc-think" + (open ? " is-open" : ""), children: [
11129
11202
  /* @__PURE__ */ jsxs81("button", { type: "button", className: "fdc-think-head", onClick: () => setOpen((v) => !v), "aria-expanded": open, children: [
@@ -11140,7 +11213,7 @@ function ThinkingBlock({ text, durationMs, streaming, defaultOpen = false }) {
11140
11213
  ] });
11141
11214
  }
11142
11215
  function Citations({ items = [], onOpen }) {
11143
- const [open, setOpen] = React57.useState(false);
11216
+ const [open, setOpen] = React58.useState(false);
11144
11217
  if (!items.length) return null;
11145
11218
  return /* @__PURE__ */ jsxs81("div", { className: "fdc-cites", children: [
11146
11219
  /* @__PURE__ */ jsxs81("button", { type: "button", className: "fdc-cites-head", onClick: () => setOpen((v) => !v), "aria-expanded": open, children: [
@@ -11166,7 +11239,7 @@ function clampText(text, max) {
11166
11239
  return (at > max * 0.6 ? cut.slice(0, at) : cut).trimEnd() + "\u2026";
11167
11240
  }
11168
11241
  function MessageBody({ message: m, ctx }) {
11169
- const [expanded, setExpanded] = React57.useState(false);
11242
+ const [expanded, setExpanded] = React58.useState(false);
11170
11243
  const isUser = m.role === "user";
11171
11244
  const raw = m.text || "";
11172
11245
  const clamped = !expanded && !m.streaming ? clampText(raw, ctx.maxVisibleChars) : null;
@@ -11233,9 +11306,9 @@ function MessageBody({ message: m, ctx }) {
11233
11306
  ] });
11234
11307
  }
11235
11308
  function useCopyRun() {
11236
- const [done, setDone] = React57.useState(false);
11237
- const t = React57.useRef(null);
11238
- React57.useEffect(() => () => {
11309
+ const [done, setDone] = React58.useState(false);
11310
+ const t = React58.useRef(null);
11311
+ React58.useEffect(() => () => {
11239
11312
  if (t.current) clearTimeout(t.current);
11240
11313
  }, []);
11241
11314
  return [done, (text) => {
@@ -11262,7 +11335,7 @@ function RunActions({ group, ctx }) {
11262
11335
  ] }),
11263
11336
  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
11337
  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: [
11338
+ isAssistant && ctx.onFeedback ? /* @__PURE__ */ jsxs81(React58.Fragment, { children: [
11266
11339
  /* @__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
11340
  /* @__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
11341
  ] }) : null,
@@ -11357,10 +11430,10 @@ function ChatTranscript({
11357
11430
  renderEmpty,
11358
11431
  className = ""
11359
11432
  }) {
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);
11433
+ const scroller = React59.useRef(null);
11434
+ const stick = React59.useRef(true);
11435
+ const [pill, setPill] = React59.useState(0);
11436
+ const seen = React59.useRef(0);
11364
11437
  const toBottom = (smooth) => {
11365
11438
  const el = scroller.current;
11366
11439
  if (!el) return;
@@ -11379,7 +11452,7 @@ function ChatTranscript({
11379
11452
  seen.current = messages.length;
11380
11453
  }
11381
11454
  };
11382
- React58.useLayoutEffect(() => {
11455
+ React59.useLayoutEffect(() => {
11383
11456
  const el = scroller.current;
11384
11457
  if (!el) return;
11385
11458
  if (stick.current) {
@@ -11387,7 +11460,7 @@ function ChatTranscript({
11387
11460
  seen.current = messages.length;
11388
11461
  } else setPill(Math.max(0, messages.length - seen.current));
11389
11462
  }, [messages]);
11390
- React58.useEffect(() => {
11463
+ React59.useEffect(() => {
11391
11464
  const el = scroller.current;
11392
11465
  const inner = el && el.firstChild;
11393
11466
  if (!el || !inner || typeof ResizeObserver === "undefined") return;
@@ -11397,7 +11470,7 @@ function ChatTranscript({
11397
11470
  ro.observe(inner);
11398
11471
  return () => ro.disconnect();
11399
11472
  }, []);
11400
- const groups = React58.useMemo(() => groupMessages(messages), [messages]);
11473
+ const groups = React59.useMemo(() => groupMessages(messages), [messages]);
11401
11474
  const empty = !messages.length && status === "ready";
11402
11475
  return /* @__PURE__ */ jsxs82("div", { className: ["fdc-scroll", className].filter(Boolean).join(" "), ref: scroller, onScroll, children: [
11403
11476
  /* @__PURE__ */ jsxs82("div", { className: "fdc-log", role: "log", "aria-label": "Conversation", children: [
@@ -11421,7 +11494,7 @@ function ChatTranscript({
11421
11494
  const prev = groups[i - 1];
11422
11495
  const k = dayKey(g.messages[0].timestamp);
11423
11496
  const showDay = !!k && (!prev || dayKey(prev.messages[0].timestamp) !== k);
11424
- return /* @__PURE__ */ jsxs82(React58.Fragment, { children: [
11497
+ return /* @__PURE__ */ jsxs82(React59.Fragment, { children: [
11425
11498
  showDay ? /* @__PURE__ */ jsx88("div", { className: "fdc-day", children: /* @__PURE__ */ jsx88("span", { children: dayLabel(k) }) }) : null,
11426
11499
  /* @__PURE__ */ jsx88(ChatTurn, { group: g, ctx })
11427
11500
  ] }, g.key || i);
@@ -11438,7 +11511,7 @@ function ChatTranscript({
11438
11511
  var TranscriptKit = { groupMessages };
11439
11512
 
11440
11513
  // src/components/chat/ChatComposer.tsx
11441
- import * as React59 from "react";
11514
+ import * as React60 from "react";
11442
11515
  import { jsx as jsx89, jsxs as jsxs83 } from "react/jsx-runtime";
11443
11516
  function ChatComposer({
11444
11517
  onSubmit,
@@ -11466,17 +11539,17 @@ function ChatComposer({
11466
11539
  onReject,
11467
11540
  onOpenAttachment
11468
11541
  }) {
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);
11542
+ const [text, setText] = React60.useState(draft || "");
11543
+ const [trigger, setTrigger] = React60.useState(null);
11544
+ const [mentionItems, setMentionItems] = React60.useState([]);
11545
+ const [listening, setListening] = React60.useState(false);
11546
+ const [notice, setNotice] = React60.useState(null);
11547
+ const editor = React60.useRef(null);
11548
+ const wrap = React60.useRef(null);
11549
+ const stopVoice = React60.useRef(null);
11477
11550
  const staged = useStagedFiles(fileUploadHandler, { onError: () => {
11478
11551
  } });
11479
- React59.useEffect(() => {
11552
+ React60.useEffect(() => {
11480
11553
  if (draft != null && draft !== text) setText(draft);
11481
11554
  }, [draft]);
11482
11555
  const change = (v) => {
@@ -11512,7 +11585,7 @@ function ChatComposer({
11512
11585
  e.preventDefault();
11513
11586
  staged.add(found.files);
11514
11587
  };
11515
- React59.useEffect(() => {
11588
+ React60.useEffect(() => {
11516
11589
  if (!trigger || trigger.type !== "mention" || !mentionSources) {
11517
11590
  setMentionItems([]);
11518
11591
  return;
@@ -11530,7 +11603,7 @@ function ChatComposer({
11530
11603
  const q = (trigger.query || "").toLowerCase();
11531
11604
  setMentionItems(mentionSources.filter((m) => !q || (m.label + " " + (m.description || "")).toLowerCase().includes(q)));
11532
11605
  }, [trigger, mentionSources]);
11533
- const slashItems = React59.useMemo(() => {
11606
+ const slashItems = React60.useMemo(() => {
11534
11607
  if (!trigger || trigger.type !== "slash" || !slashCommands) return [];
11535
11608
  const q = (trigger.query || "").toLowerCase();
11536
11609
  return slashCommands.filter((c) => !q || (c.id + " " + c.label + " " + (c.description || "")).toLowerCase().includes(q));
@@ -11704,12 +11777,12 @@ function ChatComposer({
11704
11777
  }
11705
11778
 
11706
11779
  // src/components/chat/ChatSessionBar.tsx
11707
- import * as React60 from "react";
11780
+ import * as React61 from "react";
11708
11781
  import { jsx as jsx90, jsxs as jsxs84 } from "react/jsx-runtime";
11709
11782
  var compact2 = meterFormats.compact;
11710
11783
  function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extras }) {
11711
- const [open, setOpen] = React60.useState(false);
11712
- const anchor = React60.useRef(null);
11784
+ const [open, setOpen] = React61.useState(false);
11785
+ const anchor = React61.useRef(null);
11713
11786
  const stats = sessionStats || null;
11714
11787
  const cu = contextUsage || null;
11715
11788
  const limits = usageLimits || null;
@@ -11724,7 +11797,7 @@ function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extr
11724
11797
  if (stats && stats.runningTasks) bits.push(stats.runningTasks + " running task" + (stats.runningTasks === 1 ? "" : "s"));
11725
11798
  if (cu) bits.push(pct + "% context");
11726
11799
  const expandable = !!(cu || limits);
11727
- return /* @__PURE__ */ jsxs84(React60.Fragment, { children: [
11800
+ return /* @__PURE__ */ jsxs84(React61.Fragment, { children: [
11728
11801
  /* @__PURE__ */ jsxs84("div", { className: "fdc-bar", children: [
11729
11802
  /* @__PURE__ */ jsxs84(
11730
11803
  "button",
@@ -11865,7 +11938,7 @@ function ModelControls({
11865
11938
  ] })
11866
11939
  });
11867
11940
  }
11868
- return /* @__PURE__ */ jsxs84(React60.Fragment, { children: [
11941
+ return /* @__PURE__ */ jsxs84(React61.Fragment, { children: [
11869
11942
  /* @__PURE__ */ jsx90(
11870
11943
  MenuButton,
11871
11944
  {
@@ -11982,11 +12055,11 @@ function AgentChatPanel({
11982
12055
  onFeedback,
11983
12056
  onEditMessage
11984
12057
  }) {
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]);
12058
+ const [panelWidth, setPanelWidth] = React62.useState(width);
12059
+ const [applied, setApplied] = React62.useState({});
12060
+ const [draft, setDraft] = React62.useState("");
12061
+ const dragging = React62.useRef(null);
12062
+ React62.useEffect(() => setPanelWidth(width), [width]);
11990
12063
  const engine = useChatEngine({
11991
12064
  contextType,
11992
12065
  contextId,
@@ -12190,7 +12263,7 @@ function AgentChatPanel({
12190
12263
  }
12191
12264
 
12192
12265
  // src/components/files/FileViewer.tsx
12193
- import * as React62 from "react";
12266
+ import * as React63 from "react";
12194
12267
  import { jsx as jsx92, jsxs as jsxs86 } from "react/jsx-runtime";
12195
12268
  function previewKindOf(file) {
12196
12269
  const mime = (file.mime || "").toLowerCase();
@@ -12247,13 +12320,13 @@ function parseDelimited(text, delimiter) {
12247
12320
  return rows;
12248
12321
  }
12249
12322
  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(() => {
12323
+ const [state, setState] = React63.useState({ status: "loading" });
12324
+ const [attempt, setAttempt] = React63.useState(0);
12325
+ const resolveRef = React63.useRef(resolveUrl);
12326
+ React63.useEffect(() => {
12254
12327
  resolveRef.current = resolveUrl;
12255
12328
  }, [resolveUrl]);
12256
- React62.useEffect(() => {
12329
+ React63.useEffect(() => {
12257
12330
  if (kind === "none") {
12258
12331
  setState({ status: "ready" });
12259
12332
  return;
@@ -12283,7 +12356,7 @@ function usePreview(file, kind, resolveUrl) {
12283
12356
  if (ac) ac.abort();
12284
12357
  };
12285
12358
  }, [file.id, kind, attempt]);
12286
- return { ...state, retry: React62.useCallback(() => setAttempt((a) => a + 1), []) };
12359
+ return { ...state, retry: React63.useCallback(() => setAttempt((a) => a + 1), []) };
12287
12360
  }
12288
12361
  function PreviewBody({ file, kind, state }) {
12289
12362
  if (kind === "image") return /* @__PURE__ */ jsx92("img", { className: "fd-fv-image", src: state.url, alt: file.name || "Preview" });
@@ -12328,13 +12401,13 @@ function PreviewBody({ file, kind, state }) {
12328
12401
  ] });
12329
12402
  }
12330
12403
  function FileViewer({ file, resolveUrl, sidePanel, sidePanelLabel = "Details", onClose, actions, className = "" }) {
12331
- const kind = React62.useMemo(() => previewKindOf(file), [file]);
12404
+ const kind = React63.useMemo(() => previewKindOf(file), [file]);
12332
12405
  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);
12406
+ const [tab, setTab] = React63.useState("preview");
12407
+ const [downloading, setDownloading] = React63.useState(false);
12408
+ const [downloadError, setDownloadError] = React63.useState(null);
12336
12409
  const hasSide = sidePanel != null;
12337
- React62.useEffect(() => {
12410
+ React63.useEffect(() => {
12338
12411
  setTab("preview");
12339
12412
  }, [file.id]);
12340
12413
  const download = () => {
@@ -12401,7 +12474,7 @@ function FileViewer({ file, resolveUrl, sidePanel, sidePanelLabel = "Details", o
12401
12474
  }
12402
12475
 
12403
12476
  // src/components/files/FileBrowser.tsx
12404
- import * as React63 from "react";
12477
+ import * as React64 from "react";
12405
12478
  import { Fragment as Fragment27, jsx as jsx93, jsxs as jsxs87 } from "react/jsx-runtime";
12406
12479
  function childrenOf(folders, parentId) {
12407
12480
  return folders.filter((f) => f.parentId === parentId).sort((a, b) => a.name.localeCompare(b.name));
@@ -12472,10 +12545,10 @@ function FolderPickerMenu({
12472
12545
  destinations,
12473
12546
  onPick
12474
12547
  }) {
12475
- const [open, setOpen] = React63.useState(false);
12548
+ const [open, setOpen] = React64.useState(false);
12476
12549
  const zIndex = useOverlayLayer(open);
12477
- const ref = React63.useRef(null);
12478
- React63.useEffect(() => {
12550
+ const ref = React64.useRef(null);
12551
+ React64.useEffect(() => {
12479
12552
  if (!open) return;
12480
12553
  const away = (e) => {
12481
12554
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -12527,8 +12600,8 @@ function FolderPickerMenu({
12527
12600
  ] });
12528
12601
  }
12529
12602
  function RowShell({ icon, name, meta, onOpen, draggableId, isDropTarget, onDropPayload, actions, className = "" }) {
12530
- const [over, setOver] = React63.useState(false);
12531
- const dragCounter = React63.useRef(0);
12603
+ const [over, setOver] = React64.useState(false);
12604
+ const dragCounter = React64.useRef(0);
12532
12605
  const dropHandlers = isDropTarget ? {
12533
12606
  onDragEnter: (e) => {
12534
12607
  if (!hasInternalPayload(e)) return;
@@ -12576,8 +12649,8 @@ function RowShell({ icon, name, meta, onOpen, draggableId, isDropTarget, onDropP
12576
12649
  }
12577
12650
  function TreeNode({ folder, depth, folders, currentFolderId, onNavigate, onMoveInto, canMove }) {
12578
12651
  const kids = childrenOf(folders, folder.id);
12579
- const [expanded, setExpanded] = React63.useState(() => currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId));
12580
- React63.useEffect(() => {
12652
+ const [expanded, setExpanded] = React64.useState(() => currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId));
12653
+ React64.useEffect(() => {
12581
12654
  if (currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId)) setExpanded(true);
12582
12655
  }, [currentFolderId, folder.id, folders]);
12583
12656
  const isCurrent = folder.id === currentFolderId;
@@ -12643,15 +12716,15 @@ function FileBrowser({
12643
12716
  const canUpload = perm.canUpload !== false && !!onUpload;
12644
12717
  const canRemoveFile = perm.canRemoveFile !== false && !!onRemoveFile;
12645
12718
  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(
12719
+ const [createOpen, setCreateOpen] = React64.useState(false);
12720
+ const [createName, setCreateName] = React64.useState("");
12721
+ const [renameTarget, setRenameTarget] = React64.useState(null);
12722
+ const [renameValue, setRenameValue] = React64.useState("");
12723
+ const [deleteTarget, setDeleteTarget] = React64.useState(null);
12724
+ const uploadInputRef = React64.useRef(null);
12725
+ const crumbFolders = React64.useMemo(() => ancestorsOf(folders, currentFolderId), [folders, currentFolderId]);
12726
+ const subfolders = React64.useMemo(() => childrenOf(folders, currentFolderId), [folders, currentFolderId]);
12727
+ const currentFiles = React64.useMemo(
12655
12728
  () => files.filter((f) => f.folderId === currentFolderId).sort((a, b) => a.name.localeCompare(b.name)),
12656
12729
  [files, currentFolderId]
12657
12730
  );
@@ -12883,8 +12956,8 @@ function BreadcrumbCrumb({
12883
12956
  isDropTarget,
12884
12957
  onDropPayload
12885
12958
  }) {
12886
- const [over, setOver] = React63.useState(false);
12887
- const dragCounter = React63.useRef(0);
12959
+ const [over, setOver] = React64.useState(false);
12960
+ const dragCounter = React64.useRef(0);
12888
12961
  const dropHandlers = isDropTarget ? {
12889
12962
  onDragEnter: (e) => {
12890
12963
  if (!hasInternalPayload(e)) return;
@@ -12913,9 +12986,27 @@ function BreadcrumbCrumb({
12913
12986
  }
12914
12987
 
12915
12988
  // src/kits/query.ts
12916
- import * as React64 from "react";
12989
+ import * as React65 from "react";
12990
+ var isMultiSelectShaped = (v) => !!v && typeof v === "object" && Array.isArray(v.values);
12991
+ var isRangeShaped = (v) => !!v && typeof v === "object" && ("min" in v || "max" in v);
12992
+ var isDateShaped = (v) => !!v && typeof v === "object" && "kind" in v;
12993
+ function isActiveFilterValue(v) {
12994
+ if (v == null || v === "" || v === false) return false;
12995
+ if (Array.isArray(v)) return v.length > 0;
12996
+ if (isMultiSelectShaped(v)) return v.values.length > 0;
12997
+ if (isRangeShaped(v)) return v.min != null || v.max != null;
12998
+ if (isDateShaped(v)) return v.kind === "withinDays" ? v.days != null : !!(v.from && v.to);
12999
+ return true;
13000
+ }
12917
13001
  function eqFilter(get2) {
12918
- return (row, value) => Array.isArray(value) ? value.includes(get2(row)) : get2(row) === value;
13002
+ return (row, value) => {
13003
+ if (Array.isArray(value)) return value.includes(get2(row));
13004
+ if (isMultiSelectShaped(value)) {
13005
+ const hit = value.values.some((o) => o.value === get2(row));
13006
+ return value.operator === "notIn" ? !hit : hit;
13007
+ }
13008
+ return get2(row) === value;
13009
+ };
12919
13010
  }
12920
13011
  function anyOfFilter(get2) {
12921
13012
  return (row, value) => {
@@ -12926,10 +13017,15 @@ function anyOfFilter(get2) {
12926
13017
  function rangeFilter(get2) {
12927
13018
  return (row, value) => {
12928
13019
  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);
13020
+ let lo = 0;
13021
+ let hi = Number.POSITIVE_INFINITY;
13022
+ if (Array.isArray(value)) {
13023
+ lo = Number(value[0] ?? 0);
13024
+ hi = value[1] == null ? Number.POSITIVE_INFINITY : Number(value[1]);
13025
+ } else if (isRangeShaped(value)) {
13026
+ lo = value.min ?? 0;
13027
+ hi = value.max ?? Number.POSITIVE_INFINITY;
13028
+ }
12933
13029
  return n >= lo && (hi === Number.POSITIVE_INFINITY || n <= hi);
12934
13030
  };
12935
13031
  }
@@ -12945,22 +13041,22 @@ function compare(a, b, dir) {
12945
13041
  var API = null;
12946
13042
  var PREFS = null;
12947
13043
  function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
12948
- const [query, setQuery] = React64.useState(() => {
13044
+ const [query, setQuery] = React65.useState(() => {
12949
13045
  const store = PREFS || window.PlannerPrefs;
12950
13046
  const saved = prefsKey && store ? store.getTable(prefsKey) : {};
12951
13047
  return { ...DEFAULTS, ...defaults || {}, ...saved.pageSize ? { pageSize: saved.pageSize } : {}, ...saved.sort ? { sort: saved.sort, dir: saved.dir || "desc" } : {} };
12952
13048
  });
12953
- const savePref = React64.useCallback((patch2) => {
13049
+ const savePref = React65.useCallback((patch2) => {
12954
13050
  const store = PREFS || window.PlannerPrefs;
12955
13051
  if (prefsKey && store) store.setTable(prefsKey, patch2);
12956
13052
  }, [prefsKey]);
12957
- const [res, setRes] = React64.useState(null);
12958
- const [loading, setLoading] = React64.useState(true);
12959
- const seq2 = React64.useRef(0);
13053
+ const [res, setRes] = React65.useState(null);
13054
+ const [loading, setLoading] = React65.useState(true);
13055
+ const seq2 = React65.useRef(0);
12960
13056
  const depKey = (deps || []).join("|");
12961
13057
  const paramKey = JSON.stringify(params || {});
12962
13058
  const queryKey = JSON.stringify(query);
12963
- React64.useEffect(() => {
13059
+ React65.useEffect(() => {
12964
13060
  const id = ++seq2.current;
12965
13061
  setLoading(true);
12966
13062
  const t = setTimeout(() => {
@@ -12996,7 +13092,7 @@ function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
12996
13092
  loading,
12997
13093
  /** True only on the very first load — later loads keep showing stale rows. */
12998
13094
  initialLoading: loading && !res,
12999
- activeFilterCount: Object.values(query.filters).filter((v) => !(v == null || v === "" || v === false)).length,
13095
+ activeFilterCount: Object.values(query.filters).filter(isActiveFilterValue).length,
13000
13096
  setPage: (page) => patch({ page }, false),
13001
13097
  setPageSize: (pageSize) => {
13002
13098
  savePref({ pageSize });
@@ -13059,7 +13155,6 @@ var QueryKit = {
13059
13155
  const Q = { ...DEFAULTS, ...query || {} };
13060
13156
  const c = cfg;
13061
13157
  const filters = Q.filters || {};
13062
- const active = (v) => !(v == null || v === "" || v === false || Array.isArray(v) && !v.length);
13063
13158
  let out = rows;
13064
13159
  const term = (Q.q || "").trim().toLowerCase();
13065
13160
  if (term && c.searchKeys) {
@@ -13069,7 +13164,7 @@ var QueryKit = {
13069
13164
  const searched = out;
13070
13165
  Object.entries(filters).forEach(([id, val]) => {
13071
13166
  const fn = c.filters && c.filters[id];
13072
- if (!active(val) || !fn) return;
13167
+ if (!isActiveFilterValue(val) || !fn) return;
13073
13168
  out = out.filter((r) => fn(r, val));
13074
13169
  });
13075
13170
  const matched = out;
@@ -13116,9 +13211,30 @@ var QueryKit = {
13116
13211
  if (Q.sort) parts.push("sort=" + Q.sort + "&dir=" + Q.dir);
13117
13212
  if (Q.q) parts.push("q=" + encodeURIComponent(Q.q));
13118
13213
  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));
13214
+ if (!isActiveFilterValue(v)) return;
13215
+ if (Array.isArray(v)) {
13216
+ v.forEach((x) => parts.push(k + "=" + encodeURIComponent(x === Infinity ? "*" : x)));
13217
+ return;
13218
+ }
13219
+ if (isMultiSelectShaped(v)) {
13220
+ const prefix = v.operator === "notIn" ? "notIn:" : "";
13221
+ v.values.forEach((o) => parts.push(k + "=" + encodeURIComponent(prefix + String(o.value))));
13222
+ return;
13223
+ }
13224
+ if (isRangeShaped(v)) {
13225
+ if (v.min != null) parts.push(k + ".min=" + encodeURIComponent(String(v.min)));
13226
+ if (v.max != null) parts.push(k + ".max=" + encodeURIComponent(String(v.max)));
13227
+ return;
13228
+ }
13229
+ if (isDateShaped(v)) {
13230
+ if (v.kind === "withinDays") parts.push(k + "=withinDays:" + encodeURIComponent(String(v.days)));
13231
+ else {
13232
+ parts.push(k + ".from=" + encodeURIComponent(String(v.from)));
13233
+ parts.push(k + ".to=" + encodeURIComponent(String(v.to)));
13234
+ }
13235
+ return;
13236
+ }
13237
+ parts.push(k + "=" + encodeURIComponent(v));
13122
13238
  });
13123
13239
  return parts.join("&");
13124
13240
  },
@@ -13259,12 +13375,12 @@ function createVersionStore(initialState, options) {
13259
13375
  }
13260
13376
 
13261
13377
  // src/kits/documentTitle.ts
13262
- import * as React65 from "react";
13378
+ import * as React66 from "react";
13263
13379
  function formatDocumentTitle(routeTitle, appName) {
13264
13380
  return [routeTitle, appName, "Flytedesk"].filter((part) => !!part && part.length > 0).join(" \xB7 ");
13265
13381
  }
13266
13382
  function useDocumentTitle(routeTitle, appName) {
13267
- React65.useEffect(() => {
13383
+ React66.useEffect(() => {
13268
13384
  if (typeof document === "undefined") return;
13269
13385
  document.title = formatDocumentTitle(routeTitle, appName);
13270
13386
  }, [routeTitle, appName]);