@flytedan/flytebot-design-system 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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,15 @@ 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
+ function measureLayer(el) {
92
+ if (!el) return { width: 0, height: 0 };
93
+ let hidden = 0;
94
+ el.querySelectorAll(SCROLL_REGIONS).forEach((r) => {
95
+ hidden += Math.max(0, r.scrollHeight - r.clientHeight);
96
+ });
97
+ return { width: el.offsetWidth, height: el.offsetHeight ? el.offsetHeight + hidden : 0 };
98
+ }
88
99
  function usePopoverPosition(open, anchorRef, opts) {
89
100
  const o = opts || {};
90
101
  const { side: wantSide, align } = parsePlacement(o.placement);
@@ -92,6 +103,7 @@ function usePopoverPosition(open, anchorRef, opts) {
92
103
  const posRef = React.useRef(null);
93
104
  const detachRef = React.useRef(o.onDetach);
94
105
  detachRef.current = o.onDetach;
106
+ const layerRef = o.layerRef;
95
107
  React.useLayoutEffect(() => {
96
108
  if (!open || !anchorRef || !anchorRef.current) {
97
109
  posRef.current = null;
@@ -103,20 +115,22 @@ function usePopoverPosition(open, anchorRef, opts) {
103
115
  if (!el) return;
104
116
  const r = el.getBoundingClientRect();
105
117
  const offset = o.offset == null ? 8 : o.offset;
106
- const estH = o.estHeight || 300;
118
+ const layer = measureLayer(layerRef ? layerRef.current : null);
119
+ const need = layer.height || Math.min(o.estHeight || 300, 180);
107
120
  const below = window.innerHeight - r.bottom - offset - MARGIN;
108
121
  const above = r.top - offset - MARGIN;
109
122
  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;
123
+ if (side === "bottom" && below < need && above > below) side = "top";
124
+ else if (side === "top" && above < need && below > above) side = "bottom";
125
+ const exact = o.matchWidth === true;
126
+ const width = exact ? r.width : Math.max(o.width || 0, o.minWidth || 0, o.matchWidth === "min" ? r.width : 0);
127
+ const w = exact || o.width ? width : Math.max(layer.width, width) || o.estWidth || 260;
114
128
  let left = align === "end" ? r.right - w : align === "center" ? r.left + r.width / 2 - w / 2 : r.left;
115
129
  left = Math.max(MARGIN, Math.min(left, window.innerWidth - w - MARGIN));
116
130
  const maxH = Math.max(140, side === "top" ? above : below);
117
131
  const next = {
118
132
  left,
119
- width: o.matchWidth ? r.width : void 0,
133
+ width: exact ? r.width : void 0,
120
134
  minWidth: o.minWidth,
121
135
  top: side === "bottom" ? r.bottom + offset : null,
122
136
  bottom: side === "top" ? window.innerHeight - r.top + offset : null,
@@ -152,6 +166,7 @@ var portal = (node) => {
152
166
  function Popover({
153
167
  open,
154
168
  anchorRef,
169
+ triggerRef,
155
170
  onClose,
156
171
  placement = "bottom-start",
157
172
  offset = 8,
@@ -167,6 +182,8 @@ function Popover({
167
182
  returnFocus = true,
168
183
  className = "",
169
184
  style,
185
+ header,
186
+ footer,
170
187
  children
171
188
  }) {
172
189
  const ref = React.useRef(null);
@@ -177,7 +194,8 @@ function Popover({
177
194
  width,
178
195
  minWidth,
179
196
  estHeight: maxHeight || 320,
180
- onDetach: onClose
197
+ onDetach: onClose,
198
+ layerRef: ref
181
199
  });
182
200
  const restore = React.useRef(null);
183
201
  React.useEffect(() => {
@@ -198,7 +216,8 @@ function Popover({
198
216
  if (!closeOnOutside) return;
199
217
  const target = e.target;
200
218
  if (ref.current && ref.current.contains(target)) return;
201
- if (anchorRef && anchorRef.current && anchorRef.current.contains(target)) return;
219
+ const trigger = (triggerRef || anchorRef).current;
220
+ if (trigger && trigger.contains(target)) return;
202
221
  if (e.target.closest && e.target.closest(".fd-pop")) return;
203
222
  onClose && onClose();
204
223
  };
@@ -214,23 +233,23 @@ function Popover({
214
233
  document.removeEventListener("pointerdown", away, true);
215
234
  document.removeEventListener("keydown", key);
216
235
  };
217
- }, [open, closeOnOutside, closeOnEscape, onClose, anchorRef]);
236
+ }, [open, closeOnOutside, closeOnEscape, onClose, anchorRef, triggerRef]);
218
237
  if (!open || !pos) return null;
219
238
  return portal(
220
- /* @__PURE__ */ jsx3(
239
+ /* @__PURE__ */ jsxs2(
221
240
  "div",
222
241
  {
223
242
  ref,
224
243
  role,
225
244
  "aria-label": label,
226
- className: ["fd-pop", pos.side === "top" ? "is-up" : "", padded ? "fd-pop-padded" : "", className].filter(Boolean).join(" "),
245
+ className: ["fd-pop", pos.side === "top" ? "is-up" : "", className].filter(Boolean).join(" "),
227
246
  style: {
228
247
  position: "fixed",
229
248
  left: pos.left,
230
249
  top: pos.top == null ? "auto" : pos.top,
231
250
  bottom: pos.bottom == null ? "auto" : pos.bottom,
232
- width: matchWidth ? pos.width : width,
233
- minWidth: minWidth || (matchWidth ? void 0 : 200),
251
+ width: matchWidth === true ? pos.width : width,
252
+ minWidth: matchWidth === "min" ? Math.max(pos.anchorWidth, minWidth || 0) : minWidth || (matchWidth ? void 0 : 200),
234
253
  // Size to content by default — cap only at real available room between the
235
254
  // anchor and the viewport edge (pos.maxH, computed by usePopoverPosition).
236
255
  // A caller-supplied `maxHeight` narrows that further (e.g. a long menu that
@@ -243,7 +262,11 @@ function Popover({
243
262
  zIndex: 140,
244
263
  ...style
245
264
  },
246
- children
265
+ children: [
266
+ header != null ? /* @__PURE__ */ jsx3("div", { className: "fd-pop-header", children: header }) : null,
267
+ /* @__PURE__ */ jsx3("div", { className: ["fd-pop-body", padded ? "fd-pop-padded" : ""].filter(Boolean).join(" "), children }),
268
+ footer != null ? /* @__PURE__ */ jsx3("div", { className: "fd-pop-footer", children: footer }) : null
269
+ ]
247
270
  }
248
271
  )
249
272
  );
@@ -318,77 +341,79 @@ function Menu({ items = [], onSelect, onClose, autoFocus = true, className = "",
318
341
  };
319
342
  const sub = openSub != null ? items[openSub] : null;
320
343
  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(
344
+ /* @__PURE__ */ jsxs2("div", { className: "fd-menu-frame", children: [
345
+ header != null ? /* @__PURE__ */ jsx3("div", { className: "fd-menu-header", children: header }) : null,
346
+ /* @__PURE__ */ jsx3(
347
+ "div",
348
+ {
349
+ ref: listRef,
350
+ tabIndex: -1,
351
+ role: "menu",
352
+ onKeyDown: onKey,
353
+ className: ["fd-pop-list", "fd-menu", className].filter(Boolean).join(" "),
354
+ children: items.map((it, i) => {
355
+ if (!it) return null;
356
+ if (it.kind === "separator") return /* @__PURE__ */ jsx3("div", { className: "fd-menu-sep", role: "separator" }, "s" + i);
357
+ if (it.kind === "section") return /* @__PURE__ */ jsx3("div", { className: "fd-pop-group", role: "presentation", children: it.label }, "h" + i);
358
+ if (it.kind === "custom") return /* @__PURE__ */ jsx3("div", { className: "fd-menu-custom", children: it.render ? it.render() : null }, "c" + i);
359
+ const on = !!it.checked;
360
+ const row = /* @__PURE__ */ jsxs2(
371
361
  "button",
372
362
  {
373
363
  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
- }
364
+ "data-i": i,
365
+ role: it.checked != null ? "menuitemradio" : "menuitem",
366
+ "aria-checked": it.checked != null ? on : void 0,
367
+ "aria-haspopup": it.submenu ? "menu" : void 0,
368
+ "aria-expanded": it.submenu ? openSub === i : void 0,
369
+ "aria-disabled": it.disabled || void 0,
370
+ className: ["fd-opt", "fd-menu-item", i === active ? "is-active" : "", on ? "is-selected" : ""].filter(Boolean).join(" "),
371
+ onMouseEnter: () => {
372
+ setActive(i);
373
+ if (openSub != null && openSub !== i) setOpenSub(null);
383
374
  },
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,
375
+ onClick: () => choose(it, i),
376
+ children: [
377
+ it.icon ? /* @__PURE__ */ jsx3("span", { className: "fd-menu-icon", children: /* @__PURE__ */ jsx3("i", { className: "ph ph-" + it.icon, "aria-hidden": "true" }) }) : null,
378
+ /* @__PURE__ */ jsxs2("span", { className: "fd-menu-text", children: [
379
+ /* @__PURE__ */ jsx3("span", { className: "fd-opt-label", children: it.label }),
380
+ it.description ? /* @__PURE__ */ jsx3("span", { className: "fd-opt-desc", children: it.description }) : null
381
+ ] }),
382
+ it.meta ? /* @__PURE__ */ jsx3("span", { className: "fd-opt-meta", children: it.meta }) : null,
383
+ it.shortcut ? /* @__PURE__ */ jsx3("span", { className: "fd-menu-shortcut", "aria-hidden": "true", children: it.shortcut }) : null,
384
+ it.submenu ? /* @__PURE__ */ jsx3("i", { className: "ph ph-caret-right fd-menu-more", "aria-hidden": "true" }) : null,
385
+ it.checked != null ? /* @__PURE__ */ jsx3("span", { className: "fd-opt-check", children: on ? /* @__PURE__ */ jsx3("i", { className: "ph ph-check", "aria-hidden": "true" }) : null }) : null
386
+ ]
387
+ },
388
+ it.id || it.label || i
389
+ );
390
+ const ta = it.trailingAction;
391
+ if (!ta) return row;
392
+ return /* @__PURE__ */ jsxs2("div", { className: "fd-menu-row", onMouseEnter: () => setActive(i), children: [
393
+ row,
394
+ /* @__PURE__ */ jsx3(
395
+ "button",
396
+ {
397
+ type: "button",
398
+ className: ["fd-menu-row-act", ta.danger ? "is-danger" : ""].filter(Boolean).join(" "),
399
+ "aria-label": ta.label,
400
+ title: ta.label,
401
+ onClick: (e) => {
402
+ e.stopPropagation();
403
+ ta.onSelect && ta.onSelect(it);
404
+ if (ta.closeOnSelect !== false) {
405
+ onClose && onClose();
406
+ }
407
+ },
408
+ children: /* @__PURE__ */ jsx3("i", { className: "ph ph-" + (ta.icon || "trash"), "aria-hidden": "true" })
409
+ }
410
+ )
411
+ ] }, it.id || it.label || i);
412
+ })
413
+ }
414
+ ),
415
+ footer != null ? /* @__PURE__ */ jsx3("div", { className: "fd-menu-footer", children: footer }) : null
416
+ ] }),
392
417
  sub && sub.submenu ? /* @__PURE__ */ jsx3(
393
418
  Popover,
394
419
  {
@@ -1016,20 +1041,123 @@ function Toast({ title, children, tone = "success", onUndo, onDismiss, className
1016
1041
  // src/components/feedback/Tooltip.tsx
1017
1042
  import * as React6 from "react";
1018
1043
  import { jsx as jsx24, jsxs as jsxs19 } from "react/jsx-runtime";
1044
+ var CLOSE_DELAY_MS = 120;
1019
1045
  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%)" };
1046
+ const anchorRef = React6.useRef(null);
1047
+ const bubbleRef = React6.useRef(null);
1048
+ const [reason, setReason] = React6.useState(null);
1049
+ const closeTimer = React6.useRef(null);
1050
+ const id = React6.useId();
1051
+ const descId = id + "-desc";
1052
+ const open = reason != null;
1053
+ const cancelClose = React6.useCallback(() => {
1054
+ if (closeTimer.current) {
1055
+ clearTimeout(closeTimer.current);
1056
+ closeTimer.current = null;
1057
+ }
1058
+ }, []);
1059
+ const show = (why) => {
1060
+ cancelClose();
1061
+ setReason(why);
1062
+ };
1063
+ const hide = React6.useCallback(() => {
1064
+ cancelClose();
1065
+ setReason(null);
1066
+ }, [cancelClose]);
1067
+ const hover = () => {
1068
+ cancelClose();
1069
+ setReason((r) => r || "hover");
1070
+ };
1071
+ const hideSoon = () => {
1072
+ cancelClose();
1073
+ closeTimer.current = setTimeout(() => {
1074
+ closeTimer.current = null;
1075
+ setReason((r) => r === "hover" ? null : r);
1076
+ }, CLOSE_DELAY_MS);
1077
+ };
1078
+ React6.useEffect(() => cancelClose, [cancelClose]);
1079
+ const pos = usePopoverPosition(open, anchorRef, {
1080
+ placement: placement === "bottom" ? "bottom-center" : "top-center",
1081
+ offset: 8,
1082
+ estHeight: 40,
1083
+ estWidth: 160,
1084
+ layerRef: bubbleRef,
1085
+ onDetach: hide
1086
+ });
1087
+ React6.useEffect(() => {
1088
+ if (!open) return;
1089
+ const key = (e) => {
1090
+ if (e.key === "Escape") hide();
1091
+ };
1092
+ const away = (e) => {
1093
+ const t = e.target;
1094
+ if (anchorRef.current && anchorRef.current.contains(t)) return;
1095
+ if (bubbleRef.current && bubbleRef.current.contains(t)) return;
1096
+ hide();
1097
+ };
1098
+ document.addEventListener("keydown", key);
1099
+ document.addEventListener("pointerdown", away, true);
1100
+ return () => {
1101
+ document.removeEventListener("keydown", key);
1102
+ document.removeEventListener("pointerdown", away, true);
1103
+ };
1104
+ }, [open, hide]);
1105
+ const isMouse = (e) => e.pointerType === "mouse";
1106
+ const onFocus = (e) => {
1107
+ const el = e.target;
1108
+ let keyboard = true;
1109
+ try {
1110
+ keyboard = el.matches(":focus-visible");
1111
+ } catch {
1112
+ }
1113
+ if (keyboard) show("focus");
1114
+ };
1115
+ const described = React6.isValidElement(children) ? React6.cloneElement(children, {
1116
+ "aria-describedby": [children.props["aria-describedby"], descId].filter(Boolean).join(" ")
1117
+ }) : children;
1022
1118
  return /* @__PURE__ */ jsxs19(
1023
1119
  "span",
1024
1120
  {
1025
- style: { position: "relative", display: "inline-flex" },
1026
- onMouseEnter: () => setShow(true),
1027
- onMouseLeave: () => setShow(false),
1028
- onFocus: () => setShow(true),
1029
- onBlur: () => setShow(false),
1121
+ ref: anchorRef,
1122
+ className: "fd-tooltip-anchor",
1123
+ "aria-describedby": React6.isValidElement(children) ? void 0 : descId,
1124
+ onPointerEnter: (e) => {
1125
+ if (isMouse(e)) hover();
1126
+ },
1127
+ onPointerLeave: (e) => {
1128
+ if (isMouse(e)) hideSoon();
1129
+ },
1130
+ onPointerUp: (e) => {
1131
+ if (!isMouse(e)) {
1132
+ if (reason === "tap") hide();
1133
+ else show("tap");
1134
+ }
1135
+ },
1136
+ onFocus,
1137
+ onBlur: () => {
1138
+ if (reason === "focus") hide();
1139
+ },
1030
1140
  children: [
1031
- children,
1032
- show ? /* @__PURE__ */ jsx24("span", { className: ["fd-tooltip", className].filter(Boolean).join(" "), role: "tooltip", style: pos, children: label }) : null
1141
+ described,
1142
+ /* @__PURE__ */ jsx24("span", { id: descId, hidden: true, children: label }),
1143
+ open && pos ? portal(
1144
+ /* @__PURE__ */ jsx24(
1145
+ "span",
1146
+ {
1147
+ ref: bubbleRef,
1148
+ role: "tooltip",
1149
+ className: ["fd-tooltip", pos.side === "bottom" ? "is-below" : "", className].filter(Boolean).join(" "),
1150
+ style: { left: pos.left, top: pos.top == null ? "auto" : pos.top, bottom: pos.bottom == null ? "auto" : pos.bottom },
1151
+ onPointerEnter: (e) => {
1152
+ if (isMouse(e)) cancelClose();
1153
+ },
1154
+ onPointerLeave: (e) => {
1155
+ if (isMouse(e)) hideSoon();
1156
+ },
1157
+ children: label
1158
+ }
1159
+ )
1160
+ ) : null
1033
1161
  ]
1034
1162
  }
1035
1163
  );
@@ -2663,6 +2791,7 @@ function VirtualList({
2663
2791
  }
2664
2792
 
2665
2793
  // src/components/data/EntityRow.tsx
2794
+ import * as React17 from "react";
2666
2795
  import { jsx as jsx39, jsxs as jsxs35 } from "react/jsx-runtime";
2667
2796
  var ENTITY_ROW_METRIC_WIDTH = 74;
2668
2797
  var METRIC_TONE_COLOR = {
@@ -2671,20 +2800,20 @@ var METRIC_TONE_COLOR = {
2671
2800
  danger: "var(--danger-text)"
2672
2801
  };
2673
2802
  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
- )) });
2803
+ return /* @__PURE__ */ jsx39("span", { className: ["fd-erow-metrics", className].filter(Boolean).join(" "), style, children: metrics.map((m, i) => {
2804
+ const key = m.id || m.label || i;
2805
+ const className2 = ["fd-erow-metric", m.tone && m.tone !== "default" ? "is-" + m.tone : "", m.tooltip != null ? "is-described" : ""].filter(Boolean).join(" ");
2806
+ const cellStyle = { width: (m.width || ENTITY_ROW_METRIC_WIDTH) + "px", color: METRIC_TONE_COLOR[m.tone || "default"] };
2807
+ const content = /* @__PURE__ */ jsxs35(React17.Fragment, { children: [
2808
+ m.icon ? /* @__PURE__ */ jsx39("i", { className: "ph ph-" + m.icon, "aria-hidden": "true" }) : null,
2809
+ /* @__PURE__ */ jsx39("span", { className: "fd-erow-metric-value fd-tabular", children: m.value }),
2810
+ /* @__PURE__ */ jsx39("span", { className: "fd-sr", children: " " + m.label })
2811
+ ] });
2812
+ if (m.tooltip == null) {
2813
+ return /* @__PURE__ */ jsx39("span", { className: className2, style: cellStyle, title: m.label, children: content }, key);
2814
+ }
2815
+ return /* @__PURE__ */ jsx39(Tooltip, { label: m.tooltip, children: /* @__PURE__ */ jsx39("button", { type: "button", className: className2, style: cellStyle, children: content }) }, key);
2816
+ }) });
2688
2817
  }
2689
2818
  function EntityRow({
2690
2819
  title,
@@ -2734,8 +2863,10 @@ function EntityRow({
2734
2863
  icon: action.icon,
2735
2864
  label: action.label,
2736
2865
  size: "sm",
2737
- onClick: action.onClick,
2738
- style: { color: action.tone === "add" ? "var(--ok-text)" : action.tone === "remove" ? "var(--danger-text)" : void 0 }
2866
+ variant: "solid",
2867
+ tone: action.tone === "add" ? "ok" : action.tone === "remove" ? "danger" : "neutral",
2868
+ className: "fd-erow-action",
2869
+ onClick: action.onClick
2739
2870
  }
2740
2871
  ) : null
2741
2872
  ]
@@ -2744,14 +2875,14 @@ function EntityRow({
2744
2875
  }
2745
2876
 
2746
2877
  // src/components/data/TransferList.tsx
2747
- import * as React18 from "react";
2878
+ import * as React19 from "react";
2748
2879
  import { createPortal as createPortal4 } from "react-dom";
2749
2880
 
2750
2881
  // src/components/forms/field.tsx
2751
- import * as React17 from "react";
2882
+ import * as React18 from "react";
2752
2883
  import { jsx as jsx40, jsxs as jsxs36 } from "react/jsx-runtime";
2753
2884
  function useFieldId(id) {
2754
- const generated = React17.useId();
2885
+ const generated = React18.useId();
2755
2886
  return id || generated;
2756
2887
  }
2757
2888
  function FieldLabel({ htmlFor, id, required = false, onClick, children }) {
@@ -2792,10 +2923,9 @@ function Input({
2792
2923
  numeric ? "fd-input-num" : "",
2793
2924
  error ? "fd-input-error" : "",
2794
2925
  disabled ? "fd-input-disabled" : "",
2795
- size === "lg" ? "fd-input-lg" : "",
2796
- className
2926
+ size === "lg" ? "fd-input-lg" : ""
2797
2927
  ].filter(Boolean).join(" ");
2798
- return /* @__PURE__ */ jsxs37("div", { className: "fd-field", style, children: [
2928
+ return /* @__PURE__ */ jsxs37("div", { className: ["fd-field", className].filter(Boolean).join(" "), style, children: [
2799
2929
  label ? /* @__PURE__ */ jsx41(FieldLabel, { htmlFor: fieldId, required, children: label }) : null,
2800
2930
  /* @__PURE__ */ jsxs37("div", { className: box, children: [
2801
2931
  icon ? /* @__PURE__ */ jsx41("span", { className: "fd-input-icon", children: /* @__PURE__ */ jsx41("i", { className: "ph ph-" + icon, "aria-hidden": "true" }) }) : null,
@@ -2870,12 +3000,12 @@ function TransferList({
2870
3000
  listHeight = 440,
2871
3001
  className = ""
2872
3002
  }) {
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) => {
3003
+ const [drag, setDrag] = React19.useState(null);
3004
+ const [dragOverSide, setDragOverSide] = React19.useState(null);
3005
+ const dragRef = React19.useRef(null);
3006
+ const armedRef = React19.useRef(null);
3007
+ const sideRefs = React19.useRef({ left: null, right: null });
3008
+ const hitTestSide = React19.useCallback((x, y) => {
2879
3009
  for (const side of ["left", "right"]) {
2880
3010
  const el = sideRefs.current[side];
2881
3011
  if (!el) continue;
@@ -2884,7 +3014,7 @@ function TransferList({
2884
3014
  }
2885
3015
  return null;
2886
3016
  }, []);
2887
- const findItem = React18.useCallback(
3017
+ const findItem = React19.useCallback(
2888
3018
  (side, key) => (side === "left" ? left.items : right.items).find((it) => keyOf(it) === key),
2889
3019
  [left.items, right.items, keyOf]
2890
3020
  );
@@ -2906,7 +3036,7 @@ function TransferList({
2906
3036
  height: rect.height
2907
3037
  };
2908
3038
  };
2909
- React18.useEffect(() => {
3039
+ React19.useEffect(() => {
2910
3040
  const onMovePointer = (e) => {
2911
3041
  const armed = armedRef.current;
2912
3042
  if (!armed || e.pointerId !== armed.pointerId) return;
@@ -2959,7 +3089,7 @@ function TransferList({
2959
3089
  window.removeEventListener("pointercancel", endDrag);
2960
3090
  };
2961
3091
  }, [hitTestSide, findItem, onMove]);
2962
- React18.useEffect(() => {
3092
+ React19.useEffect(() => {
2963
3093
  if (!drag || typeof document === "undefined") return;
2964
3094
  const prev = document.body.style.userSelect;
2965
3095
  document.body.style.userSelect = "none";
@@ -3071,11 +3201,11 @@ function TransferList({
3071
3201
  }
3072
3202
 
3073
3203
  // src/components/forms/Checkbox.tsx
3074
- import * as React19 from "react";
3204
+ import * as React20 from "react";
3075
3205
  import { jsx as jsx44, jsxs as jsxs39 } from "react/jsx-runtime";
3076
3206
  function Checkbox({ label, description, card = false, indeterminate = false, className = "", ...rest }) {
3077
- const ref = React19.useRef(null);
3078
- React19.useEffect(() => {
3207
+ const ref = React20.useRef(null);
3208
+ React20.useEffect(() => {
3079
3209
  if (ref.current) ref.current.indeterminate = indeterminate;
3080
3210
  }, [indeterminate]);
3081
3211
  return /* @__PURE__ */ jsxs39("label", { className: ["fd-choice", card ? "fd-choice-card" : "", className].filter(Boolean).join(" "), children: [
@@ -3122,8 +3252,8 @@ function Switch({ label, description, className = "", ...rest }) {
3122
3252
  import { jsx as jsx47, jsxs as jsxs42 } from "react/jsx-runtime";
3123
3253
  function Textarea({ label, help, error, required = false, rows = 4, disabled = false, id, className = "", style, ...rest }) {
3124
3254
  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: [
3255
+ const box = ["fd-input", "fd-input-textarea", error ? "fd-input-error" : "", disabled ? "fd-input-disabled" : ""].filter(Boolean).join(" ");
3256
+ return /* @__PURE__ */ jsxs42("div", { className: ["fd-field", className].filter(Boolean).join(" "), style, children: [
3127
3257
  label ? /* @__PURE__ */ jsx47(FieldLabel, { htmlFor: fieldId, required, children: label }) : null,
3128
3258
  /* @__PURE__ */ jsx47("div", { className: box, children: /* @__PURE__ */ jsx47("textarea", { id: fieldId, rows, disabled, "aria-invalid": error ? "true" : void 0, ...rest }) }),
3129
3259
  error ? /* @__PURE__ */ jsxs42("span", { className: "fd-field-error", children: [
@@ -3134,7 +3264,7 @@ function Textarea({ label, help, error, required = false, rows = 4, disabled = f
3134
3264
  }
3135
3265
 
3136
3266
  // src/components/forms/NumberInput.tsx
3137
- import * as React20 from "react";
3267
+ import * as React21 from "react";
3138
3268
  import { jsx as jsx48, jsxs as jsxs43 } from "react/jsx-runtime";
3139
3269
  function NumberInput({
3140
3270
  label,
@@ -3162,10 +3292,10 @@ function NumberInput({
3162
3292
  const n = Number(String(v == null ? "" : v).replace(/[^0-9.-]/g, ""));
3163
3293
  return isNaN(n) ? null : n;
3164
3294
  };
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(() => {
3295
+ const [text, setText] = React21.useState(value == null || value === "" ? "" : String(value));
3296
+ const [editing, setEditing] = React21.useState(false);
3297
+ const timer = React21.useRef(null);
3298
+ React21.useEffect(() => {
3169
3299
  if (!editing) setText(value == null || value === "" ? "" : String(value));
3170
3300
  }, [value, editing]);
3171
3301
  const clamp = (n) => Math.min(max, Math.max(min, n));
@@ -3189,7 +3319,7 @@ function NumberInput({
3189
3319
  const release = () => {
3190
3320
  if (timer.current) clearTimeout(timer.current);
3191
3321
  };
3192
- React20.useEffect(() => () => {
3322
+ React21.useEffect(() => () => {
3193
3323
  if (timer.current) clearTimeout(timer.current);
3194
3324
  }, []);
3195
3325
  const shown = editing ? text : (() => {
@@ -3276,52 +3406,9 @@ function NumberInput({
3276
3406
  }
3277
3407
 
3278
3408
  // src/components/forms/Select.tsx
3279
- import * as React21 from "react";
3280
- import { createPortal as createPortal5 } from "react-dom";
3409
+ import * as React22 from "react";
3281
3410
  import { jsx as jsx49, jsxs as jsxs44 } from "react/jsx-runtime";
3282
3411
  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
3412
  function Select({
3326
3413
  label,
3327
3414
  help,
@@ -3346,17 +3433,15 @@ function Select({
3346
3433
  const isOn = (v) => multiple ? vals.includes(v) : v === value;
3347
3434
  const hasSearch = searchable === void 0 ? opts.length > 8 : searchable;
3348
3435
  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 });
3436
+ const [open, setOpen] = React22.useState(false);
3437
+ const [q, setQ] = React22.useState("");
3438
+ const [active, setActive] = React22.useState(-1);
3439
+ const rootRef = React22.useRef(null);
3440
+ const boxRef = React22.useRef(null);
3441
+ const listRef = React22.useRef(null);
3442
+ const typeBuf = React22.useRef({ s: "", t: 0 });
3357
3443
  const selected = multiple ? null : opts.find((o) => o.value === value);
3358
3444
  const chosen = multiple ? opts.filter((o) => vals.includes(o.value)) : [];
3359
- const pos = usePopPos(open, boxRef, hasSearch ? 390 : 340, 260);
3360
3445
  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
3446
  const visible = q ? opts.filter((o) => (o.label + " " + (o.description || "") + " " + (o.group || "")).toLowerCase().includes(q.toLowerCase())) : opts;
3362
3447
  const fire = (v) => {
@@ -3379,17 +3464,7 @@ function Select({
3379
3464
  }
3380
3465
  setOpen(!open);
3381
3466
  };
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(() => {
3467
+ React22.useEffect(() => {
3393
3468
  if (!open || active < 0 || !listRef.current) return;
3394
3469
  const el = listRef.current.querySelector('[data-i="' + active + '"]');
3395
3470
  if (el) {
@@ -3480,94 +3555,96 @@ function Select({
3480
3555
  ) : null,
3481
3556
  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
3557
  ] }),
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,
3558
+ /* @__PURE__ */ jsx49(
3559
+ Popover,
3560
+ {
3561
+ open,
3562
+ anchorRef: boxRef,
3563
+ triggerRef: rootRef,
3564
+ onClose: () => setOpen(false),
3565
+ placement: "bottom-start",
3566
+ offset: 6,
3567
+ matchWidth: "min",
3568
+ minWidth: 260,
3569
+ role: "presentation",
3570
+ style: { maxWidth: 380, zIndex: 130 },
3571
+ header: hasSearch ? /* @__PURE__ */ jsxs44("div", { className: "fd-pop-search", children: [
3572
+ /* @__PURE__ */ jsx49("i", { className: "ph ph-magnifying-glass", style: { color: "var(--text-muted)", fontSize: 14 } }),
3573
+ /* @__PURE__ */ jsx49(
3574
+ "input",
3575
+ {
3576
+ autoFocus: true,
3577
+ placeholder: "Search\u2026",
3578
+ value: q,
3579
+ onKeyDown: onKey,
3580
+ onChange: (e) => {
3581
+ setQ(e.target.value);
3582
+ setActive(0);
3583
+ }
3584
+ }
3585
+ ),
3586
+ q ? /* @__PURE__ */ jsx49("span", { className: "fd-body-sm fd-muted", children: visible.length }) : null
3587
+ ] }) : void 0,
3588
+ footer: multiple ? /* @__PURE__ */ jsxs44(React22.Fragment, { children: [
3589
+ /* @__PURE__ */ jsx49(
3590
+ "button",
3591
+ {
3592
+ type: "button",
3593
+ onClick: () => fire(visible.filter((o) => !o.disabled).map((o) => o.value)),
3594
+ style: { all: "unset", cursor: "pointer", fontSize: "var(--body-sm-size)", fontWeight: 600, color: "var(--brand)" },
3595
+ children: "Select all"
3596
+ }
3597
+ ),
3598
+ /* @__PURE__ */ jsx49("span", { style: { flex: 1 } }),
3599
+ /* @__PURE__ */ jsxs44("span", { className: "fd-body-sm fd-muted", children: [
3600
+ vals.length,
3601
+ " of ",
3602
+ opts.length
3603
+ ] }),
3604
+ /* @__PURE__ */ jsx49(
3605
+ "button",
3606
+ {
3607
+ type: "button",
3608
+ disabled: !vals.length,
3609
+ onClick: () => fire([]),
3610
+ style: { all: "unset", cursor: vals.length ? "pointer" : "default", fontSize: "var(--body-sm-size)", fontWeight: 600, color: vals.length ? "var(--text-2)" : "var(--text-muted)" },
3611
+ children: "Clear"
3612
+ }
3613
+ )
3614
+ ] }) : void 0,
3615
+ 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: [
3616
+ 'Nothing matches "',
3617
+ q,
3618
+ '".'
3619
+ ] }) : groups.map((grp) => /* @__PURE__ */ jsxs44(React22.Fragment, { children: [
3620
+ grp.g ? /* @__PURE__ */ jsx49("div", { className: "fd-pop-group", children: grp.g }) : null,
3621
+ grp.items.map(({ o, i }) => /* @__PURE__ */ jsxs44(
3622
+ "button",
3623
+ {
3624
+ type: "button",
3625
+ "data-i": i,
3626
+ role: "option",
3627
+ "aria-selected": isOn(o.value),
3628
+ "aria-disabled": o.disabled || void 0,
3629
+ className: ["fd-opt", i === active ? "is-active" : "", isOn(o.value) ? "is-selected" : ""].filter(Boolean).join(" "),
3630
+ onMouseEnter: () => setActive(i),
3631
+ onClick: () => pick(o),
3632
+ children: [
3633
+ 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,
3634
+ o.icon ? /* @__PURE__ */ jsx49("span", { className: "fd-opt-icon", children: /* @__PURE__ */ jsx49("i", { className: "ph ph-" + o.icon }) }) : null,
3635
+ /* @__PURE__ */ jsxs44("span", { style: { flex: 1, minWidth: 0 }, children: [
3636
+ /* @__PURE__ */ jsx49("span", { className: "fd-opt-label", children: o.label }),
3637
+ o.description ? /* @__PURE__ */ jsx49("span", { className: "fd-opt-desc", children: o.description }) : null
3638
+ ] }),
3639
+ o.meta ? /* @__PURE__ */ jsx49("span", { className: "fd-opt-meta", children: o.meta }) : null,
3640
+ multiple ? null : /* @__PURE__ */ jsx49("span", { className: "fd-opt-check", children: o.value === value ? /* @__PURE__ */ jsx49("i", { className: "ph ph-check" }) : null })
3641
+ ]
3642
+ },
3643
+ String(o.value)
3644
+ ))
3645
+ ] }, grp.g || "_")) })
3646
+ }
3647
+ ),
3571
3648
  error ? /* @__PURE__ */ jsxs44("span", { className: "fd-field-error", children: [
3572
3649
  /* @__PURE__ */ jsx49("i", { className: "ph ph-warning-circle", "aria-hidden": "true" }),
3573
3650
  error
@@ -3576,8 +3653,7 @@ function Select({
3576
3653
  }
3577
3654
 
3578
3655
  // src/components/forms/DatePicker.tsx
3579
- import * as React22 from "react";
3580
- import { createPortal as createPortal6 } from "react-dom";
3656
+ import * as React23 from "react";
3581
3657
  import { jsx as jsx50, jsxs as jsxs45 } from "react/jsx-runtime";
3582
3658
  var MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
3583
3659
  var DOW = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
@@ -3591,56 +3667,14 @@ var fmt = (s) => {
3591
3667
  const d = parse(s);
3592
3668
  return d ? MONTHS[d.getMonth()].slice(0, 3) + " " + d.getDate() + ", " + d.getFullYear() : "";
3593
3669
  };
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
3670
  function Calendar({ value, range = false, onPick, initialMonth, months = 1 }) {
3637
3671
  const today = /* @__PURE__ */ new Date();
3638
3672
  const sel = range ? value || {} : { start: value, end: value };
3639
3673
  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);
3674
+ const [vy, setVy] = React23.useState(anchor.getFullYear());
3675
+ const [vm, setVm] = React23.useState(anchor.getMonth());
3676
+ const [mode2, setMode] = React23.useState("days");
3677
+ const [hover, setHover] = React23.useState(null);
3644
3678
  const s = parse(sel.start), e = parse(sel.end);
3645
3679
  const hoverEnd = range && s && !e && hover ? parse(hover) : null;
3646
3680
  const inRange = (d) => {
@@ -3756,28 +3790,9 @@ function Calendar({ value, range = false, onPick, initialMonth, months = 1 }) {
3756
3790
  }
3757
3791
  function DatePicker({ label, help, error, required = false, disabled = false, range = false, value, onChange, placeholder, id, className = "", style, ...rest }) {
3758
3792
  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]);
3793
+ const [open, setOpen] = React23.useState(false);
3794
+ const rootRef = React23.useRef(null);
3795
+ const boxRef = React23.useRef(null);
3781
3796
  const display = range ? value && value.start ? fmt(value.start) + (value.end ? " \u2192 " + fmt(value.end) : " \u2192 \u2026") : "" : fmt(value);
3782
3797
  const toggle = () => {
3783
3798
  if (!disabled) setOpen(!open);
@@ -3806,21 +3821,31 @@ function DatePicker({ label, help, error, required = false, disabled = false, ra
3806
3821
  ),
3807
3822
  /* @__PURE__ */ jsx50("span", { className: "fd-select-caret", children: /* @__PURE__ */ jsx50("i", { className: "ph ph-caret-down", "aria-hidden": "true" }) })
3808
3823
  ] }),
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);
3824
+ /* @__PURE__ */ jsx50(
3825
+ Popover,
3826
+ {
3827
+ open,
3828
+ anchorRef: boxRef,
3829
+ triggerRef: rootRef,
3830
+ onClose: () => setOpen(false),
3831
+ placement: "bottom-start",
3832
+ offset: 6,
3833
+ role: "presentation",
3834
+ style: { width: "max-content", zIndex: 130 },
3835
+ children: /* @__PURE__ */ jsx50(
3836
+ Calendar,
3837
+ {
3838
+ range,
3839
+ months: range ? 2 : 1,
3840
+ value,
3841
+ onPick: (v) => {
3842
+ onChange && onChange({ target: { value: v } });
3843
+ if (!range || v.start && v.end) setOpen(false);
3844
+ }
3819
3845
  }
3820
- }
3821
- ) }),
3822
- document.body
3823
- ) : null,
3846
+ )
3847
+ }
3848
+ ),
3824
3849
  error ? /* @__PURE__ */ jsxs45("span", { className: "fd-field-error", children: [
3825
3850
  /* @__PURE__ */ jsx50("i", { className: "ph ph-warning-circle", "aria-hidden": "true" }),
3826
3851
  error
@@ -3829,61 +3854,18 @@ function DatePicker({ label, help, error, required = false, disabled = false, ra
3829
3854
  }
3830
3855
 
3831
3856
  // src/components/forms/TimePicker.tsx
3832
- import * as React23 from "react";
3833
- import { createPortal as createPortal7 } from "react-dom";
3857
+ import * as React24 from "react";
3834
3858
  import { jsx as jsx51, jsxs as jsxs46 } from "react/jsx-runtime";
3835
3859
  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
3860
  function ClockFace({ value = "09:00", onChange }) {
3879
3861
  let [h24, m] = value.split(":").map(Number);
3880
3862
  if (isNaN(h24)) h24 = 9;
3881
3863
  if (isNaN(m)) m = 0;
3882
3864
  const pm = h24 >= 12;
3883
3865
  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);
3866
+ const [mode2, setMode] = React24.useState("h");
3867
+ const faceRef = React24.useRef(null);
3868
+ const dragging = React24.useRef(false);
3887
3869
  const set = (h, mm, isPm) => onChange((isPm ? h % 12 + 12 : h % 12) + ":" + pad(mm));
3888
3870
  const R = 108, NR = 80;
3889
3871
  const nums = mode2 === "h" ? Array.from({ length: 12 }, (_, i) => i + 1) : Array.from({ length: 12 }, (_, i) => i * 5);
@@ -3956,28 +3938,9 @@ function ClockFace({ value = "09:00", onChange }) {
3956
3938
  }
3957
3939
  function TimePicker({ label, help, error, required = false, disabled = false, value = "", onChange, placeholder = "Pick a time", id, className = "", style }) {
3958
3940
  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]);
3941
+ const [open, setOpen] = React24.useState(false);
3942
+ const rootRef = React24.useRef(null);
3943
+ const boxRef = React24.useRef(null);
3981
3944
  const disp = () => {
3982
3945
  if (!value) return "";
3983
3946
  const [h, m] = value.split(":").map(Number);
@@ -4009,10 +3972,19 @@ function TimePicker({ label, help, error, required = false, disabled = false, va
4009
3972
  ),
4010
3973
  /* @__PURE__ */ jsx51("span", { className: "fd-select-caret", children: /* @__PURE__ */ jsx51("i", { className: "ph ph-caret-down", "aria-hidden": "true" }) })
4011
3974
  ] }),
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: [
3975
+ /* @__PURE__ */ jsx51(
3976
+ Popover,
3977
+ {
3978
+ open,
3979
+ anchorRef: boxRef,
3980
+ triggerRef: rootRef,
3981
+ onClose: () => setOpen(false),
3982
+ placement: "bottom-start",
3983
+ offset: 6,
3984
+ width: 262,
3985
+ role: "presentation",
3986
+ style: { zIndex: 130 },
3987
+ footer: /* @__PURE__ */ jsxs46(React24.Fragment, { children: [
4016
3988
  /* @__PURE__ */ jsx51(
4017
3989
  "button",
4018
3990
  {
@@ -4028,10 +4000,10 @@ function TimePicker({ label, help, error, required = false, disabled = false, va
4028
4000
  ),
4029
4001
  /* @__PURE__ */ jsx51("span", { style: { flex: 1 } }),
4030
4002
  /* @__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,
4003
+ ] }),
4004
+ children: /* @__PURE__ */ jsx51(ClockFace, { value: value || "09:00", onChange: (v) => onChange && onChange({ target: { value: v } }) })
4005
+ }
4006
+ ),
4035
4007
  error ? /* @__PURE__ */ jsxs46("span", { className: "fd-field-error", children: [
4036
4008
  /* @__PURE__ */ jsx51("i", { className: "ph ph-warning-circle", "aria-hidden": "true" }),
4037
4009
  error
@@ -4040,7 +4012,7 @@ function TimePicker({ label, help, error, required = false, disabled = false, va
4040
4012
  }
4041
4013
 
4042
4014
  // src/components/forms/Slider.tsx
4043
- import * as React24 from "react";
4015
+ import * as React25 from "react";
4044
4016
  import { jsx as jsx52, jsxs as jsxs47 } from "react/jsx-runtime";
4045
4017
  function Slider({
4046
4018
  label,
@@ -4058,7 +4030,7 @@ function Slider({
4058
4030
  ...rest
4059
4031
  }) {
4060
4032
  const fieldId = useFieldId(id);
4061
- const [dragging, setDragging] = React24.useState(false);
4033
+ const [dragging, setDragging] = React25.useState(false);
4062
4034
  const v = value === void 0 ? min : Number(value);
4063
4035
  const pct = max === min ? 0 : (v - min) / (max - min) * 100;
4064
4036
  return /* @__PURE__ */ jsxs47("div", { className: ["fd-field", className].filter(Boolean).join(" "), children: [
@@ -4092,7 +4064,7 @@ function Slider({
4092
4064
  }
4093
4065
 
4094
4066
  // src/components/forms/RangeSlider.tsx
4095
- import * as React25 from "react";
4067
+ import * as React26 from "react";
4096
4068
  import { jsx as jsx53, jsxs as jsxs48 } from "react/jsx-runtime";
4097
4069
  function RangeSlider({
4098
4070
  label,
@@ -4112,9 +4084,9 @@ function RangeSlider({
4112
4084
  }) {
4113
4085
  const fmt2 = format || ((v) => String(v));
4114
4086
  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);
4087
+ const [drag, setDrag] = React26.useState(null);
4088
+ const [focus, setFocus] = React26.useState(null);
4089
+ const railRef = React26.useRef(null);
4118
4090
  const pct = (v) => max === min ? 0 : (v - min) / (max - min) * 100;
4119
4091
  const clampPair = (i, v) => {
4120
4092
  v = Math.min(max, Math.max(min, Math.round(v / step) * step));
@@ -4129,7 +4101,7 @@ function RangeSlider({
4129
4101
  const r = railRef.current.getBoundingClientRect();
4130
4102
  return min + Math.min(1, Math.max(0, (e.clientX - r.left) / r.width)) * (max - min);
4131
4103
  };
4132
- React25.useEffect(() => {
4104
+ React26.useEffect(() => {
4133
4105
  if (drag === null) return;
4134
4106
  const mv = (e) => onChange && onChange(clampPair(drag, fromEvent(e)));
4135
4107
  const upH = () => setDrag(null);
@@ -4210,7 +4182,7 @@ function RangeSlider({
4210
4182
  /* @__PURE__ */ jsxs48("div", { className: "fd-range" + (hasLabels ? " has-labels" : ""), onPointerDown: onRailDown, children: [
4211
4183
  /* @__PURE__ */ jsx53("span", { className: "fd-range-rail", ref: railRef }),
4212
4184
  /* @__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: [
4185
+ marks.map((m) => /* @__PURE__ */ jsxs48(React26.Fragment, { children: [
4214
4186
  /* @__PURE__ */ jsx53("span", { className: "fd-range-mark" + (m.value >= a && m.value <= b ? " is-in" : ""), style: { left: pct(m.value) + "%" } }),
4215
4187
  m.label ? /* @__PURE__ */ jsx53("span", { className: "fd-range-mark-label", style: { left: pct(m.value) + "%" }, children: m.label }) : null
4216
4188
  ] }, m.value)),
@@ -4239,7 +4211,7 @@ function RangeSlider({
4239
4211
  }
4240
4212
 
4241
4213
  // src/components/forms/Dropzone.tsx
4242
- import * as React26 from "react";
4214
+ import * as React27 from "react";
4243
4215
  import { jsx as jsx54, jsxs as jsxs49 } from "react/jsx-runtime";
4244
4216
  function Dropzone({
4245
4217
  onFiles,
@@ -4254,8 +4226,8 @@ function Dropzone({
4254
4226
  className = "",
4255
4227
  style
4256
4228
  }) {
4257
- const [over, setOver] = React26.useState(false);
4258
- const depth = React26.useRef(0);
4229
+ const [over, setOver] = React27.useState(false);
4230
+ const depth = React27.useRef(0);
4259
4231
  const has = (e) => {
4260
4232
  const dt = e.dataTransfer;
4261
4233
  if (!dt) return false;
@@ -4317,8 +4289,8 @@ function FilePickButton({
4317
4289
  className = "",
4318
4290
  children
4319
4291
  }) {
4320
- const ref = React26.useRef(null);
4321
- return /* @__PURE__ */ jsxs49(React26.Fragment, { children: [
4292
+ const ref = React27.useRef(null);
4293
+ return /* @__PURE__ */ jsxs49(React27.Fragment, { children: [
4322
4294
  /* @__PURE__ */ jsx54(
4323
4295
  "button",
4324
4296
  {
@@ -4351,10 +4323,10 @@ function FilePickButton({
4351
4323
  }
4352
4324
  function useStagedFiles(upload, opts) {
4353
4325
  const o = opts || {};
4354
- const [items, setItems] = React26.useState([]);
4355
- const controllers = React26.useRef({});
4326
+ const [items, setItems] = React27.useState([]);
4327
+ const controllers = React27.useRef({});
4356
4328
  const patch = (id, next) => setItems((list) => list.map((f) => f.id === id ? Object.assign({}, f, next) : f));
4357
- const run = React26.useCallback((att) => {
4329
+ const run = React27.useCallback((att) => {
4358
4330
  if (!upload) return;
4359
4331
  const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
4360
4332
  controllers.current[att.id] = ac;
@@ -4371,14 +4343,14 @@ function useStagedFiles(upload, opts) {
4371
4343
  delete controllers.current[att.id];
4372
4344
  });
4373
4345
  }, [upload]);
4374
- const add = React26.useCallback((files) => {
4346
+ const add = React27.useCallback((files) => {
4375
4347
  if (!upload) return [];
4376
4348
  const atts = Array.from(files).map((f) => toAttachment(f));
4377
4349
  setItems((list) => list.concat(atts));
4378
4350
  atts.forEach(run);
4379
4351
  return atts;
4380
4352
  }, [upload, run]);
4381
- const remove = React26.useCallback((att) => {
4353
+ const remove = React27.useCallback((att) => {
4382
4354
  const ac = controllers.current[att.id];
4383
4355
  if (ac) {
4384
4356
  try {
@@ -4389,10 +4361,10 @@ function useStagedFiles(upload, opts) {
4389
4361
  }
4390
4362
  setItems((list) => list.filter((f) => f.id !== att.id));
4391
4363
  }, []);
4392
- const retry = React26.useCallback((att) => {
4364
+ const retry = React27.useCallback((att) => {
4393
4365
  run(att);
4394
4366
  }, [run]);
4395
- const clear = React26.useCallback(() => {
4367
+ const clear = React27.useCallback(() => {
4396
4368
  Object.values(controllers.current).forEach((ac) => {
4397
4369
  try {
4398
4370
  ac && ac.abort();
@@ -4516,7 +4488,7 @@ function FileGrid({ files = [], onOpen, onRemove, onRetry, tiles = true, maxHeig
4516
4488
  }
4517
4489
 
4518
4490
  // src/components/forms/MarkdownEditor.tsx
4519
- import * as React27 from "react";
4491
+ import * as React28 from "react";
4520
4492
  import { jsx as jsx56, jsxs as jsxs51 } from "react/jsx-runtime";
4521
4493
  var isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent || "");
4522
4494
  var INLINE_RE = /(\*\*\*[^*\n]+\*\*\*|\*\*[^*\n]+\*\*|__[^_\n]+__|~~[^~\n]+~~|`[^`\n]+`|\*[^*\s][^*\n]*\*|(?<![A-Za-z0-9_])_[^_\s][^_\n]*_|\[[^\]\n]*\]\([^)\s\n]*\)|https?:\/\/\S+)/g;
@@ -4730,7 +4702,7 @@ function syncDom(root, value) {
4730
4702
  while (root.children.length > lines.length) root.removeChild(root.lastChild);
4731
4703
  }
4732
4704
  var LIST_CONT = RE_LI;
4733
- var MarkdownEditor = React27.forwardRef(function MarkdownEditor2({
4705
+ var MarkdownEditor = React28.forwardRef(function MarkdownEditor2({
4734
4706
  value = "",
4735
4707
  onChange,
4736
4708
  onSubmit,
@@ -4749,10 +4721,10 @@ var MarkdownEditor = React27.forwardRef(function MarkdownEditor2({
4749
4721
  className = "",
4750
4722
  id
4751
4723
  }, ref) {
4752
- const boxRef = React27.useRef(null);
4753
- const composing = React27.useRef(false);
4754
- const pendingCaret = React27.useRef(null);
4755
- React27.useLayoutEffect(() => {
4724
+ const boxRef = React28.useRef(null);
4725
+ const composing = React28.useRef(false);
4726
+ const pendingCaret = React28.useRef(null);
4727
+ React28.useLayoutEffect(() => {
4756
4728
  const root = boxRef.current;
4757
4729
  if (!root || composing.current) return;
4758
4730
  const active = document.activeElement === root || root.contains(document.activeElement);
@@ -4761,7 +4733,7 @@ var MarkdownEditor = React27.forwardRef(function MarkdownEditor2({
4761
4733
  pendingCaret.current = null;
4762
4734
  if (active && caret != null) placeCaret(root, caret);
4763
4735
  }, [value]);
4764
- React27.useEffect(() => {
4736
+ React28.useEffect(() => {
4765
4737
  if (autoFocus && boxRef.current) boxRef.current.focus();
4766
4738
  }, [autoFocus]);
4767
4739
  const caretNow = () => {
@@ -4828,7 +4800,7 @@ var MarkdownEditor = React27.forwardRef(function MarkdownEditor2({
4828
4800
  api.replaceRange(from, to, next, from + next.length);
4829
4801
  }
4830
4802
  };
4831
- React27.useImperativeHandle(ref, () => api);
4803
+ React28.useImperativeHandle(ref, () => api);
4832
4804
  function detect(text, caret) {
4833
4805
  if (!onTrigger) return;
4834
4806
  const upto = text.slice(0, caret);
@@ -4980,10 +4952,10 @@ var MarkdownEditor = React27.forwardRef(function MarkdownEditor2({
4980
4952
  });
4981
4953
 
4982
4954
  // src/components/platform/AccountMenu.tsx
4983
- import * as React29 from "react";
4955
+ import * as React30 from "react";
4984
4956
 
4985
4957
  // src/kits/session.ts
4986
- import * as React28 from "react";
4958
+ import * as React29 from "react";
4987
4959
  var PERMISSION_CATALOG = [
4988
4960
  { group: "Plans", items: [
4989
4961
  { key: "plan.view", label: "View plans", detail: "Read any plan in the workspace." },
@@ -6550,8 +6522,8 @@ function roadmap(overrides) {
6550
6522
  };
6551
6523
  }
6552
6524
  function useSession() {
6553
- const [s, setS] = React28.useState(getSession);
6554
- React28.useEffect(() => subscribe(setS), []);
6525
+ const [s, setS] = React29.useState(getSession);
6526
+ React29.useEffect(() => subscribe(setS), []);
6555
6527
  return s;
6556
6528
  }
6557
6529
  var SessionKit = {
@@ -6584,7 +6556,7 @@ var SessionKit = {
6584
6556
  };
6585
6557
 
6586
6558
  // src/components/platform/AccountMenu.tsx
6587
- import { Fragment as Fragment12, jsx as jsx57, jsxs as jsxs52 } from "react/jsx-runtime";
6559
+ import { Fragment as Fragment14, jsx as jsx57, jsxs as jsxs52 } from "react/jsx-runtime";
6588
6560
  var DEFAULT_LINKS = [
6589
6561
  { id: "profile", label: "Your profile", icon: "user-circle", href: "../admin/index.html#profile" },
6590
6562
  { id: "preferences", label: "Preferences", icon: "sliders-horizontal", href: "../admin/index.html#preferences" }
@@ -6621,9 +6593,9 @@ function AccountMenu({
6621
6593
  ...rest
6622
6594
  }) {
6623
6595
  const session = SessionKit.useSession();
6624
- const [open, setOpen] = React29.useState(false);
6625
- const [switching, setSwitching] = React29.useState(false);
6626
- const ref = React29.useRef(null);
6596
+ const [open, setOpen] = React30.useState(false);
6597
+ const [switching, setSwitching] = React30.useState(false);
6598
+ const ref = React30.useRef(null);
6627
6599
  const user = session.user;
6628
6600
  const visibleAdmin = adminLinks.filter((l) => !l.perm || SessionKit.can(l.perm));
6629
6601
  const pick = (item) => {
@@ -6637,7 +6609,7 @@ function AccountMenu({
6637
6609
  if (onSignOut) return onSignOut();
6638
6610
  window.alert("Signed out. (Simulated \u2014 no auth provider is wired up.)");
6639
6611
  };
6640
- return /* @__PURE__ */ jsxs52(Fragment12, { children: [
6612
+ return /* @__PURE__ */ jsxs52(Fragment14, { children: [
6641
6613
  /* @__PURE__ */ jsxs52(
6642
6614
  "button",
6643
6615
  {
@@ -6675,7 +6647,7 @@ function AccountMenu({
6675
6647
  /* @__PURE__ */ jsx57("span", { style: { flex: 1 }, children: "View as another member" }),
6676
6648
  /* @__PURE__ */ jsx57("i", { className: "ph ph-caret-" + (switching ? "up" : "down"), "aria-hidden": "true", style: { fontSize: 11 } })
6677
6649
  ] }),
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(
6650
+ switching ? /* @__PURE__ */ jsx57("div", { className: "fd-stack", style: { gap: 0 }, children: session.allUsers.filter((u) => u.status === "active").map((u) => /* @__PURE__ */ jsxs52(
6679
6651
  "button",
6680
6652
  {
6681
6653
  type: "button",
@@ -6707,14 +6679,14 @@ function AccountMenu({
6707
6679
  }
6708
6680
 
6709
6681
  // src/components/platform/ApiSpecBrowser.tsx
6710
- import * as React30 from "react";
6682
+ import * as React31 from "react";
6711
6683
  import { jsx as jsx58, jsxs as jsxs53 } from "react/jsx-runtime";
6712
6684
  var METHOD_TONE = { GET: "success", POST: "info", PATCH: "warning", PUT: "warning", DELETE: "danger" };
6713
6685
  function Json({ obj }) {
6714
6686
  return /* @__PURE__ */ jsx58("pre", { className: "fd-json", children: JSON.stringify(obj, null, 2) });
6715
6687
  }
6716
6688
  function Endpoint({ s, onRequest }) {
6717
- const [tried, setTried] = React30.useState(null);
6689
+ const [tried, setTried] = React31.useState(null);
6718
6690
  const run = async () => {
6719
6691
  setTried("busy");
6720
6692
  const t0 = (window.performance || Date).now();
@@ -6787,10 +6759,10 @@ function ApiSpecBrowser({
6787
6759
  className = "",
6788
6760
  ...rest
6789
6761
  }) {
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);
6762
+ const [q, setQ] = React31.useState("");
6763
+ const [method, setMethod] = React31.useState(null);
6764
+ const [mod, setMod] = React31.useState(null);
6765
+ const [listOnly, setListOnly] = React31.useState(false);
6794
6766
  const activeModule = modules && modules.find((m) => m.id === mod);
6795
6767
  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
6768
  const effGroups = groups && groups.length ? groups : [["All endpoints", spec.map((s) => s.id)]];
@@ -6869,7 +6841,7 @@ function ApiSpecBrowser({
6869
6841
  }
6870
6842
 
6871
6843
  // src/components/platform/ProfilePage.tsx
6872
- import * as React31 from "react";
6844
+ import * as React32 from "react";
6873
6845
  import { jsx as jsx59, jsxs as jsxs54 } from "react/jsx-runtime";
6874
6846
  var TIMEZONES = ["America/New_York", "America/Chicago", "America/Denver", "America/Los_Angeles", "America/Anchorage", "Pacific/Honolulu", "Europe/London", "Europe/Berlin"];
6875
6847
  var NOTIFY = [
@@ -6882,7 +6854,7 @@ var NOTIFY = [
6882
6854
  function ProfilePage({ user: userProp, onSave, onNavigate, sessions, showSessions = true, className = "", ...rest }) {
6883
6855
  const session = SessionKit.useSession();
6884
6856
  const user = userProp || session.user;
6885
- const [draft, setDraft] = React31.useState(() => ({
6857
+ const [draft, setDraft] = React32.useState(() => ({
6886
6858
  name: user.name || "",
6887
6859
  title: user.title || "",
6888
6860
  email: user.email || "",
@@ -6891,8 +6863,8 @@ function ProfilePage({ user: userProp, onSave, onNavigate, sessions, showSession
6891
6863
  bio: user.bio || "",
6892
6864
  notify: user.notify || { planShared: true, planChanged: true, goalMissed: true, flagChanged: false, weekly: true }
6893
6865
  }));
6894
- const [saving, setSaving] = React31.useState(false);
6895
- const [saved, setSaved] = React31.useState(false);
6866
+ const [saving, setSaving] = React32.useState(false);
6867
+ const [saved, setSaved] = React32.useState(false);
6896
6868
  const set = (k, v) => {
6897
6869
  setDraft((d) => Object.assign({}, d, { [k]: v }));
6898
6870
  setSaved(false);
@@ -7082,10 +7054,10 @@ function ProfilePage({ user: userProp, onSave, onNavigate, sessions, showSession
7082
7054
  }
7083
7055
 
7084
7056
  // src/components/platform/RoadmapTimeline.tsx
7085
- import * as React33 from "react";
7057
+ import * as React34 from "react";
7086
7058
 
7087
7059
  // src/kits/runtime.ts
7088
- import * as React32 from "react";
7060
+ import * as React33 from "react";
7089
7061
  var WIRED_ENDPOINTS = [
7090
7062
  // Nothing yet. Every id below would come from a real service:
7091
7063
  // "plan.get", "placements.list", …
@@ -7256,8 +7228,8 @@ var RuntimeKit = {
7256
7228
  };
7257
7229
  RuntimeKit.declare(FEATURE_NEEDS);
7258
7230
  function useRuntimeMode() {
7259
- const [m, setM] = React32.useState(RuntimeKit.getMode());
7260
- React32.useEffect(() => RuntimeKit.subscribe(setM), []);
7231
+ const [m, setM] = React33.useState(RuntimeKit.getMode());
7232
+ React33.useEffect(() => RuntimeKit.subscribe(setM), []);
7261
7233
  return m;
7262
7234
  }
7263
7235
  function useFeatureStatus(key) {
@@ -7340,12 +7312,12 @@ function RoadmapCard({ item, byKey, onOpenFlag }) {
7340
7312
  }
7341
7313
  function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
7342
7314
  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(() => {
7315
+ const [project, setProject] = React34.useState("");
7316
+ const [q, setQ] = React34.useState("");
7317
+ const scrollRef = React34.useRef(null);
7318
+ const nowRef = React34.useRef(null);
7319
+ const rm = React34.useMemo(() => SessionKit.roadmap({ isComplete: RuntimeKit.isComplete }), [session]);
7320
+ const byKey = React34.useMemo(() => {
7349
7321
  const m = {};
7350
7322
  rm.items.forEach((i) => {
7351
7323
  m[i.key] = i;
@@ -7354,7 +7326,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
7354
7326
  }, [rm]);
7355
7327
  const items = rm.items.filter((i) => (!project || i.project === project) && (!q || (i.label + " " + i.description + " " + (i.backend || "") + " " + i.key).toLowerCase().includes(q.toLowerCase())));
7356
7328
  const projects = [...new Set(rm.items.map((i) => i.project))];
7357
- React33.useEffect(() => {
7329
+ React34.useEffect(() => {
7358
7330
  let raf1 = 0, raf2 = 0;
7359
7331
  const place = () => {
7360
7332
  const box = scrollRef.current, mark = nowRef.current;
@@ -7428,7 +7400,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
7428
7400
  lastPhase = item.phase;
7429
7401
  const inPhase = items.filter((i) => i.phase === item.phase).length;
7430
7402
  const isBoundary = n === firstPending;
7431
- return /* @__PURE__ */ jsxs55(React33.Fragment, { children: [
7403
+ return /* @__PURE__ */ jsxs55(React34.Fragment, { children: [
7432
7404
  showPhase ? /* @__PURE__ */ jsxs55("div", { className: "fd-rm-era", children: [
7433
7405
  /* @__PURE__ */ jsxs55("span", { className: "fd-row", style: { gap: 8, flexWrap: "wrap", alignItems: "baseline" }, children: [
7434
7406
  /* @__PURE__ */ jsx60("span", { style: { fontWeight: 700 }, children: item.phase === 0 ? "Shipped" : "Phase " + item.phase + " \u2014 " + item.phaseName }),
@@ -7472,9 +7444,9 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
7472
7444
  }
7473
7445
 
7474
7446
  // 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";
7447
+ import * as React35 from "react";
7448
+ import { createPortal as createPortal5 } from "react-dom";
7449
+ import { Fragment as Fragment16, jsx as jsx61, jsxs as jsxs56 } from "react/jsx-runtime";
7478
7450
  var BYPASS_STORE = "fd.soon.bypass.v1";
7479
7451
  function readBypassed() {
7480
7452
  try {
@@ -7490,8 +7462,8 @@ function writeBypassed(list) {
7490
7462
  }
7491
7463
  }
7492
7464
  function useBypass(key) {
7493
- const [on, setOn] = React34.useState(() => !!key && readBypassed().indexOf(key) >= 0);
7494
- React34.useEffect(() => {
7465
+ const [on, setOn] = React35.useState(() => !!key && readBypassed().indexOf(key) >= 0);
7466
+ React35.useEffect(() => {
7495
7467
  setOn(!!key && readBypassed().indexOf(key) >= 0);
7496
7468
  }, [key]);
7497
7469
  const set = (next) => {
@@ -7504,7 +7476,7 @@ function useBypass(key) {
7504
7476
  return [on, set];
7505
7477
  }
7506
7478
  function SoonCard({ label, detail, backend, eta, effort, onRoadmap, allowed, onBypass }) {
7507
- return /* @__PURE__ */ jsxs56(Fragment14, { children: [
7479
+ return /* @__PURE__ */ jsxs56(Fragment16, { children: [
7508
7480
  /* @__PURE__ */ jsxs56("span", { className: "fd-soon-badge", children: [
7509
7481
  /* @__PURE__ */ jsx61("i", { className: "ph ph-traffic-cone", "aria-hidden": "true" }),
7510
7482
  label
@@ -7538,9 +7510,9 @@ function SoonCard({ label, detail, backend, eta, effort, onRoadmap, allowed, onB
7538
7510
  ] });
7539
7511
  }
7540
7512
  function useHoverCard(open) {
7541
- const anchor = React34.useRef(null);
7542
- const [pos, setPos] = React34.useState(null);
7543
- React34.useLayoutEffect(() => {
7513
+ const anchor = React35.useRef(null);
7514
+ const [pos, setPos] = React35.useState(null);
7515
+ React35.useLayoutEffect(() => {
7544
7516
  if (!open || !anchor.current) {
7545
7517
  setPos(null);
7546
7518
  return;
@@ -7667,9 +7639,9 @@ function ComingSoon({
7667
7639
  ] });
7668
7640
  }
7669
7641
  function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, onBypass, blur, tip, className, rest, children }) {
7670
- const [open, setOpen] = React34.useState(false);
7642
+ const [open, setOpen] = React35.useState(false);
7671
7643
  const [anchor, pos] = useHoverCard(open);
7672
- const close = React34.useRef(null);
7644
+ const close = React35.useRef(null);
7673
7645
  const show = () => {
7674
7646
  if (close.current) {
7675
7647
  clearTimeout(close.current);
@@ -7685,7 +7657,7 @@ function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, o
7685
7657
  setOpen(false);
7686
7658
  }, 140);
7687
7659
  };
7688
- React34.useEffect(() => () => {
7660
+ React35.useEffect(() => () => {
7689
7661
  if (close.current) clearTimeout(close.current);
7690
7662
  }, []);
7691
7663
  return /* @__PURE__ */ jsxs56(
@@ -7714,7 +7686,7 @@ function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, o
7714
7686
  children: /* @__PURE__ */ jsx61("i", { className: "ph ph-traffic-cone", "aria-hidden": "true" })
7715
7687
  }
7716
7688
  ),
7717
- open && pos ? createPortal8(
7689
+ open && pos ? createPortal5(
7718
7690
  /* @__PURE__ */ jsx61(
7719
7691
  "div",
7720
7692
  {
@@ -7854,13 +7826,13 @@ function PermissionHint({ perm, children }) {
7854
7826
  }
7855
7827
 
7856
7828
  // 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";
7829
+ import * as React36 from "react";
7830
+ import { Fragment as Fragment17, jsx as jsx63, jsxs as jsxs58 } from "react/jsx-runtime";
7859
7831
  function ModeSwitch({ canToggle = false, requestCount = 0, onClearLog, renderLog, onOpenSpec, summary }) {
7860
7832
  const mode2 = useRuntimeMode();
7861
- const [open, setOpen] = React35.useState(false);
7862
- const ref = React35.useRef(null);
7863
- React35.useEffect(() => {
7833
+ const [open, setOpen] = React36.useState(false);
7834
+ const ref = React36.useRef(null);
7835
+ React36.useEffect(() => {
7864
7836
  if (!open) return;
7865
7837
  const away = (e) => {
7866
7838
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -7925,7 +7897,7 @@ function ModeSwitch({ canToggle = false, requestCount = 0, onClearLog, renderLog
7925
7897
  s.total,
7926
7898
  " features complete"
7927
7899
  ] }),
7928
- onOpenSpec ? /* @__PURE__ */ jsxs58(Fragment15, { children: [
7900
+ onOpenSpec ? /* @__PURE__ */ jsxs58(Fragment17, { children: [
7929
7901
  /* @__PURE__ */ jsx63("span", { style: { flex: 1 } }),
7930
7902
  /* @__PURE__ */ jsxs58("button", { type: "button", className: "fd-soon-link", onClick: () => {
7931
7903
  setOpen(false);
@@ -8109,11 +8081,11 @@ function SaturationDistribution({ campuses = [], floorBand, floorScore, onSelect
8109
8081
  }
8110
8082
 
8111
8083
  // 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";
8084
+ import * as React37 from "react";
8085
+ import { Fragment as Fragment18, jsx as jsx66, jsxs as jsxs61 } from "react/jsx-runtime";
8114
8086
  function MixGap({ rows = [], loading = false, className = "" }) {
8115
8087
  const max = Math.max(1, ...rows.flatMap((r) => [r.target, r.realized]));
8116
- const [hover, setHover] = React36.useState(null);
8088
+ const [hover, setHover] = React37.useState(null);
8117
8089
  const toneOf = (gap) => gap >= -1 ? "ok" : gap >= -4 ? "warn" : "danger";
8118
8090
  const TONE = { ok: "var(--ok-solid)", warn: "var(--warn-solid)", danger: "var(--danger-solid)" };
8119
8091
  return /* @__PURE__ */ jsxs61("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 4 }, children: [
@@ -8140,7 +8112,7 @@ function MixGap({ rows = [], loading = false, className = "" }) {
8140
8112
  onMouseLeave: () => setHover(null),
8141
8113
  children: [
8142
8114
  /* @__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: [
8115
+ /* @__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
8116
  /* @__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
8117
  /* @__PURE__ */ jsx66("span", { style: { position: "absolute", left: r.target / max * 100 + "%", top: 0, width: 3, height: 22, background: "var(--n-500)", borderRadius: 1 } }),
8146
8118
  /* @__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 +8225,8 @@ function ChannelContribution({
8253
8225
  }
8254
8226
 
8255
8227
  // 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";
8228
+ import * as React38 from "react";
8229
+ import { Fragment as Fragment19, jsx as jsx68, jsxs as jsxs63 } from "react/jsx-runtime";
8258
8230
  var bandFor = (crp) => crp >= 200 ? "dominant" : crp >= 100 ? "strong" : crp >= 50 ? "adequate" : "weak";
8259
8231
  function BudgetReallocator({
8260
8232
  campus,
@@ -8269,8 +8241,8 @@ function BudgetReallocator({
8269
8241
  onCancel,
8270
8242
  className = ""
8271
8243
  }) {
8272
- const [draft, setDraft] = React37.useState(spend);
8273
- React37.useEffect(() => setDraft(spend), [spend]);
8244
+ const [draft, setDraft] = React38.useState(spend);
8245
+ React38.useEffect(() => setDraft(spend), [spend]);
8274
8246
  const dirty = draft !== spend;
8275
8247
  const nextCrp = scoreFor ? scoreFor(draft) : crp;
8276
8248
  const nextBand = bandFor(nextCrp);
@@ -8294,7 +8266,7 @@ function BudgetReallocator({
8294
8266
  /* @__PURE__ */ jsxs63("div", { className: "fd-row", style: { gap: 12, flexWrap: "wrap" }, children: [
8295
8267
  /* @__PURE__ */ jsx68("span", { className: "fd-h4", style: { flex: 1, minWidth: 140 }, children: campus }),
8296
8268
  /* @__PURE__ */ jsx68(CsiBadge, { band: nowBand, crp, size: "medium" }),
8297
- dirty ? /* @__PURE__ */ jsxs63(Fragment17, { children: [
8269
+ dirty ? /* @__PURE__ */ jsxs63(Fragment19, { children: [
8298
8270
  /* @__PURE__ */ jsx68("i", { className: "ph ph-arrow-right fd-muted", "aria-hidden": "true" }),
8299
8271
  /* @__PURE__ */ jsx68(CsiBadge, { band: nextBand, crp: nextCrp, size: "medium", preview: true })
8300
8272
  ] }) : null
@@ -8440,7 +8412,7 @@ function ImportanceTag({ level, showLabel = true, size = "sm", className = "" })
8440
8412
  }
8441
8413
 
8442
8414
  // src/components/planner/PreferenceMeter.tsx
8443
- import * as React38 from "react";
8415
+ import * as React39 from "react";
8444
8416
  import { jsx as jsx71, jsxs as jsxs66 } from "react/jsx-runtime";
8445
8417
  function preferenceScore(criteria) {
8446
8418
  const earned = criteria.reduce((n, c) => n + (Number(c.earned) || 0), 0);
@@ -8470,7 +8442,7 @@ function PreferenceMeter({
8470
8442
  className = ""
8471
8443
  }) {
8472
8444
  const value = score == null ? preferenceScore(criteria) : score;
8473
- const segments = React38.useMemo(() => preferenceSegments(criteria), [criteria]);
8445
+ const segments = React39.useMemo(() => preferenceSegments(criteria), [criteria]);
8474
8446
  return /* @__PURE__ */ jsx71(
8475
8447
  ScoreMeter,
8476
8448
  {
@@ -8509,10 +8481,10 @@ function PreferenceMeter({
8509
8481
  }
8510
8482
 
8511
8483
  // src/components/chat/AgentChatPanel.tsx
8512
- import * as React44 from "react";
8484
+ import * as React45 from "react";
8513
8485
 
8514
8486
  // src/components/chat/chatEngine.ts
8515
- import * as React39 from "react";
8487
+ import * as React40 from "react";
8516
8488
  var CHAT_UNAVAILABLE = "chat_unavailable";
8517
8489
  var JOB_PENDING = ["queued", "running"];
8518
8490
  var JOB_SUCCESS = ["completed", "recovered"];
@@ -8566,22 +8538,22 @@ function useChatEngine(opts) {
8566
8538
  onClear,
8567
8539
  onFeedback
8568
8540
  } = 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(() => {
8541
+ const [status, setStatus] = React40.useState("idle");
8542
+ const [threadId, setThreadId] = React40.useState(null);
8543
+ const [messages, setMessages] = React40.useState([]);
8544
+ const [queue, setQueue] = React40.useState([]);
8545
+ const [fatal, setFatal] = React40.useState(null);
8546
+ const [busy, setBusy] = React40.useState(false);
8547
+ const [turnStartedAt, setTurnStartedAt] = React40.useState(null);
8548
+ const listRef = React40.useRef([]);
8549
+ const queueRef = React40.useRef([]);
8550
+ const busyRef = React40.useRef(false);
8551
+ const stoppedRef = React40.useRef(false);
8552
+ const abortRef = React40.useRef(null);
8553
+ const serverCount = React40.useRef(0);
8554
+ const threadRef = React40.useRef(null);
8555
+ const mounted = React40.useRef(true);
8556
+ React40.useEffect(() => {
8585
8557
  mounted.current = true;
8586
8558
  return () => {
8587
8559
  mounted.current = false;
@@ -8603,14 +8575,14 @@ function useChatEngine(opts) {
8603
8575
  }
8604
8576
  return false;
8605
8577
  };
8606
- const loadThread = React39.useCallback(async (id) => {
8578
+ const loadThread = React40.useCallback(async (id) => {
8607
8579
  const data = await apiAdapter.getThread(id);
8608
8580
  const list = [...data && data.messages || []];
8609
8581
  serverCount.current = list.length;
8610
8582
  commit(list);
8611
8583
  return list;
8612
8584
  }, [apiAdapter]);
8613
- React39.useEffect(() => {
8585
+ React40.useEffect(() => {
8614
8586
  if (!apiAdapter) {
8615
8587
  setStatus("idle");
8616
8588
  setFatal(null);
@@ -8823,7 +8795,7 @@ function useChatEngine(opts) {
8823
8795
  await dispatchTurn(turn);
8824
8796
  }
8825
8797
  }
8826
- const send = React39.useCallback((text, attachments) => {
8798
+ const send = React40.useCallback((text, attachments) => {
8827
8799
  const body = (text || "").trim();
8828
8800
  if (!body && !(attachments && attachments.length)) return;
8829
8801
  if (status === "disconnected") return;
@@ -8833,7 +8805,7 @@ function useChatEngine(opts) {
8833
8805
  stoppedRef.current = false;
8834
8806
  drain();
8835
8807
  }, [status]);
8836
- const stop = React39.useCallback(() => {
8808
+ const stop = React40.useCallback(() => {
8837
8809
  stoppedRef.current = true;
8838
8810
  const ac = abortRef.current;
8839
8811
  if (ac) {
@@ -8852,11 +8824,11 @@ function useChatEngine(opts) {
8852
8824
  store.del(STORAGE_PREFIX + threadRef.current);
8853
8825
  }
8854
8826
  }, [apiAdapter]);
8855
- const removeQueued = React39.useCallback((id) => {
8827
+ const removeQueued = React40.useCallback((id) => {
8856
8828
  queueRef.current = queueRef.current.filter((t) => t.id !== id);
8857
8829
  setQueue(queueRef.current.slice());
8858
8830
  }, []);
8859
- const retry = React39.useCallback(() => {
8831
+ const retry = React40.useCallback(() => {
8860
8832
  const list = listRef.current;
8861
8833
  let at = -1;
8862
8834
  for (let i = list.length - 1; i >= 0; i--) if (list[i].role === "user") {
@@ -8872,19 +8844,19 @@ function useChatEngine(opts) {
8872
8844
  setQueue(queueRef.current.slice());
8873
8845
  drain();
8874
8846
  }, []);
8875
- const clear = React39.useCallback(() => {
8847
+ const clear = React40.useCallback(() => {
8876
8848
  commit([]);
8877
8849
  serverCount.current = 0;
8878
8850
  queueRef.current = [];
8879
8851
  setQueue([]);
8880
8852
  onClear && onClear();
8881
8853
  }, [onClear]);
8882
- const setFeedback = React39.useCallback((id, value) => {
8854
+ const setFeedback = React40.useCallback((id, value) => {
8883
8855
  patch(id, (m) => ({ feedback: m.feedback === value ? null : value }));
8884
8856
  const msg = listRef.current.find((m) => m.id === id);
8885
8857
  onFeedback && onFeedback({ message: msg, feedback: msg ? msg.feedback : value });
8886
8858
  }, [onFeedback]);
8887
- const reload = React39.useCallback(async () => {
8859
+ const reload = React40.useCallback(async () => {
8888
8860
  if (!threadRef.current) return;
8889
8861
  setStatus("loading");
8890
8862
  try {
@@ -8922,10 +8894,10 @@ var ChatKit = {
8922
8894
  };
8923
8895
 
8924
8896
  // src/components/chat/ChatTranscript.tsx
8925
- import * as React41 from "react";
8897
+ import * as React42 from "react";
8926
8898
 
8927
8899
  // src/components/chat/ChatTurn.tsx
8928
- import * as React40 from "react";
8900
+ import * as React41 from "react";
8929
8901
  import { jsx as jsx72, jsxs as jsxs67 } from "react/jsx-runtime";
8930
8902
  function JsonView({ value }) {
8931
8903
  let text;
@@ -8962,7 +8934,7 @@ function PacketCard({ packet, schema, render, onApply, applied }) {
8962
8934
  ] });
8963
8935
  }
8964
8936
  function ThinkingBlock({ text, durationMs, streaming, defaultOpen = false }) {
8965
- const [open, setOpen] = React40.useState(defaultOpen);
8937
+ const [open, setOpen] = React41.useState(defaultOpen);
8966
8938
  if (!text) return null;
8967
8939
  return /* @__PURE__ */ jsxs67("div", { className: "fdc-think" + (open ? " is-open" : ""), children: [
8968
8940
  /* @__PURE__ */ jsxs67("button", { type: "button", className: "fdc-think-head", onClick: () => setOpen((v) => !v), "aria-expanded": open, children: [
@@ -8979,7 +8951,7 @@ function ThinkingBlock({ text, durationMs, streaming, defaultOpen = false }) {
8979
8951
  ] });
8980
8952
  }
8981
8953
  function Citations({ items = [], onOpen }) {
8982
- const [open, setOpen] = React40.useState(false);
8954
+ const [open, setOpen] = React41.useState(false);
8983
8955
  if (!items.length) return null;
8984
8956
  return /* @__PURE__ */ jsxs67("div", { className: "fdc-cites", children: [
8985
8957
  /* @__PURE__ */ jsxs67("button", { type: "button", className: "fdc-cites-head", onClick: () => setOpen((v) => !v), "aria-expanded": open, children: [
@@ -9005,7 +8977,7 @@ function clampText(text, max) {
9005
8977
  return (at > max * 0.6 ? cut.slice(0, at) : cut).trimEnd() + "\u2026";
9006
8978
  }
9007
8979
  function MessageBody({ message: m, ctx }) {
9008
- const [expanded, setExpanded] = React40.useState(false);
8980
+ const [expanded, setExpanded] = React41.useState(false);
9009
8981
  const isUser = m.role === "user";
9010
8982
  const raw = m.text || "";
9011
8983
  const clamped = !expanded && !m.streaming ? clampText(raw, ctx.maxVisibleChars) : null;
@@ -9072,9 +9044,9 @@ function MessageBody({ message: m, ctx }) {
9072
9044
  ] });
9073
9045
  }
9074
9046
  function useCopyRun() {
9075
- const [done, setDone] = React40.useState(false);
9076
- const t = React40.useRef(null);
9077
- React40.useEffect(() => () => {
9047
+ const [done, setDone] = React41.useState(false);
9048
+ const t = React41.useRef(null);
9049
+ React41.useEffect(() => () => {
9078
9050
  if (t.current) clearTimeout(t.current);
9079
9051
  }, []);
9080
9052
  return [done, (text) => {
@@ -9101,7 +9073,7 @@ function RunActions({ group, ctx }) {
9101
9073
  ] }),
9102
9074
  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
9075
  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: [
9076
+ isAssistant && ctx.onFeedback ? /* @__PURE__ */ jsxs67(React41.Fragment, { children: [
9105
9077
  /* @__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
9078
  /* @__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
9079
  ] }) : null,
@@ -9196,10 +9168,10 @@ function ChatTranscript({
9196
9168
  renderEmpty,
9197
9169
  className = ""
9198
9170
  }) {
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);
9171
+ const scroller = React42.useRef(null);
9172
+ const stick = React42.useRef(true);
9173
+ const [pill, setPill] = React42.useState(0);
9174
+ const seen = React42.useRef(0);
9203
9175
  const toBottom = (smooth) => {
9204
9176
  const el = scroller.current;
9205
9177
  if (!el) return;
@@ -9218,7 +9190,7 @@ function ChatTranscript({
9218
9190
  seen.current = messages.length;
9219
9191
  }
9220
9192
  };
9221
- React41.useLayoutEffect(() => {
9193
+ React42.useLayoutEffect(() => {
9222
9194
  const el = scroller.current;
9223
9195
  if (!el) return;
9224
9196
  if (stick.current) {
@@ -9226,7 +9198,7 @@ function ChatTranscript({
9226
9198
  seen.current = messages.length;
9227
9199
  } else setPill(Math.max(0, messages.length - seen.current));
9228
9200
  }, [messages]);
9229
- React41.useEffect(() => {
9201
+ React42.useEffect(() => {
9230
9202
  const el = scroller.current;
9231
9203
  const inner = el && el.firstChild;
9232
9204
  if (!el || !inner || typeof ResizeObserver === "undefined") return;
@@ -9236,7 +9208,7 @@ function ChatTranscript({
9236
9208
  ro.observe(inner);
9237
9209
  return () => ro.disconnect();
9238
9210
  }, []);
9239
- const groups = React41.useMemo(() => groupMessages(messages), [messages]);
9211
+ const groups = React42.useMemo(() => groupMessages(messages), [messages]);
9240
9212
  const empty = !messages.length && status === "ready";
9241
9213
  return /* @__PURE__ */ jsxs68("div", { className: ["fdc-scroll", className].filter(Boolean).join(" "), ref: scroller, onScroll, children: [
9242
9214
  /* @__PURE__ */ jsxs68("div", { className: "fdc-log", role: "log", "aria-label": "Conversation", children: [
@@ -9260,7 +9232,7 @@ function ChatTranscript({
9260
9232
  const prev = groups[i - 1];
9261
9233
  const k = dayKey(g.messages[0].timestamp);
9262
9234
  const showDay = !!k && (!prev || dayKey(prev.messages[0].timestamp) !== k);
9263
- return /* @__PURE__ */ jsxs68(React41.Fragment, { children: [
9235
+ return /* @__PURE__ */ jsxs68(React42.Fragment, { children: [
9264
9236
  showDay ? /* @__PURE__ */ jsx73("div", { className: "fdc-day", children: /* @__PURE__ */ jsx73("span", { children: dayLabel(k) }) }) : null,
9265
9237
  /* @__PURE__ */ jsx73(ChatTurn, { group: g, ctx })
9266
9238
  ] }, g.key || i);
@@ -9277,7 +9249,7 @@ function ChatTranscript({
9277
9249
  var TranscriptKit = { groupMessages };
9278
9250
 
9279
9251
  // src/components/chat/ChatComposer.tsx
9280
- import * as React42 from "react";
9252
+ import * as React43 from "react";
9281
9253
  import { jsx as jsx74, jsxs as jsxs69 } from "react/jsx-runtime";
9282
9254
  function ChatComposer({
9283
9255
  onSubmit,
@@ -9305,17 +9277,17 @@ function ChatComposer({
9305
9277
  onReject,
9306
9278
  onOpenAttachment
9307
9279
  }) {
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);
9280
+ const [text, setText] = React43.useState(draft || "");
9281
+ const [trigger, setTrigger] = React43.useState(null);
9282
+ const [mentionItems, setMentionItems] = React43.useState([]);
9283
+ const [listening, setListening] = React43.useState(false);
9284
+ const [notice, setNotice] = React43.useState(null);
9285
+ const editor = React43.useRef(null);
9286
+ const wrap = React43.useRef(null);
9287
+ const stopVoice = React43.useRef(null);
9316
9288
  const staged = useStagedFiles(fileUploadHandler, { onError: () => {
9317
9289
  } });
9318
- React42.useEffect(() => {
9290
+ React43.useEffect(() => {
9319
9291
  if (draft != null && draft !== text) setText(draft);
9320
9292
  }, [draft]);
9321
9293
  const change = (v) => {
@@ -9349,7 +9321,7 @@ function ChatComposer({
9349
9321
  e.preventDefault();
9350
9322
  staged.add(found.files);
9351
9323
  };
9352
- React42.useEffect(() => {
9324
+ React43.useEffect(() => {
9353
9325
  if (!trigger || trigger.type !== "mention" || !mentionSources) {
9354
9326
  setMentionItems([]);
9355
9327
  return;
@@ -9367,7 +9339,7 @@ function ChatComposer({
9367
9339
  const q = (trigger.query || "").toLowerCase();
9368
9340
  setMentionItems(mentionSources.filter((m) => !q || (m.label + " " + (m.description || "")).toLowerCase().includes(q)));
9369
9341
  }, [trigger, mentionSources]);
9370
- const slashItems = React42.useMemo(() => {
9342
+ const slashItems = React43.useMemo(() => {
9371
9343
  if (!trigger || trigger.type !== "slash" || !slashCommands) return [];
9372
9344
  const q = (trigger.query || "").toLowerCase();
9373
9345
  return slashCommands.filter((c) => !q || (c.id + " " + c.label + " " + (c.description || "")).toLowerCase().includes(q));
@@ -9541,12 +9513,12 @@ function ChatComposer({
9541
9513
  }
9542
9514
 
9543
9515
  // src/components/chat/ChatSessionBar.tsx
9544
- import * as React43 from "react";
9516
+ import * as React44 from "react";
9545
9517
  import { jsx as jsx75, jsxs as jsxs70 } from "react/jsx-runtime";
9546
9518
  var compact2 = meterFormats.compact;
9547
9519
  function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extras }) {
9548
- const [open, setOpen] = React43.useState(false);
9549
- const anchor = React43.useRef(null);
9520
+ const [open, setOpen] = React44.useState(false);
9521
+ const anchor = React44.useRef(null);
9550
9522
  const stats = sessionStats || null;
9551
9523
  const cu = contextUsage || null;
9552
9524
  const limits = usageLimits || null;
@@ -9561,7 +9533,7 @@ function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extr
9561
9533
  if (stats && stats.runningTasks) bits.push(stats.runningTasks + " running task" + (stats.runningTasks === 1 ? "" : "s"));
9562
9534
  if (cu) bits.push(pct + "% context");
9563
9535
  const expandable = !!(cu || limits);
9564
- return /* @__PURE__ */ jsxs70(React43.Fragment, { children: [
9536
+ return /* @__PURE__ */ jsxs70(React44.Fragment, { children: [
9565
9537
  /* @__PURE__ */ jsxs70("div", { className: "fdc-bar", children: [
9566
9538
  /* @__PURE__ */ jsxs70(
9567
9539
  "button",
@@ -9582,60 +9554,73 @@ function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extr
9582
9554
  ),
9583
9555
  extras
9584
9556
  ] }),
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
- ] }) })
9557
+ /* @__PURE__ */ jsx75(
9558
+ Popover,
9559
+ {
9560
+ open,
9561
+ anchorRef: anchor,
9562
+ placement: "top-start",
9563
+ onClose: () => setOpen(false),
9564
+ minWidth: 330,
9565
+ maxHeight: 460,
9566
+ padded: true,
9567
+ label: "Usage",
9568
+ footer: onClear ? /* @__PURE__ */ jsxs70("button", { type: "button", className: "fdc-usage-clear", onClick: () => {
9569
+ setOpen(false);
9570
+ onClear();
9571
+ }, children: [
9572
+ /* @__PURE__ */ jsx75("i", { className: "ph ph-trash", "aria-hidden": "true" }),
9573
+ "Clear conversation"
9574
+ ] }) : void 0,
9575
+ children: /* @__PURE__ */ jsxs70("div", { className: "fdc-usage", children: [
9576
+ cu ? /* @__PURE__ */ jsx75("section", { className: "fdc-usage-sec", children: /* @__PURE__ */ jsx75(
9577
+ SegmentedMeter,
9578
+ {
9579
+ total,
9580
+ segments: cu.segments,
9581
+ label: cu.label || "Context window",
9582
+ format: cu.format === "bytes" ? meterFormats.bytes : compact2,
9583
+ height: 8,
9584
+ legend: true,
9585
+ remainderLabel: "Free"
9586
+ }
9587
+ ) }) : null,
9588
+ limits && limits.length ? /* @__PURE__ */ jsxs70("section", { className: "fdc-usage-sec", children: [
9589
+ /* @__PURE__ */ jsx75("h4", { className: "fdc-usage-h", children: "Usage limits" }),
9590
+ /* @__PURE__ */ jsx75("div", { className: "fdc-usage-rows", children: limits.map((l) => /* @__PURE__ */ jsx75(
9591
+ QuotaRow,
9592
+ {
9593
+ label: l.label,
9594
+ percent: l.percent,
9595
+ note: l.resetsAt ? "Resets " + formatRelative(l.resetsAt) : l.note
9596
+ },
9597
+ l.id
9598
+ )) })
9599
+ ] }) : null,
9600
+ stats ? /* @__PURE__ */ jsxs70("section", { className: "fdc-usage-sec fdc-usage-stats", children: [
9601
+ stats.elapsedMs ? /* @__PURE__ */ jsxs70("div", { children: [
9602
+ /* @__PURE__ */ jsx75("span", { children: "Session" }),
9603
+ /* @__PURE__ */ jsx75("b", { className: "fd-tabular", children: formatDuration(stats.elapsedMs) })
9604
+ ] }) : null,
9605
+ stats.tokens ? /* @__PURE__ */ jsxs70("div", { children: [
9606
+ /* @__PURE__ */ jsx75("span", { children: "Tokens" }),
9607
+ /* @__PURE__ */ jsx75("b", { className: "fd-tabular", children: stats.tokens.toLocaleString() })
9608
+ ] }) : null,
9609
+ stats.costUsd != null ? /* @__PURE__ */ jsxs70("div", { children: [
9610
+ /* @__PURE__ */ jsx75("span", { children: "Cost" }),
9611
+ /* @__PURE__ */ jsxs70("b", { className: "fd-tabular", children: [
9612
+ "$",
9613
+ Number(stats.costUsd).toFixed(3)
9614
+ ] })
9615
+ ] }) : null,
9616
+ stats.turns ? /* @__PURE__ */ jsxs70("div", { children: [
9617
+ /* @__PURE__ */ jsx75("span", { children: "Turns" }),
9618
+ /* @__PURE__ */ jsx75("b", { className: "fd-tabular", children: stats.turns })
9619
+ ] }) : null
9620
+ ] }) : null
9621
+ ] })
9622
+ }
9623
+ )
9639
9624
  ] });
9640
9625
  }
9641
9626
  function ModelControls({
@@ -9689,7 +9674,7 @@ function ModelControls({
9689
9674
  ] })
9690
9675
  });
9691
9676
  }
9692
- return /* @__PURE__ */ jsxs70(React43.Fragment, { children: [
9677
+ return /* @__PURE__ */ jsxs70(React44.Fragment, { children: [
9693
9678
  /* @__PURE__ */ jsx75(
9694
9679
  MenuButton,
9695
9680
  {
@@ -9806,11 +9791,11 @@ function AgentChatPanel({
9806
9791
  onFeedback,
9807
9792
  onEditMessage
9808
9793
  }) {
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]);
9794
+ const [panelWidth, setPanelWidth] = React45.useState(width);
9795
+ const [applied, setApplied] = React45.useState({});
9796
+ const [draft, setDraft] = React45.useState("");
9797
+ const dragging = React45.useRef(null);
9798
+ React45.useEffect(() => setPanelWidth(width), [width]);
9814
9799
  const engine = useChatEngine({
9815
9800
  contextType,
9816
9801
  contextId,
@@ -10014,7 +9999,7 @@ function AgentChatPanel({
10014
9999
  }
10015
10000
 
10016
10001
  // src/kits/query.ts
10017
- import * as React45 from "react";
10002
+ import * as React46 from "react";
10018
10003
  function eqFilter(get2) {
10019
10004
  return (row, value) => Array.isArray(value) ? value.includes(get2(row)) : get2(row) === value;
10020
10005
  }
@@ -10046,22 +10031,22 @@ function compare(a, b, dir) {
10046
10031
  var API = null;
10047
10032
  var PREFS = null;
10048
10033
  function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
10049
- const [query, setQuery] = React45.useState(() => {
10034
+ const [query, setQuery] = React46.useState(() => {
10050
10035
  const store = PREFS || window.PlannerPrefs;
10051
10036
  const saved = prefsKey && store ? store.getTable(prefsKey) : {};
10052
10037
  return { ...DEFAULTS, ...defaults || {}, ...saved.pageSize ? { pageSize: saved.pageSize } : {}, ...saved.sort ? { sort: saved.sort, dir: saved.dir || "desc" } : {} };
10053
10038
  });
10054
- const savePref = React45.useCallback((patch2) => {
10039
+ const savePref = React46.useCallback((patch2) => {
10055
10040
  const store = PREFS || window.PlannerPrefs;
10056
10041
  if (prefsKey && store) store.setTable(prefsKey, patch2);
10057
10042
  }, [prefsKey]);
10058
- const [res, setRes] = React45.useState(null);
10059
- const [loading, setLoading] = React45.useState(true);
10060
- const seq2 = React45.useRef(0);
10043
+ const [res, setRes] = React46.useState(null);
10044
+ const [loading, setLoading] = React46.useState(true);
10045
+ const seq2 = React46.useRef(0);
10061
10046
  const depKey = (deps || []).join("|");
10062
10047
  const paramKey = JSON.stringify(params || {});
10063
10048
  const queryKey = JSON.stringify(query);
10064
- React45.useEffect(() => {
10049
+ React46.useEffect(() => {
10065
10050
  const id = ++seq2.current;
10066
10051
  setLoading(true);
10067
10052
  const t = setTimeout(() => {