@crispy-seed/data-table 0.10.5 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -18,6 +18,9 @@ import { Checkbox, useCheckboxContext } from '@seed-design/react-checkbox';
18
18
  import { useScaleFeedback } from '@seed-design/react-scale-feedback';
19
19
  import { checkboxGroup } from '@seed-design/css/recipes/checkbox-group';
20
20
  import { createPortal } from 'react-dom';
21
+ import { Menu, useMenuContext, useMenuItemContext } from '@seed-design/react-menu';
22
+ import { menu } from '@seed-design/css/recipes/menu';
23
+ import { menuItem } from '@seed-design/css/recipes/menu-item';
21
24
  import { Image } from '@seed-design/react-image';
22
25
  import { avatar } from '@seed-design/css/recipes/avatar';
23
26
  import '@seed-design/css/recipes/avatar-stack';
@@ -32,9 +35,6 @@ import { switchStyle } from '@seed-design/css/recipes/switch';
32
35
  import { switchmark } from '@seed-design/css/recipes/switchmark';
33
36
  import { TextField, useTextFieldContext } from '@seed-design/react-text-field';
34
37
  import { textInput } from '@seed-design/css/recipes/text-input';
35
- import { Menu, useMenuContext, useMenuItemContext } from '@seed-design/react-menu';
36
- import { menu } from '@seed-design/css/recipes/menu';
37
- import { menuItem } from '@seed-design/css/recipes/menu-item';
38
38
  import { tabs } from '@seed-design/css/recipes/tabs';
39
39
 
40
40
  // src/IconArrowUpLine.tsx
