@crispy-seed/data-table 0.10.1 → 0.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -16,6 +16,7 @@ var checkmark = require('@seed-design/css/recipes/checkmark');
16
16
  var reactCheckbox = require('@seed-design/react-checkbox');
17
17
  var reactScaleFeedback = require('@seed-design/react-scale-feedback');
18
18
  var checkboxGroup = require('@seed-design/css/recipes/checkbox-group');
19
+ var reactDom = require('react-dom');
19
20
  var reactImage = require('@seed-design/react-image');
20
21
  var avatar = require('@seed-design/css/recipes/avatar');
21
22
  require('@seed-design/css/recipes/avatar-stack');
@@ -2578,6 +2579,50 @@ const Badge = /*#__PURE__*/ React.forwardRef(({ className, children, ...props },
2578
2579
  });
2579
2580
  Badge.displayName = "Badge";
2580
2581
 
2582
+ // Feature B rework (#8, chip-list display) -- the pure "how many chips fit" decision,
2583
+ // isolated from the DOM measuring that drives it (`cells.tsx`'s `ChipListCell`) the same
2584
+ // way `truncate.ts`/`filtering.ts` already split a pure calculation out of the styled
2585
+ // layer: this file touches no DOM, so the actual fitting algorithm is unit-testable with
2586
+ // plain numbers instead of a real laid-out browser.
2587
+ //
2588
+ // Attio's own chip-list cell (e.g. Categories: "Internet", "Broadcasting", "+7") shows as
2589
+ // many WHOLE chips as fit left-to-right, then one "+N" badge for the rest -- never a
2590
+ // chip clipped mid-render, never a second wrapped line. `ChipListCell` measures every
2591
+ // chip's real rendered width (font/padding-dependent, so it can't be estimated from
2592
+ // character count) plus the container's current width, then asks this function how many
2593
+ // of those chips it can show before the running total (chips + gaps + the badge itself)
2594
+ // would exceed the container.
2595
+ /**
2596
+ * `overflowBadgeWidth` is the WORST-CASE badge width the caller measured (e.g. the
2597
+ * width of a badge showing every tag: `+<chipWidths.length>`) rather than the width of
2598
+ * the badge for whatever count this call actually returns -- the real badge (fewer
2599
+ * digits once some chips DO fit) is never wider than that, so reserving the worst case
2600
+ * up front avoids a second measuring pass and always yields a result that fits. It may
2601
+ * slightly under-fill the row by the odd chip in a boundary case (e.g. "+9" vs "+10" --
2602
+ * one fewer digit than reserved), which is the deliberate trade against re-measuring per
2603
+ * candidate count.
2604
+ */ function computeVisibleChipCount(chipWidths, containerWidth, gap, overflowBadgeWidth) {
2605
+ // An unmeasured/zero-width container (not yet laid out, e.g. `display: none` or a
2606
+ // pre-hydration frame) has no meaningful "fits" answer -- show everything rather than
2607
+ // collapsing every chip behind "+N" for a frame it'll never actually paint like that.
2608
+ if (containerWidth <= 0) return chipWidths.length;
2609
+ let used = 0;
2610
+ let count = 0;
2611
+ for(let index = 0; index < chipWidths.length; index++){
2612
+ const withGap = count > 0 ? gap : 0;
2613
+ const usedIfShown = used + withGap + chipWidths[index];
2614
+ const remainingAfterThis = chipWidths.length - (index + 1);
2615
+ // Every chip except the very last one needs room left over for the overflow badge
2616
+ // TOO (if it's going to exist at all) -- otherwise this loop would happily show
2617
+ // every chip except the last, then have nowhere left to put "+1".
2618
+ const badgeReserve = remainingAfterThis > 0 ? gap + overflowBadgeWidth : 0;
2619
+ if (usedIfShown + badgeReserve > containerWidth) break;
2620
+ used = usedIfShown;
2621
+ count++;
2622
+ }
2623
+ return count;
2624
+ }
2625
+
2581
2626
  // Attio cell-type renderers, built from seed's own components -- not exported from
2582
2627
  // the package's public surface (kept alongside DataTable.tsx as an implementation
2583
2628
  // detail, the way Select keeps `InternalIcon` under `components/private`). Each
@@ -2792,19 +2837,125 @@ function StatusCell({ value }) {
2792
2837
  className: className
2793
2838
  });
2794
2839
  }
