@crisp-ui-kit/crisp 0.54.11 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/cjs/components/datatable/datatable.js +130 -38
  2. package/dist/cjs/components/resizablepanel/resizablepanel.js +10 -1
  3. package/dist/cjs/components/table/table.js +8 -1
  4. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +110 -27
  5. package/dist/cjs/index.js +0 -2
  6. package/dist/cjs/product/recordpage/index.js +6 -1
  7. package/dist/cjs/product/recordpage/recordpage.js +20 -4
  8. package/dist/esm/components/datatable/datatable.js +131 -39
  9. package/dist/esm/components/resizablepanel/resizablepanel.js +10 -1
  10. package/dist/esm/components/table/table.js +8 -1
  11. package/dist/esm/components/tabletoolbar/tabletoolbar.js +111 -28
  12. package/dist/esm/index.js +0 -2
  13. package/dist/esm/product/recordpage/index.js +2 -0
  14. package/dist/esm/product/recordpage/recordpage.js +20 -4
  15. package/dist/styles.css +0 -272
  16. package/dist/styles.layered.css +0 -272
  17. package/dist/types/components/table/table.d.ts +14 -0
  18. package/dist/types/index.d.ts +0 -2
  19. package/dist/types/product/recordpage/index.d.ts +2 -0
  20. package/package.json +2 -12
  21. package/dist/cjs/product/recordstable/company-logos.js +0 -29
  22. package/dist/cjs/product/recordstable/index.js +0 -13
  23. package/dist/cjs/product/recordstable/recordstable.js +0 -1362
  24. package/dist/cjs/product/recordstable/recordstable.recipe.js +0 -175
  25. package/dist/cjs/product/recordstable/recordstable.tag.js +0 -26
  26. package/dist/cjs/product/taskstable/index.js +0 -7
  27. package/dist/cjs/product/taskstable/taskstable.js +0 -32
  28. package/dist/cjs/product/taskstable/taskstable.recipe.js +0 -56
  29. package/dist/esm/product/recordstable/company-logos.js +0 -26
  30. package/dist/esm/product/recordstable/index.js +0 -3
  31. package/dist/esm/product/recordstable/recordstable.js +0 -1322
  32. package/dist/esm/product/recordstable/recordstable.recipe.js +0 -172
  33. package/dist/esm/product/recordstable/recordstable.tag.js +0 -23
  34. package/dist/esm/product/taskstable/index.js +0 -2
  35. package/dist/esm/product/taskstable/taskstable.js +0 -29
  36. package/dist/esm/product/taskstable/taskstable.recipe.js +0 -53
  37. package/dist/recordstable.css +0 -240
  38. package/dist/recordstable.d.ts +0 -7
  39. package/dist/recordstable.layered.css +0 -242
  40. package/dist/taskstable.css +0 -30
  41. package/dist/taskstable.d.ts +0 -7
  42. package/dist/taskstable.layered.css +0 -32
  43. package/dist/types/product/recordstable/company-logos.d.ts +0 -1
  44. package/dist/types/product/recordstable/index.d.ts +0 -3
  45. package/dist/types/product/recordstable/recordstable.d.ts +0 -294
  46. package/dist/types/product/recordstable/recordstable.recipe.d.ts +0 -5
  47. package/dist/types/product/recordstable/recordstable.tag.d.ts +0 -15
  48. package/dist/types/product/taskstable/index.d.ts +0 -2
  49. package/dist/types/product/taskstable/taskstable.d.ts +0 -59
  50. package/dist/types/product/taskstable/taskstable.recipe.d.ts +0 -3
@@ -50,6 +50,20 @@ const core_1 = require("../../core");
50
50
  const column_1 = require("./column");
51
51
  const editable_field_1 = require("./editable-field");
52
52
  const recordpage_recipe_1 = require("./recordpage.recipe");
53
+ // crisp/#798: the details column's own drag clamp -- re-measured live on
54
+ // Attio's own record page (a real drag test, not a source read): min 619px,
55
+ // max 1170px, default (also the double-click-reset target) 734px. These
56
+ // replace an earlier, much narrower 300/560/320 that was never actually
57
+ // checked against Attio and turned out roughly half the real max. Confirmed
58
+ // live these are literal px, not a viewport ratio: at a narrower emulated
59
+ // width the already-dragged-to-max column shrank below 1170px, but its OWN
60
+ // `getComputedStyle(...).width` still read a plain px number the whole
61
+ // time (not a `%`) — that shrink is ordinary flex-shrink (both panels
62
+ // share `flex: 1 1 auto` and can't both keep their preferred width once the
63
+ // window is too narrow), not the clamp itself changing with viewport width.
64
+ const DETAILS_MIN_W = 619;
65
+ const DETAILS_MAX_W = 1170;
66
+ const DETAILS_DEFAULT_W = 734;
53
67
  function valueEmpty(v) {
54
68
  return v == null || v === "" || (Array.isArray(v) && v.length === 0);
55
69
  }
@@ -457,7 +471,7 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
457
471
  const tasks = tasksProp ?? internalTasks;
458
472
  const [showAllActivity, setShowAllActivity] = React.useState(false);
