@flytedan/flytebot-design-system 0.10.0 → 0.11.1

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
@@ -57,6 +57,7 @@ function IconButton({
57
57
  label,
58
58
  size = "md",
59
59
  variant = "ghost",
60
+ tone = "neutral",
60
61
  round = false,
61
62
  disabled = false,
62
63
  className = "",
@@ -64,7 +65,8 @@ function IconButton({
64
65
  }) {
65
66
  const cls = [
66
67
  "fd-btn-icon",
67
- variant === "outline" ? "fd-btn-icon-outline" : "",
68
+ variant === "outline" ? "fd-btn-icon-outline" : variant === "solid" ? "fd-btn-icon-solid" : "",
69
+ tone !== "neutral" ? "is-" + tone : "",
68
70
  size === "sm" ? "fd-btn-icon-sm" : size === "lg" ? "fd-btn-icon-lg" : "",
69
71
  round ? "fd-btn-icon-round" : "",
70
72
  className
@@ -85,6 +87,23 @@ var samePosition = (a, b) => {
85
87
  if (!a || !b) return a === b;
86
88
  return a.left === b.left && a.top === b.top && a.bottom === b.bottom && a.width === b.width && a.minWidth === b.minWidth && a.maxH === b.maxH && a.side === b.side && a.anchorWidth === b.anchorWidth;
87
89
  };
90
+ var SCROLL_REGIONS = ".fd-pop-body, .fd-pop-list";
91
+ var PINNED_REGIONS = ":scope > .fd-pop-header, :scope > .fd-pop-footer";
92
+ var POPOVER_BODY_MIN = 96;
93
+ function measureLayer(el) {
94
+ if (!el || !el.offsetHeight) return { width: el ? el.offsetWidth : 0, height: 0, pinned: 0 };
95
+ let hidden = 0;
96
+ el.querySelectorAll(SCROLL_REGIONS).forEach((r) => {
97
+ hidden += Math.max(0, r.scrollHeight - r.clientHeight);
98
+ });
99
+ let pinned = 0;
100
+ el.querySelectorAll(PINNED_REGIONS).forEach((r) => {
101
+ pinned += r.offsetHeight;
102
+ });
103
+ const border = Math.max(0, el.offsetHeight - el.clientHeight);
104
+ const content = Math.max(el.scrollHeight, el.clientHeight);
105
+ return { width: el.offsetWidth, height: content + border + hidden, pinned: pinned + border };
106
+ }
88
107
  function usePopoverPosition(open, anchorRef, opts) {
89
108
  const o = opts || {};
90
109
  const { side: wantSide, align } = parsePlacement(o.placement);
@@ -92,6 +111,7 @@ function usePopoverPosition(open, anchorRef, opts) {
92
111
  const posRef = React.useRef(null);
93
112
  const detachRef = React.useRef(o.onDetach);
94
113
  detachRef.current = o.onDetach;
114
+ const layerRef = o.layerRef;
95
115
  React.useLayoutEffect(() => {
96
116
  if (!open || !anchorRef || !anchorRef.current) {
97
117
  posRef.current = null;
@@ -103,23 +123,36 @@ function usePopoverPosition(open, anchorRef, opts) {
103
123
  if (!el) return;
104
124
  const r = el.getBoundingClientRect();
105
125
  const offset = o.offset == null ? 8 : o.offset;
106
- const estH = o.estHeight || 300;
107
- const below = window.innerHeight - r.bottom - offset - MARGIN;
126
+ const layer = measureLayer(layerRef ? layerRef.current : null);
127
+ const vh = window.innerHeight;
128
+ const need = layer.height || Math.min(o.estHeight || 300, 180);
129
+ const below = vh - r.bottom - offset - MARGIN;
108
130
  const above = r.top - offset - MARGIN;
109
131
  let side = wantSide;
110
- if (side === "bottom" && below < Math.min(estH, 180) && above > below) side = "top";
111
- else if (side === "top" && above < Math.min(estH, 180) && below > above) side = "bottom";
112
- const width = o.matchWidth ? r.width : Math.max(o.width || 0, o.minWidth || 0);
113
- const w = width || o.estWidth || 260;
132
+ if (side === "bottom" && below < need && above > below) side = "top";
133
+ else if (side === "top" && above < need && below > above) side = "bottom";
134
+ const ceiling = Math.min(vh - 2 * MARGIN, o.maxHeight != null ? o.maxHeight : Infinity);
135
+ const floor = Math.min(
136
+ ceiling,
137
+ layer.height ? layer.pinned + Math.min(POPOVER_BODY_MIN, layer.height - layer.pinned) : Math.min(need, 140)
138
+ );
139
+ const room = side === "top" ? above : below;
140
+ const beside = room >= floor;
141
+ const edge = beside ? 0 : Math.max(MARGIN, vh - MARGIN - Math.min(need, ceiling));
142
+ const top = side === "bottom" ? beside ? r.bottom + offset : edge : null;
143
+ const bottom = side === "top" ? beside ? vh - r.top + offset : edge : null;
144
+ const maxH = beside ? room : vh - MARGIN - edge;
145
+ const exact = o.matchWidth === true;
146
+ const width = exact ? r.width : Math.max(o.width || 0, o.minWidth || 0, o.matchWidth === "min" ? r.width : 0);
147
+ const w = exact || o.width ? width : Math.max(layer.width, width) || o.estWidth || 260;
114
148
  let left = align === "end" ? r.right - w : align === "center" ? r.left + r.width / 2 - w / 2 : r.left;
115
149
  left = Math.max(MARGIN, Math.min(left, window.innerWidth - w - MARGIN));
116
- const maxH = Math.max(140, side === "top" ? above : below);
117
150
  const next = {
118
151
  left,
119
- width: o.matchWidth ? r.width : void 0,
152
+ width: exact ? r.width : void 0,
120
153
  minWidth: o.minWidth,
121
- top: side === "bottom" ? r.bottom + offset : null,
122
- bottom: side === "top" ? window.innerHeight - r.top + offset : null,
154
+ top,
155
+ bottom,
123
156
  maxH,
124
157
  side,
125
158
  anchorWidth: r.width
@@ -142,7 +175,7 @@ function usePopoverPosition(open, anchorRef, opts) {
142
175
  };
143
176
  raf = requestAnimationFrame(tick);
144
177
  return () => cancelAnimationFrame(raf);
145
- }, [open, o.placement, o.offset, o.matchWidth, o.width, o.minWidth, o.estHeight]);
178
+ }, [open, o.placement, o.offset, o.matchWidth, o.width, o.minWidth, o.maxHeight, o.estHeight]);
146
179
  return pos;
147
180
  }
148
181
  var portal = (node) => {
@@ -152,6 +185,7 @@ var portal = (node) => {
152
185
  function Popover({
153
186
  open,
154
187
  anchorRef,
188
+ triggerRef,
155
189
  onClose,
156
190
  placement = "bottom-start",
157
191
  offset = 8,
@@ -167,6 +201,8 @@ function Popover({
167
201
  returnFocus = true,
168
202
  className = "",
169
203
  style,
204
+ header,
205
+ footer,
170
206
  children
171
207
  }) {
172
208
  const ref = React.useRef(null);
@@ -176,8 +212,10 @@ function Popover({
176
212
  matchWidth,
177
213
  width,
178
214
  minWidth,
215
+ maxHeight,
179
216
  estHeight: maxHeight || 320,
180
- onDetach: onClose
217
+ onDetach: onClose,
218
+ layerRef: ref
181
219
  });
182
220
  const restore = React.useRef(null);
183
221
  React.useEffect(() => {
@@ -198,7 +236,8 @@ function Popover({
198
236
  if (!closeOnOutside) return;
199
237
  const target = e.target;
200
238
  if (ref.current && ref.current.contains(target)) return;
201
- if (anchorRef && anchorRef.current && anchorRef.current.contains(target)) return;
239
+ const trigger = (triggerRef || anchorRef).current;
240
+ if (trigger && trigger.contains(target)) return;
202
241
  if (e.target.closest && e.target.closest(".fd-pop")) return;
203
242
  onClose && onClose();
204
243
  };
@@ -214,23 +253,23 @@ function Popover({
214
253
  document.removeEventListener("pointerdown", away, true);
215
254
  document.removeEventListener("keydown", key);
216
255
  };
217
- }, [open, closeOnOutside, closeOnEscape, onClose, anchorRef]);
256
+ }, [open, closeOnOutside, closeOnEscape, onClose, anchorRef, triggerRef]);
218
257
  if (!open || !pos) return null;
219
258
  return portal(
220
- /* @__PURE__ */ jsx3(
259
+ /* @__PURE__ */ jsxs2(
221
260
  "div",
222
261
  {
223
262
  ref,
224
263
  role,
225
264
  "aria-label": label,
226
- className: ["fd-pop", pos.side === "top" ? "is-up" : "", padded ? "fd-pop-padded" : "", className].filter(Boolean).join(" "),
265
+ className: ["fd-pop", pos.side === "top" ? "is-up" : "", className].filter(Boolean).join(" "),
227
266
  style: {
228
267
  position: "fixed",
229
268
  left: pos.left,
230
269
  top: pos.top == null ? "auto" : pos.top,
231
270
  bottom: pos.bottom == null ? "auto" : pos.bottom,
232
- width: matchWidth ? pos.width : width,
233
- minWidth: minWidth || (matchWidth ? void 0 : 200),
271
+ width: matchWidth === true ? pos.width : width,
272
+ minWidth: matchWidth === "min" ? Math.max(pos.anchorWidth, minWidth || 0) : minWidth || (matchWidth ? void 0 : 200),
234
273
  // Size to content by default — cap only at real available room between the
235
274
  // anchor and the viewport edge (pos.maxH, computed by usePopoverPosition).
236
275
  // A caller-supplied `maxHeight` narrows that further (e.g. a long menu that
@@ -243,7 +282,11 @@ function Popover({
243
282
  zIndex: 140,
244
283
  ...style
245
284
  },
246
- children
285
+ children: [
286
+ header != null ? /* @__PURE__ */ jsx3("div", { className: "fd-pop-header", children: header }) : null,
287
+ /* @__PURE__ */ jsx3("div", { className: ["fd-pop-body", padded ? "fd-pop-padded" : ""].filter(Boolean).join(" "), children }),
288
+ footer != null ? /* @__PURE__ */ jsx3("div", { className: "fd-pop-footer", children: footer }) : null
289
+ ]
247
290
  }
248
291
  )
249
292
  );
@@ -318,77 +361,79 @@ function Menu({ items = [], onSelect, onClose, autoFocus = true, className = "",
318
361
  };
319
362
  const sub = openSub != null ? items[openSub] : null;
320
363
  return /* @__PURE__ */ jsxs2(React.Fragment, { children: [
321
- header,
322
- /* @__PURE__ */ jsx3(
323
- "div",
324
- {
325
- ref: listRef,
326
- tabIndex: -1,
327
- role: "menu",
328
- onKeyDown: onKey,
329
- className: ["fd-pop-list", "fd-menu", className].filter(Boolean).join(" "),
330
- children: items.map((it, i) => {
331
- if (!it) return null;
332
- if (it.kind === "separator") return /* @__PURE__ */ jsx3("div", { className: "fd-menu-sep", role: "separator" }, "s" + i);
333
- if (it.kind === "section") return /* @__PURE__ */ jsx3("div", { className: "fd-pop-group", role: "presentation", children: it.label }, "h" + i);
334
- if (it.kind === "custom") return /* @__PURE__ */ jsx3("div", { className: "fd-menu-custom", children: it.render ? it.render() : null }, "c" + i);
335
- const on = !!it.checked;
336
- const row = /* @__PURE__ */ jsxs2(
337
- "button",
338
- {
339
- type: "button",
340
- "data-i": i,
341
- role: it.checked != null ? "menuitemradio" : "menuitem",
342
- "aria-checked": it.checked != null ? on : void 0,
343
- "aria-haspopup": it.submenu ? "menu" : void 0,
344
- "aria-expanded": it.submenu ? openSub === i : void 0,
345
- "aria-disabled": it.disabled || void 0,
346
- className: ["fd-opt", "fd-menu-item", i === active ? "is-active" : "", on ? "is-selected" : ""].filter(Boolean).join(" "),
347
- onMouseEnter: () => {
348
- setActive(i);
349
- if (openSub != null && openSub !== i) setOpenSub(null);
350
- },
351
- onClick: () => choose(it, i),
352
- children: [
353
- it.icon ? /* @__PURE__ */ jsx3("span", { className: "fd-menu-icon", children: /* @__PURE__ */ jsx3("i", { className: "ph ph-" + it.icon, "aria-hidden": "true" }) }) : null,
354
- /* @__PURE__ */ jsxs2("span", { className: "fd-menu-text", children: [
355
- /* @__PURE__ */ jsx3("span", { className: "fd-opt-label", children: it.label }),
356
- it.description ? /* @__PURE__ */ jsx3("span", { className: "fd-opt-desc", children: it.description }) : null
357
- ] }),
358
- it.meta ? /* @__PURE__ */ jsx3("span", { className: "fd-opt-meta", children: it.meta }) : null,
359
- it.shortcut ? /* @__PURE__ */ jsx3("span", { className: "fd-menu-shortcut", "aria-hidden": "true", children: it.shortcut }) : null,
360
- it.submenu ? /* @__PURE__ */ jsx3("i", { className: "ph ph-caret-right fd-menu-more", "aria-hidden": "true" }) : null,
361
- it.checked != null ? /* @__PURE__ */ jsx3("span", { className: "fd-opt-check", children: on ? /* @__PURE__ */ jsx3("i", { className: "ph ph-check", "aria-hidden": "true" }) : null }) : null
362
- ]
363
- },
364
- it.id || it.label || i
365
- );
366
- const ta = it.trailingAction;
367
- if (!ta) return row;
368
- return /* @__PURE__ */ jsxs2("div", { className: "fd-menu-row", onMouseEnter: () => setActive(i), children: [
369
- row,
370
- /* @__PURE__ */ jsx3(
364
+ /* @__PURE__ */ jsxs2("div", { className: "fd-menu-frame", children: [
365
+ header != null ? /* @__PURE__ */ jsx3("div", { className: "fd-menu-header", children: header }) : null,
366
+ /* @__PURE__ */ jsx3(
367
+ "div",
368
+ {
369
+ ref: listRef,
370
+ tabIndex: -1,
371
+ role: "menu",
372
+ onKeyDown: onKey,
373
+ className: ["fd-pop-list", "fd-menu", className].filter(Boolean).join(" "),
374
+ children: items.map((it, i) => {
375
+ if (!it) return null;
376
+ if (it.kind === "separator") return /* @__PURE__ */ jsx3("div", { className: "fd-menu-sep", role: "separator" }, "s" + i);
377
+ if (it.kind === "section") return /* @__PURE__ */ jsx3("div", { className: "fd-pop-group", role: "presentation", children: it.label }, "h" + i);
378
+ if (it.kind === "custom") return /* @__PURE__ */ jsx3("div", { className: "fd-menu-custom", children: it.render ? it.render() : null }, "c" + i);
379
+ const on = !!it.checked;
380
+ const row = /* @__PURE__ */ jsxs2(
371
381
  "button",
372
382
  {
373
383
  type: "button",
374
- className: ["fd-menu-row-act", ta.danger ? "is-danger" : ""].filter(Boolean).join(" "),
375
- "aria-label": ta.label,
376
- title: ta.label,
377
- onClick: (e) => {
378
- e.stopPropagation();
379
- ta.onSelect && ta.onSelect(it);
380
- if (ta.closeOnSelect !== false) {
381
- onClose && onClose();
382
- }
384
+ "data-i": i,
385
+ role: it.checked != null ? "menuitemradio" : "menuitem",
386
+ "aria-checked": it.checked != null ? on : void 0,
387
+ "aria-haspopup": it.submenu ? "menu" : void 0,
388
+ "aria-expanded": it.submenu ? openSub === i : void 0,
389
+ "aria-disabled": it.disabled || void 0,
390
+ className: ["fd-opt", "fd-menu-item", i === active ? "is-active" : "", on ? "is-selected" : ""].filter(Boolean).join(" "),
391
+ onMouseEnter: () => {
392
+ setActive(i);
393
+ if (openSub != null && openSub !== i) setOpenSub(null);
383
394
  },
384
- children: /* @__PURE__ */ jsx3("i", { className: "ph ph-" + (ta.icon || "trash"), "aria-hidden": "true" })
385
- }
386
- )
387
- ] }, it.id || it.label || i);
388
- })
389
- }
390
- ),
391
- footer,
395
+ onClick: () => choose(it, i),
396
+ children: [
397
+ it.icon ? /* @__PURE__ */ jsx3("span", { className: "fd-menu-icon", children: /* @__PURE__ */ jsx3("i", { className: "ph ph-" + it.icon, "aria-hidden": "true" }) }) : null,
398
+ /* @__PURE__ */ jsxs2("span", { className: "fd-menu-text", children: [
399
+ /* @__PURE__ */ jsx3("span", { className: "fd-opt-label", children: it.label }),
400
+ it.description ? /* @__PURE__ */ jsx3("span", { className: "fd-opt-desc", children: it.description }) : null
401
+ ] }),
402
+ it.meta ? /* @__PURE__ */ jsx3("span", { className: "fd-opt-meta", children: it.meta }) : null,
403
+ it.shortcut ? /* @__PURE__ */ jsx3("span", { className: "fd-menu-shortcut", "aria-hidden": "true", children: it.shortcut }) : null,
404
+ it.submenu ? /* @__PURE__ */ jsx3("i", { className: "ph ph-caret-right fd-menu-more", "aria-hidden": "true" }) : null,
405
+ it.checked != null ? /* @__PURE__ */ jsx3("span", { className: "fd-opt-check", children: on ? /* @__PURE__ */ jsx3("i", { className: "ph ph-check", "aria-hidden": "true" }) : null }) : null
406
+ ]
407
+ },
408
+ it.id || it.label || i
409
+ );
410
+ const ta = it.trailingAction;
411
+ if (!ta) return row;
412
+ return /* @__PURE__ */ jsxs2("div", { className: "fd-menu-row", onMouseEnter: () => setActive(i), children: [
413
+ row,
414
+ /* @__PURE__ */ jsx3(
415
+ "button",
416
+ {
417
+ type: "button",
418
+ className: ["fd-menu-row-act", ta.danger ? "is-danger" : ""].filter(Boolean).join(" "),
419
+ "aria-label": ta.label,
420
+ title: ta.label,
421
+ onClick: (e) => {
422
+ e.stopPropagation();
423
+ ta.onSelect && ta.onSelect(it);
424
+ if (ta.closeOnSelect !== false) {
425
+ onClose && onClose();
426
+ }
427
+ },
428
+ children: /* @__PURE__ */ jsx3("i", { className: "ph ph-" + (ta.icon || "trash"), "aria-hidden": "true" })
429
+ }
430
+ )
431
+ ] }, it.id || it.label || i);
432
+ })
433
+ }
434
+ ),
435
+ footer != null ? /* @__PURE__ */ jsx3("div", { className: "fd-menu-footer", children: footer }) : null
436
+ ] }),
392
437
  sub && sub.submenu ? /* @__PURE__ */ jsx3(
393
438
  Popover,
394
439
  {
@@ -1016,20 +1061,123 @@ function Toast({ title, children, tone = "success", onUndo, onDismiss, className
1016
1061
  // src/components/feedback/Tooltip.tsx
1017
1062
  import * as React6 from "react";
1018
1063
  import { jsx as jsx24, jsxs as jsxs19 } from "react/jsx-runtime";
1064
+ var CLOSE_DELAY_MS = 120;
1019
1065
  function Tooltip({ label, placement = "top", children, className = "" }) {
1020
- const [show, setShow] = React6.useState(false);
1021
- const pos = placement === "bottom" ? { top: "calc(100% + 8px)", left: "50%", transform: "translateX(-50%)" } : { bottom: "calc(100% + 8px)", left: "50%", transform: "translateX(-50%)" };
1066
+ const anchorRef = React6.useRef(null);
1067
+ const bubbleRef = React6.useRef(null);
1068
+ const [reason, setReason] = React6.useState(null);
1069
+ const closeTimer = React6.useRef(null);
1070
+ const id = React6.useId();
1071
+ const descId = id + "-desc";
1072
+ const open = reason != null;
1073
+ const cancelClose = React6.useCallback(() => {
1074
+ if (closeTimer.current) {
1075
+ clearTimeout(closeTimer.current);
1076
+ closeTimer.current = null;
1077
+ }
1078
+ }, []);
1079
+ const show = (why) => {
1080
+ cancelClose();
1081
+ setReason(why);
1082
+ };
1083
+ const hide = React6.useCallback(() => {
1084
+ cancelClose();
1085
+ setReason(null);
1086
+ }, [cancelClose]);
1087
+ const hover = () => {
1088
+ cancelClose();
1089
+ setReason((r) => r || "hover");
1090
+ };
1091
+ const hideSoon = () => {
1092
+ cancelClose();
1093
+ closeTimer.current = setTimeout(() => {
1094
+ closeTimer.current = null;
1095
+ setReason((r) => r === "hover" ? null : r);
1096
+ }, CLOSE_DELAY_MS);
1097
+ };
1098
+ React6.useEffect(() => cancelClose, [cancelClose]);
1099
+ const pos = usePopoverPosition(open, anchorRef, {
1100
+ placement: placement === "bottom" ? "bottom-center" : "top-center",
1101
+ offset: 8,
1102
+ estHeight: 40,
1103
+ estWidth: 160,
1104
+ layerRef: bubbleRef,
1105
+ onDetach: hide
1106
+ });
1107
+ React6.useEffect(() => {
1108
+ if (!open) return;
1109
+ const key = (e) => {
1110
+ if (e.key === "Escape") hide();
1111
+ };
1112
+ const away = (e) => {
1113
+ const t = e.target;
1114
+ if (anchorRef.current && anchorRef.current.contains(t)) return;
1115
+ if (bubbleRef.current && bubbleRef.current.contains(t)) return;
1116
+ hide();
1117
+ };
1118
+ document.addEventListener("keydown", key);
1119
+ document.addEventListener("pointerdown", away, true);
1120
+ return () => {
1121
+ document.removeEventListener("keydown", key);
1122
+ document.removeEventListener("pointerdown", away, true);
1123
+ };
1124
+ }, [open, hide]);
1125
+ const isMouse = (e) => e.pointerType === "mouse";
1126
+ const onFocus = (e) => {
1127
+ const el = e.target;
1128
+ let keyboard = true;
1129
+ try {
1130
+ keyboard = el.matches(":focus-visible");
1131
+ } catch {
1132
+ }
1133
+ if (keyboard) show("focus");
1134
+ };
1135
+ const described = React6.isValidElement(children) ? React6.cloneElement(children, {
1136
+ "aria-describedby": [children.props["aria-describedby"], descId].filter(Boolean).join(" ")
1137
+ }) : children;
1022
1138
  return /* @__PURE__ */ jsxs19(
1023
1139
  "span",
1024
1140
  {
1025
- style: { position: "relative", display: "inline-flex" },
1026
- onMouseEnter: () => setShow(true),
1027
- onMouseLeave: () => setShow(false),
1028
- onFocus: () => setShow(true),
1029
- onBlur: () => setShow(false),
1141
+ ref: anchorRef,
1142
+ className: "fd-tooltip-anchor",
1143
+ "aria-describedby": React6.isValidElement(children) ? void 0 : descId,
1144
+ onPointerEnter: (e) => {
1145
+ if (isMouse(e)) hover();
1146
+ },
1147
+ onPointerLeave: (e) => {
1148
+ if (isMouse(e)) hideSoon();
1149
+ },
1150
+ onPointerUp: (e) => {
1151
+ if (!isMouse(e)) {
1152
+ if (reason === "tap") hide();
1153
+ else show("tap");
1154
+ }
1155
+ },
1156
+ onFocus,
1157
+ onBlur: () => {
1158
+ if (reason === "focus") hide();
1159
+ },
1030
1160
  children: [
1031
- children,
1032
- show ? /* @__PURE__ */ jsx24("span", { className: ["fd-tooltip", className].filter(Boolean).join(" "), role: "tooltip", style: pos, children: label }) : null
1161
+ described,
1162
+ /* @__PURE__ */ jsx24("span", { id: descId, hidden: true, children: label }),
1163
+ open && pos ? portal(
1164
+ /* @__PURE__ */ jsx24(
1165
+ "span",
1166
+ {
1167
+ ref: bubbleRef,
1168
+ role: "tooltip",
1169
+ className: ["fd-tooltip", pos.side === "bottom" ? "is-below" : "", className].filter(Boolean).join(" "),
1170
+ style: { left: pos.left, top: pos.top == null ? "auto" : pos.top, bottom: pos.bottom == null ? "auto" : pos.bottom },
1171
+ onPointerEnter: (e) => {
1172
+ if (isMouse(e)) cancelClose();
1173
+ },
1174
+ onPointerLeave: (e) => {
1175
+ if (isMouse(e)) hideSoon();
1176
+ },
1177
+ children: label
1178
+ }
1179
+ )
1180
+ ) : null
1033
1181
  ]
1034
1182
  }
1035
1183
  );
@@ -2663,6 +2811,7 @@ function VirtualList({
2663
2811
  }
2664
2812
 
2665
2813
  // src/components/data/EntityRow.tsx
2814
+ import * as React17 from "react";
2666
2815
  import { jsx as jsx39, jsxs as jsxs35 } from "react/jsx-runtime";
2667
2816
  var ENTITY_ROW_METRIC_WIDTH = 74;
2668
2817
  var METRIC_TONE_COLOR = {
@@ -2671,20 +2820,20 @@ var METRIC_TONE_COLOR = {
2671
2820
  danger: "var(--danger-text)"
2672
2821
  };
2673
2822
  function EntityRowMetrics({ metrics, className = "", style }) {
2674
- return /* @__PURE__ */ jsx39("span", { className: ["fd-erow-metrics", className].filter(Boolean).join(" "), style, children: metrics.map((m, i) => /* @__PURE__ */ jsxs35(
2675
- "span",
2676
- {
2677
- className: ["fd-erow-metric", m.tone && m.tone !== "default" ? "is-" + m.tone : ""].filter(Boolean).join(" "),
2678
- style: { width: (m.width || ENTITY_ROW_METRIC_WIDTH) + "px", color: METRIC_TONE_COLOR[m.tone || "default"] },
2679
- title: m.label,
2680
- children: [
2681
- m.icon ? /* @__PURE__ */ jsx39("i", { className: "ph ph-" + m.icon, "aria-hidden": "true" }) : null,
2682
- /* @__PURE__ */ jsx39("span", { className: "fd-erow-metric-value fd-tabular", children: m.value }),
2683
- /* @__PURE__ */ jsx39("span", { className: "fd-sr", children: " " + m.label })
2684
- ]
2685
- },
2686
- m.id || m.label || i
2687
- )) });
2823
+ return /* @__PURE__ */ jsx39("span", { className: ["fd-erow-metrics", className].filter(Boolean).join(" "), style, children: metrics.map((m, i) => {
2824
+ const key = m.id || m.label || i;
2825
+ const className2 = ["fd-erow-metric", m.tone && m.tone !== "default" ? "is-" + m.tone : "", m.tooltip != null ? "is-described" : ""].filter(Boolean).join(" ");
2826
+ const cellStyle = { width: (m.width || ENTITY_ROW_METRIC_WIDTH) + "px", color: METRIC_TONE_COLOR[m.tone || "default"] };
2827
+ const content = /* @__PURE__ */ jsxs35(React17.Fragment, { children: [
2828
+ m.icon ? /* @__PURE__ */ jsx39("i", { className: "ph ph-" + m.icon, "aria-hidden": "true" }) : null,
2829
+ /* @__PURE__ */ jsx39("span", { className: "fd-erow-metric-value fd-tabular", children: m.value }),
2830
+ /* @__PURE__ */ jsx39("span", { className: "fd-sr", children: " " + m.label })
2831
+ ] });
2832
+ if (m.tooltip == null) {
2833
+ return /* @__PURE__ */ jsx39("span", { className: className2, style: cellStyle, title: m.label, children: content }, key);
2834
+ }
2835
+ return /* @__PURE__ */ jsx39(Tooltip, { label: m.tooltip, children: /* @__PURE__ */ jsx39("button", { type: "button", className: className2, style: cellStyle, children: content }) }, key);
2836
+ }) });
2688
2837
  }
2689
2838
  function EntityRow({
2690
2839
  title,
@@ -2734,8 +2883,10 @@ function EntityRow({
2734
2883
  icon: action.icon,
2735
2884
  label: action.label,
2736
2885
  size: "sm",
2737
- onClick: action.onClick,
2738
- style: { color: action.tone === "add" ? "var(--ok-text)" : action.tone === "remove" ? "var(--danger-text)" : void 0 }
2886
+ variant: "solid",
2887
+ tone: action.tone === "add" ? "ok" : action.tone === "remove" ? "danger" : "neutral",
2888
+ className: "fd-erow-action",
2889
+ onClick: action.onClick
2739
2890
  }
2740
2891
  ) : null
2741
2892
  ]
@@ -2744,14 +2895,14 @@ function EntityRow({
2744
2895
  }
2745
2896
 
2746
2897
  // src/components/data/TransferList.tsx
2747
- import * as React18 from "react";
2898
+ import * as React19 from "react";
2748
2899
  import { createPortal as createPortal4 } from "react-dom";
2749
2900
 
2750
2901
  // src/components/forms/field.tsx
2751
- import * as React17 from "react";
2902
+ import * as React18 from "react";
2752
2903
  import { jsx as jsx40, jsxs as jsxs36 } from "react/jsx-runtime";
2753
2904
  function useFieldId(id) {
2754
- const generated = React17.useId();
2905
+ const generated = React18.useId();
2755
2906
  return id || generated;
2756
2907
  }
2757
2908
  function FieldLabel({ htmlFor, id, required = false, onClick, children }) {
@@ -2792,10 +2943,9 @@ function Input({
2792
2943
  numeric ? "fd-input-num" : "",
2793
2944
  error ? "fd-input-error" : "",
2794
2945
  disabled ? "fd-input-disabled" : "",
2795
- size === "lg" ? "fd-input-lg" : "",
2796
- className
2946
+ size === "lg" ? "fd-input-lg" : ""
2797
2947
  ].filter(Boolean).join(" ");
2798
- return /* @__PURE__ */ jsxs37("div", { className: "fd-field", style, children: [
2948
+ return /* @__PURE__ */ jsxs37("div", { className: ["fd-field", className].filter(Boolean).join(" "), style, children: [
2799
2949
  label ? /* @__PURE__ */ jsx41(FieldLabel, { htmlFor: fieldId, required, children: label }) : null,
2800
2950
  /* @__PURE__ */ jsxs37("div", { className: box, children: [
2801
2951
  icon ? /* @__PURE__ */ jsx41("span", { className: "fd-input-icon", children: /* @__PURE__ */ jsx41("i", { className: "ph ph-" + icon, "aria-hidden": "true" }) }) : null,
@@ -2870,12 +3020,12 @@ function TransferList({
2870
3020
  listHeight = 440,
2871
3021
  className = ""
2872
3022
  }) {
2873
- const [drag, setDrag] = React18.useState(null);
2874
- const [dragOverSide, setDragOverSide] = React18.useState(null);
2875
- const dragRef = React18.useRef(null);
2876
- const armedRef = React18.useRef(null);
2877
- const sideRefs = React18.useRef({ left: null, right: null });
2878
- const hitTestSide = React18.useCallback((x, y) => {
3023
+ const [drag, setDrag] = React19.useState(null);
3024
+ const [dragOverSide, setDragOverSide] = React19.useState(null);
3025
+ const dragRef = React19.useRef(null);
3026
+ const armedRef = React19.useRef(null);
3027
+ const sideRefs = React19.useRef({ left: null, right: null });
3028
+ const hitTestSide = React19.useCallback((x, y) => {
2879
3029
  for (const side of ["left", "right"]) {
2880
3030
  const el = sideRefs.current[side];
2881
3031
  if (!el) continue;
@@ -2884,7 +3034,7 @@ function TransferList({
2884
3034
  }
2885
3035
  return null;
2886
3036
  }, []);
2887
- const findItem = React18.useCallback(
3037
+ const findItem = React19.useCallback(
2888
3038
  (side, key) => (side === "left" ? left.items : right.items).find((it) => keyOf(it) === key),
2889
3039
  [left.items, right.items, keyOf]
2890
3040
  );
@@ -2906,7 +3056,7 @@ function TransferList({
2906
3056
  height: rect.height
2907
3057
  };
2908
3058
  };
2909
- React18.useEffect(() => {
3059
+ React19.useEffect(() => {
2910
3060
  const onMovePointer = (e) => {
2911
3061
  const armed = armedRef.current;
2912
3062
  if (!armed || e.pointerId !== armed.pointerId) return;
@@ -2959,7 +3109,7 @@ function TransferList({
2959
3109
  window.removeEventListener("pointercancel", endDrag);
2960
3110
  };
2961
3111
  }, [hitTestSide, findItem, onMove]);
2962
- React18.useEffect(() => {
3112
+ React19.useEffect(() => {
2963
3113
  if (!drag || typeof document === "undefined") return;
2964
3114
  const prev = document.body.style.userSelect;
2965
3115
  document.body.style.userSelect = "none";
@@ -3071,11 +3221,11 @@ function TransferList({
3071
3221
  }
3072
3222
 
3073
3223
  // src/components/forms/Checkbox.tsx
3074
- import * as React19 from "react";
3224
+ import * as React20 from "react";
3075
3225
  import { jsx as jsx44, jsxs as jsxs39 } from "react/jsx-runtime";
3076
3226
  function Checkbox({ label, description, card = false, indeterminate = false, className = "", ...rest }) {
3077
- const ref = React19.useRef(null);
3078
- React19.useEffect(() => {
3227
+ const ref = React20.useRef(null);
3228
+ React20.useEffect(() => {
3079
3229
  if (ref.current) ref.current.indeterminate = indeterminate;
3080
3230
  }, [indeterminate]);
3081
3231
  return /* @__PURE__ */ jsxs39("label", { className: ["fd-choice", card ? "fd-choice-card" : "", className].filter(Boolean).join(" "), children: [
@@ -3122,8 +3272,8 @@ function Switch({ label, description, className = "", ...rest }) {
3122
3272
  import { jsx as jsx47, jsxs as jsxs42 } from "react/jsx-runtime";
3123
3273
  function Textarea({ label, help, error, required = false, rows = 4, disabled = false, id, className = "", style, ...rest }) {
3124
3274
  const fieldId = useFieldId(id);
3125
- const box = ["fd-input", "fd-input-textarea", error ? "fd-input-error" : "", disabled ? "fd-input-disabled" : "", className].filter(Boolean).join(" ");
3126
- return /* @__PURE__ */ jsxs42("div", { className: "fd-field", style, children: [
3275
+ const box = ["fd-input", "fd-input-textarea", error ? "fd-input-error" : "", disabled ? "fd-input-disabled" : ""].filter(Boolean).join(" ");
3276
+ return /* @__PURE__ */ jsxs42("div", { className: ["fd-field", className].filter(Boolean).join(" "), style, children: [
3127
3277
  label ? /* @__PURE__ */ jsx47(FieldLabel, { htmlFor: fieldId, required, children: label }) : null,
3128
3278
  /* @__PURE__ */ jsx47("div", { className: box, children: /* @__PURE__ */ jsx47("textarea", { id: fieldId, rows, disabled, "aria-invalid": error ? "true" : void 0, ...rest }) }),
3129
3279
  error ? /* @__PURE__ */ jsxs42("span", { className: "fd-field-error", children: [
@@ -3134,7 +3284,7 @@ function Textarea({ label, help, error, required = false, rows = 4, disabled = f
3134
3284
  }
3135
3285
 
3136
3286
  // src/components/forms/NumberInput.tsx
3137
- import * as React20 from "react";
3287
+ import * as React21 from "react";
3138
3288
  import { jsx as jsx48, jsxs as jsxs43 } from "react/jsx-runtime";
3139
3289
  function NumberInput({
3140
3290
  label,
@@ -3162,10 +3312,10 @@ function NumberInput({
3162
3312
  const n = Number(String(v == null ? "" : v).replace(/[^0-9.-]/g, ""));
3163
3313
  return isNaN(n) ? null : n;
3164
3314
  };
3165
- const [text, setText] = React20.useState(value == null || value === "" ? "" : String(value));
3166
- const [editing, setEditing] = React20.useState(false);
3167
- const timer = React20.useRef(null);
3168
- React20.useEffect(() => {
3315
+ const [text, setText] = React21.useState(value == null || value === "" ? "" : String(value));
3316
+ const [editing, setEditing] = React21.useState(false);
3317
+ const timer = React21.useRef(null);
3318
+ React21.useEffect(() => {
3169
3319
  if (!editing) setText(value == null || value === "" ? "" : String(value));
3170
3320
  }, [value, editing]);
3171
3321
  const clamp = (n) => Math.min(max, Math.max(min, n));
@@ -3189,7 +3339,7 @@ function NumberInput({
3189
3339
  const release = () => {
3190
3340
  if (timer.current) clearTimeout(timer.current);
3191
3341
  };
3192
- React20.useEffect(() => () => {
3342
+ React21.useEffect(() => () => {
3193
3343
  if (timer.current) clearTimeout(timer.current);
3194
3344
  }, []);
3195
3345
  const shown = editing ? text : (() => {
@@ -3276,52 +3426,9 @@ function NumberInput({
3276
3426
  }
3277
3427
 
3278
3428
  // src/components/forms/Select.tsx
3279
- import * as React21 from "react";
3280
- import { createPortal as createPortal5 } from "react-dom";
3429
+ import * as React22 from "react";
3281
3430
  import { jsx as jsx49, jsxs as jsxs44 } from "react/jsx-runtime";
3282
3431
  var norm = (o) => typeof o === "string" ? { value: o, label: o } : o;
3283
- function usePopPos(open, ref, estH, estW) {
3284
- const [pos, setPos] = React21.useState(null);
3285
- React21.useLayoutEffect(() => {
3286
- if (!open || !ref.current) {
3287
- setPos(null);
3288
- return;
3289
- }
3290
- const calc = () => {
3291
- const r = ref.current.getBoundingClientRect();
3292
- const below = window.innerHeight - r.bottom;
3293
- const up = below < estH && r.top > below;
3294
- const w = Math.max(r.width, estW || 0);
3295
- const maxH = Math.min(window.innerHeight - 16, Math.max(160, (up ? r.top : below) - 14));
3296
- setPos({
3297
- left: Math.max(8, Math.min(r.left, window.innerWidth - w - 8)),
3298
- width: r.width,
3299
- top: up ? null : r.bottom + 6,
3300
- bottom: up ? window.innerHeight - r.top + 6 : null,
3301
- up,
3302
- maxH
3303
- });
3304
- };
3305
- calc();
3306
- window.addEventListener("scroll", calc, true);
3307
- window.addEventListener("resize", calc);
3308
- return () => {
3309
- window.removeEventListener("scroll", calc, true);
3310
- window.removeEventListener("resize", calc);
3311
- };
3312
- }, [open]);
3313
- return pos;
3314
- }
3315
- var popStyle = (pos, extra) => ({
3316
- position: "fixed",
3317
- left: pos.left,
3318
- top: pos.top == null ? "auto" : pos.top,
3319
- bottom: pos.bottom == null ? "auto" : pos.bottom,
3320
- transformOrigin: pos.up ? "bottom center" : "top center",
3321
- maxHeight: pos.maxH,
3322
- overflowY: "auto",
3323
- ...extra
3324
- });
3325
3432
  function Select({
3326
3433
  label,
3327
3434
  help,
@@ -3346,17 +3453,15 @@ function Select({
3346
3453
  const isOn = (v) => multiple ? vals.includes(v) : v === value;
3347
3454
  const hasSearch = searchable === void 0 ? opts.length > 8 : searchable;
3348
3455
  const { fieldId, labelId, ariaLabelledBy } = useTriggerLabelling(label, id);
3349
- const [open, setOpen] = React21.useState(false);
3350
- const [q, setQ] = React21.useState("");
3351
- const [active, setActive] = React21.useState(-1);
3352
- const rootRef = React21.useRef(null);
3353
- const boxRef = React21.useRef(null);
3354
- const popRef = React21.useRef(null);
3355
- const listRef = React21.useRef(null);
3356
- const typeBuf = React21.useRef({ s: "", t: 0 });
3456
+ const [open, setOpen] = React22.useState(false);
3457
+ const [q, setQ] = React22.useState("");
3458
+ const [active, setActive] = React22.useState(-1);
3459
+ const rootRef = React22.useRef(null);
3460
+ const boxRef = React22.useRef(null);
3461
+ const listRef = React22.useRef(null);
3462
+ const typeBuf = React22.useRef({ s: "", t: 0 });
3357
3463
  const selected = multiple ? null : opts.find((o) => o.value === value);
3358
3464
  const chosen = multiple ? opts.filter((o) => vals.includes(o.value)) : [];
3359
- const pos = usePopPos(open, boxRef, hasSearch ? 390 : 340, 260);
3360
3465
  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;
3361
3466
  const visible = q ? opts.filter((o) => (o.label + " " + (o.description || "") + " " + (o.group || "")).toLowerCase().includes(q.toLowerCase())) : opts;
3362
3467
  const fire = (v) => {
@@ -3379,17 +3484,7 @@ function Select({
3379
3484
  }
3380
3485
  setOpen(!open);
3381
3486
  };
3382
- React21.useEffect(() => {
3383
- if (!open) return;
3384
- const away = (e) => {
3385
- if (rootRef.current && rootRef.current.contains(e.target)) return;
3386
- if (popRef.current && popRef.current.contains(e.target)) return;
3387
- setOpen(false);
3388
- };
3389
- document.addEventListener("pointerdown", away);
3390
- return () => document.removeEventListener("pointerdown", away);
3391
- }, [open]);
3392
- React21.useEffect(() => {
3487
+ React22.useEffect(() => {
3393
3488
  if (!open || active < 0 || !listRef.current) return;
3394
3489
  const el = listRef.current.querySelector('[data-i="' + active + '"]');
3395
3490
  if (el) {
@@ -3480,94 +3575,96 @@ function Select({
3480
3575
  ) : null,
3481
3576
  loading ? /* @__PURE__ */ jsx49("span", { className: "fd-spinner", style: { color: "var(--text-muted)" }, "aria-label": "Loading options" }) : /* @__PURE__ */ jsx49("span", { className: "fd-select-caret", onClick: toggle, children: /* @__PURE__ */ jsx49("i", { className: "ph ph-caret-down", "aria-hidden": "true" }) })
3482
3577
  ] }),
3483
- open && pos ? createPortal5(
3484
- /* @__PURE__ */ jsxs44(
3485
- "div",
3486
- {
3487
- className: "fd-pop" + (pos.up ? " is-up" : ""),
3488
- ref: popRef,
3489
- style: popStyle(pos, { minWidth: Math.max(pos.width, 260), maxWidth: 380, zIndex: 130, overflowY: "hidden" }),
3490
- children: [
3491
- hasSearch ? /* @__PURE__ */ jsxs44("div", { className: "fd-pop-search", children: [
3492
- /* @__PURE__ */ jsx49("i", { className: "ph ph-magnifying-glass", style: { color: "var(--text-muted)", fontSize: 14 } }),
3493
- /* @__PURE__ */ jsx49(
3494
- "input",
3495
- {
3496
- autoFocus: true,
3497
- placeholder: "Search\u2026",
3498
- value: q,
3499
- onKeyDown: onKey,
3500
- onChange: (e) => {
3501
- setQ(e.target.value);
3502
- setActive(0);
3503
- }
3504
- }
3505
- ),
3506
- q ? /* @__PURE__ */ jsx49("span", { className: "fd-body-sm fd-muted", children: visible.length }) : null
3507
- ] }) : null,
3508
- /* @__PURE__ */ jsx49("div", { className: "fd-pop-list", role: "listbox", "aria-multiselectable": multiple || void 0, ref: listRef, style: { maxHeight: Math.max(120, pos.maxH - (hasSearch ? 64 : 12) - (multiple ? 42 : 0)) }, children: visible.length === 0 ? /* @__PURE__ */ jsxs44("div", { className: "fd-pop-empty", children: [
3509
- 'Nothing matches "',
3510
- q,
3511
- '".'
3512
- ] }) : groups.map((grp) => /* @__PURE__ */ jsxs44(React21.Fragment, { children: [
3513
- grp.g ? /* @__PURE__ */ jsx49("div", { className: "fd-pop-group", children: grp.g }) : null,
3514
- grp.items.map(({ o, i }) => /* @__PURE__ */ jsxs44(
3515
- "button",
3516
- {
3517
- type: "button",
3518
- "data-i": i,
3519
- role: "option",
3520
- "aria-selected": isOn(o.value),
3521
- "aria-disabled": o.disabled || void 0,
3522
- className: ["fd-opt", i === active ? "is-active" : "", isOn(o.value) ? "is-selected" : ""].filter(Boolean).join(" "),
3523
- onMouseEnter: () => setActive(i),
3524
- onClick: () => pick(o),
3525
- children: [
3526
- multiple ? /* @__PURE__ */ jsx49("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__ */ jsx49("i", { className: "ph ph-check", style: { fontSize: 11 } }) : null }) : null,
3527
- o.icon ? /* @__PURE__ */ jsx49("span", { className: "fd-opt-icon", children: /* @__PURE__ */ jsx49("i", { className: "ph ph-" + o.icon }) }) : null,
3528
- /* @__PURE__ */ jsxs44("span", { style: { flex: 1, minWidth: 0 }, children: [
3529
- /* @__PURE__ */ jsx49("span", { className: "fd-opt-label", children: o.label }),
3530
- o.description ? /* @__PURE__ */ jsx49("span", { className: "fd-opt-desc", children: o.description }) : null
3531
- ] }),
3532
- o.meta ? /* @__PURE__ */ jsx49("span", { className: "fd-opt-meta", children: o.meta }) : null,
3533
- multiple ? null : /* @__PURE__ */ jsx49("span", { className: "fd-opt-check", children: o.value === value ? /* @__PURE__ */ jsx49("i", { className: "ph ph-check" }) : null })
3534
- ]
3535
- },
3536
- String(o.value)
3537
- ))
3538
- ] }, grp.g || "_")) }),
3539
- multiple ? /* @__PURE__ */ jsxs44("div", { className: "fd-row", style: { gap: 10, padding: "8px 12px", borderTop: "1px solid var(--border)" }, children: [
3540
- /* @__PURE__ */ jsx49(
3541
- "button",
3542
- {
3543
- type: "button",
3544
- onClick: () => fire(visible.filter((o) => !o.disabled).map((o) => o.value)),
3545
- style: { all: "unset", cursor: "pointer", fontSize: "var(--body-sm-size)", fontWeight: 600, color: "var(--brand)" },
3546
- children: "Select all"
3547
- }
3548
- ),
3549
- /* @__PURE__ */ jsx49("span", { style: { flex: 1 } }),
3550
- /* @__PURE__ */ jsxs44("span", { className: "fd-body-sm fd-muted", children: [
3551
- vals.length,
3552
- " of ",
3553
- opts.length
3554
- ] }),
3555
- /* @__PURE__ */ jsx49(
3556
- "button",
3557
- {
3558
- type: "button",
3559
- disabled: !vals.length,
3560
- onClick: () => fire([]),
3561
- style: { all: "unset", cursor: vals.length ? "pointer" : "default", fontSize: "var(--body-sm-size)", fontWeight: 600, color: vals.length ? "var(--text-2)" : "var(--text-muted)" },
3562
- children: "Clear"
3563
- }
3564
- )
3565
- ] }) : null
3566
- ]
3567
- }
3568
- ),
3569
- document.body
3570
- ) : null,
3578
+ /* @__PURE__ */ jsx49(
3579
+ Popover,
3580
+ {
3581
+ open,
3582
+ anchorRef: boxRef,
3583
+ triggerRef: rootRef,
3584
+ onClose: () => setOpen(false),
3585
+ placement: "bottom-start",
3586
+ offset: 6,
3587
+ matchWidth: "min",
3588
+ minWidth: 260,
3589
+ role: "presentation",
3590
+ style: { maxWidth: 380, zIndex: 130 },
3591
+ header: hasSearch ? /* @__PURE__ */ jsxs44("div", { className: "fd-pop-search", children: [
3592
+ /* @__PURE__ */ jsx49("i", { className: "ph ph-magnifying-glass", style: { color: "var(--text-muted)", fontSize: 14 } }),
3593
+ /* @__PURE__ */ jsx49(
3594
+ "input",
3595
+ {
3596
+ autoFocus: true,
3597
+ placeholder: "Search\u2026",
3598
+ value: q,
3599
+ onKeyDown: onKey,
3600
+ onChange: (e) => {
3601
+ setQ(e.target.value);
3602
+ setActive(0);
3603
+ }
3604
+ }
3605
+ ),
3606
+ q ? /* @__PURE__ */ jsx49("span", { className: "fd-body-sm fd-muted", children: visible.length }) : null
3607
+ ] }) : void 0,
3608
+ footer: multiple ? /* @__PURE__ */ jsxs44(React22.Fragment, { children: [
3609
+ /* @__PURE__ */ jsx49(
3610
+ "button",
3611
+ {
3612
+ type: "button",
3613
+ onClick: () => fire(visible.filter((o) => !o.disabled).map((o) => o.value)),
3614
+ style: { all: "unset", cursor: "pointer", fontSize: "var(--body-sm-size)", fontWeight: 600, color: "var(--brand)" },
3615
+ children: "Select all"
3616
+ }
3617
+ ),
3618
+ /* @__PURE__ */ jsx49("span", { style: { flex: 1 } }),
3619
+ /* @__PURE__ */ jsxs44("span", { className: "fd-body-sm fd-muted", children: [
3620
+ vals.length,
3621
+ " of ",
3622
+ opts.length
3623
+ ] }),
3624
+ /* @__PURE__ */ jsx49(
3625
+ "button",
3626
+ {
3627
+ type: "button",
3628
+ disabled: !vals.length,
3629
+ onClick: () => fire([]),
3630
+ style: { all: "unset", cursor: vals.length ? "pointer" : "default", fontSize: "var(--body-sm-size)", fontWeight: 600, color: vals.length ? "var(--text-2)" : "var(--text-muted)" },
3631
+ children: "Clear"
3632
+ }
3633
+ )
3634
+ ] }) : void 0,
3635
+ children: /* @__PURE__ */ jsx49("div", { className: "fd-pop-list", role: "listbox", "aria-multiselectable": multiple || void 0, ref: listRef, children: visible.length === 0 ? /* @__PURE__ */ jsxs44("div", { className: "fd-pop-empty", children: [
3636
+ 'Nothing matches "',
3637
+ q,
3638
+ '".'
3639
+ ] }) : groups.map((grp) => /* @__PURE__ */ jsxs44(React22.Fragment, { children: [
3640
+ grp.g ? /* @__PURE__ */ jsx49("div", { className: "fd-pop-group", children: grp.g }) : null,
3641
+ grp.items.map(({ o, i }) => /* @__PURE__ */ jsxs44(
3642
+ "button",
3643
+ {
3644
+ type: "button",
3645
+ "data-i": i,
3646
+ role: "option",
3647
+ "aria-selected": isOn(o.value),
3648
+ "aria-disabled": o.disabled || void 0,
3649
+ className: ["fd-opt", i === active ? "is-active" : "", isOn(o.value) ? "is-selected" : ""].filter(Boolean).join(" "),
3650
+ onMouseEnter: () => setActive(i),
3651
+ onClick: () => pick(o),
3652
+ children: [
3653
+ multiple ? /* @__PURE__ */ jsx49("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__ */ jsx49("i", { className: "ph ph-check", style: { fontSize: 11 } }) : null }) : null,
3654
+ o.icon ? /* @__PURE__ */ jsx49("span", { className: "fd-opt-icon", children: /* @__PURE__ */ jsx49("i", { className: "ph ph-" + o.icon }) }) : null,
3655
+ /* @__PURE__ */ jsxs44("span", { style: { flex: 1, minWidth: 0 }, children: [
3656
+ /* @__PURE__ */ jsx49("span", { className: "fd-opt-label", children: o.label }),
3657
+ o.description ? /* @__PURE__ */ jsx49("span", { className: "fd-opt-desc", children: o.description }) : null
3658
+ ] }),
3659
+ o.meta ? /* @__PURE__ */ jsx49("span", { className: "fd-opt-meta", children: o.meta }) : null,
3660
+ multiple ? null : /* @__PURE__ */ jsx49("span", { className: "fd-opt-check", children: o.value === value ? /* @__PURE__ */ jsx49("i", { className: "ph ph-check" }) : null })
3661
+ ]
3662
+ },
3663
+ String(o.value)
3664
+ ))
3665
+ ] }, grp.g || "_")) })
3666
+ }
3667
+ ),
3571
3668
  error ? /* @__PURE__ */ jsxs44("span", { className: "fd-field-error", children: [
3572
3669
  /* @__PURE__ */ jsx49("i", { className: "ph ph-warning-circle", "aria-hidden": "true" }),
3573
3670
  error
@@ -3576,8 +3673,7 @@ function Select({
3576
3673
  }
3577
3674
 
3578
3675
  // src/components/forms/DatePicker.tsx
3579
- import * as React22 from "react";
3580
- import { createPortal as createPortal6 } from "react-dom";
3676
+ import * as React23 from "react";
3581
3677
  import { jsx as jsx50, jsxs as jsxs45 } from "react/jsx-runtime";
3582
3678
  var MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
3583
3679
  var DOW = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
@@ -3591,56 +3687,14 @@ var fmt = (s) => {
3591
3687
  const d = parse(s);
3592
3688
  return d ? MONTHS[d.getMonth()].slice(0, 3) + " " + d.getDate() + ", " + d.getFullYear() : "";
3593
3689
  };
3594
- function usePopPos2(open, ref, estH, estW) {
3595
- const [pos, setPos] = React22.useState(null);
3596
- React22.useLayoutEffect(() => {
3597
- if (!open || !ref.current) {
3598
- setPos(null);
3599
- return;
3600
- }
3601
- const calc = () => {
3602
- const r = ref.current.getBoundingClientRect();
3603
- const below = window.innerHeight - r.bottom;
3604
- const up = below < estH && r.top > below;
3605
- const w = Math.max(r.width, estW || 0);
3606
- const maxH = Math.min(window.innerHeight - 16, Math.max(160, (up ? r.top : below) - 14));
3607
- setPos({
3608
- left: Math.max(8, Math.min(r.left, window.innerWidth - w - 8)),
3609
- width: r.width,
3610
- top: up ? null : r.bottom + 6,
3611
- bottom: up ? window.innerHeight - r.top + 6 : null,
3612
- up,
3613
- maxH
3614
- });
3615
- };
3616
- calc();
3617
- window.addEventListener("scroll", calc, true);
3618
- window.addEventListener("resize", calc);
3619
- return () => {
3620
- window.removeEventListener("scroll", calc, true);
3621
- window.removeEventListener("resize", calc);
3622
- };
3623
- }, [open]);
3624
- return pos;
3625
- }
3626
- var popStyle2 = (pos, extra) => ({
3627
- position: "fixed",
3628
- left: pos.left,
3629
- top: pos.top == null ? "auto" : pos.top,
3630
- bottom: pos.bottom == null ? "auto" : pos.bottom,
3631
- transformOrigin: pos.up ? "bottom center" : "top center",
3632
- maxHeight: pos.maxH,
3633
- overflowY: "auto",
3634
- ...extra
3635
- });
3636
3690
  function Calendar({ value, range = false, onPick, initialMonth, months = 1 }) {
3637
3691
  const today = /* @__PURE__ */ new Date();
3638
3692
  const sel = range ? value || {} : { start: value, end: value };
3639
3693
  const anchor = parse(sel.start) || parse(initialMonth) || today;
3640
- const [vy, setVy] = React22.useState(anchor.getFullYear());
3641
- const [vm, setVm] = React22.useState(anchor.getMonth());
3642
- const [mode2, setMode] = React22.useState("days");
3643
- const [hover, setHover] = React22.useState(null);
3694
+ const [vy, setVy] = React23.useState(anchor.getFullYear());
3695
+ const [vm, setVm] = React23.useState(anchor.getMonth());
3696
+ const [mode2, setMode] = React23.useState("days");
3697
+ const [hover, setHover] = React23.useState(null);
3644
3698
  const s = parse(sel.start), e = parse(sel.end);
3645
3699
  const hoverEnd = range && s && !e && hover ? parse(hover) : null;
3646
3700
  const inRange = (d) => {
@@ -3756,28 +3810,9 @@ function Calendar({ value, range = false, onPick, initialMonth, months = 1 }) {
3756
3810
  }
3757
3811
  function DatePicker({ label, help, error, required = false, disabled = false, range = false, value, onChange, placeholder, id, className = "", style, ...rest }) {
3758
3812
  const { fieldId, labelId, ariaLabelledBy } = useTriggerLabelling(label, id);
3759
- const [open, setOpen] = React22.useState(false);
3760
- const rootRef = React22.useRef(null);
3761
- const boxRef = React22.useRef(null);
3762
- const popRef = React22.useRef(null);
3763
- const pos = usePopPos2(open, boxRef, 430, range ? 600 : 316);
3764
- React22.useEffect(() => {
3765
- if (!open) return;
3766
- const away = (e) => {
3767
- if (rootRef.current && rootRef.current.contains(e.target)) return;
3768
- if (popRef.current && popRef.current.contains(e.target)) return;
3769
- setOpen(false);
3770
- };
3771
- const esc = (e) => {
3772
- if (e.key === "Escape") setOpen(false);
3773
- };
3774
- document.addEventListener("pointerdown", away);
3775
- document.addEventListener("keydown", esc);
3776
- return () => {
3777
- document.removeEventListener("pointerdown", away);
3778
- document.removeEventListener("keydown", esc);
3779
- };
3780
- }, [open]);
3813
+ const [open, setOpen] = React23.useState(false);
3814
+ const rootRef = React23.useRef(null);
3815
+ const boxRef = React23.useRef(null);
3781
3816
  const display = range ? value && value.start ? fmt(value.start) + (value.end ? " \u2192 " + fmt(value.end) : " \u2192 \u2026") : "" : fmt(value);
3782
3817
  const toggle = () => {
3783
3818
  if (!disabled) setOpen(!open);
@@ -3806,21 +3841,31 @@ function DatePicker({ label, help, error, required = false, disabled = false, ra
3806
3841
  ),
3807
3842
  /* @__PURE__ */ jsx50("span", { className: "fd-select-caret", children: /* @__PURE__ */ jsx50("i", { className: "ph ph-caret-down", "aria-hidden": "true" }) })
3808
3843
  ] }),
3809
- open && pos ? createPortal6(
3810
- /* @__PURE__ */ jsx50("div", { className: "fd-pop" + (pos.up ? " is-up" : ""), ref: popRef, style: popStyle2(pos, { width: "max-content", minWidth: 0, zIndex: 130 }), children: /* @__PURE__ */ jsx50(
3811
- Calendar,
3812
- {
3813
- range,
3814
- months: range ? 2 : 1,
3815
- value,
3816
- onPick: (v) => {
3817
- onChange && onChange({ target: { value: v } });
3818
- if (!range || v.start && v.end) setOpen(false);
3844
+ /* @__PURE__ */ jsx50(
3845
+ Popover,
3846
+ {
3847
+ open,
3848
+ anchorRef: boxRef,
3849
+ triggerRef: rootRef,
3850
+ onClose: () => setOpen(false),
3851
+ placement: "bottom-start",
3852
+ offset: 6,
3853
+ role: "presentation",
3854
+ style: { width: "max-content", zIndex: 130 },
3855
+ children: /* @__PURE__ */ jsx50(
3856
+ Calendar,
3857
+ {
3858
+ range,
3859
+ months: range ? 2 : 1,
3860
+ value,
3861
+ onPick: (v) => {
3862
+ onChange && onChange({ target: { value: v } });
3863
+ if (!range || v.start && v.end) setOpen(false);
3864
+ }
3819
3865
  }
3820
- }
3821
- ) }),
3822
- document.body
3823
- ) : null,
3866
+ )
3867
+ }
3868
+ ),
3824
3869
  error ? /* @__PURE__ */ jsxs45("span", { className: "fd-field-error", children: [
3825
3870
  /* @__PURE__ */ jsx50("i", { className: "ph ph-warning-circle", "aria-hidden": "true" }),
3826
3871
  error
@@ -3829,61 +3874,18 @@ function DatePicker({ label, help, error, required = false, disabled = false, ra
3829
3874
  }
3830
3875
 
3831
3876
  // src/components/forms/TimePicker.tsx
3832
- import * as React23 from "react";
3833
- import { createPortal as createPortal7 } from "react-dom";
3877
+ import * as React24 from "react";
3834
3878
  import { jsx as jsx51, jsxs as jsxs46 } from "react/jsx-runtime";
3835
3879
  var pad = (n) => String(n).padStart(2, "0");
3836
- function usePopPos3(open, ref, estH, estW) {
3837
- const [pos, setPos] = React23.useState(null);
3838
- React23.useLayoutEffect(() => {
3839
- if (!open || !ref.current) {
3840
- setPos(null);
3841
- return;
3842
- }
3843
- const calc = () => {
3844
- const r = ref.current.getBoundingClientRect();
3845
- const below = window.innerHeight - r.bottom;
3846
- const up = below < estH && r.top > below;
3847
- const w = Math.max(r.width, estW || 0);
3848
- const maxH = Math.min(window.innerHeight - 16, Math.max(160, (up ? r.top : below) - 14));
3849
- setPos({
3850
- left: Math.max(8, Math.min(r.left, window.innerWidth - w - 8)),
3851
- width: r.width,
3852
- top: up ? null : r.bottom + 6,
3853
- bottom: up ? window.innerHeight - r.top + 6 : null,
3854
- up,
3855
- maxH
3856
- });
3857
- };
3858
- calc();
3859
- window.addEventListener("scroll", calc, true);
3860
- window.addEventListener("resize", calc);
3861
- return () => {
3862
- window.removeEventListener("scroll", calc, true);
3863
- window.removeEventListener("resize", calc);
3864
- };
3865
- }, [open]);
3866
- return pos;
3867
- }
3868
- var popStyle3 = (pos, extra) => ({
3869
- position: "fixed",
3870
- left: pos.left,
3871
- top: pos.top == null ? "auto" : pos.top,
3872
- bottom: pos.bottom == null ? "auto" : pos.bottom,
3873
- transformOrigin: pos.up ? "bottom center" : "top center",
3874
- maxHeight: pos.maxH,
3875
- overflowY: "auto",
3876
- ...extra
3877
- });
3878
3880
  function ClockFace({ value = "09:00", onChange }) {
3879
3881
  let [h24, m] = value.split(":").map(Number);
3880
3882
  if (isNaN(h24)) h24 = 9;
3881
3883
  if (isNaN(m)) m = 0;
3882
3884
  const pm = h24 >= 12;
3883
3885
  const h12 = h24 % 12 === 0 ? 12 : h24 % 12;
3884
- const [mode2, setMode] = React23.useState("h");
3885
- const faceRef = React23.useRef(null);
3886
- const dragging = React23.useRef(false);
3886
+ const [mode2, setMode] = React24.useState("h");
3887
+ const faceRef = React24.useRef(null);
3888
+ const dragging = React24.useRef(false);
3887
3889
  const set = (h, mm, isPm) => onChange((isPm ? h % 12 + 12 : h % 12) + ":" + pad(mm));
3888
3890
  const R = 108, NR = 80;
3889
3891
  const nums = mode2 === "h" ? Array.from({ length: 12 }, (_, i) => i + 1) : Array.from({ length: 12 }, (_, i) => i * 5);
@@ -3956,28 +3958,9 @@ function ClockFace({ value = "09:00", onChange }) {
3956
3958
  }
3957
3959
  function TimePicker({ label, help, error, required = false, disabled = false, value = "", onChange, placeholder = "Pick a time", id, className = "", style }) {
3958
3960
  const { fieldId, labelId, ariaLabelledBy } = useTriggerLabelling(label, id);
3959
- const [open, setOpen] = React23.useState(false);
3960
- const rootRef = React23.useRef(null);
3961
- const boxRef = React23.useRef(null);
3962
- const popRef = React23.useRef(null);
3963
- const pos = usePopPos3(open, boxRef, 420, 262);
3964
- React23.useEffect(() => {
3965
- if (!open) return;
3966
- const away = (e) => {
3967
- if (rootRef.current && rootRef.current.contains(e.target)) return;
3968
- if (popRef.current && popRef.current.contains(e.target)) return;
3969
- setOpen(false);
3970
- };
3971
- const esc = (e) => {
3972
- if (e.key === "Escape") setOpen(false);
3973
- };
3974
- document.addEventListener("pointerdown", away);
3975
- document.addEventListener("keydown", esc);
3976
- return () => {
3977
- document.removeEventListener("pointerdown", away);
3978
- document.removeEventListener("keydown", esc);
3979
- };
3980
- }, [open]);
3961
+ const [open, setOpen] = React24.useState(false);
3962
+ const rootRef = React24.useRef(null);
3963
+ const boxRef = React24.useRef(null);
3981
3964
  const disp = () => {
3982
3965
  if (!value) return "";
3983
3966
  const [h, m] = value.split(":").map(Number);
@@ -4009,10 +3992,19 @@ function TimePicker({ label, help, error, required = false, disabled = false, va
4009
3992
  ),
4010
3993
  /* @__PURE__ */ jsx51("span", { className: "fd-select-caret", children: /* @__PURE__ */ jsx51("i", { className: "ph ph-caret-down", "aria-hidden": "true" }) })
4011
3994
  ] }),
4012
- open && pos ? createPortal7(
4013
- /* @__PURE__ */ jsxs46("div", { className: "fd-pop" + (pos.up ? " is-up" : ""), ref: popRef, style: popStyle3(pos, { width: 262, minWidth: 0, zIndex: 130 }), children: [
4014
- /* @__PURE__ */ jsx51(ClockFace, { value: value || "09:00", onChange: (v) => onChange && onChange({ target: { value: v } }) }),
4015
- /* @__PURE__ */ jsxs46("div", { className: "fd-cal-foot", style: { margin: "0 14px 12px", paddingTop: 10 }, children: [
3995
+ /* @__PURE__ */ jsx51(
3996
+ Popover,
3997
+ {
3998
+ open,
3999
+ anchorRef: boxRef,
4000
+ triggerRef: rootRef,
4001
+ onClose: () => setOpen(false),
4002
+ placement: "bottom-start",
4003
+ offset: 6,
4004
+ width: 262,
4005
+ role: "presentation",
4006
+ style: { zIndex: 130 },
4007
+ footer: /* @__PURE__ */ jsxs46(React24.Fragment, { children: [
4016
4008
  /* @__PURE__ */ jsx51(
4017
4009
  "button",
4018
4010
  {
@@ -4028,10 +4020,10 @@ function TimePicker({ label, help, error, required = false, disabled = false, va
4028
4020
  ),
4029
4021
  /* @__PURE__ */ jsx51("span", { style: { flex: 1 } }),
4030
4022
  /* @__PURE__ */ jsx51("button", { type: "button", className: "fd-btn fd-btn-primary fd-btn-sm", onClick: () => setOpen(false), children: "Done" })
4031
- ] })
4032
- ] }),
4033
- document.body
4034
- ) : null,
4023
+ ] }),
4024
+ children: /* @__PURE__ */ jsx51(ClockFace, { value: value || "09:00", onChange: (v) => onChange && onChange({ target: { value: v } }) })
4025
+ }
4026
+ ),
4035
4027
  error ? /* @__PURE__ */ jsxs46("span", { className: "fd-field-error", children: [
4036
4028
  /* @__PURE__ */ jsx51("i", { className: "ph ph-warning-circle", "aria-hidden": "true" }),
4037
4029
  error
@@ -4040,7 +4032,7 @@ function TimePicker({ label, help, error, required = false, disabled = false, va
4040
4032
  }
4041
4033
 
4042
4034
  // src/components/forms/Slider.tsx
4043
- import * as React24 from "react";
4035
+ import * as React25 from "react";
4044
4036
  import { jsx as jsx52, jsxs as jsxs47 } from "react/jsx-runtime";
4045
4037
  function Slider({
4046
4038
  label,
@@ -4058,7 +4050,7 @@ function Slider({
4058
4050
  ...rest
4059
4051
  }) {
4060
4052
  const fieldId = useFieldId(id);
4061
- const [dragging, setDragging] = React24.useState(false);
4053
+ const [dragging, setDragging] = React25.useState(false);
4062
4054
  const v = value === void 0 ? min : Number(value);
4063
4055
  const pct = max === min ? 0 : (v - min) / (max - min) * 100;
4064
4056
  return /* @__PURE__ */ jsxs47("div", { className: ["fd-field", className].filter(Boolean).join(" "), children: [
@@ -4092,7 +4084,7 @@ function Slider({
4092
4084
  }
4093
4085
 
4094
4086
  // src/components/forms/RangeSlider.tsx
4095
- import * as React25 from "react";
4087
+ import * as React26 from "react";
4096
4088
  import { jsx as jsx53, jsxs as jsxs48 } from "react/jsx-runtime";
4097
4089
  function RangeSlider({
4098
4090
  label,
@@ -4112,9 +4104,9 @@ function RangeSlider({
4112
4104
  }) {
4113
4105
  const fmt2 = format || ((v) => String(v));
4114
4106
  const [a, b] = value || [min, max];
4115
- const [drag, setDrag] = React25.useState(null);
4116
- const [focus, setFocus] = React25.useState(null);
4117
- const railRef = React25.useRef(null);
4107
+ const [drag, setDrag] = React26.useState(null);
4108
+ const [focus, setFocus] = React26.useState(null);
4109
+ const railRef = React26.useRef(null);
4118
4110
  const pct = (v) => max === min ? 0 : (v - min) / (max - min) * 100;
4119
4111
  const clampPair = (i, v) => {
4120
4112
  v = Math.min(max, Math.max(min, Math.round(v / step) * step));
@@ -4129,7 +4121,7 @@ function RangeSlider({
4129
4121
  const r = railRef.current.getBoundingClientRect();
4130
4122
  return min + Math.min(1, Math.max(0, (e.clientX - r.left) / r.width)) * (max - min);
4131
4123
  };
4132
- React25.useEffect(() => {
4124
+ React26.useEffect(() => {
4133
4125
  if (drag === null) return;
4134
4126
  const mv = (e) => onChange && onChange(clampPair(drag, fromEvent(e)));
4135
4127
  const upH = () => setDrag(null);
@@ -4210,7 +4202,7 @@ function RangeSlider({
4210
4202
  /* @__PURE__ */ jsxs48("div", { className: "fd-range" + (hasLabels ? " has-labels" : ""), onPointerDown: onRailDown, children: [
4211
4203
  /* @__PURE__ */ jsx53("span", { className: "fd-range-rail", ref: railRef }),
4212
4204
  /* @__PURE__ */ jsx53("span", { className: "fd-range-fill", style: { left: pct(a) + "%", width: pct(b) - pct(a) + "%" } }),
4213
- marks.map((m) => /* @__PURE__ */ jsxs48(React25.Fragment, { children: [
4205
+ marks.map((m) => /* @__PURE__ */ jsxs48(React26.Fragment, { children: [
4214
4206
  /* @__PURE__ */ jsx53("span", { className: "fd-range-mark" + (m.value >= a && m.value <= b ? " is-in" : ""), style: { left: pct(m.value) + "%" } }),
4215
4207
  m.label ? /* @__PURE__ */ jsx53("span", { className: "fd-range-mark-label", style: { left: pct(m.value) + "%" }, children: m.label }) : null
4216
4208
  ] }, m.value)),
@@ -4239,7 +4231,7 @@ function RangeSlider({
4239
4231
  }
4240
4232
 
4241
4233
  // src/components/forms/Dropzone.tsx
4242
- import * as React26 from "react";
4234
+ import * as React27 from "react";
4243
4235
  import { jsx as jsx54, jsxs as jsxs49 } from "react/jsx-runtime";
4244
4236
  function Dropzone({
4245
4237
  onFiles,
@@ -4254,8 +4246,8 @@ function Dropzone({
4254
4246
  className = "",
4255
4247
  style
4256
4248
  }) {
4257
- const [over, setOver] = React26.useState(false);
4258
- const depth = React26.useRef(0);
4249
+ const [over, setOver] = React27.useState(false);
4250
+ const depth = React27.useRef(0);
4259
4251
  const has = (e) => {
4260
4252
  const dt = e.dataTransfer;
4261
4253
  if (!dt) return false;
@@ -4317,8 +4309,8 @@ function FilePickButton({
4317
4309
  className = "",
4318
4310
  children
4319
4311
  }) {
4320
- const ref = React26.useRef(null);
4321
- return /* @__PURE__ */ jsxs49(React26.Fragment, { children: [
4312
+ const ref = React27.useRef(null);
4313
+ return /* @__PURE__ */ jsxs49(React27.Fragment, { children: [
4322
4314
  /* @__PURE__ */ jsx54(
4323
4315
  "button",
4324
4316
  {
@@ -4351,10 +4343,10 @@ function FilePickButton({
4351
4343
  }
4352
4344
  function useStagedFiles(upload, opts) {
4353
4345
  const o = opts || {};
4354
- const [items, setItems] = React26.useState([]);
4355
- const controllers = React26.useRef({});
4346
+ const [items, setItems] = React27.useState([]);
4347
+ const controllers = React27.useRef({});
4356
4348
  const patch = (id, next) => setItems((list) => list.map((f) => f.id === id ? Object.assign({}, f, next) : f));
4357
- const run = React26.useCallback((att) => {
4349
+ const run = React27.useCallback((att) => {
4358
4350
  if (!upload) return;
4359
4351
  const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
4360
4352
  controllers.current[att.id] = ac;
@@ -4371,14 +4363,14 @@ function useStagedFiles(upload, opts) {
4371
4363
  delete controllers.current[att.id];
4372
4364
  });
4373
4365
  }, [upload]);
4374
- const add = React26.useCallback((files) => {
4366
+ const add = React27.useCallback((files) => {
4375
4367
  if (!upload) return [];
4376
4368
  const atts = Array.from(files).map((f) => toAttachment(f));
4377
4369
  setItems((list) => list.concat(atts));
4378
4370
  atts.forEach(run);
4379
4371
  return atts;
4380
4372
  }, [upload, run]);
4381
- const remove = React26.useCallback((att) => {
4373
+ const remove = React27.useCallback((att) => {
4382
4374
  const ac = controllers.current[att.id];
4383
4375
  if (ac) {
4384
4376
  try {
@@ -4389,10 +4381,10 @@ function useStagedFiles(upload, opts) {
4389
4381
  }
4390
4382
  setItems((list) => list.filter((f) => f.id !== att.id));
4391
4383
  }, []);
4392
- const retry = React26.useCallback((att) => {
4384
+ const retry = React27.useCallback((att) => {
4393
4385
  run(att);
4394
4386
  }, [run]);
4395
- const clear = React26.useCallback(() => {
4387
+ const clear = React27.useCallback(() => {
4396
4388
  Object.values(controllers.current).forEach((ac) => {
4397
4389
  try {
4398
4390
  ac && ac.abort();
@@ -4516,7 +4508,7 @@ function FileGrid({ files = [], onOpen, onRemove, onRetry, tiles = true, maxHeig
4516
4508
  }
4517
4509
 
4518
4510
  // src/components/forms/MarkdownEditor.tsx
4519
- import * as React27 from "react";
4511
+ import * as React28 from "react";
4520
4512
  import { jsx as jsx56, jsxs as jsxs51 } from "react/jsx-runtime";
4521
4513
  var isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent || "");
4522
4514
  var INLINE_RE = /(\*\*\*[^*\n]+\*\*\*|\*\*[^*\n]+\*\*|__[^_\n]+__|~~[^~\n]+~~|`[^`\n]+`|\*[^*\s][^*\n]*\*|(?<![A-Za-z0-9_])_[^_\s][^_\n]*_|\[[^\]\n]*\]\([^)\s\n]*\)|https?:\/\/\S+)/g;
@@ -4730,7 +4722,7 @@ function syncDom(root, value) {
4730
4722
  while (root.children.length > lines.length) root.removeChild(root.lastChild);
4731
4723
  }
4732
4724
  var LIST_CONT = RE_LI;
4733
- var MarkdownEditor = React27.forwardRef(function MarkdownEditor2({
4725
+ var MarkdownEditor = React28.forwardRef(function MarkdownEditor2({
4734
4726
  value = "",
4735
4727
  onChange,
4736
4728
  onSubmit,
@@ -4749,10 +4741,10 @@ var MarkdownEditor = React27.forwardRef(function MarkdownEditor2({
4749
4741
  className = "",
4750
4742
  id
4751
4743
  }, ref) {
4752
- const boxRef = React27.useRef(null);
4753
- const composing = React27.useRef(false);
4754
- const pendingCaret = React27.useRef(null);
4755
- React27.useLayoutEffect(() => {
4744
+ const boxRef = React28.useRef(null);
4745
+ const composing = React28.useRef(false);
4746
+ const pendingCaret = React28.useRef(null);
4747
+ React28.useLayoutEffect(() => {
4756
4748
  const root = boxRef.current;
4757
4749
  if (!root || composing.current) return;
4758
4750
  const active = document.activeElement === root || root.contains(document.activeElement);
@@ -4761,7 +4753,7 @@ var MarkdownEditor = React27.forwardRef(function MarkdownEditor2({
4761
4753
  pendingCaret.current = null;
4762
4754
  if (active && caret != null) placeCaret(root, caret);
4763
4755
  }, [value]);
4764
- React27.useEffect(() => {
4756
+ React28.useEffect(() => {
4765
4757
  if (autoFocus && boxRef.current) boxRef.current.focus();
4766
4758
  }, [autoFocus]);
4767
4759
  const caretNow = () => {
@@ -4828,7 +4820,7 @@ var MarkdownEditor = React27.forwardRef(function MarkdownEditor2({
4828
4820
  api.replaceRange(from, to, next, from + next.length);
4829
4821
  }
4830
4822
  };
4831
- React27.useImperativeHandle(ref, () => api);
4823
+ React28.useImperativeHandle(ref, () => api);
4832
4824
  function detect(text, caret) {
4833
4825
  if (!onTrigger) return;
4834
4826
  const upto = text.slice(0, caret);
@@ -4980,10 +4972,10 @@ var MarkdownEditor = React27.forwardRef(function MarkdownEditor2({
4980
4972
  });
4981
4973
 
4982
4974
  // src/components/platform/AccountMenu.tsx
4983
- import * as React29 from "react";
4975
+ import * as React30 from "react";
4984
4976
 
4985
4977
  // src/kits/session.ts
4986
- import * as React28 from "react";
4978
+ import * as React29 from "react";
4987
4979
  var PERMISSION_CATALOG = [
4988
4980
  { group: "Plans", items: [
4989
4981
  { key: "plan.view", label: "View plans", detail: "Read any plan in the workspace." },
@@ -6550,8 +6542,8 @@ function roadmap(overrides) {
6550
6542
  };
6551
6543
  }
6552
6544
  function useSession() {
6553
- const [s, setS] = React28.useState(getSession);
6554
- React28.useEffect(() => subscribe(setS), []);
6545
+ const [s, setS] = React29.useState(getSession);
6546
+ React29.useEffect(() => subscribe(setS), []);
6555
6547
  return s;
6556
6548
  }
6557
6549
  var SessionKit = {
@@ -6584,7 +6576,7 @@ var SessionKit = {
6584
6576
  };
6585
6577
 
6586
6578
  // src/components/platform/AccountMenu.tsx
6587
- import { Fragment as Fragment12, jsx as jsx57, jsxs as jsxs52 } from "react/jsx-runtime";
6579
+ import { Fragment as Fragment14, jsx as jsx57, jsxs as jsxs52 } from "react/jsx-runtime";
6588
6580
  var DEFAULT_LINKS = [
6589
6581
  { id: "profile", label: "Your profile", icon: "user-circle", href: "../admin/index.html#profile" },
6590
6582
  { id: "preferences", label: "Preferences", icon: "sliders-horizontal", href: "../admin/index.html#preferences" }
@@ -6621,9 +6613,9 @@ function AccountMenu({
6621
6613
  ...rest
6622
6614
  }) {
6623
6615
  const session = SessionKit.useSession();
6624
- const [open, setOpen] = React29.useState(false);
6625
- const [switching, setSwitching] = React29.useState(false);
6626
- const ref = React29.useRef(null);
6616
+ const [open, setOpen] = React30.useState(false);
6617
+ const [switching, setSwitching] = React30.useState(false);
6618
+ const ref = React30.useRef(null);
6627
6619
  const user = session.user;
6628
6620
  const visibleAdmin = adminLinks.filter((l) => !l.perm || SessionKit.can(l.perm));
6629
6621
  const pick = (item) => {
@@ -6637,7 +6629,7 @@ function AccountMenu({
6637
6629
  if (onSignOut) return onSignOut();
6638
6630
  window.alert("Signed out. (Simulated \u2014 no auth provider is wired up.)");
6639
6631
  };
6640
- return /* @__PURE__ */ jsxs52(Fragment12, { children: [
6632
+ return /* @__PURE__ */ jsxs52(Fragment14, { children: [
6641
6633
  /* @__PURE__ */ jsxs52(
6642
6634
  "button",
6643
6635
  {
@@ -6675,7 +6667,7 @@ function AccountMenu({
6675
6667
  /* @__PURE__ */ jsx57("span", { style: { flex: 1 }, children: "View as another member" }),
6676
6668
  /* @__PURE__ */ jsx57("i", { className: "ph ph-caret-" + (switching ? "up" : "down"), "aria-hidden": "true", style: { fontSize: 11 } })
6677
6669
  ] }),
6678
- switching ? /* @__PURE__ */ jsx57("div", { className: "fd-stack", style: { gap: 0, maxHeight: 208, overflowY: "auto" }, children: session.allUsers.filter((u) => u.status === "active").map((u) => /* @__PURE__ */ jsxs52(
6670
+ switching ? /* @__PURE__ */ jsx57("div", { className: "fd-stack", style: { gap: 0 }, children: session.allUsers.filter((u) => u.status === "active").map((u) => /* @__PURE__ */ jsxs52(
6679
6671
  "button",
6680
6672
  {
6681
6673
  type: "button",
@@ -6707,14 +6699,14 @@ function AccountMenu({
6707
6699
  }
6708
6700
 
6709
6701
  // src/components/platform/ApiSpecBrowser.tsx
6710
- import * as React30 from "react";
6702
+ import * as React31 from "react";
6711
6703
  import { jsx as jsx58, jsxs as jsxs53 } from "react/jsx-runtime";
6712
6704
  var METHOD_TONE = { GET: "success", POST: "info", PATCH: "warning", PUT: "warning", DELETE: "danger" };
6713
6705
  function Json({ obj }) {
6714
6706
  return /* @__PURE__ */ jsx58("pre", { className: "fd-json", children: JSON.stringify(obj, null, 2) });
6715
6707
  }
6716
6708
  function Endpoint({ s, onRequest }) {
6717
- const [tried, setTried] = React30.useState(null);
6709
+ const [tried, setTried] = React31.useState(null);
6718
6710
  const run = async () => {
6719
6711
  setTried("busy");
6720
6712
  const t0 = (window.performance || Date).now();
@@ -6787,10 +6779,10 @@ function ApiSpecBrowser({
6787
6779
  className = "",
6788
6780
  ...rest
6789
6781
  }) {
6790
- const [q, setQ] = React30.useState("");
6791
- const [method, setMethod] = React30.useState(null);
6792
- const [mod, setMod] = React30.useState(null);
6793
- const [listOnly, setListOnly] = React30.useState(false);
6782
+ const [q, setQ] = React31.useState("");
6783
+ const [method, setMethod] = React31.useState(null);
6784
+ const [mod, setMod] = React31.useState(null);
6785
+ const [listOnly, setListOnly] = React31.useState(false);
6794
6786
  const activeModule = modules && modules.find((m) => m.id === mod);
6795
6787
  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())));
6796
6788
  const effGroups = groups && groups.length ? groups : [["All endpoints", spec.map((s) => s.id)]];
@@ -6869,7 +6861,7 @@ function ApiSpecBrowser({
6869
6861
  }
6870
6862
 
6871
6863
  // src/components/platform/ProfilePage.tsx
6872
- import * as React31 from "react";
6864
+ import * as React32 from "react";
6873
6865
  import { jsx as jsx59, jsxs as jsxs54 } from "react/jsx-runtime";
6874
6866
  var TIMEZONES = ["America/New_York", "America/Chicago", "America/Denver", "America/Los_Angeles", "America/Anchorage", "Pacific/Honolulu", "Europe/London", "Europe/Berlin"];
6875
6867
  var NOTIFY = [
@@ -6882,7 +6874,7 @@ var NOTIFY = [
6882
6874
  function ProfilePage({ user: userProp, onSave, onNavigate, sessions, showSessions = true, className = "", ...rest }) {
6883
6875
  const session = SessionKit.useSession();
6884
6876
  const user = userProp || session.user;
6885
- const [draft, setDraft] = React31.useState(() => ({
6877
+ const [draft, setDraft] = React32.useState(() => ({
6886
6878
  name: user.name || "",
6887
6879
  title: user.title || "",
6888
6880
  email: user.email || "",
@@ -6891,8 +6883,8 @@ function ProfilePage({ user: userProp, onSave, onNavigate, sessions, showSession
6891
6883
  bio: user.bio || "",
6892
6884
  notify: user.notify || { planShared: true, planChanged: true, goalMissed: true, flagChanged: false, weekly: true }
6893
6885
  }));
6894
- const [saving, setSaving] = React31.useState(false);
6895
- const [saved, setSaved] = React31.useState(false);
6886
+ const [saving, setSaving] = React32.useState(false);
6887
+ const [saved, setSaved] = React32.useState(false);
6896
6888
  const set = (k, v) => {
6897
6889
  setDraft((d) => Object.assign({}, d, { [k]: v }));
6898
6890
  setSaved(false);
@@ -7082,10 +7074,10 @@ function ProfilePage({ user: userProp, onSave, onNavigate, sessions, showSession
7082
7074
  }
7083
7075
 
7084
7076
  // src/components/platform/RoadmapTimeline.tsx
7085
- import * as React33 from "react";
7077
+ import * as React34 from "react";
7086
7078
 
7087
7079
  // src/kits/runtime.ts
7088
- import * as React32 from "react";
7080
+ import * as React33 from "react";
7089
7081
  var WIRED_ENDPOINTS = [
7090
7082
  // Nothing yet. Every id below would come from a real service:
7091
7083
  // "plan.get", "placements.list", …
@@ -7256,8 +7248,8 @@ var RuntimeKit = {
7256
7248
  };
7257
7249
  RuntimeKit.declare(FEATURE_NEEDS);
7258
7250
  function useRuntimeMode() {
7259
- const [m, setM] = React32.useState(RuntimeKit.getMode());
7260
- React32.useEffect(() => RuntimeKit.subscribe(setM), []);
7251
+ const [m, setM] = React33.useState(RuntimeKit.getMode());
7252
+ React33.useEffect(() => RuntimeKit.subscribe(setM), []);
7261
7253
  return m;
7262
7254
  }
7263
7255
  function useFeatureStatus(key) {
@@ -7340,12 +7332,12 @@ function RoadmapCard({ item, byKey, onOpenFlag }) {
7340
7332
  }
7341
7333
  function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
7342
7334
  const session = SessionKit.useSession();
7343
- const [project, setProject] = React33.useState("");
7344
- const [q, setQ] = React33.useState("");
7345
- const scrollRef = React33.useRef(null);
7346
- const nowRef = React33.useRef(null);
7347
- const rm = React33.useMemo(() => SessionKit.roadmap({ isComplete: RuntimeKit.isComplete }), [session]);
7348
- const byKey = React33.useMemo(() => {
7335
+ const [project, setProject] = React34.useState("");
7336
+ const [q, setQ] = React34.useState("");
7337
+ const scrollRef = React34.useRef(null);
7338
+ const nowRef = React34.useRef(null);
7339
+ const rm = React34.useMemo(() => SessionKit.roadmap({ isComplete: RuntimeKit.isComplete }), [session]);
7340
+ const byKey = React34.useMemo(() => {
7349
7341
  const m = {};
7350
7342
  rm.items.forEach((i) => {
7351
7343
  m[i.key] = i;
@@ -7354,7 +7346,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
7354
7346
  }, [rm]);
7355
7347
  const items = rm.items.filter((i) => (!project || i.project === project) && (!q || (i.label + " " + i.description + " " + (i.backend || "") + " " + i.key).toLowerCase().includes(q.toLowerCase())));
7356
7348
  const projects = [...new Set(rm.items.map((i) => i.project))];
7357
- React33.useEffect(() => {
7349
+ React34.useEffect(() => {
7358
7350
  let raf1 = 0, raf2 = 0;
7359
7351
  const place = () => {
7360
7352
  const box = scrollRef.current, mark = nowRef.current;
@@ -7428,7 +7420,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
7428
7420
  lastPhase = item.phase;
7429
7421
  const inPhase = items.filter((i) => i.phase === item.phase).length;
7430
7422
  const isBoundary = n === firstPending;
7431
- return /* @__PURE__ */ jsxs55(React33.Fragment, { children: [
7423
+ return /* @__PURE__ */ jsxs55(React34.Fragment, { children: [
7432
7424
  showPhase ? /* @__PURE__ */ jsxs55("div", { className: "fd-rm-era", children: [
7433
7425
  /* @__PURE__ */ jsxs55("span", { className: "fd-row", style: { gap: 8, flexWrap: "wrap", alignItems: "baseline" }, children: [
7434
7426
  /* @__PURE__ */ jsx60("span", { style: { fontWeight: 700 }, children: item.phase === 0 ? "Shipped" : "Phase " + item.phase + " \u2014 " + item.phaseName }),
@@ -7472,9 +7464,9 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
7472
7464
  }
7473
7465
 
7474
7466
  // src/components/platform/ComingSoon.tsx
7475
- import * as React34 from "react";
7476
- import { createPortal as createPortal8 } from "react-dom";
7477
- import { Fragment as Fragment14, jsx as jsx61, jsxs as jsxs56 } from "react/jsx-runtime";
7467
+ import * as React35 from "react";
7468
+ import { createPortal as createPortal5 } from "react-dom";
7469
+ import { Fragment as Fragment16, jsx as jsx61, jsxs as jsxs56 } from "react/jsx-runtime";
7478
7470
  var BYPASS_STORE = "fd.soon.bypass.v1";
7479
7471
  function readBypassed() {
7480
7472
  try {
@@ -7490,8 +7482,8 @@ function writeBypassed(list) {
7490
7482
  }
7491
7483
  }
7492
7484
  function useBypass(key) {
7493
- const [on, setOn] = React34.useState(() => !!key && readBypassed().indexOf(key) >= 0);
7494
- React34.useEffect(() => {
7485
+ const [on, setOn] = React35.useState(() => !!key && readBypassed().indexOf(key) >= 0);
7486
+ React35.useEffect(() => {
7495
7487
  setOn(!!key && readBypassed().indexOf(key) >= 0);
7496
7488
  }, [key]);
7497
7489
  const set = (next) => {
@@ -7504,7 +7496,7 @@ function useBypass(key) {
7504
7496
  return [on, set];
7505
7497
  }
7506
7498
  function SoonCard({ label, detail, backend, eta, effort, onRoadmap, allowed, onBypass }) {
7507
- return /* @__PURE__ */ jsxs56(Fragment14, { children: [
7499
+ return /* @__PURE__ */ jsxs56(Fragment16, { children: [
7508
7500
  /* @__PURE__ */ jsxs56("span", { className: "fd-soon-badge", children: [
7509
7501
  /* @__PURE__ */ jsx61("i", { className: "ph ph-traffic-cone", "aria-hidden": "true" }),
7510
7502
  label
@@ -7538,9 +7530,9 @@ function SoonCard({ label, detail, backend, eta, effort, onRoadmap, allowed, onB
7538
7530
  ] });
7539
7531
  }
7540
7532
  function useHoverCard(open) {
7541
- const anchor = React34.useRef(null);
7542
- const [pos, setPos] = React34.useState(null);
7543
- React34.useLayoutEffect(() => {
7533
+ const anchor = React35.useRef(null);
7534
+ const [pos, setPos] = React35.useState(null);
7535
+ React35.useLayoutEffect(() => {
7544
7536
  if (!open || !anchor.current) {
7545
7537
  setPos(null);
7546
7538
  return;
@@ -7667,9 +7659,9 @@ function ComingSoon({
7667
7659
  ] });
7668
7660
  }
7669
7661
  function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, onBypass, blur, tip, className, rest, children }) {
7670
- const [open, setOpen] = React34.useState(false);
7662
+ const [open, setOpen] = React35.useState(false);
7671
7663
  const [anchor, pos] = useHoverCard(open);
7672
- const close = React34.useRef(null);
7664
+ const close = React35.useRef(null);
7673
7665
  const show = () => {
7674
7666
  if (close.current) {
7675
7667
  clearTimeout(close.current);
@@ -7685,7 +7677,7 @@ function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, o
7685
7677
  setOpen(false);
7686
7678
  }, 140);
7687
7679
  };
7688
- React34.useEffect(() => () => {
7680
+ React35.useEffect(() => () => {
7689
7681
  if (close.current) clearTimeout(close.current);
7690
7682
  }, []);
7691
7683
  return /* @__PURE__ */ jsxs56(
@@ -7714,7 +7706,7 @@ function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, o
7714
7706
  children: /* @__PURE__ */ jsx61("i", { className: "ph ph-traffic-cone", "aria-hidden": "true" })
7715
7707
  }
7716
7708
  ),
7717
- open && pos ? createPortal8(
7709
+ open && pos ? createPortal5(
7718
7710
  /* @__PURE__ */ jsx61(
7719
7711
  "div",
7720
7712
  {
@@ -7854,13 +7846,13 @@ function PermissionHint({ perm, children }) {
7854
7846
  }
7855
7847
 
7856
7848
  // src/components/platform/ModeSwitch.tsx
7857
- import * as React35 from "react";
7858
- import { Fragment as Fragment15, jsx as jsx63, jsxs as jsxs58 } from "react/jsx-runtime";
7849
+ import * as React36 from "react";
7850
+ import { Fragment as Fragment17, jsx as jsx63, jsxs as jsxs58 } from "react/jsx-runtime";
7859
7851
  function ModeSwitch({ canToggle = false, requestCount = 0, onClearLog, renderLog, onOpenSpec, summary }) {
7860
7852
  const mode2 = useRuntimeMode();
7861
- const [open, setOpen] = React35.useState(false);
7862
- const ref = React35.useRef(null);
7863
- React35.useEffect(() => {
7853
+ const [open, setOpen] = React36.useState(false);
7854
+ const ref = React36.useRef(null);
7855
+ React36.useEffect(() => {
7864
7856
  if (!open) return;
7865
7857
  const away = (e) => {
7866
7858
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -7925,7 +7917,7 @@ function ModeSwitch({ canToggle = false, requestCount = 0, onClearLog, renderLog
7925
7917
  s.total,
7926
7918
  " features complete"
7927
7919
  ] }),
7928
- onOpenSpec ? /* @__PURE__ */ jsxs58(Fragment15, { children: [
7920
+ onOpenSpec ? /* @__PURE__ */ jsxs58(Fragment17, { children: [
7929
7921
  /* @__PURE__ */ jsx63("span", { style: { flex: 1 } }),
7930
7922
  /* @__PURE__ */ jsxs58("button", { type: "button", className: "fd-soon-link", onClick: () => {
7931
7923
  setOpen(false);
@@ -8109,11 +8101,11 @@ function SaturationDistribution({ campuses = [], floorBand, floorScore, onSelect
8109
8101
  }
8110
8102
 
8111
8103
  // src/components/planner/MixGap.tsx
8112
- import * as React36 from "react";
8113
- import { Fragment as Fragment16, jsx as jsx66, jsxs as jsxs61 } from "react/jsx-runtime";
8104
+ import * as React37 from "react";
8105
+ import { Fragment as Fragment18, jsx as jsx66, jsxs as jsxs61 } from "react/jsx-runtime";
8114
8106
  function MixGap({ rows = [], loading = false, className = "" }) {
8115
8107
  const max = Math.max(1, ...rows.flatMap((r) => [r.target, r.realized]));
8116
- const [hover, setHover] = React36.useState(null);
8108
+ const [hover, setHover] = React37.useState(null);
8117
8109
  const toneOf = (gap) => gap >= -1 ? "ok" : gap >= -4 ? "warn" : "danger";
8118
8110
  const TONE = { ok: "var(--ok-solid)", warn: "var(--warn-solid)", danger: "var(--danger-solid)" };
8119
8111
  return /* @__PURE__ */ jsxs61("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 4 }, children: [
@@ -8140,7 +8132,7 @@ function MixGap({ rows = [], loading = false, className = "" }) {
8140
8132
  onMouseLeave: () => setHover(null),
8141
8133
  children: [
8142
8134
  /* @__PURE__ */ jsx66("span", { style: { width: 128, flex: "none" }, children: /* @__PURE__ */ jsx66(ChannelTag, { channel: r.channel, size: "sm" }) }),
8143
- /* @__PURE__ */ jsx66("span", { style: { position: "relative", flex: 1, height: 22, minWidth: 120 }, children: loading ? /* @__PURE__ */ jsx66("span", { className: "fd-skel", style: { position: "absolute", inset: "5px 0", borderRadius: 3 } }) : /* @__PURE__ */ jsxs61(Fragment16, { children: [
8135
+ /* @__PURE__ */ jsx66("span", { style: { position: "relative", flex: 1, height: 22, minWidth: 120 }, children: loading ? /* @__PURE__ */ jsx66("span", { className: "fd-skel", style: { position: "absolute", inset: "5px 0", borderRadius: 3 } }) : /* @__PURE__ */ jsxs61(Fragment18, { children: [
8144
8136
  /* @__PURE__ */ jsx66("span", { style: { position: "absolute", left: 0, top: 5, height: 12, width: r.realized / max * 100 + "%", background: TONE[tone], borderRadius: "2px 3px 3px 2px", transition: "width var(--dur-slow) var(--ease), background var(--dur-base) var(--ease)" } }),
8145
8137
  /* @__PURE__ */ jsx66("span", { style: { position: "absolute", left: r.target / max * 100 + "%", top: 0, width: 3, height: 22, background: "var(--n-500)", borderRadius: 1 } }),
8146
8138
  /* @__PURE__ */ jsxs61("span", { className: "fd-num", style: { position: "absolute", right: Math.max(r.realized, r.target) / max > 0.82 ? 0 : "auto", left: Math.max(r.realized, r.target) / max > 0.82 ? "auto" : "calc(" + Math.max(r.realized, r.target) / max * 100 + "% + 10px)", top: 2, fontSize: 12, color: "var(--text-muted)", whiteSpace: "nowrap", background: Math.max(r.realized, r.target) / max > 0.82 ? "var(--surface)" : "none", paddingLeft: 3 }, children: [
@@ -8253,8 +8245,8 @@ function ChannelContribution({
8253
8245
  }
8254
8246
 
8255
8247
  // src/components/planner/BudgetReallocator.tsx
8256
- import * as React37 from "react";
8257
- import { Fragment as Fragment17, jsx as jsx68, jsxs as jsxs63 } from "react/jsx-runtime";
8248
+ import * as React38 from "react";
8249
+ import { Fragment as Fragment19, jsx as jsx68, jsxs as jsxs63 } from "react/jsx-runtime";
8258
8250
  var bandFor = (crp) => crp >= 200 ? "dominant" : crp >= 100 ? "strong" : crp >= 50 ? "adequate" : "weak";
8259
8251
  function BudgetReallocator({
8260
8252
  campus,
@@ -8269,8 +8261,8 @@ function BudgetReallocator({
8269
8261
  onCancel,
8270
8262
  className = ""
8271
8263
  }) {
8272
- const [draft, setDraft] = React37.useState(spend);
8273
- React37.useEffect(() => setDraft(spend), [spend]);
8264
+ const [draft, setDraft] = React38.useState(spend);
8265
+ React38.useEffect(() => setDraft(spend), [spend]);
8274
8266
  const dirty = draft !== spend;
8275
8267
  const nextCrp = scoreFor ? scoreFor(draft) : crp;
8276
8268
  const nextBand = bandFor(nextCrp);
@@ -8294,7 +8286,7 @@ function BudgetReallocator({
8294
8286
  /* @__PURE__ */ jsxs63("div", { className: "fd-row", style: { gap: 12, flexWrap: "wrap" }, children: [
8295
8287
  /* @__PURE__ */ jsx68("span", { className: "fd-h4", style: { flex: 1, minWidth: 140 }, children: campus }),
8296
8288
  /* @__PURE__ */ jsx68(CsiBadge, { band: nowBand, crp, size: "medium" }),
8297
- dirty ? /* @__PURE__ */ jsxs63(Fragment17, { children: [
8289
+ dirty ? /* @__PURE__ */ jsxs63(Fragment19, { children: [
8298
8290
  /* @__PURE__ */ jsx68("i", { className: "ph ph-arrow-right fd-muted", "aria-hidden": "true" }),
8299
8291
  /* @__PURE__ */ jsx68(CsiBadge, { band: nextBand, crp: nextCrp, size: "medium", preview: true })
8300
8292
  ] }) : null
@@ -8440,7 +8432,7 @@ function ImportanceTag({ level, showLabel = true, size = "sm", className = "" })
8440
8432
  }
8441
8433
 
8442
8434
  // src/components/planner/PreferenceMeter.tsx
8443
- import * as React38 from "react";
8435
+ import * as React39 from "react";
8444
8436
  import { jsx as jsx71, jsxs as jsxs66 } from "react/jsx-runtime";
8445
8437
  function preferenceScore(criteria) {
8446
8438
  const earned = criteria.reduce((n, c) => n + (Number(c.earned) || 0), 0);
@@ -8470,7 +8462,7 @@ function PreferenceMeter({
8470
8462
  className = ""
8471
8463
  }) {
8472
8464
  const value = score == null ? preferenceScore(criteria) : score;
8473
- const segments = React38.useMemo(() => preferenceSegments(criteria), [criteria]);
8465
+ const segments = React39.useMemo(() => preferenceSegments(criteria), [criteria]);
8474
8466
  return /* @__PURE__ */ jsx71(
8475
8467
  ScoreMeter,
8476
8468
  {
@@ -8509,10 +8501,10 @@ function PreferenceMeter({
8509
8501
  }
8510
8502
 
8511
8503
  // src/components/chat/AgentChatPanel.tsx
8512
- import * as React44 from "react";
8504
+ import * as React45 from "react";
8513
8505
 
8514
8506
  // src/components/chat/chatEngine.ts
8515
- import * as React39 from "react";
8507
+ import * as React40 from "react";
8516
8508
  var CHAT_UNAVAILABLE = "chat_unavailable";
8517
8509
  var JOB_PENDING = ["queued", "running"];
8518
8510
  var JOB_SUCCESS = ["completed", "recovered"];
@@ -8566,22 +8558,22 @@ function useChatEngine(opts) {
8566
8558
  onClear,
8567
8559
  onFeedback
8568
8560
  } = opts || {};
8569
- const [status, setStatus] = React39.useState("idle");
8570
- const [threadId, setThreadId] = React39.useState(null);
8571
- const [messages, setMessages] = React39.useState([]);
8572
- const [queue, setQueue] = React39.useState([]);
8573
- const [fatal, setFatal] = React39.useState(null);
8574
- const [busy, setBusy] = React39.useState(false);
8575
- const [turnStartedAt, setTurnStartedAt] = React39.useState(null);
8576
- const listRef = React39.useRef([]);
8577
- const queueRef = React39.useRef([]);
8578
- const busyRef = React39.useRef(false);
8579
- const stoppedRef = React39.useRef(false);
8580
- const abortRef = React39.useRef(null);
8581
- const serverCount = React39.useRef(0);
8582
- const threadRef = React39.useRef(null);
8583
- const mounted = React39.useRef(true);
8584
- React39.useEffect(() => {
8561
+ const [status, setStatus] = React40.useState("idle");
8562
+ const [threadId, setThreadId] = React40.useState(null);
8563
+ const [messages, setMessages] = React40.useState([]);
8564
+ const [queue, setQueue] = React40.useState([]);
8565
+ const [fatal, setFatal] = React40.useState(null);
8566
+ const [busy, setBusy] = React40.useState(false);
8567
+ const [turnStartedAt, setTurnStartedAt] = React40.useState(null);
8568
+ const listRef = React40.useRef([]);
8569
+ const queueRef = React40.useRef([]);
8570
+ const busyRef = React40.useRef(false);
8571
+ const stoppedRef = React40.useRef(false);
8572
+ const abortRef = React40.useRef(null);
8573
+ const serverCount = React40.useRef(0);
8574
+ const threadRef = React40.useRef(null);
8575
+ const mounted = React40.useRef(true);
8576
+ React40.useEffect(() => {
8585
8577
  mounted.current = true;
8586
8578
  return () => {
8587
8579
  mounted.current = false;
@@ -8603,14 +8595,14 @@ function useChatEngine(opts) {
8603
8595
  }
8604
8596
  return false;
8605
8597
  };
8606
- const loadThread = React39.useCallback(async (id) => {
8598
+ const loadThread = React40.useCallback(async (id) => {
8607
8599
  const data = await apiAdapter.getThread(id);
8608
8600
  const list = [...data && data.messages || []];
8609
8601
  serverCount.current = list.length;
8610
8602
  commit(list);
8611
8603
  return list;
8612
8604
  }, [apiAdapter]);
8613
- React39.useEffect(() => {
8605
+ React40.useEffect(() => {
8614
8606
  if (!apiAdapter) {
8615
8607
  setStatus("idle");
8616
8608
  setFatal(null);
@@ -8823,7 +8815,7 @@ function useChatEngine(opts) {
8823
8815
  await dispatchTurn(turn);
8824
8816
  }
8825
8817
  }
8826
- const send = React39.useCallback((text, attachments) => {
8818
+ const send = React40.useCallback((text, attachments) => {
8827
8819
  const body = (text || "").trim();
8828
8820
  if (!body && !(attachments && attachments.length)) return;
8829
8821
  if (status === "disconnected") return;
@@ -8833,7 +8825,7 @@ function useChatEngine(opts) {
8833
8825
  stoppedRef.current = false;
8834
8826
  drain();
8835
8827
  }, [status]);
8836
- const stop = React39.useCallback(() => {
8828
+ const stop = React40.useCallback(() => {
8837
8829
  stoppedRef.current = true;
8838
8830
  const ac = abortRef.current;
8839
8831
  if (ac) {
@@ -8852,11 +8844,11 @@ function useChatEngine(opts) {
8852
8844
  store.del(STORAGE_PREFIX + threadRef.current);
8853
8845
  }
8854
8846
  }, [apiAdapter]);
8855
- const removeQueued = React39.useCallback((id) => {
8847
+ const removeQueued = React40.useCallback((id) => {
8856
8848
  queueRef.current = queueRef.current.filter((t) => t.id !== id);
8857
8849
  setQueue(queueRef.current.slice());
8858
8850
  }, []);
8859
- const retry = React39.useCallback(() => {
8851
+ const retry = React40.useCallback(() => {
8860
8852
  const list = listRef.current;
8861
8853
  let at = -1;
8862
8854
  for (let i = list.length - 1; i >= 0; i--) if (list[i].role === "user") {
@@ -8872,19 +8864,19 @@ function useChatEngine(opts) {
8872
8864
  setQueue(queueRef.current.slice());
8873
8865
  drain();
8874
8866
  }, []);
8875
- const clear = React39.useCallback(() => {
8867
+ const clear = React40.useCallback(() => {
8876
8868
  commit([]);
8877
8869
  serverCount.current = 0;
8878
8870
  queueRef.current = [];
8879
8871
  setQueue([]);
8880
8872
  onClear && onClear();
8881
8873
  }, [onClear]);
8882
- const setFeedback = React39.useCallback((id, value) => {
8874
+ const setFeedback = React40.useCallback((id, value) => {
8883
8875
  patch(id, (m) => ({ feedback: m.feedback === value ? null : value }));
8884
8876
  const msg = listRef.current.find((m) => m.id === id);
8885
8877
  onFeedback && onFeedback({ message: msg, feedback: msg ? msg.feedback : value });
8886
8878
  }, [onFeedback]);
8887
- const reload = React39.useCallback(async () => {
8879
+ const reload = React40.useCallback(async () => {
8888
8880
  if (!threadRef.current) return;
8889
8881
  setStatus("loading");
8890
8882
  try {
@@ -8922,10 +8914,10 @@ var ChatKit = {
8922
8914
  };
8923
8915
 
8924
8916
  // src/components/chat/ChatTranscript.tsx
8925
- import * as React41 from "react";
8917
+ import * as React42 from "react";
8926
8918
 
8927
8919
  // src/components/chat/ChatTurn.tsx
8928
- import * as React40 from "react";
8920
+ import * as React41 from "react";
8929
8921
  import { jsx as jsx72, jsxs as jsxs67 } from "react/jsx-runtime";
8930
8922
  function JsonView({ value }) {
8931
8923
  let text;
@@ -8962,7 +8954,7 @@ function PacketCard({ packet, schema, render, onApply, applied }) {
8962
8954
  ] });
8963
8955
  }
8964
8956
  function ThinkingBlock({ text, durationMs, streaming, defaultOpen = false }) {
8965
- const [open, setOpen] = React40.useState(defaultOpen);
8957
+ const [open, setOpen] = React41.useState(defaultOpen);
8966
8958
  if (!text) return null;
8967
8959
  return /* @__PURE__ */ jsxs67("div", { className: "fdc-think" + (open ? " is-open" : ""), children: [
8968
8960
  /* @__PURE__ */ jsxs67("button", { type: "button", className: "fdc-think-head", onClick: () => setOpen((v) => !v), "aria-expanded": open, children: [
@@ -8979,7 +8971,7 @@ function ThinkingBlock({ text, durationMs, streaming, defaultOpen = false }) {
8979
8971
  ] });
8980
8972
  }
8981
8973
  function Citations({ items = [], onOpen }) {
8982
- const [open, setOpen] = React40.useState(false);
8974
+ const [open, setOpen] = React41.useState(false);
8983
8975
  if (!items.length) return null;
8984
8976
  return /* @__PURE__ */ jsxs67("div", { className: "fdc-cites", children: [
8985
8977
  /* @__PURE__ */ jsxs67("button", { type: "button", className: "fdc-cites-head", onClick: () => setOpen((v) => !v), "aria-expanded": open, children: [
@@ -9005,7 +8997,7 @@ function clampText(text, max) {
9005
8997
  return (at > max * 0.6 ? cut.slice(0, at) : cut).trimEnd() + "\u2026";
9006
8998
  }
9007
8999
  function MessageBody({ message: m, ctx }) {
9008
- const [expanded, setExpanded] = React40.useState(false);
9000
+ const [expanded, setExpanded] = React41.useState(false);
9009
9001
  const isUser = m.role === "user";
9010
9002
  const raw = m.text || "";
9011
9003
  const clamped = !expanded && !m.streaming ? clampText(raw, ctx.maxVisibleChars) : null;
@@ -9072,9 +9064,9 @@ function MessageBody({ message: m, ctx }) {
9072
9064
  ] });
9073
9065
  }
9074
9066
  function useCopyRun() {
9075
- const [done, setDone] = React40.useState(false);
9076
- const t = React40.useRef(null);
9077
- React40.useEffect(() => () => {
9067
+ const [done, setDone] = React41.useState(false);
9068
+ const t = React41.useRef(null);
9069
+ React41.useEffect(() => () => {
9078
9070
  if (t.current) clearTimeout(t.current);
9079
9071
  }, []);
9080
9072
  return [done, (text) => {
@@ -9101,7 +9093,7 @@ function RunActions({ group, ctx }) {
9101
9093
  ] }),
9102
9094
  isAssistant && ctx.onRetry ? /* @__PURE__ */ jsx72("button", { type: "button", className: "fdc-act", onClick: ctx.onRetry, "aria-label": "Retry this turn", children: /* @__PURE__ */ jsx72("i", { className: "ph ph-arrow-clockwise", "aria-hidden": "true" }) }) : null,
9103
9095
  group.role === "user" && ctx.onEdit ? /* @__PURE__ */ jsx72("button", { type: "button", className: "fdc-act", onClick: () => ctx.onEdit(last), "aria-label": "Edit and resend", children: /* @__PURE__ */ jsx72("i", { className: "ph ph-pencil-simple", "aria-hidden": "true" }) }) : null,
9104
- isAssistant && ctx.onFeedback ? /* @__PURE__ */ jsxs67(React40.Fragment, { children: [
9096
+ isAssistant && ctx.onFeedback ? /* @__PURE__ */ jsxs67(React41.Fragment, { children: [
9105
9097
  /* @__PURE__ */ jsx72("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__ */ jsx72("i", { className: "ph ph-thumbs-up", "aria-hidden": "true" }) }),
9106
9098
  /* @__PURE__ */ jsx72("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__ */ jsx72("i", { className: "ph ph-thumbs-down", "aria-hidden": "true" }) })
9107
9099
  ] }) : null,
@@ -9196,10 +9188,10 @@ function ChatTranscript({
9196
9188
  renderEmpty,
9197
9189
  className = ""
9198
9190
  }) {
9199
- const scroller = React41.useRef(null);
9200
- const stick = React41.useRef(true);
9201
- const [pill, setPill] = React41.useState(0);
9202
- const seen = React41.useRef(0);
9191
+ const scroller = React42.useRef(null);
9192
+ const stick = React42.useRef(true);
9193
+ const [pill, setPill] = React42.useState(0);
9194
+ const seen = React42.useRef(0);
9203
9195
  const toBottom = (smooth) => {
9204
9196
  const el = scroller.current;
9205
9197
  if (!el) return;
@@ -9218,7 +9210,7 @@ function ChatTranscript({
9218
9210
  seen.current = messages.length;
9219
9211
  }
9220
9212
  };
9221
- React41.useLayoutEffect(() => {
9213
+ React42.useLayoutEffect(() => {
9222
9214
  const el = scroller.current;
9223
9215
  if (!el) return;
9224
9216
  if (stick.current) {
@@ -9226,7 +9218,7 @@ function ChatTranscript({
9226
9218
  seen.current = messages.length;
9227
9219
  } else setPill(Math.max(0, messages.length - seen.current));
9228
9220
  }, [messages]);
9229
- React41.useEffect(() => {
9221
+ React42.useEffect(() => {
9230
9222
  const el = scroller.current;
9231
9223
  const inner = el && el.firstChild;
9232
9224
  if (!el || !inner || typeof ResizeObserver === "undefined") return;
@@ -9236,7 +9228,7 @@ function ChatTranscript({
9236
9228
  ro.observe(inner);
9237
9229
  return () => ro.disconnect();
9238
9230
  }, []);
9239
- const groups = React41.useMemo(() => groupMessages(messages), [messages]);
9231
+ const groups = React42.useMemo(() => groupMessages(messages), [messages]);
9240
9232
  const empty = !messages.length && status === "ready";
9241
9233
  return /* @__PURE__ */ jsxs68("div", { className: ["fdc-scroll", className].filter(Boolean).join(" "), ref: scroller, onScroll, children: [
9242
9234
  /* @__PURE__ */ jsxs68("div", { className: "fdc-log", role: "log", "aria-label": "Conversation", children: [
@@ -9260,7 +9252,7 @@ function ChatTranscript({
9260
9252
  const prev = groups[i - 1];
9261
9253
  const k = dayKey(g.messages[0].timestamp);
9262
9254
  const showDay = !!k && (!prev || dayKey(prev.messages[0].timestamp) !== k);
9263
- return /* @__PURE__ */ jsxs68(React41.Fragment, { children: [
9255
+ return /* @__PURE__ */ jsxs68(React42.Fragment, { children: [
9264
9256
  showDay ? /* @__PURE__ */ jsx73("div", { className: "fdc-day", children: /* @__PURE__ */ jsx73("span", { children: dayLabel(k) }) }) : null,
9265
9257
  /* @__PURE__ */ jsx73(ChatTurn, { group: g, ctx })
9266
9258
  ] }, g.key || i);
@@ -9277,7 +9269,7 @@ function ChatTranscript({
9277
9269
  var TranscriptKit = { groupMessages };
9278
9270
 
9279
9271
  // src/components/chat/ChatComposer.tsx
9280
- import * as React42 from "react";
9272
+ import * as React43 from "react";
9281
9273
  import { jsx as jsx74, jsxs as jsxs69 } from "react/jsx-runtime";
9282
9274
  function ChatComposer({
9283
9275
  onSubmit,
@@ -9305,17 +9297,17 @@ function ChatComposer({
9305
9297
  onReject,
9306
9298
  onOpenAttachment
9307
9299
  }) {
9308
- const [text, setText] = React42.useState(draft || "");
9309
- const [trigger, setTrigger] = React42.useState(null);
9310
- const [mentionItems, setMentionItems] = React42.useState([]);
9311
- const [listening, setListening] = React42.useState(false);
9312
- const [notice, setNotice] = React42.useState(null);
9313
- const editor = React42.useRef(null);
9314
- const wrap = React42.useRef(null);
9315
- const stopVoice = React42.useRef(null);
9300
+ const [text, setText] = React43.useState(draft || "");
9301
+ const [trigger, setTrigger] = React43.useState(null);
9302
+ const [mentionItems, setMentionItems] = React43.useState([]);
9303
+ const [listening, setListening] = React43.useState(false);
9304
+ const [notice, setNotice] = React43.useState(null);
9305
+ const editor = React43.useRef(null);
9306
+ const wrap = React43.useRef(null);
9307
+ const stopVoice = React43.useRef(null);
9316
9308
  const staged = useStagedFiles(fileUploadHandler, { onError: () => {
9317
9309
  } });
9318
- React42.useEffect(() => {
9310
+ React43.useEffect(() => {
9319
9311
  if (draft != null && draft !== text) setText(draft);
9320
9312
  }, [draft]);
9321
9313
  const change = (v) => {
@@ -9349,7 +9341,7 @@ function ChatComposer({
9349
9341
  e.preventDefault();
9350
9342
  staged.add(found.files);
9351
9343
  };
9352
- React42.useEffect(() => {
9344
+ React43.useEffect(() => {
9353
9345
  if (!trigger || trigger.type !== "mention" || !mentionSources) {
9354
9346
  setMentionItems([]);
9355
9347
  return;
@@ -9367,7 +9359,7 @@ function ChatComposer({
9367
9359
  const q = (trigger.query || "").toLowerCase();
9368
9360
  setMentionItems(mentionSources.filter((m) => !q || (m.label + " " + (m.description || "")).toLowerCase().includes(q)));
9369
9361
  }, [trigger, mentionSources]);
9370
- const slashItems = React42.useMemo(() => {
9362
+ const slashItems = React43.useMemo(() => {
9371
9363
  if (!trigger || trigger.type !== "slash" || !slashCommands) return [];
9372
9364
  const q = (trigger.query || "").toLowerCase();
9373
9365
  return slashCommands.filter((c) => !q || (c.id + " " + c.label + " " + (c.description || "")).toLowerCase().includes(q));
@@ -9541,12 +9533,12 @@ function ChatComposer({
9541
9533
  }
9542
9534
 
9543
9535
  // src/components/chat/ChatSessionBar.tsx
9544
- import * as React43 from "react";
9536
+ import * as React44 from "react";
9545
9537
  import { jsx as jsx75, jsxs as jsxs70 } from "react/jsx-runtime";
9546
9538
  var compact2 = meterFormats.compact;
9547
9539
  function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extras }) {
9548
- const [open, setOpen] = React43.useState(false);
9549
- const anchor = React43.useRef(null);
9540
+ const [open, setOpen] = React44.useState(false);
9541
+ const anchor = React44.useRef(null);
9550
9542
  const stats = sessionStats || null;
9551
9543
  const cu = contextUsage || null;
9552
9544
  const limits = usageLimits || null;
@@ -9561,7 +9553,7 @@ function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extr
9561
9553
  if (stats && stats.runningTasks) bits.push(stats.runningTasks + " running task" + (stats.runningTasks === 1 ? "" : "s"));
9562
9554
  if (cu) bits.push(pct + "% context");
9563
9555
  const expandable = !!(cu || limits);
9564
- return /* @__PURE__ */ jsxs70(React43.Fragment, { children: [
9556
+ return /* @__PURE__ */ jsxs70(React44.Fragment, { children: [
9565
9557
  /* @__PURE__ */ jsxs70("div", { className: "fdc-bar", children: [
9566
9558
  /* @__PURE__ */ jsxs70(
9567
9559
  "button",
@@ -9582,60 +9574,73 @@ function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extr
9582
9574
  ),
9583
9575
  extras
9584
9576
  ] }),
9585
- /* @__PURE__ */ jsx75(Popover, { open, anchorRef: anchor, placement: "top-start", onClose: () => setOpen(false), minWidth: 330, maxHeight: 460, padded: true, label: "Usage", children: /* @__PURE__ */ jsxs70("div", { className: "fdc-usage", children: [
9586
- cu ? /* @__PURE__ */ jsx75("section", { className: "fdc-usage-sec", children: /* @__PURE__ */ jsx75(
9587
- SegmentedMeter,
9588
- {
9589
- total,
9590
- segments: cu.segments,
9591
- label: cu.label || "Context window",
9592
- format: cu.format === "bytes" ? meterFormats.bytes : compact2,
9593
- height: 8,
9594
- legend: true,
9595
- remainderLabel: "Free"
9596
- }
9597
- ) }) : null,
9598
- limits && limits.length ? /* @__PURE__ */ jsxs70("section", { className: "fdc-usage-sec", children: [
9599
- /* @__PURE__ */ jsx75("h4", { className: "fdc-usage-h", children: "Usage limits" }),
9600
- /* @__PURE__ */ jsx75("div", { className: "fdc-usage-rows", children: limits.map((l) => /* @__PURE__ */ jsx75(
9601
- QuotaRow,
9602
- {
9603
- label: l.label,
9604
- percent: l.percent,
9605
- note: l.resetsAt ? "Resets " + formatRelative(l.resetsAt) : l.note
9606
- },
9607
- l.id
9608
- )) })
9609
- ] }) : null,
9610
- stats ? /* @__PURE__ */ jsxs70("section", { className: "fdc-usage-sec fdc-usage-stats", children: [
9611
- stats.elapsedMs ? /* @__PURE__ */ jsxs70("div", { children: [
9612
- /* @__PURE__ */ jsx75("span", { children: "Session" }),
9613
- /* @__PURE__ */ jsx75("b", { className: "fd-tabular", children: formatDuration(stats.elapsedMs) })
9614
- ] }) : null,
9615
- stats.tokens ? /* @__PURE__ */ jsxs70("div", { children: [
9616
- /* @__PURE__ */ jsx75("span", { children: "Tokens" }),
9617
- /* @__PURE__ */ jsx75("b", { className: "fd-tabular", children: stats.tokens.toLocaleString() })
9618
- ] }) : null,
9619
- stats.costUsd != null ? /* @__PURE__ */ jsxs70("div", { children: [
9620
- /* @__PURE__ */ jsx75("span", { children: "Cost" }),
9621
- /* @__PURE__ */ jsxs70("b", { className: "fd-tabular", children: [
9622
- "$",
9623
- Number(stats.costUsd).toFixed(3)
9624
- ] })
9625
- ] }) : null,
9626
- stats.turns ? /* @__PURE__ */ jsxs70("div", { children: [
9627
- /* @__PURE__ */ jsx75("span", { children: "Turns" }),
9628
- /* @__PURE__ */ jsx75("b", { className: "fd-tabular", children: stats.turns })
9629
- ] }) : null
9630
- ] }) : null,
9631
- onClear ? /* @__PURE__ */ jsx75("footer", { className: "fdc-usage-foot", children: /* @__PURE__ */ jsxs70("button", { type: "button", className: "fdc-usage-clear", onClick: () => {
9632
- setOpen(false);
9633
- onClear();
9634
- }, children: [
9635
- /* @__PURE__ */ jsx75("i", { className: "ph ph-trash", "aria-hidden": "true" }),
9636
- "Clear conversation"
9637
- ] }) }) : null
9638
- ] }) })
9577
+ /* @__PURE__ */ jsx75(
9578
+ Popover,
9579
+ {
9580
+ open,
9581
+ anchorRef: anchor,
9582
+ placement: "top-start",
9583
+ onClose: () => setOpen(false),
9584
+ minWidth: 330,
9585
+ maxHeight: 460,
9586
+ padded: true,
9587
+ label: "Usage",
9588
+ footer: onClear ? /* @__PURE__ */ jsxs70("button", { type: "button", className: "fdc-usage-clear", onClick: () => {
9589
+ setOpen(false);
9590
+ onClear();
9591
+ }, children: [
9592
+ /* @__PURE__ */ jsx75("i", { className: "ph ph-trash", "aria-hidden": "true" }),
9593
+ "Clear conversation"
9594
+ ] }) : void 0,
9595
+ children: /* @__PURE__ */ jsxs70("div", { className: "fdc-usage", children: [
9596
+ cu ? /* @__PURE__ */ jsx75("section", { className: "fdc-usage-sec", children: /* @__PURE__ */ jsx75(
9597
+ SegmentedMeter,
9598
+ {
9599
+ total,
9600
+ segments: cu.segments,
9601
+ label: cu.label || "Context window",
9602
+ format: cu.format === "bytes" ? meterFormats.bytes : compact2,
9603
+ height: 8,
9604
+ legend: true,
9605
+ remainderLabel: "Free"
9606
+ }
9607
+ ) }) : null,
9608
+ limits && limits.length ? /* @__PURE__ */ jsxs70("section", { className: "fdc-usage-sec", children: [
9609
+ /* @__PURE__ */ jsx75("h4", { className: "fdc-usage-h", children: "Usage limits" }),
9610
+ /* @__PURE__ */ jsx75("div", { className: "fdc-usage-rows", children: limits.map((l) => /* @__PURE__ */ jsx75(
9611
+ QuotaRow,
9612
+ {
9613
+ label: l.label,
9614
+ percent: l.percent,
9615
+ note: l.resetsAt ? "Resets " + formatRelative(l.resetsAt) : l.note
9616
+ },
9617
+ l.id
9618
+ )) })
9619
+ ] }) : null,
9620
+ stats ? /* @__PURE__ */ jsxs70("section", { className: "fdc-usage-sec fdc-usage-stats", children: [
9621
+ stats.elapsedMs ? /* @__PURE__ */ jsxs70("div", { children: [
9622
+ /* @__PURE__ */ jsx75("span", { children: "Session" }),
9623
+ /* @__PURE__ */ jsx75("b", { className: "fd-tabular", children: formatDuration(stats.elapsedMs) })
9624
+ ] }) : null,
9625
+ stats.tokens ? /* @__PURE__ */ jsxs70("div", { children: [
9626
+ /* @__PURE__ */ jsx75("span", { children: "Tokens" }),
9627
+ /* @__PURE__ */ jsx75("b", { className: "fd-tabular", children: stats.tokens.toLocaleString() })
9628
+ ] }) : null,
9629
+ stats.costUsd != null ? /* @__PURE__ */ jsxs70("div", { children: [
9630
+ /* @__PURE__ */ jsx75("span", { children: "Cost" }),
9631
+ /* @__PURE__ */ jsxs70("b", { className: "fd-tabular", children: [
9632
+ "$",
9633
+ Number(stats.costUsd).toFixed(3)
9634
+ ] })
9635
+ ] }) : null,
9636
+ stats.turns ? /* @__PURE__ */ jsxs70("div", { children: [
9637
+ /* @__PURE__ */ jsx75("span", { children: "Turns" }),
9638
+ /* @__PURE__ */ jsx75("b", { className: "fd-tabular", children: stats.turns })
9639
+ ] }) : null
9640
+ ] }) : null
9641
+ ] })
9642
+ }
9643
+ )
9639
9644
  ] });
9640
9645
  }
9641
9646
  function ModelControls({
@@ -9689,7 +9694,7 @@ function ModelControls({
9689
9694
  ] })
9690
9695
  });
9691
9696
  }
9692
- return /* @__PURE__ */ jsxs70(React43.Fragment, { children: [
9697
+ return /* @__PURE__ */ jsxs70(React44.Fragment, { children: [
9693
9698
  /* @__PURE__ */ jsx75(
9694
9699
  MenuButton,
9695
9700
  {
@@ -9806,11 +9811,11 @@ function AgentChatPanel({
9806
9811
  onFeedback,
9807
9812
  onEditMessage
9808
9813
  }) {
9809
- const [panelWidth, setPanelWidth] = React44.useState(width);
9810
- const [applied, setApplied] = React44.useState({});
9811
- const [draft, setDraft] = React44.useState("");
9812
- const dragging = React44.useRef(null);
9813
- React44.useEffect(() => setPanelWidth(width), [width]);
9814
+ const [panelWidth, setPanelWidth] = React45.useState(width);
9815
+ const [applied, setApplied] = React45.useState({});
9816
+ const [draft, setDraft] = React45.useState("");
9817
+ const dragging = React45.useRef(null);
9818
+ React45.useEffect(() => setPanelWidth(width), [width]);
9814
9819
  const engine = useChatEngine({
9815
9820
  contextType,
9816
9821
  contextId,
@@ -10014,7 +10019,7 @@ function AgentChatPanel({
10014
10019
  }
10015
10020
 
10016
10021
  // src/kits/query.ts
10017
- import * as React45 from "react";
10022
+ import * as React46 from "react";
10018
10023
  function eqFilter(get2) {
10019
10024
  return (row, value) => Array.isArray(value) ? value.includes(get2(row)) : get2(row) === value;
10020
10025
  }
@@ -10046,22 +10051,22 @@ function compare(a, b, dir) {
10046
10051
  var API = null;
10047
10052
  var PREFS = null;
10048
10053
  function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
10049
- const [query, setQuery] = React45.useState(() => {
10054
+ const [query, setQuery] = React46.useState(() => {
10050
10055
  const store = PREFS || window.PlannerPrefs;
10051
10056
  const saved = prefsKey && store ? store.getTable(prefsKey) : {};
10052
10057
  return { ...DEFAULTS, ...defaults || {}, ...saved.pageSize ? { pageSize: saved.pageSize } : {}, ...saved.sort ? { sort: saved.sort, dir: saved.dir || "desc" } : {} };
10053
10058
  });
10054
- const savePref = React45.useCallback((patch2) => {
10059
+ const savePref = React46.useCallback((patch2) => {
10055
10060
  const store = PREFS || window.PlannerPrefs;
10056
10061
  if (prefsKey && store) store.setTable(prefsKey, patch2);
10057
10062
  }, [prefsKey]);
10058
- const [res, setRes] = React45.useState(null);
10059
- const [loading, setLoading] = React45.useState(true);
10060
- const seq2 = React45.useRef(0);
10063
+ const [res, setRes] = React46.useState(null);
10064
+ const [loading, setLoading] = React46.useState(true);
10065
+ const seq2 = React46.useRef(0);
10061
10066
  const depKey = (deps || []).join("|");
10062
10067
  const paramKey = JSON.stringify(params || {});
10063
10068
  const queryKey = JSON.stringify(query);
10064
- React45.useEffect(() => {
10069
+ React46.useEffect(() => {
10065
10070
  const id = ++seq2.current;
10066
10071
  setLoading(true);
10067
10072
  const t = setTimeout(() => {
@@ -10435,6 +10440,7 @@ export {
10435
10440
  ModeSwitch,
10436
10441
  ModelControls,
10437
10442
  NumberInput,
10443
+ POPOVER_BODY_MIN,
10438
10444
  PacketCard,
10439
10445
  Pagination,
10440
10446
  PermissionDenied,