2795
- /** `type: "chip-list"`, READ-ONLY (Feature B, #8) -- multiple swatch chips, no add/
2796
- * remove affordances. The editable case is editors.tsx's `ChipListEditor`, an
2797
- * always-live widget (interacting with it IS the commit, the same `switch`/
2798
- * `rating`/`checkbox` shape `body-cells.tsx`'s `isAlwaysLiveEditable` already
2799
- * covers) -- this component only ever renders the REST display. */ function ChipListCell({ value, className, chipClassName }) {
2840
+ // Feature B rework (#8) -- the chip-list DISPLAY row's own gap, mirrored from the
2841
+ // qvism recipe's `chipList` slot (`packages/qvism-preset/src/recipes/data-table.ts`'s
2842
+ // `CHIP_GAP`, measured live off Attio at 3px between chips). Kept as a literal here
2843
+ // too, same footnote as `CHIP_SWATCHES` above: this file and the recipe are two
2844
+ // independent sources for numbers that have to agree, not one importing the other
2845
+ // (react/AGENTS.md -- style stays in the recipe; this is the one JS-side computation,
2846
+ // `computeVisibleChipCount`, that needs the SAME number to size against).
2847
+ const CHIP_LIST_GAP_PX = 3;
2848
+ /** `type: "chip-list"` display -- every editable/non-editable chip-list cell renders
2849
+ * through this component for its REST state (editable: when its click-to-edit
2850
+ * popover, editors.tsx's `ChipListEditor`, isn't open; read-only: always). Attio's
2851
+ * own model (its Categories cell: "Internet", "Broadcasting", "+7") -- show as many
2852
+ * WHOLE chips as fit the cell's current width, then one "+N" badge for the rest,
2853
+ * never a chip clipped mid-render, never a second wrapped line, never overflowing
2854
+ * into a neighboring (possibly sticky) column. `body-cells.tsx`'s `isAlwaysLiveEditable`
2855
+ * no longer lists `chip-list` -- the always-live inline editor this replaced is what
2856
+ * produced exactly that overflow (crispy-seed's #1094).
2857
+ *
2858
+ * The fitting decision itself (`computeVisibleChipCount`, `chip-overflow.ts`) is pure
2859
+ * and unit-tested on its own; this component's only job is feeding it real measured
2860
+ * widths. Two widths need measuring that can't be estimated from character count
2861
+ * (font/padding-dependent): every chip's own rendered width, and the "+N" badge's
2862
+ * worst-case width (reserved for the FULL tag count, so the real badge -- fewer
2863
+ * digits once some chips fit -- never overflows what was reserved for it; see
2864
+ * `computeVisibleChipCount`'s own doc comment). Both are measured off a permanently
2865
+ * mounted, invisible copy (`visibility: hidden`, absolutely positioned so it never
2866
+ * affects layout -- `position: relative` on the `chipList` slot's own recipe rule
2867
+ * makes THIS element, not the `<td>` two levels up, its containing block, and the
2868
+ * `chipList` slot's own `overflow: hidden` clips it there too) -- kept mounted at ALL
2869
+ * times, not just while a `ResizeObserver` fires, because widening the column back
2870
+ * out needs to know the width of chips currently hidden behind "+N" to decide
2871
+ * whether they now fit again.
2872
+ */ function ChipListCell({ value, className, chipClassName, overflowClassName }) {
2800
2873
  const tags = Array.isArray(value) ? value.filter((tag)=>!!tag) : [];
2874
+ const containerRef = React__namespace.useRef(null);
2875
+ const measureRef = React__namespace.useRef(null);
2876
+ const overflowMeasureRef = React__namespace.useRef(null);
2877
+ const [visibleCount, setVisibleCount] = React__namespace.useState(tags.length);
2878
+ // Every tag joined into one string -- a stable primitive dependency for the effect
2879
+ // below, so it only re-measures when the actual tag SET changes, not on every
2880
+ // unrelated re-render of the row (a new `tags` array identity from a fresh
2881
+ // `getColumnValue` call on every render would otherwise re-run this every time).
2882
+ const tagsKey = tags.join("\u0000");
2883
+ // biome-ignore lint/correctness/useExhaustiveDependencies: `tagsKey` is a deliberate serialized proxy for `tags` (a fresh array identity every render would otherwise re-run this effect on every unrelated re-render) -- `tags` itself is intentionally excluded, and `tags.length` (referenced below, in the early-return branch) is already covered by `tagsKey` changing whenever the tag SET does, including a same-length rename/reorder that `tags.length` alone would miss.
2884
+ React__namespace.useLayoutEffect(()=>{
2885
+ const container = containerRef.current;
2886
+ const measure = measureRef.current;
2887
+ // No real layout to measure against (SSR, or a test environment with no layout
2888
+ // engine, e.g. this package's own `bun test` -- happy-dom lays out nothing, every
2889
+ // rect comes back zero) -- show every chip rather than a permanently-wrong "+N"
2890
+ // for a width this environment can never actually report.
2891
+ if (!container || !measure || typeof ResizeObserver === "undefined") {
2892
+ setVisibleCount(tags.length);
2893
+ return;
2894
+ }
2895
+ // Rebound to non-null consts (rather than a `!` assertion inside `recompute`
2896
+ // below) -- `container`/`measure` are already narrowed non-null here, and these
2897
+ // two bindings carry that narrowing into the closure without re-deriving it.
2898
+ const measuredContainer = container;
2899
+ const measuredList = measure;
2900
+ function recompute() {
2901
+ const chipWidths = Array.from(measuredList.children, (el)=>el.getBoundingClientRect().width);
2902
+ const overflowWidth = overflowMeasureRef.current?.getBoundingClientRect().width ?? 0;
2903
+ setVisibleCount(computeVisibleChipCount(chipWidths, measuredContainer.clientWidth, CHIP_LIST_GAP_PX, overflowWidth));
2904
+ }
2905
+ recompute();
2906
+ const observer = new ResizeObserver(recompute);
2907
+ observer.observe(container);
2908
+ return ()=>observer.disconnect();
2909
+ }, [
2910
+ tagsKey
2911
+ ]);
2801
2912
  if (!tags.length) return null;
2802
- return /*#__PURE__*/ jsxRuntime.jsx("span", {
2913
+ const clampedVisible = Math.min(visibleCount, tags.length);
2914
+ const overflowCount = tags.length - clampedVisible;
2915
+ return /*#__PURE__*/ jsxRuntime.jsxs("span", {
2803
2916
  className: className,
2804
- children: tags.map((tag)=>/*#__PURE__*/ jsxRuntime.jsx(SwatchChip, {
2805
- value: tag,
2806
- className: chipClassName
2807
- }, tag))
2917
+ ref: containerRef,
2918
+ children: [
2919
+ tags.slice(0, clampedVisible).map((tag)=>/*#__PURE__*/ jsxRuntime.jsx(SwatchChip, {
2920
+ value: tag,
2921
+ className: chipClassName
2922
+ }, tag)),
2923
+ overflowCount > 0 && /*#__PURE__*/ jsxRuntime.jsxs("span", {
2924
+ className: overflowClassName,
2925
+ children: [
2926
+ "+",
2927
+ overflowCount
2928
+ ]
2929
+ }),
2930
+ /*#__PURE__*/ jsxRuntime.jsx("span", {
2931
+ ref: measureRef,
2932
+ "aria-hidden": "true",
2933
+ className: className,
2934
+ style: {
2935
+ position: "absolute",
2936
+ visibility: "hidden",
2937
+ pointerEvents: "none"
2938
+ },
2939
+ children: tags.map((tag)=>/*#__PURE__*/ jsxRuntime.jsx(SwatchChip, {
2940
+ value: tag,
2941
+ className: chipClassName
2942
+ }, tag))
2943
+ }),
2944
+ /*#__PURE__*/ jsxRuntime.jsxs("span", {
2945
+ ref: overflowMeasureRef,
2946
+ "aria-hidden": "true",
2947
+ className: overflowClassName,
2948
+ style: {
2949
+ position: "absolute",
2950
+ visibility: "hidden",
2951
+ pointerEvents: "none"
2952
+ },
2953
+ children: [
2954
+ "+",
2955
+ tags.length
2956
+ ]
2957
+ })
2958
+ ]
2808
2959
  });
2809
2960
  }
2810
2961
  /**
@@ -3376,7 +3527,7 @@ const TextFieldTextarea = /*#__PURE__*/ React.forwardRef(({ className, autoresiz
3376
3527
  });
3377
3528
  TextFieldTextarea.displayName = "TextFieldTextarea";
3378
3529
 
3379
- __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: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-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:22px;overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__chipList{display:flex;align-items:center;flex-wrap:nowrap;gap:var(--seed-dimension-x1_5);overflow:hidden;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:var(--seed-radius-r5);box-shadow:inset 0 0 0 1px var(--seed-color-stroke-neutral-weak),var(--seed-shadow-s3);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))}");
3530
+ __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: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:var(--seed-radius-r5);box-shadow:inset 0 0 0 1px var(--seed-color-stroke-neutral-weak),var(--seed-shadow-s3);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))}");
3380
3531
 