459
473
  // Drag-resizable left details column.
460
- const [detailsW, setDetailsW] = React.useState(320);
474
+ const [detailsW, setDetailsW] = React.useState(DETAILS_DEFAULT_W);
461
475
  const [resizing, setResizing] = React.useState(false);
462
476
  const dragRef = React.useRef(null);
463
477
  const onResizeDown = (e) => {
@@ -475,7 +489,7 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
475
489
  if (!dragRef.current)
476
490
  return;
477
491
  const next = dragRef.current.w + dragRef.current.dir * (e.clientX - dragRef.current.x);
478
- setDetailsW(Math.max(300, Math.min(560, next)));
492
+ setDetailsW(Math.max(DETAILS_MIN_W, Math.min(DETAILS_MAX_W, next)));
479
493
  };
480
494
  const onResizeUp = (e) => {
481
495
  dragRef.current = null;
@@ -660,7 +674,7 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
660
674
  const effectivePanel = tabsProp
661
675
  ? (active) => tabsProp.find((tb) => tb.id === active)?.render()
662
676
  : builtinPanel;
663
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, recordpage_recipe_1.recordpageRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-topbar", children: [(0, jsx_runtime_1.jsxs)("nav", { className: "crisp-recordpage-crumbs", "aria-label": t.breadcrumb, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-crumb", onClick: onBack, "aria-label": `Back to ${collection}`, children: collection }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-crumb-sep", children: "/" }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-crumb-name", children: name })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-actions", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { size: 14, strokeWidth: 1.75, color: "var(--crisp-bg-brand-solid)" }), t.askAttio] }), onToggleFull && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: onToggleFull, "aria-label": full ? t.exitFullScreen : t.fullScreen, children: full ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Minimize2, { size: 15, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.Maximize2, { size: 15, strokeWidth: 1.75 })) })), (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", "aria-label": t.recordActions, trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", "aria-label": t.recordActions, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }), shortcut: "\u2318C", children: t.copyRecordLink }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), children: t.addToList }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }), onSelect: () => setFavorite((f) => !f), children: favorite ? t.removeFromFavourites : t.addToFavourites }), (0, jsx_runtime_1.jsx)(menu_1.MenuSeparator, {}), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, shortcut: "\u232B", children: t.deleteRecord })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-body", children: [(0, jsx_runtime_1.jsxs)("aside", { className: "crisp-recordpage-details", style: { width: detailsW }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-entity", children: [entity?.avatar ? ((0, jsx_runtime_1.jsx)("img", { className: "crisp-recordpage-entity-logo", src: entity.avatar, alt: "" })) : ((0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "lg", shape: "rounded", tone: "brand", children: name.charAt(0) })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-entity-name", children: name }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "aria-label": t.editRecord, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Pencil, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "data-active": favorite ? "" : undefined, "aria-pressed": favorite, onClick: () => setFavorite((f) => !f), "aria-label": favorite ? t.removeFromFavourites : t.addToFavourites, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-quickactions", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-compose", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Mail, { size: 16, strokeWidth: 1.75 }), t.composeEmail] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.copyRecordLink, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.logANote, children: (0, jsx_runtime_1.jsx)(lucide_react_1.FileText, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.linkRecords, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Share2, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.createATask, children: (0, jsx_runtime_1.jsx)(lucide_react_1.SquareCheck, { size: 15, strokeWidth: 1.75 }) })] }), (0, jsx_runtime_1.jsx)(RecordDetails, { record: record, columns: columns, sections: sections, onEdit: onEdit, labels: labels }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-section", children: [(0, jsx_runtime_1.jsx)(SectionHead, { title: t.tabs.lists, action: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-addbtn", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 14, strokeWidth: 2 }), t.addToList] }) }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-emptystate", children: t.noLists })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-resizer", "data-active": resizing ? "" : undefined, role: "separator", "aria-orientation": "vertical", "aria-label": t.resizeDetails, "aria-valuenow": Math.round(detailsW), "aria-valuemin": 300, "aria-valuemax": 560, tabIndex: 0, onPointerDown: onResizeDown, onPointerMove: onResizeMove, onPointerUp: onResizeUp, onKeyDown: (e) => {
677
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, recordpage_recipe_1.recordpageRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-topbar", children: [(0, jsx_runtime_1.jsxs)("nav", { className: "crisp-recordpage-crumbs", "aria-label": t.breadcrumb, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-crumb", onClick: onBack, "aria-label": `Back to ${collection}`, children: collection }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-crumb-sep", children: "/" }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-crumb-name", children: name })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-actions", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { size: 14, strokeWidth: 1.75, color: "var(--crisp-bg-brand-solid)" }), t.askAttio] }), onToggleFull && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: onToggleFull, "aria-label": full ? t.exitFullScreen : t.fullScreen, children: full ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Minimize2, { size: 15, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.Maximize2, { size: 15, strokeWidth: 1.75 })) })), (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", "aria-label": t.recordActions, trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", "aria-label": t.recordActions, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }), shortcut: "\u2318C", children: t.copyRecordLink }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), children: t.addToList }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }), onSelect: () => setFavorite((f) => !f), children: favorite ? t.removeFromFavourites : t.addToFavourites }), (0, jsx_runtime_1.jsx)(menu_1.MenuSeparator, {}), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, shortcut: "\u232B", children: t.deleteRecord })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-body", children: [(0, jsx_runtime_1.jsxs)("aside", { className: "crisp-recordpage-details", style: { width: detailsW }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-entity", children: [entity?.avatar ? ((0, jsx_runtime_1.jsx)("img", { className: "crisp-recordpage-entity-logo", src: entity.avatar, alt: "" })) : ((0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "lg", shape: "rounded", tone: "brand", children: name.charAt(0) })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-entity-name", children: name }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "aria-label": t.editRecord, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Pencil, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "data-active": favorite ? "" : undefined, "aria-pressed": favorite, onClick: () => setFavorite((f) => !f), "aria-label": favorite ? t.removeFromFavourites : t.addToFavourites, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-quickactions", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-compose", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Mail, { size: 16, strokeWidth: 1.75 }), t.composeEmail] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.copyRecordLink, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.logANote, children: (0, jsx_runtime_1.jsx)(lucide_react_1.FileText, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.linkRecords, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Share2, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.createATask, children: (0, jsx_runtime_1.jsx)(lucide_react_1.SquareCheck, { size: 15, strokeWidth: 1.75 }) })] }), (0, jsx_runtime_1.jsx)(RecordDetails, { record: record, columns: columns, sections: sections, onEdit: onEdit, labels: labels }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-section", children: [(0, jsx_runtime_1.jsx)(SectionHead, { title: t.tabs.lists, action: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-addbtn", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 14, strokeWidth: 2 }), t.addToList] }) }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-emptystate", children: t.noLists })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-resizer", "data-active": resizing ? "" : undefined, role: "separator", "aria-orientation": "vertical", "aria-label": t.resizeDetails, "aria-valuenow": Math.round(detailsW), "aria-valuemin": DETAILS_MIN_W, "aria-valuemax": DETAILS_MAX_W, tabIndex: 0, onPointerDown: onResizeDown, onPointerMove: onResizeMove, onPointerUp: onResizeUp, onKeyDown: (e) => {
664
678
  if (e.key !== "ArrowLeft" && e.key !== "ArrowRight")
665
679
  return;
666
680
  // The pointer drag learned its direction in #212; this did not.
@@ -669,7 +683,9 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
669
683
  // half-conversion the drag had, one handler away (#255).
670
684
  const rtl = (0, core_1.isRtlElement)(e.currentTarget);
671
685
  const grow = e.key === (rtl ? "ArrowLeft" : "ArrowRight");
672
- setDetailsW((w) => grow ? Math.min(560, w + 16) : Math.max(300, w - 16));
686
+ setDetailsW((w) => grow
687
+ ? Math.min(DETAILS_MAX_W, w + 16)
688
+ : Math.max(DETAILS_MIN_W, w - 16));
673
689
  } }), (0, jsx_runtime_1.jsx)("main", { className: "crisp-recordpage-main", children: (0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: effectiveTabs, defaultValue: tabsProp ? defaultTab : "overview", "aria-label": `${name} record`, children: effectivePanel }) })] })] }));
