@crispy-seed/data-table 0.10.4 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -18,6 +18,9 @@ import { Checkbox, useCheckboxContext } from '@seed-design/react-checkbox';
18
18
  import { useScaleFeedback } from '@seed-design/react-scale-feedback';
19
19
  import { checkboxGroup } from '@seed-design/css/recipes/checkbox-group';
20
20
  import { createPortal } from 'react-dom';
21
+ import { Menu, useMenuContext, useMenuItemContext } from '@seed-design/react-menu';
22
+ import { menu } from '@seed-design/css/recipes/menu';
23
+ import { menuItem } from '@seed-design/css/recipes/menu-item';
21
24
  import { Image } from '@seed-design/react-image';
22
25
  import { avatar } from '@seed-design/css/recipes/avatar';
23
26
  import '@seed-design/css/recipes/avatar-stack';
@@ -32,9 +35,6 @@ import { switchStyle } from '@seed-design/css/recipes/switch';
32
35
  import { switchmark } from '@seed-design/css/recipes/switchmark';
33
36
  import { TextField, useTextFieldContext } from '@seed-design/react-text-field';
34
37
  import { textInput } from '@seed-design/css/recipes/text-input';
35
- import { Menu, useMenuContext, useMenuItemContext } from '@seed-design/react-menu';
36
- import { menu } from '@seed-design/css/recipes/menu';
37
- import { menuItem } from '@seed-design/css/recipes/menu-item';
38
38
  import { tabs } from '@seed-design/css/recipes/tabs';
39
39
 
40
40
  // src/IconArrowUpLine.tsx