3381
3532
  const createClassName = (className, variants, compoundVariants = [])=>{
3382
3533
  const variantKeys = Object.keys(variants);
@@ -3479,6 +3630,14 @@ const dataTableSlotNames = [
3479
3630
  "chipList",
3480
3631
  "seed-data-table__chipList"
3481
3632
  ],
3633
+ [
3634
+ "chipOverflow",
3635
+ "seed-data-table__chipOverflow"
3636
+ ],
3637
+ [
3638
+ "chipListEditor",
3639
+ "seed-data-table__chipListEditor"
3640
+ ],
3482
3641
  [
3483
3642
  "chipRemove",
3484
3643
  "seed-data-table__chipRemove"
@@ -4082,20 +4241,31 @@ function RatingEditor({ column, value, pending, onCommit }) {
4082
4241
  });
4083
4242
  }
4084
4243
  ////////////////////////////////////////////////////////////////////////////////////
4085
- // -- editable: "chip-list" (Feature B, #8) -- always-live, the same shape
4086
- // `SwitchEditor`/`CheckboxEditor`/`RatingEditor` above already establish
4087
- // (interacting with the widget IS the commit, no double-click gate, no separate
4088
- // rest display -- `body-cells.tsx`'s `isAlwaysLiveEditable` covers it alongside
4089
- // them). A gated open/close state (like `SelectEditor`'s) would fight adding
4090
- // MULTIPLE tags in one sitting: `useDataTableEdit`'s `commitEdit` closes editing
4091
- // the instant a SYNC commit resolves, so a gated chip editor would close itself
4092
- // after the very first tag. Always-live sidesteps that entirely -- there is no
4093
- // `editingCell` to close, the widget is simply always the cell's own content.
4094
- // Every add/remove commits the FULL next array through the same one-string
4244
+ // -- editable: "chip-list" (Feature B rework, #8) -- click-to-edit, Attio's own model
4245
+ // rather than the always-live widget this replaced (crispy-seed#1094: every row
4246
+ // rendering a live add-tag editor at once overflowed the cell and collided with
4247
+ // sticky columns). `body-cells.tsx`'s `DataTableBodyCell` opens this INSIDE the
4248
+ // same anchored `CellExpandOverlay` Feature C already built for a gated editor's
4249
+ // expanded state, on a plain single click (no truncation gate, unlike every other
4250
+ // read-only cell's peek) -- clicking a short 2-tag cell must open the editor just
4251
+ // as readily as a 10-tag one. It stays open across MULTIPLE add/remove commits
4252
+ // (closes only on blur/outside-click/Escape, `DataTableBodyCell`'s own effect) by
4253
+ // NOT going through `useDataTableEdit`'s `isEditing` gate the way `SelectEditor`/
4254
+ // `TextEditor` do -- `commitEdit` closes `editingCell` the instant a SYNC commit
4255
+ // resolves (its own doc comment), which would have closed a gated chip editor
4256
+ // after the very first tag. `DataTableBodyCell` instead tracks THIS editor's own
4257
+ // open/closed state in its local `overlayOpen`, independent of `editingCell`.
4258
+ // Every add/remove still commits the FULL next array through the same one-string
4095
4259
  // contract every other editor already uses (`useDataTableEdit`'s own doc