674
690
  }
675
691
  RecordPage.displayName = "RecordPage";
@@ -1,7 +1,7 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ArrowDown, ArrowUp, Check, ChevronsUpDown, CircleUserRound, ClipboardPaste, Copy, GripVertical, History, Lock, PanelRight, Plus, Search, SlidersHorizontal, X, } from "lucide-react";
3
3
  import * as React from "react";
4
- import { cellDatePrecision, formatCellDate, isRtlElement, parseCellDate, useCrispMessage, useCrispMessages, } from "../../core/index.js";
4
+ import { cellDatePrecision, formatCellDate, isRtlElement, parseCellDate, useCrispMessage, useCrispMessages, usePrefersReducedMotion, } from "../../core/index.js";
5
5
  // crisp/#601 supersession: a deliberate exception to "DS atoms stay
6
6
  // generic; product components compose them" (AUTHORING.md) -- the row's
7
7
  // own comment affordance is `CommentCountBadge` (product/comments, #625),
@@ -1033,14 +1033,48 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
1033
1033
  const colAriaIndex = (i) => ariaColIndexBase + i + 1;
1034
1034
  const allSelected = rows.length > 0 && rows.every((r) => selected.has(r.id));
1035
1035
  const someSelected = selected.size > 0 && !allSelected;
1036
+ // crisp/#782, ported from RecordsTable's own already-shipped logic: the
1037
+ // header checkbox speaks for the rows you can SEE. Rebuilding the whole
1038
+ // set from `rows` (the filtered/visible set) would silently drop any
1039
+ // selection an active filter is hiding -- add/remove only the visible
1040
+ // rows and leave the rest of `selected` alone.
1036
1041
  function toggleAll() {
1037
- setSelected(allSelected ? new Set() : new Set(rows.map((r) => r.id)));
1042
+ const next = new Set(selected);
1043
+ for (const r of rows) {
1044
+ if (allSelected)
1045
+ next.delete(r.id);
1046
+ else
1047
+ next.add(r.id);
1048
+ }
1049
+ setSelected(next);
1038
1050
  }
