@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.cjs CHANGED
@@ -17,6 +17,9 @@ var reactCheckbox = require('@seed-design/react-checkbox');
17
17
  var reactScaleFeedback = require('@seed-design/react-scale-feedback');
18
18
  var checkboxGroup = require('@seed-design/css/recipes/checkbox-group');
19
19
  var reactDom = require('react-dom');
20
+ var reactMenu = require('@seed-design/react-menu');
21
+ var menu = require('@seed-design/css/recipes/menu');
22
+ var menuItem = require('@seed-design/css/recipes/menu-item');
20
23
  var reactImage = require('@seed-design/react-image');
21
24
  var avatar = require('@seed-design/css/recipes/avatar');
22
25
  require('@seed-design/css/recipes/avatar-stack');
@@ -31,9 +34,6 @@ var _switch = require('@seed-design/css/recipes/switch');
31
34
  var switchmark = require('@seed-design/css/recipes/switchmark');
32
35
  var reactTextField = require('@seed-design/react-text-field');
33
36
  var textInput = require('@seed-design/css/recipes/text-input');
34
- var reactMenu = require('@seed-design/react-menu');
35
- var menu = require('@seed-design/css/recipes/menu');
36
- var menuItem = require('@seed-design/css/recipes/menu-item');
37
37
  var tabs = require('@seed-design/css/recipes/tabs');
38
38
 
