@crispy-seed/data-table 0.12.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1340,13 +1340,15 @@ const EMPTY_RECORD = {};
1340
1340
  })), [
1341
1341
  setColumnWidths
1342
1342
  ]);
1343
- const moveColumn = React.useCallback((key, direction)=>setRawOrder((current)=>moveColumnKey(current ?? columnKeys, key, direction)), [
1343
+ const moveColumn = React.useCallback((key, direction)=>setRawOrder((current)=>moveColumnKey(reconcileColumnOrder(current ?? columnKeys, columnKeys, actionsColumnKey), key, direction)), [
1344
1344
  setRawOrder,
1345
- columnKeys
1345
+ columnKeys,
1346
+ actionsColumnKey
1346
1347
  ]);
1347
- const reorderColumn = React.useCallback((dragKey, dropKey)=>setRawOrder((current)=>reorderColumnKey(current ?? columnKeys, dragKey, dropKey)), [
1348
+ const reorderColumn = React.useCallback((dragKey, dropKey)=>setRawOrder((current)=>reorderColumnKey(reconcileColumnOrder(current ?? columnKeys, columnKeys, actionsColumnKey), dragKey, dropKey)), [
1348
1349
  setRawOrder,
1349
- columnKeys
1350
+ columnKeys,
1351
+ actionsColumnKey
1350
1352
  ]);
1351
1353
  // -- drag-to-reorder ---------------------------------------------------------------
1352
1354
  // Live state only (which column is currently being dragged) -- the styled layer's
@@ -1514,11 +1516,11 @@ function sameCell(a, id, key) {
1514
1516
  key
1515
1517
  });
1516
1518
  result.then(()=>{
1517
- setPendingCell(null);
1518
- setEditingCell(null);
1519
+ setPendingCell((c)=>sameCell(c, id, key) ? null : c);
1520
+ setEditingCell((c)=>sameCell(c, id, key) ? null : c);
1519
1521
  }, (err)=>{
1520
- setPendingCell(null);
1521
- setEditingCell(null);
1522
+ setPendingCell((c)=>sameCell(c, id, key) ? null : c);
1523
+ setEditingCell((c)=>sameCell(c, id, key) ? null : c);
1522
1524
  setEditError({
1523
1525
  id,
1524
1526
  key,
@@ -2444,9 +2446,20 @@ DataTableCell$1.displayName = "DataTableCell";
2444
2446
 
2445
2447
  const EMPTY_VIEWS = [];
2446
2448
  function readStoredViews(storageKey) {
2447
- if (!storageKey || typeof window === "undefined") return EMPTY_VIEWS;
2449
+ if (!storageKey || typeof window === "undefined") return null;
2448
2450
  try {
2449
- return deserializeDataTableViews(window.localStorage.getItem(storageKey));
2451
+ const raw = window.localStorage.getItem(storageKey);
2452
+ if (raw === null) return null;
2453
+ // Only a well-formed array counts as "stored" -- `[]` is a real answer (the user
2454
+ // deleted every view), whereas garbage/non-array JSON is treated as absent so the
2455
+ // defaults still seed.
2456
+ let parsed;
2457
+ try {
2458
+ parsed = JSON.parse(raw);
2459
+ } catch {
2460
+ return null;
2461
+ }
2462
+ return Array.isArray(parsed) ? deserializeDataTableViews(raw) : null;
2450
2463
  } catch {
2451
2464
  // A private-browsing tab / blocked storage throws on access, not just on quota --
2452
2465
  // this hook degrades to in-memory-only rather than crash the table over it.
@@ -2484,7 +2497,7 @@ function readStoredViews(storageKey) {
2484
2497
  // Nothing in storage yet (first run, or no `storageKey` at all) -- seed from
2485
2498
  // `defaultViews` instead of an empty list, matching every other hook's own
2486
2499
  // `defaultProp` semantics.
2487
- initialViewsRef.current = stored.length > 0 ? stored : defaultViews;
2500
+ initialViewsRef.current = stored !== null ? stored : defaultViews;
2488
2501
  }
2489
2502
  }
2490
2503
  const [views = EMPTY_VIEWS, setViews] = useControllableState$1({
@@ -4480,6 +4493,10 @@ const { ClassNamesProvider, withContext: withContext$1, useClassNames } = create
4480
4493
  });
4481
4494
  }
4482
4495
 
4496
+ /** Attribute on the floating-ui-portaled option list of the `select`/`reference`/`actor`
4497
+ * editors. The portal lands in `document.body`, outside the cell and its expand overlay,
4498
+ * so `body-cells.tsx`'s outside-click detector checks for this marker to avoid reading an
4499
+ * option click as "outside" and cancelling the edit before it commits. */ const EDITOR_PORTAL_ATTR = "data-datatable-editor-portal";
4483
4500
  /** The raw value a live editor reads, distinct from `DataTableColumn.value` (the
4484
4501
  * accessor sort/search/filter/export share): `editValue` returns whatever TYPE the
4485
4502
  * editor itself expects -- a real `boolean` for `editor: "switch"`/`type:
@@ -4628,6 +4645,7 @@ function SelectEditor({ column, value, pending, onCommit, onCancel }) {
4628
4645
  ]
4629
4646
  }),
4630
4647
  /*#__PURE__*/ jsxRuntime.jsx(SelectPositioner, {
4648
+ [EDITOR_PORTAL_ATTR]: "",
4631
4649
  children: /*#__PURE__*/ jsxRuntime.jsxs(SelectContent, {
4632
4650
  children: [
4633
4651
  searchable && /*#__PURE__*/ jsxRuntime.jsx(SelectSearchField, {
@@ -4703,6 +4721,7 @@ function ReferenceEditor({ column, value, pending, onCommit, onCancel }) {
4703
4721
  ]
4704
4722
  }),
4705
4723
  /*#__PURE__*/ jsxRuntime.jsx(SelectPositioner, {
4724
+ [EDITOR_PORTAL_ATTR]: "",
4706
4725
  children: /*#__PURE__*/ jsxRuntime.jsxs(SelectContent, {
4707
4726
  children: [
4708
4727
  /*#__PURE__*/ jsxRuntime.jsx(SelectSearchField, {
@@ -5318,8 +5337,14 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
5318
5337
  function isInside(target) {
5319
5338
  return (cellRef.current?.contains(target) ?? false) || (popoverRef.current?.contains(target) ?? false) || (expandOverlayRef.current?.contains(target) ?? false);
5320
5339
  }
5340
+ // The select/reference/actor editor's option list is portaled to `document.body` by
5341
+ // floating-ui, outside both `cellRef` and `expandOverlayRef`; `editors.tsx` marks its
5342
+ // positioner with `EDITOR_PORTAL_ATTR`, so a click on an option is not an outside click.
5343
+ function isInsideEditorPortal(target) {
5344
+ return target instanceof Element && target.closest(`[${EDITOR_PORTAL_ATTR}]`) != null;
5345
+ }
5321
5346
  function handlePointerDown(event) {
5322
- if (!isInside(event.target)) {
5347
+ if (!isInside(event.target) && !isInsideEditorPortal(event.target)) {
5323
5348
  setOverlayOpen(false);
5324
5349
  setPopoverStyle(null);
5325
5350
  closePeek();
@@ -11193,7 +11218,13 @@ const toDateOrNull = (v)=>{
11193
11218
  return f.op === "before" ? d < cd : d > cd;
11194
11219
  }
11195
11220
  const t = asText(raw).toLowerCase();
11196
- if (f.op === "isanyof") return (f.values ?? []).some((v)=>v.toLowerCase() === t);
11221
+ if (f.op === "isanyof") {
11222
+ const wanted = (f.values ?? []).map((v)=>v.toLowerCase());
11223
+ const cells = Array.isArray(raw) ? raw.map((x)=>asText(x).toLowerCase()) : [
11224
+ t
11225
+ ];
11226
+ return cells.some((c)=>wanted.includes(c));
11227
+ }
11197
11228
  const v = f.value.trim().toLowerCase();
11198
11229
  if (f.op === "contains") return t.includes(v);
11199
11230
  if (f.op === "doesnotcontain") return !t.includes(v);
package/dist/index.js CHANGED
@@ -1321,13 +1321,15 @@ const EMPTY_RECORD = {};
1321
1321
  })), [
1322
1322
  setColumnWidths
1323
1323
  ]);
1324
- const moveColumn = useCallback((key, direction)=>setRawOrder((current)=>moveColumnKey(current ?? columnKeys, key, direction)), [
1324
+ const moveColumn = useCallback((key, direction)=>setRawOrder((current)=>moveColumnKey(reconcileColumnOrder(current ?? columnKeys, columnKeys, actionsColumnKey), key, direction)), [
1325
1325
  setRawOrder,
1326
- columnKeys
1326
+ columnKeys,
1327
+ actionsColumnKey
1327
1328
  ]);
1328
- const reorderColumn = useCallback((dragKey, dropKey)=>setRawOrder((current)=>reorderColumnKey(current ?? columnKeys, dragKey, dropKey)), [
1329
+ const reorderColumn = useCallback((dragKey, dropKey)=>setRawOrder((current)=>reorderColumnKey(reconcileColumnOrder(current ?? columnKeys, columnKeys, actionsColumnKey), dragKey, dropKey)), [
1329
1330
  setRawOrder,
1330
- columnKeys
1331
+ columnKeys,
1332
+ actionsColumnKey
1331
1333
  ]);
1332
1334
  // -- drag-to-reorder ---------------------------------------------------------------
1333
1335
  // Live state only (which column is currently being dragged) -- the styled layer's
@@ -1495,11 +1497,11 @@ function sameCell(a, id, key) {
1495
1497
  key
1496
1498
  });
1497
1499
  result.then(()=>{
1498
- setPendingCell(null);
1499
- setEditingCell(null);
1500
+ setPendingCell((c)=>sameCell(c, id, key) ? null : c);
1501
+ setEditingCell((c)=>sameCell(c, id, key) ? null : c);
1500
1502
  }, (err)=>{
1501
- setPendingCell(null);
1502
- setEditingCell(null);
1503
+ setPendingCell((c)=>sameCell(c, id, key) ? null : c);
1504
+ setEditingCell((c)=>sameCell(c, id, key) ? null : c);
1503
1505
  setEditError({
1504
1506
  id,
1505
1507
  key,
@@ -2425,9 +2427,20 @@ DataTableCell$1.displayName = "DataTableCell";
2425
2427
 
2426
2428
  const EMPTY_VIEWS = [];
2427
2429
  function readStoredViews(storageKey) {
2428
- if (!storageKey || typeof window === "undefined") return EMPTY_VIEWS;
2430
+ if (!storageKey || typeof window === "undefined") return null;
2429
2431
  try {
2430
- return deserializeDataTableViews(window.localStorage.getItem(storageKey));
2432
+ const raw = window.localStorage.getItem(storageKey);
2433
+ if (raw === null) return null;
2434
+ // Only a well-formed array counts as "stored" -- `[]` is a real answer (the user
2435
+ // deleted every view), whereas garbage/non-array JSON is treated as absent so the
2436
+ // defaults still seed.
2437
+ let parsed;
2438
+ try {
2439
+ parsed = JSON.parse(raw);
2440
+ } catch {
2441
+ return null;
2442
+ }
2443
+ return Array.isArray(parsed) ? deserializeDataTableViews(raw) : null;
2431
2444
  } catch {
2432
2445
  // A private-browsing tab / blocked storage throws on access, not just on quota --
2433
2446
  // this hook degrades to in-memory-only rather than crash the table over it.
@@ -2465,7 +2478,7 @@ function readStoredViews(storageKey) {
2465
2478
  // Nothing in storage yet (first run, or no `storageKey` at all) -- seed from
2466
2479
  // `defaultViews` instead of an empty list, matching every other hook's own
2467
2480
  // `defaultProp` semantics.
2468
- initialViewsRef.current = stored.length > 0 ? stored : defaultViews;
2481
+ initialViewsRef.current = stored !== null ? stored : defaultViews;
2469
2482
  }
2470
2483
  }
2471
2484
  const [views = EMPTY_VIEWS, setViews] = useControllableState$1({
@@ -4461,6 +4474,10 @@ const { ClassNamesProvider, withContext: withContext$1, useClassNames } = create
4461
4474
  });
4462
4475
  }
4463
4476
 
4477
+ /** Attribute on the floating-ui-portaled option list of the `select`/`reference`/`actor`
4478
+ * editors. The portal lands in `document.body`, outside the cell and its expand overlay,
4479
+ * so `body-cells.tsx`'s outside-click detector checks for this marker to avoid reading an
4480
+ * option click as "outside" and cancelling the edit before it commits. */ const EDITOR_PORTAL_ATTR = "data-datatable-editor-portal";
4464
4481
  /** The raw value a live editor reads, distinct from `DataTableColumn.value` (the
4465
4482
  * accessor sort/search/filter/export share): `editValue` returns whatever TYPE the
4466
4483
  * editor itself expects -- a real `boolean` for `editor: "switch"`/`type:
@@ -4609,6 +4626,7 @@ function SelectEditor({ column, value, pending, onCommit, onCancel }) {
4609
4626
  ]
4610
4627
  }),
4611
4628
  /*#__PURE__*/ jsx(SelectPositioner, {
4629
+ [EDITOR_PORTAL_ATTR]: "",
4612
4630
  children: /*#__PURE__*/ jsxs(SelectContent, {
4613
4631
  children: [
4614
4632
  searchable && /*#__PURE__*/ jsx(SelectSearchField, {
@@ -4684,6 +4702,7 @@ function ReferenceEditor({ column, value, pending, onCommit, onCancel }) {
4684
4702
  ]
4685
4703
  }),
4686
4704
  /*#__PURE__*/ jsx(SelectPositioner, {
4705
+ [EDITOR_PORTAL_ATTR]: "",
4687
4706
  children: /*#__PURE__*/ jsxs(SelectContent, {
4688
4707
  children: [
4689
4708
  /*#__PURE__*/ jsx(SelectSearchField, {
@@ -5299,8 +5318,14 @@ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyF
5299
5318
  function isInside(target) {
5300
5319
  return (cellRef.current?.contains(target) ?? false) || (popoverRef.current?.contains(target) ?? false) || (expandOverlayRef.current?.contains(target) ?? false);
5301
5320
  }
5321
+ // The select/reference/actor editor's option list is portaled to `document.body` by
5322
+ // floating-ui, outside both `cellRef` and `expandOverlayRef`; `editors.tsx` marks its
5323
+ // positioner with `EDITOR_PORTAL_ATTR`, so a click on an option is not an outside click.
5324
+ function isInsideEditorPortal(target) {
5325
+ return target instanceof Element && target.closest(`[${EDITOR_PORTAL_ATTR}]`) != null;
5326
+ }
5302
5327
  function handlePointerDown(event) {
5303
- if (!isInside(event.target)) {
5328
+ if (!isInside(event.target) && !isInsideEditorPortal(event.target)) {
5304
5329
  setOverlayOpen(false);
5305
5330
  setPopoverStyle(null);
5306
5331
  closePeek();
@@ -11174,7 +11199,13 @@ const toDateOrNull = (v)=>{
11174
11199
  return f.op === "before" ? d < cd : d > cd;
11175
11200
  }
11176
11201
  const t = asText(raw).toLowerCase();
11177
- if (f.op === "isanyof") return (f.values ?? []).some((v)=>v.toLowerCase() === t);
11202
+ if (f.op === "isanyof") {
11203
+ const wanted = (f.values ?? []).map((v)=>v.toLowerCase());
11204
+ const cells = Array.isArray(raw) ? raw.map((x)=>asText(x).toLowerCase()) : [
11205
+ t
11206
+ ];
11207
+ return cells.some((c)=>wanted.includes(c));
11208
+ }
11178
11209
  const v = f.value.trim().toLowerCase();
11179
11210
  if (f.op === "contains") return t.includes(v);
11180
11211
  if (f.op === "doesnotcontain") return !t.includes(v);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crispy-seed/data-table",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Self-contained, publishable build of seed-design's Attio-measured DataTable, ResizablePanel and RecordPreview — one install, one CSS import.",
5
5
  "repository": {
6
6
  "type": "git",