1039
1051
  function toggleRow(id) {
1040
1052
  const next = new Set(selected);
1041
1053
  next.has(id) ? next.delete(id) : next.add(id);
1042
1054
  setSelected(next);
1043
1055
  }
1056
+ // crisp/#781: Shift+Click on a row checkbox selects the whole contiguous
1057
+ // range from the last plain click (the anchor) to this row -- confirmed
1058
+ // live, the mouse counterpart to #749's Shift+Arrow keyboard gesture,
1059
+ // sharing the same `rowSelectAnchor` state and the same recompute-the-
1060
+ // whole-span shape (not accumulate) so shift-clicking back toward the
1061
+ // anchor shrinks the same way clicking away from it grows. A plain click
1062
+ // both toggles normally AND re-anchors, so the NEXT Shift+Click extends
1063
+ // from wherever the user last plain-clicked, matching Attio.
1064
+ function toggleRowOrExtend(id, shiftKey) {
1065
+ if (shiftKey && rowSelectAnchor != null) {
1066
+ const anchorIdx = rows.findIndex((r) => r.id === rowSelectAnchor);
1067
+ const idx = rows.findIndex((r) => r.id === id);
1068
+ if (anchorIdx >= 0 && idx >= 0) {
1069
+ const lo = Math.min(anchorIdx, idx);
1070
+ const hi = Math.max(anchorIdx, idx);
1071
+ setSelected(new Set(rows.slice(lo, hi + 1).map((r) => r.id)));
1072
+ return;
1073
+ }
1074
+ }
1075
+ setRowSelectAnchor(id);
1076
+ toggleRow(id);
1077
+ }
1044
1078
  function cycleSort(key) {
1045
1079
  // Clicking a header drives a single sort, as before. Multi-sort is supplied
1046
1080
  // by the caller through the `sort` prop (built in a sort popover).
@@ -1247,6 +1281,28 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
1247
1281
  setSelected(new Set(rows.map((rw) => rw.id)));
1248
1282
  return;
1249
1283
  }
1284
+ // crisp/#772: Cmd/Ctrl+ArrowUp jumps the selected cell to the first row,
1285
+ // Cmd/Ctrl+ArrowDown to the last -- confirmed live (Attio's own help
1286
+ // drawer documents "Jump to top"/"Jump to bottom" on these exact
1287
+ // bindings). `!e.shiftKey` guards this from stealing Cmd+Shift+Arrow,
1288
+ // a separate gesture (#773, extend-selection-to-first/last) that falls
1289
+ // through to the Shift+Arrow branch below untouched. Placed before that
1290
+ // branch for the same reason: without the guard, Cmd+Shift+Arrow would
1291
+ // still reach it (that branch doesn't check metaKey/ctrlKey either),
1292
+ // just not do the "jump" part -- the guard here is what keeps the two
1293
+ // gestures from fighting over the same keys.
1294
+ if ((e.metaKey || e.ctrlKey) &&
1295
+ !e.shiftKey &&
1296
+ (e.key === "ArrowUp" || e.key === "ArrowDown")) {
1297
+ const r0 = rows.findIndex((row) => row.id === selCell.id);
1298
+ if (r0 < 0)
1299
+ return;
1300
+ e.preventDefault();
1301
+ const nr0 = e.key === "ArrowUp" ? 0 : rows.length - 1;
1302
+ setSelCell({ id: rows[nr0].id, key: selCell.key });
1303
+ clipboardProxyRef.current?.select();
1304
+ return;
1305
+ }
1250
1306
  // crisp/#749: Shift+ArrowUp/Down extends the row-checkbox selection --
1251
1307
  // confirmed live, distinct from Shift+Click's cell-range gesture above
1252
1308
  // (that one is `selAnchor`; this is `rowSelectAnchor`, and the two
@@ -1398,15 +1454,21 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
1398
1454
  }
1399
1455
  setEditing(null);
1400
1456
  }
1457
+ const reduceMotion = usePrefersReducedMotion();
1401
1458
  // crisp/#577 -- keyboard nav to an off-screen (virtualized-out) cell
1402
- // scrolls it into the rendered window, on both axes. Column scroll-into-
1403
- // view runs regardless of `height` -- the horizontal scroll container (and
1404
- // its own virtualization) exists either way; row scroll-into-view only
1405
- // makes sense once `height` gives the grid an internal vertical scroll
1406
- // position to move. `widths` is read only indirectly, through `colWidth`'s
1407
- // closure over it -- biome can't see that, but a column resize while a
1408
- // cell is selected genuinely needs to re-run this to keep it in view, so
1409
- // the dependency is real even though the lint rule can't prove it.
1459
+ // scrolls it into the rendered window, on both axes. `renderItems`/
1460
+ // `rowOffsets` are computed unconditionally (`height == null` just makes
1461
+ // them a no-op elsewhere, see the comment above `renderItems`), so this
1462
+ // runs on both axes regardless of `height` too (crisp/#780 -- it used to
1463
+ // gate the vertical half on `height != null`, on the reasoning that only
1464
+ // `height` gives the grid an internal scroll position to move; that's
1465
+ // wrong when the grid's own container still scrolls without an explicit
1466
+ // height, e.g. inside a shorter ancestor, matching `RecordsTable`'s own
1467
+ // already-shipped #257 fix, which had no such gate). `widths` is read only
1468
+ // indirectly, through `colWidth`'s closure over it -- biome can't see
1469
+ // that, but a column resize while a cell is selected genuinely needs to
1470
+ // re-run this to keep it in view, so the dependency is real even though
1471
+ // the lint rule can't prove it.
1410
1472
  // biome-ignore lint/correctness/useExhaustiveDependencies: widths is read via colWidth's closure, see comment above
