@crispy-seed/data-table 0.13.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1649,6 +1649,11 @@ const ROOT_MARGIN = "160px";
1649
1649
  const elementRef = useRef(null);
1650
1650
  const [scrolledStart, setScrolledStart] = useState(false);
1651
1651
  const [scrolledEnd, setScrolledEnd] = useState(false);
1652
+ // The scroll container's own content-box width (0 until first measured). The
1653
+ // styled layer reads it to cap how much of a narrow viewport the sticky-left
1654
+ // pinned group may occupy (`useEffectiveColumns`) -- without a cap, a pinned
1655
+ // group wider than the container covers every scrolling column for good.
1656
+ const [viewportWidth, setViewportWidth] = useState(0);
1652
1657
  const [viewport, setViewport] = useState({
1653
1658
  scrollTop: 0,
1654
1659
  viewportHeight: 0
@@ -1669,6 +1674,7 @@ const ROOT_MARGIN = "160px";
1669
1674
  viewportHeight
1670
1675
  });
1671
1676
  }
1677
+ setViewportWidth(element.clientWidth);
1672
1678
  setScrolledStart(element.scrollLeft > 0);
1673
1679
  // A 1px slop: sub-pixel layout can leave `scrollLeft + clientWidth` a fraction
1674
1680
  // short of `scrollWidth` even when fully scrolled, which would otherwise leave
@@ -1678,16 +1684,16 @@ const ROOT_MARGIN = "160px";
1678
1684
  virtualized,
1679
1685
  rowHeight
1680
1686
  ]);
1681
- // The container can resize without scrolling (window resize, a bounded parent
1682
- // changing height) -- the viewport height feeds the window, so re-measure then too.
1687
+ // The container can resize without scrolling (window resize, rotating a phone, a
1688
+ // bounded parent changing height) -- the viewport height feeds the virtual window
1689
+ // and the width feeds the pinned-group cap, so re-measure then too.
1683
1690
  useEffect(()=>{
1684
1691
  const element = elementRef.current;
1685
- if (!virtualized || !element || typeof ResizeObserver === "undefined") return;
1692
+ if (!element || typeof ResizeObserver === "undefined") return;
1686
1693
  const observer = new ResizeObserver(()=>remeasure());
1687
1694
  observer.observe(element);
1688
1695
  return ()=>observer.disconnect();
1689
1696
  }, [
1690
- virtualized,
1691
1697
  remeasure
1692
1698
  ]);