39
39
  function _interopNamespace(e) {
@@ -780,7 +780,10 @@ function isFunction$1(value) {
780
780
  const DATA_TABLE_UNIVERSAL_CALC_OPS = [
781
781
  "count",
782
782
  "empty",
783
- "notEmpty"
783
+ "notEmpty",
784
+ "percentEmpty",
785
+ "percentFilled",
786
+ "none"
784
787
  ];
785
788
  const DATA_TABLE_NUMERIC_CALC_OPS = [
786
789
  "sum",
@@ -795,8 +798,11 @@ const DATA_TABLE_NUMERIC_CALC_OPS = [
795
798
  ];
796
799
  const DATA_TABLE_CALC_LABEL = {
797
800
  count: "Count",
798
- empty: "Empty",
799
- notEmpty: "Not empty",
801
+ empty: "Count empty",
802
+ notEmpty: "Count filled",
803
+ percentEmpty: "Percent empty",
804
+ percentFilled: "Percent filled",
805
+ none: "None",
800
806
  sum: "Sum",
801
807
  average: "Average",
802
808
  median: "Median",
@@ -826,7 +832,12 @@ const DATA_TABLE_CALC_LABEL = {
826
832
  * (`JSON.stringify`-normalized, so two arrays/objects with the same shape count
827
833
  * once) -- empties are already their own op (`empty`), so folding them into "one
828
834
  * more unique value" here would double-count the same information two different
829
- * ways in the same menu.
835
+ * ways in the same menu. `percentEmpty`/`percentFilled` are `empty`/`notEmpty` as a
836
+ * share of the row count (0-100, unrounded -- formatting rounds it for display) --
837
+ * `null` on zero rows, since 0/0 has no meaningful percentage (not 0%, not 100%).
838
+ * `none` always renders nothing: it is Attio's own "no calculation" menu entry, not a
839
+ * separate always-appended "Clear" item, and picking it is a real, storable op so it
840
+ * overrides a `summaryDefaultOp` instead of falling back to it.
830
841
  */ function computeColumnCalc(values, op) {
831
842
  switch(op){
832
843
  case "count":
@@ -835,6 +846,14 @@ const DATA_TABLE_CALC_LABEL = {
835
846
  return values.filter(isEmptyValueForCalc).length;
836
847
  case "notEmpty":
837
848
  return values.filter((value)=>!isEmptyValueForCalc(value)).length;
849
+ case "percentEmpty":
850
+ if (values.length === 0) return null;
851
+ return values.filter(isEmptyValueForCalc).length / values.length * 100;
852
+ case "percentFilled":
853
+ if (values.length === 0) return null;
854
+ return values.filter((value)=>!isEmptyValueForCalc(value)).length / values.length * 100;
855
+ case "none":
856
+ return null;
838
857
  case "countUnique":
839
858
  {
840
859
  const seen = new Set(values.filter((value)=>!isEmptyValueForCalc(value)).map((value)=>JSON.stringify(value)));
@@ -968,15 +987,33 @@ function sameCell$1(a, id, key) {
968
987
  * first captured) nor keeps a ghost entry for one that was removed. Known keys keep
969
988
  * their position from `order`; any key in `keys` missing from `order` is appended, in
970
989
  * `keys`' own order; any entry in `order` no longer in `keys` is dropped.
971
- */ function reconcileColumnOrder(order, keys) {
990
+ *
991
+ * `actionsKey` (the styled layer's `actions`-flagged column, e.g. the row-menu `⋯`
992
+ * column) is kept LAST no matter what -- without this, a brand-new column added to
993
+ * `columns` after the table first mounted got appended past it (the "any key in `keys`
994
+ * missing from `order` is appended" rule above has no notion of "past the end" vs. "at
995
+ * the very end"), stranding it in the middle of the table since `stickyLast`/`actions`
996
+ * only sticks the actual trailing column. A real consumer hit this: their newly-added
997
+ * column landed to the right of the already-pinned-sticky `actions` column, covered by
998
+ * it under horizontal scroll. Column-layout's own `pinnedCellProps` independently
999
+ * targets the `actions` column by FLAG rather than by position (belt and suspenders --
1000
+ * see that file's own doc comment), but this still keeps the VISUAL order sane so nothing
1001
+ * renders hidden behind the sticky cell in the first place.
1002
+ */ function reconcileColumnOrder(order, keys, actionsKey) {
972
1003
  const keySet = new Set(keys);
973
1004
  const known = order.filter((key)=>keySet.has(key));
974
1005
  const knownSet = new Set(known);
975
1006
  const added = keys.filter((key)=>!knownSet.has(key));
976
- return [
1007
+ const merged = [
977
1008
  ...known,
978
1009
  ...added
979
1010
  ];
1011
+ if (actionsKey === undefined || !keySet.has(actionsKey)) return merged;
1012
+ const withoutActions = merged.filter((key)=>key !== actionsKey);
1013
+ return [
1014
+ ...withoutActions,
1015
+ actionsKey
1016
+ ];
980
1017
  }
981
1018
  /**
982
1019
  * Moves `key` one step toward `direction` in `order` -- a no-op (same array, well,
@@ -1155,7 +1192,7 @@ const EMPTY_RECORD = {};
1155
1192
  * state this hook already owns, not independent state
1156
1193
  * of their own.
1157
1194
  */ function useDataTableColumns(props) {
1158
- 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;
1195
+ 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;
1159
1196
  const [rawOrder, setRawOrder] = useControllableState$1({
1160
1197
  prop: columnOrderProp,
1161
1198
  defaultProp: defaultColumnOrder ?? columnKeys,
@@ -1166,9 +1203,10 @@ const EMPTY_RECORD = {};
1166
1203
  // consumer persisted before a new column existed must still show that new column
1167
1204
  // (appended), and one holding a column that got removed from `columns` must not
1168
1205
  // render a ghost header for it.
1169
- const columnOrder = React.useMemo(()=>reconcileColumnOrder(rawOrder ?? columnKeys, columnKeys), [
1206
+ const columnOrder = React.useMemo(()=>reconcileColumnOrder(rawOrder ?? columnKeys, columnKeys, actionsColumnKey), [
1170
1207
  rawOrder,
1171
- columnKeys
1208
+ columnKeys,
1209
+ actionsColumnKey
1172
1210
  ]);
1173
1211
  const [hiddenColumnKeys = EMPTY_STRINGS, setHiddenColumnKeys] = useControllableState$1({
1174
1212
  prop: hiddenProp,
@@ -1653,6 +1691,17 @@ function useDataTableSelection(props) {
1653
1691
  rowIds,
1654
1692
  setSelectedIds
1655
1693
  ]);
1694
+ // Bulk-action bar's own "×" (gap #4, DATATABLE-ATTIO-SPEC's Attio review) --
1695
+ // deliberately its own function rather than reusing `toggleAll`: `toggleAll` only
1696
+ // clears from an "all" selection, and otherwise SELECTS every row (`selectionState
1697
+ // === "some"` -> `rowIds`, above) -- exactly the wrong direction for a "clear
1698
+ // selection" affordance shown while `selectionState` is "some". This always empties
1699
+ // the set, regardless of `selectionState`.
1700
+ const clearSelection = React.useCallback(()=>{
1701
+ setSelectedIds([]);
1702
+ }, [
1703
+ setSelectedIds
1704
+ ]);
1656
1705
  const getRowProps = React.useCallback((rowId)=>{
1657
1706
  const isRowSelected = isSelected(rowId);
1658
1707
  return domUtils.elementProps({
@@ -1671,6 +1720,7 @@ function useDataTableSelection(props) {
1671
1720
  isSelected,
1672
1721
  toggleRow,
1673
1722
  toggleAll,
1723
+ clearSelection,
1674
1724
  getRowProps
1675
1725
  };
1676
1726
  }
@@ -2138,7 +2188,7 @@ function useDataTableRowContext({ strict = true } = {}) {
2138
2188
  return context;
2139
2189
  }
2140
2190
  const DataTableRoot = /*#__PURE__*/ React.forwardRef((props, ref)=>{
2141
- 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;
2191
+ 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;
2142
2192
  const api = useDataTable({
2143
2193
  sort,
2144
2194
  defaultSort,
@@ -2158,6 +2208,7 @@ const DataTableRoot = /*#__PURE__*/ React.forwardRef((props, ref)=>{
2158
2208
  defaultToolbarFilters,
2159
2209
  onToolbarFiltersChange,
2160
2210
  columnKeys,
2211
+ actionsColumnKey,
2161
2212
  columnOrder,
2162
2213
  defaultColumnOrder,
2163
2214
  onColumnOrderChange,
@@ -2679,11 +2730,70 @@ CheckboxIndicator.displayName = "CheckboxIndicator";
2679
2730
  const CheckboxLabel = withContext$6(withStateProps$4(Primitive.span), "label");
2680
2731
  const CheckboxHiddenInput = reactCheckbox.Checkbox.HiddenInput;
2681
2732
 
2682
- const { withProvider: withProvider$2, withContext: withContext$5 } = createSlotRecipeContext(avatar.avatar);
2733
+ const { ClassNamesProvider: ClassNamesProvider$2, withContext: withContext$5, useClassNames: useClassNames$2 } = createSlotRecipeContext(menu.menu);
2734
+ const { PropsProvider: ItemPropsProvider$1, useProps: useItemProps$1, withContext: withItemContext$1, ClassNamesProvider: ItemClassNamesProvider$1 } = createSlotRecipeContext(menuItem.menuItem);
2735
+ const withStateProps$3 = createWithStateProps([
2736
+ reactMenu.useMenuContext
2737
+ ]);
2738
+ const withItemStateProps$1 = createWithStateProps([
2739
+ reactMenu.useMenuItemContext
2740
+ ]);
2741
+ const MenuRoot = (props)=>{
2742
+ const [variantProps, otherProps] = menu.menu.splitVariantProps(props);
2743
+ const classNames = menu.menu(variantProps);
2744
+ return /*#__PURE__*/ jsxRuntime.jsx(ClassNamesProvider$2, {
2745
+ value: classNames,
2746
+ children: /*#__PURE__*/ jsxRuntime.jsx(ItemPropsProvider$1, {
2747
+ value: {
2748
+ size: variantProps.size
2749
+ },
2750
+ children: /*#__PURE__*/ jsxRuntime.jsx(reactMenu.Menu.Root, {
2751
+ ...otherProps
2752
+ })
2753
+ })
2754
+ });
2755
+ };
2756
+ const MenuAnchor = reactMenu.Menu.Anchor;
2757
+ const MenuTrigger = reactMenu.Menu.Trigger;
2758
+ const MenuPositioner = /*#__PURE__*/ React__namespace.forwardRef(({ className, ...props }, ref)=>{
2759
+ const classNames = useClassNames$2();
2760
+ return /*#__PURE__*/ jsxRuntime.jsx(reactMenu.Menu.Positioner, {
2761
+ ref: ref,
2762
+ className: clsx(classNames.positioner, className),
2763
+ ...props
2764
+ });
2765
+ });
2766
+ MenuPositioner.displayName = "MenuPositioner";
2767
+ const MenuContent = withContext$5(reactMenu.Menu.Content, "content");
2768
+ const MenuScrollArea = withContext$5(withStateProps$3(Primitive.div), "scrollArea");
2769
+ withContext$5(reactMenu.Menu.Group, "group");
2770
+ withContext$5(reactMenu.Menu.GroupLabel, "groupLabel");
2771
+ const MenuItem = /*#__PURE__*/ React__namespace.forwardRef(({ className: propClassName, ...props }, ref)=>{
2772
+ const [variantProps, otherProps] = menuItem.menuItem.splitVariantProps(props);
2773
+ const parentProps = useItemProps$1();
2774
+ const classNames = menuItem.menuItem({
2775
+ ...parentProps,
2776
+ ...variantProps
2777
+ });
2778
+ return /*#__PURE__*/ jsxRuntime.jsx(ItemClassNamesProvider$1, {
2779
+ value: classNames,
2780
+ children: /*#__PURE__*/ jsxRuntime.jsx(reactMenu.Menu.Item, {
2781
+ ref: ref,
2782
+ className: clsx(classNames.root, propClassName),
2783
+ ...otherProps
2784
+ })
2785
+ });
2786
+ });
2787
+ MenuItem.displayName = "MenuItem";
2788
+ withItemContext$1(withItemStateProps$1(Primitive.div), "body");
2789
+ const MenuItemLabel = withItemContext$1(withItemStateProps$1(Primitive.span), "label");
2790
+ withItemContext$1(withItemStateProps$1(Primitive.span), "description");
2791
+
2792
+ const { withProvider: withProvider$2, withContext: withContext$4 } = createSlotRecipeContext(avatar.avatar);
2683
2793
  const AvatarRoot = withProvider$2(reactImage.Image.Root, "root");
2684
- const AvatarImage = withContext$5(reactImage.Image.Content, "image");
2685
- const AvatarFallback = withContext$5(reactImage.Image.Fallback, "fallback");
2686
- withContext$5(Primitive.div, "badge");
2794
+ const AvatarImage = withContext$4(reactImage.Image.Content, "image");
2795
+ const AvatarFallback = withContext$4(reactImage.Image.Fallback, "fallback");
2796
+ withContext$4(Primitive.div, "badge");
2687
2797
 
2688
2798
  const Badge = /*#__PURE__*/ React.forwardRef(({ className, children, ...props }, ref)=>{
2689
2799
  const [variantProps, restProps] = badge.badge.splitVariantProps(props);
@@ -3227,6 +3337,141 @@ function RatingCell({ value, maxRating = 5 }) {
3227
3337
  });
3228
3338
  }
3229
3339
 
3340
+ /** Clear is withheld for `chip-list`: that column already owns its own per-tag remove
3341
+ * affordance (`ChipListEditor`) and commits a JSON-STRINGIFIED array through `onEdit`
3342
+ * (`DataTable.chips.test.tsx`'s own `TagsHarness` -- and this package's own Storybook
3343
+ * demo, `CellFeaturesDemo`, does exactly `JSON.parse(value)` on commit) -- committing
3344
+ * a bare `""` through that same path throws in the consumer's `onEdit`, it doesn't
3345
+ * clear anything. Every OTHER editable shape's commit value is a plain string
3346
+ * (`"true"`/`"false"`, a stringified rating, an option's own `value`, free text) that
3347
+ * a consumer's `onEdit` already has to accept a possibly-empty version of. */ function isClearableEditableCell(column) {
3348
+ return !!column.editable && column.type !== "chip-list";
3349
+ }
3350
+ /** Paste is narrower still -- restricted to a PLAIN `editable` column (no `editor`,
3351
+ * and not one of the always-live widget types) whose committed value IS the raw
3352
+ * text/number a user typed. `select`/`reference`/`actor` expect one of THEIR OWN
3353
+ * option values (not arbitrary clipboard text), `switch`/`rating`/`checkbox` are
3354
+ * always-live widgets with their own encoding, and `chip-list` is excluded for the
3355
+ * same JSON reason `isClearableEditableCell` documents above. This is the task's own
3356
+ * "only for simple editable text/number cells" guard -- committing pasted text
3357
+ * through any of the excluded shapes would corrupt the row rather than edit it
3358
+ * (crispy-seed's own #1094 lesson: never assume an editor's commit shape).
3359
+ */ function isSimpleEditableCell(column) {
3360
+ if (!column.editable) return false;
3361
+ if (column.editor) return false;
3362
+ if (column.type === "chip-list" || column.type === "checkbox" || column.type === "rating") {
3363
+ return false;
3364
+ }
3365
+ return true;
3366
+ }
3367
+ /** Copy's own text representation -- deliberately mirrors `DisplayCellContent`'s
3368
+ * (body-cells.tsx) switch closely enough to read the same as what's on screen, but
3369
+ * returns a plain string (`navigator.clipboard.writeText`) rather than JSX. Not
3370
+ * exported -- it exists only to feed Copy, not as a general-purpose formatter. */ function cellValueAsText(column, value, locale) {
3371
+ if (value == null) return "";
3372
+ switch(column.type){
3373
+ case "entity":
3374
+ if (typeof value === "object") return String(value.name ?? "");
3375
+ return String(value);
3376
+ case "tags":
3377
+ case "chip-list":
3378
+ return Array.isArray(value) ? value.join(", ") : String(value);
3379
+ case "number":
3380
+ return typeof value === "number" ? String(formatNumberCell(value, locale) ?? "") : String(value);
3381
+ case "currency":
3382
+ return typeof value === "number" ? String(formatCurrencyCell(value, column.symbol ?? "$", locale) ?? "") : String(value);
3383
+ case "date":
3384
+ return String(formatDateCell(value, locale) ?? "");
3385
+ case "datetime":
3386
+ return String(formatDateTimeCell(value, locale) ?? "");
3387
+ case "checkbox":
3388
+ return value === true || value === "true" ? "true" : "false";
3389
+ default:
3390
+ return String(value);
3391
+ }
3392
+ }
3393
+ /**
3394
+ * One cell's right-click menu -- mounted by `body-cells.tsx`'s `DataTableBodyCell`
3395
+ * only while actually open (the same "don't keep an unused floating-ui instance alive
3396
+ * per cell" reasoning as that file's own portaled chip-list popover), so this never
3397
+ * costs anything for the row×column cells a user never right-clicks.
3398
+ */ function CellContextMenu({ column, row, value, locale, point, open, onOpenChange, onCommit, onViewEditHistory, cellContextMenuItems }) {
3399
+ const clearable = isClearableEditableCell(column);
3400
+ const pasteable = isSimpleEditableCell(column);
3401
+ return /*#__PURE__*/ jsxRuntime.jsxs(MenuRoot, {
3402
+ open: open,
3403
+ onOpenChange: onOpenChange,
3404
+ placement: "bottom-start",
3405
+ strategy: "fixed",
3406
+ gutter: 2,
3407
+ children: [
3408
+ /*#__PURE__*/ jsxRuntime.jsx(MenuAnchor, {
3409
+ "aria-hidden": true,
3410
+ style: {
3411
+ position: "fixed",
3412
+ top: point.y,
3413
+ left: point.x,
3414
+ width: 0,
3415
+ height: 0,
3416
+ pointerEvents: "none"
3417
+ }
3418
+ }),
3419
+ /*#__PURE__*/ jsxRuntime.jsx(MenuPositioner, {
3420
+ children: /*#__PURE__*/ jsxRuntime.jsx(MenuContent, {
3421
+ style: {
3422
+ minWidth: 180
3423
+ },
3424
+ children: /*#__PURE__*/ jsxRuntime.jsxs(MenuScrollArea, {
3425
+ children: [
3426
+ /*#__PURE__*/ jsxRuntime.jsx(MenuItem, {
3427
+ onClick: ()=>{
3428
+ const text = cellValueAsText(column, value, locale);
3429
+ // Best-effort: an insecure context or a denied permission rejects
3430
+ // this silently (no `MenuItem` state depends on the outcome) --
3431
+ // there's nothing else for a plain "Copy" item to do about a
3432
+ // browser that refused the write.
3433
+ void navigator.clipboard?.writeText?.(text);
3434
+ },
3435
+ children: /*#__PURE__*/ jsxRuntime.jsx(MenuItemLabel, {
3436
+ children: "Copy"
3437
+ })
3438
+ }),
3439
+ pasteable && /*#__PURE__*/ jsxRuntime.jsx(MenuItem, {
3440
+ onClick: ()=>{
3441
+ void navigator.clipboard?.readText?.().then((text)=>onCommit(text)).catch(()=>{
3442
+ // Clipboard read denied/unavailable -- best-effort, same
3443
+ // reasoning as Copy above.
3444
+ });
3445
+ },
3446
+ children: /*#__PURE__*/ jsxRuntime.jsx(MenuItemLabel, {
3447
+ children: "Paste"
3448
+ })
3449
+ }),
3450
+ clearable && /*#__PURE__*/ jsxRuntime.jsx(MenuItem, {
3451
+ onClick: ()=>onCommit(""),
3452
+ children: /*#__PURE__*/ jsxRuntime.jsx(MenuItemLabel, {
3453
+ children: "Clear value"
3454
+ })
3455
+ }),
3456
+ onViewEditHistory && /*#__PURE__*/ jsxRuntime.jsx(MenuItem, {
3457
+ onClick: ()=>onViewEditHistory(row, column.key),
3458
+ children: /*#__PURE__*/ jsxRuntime.jsx(MenuItemLabel, {
3459
+ children: "View edit history"
3460
+ })
3461
+ }),
3462
+ cellContextMenuItems?.({
3463
+ row,
3464
+ column,
3465
+ value
3466
+ })
3467
+ ]
3468
+ })
3469
+ })
3470
+ })
3471
+ ]
3472
+ });
3473
+ }
3474
+
3230
3475
  /** Fallback width (px) used to compute a pinned column's cumulative sticky offset when
3231
3476
  * it has no explicit `width` and hasn't been drag-resized -- pinning needs a STATIC
3232
3477
  * number to put in an inline `insetInlineStart`, and `table-layout: fixed` only
@@ -3308,10 +3553,25 @@ function RatingCell({ value, maxRating = 5 }) {
3308
3553
  width,
3309
3554
  pinned,
3310
3555
  pinOffset,
3311
- pinnedEdge: false
3556
+ pinnedEdge: false,
3557
+ stickyLastTarget: false
3312
3558
  };
3313
3559
  });
3314
3560
  if (pinnedCount > 0) entries[pinnedCount - 1].pinnedEdge = true;
3561
+ // `stickyLast` sticks the `actions`-flagged column specifically -- not whichever
3562
+ // column merely landed last in render order. Without this, `reconcileColumnOrder`
3563
+ // appending a brand-new column past `actions` (this file's own `<colgroup>` doc
3564
+ // comment on why order must stay in lockstep) silently moved the sticky-right
3565
+ // treatment onto that new column instead, stranding `actions` in the middle of the
3566
+ // table -- a real consumer hit this. Falls back to the genuine last entry when no
3567
+ // column is flagged `actions` at all, matching `stickyLast`'s original single-
3568
+ // column behavior.
3569
+ if (entries.length > 0) {
3570
+ const actionsColumn = columns.find((column)=>column.actions);
3571
+ const actionsIndex = actionsColumn ? entries.findIndex((entry)=>entry.column.key === actionsColumn.key) : -1;
3572
+ const stickyLastIndex = actionsIndex !== -1 ? actionsIndex : entries.length - 1;
3573
+ entries[stickyLastIndex].stickyLastTarget = true;
3574
+ }
3315
3575
  return entries;
3316
3576
  }, [
3317
3577
  columns,
@@ -3351,9 +3611,12 @@ function RatingCell({ value, maxRating = 5 }) {
3351
3611
  }
3352
3612
  /** Sticky positioning for one DATA cell (header, body, or ColumnCalc footer) -- pinned-
3353
3613
  * left (this column's own `pinOffset`, `data-sticky-edge` only on the group's
3354
- * trailing-most member) takes priority over sticky-right (`stickyLast`, always just
3355
- * the last VISIBLE column, e.g. an `actions` column) since a column can only stick to
3356
- * one edge. */ function pinnedCellProps(entry, stickyLast, isLast) {
3614
+ * trailing-most member) takes priority over sticky-right (`stickyLast`, the
3615
+ * `actions`-flagged column when one exists, else the genuine last VISIBLE column)
3616
+ * since a column can only stick to one edge. Reads `entry.stickyLastTarget`
3617
+ * (`useEffectiveColumns`) rather than an `isLast` index the caller computes itself --
3618
+ * that index is just "last in render order", which is exactly what stranded a
3619
+ * reordered-past `actions` column in the first place. */ function pinnedCellProps(entry, stickyLast) {
3357
3620
  if (entry.pinned) {
3358
3621
  return {
3359
3622
  "data-sticky": "",
@@ -3365,7 +3628,7 @@ function RatingCell({ value, maxRating = 5 }) {
3365
3628
  }
3366
3629
  };
3367
3630
  }
3368
- if (stickyLast && isLast) {
3631
+ if (stickyLast && entry.stickyLastTarget) {
3369
3632
  return {
3370
3633
  "data-sticky": "",
3371
3634
  "data-sticky-edge-end": "",
@@ -3385,11 +3648,11 @@ function RatingCell({ value, maxRating = 5 }) {
3385
3648
  // recipe, so a per-instance variant can override the root's default.
3386
3649
  const { ClassNamesProvider: TriggerClassNamesProvider, PropsProvider: TriggerPropsProvider, useProps: useTriggerProps, withContext: withTriggerContext, useClassNames: useTriggerClassNames } = createSlotRecipeContext(selectTrigger.selectTrigger);
3387
3650
  const { ClassNamesProvider: ContentClassNamesProvider, withContext: withContentContext, useClassNames: useContentClassNames } = createSlotRecipeContext(select.select);
3388
- const { ClassNamesProvider: ItemClassNamesProvider$1, PropsProvider: ItemPropsProvider$1, useProps: useItemProps$1, withContext: withItemContext$1, useClassNames: useItemClassNames } = createSlotRecipeContext(selectItem.selectItem);
3389
- const withStateProps$3 = createWithStateProps([
3651
+ const { ClassNamesProvider: ItemClassNamesProvider, PropsProvider: ItemPropsProvider, useProps: useItemProps, withContext: withItemContext, useClassNames: useItemClassNames } = createSlotRecipeContext(selectItem.selectItem);
3652
+ const withStateProps$2 = createWithStateProps([
3390
3653
  reactSelect.useSelectContext
3391
3654
  ]);
3392
- const withItemStateProps$1 = createWithStateProps([
3655
+ const withItemStateProps = createWithStateProps([
3393
3656
  reactSelect.useSelectItemContext
3394
3657
  ]);
3395
3658
  // NOTE: `gutter` and `overflowPadding` are specified in Rootage (`select.yaml`,
@@ -3410,7 +3673,7 @@ const SelectRoot = (props)=>{
3410
3673
  value: contentClassNames,
3411
3674
  children: /*#__PURE__*/ jsxRuntime.jsx(TriggerPropsProvider, {
3412
3675
  value: triggerVariantProps,
3413
- children: /*#__PURE__*/ jsxRuntime.jsx(ItemPropsProvider$1, {
3676
+ children: /*#__PURE__*/ jsxRuntime.jsx(ItemPropsProvider, {
3414
3677
  value: itemVariantProps,
3415
3678
  children: /*#__PURE__*/ jsxRuntime.jsx(reactSelect.Select.Root, {
3416
3679
  ...otherProps
@@ -3450,8 +3713,8 @@ const SelectTrigger = /*#__PURE__*/ React__namespace.forwardRef(({ className, ..
3450
3713
  });
3451
3714
  });
3452
3715
  SelectTrigger.displayName = "SelectTrigger";
3453
- const SelectValue = withTriggerContext(withStateProps$3(reactSelect.Select.Value), "value");
3454
- const SelectPlaceholder = withTriggerContext(withStateProps$3(reactSelect.Select.Placeholder), "placeholder");
3716
+ const SelectValue = withTriggerContext(withStateProps$2(reactSelect.Select.Value), "value");
3717
+ const SelectPlaceholder = withTriggerContext(withStateProps$2(reactSelect.Select.Placeholder), "placeholder");
3455
3718
  const SelectPrefixIcon = /*#__PURE__*/ React__namespace.forwardRef(({ fallback, className, ...otherProps }, ref)=>{
3456
3719
  const { selectedItem, stateProps } = reactSelect.useSelectContext();
3457
3720
  const classNames = useTriggerClassNames();
@@ -3466,7 +3729,7 @@ const SelectPrefixIcon = /*#__PURE__*/ React__namespace.forwardRef(({ fallback,
3466
3729
  });
3467
3730
  });
3468
3731
  SelectPrefixIcon.displayName = "SelectPrefixIcon";
3469
- const SelectSuffixIcon = withTriggerContext(withStateProps$3(InternalIcon), "suffixIcon");
3732
+ const SelectSuffixIcon = withTriggerContext(withStateProps$2(InternalIcon), "suffixIcon");
3470
3733
  const SelectPositioner = /*#__PURE__*/ React__namespace.forwardRef(({ className, ...props }, ref)=>{
3471
3734
  const classNames = useContentClassNames();
3472
3735
  return /*#__PURE__*/ jsxRuntime.jsx(reactSelect.Select.Positioner, {
@@ -3497,12 +3760,12 @@ withContentContext(reactSelect.Select.Group, "group");
3497
3760
  withContentContext(reactSelect.Select.GroupLabel, "groupLabel");
3498
3761
  const SelectItem = /*#__PURE__*/ React__namespace.forwardRef(({ prefixIcon, className, ...props }, ref)=>{
3499
3762
  const [variantProps, otherProps] = selectItem.selectItem.splitVariantProps(props);
3500
- const rootProps = useItemProps$1();
3763
+ const rootProps = useItemProps();
3501
3764
  const classNames = selectItem.selectItem({
3502
3765
  ...rootProps,
3503
3766
  ...variantProps
3504
3767
  });
3505
- return /*#__PURE__*/ jsxRuntime.jsx(ItemClassNamesProvider$1, {
3768
+ return /*#__PURE__*/ jsxRuntime.jsx(ItemClassNamesProvider, {
3506
3769
  value: classNames,
3507
3770
  children: /*#__PURE__*/ jsxRuntime.jsx(reactSelect.Select.Item, {
3508
3771
  ref: ref,
@@ -3527,7 +3790,7 @@ const SelectItemPrefixIcon = /*#__PURE__*/ React__namespace.forwardRef(({ svg: s
3527
3790
  });
3528
3791
  });
3529
3792
  SelectItemPrefixIcon.displayName = "SelectItemPrefixIcon";
3530
- const SelectItemBody = withItemContext$1(withItemStateProps$1(Primitive.div), "body");
3793
+ const SelectItemBody = withItemContext(withItemStateProps(Primitive.div), "body");
3531
3794
  const SelectItemLabel = /*#__PURE__*/ React__namespace.forwardRef(({ children, className, ...otherProps }, ref)=>{
3532
3795
  const { label, stateProps } = reactSelect.useSelectItemContext();
3533
3796
  const classNames = useItemClassNames();
@@ -3542,7 +3805,7 @@ const SelectItemLabel = /*#__PURE__*/ React__namespace.forwardRef(({ children, c
3542
3805
  });
3543
3806
  });
3544
3807
  SelectItemLabel.displayName = "SelectItemLabel";
3545
- withItemContext$1(withItemStateProps$1(Primitive.span), "description");
3808
+ withItemContext(withItemStateProps(Primitive.span), "description");
3546
3809
  const SelectItemIndicator = /*#__PURE__*/ React__namespace.forwardRef(({ selected: selectedSvg, unselected: unselectedSvg, className, ...otherProps }, ref)=>{
3547
3810
  const { isSelected, stateProps } = reactSelect.useSelectItemContext();
3548
3811
  const classNames = useItemClassNames();
@@ -3574,9 +3837,9 @@ const SelectHiddenSelect = /*#__PURE__*/ React__namespace.forwardRef((props, ref
3574
3837
  });
3575
3838
  SelectHiddenSelect.displayName = "SelectHiddenSelect";
3576
3839
 
3577
- const { withContext: withContext$4, ClassNamesProvider: ClassNamesProvider$2 } = createSlotRecipeContext(_switch.switchStyle);
3840
+ const { withContext: withContext$3, ClassNamesProvider: ClassNamesProvider$1 } = createSlotRecipeContext(_switch.switchStyle);
3578
3841
  const { withContext: withControlContext, PropsProvider: ControlPropsProvider, withProvider: withControlProvider } = createSlotRecipeContext(switchmark.switchmark);
3579
- const withStateProps$2 = createWithStateProps([
3842
+ const withStateProps$1 = createWithStateProps([
3580
3843
  reactSwitch.useSwitchContext
3581
3844
  ]);
3582
3845
  const SwitchRoot = /*#__PURE__*/ React__namespace.default.forwardRef(({ className, ...props }, ref)=>{
@@ -3587,7 +3850,7 @@ const SwitchRoot = /*#__PURE__*/ React__namespace.default.forwardRef(({ classNam
3587
3850
  const classNames = _switch.switchStyle(switchVariantProps);
3588
3851
  return /*#__PURE__*/ jsxRuntime.jsx(ControlPropsProvider, {
3589
3852
  value: switchmarkVariantProps,
3590
- children: /*#__PURE__*/ jsxRuntime.jsx(ClassNamesProvider$2, {
3853
+ children: /*#__PURE__*/ jsxRuntime.jsx(ClassNamesProvider$1, {
3591
3854
  value: classNames,
3592
3855
  children: /*#__PURE__*/ jsxRuntime.jsx(reactSwitch.Switch.Root, {
3593
3856
  ref: ref,
@@ -3600,20 +3863,20 @@ const SwitchRoot = /*#__PURE__*/ React__namespace.default.forwardRef(({ classNam
3600
3863
  SwitchRoot.displayName = "SwitchRoot";
3601
3864
  const SwitchControl = withScaleFeedback(withControlProvider(reactSwitch.Switch.Control, "root"));
3602
3865
  const SwitchThumb = withControlContext(reactSwitch.Switch.Thumb, "thumb");
3603
- withContext$4(withStateProps$2(Primitive.span), "label");
3866
+ withContext$3(withStateProps$1(Primitive.span), "label");
3604
3867
  const SwitchHiddenInput = reactSwitch.Switch.HiddenInput;
3605
3868
 
3606
3869
  // src/use-layout-effect.tsx
3607
3870
  var useLayoutEffect2 = globalThis?.document ? React__namespace.useLayoutEffect : ()=>{};
3608
3871
 
3609
- const { withProvider: withProvider$1, withContext: withContext$3, useClassNames: useClassNames$2 } = createSlotRecipeContext(textInput.textInput);
3872
+ const { withProvider: withProvider$1, withContext: withContext$2, useClassNames: useClassNames$1 } = createSlotRecipeContext(textInput.textInput);
3610
3873
  const withFieldStateProps = createWithStateProps([
3611
3874
  {
3612
3875
  useContext: reactField.useFieldContext,
3613
3876
  strict: false
3614
3877
  }
3615
3878
  ]);
3616
- const withStateProps$1 = createWithStateProps([
3879
+ const withStateProps = createWithStateProps([
3617
3880
  reactTextField.useTextFieldContext,
3618
3881
  {
3619
3882
  useContext: reactField.useFieldContext,
@@ -3621,12 +3884,12 @@ const withStateProps$1 = createWithStateProps([
3621
3884
  }
3622
3885
  ]);
3623
3886
  const TextFieldRoot = withProvider$1(withFieldStateProps(reactTextField.TextField.Root), "root");
3624
- withContext$3(withStateProps$1(InternalIcon), "prefixIcon");
3625
- withContext$3(withStateProps$1(Primitive.span), "prefixText");
3626
- withContext$3(withStateProps$1(InternalIcon), "suffixIcon");
3627
- withContext$3(withStateProps$1(Primitive.span), "suffixText");
3887
+ withContext$2(withStateProps(InternalIcon), "prefixIcon");
3888
+ withContext$2(withStateProps(Primitive.span), "prefixText");
3889
+ withContext$2(withStateProps(InternalIcon), "suffixIcon");
3890
+ withContext$2(withStateProps(Primitive.span), "suffixText");
3628
3891
  const TextFieldInput = /*#__PURE__*/ React.forwardRef(({ className, ...otherProps }, ref)=>{
3629
- const classNames = useClassNames$2();
3892
+ const classNames = useClassNames$1();
3630
3893
  const textFieldContext = reactTextField.useTextFieldContext();
3631
3894
  const fieldContext = reactField.useFieldContext({
3632
3895
  strict: false
@@ -3644,7 +3907,7 @@ const TextFieldInput = /*#__PURE__*/ React.forwardRef(({ className, ...otherProp
3644
3907
  });
3645
3908
  TextFieldInput.displayName = "TextFieldInput";
3646
3909
  const TextFieldTextarea = /*#__PURE__*/ React.forwardRef(({ className, autoresize = true, ...otherProps }, ref)=>{
3647
- const classNames = useClassNames$2();
3910
+ const classNames = useClassNames$1();
3648
3911
  const textFieldContext = reactTextField.useTextFieldContext();
3649
3912
  const fieldContext = reactField.useFieldContext({
3650
3913
  strict: false
@@ -3701,7 +3964,7 @@ const TextFieldTextarea = /*#__PURE__*/ React.forwardRef(({ className, autoresiz
3701
3964
  });
3702
3965
  TextFieldTextarea.displayName = "TextFieldTextarea";
3703
3966
 
3704
- __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))}");
3967
+ __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))}");
3705
3968
 
3706
3969
  const createClassName = (className, variants, compoundVariants = [])=>{
3707
3970
  const variantKeys = Object.keys(variants);
@@ -3895,6 +4158,22 @@ const dataTableSlotNames = [
3895
4158
  [
3896
4159
  "selectionCell",
3897
4160
  "seed-data-table__selectionCell"
4161
+ ],
4162
+ [
4163
+ "bulkBar",
4164
+ "seed-data-table__bulkBar"
4165
+ ],
4166
+ [
4167
+ "bulkBarCount",
4168
+ "seed-data-table__bulkBarCount"
4169
+ ],
4170
+ [
4171
+ "bulkBarClear",
4172
+ "seed-data-table__bulkBarClear"
4173
+ ],
4174
+ [
4175
+ "bulkBarActions",
4176
+ "seed-data-table__bulkBarActions"
3898
4177
  ]
3899
4178
  ];
3900
4179
  const defaultVariant$6 = {
@@ -3932,7 +4211,7 @@ Object.assign(dataTable, {
3932
4211
  // other file (`DataTable.tsx`, `column-menu.tsx`, `resize.tsx`, `column-calc.tsx`,
3933
4212
  // `toolbar.tsx`, `body-cells.tsx`, `editors.tsx`) imports its exports from here rather
3934
4213
  // than re-deriving its own.
3935
- const { ClassNamesProvider: ClassNamesProvider$1, withContext: withContext$2, useClassNames: useClassNames$1 } = createSlotRecipeContext(dataTable);
4214
+ const { ClassNamesProvider, withContext: withContext$1, useClassNames } = createSlotRecipeContext(dataTable);
3936
4215
 
3937
4216
  /**
3938
4217
  * The pure filter itself, split out of `useSelectSearch` below so it can be
@@ -4508,26 +4787,87 @@ function ChipListEditor({ column, value, pending, onCommit, classNames }) {
4508
4787
  }));
4509
4788
  }
4510
4789
  ////////////////////////////////////////////////////////////////////////////////////
4511
- // -- Feature C (#12): cell-expand "show full content" overlay -- an anchored box
4512
- // (position:absolute over the cell it belongs to, grows in HEIGHT, elevated)
4513
- // that wraps whatever a truncated cell would otherwise show inline: an editable
4514
- // column's OWN editor node (unchanged, just relocated into this box instead of
4515
- // rendered flush in the 36px row) for the editable case, or the plain full value
4516
- // for a read-only one. `body-cells.tsx` decides WHEN to render this (a click on a
4517
- // cell whose content is actually truncated, `truncate.ts`'s `isCellTruncated`);
4518
- // this component only supplies the box.
4519
- function CellExpandOverlay({ className, children }) {
4520
- return(// `stopPropagation` -- an overlay sits on top of (and past) its own cell/row;
4790
+ // -- Feature C (#12): cell-expand "show full content" overlay -- a box that wraps
4791
+ // whatever a truncated cell would otherwise show inline: an editable column's OWN
4792
+ // editor node (unchanged, just relocated into this box instead of rendered flush in
4793
+ // the 36px row) for the editable case, or the plain full value for a read-only one.
4794
+ // `body-cells.tsx` decides WHEN to render this (a click on a cell whose content is
4795
+ // actually truncated, `truncate.ts`'s `isCellTruncated`); this component only
4796
+ // supplies the box.
4797
+ //
4798
+ // Portals to `document.body` (crispy-seed#1094's own pinned-column follow-up,
4799
+ // ported from the chip-list editor's popover, `body-cells.tsx`'s
4800
+ // `isChipListEditable` -- that one shipped this exact fix in 0.10.3) rather than
4801
+ // positioning `absolute` against its own cell: a `position: sticky` `<td>` clips ANY
4802
+ // descendant that grows past its own box, in every browser tested, regardless of
4803
+ // `overflow`/`z-index` on anything in between, so a pinned column's own truncated
4804
+ // cell used to clip this overlay's grown content instead of showing it in full. A
4805
+ // portal sidesteps that by not being a descendant of the sticky `<td>` at all once
4806
+ // mounted -- position is instead measured live off `anchorRef`'s own
4807
+ // `getBoundingClientRect()`, the same technique the chip-list popover already uses.
4808
+ function CellExpandOverlay({ className, anchorRef, contentRef, children }) {
4809
+ const [style, setStyle] = React__namespace.useState(null);
4810
+ // Grows 1px past the cell's own edges in every direction, matching this box's
4811
+ // pre-portal `top: -1px; insetInlineStart: -1px; insetInlineEnd: -1px` (the recipe's
4812
+ // `expandOverlay` class, `data-table.ts`) -- hides the 1px border seam against the
4813
+ // cell it's expanding out of. `minHeight`/`insetInlineEnd` from that same class no
4814
+ // longer apply once portaled (they resolved against the `<td>` as this box's own
4815
+ // containing block, meaningless once it's a `position: fixed` child of `<body>`
4816
+ // instead -- the same override the chip-list popover's own `measurePopoverPosition`,
4817
+ // `body-cells.tsx`, already makes), so this overrides both explicitly.
4818
+ const measure = React__namespace.useCallback(()=>{
4819
+ const cell = anchorRef.current;
4820
+ if (!cell) return;
4821
+ const rect = cell.getBoundingClientRect();
4822
+ setStyle({
4823
+ position: "fixed",
4824
+ top: rect.top - 1,
4825
+ insetInlineStart: rect.left - 1,
4826
+ insetInlineEnd: "auto",
4827
+ width: rect.width + 2,
4828
+ minHeight: "auto",
4829
+ zIndex: 1000
4830
+ });
4831
+ }, [
4832
+ anchorRef
4833
+ ]);
4834
+ // Measured before paint, on mount -- see this component's own prop doc comment for
4835
+ // why it can't just trust a caller to have measured first.
4836
+ React__namespace.useLayoutEffect(()=>{
4837
+ measure();
4838
+ }, [
4839
+ measure
4840
+ ]);
4841
+ // Same capture-phase listeners as the chip-list popover's own reposition effect
4842
+ // (`body-cells.tsx`) -- `scroll` doesn't bubble, so the table's own horizontal/
4843
+ // vertical scroll container (an arbitrary ancestor, not necessarily `window` itself)
4844
+ // is only observable this way.
4845
+ React__namespace.useEffect(()=>{
4846
+ window.addEventListener("scroll", measure, true);
4847
+ window.addEventListener("resize", measure);
4848
+ return ()=>{
4849
+ window.removeEventListener("scroll", measure, true);
4850
+ window.removeEventListener("resize", measure);
4851
+ };
4852
+ }, [
4853
+ measure
4854
+ ]);
4855
+ // Nothing to portal until the first measurement lands (avoids a one-frame flash at
4856
+ // the origin corner before `useLayoutEffect` runs).
4857
+ if (!style) return null;
4858
+ return /*#__PURE__*/ reactDom.createPortal(// `stopPropagation` -- an overlay sits on top of (and past) its own cell/row;
4521
4859
  // a click anywhere inside it must never ALSO fire the row's own `onRowClick`,
4522
4860
  // the same guard every other interactive cell affordance in this directory
4523
4861
  // already applies (`LinkCell`, `EntityCell`'s preview button, editors.tsx's
4524
4862
  // `TextEditor` root).
4525
4863
  // 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.
4526
4864
  /*#__PURE__*/ jsxRuntime.jsx("div", {
4865
+ ref: contentRef,
4527
4866
  className: className,
4867
+ style: style,
4528
4868
  onClick: (event)=>event.stopPropagation(),
4529
4869
  children: children
4530
- }));
4870
+ }), document.body);
4531
4871
  }
4532
4872
 
4533
4873
  // Row-narrowing for the toolbar's search box -- split out of DataTable.tsx the way
@@ -4623,39 +4963,37 @@ function isCellTruncated(cell) {
4623
4963
  * close itself after the first tag commits, see `ChipListEditor`'s own doc
4624
4964
  * comment).
4625
4965
  *
4626
- * Unlike every OTHER open editor in this file, this one does NOT render inside
4627
- * Feature C's `CellExpandOverlay` (an anchored box positioned relative to the
4628
- * `<td>`, `position: absolute` against it) -- it renders through a REAL React
4629
- * portal to `document.body` instead (`DataTableBodyCell`'s own `popoverStyle`).
4630
- * Confirmed live, crispy-seed#1094's pinned-column follow-up: a `position: sticky`
4631
- * `<td>` clips ANY descendant content that grows past its own box to the table's
4632
- * scrollport, in EVERY browser tested, regardless of `overflow: visible` on every
4633
- * element in between and regardless of how high its `z-index` is raised -- neither
4634
- * of which is a stacking-context or overflow bug this file can fix from the
4635
- * INSIDE. A portal sidesteps it entirely by not being a descendant of the sticky
4636
- * `<td>` at all once mounted. The same sticky-clip almost certainly affects the
4637
- * OTHER gated editors' `CellExpandOverlay` too when their own column is pinned,
4638
- * but porting all of Feature C to this same portal model is its own change,
4639
- * out of scope here -- this fix is scoped to the one editor crispy-seed#1094
4640
- * actually reported against a pinned column. */ function isChipListEditable(column) {
4966
+ * This one renders through its OWN REAL React portal to `document.body`
4967
+ * (`DataTableBodyCell`'s own `popoverStyle`) rather than `editors.tsx`'s shared
4968
+ * `CellExpandOverlay`, since its shape is different (Attio's own click-anywhere-
4969
+ * opens gesture, no truncation gate). `CellExpandOverlay` itself ALSO portals to
4970
+ * `document.body` now (ported from this exact fix) -- both exist because a
4971
+ * `position: sticky` `<td>` clips ANY descendant content that grows past its own
4972
+ * box to the table's scrollport, in EVERY browser tested, regardless of
4973
+ * `overflow: visible` on every element in between and regardless of how high its
4974
+ * `z-index` is raised -- neither of which is a stacking-context or overflow bug
4975
+ * fixable from the INSIDE. A portal sidesteps it entirely by not being a
4976
+ * descendant of the sticky `<td>` at all once mounted. */ function isChipListEditable(column) {
4641
4977
  return !!column.editable && column.type === "chip-list";
4642
4978
  }
4643
- function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyFirst, stickyLast }) {
4979
+ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyFirst, stickyLast, cellContextMenu, onViewEditHistory, cellContextMenuItems }) {
4644
4980
  const effective = useEffectiveColumns(columns, {
4645
4981
  selectable,
4646
4982
  stickyFirst
4647
4983
  });
4648
4984
  return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
4649
- children: effective.map((entry, index)=>{
4985
+ children: effective.map((entry)=>{
4650
4986
  const { column } = entry;
4651
- const isLast = index === effective.length - 1;
4652
- const sticky = pinnedCellProps(entry, stickyLast, isLast);
4987
+ const sticky = pinnedCellProps(entry, stickyLast);
4653
4988
  return /*#__PURE__*/ jsxRuntime.jsx(DataTableBodyCell, {
4654
4989
  column: column,
4655
4990
  row: row,
4656
4991
  classNames: classNames,
4657
4992
  locale: locale,
4658
- sticky: sticky
4993
+ sticky: sticky,
4994
+ cellContextMenu: cellContextMenu,
4995
+ onViewEditHistory: onViewEditHistory,
4996
+ cellContextMenuItems: cellContextMenuItems
4659
4997
  }, column.key);
4660
4998
  })
4661
4999
  });
@@ -4667,7 +5005,7 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4667
5005
  * on every row's render. React keeps this instance stable across re-renders because
4668
5006
  * `ColumnBodyCells` keys it by `column.key` the same way it always keyed the bare
4669
5007
  * `<DataTableCell>` before this split.
4670
- */ function DataTableBodyCell({ column, row, classNames, locale, sticky }) {
5008
+ */ function DataTableBodyCell({ column, row, classNames, locale, sticky, cellContextMenu, onViewEditHistory, cellContextMenuItems }) {
4671
5009
  const { startEdit, cancelEdit, isEditing, isPending, commitEdit, isPeeking, openPeek, closePeek } = useDataTableContext();
4672
5010
  const cellRef = React__namespace.useRef(null);
4673
5011
  // Feature B rework (#8) -- the chip-list popover's own live position, in VIEWPORT
@@ -4683,6 +5021,16 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4683
5021
  // editable cell's existing double-click-to-edit (tranche 5, unchanged) must keep
4684
5022
  // rendering exactly as it always has, not suddenly gain overlay chrome.
4685
5023
  const [overlayOpen, setOverlayOpen] = React__namespace.useState(false);
5024
+ // Attio parity (attio-column-features-full.md gap #1) -- the right-click menu's own
5025
+ // point + open state. `contextMenuPoint` is frozen at the moment of the right-click
5026
+ // (never re-read live), same reasoning as `overlayOpen` above: the menu shouldn't
5027
+ // chase the cursor after it opens. `<CellContextMenu>` itself is only rendered below
5028
+ // while `contextMenuOpen` is true (not permanently mounted per cell) -- unlike
5029
+ // `ColumnMenu` (one per COLUMN, header row only), this component runs once per
5030
+ // row×column cell, and a floating-ui instance genuinely idle in every one of
5031
+ // thousands of cells would be real, avoidable overhead.
5032
+ const [contextMenuPoint, setContextMenuPoint] = React__namespace.useState(null);
5033
+ const [contextMenuOpen, setContextMenuOpen] = React__namespace.useState(false);
4686
5034
  const alwaysLive = isAlwaysLiveEditable(column);
4687
5035
  const chipListEditable = isChipListEditable(column);
4688
5036
  // A gated `editable` column (everything except the always-live widgets AND the
@@ -4734,6 +5082,19 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4734
5082
  setOverlayOpen(isCellTruncated(event.currentTarget));
4735
5083
  startEdit(row.id, column.key);
4736
5084
  }
5085
+ function handleContextMenu(event) {
5086
+ if (!cellContextMenu) return;
5087
+ // Replaces the browser's own native context menu (Attio's own gesture) -- and
5088
+ // must not also reach the row's own `onRowClick`, same reasoning as every other
5089
+ // cell-local click handler in this file.
5090
+ event.preventDefault();
5091
+ event.stopPropagation();
5092
+ setContextMenuPoint({
5093
+ x: event.clientX,
5094
+ y: event.clientY
5095
+ });
5096
+ setContextMenuOpen(true);
5097
+ }
4737
5098
  function handleClick(event) {
4738
5099
  if (chipListEditable) {
4739
5100
  // Attio's own chip-list gesture: click opens the editor regardless of
@@ -4771,6 +5132,16 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4771
5132
  // mounted, in `document.body` -- see `isChipListEditable`'s doc comment) and by
4772
5133
  // the reposition effect further down.
4773
5134
  const popoverRef = React__namespace.useRef(null);
5135
+ // Feature C's own portaled overlay (`CellExpandOverlay`, editors.tsx) -- its own DOM
5136
+ // node, once mounted, same reasoning as `popoverRef` above: it lives outside
5137
+ // `cellRef`'s subtree (a sibling of the table in `document.body`, not a descendant of
5138
+ // the `<td>`), so the outside-click detector below needs a ref to it too. Without
5139
+ // this, `isInside` saw every click/keystroke INSIDE the portaled editor as "outside"
5140
+ // once the overlay actually escaped the `<td>` (this fix's whole point) -- e.g.
5141
+ // typing into the overlay's own `TextEditor` fires a pointerdown on an input that is
5142
+ // no longer `cellRef.current`'s descendant, so every keystroke closed the overlay and
5143
+ // cancelled the edit before the user finished typing.
5144
+ const expandOverlayRef = React__namespace.useRef(null);
4774
5145
  // Closes the overlay on Escape or an outside click/pointerdown -- a gated
4775
5146
  // editor's OWN Escape/blur handling already ends `isEditing` on its own (see
4776
5147
  // `TextEditor`/`SelectEditor`'s existing onCancel/onOpenChange wiring), which
@@ -4783,7 +5154,7 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4783
5154
  React__namespace.useEffect(()=>{
4784
5155
  if (!overlayOpen) return;
4785
5156
  function isInside(target) {
4786
- return (cellRef.current?.contains(target) ?? false) || (popoverRef.current?.contains(target) ?? false);
5157
+ return (cellRef.current?.contains(target) ?? false) || (popoverRef.current?.contains(target) ?? false) || (expandOverlayRef.current?.contains(target) ?? false);
4787
5158
  }
4788
5159
  function handlePointerDown(event) {
4789
5160
  if (!isInside(event.target)) {
@@ -4837,7 +5208,7 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4837
5208
  const pending = isPending(row.id, column.key);
4838
5209
  return /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
4839
5210
  children: [
4840
- /*#__PURE__*/ jsxRuntime.jsx(DataTableCell, {
5211
+ /*#__PURE__*/ jsxRuntime.jsxs(DataTableCell, {
4841
5212
  ref: cellRef,
4842
5213
  "data-numeric": domUtils.dataAttr(column.type === "number" || column.type === "currency"),
4843
5214
  "data-editable": domUtils.dataAttr(!!column.editable),
@@ -4848,14 +5219,36 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4848
5219
  style: sticky.style,
4849
5220
  onClick: handleClick,
4850
5221
  onDoubleClick: handleDoubleClick,
4851
- children: /*#__PURE__*/ jsxRuntime.jsx(CellContent, {
4852
- column: column,
4853
- row: row,
4854
- rowId: row.id,
4855
- classNames: classNames,
4856
- locale: locale,
4857
- overlayActive: overlayActive
4858
- })
5222
+ onContextMenu: handleContextMenu,
5223
+ children: [
5224
+ /*#__PURE__*/ jsxRuntime.jsx(CellContent, {
5225
+ column: column,
5226
+ row: row,
5227
+ rowId: row.id,
5228
+ classNames: classNames,
5229
+ locale: locale,
5230
+ overlayActive: overlayActive,
5231
+ cellRef: cellRef,
5232
+ expandOverlayRef: expandOverlayRef
5233
+ }),
5234
+ cellContextMenu && contextMenuOpen && contextMenuPoint && // A `<td>` (unlike its own parent `<tr>`) can validly hold any flow
5235
+ // content, so `CellContextMenu`'s own 0x0 `MenuAnchor` -- itself never
5236
+ // portaled, only `MenuPositioner`'s actual dropdown content is (via
5237
+ // floating-ui's own `FloatingPortal`, straight to `document.body`) --
5238
+ // renders here, INSIDE this cell, rather than as a sibling of it.
5239
+ /*#__PURE__*/ jsxRuntime.jsx(CellContextMenu, {
5240
+ column: column,
5241
+ row: row,
5242
+ value: rawValue,
5243
+ locale: locale,
5244
+ point: contextMenuPoint,
5245
+ open: contextMenuOpen,
5246
+ onOpenChange: setContextMenuOpen,
5247
+ onCommit: (value)=>commitEdit(row.id, column.key, value),
5248
+ onViewEditHistory: onViewEditHistory,
5249
+ cellContextMenuItems: cellContextMenuItems
5250
+ })
5251
+ ]
4859
5252
  }),
4860
5253
  chipListEditorOpen && popoverStyle && /*#__PURE__*/ reactDom.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.
4861
5254
  /*#__PURE__*/ jsxRuntime.jsx("div", {
@@ -4881,7 +5274,7 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4881
5274
  * display (a custom `render` wins there, same as before `editable` existed; it never
4882
5275
  * overrides an actual editing/always-live state, matching crisp's own split between
4883
5276
  * `renderTyped`'s rest-state branch and the editing branches above it).
4884
- */ function CellContent({ column, row, rowId, classNames, locale, overlayActive }) {
5277
+ */ function CellContent({ column, row, rowId, classNames, locale, overlayActive, cellRef, expandOverlayRef }) {
4885
5278
  const { isEditing, isPending, getEditError, commitEdit, cancelEdit } = useDataTableContext();
4886
5279
  const rawValue = getColumnValue(column, row);
4887
5280
  const pending = isPending(rowId, column.key);
@@ -4975,6 +5368,8 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4975
5368
  children: [
4976
5369
  overlayActive ? /*#__PURE__*/ jsxRuntime.jsx(CellExpandOverlay, {
4977
5370
  className: classNames.expandOverlay,
5371
+ anchorRef: cellRef,
5372
+ contentRef: expandOverlayRef,
4978
5373
  children: column.editable ? node : /*#__PURE__*/ jsxRuntime.jsx("span", {
4979
5374
  className: classNames.expandOverlayText,
4980
5375
  children: node
@@ -5085,64 +5480,52 @@ function DisplayCellContent({ column, row, value, classNames, locale }) {
5085
5480
  }
5086
5481
  }
5087
5482
 
5088
- const { ClassNamesProvider, withContext: withContext$1, useClassNames } = createSlotRecipeContext(menu.menu);
5089
- const { PropsProvider: ItemPropsProvider, useProps: useItemProps, withContext: withItemContext, ClassNamesProvider: ItemClassNamesProvider } = createSlotRecipeContext(menuItem.menuItem);
5090
- const withStateProps = createWithStateProps([
5091
- reactMenu.useMenuContext
5092
- ]);
5093
- const withItemStateProps = createWithStateProps([
5094
- reactMenu.useMenuItemContext
5095
- ]);
5096
- const MenuRoot = (props)=>{
5097
- const [variantProps, otherProps] = menu.menu.splitVariantProps(props);
5098
- const classNames = menu.menu(variantProps);
5099
- return /*#__PURE__*/ jsxRuntime.jsx(ClassNamesProvider, {
5100
- value: classNames,
5101
- children: /*#__PURE__*/ jsxRuntime.jsx(ItemPropsProvider, {
5102
- value: {
5103
- size: variantProps.size
5104
- },
5105
- children: /*#__PURE__*/ jsxRuntime.jsx(reactMenu.Menu.Root, {
5106
- ...otherProps
5483
+ const DataTableBulkBarRoot = withContext$1(Primitive.div, "bulkBar");
5484
+ const DataTableBulkBarCount = withContext$1(Primitive.span, "bulkBarCount");
5485
+ const DataTableBulkBarClear = withContext$1(Primitive.button, "bulkBarClear");
5486
+ /** Wraps whatever the CONSUMER passes as this bar's own action buttons -- the bar
5487
+ * itself only ever supplies chrome (count + clear); every actual action (Add to
5488
+ * list, Send email, …) is the consumer's own `<ActionButton>`/`<Menu>`, the same
5489
+ * "chrome vs. consumer content" split `toolbarActions`/`toolbarEnd` already draw for
5490
+ * the toolbar's own custom actions. */ const DataTableBulkBarActions = withContext$1(Primitive.div, "bulkBarActions");
5491
+ /**
5492
+ * The floating bulk-action pill (gap #4) -- renders nothing while no row is selected
5493
+ * (reads `selectedIds` off the table-wide context itself, so a consumer doesn't have
5494
+ * to gate this on `selectable`/`selectedIds` on their own). Composed as the LAST
5495
+ * child of the table's own root (`DataTable.tsx`), which is `position: relative` for
5496
+ * exactly this reason -- see `data-table.ts`'s own `bulkBar` slot comment for why its
5497
+ * geometry borrows `contextual-floating-button`'s already-measured vars rather than a
5498
+ * guessed literal.
5499
+ */ function DataTableBulkBar({ clearLabel = "Clear selection", children }) {
5500
+ const { selectedIds, clearSelection } = useDataTableContext();
5501
+ if (selectedIds.length === 0) return null;
5502
+ return /*#__PURE__*/ jsxRuntime.jsxs(DataTableBulkBarRoot, {
5503
+ role: "toolbar",
5504
+ "aria-label": "Bulk actions",
5505
+ "aria-orientation": "horizontal",
5506
+ children: [
5507
+ /*#__PURE__*/ jsxRuntime.jsxs(DataTableBulkBarCount, {
5508
+ "aria-live": "polite",
5509
+ children: [
5510
+ selectedIds.length,
5511
+ " selected"
5512
+ ]
5513
+ }),
5514
+ /*#__PURE__*/ jsxRuntime.jsx(DataTableBulkBarClear, {
5515
+ type: "button",
5516
+ "aria-label": clearLabel,
5517
+ onClick: clearSelection,
5518
+ children: /*#__PURE__*/ jsxRuntime.jsx(IconXmarkLine_default, {
5519
+ size: 12,
5520
+ "aria-hidden": "true"
5521
+ })
5522
+ }),
5523
+ children && /*#__PURE__*/ jsxRuntime.jsx(DataTableBulkBarActions, {
5524
+ children: children(selectedIds)
5107
5525
  })
5108
- })
5109
- });
5110
- };
5111
- reactMenu.Menu.Anchor;
5112
- const MenuTrigger = reactMenu.Menu.Trigger;
5113
- const MenuPositioner = /*#__PURE__*/ React__namespace.forwardRef(({ className, ...props }, ref)=>{
5114
- const classNames = useClassNames();
5115
- return /*#__PURE__*/ jsxRuntime.jsx(reactMenu.Menu.Positioner, {
5116
- ref: ref,
5117
- className: clsx(classNames.positioner, className),
5118
- ...props
5119
- });
5120
- });
5121
- MenuPositioner.displayName = "MenuPositioner";
5122
- const MenuContent = withContext$1(reactMenu.Menu.Content, "content");
5123
- const MenuScrollArea = withContext$1(withStateProps(Primitive.div), "scrollArea");
5124
- withContext$1(reactMenu.Menu.Group, "group");
5125
- withContext$1(reactMenu.Menu.GroupLabel, "groupLabel");
5126
- const MenuItem = /*#__PURE__*/ React__namespace.forwardRef(({ className: propClassName, ...props }, ref)=>{
5127
- const [variantProps, otherProps] = menuItem.menuItem.splitVariantProps(props);
5128
- const parentProps = useItemProps();
5129
- const classNames = menuItem.menuItem({
5130
- ...parentProps,
5131
- ...variantProps
5132
- });
5133
- return /*#__PURE__*/ jsxRuntime.jsx(ItemClassNamesProvider, {
5134
- value: classNames,
5135
- children: /*#__PURE__*/ jsxRuntime.jsx(reactMenu.Menu.Item, {
5136
- ref: ref,
5137
- className: clsx(classNames.root, propClassName),
5138
- ...otherProps
5139
- })
5526
+ ]
5140
5527
  });
5141
- });
5142
- MenuItem.displayName = "MenuItem";
5143
- withItemContext(withItemStateProps(Primitive.div), "body");
5144
- const MenuItemLabel = withItemContext(withItemStateProps(Primitive.span), "label");
5145
- withItemContext(withItemStateProps(Primitive.span), "description");
5528
+ }
5146
5529
 
5147
5530
  function ColumnCalcCells({ columns, rows, selectable, stickyFirst, stickyLast }) {
5148
5531
  const effective = useEffectiveColumns(columns, {
@@ -5150,10 +5533,9 @@ function ColumnCalcCells({ columns, rows, selectable, stickyFirst, stickyLast })
5150
5533
  stickyFirst
5151
5534
  });
5152
5535
  return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
5153
- children: effective.map((entry, index)=>{
5536
+ children: effective.map((entry)=>{
5154
5537
  const { column } = entry;
5155
- const isLast = index === effective.length - 1;
5156
- const sticky = pinnedCellProps(entry, stickyLast, isLast);
5538
+ const sticky = pinnedCellProps(entry, stickyLast);
5157
5539
  return /*#__PURE__*/ jsxRuntime.jsx(DataTableFooterCell, {
5158
5540
  "data-sticky": sticky["data-sticky"],
5159
5541
  "data-sticky-edge": sticky["data-sticky-edge"],
@@ -5169,10 +5551,11 @@ function ColumnCalcCells({ columns, rows, selectable, stickyFirst, stickyLast })
5169
5551
  })
5170
5552
  });
5171
5553
  }
5172
- function formatCalcValue(value) {
5173
- return value.toLocaleString(undefined, {
5554
+ function formatCalcValue(value, op) {
5555
+ const formatted = value.toLocaleString(undefined, {
5174
5556
  maximumFractionDigits: 2
5175
5557
  });
5558
+ return op === "percentEmpty" || op === "percentFilled" ? `${formatted}%` : formatted;
5176
5559
  }
5177
5560
  /**
5178
5561
  * One ColumnCalc footer cell -- a click-anywhere-in-the-cell trigger (crisp's own
@@ -5207,7 +5590,7 @@ function formatCalcValue(value) {
5207
5590
  children: [
5208
5591
  /*#__PURE__*/ jsxRuntime.jsx("span", {
5209
5592
  className: "seed-data-table-column-calc-value",
5210
- children: formatCalcValue(result)
5593
+ children: formatCalcValue(result, op)
5211
5594
  }),
5212
5595
  /*#__PURE__*/ jsxRuntime.jsx("span", {
5213
5596
  className: "seed-data-table-column-calc-op",
@@ -5228,21 +5611,13 @@ function formatCalcValue(value) {
5228
5611
  style: {
5229
5612
  minWidth: 160
5230
5613
  },
5231
- children: /*#__PURE__*/ jsxRuntime.jsxs(MenuScrollArea, {
5232
- children: [
5233
- ops.map((candidate)=>/*#__PURE__*/ jsxRuntime.jsx(MenuItem, {
5234
- onClick: ()=>setColumnCalcOp(column.key, candidate),
5235
- children: /*#__PURE__*/ jsxRuntime.jsx(MenuItemLabel, {
5236
- children: DATA_TABLE_CALC_LABEL[candidate]
5237
- })
5238
- }, candidate)),
5239
- op && /*#__PURE__*/ jsxRuntime.jsx(MenuItem, {
5240
- onClick: ()=>setColumnCalcOp(column.key, undefined),
5614
+ children: /*#__PURE__*/ jsxRuntime.jsx(MenuScrollArea, {
5615
+ children: ops.map((candidate)=>/*#__PURE__*/ jsxRuntime.jsx(MenuItem, {
5616
+ onClick: ()=>setColumnCalcOp(column.key, candidate),
5241
5617
  children: /*#__PURE__*/ jsxRuntime.jsx(MenuItemLabel, {
5242
- children: "Clear"
5618
+ children: DATA_TABLE_CALC_LABEL[candidate]
5243
5619
  })
5244
- })
5245
- ]
5620
+ }, candidate))
5246
5621
  })
5247
5622
  })
5248
5623
  })
@@ -5383,10 +5758,9 @@ const DEFAULT_SORT_ICON = {
5383
5758
  });
5384
5759
  const [renamingKey, setRenamingKey] = React__namespace.useState(null);
5385
5760
  return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
5386
- children: effective.map((entry, index)=>{
5761
+ children: effective.map((entry)=>{
5387
5762
  const { column, label, width } = entry;
5388
- const isLast = index === effective.length - 1;
5389
- const sticky = pinnedCellProps(entry, stickyLast, isLast);
5763
+ const sticky = pinnedCellProps(entry, stickyLast);
5390
5764
  return /*#__PURE__*/ jsxRuntime.jsxs(DataTableHeaderCell, {
5391
5765
  columnKey: column.key,
5392
5766
  sortable: column.sortable,
@@ -10507,6 +10881,18 @@ ContinuousDatePicker.displayName = "ContinuousDatePicker";
10507
10881
  // value list) down to `is`/`is not`/`empty`/`not empty`. `"isanyof"` is appended to any
10508
10882
  // of the four whenever the column supplies `filterValues` -- a faceted checkbox list,
10509
10883
  // independent of the column's own kind.
10884
+ //
10885
+ // Multi-select fix (DATATABLE-ATTIO-SPEC.md gap #12, live-measured 2026-09-30): a
10886
+ // `kind: "select"` column whose CELL is multi-valued (`type: "tags"`/`"chip-list"` --
10887
+ // a chip-list/tag column, not a single status pill) offers Attio's own
10888
+ // `contains`/`does not contain` instead of `is`/`is not` -- "is" reads as an exact-
10889
+ // set-equality check against an array value, which is not what Attio's own multi-
10890
+ // select filter means or what `applyToolbarFilters`' `is`/`isnot` branch (a plain
10891
+ // string-equality compare via `asText`) computes. `contains`/`doesnotcontain` are
10892
+ // already correct here unmodified: `applyToolbarFilters` already runs them against
10893
+ // `asText(raw)` (an array's own values joined), which is exactly "does any tag
10894
+ // match". Single-select (no `filterValues`-array cell type) keeps `is`/`is not` --
10895
+ // not re-measured against Attio, so left alone per the discovery doc's own hedge.
10510
10896
  ////////////////////////////////////////////////////////////////////////////////////
10511
10897
  const OP_ORDER = [
10512
10898
  "contains",
@@ -10540,9 +10926,19 @@ const SELECT_OP_ORDER = [
10540
10926
  "empty",
10541
10927
  "notempty"
10542
10928
  ];
10929
+ const MULTI_SELECT_OP_ORDER = [
10930
+ "contains",
10931
+ "doesnotcontain",
10932
+ "empty",
10933
+ "notempty"
10934
+ ];
10543
10935
  /** The operators a column of this `kind` offers, in display order -- `hasValues`
10544
- * (the column supplies `filterValues`) appends `"isanyof"` regardless of kind. */ function opsForKind(kind, hasValues) {
10545
- const base = kind === "number" ? NUMBER_OP_ORDER : kind === "date" ? DATE_OP_ORDER : kind === "select" ? SELECT_OP_ORDER : OP_ORDER;
10936
+ * (the column supplies `filterValues`) appends `"isanyof"` regardless of kind.
10937
+ * `multiValue` (the column's CELL type is a chip-list/tags column, not just its
10938
+ * filter `kind`) swaps `SELECT_OP_ORDER`'s `is`/`is not` for `contains`/`does not
10939
+ * contain`, matching Attio's own multi-select filter -- see the operator-sets
10940
+ * comment above. */ function opsForKind(kind, hasValues, multiValue = false) {
10941
+ const base = kind === "number" ? NUMBER_OP_ORDER : kind === "date" ? DATE_OP_ORDER : kind === "select" ? multiValue ? MULTI_SELECT_OP_ORDER : SELECT_OP_ORDER : OP_ORDER;
10546
10942
  return hasValues ? [
10547
10943
  ...base,
10548
10944
  "isanyof"
@@ -10910,66 +11306,58 @@ const formatFilterDate = (iso)=>{
10910
11306
  return `${OPERATOR_LABELS[filter.op]} ${value}`;
10911
11307
  }
10912
11308
  /**
10913
- * One filter condition, INSIDE the Filter popover -- a compact one-line summary row
10914
- * (field: operator value) with a trailing remove "x", expanding in place (accordion,
10915
- * not a second popover) to the field/operator/value editor when clicked. Presentation
10916
- * note (crisp/#593-style live re-check, 2026-09-28): Attio's own toolbar keeps active
10917
- * conditions as a compact "Filter · N" count on the trigger, not a row of always-on
10918
- * pill chips spread across the toolbar -- this replaces that first pass (which DID
10919
- * render one big chip per condition, directly in the toolbar row) with conditions
10920
- * living inside the ONE popover the trigger opens, the same list-of-rows shape
10921
- * `SortControl` above already uses for its own conditions.
10922
- */ function FilterConditionRow({ filter, columns, expanded, onToggle }) {
11309
+ * One filter condition, INSIDE the Filter popover -- Attio's own compact chip shape
11310
+ * (DATATABLE-ATTIO-SPEC.md gap #12, re-measured live 2026-09-30): `[attribute ▾]
11311
+ * [operator …] [value] [remove ×]`, ALL of it visible at once, never behind a click-
11312
+ * to-expand accordion. Supersedes this file's earlier 2026-09-28 note (which read
11313
+ * Attio as keeping conditions collapsed behind a bare "Filter · N" count and put them
11314
+ * in an accordion for that reason) -- the fresh read found the opposite: every active
11315
+ * condition shows attribute/operator/value simultaneously. This keeps the outer shell
11316
+ * (the "Filter"/"Filter · N" trigger opening ONE popover, `SortControl`'s own
11317
+ * list-of-rows shape) rather than moving chips into the toolbar row itself, so the
11318
+ * already-covered trigger/`Add filter` DOM (`DataTable.test.tsx`) stays put; only the
11319
+ * PER-CONDITION row tightens, from an accordion to an always-open card. The operator
11320
+ * set stays a row of small pill buttons (not a dropdown) so every existing test that
11321
+ * clicks an operator by its exact button name keeps working unchanged.
11322
+ */ function FilterConditionRow({ filter, columns }) {
10923
11323
  const { patchToolbarFilter, changeToolbarFilterField, removeToolbarFilter } = useDataTableContext();
10924
11324
  const column = columns.find((c)=>c.key === filter.key);
10925
11325
  const kind = column?.kind;
11326
+ const multiValue = column?.type === "tags" || column?.type === "chip-list";
10926
11327
  const values = column?.filterValues ?? [];
10927
- const ops = opsForKind(kind, values.length > 0);
10928
- return /*#__PURE__*/ jsxRuntime.jsxs("div", {
11328
+ const ops = opsForKind(kind, values.length > 0, multiValue);
11329
+ return /*#__PURE__*/ jsxRuntime.jsxs("fieldset", {
11330
+ "aria-label": `${column ? columnLabel(column) : filter.key} filter: ${conditionSummary(filter, column)}`,
10929
11331
  style: {
11332
+ margin: 0,
10930
11333
  display: "flex",
10931
11334
  flexDirection: "column",
10932
- gap: 6,
11335
+ gap: 8,
10933
11336
  borderRadius: 6,
10934
- background: expanded ? "var(--seed-color-bg-neutral-weak)" : "transparent",
10935
- padding: expanded ? 6 : 0
11337
+ border: "1px solid var(--seed-color-border-neutral)",
11338
+ background: "var(--seed-color-bg-neutral-weak)",
11339
+ padding: 6
10936
11340
  },
10937
11341
  children: [
10938
11342
  /*#__PURE__*/ jsxRuntime.jsxs("div", {
10939
11343
  style: {
10940
11344
  display: "flex",
10941
11345
  alignItems: "center",
10942
- gap: 2
11346
+ gap: 4
10943
11347
  },
10944
11348
  children: [
10945
- /*#__PURE__*/ jsxRuntime.jsxs("button", {
10946
- type: "button",
10947
- onClick: onToggle,
11349
+ /*#__PURE__*/ jsxRuntime.jsx(InlineSelect, {
11350
+ ariaLabel: "Filter by field",
11351
+ value: filter.key,
11352
+ onChange: (key)=>changeToolbarFilterField(filter.id, key),
11353
+ options: columns.map((c)=>({
11354
+ value: c.key,
11355
+ label: columnLabel(c)
11356
+ })),
10948
11357
  style: {
10949
11358
  flex: 1,
10950
- minWidth: 0,
10951
- textAlign: "left",
10952
- padding: "6px 8px",
10953
- border: "none",
10954
- borderRadius: 6,
10955
- background: "transparent",
10956
- cursor: "pointer",
10957
- fontSize: 13,
10958
- overflow: "hidden",
10959
- textOverflow: "ellipsis",
10960
- whiteSpace: "nowrap"
10961
- },
10962
- children: [
10963
- /*#__PURE__*/ jsxRuntime.jsx("strong", {
10964
- style: {
10965
- fontWeight: 500
10966
- },
10967
- children: column ? columnLabel(column) : filter.key
10968
- }),
10969
- ":",
10970
- " ",
10971
- conditionSummary(filter, column)
10972
- ]
11359
+ minWidth: 0
11360
+ }
10973
11361
  }),
10974
11362
  /*#__PURE__*/ jsxRuntime.jsx("button", {
10975
11363
  type: "button",
@@ -10983,136 +11371,103 @@ const formatFilterDate = (iso)=>{
10983
11371
  })
10984
11372
  ]
10985
11373
  }),
10986
- expanded && /*#__PURE__*/ jsxRuntime.jsxs("div", {
11374
+ /*#__PURE__*/ jsxRuntime.jsx("div", {
10987
11375
  style: {
10988
- padding: "0 8px 4px",
10989
11376
  display: "flex",
10990
- flexDirection: "column",
10991
- gap: 8
11377
+ flexWrap: "wrap",
11378
+ gap: 2
10992
11379
  },
10993
- children: [
10994
- /*#__PURE__*/ jsxRuntime.jsx("div", {
10995
- style: {
10996
- fontSize: 12,
10997
- color: "var(--seed-color-fg-neutral-subtle)"
10998
- },
10999
- children: "Field"
11000
- }),
11001
- /*#__PURE__*/ jsxRuntime.jsx(InlineSelect, {
11002
- ariaLabel: "Filter by field",
11003
- value: filter.key,
11004
- onChange: (key)=>changeToolbarFilterField(filter.id, key),
11005
- options: columns.map((c)=>({
11006
- value: c.key,
11007
- label: columnLabel(c)
11008
- }))
11009
- }),
11010
- /*#__PURE__*/ jsxRuntime.jsx("div", {
11011
- style: {
11012
- fontSize: 12,
11013
- color: "var(--seed-color-fg-neutral-subtle)"
11014
- },
11015
- children: "Condition"
11016
- }),
11017
- /*#__PURE__*/ jsxRuntime.jsx("div", {
11380
+ children: ops.map((op)=>/*#__PURE__*/ jsxRuntime.jsx("button", {
11381
+ type: "button",
11382
+ "data-active": filter.op === op ? "" : undefined,
11383
+ onClick: ()=>patchToolbarFilter(filter.id, {
11384
+ op
11385
+ }),
11018
11386
  style: {
11019
- display: "flex",
11020
- flexDirection: "column",
11021
- gap: 2
11387
+ textAlign: "left",
11388
+ padding: "4px 8px",
11389
+ borderRadius: 999,
11390
+ border: "none",
11391
+ cursor: "pointer",
11392
+ background: filter.op === op ? "var(--seed-color-bg-layer-default)" : "transparent",
11393
+ fontSize: 12
11022
11394
  },
11023
- children: ops.map((op)=>/*#__PURE__*/ jsxRuntime.jsx("button", {
11024
- type: "button",
11025
- "data-active": filter.op === op ? "" : undefined,
11026
- onClick: ()=>patchToolbarFilter(filter.id, {
11027
- op
11028
- }),
11029
- style: {
11030
- textAlign: "left",
11031
- padding: "6px 8px",
11032
- borderRadius: 6,
11033
- border: "none",
11034
- cursor: "pointer",
11035
- background: filter.op === op ? "var(--seed-color-bg-layer-default)" : "transparent",
11036
- fontSize: 13
11037
- },
11038
- children: OPERATOR_LABELS[op]
11039
- }, op))
11040
- }),
11041
- opNeedsValues(filter.op) && /*#__PURE__*/ jsxRuntime.jsx("div", {
11395
+ children: OPERATOR_LABELS[op]
11396
+ }, op))
11397
+ }),
11398
+ opNeedsValues(filter.op) && /*#__PURE__*/ jsxRuntime.jsx("div", {
11399
+ style: {
11400
+ display: "flex",
11401
+ flexDirection: "column",
11402
+ gap: 4,
11403
+ maxHeight: 160,
11404
+ overflow: "auto"
11405
+ },
11406
+ children: values.map((value)=>{
11407
+ const checked = (filter.values ?? []).includes(value);
11408
+ return /*#__PURE__*/ jsxRuntime.jsxs(CheckboxRoot, {
11409
+ checked: checked,
11410
+ onCheckedChange: ()=>patchToolbarFilter(filter.id, {
11411
+ values: checked ? (filter.values ?? []).filter((v)=>v !== value) : [
11412
+ ...filter.values ?? [],
11413
+ value
11414
+ ]
11415
+ }),
11042
11416
  style: {
11043
11417
  display: "flex",
11044
- flexDirection: "column",
11045
- gap: 4,
11046
- maxHeight: 160,
11047
- overflow: "auto"
11418
+ alignItems: "center",
11419
+ gap: 8,
11420
+ padding: "4px 0",
11421
+ cursor: "pointer"
11048
11422
  },
11049
- children: values.map((value)=>{
11050
- const checked = (filter.values ?? []).includes(value);
11051
- return /*#__PURE__*/ jsxRuntime.jsxs(CheckboxRoot, {
11052
- checked: checked,
11053
- onCheckedChange: ()=>patchToolbarFilter(filter.id, {
11054
- values: checked ? (filter.values ?? []).filter((v)=>v !== value) : [
11055
- ...filter.values ?? [],
11056
- value
11057
- ]
11058
- }),
11059
- style: {
11060
- display: "flex",
11061
- alignItems: "center",
11062
- gap: 8,
11063
- padding: "4px 0",
11064
- cursor: "pointer"
11065
- },
11066
- children: [
11067
- /*#__PURE__*/ jsxRuntime.jsx(CheckboxControl, {
11068
- children: /*#__PURE__*/ jsxRuntime.jsx(CheckboxIndicator, {
11069
- checked: /*#__PURE__*/ jsxRuntime.jsx(IconCheckmarkLine_default, {
11070
- size: 12
11071
- })
11072
- })
11073
- }),
11074
- /*#__PURE__*/ jsxRuntime.jsx(CheckboxLabel, {
11075
- children: value
11076
- }),
11077
- /*#__PURE__*/ jsxRuntime.jsx(CheckboxHiddenInput, {
11078
- "aria-label": value
11423
+ children: [
11424
+ /*#__PURE__*/ jsxRuntime.jsx(CheckboxControl, {
11425
+ children: /*#__PURE__*/ jsxRuntime.jsx(CheckboxIndicator, {
11426
+ checked: /*#__PURE__*/ jsxRuntime.jsx(IconCheckmarkLine_default, {
11427
+ size: 12
11079
11428
  })
11080
- ]
11081
- }, value);
11082
- })
11083
- }),
11084
- opNeedsValue(filter.op) && kind === "date" && /*#__PURE__*/ jsxRuntime.jsx(DatePicker, {
11085
- value: filter.value ? toDatePickerDate(isoToDate(filter.value) ?? new Date()) : undefined,
11086
- onValueChange: (d)=>d && patchToolbarFilter(filter.id, {
11087
- value: dateToISO(fromDatePickerDate(d))
11088
- })
11089
- }),
11090
- opNeedsValue(filter.op) && kind === "select" && /*#__PURE__*/ jsxRuntime.jsx(InlineSelect, {
11091
- ariaLabel: "Filter value",
11092
- placeholder: "Value…",
11093
- value: filter.value || undefined,
11094
- onChange: (value)=>patchToolbarFilter(filter.id, {
11095
- value
11429
+ })
11096
11430
  }),
11097
- options: values.map((value)=>({
11098
- value,
11099
- label: value
11100
- }))
11101
- }),
11102
- opNeedsValue(filter.op) && kind !== "date" && kind !== "select" && /*#__PURE__*/ jsxRuntime.jsx(TextFieldRoot, {
11103
- variant: "outline",
11104
- size: "medium",
11105
- children: /*#__PURE__*/ jsxRuntime.jsx(TextFieldInput, {
11106
- type: kind === "number" ? "number" : "text",
11107
- value: filter.value,
11108
- onChange: (e)=>patchToolbarFilter(filter.id, {
11109
- value: e.target.value
11110
- }),
11111
- placeholder: "Value…",
11112
- "aria-label": "Filter value"
11113
- })
11431
+ /*#__PURE__*/ jsxRuntime.jsx(CheckboxLabel, {
11432
+ children: value
11433
+ }),
11434
+ /*#__PURE__*/ jsxRuntime.jsx(CheckboxHiddenInput, {
11435
+ "aria-label": value
11436
+ })
11437
+ ]
11438
+ }, value);
11439
+ })
11440
+ }),
11441
+ opNeedsValue(filter.op) && kind === "date" && /*#__PURE__*/ jsxRuntime.jsx(DatePicker, {
11442
+ value: filter.value ? toDatePickerDate(isoToDate(filter.value) ?? new Date()) : undefined,
11443
+ onValueChange: (d)=>d && patchToolbarFilter(filter.id, {
11444
+ value: dateToISO(fromDatePickerDate(d))
11114
11445
  })
11115
- ]
11446
+ }),
11447
+ opNeedsValue(filter.op) && kind === "select" && /*#__PURE__*/ jsxRuntime.jsx(InlineSelect, {
11448
+ ariaLabel: "Filter value",
11449
+ placeholder: "Value…",
11450
+ value: filter.value || undefined,
11451
+ onChange: (value)=>patchToolbarFilter(filter.id, {
11452
+ value
11453
+ }),
11454
+ options: values.map((value)=>({
11455
+ value,
11456
+ label: value
11457
+ }))
11458
+ }),
11459
+ opNeedsValue(filter.op) && kind !== "date" && kind !== "select" && /*#__PURE__*/ jsxRuntime.jsx(TextFieldRoot, {
11460
+ variant: "outline",
11461
+ size: "medium",
11462
+ children: /*#__PURE__*/ jsxRuntime.jsx(TextFieldInput, {
11463
+ type: kind === "number" ? "number" : "text",
11464
+ value: filter.value,
11465
+ onChange: (e)=>patchToolbarFilter(filter.id, {
11466
+ value: e.target.value
11467
+ }),
11468
+ placeholder: "Value…",
11469
+ "aria-label": "Filter value"
11470
+ })
11116
11471
  })
11117
11472
  ]
11118
11473
  });
@@ -11120,15 +11475,14 @@ const formatFilterDate = (iso)=>{
11120
11475
  /**
11121
11476
  * The toolbar's Filter trigger + popover -- a compact `"Filter"` / `"Filter · N"`
11122
11477
  * button (matching `SortControl`'s own `"Sort"` / `"Sort · N"`), opening ONE popover
11123
- * that holds both the active conditions (compact rows, `FilterConditionRow` above,
11124
- * one expanded at a time) and, behind "+ Add filter", the field picker that appends a
11125
- * new one -- not a row of always-on pill chips spread across the toolbar (this
11126
- * replaces that first pass; see `FilterConditionRow`'s own doc comment).
11478
+ * that holds both the active conditions (compact chips, `FilterConditionRow` above,
11479
+ * ALL open at once -- no accordion) and, behind "+ Add filter", the field picker that
11480
+ * appends a new one. See `FilterConditionRow`'s own doc comment for why the chips
11481
+ * live inside this one popover rather than spread across the toolbar row itself.
11127
11482
  */ function FilterControl({ columns, label = "Filter" }) {
11128
11483
  const { toolbarFilters, addToolbarFilter } = useDataTableContext();
11129
11484
  const [open, setOpen] = React__namespace.useState(false);
11130
11485
  const [pickingField, setPickingField] = React__namespace.useState(false);
11131
- const [expandedId, setExpandedId] = React__namespace.useState(null);
11132
11486
  return /*#__PURE__*/ jsxRuntime.jsxs(MenuRoot, {
11133
11487
  open: open,
11134
11488
  onOpenChange: (next)=>{
@@ -11190,8 +11544,7 @@ const formatFilterDate = (iso)=>{
11190
11544
  columns.map((c)=>/*#__PURE__*/ jsxRuntime.jsx("button", {
11191
11545
  type: "button",
11192
11546
  onClick: ()=>{
11193
- const id = addToolbarFilter(c.key);
11194
- setExpandedId(id);
11547
+ addToolbarFilter(c.key);
11195
11548
  setPickingField(false);
11196
11549
  },
11197
11550
  style: {
@@ -11224,9 +11577,7 @@ const formatFilterDate = (iso)=>{
11224
11577
  }),
11225
11578
  toolbarFilters.map((f)=>/*#__PURE__*/ jsxRuntime.jsx(FilterConditionRow, {
11226
11579
  filter: f,
11227
- columns: columns,
11228
- expanded: expandedId === f.id,
11229
- onToggle: ()=>setExpandedId((id)=>id === f.id ? null : f.id)
11580
+ columns: columns
11230
11581
  }, f.id)),
11231
11582
  /*#__PURE__*/ jsxRuntime.jsxs("button", {
11232
11583
  type: "button",
@@ -11255,8 +11606,8 @@ const formatFilterDate = (iso)=>{
11255
11606
  });
11256
11607
  }
11257
11608
 
11258
- const DataTableToolbar = withContext$2(Primitive.div, "toolbar");
11259
- const DataTableCount = withContext$2(Primitive.span, "count");
11609
+ const DataTableToolbar = withContext$1(Primitive.div, "toolbar");
11610
+ const DataTableCount = withContext$1(Primitive.span, "count");
11260
11611
  /**
11261
11612
  * The toolbar's own right-hand group -- `count`'s `marginInlineStart: auto` moved
11262
11613
  * here (off the `<span>` itself) so a consumer's `toolbarActions` and the row count
@@ -11267,7 +11618,7 @@ const DataTableCount = withContext$2(Primitive.span, "count");
11267
11618
  * it. `DataTable.tsx` always renders this wrapper once either `toolbarActions` or
11268
11619
  * `showCount` is on, so it degrades to exactly the old single-count layout when no
11269
11620
  * `toolbarActions` is passed.
11270
- */ const DataTableToolbarEnd = withContext$2(Primitive.div, "toolbarEnd");
11621
+ */ const DataTableToolbarEnd = withContext$1(Primitive.div, "toolbarEnd");
11271
11622
  /**
11272
11623
  * A compact, borderless search box -- crisp's own measured `.crisp-datatable-search`
11273
11624
  * (32px, `bgSurface` background, a leading icon, a soft rest-state shadow, a 1px brand
@@ -11317,7 +11668,7 @@ function RowCount({ shown, total, countLabel }) {
11317
11668
  }
11318
11669
 
11319
11670
  function DataTableInner(props, ref) {
11320
- 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;
11671
+ 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;
11321
11672
  const classNames = dataTable({
11322
11673
  surface,
11323
11674
  interactive
@@ -11419,6 +11770,12 @@ function DataTableInner(props, ref) {
11419
11770
  const columnKeys = React__namespace.useMemo(()=>columns.map((column)=>column.key), [
11420
11771
  columns
11421
11772
  ]);
11773
+ // Threaded down to `reconcileColumnOrder` (react-headless's `dom.ts`) so a brand-new
11774
+ // column added to `columns` after mount gets appended BEFORE this one, not past it --
11775
+ // see that function's own doc comment for the bug this prevents.
11776
+ const actionsColumnKey = React__namespace.useMemo(()=>columns.find((column)=>column.actions)?.key, [
11777
+ columns
11778
+ ]);
11422
11779
  const searchable = React__namespace.useMemo(()=>columns.some((column)=>column.searchable), [
11423
11780
  columns
11424
11781
  ]);
@@ -11441,7 +11798,7 @@ function DataTableInner(props, ref) {
11441
11798
  // loading/error/empty body, infinite scroll's sentinel, the add-row footer) reads
11442
11799
  // this ONE value rather than each re-deriving it, so they can never drift apart.
11443
11800
  const totalCols = columns.length + (selectable ? 1 : 0);
11444
- return /*#__PURE__*/ jsxRuntime.jsx(ClassNamesProvider$1, {
11801
+ return /*#__PURE__*/ jsxRuntime.jsx(ClassNamesProvider, {
11445
11802
  value: classNames,
11446
11803
  children: /*#__PURE__*/ jsxRuntime.jsxs(DataTableRoot, {
11447
11804
  ref: ref,
@@ -11461,6 +11818,7 @@ function DataTableInner(props, ref) {
11461
11818
  toolbarFilters: resolvedToolbarFilters,
11462
11819
  onToolbarFiltersChange: handleToolbarFiltersChange,
11463
11820
  columnKeys: columnKeys,
11821
+ actionsColumnKey: actionsColumnKey,
11464
11822
  columnOrder: columnOrder,
11465
11823
  defaultColumnOrder: defaultColumnOrder,
11466
11824
  onColumnOrderChange: onColumnOrderChange,
@@ -11597,7 +11955,10 @@ function DataTableInner(props, ref) {
11597
11955
  locale: locale,
11598
11956
  selectable: !!selectable,
11599
11957
  stickyFirst: stickyFirst,
11600
- stickyLast: resolvedStickyLast
11958
+ stickyLast: resolvedStickyLast,
11959
+ cellContextMenu: cellContextMenu,
11960
+ onViewEditHistory: onViewEditHistory,
11961
+ cellContextMenuItems: cellContextMenuItems
11601
11962
  })
11602
11963
  ]
11603
11964
  }, row.id)),
@@ -11636,6 +11997,10 @@ function DataTableInner(props, ref) {
11636
11997
  })
11637
11998
  ]
11638
11999
  })
12000
+ }),
12001
+ selectable && /*#__PURE__*/ jsxRuntime.jsx(DataTableBulkBar, {
12002
+ clearLabel: clearSelectionLabel,
12003
+ children: bulkActions
11639
12004
  })
11640
12005
  ]
11641
12006
  })
@@ -11689,7 +12054,7 @@ DataTableInner.displayName = "DataTable";
11689
12054
  // is a SIBLING of the toolbar (both inside `DataTablePrimitiveRoot`'s card `<div>`)
11690
12055
  // rather than `DataTablePrimitiveRoot` itself: a `<table>` cannot host the toolbar as a
11691
12056
  // child without the HTML parser foster-parenting it back out.
11692
- const DataTableTable = withContext$2(DataTableTable$1, "table");
12057
+ const DataTableTable = withContext$1(DataTableTable$1, "table");
11693
12058
  /** One `<col>` per rendered column (plus the 40px selection column, if any) -- the
11694
12059
  * SINGLE place a column's width is ever set now. Every row's header/body/footer cell
11695
12060
  * reads its own width off this shared `<colgroup>` by construction (native table
@@ -11726,15 +12091,15 @@ const DataTableTable = withContext$2(DataTableTable$1, "table");
11726
12091
  ]
11727
12092
  });
11728
12093
  }
11729
- const DataTableHeader = withContext$2(DataTableHeader$1, "header");
11730
- const DataTableHeaderCell = withContext$2(DataTableHeaderCell$1, "headerCell");
12094
+ const DataTableHeader = withContext$1(DataTableHeader$1, "header");
12095
+ const DataTableHeaderCell = withContext$1(DataTableHeaderCell$1, "headerCell");
11731
12096
  // `.crisp-datatable-th-inner` -- the flex row INSIDE a `<th>` (label + filter trigger +
11732
12097
  // sort chevron + column menu). See qvism-preset's own `headerCellInner` slot comment
11733
12098
  // for why this lives one level below the cell itself rather than on it.
11734
- const DataTableHeaderCellInner = withContext$2(Primitive.div, "headerCellInner");
12099
+ const DataTableHeaderCellInner = withContext$1(Primitive.div, "headerCellInner");
11735
12100
  /** The label text specifically -- the one thing in `DataTableHeaderCellInner` that
11736
12101
  * truncates; the trailing filter/sort/menu icons stay fully visible regardless of how
11737
- * long a column's own header text is. */ const DataTableHeaderCellLabel = withContext$2(Primitive.span, "headerCellLabel");
12102
+ * long a column's own header text is. */ const DataTableHeaderCellLabel = withContext$1(Primitive.span, "headerCellLabel");
11738
12103
  /**
11739
12104
  * A selection `<th>`/`<td>`/footer `<td>` composes ITS base slot (`headerCell`/`cell`/
11740
12105
  * `footerCell`) together with the `selectionCell` MODIFIER, rather than replacing it --
@@ -11745,7 +12110,7 @@ const DataTableHeaderCellInner = withContext$2(Primitive.div, "headerCellInner")
11745
12110
  * a second recipe call.
11746
12111
  */ function withSelectionCell(Component, baseSlot) {
11747
12112
  const StyledComponent = /*#__PURE__*/ React.forwardRef((props, ref)=>{
11748
- const classNames = useClassNames$1();
12113
+ const classNames = useClassNames();
11749
12114
  return /*#__PURE__*/ jsxRuntime.jsx(Component, {
11750
12115
  ref: ref,
11751
12116
  ...props,
@@ -11756,12 +12121,12 @@ const DataTableHeaderCellInner = withContext$2(Primitive.div, "headerCellInner")
11756
12121
  return StyledComponent;
11757
12122
  }
11758
12123
  const DataTableSelectionHeaderCell = withSelectionCell(DataTableHeaderCell$1, "headerCell");
11759
- const DataTableHeaderCellIcon = withContext$2(Primitive.span, "headerCellIcon");
12124
+ const DataTableHeaderCellIcon = withContext$1(Primitive.span, "headerCellIcon");
11760
12125
  // body has no CSS slot of its own in the rootage spec -- it is a plain structural
11761
12126
  // `<tbody>`, so it is not wrapped in `withContext`.
11762
12127
  const DataTableBody = DataTableBody$1;
11763
- const DataTableRow = withContext$2(DataTableRow$1, "row");
11764
- const DataTableCell = withContext$2(DataTableCell$1, "cell");
12128
+ const DataTableRow = withContext$1(DataTableRow$1, "row");
12129
+ const DataTableCell = withContext$1(DataTableCell$1, "cell");
11765
12130
  const DataTableSelectionCell = withSelectionCell(DataTableCell$1, "cell");
11766
12131
  // Tranche 4 -- column menu + reorder + resize + sticky columns. A scroll boundary
11767
12132
  // wrapping the whole `<table>`: `overflowX: auto` is what makes a wide table (more
@@ -11771,7 +12136,7 @@ const DataTableSelectionCell = withSelectionCell(DataTableCell$1, "cell");
11771
12136
  // stays out of this slot's own base css (nothing here has a bounded height of its own
11772
12137
  // to clip), so it never introduces a second vertical scrollbar next to whatever
11773
12138
  // ambient container the whole DataTable already sits in.
11774
- const DataTableScrollBodyBase = withContext$2(Primitive.div, "scrollArea");
12139
+ const DataTableScrollBodyBase = withContext$1(Primitive.div, "scrollArea");
11775
12140
  function DataTableScrollBody({ children, scrollProps }) {
11776
12141
  const { scrollAreaRef, scrolledStart, scrolledEnd, onScroll } = useDataTableContext();
11777
12142
  const { className: scrollClassName, style: scrollStyle, ...otherScrollProps } = scrollProps ?? {};
@@ -11786,7 +12151,7 @@ function DataTableScrollBody({ children, scrollProps }) {
11786
12151
  children: children
11787
12152
  });
11788
12153
  }
11789
- const DataTableFooterBase = withContext$2(Primitive.tfoot, "footer");
12154
+ const DataTableFooterBase = withContext$1(Primitive.tfoot, "footer");
11790
12155
  function DataTableFooter({ children, ...props }) {
11791
12156
  return /*#__PURE__*/ jsxRuntime.jsx(DataTableFooterBase, {
11792
12157
  ...props,
@@ -11795,7 +12160,7 @@ function DataTableFooter({ children, ...props }) {
11795
12160
  })
11796
12161
  });
11797
12162
  }
11798
- const DataTableFooterCell = withContext$2(Primitive.td, "footerCell");
12163
+ const DataTableFooterCell = withContext$1(Primitive.td, "footerCell");
11799
12164
  // The selection column's OWN sticky wrappers -- one each for header/body/footer,
11800
12165
  // reading the live pinned set via `useSelectionStickyFirst` (column-layout.ts) instead
11801
12166
  // of the `stickyFirst` PROP alone, so the checkbox column sticks the moment ANY column
@@ -15733,6 +16098,7 @@ function RecordPreviewPanel({ open: _open, record, onClose, className, animation
15733
16098
  RecordPreviewPanel.displayName = "RecordPreviewPanel";
15734
16099
 
15735
16100
  exports.DataTable = DataTable;
16101
+ exports.DataTableBulkBar = DataTableBulkBar;
15736
16102
  exports.DataTableViewsControl = DataTableViewsControl;
15737
16103
  exports.RecordPreview = RecordPreview;
15738
16104
  exports.ResizablePanel = ResizablePanel;