hs-uix 1.6.5 → 1.7.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.
@@ -1,6 +1,135 @@
1
1
  // packages/datatable/src/DataTable.jsx
2
- import React, { useState, useMemo, useEffect, useCallback, useRef } from "react";
2
+ import React, { useState, useMemo, useEffect as useEffect2, useCallback as useCallback2, useRef as useRef2 } from "react";
3
+
4
+ // src/utils/query.js
3
5
  import Fuse from "fuse.js";
6
+ var getEmptyFilterValue = (filter) => {
7
+ const type = filter.type || "select";
8
+ if (type === "multiselect") return [];
9
+ if (type === "dateRange") return { from: null, to: null };
10
+ return "";
11
+ };
12
+ var isFilterActive = (filter, value) => {
13
+ const type = filter.type || "select";
14
+ if (type === "multiselect") return Array.isArray(value) && value.length > 0;
15
+ if (type === "dateRange") return value && (value.from || value.to);
16
+ return !!value;
17
+ };
18
+ var formatDateChip = (dateObj) => {
19
+ if (!dateObj) return "";
20
+ const { year, month, date } = dateObj;
21
+ return new Intl.DateTimeFormat("en-US", {
22
+ month: "short",
23
+ day: "numeric",
24
+ year: "numeric"
25
+ }).format(new Date(year, month, date));
26
+ };
27
+ var dateToTimestamp = (dateObj) => {
28
+ if (!dateObj) return null;
29
+ return new Date(dateObj.year, dateObj.month, dateObj.date).getTime();
30
+ };
31
+ var toStableKey = (value) => {
32
+ try {
33
+ return JSON.stringify(value);
34
+ } catch (_error) {
35
+ return String(value);
36
+ }
37
+ };
38
+ var filterRows = (rows, filters, values) => {
39
+ let result = rows;
40
+ for (const filter of filters || []) {
41
+ const value = values[filter.name];
42
+ if (!isFilterActive(filter, value)) continue;
43
+ const type = filter.type || "select";
44
+ if (filter.filterFn) {
45
+ result = result.filter((row) => filter.filterFn(row, value));
46
+ } else if (type === "multiselect") {
47
+ result = result.filter((row) => value.includes(row[filter.name]));
48
+ } else if (type === "dateRange") {
49
+ const fromTs = dateToTimestamp(value.from);
50
+ const toTs = value.to ? dateToTimestamp(value.to) + 864e5 - 1 : null;
51
+ result = result.filter((row) => {
52
+ const rowTs = new Date(row[filter.name]).getTime();
53
+ if (Number.isNaN(rowTs)) return false;
54
+ if (fromTs && rowTs < fromTs) return false;
55
+ if (toTs && rowTs > toTs) return false;
56
+ return true;
57
+ });
58
+ } else {
59
+ result = result.filter((row) => row[filter.name] === value);
60
+ }
61
+ }
62
+ return result;
63
+ };
64
+ var searchRows = (rows, term, fields, opts = {}) => {
65
+ const { fuzzy = false, fuzzyOptions } = opts;
66
+ const t = String(term ?? "").toLowerCase();
67
+ if (!t || !fields || fields.length === 0) return rows;
68
+ if (fuzzy) {
69
+ const fuse = new Fuse(rows, {
70
+ keys: fields,
71
+ threshold: 0.4,
72
+ distance: 100,
73
+ ignoreLocation: true,
74
+ ...fuzzyOptions
75
+ });
76
+ return fuse.search(t).map((r) => r.item);
77
+ }
78
+ return rows.filter(
79
+ (row) => fields.some((field) => {
80
+ const val = row[field];
81
+ return val && String(val).toLowerCase().includes(t);
82
+ })
83
+ );
84
+ };
85
+
86
+ // src/utils/interactionHooks.js
87
+ import { useRef, useEffect, useCallback } from "react";
88
+ import { useDebounce } from "@hubspot/ui-extensions";
89
+ var useDebouncedDispatch = (value, debounceMs, dispatch) => {
90
+ const debounced = useDebounce(value, debounceMs > 0 ? debounceMs : 300);
91
+ const pendingRef = useRef(null);
92
+ useEffect(() => {
93
+ if (debounceMs <= 0) return;
94
+ if (pendingRef.current == null) return;
95
+ if (debounced !== pendingRef.current) return;
96
+ const next = pendingRef.current;
97
+ pendingRef.current = null;
98
+ dispatch(next);
99
+ }, [debounceMs, debounced, dispatch]);
100
+ return useCallback(
101
+ (next) => {
102
+ if (debounceMs > 0) {
103
+ pendingRef.current = next;
104
+ } else {
105
+ pendingRef.current = null;
106
+ dispatch(next);
107
+ }
108
+ },
109
+ [debounceMs, dispatch]
110
+ );
111
+ };
112
+ var useSelectionReset = ({ resetKey, enabled, isControlled, clearSelection }) => {
113
+ const ref = useRef("");
114
+ useEffect(() => {
115
+ if (!enabled || isControlled) {
116
+ ref.current = resetKey;
117
+ return;
118
+ }
119
+ if (ref.current && ref.current !== resetKey) {
120
+ clearSelection();
121
+ }
122
+ ref.current = resetKey;
123
+ }, [resetKey, enabled, isControlled, clearSelection]);
124
+ };
125
+
126
+ // packages/datatable/src/editValidation.js
127
+ var editValidationError = (result) => {
128
+ if (result === true || result === void 0 || result === null) return null;
129
+ return typeof result === "string" ? result : "Invalid value";
130
+ };
131
+
132
+ // packages/datatable/src/DataTable.jsx
4
133
  import {
5
134
  Box,
6
135
  Button,
@@ -13,7 +142,6 @@ import {
13
142
  Icon,
14
143
  Input,
15
144
  Link,
16
- LoadingSpinner,
17
145
  MultiSelect,
18
146
  NumberInput,
19
147
  SearchInput,
@@ -27,25 +155,13 @@ import {
27
155
  TableHeader,
28
156
  TableRow,
29
157
  Tag,
158
+ Tile,
30
159
  Text,
31
160
  TextArea,
32
161
  TimeInput,
33
162
  Toggle,
34
163
  Tooltip
35
164
  } from "@hubspot/ui-extensions";
36
- var formatDateChip = (dateObj) => {
37
- if (!dateObj) return "";
38
- const { year, month, date } = dateObj;
39
- return new Intl.DateTimeFormat("en-US", {
40
- month: "short",
41
- day: "numeric",
42
- year: "numeric"
43
- }).format(new Date(year, month, date));
44
- };
45
- var dateToTimestamp = (dateObj) => {
46
- if (!dateObj) return null;
47
- return new Date(dateObj.year, dateObj.month, dateObj.date).getTime();
48
- };
49
165
  var NARROW_EDIT_TYPES = /* @__PURE__ */ new Set(["checkbox", "toggle"]);
50
166
  var DATE_PATTERN = /^\d{4}[-/]\d{2}[-/]\d{2}/;
51
167
  var BOOL_VALUES = /* @__PURE__ */ new Set(["true", "false", "yes", "no", "0", "1"]);
@@ -71,13 +187,6 @@ var serializeSortState = (sortState) => {
71
187
  if (!activeField) return null;
72
188
  return { field: activeField, direction: sortState[activeField] };
73
189
  };
74
- var toStableKey = (value) => {
75
- try {
76
- return JSON.stringify(value);
77
- } catch (_error) {
78
- return String(value);
79
- }
80
- };
81
190
  var computeAutoWidths = (columns, data) => {
82
191
  if (!data || data.length === 0) return {};
83
192
  const sample = data.slice(0, 50);
@@ -129,12 +238,6 @@ var computeAutoWidths = (columns, data) => {
129
238
  });
130
239
  return results;
131
240
  };
132
- var getEmptyFilterValue = (filter) => {
133
- const type = filter.type || "select";
134
- if (type === "multiselect") return [];
135
- if (type === "dateRange") return { from: null, to: null };
136
- return "";
137
- };
138
241
  var BOOLEAN_SELECT_OPTIONS = [
139
242
  { label: "Yes", value: true },
140
243
  { label: "No", value: false }
@@ -145,12 +248,6 @@ var resolveEditOptions = (col, data) => {
145
248
  if (sample && typeof sample[col.field] === "boolean") return BOOLEAN_SELECT_OPTIONS;
146
249
  return [];
147
250
  };
148
- var isFilterActive = (filter, value) => {
149
- const type = filter.type || "select";
150
- if (type === "multiselect") return Array.isArray(value) && value.length > 0;
151
- if (type === "dateRange") return value && (value.from || value.to);
152
- return !!value;
153
- };
154
251
  var DataTable = ({
155
252
  // Data
156
253
  data,
@@ -169,8 +266,8 @@ var DataTable = ({
169
266
  filters = [],
170
267
  showFilterBadges = true,
171
268
  // show active filter chips/badges
172
- showClearFiltersButton = true,
173
- // show "Clear all" filters reset button
269
+ showClearFiltersButton,
270
+ // show "Clear all" reset button; defaults to showFilterBadges when omitted
174
271
  filterInlineLimit = 2,
175
272
  // number of filters shown inline before overflow
176
273
  // Pagination
@@ -320,11 +417,11 @@ var DataTable = ({
320
417
  const [internalSortState, setInternalSortState] = useState(initialSortState);
321
418
  const [currentPage, setCurrentPage] = useState(1);
322
419
  const [showMoreFilters, setShowMoreFilters] = useState(false);
323
- const lastAppliedSearchRef = useRef(
420
+ const lastAppliedSearchRef = useRef2(
324
421
  serverSide && searchValue != null ? searchValue : ""
325
422
  );
326
423
  const searchTerm = serverSide && searchValue != null ? searchValue : internalSearchTerm;
327
- useEffect(() => {
424
+ useEffect2(() => {
328
425
  if (!serverSide || searchValue == null) return;
329
426
  if (searchValue === lastAppliedSearchRef.current) return;
330
427
  lastAppliedSearchRef.current = searchValue;
@@ -337,14 +434,13 @@ var DataTable = ({
337
434
  );
338
435
  const sortState = serverSide && externalSort != null ? externalSortState : internalSortState;
339
436
  const activePage = serverSide && externalPage != null ? externalPage : currentPage;
340
- useEffect(() => {
437
+ useEffect2(() => {
341
438
  if (!serverSide) setCurrentPage(1);
342
439
  }, [internalSearchTerm, internalFilterValues, internalSortState, serverSide]);
343
- const debounceRef = useRef(null);
344
- const fireSearchCallback = useCallback((term) => {
440
+ const fireSearchCallback = useCallback2((term) => {
345
441
  if (serverSide && onSearchChange) onSearchChange(term);
346
442
  }, [serverSide, onSearchChange]);
347
- const fireParamsChange = useCallback((overrides) => {
443
+ const fireParamsChange = useCallback2((overrides) => {
348
444
  if (!onParamsChange) return;
349
445
  const nextSortState = overrides.sort != null ? normalizeSortState(columns, overrides.sort) : sortState;
350
446
  onParamsChange({
@@ -354,38 +450,35 @@ var DataTable = ({
354
450
  page: overrides.page != null ? overrides.page : activePage
355
451
  });
356
452
  }, [onParamsChange, columns, searchTerm, filterValues, sortState, activePage]);
357
- const resetPage = useCallback(() => {
453
+ const resetPage = useCallback2(() => {
358
454
  if (resetPageOnChange) {
359
455
  setCurrentPage(1);
360
456
  if (serverSide && onPageChange) onPageChange(1);
361
457
  }
362
458
  }, [resetPageOnChange, serverSide, onPageChange]);
363
- const handleSearchChange = useCallback((term) => {
459
+ const dispatchSearchChange = useCallback2((term) => {
460
+ lastAppliedSearchRef.current = term;
461
+ fireSearchCallback(term);
462
+ fireParamsChange({ search: term, page: resetPageOnChange ? 1 : void 0 });
463
+ }, [fireSearchCallback, fireParamsChange, resetPageOnChange]);
464
+ const dispatchSearchDebounced = useDebouncedDispatch(
465
+ internalSearchTerm,
466
+ searchDebounce,
467
+ dispatchSearchChange
468
+ );
469
+ const handleSearchChange = useCallback2((term) => {
364
470
  setInternalSearchTerm(term);
365
471
  resetPage();
366
- const dispatch = () => {
367
- lastAppliedSearchRef.current = term;
368
- fireSearchCallback(term);
369
- fireParamsChange({ search: term, page: resetPageOnChange ? 1 : void 0 });
370
- };
371
- if (searchDebounce > 0) {
372
- if (debounceRef.current) clearTimeout(debounceRef.current);
373
- debounceRef.current = setTimeout(dispatch, searchDebounce);
374
- } else {
375
- dispatch();
376
- }
377
- }, [searchDebounce, fireSearchCallback, fireParamsChange, resetPage, resetPageOnChange]);
378
- useEffect(() => () => {
379
- if (debounceRef.current) clearTimeout(debounceRef.current);
380
- }, []);
381
- const handleFilterChange = useCallback((name, value) => {
472
+ dispatchSearchDebounced(term);
473
+ }, [dispatchSearchDebounced, resetPage]);
474
+ const handleFilterChange = useCallback2((name, value) => {
382
475
  const next = { ...filterValues, [name]: value };
383
476
  setInternalFilterValues(next);
384
477
  if (serverSide && onFilterChange) onFilterChange(next);
385
478
  resetPage();
386
479
  fireParamsChange({ filters: next, page: resetPageOnChange ? 1 : void 0 });
387
480
  }, [filterValues, serverSide, onFilterChange, fireParamsChange, resetPage, resetPageOnChange]);
388
- const handleSortChange = useCallback((field) => {
481
+ const handleSortChange = useCallback2((field) => {
389
482
  const current = sortState[field] || "none";
390
483
  const nextDirection = current === "none" ? "ascending" : current === "ascending" ? "descending" : "none";
391
484
  const reset = {};
@@ -398,55 +491,19 @@ var DataTable = ({
398
491
  resetPage();
399
492
  fireParamsChange({ sort: next, page: resetPageOnChange ? 1 : void 0 });
400
493
  }, [sortState, columns, serverSide, onSortChange, fireParamsChange, resetPage, resetPageOnChange]);
401
- const handlePageChange = useCallback((page) => {
494
+ const handlePageChange = useCallback2((page) => {
402
495
  setCurrentPage(page);
403
496
  if (serverSide && onPageChange) onPageChange(page);
404
497
  fireParamsChange({ page });
405
498
  }, [serverSide, onPageChange, fireParamsChange]);
406
499
  const filteredData = useMemo(() => {
407
500
  if (serverSide) return data;
408
- let result = data;
409
- filters.forEach((filter) => {
410
- const value = filterValues[filter.name];
411
- if (!isFilterActive(filter, value)) return;
412
- const type = filter.type || "select";
413
- if (filter.filterFn) {
414
- result = result.filter((row) => filter.filterFn(row, value));
415
- } else if (type === "multiselect") {
416
- result = result.filter((row) => value.includes(row[filter.name]));
417
- } else if (type === "dateRange") {
418
- const fromTs = dateToTimestamp(value.from);
419
- const toTs = value.to ? dateToTimestamp(value.to) + 864e5 - 1 : null;
420
- result = result.filter((row) => {
421
- const rowTs = new Date(row[filter.name]).getTime();
422
- if (Number.isNaN(rowTs)) return false;
423
- if (fromTs && rowTs < fromTs) return false;
424
- if (toTs && rowTs > toTs) return false;
425
- return true;
426
- });
427
- } else {
428
- result = result.filter((row) => row[filter.name] === value);
429
- }
430
- });
501
+ let result = filterRows(data, filters, filterValues);
431
502
  if (searchTerm && searchFields.length > 0) {
432
- if (fuzzySearch) {
433
- const fuse = new Fuse(result, {
434
- keys: searchFields,
435
- threshold: 0.4,
436
- distance: 100,
437
- ignoreLocation: true,
438
- ...fuzzyOptions
439
- });
440
- result = fuse.search(searchTerm).map((r) => r.item);
441
- } else {
442
- const term = searchTerm.toLowerCase();
443
- result = result.filter(
444
- (row) => searchFields.some((field) => {
445
- const val = row[field];
446
- return val && String(val).toLowerCase().includes(term);
447
- })
448
- );
449
- }
503
+ result = searchRows(result, searchTerm, searchFields, {
504
+ fuzzy: fuzzySearch,
505
+ fuzzyOptions
506
+ });
450
507
  }
451
508
  return result;
452
509
  }, [data, filterValues, searchTerm, filters, searchFields, serverSide, fuzzySearch, fuzzyOptions]);
@@ -511,7 +568,7 @@ var DataTable = ({
511
568
  }
512
569
  return /* @__PURE__ */ new Set();
513
570
  });
514
- useEffect(() => {
571
+ useEffect2(() => {
515
572
  if (!groupedData) return;
516
573
  const defaultExpanded = (groupBy == null ? void 0 : groupBy.defaultExpanded) !== false;
517
574
  if (defaultExpanded) {
@@ -522,7 +579,7 @@ var DataTable = ({
522
579
  });
523
580
  }
524
581
  }, [groupedData, groupBy]);
525
- const toggleGroup = useCallback((key) => {
582
+ const toggleGroup = useCallback2((key) => {
526
583
  setExpandedGroups((prev) => {
527
584
  const next = new Set(prev);
528
585
  if (next.has(key)) next.delete(key);
@@ -583,7 +640,7 @@ var DataTable = ({
583
640
  });
584
641
  return chips;
585
642
  }, [filterValues, filters]);
586
- const handleFilterRemove = useCallback((key) => {
643
+ const handleFilterRemove = useCallback2((key) => {
587
644
  if (key === "all") {
588
645
  const cleared = {};
589
646
  filters.forEach((f) => {
@@ -619,13 +676,14 @@ var DataTable = ({
619
676
  const resolvedDateFromLabel = (labels == null ? void 0 : labels.dateFrom) || "From";
620
677
  const resolvedDateToLabel = (labels == null ? void 0 : labels.dateTo) || "To";
621
678
  const resolvedLoadingLabel = (labels == null ? void 0 : labels.loading) || `Loading ${pluralLabel}...`;
679
+ const resolvedLoadingMessage = (labels == null ? void 0 : labels.loadingMessage) || "This should only take a moment.";
622
680
  const resolvedErrorTitle = (labels == null ? void 0 : labels.errorTitle) || "Something went wrong.";
623
681
  const resolvedErrorMessage = (labels == null ? void 0 : labels.errorMessage) || "An error occurred while loading data.";
624
682
  const resolvedRetryMessage = (labels == null ? void 0 : labels.retryMessage) || "Please try again.";
683
+ const resolvedShowClearFiltersButton = showClearFiltersButton ?? showFilterBadges;
625
684
  const recordCountLabel = rowCountText ? rowCountText(shownOnPageCount, displayCount) : displayCount === totalDataCount ? `${totalDataCount} ${countLabel(totalDataCount)}` : `${displayCount} of ${totalDataCount} ${countLabel(totalDataCount)}`;
626
685
  const [internalSelectedIds, setInternalSelectedIds] = useState(/* @__PURE__ */ new Set());
627
- const selectionResetRef = useRef("");
628
- useEffect(() => {
686
+ useEffect2(() => {
629
687
  if (externalSelectedIds != null) {
630
688
  setInternalSelectedIds(new Set(externalSelectedIds));
631
689
  }
@@ -642,21 +700,21 @@ var DataTable = ({
642
700
  () => `${selectionQueryKey}::${selectionResetKey == null ? "" : toStableKey(selectionResetKey)}`,
643
701
  [selectionQueryKey, selectionResetKey]
644
702
  );
645
- useEffect(() => {
646
- if (!selectable || externalSelectedIds != null) {
647
- selectionResetRef.current = combinedSelectionResetKey;
648
- return;
649
- }
650
- if (selectionResetRef.current && selectionResetRef.current !== combinedSelectionResetKey) {
651
- setInternalSelectedIds(/* @__PURE__ */ new Set());
652
- }
653
- selectionResetRef.current = combinedSelectionResetKey;
654
- }, [combinedSelectionResetKey, selectable, externalSelectedIds]);
703
+ const clearSelection = useCallback2(() => setInternalSelectedIds(/* @__PURE__ */ new Set()), []);
704
+ useSelectionReset({
705
+ resetKey: combinedSelectionResetKey,
706
+ enabled: selectable,
707
+ isControlled: externalSelectedIds != null,
708
+ clearSelection
709
+ });
655
710
  const selectedIds = externalSelectedIds != null ? new Set(externalSelectedIds) : internalSelectedIds;
656
711
  const showToolbarCount = showRowCount && displayCount > 0 && !(showSelectionBar && selectable && selectedIds.size > 0);
657
- const hasToolbarContent = showSearch && searchFields.length > 0 || filters.length > 0 || activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) || showToolbarCount;
712
+ const hasToolbarLeft = showSearch && searchFields.length > 0 || filters.length > 0 || activeChips.length > 0 && (showFilterBadges || resolvedShowClearFiltersButton);
713
+ const countInTitleRow = !!title && showToolbarCount && !hasToolbarLeft;
714
+ const countInToolbar = showToolbarCount && !countInTitleRow;
715
+ const hasToolbarContent = hasToolbarLeft || countInToolbar;
658
716
  const showRowActionsColumn = !!rowActions && !(hideRowActionsWhenSelectionActive && selectable && selectedIds.size > 0);
659
- const applySelection = useCallback((nextSet) => {
717
+ const applySelection = useCallback2((nextSet) => {
660
718
  if (externalSelectedIds == null) {
661
719
  setInternalSelectedIds(nextSet);
662
720
  }
@@ -672,13 +730,13 @@ var DataTable = ({
672
730
  () => datasetRows.map((row) => row[rowIdField]).filter((id) => id != null),
673
731
  [datasetRows, rowIdField]
674
732
  );
675
- const handleSelectRow = useCallback((rowId, checked) => {
733
+ const handleSelectRow = useCallback2((rowId, checked) => {
676
734
  const next = new Set(selectedIds);
677
735
  if (checked) next.add(rowId);
678
736
  else next.delete(rowId);
679
737
  applySelection(next);
680
738
  }, [selectedIds, applySelection]);
681
- const handleSelectAll = useCallback((checked) => {
739
+ const handleSelectAll = useCallback2((checked) => {
682
740
  const next = new Set(selectedIds);
683
741
  pageRowIds.forEach((id) => {
684
742
  if (checked) next.add(id);
@@ -689,7 +747,7 @@ var DataTable = ({
689
747
  const allVisibleSelected = useMemo(() => {
690
748
  return pageRowIds.length > 0 && pageRowIds.every((id) => selectedIds.has(id));
691
749
  }, [pageRowIds, selectedIds]);
692
- const handleSelectAllRows = useCallback(() => {
750
+ const handleSelectAllRows = useCallback2(() => {
693
751
  const idsToAdd = serverSide ? pageRowIds : allRowIds;
694
752
  const next = new Set(selectedIds);
695
753
  idsToAdd.forEach((id) => next.add(id));
@@ -702,13 +760,13 @@ var DataTable = ({
702
760
  });
703
761
  }
704
762
  }, [serverSide, pageRowIds, allRowIds, selectedIds, applySelection, onSelectAllRequest, totalCount, data.length]);
705
- const handleDeselectAll = useCallback(() => {
763
+ const handleDeselectAll = useCallback2(() => {
706
764
  applySelection(/* @__PURE__ */ new Set());
707
765
  }, [applySelection]);
708
766
  const [editingCell, setEditingCell] = useState(null);
709
767
  const [editValue, setEditValue] = useState(null);
710
768
  const [editError, setEditError] = useState(null);
711
- const startEditing = useCallback((rowId, field, currentValue) => {
769
+ const startEditing = useCallback2((rowId, field, currentValue) => {
712
770
  setEditingCell({ rowId, field });
713
771
  setEditValue(currentValue);
714
772
  setEditError(null);
@@ -717,13 +775,13 @@ var DataTable = ({
717
775
  if (row) onEditStart(row, field, currentValue);
718
776
  }
719
777
  }, [onEditStart, data, rowIdField]);
720
- const commitEdit = useCallback((row, field, value, options = {}) => {
778
+ const commitEdit = useCallback2((row, field, value, options = {}) => {
721
779
  const { keepEditing = false } = options;
722
780
  const col = columns.find((c) => c.field === field);
723
781
  if (col == null ? void 0 : col.editValidate) {
724
- const result = col.editValidate(value, row);
725
- if (result !== true && result !== void 0 && result !== null) {
726
- setEditError(typeof result === "string" ? result : "Invalid value");
782
+ const err = editValidationError(col.editValidate(value, row));
783
+ if (err) {
784
+ setEditError(err);
727
785
  return false;
728
786
  }
729
787
  }
@@ -751,14 +809,7 @@ var DataTable = ({
751
809
  const extra = col.editProps || {};
752
810
  const validate = col.editValidate;
753
811
  const validationProps = validate && editError ? { error: true, validationMessage: editError } : {};
754
- const onInputValidate = validate ? (val) => {
755
- const result = validate(val, row);
756
- if (result !== true && result !== void 0 && result !== null) {
757
- setEditError(typeof result === "string" ? result : "Invalid value");
758
- } else {
759
- setEditError(null);
760
- }
761
- } : void 0;
812
+ const onInputValidate = validate ? (val) => setEditError(editValidationError(validate(val, row))) : void 0;
762
813
  const handleInput = (val) => {
763
814
  setEditValue(val);
764
815
  if (onInputValidate) onInputValidate(val);
@@ -834,9 +885,9 @@ var DataTable = ({
834
885
  const validate = col.editValidate;
835
886
  const fire = (val) => {
836
887
  if (validate) {
837
- const result = validate(val, row);
838
- if (result !== true && result !== void 0 && result !== null) {
839
- setInlineErrors((prev) => ({ ...prev, [cellKey]: typeof result === "string" ? result : "Invalid value" }));
888
+ const err = editValidationError(validate(val, row));
889
+ if (err) {
890
+ setInlineErrors((prev) => ({ ...prev, [cellKey]: err }));
840
891
  return;
841
892
  }
842
893
  setInlineErrors((prev) => {
@@ -851,16 +902,13 @@ var DataTable = ({
851
902
  const cellError = inlineErrors[cellKey];
852
903
  const validationProps = cellError ? { error: true, validationMessage: cellError } : {};
853
904
  const onInputValidate = validate ? (val) => {
854
- const result = validate(val, row);
855
- if (result !== true && result !== void 0 && result !== null) {
856
- setInlineErrors((prev) => ({ ...prev, [cellKey]: typeof result === "string" ? result : "Invalid value" }));
857
- } else {
858
- setInlineErrors((prev) => {
859
- const next = { ...prev };
860
- delete next[cellKey];
861
- return next;
862
- });
863
- }
905
+ const err = editValidationError(validate(val, row));
906
+ setInlineErrors((prev) => {
907
+ if (err) return { ...prev, [cellKey]: err };
908
+ const next = { ...prev };
909
+ delete next[cellKey];
910
+ return next;
911
+ });
864
912
  } : void 0;
865
913
  const emitInput = (val) => {
866
914
  if (onInputValidate) onInputValidate(val);
@@ -1004,7 +1052,7 @@ var DataTable = ({
1004
1052
  }
1005
1053
  );
1006
1054
  };
1007
- return /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "xs" }, title && /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", justify: "between", gap: "sm" }, /* @__PURE__ */ React.createElement(Text, { format: { fontWeight: "demibold" } }, title)), hasToolbarContent && /* @__PURE__ */ React.createElement(Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ React.createElement(Box, { flex: 3 }, /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showSearch && searchFields.length > 0 && /* @__PURE__ */ React.createElement(
1055
+ return /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "xs" }, title && /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", justify: "between", gap: "sm" }, /* @__PURE__ */ React.createElement(Text, { format: { fontWeight: "demibold" } }, title), countInTitleRow && /* @__PURE__ */ React.createElement(Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel)), hasToolbarContent && /* @__PURE__ */ React.createElement(Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ React.createElement(Box, { flex: 3 }, /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showSearch && searchFields.length > 0 && /* @__PURE__ */ React.createElement(
1008
1056
  SearchInput,
1009
1057
  {
1010
1058
  name: "datatable-search",
@@ -1022,7 +1070,7 @@ var DataTable = ({
1022
1070
  /* @__PURE__ */ React.createElement(Icon, { name: "filter", size: "sm" }),
1023
1071
  " ",
1024
1072
  resolvedFiltersButtonLabel
1025
- )), showMoreFilters && filters.length > filterInlineLimit && /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, filters.slice(filterInlineLimit).map(renderFilterControl)), activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) && /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showFilterBadges && activeChips.map((chip) => /* @__PURE__ */ React.createElement(Tag, { key: chip.key, variant: "default", onDelete: () => handleFilterRemove(chip.key) }, chip.label)), showClearFiltersButton && /* @__PURE__ */ React.createElement(
1073
+ )), showMoreFilters && filters.length > filterInlineLimit && /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, filters.slice(filterInlineLimit).map(renderFilterControl)), activeChips.length > 0 && (showFilterBadges || resolvedShowClearFiltersButton) && /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showFilterBadges && activeChips.map((chip) => /* @__PURE__ */ React.createElement(Tag, { key: chip.key, variant: "default", onDelete: () => handleFilterRemove(chip.key) }, chip.label)), resolvedShowClearFiltersButton && /* @__PURE__ */ React.createElement(
1026
1074
  Button,
1027
1075
  {
1028
1076
  variant: "transparent",
@@ -1030,7 +1078,7 @@ var DataTable = ({
1030
1078
  onClick: () => handleFilterRemove("all")
1031
1079
  },
1032
1080
  resolvedClearAllLabel
1033
- )))), showToolbarCount && /* @__PURE__ */ React.createElement(Box, { flex: 1, alignSelf: "end" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ React.createElement(Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel)))), showSelectionBar && selectable && selectedIds.size > 0 && (renderSelectionBar ? renderSelectionBar({
1081
+ )))), countInToolbar && /* @__PURE__ */ React.createElement(Box, { flex: 1, alignSelf: "end" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ React.createElement(Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel)))), showSelectionBar && selectable && selectedIds.size > 0 && (renderSelectionBar ? renderSelectionBar({
1034
1082
  selectedIds,
1035
1083
  selectedCount: selectedIds.size,
1036
1084
  displayCount,
@@ -1050,11 +1098,15 @@ var DataTable = ({
1050
1098
  action.icon && /* @__PURE__ */ React.createElement(Icon, { name: action.icon, size: "sm" }),
1051
1099
  " ",
1052
1100
  action.label
1053
- )))), showRowCount && displayCount > 0 && /* @__PURE__ */ React.createElement(Box, { flex: 1, alignSelf: "center" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ React.createElement(Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel))))), loading ? renderLoadingState ? renderLoadingState({ label: resolvedLoadingLabel }) : /* @__PURE__ */ React.createElement(LoadingSpinner, { label: resolvedLoadingLabel, layout: "centered" }) : error ? renderErrorState ? renderErrorState({
1101
+ )))), showRowCount && displayCount > 0 && /* @__PURE__ */ React.createElement(Box, { flex: 1, alignSelf: "center" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ React.createElement(Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel))))), loading ? renderLoadingState ? renderLoadingState({ label: resolvedLoadingLabel }) : (
1102
+ // Same EmptyState layout as the empty state, just the "building" image
1103
+ // + a loading message — so loading and empty match with no layout shift.
1104
+ /* @__PURE__ */ React.createElement(Tile, null, /* @__PURE__ */ React.createElement(Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ React.createElement(EmptyState, { title: resolvedLoadingLabel, imageName: "building", layout: "vertical" }, /* @__PURE__ */ React.createElement(Text, null, resolvedLoadingMessage))))
1105
+ ) : error ? renderErrorState ? renderErrorState({
1054
1106
  error,
1055
1107
  title: typeof error === "string" ? error : resolvedErrorTitle,
1056
1108
  message: typeof error === "string" ? resolvedRetryMessage : resolvedErrorMessage
1057
- }) : /* @__PURE__ */ React.createElement(ErrorState, { title: typeof error === "string" ? error : resolvedErrorTitle }, /* @__PURE__ */ React.createElement(Text, null, typeof error === "string" ? resolvedRetryMessage : resolvedErrorMessage)) : displayRows.length === 0 ? renderEmptyState ? renderEmptyState({ title: resolvedEmptyTitle, message: resolvedEmptyMessage }) : /* @__PURE__ */ React.createElement(Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ React.createElement(EmptyState, { title: resolvedEmptyTitle, layout: "vertical" }, /* @__PURE__ */ React.createElement(Text, null, resolvedEmptyMessage))) : /* @__PURE__ */ React.createElement(
1109
+ }) : /* @__PURE__ */ React.createElement(ErrorState, { title: typeof error === "string" ? error : resolvedErrorTitle }, /* @__PURE__ */ React.createElement(Text, null, typeof error === "string" ? resolvedRetryMessage : resolvedErrorMessage)) : displayRows.length === 0 ? renderEmptyState ? renderEmptyState({ title: resolvedEmptyTitle, message: resolvedEmptyMessage }) : /* @__PURE__ */ React.createElement(Tile, null, /* @__PURE__ */ React.createElement(Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ React.createElement(EmptyState, { title: resolvedEmptyTitle, layout: "vertical" }, /* @__PURE__ */ React.createElement(Text, null, resolvedEmptyMessage)))) : /* @__PURE__ */ React.createElement(
1058
1110
  Table,
1059
1111
  {
1060
1112
  bordered,