1411
1473
  React.useEffect(() => {
1412
1474
  if (!selCell)
@@ -1414,28 +1476,37 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
1414
1476
  const el = scrollRef.current;
1415
1477
  if (!el)
1416
1478
  return;
1417
- // Sets `viewport` directly from the SAME value just assigned to the
1418
- // real DOM property, rather than waiting for a follow-up `scroll` event
1419
- // to round-trip through the `onScroll` handler above -- a real browser
1420
- // does fire one when `scrollTop`/`scrollLeft` is set programmatically,
1421
- // but relying on that would make this effect's own visible result (the
1479
+ // `el.scrollTo` rather than a direct `el.scrollTop =`/`el.scrollLeft =`
1480
+ // assignment (crisp/#780, matching RecordsTable's own #208/#257 rule):
1481
+ // property assignment is ALWAYS instant, so it can never honour a
1482
+ // consumer's own `scroll-behavior: smooth` on this container; `scrollTo`
1483
+ // defers to that CSS when no `behavior` is given, and only forces
1484
+ // `"auto"` under `prefers-reduced-motion`, which is the one case that
1485
+ // must win even over a host page's own smooth-scroll opt-in.
1486
+ const scrollTo = (opts) => el.scrollTo({
1487
+ ...opts,
1488
+ ...(reduceMotion ? { behavior: "auto" } : null),
1489
+ });
1490
+ // Sets `viewport` directly from the SAME value just passed to
1491
+ // `scrollTo`, rather than waiting for a follow-up `scroll` event to
1492
+ // round-trip through the `onScroll` handler above -- a real browser
1493
+ // does fire one when the scroll position changes programmatically, but
1494
+ // relying on that would make this effect's own visible result (the
1422
1495
  // newly-selected cell actually rendering) depend on a second render
1423
1496
  // triggered from outside React's own update, which is both slower and
1424
1497
  // one more thing that has to be true for this to work.
1425
- if (height != null) {
1426
- const itemIndex = rowIdToItemIndex.get(selCell.id);
1427
- if (itemIndex != null) {
1428
- const itemTop = rowOffsets[itemIndex];
1429
- const itemBottom = itemTop + renderItems[itemIndex].height;
1430
- if (itemTop < el.scrollTop) {
1431
- el.scrollTop = itemTop;
1432
- setViewport((v) => ({ ...v, top: itemTop }));
1433
- }
1434
- else if (itemBottom > el.scrollTop + el.clientHeight) {
1435
- const newTop = itemBottom - el.clientHeight;
1436
- el.scrollTop = newTop;
1437
- setViewport((v) => ({ ...v, top: newTop }));
1438
- }
1498
+ const itemIndex = rowIdToItemIndex.get(selCell.id);
1499
+ if (itemIndex != null) {
1500
+ const itemTop = rowOffsets[itemIndex];
1501
+ const itemBottom = itemTop + renderItems[itemIndex].height;
1502
+ if (itemTop < el.scrollTop) {
1503
+ scrollTo({ top: itemTop });
1504
+ setViewport((v) => ({ ...v, top: itemTop }));
1505
+ }
1506
+ else if (itemBottom > el.scrollTop + el.clientHeight) {
1507
+ const newTop = itemBottom - el.clientHeight;
1508
+ scrollTo({ top: newTop });
1509
+ setViewport((v) => ({ ...v, top: newTop }));
1439
1510
  }
1440
1511
  }
1441
1512
  const ci = columns.findIndex((c) => c.key === selCell.key);
@@ -1445,24 +1516,24 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
1445
1516
  left += colWidth(columns[i]);
1446
1517
  const right = left + colWidth(columns[ci]);
1447
1518
  if (left < el.scrollLeft) {
1448
- el.scrollLeft = left;
1519
+ scrollTo({ left });
1449
1520
  setViewport((v) => ({ ...v, left }));
1450
1521
  }
1451
1522
  else if (right > el.scrollLeft + el.clientWidth) {
1452
1523
  const newLeft = right - el.clientWidth;
1453
- el.scrollLeft = newLeft;
1524
+ scrollTo({ left: newLeft });
1454
1525
  setViewport((v) => ({ ...v, left: newLeft }));
1455
1526
  }
1456
1527
  }
1457
1528
  }, [
1458
1529
  selCell,
1459
- height,
1460
1530
  columns,
1461
1531
  widths,
1462
1532
  selectable,
1463
1533
  rowIdToItemIndex,
1464
1534
  rowOffsets,
1465
1535
  renderItems,
1536
+ reduceMotion,
1466
1537
  ]);
1467
1538
  const totalCols = columns.length + (selectable ? 1 : 0) + (showGrip ? 1 : 0);
1468
1539
  const hasSummary = columns.some((c) => c.summary || c.calc);
@@ -1836,12 +1907,33 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
1836
1907
  }, onDragEnd: () => {
1837
1908
  setDragRowId(null);
1838
1909
  setDropRowId(null);
1839
- }, children: _jsx(GripVertical, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }) })), selectable && (_jsx(TableCell, { className: "crisp-datatable-col-check", "aria-colindex": showGrip ? 2 : 1, ...stickyCol(-1), onClick: (e) => e.stopPropagation(), children: _jsx("span", { className: "crisp-datatable-check", children: _jsx(Checkbox, { "aria-label": selectRowLabel(row), checked: selected.has(row.id), onChange: () => toggleRow(row.id) }) }) })), colSlots.map((slot) => {
1910
+ }, children: _jsx(GripVertical, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }) })), selectable && (_jsx(TableCell, { className: "crisp-datatable-col-check", "aria-colindex": showGrip ? 2 : 1, ...stickyCol(-1), onClick: (e) => e.stopPropagation(), children: _jsx("span", { className: "crisp-datatable-check", children: _jsx(Checkbox, { "aria-label": selectRowLabel(row), checked: selected.has(row.id),
1911
+ // preventDefault stops the native check/change
1912
+ // this click would otherwise also fire -- this
1913
+ // handler is the single source of truth so a
1914
+ // Shift+Click's range-select and a plain
1915
+ // click's toggle-and-re-anchor never race each
1916
+ // other (crisp/#781).
1917
+ onClick: (e) => {
1918
+ e.preventDefault();
1919
+ toggleRowOrExtend(row.id, e.shiftKey);
1920
+ }, onChange: () => { } }) }) })), colSlots.map((slot) => {
1840
1921
  if (slot.type === "spacer") {
1841
1922
  return (_jsx("td", { role: "presentation", "aria-hidden": "true", style: { padding: 0, border: "none" } }, `__spacer_${slot.side}`));
1842
1923
  }
1843
1924
  const col = columns[slot.i];
1844
1925
  const i = slot.i;
1926
+ // crisp/#779, matching RecordsTable's own #231 fix:
1927
+ // column 0 is the row's own identity (its name), not
1928
+ // a value it holds -- a screen reader crossing the
1929
+ // row should read "Google, Employees, 182,000", not a
1930
+ // bare "182,000". Spread onto every TableCell this
1931
+ // column can render as (every editor branch, not just
1932
+ // the default one), since the row-identity semantics
1933
+ // don't depend on which editor is open.
1934
+ const rowHeaderProps = i === 0
1935
+ ? { as: "th", scope: "row" }
1936
+ : undefined;
1845
1937
  const isEditing = editing?.id === row.id && editing?.key === col.key;
1846
1938
  const value = cellValue(col, row);
1847
1939
  const sticky = dataStickyCol(i);
@@ -1864,14 +1956,14 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
1864
1956
  // right). See the `isEditing` block below for their
1865
1957
  // actual editor.
1866
1958
  if (col.editable && col.editor === "switch") {
1867
- return (_jsxs(TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, className: "crisp-datatable-td--picker", "data-pending": isPending ? "" : undefined, children: [_jsxs("span", { className: "crisp-datatable-edit-picker", children: [_jsx(Switch, { "aria-label": `Edit ${col.key}`, checked: value === true || value === "true", disabled: isPending, onCheckedChange: (checked) => commitEdit(row.id, col.key, String(checked)) }), isPending && (_jsx(Spinner, { size: 14, className: "crisp-datatable-edit-pending" }))] }), errorHere && (_jsx("span", { className: "crisp-datatable-edit-error", children: errorHere }))] }, col.key));
1959
+ return (_jsxs(TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, ...rowHeaderProps, className: "crisp-datatable-td--picker", "data-pending": isPending ? "" : undefined, children: [_jsxs("span", { className: "crisp-datatable-edit-picker", children: [_jsx(Switch, { "aria-label": `Edit ${col.key}`, checked: value === true || value === "true", disabled: isPending, onCheckedChange: (checked) => commitEdit(row.id, col.key, String(checked)) }), isPending && (_jsx(Spinner, { size: 14, className: "crisp-datatable-edit-pending" }))] }), errorHere && (_jsx("span", { className: "crisp-datatable-edit-error", children: errorHere }))] }, col.key));
1868
1960
  }
