@flytedan/flytebot-design-system 0.8.2 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -69,12 +69,27 @@ interface PopoverPosition {
69
69
  }
70
70
  /**
71
71
  * Fixed-position layer geometry for an anchored surface. Flips side when the
72
- * preferred one doesn't fit, clamps to the viewport, and re-measures on scroll
73
- * and resize (capture phase, so it tracks inside scrolling panels too).
72
+ * preferred one doesn't fit, clamps to the viewport, and re-measures continuously
73
+ * while open.
74
+ *
75
+ * Continuously, on an animation frame, rather than on scroll and resize events: those
76
+ * two cover an anchor that moves because the PAGE moved, and miss every other reason an
77
+ * anchor moves. The one that broke this was a row inside a virtualized list — rows are
78
+ * positioned by a CSS transform off their index, so re-sorting the list slides a row to
79
+ * a new slot with no scroll, no resize and no layout event of any kind, and a popover
80
+ * anchored to it simply stayed behind. Re-measuring per frame costs one
81
+ * getBoundingClientRect on one element for as long as a popover is open, and state is
82
+ * only set when the geometry actually changed (see samePosition), so a stationary anchor
83
+ * causes no re-renders at all.
84
+ *
85
+ * The same loop is what notices the anchor being REMOVED — a virtualized row scrolled
86
+ * out of the mounted band, a menu item deleted — and reports it through `onDetach`, so
87
+ * an anchored layer can never end up pinned to an element that no longer exists.
74
88
  */
75
89
  declare function usePopoverPosition(open: boolean, anchorRef: React.RefObject<HTMLElement>, opts?: Partial<PopoverProps> & {
76
90
  estHeight?: number;
77
91
  estWidth?: number;
92
+ onDetach?: () => void;
78
93
  }): PopoverPosition | null;
79
94
  /**
80
95
  * An anchored floating surface: menus, pickers, disclosure panels, meters.
@@ -906,6 +921,61 @@ interface QuotaRowProps {
906
921
  */
907
922
  declare function QuotaRow({ label, percent, note, tone, className }: QuotaRowProps): React.JSX.Element;
908
923
 