4096
- // comment): `JSON.stringify(nextTags)`, which the consumer's `onEdit` parses
4097
- // back -- crisp/GRC's contract stays "one string in, no typed union", not a
4098
- // second commit shape invented for just this column.
4260
+ // comment): `JSON.stringify(nextTags)`, which the consumer's `onEdit` parses back
4261
+ // -- crisp/GRC's contract stays "one string in, no typed union", not a second
4262
+ // commit shape invented for just this column.
4263
+ //
4264
+ // Renders through `classNames.chipListEditor`, NOT the read-only `chipList` slot
4265
+ // (cells.tsx's `ChipListCell`) -- the display row clips to one line with a "+N"
4266
+ // badge; this editor instead WRAPS onto as many lines as it needs inside the
4267
+ // overlay's own grow-in-height box, since every tag (plus the add-input) has to
4268
+ // stay visible and editable at once.
4099
4269
  function ChipListEditor({ column, value, pending, onCommit, classNames }) {
4100
4270
  const tags = Array.isArray(value) ? value.filter((tag)=>!!tag) : [];
4101
4271
  const [draft, setDraft] = React__namespace.useState("");
@@ -4116,7 +4286,7 @@ function ChipListEditor({ column, value, pending, onCommit, classNames }) {
4116
4286
  }
4117
4287
  return(// biome-ignore lint/a11y/noStaticElementInteractions: not a widget -- this span never responds to interaction on ITSELF, only stops propagation of events that already occurred on a real interactive descendant (the remove buttons, the add-tag input). No role fits it because it isn't a control.
4118
4288
  /*#__PURE__*/ jsxRuntime.jsxs("span", {
4119
- className: classNames.chipList,
4289
+ className: classNames.chipListEditor,
4120
4290
  onClick: (event)=>event.stopPropagation(),
4121
4291
  children: [
4122
4292
  tags.map((tag)=>/*#__PURE__*/ jsxRuntime.jsx(SwatchChip, {
@@ -4134,6 +4304,8 @@ function ChipListEditor({ column, value, pending, onCommit, classNames }) {
4134
4304
  })
4135
4305
  }, tag)),
4136
4306
  /*#__PURE__*/ jsxRuntime.jsx("input", {
4307
+ // biome-ignore lint/a11y/noAutofocus: this editor only ever mounts in response to an explicit click that just opened it (never on page load), matching `SelectEditor`'s own `open` (controlled, always-open-on-mount) -- the same UX every gated editor in this file already gives via a component the linter can't see through (`TextFieldInput`/`SelectSearchField`), just on a bare `<input>` here since `chipInput` has no such wrapper.
4308
+ autoFocus: true,
4137
4309
  className: classNames.chipInput,
4138
4310
  value: draft,
4139
4311
  disabled: pending,
@@ -4141,14 +4313,19 @@ function ChipListEditor({ column, value, pending, onCommit, classNames }) {
4141
4313
  "aria-label": `Add tag to ${column.key}`,
4142
4314
  onChange: (event)=>setDraft(event.target.value),
4143
4315
  onKeyDown: (event)=>{
4316
+ // Escape is the ONE key this input must NOT stop propagation for: there is
4317
+ // no in-progress draft state worth clearing locally (every add/remove
4318
+ // already commits immediately), so it needs to bubble all the way to
4319
+ // `document` -- `DataTableBodyCell`'s own Escape listener is what actually
4320
+ // closes this popover. Every other key stops here, same as before, so it
4321
+ // never reaches the row's own click/keyboard handling.
4322
+ if (event.key === "Escape") return;
4144
4323
  event.stopPropagation();
4145
4324
  if (event.key === "Enter") {
4146
4325
  event.preventDefault();
4147
4326
  addTag();
4148
4327
  } else if (event.key === "Backspace" && draft === "" && tags.length > 0) {
4149
4328
  removeTag(tags[tags.length - 1]);
4150
- } else if (event.key === "Escape") {
4151
- setDraft("");
4152
4329
  }
4153
4330
  }
4154
4331
  }),
@@ -4253,12 +4430,41 @@ function isCellTruncated(cell) {
4253
4430
  /** `editable` + one of these shapes needs no double-click gate and no separate rest
4254
4431
  * display -- interacting with the widget itself IS the commit gesture (crisp's own
4255
4432
  * #453/#760/#761: `switch`/`rating`/`checkbox` never had a meaningfully different
4256
- * "rest" state to show). `chip-list` (Feature B, #8) joins them for a different
4257
- * reason -- see `ChipListEditor`'s own doc comment (editors.tsx): a GATED chip
4258
- * editor would close itself the instant the first tag committed. Every other
4259
- * `editable` column opens on double-click. */ function isAlwaysLiveEditable(column) {
4433
+ * "rest" state to show). `chip-list` used to join them here (Feature B, #8) but no
4434
+ * longer does -- see `isChipListEditable` below, its own separate "click opens a
4435
+ * popover, closed the rest of the time" category, and `ChipListEditor`'s own doc
4436
+ * comment (editors.tsx) for why it isn't just the ordinary GATED (double-click)
4437
+ * shape either. Every other `editable` column opens on double-click. */ function isAlwaysLiveEditable(column) {
4260
4438
  if (!column.editable) return false;
4261
- return column.editor === "switch" || column.type === "rating" || column.type === "checkbox" || column.type === "chip-list";
4439
+ return column.editor === "switch" || column.type === "rating" || column.type === "checkbox";
4440
+ }
4441
+ /** Feature B rework (#8) -- an editable `chip-list` column's own third category,
4442
+ * alongside `isAlwaysLiveEditable` (above) and the ordinary double-click GATED
4443
+ * shape: a single click (no truncation gate -- even a short, unclipped 2-tag cell
4444
+ * must open just as readily as a long one) opens `editors.tsx`'s `ChipListEditor`,
4445
+ * and it closes back to the plain `ChipListCell` display on blur/outside-click/
4446
+ * Escape (`DataTableBodyCell`'s own `overlayOpen`) -- never always-live
4447
+ * (crispy-seed#1094: that overflowed the cell and collided with sticky columns),
4448
+ * never simply GATED either (a `useDataTableEdit`-driven `isEditing` gate would
4449
+ * close itself after the first tag commits, see `ChipListEditor`'s own doc
4450
+ * comment).
4451
+ *
4452
+ * Unlike every OTHER open editor in this file, this one does NOT render inside
4453
+ * Feature C's `CellExpandOverlay` (an anchored box positioned relative to the
4454
+ * `<td>`, `position: absolute` against it) -- it renders through a REAL React
4455
+ * portal to `document.body` instead (`DataTableBodyCell`'s own `popoverStyle`).
4456
+ * Confirmed live, crispy-seed#1094's pinned-column follow-up: a `position: sticky`
4457
+ * `<td>` clips ANY descendant content that grows past its own box to the table's
4458
+ * scrollport, in EVERY browser tested, regardless of `overflow: visible` on every
4459
+ * element in between and regardless of how high its `z-index` is raised -- neither
4460
+ * of which is a stacking-context or overflow bug this file can fix from the
4461
+ * INSIDE. A portal sidesteps it entirely by not being a descendant of the sticky
4462
+ * `<td>` at all once mounted. The same sticky-clip almost certainly affects the
4463
+ * OTHER gated editors' `CellExpandOverlay` too when their own column is pinned,
4464
+ * but porting all of Feature C to this same portal model is its own change,
4465
+ * out of scope here -- this fix is scoped to the one editor crispy-seed#1094
4466
+ * actually reported against a pinned column. */ function isChipListEditable(column) {
4467
+ return !!column.editable && column.type === "chip-list";
4262
4468
  }
4263
4469
  function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyFirst, stickyLast }) {
4264
4470
  const effective = useEffectiveColumns(columns, {
@@ -4288,8 +4494,13 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4288
4494
  * `ColumnBodyCells` keys it by `column.key` the same way it always keyed the bare
4289
4495
  * `<DataTableCell>` before this split.
4290
4496
  */ function DataTableBodyCell({ column, row, classNames, locale, sticky }) {
4291
- const { startEdit, isEditing, isPeeking, openPeek, closePeek } = useDataTableContext();
4497
+ const { startEdit, cancelEdit, isEditing, isPending, commitEdit, isPeeking, openPeek, closePeek } = useDataTableContext();
4292
4498
  const cellRef = React__namespace.useRef(null);
4499
+ // Feature B rework (#8) -- the chip-list popover's own live position, in VIEWPORT
4500
+ // coordinates (`position: fixed`), since it portals to `document.body` rather than
4501
+ // anchoring inside the `<td>` (see `isChipListEditable`'s own doc comment for why).
4502
+ // `null` while closed -- nothing to portal yet.
4503
+ const [popoverStyle, setPopoverStyle] = React__namespace.useState(null);
4293
4504
  // Feature C (#12) -- whether THIS activation (click or double-click) should
4294
4505
  // render as the anchored overlay rather than plain inline. Frozen at the moment
4295
4506
  // of activation (re-measured every time, never left stale) rather than derived
@@ -4299,20 +4510,68 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4299
4510
  // rendering exactly as it always has, not suddenly gain overlay chrome.
4300
4511
  const [overlayOpen, setOverlayOpen] = React__namespace.useState(false);
4301
4512
  const alwaysLive = isAlwaysLiveEditable(column);
4302
- // A gated `editable` column (everything except the always-live widgets) opens on
4303
- // double-click -- Attio's own measured gesture (crisp's #411), matched here
4304
- // rather than a single click, which stays "select this cell" in crisp's own
4305
- // spreadsheet model and would otherwise be indistinguishable from this table's
4306
- // plain row click. Feature C ADDS a single click as a SECOND way in, but only
4307
- // when the cell's content is actually truncated (see `handleClick` below) --
4308
- // double-click keeps working unconditionally, same as before this feature.
4309
- const gated = column.editable && !alwaysLive;
4513
+ const chipListEditable = isChipListEditable(column);
4514
+ // A gated `editable` column (everything except the always-live widgets AND the
4515
+ // click-to-edit chip-list) opens on double-click -- Attio's own measured gesture
4516
+ // (crisp's #411), matched here rather than a single click, which stays "select
4517
+ // this cell" in crisp's own spreadsheet model and would otherwise be
4518
+ // indistinguishable from this table's plain row click. Feature C ADDS a single
4519
+ // click as a SECOND way in, but only when the cell's content is actually
4520
+ // truncated (see `handleClick` below) -- double-click keeps working
4521
+ // unconditionally, same as before this feature.
4522
+ const gated = column.editable && !alwaysLive && !chipListEditable;
4523
+ // This cell's chip-list popover is showing right now -- `overlayOpen` alone isn't
4524
+ // enough (it's shared with the read-only peek/gated-edit cases below), so this
4525
+ // ANDs in "is this actually a chip-list column" too. Gates the portal render
4526
+ // below (`popoverStyle` is only ever non-null while this is true).
4527
+ const chipListEditorOpen = chipListEditable && overlayOpen;
4528
+ // Feature B rework (#8) -- the chip-list popover's own position, read off the
4529
+ // `<td>`'s LIVE `getBoundingClientRect()` (viewport coordinates, matching
4530
+ // `position: fixed`) rather than anything Feature C's `pinOffset`/sticky math
4531
+ // already tracks -- those describe offsets WITHIN the scrolling table, not where
4532
+ // the cell actually sits in the viewport right now, which is what a portaled,
4533
+ // `position: fixed` box anchored below the cell needs.
4534
+ const measurePopoverPosition = React__namespace.useCallback(()=>{
4535
+ const cell = cellRef.current;
4536
+ if (!cell) return;
4537
+ const rect = cell.getBoundingClientRect();
4538
+ setPopoverStyle({
4539
+ position: "fixed",
4540
+ top: rect.bottom,
4541
+ insetInlineStart: rect.left,
4542
+ insetInlineEnd: "auto",
4543
+ minWidth: rect.width,
4544
+ // Both override the SAME `expandOverlay` class properties this box also
4545
+ // carries (`className={classNames.expandOverlay}`, below) -- that class's
4546
+ // `minHeight: calc(100% + 2px)` resolves against an ANCHORED box's own
4547
+ // containing block (the `<td>`, ~36px), which is meaningless (and would
4548
+ // resolve against the VIEWPORT instead, ~100% of the page's height) for a
4549
+ // portaled `position: fixed` box; `auto` lets the content size it instead.
4550
+ // `zIndex: 10` is Feature C's own value for an in-place overlay competing
4551
+ // only with OTHER table content -- a portal to `document.body` can land
4552
+ // beside arbitrary page chrome (this Storybook's own toolbar, a consuming
4553
+ // app's header), so it gets a value comfortably above typical page chrome.
4554
+ minHeight: "auto",
4555
+ zIndex: 1000
4556
+ });
4557
+ }, []);
4310
4558
  function handleDoubleClick(event) {
4311
4559
  if (!gated) return;
4312
4560
  setOverlayOpen(isCellTruncated(event.currentTarget));
4313
4561
  startEdit(row.id, column.key);
4314
4562
  }
4315
4563
  function handleClick(event) {
4564
+ if (chipListEditable) {
4565
+ // Attio's own chip-list gesture: click opens the editor regardless of
4566
+ // truncation -- a short, fully-visible 2-tag cell opens just as readily as
4567
+ // one already showing "+N". Must not also reach the row's own `onRowClick`,
4568
+ // same reasoning as the read-only peek/preview-button cases below.
4569
+ event.stopPropagation();
4570
+ measurePopoverPosition();
4571
+ setOverlayOpen(true);
4572
+ startEdit(row.id, column.key);
4573
+ return;
4574
+ }
4316
4575
  if (!isCellTruncated(event.currentTarget)) return;
4317
4576
  if (gated) {
4318
4577
  setOverlayOpen(true);
@@ -4320,7 +4579,7 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4320
4579
  } else if (alwaysLive) {
4321
4580
  // No separate open/edit state to start -- the always-live widget already
4322
4581
  // renders unconditionally; this only switches ITS OWN box to the anchored
4323
- // overlay so a long chip list (etc.) gets room to show every chip.
4582
+ // overlay so a long value (etc.) gets room to show in full.
4324
4583
  setOverlayOpen(true);
4325
4584
  } else {
4326
4585
  // Read-only truncated cell: this click's only job is opening the peek
@@ -4333,25 +4592,39 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4333
4592
  openPeek(row.id, column.key);
4334
4593
  }
4335
4594
  }
4595
+ // Feature B rework (#8) -- the portaled popover's own DOM node. Read by the
4596
+ // outside-click detector below (it lives outside `cellRef`'s subtree once
4597
+ // mounted, in `document.body` -- see `isChipListEditable`'s doc comment) and by
4598
+ // the reposition effect further down.
4599
+ const popoverRef = React__namespace.useRef(null);
4336
4600
  // Closes the overlay on Escape or an outside click/pointerdown -- a gated
4337
4601
  // editor's OWN Escape/blur handling already ends `isEditing` on its own (see
4338
4602
  // `TextEditor`/`SelectEditor`'s existing onCancel/onOpenChange wiring), which
4339
4603
  // alone is enough to hide a GATED cell's overlay (its render below is gated on
4340
- // `isEditing` too); this listener is what the PEEK and ALWAYS-LIVE cases need,
4341
- // since neither has any other mechanism that ever calls `closePeek`/resets
4342
- // `overlayOpen` on its own.
4604
+ // `isEditing` too); this listener is what the PEEK, ALWAYS-LIVE and chip-list-edit
4605
+ // cases need, since none of them has any other mechanism that ever calls
4606
+ // `closePeek`/`cancelEdit`/resets `overlayOpen` on its own. `cancelEdit()` is a
4607
+ // no-op for the peek/always-live cases (there is no `editingCell` to clear) and is
4608
+ // what actually ends the chip-list editor's own `startEdit` call above.
4343
4609
  React__namespace.useEffect(()=>{
4344
4610
  if (!overlayOpen) return;
4611
+ function isInside(target) {
4612
+ return (cellRef.current?.contains(target) ?? false) || (popoverRef.current?.contains(target) ?? false);
4613
+ }
4345
4614
  function handlePointerDown(event) {
4346
- if (cellRef.current && !cellRef.current.contains(event.target)) {
4615
+ if (!isInside(event.target)) {
4347
4616
  setOverlayOpen(false);
4617
+ setPopoverStyle(null);
4348
4618
  closePeek();
4619
+ cancelEdit();
4349
4620
  }
4350
4621
  }
4351
4622
  function handleKeyDown(event) {
4352
4623
  if (event.key !== "Escape") return;
4353
4624
  setOverlayOpen(false);
4625
+ setPopoverStyle(null);
4354
4626
  closePeek();
4627
+ cancelEdit();
4355
4628
  }
4356
4629
  document.addEventListener("pointerdown", handlePointerDown);
4357
4630
  document.addEventListener("keydown", handleKeyDown);
@@ -4361,27 +4634,70 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4361
4634
  };
4362
4635
  }, [
4363
4636
  overlayOpen,
4364
- closePeek
4637
+ closePeek,
4638
+ cancelEdit
4639
+ ]);
4640
+ // Feature B rework (#8) -- keeps the portaled popover glued to its cell while
4641
+ // open: any scroll on ANY ancestor (the table's own horizontal/vertical
4642
+ // `scrollArea`, or the page itself) or a viewport resize re-measures. `scroll`
4643
+ // doesn't bubble, so this listens in the CAPTURE phase on `window` -- the
4644
+ // standard way to observe a scroll on an arbitrary descendant scroll container
4645
+ // without knowing which element it is ahead of time.
4646
+ React__namespace.useEffect(()=>{
4647
+ if (!chipListEditorOpen) return;
4648
+ function reposition() {
4649
+ measurePopoverPosition();
4650
+ }
4651
+ window.addEventListener("scroll", reposition, true);
4652
+ window.addEventListener("resize", reposition);
4653
+ return ()=>{
4654
+ window.removeEventListener("scroll", reposition, true);
4655
+ window.removeEventListener("resize", reposition);
4656
+ };
4657
+ }, [
4658
+ chipListEditorOpen,
4659
+ measurePopoverPosition
4365
4660
  ]);
4366
4661
  const overlayActive = overlayOpen && (alwaysLive || isEditing(row.id, column.key) || isPeeking(row.id, column.key));
4367
- return /*#__PURE__*/ jsxRuntime.jsx(DataTableCell, {
4368
- ref: cellRef,
4369
- "data-numeric": domUtils.dataAttr(column.type === "number" || column.type === "currency"),
4370
- "data-editable": domUtils.dataAttr(!!column.editable),
4371
- "data-sticky": sticky["data-sticky"],
4372
- "data-sticky-edge": sticky["data-sticky-edge"],
4373
- "data-sticky-edge-end": sticky["data-sticky-edge-end"],
4374
- style: sticky.style,
4375
- onClick: handleClick,
4376
- onDoubleClick: handleDoubleClick,
4377
- children: /*#__PURE__*/ jsxRuntime.jsx(CellContent, {
4378
- column: column,
4379
- row: row,
4380
- rowId: row.id,
4381
- classNames: classNames,
4382
- locale: locale,
4383
- overlayActive: overlayActive
4384
- })
4662
+ const rawValue = getColumnValue(column, row);
4663
+ const pending = isPending(row.id, column.key);
4664
+ return /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
4665
+ children: [
4666
+ /*#__PURE__*/ jsxRuntime.jsx(DataTableCell, {
4667
+ ref: cellRef,
4668
+ "data-numeric": domUtils.dataAttr(column.type === "number" || column.type === "currency"),
4669
+ "data-editable": domUtils.dataAttr(!!column.editable),
4670
+ "data-sticky": sticky["data-sticky"],
4671
+ "data-sticky-edge": sticky["data-sticky-edge"],
4672
+ "data-sticky-edge-end": sticky["data-sticky-edge-end"],
4673
+ "data-overlay-open": domUtils.dataAttr(overlayActive),
4674
+ style: sticky.style,
4675
+ onClick: handleClick,
4676
+ onDoubleClick: handleDoubleClick,
4677
+ children: /*#__PURE__*/ jsxRuntime.jsx(CellContent, {
4678
+ column: column,
4679
+ row: row,
4680
+ rowId: row.id,
4681
+ classNames: classNames,
4682
+ locale: locale,
4683
+ overlayActive: overlayActive
4684
+ })
4685
+ }),
4686
+ chipListEditorOpen && popoverStyle && /*#__PURE__*/ reactDom.createPortal(// biome-ignore lint/a11y/noStaticElementInteractions: not a widget -- same shape as `CellExpandOverlay` (editors.tsx), which this box is a portaled variant of: it never responds to interaction on ITSELF, only stops propagation of clicks that already occurred on a real interactive descendant (the editor's input/remove buttons). No role fits it because it isn't a control.
4687
+ /*#__PURE__*/ jsxRuntime.jsx("div", {
4688
+ ref: popoverRef,
4689
+ className: classNames.expandOverlay,
4690
+ style: popoverStyle,
4691
+ onClick: (event)=>event.stopPropagation(),
4692
+ children: /*#__PURE__*/ jsxRuntime.jsx(ChipListEditor, {
4693
+ column: column,
4694
+ value: resolveEditValue(column, row, rawValue),
4695
+ pending: pending,
4696
+ onCommit: (value)=>commitEdit(row.id, column.key, value),
4697
+ classNames: classNames
4698
+ })
4699
+ }), document.body)
4700
+ ]
4385
4701
  });
4386
4702
  }
4387
4703
  /**
@@ -4421,12 +4737,18 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
4421
4737
  onCommit: commit
4422
4738
  });
4423
4739
  } else if (column.editable && column.type === "chip-list") {
4424
- node = /*#__PURE__*/ jsxRuntime.jsx(ChipListEditor, {
4425
- column: column,
4426
- value: resolveEditValue(column, row, rawValue),
4427
- pending: pending,
4428
- onCommit: commit,
4429
- classNames: classNames
4740
+ // Feature B rework (#8) -- this column ALWAYS dispatches here to the plain
4741
+ // display (never to `DisplayCellContent`'s own read-only `chip-list` case,
4742
+ // never through the generic `isEditing` branch below): the click-to-edit
4743
+ // popover is a separate portal `DataTableBodyCell` renders alongside this
4744
+ // whole `<td>`, not a `node` this function returns -- see `isChipListEditable`'s
4745
+ // own doc comment (body-cells.tsx) for why it isn't anchored in place like
4746
+ // every other editor here.
4747
+ node = /*#__PURE__*/ jsxRuntime.jsx(ChipListCell, {
4748
+ value: rawValue,
4749
+ className: classNames.chipList,
4750
+ chipClassName: classNames.chip,
4751
+ overflowClassName: classNames.chipOverflow
4430
4752
  });
4431
4753
  } else if (column.editable && isEditing(rowId, column.key)) {
4432
4754
  if (column.editor === "select") {
@@ -4535,10 +4857,13 @@ function DisplayCellContent({ column, row, value, classNames, locale }) {
4535
4857
  className: classNames.chip
4536
4858
  });
4537
4859
  case "chip-list":
4860
+ // Non-editable chip-list only -- an editable one is fully handled by
4861
+ // `CellContent`'s own dedicated branch above and never reaches here.
4538
4862
  return /*#__PURE__*/ jsxRuntime.jsx(ChipListCell, {
4539
4863
  value: value,
4540
4864
  className: classNames.chipList,
4541
- chipClassName: classNames.chip
4865
+ chipClassName: classNames.chip,
4866
+ overflowClassName: classNames.chipOverflow
4542
4867
  });
4543
4868
  case "number":
4544
4869
  return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
@@ -4925,6 +5250,7 @@ const DEFAULT_SORT_ICON = {
4925
5250
  }),
4926
5251
  columnMenu && /*#__PURE__*/ jsxRuntime.jsx(ColumnMenu, {
4927
5252
  column: column,
5253
+ label: label,
4928
5254
  onRename: ()=>setRenamingKey(column.key)
4929
5255
  })
4930
5256
  ]
@@ -5016,9 +5342,9 @@ function ColumnRenameInput({ column, initialLabel, onDone }) {
5016
5342
  * `data-table.ts`) -- crisp's own header shows the trigger only on hover, not
5017
5343
  * permanently, so this one carries its own marker attribute distinct from that
5018
5344
  * slot's OTHER occupants (the sort chevron, the filter trigger), which stay visible
5019
- * under their own separate rules regardless of hover. */ function ColumnMenu({ column, onRename }) {
5345
+ * under their own separate rules regardless of hover. */ function ColumnMenu({ column, label, onRename }) {
5020
5346
  const { moveColumn, hideColumn, isColumnPinned, togglePinColumn, sort, clearSort } = useDataTableContext();
5021
- const columnLabel = typeof column.header === "string" ? column.header : column.key;
5347
+ const columnLabel = typeof label === "string" ? label : column.key;
5022
5348
  const pinned = isColumnPinned(column.key);
5023
5349
  // Attio/GRC's "Remove sort" (live-review fix D) -- the header's own click-to-sort
5024
5350
  // (`SortIndicator`/`toggleSort`, above) only cycles asc/desc once a column has been