1693
1699
  const scrollAreaRef = useCallback((node)=>{
@@ -1703,6 +1709,7 @@ const ROOT_MARGIN = "160px";
1703
1709
  scrollAreaRef,
1704
1710
  scrolledStart,
1705
1711
  scrolledEnd,
1712
+ viewportWidth,
1706
1713
  onScroll,
1707
1714
  remeasure,
1708
1715
  /** Row-virtualization state; `enabled: false` (the default) means render every row. */ virtual: {
@@ -3232,19 +3239,20 @@ function StatusCell({ value }) {
3232
3239
  * same "literal color as a computed prop" call `RatingCell`'s star fill already
3233
3240
  * makes. Shared by `ChipCell` (Feature A, single value) and `ChipListCell`/
3234
3241
  * editors.tsx's `ChipListEditor` (Feature B, multi value) so all three chip
3235
- * renderings stay byte-identical. */ function SwatchChip({ value, className, children }) {
3242
+ * renderings stay byte-identical. */ function SwatchChip({ value, label, color, className, children }) {
3236
3243
  return /*#__PURE__*/ jsxs("span", {
3237
3244
  className: className,
3238
3245
  style: {
3239
- backgroundColor: swatchColorFor(value),
3240
- color: swatchInkFor(value)
3246
+ backgroundColor: color ?? swatchColorFor(value),
3247
+ color: color ? CHIP_CUSTOM_COLOR_INK : swatchInkFor(value)
3241
3248
  },
3242
3249
  children: [
3243
- value,
3250
+ label ?? value,
3244
3251
  children
3245
3252
  ]
3246
3253
  });
3247
3254
  }
3255
+ const CHIP_CUSTOM_COLOR_INK = "#1f2937";
3248
3256
  /** `type: "chip"` (Feature A, #9) -- a single value rendered as one colored chip,
3249
3257
  * the color derived deterministically from the value string (`swatchColorFor`):
3250
3258
  * same value, same color, every render, every column. For a column like provider/
@@ -3257,6 +3265,14 @@ function StatusCell({ value }) {
3257
3265
  className: className
3258
3266
  });
3259
3267
  }
3268
+ /** The `label`/`color` a `chipOptions` entry gives `tag`, spread onto its chip so a
3269
+ * tag looks identical in the closed cell and in the editor's option list. */ function optionProps(chipOptions, tag) {
3270
+ const option = chipOptions?.find((entry)=>entry.value === tag);
3271
+ return option ? {
3272
+ label: option.label,
3273
+ color: option.color
3274
+ } : {};
3275
+ }
3260
3276
  // Feature B rework (#8) -- the chip-list DISPLAY row's own gap, mirrored from the
3261
3277
  // qvism recipe's `chipList` slot (`packages/qvism-preset/src/recipes/data-table.ts`'s
3262
3278
  // `CHIP_GAP`, measured live off Attio at 3px between chips). Kept as a literal here
@@ -3289,7 +3305,7 @@ const CHIP_LIST_GAP_PX = 3;
3289
3305
  * times, not just while a `ResizeObserver` fires, because widening the column back
3290
3306
  * out needs to know the width of chips currently hidden behind "+N" to decide
3291
3307
  * whether they now fit again.
3292
- */ function ChipListCell({ value, className, chipClassName, overflowClassName }) {
3308
+ */ function ChipListCell({ value, className, chipClassName, overflowClassName, chipOptions }) {
3293
3309
  const tags = Array.isArray(value) ? value.filter((tag)=>!!tag) : [];
3294
3310
  const containerRef = React.useRef(null);
3295
3311
  const measureRef = React.useRef(null);
@@ -3338,6 +3354,7 @@ const CHIP_LIST_GAP_PX = 3;
3338
3354
  children: [
3339
3355
  tags.slice(0, clampedVisible).map((tag)=>/*#__PURE__*/ jsx(SwatchChip, {
3340
3356
  value: tag,
3357
+ ...optionProps(chipOptions, tag),
3341
3358
  className: chipClassName
3342
3359
  }, tag)),
3343
3360
  overflowCount > 0 && /*#__PURE__*/ jsxs("span", {
@@ -3358,6 +3375,7 @@ const CHIP_LIST_GAP_PX = 3;
3358
3375
  },
3359
3376
  children: tags.map((tag)=>/*#__PURE__*/ jsx(SwatchChip, {
3360
3377
  value: tag,
3378
+ ...optionProps(chipOptions, tag),
3361
3379
  className: chipClassName
3362
3380
  }, tag))
3363
3381
  }),
@@ -3628,6 +3646,13 @@ function RatingCell({ value, maxRating = 5 }) {
3628
3646
  });
3629
3647
  }
3630
3648
 
3649
+ /** Share of the scroll container's width the sticky-left pinned group may occupy
3650
+ * before FURTHER pinned columns stop sticking and scroll like any other column. A
3651
+ * pinned group at or past the container's width pins every pixel of it: the
3652
+ * scrolling columns slide in underneath the sticky cells and can never be seen (a
3653
+ * 375px phone with Serial + one more pinned column showed nothing else, ever). The
3654
+ * FIRST pinned column always sticks regardless -- the entity/name column staying
3655
+ * frozen is the whole point of pinning. */ const MAX_PINNED_VIEWPORT_SHARE = 0.6;
3631
3656
  /** Fallback width (px) used to compute a pinned column's cumulative sticky offset when
3632
3657
  * it has no explicit `width` and hasn't been drag-resized -- pinning needs a STATIC
3633
3658
  * number to put in an inline `insetInlineStart`, and `table-layout: fixed` only
@@ -3647,7 +3672,7 @@ function RatingCell({ value, maxRating = 5 }) {
3647
3672
  * one and a header row built from the other drift out of sync under `table-layout:
3648
3673
  * fixed`'s own column-to-cell matching.
3649
3674
  */ function useEffectiveColumns(columns, options) {
3650
- const { visibleColumnOrder, getColumnLabel, getColumnWidth, pinnedColumnKeys } = useDataTableContext();
3675
+ const { visibleColumnOrder, getColumnLabel, getColumnWidth, pinnedColumnKeys, viewportWidth } = useDataTableContext();
3651
3676
  const { selectable, stickyFirst } = options;
3652
3677
  return React.useMemo(()=>{
3653
3678
  const byKey = new Map(columns.map((column)=>[
@@ -3673,11 +3698,14 @@ function RatingCell({ value, maxRating = 5 }) {
3673
3698
  const bPinned = pinnedSet.has(b) ? 0 : 1;
3674
3699
  return aPinned - bPinned;
3675
3700
  });
3701
+ // 0 = not measured yet (first paint / jsdom) -- no cap rather than a guess.
3702
+ const pinBudget = viewportWidth ? viewportWidth * MAX_PINNED_VIEWPORT_SHARE : Number.POSITIVE_INFINITY;
3676
3703
  let cursor = selectable ? 40 : 0;
3677
3704
  let pinnedCount = 0;
3705
+ let budgetSpent = false;
3678
3706
  const entries = ordered.map((key)=>byKey.get(key)).filter((column)=>Boolean(column)).map((column)=>{
3679
3707
  const rawWidth = getColumnWidth(column.key) ?? column.width;
3680
- const pinned = pinnedSet.has(column.key);
3708
+ const inPinnedSet = pinnedSet.has(column.key);
3681
3709
  // Fix (0.10.4, screenshot-confirmed pinned-column overlay): a pinned column
3682
3710
  // with no explicit/resized width used to keep `width` as `undefined` here,
3683
3711
  // which `DataTableColGroup` (DataTable.tsx) renders as an AUTO `<col>` --
@@ -3697,7 +3725,15 @@ function RatingCell({ value, maxRating = 5 }) {
3697
3725
  // without its own width now always renders at exactly
3698
3726
  // `DEFAULT_PINNED_COLUMN_WIDTH`, matching what every other pinned column's
3699
3727
  // offset already assumed it would be.
3700
- const width = pinned && rawWidth === undefined ? DEFAULT_PINNED_COLUMN_WIDTH : rawWidth;
3728
+ const width = inPinnedSet && rawWidth === undefined ? DEFAULT_PINNED_COLUMN_WIDTH : rawWidth;
3729
+ // Sticks only while the group still fits the pin budget (see
3730
+ // MAX_PINNED_VIEWPORT_SHARE); the first pinned column always sticks.
3731
+ // Once one pinned column overflows, every later one scrolls too -- a narrower
3732
+ // late column must not stick past a gap.
3733
+ if (inPinnedSet && pinnedCount > 0 && cursor + (width ?? DEFAULT_PINNED_COLUMN_WIDTH) > pinBudget) {
3734
+ budgetSpent = true;
3735
+ }
3736
+ const pinned = inPinnedSet && !budgetSpent;
3701
3737
  const pinOffset = pinned ? cursor : 0;
3702
3738
  if (pinned) {
3703
3739
  cursor += width ?? DEFAULT_PINNED_COLUMN_WIDTH;
@@ -3736,7 +3772,8 @@ function RatingCell({ value, maxRating = 5 }) {
3736
3772
  getColumnWidth,
3737
3773
  pinnedColumnKeys,
3738
3774
  selectable,
3739
- stickyFirst
3775
+ stickyFirst,
3776
+ viewportWidth
3740
3777
  ]);
3741
3778
  }
3742
3779
  /** The 40px selection column's own sticky-left state -- `stickyFirst` (the table-wide
@@ -4120,7 +4157,45 @@ const TextFieldTextarea = /*#__PURE__*/ forwardRef(({ className, autoresize = tr
4120
4157
  });
4121
4158
  TextFieldTextarea.displayName = "TextFieldTextarea";
4122
4159
 
4123
- __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__entityName[data-clickable]{cursor:pointer;border:none;background:0 0;padding:0;margin:0;font-family:inherit;font-size:inherit;line-height:inherit;color:inherit;text-align:start}.seed-data-table__entityName[data-clickable]:is(:hover,[data-hover]){text-decoration:underline}.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:13px;font-weight:var(--seed-font-weight-medium);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[data-sticky]{z-index:3}.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__bulkBar[data-above-footer]{bottom:calc(var(--seed-dimension-x4) + var(--seed-dimension-x9))}.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))}");
4160
+ // Row-narrowing for the toolbar's search box -- split out of DataTable.tsx the way
4161
+ // cells.tsx splits out cell-type rendering: a self-contained, independently testable
4162
+ // concern DataTable.tsx just calls, rather than inline `.filter()` calls piling up in
4163
+ // the same component that also owns markup. The toolbar's typed Filter CONDITIONS
4164
+ // (field/operator/value chips, DATATABLE-ATTIO-SPEC.md's Attio/crisp model) live in
4165
+ // `toolbar-conditions.tsx` instead, alongside the Sort/Filter chrome that manages
4166
+ // them -- this file is search only now; the old per-column "is/is not" picker
4167
+ // (`matchesFilter`/`DataTableFilterState`) is gone, replaced by that toolbar model.
4168
+ /**
4169
+ * A column's `value(row)` if it supplies one, else the raw field at `column.key` --
4170
+ * the single accessor sort/search/filter/export all read through
4171
+ * (DATATABLE-ATTIO-SPEC.md's Column API), so a consumer only teaches the table how to
4172
+ * read a value ONCE rather than once per feature.
4173
+ */ function getColumnValue(column, row) {
4174
+ return column.value ? column.value(row) : row[column.key];
4175
+ }
4176
+ /**
4177
+ * Case-insensitive substring match against a row's stringified value -- crisp's own
4178
+ * `applyFilters` "contains" semantics (`tabletoolbar.tsx`), reused here for the
4179
+ * toolbar's single free-text query. `null`/`undefined` never match a non-empty query --
4180
+ * there is no text to search.
4181
+ */ function matchesSearch(value, query) {
4182
+ if (!query) return true;
4183
+ if (value == null) return false;
4184
+ return String(value).toLowerCase().includes(query.toLowerCase());
4185
+ }
4186
+ /**
4187
+ * The toolbar's search query -- an OR across `searchable` columns, matching GRC's own
4188
+ * `useFilteredRows`. The toolbar's own typed Filter conditions apply SEPARATELY, as a
4189
+ * second pass over this function's result (`toolbar-conditions.tsx`'s
4190
+ * `applyToolbarFilters`, called by `DataTable.tsx` right after this) -- narrowing what
4191
+ * the search box already found, not the other way round.
4192
+ */ function getVisibleRows(data, columns, query) {
4193
+ const searchableColumns = columns.filter((column)=>column.searchable);
4194
+ if (!query) return data;
4195
+ return data.filter((row)=>searchableColumns.some((column)=>matchesSearch(getColumnValue(column, row), query)));
4196
+ }
4197
+
4198
+ __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__entityName[data-clickable]{cursor:pointer;border:none;background:0 0;padding:0;margin:0;font-family:inherit;font-size:inherit;line-height:inherit;color:inherit;text-align:start}.seed-data-table__entityName[data-clickable]:is(:hover,[data-hover]){text-decoration:underline}.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:13px;font-weight:var(--seed-font-weight-medium);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__chipPicker{display:flex;flex-direction:column;width:100%;min-width:0}.seed-data-table__chipPickerList{display:flex;flex-direction:column;max-height:240px;overflow-y:auto;padding:0 4px 4px}.seed-data-table__chipPickerRow{box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;min-height:32px;margin:0;padding:4px 8px;border:none;border-radius:6px;background:0 0;font:inherit;color:inherit;text-align:start;cursor:pointer}.seed-data-table__chipPickerRow:hover{background-color:#f6f7f7}.seed-data-table__chipPickerRow:disabled{cursor:default;opacity:.6}.seed-data-table__chipPickerBox{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:16px;height:16px;border-radius:4px;border:1px solid rgba(0,0,0,.25);background-color:#fff;color:#fff}.seed-data-table__chipPickerBox[data-checked]{border-color:#266df0;background-color:#266df0}.seed-data-table__chipPickerBox svg{width:12px;height:12px}.seed-data-table__chipPickerCreate{box-sizing:border-box;display:flex;align-items:center;gap:6px;width:100%;min-height:32px;margin:0;padding:4px 8px;border:none;border-radius:6px;background:0 0;font:inherit;font-size:var(--seed-font-size-t4);color:var(--seed-color-fg-neutral);text-align:start;cursor:pointer}.seed-data-table__chipPickerCreate:hover{background-color:#f6f7f7}.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[data-sticky]{z-index:3}.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__bulkBar[data-above-footer]{bottom:calc(var(--seed-dimension-x4) + var(--seed-dimension-x9))}.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))}");
4124
4199
 
4125
4200
  const createClassName = (className, variants, compoundVariants = [])=>{
4126
4201
  const variantKeys = Object.keys(variants);
@@ -4228,16 +4303,24 @@ const dataTableSlotNames = [
4228
4303
  "seed-data-table__chipOverflow"
4229
4304
  ],
4230
4305
  [
4231
- "chipListEditor",
4232
- "seed-data-table__chipListEditor"
4306
+ "chipPicker",
4307
+ "seed-data-table__chipPicker"
4308
+ ],
4309
+ [
4310
+ "chipPickerList",
4311
+ "seed-data-table__chipPickerList"
4312
+ ],
4313
+ [
4314
+ "chipPickerRow",
4315
+ "seed-data-table__chipPickerRow"
4233
4316
  ],
4234
4317
  [
4235
- "chipRemove",
4236
- "seed-data-table__chipRemove"
4318
+ "chipPickerBox",
4319
+ "seed-data-table__chipPickerBox"
4237
4320
  ],
4238
4321
  [
4239
- "chipInput",
4240
- "seed-data-table__chipInput"
4322
+ "chipPickerCreate",
4323
+ "seed-data-table__chipPickerCreate"
4241
4324
  ],
4242
4325
  [
4243
4326
  "expandOverlay",
@@ -4856,94 +4939,154 @@ function RatingEditor({ column, value, pending, onCommit }) {
4856
4939
  });
4857
4940
  }
4858
4941
  ////////////////////////////////////////////////////////////////////////////////////
4859
- // -- editable: "chip-list" (Feature B rework, #8) -- click-to-edit, Attio's own model
4860
- // rather than the always-live widget this replaced (crispy-seed#1094: every row
4861
- // rendering a live add-tag editor at once overflowed the cell and collided with
4862
- // sticky columns). `body-cells.tsx`'s `DataTableBodyCell` opens this INSIDE the
4863
- // same anchored `CellExpandOverlay` Feature C already built for a gated editor's
4864
- // expanded state, on a plain single click (no truncation gate, unlike every other
4865
- // read-only cell's peek) -- clicking a short 2-tag cell must open the editor just
4866
- // as readily as a 10-tag one. It stays open across MULTIPLE add/remove commits
4867
- // (closes only on blur/outside-click/Escape, `DataTableBodyCell`'s own effect) by
4868
- // NOT going through `useDataTableEdit`'s `isEditing` gate the way `SelectEditor`/
4869
- // `TextEditor` do -- `commitEdit` closes `editingCell` the instant a SYNC commit
4870
- // resolves (its own doc comment), which would have closed a gated chip editor
4871
- // after the very first tag. `DataTableBodyCell` instead tracks THIS editor's own
4872
- // open/closed state in its local `overlayOpen`, independent of `editingCell`.
4873
- // Every add/remove still commits the FULL next array through the same one-string
4874
- // contract every other editor already uses (`useDataTableEdit`'s own doc
4875
- // comment): `JSON.stringify(nextTags)`, which the consumer's `onEdit` parses back
4876
- // -- crisp/GRC's contract stays "one string in, no typed union", not a second
4877
- // commit shape invented for just this column.
4942
+ // -- editable: "chip-list" -- Attio's multi-select popover (0.14.0; replaces the
4943
+ // 0.10.0-0.13.0 free-text "Add..." tag adder, which stacked an empty input over the
4944
+ // chips and read as broken on an empty cell). Measured off Attio's Company
4945
+ // "Categories" cell: a search field pinned on top, below it a scrolling list of the
4946
+ // column's OPTIONS, each drawn as its own colored chip with a checkbox on the RIGHT
4947
+ // edge (filled blue when the tag is on this row); clicking a row toggles that tag
4948
+ // and the popover STAYS OPEN across toggles; typing filters the list; text matching
4949
+ // no option offers a "Create '<text>'" row (freeform tag columns, e.g. tether's
4950
+ // Labels). `body-cells.tsx`'s `DataTableBodyCell` opens this inside a portaled
4951
+ // `expandOverlay` on a plain click and closes it on blur/outside-click/Escape; it
4952
+ // tracks its own `overlayOpen` rather than `useDataTableEdit`'s `isEditing` gate,
4953
+ // since a sync commit closes `editingCell` on the first toggle.
4878
4954
  //
4879
- // Renders through `classNames.chipListEditor`, NOT the read-only `chipList` slot
4880
- // (cells.tsx's `ChipListCell`) -- the display row clips to one line with a "+N"
4881
- // badge; this editor instead WRAPS onto as many lines as it needs inside the
4882
- // overlay's own grow-in-height box, since every tag (plus the add-input) has to
4883
- // stay visible and editable at once.
4884
- function ChipListEditor({ column, value, pending, onCommit, classNames }) {
4955
+ // Commit contract is unchanged: every toggle/create commits the FULL next array as
4956
+ // `JSON.stringify(nextTags)` through the one-string `onCommit`, which the consumer's
4957
+ // `onEdit` parses back.
4958
+ //
4959
+ // Option source: `column.chipOptions` (value/label?/color?); tags already on the row
4960
+ // but absent from it are still listed (so they can be unchecked); with no
4961
+ // `chipOptions` at all, the distinct tags across the table's rows.
4962
+ /** Adds `tag` when absent, removes it when present -- the picker's one toggle. */ function toggleTag(tags, tag) {
4963
+ return tags.includes(tag) ? tags.filter((existing)=>existing !== tag) : [
4964
+ ...tags,
4965
+ tag
4966
+ ];
4967
+ }
4968
+ /** The option list a `chip-list` popover shows: declared `chipOptions` first, then any
4969
+ * selected tag not declared, then (only when nothing was declared) the distinct tags
4970
+ * across `rows`. */ function buildChipPickerOptions(column, tags, rows) {
4971
+ const seen = new Set();
4972
+ const out = [];
4973
+ function push(option) {
4974
+ if (!option.value || seen.has(option.value)) return;
4975
+ seen.add(option.value);
4976
+ out.push(option);
4977
+ }
4978
+ for (const option of column.chipOptions ?? [])push(option);
4979
+ for (const tag of tags)push({
4980
+ value: tag
4981
+ });
4982
+ if (!column.chipOptions) {
4983
+ const distinct = new Set();
4984
+ for (const row of rows){
4985
+ const raw = getColumnValue(column, row);
4986
+ if (Array.isArray(raw)) {
4987
+ for (const tag of raw)if (typeof tag === "string") distinct.add(tag);
4988
+ }
4989
+ }
4990
+ for (const tag of [
4991
+ ...distinct
4992
+ ].sort((x, y)=>x.localeCompare(y)))push({
4993
+ value: tag
4994
+ });
4995
+ }
4996
+ return out;
4997
+ }
4998
+ function ChipListEditor({ column, value, pending, onCommit, classNames, allRows = [] }) {
4885
4999
  const tags = Array.isArray(value) ? value.filter((tag)=>!!tag) : [];
4886
- const [draft, setDraft] = React.useState("");
5000
+ const built = buildChipPickerOptions(column, tags, allRows);
5001
+ // Options only ever GROW while the popover is open: with the row-derived fallback,
5002
+ // unchecking a tag that no other row carries would otherwise drop it from the list
5003
+ // and leave no way to re-check it.
5004
+ const knownRef = React.useRef([]);
5005
+ const known = knownRef.current;
5006
+ for (const option of built){
5007
+ const at = known.findIndex((entry)=>entry.value === option.value);
5008
+ if (at === -1) known.push(option);
5009
+ else known[at] = option;
5010
+ }
5011
+ const options = [
5012
+ ...known
5013
+ ];
5014
+ const optionText = React.useCallback((option)=>option.label ?? option.value, []);
5015
+ const { query, setQuery, filtered } = useSelectSearch(options, optionText);
5016
+ const trimmed = query.trim();
5017
+ const canCreate = column.chipCreatable !== false && trimmed !== "" && !options.some((option)=>option.value.toLowerCase() === trimmed.toLowerCase() || optionText(option).toLowerCase() === trimmed.toLowerCase());
4887
5018
  function commitTags(next) {
4888
5019
  onCommit(JSON.stringify(next));
4889
5020
  }
4890
- function addTag() {
4891
- const tag = draft.trim();
4892
- setDraft("");
4893
- if (!tag || tags.includes(tag)) return;
4894
- commitTags([
4895
- ...tags,
4896
- tag
4897
- ]);
4898
- }
4899
- function removeTag(tag) {
4900
- commitTags(tags.filter((existing)=>existing !== tag));
5021
+ function create() {
5022
+ setQuery("");
5023
+ commitTags(toggleTag(tags, trimmed));
4901
5024
  }
4902
- 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.
4903
- /*#__PURE__*/ jsxs("span", {
4904
- className: classNames.chipListEditor,
5025
+ return(// biome-ignore lint/a11y/noStaticElementInteractions: not a widget -- this wrapper never responds to interaction on ITSELF, only stops propagation of events that already occurred on a real interactive descendant (the search input, the option rows). No role fits it because it isn't a control.
5026
+ /*#__PURE__*/ jsxs("div", {
5027
+ className: classNames.chipPicker,
4905
5028
  onClick: (event)=>event.stopPropagation(),
4906
5029
  children: [
4907
- tags.map((tag)=>/*#__PURE__*/ jsx(SwatchChip, {
4908
- value: tag,
4909
- className: classNames.chip,
4910
- children: /*#__PURE__*/ jsx("button", {
4911
- type: "button",
4912
- disabled: pending,
4913
- "aria-label": `Remove ${tag}`,
4914
- className: classNames.chipRemove,
4915
- onClick: ()=>removeTag(tag),
4916
- children: /*#__PURE__*/ jsx(IconXmarkLine_default, {
4917
- size: 10
4918
- })
4919
- })
4920
- }, tag)),
4921
- /*#__PURE__*/ jsx("input", {
4922
- // 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.
5030
+ /*#__PURE__*/ jsx(SelectSearchField, {
4923
5031
  autoFocus: true,
4924
- className: classNames.chipInput,
4925
- value: draft,
4926
- disabled: pending,
4927
- placeholder: tags.length ? "" : "Add…",
4928
- "aria-label": `Add tag to ${column.key}`,
4929
- onChange: (event)=>setDraft(event.target.value),
5032
+ value: query,
5033
+ onChange: setQuery,
5034
+ placeholder: column.editorSearchPlaceholder ?? "Search…",
5035
+ "aria-label": `Search ${column.key}`,
4930
5036
  onKeyDown: (event)=>{
4931
- // Escape is the ONE key this input must NOT stop propagation for: there is
4932
- // no in-progress draft state worth clearing locally (every add/remove
4933
- // already commits immediately), so it needs to bubble all the way to
4934
- // `document` -- `DataTableBodyCell`'s own Escape listener is what actually
4935
- // closes this popover. Every other key stops here, same as before, so it
4936
- // never reaches the row's own click/keyboard handling.
4937
- if (event.key === "Escape") return;
4938
- event.stopPropagation();
4939
- if (event.key === "Enter") {
4940
- event.preventDefault();
4941
- addTag();
4942
- } else if (event.key === "Backspace" && draft === "" && tags.length > 0) {
4943
- removeTag(tags[tags.length - 1]);
4944
- }
5037
+ if (event.key !== "Enter") return;
5038
+ event.preventDefault();
5039
+ if (pending) return;
5040
+ if (canCreate) create();
5041
+ else if (trimmed && filtered.length > 0) commitTags(toggleTag(tags, filtered[0].value));
4945
5042
  }
4946
5043
  }),
5044
+ /*#__PURE__*/ jsxs("div", {
5045
+ className: classNames.chipPickerList,
5046
+ role: "group",
5047
+ "aria-label": `${column.key} options`,
5048
+ children: [
5049
+ filtered.map((option)=>{
5050
+ const checked = tags.includes(option.value);
5051
+ return /*#__PURE__*/ jsxs("button", {
5052
+ type: "button",
5053
+ role: "checkbox",
5054
+ "aria-checked": checked,
5055
+ disabled: pending,
5056
+ className: classNames.chipPickerRow,
5057
+ // Keep focus in the search field so typing keeps filtering after a toggle.
5058
+ onMouseDown: (event)=>event.preventDefault(),
5059
+ onClick: ()=>commitTags(toggleTag(tags, option.value)),
5060
+ children: [
5061
+ /*#__PURE__*/ jsx(SwatchChip, {
5062
+ value: option.value,
5063
+ label: option.label,
5064
+ color: option.color,
5065
+ className: classNames.chip
5066
+ }),
5067
+ /*#__PURE__*/ jsx("span", {
5068
+ className: classNames.chipPickerBox,
5069
+ "data-checked": checked ? "" : undefined,
5070
+ children: checked && /*#__PURE__*/ jsx(IconCheckmarkFatFill_default, {
5071
+ "aria-hidden": "true"
5072
+ })
5073
+ })
5074
+ ]
5075
+ }, option.value);
5076
+ }),
5077
+ canCreate && /*#__PURE__*/ jsx("button", {
5078
+ type: "button",
5079
+ disabled: pending,
5080
+ className: classNames.chipPickerCreate,
5081
+ onMouseDown: (event)=>event.preventDefault(),
5082
+ onClick: create,
5083
+ children: `Create '${trimmed}'`
5084
+ }),
5085
+ !canCreate && filtered.length === 0 && /*#__PURE__*/ jsx(SelectSearchEmpty, {
5086
+ children: column.editorEmptyLabel ?? "No results"
5087
+ })
5088
+ ]
5089
+ }),
4947
5090
  pending && /*#__PURE__*/ jsx(EditPendingIndicator, {})
4948
5091
  ]
4949
5092
  }));
@@ -5032,44 +5175,6 @@ function CellExpandOverlay({ className, anchorRef, contentRef, children }) {
5032
5175
  }), document.body);
5033
5176
  }
5034
5177
 
5035
- // Row-narrowing for the toolbar's search box -- split out of DataTable.tsx the way
5036
- // cells.tsx splits out cell-type rendering: a self-contained, independently testable
5037
- // concern DataTable.tsx just calls, rather than inline `.filter()` calls piling up in
5038
- // the same component that also owns markup. The toolbar's typed Filter CONDITIONS
5039
- // (field/operator/value chips, DATATABLE-ATTIO-SPEC.md's Attio/crisp model) live in
5040
- // `toolbar-conditions.tsx` instead, alongside the Sort/Filter chrome that manages
5041
- // them -- this file is search only now; the old per-column "is/is not" picker
5042
- // (`matchesFilter`/`DataTableFilterState`) is gone, replaced by that toolbar model.
5043
- /**
5044
- * A column's `value(row)` if it supplies one, else the raw field at `column.key` --
5045
- * the single accessor sort/search/filter/export all read through
5046
- * (DATATABLE-ATTIO-SPEC.md's Column API), so a consumer only teaches the table how to
5047
- * read a value ONCE rather than once per feature.
5048
- */ function getColumnValue(column, row) {
5049
- return column.value ? column.value(row) : row[column.key];
5050
- }
5051
- /**
5052
- * Case-insensitive substring match against a row's stringified value -- crisp's own
5053
- * `applyFilters` "contains" semantics (`tabletoolbar.tsx`), reused here for the
5054
- * toolbar's single free-text query. `null`/`undefined` never match a non-empty query --
5055
- * there is no text to search.
5056
- */ function matchesSearch(value, query) {
5057
- if (!query) return true;
5058
- if (value == null) return false;
5059
- return String(value).toLowerCase().includes(query.toLowerCase());
5060
- }
5061
- /**
5062
- * The toolbar's search query -- an OR across `searchable` columns, matching GRC's own
5063
- * `useFilteredRows`. The toolbar's own typed Filter conditions apply SEPARATELY, as a
5064
- * second pass over this function's result (`toolbar-conditions.tsx`'s
5065
- * `applyToolbarFilters`, called by `DataTable.tsx` right after this) -- narrowing what
5066
- * the search box already found, not the other way round.
5067
- */ function getVisibleRows(data, columns, query) {
5068
- const searchableColumns = columns.filter((column)=>column.searchable);
5069
- if (!query) return data;
5070
- return data.filter((row)=>searchableColumns.some((column)=>matchesSearch(getColumnValue(column, row), query)));
5071
- }
5072
-
5073
5178
  // Feature C (#12) -- the click-to-expand gate: measures whether a cell's rendered
5074
5179
  // content is ACTUALLY truncated right now, so a single click only opens the
5075
5180
  // anchored overlay (editors.tsx's `CellExpandOverlay`) when there's real hidden
@@ -5135,7 +5240,10 @@ function isCellTruncated(cell) {
5135
5240
  * `overflow: visible` on every element in between and regardless of how high its
5136
5241
  * `z-index` is raised -- neither of which is a stacking-context or overflow bug
5137
5242
  * fixable from the INSIDE. A portal sidesteps it entirely by not being a
5138
- * descendant of the sticky `<td>` at all once mounted. */ function isChipListEditable(column) {
5243
+ * descendant of the sticky `<td>` at all once mounted. */ /** The table's full `data`, so an editable chip-list column with no `chipOptions` can
5244
+ * fall back to the distinct tags already present across rows. */ const DataTableRowsContext = /*#__PURE__*/ React.createContext([]);
5245
+ /** Attio's multi-select popover is wider than a narrow tags column. */ const CHIP_PICKER_MIN_WIDTH = 240;
5246
+ function isChipListEditable(column) {
5139
5247
  return !!column.editable && column.type === "chip-list";
5140
5248
  }
5141
5249
  function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyFirst, stickyLast, cellContextMenu, onViewEditHistory, cellContextMenuItems }) {
@@ -5195,6 +5303,7 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
5195
5303
  const [contextMenuOpen, setContextMenuOpen] = React.useState(false);
5196
5304
  const alwaysLive = isAlwaysLiveEditable(column);
5197
5305
  const chipListEditable = isChipListEditable(column);
5306
+ const allRows = React.useContext(DataTableRowsContext);
5198
5307
  // A gated `editable` column (everything except the always-live widgets AND the
5199
5308
  // click-to-edit chip-list) opens on double-click -- Attio's own measured gesture
5200
5309
  // (crisp's #411), matched here rather than a single click, which stays "select
@@ -5224,7 +5333,10 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
5224
5333
  top: rect.bottom,
5225
5334
  insetInlineStart: rect.left,
5226
5335
  insetInlineEnd: "auto",
5227
- minWidth: rect.width,
5336
+ minWidth: Math.max(rect.width, CHIP_PICKER_MIN_WIDTH),
5337
+ // The multi-select popover owns its own inner padding (search field + option
5338
+ // rows inset themselves); the overlay's cell-editor padding would double it.
5339
+ padding: 0,
5228
5340
  // Both override the SAME `expandOverlay` class properties this box also
5229
5341
  // carries (`className={classNames.expandOverlay}`, below) -- that class's
5230
5342
  // `minHeight: calc(100% + 2px)` resolves against an ANCHORED box's own
@@ -5371,7 +5483,10 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
5371
5483
  chipListEditorOpen,
5372
5484
  measurePopoverPosition
5373
5485
  ]);
5374
- const overlayActive = overlayOpen && (alwaysLive || isEditing(row.id, column.key) || isPeeking(row.id, column.key));
5486
+ // Never for a chip-list cell: its picker is its own portaled popover, and the
5487
+ // in-cell overlay box would show for the first frame only (a sync commit closes
5488
+ // `isEditing` on the first toggle) and overlap the popover's top edge.
5489
+ const overlayActive = overlayOpen && !chipListEditable && (alwaysLive || isEditing(row.id, column.key) || isPeeking(row.id, column.key));
5375
5490
  const rawValue = getColumnValue(column, row);
5376
5491
  const pending = isPending(row.id, column.key);
5377
5492
  return /*#__PURE__*/ jsxs(Fragment, {
@@ -5429,7 +5544,8 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
5429
5544
  value: resolveEditValue(column, row, rawValue),
5430
5545
  pending: pending,
5431
5546
  onCommit: (value)=>commitEdit(row.id, column.key, value),
5432
- classNames: classNames
5547
+ classNames: classNames,
5548
+ allRows: allRows
5433
5549
  })
5434
5550
  }), document.body)
5435
5551
  ]
@@ -5483,7 +5599,8 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
5483
5599
  value: rawValue,
5484
5600
  className: classNames.chipList,
5485
5601
  chipClassName: classNames.chip,
5486
- overflowClassName: classNames.chipOverflow
5602
+ overflowClassName: classNames.chipOverflow,
5603
+ chipOptions: column.chipOptions
5487
5604
  });
5488
5605
  } else if (column.editable && isEditing(rowId, column.key)) {
5489
5606
  if (column.editor === "select") {
@@ -5600,7 +5717,8 @@ function DisplayCellContent({ column, row, value, classNames, locale }) {
5600
5717
  value: value,
5601
5718
  className: classNames.chipList,
5602
5719
  chipClassName: classNames.chip,
5603
- overflowClassName: classNames.chipOverflow
5720
+ overflowClassName: classNames.chipOverflow,
5721
+ chipOptions: column.chipOptions
5604
5722
  });
5605
5723
  case "number":
5606
5724
  return /*#__PURE__*/ jsx(Fragment, {
@@ -12020,223 +12138,226 @@ function DataTableInner(props, ref) {
12020
12138
  const totalCols = columns.length + (selectable ? 1 : 0);
12021
12139
  return /*#__PURE__*/ jsx(ClassNamesProvider, {
12022
12140
  value: classNames,
12023
- children: /*#__PURE__*/ jsxs(DataTableRoot, {
12024
- ref: ref,
12025
- className: clsx(classNames.root, className),
12026
- rowIds: rowIds,
12027
- selectable: selectable,
12028
- selectedIds: selectedIds,
12029
- defaultSelectedIds: defaultSelectedIds,
12030
- onSelectionChange: onSelectionChange,
12031
- sort: sort,
12032
- defaultSort: defaultSort,
12033
- onSortChange: handleSortChange,
12034
- query: resolvedQuery,
12035
- onQueryChange: handleQueryChange,
12036
- toolbarSorts: resolvedToolbarSorts,
12037
- onToolbarSortsChange: handleToolbarSortsChange,
12038
- toolbarFilters: resolvedToolbarFilters,
12039
- onToolbarFiltersChange: handleToolbarFiltersChange,
12040
- columnKeys: columnKeys,
12041
- actionsColumnKey: actionsColumnKey,
12042
- columnOrder: columnOrder,
12043
- defaultColumnOrder: defaultColumnOrder,
12044
- onColumnOrderChange: onColumnOrderChange,
12045
- hiddenColumnKeys: hiddenColumnKeys,
12046
- defaultHiddenColumnKeys: defaultHiddenColumnKeys,
12047
- onHiddenColumnKeysChange: onHiddenColumnKeysChange,
12048
- columnWidths: columnWidths,
12049
- defaultColumnWidths: defaultColumnWidths,
12050
- onColumnWidthsChange: onColumnWidthsChange,
12051
- columnLabels: columnLabels,
12052
- defaultColumnLabels: defaultColumnLabels,
12053
- onColumnLabelsChange: onColumnLabelsChange,
12054
- pinnedColumnKeys: pinnedColumnKeys,
12055
- defaultPinnedColumnKeys: defaultPinnedColumnKeys,
12056
- onPinnedColumnKeysChange: onPinnedColumnKeysChange,
12057
- columnCalcOps: columnCalcOps,
12058
- defaultColumnCalcOps: defaultColumnCalcOps,
12059
- onColumnCalcOpsChange: onColumnCalcOpsChange,
12060
- onEdit: onEdit,
12061
- onLoadMore: onLoadMore,
12062
- hasMore: hasMore,
12063
- loadingMore: loadingMore,
12064
- virtualized: virtualized,
12065
- rowHeight: rowHeight,
12066
- overscan: overscan,
12067
- ...restProps,
12068
- children: [
12069
- onColumnsChange && /*#__PURE__*/ jsx(ColumnsChangeEffect, {
12070
- columns: columns,
12071
- onColumnsChange: onColumnsChange
12072
- }),
12073
- showToolbar && /*#__PURE__*/ jsxs(DataTableToolbar, {
12074
- children: [
12075
- searchable && /*#__PURE__*/ jsx(SearchBox, {
12076
- placeholder: searchPlaceholder,
12077
- label: searchLabel
12078
- }),
12079
- /*#__PURE__*/ jsx(SortControl, {
12080
- columns: columns
12081
- }),
12082
- /*#__PURE__*/ jsx(FilterControl, {
12083
- columns: columns
12084
- }),
12085
- (showCount || toolbarActions) && /*#__PURE__*/ jsxs(DataTableToolbarEnd, {
12086
- children: [
12087
- toolbarActions,
12088
- showCount && /*#__PURE__*/ jsx(RowCount, {
12089
- shown: sortedData.length,
12090
- total: data.length,
12091
- countLabel: countLabel
12092
- })
12093
- ]
12094
- })
12095
- ]
12096
- }),
12097
- /*#__PURE__*/ jsx(DataTableScrollBody, {
12098
- scrollProps: scrollProps,
12099
- virtualized: virtualized,
12100
- children: /*#__PURE__*/ jsxs(DataTableTable, {
12101
- "aria-rowcount": virtualized ? sortedData.length + 1 : undefined,
12141
+ children: /*#__PURE__*/ jsx(DataTableRowsContext.Provider, {
12142
+ value: data,
12143
+ children: /*#__PURE__*/ jsxs(DataTableRoot, {
12144
+ ref: ref,
12145
+ className: clsx(classNames.root, className),
12146
+ rowIds: rowIds,
12147
+ selectable: selectable,
12148
+ selectedIds: selectedIds,
12149
+ defaultSelectedIds: defaultSelectedIds,
12150
+ onSelectionChange: onSelectionChange,
12151
+ sort: sort,
12152
+ defaultSort: defaultSort,
12153
+ onSortChange: handleSortChange,
12154
+ query: resolvedQuery,
12155
+ onQueryChange: handleQueryChange,
12156
+ toolbarSorts: resolvedToolbarSorts,
12157
+ onToolbarSortsChange: handleToolbarSortsChange,
12158
+ toolbarFilters: resolvedToolbarFilters,
12159
+ onToolbarFiltersChange: handleToolbarFiltersChange,
12160
+ columnKeys: columnKeys,
12161
+ actionsColumnKey: actionsColumnKey,
12162
+ columnOrder: columnOrder,
12163
+ defaultColumnOrder: defaultColumnOrder,
12164
+ onColumnOrderChange: onColumnOrderChange,
12165
+ hiddenColumnKeys: hiddenColumnKeys,
12166
+ defaultHiddenColumnKeys: defaultHiddenColumnKeys,
12167
+ onHiddenColumnKeysChange: onHiddenColumnKeysChange,
12168
+ columnWidths: columnWidths,
12169
+ defaultColumnWidths: defaultColumnWidths,
12170
+ onColumnWidthsChange: onColumnWidthsChange,
12171
+ columnLabels: columnLabels,
12172
+ defaultColumnLabels: defaultColumnLabels,
12173
+ onColumnLabelsChange: onColumnLabelsChange,
12174
+ pinnedColumnKeys: pinnedColumnKeys,
12175
+ defaultPinnedColumnKeys: defaultPinnedColumnKeys,
12176
+ onPinnedColumnKeysChange: onPinnedColumnKeysChange,
12177
+ columnCalcOps: columnCalcOps,
12178
+ defaultColumnCalcOps: defaultColumnCalcOps,
12179
+ onColumnCalcOpsChange: onColumnCalcOpsChange,
12180
+ onEdit: onEdit,
12181
+ onLoadMore: onLoadMore,
12182
+ hasMore: hasMore,
12183
+ loadingMore: loadingMore,
12184
+ virtualized: virtualized,
12185
+ rowHeight: rowHeight,
12186
+ overscan: overscan,
12187
+ ...restProps,
12188
+ children: [
12189
+ onColumnsChange && /*#__PURE__*/ jsx(ColumnsChangeEffect, {
12190
+ columns: columns,
12191
+ onColumnsChange: onColumnsChange
12192
+ }),
12193
+ showToolbar && /*#__PURE__*/ jsxs(DataTableToolbar, {
12102
12194
  children: [
12103
- /*#__PURE__*/ jsx(DataTableColGroup, {
12104
- columns: columns,
12105
- selectable: !!selectable,
12106
- stickyFirst: stickyFirst
12195
+ searchable && /*#__PURE__*/ jsx(SearchBox, {
12196
+ placeholder: searchPlaceholder,
12197
+ label: searchLabel
12107
12198
  }),
12108
- /*#__PURE__*/ jsxs(DataTableHeader, {
12199
+ /*#__PURE__*/ jsx(SortControl, {
12200
+ columns: columns
12201
+ }),
12202
+ /*#__PURE__*/ jsx(FilterControl, {
12203
+ columns: columns
12204
+ }),
12205
+ (showCount || toolbarActions) && /*#__PURE__*/ jsxs(DataTableToolbarEnd, {
12109
12206
  children: [
12110
- selectable && /*#__PURE__*/ jsx(SelectionHeaderCell, {
12111
- stickyFirst: stickyFirst,
12112
- children: /*#__PURE__*/ jsx(SelectAllCheckbox, {
12113
- label: selectAllLabel,
12114
- checkedIcon: selectionCheckedIcon ?? DEFAULT_CHECKED_ICON,
12115
- indeterminateIcon: selectionIndeterminateIcon ?? DEFAULT_INDETERMINATE_ICON
12116
- })
12117
- }),
12118
- /*#__PURE__*/ jsx(ColumnHeaderCells, {
12119
- columns: columns,
12120
- columnMenu: columnMenu,
12121
- reorderable: reorderable,
12122
- resizable: resizable,
12123
- sortIcon: sortIcon,
12124
- selectable: !!selectable,
12125
- stickyFirst: stickyFirst,
12126
- stickyLast: resolvedStickyLast
12207
+ toolbarActions,
12208
+ showCount && /*#__PURE__*/ jsx(RowCount, {
12209
+ shown: sortedData.length,
12210
+ total: data.length,
12211
+ countLabel: countLabel
12127
12212
  })
12128
12213
  ]
12129
- }),
12130
- /*#__PURE__*/ jsx(DataTableBody, {
12131
- children: loading ? /*#__PURE__*/ jsx("tr", {
12132
- children: /*#__PURE__*/ jsx(DataTableCell, {
12133
- colSpan: totalCols,
12134
- children: /*#__PURE__*/ jsx(LoadingStateContent, {
12135
- label: loadingLabel,
12136
- classNames: classNames
12214
+ })
12215
+ ]
12216
+ }),
12217
+ /*#__PURE__*/ jsx(DataTableScrollBody, {
12218
+ scrollProps: scrollProps,
12219
+ virtualized: virtualized,
12220
+ children: /*#__PURE__*/ jsxs(DataTableTable, {
12221
+ "aria-rowcount": virtualized ? sortedData.length + 1 : undefined,
12222
+ children: [
12223
+ /*#__PURE__*/ jsx(DataTableColGroup, {
12224
+ columns: columns,
12225
+ selectable: !!selectable,
12226
+ stickyFirst: stickyFirst
12227
+ }),
12228
+ /*#__PURE__*/ jsxs(DataTableHeader, {
12229
+ children: [
12230
+ selectable && /*#__PURE__*/ jsx(SelectionHeaderCell, {
12231
+ stickyFirst: stickyFirst,
12232
+ children: /*#__PURE__*/ jsx(SelectAllCheckbox, {
12233
+ label: selectAllLabel,
12234
+ checkedIcon: selectionCheckedIcon ?? DEFAULT_CHECKED_ICON,
12235
+ indeterminateIcon: selectionIndeterminateIcon ?? DEFAULT_INDETERMINATE_ICON
12236
+ })
12237
+ }),
12238
+ /*#__PURE__*/ jsx(ColumnHeaderCells, {
12239
+ columns: columns,
12240
+ columnMenu: columnMenu,
12241
+ reorderable: reorderable,
12242
+ resizable: resizable,
12243
+ sortIcon: sortIcon,
12244
+ selectable: !!selectable,
12245
+ stickyFirst: stickyFirst,
12246
+ stickyLast: resolvedStickyLast
12137
12247
  })
12138
- })
12139
- }) : error ? /*#__PURE__*/ jsx("tr", {
12140
- children: /*#__PURE__*/ jsx(DataTableCell, {
12141
- colSpan: totalCols,
12142
- children: /*#__PURE__*/ jsx(EmptyStateContent, {
12143
- classNames: classNames,
12144
- children: error
12248
+ ]
12249
+ }),
12250
+ /*#__PURE__*/ jsx(DataTableBody, {
12251
+ children: loading ? /*#__PURE__*/ jsx("tr", {
12252
+ children: /*#__PURE__*/ jsx(DataTableCell, {
12253
+ colSpan: totalCols,
12254
+ children: /*#__PURE__*/ jsx(LoadingStateContent, {
12255
+ label: loadingLabel,
12256
+ classNames: classNames
12257
+ })
12145
12258
  })
12146
- })
12147
- }) : sortedData.length === 0 ? /*#__PURE__*/ jsx("tr", {
12148
- children: /*#__PURE__*/ jsx(DataTableCell, {
12149
- colSpan: totalCols,
12150
- children: /*#__PURE__*/ jsx(EmptyStateContent, {
12151
- classNames: classNames,
12152
- children: empty
12259
+ }) : error ? /*#__PURE__*/ jsx("tr", {
12260
+ children: /*#__PURE__*/ jsx(DataTableCell, {
12261
+ colSpan: totalCols,
12262
+ children: /*#__PURE__*/ jsx(EmptyStateContent, {
12263
+ classNames: classNames,
12264
+ children: error
12265
+ })
12153
12266
  })
12154
- })
12155
- }) : /*#__PURE__*/ jsxs(Fragment, {
12156
- children: [
12157
- /*#__PURE__*/ jsx(WindowedRows, {
12158
- rows: sortedData,
12159
- virtualized: virtualized,
12267
+ }) : sortedData.length === 0 ? /*#__PURE__*/ jsx("tr", {
12268
+ children: /*#__PURE__*/ jsx(DataTableCell, {
12160
12269
  colSpan: totalCols,
12161
- children: (windowRows, firstIndex)=>/*#__PURE__*/ jsx(Fragment, {
12162
- children: windowRows.map((row, offset)=>/*#__PURE__*/ jsxs(DataTableRow, {
12163
- rowId: row.id,
12164
- "aria-rowindex": virtualized ? firstIndex + offset + 2 : undefined,
12165
- "data-clickable": onRowClick ? "" : undefined,
12166
- onClick: onRowClick ? ()=>onRowClick(row) : undefined,
12167
- children: [
12168
- selectable && /*#__PURE__*/ jsx(SelectionCell, {
12169
- // The row itself carries `onClick={onRowClick}` above, and a
12170
- // native click on this cell's checkbox bubbles up through it
12171
- // (React attaches DataTableRow's handler on the <tr>, which
12172
- // sits between the checkbox and anything a consumer could stop
12173
- // it from at -- so this has to live here, not in the consumer).
12174
- // Without this, checking a row's box also fires `onRowClick`.
12175
- onClick: (event)=>event.stopPropagation(),
12176
- stickyFirst: stickyFirst,
12177
- children: /*#__PURE__*/ jsx(RowCheckbox, {
12178
- label: getRowSelectionLabel(row),
12179
- checkedIcon: selectionCheckedIcon ?? DEFAULT_CHECKED_ICON
12270
+ children: /*#__PURE__*/ jsx(EmptyStateContent, {
12271
+ classNames: classNames,
12272
+ children: empty
12273
+ })
12274
+ })
12275
+ }) : /*#__PURE__*/ jsxs(Fragment, {
12276
+ children: [
12277
+ /*#__PURE__*/ jsx(WindowedRows, {
12278
+ rows: sortedData,
12279
+ virtualized: virtualized,
12280
+ colSpan: totalCols,
12281
+ children: (windowRows, firstIndex)=>/*#__PURE__*/ jsx(Fragment, {
12282
+ children: windowRows.map((row, offset)=>/*#__PURE__*/ jsxs(DataTableRow, {
12283
+ rowId: row.id,
12284
+ "aria-rowindex": virtualized ? firstIndex + offset + 2 : undefined,
12285
+ "data-clickable": onRowClick ? "" : undefined,
12286
+ onClick: onRowClick ? ()=>onRowClick(row) : undefined,
12287
+ children: [
12288
+ selectable && /*#__PURE__*/ jsx(SelectionCell, {
12289
+ // The row itself carries `onClick={onRowClick}` above, and a
12290
+ // native click on this cell's checkbox bubbles up through it
12291
+ // (React attaches DataTableRow's handler on the <tr>, which
12292
+ // sits between the checkbox and anything a consumer could stop
12293
+ // it from at -- so this has to live here, not in the consumer).
12294
+ // Without this, checking a row's box also fires `onRowClick`.
12295
+ onClick: (event)=>event.stopPropagation(),
12296
+ stickyFirst: stickyFirst,
12297
+ children: /*#__PURE__*/ jsx(RowCheckbox, {
12298
+ label: getRowSelectionLabel(row),
12299
+ checkedIcon: selectionCheckedIcon ?? DEFAULT_CHECKED_ICON
12300
+ })
12301
+ }),
12302
+ /*#__PURE__*/ jsx(ColumnBodyCells, {
12303
+ columns: columns,
12304
+ row: row,
12305
+ classNames: classNames,
12306
+ locale: locale,
12307
+ selectable: !!selectable,
12308
+ stickyFirst: stickyFirst,
12309
+ stickyLast: resolvedStickyLast,
12310
+ cellContextMenu: cellContextMenu,
12311
+ onViewEditHistory: onViewEditHistory,
12312
+ cellContextMenuItems: cellContextMenuItems
12180
12313
  })
12181
- }),
12182
- /*#__PURE__*/ jsx(ColumnBodyCells, {
12183
- columns: columns,
12184
- row: row,
12185
- classNames: classNames,
12186
- locale: locale,
12187
- selectable: !!selectable,
12188
- stickyFirst: stickyFirst,
12189
- stickyLast: resolvedStickyLast,
12190
- cellContextMenu: cellContextMenu,
12191
- onViewEditHistory: onViewEditHistory,
12192
- cellContextMenuItems: cellContextMenuItems
12193
- })
12194
- ]
12195
- }, row.id))
12196
- })
12314
+ ]
12315
+ }, row.id))
12316
+ })
12317
+ }),
12318
+ onLoadMore && /*#__PURE__*/ jsx(InfiniteScrollRow, {
12319
+ colSpan: totalCols,
12320
+ classNames: classNames,
12321
+ loadingLabel: loadingLabel
12322
+ })
12323
+ ]
12324
+ })
12325
+ }),
12326
+ showFooter && /*#__PURE__*/ jsxs(DataTableFooter, {
12327
+ children: [
12328
+ selectable && // The 40px-wide selection SPACER cell is now a plain `DataTableFooterCell`
12329
+ // (not the `selectionCell` slot) -- the `<colgroup>`'s own 40px `<col>`
12330
+ // is what makes every row's selection column line up now, not a matching
12331
+ // per-slot width formula, so there is no more reason for this cell to
12332
+ // borrow a different slot than its footer siblings.
12333
+ /*#__PURE__*/ jsx(SelectionFooterCell, {
12334
+ stickyFirst: stickyFirst
12197
12335
  }),
12198
- onLoadMore && /*#__PURE__*/ jsx(InfiniteScrollRow, {
12199
- colSpan: totalCols,
12200
- classNames: classNames,
12201
- loadingLabel: loadingLabel
12336
+ /*#__PURE__*/ jsx(ColumnCalcCells, {
12337
+ columns: columns,
12338
+ rows: sortedData,
12339
+ selectable: !!selectable,
12340
+ stickyFirst: stickyFirst,
12341
+ stickyLast: resolvedStickyLast
12202
12342
  })
12203
12343
  ]
12344
+ }),
12345
+ onAddRow && /*#__PURE__*/ jsx(AddRowFooter, {
12346
+ onAddRow: onAddRow,
12347
+ addRowLabel: addRowLabel,
12348
+ colSpan: totalCols,
12349
+ classNames: classNames
12204
12350
  })
12205
- }),
12206
- showFooter && /*#__PURE__*/ jsxs(DataTableFooter, {
12207
- children: [
12208
- selectable && // The 40px-wide selection SPACER cell is now a plain `DataTableFooterCell`
12209
- // (not the `selectionCell` slot) -- the `<colgroup>`'s own 40px `<col>`
12210
- // is what makes every row's selection column line up now, not a matching
12211
- // per-slot width formula, so there is no more reason for this cell to
12212
- // borrow a different slot than its footer siblings.
12213
- /*#__PURE__*/ jsx(SelectionFooterCell, {
12214
- stickyFirst: stickyFirst
12215
- }),
12216
- /*#__PURE__*/ jsx(ColumnCalcCells, {
12217
- columns: columns,
12218
- rows: sortedData,
12219
- selectable: !!selectable,
12220
- stickyFirst: stickyFirst,
12221
- stickyLast: resolvedStickyLast
12222
- })
12223
- ]
12224
- }),
12225
- onAddRow && /*#__PURE__*/ jsx(AddRowFooter, {
12226
- onAddRow: onAddRow,
12227
- addRowLabel: addRowLabel,
12228
- colSpan: totalCols,
12229
- classNames: classNames
12230
- })
12231
- ]
12351
+ ]
12352
+ })
12353
+ }),
12354
+ selectable && /*#__PURE__*/ jsx(DataTableBulkBar, {
12355
+ clearLabel: clearSelectionLabel,
12356
+ aboveFooter: showFooter || !!onAddRow,
12357
+ children: bulkActions
12232
12358
  })
12233
- }),
12234
- selectable && /*#__PURE__*/ jsx(DataTableBulkBar, {
12235
- clearLabel: clearSelectionLabel,
12236
- aboveFooter: showFooter || !!onAddRow,
12237
- children: bulkActions
12238
- })
12239
- ]
12359
+ ]
12360
+ })
12240
12361
  })
12241
12362
  });
12242
12363
  }