1869
1961
  // crisp/#760: `editor: "rating"` is the same always-
1870
1962
  // live shape as `switch` above -- clicking a star IS
1871
1963
  // the commit gesture, confirmed live.
1872
1964
  if (col.editable && col.editor === "rating") {
1873
1965
  const n = typeof value === "number" ? value : Number(value);
1874
- return (_jsxs(TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, className: "crisp-datatable-td--picker", "data-pending": isPending ? "" : undefined, children: [_jsxs("span", { className: "crisp-datatable-edit-picker", children: [_jsx(RatingStars, { "aria-label": `Edit ${col.key}`, value: Number.isNaN(n) ? 0 : n, max: col.maxRating ?? 5, onChange: (next) => commitEdit(row.id, col.key, String(next)) }), isPending && (_jsx(Spinner, { size: 14, className: "crisp-datatable-edit-pending" }))] }), errorHere && (_jsx("span", { className: "crisp-datatable-edit-error", children: errorHere }))] }, col.key));
1966
+ return (_jsxs(TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, ...rowHeaderProps, className: "crisp-datatable-td--picker", "data-pending": isPending ? "" : undefined, children: [_jsxs("span", { className: "crisp-datatable-edit-picker", children: [_jsx(RatingStars, { "aria-label": `Edit ${col.key}`, value: Number.isNaN(n) ? 0 : n, max: col.maxRating ?? 5, onChange: (next) => commitEdit(row.id, col.key, String(next)) }), isPending && (_jsx(Spinner, { size: 14, className: "crisp-datatable-edit-pending" }))] }), errorHere && (_jsx("span", { className: "crisp-datatable-edit-error", children: errorHere }))] }, col.key));
1875
1967
  }