@@ -761,7 +761,10 @@ function isFunction$1(value) {
761
761
  const DATA_TABLE_UNIVERSAL_CALC_OPS = [
762
762
  "count",
763
763
  "empty",
764
- "notEmpty"
764
+ "notEmpty",
765
+ "percentEmpty",
766
+ "percentFilled",
767
+ "none"
765
768
  ];
766
769
  const DATA_TABLE_NUMERIC_CALC_OPS = [
767
770
  "sum",
@@ -776,8 +779,11 @@ const DATA_TABLE_NUMERIC_CALC_OPS = [
776
779
  ];
777
780
  const DATA_TABLE_CALC_LABEL = {
778
781
  count: "Count",
779
- empty: "Empty",
780
- notEmpty: "Not empty",
782
+ empty: "Count empty",
783
+ notEmpty: "Count filled",
784
+ percentEmpty: "Percent empty",
785
+ percentFilled: "Percent filled",
786
+ none: "None",
781
787
  sum: "Sum",
782
788
  average: "Average",
783
789
  median: "Median",
@@ -807,7 +813,12 @@ const DATA_TABLE_CALC_LABEL = {
807
813
  * (`JSON.stringify`-normalized, so two arrays/objects with the same shape count
808
814
  * once) -- empties are already their own op (`empty`), so folding them into "one
809
815
  * more unique value" here would double-count the same information two different
810
- * ways in the same menu.
816
+ * ways in the same menu. `percentEmpty`/`percentFilled` are `empty`/`notEmpty` as a
817
+ * share of the row count (0-100, unrounded -- formatting rounds it for display) --
818
+ * `null` on zero rows, since 0/0 has no meaningful percentage (not 0%, not 100%).
819
+ * `none` always renders nothing: it is Attio's own "no calculation" menu entry, not a
820
+ * separate always-appended "Clear" item, and picking it is a real, storable op so it
821
+ * overrides a `summaryDefaultOp` instead of falling back to it.
811
822
  */ function computeColumnCalc(values, op) {
812
823
  switch(op){
813
824
  case "count":
@@ -816,6 +827,14 @@ const DATA_TABLE_CALC_LABEL = {
816
827
  return values.filter(isEmptyValueForCalc).length;
817
828
  case "notEmpty":
818
829
  return values.filter((value)=>!isEmptyValueForCalc(value)).length;
830
+ case "percentEmpty":
831
+ if (values.length === 0) return null;
832
+ return values.filter(isEmptyValueForCalc).length / values.length * 100;
833
+ case "percentFilled":
834
+ if (values.length === 0) return null;
835
+ return values.filter((value)=>!isEmptyValueForCalc(value)).length / values.length * 100;
836
+ case "none":
837
+ return null;
819
838
  case "countUnique":
820
839
  {
821
840
  const seen = new Set(values.filter((value)=>!isEmptyValueForCalc(value)).map((value)=>JSON.stringify(value)));
@@ -949,15 +968,33 @@ function sameCell$1(a, id, key) {
949
968
  * first captured) nor keeps a ghost entry for one that was removed. Known keys keep
950
969
  * their position from `order`; any key in `keys` missing from `order` is appended, in
951
970
  * `keys`' own order; any entry in `order` no longer in `keys` is dropped.
952
- */ function reconcileColumnOrder(order, keys) {
971
+ *
972
+ * `actionsKey` (the styled layer's `actions`-flagged column, e.g. the row-menu `⋯`
973
+ * column) is kept LAST no matter what -- without this, a brand-new column added to
974
+ * `columns` after the table first mounted got appended past it (the "any key in `keys`
975
+ * missing from `order` is appended" rule above has no notion of "past the end" vs. "at
976
+ * the very end"), stranding it in the middle of the table since `stickyLast`/`actions`
977
+ * only sticks the actual trailing column. A real consumer hit this: their newly-added
978
+ * column landed to the right of the already-pinned-sticky `actions` column, covered by
979
+ * it under horizontal scroll. Column-layout's own `pinnedCellProps` independently
980
+ * targets the `actions` column by FLAG rather than by position (belt and suspenders --
981
+ * see that file's own doc comment), but this still keeps the VISUAL order sane so nothing
982
+ * renders hidden behind the sticky cell in the first place.
983
+ */ function reconcileColumnOrder(order, keys, actionsKey) {
953
984
  const keySet = new Set(keys);
954
985
  const known = order.filter((key)=>keySet.has(key));
955
986
  const knownSet = new Set(known);
956
987
  const added = keys.filter((key)=>!knownSet.has(key));
957
- return [
988
+ const merged = [
958
989
  ...known,
959
990
  ...added
960
991
  ];
992
+ if (actionsKey === undefined || !keySet.has(actionsKey)) return merged;
993
+ const withoutActions = merged.filter((key)=>key !== actionsKey);
994
+ return [
995
+ ...withoutActions,
996
+ actionsKey
997
+ ];
961
998
  }
962
999
  /**
963
1000
  * Moves `key` one step toward `direction` in `order` -- a no-op (same array, well,
@@ -1136,7 +1173,7 @@ const EMPTY_RECORD = {};
1136
1173
  * state this hook already owns, not independent state
1137
1174
  * of their own.
1138
1175
  */ function useDataTableColumns(props) {
1139
- const { columnKeys = EMPTY_STRINGS, columnOrder: columnOrderProp, defaultColumnOrder, onColumnOrderChange, hiddenColumnKeys: hiddenProp, defaultHiddenColumnKeys = EMPTY_STRINGS, onHiddenColumnKeysChange, columnWidths: widthsProp, defaultColumnWidths = EMPTY_RECORD, onColumnWidthsChange, columnLabels: labelsProp, defaultColumnLabels = EMPTY_RECORD, onColumnLabelsChange, pinnedColumnKeys: pinnedProp, defaultPinnedColumnKeys = EMPTY_STRINGS, onPinnedColumnKeysChange } = props;
1176
+ const { columnKeys = EMPTY_STRINGS, actionsColumnKey, columnOrder: columnOrderProp, defaultColumnOrder, onColumnOrderChange, hiddenColumnKeys: hiddenProp, defaultHiddenColumnKeys = EMPTY_STRINGS, onHiddenColumnKeysChange, columnWidths: widthsProp, defaultColumnWidths = EMPTY_RECORD, onColumnWidthsChange, columnLabels: labelsProp, defaultColumnLabels = EMPTY_RECORD, onColumnLabelsChange, pinnedColumnKeys: pinnedProp, defaultPinnedColumnKeys = EMPTY_STRINGS, onPinnedColumnKeysChange } = props;
1140
1177
  const [rawOrder, setRawOrder] = useControllableState$1({
1141
1178
  prop: columnOrderProp,
1142
1179
  defaultProp: defaultColumnOrder ?? columnKeys,
@@ -1147,9 +1184,10 @@ const EMPTY_RECORD = {};
1147
1184
  // consumer persisted before a new column existed must still show that new column
1148
1185
  // (appended), and one holding a column that got removed from `columns` must not
1149
1186
  // render a ghost header for it.
1150
- const columnOrder = useMemo(()=>reconcileColumnOrder(rawOrder ?? columnKeys, columnKeys), [
1187
+ const columnOrder = useMemo(()=>reconcileColumnOrder(rawOrder ?? columnKeys, columnKeys, actionsColumnKey), [
1151
1188
  rawOrder,
1152
- columnKeys
1189
+ columnKeys,
1190
+ actionsColumnKey
1153
1191
  ]);
1154
1192
  const [hiddenColumnKeys = EMPTY_STRINGS, setHiddenColumnKeys] = useControllableState$1({
1155
1193
  prop: hiddenProp,
@@ -1634,6 +1672,17 @@ function useDataTableSelection(props) {
1634
1672
  rowIds,
1635
1673
  setSelectedIds
1636
1674
  ]);
1675
+ // Bulk-action bar's own "×" (gap #4, DATATABLE-ATTIO-SPEC's Attio review) --
1676
+ // deliberately its own function rather than reusing `toggleAll`: `toggleAll` only
1677
+ // clears from an "all" selection, and otherwise SELECTS every row (`selectionState
1678
+ // === "some"` -> `rowIds`, above) -- exactly the wrong direction for a "clear
1679
+ // selection" affordance shown while `selectionState` is "some". This always empties
1680
+ // the set, regardless of `selectionState`.
1681
+ const clearSelection = useCallback(()=>{
1682
+ setSelectedIds([]);
1683
+ }, [
1684
+ setSelectedIds
1685
+ ]);
1637
1686
  const getRowProps = useCallback((rowId)=>{
1638
1687
  const isRowSelected = isSelected(rowId);
1639
1688
  return elementProps({
@@ -1652,6 +1701,7 @@ function useDataTableSelection(props) {
1652
1701
  isSelected,
1653
1702
  toggleRow,
1654
1703
  toggleAll,
1704
+ clearSelection,
1655
1705
  getRowProps
1656
1706
  };
1657
1707
  }
@@ -2119,7 +2169,7 @@ function useDataTableRowContext({ strict = true } = {}) {
2119
2169
  return context;
2120
2170
  }
2121
2171
  const DataTableRoot = /*#__PURE__*/ forwardRef((props, ref)=>{
2122
- const { sort, defaultSort, onSortChange, rowIds, selectable, selectedIds, defaultSelectedIds, onSelectionChange, query, defaultQuery, onQueryChange, toolbarSorts, defaultToolbarSorts, onToolbarSortsChange, toolbarFilters, defaultToolbarFilters, onToolbarFiltersChange, columnKeys, columnOrder, defaultColumnOrder, onColumnOrderChange, hiddenColumnKeys, defaultHiddenColumnKeys, onHiddenColumnKeysChange, columnWidths, defaultColumnWidths, onColumnWidthsChange, columnLabels, defaultColumnLabels, onColumnLabelsChange, pinnedColumnKeys, defaultPinnedColumnKeys, onPinnedColumnKeysChange, columnCalcOps, defaultColumnCalcOps, onColumnCalcOpsChange, onEdit, onLoadMore, hasMore, loadingMore, ...otherProps } = props;
2172
+ const { sort, defaultSort, onSortChange, rowIds, selectable, selectedIds, defaultSelectedIds, onSelectionChange, query, defaultQuery, onQueryChange, toolbarSorts, defaultToolbarSorts, onToolbarSortsChange, toolbarFilters, defaultToolbarFilters, onToolbarFiltersChange, columnKeys, actionsColumnKey, columnOrder, defaultColumnOrder, onColumnOrderChange, hiddenColumnKeys, defaultHiddenColumnKeys, onHiddenColumnKeysChange, columnWidths, defaultColumnWidths, onColumnWidthsChange, columnLabels, defaultColumnLabels, onColumnLabelsChange, pinnedColumnKeys, defaultPinnedColumnKeys, onPinnedColumnKeysChange, columnCalcOps, defaultColumnCalcOps, onColumnCalcOpsChange, onEdit, onLoadMore, hasMore, loadingMore, ...otherProps } = props;
2123
2173
  const api = useDataTable({
2124
2174
  sort,
2125
2175
  defaultSort,
@@ -2139,6 +2189,7 @@ const DataTableRoot = /*#__PURE__*/ forwardRef((props, ref)=>{
2139
2189
  defaultToolbarFilters,
2140
2190
  onToolbarFiltersChange,
2141
2191
  columnKeys,
2192
+ actionsColumnKey,
2142
2193
  columnOrder,
2143
2194
  defaultColumnOrder,
2144
2195
  onColumnOrderChange,
@@ -2660,11 +2711,70 @@ CheckboxIndicator.displayName = "CheckboxIndicator";
2660
2711
  const CheckboxLabel = withContext$6(withStateProps$4(Primitive.span), "label");
2661
2712
  const CheckboxHiddenInput = Checkbox.HiddenInput;
2662
2713
 
2663
- const { withProvider: withProvider$2, withContext: withContext$5 } = createSlotRecipeContext(avatar);
2714
+ const { ClassNamesProvider: ClassNamesProvider$2, withContext: withContext$5, useClassNames: useClassNames$2 } = createSlotRecipeContext(menu);
2715
+ const { PropsProvider: ItemPropsProvider$1, useProps: useItemProps$1, withContext: withItemContext$1, ClassNamesProvider: ItemClassNamesProvider$1 } = createSlotRecipeContext(menuItem);
2716
+ const withStateProps$3 = createWithStateProps([
2717
+ useMenuContext
2718
+ ]);
2719
+ const withItemStateProps$1 = createWithStateProps([
2720
+ useMenuItemContext
2721
+ ]);
2722
+ const MenuRoot = (props)=>{
2723
+ const [variantProps, otherProps] = menu.splitVariantProps(props);
2724
+ const classNames = menu(variantProps);
2725
+ return /*#__PURE__*/ jsx(ClassNamesProvider$2, {
2726
+ value: classNames,
2727
+ children: /*#__PURE__*/ jsx(ItemPropsProvider$1, {
2728
+ value: {
2729
+ size: variantProps.size
2730
+ },
2731
+ children: /*#__PURE__*/ jsx(Menu.Root, {
2732
+ ...otherProps
2733
+ })
2734
+ })
2735
+ });
2736
+ };
2737
+ const MenuAnchor = Menu.Anchor;
2738
+ const MenuTrigger = Menu.Trigger;
2739
+ const MenuPositioner = /*#__PURE__*/ React.forwardRef(({ className, ...props }, ref)=>{
2740
+ const classNames = useClassNames$2();
2741
+ return /*#__PURE__*/ jsx(Menu.Positioner, {
2742
+ ref: ref,
2743
+ className: clsx(classNames.positioner, className),
2744
+ ...props
2745
+ });
2746
+ });
2747
+ MenuPositioner.displayName = "MenuPositioner";
2748
+ const MenuContent = withContext$5(Menu.Content, "content");
2749
+ const MenuScrollArea = withContext$5(withStateProps$3(Primitive.div), "scrollArea");
2750
+ withContext$5(Menu.Group, "group");
2751
+ withContext$5(Menu.GroupLabel, "groupLabel");
2752
+ const MenuItem = /*#__PURE__*/ React.forwardRef(({ className: propClassName, ...props }, ref)=>{
2753
+ const [variantProps, otherProps] = menuItem.splitVariantProps(props);
2754
+ const parentProps = useItemProps$1();
2755
+ const classNames = menuItem({
2756
+ ...parentProps,
2757
+ ...variantProps
2758
+ });
2759
+ return /*#__PURE__*/ jsx(ItemClassNamesProvider$1, {
2760
+ value: classNames,
2761
+ children: /*#__PURE__*/ jsx(Menu.Item, {
2762
+ ref: ref,
2763
+ className: clsx(classNames.root, propClassName),
2764
+ ...otherProps
2765
+ })
2766
+ });
2767
+ });
2768
+ MenuItem.displayName = "MenuItem";
2769
+ withItemContext$1(withItemStateProps$1(Primitive.div), "body");
2770
+ const MenuItemLabel = withItemContext$1(withItemStateProps$1(Primitive.span), "label");
2771
+ withItemContext$1(withItemStateProps$1(Primitive.span), "description");
2772
+
2773
+ const { withProvider: withProvider$2, withContext: withContext$4 } = createSlotRecipeContext(avatar);
2664
2774
  const AvatarRoot = withProvider$2(Image.Root, "root");
2665
- const AvatarImage = withContext$5(Image.Content, "image");
2666
- const AvatarFallback = withContext$5(Image.Fallback, "fallback");
2667
- withContext$5(Primitive.div, "badge");
2775
+ const AvatarImage = withContext$4(Image.Content, "image");
2776
+ const AvatarFallback = withContext$4(Image.Fallback, "fallback");
2777
+ withContext$4(Primitive.div, "badge");
2668
2778
 
2669
2779
  const Badge = /*#__PURE__*/ forwardRef(({ className, children, ...props }, ref)=>{
2670
2780
  const [variantProps, restProps] = badge.splitVariantProps(props);
@@ -3208,6 +3318,141 @@ function RatingCell({ value, maxRating = 5 }) {
3208
3318
  });
3209
3319
  }
3210
3320
 
3321
+ /** Clear is withheld for `chip-list`: that column already owns its own per-tag remove
3322
+ * affordance (`ChipListEditor`) and commits a JSON-STRINGIFIED array through `onEdit`
3323
+ * (`DataTable.chips.test.tsx`'s own `TagsHarness` -- and this package's own Storybook
3324
+ * demo, `CellFeaturesDemo`, does exactly `JSON.parse(value)` on commit) -- committing
3325
+ * a bare `""` through that same path throws in the consumer's `onEdit`, it doesn't
3326
+ * clear anything. Every OTHER editable shape's commit value is a plain string
3327
+ * (`"true"`/`"false"`, a stringified rating, an option's own `value`, free text) that
3328
+ * a consumer's `onEdit` already has to accept a possibly-empty version of. */ function isClearableEditableCell(column) {
3329
+ return !!column.editable && column.type !== "chip-list";
3330
+ }
3331
+ /** Paste is narrower still -- restricted to a PLAIN `editable` column (no `editor`,
3332
+ * and not one of the always-live widget types) whose committed value IS the raw
3333
+ * text/number a user typed. `select`/`reference`/`actor` expect one of THEIR OWN
3334
+ * option values (not arbitrary clipboard text), `switch`/`rating`/`checkbox` are
3335
+ * always-live widgets with their own encoding, and `chip-list` is excluded for the
3336
+ * same JSON reason `isClearableEditableCell` documents above. This is the task's own
3337
+ * "only for simple editable text/number cells" guard -- committing pasted text
3338
+ * through any of the excluded shapes would corrupt the row rather than edit it
3339
+ * (crispy-seed's own #1094 lesson: never assume an editor's commit shape).
3340
+ */ function isSimpleEditableCell(column) {
3341
+ if (!column.editable) return false;
3342
+ if (column.editor) return false;
3343
+ if (column.type === "chip-list" || column.type === "checkbox" || column.type === "rating") {
3344
+ return false;
3345
+ }
3346
+ return true;
3347
+ }
3348
+ /** Copy's own text representation -- deliberately mirrors `DisplayCellContent`'s
3349
+ * (body-cells.tsx) switch closely enough to read the same as what's on screen, but
3350
+ * returns a plain string (`navigator.clipboard.writeText`) rather than JSX. Not
3351
+ * exported -- it exists only to feed Copy, not as a general-purpose formatter. */ function cellValueAsText(column, value, locale) {
3352
+ if (value == null) return "";
3353
+ switch(column.type){
3354
+ case "entity":
3355
+ if (typeof value === "object") return String(value.name ?? "");
3356
+ return String(value);
3357
+ case "tags":
3358
+ case "chip-list":
3359
+ return Array.isArray(value) ? value.join(", ") : String(value);
3360
+ case "number":
3361
+ return typeof value === "number" ? String(formatNumberCell(value, locale) ?? "") : String(value);
3362
+ case "currency":
3363
+ return typeof value === "number" ? String(formatCurrencyCell(value, column.symbol ?? "$", locale) ?? "") : String(value);
3364
+ case "date":
3365
+ return String(formatDateCell(value, locale) ?? "");
3366
+ case "datetime":
3367
+ return String(formatDateTimeCell(value, locale) ?? "");
3368
+ case "checkbox":
3369
+ return value === true || value === "true" ? "true" : "false";
3370
+ default:
3371
+ return String(value);
3372
+ }
3373
+ }
3374
+ /**
3375
+ * One cell's right-click menu -- mounted by `body-cells.tsx`'s `DataTableBodyCell`
3376
+ * only while actually open (the same "don't keep an unused floating-ui instance alive
3377
+ * per cell" reasoning as that file's own portaled chip-list popover), so this never
3378
+ * costs anything for the row×column cells a user never right-clicks.
3379
+ */ function CellContextMenu({ column, row, value, locale, point, open, onOpenChange, onCommit, onViewEditHistory, cellContextMenuItems }) {
3380
+ const clearable = isClearableEditableCell(column);
3381
+ const pasteable = isSimpleEditableCell(column);
3382
+ return /*#__PURE__*/ jsxs(MenuRoot, {
3383
+ open: open,
3384
+ onOpenChange: onOpenChange,
3385
+ placement: "bottom-start",
3386
+ strategy: "fixed",
3387
+ gutter: 2,
3388
+ children: [
3389
+ /*#__PURE__*/ jsx(MenuAnchor, {
3390
+ "aria-hidden": true,
3391
+ style: {
3392
+ position: "fixed",
3393
+ top: point.y,
3394
+ left: point.x,
3395
+ width: 0,
3396
+ height: 0,
3397
+ pointerEvents: "none"
3398
+ }
3399
+ }),
3400
+ /*#__PURE__*/ jsx(MenuPositioner, {
3401
+ children: /*#__PURE__*/ jsx(MenuContent, {
3402
+ style: {
3403
+ minWidth: 180
3404
+ },
3405
+ children: /*#__PURE__*/ jsxs(MenuScrollArea, {
3406
+ children: [
3407
+ /*#__PURE__*/ jsx(MenuItem, {
3408
+ onClick: ()=>{
3409
+ const text = cellValueAsText(column, value, locale);
3410
+ // Best-effort: an insecure context or a denied permission rejects
3411
+ // this silently (no `MenuItem` state depends on the outcome) --
3412
+ // there's nothing else for a plain "Copy" item to do about a
3413
+ // browser that refused the write.
3414
+ void navigator.clipboard?.writeText?.(text);
3415
+ },
3416
+ children: /*#__PURE__*/ jsx(MenuItemLabel, {
3417
+ children: "Copy"
3418
+ })
3419
+ }),
3420
+ pasteable && /*#__PURE__*/ jsx(MenuItem, {
3421
+ onClick: ()=>{
3422
+ void navigator.clipboard?.readText?.().then((text)=>onCommit(text)).catch(()=>{
3423
+ // Clipboard read denied/unavailable -- best-effort, same
3424
+ // reasoning as Copy above.
3425
+ });
3426
+ },
3427
+ children: /*#__PURE__*/ jsx(MenuItemLabel, {
3428
+ children: "Paste"
3429
+ })
3430
+ }),
3431
+ clearable && /*#__PURE__*/ jsx(MenuItem, {
3432
+ onClick: ()=>onCommit(""),
3433
+ children: /*#__PURE__*/ jsx(MenuItemLabel, {
3434
+ children: "Clear value"
3435
+ })
3436
+ }),
3437
+ onViewEditHistory && /*#__PURE__*/ jsx(MenuItem, {
3438
+ onClick: ()=>onViewEditHistory(row, column.key),
3439
+ children: /*#__PURE__*/ jsx(MenuItemLabel, {
3440
+ children: "View edit history"
3441
+ })
3442
+ }),
3443
+ cellContextMenuItems?.({
3444
+ row,
3445
+ column,
3446
+ value
3447
+ })
3448
+ ]
3449
+ })
3450
+ })
3451
+ })
3452
+ ]
3453
+ });
3454
+ }
3455
+
3211
3456
  /** Fallback width (px) used to compute a pinned column's cumulative sticky offset when
3212
3457
  * it has no explicit `width` and hasn't been drag-resized -- pinning needs a STATIC
3213
3458
  * number to put in an inline `insetInlineStart`, and `table-layout: fixed` only
@@ -3289,10 +3534,25 @@ function RatingCell({ value, maxRating = 5 }) {
3289
3534
  width,
3290
3535
  pinned,
3291
3536
  pinOffset,
3292
- pinnedEdge: false
3537
+ pinnedEdge: false,
3538
+ stickyLastTarget: false
3293
3539
  };
3294
3540
  });
3295
3541
  if (pinnedCount > 0) entries[pinnedCount - 1].pinnedEdge = true;
3542
+ // `stickyLast` sticks the `actions`-flagged column specifically -- not whichever
3543
+ // column merely landed last in render order. Without this, `reconcileColumnOrder`
3544
+ // appending a brand-new column past `actions` (this file's own `<colgroup>` doc
3545
+ // comment on why order must stay in lockstep) silently moved the sticky-right
3546
+ // treatment onto that new column instead, stranding `actions` in the middle of the
3547
+ // table -- a real consumer hit this. Falls back to the genuine last entry when no
3548
+ // column is flagged `actions` at all, matching `stickyLast`'s original single-
3549
+ // column behavior.
3550
+ if (entries.length > 0) {
3551
+ const actionsColumn = columns.find((column)=>column.actions);
3552
+ const actionsIndex = actionsColumn ? entries.findIndex((entry)=>entry.column.key === actionsColumn.key) : -1;
3553
+ const stickyLastIndex = actionsIndex !== -1 ? actionsIndex : entries.length - 1;
3554
+ entries[stickyLastIndex].stickyLastTarget = true;
3555
+ }
3296
3556
  return entries;
3297
3557
  }, [
3298
3558
  columns,
@@ -3332,9 +3592,12 @@ function RatingCell({ value, maxRating = 5 }) {
3332
3592
  }
3333
3593
  /** Sticky positioning for one DATA cell (header, body, or ColumnCalc footer) -- pinned-
3334
3594
  * left (this column's own `pinOffset`, `data-sticky-edge` only on the group's
3335
- * trailing-most member) takes priority over sticky-right (`stickyLast`, always just
3336
- * the last VISIBLE column, e.g. an `actions` column) since a column can only stick to
3337
- * one edge. */ function pinnedCellProps(entry, stickyLast, isLast) {
3595
+ * trailing-most member) takes priority over sticky-right (`stickyLast`, the
3596
+ * `actions`-flagged column when one exists, else the genuine last VISIBLE column)
3597
+ * since a column can only stick to one edge. Reads `entry.stickyLastTarget`
3598
+ * (`useEffectiveColumns`) rather than an `isLast` index the caller computes itself --
3599
+ * that index is just "last in render order", which is exactly what stranded a
3600
+ * reordered-past `actions` column in the first place. */ function pinnedCellProps(entry, stickyLast) {
3338
3601
  if (entry.pinned) {
3339
3602
  return {
3340
3603
  "data-sticky": "",
@@ -3346,7 +3609,7 @@ function RatingCell({ value, maxRating = 5 }) {
3346
3609
  }
3347
3610
  };
3348
3611
  }
3349
- if (stickyLast && isLast) {
3612
+ if (stickyLast && entry.stickyLastTarget) {
3350
3613
  return {
3351
3614
  "data-sticky": "",
3352
3615
  "data-sticky-edge-end": "",
@@ -3366,11 +3629,11 @@ function RatingCell({ value, maxRating = 5 }) {
3366
3629
  // recipe, so a per-instance variant can override the root's default.
3367
3630
  const { ClassNamesProvider: TriggerClassNamesProvider, PropsProvider: TriggerPropsProvider, useProps: useTriggerProps, withContext: withTriggerContext, useClassNames: useTriggerClassNames } = createSlotRecipeContext(selectTrigger);
3368
3631
  const { ClassNamesProvider: ContentClassNamesProvider, withContext: withContentContext, useClassNames: useContentClassNames } = createSlotRecipeContext(select);
3369
- const { ClassNamesProvider: ItemClassNamesProvider$1, PropsProvider: ItemPropsProvider$1, useProps: useItemProps$1, withContext: withItemContext$1, useClassNames: useItemClassNames } = createSlotRecipeContext(selectItem);
3370
- const withStateProps$3 = createWithStateProps([
3632
+ const { ClassNamesProvider: ItemClassNamesProvider, PropsProvider: ItemPropsProvider, useProps: useItemProps, withContext: withItemContext, useClassNames: useItemClassNames } = createSlotRecipeContext(selectItem);
3633
+ const withStateProps$2 = createWithStateProps([
3371
3634
  useSelectContext
3372
3635
  ]);
3373
- const withItemStateProps$1 = createWithStateProps([
3636
+ const withItemStateProps = createWithStateProps([
3374
3637
  useSelectItemContext
3375
3638
  ]);
3376
3639
  // NOTE: `gutter` and `overflowPadding` are specified in Rootage (`select.yaml`,
@@ -3391,7 +3654,7 @@ const SelectRoot = (props)=>{
3391
3654
  value: contentClassNames,
3392
3655
  children: /*#__PURE__*/ jsx(TriggerPropsProvider, {
3393
3656
  value: triggerVariantProps,
3394
- children: /*#__PURE__*/ jsx(ItemPropsProvider$1, {
3657
+ children: /*#__PURE__*/ jsx(ItemPropsProvider, {
3395
3658
  value: itemVariantProps,
3396
3659
  children: /*#__PURE__*/ jsx(Select.Root, {
3397
3660
  ...otherProps
@@ -3431,8 +3694,8 @@ const SelectTrigger = /*#__PURE__*/ React.forwardRef(({ className, ...props }, r
3431
3694
  });
3432
3695
  });
3433
3696
  SelectTrigger.displayName = "SelectTrigger";
3434
- const SelectValue = withTriggerContext(withStateProps$3(Select.Value), "value");
3435
- const SelectPlaceholder = withTriggerContext(withStateProps$3(Select.Placeholder), "placeholder");
3697
+ const SelectValue = withTriggerContext(withStateProps$2(Select.Value), "value");
3698
+ const SelectPlaceholder = withTriggerContext(withStateProps$2(Select.Placeholder), "placeholder");
3436
3699
  const SelectPrefixIcon = /*#__PURE__*/ React.forwardRef(({ fallback, className, ...otherProps }, ref)=>{
3437
3700
  const { selectedItem, stateProps } = useSelectContext();
3438
3701
  const classNames = useTriggerClassNames();
@@ -3447,7 +3710,7 @@ const SelectPrefixIcon = /*#__PURE__*/ React.forwardRef(({ fallback, className,
3447
3710
  });
3448
3711
  });
3449
3712
  SelectPrefixIcon.displayName = "SelectPrefixIcon";
3450
- const SelectSuffixIcon = withTriggerContext(withStateProps$3(InternalIcon), "suffixIcon");
3713
+ const SelectSuffixIcon = withTriggerContext(withStateProps$2(InternalIcon), "suffixIcon");
3451
3714
  const SelectPositioner = /*#__PURE__*/ React.forwardRef(({ className, ...props }, ref)=>{
3452
3715
  const classNames = useContentClassNames();
3453
3716
  return /*#__PURE__*/ jsx(Select.Positioner, {
@@ -3478,12 +3741,12 @@ withContentContext(Select.Group, "group");
3478
3741
  withContentContext(Select.GroupLabel, "groupLabel");
3479
3742
  const SelectItem = /*#__PURE__*/ React.forwardRef(({ prefixIcon, className, ...props }, ref)=>{
3480
3743
  const [variantProps, otherProps] = selectItem.splitVariantProps(props);
3481
- const rootProps = useItemProps$1();
3744
+ const rootProps = useItemProps();
3482
3745
  const classNames = selectItem({
3483
3746
  ...rootProps,
3484
3747
  ...variantProps
3485
3748
  });
3486
- return /*#__PURE__*/ jsx(ItemClassNamesProvider$1, {
3749
+ return /*#__PURE__*/ jsx(ItemClassNamesProvider, {
3487
3750
  value: classNames,
3488
3751
  children: /*#__PURE__*/ jsx(Select.Item, {
3489
3752
  ref: ref,
@@ -3508,7 +3771,7 @@ const SelectItemPrefixIcon = /*#__PURE__*/ React.forwardRef(({ svg: svgOverride,
3508
3771
  });
3509
3772
  });
3510
3773
  SelectItemPrefixIcon.displayName = "SelectItemPrefixIcon";
3511
- const SelectItemBody = withItemContext$1(withItemStateProps$1(Primitive.div), "body");
3774
+ const SelectItemBody = withItemContext(withItemStateProps(Primitive.div), "body");
3512
3775
  const SelectItemLabel = /*#__PURE__*/ React.forwardRef(({ children, className, ...otherProps }, ref)=>{
3513
3776
  const { label, stateProps } = useSelectItemContext();
3514
3777
  const classNames = useItemClassNames();
@@ -3523,7 +3786,7 @@ const SelectItemLabel = /*#__PURE__*/ React.forwardRef(({ children, className, .
3523
3786
  });
3524
3787
  });
3525
3788
  SelectItemLabel.displayName = "SelectItemLabel";
3526
- withItemContext$1(withItemStateProps$1(Primitive.span), "description");
3789
+ withItemContext(withItemStateProps(Primitive.span), "description");
3527
3790
  const SelectItemIndicator = /*#__PURE__*/ React.forwardRef(({ selected: selectedSvg, unselected: unselectedSvg, className, ...otherProps }, ref)=>{
3528
3791
  const { isSelected, stateProps } = useSelectItemContext();
3529
3792
  const classNames = useItemClassNames();
@@ -3555,9 +3818,9 @@ const SelectHiddenSelect = /*#__PURE__*/ React.forwardRef((props, ref)=>{
3555
3818
  });
3556
3819
  SelectHiddenSelect.displayName = "SelectHiddenSelect";
3557
3820
 
3558
- const { withContext: withContext$4, ClassNamesProvider: ClassNamesProvider$2 } = createSlotRecipeContext(switchStyle);
3821
+ const { withContext: withContext$3, ClassNamesProvider: ClassNamesProvider$1 } = createSlotRecipeContext(switchStyle);
3559
3822
  const { withContext: withControlContext, PropsProvider: ControlPropsProvider, withProvider: withControlProvider } = createSlotRecipeContext(switchmark);
3560
- const withStateProps$2 = createWithStateProps([
3823
+ const withStateProps$1 = createWithStateProps([
3561
3824
  useSwitchContext
3562
3825
  ]);
3563
3826
  const SwitchRoot = /*#__PURE__*/ React__default.forwardRef(({ className, ...props }, ref)=>{
@@ -3568,7 +3831,7 @@ const SwitchRoot = /*#__PURE__*/ React__default.forwardRef(({ className, ...prop
3568
3831
  const classNames = switchStyle(switchVariantProps);
3569
3832
  return /*#__PURE__*/ jsx(ControlPropsProvider, {
3570
3833
  value: switchmarkVariantProps,
3571
- children: /*#__PURE__*/ jsx(ClassNamesProvider$2, {
3834
+ children: /*#__PURE__*/ jsx(ClassNamesProvider$1, {
3572
3835
  value: classNames,
3573
3836
  children: /*#__PURE__*/ jsx(Switch.Root, {
3574
3837
  ref: ref,
@@ -3581,20 +3844,20 @@ const SwitchRoot = /*#__PURE__*/ React__default.forwardRef(({ className, ...prop
3581
3844
  SwitchRoot.displayName = "SwitchRoot";
3582
3845
  const SwitchControl = withScaleFeedback(withControlProvider(Switch.Control, "root"));
3583
3846
  const SwitchThumb = withControlContext(Switch.Thumb, "thumb");
3584
- withContext$4(withStateProps$2(Primitive.span), "label");
3847
+ withContext$3(withStateProps$1(Primitive.span), "label");
3585
3848
  const SwitchHiddenInput = Switch.HiddenInput;
3586
3849
 
3587
3850
  // src/use-layout-effect.tsx
3588
3851
  var useLayoutEffect2 = globalThis?.document ? React.useLayoutEffect : ()=>{};
3589
3852
 
3590
- const { withProvider: withProvider$1, withContext: withContext$3, useClassNames: useClassNames$2 } = createSlotRecipeContext(textInput);
3853
+ const { withProvider: withProvider$1, withContext: withContext$2, useClassNames: useClassNames$1 } = createSlotRecipeContext(textInput);
3591
3854
  const withFieldStateProps = createWithStateProps([
3592
3855
  {
3593
3856
  useContext: useFieldContext,
3594
3857
  strict: false
3595
3858
  }
3596
3859
  ]);
3597
- const withStateProps$1 = createWithStateProps([
3860
+ const withStateProps = createWithStateProps([
3598
3861
  useTextFieldContext,
3599
3862
  {
3600
3863
  useContext: useFieldContext,
@@ -3602,12 +3865,12 @@ const withStateProps$1 = createWithStateProps([
3602
3865
  }
3603
3866
  ]);
3604
3867
  const TextFieldRoot = withProvider$1(withFieldStateProps(TextField.Root), "root");
3605
- withContext$3(withStateProps$1(InternalIcon), "prefixIcon");
3606
- withContext$3(withStateProps$1(Primitive.span), "prefixText");
3607
- withContext$3(withStateProps$1(InternalIcon), "suffixIcon");
3608
- withContext$3(withStateProps$1(Primitive.span), "suffixText");
3868
+ withContext$2(withStateProps(InternalIcon), "prefixIcon");
3869
+ withContext$2(withStateProps(Primitive.span), "prefixText");
3870
+ withContext$2(withStateProps(InternalIcon), "suffixIcon");
3871
+ withContext$2(withStateProps(Primitive.span), "suffixText");
3609
3872
  const TextFieldInput = /*#__PURE__*/ forwardRef(({ className, ...otherProps }, ref)=>{
3610
- const classNames = useClassNames$2();
3873
+ const classNames = useClassNames$1();
3611
3874
  const textFieldContext = useTextFieldContext();
3612
3875
  const fieldContext = useFieldContext({
3613
3876
  strict: false
@@ -3625,7 +3888,7 @@ const TextFieldInput = /*#__PURE__*/ forwardRef(({ className, ...otherProps }, r
3625
3888
  });
3626
3889
  TextFieldInput.displayName = "TextFieldInput";
3627
3890
  const TextFieldTextarea = /*#__PURE__*/ forwardRef(({ className, autoresize = true, ...otherProps }, ref)=>{
3628
- const classNames = useClassNames$2();
3891
+ const classNames = useClassNames$1();
3629
3892
  const textFieldContext = useTextFieldContext();
3630
3893
  const fieldContext = useFieldContext({
3631
3894
  strict: false
@@ -3682,7 +3945,7 @@ const TextFieldTextarea = /*#__PURE__*/ forwardRef(({ className, autoresize = tr
3682
3945
  });
3683
3946
  TextFieldTextarea.displayName = "TextFieldTextarea";
3684
3947
 
3685
- __insertCSS(".seed-data-table__root{box-sizing:border-box;display:flex;flex-direction:column;width:100%;border-width:1px;border-style:solid;font-family:Inter,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}.seed-data-table__table{width:100%;border-collapse:separate;border-spacing:0;table-layout:fixed}.seed-data-table__headerCell{box-sizing:border-box;position:sticky;top:0;z-index:2;vertical-align:middle;text-align:start;padding:0;height:var(--seed-dimension-x10);background-color:var(--seed-color-bg-layer-default);border-top:1px solid var(--seed-color-stroke-neutral-muted);border-bottom:1px solid var(--seed-color-stroke-neutral-muted);border-inline-end:1px solid var(--seed-color-stroke-neutral-muted);border-inline-start:none;color:var(--seed-color-fg-neutral);font-size:var(--seed-font-size-t4);line-height:var(--seed-line-height-t4);font-weight:var(--seed-font-weight-medium);letter-spacing:-.14px}.seed-data-table__headerCell:last-child{border-inline-end:none}.seed-data-table__headerCell[data-sortable]{cursor:pointer;user-select:none}.seed-data-table__headerCell [data-chevron]{opacity:0;transition:opacity .12s}.seed-data-table__headerCell[data-sortable]:hover [data-chevron]{opacity:.6}.seed-data-table__headerCell[data-sorted] [data-chevron]{opacity:1;color:var(--seed-color-fg-neutral)}.seed-data-table__headerCell:hover [data-column-menu]{opacity:1}.seed-data-table__headerCell [data-column-menu]:focus-visible,.seed-data-table__headerCell [data-column-menu][data-open]{opacity:1}.seed-data-table__headerCell[data-dragging]{opacity:.5}.seed-data-table__headerCell[data-sticky]{position:sticky;z-index:3;background-color:var(--seed-color-bg-layer-default)}.seed-data-table__headerCell{outline:var(--seed-dimension-x0_5) solid transparent;outline-offset:calc(var(--seed-dimension-x0_5) * -1)}.seed-data-table__headerCell:is(:focus-visible,[data-focus-visible]){outline:var(--seed-dimension-x0_5) solid var(--seed-color-stroke-focus-ring);outline-offset:calc(var(--seed-dimension-x0_5) * -1)}.seed-data-table__headerCell .seed-data-table-resize-handle{position:absolute;top:0;inset-inline-end:calc(7px / -2);width:7px;height:100%;z-index:4;display:flex;align-items:center;justify-content:center;cursor:col-resize;touch-action:none}.seed-data-table__headerCell .seed-data-table-resize-handle-inner{width:3px;height:100%;border-radius:3px;background-color:#0298d9;opacity:0;transition:opacity .1s}.seed-data-table__headerCell .seed-data-table-resize-handle:hover .seed-data-table-resize-handle-inner,.seed-data-table__headerCell .seed-data-table-resize-handle[data-resizing] .seed-data-table-resize-handle-inner{opacity:1}.seed-data-table__headerCellInner{box-sizing:border-box;display:flex;align-items:center;width:100%;height:100%;min-width:0;gap:var(--seed-dimension-x1_5);padding-inline:var(--seed-dimension-x3)}.seed-data-table__headerCellLabel{min-width:0;flex:1 1 0%;overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__headerCellIcon{flex-shrink:0;margin-inline-start:auto;display:inline-flex;align-items:center;justify-content:center;width:var(--seed-dimension-x4);height:var(--seed-dimension-x4);color:var(--seed-color-fg-neutral-subtle);border:none;background:0 0;padding:0;font:inherit;cursor:pointer}.seed-data-table__headerCellIcon[data-active]{color:var(--seed-color-fg-neutral)}.seed-data-table__headerCellIcon[data-column-menu]{opacity:0;transition:opacity .12s}.seed-data-table__row{position:relative;background-color:var(--seed-color-bg-layer-default);--seed-data-table-row-bg:var(--seed-color-bg-layer-default)}.seed-data-table__row[data-clickable]{cursor:pointer}.seed-data-table__row:is([aria-selected=true],[data-selected]){background-color:color-mix(in srgb,#0298d9 10%,var(--seed-color-bg-layer-default));--seed-data-table-row-bg:color-mix(in srgb, #0298d9 10%, var(--seed-color-bg-layer-default))}.seed-data-table__row:is([aria-selected=true],[data-selected]):is(:hover,[data-hover]){background-color:color-mix(in srgb,var(--seed-color-fg-neutral) 3%,color-mix(in srgb,#0298d9 10%,var(--seed-color-bg-layer-default)));--seed-data-table-row-bg:color-mix(in srgb, var(--seed-color-fg-neutral) 3%, color-mix(in srgb, #0298d9 10%, var(--seed-color-bg-layer-default)))}.seed-data-table__cell{box-sizing:border-box;position:relative;vertical-align:middle;text-align:start;height:var(--seed-dimension-x9);border-bottom:1px solid var(--seed-color-stroke-neutral-muted);border-inline-end:1px solid var(--seed-color-stroke-neutral-muted);border-top:none;border-inline-start:none;padding-block:0;padding-inline:var(--seed-dimension-x3);color:var(--seed-color-fg-neutral);font-size:var(--seed-font-size-t4);line-height:var(--seed-line-height-t4);font-weight:var(--seed-font-weight-regular);letter-spacing:-.14px;overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__cell:not([data-editable]){user-select:none}.seed-data-table__cell:last-child{border-inline-end:none}tbody tr:last-child .seed-data-table__cell{border-bottom:none}.seed-data-table__cell[data-numeric]{text-align:end;font-variant-numeric:tabular-nums}.seed-data-table__cell[data-sticky]{position:sticky;z-index:1;background-color:var(--seed-data-table-row-bg)}.seed-data-table__cell[data-overlay-open]{overflow:visible;z-index:20}.seed-data-table__cell[data-editable]{cursor:default}.seed-data-table__cell .seed-data-table-edit-picker{display:flex;align-items:center;gap:var(--seed-dimension-x1_5);min-width:0}.seed-data-table__cell .seed-data-table-edit-pending{flex-shrink:0;color:var(--seed-color-fg-neutral-subtle);animation:rotate .6s linear infinite}.seed-data-table__cell .seed-data-table-edit-error{position:absolute;top:100%;inset-inline-start:var(--seed-dimension-x3);inset-inline-end:var(--seed-dimension-x3);z-index:2;padding-top:2px;font-size:12px;line-height:1.3;color:var(--seed-color-fg-critical);overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__entity{display:flex;align-items:center;min-width:0;gap:var(--seed-dimension-x1_5)}.seed-data-table__entityName{min-width:0;font-weight:var(--seed-font-weight-medium);overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__tagList{display:flex;align-items:center;gap:var(--seed-dimension-x1_5);overflow:hidden;min-width:0}.seed-data-table__entityActions{display:flex;align-items:center;gap:6px;margin-inline-start:auto;flex-shrink:0;opacity:0;transition:opacity .15s}tr:hover .seed-data-table__entityActions{opacity:1}.seed-data-table__entityActions:focus-within{opacity:1}.seed-data-table__entityAction{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;margin:0;padding:2px;border:none;border-radius:6px;background:0 0;font-family:inherit;color:color-mix(in srgb,var(--seed-color-fg-neutral) 55%,transparent);cursor:pointer;flex-shrink:0;transition:background-color .2s,color .2s,box-shadow .2s}.seed-data-table__entityAction:hover{background:color-mix(in srgb,var(--seed-color-fg-neutral) 4%,transparent);color:var(--seed-color-fg-neutral)}.seed-data-table__link{color:var(--seed-color-fg-neutral);text-decoration:none;min-width:0;overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__link:is(:hover,[data-hover]){text-decoration:underline}.seed-data-table__chip{box-sizing:border-box;display:inline-flex;align-items:center;flex-shrink:0;max-width:100%;height:22px;padding:0 4px;border-radius:7px;font-size:10px;font-weight:var(--seed-font-weight-regular);line-height:15px;overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__chipList{position:relative;display:flex;align-items:center;flex-wrap:nowrap;gap:3px;overflow:hidden;min-width:0;width:100%}.seed-data-table__chipOverflow{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;height:20px;padding:0 4px;border-radius:6px;background-color:#f6f7f7;color:#000;font-size:10px;font-weight:var(--seed-font-weight-regular);line-height:15px;white-space:nowrap}.seed-data-table__chipListEditor{display:flex;align-items:center;flex-wrap:wrap;gap:3px;min-width:0;width:100%}.seed-data-table__chipRemove{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:12px;height:12px;margin:0;margin-inline-start:2px;padding:0;border:none;border-radius:999px;background:0 0;color:inherit;opacity:.6;cursor:pointer;transition:opacity .12s,background-color .12s}.seed-data-table__chipRemove:hover{opacity:1;background-color:rgba(0,0,0,.08)}.seed-data-table__chipInput{flex:1 1 auto;min-width:40px;height:22px;margin:0;padding:0;border:none;outline:0;background:0 0;font-family:inherit;font-size:var(--seed-font-size-t4);color:var(--seed-color-fg-neutral);letter-spacing:-.14px}.seed-data-table__expandOverlay{box-sizing:border-box;position:absolute;top:-1px;inset-inline-start:-1px;inset-inline-end:-1px;z-index:10;min-height:calc(100% + 2px);display:flex;align-items:flex-start;padding-inline:var(--seed-dimension-x3);padding-block:var(--seed-dimension-x3);background-color:var(--seed-color-bg-layer-floating);border-radius:4px;box-shadow:none;border:1px solid #266df0;color:var(--seed-color-fg-neutral);font-size:var(--seed-font-size-t4);line-height:var(--seed-line-height-t4);letter-spacing:-.14px;cursor:default}.seed-data-table__expandOverlayText{width:100%;min-width:0;white-space:normal;word-break:break-word}.seed-data-table__cellSuffixRow{display:flex;align-items:center;gap:4px;min-width:0;width:100%}.seed-data-table__cellSuffixText{flex:1 1 0%;min-width:0;overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__cellSuffixIcon{flex-shrink:0;display:inline-flex;align-items:center;color:color-mix(in srgb,var(--seed-color-fg-neutral) 55%,transparent)}.seed-data-table__toolbar{box-sizing:border-box;display:flex;align-items:center;width:100%;flex-shrink:0;min-height:49px;gap:var(--seed-dimension-x2);padding-inline:var(--seed-dimension-x3);border-top:1px solid var(--seed-color-stroke-neutral-muted)}.seed-data-table__search{position:relative;display:inline-flex;align-items:center;flex:1 1 0%;max-width:280px}.seed-data-table__search svg{position:absolute;inset-inline-start:var(--seed-dimension-x2);width:var(--seed-dimension-x4);height:var(--seed-dimension-x4);color:var(--seed-color-fg-neutral-muted);pointer-events:none}.seed-data-table__search input{width:100%;height:var(--seed-dimension-x8);padding-inline-start:var(--seed-dimension-x7);padding-inline-end:var(--seed-dimension-x2);border:none;border-radius:var(--seed-radius-r2);background-color:var(--seed-color-bg-layer-default);box-shadow:inset 0 0 0 1px var(--seed-color-stroke-neutral-weak);font-family:inherit;font-size:var(--seed-font-size-t4);color:var(--seed-color-fg-neutral);outline:0;transition:box-shadow .1s var(--seed-timing-function-easing)}.seed-data-table__search input::placeholder{color:var(--seed-color-fg-neutral-subtle)}.seed-data-table__search input:focus{box-shadow:inset 0 0 0 2px var(--seed-color-stroke-neutral-contrast)}.seed-data-table__count{flex-shrink:0;color:var(--seed-color-fg-neutral-subtle);font-size:var(--seed-font-size-t3);font-variant-numeric:tabular-nums}.seed-data-table__toolbarEnd{flex-shrink:0;margin-inline-start:auto;display:inline-flex;align-items:center;gap:var(--seed-dimension-x2)}.seed-data-table__scrollArea{position:relative;display:flex;flex-direction:column;width:100%;min-width:0;flex:1 1 auto;min-height:0;overflow-x:auto;overflow-y:auto}.seed-data-table__scrollArea[data-scrolled-start] [data-sticky-edge]{clip-path:inset(0 -38px 0 0);box-shadow:6px 0 16px 4px rgba(0,0,0,.12)}.seed-data-table__scrollArea[data-scrolled-end] [data-sticky-edge-end]{clip-path:inset(0 0 0 -38px);box-shadow:-6px 0 16px 4px rgba(0,0,0,.12)}.seed-data-table__footerCell{box-sizing:border-box;position:sticky;bottom:0;z-index:2;vertical-align:middle;padding:0;height:var(--seed-dimension-x9);background-color:var(--seed-color-bg-layer-default);border-top:1px solid var(--seed-color-stroke-neutral-muted);border-inline-end:1px solid var(--seed-color-stroke-neutral-muted);border-inline-start:none}.seed-data-table__footerCell:last-child{border-inline-end:none}.seed-data-table__footerCell .seed-data-table-column-calc-trigger{display:flex;align-items:center;gap:var(--seed-dimension-x1_5);width:100%;height:100%;min-width:0;margin:0;padding:0 var(--seed-dimension-x3);border:none;background:0 0;color:var(--seed-color-fg-neutral-subtle);font-family:inherit;font-size:var(--seed-font-size-t4);font-weight:var(--seed-font-weight-medium);white-space:nowrap;overflow:clip;cursor:pointer;transition:background-color .12s,color .12s}.seed-data-table__footerCell .seed-data-table-column-calc-trigger:hover{background-color:color-mix(in srgb,var(--seed-color-fg-neutral) 4%,transparent);color:var(--seed-color-fg-neutral)}.seed-data-table__footerCell .seed-data-table-column-calc-trigger[data-align=right]{justify-content:flex-end}.seed-data-table__footerCell .seed-data-table-column-calc-trigger svg{flex-shrink:0;opacity:.7}.seed-data-table__footerCell .seed-data-table-column-calc-placeholder{overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__footerCell .seed-data-table-column-calc-value{min-width:0;flex-shrink:1;color:var(--seed-color-fg-neutral);font-variant-numeric:tabular-nums;overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__footerCell .seed-data-table-column-calc-op{min-width:0;flex-shrink:1;color:color-mix(in srgb,var(--seed-color-fg-neutral) 63%,transparent);overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__emptyState{box-sizing:border-box;text-align:center;padding-block:var(--seed-dimension-x8);padding-inline:var(--seed-dimension-x3);color:var(--seed-color-fg-neutral-subtle);font-size:var(--seed-font-size-t4)}.seed-data-table__loadingState{box-sizing:border-box;display:flex;align-items:center;justify-content:center;gap:10px;padding-block:var(--seed-dimension-x8);padding-inline:var(--seed-dimension-x3);color:color-mix(in srgb,var(--seed-color-fg-neutral) 63%,transparent);font-size:var(--seed-font-size-t4);line-height:var(--seed-line-height-t4);font-weight:var(--seed-font-weight-medium)}.seed-data-table__loadingState[data-inline]{padding-block:0;padding-inline:0;height:var(--seed-dimension-x9)}.seed-data-table__loadingState .seed-data-table-loading-spinner{flex-shrink:0;color:color-mix(in srgb,var(--seed-color-fg-neutral) 63%,transparent);animation:rotate .6s linear infinite}.seed-data-table__addRowCell{box-sizing:border-box;padding:0;border-top:none}.seed-data-table__addRow{box-sizing:border-box;display:flex;align-items:center;width:100%;margin:0;border:none;border-top:1px solid var(--seed-color-stroke-neutral-muted);background-color:var(--seed-color-bg-layer-default);height:var(--seed-dimension-x9);gap:var(--seed-dimension-x2);padding-inline:var(--seed-dimension-x2);color:var(--seed-color-fg-neutral-subtle);font-family:inherit;font-size:var(--seed-font-size-t4);cursor:pointer;transition:background-color .12s,color .12s}.seed-data-table__addRow:hover{background-color:color-mix(in srgb,var(--seed-color-fg-neutral) 4%,var(--seed-color-bg-layer-default));color:var(--seed-color-fg-neutral)}.seed-data-table__addRow svg{flex-shrink:0}.seed-data-table__selectionCell{text-align:center;padding:0}.seed-data-table__root--surface_card{border-color:var(--seed-color-stroke-neutral-muted);background-color:var(--seed-color-bg-layer-default);border-radius:var(--seed-radius-r3)}.seed-data-table__root--surface_bleed{border-color:var(--seed-color-bg-transparent);background-color:var(--seed-color-bg-transparent)}.seed-data-table__row--interactive_true{transition:background-color .12s}.seed-data-table__row--interactive_true:is(:hover,[data-hover]){background-color:color-mix(in srgb,var(--seed-color-fg-neutral) 3%,transparent);--seed-data-table-row-bg:color-mix(in srgb, var(--seed-color-fg-neutral) 3%, var(--seed-color-bg-layer-default))}");
3948
+ __insertCSS(".seed-data-table__root{box-sizing:border-box;position:relative;display:flex;flex-direction:column;width:100%;border-width:1px;border-style:solid;font-family:Inter,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}.seed-data-table__table{width:100%;border-collapse:separate;border-spacing:0;table-layout:fixed}.seed-data-table__headerCell{box-sizing:border-box;position:sticky;top:0;z-index:2;vertical-align:middle;text-align:start;padding:0;height:var(--seed-dimension-x10);background-color:var(--seed-color-bg-layer-default);border-top:1px solid var(--seed-color-stroke-neutral-muted);border-bottom:1px solid var(--seed-color-stroke-neutral-muted);border-inline-end:1px solid var(--seed-color-stroke-neutral-muted);border-inline-start:none;color:var(--seed-color-fg-neutral);font-size:var(--seed-font-size-t4);line-height:var(--seed-line-height-t4);font-weight:var(--seed-font-weight-medium);letter-spacing:-.14px}.seed-data-table__headerCell:last-child{border-inline-end:none}.seed-data-table__headerCell[data-sortable]{cursor:pointer;user-select:none}.seed-data-table__headerCell [data-chevron]{opacity:0;transition:opacity .12s}.seed-data-table__headerCell[data-sortable]:hover [data-chevron]{opacity:.6}.seed-data-table__headerCell[data-sorted] [data-chevron]{opacity:1;color:var(--seed-color-fg-neutral)}.seed-data-table__headerCell:hover [data-column-menu]{opacity:1}.seed-data-table__headerCell [data-column-menu]:focus-visible,.seed-data-table__headerCell [data-column-menu][data-open]{opacity:1}.seed-data-table__headerCell[data-dragging]{opacity:.5}.seed-data-table__headerCell[data-sticky]{position:sticky;z-index:3;background-color:var(--seed-color-bg-layer-default)}.seed-data-table__headerCell{outline:var(--seed-dimension-x0_5) solid transparent;outline-offset:calc(var(--seed-dimension-x0_5) * -1)}.seed-data-table__headerCell:is(:focus-visible,[data-focus-visible]){outline:var(--seed-dimension-x0_5) solid var(--seed-color-stroke-focus-ring);outline-offset:calc(var(--seed-dimension-x0_5) * -1)}.seed-data-table__headerCell .seed-data-table-resize-handle{position:absolute;top:0;inset-inline-end:calc(7px / -2);width:7px;height:100%;z-index:4;display:flex;align-items:center;justify-content:center;cursor:col-resize;touch-action:none}.seed-data-table__headerCell .seed-data-table-resize-handle-inner{width:3px;height:100%;border-radius:3px;background-color:#0298d9;opacity:0;transition:opacity .1s}.seed-data-table__headerCell .seed-data-table-resize-handle:hover .seed-data-table-resize-handle-inner,.seed-data-table__headerCell .seed-data-table-resize-handle[data-resizing] .seed-data-table-resize-handle-inner{opacity:1}.seed-data-table__headerCellInner{box-sizing:border-box;display:flex;align-items:center;width:100%;height:100%;min-width:0;gap:var(--seed-dimension-x1_5);padding-inline:var(--seed-dimension-x3)}.seed-data-table__headerCellLabel{min-width:0;flex:1 1 0%;overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__headerCellIcon{flex-shrink:0;margin-inline-start:auto;display:inline-flex;align-items:center;justify-content:center;width:var(--seed-dimension-x4);height:var(--seed-dimension-x4);color:var(--seed-color-fg-neutral-subtle);border:none;background:0 0;padding:0;font:inherit;cursor:pointer}.seed-data-table__headerCellIcon[data-active]{color:var(--seed-color-fg-neutral)}.seed-data-table__headerCellIcon[data-column-menu]{opacity:0;transition:opacity .12s}.seed-data-table__row{position:relative;background-color:var(--seed-color-bg-layer-default);--seed-data-table-row-bg:var(--seed-color-bg-layer-default)}.seed-data-table__row[data-clickable]{cursor:pointer}.seed-data-table__row:is([aria-selected=true],[data-selected]){background-color:color-mix(in srgb,#0298d9 10%,var(--seed-color-bg-layer-default));--seed-data-table-row-bg:color-mix(in srgb, #0298d9 10%, var(--seed-color-bg-layer-default))}.seed-data-table__row:is([aria-selected=true],[data-selected]):is(:hover,[data-hover]){background-color:color-mix(in srgb,var(--seed-color-fg-neutral) 3%,color-mix(in srgb,#0298d9 10%,var(--seed-color-bg-layer-default)));--seed-data-table-row-bg:color-mix(in srgb, var(--seed-color-fg-neutral) 3%, color-mix(in srgb, #0298d9 10%, var(--seed-color-bg-layer-default)))}.seed-data-table__cell{box-sizing:border-box;position:relative;vertical-align:middle;text-align:start;height:var(--seed-dimension-x9);border-bottom:1px solid var(--seed-color-stroke-neutral-muted);border-inline-end:1px solid var(--seed-color-stroke-neutral-muted);border-top:none;border-inline-start:none;padding-block:0;padding-inline:var(--seed-dimension-x3);color:var(--seed-color-fg-neutral);font-size:var(--seed-font-size-t4);line-height:var(--seed-line-height-t4);font-weight:var(--seed-font-weight-regular);letter-spacing:-.14px;overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__cell:not([data-editable]){user-select:none}.seed-data-table__cell:last-child{border-inline-end:none}tbody tr:last-child .seed-data-table__cell{border-bottom:none}.seed-data-table__cell[data-numeric]{text-align:end;font-variant-numeric:tabular-nums}.seed-data-table__cell[data-sticky]{position:sticky;z-index:1;background-color:var(--seed-data-table-row-bg)}.seed-data-table__cell[data-overlay-open]{overflow:visible;z-index:20}.seed-data-table__cell[data-editable]{cursor:default}.seed-data-table__cell .seed-data-table-edit-picker{display:flex;align-items:center;gap:var(--seed-dimension-x1_5);min-width:0}.seed-data-table__cell .seed-data-table-edit-pending{flex-shrink:0;color:var(--seed-color-fg-neutral-subtle);animation:rotate .6s linear infinite}.seed-data-table__cell .seed-data-table-edit-error{position:absolute;top:100%;inset-inline-start:var(--seed-dimension-x3);inset-inline-end:var(--seed-dimension-x3);z-index:2;padding-top:2px;font-size:12px;line-height:1.3;color:var(--seed-color-fg-critical);overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__entity{display:flex;align-items:center;min-width:0;gap:var(--seed-dimension-x1_5)}.seed-data-table__entityName{min-width:0;font-weight:var(--seed-font-weight-medium);overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__tagList{display:flex;align-items:center;gap:var(--seed-dimension-x1_5);overflow:hidden;min-width:0}.seed-data-table__entityActions{display:flex;align-items:center;gap:6px;margin-inline-start:auto;flex-shrink:0;opacity:0;transition:opacity .15s}tr:hover .seed-data-table__entityActions{opacity:1}.seed-data-table__entityActions:focus-within{opacity:1}.seed-data-table__entityAction{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;margin:0;padding:2px;border:none;border-radius:6px;background:0 0;font-family:inherit;color:color-mix(in srgb,var(--seed-color-fg-neutral) 55%,transparent);cursor:pointer;flex-shrink:0;transition:background-color .2s,color .2s,box-shadow .2s}.seed-data-table__entityAction:hover{background:color-mix(in srgb,var(--seed-color-fg-neutral) 4%,transparent);color:var(--seed-color-fg-neutral)}.seed-data-table__link{color:var(--seed-color-fg-neutral);text-decoration:none;min-width:0;overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__link:is(:hover,[data-hover]){text-decoration:underline}.seed-data-table__chip{box-sizing:border-box;display:inline-flex;align-items:center;flex-shrink:0;max-width:100%;height:22px;padding:0 4px;border-radius:7px;font-size:10px;font-weight:var(--seed-font-weight-regular);line-height:15px;overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__chipList{position:relative;display:flex;align-items:center;flex-wrap:nowrap;gap:3px;overflow:hidden;min-width:0;width:100%}.seed-data-table__chipOverflow{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;height:20px;padding:0 4px;border-radius:6px;background-color:#f6f7f7;color:#000;font-size:10px;font-weight:var(--seed-font-weight-regular);line-height:15px;white-space:nowrap}.seed-data-table__chipListEditor{display:flex;align-items:center;flex-wrap:wrap;gap:3px;min-width:0;width:100%}.seed-data-table__chipRemove{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:12px;height:12px;margin:0;margin-inline-start:2px;padding:0;border:none;border-radius:999px;background:0 0;color:inherit;opacity:.6;cursor:pointer;transition:opacity .12s,background-color .12s}.seed-data-table__chipRemove:hover{opacity:1;background-color:rgba(0,0,0,.08)}.seed-data-table__chipInput{flex:1 1 auto;min-width:40px;height:22px;margin:0;padding:0;border:none;outline:0;background:0 0;font-family:inherit;font-size:var(--seed-font-size-t4);color:var(--seed-color-fg-neutral);letter-spacing:-.14px}.seed-data-table__expandOverlay{box-sizing:border-box;position:absolute;top:-1px;inset-inline-start:-1px;inset-inline-end:-1px;z-index:10;min-height:calc(100% + 2px);display:flex;align-items:flex-start;padding-inline:var(--seed-dimension-x3);padding-block:var(--seed-dimension-x3);background-color:var(--seed-color-bg-layer-floating);border-radius:4px;box-shadow:none;border:1px solid #266df0;color:var(--seed-color-fg-neutral);font-size:var(--seed-font-size-t4);line-height:var(--seed-line-height-t4);letter-spacing:-.14px;cursor:default}.seed-data-table__expandOverlayText{width:100%;min-width:0;white-space:normal;word-break:break-word}.seed-data-table__cellSuffixRow{display:flex;align-items:center;gap:4px;min-width:0;width:100%}.seed-data-table__cellSuffixText{flex:1 1 0%;min-width:0;overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__cellSuffixIcon{flex-shrink:0;display:inline-flex;align-items:center;color:color-mix(in srgb,var(--seed-color-fg-neutral) 55%,transparent)}.seed-data-table__toolbar{box-sizing:border-box;display:flex;align-items:center;width:100%;flex-shrink:0;min-height:49px;gap:var(--seed-dimension-x2);padding-inline:var(--seed-dimension-x3);border-top:1px solid var(--seed-color-stroke-neutral-muted)}.seed-data-table__search{position:relative;display:inline-flex;align-items:center;flex:1 1 0%;max-width:280px}.seed-data-table__search svg{position:absolute;inset-inline-start:var(--seed-dimension-x2);width:var(--seed-dimension-x4);height:var(--seed-dimension-x4);color:var(--seed-color-fg-neutral-muted);pointer-events:none}.seed-data-table__search input{width:100%;height:var(--seed-dimension-x8);padding-inline-start:var(--seed-dimension-x7);padding-inline-end:var(--seed-dimension-x2);border:none;border-radius:var(--seed-radius-r2);background-color:var(--seed-color-bg-layer-default);box-shadow:inset 0 0 0 1px var(--seed-color-stroke-neutral-weak);font-family:inherit;font-size:var(--seed-font-size-t4);color:var(--seed-color-fg-neutral);outline:0;transition:box-shadow .1s var(--seed-timing-function-easing)}.seed-data-table__search input::placeholder{color:var(--seed-color-fg-neutral-subtle)}.seed-data-table__search input:focus{box-shadow:inset 0 0 0 2px var(--seed-color-stroke-neutral-contrast)}.seed-data-table__count{flex-shrink:0;color:var(--seed-color-fg-neutral-subtle);font-size:var(--seed-font-size-t3);font-variant-numeric:tabular-nums}.seed-data-table__toolbarEnd{flex-shrink:0;margin-inline-start:auto;display:inline-flex;align-items:center;gap:var(--seed-dimension-x2)}.seed-data-table__scrollArea{position:relative;display:flex;flex-direction:column;width:100%;min-width:0;flex:1 1 auto;min-height:0;overflow-x:auto;overflow-y:auto}.seed-data-table__scrollArea[data-scrolled-start] [data-sticky-edge]{clip-path:inset(0 -38px 0 0);box-shadow:6px 0 16px 4px rgba(0,0,0,.12)}.seed-data-table__scrollArea[data-scrolled-end] [data-sticky-edge-end]{clip-path:inset(0 0 0 -38px);box-shadow:-6px 0 16px 4px rgba(0,0,0,.12)}.seed-data-table__footerCell{box-sizing:border-box;position:sticky;bottom:0;z-index:2;vertical-align:middle;padding:0;height:var(--seed-dimension-x9);background-color:var(--seed-color-bg-layer-default);border-top:1px solid var(--seed-color-stroke-neutral-muted);border-inline-end:1px solid var(--seed-color-stroke-neutral-muted);border-inline-start:none}.seed-data-table__footerCell:last-child{border-inline-end:none}.seed-data-table__footerCell .seed-data-table-column-calc-trigger{display:flex;align-items:center;gap:var(--seed-dimension-x1_5);width:100%;height:100%;min-width:0;margin:0;padding:0 var(--seed-dimension-x3);border:none;background:0 0;color:var(--seed-color-fg-neutral-subtle);font-family:inherit;font-size:var(--seed-font-size-t4);font-weight:var(--seed-font-weight-medium);white-space:nowrap;overflow:clip;cursor:pointer;transition:background-color .12s,color .12s}.seed-data-table__footerCell .seed-data-table-column-calc-trigger:hover{background-color:color-mix(in srgb,var(--seed-color-fg-neutral) 4%,transparent);color:var(--seed-color-fg-neutral)}.seed-data-table__footerCell .seed-data-table-column-calc-trigger[data-align=right]{justify-content:flex-end}.seed-data-table__footerCell .seed-data-table-column-calc-trigger svg{flex-shrink:0;opacity:.7}.seed-data-table__footerCell .seed-data-table-column-calc-placeholder{overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__footerCell .seed-data-table-column-calc-value{min-width:0;flex-shrink:1;color:var(--seed-color-fg-neutral);font-variant-numeric:tabular-nums;overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__footerCell .seed-data-table-column-calc-op{min-width:0;flex-shrink:1;color:color-mix(in srgb,var(--seed-color-fg-neutral) 63%,transparent);overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__emptyState{box-sizing:border-box;text-align:center;padding-block:var(--seed-dimension-x8);padding-inline:var(--seed-dimension-x3);color:var(--seed-color-fg-neutral-subtle);font-size:var(--seed-font-size-t4)}.seed-data-table__loadingState{box-sizing:border-box;display:flex;align-items:center;justify-content:center;gap:10px;padding-block:var(--seed-dimension-x8);padding-inline:var(--seed-dimension-x3);color:color-mix(in srgb,var(--seed-color-fg-neutral) 63%,transparent);font-size:var(--seed-font-size-t4);line-height:var(--seed-line-height-t4);font-weight:var(--seed-font-weight-medium)}.seed-data-table__loadingState[data-inline]{padding-block:0;padding-inline:0;height:var(--seed-dimension-x9)}.seed-data-table__loadingState .seed-data-table-loading-spinner{flex-shrink:0;color:color-mix(in srgb,var(--seed-color-fg-neutral) 63%,transparent);animation:rotate .6s linear infinite}.seed-data-table__addRowCell{box-sizing:border-box;padding:0;border-top:none}.seed-data-table__addRow{box-sizing:border-box;display:flex;align-items:center;width:100%;margin:0;border:none;border-top:1px solid var(--seed-color-stroke-neutral-muted);background-color:var(--seed-color-bg-layer-default);height:var(--seed-dimension-x9);gap:var(--seed-dimension-x2);padding-inline:var(--seed-dimension-x2);color:var(--seed-color-fg-neutral-subtle);font-family:inherit;font-size:var(--seed-font-size-t4);cursor:pointer;transition:background-color .12s,color .12s}.seed-data-table__addRow:hover{background-color:color-mix(in srgb,var(--seed-color-fg-neutral) 4%,var(--seed-color-bg-layer-default));color:var(--seed-color-fg-neutral)}.seed-data-table__addRow svg{flex-shrink:0}.seed-data-table__selectionCell{text-align:center;padding:0}.seed-data-table__bulkBar{box-sizing:border-box;position:absolute;inset-inline-start:50%;transform:translateX(-50%);bottom:var(--seed-dimension-x4);z-index:30;display:flex;align-items:center;gap:var(--seed-dimension-x3);min-height:36px;padding-inline:var(--seed-dimension-x3_5);padding-block:var(--seed-dimension-x2);border-radius:var(--seed-radius-full);box-shadow:var(--seed-shadow-s3);background-color:var(--seed-color-bg-layer-floating);color:var(--seed-color-fg-neutral);font-family:inherit;font-size:var(--seed-font-size-t4);font-weight:var(--seed-font-weight-medium);white-space:nowrap}.seed-data-table__bulkBarCount{flex-shrink:0;color:var(--seed-color-fg-neutral);font-variant-numeric:tabular-nums}.seed-data-table__bulkBarClear{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:20px;height:20px;margin:0;padding:2px;border:none;border-radius:6px;background:0 0;font-family:inherit;color:color-mix(in srgb,var(--seed-color-fg-neutral) 55%,transparent);cursor:pointer;transition:background-color .2s,color .2s}.seed-data-table__bulkBarClear:hover{background-color:color-mix(in srgb,var(--seed-color-fg-neutral) 8%,transparent);color:var(--seed-color-fg-neutral)}.seed-data-table__bulkBarActions{display:flex;align-items:center;flex-shrink:0;gap:var(--seed-dimension-x3);padding-inline-start:var(--seed-dimension-x3);border-inline-start:1px solid color-mix(in srgb,var(--seed-color-fg-neutral) 16%,transparent)}.seed-data-table__root--surface_card{border-color:var(--seed-color-stroke-neutral-muted);background-color:var(--seed-color-bg-layer-default);border-radius:var(--seed-radius-r3)}.seed-data-table__root--surface_bleed{border-color:var(--seed-color-bg-transparent);background-color:var(--seed-color-bg-transparent)}.seed-data-table__row--interactive_true{transition:background-color .12s}.seed-data-table__row--interactive_true:is(:hover,[data-hover]){background-color:color-mix(in srgb,var(--seed-color-fg-neutral) 3%,transparent);--seed-data-table-row-bg:color-mix(in srgb, var(--seed-color-fg-neutral) 3%, var(--seed-color-bg-layer-default))}");
3686
3949
 
3687
3950
  const createClassName = (className, variants, compoundVariants = [])=>{
3688
3951
  const variantKeys = Object.keys(variants);
@@ -3876,6 +4139,22 @@ const dataTableSlotNames = [
3876
4139
  [
3877
4140
  "selectionCell",
3878
4141
  "seed-data-table__selectionCell"
4142
+ ],
4143
+ [
4144
+ "bulkBar",
4145
+ "seed-data-table__bulkBar"
4146
+ ],
4147
+ [
4148
+ "bulkBarCount",
4149
+ "seed-data-table__bulkBarCount"
4150
+ ],
4151
+ [
4152
+ "bulkBarClear",
4153
+ "seed-data-table__bulkBarClear"
4154
+ ],
4155
+ [
4156
+ "bulkBarActions",
4157
+ "seed-data-table__bulkBarActions"
3879
4158
  ]
3880
4159
  ];
3881
4160
  const defaultVariant$6 = {
@@ -3913,7 +4192,7 @@ Object.assign(dataTable, {
3913
4192
  // other file (`DataTable.tsx`, `column-menu.tsx`, `resize.tsx`, `column-calc.tsx`,
3914
4193
  // `toolbar.tsx`, `body-cells.tsx`, `editors.tsx`) imports its exports from here rather
3915
4194
  // than re-deriving its own.
3916
- const { ClassNamesProvider: ClassNamesProvider$1, withContext: withContext$2, useClassNames: useClassNames$1 } = createSlotRecipeContext(dataTable);
4195
+ const { ClassNamesProvider, withContext: withContext$1, useClassNames } = createSlotRecipeContext(dataTable);
3917
4196
 
3918
4197
  /**
3919
4198
  * The pure filter itself, split out of `useSelectSearch` below so it can be
@@ -4489,26 +4768,87 @@ function ChipListEditor({ column, value, pending, onCommit, classNames }) {
4489
4768
  }));
4490
4769
  }
4491
4770
  ////////////////////////////////////////////////////////////////////////////////////
4492
- // -- Feature C (#12): cell-expand "show full content" overlay -- an anchored box
4493
- // (position:absolute over the cell it belongs to, grows in HEIGHT, elevated)
4494
- // that wraps whatever a truncated cell would otherwise show inline: an editable
4495
- // column's OWN editor node (unchanged, just relocated into this box instead of
4496
- // rendered flush in the 36px row) for the editable case, or the plain full value
4497
- // for a read-only one. `body-cells.tsx` decides WHEN to render this (a click on a
4498
- // cell whose content is actually truncated, `truncate.ts`'s `isCellTruncated`);
4499
- // this component only supplies the box.
4500
- function CellExpandOverlay({ className, children }) {
4501
- return(// `stopPropagation` -- an overlay sits on top of (and past) its own cell/row;
4771
+ // -- Feature C (#12): cell-expand "show full content" overlay -- a box that wraps
4772
+ // whatever a truncated cell would otherwise show inline: an editable column's OWN
4773
+ // editor node (unchanged, just relocated into this box instead of rendered flush in
4774
+ // the 36px row) for the editable case, or the plain full value for a read-only one.
4775
+ // `body-cells.tsx` decides WHEN to render this (a click on a cell whose content is
4776
+ // actually truncated, `truncate.ts`'s `isCellTruncated`); this component only
4777
+ // supplies the box.
4778
+ //
4779
+ // Portals to `document.body` (crispy-seed#1094's own pinned-column follow-up,
4780
+ // ported from the chip-list editor's popover, `body-cells.tsx`'s
4781
+ // `isChipListEditable` -- that one shipped this exact fix in 0.10.3) rather than
4782
+ // positioning `absolute` against its own cell: a `position: sticky` `<td>` clips ANY
4783
+ // descendant that grows past its own box, in every browser tested, regardless of
4784
+ // `overflow`/`z-index` on anything in between, so a pinned column's own truncated
4785
+ // cell used to clip this overlay's grown content instead of showing it in full. A
4786
+ // portal sidesteps that by not being a descendant of the sticky `<td>` at all once
4787
+ // mounted -- position is instead measured live off `anchorRef`'s own
4788
+ // `getBoundingClientRect()`, the same technique the chip-list popover already uses.
4789
+ function CellExpandOverlay({ className, anchorRef, contentRef, children }) {
4790
+ const [style, setStyle] = React.useState(null);
4791
+ // Grows 1px past the cell's own edges in every direction, matching this box's
4792
+ // pre-portal `top: -1px; insetInlineStart: -1px; insetInlineEnd: -1px` (the recipe's
4793
+ // `expandOverlay` class, `data-table.ts`) -- hides the 1px border seam against the
4794
+ // cell it's expanding out of. `minHeight`/`insetInlineEnd` from that same class no
4795
+ // longer apply once portaled (they resolved against the `<td>` as this box's own
4796
+ // containing block, meaningless once it's a `position: fixed` child of `<body>`
4797
+ // instead -- the same override the chip-list popover's own `measurePopoverPosition`,
4798
+ // `body-cells.tsx`, already makes), so this overrides both explicitly.
4799
+ const measure = React.useCallback(()=>{
4800
+ const cell = anchorRef.current;
4801
+ if (!cell) return;
4802
+ const rect = cell.getBoundingClientRect();
4803
+ setStyle({
4804
+ position: "fixed",
4805
+ top: rect.top - 1,
4806
+ insetInlineStart: rect.left - 1,
4807
+ insetInlineEnd: "auto",
4808
+ width: rect.width + 2,
4809
+ minHeight: "auto",
4810
+ zIndex: 1000
4811
+ });
4812
+ }, [
4813
+ anchorRef
4814
+ ]);
4815
+ // Measured before paint, on mount -- see this component's own prop doc comment for
4816
+ // why it can't just trust a caller to have measured first.
4817
+ React.useLayoutEffect(()=>{
4818
+ measure();
4819
+ }, [
4820
+ measure
4821
+ ]);
4822
+ // Same capture-phase listeners as the chip-list popover's own reposition effect
4823
+ // (`body-cells.tsx`) -- `scroll` doesn't bubble, so the table's own horizontal/
4824
+ // vertical scroll container (an arbitrary ancestor, not necessarily `window` itself)
4825
+ // is only observable this way.
4826
+ React.useEffect(()=>{
4827
+ window.addEventListener("scroll", measure, true);
4828
+ window.addEventListener("resize", measure);
4829
+ return ()=>{
4830
+ window.removeEventListener("scroll", measure, true);
4831
+ window.removeEventListener("resize", measure);
4832
+ };
4833
+ }, [
4834
+ measure
4835
+ ]);
4836
+ // Nothing to portal until the first measurement lands (avoids a one-frame flash at
4837
+ // the origin corner before `useLayoutEffect` runs).
4838
+ if (!style) return null;
4839
+ return /*#__PURE__*/ createPortal(// `stopPropagation` -- an overlay sits on top of (and past) its own cell/row;
4502
4840
  // a click anywhere inside it must never ALSO fire the row's own `onRowClick`,
4503
4841
  // the same guard every other interactive cell affordance in this directory
4504
4842
  // already applies (`LinkCell`, `EntityCell`'s preview button, editors.tsx's
4505
4843
  // `TextEditor` root).
4506
4844
  // biome-ignore lint/a11y/noStaticElementInteractions: not a widget -- this div never responds to interaction on ITSELF, only stops propagation of events that already occurred on a real interactive descendant (the editor/text it wraps). No role fits it because it isn't a control.
4507
4845
  /*#__PURE__*/ jsx("div", {
4846
+ ref: contentRef,
4508
4847
  className: className,
4848
+ style: style,
4509
4849
  onClick: (event)=>event.stopPropagation(),
4510
4850
  children: children
4511
- }));
4851
+ }), document.body);
4512
4852
  }
4513
4853
 
4514
4854
  // Row-narrowing for the toolbar's search box -- split out of DataTable.tsx the way
@@ -4604,39 +4944,37 @@ function isCellTruncated(cell) {
4604
4944
  * close itself after the first tag commits, see `ChipListEditor`'s own doc
4605
4945
  * comment).
4606
4946
  *
4607
- * Unlike every OTHER open editor in this file, this one does NOT render inside
4608
- * Feature C's `CellExpandOverlay` (an anchored box positioned relative to the
4609
- * `<td>`, `position: absolute` against it) -- it renders through a REAL React
4610
- * portal to `document.body` instead (`DataTableBodyCell`'s own `popoverStyle`).
4611
- * Confirmed live, crispy-seed#1094's pinned-column follow-up: a `position: sticky`
4612
- * `<td>` clips ANY descendant content that grows past its own box to the table's
4613
- * scrollport, in EVERY browser tested, regardless of `overflow: visible` on every
4614
- * element in between and regardless of how high its `z-index` is raised -- neither
4615
- * of which is a stacking-context or overflow bug this file can fix from the
4616
- * INSIDE. A portal sidesteps it entirely by not being a descendant of the sticky
4617
- * `<td>` at all once mounted. The same sticky-clip almost certainly affects the
4618
- * OTHER gated editors' `CellExpandOverlay` too when their own column is pinned,
4619
- * but porting all of Feature C to this same portal model is its own change,
4620
- * out of scope here -- this fix is scoped to the one editor crispy-seed#1094
4621
- * actually reported against a pinned column. */ function isChipListEditable(column) {
4947
+ * This one renders through its OWN REAL React portal to `document.body`
4948
+ * (`DataTableBodyCell`'s own `popoverStyle`) rather than `editors.tsx`'s shared
4949
+ * `CellExpandOverlay`, since its shape is different (Attio's own click-anywhere-
4950
+ * opens gesture, no truncation gate). `CellExpandOverlay` itself ALSO portals to
4951
+ * `document.body` now (ported from this exact fix) -- both exist because a
4952
+ * `position: sticky` `<td>` clips ANY descendant content that grows past its own
4953
+ * box to the table's scrollport, in EVERY browser tested, regardless of
4954
+ * `overflow: visible` on every element in between and regardless of how high its
4955
+ * `z-index` is raised -- neither of which is a stacking-context or overflow bug
4956
+ * fixable from the INSIDE. A portal sidesteps it entirely by not being a
4957
+ * descendant of the sticky `<td>` at all once mounted. */ function isChipListEditable(column) {
4622
4958
  return !!column.editable && column.type === "chip-list";
4623
4959
  }
4624
- function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyFirst, stickyLast }) {
4960
+ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyFirst, stickyLast, cellContextMenu, onViewEditHistory, cellContextMenuItems }) {
4625
4961
  const effective = useEffectiveColumns(columns, {
4626
4962
  selectable,
4627
4963
  stickyFirst
4628
4964
  });
4629
4965
  return /*#__PURE__*/ jsx(Fragment, {
4630
- children: effective.map((entry, index)=>{
4966
+ children: effective.map((entry)=>{
4631
4967
  const { column } = entry;
4632
- const isLast = index === effective.length - 1;
4633
- const sticky = pinnedCellProps(entry, stickyLast, isLast);
4968
+ const sticky = pinnedCellProps(entry, stickyLast);
4634
4969
  return /*#__PURE__*/ jsx(DataTableBodyCell, {
4635
4970
  column: column,
4636
4971
  row: row,
4637
4972
  classNames: classNames,
4638
4973
  locale: locale,
4639
- sticky: sticky
4974
+ sticky: sticky,
4975
+ cellContextMenu: cellContextMenu,
4976
+ onViewEditHistory: onViewEditHistory,
4977
+ cellContextMenuItems: cellContextMenuItems
4640
4978
  }, column.key);
4641
4979
  })
4642
4980
  });
@@ -4648,7 +4986,7 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4648
4986
  * on every row's render. React keeps this instance stable across re-renders because
4649
4987
  * `ColumnBodyCells` keys it by `column.key` the same way it always keyed the bare
4650
4988
  * `<DataTableCell>` before this split.
4651
- */ function DataTableBodyCell({ column, row, classNames, locale, sticky }) {
4989
+ */ function DataTableBodyCell({ column, row, classNames, locale, sticky, cellContextMenu, onViewEditHistory, cellContextMenuItems }) {
4652
4990
  const { startEdit, cancelEdit, isEditing, isPending, commitEdit, isPeeking, openPeek, closePeek } = useDataTableContext();
4653
4991
  const cellRef = React.useRef(null);
4654
4992
  // Feature B rework (#8) -- the chip-list popover's own live position, in VIEWPORT
@@ -4664,6 +5002,16 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4664
5002
  // editable cell's existing double-click-to-edit (tranche 5, unchanged) must keep
4665
5003
  // rendering exactly as it always has, not suddenly gain overlay chrome.
4666
5004
  const [overlayOpen, setOverlayOpen] = React.useState(false);
5005
+ // Attio parity (attio-column-features-full.md gap #1) -- the right-click menu's own
5006
+ // point + open state. `contextMenuPoint` is frozen at the moment of the right-click
5007
+ // (never re-read live), same reasoning as `overlayOpen` above: the menu shouldn't
5008
+ // chase the cursor after it opens. `<CellContextMenu>` itself is only rendered below
5009
+ // while `contextMenuOpen` is true (not permanently mounted per cell) -- unlike
5010
+ // `ColumnMenu` (one per COLUMN, header row only), this component runs once per
5011
+ // row×column cell, and a floating-ui instance genuinely idle in every one of
5012
+ // thousands of cells would be real, avoidable overhead.
5013
+ const [contextMenuPoint, setContextMenuPoint] = React.useState(null);
5014
+ const [contextMenuOpen, setContextMenuOpen] = React.useState(false);
4667
5015
  const alwaysLive = isAlwaysLiveEditable(column);
4668
5016
  const chipListEditable = isChipListEditable(column);
4669
5017
  // A gated `editable` column (everything except the always-live widgets AND the
@@ -4715,6 +5063,19 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4715
5063
  setOverlayOpen(isCellTruncated(event.currentTarget));
4716
5064
  startEdit(row.id, column.key);
4717
5065
  }
5066
+ function handleContextMenu(event) {
5067
+ if (!cellContextMenu) return;
5068
+ // Replaces the browser's own native context menu (Attio's own gesture) -- and
5069
+ // must not also reach the row's own `onRowClick`, same reasoning as every other
5070
+ // cell-local click handler in this file.
5071
+ event.preventDefault();
5072
+ event.stopPropagation();
5073
+ setContextMenuPoint({
5074
+ x: event.clientX,
5075
+ y: event.clientY
5076
+ });
5077
+ setContextMenuOpen(true);
5078
+ }
4718
5079
  function handleClick(event) {
4719
5080
  if (chipListEditable) {
4720
5081
  // Attio's own chip-list gesture: click opens the editor regardless of
@@ -4752,6 +5113,16 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4752
5113
  // mounted, in `document.body` -- see `isChipListEditable`'s doc comment) and by
4753
5114
  // the reposition effect further down.
4754
5115
  const popoverRef = React.useRef(null);
5116
+ // Feature C's own portaled overlay (`CellExpandOverlay`, editors.tsx) -- its own DOM
5117
+ // node, once mounted, same reasoning as `popoverRef` above: it lives outside
5118
+ // `cellRef`'s subtree (a sibling of the table in `document.body`, not a descendant of
5119
+ // the `<td>`), so the outside-click detector below needs a ref to it too. Without
5120
+ // this, `isInside` saw every click/keystroke INSIDE the portaled editor as "outside"
5121
+ // once the overlay actually escaped the `<td>` (this fix's whole point) -- e.g.
5122
+ // typing into the overlay's own `TextEditor` fires a pointerdown on an input that is
5123
+ // no longer `cellRef.current`'s descendant, so every keystroke closed the overlay and
5124
+ // cancelled the edit before the user finished typing.
5125
+ const expandOverlayRef = React.useRef(null);
4755
5126
  // Closes the overlay on Escape or an outside click/pointerdown -- a gated
4756
5127
  // editor's OWN Escape/blur handling already ends `isEditing` on its own (see
4757
5128
  // `TextEditor`/`SelectEditor`'s existing onCancel/onOpenChange wiring), which
@@ -4764,7 +5135,7 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4764
5135
  React.useEffect(()=>{
4765
5136
  if (!overlayOpen) return;
4766
5137
  function isInside(target) {
4767
- return (cellRef.current?.contains(target) ?? false) || (popoverRef.current?.contains(target) ?? false);
5138
+ return (cellRef.current?.contains(target) ?? false) || (popoverRef.current?.contains(target) ?? false) || (expandOverlayRef.current?.contains(target) ?? false);
4768
5139
  }
4769
5140
  function handlePointerDown(event) {
4770
5141
  if (!isInside(event.target)) {
@@ -4818,7 +5189,7 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4818
5189
  const pending = isPending(row.id, column.key);
4819
5190
  return /*#__PURE__*/ jsxs(Fragment, {
4820
5191
  children: [
4821
- /*#__PURE__*/ jsx(DataTableCell, {
5192
+ /*#__PURE__*/ jsxs(DataTableCell, {
4822
5193
  ref: cellRef,
4823
5194
  "data-numeric": dataAttr(column.type === "number" || column.type === "currency"),
4824
5195
  "data-editable": dataAttr(!!column.editable),
@@ -4829,14 +5200,36 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4829
5200
  style: sticky.style,
4830
5201
  onClick: handleClick,
4831
5202
  onDoubleClick: handleDoubleClick,
4832
- children: /*#__PURE__*/ jsx(CellContent, {
4833
- column: column,
4834
- row: row,
4835
- rowId: row.id,
4836
- classNames: classNames,
4837
- locale: locale,
4838
- overlayActive: overlayActive
4839
- })
5203
+ onContextMenu: handleContextMenu,
5204
+ children: [
5205
+ /*#__PURE__*/ jsx(CellContent, {
5206
+ column: column,
5207
+ row: row,
5208
+ rowId: row.id,
5209
+ classNames: classNames,
5210
+ locale: locale,
5211
+ overlayActive: overlayActive,
5212
+ cellRef: cellRef,
5213
+ expandOverlayRef: expandOverlayRef
5214
+ }),
5215
+ cellContextMenu && contextMenuOpen && contextMenuPoint && // A `<td>` (unlike its own parent `<tr>`) can validly hold any flow
5216
+ // content, so `CellContextMenu`'s own 0x0 `MenuAnchor` -- itself never
5217
+ // portaled, only `MenuPositioner`'s actual dropdown content is (via
5218
+ // floating-ui's own `FloatingPortal`, straight to `document.body`) --
5219
+ // renders here, INSIDE this cell, rather than as a sibling of it.
5220
+ /*#__PURE__*/ jsx(CellContextMenu, {
5221
+ column: column,
5222
+ row: row,
5223
+ value: rawValue,
5224
+ locale: locale,
5225
+ point: contextMenuPoint,
5226
+ open: contextMenuOpen,
5227
+ onOpenChange: setContextMenuOpen,
5228
+ onCommit: (value)=>commitEdit(row.id, column.key, value),
5229
+ onViewEditHistory: onViewEditHistory,
5230
+ cellContextMenuItems: cellContextMenuItems
5231
+ })
5232
+ ]
4840
5233
  }),
4841
5234
  chipListEditorOpen && popoverStyle && /*#__PURE__*/ createPortal(// biome-ignore lint/a11y/noStaticElementInteractions: not a widget -- same shape as `CellExpandOverlay` (editors.tsx), which this box is a portaled variant of: it never responds to interaction on ITSELF, only stops propagation of clicks that already occurred on a real interactive descendant (the editor's input/remove buttons). No role fits it because it isn't a control.
4842
5235
  /*#__PURE__*/ jsx("div", {
@@ -4862,7 +5255,7 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4862
5255
  * display (a custom `render` wins there, same as before `editable` existed; it never
4863
5256
  * overrides an actual editing/always-live state, matching crisp's own split between
4864
5257
  * `renderTyped`'s rest-state branch and the editing branches above it).
4865
- */ function CellContent({ column, row, rowId, classNames, locale, overlayActive }) {
5258
+ */ function CellContent({ column, row, rowId, classNames, locale, overlayActive, cellRef, expandOverlayRef }) {
4866
5259
  const { isEditing, isPending, getEditError, commitEdit, cancelEdit } = useDataTableContext();
4867
5260
  const rawValue = getColumnValue(column, row);
4868
5261
  const pending = isPending(rowId, column.key);
@@ -4956,6 +5349,8 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4956
5349
  children: [
4957
5350
  overlayActive ? /*#__PURE__*/ jsx(CellExpandOverlay, {
4958
5351
  className: classNames.expandOverlay,
5352
+ anchorRef: cellRef,
5353
+ contentRef: expandOverlayRef,
4959
5354
  children: column.editable ? node : /*#__PURE__*/ jsx("span", {
4960
5355
  className: classNames.expandOverlayText,
4961
5356
  children: node
@@ -5066,64 +5461,52 @@ function DisplayCellContent({ column, row, value, classNames, locale }) {
5066
5461
  }
5067
5462
  }
5068
5463
 
5069
- const { ClassNamesProvider, withContext: withContext$1, useClassNames } = createSlotRecipeContext(menu);
5070
- const { PropsProvider: ItemPropsProvider, useProps: useItemProps, withContext: withItemContext, ClassNamesProvider: ItemClassNamesProvider } = createSlotRecipeContext(menuItem);
5071
- const withStateProps = createWithStateProps([
5072
- useMenuContext
5073
- ]);
5074
- const withItemStateProps = createWithStateProps([
5075
- useMenuItemContext
5076
- ]);
5077
- const MenuRoot = (props)=>{
5078
- const [variantProps, otherProps] = menu.splitVariantProps(props);
5079
- const classNames = menu(variantProps);
5080
- return /*#__PURE__*/ jsx(ClassNamesProvider, {
5081
- value: classNames,
5082
- children: /*#__PURE__*/ jsx(ItemPropsProvider, {
5083
- value: {
5084
- size: variantProps.size
5085
- },
5086
- children: /*#__PURE__*/ jsx(Menu.Root, {
5087
- ...otherProps
5464
+ const DataTableBulkBarRoot = withContext$1(Primitive.div, "bulkBar");
5465
+ const DataTableBulkBarCount = withContext$1(Primitive.span, "bulkBarCount");
5466
+ const DataTableBulkBarClear = withContext$1(Primitive.button, "bulkBarClear");
5467
+ /** Wraps whatever the CONSUMER passes as this bar's own action buttons -- the bar
5468
+ * itself only ever supplies chrome (count + clear); every actual action (Add to
5469
+ * list, Send email, …) is the consumer's own `<ActionButton>`/`<Menu>`, the same
5470
+ * "chrome vs. consumer content" split `toolbarActions`/`toolbarEnd` already draw for
5471
+ * the toolbar's own custom actions. */ const DataTableBulkBarActions = withContext$1(Primitive.div, "bulkBarActions");
5472
+ /**
5473
+ * The floating bulk-action pill (gap #4) -- renders nothing while no row is selected
5474
+ * (reads `selectedIds` off the table-wide context itself, so a consumer doesn't have
5475
+ * to gate this on `selectable`/`selectedIds` on their own). Composed as the LAST
5476
+ * child of the table's own root (`DataTable.tsx`), which is `position: relative` for
5477
+ * exactly this reason -- see `data-table.ts`'s own `bulkBar` slot comment for why its
5478
+ * geometry borrows `contextual-floating-button`'s already-measured vars rather than a
5479
+ * guessed literal.
5480
+ */ function DataTableBulkBar({ clearLabel = "Clear selection", children }) {
5481
+ const { selectedIds, clearSelection } = useDataTableContext();
5482
+ if (selectedIds.length === 0) return null;
5483
+ return /*#__PURE__*/ jsxs(DataTableBulkBarRoot, {
5484
+ role: "toolbar",
5485
+ "aria-label": "Bulk actions",
5486
+ "aria-orientation": "horizontal",
5487
+ children: [
5488
+ /*#__PURE__*/ jsxs(DataTableBulkBarCount, {
5489
+ "aria-live": "polite",
5490
+ children: [
5491
+ selectedIds.length,
5492
+ " selected"
5493
+ ]
5494
+ }),
5495
+ /*#__PURE__*/ jsx(DataTableBulkBarClear, {
5496
+ type: "button",
5497
+ "aria-label": clearLabel,
5498
+ onClick: clearSelection,
5499
+ children: /*#__PURE__*/ jsx(IconXmarkLine_default, {
5500
+ size: 12,
5501
+ "aria-hidden": "true"
5502
+ })
5503
+ }),
5504
+ children && /*#__PURE__*/ jsx(DataTableBulkBarActions, {
5505
+ children: children(selectedIds)
5088
5506
  })
5089
- })
5090
- });
5091
- };
5092
- Menu.Anchor;
5093
- const MenuTrigger = Menu.Trigger;
5094
- const MenuPositioner = /*#__PURE__*/ React.forwardRef(({ className, ...props }, ref)=>{
5095
- const classNames = useClassNames();
5096
- return /*#__PURE__*/ jsx(Menu.Positioner, {
5097
- ref: ref,
5098
- className: clsx(classNames.positioner, className),
5099
- ...props
5100
- });
5101
- });
5102
- MenuPositioner.displayName = "MenuPositioner";
5103
- const MenuContent = withContext$1(Menu.Content, "content");
5104
- const MenuScrollArea = withContext$1(withStateProps(Primitive.div), "scrollArea");
5105
- withContext$1(Menu.Group, "group");
5106
- withContext$1(Menu.GroupLabel, "groupLabel");
5107
- const MenuItem = /*#__PURE__*/ React.forwardRef(({ className: propClassName, ...props }, ref)=>{
5108
- const [variantProps, otherProps] = menuItem.splitVariantProps(props);
5109
- const parentProps = useItemProps();
5110
- const classNames = menuItem({
5111
- ...parentProps,
5112
- ...variantProps
5113
- });
5114
- return /*#__PURE__*/ jsx(ItemClassNamesProvider, {
5115
- value: classNames,
5116
- children: /*#__PURE__*/ jsx(Menu.Item, {
5117
- ref: ref,
5118
- className: clsx(classNames.root, propClassName),
5119
- ...otherProps
5120
- })
5507
+ ]
5121
5508
  });
5122
- });
5123
- MenuItem.displayName = "MenuItem";
5124
- withItemContext(withItemStateProps(Primitive.div), "body");
5125
- const MenuItemLabel = withItemContext(withItemStateProps(Primitive.span), "label");
5126
- withItemContext(withItemStateProps(Primitive.span), "description");
5509
+ }
5127
5510
 
5128
5511
  function ColumnCalcCells({ columns, rows, selectable, stickyFirst, stickyLast }) {
5129
5512
  const effective = useEffectiveColumns(columns, {
@@ -5131,10 +5514,9 @@ function ColumnCalcCells({ columns, rows, selectable, stickyFirst, stickyLast })
5131
5514
  stickyFirst
5132
5515
  });
5133
5516
  return /*#__PURE__*/ jsx(Fragment, {
5134
- children: effective.map((entry, index)=>{
5517
+ children: effective.map((entry)=>{
5135
5518
  const { column } = entry;
5136
- const isLast = index === effective.length - 1;
5137
- const sticky = pinnedCellProps(entry, stickyLast, isLast);
5519
+ const sticky = pinnedCellProps(entry, stickyLast);
5138
5520
  return /*#__PURE__*/ jsx(DataTableFooterCell, {
5139
5521
  "data-sticky": sticky["data-sticky"],
5140
5522
  "data-sticky-edge": sticky["data-sticky-edge"],
@@ -5150,10 +5532,11 @@ function ColumnCalcCells({ columns, rows, selectable, stickyFirst, stickyLast })
5150
5532
  })
5151
5533
  });
5152
5534
  }
5153
- function formatCalcValue(value) {
5154
- return value.toLocaleString(undefined, {
5535
+ function formatCalcValue(value, op) {
5536
+ const formatted = value.toLocaleString(undefined, {
5155
5537
  maximumFractionDigits: 2
5156
5538
  });
5539
+ return op === "percentEmpty" || op === "percentFilled" ? `${formatted}%` : formatted;
5157
5540
  }
5158
5541
  /**
5159
5542
  * One ColumnCalc footer cell -- a click-anywhere-in-the-cell trigger (crisp's own
@@ -5188,7 +5571,7 @@ function formatCalcValue(value) {
5188
5571
  children: [
5189
5572
  /*#__PURE__*/ jsx("span", {
5190
5573
  className: "seed-data-table-column-calc-value",
5191
- children: formatCalcValue(result)
5574
+ children: formatCalcValue(result, op)
5192
5575
  }),
5193
5576
  /*#__PURE__*/ jsx("span", {
5194
5577
  className: "seed-data-table-column-calc-op",
@@ -5209,21 +5592,13 @@ function formatCalcValue(value) {
5209
5592
  style: {
5210
5593
  minWidth: 160
5211
5594
  },
5212
- children: /*#__PURE__*/ jsxs(MenuScrollArea, {
5213
- children: [
5214
- ops.map((candidate)=>/*#__PURE__*/ jsx(MenuItem, {
5215
- onClick: ()=>setColumnCalcOp(column.key, candidate),
5216
- children: /*#__PURE__*/ jsx(MenuItemLabel, {
5217
- children: DATA_TABLE_CALC_LABEL[candidate]
5218
- })
5219
- }, candidate)),
5220
- op && /*#__PURE__*/ jsx(MenuItem, {
5221
- onClick: ()=>setColumnCalcOp(column.key, undefined),
5595
+ children: /*#__PURE__*/ jsx(MenuScrollArea, {
5596
+ children: ops.map((candidate)=>/*#__PURE__*/ jsx(MenuItem, {
5597
+ onClick: ()=>setColumnCalcOp(column.key, candidate),
5222
5598
  children: /*#__PURE__*/ jsx(MenuItemLabel, {
5223
- children: "Clear"
5599
+ children: DATA_TABLE_CALC_LABEL[candidate]
5224
5600
  })
5225
- })
5226
- ]
5601
+ }, candidate))
5227
5602
  })
5228
5603
  })
5229
5604
  })
@@ -5364,10 +5739,9 @@ const DEFAULT_SORT_ICON = {
5364
5739
  });
5365
5740
  const [renamingKey, setRenamingKey] = React.useState(null);
5366
5741
  return /*#__PURE__*/ jsx(Fragment, {
5367
- children: effective.map((entry, index)=>{
5742
+ children: effective.map((entry)=>{
5368
5743
  const { column, label, width } = entry;
5369
- const isLast = index === effective.length - 1;
5370
- const sticky = pinnedCellProps(entry, stickyLast, isLast);
5744
+ const sticky = pinnedCellProps(entry, stickyLast);
5371
5745
  return /*#__PURE__*/ jsxs(DataTableHeaderCell, {
5372
5746
  columnKey: column.key,
5373
5747
  sortable: column.sortable,
@@ -10488,6 +10862,18 @@ ContinuousDatePicker.displayName = "ContinuousDatePicker";
10488
10862
  // value list) down to `is`/`is not`/`empty`/`not empty`. `"isanyof"` is appended to any
10489
10863
  // of the four whenever the column supplies `filterValues` -- a faceted checkbox list,
10490
10864
  // independent of the column's own kind.
10865
+ //
10866
+ // Multi-select fix (DATATABLE-ATTIO-SPEC.md gap #12, live-measured 2026-09-30): a
10867
+ // `kind: "select"` column whose CELL is multi-valued (`type: "tags"`/`"chip-list"` --
10868
+ // a chip-list/tag column, not a single status pill) offers Attio's own
10869
+ // `contains`/`does not contain` instead of `is`/`is not` -- "is" reads as an exact-
10870
+ // set-equality check against an array value, which is not what Attio's own multi-
10871
+ // select filter means or what `applyToolbarFilters`' `is`/`isnot` branch (a plain
10872
+ // string-equality compare via `asText`) computes. `contains`/`doesnotcontain` are
10873
+ // already correct here unmodified: `applyToolbarFilters` already runs them against
10874
+ // `asText(raw)` (an array's own values joined), which is exactly "does any tag
10875
+ // match". Single-select (no `filterValues`-array cell type) keeps `is`/`is not` --
10876
+ // not re-measured against Attio, so left alone per the discovery doc's own hedge.
10491
10877
  ////////////////////////////////////////////////////////////////////////////////////
10492
10878
  const OP_ORDER = [
10493
10879
  "contains",
@@ -10521,9 +10907,19 @@ const SELECT_OP_ORDER = [
10521
10907
  "empty",
10522
10908
  "notempty"
10523
10909
  ];
10910
+ const MULTI_SELECT_OP_ORDER = [
10911
+ "contains",
10912
+ "doesnotcontain",
10913
+ "empty",
10914
+ "notempty"
10915
+ ];
10524
10916
  /** The operators a column of this `kind` offers, in display order -- `hasValues`
10525
- * (the column supplies `filterValues`) appends `"isanyof"` regardless of kind. */ function opsForKind(kind, hasValues) {
10526
- const base = kind === "number" ? NUMBER_OP_ORDER : kind === "date" ? DATE_OP_ORDER : kind === "select" ? SELECT_OP_ORDER : OP_ORDER;
10917
+ * (the column supplies `filterValues`) appends `"isanyof"` regardless of kind.
10918
+ * `multiValue` (the column's CELL type is a chip-list/tags column, not just its
10919
+ * filter `kind`) swaps `SELECT_OP_ORDER`'s `is`/`is not` for `contains`/`does not
10920
+ * contain`, matching Attio's own multi-select filter -- see the operator-sets
10921
+ * comment above. */ function opsForKind(kind, hasValues, multiValue = false) {
10922
+ const base = kind === "number" ? NUMBER_OP_ORDER : kind === "date" ? DATE_OP_ORDER : kind === "select" ? multiValue ? MULTI_SELECT_OP_ORDER : SELECT_OP_ORDER : OP_ORDER;
10527
10923
  return hasValues ? [
10528
10924
  ...base,
10529
10925
  "isanyof"
@@ -10891,66 +11287,58 @@ const formatFilterDate = (iso)=>{
10891
11287
  return `${OPERATOR_LABELS[filter.op]} ${value}`;
10892
11288
  }
10893
11289
  /**
10894
- * One filter condition, INSIDE the Filter popover -- a compact one-line summary row
10895
- * (field: operator value) with a trailing remove "x", expanding in place (accordion,
10896
- * not a second popover) to the field/operator/value editor when clicked. Presentation
10897
- * note (crisp/#593-style live re-check, 2026-09-28): Attio's own toolbar keeps active
10898
- * conditions as a compact "Filter · N" count on the trigger, not a row of always-on
10899
- * pill chips spread across the toolbar -- this replaces that first pass (which DID
10900
- * render one big chip per condition, directly in the toolbar row) with conditions
10901
- * living inside the ONE popover the trigger opens, the same list-of-rows shape
10902
- * `SortControl` above already uses for its own conditions.
10903
- */ function FilterConditionRow({ filter, columns, expanded, onToggle }) {
11290
+ * One filter condition, INSIDE the Filter popover -- Attio's own compact chip shape
11291
+ * (DATATABLE-ATTIO-SPEC.md gap #12, re-measured live 2026-09-30): `[attribute ▾]
11292
+ * [operator …] [value] [remove ×]`, ALL of it visible at once, never behind a click-
11293
+ * to-expand accordion. Supersedes this file's earlier 2026-09-28 note (which read
11294
+ * Attio as keeping conditions collapsed behind a bare "Filter · N" count and put them
11295
+ * in an accordion for that reason) -- the fresh read found the opposite: every active
11296
+ * condition shows attribute/operator/value simultaneously. This keeps the outer shell
11297
+ * (the "Filter"/"Filter · N" trigger opening ONE popover, `SortControl`'s own
11298
+ * list-of-rows shape) rather than moving chips into the toolbar row itself, so the
11299
+ * already-covered trigger/`Add filter` DOM (`DataTable.test.tsx`) stays put; only the
11300
+ * PER-CONDITION row tightens, from an accordion to an always-open card. The operator
11301
+ * set stays a row of small pill buttons (not a dropdown) so every existing test that
11302
+ * clicks an operator by its exact button name keeps working unchanged.
11303
+ */ function FilterConditionRow({ filter, columns }) {
10904
11304
  const { patchToolbarFilter, changeToolbarFilterField, removeToolbarFilter } = useDataTableContext();
10905
11305
  const column = columns.find((c)=>c.key === filter.key);
10906
11306
  const kind = column?.kind;
11307
+ const multiValue = column?.type === "tags" || column?.type === "chip-list";
10907
11308
  const values = column?.filterValues ?? [];
10908
- const ops = opsForKind(kind, values.length > 0);
10909
- return /*#__PURE__*/ jsxs("div", {
11309
+ const ops = opsForKind(kind, values.length > 0, multiValue);
11310
+ return /*#__PURE__*/ jsxs("fieldset", {
11311
+ "aria-label": `${column ? columnLabel(column) : filter.key} filter: ${conditionSummary(filter, column)}`,
10910
11312
  style: {
11313
+ margin: 0,
10911
11314
  display: "flex",
10912
11315
  flexDirection: "column",
10913
- gap: 6,
11316
+ gap: 8,
10914
11317
  borderRadius: 6,
10915
- background: expanded ? "var(--seed-color-bg-neutral-weak)" : "transparent",
10916
- padding: expanded ? 6 : 0
11318
+ border: "1px solid var(--seed-color-border-neutral)",
11319
+ background: "var(--seed-color-bg-neutral-weak)",
11320
+ padding: 6
10917
11321
  },
10918
11322
  children: [
10919
11323
  /*#__PURE__*/ jsxs("div", {
10920
11324
  style: {
10921
11325
  display: "flex",
10922
11326
  alignItems: "center",
10923
- gap: 2
11327
+ gap: 4
10924
11328
  },
10925
11329
  children: [
10926
- /*#__PURE__*/ jsxs("button", {
10927
- type: "button",
10928
- onClick: onToggle,
11330
+ /*#__PURE__*/ jsx(InlineSelect, {
11331
+ ariaLabel: "Filter by field",
11332
+ value: filter.key,
11333
+ onChange: (key)=>changeToolbarFilterField(filter.id, key),
11334
+ options: columns.map((c)=>({
11335
+ value: c.key,
11336
+ label: columnLabel(c)
11337
+ })),
10929
11338
  style: {
10930
11339
  flex: 1,
10931
- minWidth: 0,
10932
- textAlign: "left",
10933
- padding: "6px 8px",
10934
- border: "none",
10935
- borderRadius: 6,
10936
- background: "transparent",
10937
- cursor: "pointer",
10938
- fontSize: 13,
10939
- overflow: "hidden",
10940
- textOverflow: "ellipsis",
10941
- whiteSpace: "nowrap"
10942
- },
10943
- children: [
10944
- /*#__PURE__*/ jsx("strong", {
10945
- style: {
10946
- fontWeight: 500
10947
- },
10948
- children: column ? columnLabel(column) : filter.key
10949
- }),
10950
- ":",
10951
- " ",
10952
- conditionSummary(filter, column)
10953
- ]
11340
+ minWidth: 0
11341
+ }
10954
11342
  }),
10955
11343
  /*#__PURE__*/ jsx("button", {
10956
11344
  type: "button",
@@ -10964,136 +11352,103 @@ const formatFilterDate = (iso)=>{
10964
11352
  })
10965
11353
  ]
10966
11354
  }),
10967
- expanded && /*#__PURE__*/ jsxs("div", {
11355
+ /*#__PURE__*/ jsx("div", {
10968
11356
  style: {
10969
- padding: "0 8px 4px",
10970
11357
  display: "flex",
10971
- flexDirection: "column",
10972
- gap: 8
11358
+ flexWrap: "wrap",
11359
+ gap: 2
10973
11360
  },
10974
- children: [
10975
- /*#__PURE__*/ jsx("div", {
10976
- style: {
10977
- fontSize: 12,
10978
- color: "var(--seed-color-fg-neutral-subtle)"
10979
- },
10980
- children: "Field"
10981
- }),
10982
- /*#__PURE__*/ jsx(InlineSelect, {
10983
- ariaLabel: "Filter by field",
10984
- value: filter.key,
10985
- onChange: (key)=>changeToolbarFilterField(filter.id, key),
10986
- options: columns.map((c)=>({
10987
- value: c.key,
10988
- label: columnLabel(c)
10989
- }))
10990
- }),
10991
- /*#__PURE__*/ jsx("div", {
10992
- style: {
10993
- fontSize: 12,
10994
- color: "var(--seed-color-fg-neutral-subtle)"
10995
- },
10996
- children: "Condition"
10997
- }),
10998
- /*#__PURE__*/ jsx("div", {
11361
+ children: ops.map((op)=>/*#__PURE__*/ jsx("button", {
11362
+ type: "button",
11363
+ "data-active": filter.op === op ? "" : undefined,
11364
+ onClick: ()=>patchToolbarFilter(filter.id, {
11365
+ op
11366
+ }),
10999
11367
  style: {
11000
- display: "flex",
11001
- flexDirection: "column",
11002
- gap: 2
11368
+ textAlign: "left",
11369
+ padding: "4px 8px",
11370
+ borderRadius: 999,
11371
+ border: "none",
11372
+ cursor: "pointer",
11373
+ background: filter.op === op ? "var(--seed-color-bg-layer-default)" : "transparent",
11374
+ fontSize: 12
11003
11375
  },
11004
- children: ops.map((op)=>/*#__PURE__*/ jsx("button", {
11005
- type: "button",
11006
- "data-active": filter.op === op ? "" : undefined,
11007
- onClick: ()=>patchToolbarFilter(filter.id, {
11008
- op
11009
- }),
11010
- style: {
11011
- textAlign: "left",
11012
- padding: "6px 8px",
11013
- borderRadius: 6,
11014
- border: "none",
11015
- cursor: "pointer",
11016
- background: filter.op === op ? "var(--seed-color-bg-layer-default)" : "transparent",
11017
- fontSize: 13
11018
- },
11019
- children: OPERATOR_LABELS[op]
11020
- }, op))
11021
- }),
11022
- opNeedsValues(filter.op) && /*#__PURE__*/ jsx("div", {
11376
+ children: OPERATOR_LABELS[op]
11377
+ }, op))
11378
+ }),
11379
+ opNeedsValues(filter.op) && /*#__PURE__*/ jsx("div", {
11380
+ style: {
11381
+ display: "flex",
11382
+ flexDirection: "column",
11383
+ gap: 4,
11384
+ maxHeight: 160,
11385
+ overflow: "auto"
11386
+ },
11387
+ children: values.map((value)=>{
11388
+ const checked = (filter.values ?? []).includes(value);
11389
+ return /*#__PURE__*/ jsxs(CheckboxRoot, {
11390
+ checked: checked,
11391
+ onCheckedChange: ()=>patchToolbarFilter(filter.id, {
11392
+ values: checked ? (filter.values ?? []).filter((v)=>v !== value) : [
11393
+ ...filter.values ?? [],
11394
+ value
11395
+ ]
11396
+ }),
11023
11397
  style: {
11024
11398
  display: "flex",
11025
- flexDirection: "column",
11026
- gap: 4,
11027
- maxHeight: 160,
11028
- overflow: "auto"
11399
+ alignItems: "center",
11400
+ gap: 8,
11401
+ padding: "4px 0",
11402
+ cursor: "pointer"
11029
11403
  },
11030
- children: values.map((value)=>{
11031
- const checked = (filter.values ?? []).includes(value);
11032
- return /*#__PURE__*/ jsxs(CheckboxRoot, {
11033
- checked: checked,
11034
- onCheckedChange: ()=>patchToolbarFilter(filter.id, {
11035
- values: checked ? (filter.values ?? []).filter((v)=>v !== value) : [
11036
- ...filter.values ?? [],
11037
- value
11038
- ]
11039
- }),
11040
- style: {
11041
- display: "flex",
11042
- alignItems: "center",
11043
- gap: 8,
11044
- padding: "4px 0",
11045
- cursor: "pointer"
11046
- },
11047
- children: [
11048
- /*#__PURE__*/ jsx(CheckboxControl, {
11049
- children: /*#__PURE__*/ jsx(CheckboxIndicator, {
11050
- checked: /*#__PURE__*/ jsx(IconCheckmarkLine_default, {
11051
- size: 12
11052
- })
11053
- })
11054
- }),
11055
- /*#__PURE__*/ jsx(CheckboxLabel, {
11056
- children: value
11057
- }),
11058
- /*#__PURE__*/ jsx(CheckboxHiddenInput, {
11059
- "aria-label": value
11404
+ children: [
11405
+ /*#__PURE__*/ jsx(CheckboxControl, {
11406
+ children: /*#__PURE__*/ jsx(CheckboxIndicator, {
11407
+ checked: /*#__PURE__*/ jsx(IconCheckmarkLine_default, {
11408
+ size: 12
11060
11409
  })
11061
- ]
11062
- }, value);
11063
- })
11064
- }),
11065
- opNeedsValue(filter.op) && kind === "date" && /*#__PURE__*/ jsx(DatePicker, {
11066
- value: filter.value ? toDatePickerDate(isoToDate(filter.value) ?? new Date()) : undefined,
11067
- onValueChange: (d)=>d && patchToolbarFilter(filter.id, {
11068
- value: dateToISO(fromDatePickerDate(d))
11069
- })
11070
- }),
11071
- opNeedsValue(filter.op) && kind === "select" && /*#__PURE__*/ jsx(InlineSelect, {
11072
- ariaLabel: "Filter value",
11073
- placeholder: "Value…",
11074
- value: filter.value || undefined,
11075
- onChange: (value)=>patchToolbarFilter(filter.id, {
11076
- value
11410
+ })
11077
11411
  }),
11078
- options: values.map((value)=>({
11079
- value,
11080
- label: value
11081
- }))
11082
- }),
11083
- opNeedsValue(filter.op) && kind !== "date" && kind !== "select" && /*#__PURE__*/ jsx(TextFieldRoot, {
11084
- variant: "outline",
11085
- size: "medium",
11086
- children: /*#__PURE__*/ jsx(TextFieldInput, {
11087
- type: kind === "number" ? "number" : "text",
11088
- value: filter.value,
11089
- onChange: (e)=>patchToolbarFilter(filter.id, {
11090
- value: e.target.value
11091
- }),
11092
- placeholder: "Value…",
11093
- "aria-label": "Filter value"
11094
- })
11412
+ /*#__PURE__*/ jsx(CheckboxLabel, {
11413
+ children: value
11414
+ }),
11415
+ /*#__PURE__*/ jsx(CheckboxHiddenInput, {
11416
+ "aria-label": value
11417
+ })
11418
+ ]
11419
+ }, value);
11420
+ })
11421
+ }),
11422
+ opNeedsValue(filter.op) && kind === "date" && /*#__PURE__*/ jsx(DatePicker, {
11423
+ value: filter.value ? toDatePickerDate(isoToDate(filter.value) ?? new Date()) : undefined,
11424
+ onValueChange: (d)=>d && patchToolbarFilter(filter.id, {
11425
+ value: dateToISO(fromDatePickerDate(d))
11095
11426
  })
11096
- ]
11427
+ }),
11428
+ opNeedsValue(filter.op) && kind === "select" && /*#__PURE__*/ jsx(InlineSelect, {
11429
+ ariaLabel: "Filter value",
11430
+ placeholder: "Value…",
11431
+ value: filter.value || undefined,
11432
+ onChange: (value)=>patchToolbarFilter(filter.id, {
11433
+ value
11434
+ }),
11435
+ options: values.map((value)=>({
11436
+ value,
11437
+ label: value
11438
+ }))
11439
+ }),
11440
+ opNeedsValue(filter.op) && kind !== "date" && kind !== "select" && /*#__PURE__*/ jsx(TextFieldRoot, {
11441
+ variant: "outline",
11442
+ size: "medium",
11443
+ children: /*#__PURE__*/ jsx(TextFieldInput, {
11444
+ type: kind === "number" ? "number" : "text",
11445
+ value: filter.value,
11446
+ onChange: (e)=>patchToolbarFilter(filter.id, {
11447
+ value: e.target.value
11448
+ }),
11449
+ placeholder: "Value…",
11450
+ "aria-label": "Filter value"
11451
+ })
11097
11452
  })
11098
11453
  ]
11099
11454
  });
@@ -11101,15 +11456,14 @@ const formatFilterDate = (iso)=>{
11101
11456
  /**
11102
11457
  * The toolbar's Filter trigger + popover -- a compact `"Filter"` / `"Filter · N"`
11103
11458
  * button (matching `SortControl`'s own `"Sort"` / `"Sort · N"`), opening ONE popover
11104
- * that holds both the active conditions (compact rows, `FilterConditionRow` above,
11105
- * one expanded at a time) and, behind "+ Add filter", the field picker that appends a
11106
- * new one -- not a row of always-on pill chips spread across the toolbar (this
11107
- * replaces that first pass; see `FilterConditionRow`'s own doc comment).
11459
+ * that holds both the active conditions (compact chips, `FilterConditionRow` above,
11460
+ * ALL open at once -- no accordion) and, behind "+ Add filter", the field picker that
11461
+ * appends a new one. See `FilterConditionRow`'s own doc comment for why the chips
11462
+ * live inside this one popover rather than spread across the toolbar row itself.
11108
11463
  */ function FilterControl({ columns, label = "Filter" }) {
11109
11464
  const { toolbarFilters, addToolbarFilter } = useDataTableContext();
11110
11465
  const [open, setOpen] = React.useState(false);
11111
11466
  const [pickingField, setPickingField] = React.useState(false);
11112
- const [expandedId, setExpandedId] = React.useState(null);
11113
11467
  return /*#__PURE__*/ jsxs(MenuRoot, {
11114
11468
  open: open,
11115
11469
  onOpenChange: (next)=>{
@@ -11171,8 +11525,7 @@ const formatFilterDate = (iso)=>{
11171
11525
  columns.map((c)=>/*#__PURE__*/ jsx("button", {
11172
11526
  type: "button",
11173
11527
  onClick: ()=>{
11174
- const id = addToolbarFilter(c.key);
11175
- setExpandedId(id);
11528
+ addToolbarFilter(c.key);
11176
11529
  setPickingField(false);
11177
11530
  },
11178
11531
  style: {
@@ -11205,9 +11558,7 @@ const formatFilterDate = (iso)=>{
11205
11558
  }),
11206
11559
  toolbarFilters.map((f)=>/*#__PURE__*/ jsx(FilterConditionRow, {
11207
11560
  filter: f,
11208
- columns: columns,
11209
- expanded: expandedId === f.id,
11210
- onToggle: ()=>setExpandedId((id)=>id === f.id ? null : f.id)
11561
+ columns: columns
11211
11562
  }, f.id)),
11212
11563
  /*#__PURE__*/ jsxs("button", {
11213
11564
  type: "button",
@@ -11236,8 +11587,8 @@ const formatFilterDate = (iso)=>{
11236
11587
  });
11237
11588
  }
11238
11589
 
11239
- const DataTableToolbar = withContext$2(Primitive.div, "toolbar");
11240
- const DataTableCount = withContext$2(Primitive.span, "count");
11590
+ const DataTableToolbar = withContext$1(Primitive.div, "toolbar");
11591
+ const DataTableCount = withContext$1(Primitive.span, "count");
11241
11592
  /**
11242
11593
  * The toolbar's own right-hand group -- `count`'s `marginInlineStart: auto` moved
11243
11594
  * here (off the `<span>` itself) so a consumer's `toolbarActions` and the row count
@@ -11248,7 +11599,7 @@ const DataTableCount = withContext$2(Primitive.span, "count");
11248
11599
  * it. `DataTable.tsx` always renders this wrapper once either `toolbarActions` or
11249
11600
  * `showCount` is on, so it degrades to exactly the old single-count layout when no
11250
11601
  * `toolbarActions` is passed.
11251
- */ const DataTableToolbarEnd = withContext$2(Primitive.div, "toolbarEnd");
11602
+ */ const DataTableToolbarEnd = withContext$1(Primitive.div, "toolbarEnd");
11252
11603
  /**
11253
11604
  * A compact, borderless search box -- crisp's own measured `.crisp-datatable-search`
11254
11605
  * (32px, `bgSurface` background, a leading icon, a soft rest-state shadow, a 1px brand
@@ -11298,7 +11649,7 @@ function RowCount({ shown, total, countLabel }) {
11298
11649
  }
11299
11650
 
11300
11651
  function DataTableInner(props, ref) {
11301
- const { columns, data, surface, className, locale = "en-US", sort, defaultSort, onSortChange, sortIcon, selectable, selectedIds, defaultSelectedIds, onSelectionChange, selectAllLabel = "Select all rows", getRowSelectionLabel = ()=>"Select row", selectionCheckedIcon, selectionIndeterminateIcon, query, defaultQuery, onQueryChange, searchPlaceholder = "Search…", searchLabel = "Search table", toolbarSorts, defaultToolbarSorts, onToolbarSortsChange, toolbarFilters, defaultToolbarFilters, onToolbarFiltersChange, showCount = true, countLabel, toolbarActions, columnMenu = false, reorderable = true, resizable = true, columnOrder, defaultColumnOrder, onColumnOrderChange, hiddenColumnKeys, defaultHiddenColumnKeys, onHiddenColumnKeysChange, columnWidths, defaultColumnWidths, onColumnWidthsChange, columnLabels, defaultColumnLabels, onColumnLabelsChange, pinnedColumnKeys, defaultPinnedColumnKeys, onPinnedColumnKeysChange, onColumnsChange, columnCalcOps, defaultColumnCalcOps, onColumnCalcOpsChange, stickyFirst = false, stickyLast = false, onEdit, interactive = false, onRowClick, onAddRow, addRowLabel = "New record", loading = false, loadingLabel = "Loading...", error, empty = "No results.", onLoadMore, hasMore, loadingMore, scrollProps, ...restProps } = props;
11652
+ const { columns, data, surface, className, locale = "en-US", sort, defaultSort, onSortChange, sortIcon, selectable, selectedIds, defaultSelectedIds, onSelectionChange, selectAllLabel = "Select all rows", getRowSelectionLabel = ()=>"Select row", selectionCheckedIcon, selectionIndeterminateIcon, bulkActions, clearSelectionLabel, query, defaultQuery, onQueryChange, searchPlaceholder = "Search…", searchLabel = "Search table", toolbarSorts, defaultToolbarSorts, onToolbarSortsChange, toolbarFilters, defaultToolbarFilters, onToolbarFiltersChange, showCount = true, countLabel, toolbarActions, columnMenu = false, reorderable = true, resizable = true, columnOrder, defaultColumnOrder, onColumnOrderChange, hiddenColumnKeys, defaultHiddenColumnKeys, onHiddenColumnKeysChange, columnWidths, defaultColumnWidths, onColumnWidthsChange, columnLabels, defaultColumnLabels, onColumnLabelsChange, pinnedColumnKeys, defaultPinnedColumnKeys, onPinnedColumnKeysChange, onColumnsChange, columnCalcOps, defaultColumnCalcOps, onColumnCalcOpsChange, stickyFirst = false, stickyLast = false, onEdit, cellContextMenu = true, onViewEditHistory, cellContextMenuItems, interactive = false, onRowClick, onAddRow, addRowLabel = "New record", loading = false, loadingLabel = "Loading...", error, empty = "No results.", onLoadMore, hasMore, loadingMore, scrollProps, ...restProps } = props;
11302
11653
  const classNames = dataTable({
11303
11654
  surface,
11304
11655
  interactive
@@ -11400,6 +11751,12 @@ function DataTableInner(props, ref) {
11400
11751
  const columnKeys = React.useMemo(()=>columns.map((column)=>column.key), [
11401
11752
  columns
11402
11753
  ]);
11754
+ // Threaded down to `reconcileColumnOrder` (react-headless's `dom.ts`) so a brand-new
11755
+ // column added to `columns` after mount gets appended BEFORE this one, not past it --
11756
+ // see that function's own doc comment for the bug this prevents.
11757
+ const actionsColumnKey = React.useMemo(()=>columns.find((column)=>column.actions)?.key, [
11758
+ columns
11759
+ ]);
11403
11760
  const searchable = React.useMemo(()=>columns.some((column)=>column.searchable), [
11404
11761
  columns
11405
11762
  ]);
@@ -11422,7 +11779,7 @@ function DataTableInner(props, ref) {
11422
11779
  // loading/error/empty body, infinite scroll's sentinel, the add-row footer) reads
11423
11780
  // this ONE value rather than each re-deriving it, so they can never drift apart.
11424
11781
  const totalCols = columns.length + (selectable ? 1 : 0);
11425
- return /*#__PURE__*/ jsx(ClassNamesProvider$1, {
11782
+ return /*#__PURE__*/ jsx(ClassNamesProvider, {
11426
11783
  value: classNames,
11427
11784
  children: /*#__PURE__*/ jsxs(DataTableRoot, {
11428
11785
  ref: ref,
@@ -11442,6 +11799,7 @@ function DataTableInner(props, ref) {
11442
11799
  toolbarFilters: resolvedToolbarFilters,
11443
11800
  onToolbarFiltersChange: handleToolbarFiltersChange,
11444
11801
  columnKeys: columnKeys,
11802
+ actionsColumnKey: actionsColumnKey,
11445
11803
  columnOrder: columnOrder,
11446
11804
  defaultColumnOrder: defaultColumnOrder,
11447
11805
  onColumnOrderChange: onColumnOrderChange,
@@ -11578,7 +11936,10 @@ function DataTableInner(props, ref) {
11578
11936
  locale: locale,
11579
11937
  selectable: !!selectable,
11580
11938
  stickyFirst: stickyFirst,
11581
- stickyLast: resolvedStickyLast
11939
+ stickyLast: resolvedStickyLast,
11940
+ cellContextMenu: cellContextMenu,
11941
+ onViewEditHistory: onViewEditHistory,
11942
+ cellContextMenuItems: cellContextMenuItems
11582
11943
  })
11583
11944
  ]
11584
11945
  }, row.id)),
@@ -11617,6 +11978,10 @@ function DataTableInner(props, ref) {
11617
11978
  })
11618
11979
  ]
11619
11980
  })
11981
+ }),
11982
+ selectable && /*#__PURE__*/ jsx(DataTableBulkBar, {
11983
+ clearLabel: clearSelectionLabel,
11984
+ children: bulkActions
11620
11985
  })
11621
11986
  ]
11622
11987
  })
@@ -11670,7 +12035,7 @@ DataTableInner.displayName = "DataTable";
11670
12035
  // is a SIBLING of the toolbar (both inside `DataTablePrimitiveRoot`'s card `<div>`)
11671
12036
  // rather than `DataTablePrimitiveRoot` itself: a `<table>` cannot host the toolbar as a
11672
12037
  // child without the HTML parser foster-parenting it back out.
11673
- const DataTableTable = withContext$2(DataTableTable$1, "table");
12038
+ const DataTableTable = withContext$1(DataTableTable$1, "table");
11674
12039
  /** One `<col>` per rendered column (plus the 40px selection column, if any) -- the
11675
12040
  * SINGLE place a column's width is ever set now. Every row's header/body/footer cell
11676
12041
  * reads its own width off this shared `<colgroup>` by construction (native table
@@ -11707,15 +12072,15 @@ const DataTableTable = withContext$2(DataTableTable$1, "table");
11707
12072
  ]
11708
12073
  });
11709
12074
  }
11710
- const DataTableHeader = withContext$2(DataTableHeader$1, "header");
11711
- const DataTableHeaderCell = withContext$2(DataTableHeaderCell$1, "headerCell");
12075
+ const DataTableHeader = withContext$1(DataTableHeader$1, "header");
12076
+ const DataTableHeaderCell = withContext$1(DataTableHeaderCell$1, "headerCell");
11712
12077
  // `.crisp-datatable-th-inner` -- the flex row INSIDE a `<th>` (label + filter trigger +
11713
12078
  // sort chevron + column menu). See qvism-preset's own `headerCellInner` slot comment
11714
12079
  // for why this lives one level below the cell itself rather than on it.
11715
- const DataTableHeaderCellInner = withContext$2(Primitive.div, "headerCellInner");
12080
+ const DataTableHeaderCellInner = withContext$1(Primitive.div, "headerCellInner");
11716
12081
  /** The label text specifically -- the one thing in `DataTableHeaderCellInner` that
11717
12082
  * truncates; the trailing filter/sort/menu icons stay fully visible regardless of how
11718
- * long a column's own header text is. */ const DataTableHeaderCellLabel = withContext$2(Primitive.span, "headerCellLabel");
12083
+ * long a column's own header text is. */ const DataTableHeaderCellLabel = withContext$1(Primitive.span, "headerCellLabel");
11719
12084
  /**
11720
12085
  * A selection `<th>`/`<td>`/footer `<td>` composes ITS base slot (`headerCell`/`cell`/
11721
12086
  * `footerCell`) together with the `selectionCell` MODIFIER, rather than replacing it --
@@ -11726,7 +12091,7 @@ const DataTableHeaderCellInner = withContext$2(Primitive.div, "headerCellInner")
11726
12091
  * a second recipe call.
11727
12092
  */ function withSelectionCell(Component, baseSlot) {
11728
12093
  const StyledComponent = /*#__PURE__*/ forwardRef((props, ref)=>{
11729
- const classNames = useClassNames$1();
12094
+ const classNames = useClassNames();
11730
12095
  return /*#__PURE__*/ jsx(Component, {
11731
12096
  ref: ref,
11732
12097
  ...props,
@@ -11737,12 +12102,12 @@ const DataTableHeaderCellInner = withContext$2(Primitive.div, "headerCellInner")
11737
12102
  return StyledComponent;
11738
12103
  }
11739
12104
  const DataTableSelectionHeaderCell = withSelectionCell(DataTableHeaderCell$1, "headerCell");
11740
- const DataTableHeaderCellIcon = withContext$2(Primitive.span, "headerCellIcon");
12105
+ const DataTableHeaderCellIcon = withContext$1(Primitive.span, "headerCellIcon");
11741
12106
  // body has no CSS slot of its own in the rootage spec -- it is a plain structural
11742
12107
  // `<tbody>`, so it is not wrapped in `withContext`.
11743
12108
  const DataTableBody = DataTableBody$1;
11744
- const DataTableRow = withContext$2(DataTableRow$1, "row");
11745
- const DataTableCell = withContext$2(DataTableCell$1, "cell");
12109
+ const DataTableRow = withContext$1(DataTableRow$1, "row");
12110
+ const DataTableCell = withContext$1(DataTableCell$1, "cell");
11746
12111
  const DataTableSelectionCell = withSelectionCell(DataTableCell$1, "cell");
11747
12112
  // Tranche 4 -- column menu + reorder + resize + sticky columns. A scroll boundary
11748
12113
  // wrapping the whole `<table>`: `overflowX: auto` is what makes a wide table (more
@@ -11752,7 +12117,7 @@ const DataTableSelectionCell = withSelectionCell(DataTableCell$1, "cell");
11752
12117
  // stays out of this slot's own base css (nothing here has a bounded height of its own
11753
12118
  // to clip), so it never introduces a second vertical scrollbar next to whatever
11754
12119
  // ambient container the whole DataTable already sits in.
11755
- const DataTableScrollBodyBase = withContext$2(Primitive.div, "scrollArea");
12120
+ const DataTableScrollBodyBase = withContext$1(Primitive.div, "scrollArea");
11756
12121
  function DataTableScrollBody({ children, scrollProps }) {
11757
12122
  const { scrollAreaRef, scrolledStart, scrolledEnd, onScroll } = useDataTableContext();
11758
12123
  const { className: scrollClassName, style: scrollStyle, ...otherScrollProps } = scrollProps ?? {};
@@ -11767,7 +12132,7 @@ function DataTableScrollBody({ children, scrollProps }) {
11767
12132
  children: children
11768
12133
  });
11769
12134
  }
11770
- const DataTableFooterBase = withContext$2(Primitive.tfoot, "footer");
12135
+ const DataTableFooterBase = withContext$1(Primitive.tfoot, "footer");
11771
12136
  function DataTableFooter({ children, ...props }) {
11772
12137
  return /*#__PURE__*/ jsx(DataTableFooterBase, {
11773
12138
  ...props,
@@ -11776,7 +12141,7 @@ function DataTableFooter({ children, ...props }) {
11776
12141
  })
11777
12142
  });
11778
12143
  }
11779
- const DataTableFooterCell = withContext$2(Primitive.td, "footerCell");
12144
+ const DataTableFooterCell = withContext$1(Primitive.td, "footerCell");
11780
12145
  // The selection column's OWN sticky wrappers -- one each for header/body/footer,
11781
12146
  // reading the live pinned set via `useSelectionStickyFirst` (column-layout.ts) instead
11782
12147
  // of the `stickyFirst` PROP alone, so the checkbox column sticks the moment ANY column
@@ -15713,4 +16078,4 @@ function RecordPreviewPanel({ open: _open, record, onClose, className, animation
15713
16078
  }
15714
16079
  RecordPreviewPanel.displayName = "RecordPreviewPanel";
15715
16080
 
15716
- export { DataTable, DataTableViewsControl, RecordPreview, ResizablePanel };
16081
+ export { DataTable, DataTableBulkBar, DataTableViewsControl, RecordPreview, ResizablePanel };