924
+ interface ScoreMeterProps {
925
+ /** The score itself, 0-100. Shown as a plain integer next to the rail. */
926
+ score: number;
927
+ /** One segment per contributing thing, each already carrying its own colour. Widths
928
+ * are proportional to `value` out of 100, so what doesn't contribute reads as the
929
+ * empty remainder of the rail rather than as a missing segment. */
930
+ segments: MeterSegmentInput[];
931
+ /** Accessible name for the meter, e.g. "Preference score". Required — a bare bar with
932
+ * a number next to it is meaningless to a screen reader without one. */
933
+ label: string;
934
+ /** Rail width in px. A fixed width (not a flexing one) is what lets a column of these
935
+ * line up down a list. */
936
+ width?: number;
937
+ height?: number;
938
+ /** Hide the numeral and show only the rail, for somewhere the number is already said. */
939
+ showScore?: boolean;
940
+ /** Content of the breakdown popover. Supplying it is what makes the meter clickable —
941
+ * without it this renders as a plain, inert span. */
942
+ breakdown?: React.ReactNode;
943
+ /** Accessible name for the popover. Defaults to `label` + " breakdown". */
944
+ breakdownLabel?: string;
945
+ placement?: Placement;
946
+ /** Width of the breakdown popover in px. */
947
+ breakdownWidth?: number;
948
+ className?: string;
949
+ }
950
+ /**
951
+ * ScoreMeter — a small 0-100 score drawn as a categorical bar, optionally opening a
952
+ * breakdown of how the score was arrived at.
953
+ *
954
+ * Not a new meter primitive: the rail IS SegmentedMeter (chosen over CsiMeter because a
955
+ * score built from a variable number of differently-coloured contributions needs
956
+ * per-segment colour and a variable segment count, and CsiMeter is fixed at four
957
+ * segments all drawn in one `currentColor`) with its head and legend turned off, so the
958
+ * proportional-width maths, the transition, the empty-remainder rail and the full
959
+ * `role="meter"` aria that SegmentedMeter already ships are used rather than reimplemented.
960
+ * What this adds is the part SegmentedMeter deliberately lacks: a real trigger.
961
+ *
962
+ * The trigger is a `<button>`, never a div with an onClick — three things follow from
963
+ * that, and all three are the reason it's a button. It is keyboard-operable and
964
+ * announced as a control; SegmentedMeter's own `onClick` (which lands on a plain div)
965
+ * is neither. And inside a drag source it is automatically safe: TransferList arms a
966
+ * drag on any row pointerdown EXCEPT one originating inside a `button`, so the meter
967
+ * stays clickable in a row you can also pick up and drag, with no extra coordination
968
+ * between the two components.
969
+ *
970
+ * The popover is rendered as this component's own child rather than hoisted, which is
971
+ * what makes it survive a virtualized list: the anchor and the open state live and die
972
+ * together, so a row scrolled out of the mounted window takes its popover with it
973
+ * instead of leaving an orphan pinned to a dead element. While the row IS mounted, the
974
+ * popover tracks its anchor frame by frame (see usePopoverPosition), so it follows a row
975
+ * that slides to a new slot when the list is re-sorted underneath it.
976
+ */
977
+ declare function ScoreMeter({ score, segments, label, width, height, showScore, breakdown, breakdownLabel, placement, breakdownWidth, className, }: ScoreMeterProps): React.JSX.Element;
978
+
909
979
  interface SortMenuField {
910
980
  /** Sort key sent to the endpoint. */
911
981
  key: string;
@@ -980,17 +1050,29 @@ declare function StepList({ steps, label, defaultOpen, open: openProp, onToggle,
980
1050
  * (one per edge) plus the visible band itself is what keeps the mounted node count
981
1051
  * bounded — see the file doc comment below for why 20 is the right number here. */
982
1052
  declare const VIRTUAL_LIST_BUFFER_ROWS = 20;
1053
+ /** Default px of empty space left at the bottom of every row slot, which is what reads
1054
+ * as the gap between two cards. See VirtualListProps.rowGap. */
1055
+ declare const VIRTUAL_LIST_ROW_GAP = 6;
1056
+ /** Where the caller's loaded slice sits inside the full collection. `firstItemIndex` is
1057
+ * the absolute index of items[0]; `count` is how many are loaded. For a caller that
1058
+ * holds the whole collection this is simply { firstItemIndex: 0, count: items.length }. */
1059
+ interface VirtualLoadedBand {
1060
+ firstItemIndex: number;
1061
+ count: number;
1062
+ }
983
1063
  interface VirtualWindow {
984
- /** Index of the first row a viewport of this height would actually show. */
1064
+ /** ABSOLUTE index of the first row a viewport of this height would actually show. */
985
1065
  visibleStart: number;
986
- /** Index of the last row a viewport of this height would actually show (inclusive). */
1066
+ /** ABSOLUTE index of the last row a viewport of this height would actually show (inclusive). */
987
1067
  visibleEnd: number;
988
- /** Index of the first row that should be mounted in the DOM (visibleStart minus the buffer, clamped to 0). */
1068
+ /** ABSOLUTE index of the first row that should be mounted in the DOM (visibleStart minus
1069
+ * the buffer), clamped to the loaded band — a row the caller hasn't loaded can't mount. */
989
1070
  startIndex: number;
990
- /** Index of the last row that should be mounted in the DOM (inclusive; visibleEnd plus the buffer, clamped to itemCount - 1). */
1071
+ /** ABSOLUTE index of the last row that should be mounted in the DOM (inclusive). */
991
1072
  endIndex: number;
992
1073
  /** Full scrollable height in px — this is what makes the scrollbar/scroll range honest
993
- * about itemCount even though only a slice of it is ever mounted. */
1074
+ * about the whole collection even though only a slice of it is ever mounted, and it is
1075
+ * computed from the TOTAL count, never from how many rows happen to be loaded. */
994
1076
  totalHeight: number;
995
1077
  /** px the mounted block must be pushed down so its rows land at their real scroll position. */
996
1078
  offsetY: number;
@@ -1004,23 +1086,38 @@ interface VirtualWindow {
1004
1086
  * Every fixed-height virtualizer boils down to integer division: row i sits at
1005
1087
  * `i * itemHeight` px, so scrollTop/itemHeight lands on the first partially-visible row
1006
1088
  * and (scrollTop + viewportHeight)/itemHeight lands on the last one. Everything else here
1007
- * is clamping that to the buffer and to the real item count.
1089
+ * is clamping that to the buffer, to the real item count, and to whatever slice of it the
1090
+ * caller currently has in memory.
1091
+ *
1092
+ * Every index this returns is ABSOLUTE — an index into the whole collection, not into the
1093
+ * caller's loaded array. That distinction is the entire point of `loaded`: a caller that
1094
+ * keeps a sliding window (say 50 rows retained out of 4,000, discarding rows as it scrolls
1095
+ * rather than accumulating them) still gets row 3,214 drawn at 3,214 * itemHeight and a
1096
+ * scrollbar sized for 4,000 rows. Compute positions off array indices instead and the
1097
+ * whole list jumps every time the window slides.
1008
1098
  */
1009
1099
  declare function computeVirtualWindow(opts: {
1010
1100
  scrollTop: number;
1011
1101
  viewportHeight: number;
1012
1102
  itemHeight: number;
1103
+ /** TOTAL rows in the collection, loaded or not. */
1013
1104
  itemCount: number;
1105
+ /** Which slice of that total is actually in memory. Defaults to "all of it". */
1106
+ loaded?: VirtualLoadedBand;
1014
1107
  bufferRows?: number;
1015
1108
  }): VirtualWindow;
1016
1109
  /**
1017
- * Given the current window and the real item count, says whether the visible band has
1018
- * come within one buffer's width of either edge of what's currently loaded — the signal
1110
+ * Given the current window and the LOADED band, says whether the visible band has come
1111
+ * within one buffer's width of either edge of what's currently in memory — the signal
1019
1112
  * VirtualList uses to ask its caller for more. Threading it through the *visible* band
1020
1113
  * (not the mounted one) means the caller gets asked while the buffer still has rows left
1021
1114
  * to show, not only once the user has scrolled past all of them.
1115
+ *
1116
+ * Measured against the loaded band rather than the total, because "do I need to fetch"
1117
+ * is a question about memory, not about the collection: a caller holding rows 800-849 of
1118
+ * 4,000 needs more in BOTH directions even though it is nowhere near either end.
1022
1119
  */
1023
- declare function computeNeedMore(win: Pick<VirtualWindow, "visibleStart" | "visibleEnd">, itemCount: number, bufferRows?: number): {
1120
+ declare function computeNeedMore(win: Pick<VirtualWindow, "visibleStart" | "visibleEnd">, loaded: VirtualLoadedBand, bufferRows?: number): {
1024
1121
  start: boolean;
1025
1122
  end: boolean;
1026
1123
  };
@@ -1029,16 +1126,35 @@ interface VirtualListProps<T> {
1029
1126
  items: T[];
1030
1127
  /** Fixed row height in px. Every row is assumed to be exactly this tall. */
1031
1128
  itemHeight: number;
1129
+ /** px of the row's slot left empty at the bottom, which is what reads as the gap
1130
+ * between two cards. A real prop rather than a constant because the right gap depends
1131
+ * on how much the row itself weighs: a one-line 44px row wants the 6px default, a
1132
+ * two-line card wants more air. Note it comes OUT of itemHeight — the slot is still
1133
+ * exactly itemHeight tall, so the scroll math is untouched by changing this. */
1134
+ rowGap?: number;
1135
+ /** Called with the ABSOLUTE index of the row (its index in the whole collection), which
1136
+ * is not the same as its index in `items` once a sliding window is in play. */
1032
1137
  renderItem: (item: T, index: number) => React.ReactNode;
1033
- /** Fired when the visible band comes within one buffer's width of an edge of `items`
1034
- * and that edge isn't known to be exhausted. The caller owns fetching — this only asks. */
1035
- onNeedMore: (direction: "start" | "end") => void;
1138
+ /** Fired when the visible band comes within one buffer's width of an edge of what's
1139
+ * loaded and that edge isn't known to be exhausted. The caller owns fetching — this
1140
+ * only asks. The window is handed over too so a caller running a sliding window can
1141
+ * answer a big scrollbar jump (visible band nowhere near the loaded band) by fetching
1142
+ * around `win.visibleStart` instead of stepping a page at a time towards it. */
1143
+ onNeedMore: (direction: "start" | "end", win: VirtualWindow) => void;
1036
1144
  /** Which edges of `items` still have more behind them. Omitted/undefined reads as "yes,
1037
1145
  * there might be more" for that edge, since an unknown edge is the common starting state. */
1038
1146
  hasMore?: {
1039
1147
  start?: boolean;
1040
1148
  end?: boolean;
1041
1149
  };
1150
+ /** Absolute index of `items[0]` in the full collection. Non-zero when the caller keeps a
1151
+ * sliding window and has discarded the rows above. */
1152
+ firstItemIndex?: number;
1153
+ /** Size of the WHOLE collection, when the caller knows it and is holding only a slice.
1154
+ * Defaults to firstItemIndex + items.length, i.e. "what's loaded is all there is". This
1155
+ * is what the scrollbar is sized against, so a caller that discards rows as it scrolls
1156
+ * gets a scroll range that stays still instead of shrinking under the user. */
1157
+ totalCount?: number;
1042
1158
  /** Suppresses onNeedMore while a fetch the caller already started is in flight. Drives the
1043
1159
  * small pinned spinner at the bottom of the list — rows already mounted stay exactly as
1044
1160
  * they are, since this only ever means "more is being appended," never "what's on screen
@@ -1084,11 +1200,20 @@ interface VirtualListProps<T> {
1084
1200
  * next onScroll fires, without ever mounting more than a small bounded slice regardless
1085
1201
  * of how many thousand rows `items` holds.
1086
1202
  *
1203
+ * `items` does not have to be the whole collection, or even start at the top of it.
1204
+ * `firstItemIndex`/`totalCount` let the caller hand over a SLIDING WINDOW — say 50 rows
1205
+ * retained out of 4,000, fetching ten at a time in either direction and DISCARDING the
1206
+ * rows that fall out the other side. Every position here is computed from the absolute
1207
+ * index, so discarding rows above the viewport doesn't move the rows below it, and the
1208
+ * scrollbar stays sized to `totalCount` rather than shrinking to whatever is still in
1209
+ * memory. Keeping a window is strictly the caller's business — this component never
1210
+ * decides what to drop, it only refuses to assume that `items[0]` is row zero.
1211
+ *
1087
1212
  * Fully controlled: this holds no item data and no page state, only the scroll position
1088
1213
  * needed to compute the window. The caller owns fetching more rows (onNeedMore) and
1089
1214
  * knowing when an edge is exhausted (hasMore).
1090
1215
  */
1091
- declare function VirtualList<T>({ items, itemHeight, renderItem, onNeedMore, hasMore, loading, refreshing, refreshingLabel, keyOf, height, emptyState, className, style, }: VirtualListProps<T>): React.JSX.Element;
1216
+ declare function VirtualList<T>({ items, itemHeight, rowGap, renderItem, onNeedMore, hasMore, firstItemIndex, totalCount, loading, refreshing, refreshingLabel, keyOf, height, emptyState, className, style, }: VirtualListProps<T>): React.JSX.Element;
1092
1217
 
1093
1218
  interface EntityRowAction {
1094
1219
  /** Phosphor icon name, e.g. "plus" */
@@ -1100,11 +1225,54 @@ interface EntityRowAction {
1100
1225
  * (danger-text). No effect on layout, only color, so a caller can still pass any icon. */
1101
1226
  tone?: "add" | "remove";
1102
1227
  }
1103
- interface EntityRowProps {
1228
+ /** Width in px a metric cell takes when it doesn't ask for its own. Wide enough for a
1229
+ * five-digit grouped number plus its glyph at --overline-size. */
1230
+ declare const ENTITY_ROW_METRIC_WIDTH = 74;
1231
+ interface EntityRowMetric {
1232
+ /** Stable identity for the column. Falls back to `label`, which is what actually has
1233
+ * to match between rows for a column to line up, so an id is only needed when two
1234
+ * columns legitimately share a label. */
1235
+ id?: string;
1236
+ /** What the number means — "students", "ad units". Never rendered as running text:
1237
+ * it is the metric's accessible name (and its tooltip), so the row stays scannable
1238
+ * as numbers while a screen reader still hears "1,240 students". */
1239
+ label: string;
1240
+ value: React.ReactNode;
1241
+ /** Phosphor icon name shown before the value, e.g. "student". */
1242
+ icon?: string;
1243
+ /** Fixed cell width in px. Same value on the same column of every row is what makes
1244
+ * the strip a real aligned column rather than text that happens to sit near text. */
1245
+ width?: number;
1246
+ tone?: "default" | "muted" | "danger";
1247
+ }
1248
+ /**
1249
+ * Everything about a row that is CONTENT rather than behaviour — what the row says, as
1250
+ * opposed to what it does. Split out from EntityRowProps as its own type on purpose: a
1251
+ * caller that renders the same row twice (TransferList renders every row a second time
1252
+ * as the drag ghost) hands one of these around whole, so a prop added here can't be
1253
+ * silently dropped by the second render site the way individually-spread props were.
1254
+ */
1255
+ interface EntityRowContent {
1104
1256
  title: string;
1105
1257
  /** A secondary line — a count, a subtitle, whatever the caller's data has that a raw
1106
- * title doesn't. Optional because plenty of rows are fine with just a name. */
1258
+ * title doesn't. Optional because plenty of rows are fine with just a name. Sits
1259
+ * inline after the title; for numbers that must line up row-to-row use `metrics`. */
1107
1260
  meta?: React.ReactNode;
1261
+ /** An aligned strip of small numbers below the title — fixed-width cells, so the same
1262
+ * column lands at the same x on every row. Rendering more than one number through
1263
+ * `meta` instead would produce a ragged line that happens to contain numbers. */
1264
+ metrics?: EntityRowMetric[];
1265
+ /** A small interactive element pinned to the end of the metric strip — the row's
1266
+ * score meter, a menu, a flag. Anything focusable belongs in a real `<button>`: the
1267
+ * row is a drag source, and TransferList's drag arming explicitly skips any
1268
+ * pointerdown originating inside a button, which is what keeps this clickable. */
1269
+ accessory?: React.ReactNode;
1270
+ /** Whole-row state colour. "danger" repaints fill, border and title (e.g. a school
1271
+ * with no sellable inventory) — the tone tokens are --erow-danger-*, defined for
1272
+ * both themes. Default is the ordinary card treatment. */
1273
+ tone?: "default" | "danger";
1274
+ }
1275
+ interface EntityRowProps extends EntityRowContent {
1108
1276
  action?: EntityRowAction;
1109
1277
  /** Marks the row a drag source: shows the grip icon and a grab cursor, and reports
1110
1278
  * `onPointerDown` so the caller's own pointer-based drag (see TransferList) can pick
@@ -1115,9 +1283,22 @@ interface EntityRowProps {
1115
1283
  style?: React.CSSProperties;
1116
1284
  className?: string;
1117
1285
  }
1286
+ interface EntityRowMetricsProps {
1287
+ metrics: EntityRowMetric[];
1288
+ className?: string;
1289
+ style?: React.CSSProperties;
1290
+ }
1291
+ /**
1292
+ * The aligned number strip, on its own so the SAME columns can be rendered outside a
1293
+ * row — TransferList's per-side footer totals are literally this component with the
1294
+ * same widths, which is the only way a totals line can stay lined up with the rows it
1295
+ * totals without the two places agreeing on magic numbers by hand.
1296
+ */
1297
+ declare function EntityRowMetrics({ metrics, className, style }: EntityRowMetricsProps): React.JSX.Element;
1118
1298
  /**
1119
- * EntityRow — a single row for a name plus an optional secondary line and an optional
1120
- * action, meant to be stamped out by the hundreds inside a VirtualList.
1299
+ * EntityRow — a single row for a name plus an optional aligned strip of numbers, an
1300
+ * optional accessory and an optional action, meant to be stamped out by the hundreds
1301
+ * inside a VirtualList.
1121
1302
  *
1122
1303
  * A real card per row, not flat text: its own surface distinct from the list's
1123
1304
  * background (`--surface-2` against the list's `--surface`), a hairline border, a soft
@@ -1127,7 +1308,14 @@ interface EntityRowProps {
1127
1308
  * still left to the caller (it fills 100% of its parent) because VirtualList is what
1128
1309
  * actually fixes row height, and duplicating that number here would be one more place a
1129
1310
  * future change has to remember to update — the card treatment lives entirely in
1130
- * padding/border/shadow, not height.
1311
+ * padding/border/shadow, not height. The border is --erow-border rather than --border
1312
+ * for a measured reason: the card and the panel behind it are 1.06:1 apart in light
1313
+ * mode, so the inter-card gap is only visible if the hairline carries it.
1314
+ *
1315
+ * The row lays out as one or two lines depending on what it's given: title (plus inline
1316
+ * `meta`) always, and a second line holding the metric strip and accessory only when
1317
+ * there is one. That keeps a plain name-only row exactly as tall and as quiet as it
1318
+ * always was while letting a richer row carry three numbers and a meter.
1131
1319
  *
1132
1320
  * The drag/drop split is deliberate: a row only ever needs to say "I am the thing being
1133
1321
  * dragged" (draggable + onPointerDown), never "something was dropped on me" — nothing in
@@ -1144,7 +1332,7 @@ interface EntityRowProps {
1144
1332
  * same move — pointer-only and keyboard users get an equally real affordance, not a
1145
1333
  * degraded fallback.
1146
1334
  */
1147
- declare function EntityRow({ title, meta, action, draggable, onPointerDown, style, className }: EntityRowProps): React.JSX.Element;
1335
+ declare function EntityRow({ title, meta, metrics, accessory, tone, action, draggable, onPointerDown, style, className, }: EntityRowProps): React.JSX.Element;
1148
1336
 
1149
1337
  type TransferListSide = "left" | "right";
1150
1338
  interface TransferListSideProps<T> {
@@ -1166,14 +1354,28 @@ interface TransferListSideProps<T> {
1166
1354
  } | null;
1167
1355
  sortFields: SortMenuField[];
1168
1356
  onSort: (key: string, dir: "asc" | "desc") => void;
1169
- onNeedMore: (direction: "start" | "end") => void;
1357
+ onNeedMore: (direction: "start" | "end", win: VirtualWindow) => void;
1170
1358
  hasMore?: {
1171
1359
  start?: boolean;
1172
1360
  end?: boolean;
1173
1361
  };
1362
+ /** Absolute index of `items[0]` in this side's full collection, and the size of that
1363
+ * collection — threaded straight through to this side's VirtualList so each side can
1364
+ * independently run a sliding window. See VirtualList's own docs. */
1365
+ firstItemIndex?: number;
1366
+ totalCount?: number;
1174
1367
  /** Heading text for this side. Fully caller-supplied — this component has no opinion
1175
- * about what the two sides represent. */
1368
+ * about what the two sides represent. Stays a plain string because it is also woven
1369
+ * into the search field's and the move action's accessible names; anything richer
1370
+ * than a name belongs in `header`. */
1176
1371
  label?: string;
1372
+ /** A slot above the search row, under the label. */
1373
+ header?: React.ReactNode;
1374
+ /** A slot below the list — where a running totals line for this side goes. A real slot
1375
+ * rather than more `label`, because totals are structured content (typically an
1376
+ * EntityRowMetrics strip using the same widths as the rows, so the columns line up
1377
+ * with the numbers they total) and `label` is a string that gets concatenated. */
1378
+ footer?: React.ReactNode;
1177
1379
  /** Copy shown in the full-panel drop overlay while a drag is over this side. Falls
1178
1380
  * back to a generic "Drop here" (plus `label`, if set) when omitted — set this for
1179
1381
  * exact per-side wording (e.g. "Drop to add to audience" / "Drop to remove from
@@ -1185,15 +1387,28 @@ interface TransferListProps<T> {
1185
1387
  left: TransferListSideProps<T>;
1186
1388
  right: TransferListSideProps<T>;
1187
1389
  keyOf: (item: T) => string | number;
1188
- renderLabel: (item: T) => string;
1189
- renderMeta?: (item: T) => React.ReactNode;
1390
+ /** Everything a row SAYS, as one object — title, meta, the aligned metric strip, the
1391
+ * accessory, the row tone. One render prop returning one EntityRowContent rather than
1392
+ * a render prop per field, because a dragged row is rendered a SECOND time as the
1393
+ * ghost that follows the cursor: with per-field props that second call has to
1394
+ * remember to pass each of them, and the version of this component that did exactly
1395
+ * that quietly dropped every field but title and meta off the dragged card. Handing
1396
+ * the same object to both render sites makes that class of bug unrepresentable. */
1397
+ renderRow: (item: T) => EntityRowContent;
1190
1398
  /** Fired by both a drag-drop and the equivalent +/- click. The caller owns actually
1191
1399
  * moving the item between whatever backing lists it maintains — this component never
1192
1400
  * mutates `left.items`/`right.items` itself. */
1193
1401
  onMove: (item: T, from: TransferListSide, to: TransferListSide) => void;
1194
- /** Row height fed straight through to each side's VirtualList. Default (44) matches
1195
- * the kit's --control-h-md, since that's the height EntityRow is tuned to look right at. */
1402
+ /** Row height fed straight through to each side's VirtualList. Default (64) fits the
1403
+ * two-line card an EntityRow becomes once it carries a metric strip and an accessory:
1404
+ * a 56px card (title line + strip line, 10px of vertical padding) plus the 8px gap.
1405
+ * A list of bare title-only rows should pass 44 (the kit's --control-h-md, which is
1406
+ * what EntityRow's single-line form is tuned to) with a matching smaller rowGap. */
1196
1407
  itemHeight?: number;
1408
+ /** px of each row slot left empty, which is what draws the gap between two cards.
1409
+ * Default 8 — see the --erow-border token note for why a card on this kit's surfaces
1410
+ * needs both a real gap and a real hairline before the separation is visible. */
1411
+ rowGap?: number;
1197
1412
  /** Viewport height fed straight through to each side's VirtualList. */
1198
1413
  listHeight?: number;
1199
1414
  className?: string;
@@ -1229,12 +1444,19 @@ interface TransferListProps<T> {
1229
1444
  * it is the same onMove call a drop makes, exposed as a first-class equivalent, because
1230
1445
  * drag-and-drop alone would silently exclude anyone not using a mouse.
1231
1446
  *
1447
+ * Each side owns a `header` and a `footer` slot in addition to its `label`. `label` stays
1448
+ * a plain string because it is also woven into that side's search field and move-action
1449
+ * accessible names and gets a count concatenated onto it; the slots are where real
1450
+ * content goes — a running totals line in the footer, rendered with EntityRowMetrics at
1451
+ * the same column widths the rows use, so the totals sit under the numbers they total.
1452
+ *
1232
1453
  * Fully controlled and stateless about the actual item sets: `left`/`right` are handed in
1233
1454
  * whole, this never copies or reorders them, and the only state kept locally is which row
1234
1455
  * is mid-drag and which side a drag is currently over — pure interaction feedback, thrown
1235
- * away the moment the drag ends.
1456
+ * away the moment the drag ends. Each side may independently be a sliding window over a
1457
+ * much larger collection (`firstItemIndex`/`totalCount`, threaded to its VirtualList).
1236
1458
  */
1237
- declare function TransferList<T>({ left, right, keyOf, renderLabel, renderMeta, onMove, itemHeight, listHeight, className, }: TransferListProps<T>): React.JSX.Element;
1459
+ declare function TransferList<T>({ left, right, keyOf, renderRow, onMove, itemHeight, rowGap, listHeight, className, }: TransferListProps<T>): React.JSX.Element;
1238
1460
 
1239
1461
  /**
1240
1462
  * Multi-select control with a 44px comfortable hit area.
@@ -2283,6 +2505,120 @@ interface SurroundSoundProps {
2283
2505
  }
2284
2506
  declare function SurroundSound({ present, bonusPct, bonusMax, missedCost, className }: SurroundSoundProps): React.JSX.Element;
2285
2507
 
2508
+ type ImportanceLevelId = "nudge" | "lean" | "priority" | "driver" | "dealbreaker";
2509
+ interface ImportanceLevel {
2510
+ id: ImportanceLevelId;
2511
+ /** 1-5, ascending. This is the number the --imp-N-* tokens are keyed by. */
2512
+ rank: 1 | 2 | 3 | 4 | 5;
2513
+ /** How the level is named in the UI. */
2514
+ label: string;
2515
+ /** Phosphor icon name. */
2516
+ icon: string;
2517
+ /** One line saying what choosing this level actually does to a plan. */
2518
+ description: string;
2519
+ }
2520
+ /**
2521
+ * How much a single criterion is allowed to matter, as five ordered steps. Ordered
2522
+ * ascending, and index-stable: the position in this array IS the `rank`, which is what
2523
+ * the --imp-N-* colour tokens are keyed by, so reordering it would silently re-colour
2524
+ * every criterion in the product.
2525
+ *
2526
+ * The ramp runs slate → blue → indigo → amber → red, i.e. from "barely registers" to
2527
+ * "nothing else matters". It deliberately crosses from the neutral/interaction end of
2528
+ * the palette into the warning end, because that IS the semantic gradient: a
2529
+ * Deal-breaker isn't a stronger preference, it's a veto, and it should look like one.
2530
+ */
2531
+ declare const IMPORTANCE_LEVELS: ImportanceLevel[];
2532
+ /** The level record for an id, falling back to the lowest step for an unknown one — an
2533
+ * unrecognised level should read as "barely matters", never throw in a list row. */
2534
+ declare function importanceLevel(id: ImportanceLevelId | string): ImportanceLevel;
2535
+ interface ImportanceTokens {
2536
+ /** Background wash for a criterion card at this level. */
2537
+ fill: string;
2538
+ /** Border for that card. */
2539
+ edge: string;
2540
+ /** AA-safe text colour ON `fill`. */
2541
+ text: string;
2542
+ /** The solid — meter segments, glyphs, dots. Clears 3:1 on the meter rail. */
2543
+ mark: string;
2544
+ }
2545
+ /**
2546
+ * The four `var(--imp-N-*)` references for a level, as a plain object, so a criterion
2547
+ * card elsewhere in a consuming app tints itself from exactly the same tokens the meter
2548
+ * segments are drawn in rather than from a second hand-copied list of hexes. Returns the
2549
+ * var() references, not resolved colours — the whole point is that the theme resolves
2550
+ * them, and every one of these is defined for both light and dark ground.
2551
+ */
2552
+ declare function importanceTokens(id: ImportanceLevelId | string): ImportanceTokens;
2553
+ interface ImportanceTagProps {
2554
+ level: ImportanceLevelId | string;
2555
+ /** Show the level's name next to its glyph. Off for a bare glyph in tight spots. */
2556
+ showLabel?: boolean;
2557
+ size?: "sm" | "md";
2558
+ className?: string;
2559
+ }
2560
+ /**
2561
+ * The level itself, as a small tinted tag — glyph plus name in that level's own fill,
2562
+ * edge and text tokens. Never colour alone: the name and the icon both carry the level,
2563
+ * so the five steps are still distinguishable without colour vision.
2564
+ */
2565
+ declare function ImportanceTag({ level, showLabel, size, className }: ImportanceTagProps): React.JSX.Element;
2566
+
2567
+ interface PreferenceCriterion {
2568
+ id: string;
2569
+ /** What the planner asked for — "Within 50 miles", "Enrollment over 10,000". */
2570
+ label: string;
2571
+ importance: ImportanceLevelId | string;
2572
+ /** The most this criterion could contribute to a 100-point score, i.e. what it is
2573
+ * worth given its importance relative to the other criteria. */
2574
+ weight: number;
2575
+ /** What it actually contributed for this option, 0..weight. A partially-satisfied
2576
+ * criterion is allowed — this is not a pass/fail flag. */
2577
+ earned: number;
2578
+ /** Optional plain-language reason shown under the criterion in the breakdown, e.g.
2579
+ * "31 miles" or "no outdoor inventory". */
2580
+ note?: React.ReactNode;
2581
+ }
2582
+ /** The score a set of criteria adds up to, rounded to a whole 0-100. Exported because a
2583
+ * consumer usually needs the same number for sorting the list as for drawing the meter,
2584
+ * and two implementations of that would drift. */
2585
+ declare function preferenceScore(criteria: PreferenceCriterion[]): number;
2586
+ /** One meter segment per criterion, each sized by what that criterion actually earned
2587
+ * (as a share of 100) and coloured by its importance level's `mark` token. */
2588
+ declare function preferenceSegments(criteria: PreferenceCriterion[]): MeterSegmentInput[];
2589
+ interface PreferenceMeterProps {
2590
+ criteria: PreferenceCriterion[];
2591
+ /** Overrides the score derived from `criteria`. Pass it when the score is computed
2592
+ * server-side and the criteria are only the explanation of it. */
2593
+ score?: number;
2594
+ /** Accessible name. Defaults to "Preference score". */
2595
+ label?: string;
2596
+ width?: number;
2597
+ height?: number;
2598
+ showScore?: boolean;
2599
+ placement?: Placement;
2600
+ /** Heading shown above the breakdown list. */
2601
+ breakdownTitle?: string;
2602
+ className?: string;
2603
+ }
2604
+ /**
2605
+ * PreferenceMeter — how well one option matches the planner's stated priorities, drawn
2606
+ * as one segment per criterion coloured by that criterion's importance.
2607
+ *
2608
+ * The encoding is the point, and it is not "how high did this score". A plain 0-100 bar
2609
+ * says a school scored 72; this says WHICH of the planner's priorities it satisfies — a
2610
+ * bar that is mostly red-and-amber is meeting the deal-breakers and drivers, a bar of
2611
+ * the same length that is mostly slate is scraping the same number out of things the
2612
+ * planner said barely matter. Two schools with identical scores are visibly different
2613
+ * propositions, at a glance, in a list row.
2614
+ *
2615
+ * Segment width is what the criterion EARNED, not what it was worth, so the unfilled
2616
+ * remainder of the rail is exactly what the option is failing to deliver. Clicking opens
2617
+ * the breakdown: every criterion, its level, what it earned out of its weight, and the
2618
+ * caller's own note explaining why.
2619
+ */
2620
+ declare function PreferenceMeter({ criteria, score, label, width, height, showScore, placement, breakdownTitle, className, }: PreferenceMeterProps): React.JSX.Element;
2621
+
2286
2622
  /**
2287
2623
  * Chat state machine. Framework-shaped as a React hook, but the rules it encodes
2288
2624
  * are the portable part: thread resolution, serial sending, escalation polling,
@@ -3109,4 +3445,4 @@ interface VersionStore<TState, TDiff = unknown> {
3109
3445
  }
3110
3446
  declare function createVersionStore<TState, TDiff = unknown>(initialState: TState, options?: CreateVersionStoreOptions<TState, TDiff>): VersionStore<TState, TDiff>;
3111
3447
 
3112
- export { AccountMenu, type AccountMenuLink, type AccountMenuProps, AgentChatPanel, type AgentChatPanelProps, ApiSpecBrowser, type ApiSpecBrowserProps, type Attachment, Avatar, type AvatarProps, AvatarStack, type AvatarStackProps, Badge, type BadgeProps, BudgetReallocator, type BudgetReallocatorProps, Button, type ButtonProps, CHANNELS, CHANNEL_WEIGHTS, CHAT_UNAVAILABLE, Calendar, type CalendarProps, Card, type CardProps, CardRow, type CardRowProps, ChannelContribution, type ChannelContributionChannel, type ChannelContributionProps, ChannelMeta, type ChannelMetaRecord, ChannelTag, type ChannelTagProps, ChannelWeightOf, type ChatAdapter, ChatComposer, type ChatComposerProps, type ChatEngine, type ChatEngineOptions, ChatKit, type ChatMessage, type ChatRenderContext, ChatSessionBar, type ChatSessionBarProps, ChatTranscript, type ChatTranscriptProps, ChatTurn, type ChatTurnProps, Checkbox, type CheckboxProps, type Citation, Citations, type CitationsProps, Clamp, type ClampProps, ClockFace, type ClockFaceProps, CodeBlock, type CodeBlockProps, Collapsible, type CollapsibleProps, ComingSoon, type ComingSoonProps, type ContextUsage, type CreateVersionStoreOptions, CsiBadge, type CsiBadgeProps, CsiHero, type CsiHeroProps, CsiMeter, type CsiMeterProps, DataTable, type DataTableColumn, type DataTableProps, DatePicker, type DatePickerProps, type DeviceSession, DiffBlock, type DiffBlockProps, Drawer, type DrawerProps, Dropzone, DropzoneKit, type DropzoneProps, type EditorTrigger, EmptyState, type EmptyStateProps, type EndpointSpec, EntityRow, type EntityRowAction, type EntityRowProps, FeatureGate, type FeatureGateProps, type FeatureStatus, FileChip, type FileChipProps, FileGrid, type FileGridProps, FileKit, FilePickButton, type FilePickButtonProps, type FileRejection, FileStrip, type FileStripProps, FileTile, type FileTileProps, type FileUploadHandler, FilterBar, type FilterBarProps, type FilterField, type FilterFieldOption, type FilterFieldSelect, type FilterFieldText, Flag$1 as Flag, type FlagExplanation, type FlagProps, FlytedeskBrand, type FlytedeskBrandProps, FlytedeskMark, type FlytedeskMarkProps, FormatEta, Gate, type GateProps, IconButton, type IconButtonProps, Input, type InputProps, type JobStatus, Json, KeyHint, type KeyHintProps, type ListEnvelope, LoadingRegion, type LoadingRegionProps, Markdown, MarkdownEditor, type MarkdownEditorHandle, type MarkdownEditorProps, MarkdownInline, type MarkdownProps, type MentionSource, Menu, MenuButton, type MenuButtonProps, type MenuItem, type MenuProps, MessageBody, type MessageBodyProps, type MessageGroup, Meter, type MeterProps, type MeterSegment, type MeterSegmentInput, MixGap, type MixGapProps, type MixGapRow, MockJobKit, type MockJobStatus, Modal, type ModalProps, ModeSwitch, type ModeSwitchProps, type Model, ModelControls, type ModelControlsProps, NumberInput, type NumberInputProps, type Packet, PacketCard, type PacketCardProps, type PacketSchema, type Pager, Pagination, type PaginationProps, PermissionDenied, type PermissionDeniedProps, type PermissionGroup, PermissionHint, type PermissionItem, type Placement, type PollOptions, Popover, type PopoverProps, ProfilePage, type ProfilePageProps, ProgressBar, type ProgressBarProps, QueryKit, type QueuedTurn, QuotaRow, type QuotaRowProps, Radio, type RadioProps, RangeSlider, type RangeSliderProps, RelativeTime, type RelativeTimeProps, type RoadmapItem, type RoadmapResult, RoadmapTimeline, type RoadmapTimelineProps, type Role, RunActions, type RunActionsProps, type RunConfig, RuntimeKit, type RuntimeSummary, SaturationDistribution, type SaturationDistributionCampus, type SaturationDistributionProps, SearchField, type SearchFieldProps, SegmentedControl, type SegmentedControlProps, SegmentedMeter, type SegmentedMeterProps, Select, type SelectOption, type SelectProps, type SendMessageResult, type ServerTable, type Session, type Flag as SessionFlag, SessionKit, type SessionStats, SidebarNav, type SidebarNavItem, type SidebarNavProps, Skeleton, type SkeletonProps, SkeletonText, type SkeletonTextProps, type SlashCommand, Slider, type SliderProps, SortMenu, type SortMenuField, type SortMenuProps, type SpecModule, Spinner, type SpinnerProps, StatTile, type StatTileProps, type Step, StepList, type StepListProps, type StreamHandlers, type Suggestion, SurroundSound, type SurroundSoundProps, Switch, type SwitchProps, type TableQuery, Tabs, type TabsProps, Tag, type TagProps, TestModeBar, type TestModeBarProps, Textarea, type TextareaProps, ThinkingBlock, type ThinkingBlockProps, type ThreadSummary, TimePicker, type TimePickerProps, Toast, type ToastProps, Tooltip, type TooltipProps, Topbar, type TopbarProps, TranscriptKit, TransferList, type TransferListProps, type TransferListSide, type TransferListSideProps, type UsageLimit, UseFeatureStatus, UseRuntimeMode, type UseServerTableOptions, type User, VIRTUAL_LIST_BUFFER_ROWS, type VersionRecord, type VersionStore, VirtualList, type VirtualListProps, type VirtualWindow, type VoiceHandler, acceptMatches, anyOfFilter, channelWeightOf, computeNeedMore, computeVirtualWindow, createVersionStore, eqFilter, extensionOf, extractClipboardFiles, filterFiles, formatAbsolute, formatBytes, formatClock, formatDuration, formatEta, formatRelative, groupMessages, hasModifier, iconForMime, isImage, isSystemMessage, jobBucket, languageLabel, markdownToText, meterFormats, modifierLabel, pastedTextName, rangeFilter, revokeAttachment, toAttachment, tokenize, useChatEngine, useFeatureStatus, usePopoverPosition, useRuntimeMode, useServerTable, useStagedFiles, visibleMessages };
3448
+ export { AccountMenu, type AccountMenuLink, type AccountMenuProps, AgentChatPanel, type AgentChatPanelProps, ApiSpecBrowser, type ApiSpecBrowserProps, type Attachment, Avatar, type AvatarProps, AvatarStack, type AvatarStackProps, Badge, type BadgeProps, BudgetReallocator, type BudgetReallocatorProps, Button, type ButtonProps, CHANNELS, CHANNEL_WEIGHTS, CHAT_UNAVAILABLE, Calendar, type CalendarProps, Card, type CardProps, CardRow, type CardRowProps, ChannelContribution, type ChannelContributionChannel, type ChannelContributionProps, ChannelMeta, type ChannelMetaRecord, ChannelTag, type ChannelTagProps, ChannelWeightOf, type ChatAdapter, ChatComposer, type ChatComposerProps, type ChatEngine, type ChatEngineOptions, ChatKit, type ChatMessage, type ChatRenderContext, ChatSessionBar, type ChatSessionBarProps, ChatTranscript, type ChatTranscriptProps, ChatTurn, type ChatTurnProps, Checkbox, type CheckboxProps, type Citation, Citations, type CitationsProps, Clamp, type ClampProps, ClockFace, type ClockFaceProps, CodeBlock, type CodeBlockProps, Collapsible, type CollapsibleProps, ComingSoon, type ComingSoonProps, type ContextUsage, type CreateVersionStoreOptions, CsiBadge, type CsiBadgeProps, CsiHero, type CsiHeroProps, CsiMeter, type CsiMeterProps, DataTable, type DataTableColumn, type DataTableProps, DatePicker, type DatePickerProps, type DeviceSession, DiffBlock, type DiffBlockProps, Drawer, type DrawerProps, Dropzone, DropzoneKit, type DropzoneProps, ENTITY_ROW_METRIC_WIDTH, type EditorTrigger, EmptyState, type EmptyStateProps, type EndpointSpec, EntityRow, type EntityRowAction, type EntityRowContent, type EntityRowMetric, EntityRowMetrics, type EntityRowMetricsProps, type EntityRowProps, FeatureGate, type FeatureGateProps, type FeatureStatus, FileChip, type FileChipProps, FileGrid, type FileGridProps, FileKit, FilePickButton, type FilePickButtonProps, type FileRejection, FileStrip, type FileStripProps, FileTile, type FileTileProps, type FileUploadHandler, FilterBar, type FilterBarProps, type FilterField, type FilterFieldOption, type FilterFieldSelect, type FilterFieldText, Flag$1 as Flag, type FlagExplanation, type FlagProps, FlytedeskBrand, type FlytedeskBrandProps, FlytedeskMark, type FlytedeskMarkProps, FormatEta, Gate, type GateProps, IMPORTANCE_LEVELS, IconButton, type IconButtonProps, type ImportanceLevel, type ImportanceLevelId, ImportanceTag, type ImportanceTagProps, type ImportanceTokens, Input, type InputProps, type JobStatus, Json, KeyHint, type KeyHintProps, type ListEnvelope, LoadingRegion, type LoadingRegionProps, Markdown, MarkdownEditor, type MarkdownEditorHandle, type MarkdownEditorProps, MarkdownInline, type MarkdownProps, type MentionSource, Menu, MenuButton, type MenuButtonProps, type MenuItem, type MenuProps, MessageBody, type MessageBodyProps, type MessageGroup, Meter, type MeterProps, type MeterSegment, type MeterSegmentInput, MixGap, type MixGapProps, type MixGapRow, MockJobKit, type MockJobStatus, Modal, type ModalProps, ModeSwitch, type ModeSwitchProps, type Model, ModelControls, type ModelControlsProps, NumberInput, type NumberInputProps, type Packet, PacketCard, type PacketCardProps, type PacketSchema, type Pager, Pagination, type PaginationProps, PermissionDenied, type PermissionDeniedProps, type PermissionGroup, PermissionHint, type PermissionItem, type Placement, type PollOptions, Popover, type PopoverProps, type PreferenceCriterion, PreferenceMeter, type PreferenceMeterProps, ProfilePage, type ProfilePageProps, ProgressBar, type ProgressBarProps, QueryKit, type QueuedTurn, QuotaRow, type QuotaRowProps, Radio, type RadioProps, RangeSlider, type RangeSliderProps, RelativeTime, type RelativeTimeProps, type RoadmapItem, type RoadmapResult, RoadmapTimeline, type RoadmapTimelineProps, type Role, RunActions, type RunActionsProps, type RunConfig, RuntimeKit, type RuntimeSummary, SaturationDistribution, type SaturationDistributionCampus, type SaturationDistributionProps, ScoreMeter, type ScoreMeterProps, SearchField, type SearchFieldProps, SegmentedControl, type SegmentedControlProps, SegmentedMeter, type SegmentedMeterProps, Select, type SelectOption, type SelectProps, type SendMessageResult, type ServerTable, type Session, type Flag as SessionFlag, SessionKit, type SessionStats, SidebarNav, type SidebarNavItem, type SidebarNavProps, Skeleton, type SkeletonProps, SkeletonText, type SkeletonTextProps, type SlashCommand, Slider, type SliderProps, SortMenu, type SortMenuField, type SortMenuProps, type SpecModule, Spinner, type SpinnerProps, StatTile, type StatTileProps, type Step, StepList, type StepListProps, type StreamHandlers, type Suggestion, SurroundSound, type SurroundSoundProps, Switch, type SwitchProps, type TableQuery, Tabs, type TabsProps, Tag, type TagProps, TestModeBar, type TestModeBarProps, Textarea, type TextareaProps, ThinkingBlock, type ThinkingBlockProps, type ThreadSummary, TimePicker, type TimePickerProps, Toast, type ToastProps, Tooltip, type TooltipProps, Topbar, type TopbarProps, TranscriptKit, TransferList, type TransferListProps, type TransferListSide, type TransferListSideProps, type UsageLimit, UseFeatureStatus, UseRuntimeMode, type UseServerTableOptions, type User, VIRTUAL_LIST_BUFFER_ROWS, VIRTUAL_LIST_ROW_GAP, type VersionRecord, type VersionStore, VirtualList, type VirtualListProps, type VirtualLoadedBand, type VirtualWindow, type VoiceHandler, acceptMatches, anyOfFilter, channelWeightOf, computeNeedMore, computeVirtualWindow, createVersionStore, eqFilter, extensionOf, extractClipboardFiles, filterFiles, formatAbsolute, formatBytes, formatClock, formatDuration, formatEta, formatRelative, groupMessages, hasModifier, iconForMime, importanceLevel, importanceTokens, isImage, isSystemMessage, jobBucket, languageLabel, markdownToText, meterFormats, modifierLabel, pastedTextName, preferenceScore, preferenceSegments, rangeFilter, revokeAttachment, toAttachment, tokenize, useChatEngine, useFeatureStatus, usePopoverPosition, useRuntimeMode, useServerTable, useStagedFiles, visibleMessages };