1876
1968
  // crisp/#761: `editor: "checkbox"` is a THIRD always-
1877
1969
  // live boolean shape, distinct from `switch` --
@@ -1880,13 +1972,13 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
1880
1972
  // toggle. Reuses crisp's own `Checkbox` (already the
1881
1973
  // measured 16px/r6/brand-fill grid checkbox).
1882
1974
  if (col.editable && col.editor === "checkbox") {
1883
- return (_jsxs(TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, className: "crisp-datatable-td--picker", "data-pending": isPending ? "" : undefined, children: [_jsxs("span", { className: "crisp-datatable-edit-picker", children: [_jsx(Checkbox, { "aria-label": `Edit ${col.key}`, checked: value === true || value === "true", disabled: isPending, onChange: (e) => commitEdit(row.id, col.key, String(e.currentTarget.checked)) }), isPending && (_jsx(Spinner, { size: 14, className: "crisp-datatable-edit-pending" }))] }), errorHere && (_jsx("span", { className: "crisp-datatable-edit-error", children: errorHere }))] }, col.key));
1975
+ return (_jsxs(TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, ...rowHeaderProps, className: "crisp-datatable-td--picker", "data-pending": isPending ? "" : undefined, children: [_jsxs("span", { className: "crisp-datatable-edit-picker", children: [_jsx(Checkbox, { "aria-label": `Edit ${col.key}`, checked: value === true || value === "true", disabled: isPending, onChange: (e) => commitEdit(row.id, col.key, String(e.currentTarget.checked)) }), isPending && (_jsx(Spinner, { size: 14, className: "crisp-datatable-edit-pending" }))] }), errorHere && (_jsx("span", { className: "crisp-datatable-edit-error", children: errorHere }))] }, col.key));
1884
1976
  }
1885
1977
  if (isEditing &&
1886
1978
  (col.editor === "select" ||
1887
1979
  col.editor === "reference" ||
1888
1980
  col.editor === "actor")) {
1889
- return (_jsxs(TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, className: "crisp-datatable-td--picker", "data-pending": isPending ? "" : undefined,
1981
+ return (_jsxs(TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, ...rowHeaderProps, className: "crisp-datatable-td--picker", "data-pending": isPending ? "" : undefined,
1890
1982
  // Same as the plain text editor's own cell:
1891
1983
  // opening/using the picker must not also fire
1892
1984
  // the row's onRowClick. Escape is handled by
@@ -1900,7 +1992,7 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
1900
1992
  onClick: (e) => e.stopPropagation(), children: [_jsxs("span", { className: "crisp-datatable-edit-picker", children: [col.editor === "select" ? (_jsx(DataSelectEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v), onCancel: () => setEditing(null) })) : (_jsx(DataReferenceEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v), onCancel: () => setEditing(null) })), isPending && (_jsx(Spinner, { size: 14, className: "crisp-datatable-edit-pending" }))] }), errorHere && (_jsx("span", { className: "crisp-datatable-edit-error", children: errorHere }))] }, col.key));
1901
1993
  }