@@ -761,7 +761,10 @@ function isFunction$1(value) {
761
761
  const DATA_TABLE_UNIVERSAL_CALC_OPS = [
762
762
  "count",
763
763
  "empty",
764
- "notEmpty"
764
+ "notEmpty",
765
+ "percentEmpty",
766
+ "percentFilled",
767
+ "none"
765
768
  ];
766
769
  const DATA_TABLE_NUMERIC_CALC_OPS = [
767
770
  "sum",
@@ -776,8 +779,11 @@ const DATA_TABLE_NUMERIC_CALC_OPS = [
776
779
  ];
777
780
  const DATA_TABLE_CALC_LABEL = {
778
781
  count: "Count",
779
- empty: "Empty",
780
- notEmpty: "Not empty",
782
+ empty: "Count empty",
783
+ notEmpty: "Count filled",
784
+ percentEmpty: "Percent empty",
785
+ percentFilled: "Percent filled",
786
+ none: "None",
781
787
  sum: "Sum",
782
788
  average: "Average",
783
789
  median: "Median",
@@ -807,7 +813,12 @@ const DATA_TABLE_CALC_LABEL = {
807
813
  * (`JSON.stringify`-normalized, so two arrays/objects with the same shape count
808
814
  * once) -- empties are already their own op (`empty`), so folding them into "one
809
815
  * more unique value" here would double-count the same information two different
810
- * ways in the same menu.
816
+ * ways in the same menu. `percentEmpty`/`percentFilled` are `empty`/`notEmpty` as a
817
+ * share of the row count (0-100, unrounded -- formatting rounds it for display) --
818
+ * `null` on zero rows, since 0/0 has no meaningful percentage (not 0%, not 100%).
819
+ * `none` always renders nothing: it is Attio's own "no calculation" menu entry, not a
820
+ * separate always-appended "Clear" item, and picking it is a real, storable op so it
821
+ * overrides a `summaryDefaultOp` instead of falling back to it.
811
822
  */ function computeColumnCalc(values, op) {
812
823
  switch(op){
813
824
  case "count":
@@ -816,6 +827,14 @@ const DATA_TABLE_CALC_LABEL = {
816
827
  return values.filter(isEmptyValueForCalc).length;
817
828
  case "notEmpty":
818
829
  return values.filter((value)=>!isEmptyValueForCalc(value)).length;
830
+ case "percentEmpty":
831
+ if (values.length === 0) return null;
832
+ return values.filter(isEmptyValueForCalc).length / values.length * 100;
833
+ case "percentFilled":
834
+ if (values.length === 0) return null;
835
+ return values.filter((value)=>!isEmptyValueForCalc(value)).length / values.length * 100;
836
+ case "none":
837
+ return null;
819
838
  case "countUnique":
820
839
  {
821
840
  const seen = new Set(values.filter((value)=>!isEmptyValueForCalc(value)).map((value)=>JSON.stringify(value)));
@@ -1634,6 +1653,17 @@ function useDataTableSelection(props) {
1634
1653
  rowIds,
1635
1654
  setSelectedIds
1636
1655
  ]);
1656
+ // Bulk-action bar's own "×" (gap #4, DATATABLE-ATTIO-SPEC's Attio review) --
1657
+ // deliberately its own function rather than reusing `toggleAll`: `toggleAll` only
1658
+ // clears from an "all" selection, and otherwise SELECTS every row (`selectionState
1659
+ // === "some"` -> `rowIds`, above) -- exactly the wrong direction for a "clear
1660
+ // selection" affordance shown while `selectionState` is "some". This always empties
1661
+ // the set, regardless of `selectionState`.
1662
+ const clearSelection = useCallback(()=>{
1663
+ setSelectedIds([]);
1664
+ }, [
1665
+ setSelectedIds
1666
+ ]);
1637
1667
  const getRowProps = useCallback((rowId)=>{
1638
1668
  const isRowSelected = isSelected(rowId);
1639
1669
  return elementProps({
@@ -1652,6 +1682,7 @@ function useDataTableSelection(props) {
1652
1682
  isSelected,
1653
1683
  toggleRow,
1654
1684
  toggleAll,
1685
+ clearSelection,
1655
1686
  getRowProps
1656
1687
  };
1657
1688
  }
@@ -1677,24 +1708,33 @@ function useDataTableSort(props) {
1677
1708
  // already passes its "cleared" shape through unconditionally; this now matches.
1678
1709
  onChange: onSortChange
1679
1710
  });
1711
+ // 3-state cycle (DATATABLE-ATTIO-SPEC.md: "asc/desc/none cycle") -- clicking a
1712
+ // DIFFERENT column always starts it fresh at asc, discarding whatever the previous
1713
+ // column's direction was (this stays a SINGLE-column secondary affordance, never a
1714
+ // second multi-sort list of its own). Clicking the SAME column steps asc -> desc ->
1715
+ // undefined ("none", i.e. this column drops out of the sort entirely) -> asc again.
1716
+ // Before this, the cycle wrapped desc -> asc forever, with no click-driven way back
1717
+ // to unsorted at all (a reported gap: "sort asc/desc has no option for no sort on
1718
+ // columns") -- `clearSort` below was the ONLY way there, now it's one of two.
1680
1719
  const toggleSort = useCallback((key)=>{
1681
1720
  setSort((current)=>{
1682
1721
  if (!current || current.key !== key) return {
1683
1722
  key,
1684
1723
  dir: "asc"
1685
1724
  };
1686
- return {
1725
+ if (current.dir === "asc") return {
1687
1726
  key,
1688
- dir: current.dir === "asc" ? "desc" : "asc"
1727
+ dir: "desc"
1689
1728
  };
1729
+ return undefined;
1690
1730
  });
1691
1731
  }, [
1692
1732
  setSort
1693
1733
  ]);
1694
- /** Attio/GRC's "Remove sort" -- the header click cycle (above) only ever toggles
1695
- * asc/desc once a column has been clicked once; this is the only way back to no
1696
- * sort at all. Exposed for a column's own header menu (`column-menu.tsx`'s
1697
- * `ColumnMenu`) to offer as a "Clear sort" item when that column is the sorted one. */ const clearSort = useCallback(()=>setSort(undefined), [
1734
+ /** Attio/GRC's "Remove sort" -- a second way back to no sort at all (the header
1735
+ * click cycle above reaches it too, on a column's third click), exposed for a
1736
+ * column's own header menu (`column-menu.tsx`'s `ColumnMenu`) to offer as a
1737
+ * "Clear sort" item when that column is the sorted one, without three clicks. */ const clearSort = useCallback(()=>setSort(undefined), [
1698
1738
  setSort
1699
1739
  ]);
1700
1740
  const getHeaderCellProps = useCallback((columnKey, sortable)=>{
@@ -1727,7 +1767,12 @@ function useDataTableSort(props) {
1727
1767
  sort,
1728
1768
  toggleSort,
1729
1769
  clearSort,
1730
- getHeaderCellProps
1770
+ getHeaderCellProps,
1771
+ // Exposed for `useDataTable.ts`'s own composition ONLY (unifying this single
1772
+ // click-to-sort column with `useDataTableToolbarSort`'s ordered list) -- not part
1773
+ // of the documented header-click contract (`toggleSort`/`clearSort` above are),
1774
+ // so no consumer-facing doc comment promotes it as a control of its own.
1775
+ setSort
1731
1776
  };
1732
1777
  }
1733
1778
 
@@ -1831,11 +1876,14 @@ const EMPTY_SORTS = [];
1831
1876
  * concern rather than folded into `useDataTableSort` (DATATABLE-ATTIO-SPEC.md: "compose,
1832
1877
  * don't fatten one hook"). `useDataTableSort` still owns the single click-to-sort
1833
1878
  * column/direction a header's own click toggles -- that stays Attio's SECONDARY sort
1834
- * affordance, untouched by this hook. This one is the PRIMARY toolbar Sort control: add
1835
- * a condition, pick its field/direction, reorder, remove -- multiple conditions apply in
1836
- * list order (sort[0] wins, later ones break ties; see the styled layer's
1837
- * `applyToolbarSorts`). The styled layer decides how the two combine (DataTable.tsx:
1838
- * toolbar sorts win when any exist, the single click-to-sort column otherwise).
1879
+ * affordance, a separate hook from this one. They are NOT independent state, though:
1880
+ * `useDataTable.ts`'s own composition keeps a header-driven column's entry in THIS
1881
+ * array in sync (added/updated/removed as that column's header cycles asc/desc/none),
1882
+ * so a consumer controlling only `toolbarSorts`/`onToolbarSortsChange` still sees a
1883
+ * header click, and the toolbar's own "Remove sort" x on that same entry clears the
1884
+ * header's side too. Multiple conditions apply in list order (sort[0] wins, later ones
1885
+ * break ties; see the styled layer's `applyToolbarSorts`). The styled layer
1886
+ * (`DataTable.tsx`) decides how the merged result actually sorts `data`.
1839
1887
  */ function useDataTableToolbarSort(props) {
1840
1888
  const { toolbarSorts: sortsProp, defaultToolbarSorts = EMPTY_SORTS, onToolbarSortsChange } = props;
1841
1889
  const [toolbarSorts = EMPTY_SORTS, setToolbarSorts] = useControllableState$1({
@@ -1920,8 +1968,13 @@ const EMPTY_SORTS = [];
1920
1968
  // header "is/is not" picker) -- DATATABLE-ATTIO-SPEC.md's Attio/crisp `TableToolbar`
1921
1969
  // model is now the PRIMARY filter/sort UX, not a per-column header affordance. They stay
1922
1970
  // SEPARATE sub-hooks from `useDataTableSort` rather than folded into it ("compose, don't
1923
- // fatten one hook") -- the styled layer decides how the toolbar's multi-condition sort
1924
- // and the header's single click-to-sort combine (DataTable.tsx).
1971
+ // fatten one hook") -- but this composition point below DOES reconcile the two into one
1972
+ // state (the two `useEffect`s after `infiniteScroll`), so a header click and the
1973
+ // toolbar's own Sort popover always agree on what's sorted, regardless of which prop
1974
+ // pair (`sort`/`onSortChange` or `toolbarSorts`/`onToolbarSortsChange`) a consumer
1975
+ // actually controls. `DataTable.tsx` (styled) still decides how the RESULT actually
1976
+ // sorts `data` (`effectiveSorts`/`applyToolbarSorts`) -- this layer only keeps the two
1977
+ // STATE pools in agreement.
1925
1978
  function useDataTable(props) {
1926
1979
  const sort = useDataTableSort(props);
1927
1980
  const selection = useDataTableSelection(props);
@@ -1944,6 +1997,105 @@ function useDataTable(props) {
1944
1997
  ...props,
1945
1998
  scrollRootRef: scroll.elementRef
1946
1999
  });
2000
+ // Unifies `sort` (the header's own single click-to-sort column, above) with
2001
+ // `toolbarSort.toolbarSorts` (the toolbar Sort popover's ordered list, above) into
2002
+ // ONE state instead of two -- before this, a header click only ever moved `sort`,
2003
+ // invisible to a consumer controlling `toolbarSorts`/`onToolbarSortsChange` (the
2004
+ // reported bug: clicking a header did nothing to the array the consumer's own Sort
2005
+ // popover reads, so the toolbar's "Sort" trigger/count and its popover rows silently
2006
+ // ignored every header click).
2007
+ //
2008
+ // ONE effect, not two -- an earlier version split this into a forward
2009
+ // (`sort` -> `toolbarSorts`) and a reverse (`toolbarSorts` -> `sort`) effect, each
2010
+ // keyed off its own dependency. That raced on the very first render: `useEffect`
2011
+ // runs every effect once on mount regardless of what "changed", so with
2012
+ // `defaultSort` set, the reverse effect ran in the SAME commit as the forward one,
2013
+ // read `toolbarSorts` BEFORE the forward effect's own (still-queued, not yet
2014
+ // re-rendered) write had landed, saw no entry for the column yet, and immediately
2015
+ // cleared `sort` right back out -- the defaultSort silently never took effect.
2016
+ // `settledRef` sidesteps this: it remembers the last `{key,dir}` this effect itself
2017
+ // confirmed, so on every run it can tell WHICH side moved since then, instead of
2018
+ // guessing from which dependency happens to have triggered the re-run.
2019
+ const settledRef = useRef({
2020
+ key: undefined,
2021
+ dir: undefined
2022
+ });
2023
+ useEffect(()=>{
2024
+ const curSort = sort.sort;
2025
+ const curEntry = curSort ? toolbarSort.toolbarSorts.find((s)=>s.key === curSort.key) : undefined;
2026
+ const settled = settledRef.current;
2027
+ const sortUnchangedSinceSettled = settled.key === curSort?.key && settled.dir === curSort?.dir;
2028
+ if (sortUnchangedSinceSettled) {
2029
+ // `sort` itself hasn't moved since we last settled -- if this effect is running
2030
+ // at all, `toolbarSorts` is what changed, so REVERSE-sync: the toolbar Sort
2031
+ // popover's own "Remove sort" x, or its direction select, touching this exact
2032
+ // column. Never reaches across to adopt some OTHER column added independently
2033
+ // via that popover -- only the column `sort` is already tracking.
2034
+ if (!curSort) return;
2035
+ if (!curEntry) {
2036
+ sort.clearSort();
2037
+ settledRef.current = {
2038
+ key: undefined,
2039
+ dir: undefined
2040
+ };
2041
+ } else if (curEntry.dir !== curSort.dir) {
2042
+ sort.setSort({
2043
+ key: curSort.key,
2044
+ dir: curEntry.dir
2045
+ });
2046
+ settledRef.current = {
2047
+ key: curSort.key,
2048
+ dir: curEntry.dir
2049
+ };
2050
+ }
2051
+ return;
2052
+ }
2053
+ // `sort` moved (a header click, Enter/Space, or `clearSort` -- column-menu.tsx's
2054
+ // menu item calls that directly, bypassing `toggleSort`) -- FORWARD-sync:
2055
+ // add/update/remove THIS column's own entry in `toolbarSorts`, leaving every
2056
+ // OTHER entry (added independently via the toolbar's own "+ Add sort") untouched.
2057
+ // The functional updater reads the CURRENT array off `current`, never off
2058
+ // `toolbarSort.toolbarSorts` in this closure, so a stale render can never clobber
2059
+ // an entry the toolbar's own popover added a moment earlier.
2060
+ const prevKey = settled.key;
2061
+ toolbarSort.setToolbarSorts((current)=>{
2062
+ let updated = current;
2063
+ if (prevKey && prevKey !== curSort?.key) {
2064
+ updated = updated.filter((s)=>s.key !== prevKey);
2065
+ }
2066
+ if (curSort) {
2067
+ const idx = updated.findIndex((s)=>s.key === curSort.key);
2068
+ if (idx === -1) {
2069
+ updated = [
2070
+ ...updated,
2071
+ {
2072
+ key: curSort.key,
2073
+ dir: curSort.dir
2074
+ }
2075
+ ];
2076
+ } else if (updated[idx].dir !== curSort.dir) {
2077
+ updated = updated.map((s, i)=>i === idx ? {
2078
+ ...s,
2079
+ dir: curSort.dir
2080
+ } : s);
2081
+ }
2082
+ }
2083
+ return updated;
2084
+ });
2085
+ settledRef.current = curSort ? {
2086
+ key: curSort.key,
2087
+ dir: curSort.dir
2088
+ } : {
2089
+ key: undefined,
2090
+ dir: undefined
2091
+ };
2092
+ }, [
2093
+ sort.sort,
2094
+ toolbarSort.toolbarSorts,
2095
+ toolbarSort.setToolbarSorts,
2096
+ sort.clearSort,
2097
+ sort.setSort
2098
+ ]);
1947
2099
  return {
1948
2100
  ...sort,
1949
2101
  ...selection,
@@ -2539,11 +2691,70 @@ CheckboxIndicator.displayName = "CheckboxIndicator";
2539
2691
  const CheckboxLabel = withContext$6(withStateProps$4(Primitive.span), "label");
2540
2692
  const CheckboxHiddenInput = Checkbox.HiddenInput;
2541
2693
 
2542
- const { withProvider: withProvider$2, withContext: withContext$5 } = createSlotRecipeContext(avatar);
2694
+ const { ClassNamesProvider: ClassNamesProvider$2, withContext: withContext$5, useClassNames: useClassNames$2 } = createSlotRecipeContext(menu);
2695
+ const { PropsProvider: ItemPropsProvider$1, useProps: useItemProps$1, withContext: withItemContext$1, ClassNamesProvider: ItemClassNamesProvider$1 } = createSlotRecipeContext(menuItem);
2696
+ const withStateProps$3 = createWithStateProps([
2697
+ useMenuContext
2698
+ ]);
2699
+ const withItemStateProps$1 = createWithStateProps([
2700
+ useMenuItemContext
2701
+ ]);
2702
+ const MenuRoot = (props)=>{
2703
+ const [variantProps, otherProps] = menu.splitVariantProps(props);
2704
+ const classNames = menu(variantProps);
2705
+ return /*#__PURE__*/ jsx(ClassNamesProvider$2, {
2706
+ value: classNames,
2707
+ children: /*#__PURE__*/ jsx(ItemPropsProvider$1, {
2708
+ value: {
2709
+ size: variantProps.size
2710
+ },
2711
+ children: /*#__PURE__*/ jsx(Menu.Root, {
2712
+ ...otherProps
2713
+ })
2714
+ })
2715
+ });
2716
+ };
2717
+ const MenuAnchor = Menu.Anchor;
2718
+ const MenuTrigger = Menu.Trigger;
2719
+ const MenuPositioner = /*#__PURE__*/ React.forwardRef(({ className, ...props }, ref)=>{
2720
+ const classNames = useClassNames$2();
2721
+ return /*#__PURE__*/ jsx(Menu.Positioner, {
2722
+ ref: ref,
2723
+ className: clsx(classNames.positioner, className),
2724
+ ...props
2725
+ });
2726
+ });
2727
+ MenuPositioner.displayName = "MenuPositioner";
2728
+ const MenuContent = withContext$5(Menu.Content, "content");
2729
+ const MenuScrollArea = withContext$5(withStateProps$3(Primitive.div), "scrollArea");
2730
+ withContext$5(Menu.Group, "group");
2731
+ withContext$5(Menu.GroupLabel, "groupLabel");
2732
+ const MenuItem = /*#__PURE__*/ React.forwardRef(({ className: propClassName, ...props }, ref)=>{
2733
+ const [variantProps, otherProps] = menuItem.splitVariantProps(props);
2734
+ const parentProps = useItemProps$1();
2735
+ const classNames = menuItem({
2736
+ ...parentProps,
2737
+ ...variantProps
2738
+ });
2739
+ return /*#__PURE__*/ jsx(ItemClassNamesProvider$1, {
2740
+ value: classNames,
2741
+ children: /*#__PURE__*/ jsx(Menu.Item, {
2742
+ ref: ref,
2743
+ className: clsx(classNames.root, propClassName),
2744
+ ...otherProps
2745
+ })
2746
+ });
2747
+ });
2748
+ MenuItem.displayName = "MenuItem";
2749
+ withItemContext$1(withItemStateProps$1(Primitive.div), "body");
2750
+ const MenuItemLabel = withItemContext$1(withItemStateProps$1(Primitive.span), "label");
2751
+ withItemContext$1(withItemStateProps$1(Primitive.span), "description");
2752
+
2753
+ const { withProvider: withProvider$2, withContext: withContext$4 } = createSlotRecipeContext(avatar);
2543
2754
  const AvatarRoot = withProvider$2(Image.Root, "root");
2544
- const AvatarImage = withContext$5(Image.Content, "image");
2545
- const AvatarFallback = withContext$5(Image.Fallback, "fallback");
2546
- withContext$5(Primitive.div, "badge");
2755
+ const AvatarImage = withContext$4(Image.Content, "image");
2756
+ const AvatarFallback = withContext$4(Image.Fallback, "fallback");
2757
+ withContext$4(Primitive.div, "badge");
2547
2758
 
2548
2759
  const Badge = /*#__PURE__*/ forwardRef(({ className, children, ...props }, ref)=>{
2549
2760
  const [variantProps, restProps] = badge.splitVariantProps(props);
@@ -3087,6 +3298,141 @@ function RatingCell({ value, maxRating = 5 }) {
3087
3298
  });
3088
3299
  }
3089
3300
 
3301
+ /** Clear is withheld for `chip-list`: that column already owns its own per-tag remove
3302
+ * affordance (`ChipListEditor`) and commits a JSON-STRINGIFIED array through `onEdit`
3303
+ * (`DataTable.chips.test.tsx`'s own `TagsHarness` -- and this package's own Storybook
3304
+ * demo, `CellFeaturesDemo`, does exactly `JSON.parse(value)` on commit) -- committing
3305
+ * a bare `""` through that same path throws in the consumer's `onEdit`, it doesn't
3306
+ * clear anything. Every OTHER editable shape's commit value is a plain string
3307
+ * (`"true"`/`"false"`, a stringified rating, an option's own `value`, free text) that
3308
+ * a consumer's `onEdit` already has to accept a possibly-empty version of. */ function isClearableEditableCell(column) {
3309
+ return !!column.editable && column.type !== "chip-list";
3310
+ }
3311
+ /** Paste is narrower still -- restricted to a PLAIN `editable` column (no `editor`,
3312
+ * and not one of the always-live widget types) whose committed value IS the raw
3313
+ * text/number a user typed. `select`/`reference`/`actor` expect one of THEIR OWN
3314
+ * option values (not arbitrary clipboard text), `switch`/`rating`/`checkbox` are
3315
+ * always-live widgets with their own encoding, and `chip-list` is excluded for the
3316
+ * same JSON reason `isClearableEditableCell` documents above. This is the task's own
3317
+ * "only for simple editable text/number cells" guard -- committing pasted text
3318
+ * through any of the excluded shapes would corrupt the row rather than edit it
3319
+ * (crispy-seed's own #1094 lesson: never assume an editor's commit shape).
3320
+ */ function isSimpleEditableCell(column) {
3321
+ if (!column.editable) return false;
3322
+ if (column.editor) return false;
3323
+ if (column.type === "chip-list" || column.type === "checkbox" || column.type === "rating") {
3324
+ return false;
3325
+ }
3326
+ return true;
3327
+ }
3328
+ /** Copy's own text representation -- deliberately mirrors `DisplayCellContent`'s
3329
+ * (body-cells.tsx) switch closely enough to read the same as what's on screen, but
3330
+ * returns a plain string (`navigator.clipboard.writeText`) rather than JSX. Not
3331
+ * exported -- it exists only to feed Copy, not as a general-purpose formatter. */ function cellValueAsText(column, value, locale) {
3332
+ if (value == null) return "";
3333
+ switch(column.type){
3334
+ case "entity":
3335
+ if (typeof value === "object") return String(value.name ?? "");
3336
+ return String(value);
3337
+ case "tags":
3338
+ case "chip-list":
3339
+ return Array.isArray(value) ? value.join(", ") : String(value);
3340
+ case "number":
3341
+ return typeof value === "number" ? String(formatNumberCell(value, locale) ?? "") : String(value);
3342
+ case "currency":
3343
+ return typeof value === "number" ? String(formatCurrencyCell(value, column.symbol ?? "$", locale) ?? "") : String(value);
3344
+ case "date":
3345
+ return String(formatDateCell(value, locale) ?? "");
3346
+ case "datetime":
3347
+ return String(formatDateTimeCell(value, locale) ?? "");
3348
+ case "checkbox":
3349
+ return value === true || value === "true" ? "true" : "false";
3350
+ default:
3351
+ return String(value);
3352
+ }
3353
+ }
3354
+ /**
3355
+ * One cell's right-click menu -- mounted by `body-cells.tsx`'s `DataTableBodyCell`
3356
+ * only while actually open (the same "don't keep an unused floating-ui instance alive
3357
+ * per cell" reasoning as that file's own portaled chip-list popover), so this never
3358
+ * costs anything for the row×column cells a user never right-clicks.
3359
+ */ function CellContextMenu({ column, row, value, locale, point, open, onOpenChange, onCommit, onViewEditHistory, cellContextMenuItems }) {
3360
+ const clearable = isClearableEditableCell(column);
3361
+ const pasteable = isSimpleEditableCell(column);
3362
+ return /*#__PURE__*/ jsxs(MenuRoot, {
3363
+ open: open,
3364
+ onOpenChange: onOpenChange,
3365
+ placement: "bottom-start",
3366
+ strategy: "fixed",
3367
+ gutter: 2,
3368
+ children: [
3369
+ /*#__PURE__*/ jsx(MenuAnchor, {
3370
+ "aria-hidden": true,
3371
+ style: {
3372
+ position: "fixed",
3373
+ top: point.y,
3374
+ left: point.x,
3375
+ width: 0,
3376
+ height: 0,
3377
+ pointerEvents: "none"
3378
+ }
3379
+ }),
3380
+ /*#__PURE__*/ jsx(MenuPositioner, {
3381
+ children: /*#__PURE__*/ jsx(MenuContent, {
3382
+ style: {
3383
+ minWidth: 180
3384
+ },
3385
+ children: /*#__PURE__*/ jsxs(MenuScrollArea, {
3386
+ children: [
3387
+ /*#__PURE__*/ jsx(MenuItem, {
3388
+ onClick: ()=>{
3389
+ const text = cellValueAsText(column, value, locale);
3390
+ // Best-effort: an insecure context or a denied permission rejects
3391
+ // this silently (no `MenuItem` state depends on the outcome) --
3392
+ // there's nothing else for a plain "Copy" item to do about a
3393
+ // browser that refused the write.
3394
+ void navigator.clipboard?.writeText?.(text);
3395
+ },
3396
+ children: /*#__PURE__*/ jsx(MenuItemLabel, {
3397
+ children: "Copy"
3398
+ })
3399
+ }),
3400
+ pasteable && /*#__PURE__*/ jsx(MenuItem, {
3401
+ onClick: ()=>{
3402
+ void navigator.clipboard?.readText?.().then((text)=>onCommit(text)).catch(()=>{
3403
+ // Clipboard read denied/unavailable -- best-effort, same
3404
+ // reasoning as Copy above.
3405
+ });
3406
+ },
3407
+ children: /*#__PURE__*/ jsx(MenuItemLabel, {
3408
+ children: "Paste"
3409
+ })
3410
+ }),
3411
+ clearable && /*#__PURE__*/ jsx(MenuItem, {
3412
+ onClick: ()=>onCommit(""),
3413
+ children: /*#__PURE__*/ jsx(MenuItemLabel, {
3414
+ children: "Clear value"
3415
+ })
3416
+ }),
3417
+ onViewEditHistory && /*#__PURE__*/ jsx(MenuItem, {
3418
+ onClick: ()=>onViewEditHistory(row, column.key),
3419
+ children: /*#__PURE__*/ jsx(MenuItemLabel, {
3420
+ children: "View edit history"
3421
+ })
3422
+ }),
3423
+ cellContextMenuItems?.({
3424
+ row,
3425
+ column,
3426
+ value
3427
+ })
3428
+ ]
3429
+ })
3430
+ })
3431
+ })
3432
+ ]
3433
+ });
3434
+ }
3435
+
3090
3436
  /** Fallback width (px) used to compute a pinned column's cumulative sticky offset when
3091
3437
  * it has no explicit `width` and hasn't been drag-resized -- pinning needs a STATIC
3092
3438
  * number to put in an inline `insetInlineStart`, and `table-layout: fixed` only
@@ -3245,11 +3591,11 @@ function RatingCell({ value, maxRating = 5 }) {
3245
3591
  // recipe, so a per-instance variant can override the root's default.
3246
3592
  const { ClassNamesProvider: TriggerClassNamesProvider, PropsProvider: TriggerPropsProvider, useProps: useTriggerProps, withContext: withTriggerContext, useClassNames: useTriggerClassNames } = createSlotRecipeContext(selectTrigger);
3247
3593
  const { ClassNamesProvider: ContentClassNamesProvider, withContext: withContentContext, useClassNames: useContentClassNames } = createSlotRecipeContext(select);
3248
- const { ClassNamesProvider: ItemClassNamesProvider$1, PropsProvider: ItemPropsProvider$1, useProps: useItemProps$1, withContext: withItemContext$1, useClassNames: useItemClassNames } = createSlotRecipeContext(selectItem);
3249
- const withStateProps$3 = createWithStateProps([
3594
+ const { ClassNamesProvider: ItemClassNamesProvider, PropsProvider: ItemPropsProvider, useProps: useItemProps, withContext: withItemContext, useClassNames: useItemClassNames } = createSlotRecipeContext(selectItem);
3595
+ const withStateProps$2 = createWithStateProps([
3250
3596
  useSelectContext
3251
3597
  ]);
3252
- const withItemStateProps$1 = createWithStateProps([
3598
+ const withItemStateProps = createWithStateProps([
3253
3599
  useSelectItemContext
3254
3600
  ]);
3255
3601
  // NOTE: `gutter` and `overflowPadding` are specified in Rootage (`select.yaml`,
@@ -3270,7 +3616,7 @@ const SelectRoot = (props)=>{
3270
3616
  value: contentClassNames,
3271
3617
  children: /*#__PURE__*/ jsx(TriggerPropsProvider, {
3272
3618
  value: triggerVariantProps,
3273
- children: /*#__PURE__*/ jsx(ItemPropsProvider$1, {
3619
+ children: /*#__PURE__*/ jsx(ItemPropsProvider, {
3274
3620
  value: itemVariantProps,
3275
3621
  children: /*#__PURE__*/ jsx(Select.Root, {
3276
3622
  ...otherProps
@@ -3310,8 +3656,8 @@ const SelectTrigger = /*#__PURE__*/ React.forwardRef(({ className, ...props }, r
3310
3656
  });
3311
3657
  });
3312
3658
  SelectTrigger.displayName = "SelectTrigger";
3313
- const SelectValue = withTriggerContext(withStateProps$3(Select.Value), "value");
3314
- const SelectPlaceholder = withTriggerContext(withStateProps$3(Select.Placeholder), "placeholder");
3659
+ const SelectValue = withTriggerContext(withStateProps$2(Select.Value), "value");
3660
+ const SelectPlaceholder = withTriggerContext(withStateProps$2(Select.Placeholder), "placeholder");
3315
3661
  const SelectPrefixIcon = /*#__PURE__*/ React.forwardRef(({ fallback, className, ...otherProps }, ref)=>{
3316
3662
  const { selectedItem, stateProps } = useSelectContext();
3317
3663
  const classNames = useTriggerClassNames();
@@ -3326,7 +3672,7 @@ const SelectPrefixIcon = /*#__PURE__*/ React.forwardRef(({ fallback, className,
3326
3672
  });
3327
3673
  });
3328
3674
  SelectPrefixIcon.displayName = "SelectPrefixIcon";
3329
- const SelectSuffixIcon = withTriggerContext(withStateProps$3(InternalIcon), "suffixIcon");
3675
+ const SelectSuffixIcon = withTriggerContext(withStateProps$2(InternalIcon), "suffixIcon");
3330
3676
  const SelectPositioner = /*#__PURE__*/ React.forwardRef(({ className, ...props }, ref)=>{
3331
3677
  const classNames = useContentClassNames();
3332
3678
  return /*#__PURE__*/ jsx(Select.Positioner, {
@@ -3357,12 +3703,12 @@ withContentContext(Select.Group, "group");
3357
3703
  withContentContext(Select.GroupLabel, "groupLabel");
3358
3704
  const SelectItem = /*#__PURE__*/ React.forwardRef(({ prefixIcon, className, ...props }, ref)=>{
3359
3705
  const [variantProps, otherProps] = selectItem.splitVariantProps(props);
3360
- const rootProps = useItemProps$1();
3706
+ const rootProps = useItemProps();
3361
3707
  const classNames = selectItem({
3362
3708
  ...rootProps,
3363
3709
  ...variantProps
3364
3710
  });
3365
- return /*#__PURE__*/ jsx(ItemClassNamesProvider$1, {
3711
+ return /*#__PURE__*/ jsx(ItemClassNamesProvider, {
3366
3712
  value: classNames,
3367
3713
  children: /*#__PURE__*/ jsx(Select.Item, {
3368
3714
  ref: ref,
@@ -3387,7 +3733,7 @@ const SelectItemPrefixIcon = /*#__PURE__*/ React.forwardRef(({ svg: svgOverride,
3387
3733
  });
3388
3734
  });
3389
3735
  SelectItemPrefixIcon.displayName = "SelectItemPrefixIcon";
3390
- const SelectItemBody = withItemContext$1(withItemStateProps$1(Primitive.div), "body");
3736
+ const SelectItemBody = withItemContext(withItemStateProps(Primitive.div), "body");
3391
3737
  const SelectItemLabel = /*#__PURE__*/ React.forwardRef(({ children, className, ...otherProps }, ref)=>{
3392
3738
  const { label, stateProps } = useSelectItemContext();
3393
3739
  const classNames = useItemClassNames();
@@ -3402,7 +3748,7 @@ const SelectItemLabel = /*#__PURE__*/ React.forwardRef(({ children, className, .
3402
3748
  });
3403
3749
  });
3404
3750
  SelectItemLabel.displayName = "SelectItemLabel";
3405
- withItemContext$1(withItemStateProps$1(Primitive.span), "description");
3751
+ withItemContext(withItemStateProps(Primitive.span), "description");
3406
3752
  const SelectItemIndicator = /*#__PURE__*/ React.forwardRef(({ selected: selectedSvg, unselected: unselectedSvg, className, ...otherProps }, ref)=>{
3407
3753
  const { isSelected, stateProps } = useSelectItemContext();
3408
3754
  const classNames = useItemClassNames();
@@ -3434,9 +3780,9 @@ const SelectHiddenSelect = /*#__PURE__*/ React.forwardRef((props, ref)=>{
3434
3780
  });
3435
3781
  SelectHiddenSelect.displayName = "SelectHiddenSelect";
3436
3782
 
3437
- const { withContext: withContext$4, ClassNamesProvider: ClassNamesProvider$2 } = createSlotRecipeContext(switchStyle);
3783
+ const { withContext: withContext$3, ClassNamesProvider: ClassNamesProvider$1 } = createSlotRecipeContext(switchStyle);
3438
3784
  const { withContext: withControlContext, PropsProvider: ControlPropsProvider, withProvider: withControlProvider } = createSlotRecipeContext(switchmark);
3439
- const withStateProps$2 = createWithStateProps([
3785
+ const withStateProps$1 = createWithStateProps([
3440
3786
  useSwitchContext
3441
3787
  ]);
3442
3788
  const SwitchRoot = /*#__PURE__*/ React__default.forwardRef(({ className, ...props }, ref)=>{
@@ -3447,7 +3793,7 @@ const SwitchRoot = /*#__PURE__*/ React__default.forwardRef(({ className, ...prop
3447
3793
  const classNames = switchStyle(switchVariantProps);
3448
3794
  return /*#__PURE__*/ jsx(ControlPropsProvider, {
3449
3795
  value: switchmarkVariantProps,
3450
- children: /*#__PURE__*/ jsx(ClassNamesProvider$2, {
3796
+ children: /*#__PURE__*/ jsx(ClassNamesProvider$1, {
3451
3797
  value: classNames,
3452
3798
  children: /*#__PURE__*/ jsx(Switch.Root, {
3453
3799
  ref: ref,
@@ -3460,20 +3806,20 @@ const SwitchRoot = /*#__PURE__*/ React__default.forwardRef(({ className, ...prop
3460
3806
  SwitchRoot.displayName = "SwitchRoot";
3461
3807
  const SwitchControl = withScaleFeedback(withControlProvider(Switch.Control, "root"));
3462
3808
  const SwitchThumb = withControlContext(Switch.Thumb, "thumb");
3463
- withContext$4(withStateProps$2(Primitive.span), "label");
3809
+ withContext$3(withStateProps$1(Primitive.span), "label");
3464
3810
  const SwitchHiddenInput = Switch.HiddenInput;
3465
3811
 
3466
3812
  // src/use-layout-effect.tsx
3467
3813
  var useLayoutEffect2 = globalThis?.document ? React.useLayoutEffect : ()=>{};
3468
3814
 
3469
- const { withProvider: withProvider$1, withContext: withContext$3, useClassNames: useClassNames$2 } = createSlotRecipeContext(textInput);
3815
+ const { withProvider: withProvider$1, withContext: withContext$2, useClassNames: useClassNames$1 } = createSlotRecipeContext(textInput);
3470
3816
  const withFieldStateProps = createWithStateProps([
3471
3817
  {
3472
3818
  useContext: useFieldContext,
3473
3819
  strict: false
3474
3820
  }
3475
3821
  ]);
3476
- const withStateProps$1 = createWithStateProps([
3822
+ const withStateProps = createWithStateProps([
3477
3823
  useTextFieldContext,
3478
3824
  {
3479
3825
  useContext: useFieldContext,
@@ -3481,12 +3827,12 @@ const withStateProps$1 = createWithStateProps([
3481
3827
  }
3482
3828
  ]);
3483
3829
  const TextFieldRoot = withProvider$1(withFieldStateProps(TextField.Root), "root");
3484
- withContext$3(withStateProps$1(InternalIcon), "prefixIcon");
3485
- withContext$3(withStateProps$1(Primitive.span), "prefixText");
3486
- withContext$3(withStateProps$1(InternalIcon), "suffixIcon");
3487
- withContext$3(withStateProps$1(Primitive.span), "suffixText");
3830
+ withContext$2(withStateProps(InternalIcon), "prefixIcon");
3831
+ withContext$2(withStateProps(Primitive.span), "prefixText");
3832
+ withContext$2(withStateProps(InternalIcon), "suffixIcon");
3833
+ withContext$2(withStateProps(Primitive.span), "suffixText");
3488
3834
  const TextFieldInput = /*#__PURE__*/ forwardRef(({ className, ...otherProps }, ref)=>{
3489
- const classNames = useClassNames$2();
3835
+ const classNames = useClassNames$1();
3490
3836
  const textFieldContext = useTextFieldContext();
3491
3837
  const fieldContext = useFieldContext({
3492
3838
  strict: false
@@ -3504,7 +3850,7 @@ const TextFieldInput = /*#__PURE__*/ forwardRef(({ className, ...otherProps }, r
3504
3850
  });
3505
3851
  TextFieldInput.displayName = "TextFieldInput";
3506
3852
  const TextFieldTextarea = /*#__PURE__*/ forwardRef(({ className, autoresize = true, ...otherProps }, ref)=>{
3507
- const classNames = useClassNames$2();
3853
+ const classNames = useClassNames$1();
3508
3854
  const textFieldContext = useTextFieldContext();
3509
3855
  const fieldContext = useFieldContext({
3510
3856
  strict: false
@@ -3561,7 +3907,7 @@ const TextFieldTextarea = /*#__PURE__*/ forwardRef(({ className, autoresize = tr
3561
3907
  });
3562
3908
  TextFieldTextarea.displayName = "TextFieldTextarea";
3563
3909
 
3564
- __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))}");
3910
+ __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))}");
3565
3911
 
3566
3912
  const createClassName = (className, variants, compoundVariants = [])=>{
3567
3913
  const variantKeys = Object.keys(variants);
@@ -3755,6 +4101,22 @@ const dataTableSlotNames = [
3755
4101
  [
3756
4102
  "selectionCell",
3757
4103
  "seed-data-table__selectionCell"
4104
+ ],
4105
+ [
4106
+ "bulkBar",
4107
+ "seed-data-table__bulkBar"
4108
+ ],
4109
+ [
4110
+ "bulkBarCount",
4111
+ "seed-data-table__bulkBarCount"
4112
+ ],
4113
+ [
4114
+ "bulkBarClear",
4115
+ "seed-data-table__bulkBarClear"
4116
+ ],
4117
+ [
4118
+ "bulkBarActions",
4119
+ "seed-data-table__bulkBarActions"
3758
4120
  ]
3759
4121
  ];
3760
4122
  const defaultVariant$6 = {
@@ -3792,7 +4154,7 @@ Object.assign(dataTable, {
3792
4154
  // other file (`DataTable.tsx`, `column-menu.tsx`, `resize.tsx`, `column-calc.tsx`,
3793
4155
  // `toolbar.tsx`, `body-cells.tsx`, `editors.tsx`) imports its exports from here rather
3794
4156
  // than re-deriving its own.
3795
- const { ClassNamesProvider: ClassNamesProvider$1, withContext: withContext$2, useClassNames: useClassNames$1 } = createSlotRecipeContext(dataTable);
4157
+ const { ClassNamesProvider, withContext: withContext$1, useClassNames } = createSlotRecipeContext(dataTable);
3796
4158
 
3797
4159
  /**
3798
4160
  * The pure filter itself, split out of `useSelectSearch` below so it can be
@@ -4500,7 +4862,7 @@ function isCellTruncated(cell) {
4500
4862
  * actually reported against a pinned column. */ function isChipListEditable(column) {
4501
4863
  return !!column.editable && column.type === "chip-list";
4502
4864
  }
4503
- function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyFirst, stickyLast }) {
4865
+ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyFirst, stickyLast, cellContextMenu, onViewEditHistory, cellContextMenuItems }) {
4504
4866
  const effective = useEffectiveColumns(columns, {
4505
4867
  selectable,
4506
4868
  stickyFirst
@@ -4515,7 +4877,10 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4515
4877
  row: row,
4516
4878
  classNames: classNames,
4517
4879
  locale: locale,
4518
- sticky: sticky
4880
+ sticky: sticky,
4881
+ cellContextMenu: cellContextMenu,
4882
+ onViewEditHistory: onViewEditHistory,
4883
+ cellContextMenuItems: cellContextMenuItems
4519
4884
  }, column.key);
4520
4885
  })
4521
4886
  });
@@ -4527,7 +4892,7 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4527
4892
  * on every row's render. React keeps this instance stable across re-renders because
4528
4893
  * `ColumnBodyCells` keys it by `column.key` the same way it always keyed the bare
4529
4894
  * `<DataTableCell>` before this split.
4530
- */ function DataTableBodyCell({ column, row, classNames, locale, sticky }) {
4895
+ */ function DataTableBodyCell({ column, row, classNames, locale, sticky, cellContextMenu, onViewEditHistory, cellContextMenuItems }) {
4531
4896
  const { startEdit, cancelEdit, isEditing, isPending, commitEdit, isPeeking, openPeek, closePeek } = useDataTableContext();
4532
4897
  const cellRef = React.useRef(null);
4533
4898
  // Feature B rework (#8) -- the chip-list popover's own live position, in VIEWPORT
@@ -4543,6 +4908,16 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4543
4908
  // editable cell's existing double-click-to-edit (tranche 5, unchanged) must keep
4544
4909
  // rendering exactly as it always has, not suddenly gain overlay chrome.
4545
4910
  const [overlayOpen, setOverlayOpen] = React.useState(false);
4911
+ // Attio parity (attio-column-features-full.md gap #1) -- the right-click menu's own
4912
+ // point + open state. `contextMenuPoint` is frozen at the moment of the right-click
4913
+ // (never re-read live), same reasoning as `overlayOpen` above: the menu shouldn't
4914
+ // chase the cursor after it opens. `<CellContextMenu>` itself is only rendered below
4915
+ // while `contextMenuOpen` is true (not permanently mounted per cell) -- unlike
4916
+ // `ColumnMenu` (one per COLUMN, header row only), this component runs once per
4917
+ // row×column cell, and a floating-ui instance genuinely idle in every one of
4918
+ // thousands of cells would be real, avoidable overhead.
4919
+ const [contextMenuPoint, setContextMenuPoint] = React.useState(null);
4920
+ const [contextMenuOpen, setContextMenuOpen] = React.useState(false);
4546
4921
  const alwaysLive = isAlwaysLiveEditable(column);
4547
4922
  const chipListEditable = isChipListEditable(column);
4548
4923
  // A gated `editable` column (everything except the always-live widgets AND the
@@ -4594,6 +4969,19 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4594
4969
  setOverlayOpen(isCellTruncated(event.currentTarget));
4595
4970
  startEdit(row.id, column.key);
4596
4971
  }
4972
+ function handleContextMenu(event) {
4973
+ if (!cellContextMenu) return;
4974
+ // Replaces the browser's own native context menu (Attio's own gesture) -- and
4975
+ // must not also reach the row's own `onRowClick`, same reasoning as every other
4976
+ // cell-local click handler in this file.
4977
+ event.preventDefault();
4978
+ event.stopPropagation();
4979
+ setContextMenuPoint({
4980
+ x: event.clientX,
4981
+ y: event.clientY
4982
+ });
4983
+ setContextMenuOpen(true);
4984
+ }
4597
4985
  function handleClick(event) {
4598
4986
  if (chipListEditable) {
4599
4987
  // Attio's own chip-list gesture: click opens the editor regardless of
@@ -4697,7 +5085,7 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4697
5085
  const pending = isPending(row.id, column.key);
4698
5086
  return /*#__PURE__*/ jsxs(Fragment, {
4699
5087
  children: [
4700
- /*#__PURE__*/ jsx(DataTableCell, {
5088
+ /*#__PURE__*/ jsxs(DataTableCell, {
4701
5089
  ref: cellRef,
4702
5090
  "data-numeric": dataAttr(column.type === "number" || column.type === "currency"),
4703
5091
  "data-editable": dataAttr(!!column.editable),
@@ -4708,14 +5096,34 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4708
5096
  style: sticky.style,
4709
5097
  onClick: handleClick,
4710
5098
  onDoubleClick: handleDoubleClick,
4711
- children: /*#__PURE__*/ jsx(CellContent, {
4712
- column: column,
4713
- row: row,
4714
- rowId: row.id,
4715
- classNames: classNames,
4716
- locale: locale,
4717
- overlayActive: overlayActive
4718
- })
5099
+ onContextMenu: handleContextMenu,
5100
+ children: [
5101
+ /*#__PURE__*/ jsx(CellContent, {
5102
+ column: column,
5103
+ row: row,
5104
+ rowId: row.id,
5105
+ classNames: classNames,
5106
+ locale: locale,
5107
+ overlayActive: overlayActive
5108
+ }),
5109
+ cellContextMenu && contextMenuOpen && contextMenuPoint && // A `<td>` (unlike its own parent `<tr>`) can validly hold any flow
5110
+ // content, so `CellContextMenu`'s own 0x0 `MenuAnchor` -- itself never
5111
+ // portaled, only `MenuPositioner`'s actual dropdown content is (via
5112
+ // floating-ui's own `FloatingPortal`, straight to `document.body`) --
5113
+ // renders here, INSIDE this cell, rather than as a sibling of it.
5114
+ /*#__PURE__*/ jsx(CellContextMenu, {
5115
+ column: column,
5116
+ row: row,
5117
+ value: rawValue,
5118
+ locale: locale,
5119
+ point: contextMenuPoint,
5120
+ open: contextMenuOpen,
5121
+ onOpenChange: setContextMenuOpen,
5122
+ onCommit: (value)=>commitEdit(row.id, column.key, value),
5123
+ onViewEditHistory: onViewEditHistory,
5124
+ cellContextMenuItems: cellContextMenuItems
5125
+ })
5126
+ ]
4719
5127
  }),
4720
5128
  chipListEditorOpen && popoverStyle && /*#__PURE__*/ createPortal(// biome-ignore lint/a11y/noStaticElementInteractions: not a widget -- same shape as `CellExpandOverlay` (editors.tsx), which this box is a portaled variant of: it never responds to interaction on ITSELF, only stops propagation of clicks that already occurred on a real interactive descendant (the editor's input/remove buttons). No role fits it because it isn't a control.
4721
5129
  /*#__PURE__*/ jsx("div", {
@@ -4945,64 +5353,52 @@ function DisplayCellContent({ column, row, value, classNames, locale }) {
4945
5353
  }
4946
5354
  }
4947
5355
 
4948
- const { ClassNamesProvider, withContext: withContext$1, useClassNames } = createSlotRecipeContext(menu);
4949
- const { PropsProvider: ItemPropsProvider, useProps: useItemProps, withContext: withItemContext, ClassNamesProvider: ItemClassNamesProvider } = createSlotRecipeContext(menuItem);
4950
- const withStateProps = createWithStateProps([
4951
- useMenuContext
4952
- ]);
4953
- const withItemStateProps = createWithStateProps([
4954
- useMenuItemContext
4955
- ]);
4956
- const MenuRoot = (props)=>{
4957
- const [variantProps, otherProps] = menu.splitVariantProps(props);
4958
- const classNames = menu(variantProps);
4959
- return /*#__PURE__*/ jsx(ClassNamesProvider, {
4960
- value: classNames,
4961
- children: /*#__PURE__*/ jsx(ItemPropsProvider, {
4962
- value: {
4963
- size: variantProps.size
4964
- },
4965
- children: /*#__PURE__*/ jsx(Menu.Root, {
4966
- ...otherProps
5356
+ const DataTableBulkBarRoot = withContext$1(Primitive.div, "bulkBar");
5357
+ const DataTableBulkBarCount = withContext$1(Primitive.span, "bulkBarCount");
5358
+ const DataTableBulkBarClear = withContext$1(Primitive.button, "bulkBarClear");
5359
+ /** Wraps whatever the CONSUMER passes as this bar's own action buttons -- the bar
5360
+ * itself only ever supplies chrome (count + clear); every actual action (Add to
5361
+ * list, Send email, …) is the consumer's own `<ActionButton>`/`<Menu>`, the same
5362
+ * "chrome vs. consumer content" split `toolbarActions`/`toolbarEnd` already draw for
5363
+ * the toolbar's own custom actions. */ const DataTableBulkBarActions = withContext$1(Primitive.div, "bulkBarActions");
5364
+ /**
5365
+ * The floating bulk-action pill (gap #4) -- renders nothing while no row is selected
5366
+ * (reads `selectedIds` off the table-wide context itself, so a consumer doesn't have
5367
+ * to gate this on `selectable`/`selectedIds` on their own). Composed as the LAST
5368
+ * child of the table's own root (`DataTable.tsx`), which is `position: relative` for
5369
+ * exactly this reason -- see `data-table.ts`'s own `bulkBar` slot comment for why its
5370
+ * geometry borrows `contextual-floating-button`'s already-measured vars rather than a
5371
+ * guessed literal.
5372
+ */ function DataTableBulkBar({ clearLabel = "Clear selection", children }) {
5373
+ const { selectedIds, clearSelection } = useDataTableContext();
5374
+ if (selectedIds.length === 0) return null;
5375
+ return /*#__PURE__*/ jsxs(DataTableBulkBarRoot, {
5376
+ role: "toolbar",
5377
+ "aria-label": "Bulk actions",
5378
+ "aria-orientation": "horizontal",
5379
+ children: [
5380
+ /*#__PURE__*/ jsxs(DataTableBulkBarCount, {
5381
+ "aria-live": "polite",
5382
+ children: [
5383
+ selectedIds.length,
5384
+ " selected"
5385
+ ]
5386
+ }),
5387
+ /*#__PURE__*/ jsx(DataTableBulkBarClear, {
5388
+ type: "button",
5389
+ "aria-label": clearLabel,
5390
+ onClick: clearSelection,
5391
+ children: /*#__PURE__*/ jsx(IconXmarkLine_default, {
5392
+ size: 12,
5393
+ "aria-hidden": "true"
5394
+ })
5395
+ }),
5396
+ children && /*#__PURE__*/ jsx(DataTableBulkBarActions, {
5397
+ children: children(selectedIds)
4967
5398
  })
4968
- })
4969
- });
4970
- };
4971
- Menu.Anchor;
4972
- const MenuTrigger = Menu.Trigger;
4973
- const MenuPositioner = /*#__PURE__*/ React.forwardRef(({ className, ...props }, ref)=>{
4974
- const classNames = useClassNames();
4975
- return /*#__PURE__*/ jsx(Menu.Positioner, {
4976
- ref: ref,
4977
- className: clsx(classNames.positioner, className),
4978
- ...props
4979
- });
4980
- });
4981
- MenuPositioner.displayName = "MenuPositioner";
4982
- const MenuContent = withContext$1(Menu.Content, "content");
4983
- const MenuScrollArea = withContext$1(withStateProps(Primitive.div), "scrollArea");
4984
- withContext$1(Menu.Group, "group");
4985
- withContext$1(Menu.GroupLabel, "groupLabel");
4986
- const MenuItem = /*#__PURE__*/ React.forwardRef(({ className: propClassName, ...props }, ref)=>{
4987
- const [variantProps, otherProps] = menuItem.splitVariantProps(props);
4988
- const parentProps = useItemProps();
4989
- const classNames = menuItem({
4990
- ...parentProps,
4991
- ...variantProps
4992
- });
4993
- return /*#__PURE__*/ jsx(ItemClassNamesProvider, {
4994
- value: classNames,
4995
- children: /*#__PURE__*/ jsx(Menu.Item, {
4996
- ref: ref,
4997
- className: clsx(classNames.root, propClassName),
4998
- ...otherProps
4999
- })
5399
+ ]
5000
5400
  });
5001
- });
5002
- MenuItem.displayName = "MenuItem";
5003
- withItemContext(withItemStateProps(Primitive.div), "body");
5004
- const MenuItemLabel = withItemContext(withItemStateProps(Primitive.span), "label");
5005
- withItemContext(withItemStateProps(Primitive.span), "description");
5401
+ }
5006
5402
 
5007
5403
  function ColumnCalcCells({ columns, rows, selectable, stickyFirst, stickyLast }) {
5008
5404
  const effective = useEffectiveColumns(columns, {
@@ -5029,10 +5425,11 @@ function ColumnCalcCells({ columns, rows, selectable, stickyFirst, stickyLast })
5029
5425
  })
5030
5426
  });
5031
5427
  }
5032
- function formatCalcValue(value) {
5033
- return value.toLocaleString(undefined, {
5428
+ function formatCalcValue(value, op) {
5429
+ const formatted = value.toLocaleString(undefined, {
5034
5430
  maximumFractionDigits: 2
5035
5431
  });
5432
+ return op === "percentEmpty" || op === "percentFilled" ? `${formatted}%` : formatted;
5036
5433
  }
5037
5434
  /**
5038
5435
  * One ColumnCalc footer cell -- a click-anywhere-in-the-cell trigger (crisp's own
@@ -5067,7 +5464,7 @@ function formatCalcValue(value) {
5067
5464
  children: [
5068
5465
  /*#__PURE__*/ jsx("span", {
5069
5466
  className: "seed-data-table-column-calc-value",
5070
- children: formatCalcValue(result)
5467
+ children: formatCalcValue(result, op)
5071
5468
  }),
5072
5469
  /*#__PURE__*/ jsx("span", {
5073
5470
  className: "seed-data-table-column-calc-op",
@@ -5088,21 +5485,13 @@ function formatCalcValue(value) {
5088
5485
  style: {
5089
5486
  minWidth: 160
5090
5487
  },
5091
- children: /*#__PURE__*/ jsxs(MenuScrollArea, {
5092
- children: [
5093
- ops.map((candidate)=>/*#__PURE__*/ jsx(MenuItem, {
5094
- onClick: ()=>setColumnCalcOp(column.key, candidate),
5095
- children: /*#__PURE__*/ jsx(MenuItemLabel, {
5096
- children: DATA_TABLE_CALC_LABEL[candidate]
5097
- })
5098
- }, candidate)),
5099
- op && /*#__PURE__*/ jsx(MenuItem, {
5100
- onClick: ()=>setColumnCalcOp(column.key, undefined),
5488
+ children: /*#__PURE__*/ jsx(MenuScrollArea, {
5489
+ children: ops.map((candidate)=>/*#__PURE__*/ jsx(MenuItem, {
5490
+ onClick: ()=>setColumnCalcOp(column.key, candidate),
5101
5491
  children: /*#__PURE__*/ jsx(MenuItemLabel, {
5102
- children: "Clear"
5492
+ children: DATA_TABLE_CALC_LABEL[candidate]
5103
5493
  })
5104
- })
5105
- ]
5494
+ }, candidate))
5106
5495
  })
5107
5496
  })
5108
5497
  })
@@ -5381,9 +5770,9 @@ function ColumnRenameInput({ column, initialLabel, onDone }) {
5381
5770
  const columnLabel = typeof label === "string" ? label : column.key;
5382
5771
  const pinned = isColumnPinned(column.key);
5383
5772
  // Attio/GRC's "Remove sort" (live-review fix D) -- the header's own click-to-sort
5384
- // (`SortIndicator`/`toggleSort`, above) only cycles asc/desc once a column has been
5385
- // clicked; this is the only affordance back to no sort at all, so it only makes
5386
- // sense to show on the column that IS currently sorted.
5773
+ // (`SortIndicator`/`toggleSort`, above) already cycles asc -> desc -> none on its
5774
+ // own third click, but this stays as a one-click shortcut to the same place, so it
5775
+ // only makes sense to show on the column that IS currently sorted.
5387
5776
  const isSorted = sort?.key === column.key;
5388
5777
  const stopPropagation = (event)=>event.stopPropagation();
5389
5778
  return(// Live-review fix (D): a `MenuItem` inside `MenuPositioner` renders through a
@@ -10367,6 +10756,18 @@ ContinuousDatePicker.displayName = "ContinuousDatePicker";
10367
10756
  // value list) down to `is`/`is not`/`empty`/`not empty`. `"isanyof"` is appended to any
10368
10757
  // of the four whenever the column supplies `filterValues` -- a faceted checkbox list,
10369
10758
  // independent of the column's own kind.
10759
+ //
10760
+ // Multi-select fix (DATATABLE-ATTIO-SPEC.md gap #12, live-measured 2026-09-30): a
10761
+ // `kind: "select"` column whose CELL is multi-valued (`type: "tags"`/`"chip-list"` --
10762
+ // a chip-list/tag column, not a single status pill) offers Attio's own
10763
+ // `contains`/`does not contain` instead of `is`/`is not` -- "is" reads as an exact-
10764
+ // set-equality check against an array value, which is not what Attio's own multi-
10765
+ // select filter means or what `applyToolbarFilters`' `is`/`isnot` branch (a plain
10766
+ // string-equality compare via `asText`) computes. `contains`/`doesnotcontain` are
10767
+ // already correct here unmodified: `applyToolbarFilters` already runs them against
10768
+ // `asText(raw)` (an array's own values joined), which is exactly "does any tag
10769
+ // match". Single-select (no `filterValues`-array cell type) keeps `is`/`is not` --
10770
+ // not re-measured against Attio, so left alone per the discovery doc's own hedge.
10370
10771
  ////////////////////////////////////////////////////////////////////////////////////
10371
10772
  const OP_ORDER = [
10372
10773
  "contains",
@@ -10400,9 +10801,19 @@ const SELECT_OP_ORDER = [
10400
10801
  "empty",
10401
10802
  "notempty"
10402
10803
  ];
10804
+ const MULTI_SELECT_OP_ORDER = [
10805
+ "contains",
10806
+ "doesnotcontain",
10807
+ "empty",
10808
+ "notempty"
10809
+ ];
10403
10810
  /** The operators a column of this `kind` offers, in display order -- `hasValues`
10404
- * (the column supplies `filterValues`) appends `"isanyof"` regardless of kind. */ function opsForKind(kind, hasValues) {
10405
- const base = kind === "number" ? NUMBER_OP_ORDER : kind === "date" ? DATE_OP_ORDER : kind === "select" ? SELECT_OP_ORDER : OP_ORDER;
10811
+ * (the column supplies `filterValues`) appends `"isanyof"` regardless of kind.
10812
+ * `multiValue` (the column's CELL type is a chip-list/tags column, not just its
10813
+ * filter `kind`) swaps `SELECT_OP_ORDER`'s `is`/`is not` for `contains`/`does not
10814
+ * contain`, matching Attio's own multi-select filter -- see the operator-sets
10815
+ * comment above. */ function opsForKind(kind, hasValues, multiValue = false) {
10816
+ const base = kind === "number" ? NUMBER_OP_ORDER : kind === "date" ? DATE_OP_ORDER : kind === "select" ? multiValue ? MULTI_SELECT_OP_ORDER : SELECT_OP_ORDER : OP_ORDER;
10406
10817
  return hasValues ? [
10407
10818
  ...base,
10408
10819
  "isanyof"
@@ -10770,66 +11181,58 @@ const formatFilterDate = (iso)=>{
10770
11181
  return `${OPERATOR_LABELS[filter.op]} ${value}`;
10771
11182
  }
10772
11183
  /**
10773
- * One filter condition, INSIDE the Filter popover -- a compact one-line summary row
10774
- * (field: operator value) with a trailing remove "x", expanding in place (accordion,
10775
- * not a second popover) to the field/operator/value editor when clicked. Presentation
10776
- * note (crisp/#593-style live re-check, 2026-09-28): Attio's own toolbar keeps active
10777
- * conditions as a compact "Filter · N" count on the trigger, not a row of always-on
10778
- * pill chips spread across the toolbar -- this replaces that first pass (which DID
10779
- * render one big chip per condition, directly in the toolbar row) with conditions
10780
- * living inside the ONE popover the trigger opens, the same list-of-rows shape
10781
- * `SortControl` above already uses for its own conditions.
10782
- */ function FilterConditionRow({ filter, columns, expanded, onToggle }) {
11184
+ * One filter condition, INSIDE the Filter popover -- Attio's own compact chip shape
11185
+ * (DATATABLE-ATTIO-SPEC.md gap #12, re-measured live 2026-09-30): `[attribute ▾]
11186
+ * [operator …] [value] [remove ×]`, ALL of it visible at once, never behind a click-
11187
+ * to-expand accordion. Supersedes this file's earlier 2026-09-28 note (which read
11188
+ * Attio as keeping conditions collapsed behind a bare "Filter · N" count and put them
11189
+ * in an accordion for that reason) -- the fresh read found the opposite: every active
11190
+ * condition shows attribute/operator/value simultaneously. This keeps the outer shell
11191
+ * (the "Filter"/"Filter · N" trigger opening ONE popover, `SortControl`'s own
11192
+ * list-of-rows shape) rather than moving chips into the toolbar row itself, so the
11193
+ * already-covered trigger/`Add filter` DOM (`DataTable.test.tsx`) stays put; only the
11194
+ * PER-CONDITION row tightens, from an accordion to an always-open card. The operator
11195
+ * set stays a row of small pill buttons (not a dropdown) so every existing test that
11196
+ * clicks an operator by its exact button name keeps working unchanged.
11197
+ */ function FilterConditionRow({ filter, columns }) {
10783
11198
  const { patchToolbarFilter, changeToolbarFilterField, removeToolbarFilter } = useDataTableContext();
10784
11199
  const column = columns.find((c)=>c.key === filter.key);
10785
11200
  const kind = column?.kind;
11201
+ const multiValue = column?.type === "tags" || column?.type === "chip-list";
10786
11202
  const values = column?.filterValues ?? [];
10787
- const ops = opsForKind(kind, values.length > 0);
10788
- return /*#__PURE__*/ jsxs("div", {
11203
+ const ops = opsForKind(kind, values.length > 0, multiValue);
11204
+ return /*#__PURE__*/ jsxs("fieldset", {
11205
+ "aria-label": `${column ? columnLabel(column) : filter.key} filter: ${conditionSummary(filter, column)}`,
10789
11206
  style: {
11207
+ margin: 0,
10790
11208
  display: "flex",
10791
11209
  flexDirection: "column",
10792
- gap: 6,
11210
+ gap: 8,
10793
11211
  borderRadius: 6,
10794
- background: expanded ? "var(--seed-color-bg-neutral-weak)" : "transparent",
10795
- padding: expanded ? 6 : 0
11212
+ border: "1px solid var(--seed-color-border-neutral)",
11213
+ background: "var(--seed-color-bg-neutral-weak)",
11214
+ padding: 6
10796
11215
  },
10797
11216
  children: [
10798
11217
  /*#__PURE__*/ jsxs("div", {
10799
11218
  style: {
10800
11219
  display: "flex",
10801
11220
  alignItems: "center",
10802
- gap: 2
11221
+ gap: 4
10803
11222
  },
10804
11223
  children: [
10805
- /*#__PURE__*/ jsxs("button", {
10806
- type: "button",
10807
- onClick: onToggle,
11224
+ /*#__PURE__*/ jsx(InlineSelect, {
11225
+ ariaLabel: "Filter by field",
11226
+ value: filter.key,
11227
+ onChange: (key)=>changeToolbarFilterField(filter.id, key),
11228
+ options: columns.map((c)=>({
11229
+ value: c.key,
11230
+ label: columnLabel(c)
11231
+ })),
10808
11232
  style: {
10809
11233
  flex: 1,
10810
- minWidth: 0,
10811
- textAlign: "left",
10812
- padding: "6px 8px",
10813
- border: "none",
10814
- borderRadius: 6,
10815
- background: "transparent",
10816
- cursor: "pointer",
10817
- fontSize: 13,
10818
- overflow: "hidden",
10819
- textOverflow: "ellipsis",
10820
- whiteSpace: "nowrap"
10821
- },
10822
- children: [
10823
- /*#__PURE__*/ jsx("strong", {
10824
- style: {
10825
- fontWeight: 500
10826
- },
10827
- children: column ? columnLabel(column) : filter.key
10828
- }),
10829
- ":",
10830
- " ",
10831
- conditionSummary(filter, column)
10832
- ]
11234
+ minWidth: 0
11235
+ }
10833
11236
  }),
10834
11237
  /*#__PURE__*/ jsx("button", {
10835
11238
  type: "button",
@@ -10843,136 +11246,103 @@ const formatFilterDate = (iso)=>{
10843
11246
  })
10844
11247
  ]
10845
11248
  }),
10846
- expanded && /*#__PURE__*/ jsxs("div", {
11249
+ /*#__PURE__*/ jsx("div", {
10847
11250
  style: {
10848
- padding: "0 8px 4px",
10849
11251
  display: "flex",
10850
- flexDirection: "column",
10851
- gap: 8
11252
+ flexWrap: "wrap",
11253
+ gap: 2
10852
11254
  },
10853
- children: [
10854
- /*#__PURE__*/ jsx("div", {
10855
- style: {
10856
- fontSize: 12,
10857
- color: "var(--seed-color-fg-neutral-subtle)"
10858
- },
10859
- children: "Field"
10860
- }),
10861
- /*#__PURE__*/ jsx(InlineSelect, {
10862
- ariaLabel: "Filter by field",
10863
- value: filter.key,
10864
- onChange: (key)=>changeToolbarFilterField(filter.id, key),
10865
- options: columns.map((c)=>({
10866
- value: c.key,
10867
- label: columnLabel(c)
10868
- }))
10869
- }),
10870
- /*#__PURE__*/ jsx("div", {
10871
- style: {
10872
- fontSize: 12,
10873
- color: "var(--seed-color-fg-neutral-subtle)"
10874
- },
10875
- children: "Condition"
10876
- }),
10877
- /*#__PURE__*/ jsx("div", {
11255
+ children: ops.map((op)=>/*#__PURE__*/ jsx("button", {
11256
+ type: "button",
11257
+ "data-active": filter.op === op ? "" : undefined,
11258
+ onClick: ()=>patchToolbarFilter(filter.id, {
11259
+ op
11260
+ }),
10878
11261
  style: {
10879
- display: "flex",
10880
- flexDirection: "column",
10881
- gap: 2
11262
+ textAlign: "left",
11263
+ padding: "4px 8px",
11264
+ borderRadius: 999,
11265
+ border: "none",
11266
+ cursor: "pointer",
11267
+ background: filter.op === op ? "var(--seed-color-bg-layer-default)" : "transparent",
11268
+ fontSize: 12
10882
11269
  },
10883
- children: ops.map((op)=>/*#__PURE__*/ jsx("button", {
10884
- type: "button",
10885
- "data-active": filter.op === op ? "" : undefined,
10886
- onClick: ()=>patchToolbarFilter(filter.id, {
10887
- op
10888
- }),
10889
- style: {
10890
- textAlign: "left",
10891
- padding: "6px 8px",
10892
- borderRadius: 6,
10893
- border: "none",
10894
- cursor: "pointer",
10895
- background: filter.op === op ? "var(--seed-color-bg-layer-default)" : "transparent",
10896
- fontSize: 13
10897
- },
10898
- children: OPERATOR_LABELS[op]
10899
- }, op))
10900
- }),
10901
- opNeedsValues(filter.op) && /*#__PURE__*/ jsx("div", {
11270
+ children: OPERATOR_LABELS[op]
11271
+ }, op))
11272
+ }),
11273
+ opNeedsValues(filter.op) && /*#__PURE__*/ jsx("div", {
11274
+ style: {
11275
+ display: "flex",
11276
+ flexDirection: "column",
11277
+ gap: 4,
11278
+ maxHeight: 160,
11279
+ overflow: "auto"
11280
+ },
11281
+ children: values.map((value)=>{
11282
+ const checked = (filter.values ?? []).includes(value);
11283
+ return /*#__PURE__*/ jsxs(CheckboxRoot, {
11284
+ checked: checked,
11285
+ onCheckedChange: ()=>patchToolbarFilter(filter.id, {
11286
+ values: checked ? (filter.values ?? []).filter((v)=>v !== value) : [
11287
+ ...filter.values ?? [],
11288
+ value
11289
+ ]
11290
+ }),
10902
11291
  style: {
10903
11292
  display: "flex",
10904
- flexDirection: "column",
10905
- gap: 4,
10906
- maxHeight: 160,
10907
- overflow: "auto"
11293
+ alignItems: "center",
11294
+ gap: 8,
11295
+ padding: "4px 0",
11296
+ cursor: "pointer"
10908
11297
  },
10909
- children: values.map((value)=>{
10910
- const checked = (filter.values ?? []).includes(value);
10911
- return /*#__PURE__*/ jsxs(CheckboxRoot, {
10912
- checked: checked,
10913
- onCheckedChange: ()=>patchToolbarFilter(filter.id, {
10914
- values: checked ? (filter.values ?? []).filter((v)=>v !== value) : [
10915
- ...filter.values ?? [],
10916
- value
10917
- ]
10918
- }),
10919
- style: {
10920
- display: "flex",
10921
- alignItems: "center",
10922
- gap: 8,
10923
- padding: "4px 0",
10924
- cursor: "pointer"
10925
- },
10926
- children: [
10927
- /*#__PURE__*/ jsx(CheckboxControl, {
10928
- children: /*#__PURE__*/ jsx(CheckboxIndicator, {
10929
- checked: /*#__PURE__*/ jsx(IconCheckmarkLine_default, {
10930
- size: 12
10931
- })
10932
- })
10933
- }),
10934
- /*#__PURE__*/ jsx(CheckboxLabel, {
10935
- children: value
10936
- }),
10937
- /*#__PURE__*/ jsx(CheckboxHiddenInput, {
10938
- "aria-label": value
11298
+ children: [
11299
+ /*#__PURE__*/ jsx(CheckboxControl, {
11300
+ children: /*#__PURE__*/ jsx(CheckboxIndicator, {
11301
+ checked: /*#__PURE__*/ jsx(IconCheckmarkLine_default, {
11302
+ size: 12
10939
11303
  })
10940
- ]
10941
- }, value);
10942
- })
10943
- }),
10944
- opNeedsValue(filter.op) && kind === "date" && /*#__PURE__*/ jsx(DatePicker, {
10945
- value: filter.value ? toDatePickerDate(isoToDate(filter.value) ?? new Date()) : undefined,
10946
- onValueChange: (d)=>d && patchToolbarFilter(filter.id, {
10947
- value: dateToISO(fromDatePickerDate(d))
10948
- })
10949
- }),
10950
- opNeedsValue(filter.op) && kind === "select" && /*#__PURE__*/ jsx(InlineSelect, {
10951
- ariaLabel: "Filter value",
10952
- placeholder: "Value…",
10953
- value: filter.value || undefined,
10954
- onChange: (value)=>patchToolbarFilter(filter.id, {
10955
- value
11304
+ })
10956
11305
  }),
10957
- options: values.map((value)=>({
10958
- value,
10959
- label: value
10960
- }))
10961
- }),
10962
- opNeedsValue(filter.op) && kind !== "date" && kind !== "select" && /*#__PURE__*/ jsx(TextFieldRoot, {
10963
- variant: "outline",
10964
- size: "medium",
10965
- children: /*#__PURE__*/ jsx(TextFieldInput, {
10966
- type: kind === "number" ? "number" : "text",
10967
- value: filter.value,
10968
- onChange: (e)=>patchToolbarFilter(filter.id, {
10969
- value: e.target.value
10970
- }),
10971
- placeholder: "Value…",
10972
- "aria-label": "Filter value"
10973
- })
11306
+ /*#__PURE__*/ jsx(CheckboxLabel, {
11307
+ children: value
11308
+ }),
11309
+ /*#__PURE__*/ jsx(CheckboxHiddenInput, {
11310
+ "aria-label": value
11311
+ })
11312
+ ]
11313
+ }, value);
11314
+ })
11315
+ }),
11316
+ opNeedsValue(filter.op) && kind === "date" && /*#__PURE__*/ jsx(DatePicker, {
11317
+ value: filter.value ? toDatePickerDate(isoToDate(filter.value) ?? new Date()) : undefined,
11318
+ onValueChange: (d)=>d && patchToolbarFilter(filter.id, {
11319
+ value: dateToISO(fromDatePickerDate(d))
10974
11320
  })
10975
- ]
11321
+ }),
11322
+ opNeedsValue(filter.op) && kind === "select" && /*#__PURE__*/ jsx(InlineSelect, {
11323
+ ariaLabel: "Filter value",
11324
+ placeholder: "Value…",
11325
+ value: filter.value || undefined,
11326
+ onChange: (value)=>patchToolbarFilter(filter.id, {
11327
+ value
11328
+ }),
11329
+ options: values.map((value)=>({
11330
+ value,
11331
+ label: value
11332
+ }))
11333
+ }),
11334
+ opNeedsValue(filter.op) && kind !== "date" && kind !== "select" && /*#__PURE__*/ jsx(TextFieldRoot, {
11335
+ variant: "outline",
11336
+ size: "medium",
11337
+ children: /*#__PURE__*/ jsx(TextFieldInput, {
11338
+ type: kind === "number" ? "number" : "text",
11339
+ value: filter.value,
11340
+ onChange: (e)=>patchToolbarFilter(filter.id, {
11341
+ value: e.target.value
11342
+ }),
11343
+ placeholder: "Value…",
11344
+ "aria-label": "Filter value"
11345
+ })
10976
11346
  })
10977
11347
  ]
10978
11348
  });
@@ -10980,15 +11350,14 @@ const formatFilterDate = (iso)=>{
10980
11350
  /**
10981
11351
  * The toolbar's Filter trigger + popover -- a compact `"Filter"` / `"Filter · N"`
10982
11352
  * button (matching `SortControl`'s own `"Sort"` / `"Sort · N"`), opening ONE popover
10983
- * that holds both the active conditions (compact rows, `FilterConditionRow` above,
10984
- * one expanded at a time) and, behind "+ Add filter", the field picker that appends a
10985
- * new one -- not a row of always-on pill chips spread across the toolbar (this
10986
- * replaces that first pass; see `FilterConditionRow`'s own doc comment).
11353
+ * that holds both the active conditions (compact chips, `FilterConditionRow` above,
11354
+ * ALL open at once -- no accordion) and, behind "+ Add filter", the field picker that
11355
+ * appends a new one. See `FilterConditionRow`'s own doc comment for why the chips
11356
+ * live inside this one popover rather than spread across the toolbar row itself.
10987
11357
  */ function FilterControl({ columns, label = "Filter" }) {
10988
11358
  const { toolbarFilters, addToolbarFilter } = useDataTableContext();
10989
11359
  const [open, setOpen] = React.useState(false);
10990
11360
  const [pickingField, setPickingField] = React.useState(false);
10991
- const [expandedId, setExpandedId] = React.useState(null);
10992
11361
  return /*#__PURE__*/ jsxs(MenuRoot, {
10993
11362
  open: open,
10994
11363
  onOpenChange: (next)=>{
@@ -11050,8 +11419,7 @@ const formatFilterDate = (iso)=>{
11050
11419
  columns.map((c)=>/*#__PURE__*/ jsx("button", {
11051
11420
  type: "button",
11052
11421
  onClick: ()=>{
11053
- const id = addToolbarFilter(c.key);
11054
- setExpandedId(id);
11422
+ addToolbarFilter(c.key);
11055
11423
  setPickingField(false);
11056
11424
  },
11057
11425
  style: {
@@ -11084,9 +11452,7 @@ const formatFilterDate = (iso)=>{
11084
11452
  }),
11085
11453
  toolbarFilters.map((f)=>/*#__PURE__*/ jsx(FilterConditionRow, {
11086
11454
  filter: f,
11087
- columns: columns,
11088
- expanded: expandedId === f.id,
11089
- onToggle: ()=>setExpandedId((id)=>id === f.id ? null : f.id)
11455
+ columns: columns
11090
11456
  }, f.id)),
11091
11457
  /*#__PURE__*/ jsxs("button", {
11092
11458
  type: "button",
@@ -11115,8 +11481,8 @@ const formatFilterDate = (iso)=>{
11115
11481
  });
11116
11482
  }
11117
11483
 
11118
- const DataTableToolbar = withContext$2(Primitive.div, "toolbar");
11119
- const DataTableCount = withContext$2(Primitive.span, "count");
11484
+ const DataTableToolbar = withContext$1(Primitive.div, "toolbar");
11485
+ const DataTableCount = withContext$1(Primitive.span, "count");
11120
11486
  /**
11121
11487
  * The toolbar's own right-hand group -- `count`'s `marginInlineStart: auto` moved
11122
11488
  * here (off the `<span>` itself) so a consumer's `toolbarActions` and the row count
@@ -11127,7 +11493,7 @@ const DataTableCount = withContext$2(Primitive.span, "count");
11127
11493
  * it. `DataTable.tsx` always renders this wrapper once either `toolbarActions` or
11128
11494
  * `showCount` is on, so it degrades to exactly the old single-count layout when no
11129
11495
  * `toolbarActions` is passed.
11130
- */ const DataTableToolbarEnd = withContext$2(Primitive.div, "toolbarEnd");
11496
+ */ const DataTableToolbarEnd = withContext$1(Primitive.div, "toolbarEnd");
11131
11497
  /**
11132
11498
  * A compact, borderless search box -- crisp's own measured `.crisp-datatable-search`
11133
11499
  * (32px, `bgSurface` background, a leading icon, a soft rest-state shadow, a 1px brand
@@ -11177,7 +11543,7 @@ function RowCount({ shown, total, countLabel }) {
11177
11543
  }
11178
11544
 
11179
11545
  function DataTableInner(props, ref) {
11180
- 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;
11546
+ 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;
11181
11547
  const classNames = dataTable({
11182
11548
  surface,
11183
11549
  interactive
@@ -11301,7 +11667,7 @@ function DataTableInner(props, ref) {
11301
11667
  // loading/error/empty body, infinite scroll's sentinel, the add-row footer) reads
11302
11668
  // this ONE value rather than each re-deriving it, so they can never drift apart.
11303
11669
  const totalCols = columns.length + (selectable ? 1 : 0);
11304
- return /*#__PURE__*/ jsx(ClassNamesProvider$1, {
11670
+ return /*#__PURE__*/ jsx(ClassNamesProvider, {
11305
11671
  value: classNames,
11306
11672
  children: /*#__PURE__*/ jsxs(DataTableRoot, {
11307
11673
  ref: ref,
@@ -11457,7 +11823,10 @@ function DataTableInner(props, ref) {
11457
11823
  locale: locale,
11458
11824
  selectable: !!selectable,
11459
11825
  stickyFirst: stickyFirst,
11460
- stickyLast: resolvedStickyLast
11826
+ stickyLast: resolvedStickyLast,
11827
+ cellContextMenu: cellContextMenu,
11828
+ onViewEditHistory: onViewEditHistory,
11829
+ cellContextMenuItems: cellContextMenuItems
11461
11830
  })
11462
11831
  ]
11463
11832
  }, row.id)),
@@ -11496,6 +11865,10 @@ function DataTableInner(props, ref) {
11496
11865
  })
11497
11866
  ]
11498
11867
  })
11868
+ }),
11869
+ selectable && /*#__PURE__*/ jsx(DataTableBulkBar, {
11870
+ clearLabel: clearSelectionLabel,
11871
+ children: bulkActions
11499
11872
  })
11500
11873
  ]
11501
11874
  })
@@ -11549,7 +11922,7 @@ DataTableInner.displayName = "DataTable";
11549
11922
  // is a SIBLING of the toolbar (both inside `DataTablePrimitiveRoot`'s card `<div>`)
11550
11923
  // rather than `DataTablePrimitiveRoot` itself: a `<table>` cannot host the toolbar as a
11551
11924
  // child without the HTML parser foster-parenting it back out.
11552
- const DataTableTable = withContext$2(DataTableTable$1, "table");
11925
+ const DataTableTable = withContext$1(DataTableTable$1, "table");
11553
11926
  /** One `<col>` per rendered column (plus the 40px selection column, if any) -- the
11554
11927
  * SINGLE place a column's width is ever set now. Every row's header/body/footer cell
11555
11928
  * reads its own width off this shared `<colgroup>` by construction (native table
@@ -11586,15 +11959,15 @@ const DataTableTable = withContext$2(DataTableTable$1, "table");
11586
11959
  ]
11587
11960
  });
11588
11961
  }
11589
- const DataTableHeader = withContext$2(DataTableHeader$1, "header");
11590
- const DataTableHeaderCell = withContext$2(DataTableHeaderCell$1, "headerCell");
11962
+ const DataTableHeader = withContext$1(DataTableHeader$1, "header");
11963
+ const DataTableHeaderCell = withContext$1(DataTableHeaderCell$1, "headerCell");
11591
11964
  // `.crisp-datatable-th-inner` -- the flex row INSIDE a `<th>` (label + filter trigger +
11592
11965
  // sort chevron + column menu). See qvism-preset's own `headerCellInner` slot comment
11593
11966
  // for why this lives one level below the cell itself rather than on it.
11594
- const DataTableHeaderCellInner = withContext$2(Primitive.div, "headerCellInner");
11967
+ const DataTableHeaderCellInner = withContext$1(Primitive.div, "headerCellInner");
11595
11968
  /** The label text specifically -- the one thing in `DataTableHeaderCellInner` that
11596
11969
  * truncates; the trailing filter/sort/menu icons stay fully visible regardless of how
11597
- * long a column's own header text is. */ const DataTableHeaderCellLabel = withContext$2(Primitive.span, "headerCellLabel");
11970
+ * long a column's own header text is. */ const DataTableHeaderCellLabel = withContext$1(Primitive.span, "headerCellLabel");
11598
11971
  /**
11599
11972
  * A selection `<th>`/`<td>`/footer `<td>` composes ITS base slot (`headerCell`/`cell`/
11600
11973
  * `footerCell`) together with the `selectionCell` MODIFIER, rather than replacing it --
@@ -11605,7 +11978,7 @@ const DataTableHeaderCellInner = withContext$2(Primitive.div, "headerCellInner")
11605
11978
  * a second recipe call.
11606
11979
  */ function withSelectionCell(Component, baseSlot) {
11607
11980
  const StyledComponent = /*#__PURE__*/ forwardRef((props, ref)=>{
11608
- const classNames = useClassNames$1();
11981
+ const classNames = useClassNames();
11609
11982
  return /*#__PURE__*/ jsx(Component, {
11610
11983
  ref: ref,
11611
11984
  ...props,
@@ -11616,12 +11989,12 @@ const DataTableHeaderCellInner = withContext$2(Primitive.div, "headerCellInner")
11616
11989
  return StyledComponent;
11617
11990
  }
11618
11991
  const DataTableSelectionHeaderCell = withSelectionCell(DataTableHeaderCell$1, "headerCell");
11619
- const DataTableHeaderCellIcon = withContext$2(Primitive.span, "headerCellIcon");
11992
+ const DataTableHeaderCellIcon = withContext$1(Primitive.span, "headerCellIcon");
11620
11993
  // body has no CSS slot of its own in the rootage spec -- it is a plain structural
11621
11994
  // `<tbody>`, so it is not wrapped in `withContext`.
11622
11995
  const DataTableBody = DataTableBody$1;
11623
- const DataTableRow = withContext$2(DataTableRow$1, "row");
11624
- const DataTableCell = withContext$2(DataTableCell$1, "cell");
11996
+ const DataTableRow = withContext$1(DataTableRow$1, "row");
11997
+ const DataTableCell = withContext$1(DataTableCell$1, "cell");
11625
11998
  const DataTableSelectionCell = withSelectionCell(DataTableCell$1, "cell");
11626
11999
  // Tranche 4 -- column menu + reorder + resize + sticky columns. A scroll boundary
11627
12000
  // wrapping the whole `<table>`: `overflowX: auto` is what makes a wide table (more
@@ -11631,7 +12004,7 @@ const DataTableSelectionCell = withSelectionCell(DataTableCell$1, "cell");
11631
12004
  // stays out of this slot's own base css (nothing here has a bounded height of its own
11632
12005
  // to clip), so it never introduces a second vertical scrollbar next to whatever
11633
12006
  // ambient container the whole DataTable already sits in.
11634
- const DataTableScrollBodyBase = withContext$2(Primitive.div, "scrollArea");
12007
+ const DataTableScrollBodyBase = withContext$1(Primitive.div, "scrollArea");
11635
12008
  function DataTableScrollBody({ children, scrollProps }) {
11636
12009
  const { scrollAreaRef, scrolledStart, scrolledEnd, onScroll } = useDataTableContext();
11637
12010
  const { className: scrollClassName, style: scrollStyle, ...otherScrollProps } = scrollProps ?? {};
@@ -11646,7 +12019,7 @@ function DataTableScrollBody({ children, scrollProps }) {
11646
12019
  children: children
11647
12020
  });
11648
12021
  }
11649
- const DataTableFooterBase = withContext$2(Primitive.tfoot, "footer");
12022
+ const DataTableFooterBase = withContext$1(Primitive.tfoot, "footer");
11650
12023
  function DataTableFooter({ children, ...props }) {
11651
12024
  return /*#__PURE__*/ jsx(DataTableFooterBase, {
11652
12025
  ...props,
@@ -11655,7 +12028,7 @@ function DataTableFooter({ children, ...props }) {
11655
12028
  })
11656
12029
  });
11657
12030
  }
11658
- const DataTableFooterCell = withContext$2(Primitive.td, "footerCell");
12031
+ const DataTableFooterCell = withContext$1(Primitive.td, "footerCell");
11659
12032
  // The selection column's OWN sticky wrappers -- one each for header/body/footer,
11660
12033
  // reading the live pinned set via `useSelectionStickyFirst` (column-layout.ts) instead
11661
12034
  // of the `stickyFirst` PROP alone, so the checkbox column sticks the moment ANY column
@@ -15592,4 +15965,4 @@ function RecordPreviewPanel({ open: _open, record, onClose, className, animation
15592
15965
  }
15593
15966
  RecordPreviewPanel.displayName = "RecordPreviewPanel";
15594
15967
 
15595
- export { DataTable, DataTableViewsControl, RecordPreview, ResizablePanel };
15968
+ export { DataTable, DataTableBulkBar, DataTableViewsControl, RecordPreview, ResizablePanel };