1902
1994
  if (isEditing) {
1903
- return (_jsxs(TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, onClick: (e) => e.stopPropagation(), children: [_jsx("input", { className: "crisp-datatable-edit",
1995
+ return (_jsxs(TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, ...rowHeaderProps, onClick: (e) => e.stopPropagation(), children: [_jsx("input", { className: "crisp-datatable-edit",
1904
1996
  // biome-ignore lint/a11y/noAutofocus: focus the field the moment it opens
1905
1997
  autoFocus: true, ...editorFor(col), "data-editor": editorFor(col).type, disabled: isPending, defaultValue: col.type === "date"
1906
1998
  ? toDateInputValue(value)
@@ -1959,7 +2051,7 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
1959
2051
  const isExpanded = expandedCell?.id === row.id &&
1960
2052
  expandedCell?.key === col.key;
1961
2053
  const cellContent = (_jsxs(_Fragment, { children: [!isExpanded && rendered, errorHere && (_jsx("span", { className: "crisp-datatable-edit-error", children: errorHere })), isExpanded && (_jsx("div", { className: "crisp-datatable-cell-overlay", onClick: (e) => e.stopPropagation(), children: rendered }))] }));
1962
- return (_jsx(ContextMenu, { asChild: true, variant: "dark", menuLabel: cellMenuLabel, items: cellMenuItems(row, col), children: _jsx(TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, className: col.editable
2054
+ return (_jsx(ContextMenu, { asChild: true, variant: "dark", menuLabel: cellMenuLabel, items: cellMenuItems(row, col), children: _jsx(TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, ...rowHeaderProps, className: col.editable
1963
2055
  ? "crisp-datatable-editable"
1964
2056
  : undefined, "data-selected": selCell?.id === row.id && selCell?.key === col.key
1965
2057
  ? ""
@@ -122,6 +122,15 @@ export function ResizablePanel({ side = "left", size, defaultSize = 280, onSizeC
122
122
  const finalWidth = rect ? Math.round(rect.width) : width;
123
123
  commitSize(finalWidth);
124
124
  }
125
+ // crisp/#795: double-click resets to `defaultSize`, confirmed live on
126
+ // Attio's own record-page divider (dragged 722px -> 924px, double-click
127
+ // snapped it back to exactly 722px -- its own default/initial width, not
128
+ // some other fixed value). Reuses `commitSize`, the same commit path a
129
+ // drag already uses, so persistence (`storageKey`) picks up the reset the
130
+ // same way it picks up a drag -- no separate reset-specific write.
131
+ function onHandleDoubleClick() {
132
+ commitSize(defaultSize);
133
+ }
125
134
  const maxStyle = max == null ? undefined : typeof max === "number" ? `${max}px` : max;
126
135
  const panelSizeStyle = {
127
136
  width,
@@ -130,6 +139,6 @@ export function ResizablePanel({ side = "left", size, defaultSize = 280, onSizeC
130
139
  };
131
140
  return (_jsxs("div", { ref: containerRef, className: resizablepanelRecipe({ className }), ...rest, children: [_jsx("div", { className: "crisp-resizablepanel-spacer", style: floating ? { width: 0 } : { width, minWidth: min, maxWidth: maxStyle } }), _jsxs("div", { ref: panelRef, className: "crisp-resizablepanel-panel", "data-side": side, "data-floating": floating ? "true" : "false", style: panelSizeStyle, children: [_jsx("div", { className: "crisp-resizablepanel-inner", children: children }), _jsx("div", { className: handleVariant === "column"
132
141
  ? "crisp-resizablepanel-handle crisp-resizablepanel-handle--column"
133
- : "crisp-resizablepanel-handle", "data-side": side, "data-dragging": dragging ? "true" : undefined, "data-handle": "", onPointerDown: onHandlePointerDown, onPointerMove: onHandlePointerMove, onPointerUp: onHandlePointerUp, children: handleVariant === "column" && (_jsx("span", { className: "crisp-resizablepanel-handle-inner", "aria-hidden": "true" })) })] })] }));
142
+ : "crisp-resizablepanel-handle", "data-side": side, "data-dragging": dragging ? "true" : undefined, "data-handle": "", onPointerDown: onHandlePointerDown, onPointerMove: onHandlePointerMove, onPointerUp: onHandlePointerUp, onDoubleClick: onHandleDoubleClick, children: handleVariant === "column" && (_jsx("span", { className: "crisp-resizablepanel-handle-inner", "aria-hidden": "true" })) })] })] }));
134
143
  }
135
144
  ResizablePanel.displayName = "ResizablePanel";
@@ -28,7 +28,14 @@ export const TableRow = React.forwardRef((props, ref) => _jsx("tr", { ref: ref,
28
28
  TableRow.displayName = "TableRow";
29
29
  export const TableHead = React.forwardRef(({ align, ...props }, ref) => _jsx("th", { ref: ref, "data-align": align, ...props }));
30
30
  TableHead.displayName = "TableHead";
31
- export const TableCell = React.forwardRef(({ align, ...props }, ref) => _jsx("td", { ref: ref, "data-align": align, ...props }));
31
+ export const TableCell = React.forwardRef(({ align, as, ...props }, ref) => {
32
+ // Cast to "td", same as RecordsTable's own dynamic-tag precedent --
33
+ // `props` (which may carry `scope`) would otherwise need to satisfy
34
+ // both <td> and <th>'s JSX attribute types at once for a bare union
35
+ // element name.
36
+ const Cell = (as ?? "td");
37
+ return _jsx(Cell, { ref: ref, "data-align": align, ...props });
38
+ });
32
39
  TableCell.displayName = "TableCell";
33
40
  export const TableCaption = React.forwardRef((props, ref) => _jsx("caption", { ref: ref, ...props }));
34
41
  TableCaption.displayName = "TableCaption";