hs-uix 1.6.5 → 2.0.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.
package/dist/utils.js CHANGED
@@ -1,6 +1,8 @@
1
+ var __create = Object.create;
1
2
  var __defProp = Object.defineProperty;
2
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
4
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
5
7
  var __export = (target, all) => {
6
8
  for (var name in all)
@@ -14,13 +16,25 @@ var __copyProps = (to, from, except, desc) => {
14
16
  }
15
17
  return to;
16
18
  };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
17
27
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
28
 
19
29
  // src/utils/index.js
20
30
  var utils_exports = {};
21
31
  __export(utils_exports, {
32
+ CrmDataTable: () => CrmDataTable,
33
+ CrmKanban: () => CrmKanban,
34
+ buildCrmSearchConfig: () => buildCrmSearchConfig,
22
35
  buildOptions: () => buildOptions,
23
36
  createStatusTagSortComparator: () => createStatusTagSortComparator,
37
+ crmSearchResultToOption: () => crmSearchResultToOption,
24
38
  deriveCardFieldsFromColumns: () => deriveCardFieldsFromColumns,
25
39
  findOptionLabel: () => findOptionLabel,
26
40
  formatCurrency: () => formatCurrency,
@@ -34,7 +48,14 @@ __export(utils_exports, {
34
48
  isDateTimeValueObject: () => isDateTimeValueObject,
35
49
  isDateValueObject: () => isDateValueObject,
36
50
  isTimeValueObject: () => isTimeValueObject,
37
- sumBy: () => sumBy
51
+ makeCrmSearchMultiSelectField: () => makeCrmSearchMultiSelectField,
52
+ makeCrmSearchSelectField: () => makeCrmSearchSelectField,
53
+ normalizeCrmSearchRecord: () => normalizeCrmSearchRecord,
54
+ normalizeCrmSearchRows: () => normalizeCrmSearchRows,
55
+ resolveCrmObjectType: () => resolveCrmObjectType,
56
+ sumBy: () => sumBy,
57
+ useCrmSearchDataSource: () => useCrmSearchDataSource,
58
+ useCrmSearchOptions: () => useCrmSearchOptions
38
59
  });
39
60
  module.exports = __toCommonJS(utils_exports);
40
61
 
@@ -44,6 +65,2878 @@ var sumBy = (items, keyOrFn) => (items || []).reduce((total, item) => {
44
65
  return total + Number(value || 0);
45
66
  }, 0);
46
67
 
68
+ // src/utils/crmSearchAdapters.js
69
+ var import_react5 = __toESM(require("react"));
70
+ var import_ui_extensions5 = require("@hubspot/ui-extensions");
71
+
72
+ // packages/datatable/src/DataTable.jsx
73
+ var import_react2 = __toESM(require("react"));
74
+
75
+ // src/utils/query.js
76
+ var import_fuse = __toESM(require("fuse.js"));
77
+ var getEmptyFilterValue = (filter) => {
78
+ const type = filter.type || "select";
79
+ if (type === "multiselect") return [];
80
+ if (type === "dateRange") return { from: null, to: null };
81
+ return "";
82
+ };
83
+ var isFilterActive = (filter, value) => {
84
+ const type = filter.type || "select";
85
+ if (type === "multiselect") return Array.isArray(value) && value.length > 0;
86
+ if (type === "dateRange") return value && (value.from || value.to);
87
+ return !!value;
88
+ };
89
+ var formatDateChip = (dateObj) => {
90
+ if (!dateObj) return "";
91
+ const { year, month, date } = dateObj;
92
+ return new Intl.DateTimeFormat("en-US", {
93
+ month: "short",
94
+ day: "numeric",
95
+ year: "numeric"
96
+ }).format(new Date(year, month, date));
97
+ };
98
+ var dateToTimestamp = (dateObj) => {
99
+ if (!dateObj) return null;
100
+ return new Date(dateObj.year, dateObj.month, dateObj.date).getTime();
101
+ };
102
+ var toStableKey = (value) => {
103
+ try {
104
+ return JSON.stringify(value);
105
+ } catch (_error) {
106
+ return String(value);
107
+ }
108
+ };
109
+ var filterRows = (rows, filters, values) => {
110
+ let result = rows;
111
+ for (const filter of filters || []) {
112
+ const value = values[filter.name];
113
+ if (!isFilterActive(filter, value)) continue;
114
+ const type = filter.type || "select";
115
+ if (filter.filterFn) {
116
+ result = result.filter((row) => filter.filterFn(row, value));
117
+ } else if (type === "multiselect") {
118
+ result = result.filter((row) => value.includes(row[filter.name]));
119
+ } else if (type === "dateRange") {
120
+ const fromTs = dateToTimestamp(value.from);
121
+ const toTs = value.to ? dateToTimestamp(value.to) + 864e5 - 1 : null;
122
+ result = result.filter((row) => {
123
+ const rowTs = new Date(row[filter.name]).getTime();
124
+ if (Number.isNaN(rowTs)) return false;
125
+ if (fromTs && rowTs < fromTs) return false;
126
+ if (toTs && rowTs > toTs) return false;
127
+ return true;
128
+ });
129
+ } else {
130
+ result = result.filter((row) => row[filter.name] === value);
131
+ }
132
+ }
133
+ return result;
134
+ };
135
+ var searchRows = (rows, term, fields, opts = {}) => {
136
+ const { fuzzy = false, fuzzyOptions } = opts;
137
+ const t = String(term ?? "").toLowerCase();
138
+ if (!t || !fields || fields.length === 0) return rows;
139
+ if (fuzzy) {
140
+ const fuse = new import_fuse.default(rows, {
141
+ keys: fields,
142
+ threshold: 0.4,
143
+ distance: 100,
144
+ ignoreLocation: true,
145
+ ...fuzzyOptions
146
+ });
147
+ return fuse.search(t).map((r) => r.item);
148
+ }
149
+ return rows.filter(
150
+ (row) => fields.some((field) => {
151
+ const val = row[field];
152
+ return val && String(val).toLowerCase().includes(t);
153
+ })
154
+ );
155
+ };
156
+
157
+ // src/utils/interactionHooks.js
158
+ var import_react = require("react");
159
+ var import_ui_extensions = require("@hubspot/ui-extensions");
160
+ var useDebouncedDispatch = (value, debounceMs, dispatch) => {
161
+ const debounced = (0, import_ui_extensions.useDebounce)(value, debounceMs > 0 ? debounceMs : 300);
162
+ const pendingRef = (0, import_react.useRef)(null);
163
+ (0, import_react.useEffect)(() => {
164
+ if (debounceMs <= 0) return;
165
+ if (pendingRef.current == null) return;
166
+ if (debounced !== pendingRef.current) return;
167
+ const next = pendingRef.current;
168
+ pendingRef.current = null;
169
+ dispatch(next);
170
+ }, [debounceMs, debounced, dispatch]);
171
+ return (0, import_react.useCallback)(
172
+ (next) => {
173
+ if (debounceMs > 0) {
174
+ pendingRef.current = next;
175
+ } else {
176
+ pendingRef.current = null;
177
+ dispatch(next);
178
+ }
179
+ },
180
+ [debounceMs, dispatch]
181
+ );
182
+ };
183
+ var useSelectionReset = ({ resetKey, enabled, isControlled, clearSelection }) => {
184
+ const ref = (0, import_react.useRef)("");
185
+ (0, import_react.useEffect)(() => {
186
+ if (!enabled || isControlled) {
187
+ ref.current = resetKey;
188
+ return;
189
+ }
190
+ if (ref.current && ref.current !== resetKey) {
191
+ clearSelection();
192
+ }
193
+ ref.current = resetKey;
194
+ }, [resetKey, enabled, isControlled, clearSelection]);
195
+ };
196
+
197
+ // packages/datatable/src/editValidation.js
198
+ var editValidationError = (result) => {
199
+ if (result === true || result === void 0 || result === null) return null;
200
+ return typeof result === "string" ? result : "Invalid value";
201
+ };
202
+
203
+ // packages/datatable/src/DataTable.jsx
204
+ var import_ui_extensions2 = require("@hubspot/ui-extensions");
205
+ var NARROW_EDIT_TYPES = /* @__PURE__ */ new Set(["checkbox", "toggle"]);
206
+ var DATE_PATTERN = /^\d{4}[-/]\d{2}[-/]\d{2}/;
207
+ var BOOL_VALUES = /* @__PURE__ */ new Set(["true", "false", "yes", "no", "0", "1"]);
208
+ var SORT_DIRECTIONS = /* @__PURE__ */ new Set(["ascending", "descending", "none"]);
209
+ var normalizeSortState = (columns, sort) => {
210
+ const normalized = {};
211
+ columns.forEach((col) => {
212
+ if (col.sortable) normalized[col.field] = "none";
213
+ });
214
+ if (!sort) return normalized;
215
+ if (sort.field && SORT_DIRECTIONS.has(sort.direction) && sort.field in normalized) {
216
+ normalized[sort.field] = sort.direction;
217
+ return normalized;
218
+ }
219
+ Object.keys(normalized).forEach((field) => {
220
+ const direction = sort[field];
221
+ if (SORT_DIRECTIONS.has(direction)) normalized[field] = direction;
222
+ });
223
+ return normalized;
224
+ };
225
+ var serializeSortState = (sortState) => {
226
+ const activeField = Object.keys(sortState).find((field) => sortState[field] !== "none");
227
+ if (!activeField) return null;
228
+ return { field: activeField, direction: sortState[activeField] };
229
+ };
230
+ var computeAutoWidths = (columns, data) => {
231
+ if (!data || data.length === 0) return {};
232
+ const sample = data.slice(0, 50);
233
+ const results = {};
234
+ columns.forEach((col) => {
235
+ if (col.width && col.cellWidth) return;
236
+ const values = sample.map((row) => row[col.field]).filter((v) => v != null);
237
+ const strings = values.map((v) => {
238
+ const s = String(v);
239
+ const truncLen = typeof col.truncate === "number" ? col.truncate : col.truncate && typeof col.truncate === "object" ? col.truncate.maxLength : null;
240
+ return truncLen && s.length > truncLen ? s.slice(0, truncLen) : s;
241
+ });
242
+ let widthHint = null;
243
+ let cellWidthHint = null;
244
+ if (col.editable && col.editType && NARROW_EDIT_TYPES.has(col.editType)) {
245
+ cellWidthHint = "min";
246
+ }
247
+ if (col.truncate === true) {
248
+ cellWidthHint = cellWidthHint || "min";
249
+ }
250
+ if (strings.length > 0) {
251
+ const lengths = strings.map((s) => s.length);
252
+ const maxLen = Math.max(...lengths);
253
+ const uniqueCount = new Set(strings).size;
254
+ if (values.every((v) => typeof v === "boolean") || strings.every((s) => BOOL_VALUES.has(s.toLowerCase()))) {
255
+ widthHint = widthHint || "min";
256
+ cellWidthHint = cellWidthHint || "min";
257
+ } else if (strings.every((s) => DATE_PATTERN.test(s))) {
258
+ widthHint = widthHint || "min";
259
+ cellWidthHint = cellWidthHint || "auto";
260
+ } else if (values.every((v) => typeof v === "number")) {
261
+ widthHint = widthHint || "auto";
262
+ cellWidthHint = cellWidthHint || "auto";
263
+ } else if (uniqueCount <= 5 && maxLen <= 15) {
264
+ widthHint = widthHint || "min";
265
+ cellWidthHint = cellWidthHint || "auto";
266
+ } else {
267
+ widthHint = widthHint || "auto";
268
+ cellWidthHint = cellWidthHint || "auto";
269
+ }
270
+ }
271
+ if (col.editable && !NARROW_EDIT_TYPES.has(col.editType) && widthHint === "min") {
272
+ widthHint = "auto";
273
+ }
274
+ results[col.field] = {
275
+ width: widthHint || "auto",
276
+ cellWidth: cellWidthHint || "auto"
277
+ };
278
+ });
279
+ return results;
280
+ };
281
+ var BOOLEAN_SELECT_OPTIONS = [
282
+ { label: "Yes", value: true },
283
+ { label: "No", value: false }
284
+ ];
285
+ var resolveEditOptions = (col, data) => {
286
+ if (col.editOptions && col.editOptions.length > 0) return col.editOptions;
287
+ const sample = data.find((row) => row[col.field] != null);
288
+ if (sample && typeof sample[col.field] === "boolean") return BOOLEAN_SELECT_OPTIONS;
289
+ return [];
290
+ };
291
+ var DataTable = ({
292
+ // Data
293
+ data,
294
+ columns,
295
+ renderRow,
296
+ // Search
297
+ searchFields = [],
298
+ searchPlaceholder = "Search...",
299
+ fuzzySearch = false,
300
+ // enable fuzzy matching via Fuse.js
301
+ fuzzyOptions,
302
+ // custom Fuse.js options (threshold, distance, etc.)
303
+ showSearch = true,
304
+ // show the SearchInput in the toolbar
305
+ // Filters
306
+ filters = [],
307
+ showFilterBadges = true,
308
+ // show active filter chips/badges
309
+ showClearFiltersButton,
310
+ // show "Clear all" reset button; defaults to showFilterBadges when omitted
311
+ filterInlineLimit = 2,
312
+ // number of filters shown inline before overflow
313
+ // Pagination
314
+ pageSize = 10,
315
+ maxVisiblePageButtons,
316
+ // max page number buttons to show
317
+ showButtonLabels = true,
318
+ // show First/Prev/Next/Last text labels
319
+ showFirstLastButtons,
320
+ // show First/Last page buttons (default: auto when pageCount > 5)
321
+ // Row count
322
+ title,
323
+ // optional title shown as demibold text above the table toolbar
324
+ showRowCount = true,
325
+ // show "X records" / "X of Y records" text
326
+ rowCountBold = false,
327
+ // bold the row count text
328
+ rowCountText,
329
+ // custom formatter: (shownOnPage, totalMatching) => string
330
+ // Table appearance
331
+ bordered = true,
332
+ // show table borders
333
+ flush = true,
334
+ // remove bottom margin
335
+ scrollable = false,
336
+ // allow horizontal overflow with scrollbar
337
+ // Sorting
338
+ defaultSort = {},
339
+ // Grouping
340
+ groupBy,
341
+ // Footer
342
+ footer,
343
+ // Empty state
344
+ emptyTitle,
345
+ emptyMessage,
346
+ // -----------------------------------------------------------------------
347
+ // Server-side mode
348
+ // -----------------------------------------------------------------------
349
+ serverSide = false,
350
+ loading = false,
351
+ // show loading spinner over the table
352
+ error,
353
+ // error message string or boolean — shows ErrorState
354
+ totalCount,
355
+ // server total (server-side only)
356
+ page: externalPage,
357
+ // controlled page (server-side only)
358
+ searchValue,
359
+ // controlled search term (server-side only)
360
+ filterValues: externalFilterValues,
361
+ // controlled filter values (server-side only)
362
+ sort: externalSort,
363
+ // controlled sort state, e.g. { field: "ascending" }
364
+ searchDebounce = 0,
365
+ // ms to debounce onSearchChange callback
366
+ resetPageOnChange = true,
367
+ // auto-reset to page 1 on search/filter/sort change
368
+ onSearchChange,
369
+ // (searchTerm) => void
370
+ onFilterChange,
371
+ // (filterValues) => void
372
+ onSortChange,
373
+ // (field, direction) => void
374
+ onPageChange,
375
+ // (page) => void
376
+ onParamsChange,
377
+ // ({ search, filters, sort, page }) => void
378
+ // -----------------------------------------------------------------------
379
+ // Row selection
380
+ // -----------------------------------------------------------------------
381
+ selectable = false,
382
+ rowIdField = "id",
383
+ // field name used as unique row identifier
384
+ selectedIds: externalSelectedIds,
385
+ // controlled selection — array of row IDs
386
+ onSelectionChange,
387
+ // (selectedIds[]) => void
388
+ onSelectAllRequest,
389
+ // server-side: ({ selectedIds, pageIds, totalCount }) => void
390
+ selectionActions = [],
391
+ // [{ label, onClick(selectedIds[]), icon?, variant? }]
392
+ selectionResetKey,
393
+ // optional key to force clear uncontrolled selection memory
394
+ resetSelectionOnQueryChange = true,
395
+ // clear uncontrolled selection on search/filter/sort changes
396
+ showSelectionBar = true,
397
+ // show the selection action bar when rows are selected
398
+ recordLabel,
399
+ // { singular: "Contact", plural: "Contacts" } — defaults to Record/Records
400
+ // -----------------------------------------------------------------------
401
+ // Row actions
402
+ // -----------------------------------------------------------------------
403
+ rowActions,
404
+ // [{ label, onClick(row), icon?, variant? }] or (row) => actions[]
405
+ hideRowActionsWhenSelectionActive = false,
406
+ // hide row action column while selected-row action bar is visible
407
+ // -----------------------------------------------------------------------
408
+ // Inline editing
409
+ // -----------------------------------------------------------------------
410
+ editMode,
411
+ // "discrete" (click-to-edit) | "inline" (always show inputs)
412
+ editingRowId,
413
+ // controlled — row ID currently in full-row edit mode
414
+ onRowEdit,
415
+ // (row, field, newValue) => void
416
+ onRowEditInput,
417
+ // optional live-input callback: (row, field, inputValue) => void
418
+ onEditStart,
419
+ // (row, field, currentValue) => void — fires when editing begins
420
+ onEditCancel,
421
+ // (row, field) => void — fires when editing is cancelled without commit
422
+ // -----------------------------------------------------------------------
423
+ // Auto-width
424
+ // -----------------------------------------------------------------------
425
+ autoWidth = true,
426
+ // auto-compute column widths from content analysis
427
+ // -----------------------------------------------------------------------
428
+ // Labels / i18n
429
+ // -----------------------------------------------------------------------
430
+ labels,
431
+ // override hardcoded UI strings for i18n
432
+ // -----------------------------------------------------------------------
433
+ // Render overrides (Phase 3 — full replacement escape hatches)
434
+ // -----------------------------------------------------------------------
435
+ renderSelectionBar,
436
+ // ({ selectedIds, selectedCount, displayCount, countLabel, onSelectAll, onDeselectAll, selectionActions }) => ReactNode
437
+ renderEmptyState,
438
+ // ({ title, message }) => ReactNode
439
+ renderLoadingState,
440
+ // ({ label }) => ReactNode
441
+ renderErrorState
442
+ // ({ error, title, message }) => ReactNode
443
+ }) => {
444
+ const initialSortState = (0, import_react2.useMemo)(() => {
445
+ return normalizeSortState(columns, defaultSort);
446
+ }, [columns, defaultSort]);
447
+ const [internalSearchTerm, setInternalSearchTerm] = (0, import_react2.useState)(
448
+ () => serverSide && searchValue != null ? searchValue : ""
449
+ );
450
+ const [internalFilterValues, setInternalFilterValues] = (0, import_react2.useState)(() => {
451
+ const init = {};
452
+ filters.forEach((f) => {
453
+ init[f.name] = getEmptyFilterValue(f);
454
+ });
455
+ return init;
456
+ });
457
+ const [internalSortState, setInternalSortState] = (0, import_react2.useState)(initialSortState);
458
+ const [currentPage, setCurrentPage] = (0, import_react2.useState)(1);
459
+ const [showMoreFilters, setShowMoreFilters] = (0, import_react2.useState)(false);
460
+ const lastAppliedSearchRef = (0, import_react2.useRef)(
461
+ serverSide && searchValue != null ? searchValue : ""
462
+ );
463
+ const searchTerm = serverSide && searchValue != null ? searchValue : internalSearchTerm;
464
+ (0, import_react2.useEffect)(() => {
465
+ if (!serverSide || searchValue == null) return;
466
+ if (searchValue === lastAppliedSearchRef.current) return;
467
+ lastAppliedSearchRef.current = searchValue;
468
+ setInternalSearchTerm(searchValue);
469
+ }, [serverSide, searchValue]);
470
+ const filterValues = serverSide && externalFilterValues != null ? externalFilterValues : internalFilterValues;
471
+ const externalSortState = (0, import_react2.useMemo)(
472
+ () => normalizeSortState(columns, externalSort),
473
+ [columns, externalSort]
474
+ );
475
+ const sortState = serverSide && externalSort != null ? externalSortState : internalSortState;
476
+ const activePage = serverSide && externalPage != null ? externalPage : currentPage;
477
+ (0, import_react2.useEffect)(() => {
478
+ if (!serverSide) setCurrentPage(1);
479
+ }, [internalSearchTerm, internalFilterValues, internalSortState, serverSide]);
480
+ const fireSearchCallback = (0, import_react2.useCallback)((term) => {
481
+ if (serverSide && onSearchChange) onSearchChange(term);
482
+ }, [serverSide, onSearchChange]);
483
+ const fireParamsChange = (0, import_react2.useCallback)((overrides) => {
484
+ if (!onParamsChange) return;
485
+ const nextSortState = overrides.sort != null ? normalizeSortState(columns, overrides.sort) : sortState;
486
+ onParamsChange({
487
+ search: overrides.search != null ? overrides.search : searchTerm,
488
+ filters: overrides.filters != null ? overrides.filters : filterValues,
489
+ sort: serializeSortState(nextSortState),
490
+ page: overrides.page != null ? overrides.page : activePage
491
+ });
492
+ }, [onParamsChange, columns, searchTerm, filterValues, sortState, activePage]);
493
+ const resetPage = (0, import_react2.useCallback)(() => {
494
+ if (resetPageOnChange) {
495
+ setCurrentPage(1);
496
+ if (serverSide && onPageChange) onPageChange(1);
497
+ }
498
+ }, [resetPageOnChange, serverSide, onPageChange]);
499
+ const dispatchSearchChange = (0, import_react2.useCallback)((term) => {
500
+ lastAppliedSearchRef.current = term;
501
+ fireSearchCallback(term);
502
+ fireParamsChange({ search: term, page: resetPageOnChange ? 1 : void 0 });
503
+ }, [fireSearchCallback, fireParamsChange, resetPageOnChange]);
504
+ const dispatchSearchDebounced = useDebouncedDispatch(
505
+ internalSearchTerm,
506
+ searchDebounce,
507
+ dispatchSearchChange
508
+ );
509
+ const handleSearchChange = (0, import_react2.useCallback)((term) => {
510
+ setInternalSearchTerm(term);
511
+ resetPage();
512
+ dispatchSearchDebounced(term);
513
+ }, [dispatchSearchDebounced, resetPage]);
514
+ const handleFilterChange = (0, import_react2.useCallback)((name, value) => {
515
+ const next = { ...filterValues, [name]: value };
516
+ setInternalFilterValues(next);
517
+ if (serverSide && onFilterChange) onFilterChange(next);
518
+ resetPage();
519
+ fireParamsChange({ filters: next, page: resetPageOnChange ? 1 : void 0 });
520
+ }, [filterValues, serverSide, onFilterChange, fireParamsChange, resetPage, resetPageOnChange]);
521
+ const handleSortChange = (0, import_react2.useCallback)((field) => {
522
+ const current = sortState[field] || "none";
523
+ const nextDirection = current === "none" ? "ascending" : current === "ascending" ? "descending" : "none";
524
+ const reset = {};
525
+ columns.forEach((col) => {
526
+ if (col.sortable) reset[col.field] = "none";
527
+ });
528
+ const next = nextDirection === "none" ? reset : { ...reset, [field]: nextDirection };
529
+ setInternalSortState(next);
530
+ if (serverSide && onSortChange) onSortChange(field, nextDirection);
531
+ resetPage();
532
+ fireParamsChange({ sort: next, page: resetPageOnChange ? 1 : void 0 });
533
+ }, [sortState, columns, serverSide, onSortChange, fireParamsChange, resetPage, resetPageOnChange]);
534
+ const handlePageChange = (0, import_react2.useCallback)((page) => {
535
+ setCurrentPage(page);
536
+ if (serverSide && onPageChange) onPageChange(page);
537
+ fireParamsChange({ page });
538
+ }, [serverSide, onPageChange, fireParamsChange]);
539
+ const filteredData = (0, import_react2.useMemo)(() => {
540
+ if (serverSide) return data;
541
+ let result = filterRows(data, filters, filterValues);
542
+ if (searchTerm && searchFields.length > 0) {
543
+ result = searchRows(result, searchTerm, searchFields, {
544
+ fuzzy: fuzzySearch,
545
+ fuzzyOptions
546
+ });
547
+ }
548
+ return result;
549
+ }, [data, filterValues, searchTerm, filters, searchFields, serverSide, fuzzySearch, fuzzyOptions]);
550
+ const sortedData = (0, import_react2.useMemo)(() => {
551
+ if (serverSide) return filteredData;
552
+ const activeField = Object.keys(sortState).find((k) => sortState[k] !== "none");
553
+ if (!activeField) return filteredData;
554
+ const activeCol = columns.find((c) => c.field === activeField);
555
+ const sortOrder = Array.isArray(activeCol == null ? void 0 : activeCol.sortOrder) ? activeCol.sortOrder : null;
556
+ const sortOrderIndex = (val) => {
557
+ const idx = sortOrder.indexOf(val);
558
+ return idx === -1 ? sortOrder.length : idx;
559
+ };
560
+ return [...filteredData].sort((a, b) => {
561
+ const dir = sortState[activeField] === "ascending" ? 1 : -1;
562
+ const aVal = a[activeField];
563
+ const bVal = b[activeField];
564
+ if (typeof (activeCol == null ? void 0 : activeCol.sortComparator) === "function") {
565
+ return dir * activeCol.sortComparator(aVal, bVal, a, b);
566
+ }
567
+ if (sortOrder) {
568
+ const diff = sortOrderIndex(aVal) - sortOrderIndex(bVal);
569
+ if (diff !== 0) return dir * diff;
570
+ }
571
+ if (aVal == null && bVal == null) return 0;
572
+ if (aVal == null) return 1;
573
+ if (bVal == null) return -1;
574
+ if (aVal < bVal) return -dir;
575
+ if (aVal > bVal) return dir;
576
+ return 0;
577
+ });
578
+ }, [filteredData, sortState, serverSide, columns]);
579
+ const groupedData = (0, import_react2.useMemo)(() => {
580
+ if (!groupBy) return null;
581
+ const source = serverSide ? data : sortedData;
582
+ const groups = {};
583
+ source.forEach((row) => {
584
+ const key = row[groupBy.field] ?? "--";
585
+ if (!groups[key]) groups[key] = [];
586
+ groups[key].push(row);
587
+ });
588
+ let groupKeys = Object.keys(groups);
589
+ if (groupBy.sort) {
590
+ if (typeof groupBy.sort === "function") {
591
+ groupKeys.sort(groupBy.sort);
592
+ } else {
593
+ const dir = groupBy.sort === "desc" ? -1 : 1;
594
+ groupKeys.sort((a, b) => a < b ? -dir : a > b ? dir : 0);
595
+ }
596
+ }
597
+ return groupKeys.map((key) => ({
598
+ key,
599
+ label: groupBy.label ? groupBy.label(key, groups[key]) : key,
600
+ rows: groups[key]
601
+ }));
602
+ }, [sortedData, data, groupBy, serverSide]);
603
+ const [expandedGroups, setExpandedGroups] = (0, import_react2.useState)(() => {
604
+ if (!groupBy) return /* @__PURE__ */ new Set();
605
+ const defaultExpanded = groupBy.defaultExpanded !== false;
606
+ if (defaultExpanded && groupedData) {
607
+ return new Set(groupedData.map((g) => g.key));
608
+ }
609
+ return /* @__PURE__ */ new Set();
610
+ });
611
+ (0, import_react2.useEffect)(() => {
612
+ if (!groupedData) return;
613
+ const defaultExpanded = (groupBy == null ? void 0 : groupBy.defaultExpanded) !== false;
614
+ if (defaultExpanded) {
615
+ setExpandedGroups((prev) => {
616
+ const next = new Set(prev);
617
+ groupedData.forEach((g) => next.add(g.key));
618
+ return next;
619
+ });
620
+ }
621
+ }, [groupedData, groupBy]);
622
+ const toggleGroup = (0, import_react2.useCallback)((key) => {
623
+ setExpandedGroups((prev) => {
624
+ const next = new Set(prev);
625
+ if (next.has(key)) next.delete(key);
626
+ else next.add(key);
627
+ return next;
628
+ });
629
+ }, []);
630
+ const datasetRows = (0, import_react2.useMemo)(() => {
631
+ if (!groupedData) return serverSide ? data : sortedData;
632
+ return groupedData.flatMap((group) => group.rows);
633
+ }, [groupedData, sortedData, data, serverSide]);
634
+ const totalItems = serverSide ? totalCount || data.length : datasetRows.length;
635
+ const pageCount = Math.ceil(totalItems / pageSize);
636
+ const paginatedRows = (0, import_react2.useMemo)(() => {
637
+ if (serverSide) return datasetRows;
638
+ return datasetRows.slice(
639
+ (activePage - 1) * pageSize,
640
+ activePage * pageSize
641
+ );
642
+ }, [serverSide, datasetRows, activePage, pageSize]);
643
+ const displayRows = (0, import_react2.useMemo)(() => {
644
+ if (!groupedData) return paginatedRows.map((row) => ({ type: "data", row }));
645
+ const pageRows = new Set(paginatedRows);
646
+ const rows = [];
647
+ groupedData.forEach((group) => {
648
+ const groupPageRows = group.rows.filter((row) => pageRows.has(row));
649
+ if (groupPageRows.length === 0) return;
650
+ rows.push({ type: "group-header", group });
651
+ if (expandedGroups.has(group.key)) {
652
+ groupPageRows.forEach((row) => rows.push({ type: "data", row }));
653
+ }
654
+ });
655
+ return rows;
656
+ }, [groupedData, paginatedRows, expandedGroups]);
657
+ const footerData = serverSide ? data : filteredData;
658
+ const activeChips = (0, import_react2.useMemo)(() => {
659
+ const chips = [];
660
+ filters.forEach((filter) => {
661
+ const value = filterValues[filter.name];
662
+ if (!isFilterActive(filter, value)) return;
663
+ const type = filter.type || "select";
664
+ const prefix = filter.chipLabel || filter.placeholder || filter.name;
665
+ if (type === "multiselect") {
666
+ const labels2 = value.map((v) => {
667
+ var _a;
668
+ return ((_a = filter.options.find((o) => o.value === v)) == null ? void 0 : _a.label) || v;
669
+ }).join(", ");
670
+ chips.push({ key: filter.name, label: `${prefix}: ${labels2}` });
671
+ } else if (type === "dateRange") {
672
+ const parts = [];
673
+ if (value.from) parts.push(`from ${formatDateChip(value.from)}`);
674
+ if (value.to) parts.push(`to ${formatDateChip(value.to)}`);
675
+ chips.push({ key: filter.name, label: `${prefix}: ${parts.join(" ")}` });
676
+ } else {
677
+ const option = filter.options.find((o) => o.value === value);
678
+ chips.push({ key: filter.name, label: `${prefix}: ${(option == null ? void 0 : option.label) || value}` });
679
+ }
680
+ });
681
+ return chips;
682
+ }, [filterValues, filters]);
683
+ const handleFilterRemove = (0, import_react2.useCallback)((key) => {
684
+ if (key === "all") {
685
+ const cleared = {};
686
+ filters.forEach((f) => {
687
+ cleared[f.name] = getEmptyFilterValue(f);
688
+ });
689
+ setInternalFilterValues(cleared);
690
+ if (serverSide && onFilterChange) onFilterChange(cleared);
691
+ resetPage();
692
+ fireParamsChange({ filters: cleared, page: resetPageOnChange ? 1 : void 0 });
693
+ } else {
694
+ const filter = filters.find((f) => f.name === key);
695
+ const emptyVal = filter ? getEmptyFilterValue(filter) : "";
696
+ const next = { ...filterValues, [key]: emptyVal };
697
+ setInternalFilterValues(next);
698
+ if (serverSide && onFilterChange) onFilterChange(next);
699
+ resetPage();
700
+ fireParamsChange({ filters: next, page: resetPageOnChange ? 1 : void 0 });
701
+ }
702
+ }, [filters, filterValues, serverSide, onFilterChange, resetPage, fireParamsChange, resetPageOnChange]);
703
+ const displayCount = serverSide ? totalCount || data.length : filteredData.length;
704
+ const totalDataCount = serverSide ? totalCount || data.length : data.length;
705
+ const shownOnPageCount = displayRows.filter((item) => item.type === "data").length;
706
+ const pluralLabel = ((recordLabel == null ? void 0 : recordLabel.plural) || "records").toLowerCase();
707
+ const singularLabel = ((recordLabel == null ? void 0 : recordLabel.singular) || "record").toLowerCase();
708
+ const countLabel = (n) => n === 1 ? singularLabel : pluralLabel;
709
+ const resolvedEmptyTitle = emptyTitle || "No results found";
710
+ const resolvedEmptyMessage = emptyMessage || `No ${pluralLabel} match your search or filter criteria.`;
711
+ const resolvedSelectedLabel = (labels == null ? void 0 : labels.selected) || ((count, label) => `${count}\xA0${label}\xA0selected`);
712
+ const resolvedSelectAllLabel = (labels == null ? void 0 : labels.selectAll) || ((count, label) => `Select all ${count} ${label}`);
713
+ const resolvedDeselectAllLabel = (labels == null ? void 0 : labels.deselectAll) || "Deselect all";
714
+ const resolvedFiltersButtonLabel = (labels == null ? void 0 : labels.filtersButton) || "Filters";
715
+ const resolvedClearAllLabel = (labels == null ? void 0 : labels.clearAll) || "Clear all";
716
+ const resolvedDateFromLabel = (labels == null ? void 0 : labels.dateFrom) || "From";
717
+ const resolvedDateToLabel = (labels == null ? void 0 : labels.dateTo) || "To";
718
+ const resolvedLoadingLabel = (labels == null ? void 0 : labels.loading) || `Loading ${pluralLabel}...`;
719
+ const resolvedLoadingMessage = (labels == null ? void 0 : labels.loadingMessage) || "This should only take a moment.";
720
+ const resolvedErrorTitle = (labels == null ? void 0 : labels.errorTitle) || "Something went wrong.";
721
+ const resolvedErrorMessage = (labels == null ? void 0 : labels.errorMessage) || "An error occurred while loading data.";
722
+ const resolvedRetryMessage = (labels == null ? void 0 : labels.retryMessage) || "Please try again.";
723
+ const resolvedShowClearFiltersButton = showClearFiltersButton ?? showFilterBadges;
724
+ const recordCountLabel = rowCountText ? rowCountText(shownOnPageCount, displayCount) : displayCount === totalDataCount ? `${totalDataCount} ${countLabel(totalDataCount)}` : `${displayCount} of ${totalDataCount} ${countLabel(totalDataCount)}`;
725
+ const [internalSelectedIds, setInternalSelectedIds] = (0, import_react2.useState)(/* @__PURE__ */ new Set());
726
+ (0, import_react2.useEffect)(() => {
727
+ if (externalSelectedIds != null) {
728
+ setInternalSelectedIds(new Set(externalSelectedIds));
729
+ }
730
+ }, [externalSelectedIds]);
731
+ const selectionQueryKey = (0, import_react2.useMemo)(() => {
732
+ if (!resetSelectionOnQueryChange) return "";
733
+ return toStableKey({
734
+ search: searchTerm,
735
+ filters: filterValues,
736
+ sort: serializeSortState(sortState)
737
+ });
738
+ }, [searchTerm, filterValues, sortState, resetSelectionOnQueryChange]);
739
+ const combinedSelectionResetKey = (0, import_react2.useMemo)(
740
+ () => `${selectionQueryKey}::${selectionResetKey == null ? "" : toStableKey(selectionResetKey)}`,
741
+ [selectionQueryKey, selectionResetKey]
742
+ );
743
+ const clearSelection = (0, import_react2.useCallback)(() => setInternalSelectedIds(/* @__PURE__ */ new Set()), []);
744
+ useSelectionReset({
745
+ resetKey: combinedSelectionResetKey,
746
+ enabled: selectable,
747
+ isControlled: externalSelectedIds != null,
748
+ clearSelection
749
+ });
750
+ const selectedIds = externalSelectedIds != null ? new Set(externalSelectedIds) : internalSelectedIds;
751
+ const showToolbarCount = showRowCount && displayCount > 0 && !(showSelectionBar && selectable && selectedIds.size > 0);
752
+ const hasToolbarLeft = showSearch && searchFields.length > 0 || filters.length > 0 || activeChips.length > 0 && (showFilterBadges || resolvedShowClearFiltersButton);
753
+ const countInTitleRow = !!title && showToolbarCount && !hasToolbarLeft;
754
+ const countInToolbar = showToolbarCount && !countInTitleRow;
755
+ const hasToolbarContent = hasToolbarLeft || countInToolbar;
756
+ const showRowActionsColumn = !!rowActions && !(hideRowActionsWhenSelectionActive && selectable && selectedIds.size > 0);
757
+ const applySelection = (0, import_react2.useCallback)((nextSet) => {
758
+ if (externalSelectedIds == null) {
759
+ setInternalSelectedIds(nextSet);
760
+ }
761
+ if (onSelectionChange) onSelectionChange([...nextSet]);
762
+ }, [externalSelectedIds, onSelectionChange]);
763
+ const pageRowIds = (0, import_react2.useMemo)(() => {
764
+ if (serverSide) {
765
+ return data.map((row) => row[rowIdField]).filter((id) => id != null);
766
+ }
767
+ return displayRows.filter((r) => r.type === "data").map((r) => r.row[rowIdField]).filter((id) => id != null);
768
+ }, [serverSide, data, displayRows, rowIdField]);
769
+ const allRowIds = (0, import_react2.useMemo)(
770
+ () => datasetRows.map((row) => row[rowIdField]).filter((id) => id != null),
771
+ [datasetRows, rowIdField]
772
+ );
773
+ const handleSelectRow = (0, import_react2.useCallback)((rowId, checked) => {
774
+ const next = new Set(selectedIds);
775
+ if (checked) next.add(rowId);
776
+ else next.delete(rowId);
777
+ applySelection(next);
778
+ }, [selectedIds, applySelection]);
779
+ const handleSelectAll = (0, import_react2.useCallback)((checked) => {
780
+ const next = new Set(selectedIds);
781
+ pageRowIds.forEach((id) => {
782
+ if (checked) next.add(id);
783
+ else next.delete(id);
784
+ });
785
+ applySelection(next);
786
+ }, [selectedIds, pageRowIds, applySelection]);
787
+ const allVisibleSelected = (0, import_react2.useMemo)(() => {
788
+ return pageRowIds.length > 0 && pageRowIds.every((id) => selectedIds.has(id));
789
+ }, [pageRowIds, selectedIds]);
790
+ const handleSelectAllRows = (0, import_react2.useCallback)(() => {
791
+ const idsToAdd = serverSide ? pageRowIds : allRowIds;
792
+ const next = new Set(selectedIds);
793
+ idsToAdd.forEach((id) => next.add(id));
794
+ applySelection(next);
795
+ if (serverSide && onSelectAllRequest) {
796
+ onSelectAllRequest({
797
+ selectedIds: [...next],
798
+ pageIds: pageRowIds,
799
+ totalCount: totalCount || data.length
800
+ });
801
+ }
802
+ }, [serverSide, pageRowIds, allRowIds, selectedIds, applySelection, onSelectAllRequest, totalCount, data.length]);
803
+ const handleDeselectAll = (0, import_react2.useCallback)(() => {
804
+ applySelection(/* @__PURE__ */ new Set());
805
+ }, [applySelection]);
806
+ const [editingCell, setEditingCell] = (0, import_react2.useState)(null);
807
+ const [editValue, setEditValue] = (0, import_react2.useState)(null);
808
+ const [editError, setEditError] = (0, import_react2.useState)(null);
809
+ const startEditing = (0, import_react2.useCallback)((rowId, field, currentValue) => {
810
+ setEditingCell({ rowId, field });
811
+ setEditValue(currentValue);
812
+ setEditError(null);
813
+ if (onEditStart) {
814
+ const row = data.find((r) => r[rowIdField] === rowId);
815
+ if (row) onEditStart(row, field, currentValue);
816
+ }
817
+ }, [onEditStart, data, rowIdField]);
818
+ const commitEdit = (0, import_react2.useCallback)((row, field, value, options = {}) => {
819
+ const { keepEditing = false } = options;
820
+ const col = columns.find((c) => c.field === field);
821
+ if (col == null ? void 0 : col.editValidate) {
822
+ const err = editValidationError(col.editValidate(value, row));
823
+ if (err) {
824
+ setEditError(err);
825
+ return false;
826
+ }
827
+ }
828
+ if (onRowEdit) onRowEdit(row, field, value);
829
+ if (!keepEditing) {
830
+ setEditingCell(null);
831
+ setEditValue(null);
832
+ } else {
833
+ setEditValue(value);
834
+ }
835
+ setEditError(null);
836
+ return true;
837
+ }, [onRowEdit, columns]);
838
+ const renderEditControl = (col, row) => {
839
+ const type = col.editType || "text";
840
+ const rowId = row[rowIdField];
841
+ const fieldName = `edit-${rowId}-${col.field}`;
842
+ const commit = (val) => commitEdit(row, col.field, val);
843
+ const exitEdit = () => {
844
+ if (editError) return;
845
+ if (onEditCancel) onEditCancel(row, col.field);
846
+ setEditingCell(null);
847
+ setEditValue(null);
848
+ };
849
+ const extra = col.editProps || {};
850
+ const validate = col.editValidate;
851
+ const validationProps = validate && editError ? { error: true, validationMessage: editError } : {};
852
+ const onInputValidate = validate ? (val) => setEditError(editValidationError(validate(val, row))) : void 0;
853
+ const handleInput = (val) => {
854
+ setEditValue(val);
855
+ if (onInputValidate) onInputValidate(val);
856
+ if (onRowEditInput) onRowEditInput(row, col.field, val);
857
+ };
858
+ const maybeExitDatetimeEdit = () => {
859
+ if (typeof document === "undefined") return;
860
+ setTimeout(() => {
861
+ var _a, _b;
862
+ const activeName = (_b = (_a = document.activeElement) == null ? void 0 : _a.getAttribute) == null ? void 0 : _b.call(_a, "name");
863
+ if (activeName !== `${fieldName}-date` && activeName !== `${fieldName}-time`) {
864
+ exitEdit();
865
+ }
866
+ }, 0);
867
+ };
868
+ switch (type) {
869
+ case "textarea":
870
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TextArea, { ...extra, name: fieldName, label: "", value: editValue ?? "", onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
871
+ case "number":
872
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.NumberInput, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
873
+ case "currency":
874
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.CurrencyInput, { currencyCode: "USD", ...extra, name: fieldName, label: "", value: editValue, onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
875
+ case "stepper":
876
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.StepperInput, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
877
+ case "select":
878
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Select, { variant: "transparent", ...extra, name: fieldName, label: "", value: editValue, onChange: commit, options: resolveEditOptions(col, data) });
879
+ case "multiselect":
880
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.MultiSelect, { ...extra, name: fieldName, label: "", value: editValue || [], onChange: commit, options: resolveEditOptions(col, data) });
881
+ case "date":
882
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.DateInput, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit });
883
+ case "time":
884
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TimeInput, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit });
885
+ case "datetime":
886
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.DateInput, { ...extra, name: `${fieldName}-date`, label: "", value: editValue == null ? void 0 : editValue.date, onChange: (val) => {
887
+ const next = { ...editValue, date: val };
888
+ handleInput(next);
889
+ commitEdit(row, col.field, next, { keepEditing: true });
890
+ }, onBlur: maybeExitDatetimeEdit }), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: "", value: editValue == null ? void 0 : editValue.time, onChange: (val) => {
891
+ const next = { ...editValue, time: val };
892
+ handleInput(next);
893
+ commitEdit(row, col.field, next, { keepEditing: true });
894
+ }, onBlur: maybeExitDatetimeEdit }));
895
+ case "toggle":
896
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Toggle, { ...extra, name: fieldName, label: "", checked: !!editValue, onChange: commit });
897
+ case "checkbox":
898
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Checkbox, { ...extra, name: fieldName, checked: !!editValue, onChange: commit });
899
+ default:
900
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Input, { ...extra, name: fieldName, label: "", value: editValue ?? "", onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
901
+ }
902
+ };
903
+ const resolvedEditMode = editMode || (columns.some((col) => col.editable) ? "discrete" : null);
904
+ const useColumnRendering = selectable || !!resolvedEditMode || editingRowId != null || showRowActionsColumn || !renderRow;
905
+ const autoWidths = (0, import_react2.useMemo)(
906
+ () => autoWidth ? computeAutoWidths(columns, data) : {},
907
+ [columns, data, autoWidth]
908
+ );
909
+ const defaultWidth = scrollable ? "min" : "auto";
910
+ const getHeaderWidth = (col) => {
911
+ var _a;
912
+ return col.width || ((_a = autoWidths[col.field]) == null ? void 0 : _a.width) || defaultWidth;
913
+ };
914
+ const getCellWidth = (col) => {
915
+ var _a;
916
+ return col.cellWidth || col.width || ((_a = autoWidths[col.field]) == null ? void 0 : _a.cellWidth) || defaultWidth;
917
+ };
918
+ const [inlineErrors, setInlineErrors] = (0, import_react2.useState)({});
919
+ const renderInlineControl = (col, row) => {
920
+ const type = col.editType || "text";
921
+ const rowId = row[rowIdField];
922
+ const fieldName = `inline-${rowId}-${col.field}`;
923
+ const cellKey = `${rowId}-${col.field}`;
924
+ const value = row[col.field];
925
+ const validate = col.editValidate;
926
+ const fire = (val) => {
927
+ if (validate) {
928
+ const err = editValidationError(validate(val, row));
929
+ if (err) {
930
+ setInlineErrors((prev) => ({ ...prev, [cellKey]: err }));
931
+ return;
932
+ }
933
+ setInlineErrors((prev) => {
934
+ const next = { ...prev };
935
+ delete next[cellKey];
936
+ return next;
937
+ });
938
+ }
939
+ if (onRowEdit) onRowEdit(row, col.field, val);
940
+ };
941
+ const extra = col.editProps || {};
942
+ const cellError = inlineErrors[cellKey];
943
+ const validationProps = cellError ? { error: true, validationMessage: cellError } : {};
944
+ const onInputValidate = validate ? (val) => {
945
+ const err = editValidationError(validate(val, row));
946
+ setInlineErrors((prev) => {
947
+ if (err) return { ...prev, [cellKey]: err };
948
+ const next = { ...prev };
949
+ delete next[cellKey];
950
+ return next;
951
+ });
952
+ } : void 0;
953
+ const emitInput = (val) => {
954
+ if (onInputValidate) onInputValidate(val);
955
+ if (onRowEditInput) onRowEditInput(row, col.field, val);
956
+ };
957
+ switch (type) {
958
+ case "textarea":
959
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TextArea, { ...extra, name: fieldName, label: "", value: value ?? "", onChange: fire, ...validationProps, onInput: emitInput });
960
+ case "number":
961
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.NumberInput, { ...extra, name: fieldName, label: "", value, onChange: fire, ...validationProps, onInput: emitInput });
962
+ case "currency":
963
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.CurrencyInput, { currencyCode: "USD", ...extra, name: fieldName, label: "", value, onChange: fire, ...validationProps, onInput: emitInput });
964
+ case "stepper":
965
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.StepperInput, { ...extra, name: fieldName, label: "", value, onChange: fire, ...validationProps, onInput: emitInput });
966
+ case "select":
967
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Select, { ...extra, name: fieldName, label: "", value, onChange: fire, options: resolveEditOptions(col, data) });
968
+ case "multiselect":
969
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.MultiSelect, { ...extra, name: fieldName, label: "", value: value || [], onChange: fire, options: resolveEditOptions(col, data) });
970
+ case "date":
971
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.DateInput, { ...extra, name: fieldName, label: "", value, onChange: fire });
972
+ case "time":
973
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TimeInput, { ...extra, name: fieldName, label: "", value, onChange: fire });
974
+ case "datetime":
975
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.DateInput, { ...extra, name: `${fieldName}-date`, label: "", value: value == null ? void 0 : value.date, onChange: (val) => {
976
+ fire({ ...value, date: val });
977
+ } }), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: "", value: value == null ? void 0 : value.time, onChange: (val) => {
978
+ fire({ ...value, time: val });
979
+ } }));
980
+ case "toggle":
981
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Toggle, { ...extra, name: fieldName, label: "", checked: !!value, onChange: fire });
982
+ case "checkbox":
983
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Checkbox, { ...extra, name: fieldName, checked: !!value, onChange: fire });
984
+ default:
985
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Input, { ...extra, name: fieldName, label: "", value: value ?? "", onChange: fire, ...validationProps, onInput: emitInput });
986
+ }
987
+ };
988
+ const renderCellContent = (row, col) => {
989
+ const rowId = row[rowIdField];
990
+ if (resolvedEditMode === "inline" && col.editable) {
991
+ return renderInlineControl(col, row);
992
+ }
993
+ if (editingRowId != null && rowId === editingRowId && col.editable) {
994
+ return renderInlineControl(col, row);
995
+ }
996
+ const isEditing = (editingCell == null ? void 0 : editingCell.rowId) === rowId && (editingCell == null ? void 0 : editingCell.field) === col.field;
997
+ if (isEditing && col.editable) return renderEditControl(col, row);
998
+ const rawValue = row[col.field];
999
+ const rawStr = String(rawValue ?? "");
1000
+ if (col.truncate && rawStr.length > 0) {
1001
+ if (col.truncate === true) {
1002
+ if (col.renderCell) {
1003
+ const content2 = col.renderCell(rawValue, row);
1004
+ if (col.editable) {
1005
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--");
1006
+ }
1007
+ return content2;
1008
+ }
1009
+ if (col.editable) {
1010
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { truncate: { tooltipText: rawStr } }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, rawStr || "--"));
1011
+ }
1012
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { truncate: { tooltipText: rawStr } }, rawStr);
1013
+ }
1014
+ const maxLen = typeof col.truncate === "number" ? col.truncate : col.truncate.maxLength || 100;
1015
+ if (rawStr.length > maxLen) {
1016
+ const truncatedStr = rawStr.slice(0, maxLen) + "\u2026";
1017
+ const content2 = col.renderCell ? col.renderCell(truncatedStr, row) : truncatedStr;
1018
+ if (col.editable) {
1019
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--");
1020
+ }
1021
+ return col.renderCell ? content2 : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { truncate: { tooltipText: rawStr } }, content2 || "--");
1022
+ }
1023
+ }
1024
+ const content = col.renderCell ? col.renderCell(rawValue, row) : rawValue;
1025
+ const isEmpty = content == null || content === "";
1026
+ if (col.editable) {
1027
+ return /* @__PURE__ */ import_react2.default.createElement(
1028
+ import_ui_extensions2.Link,
1029
+ {
1030
+ variant: "dark",
1031
+ onClick: () => startEditing(rowId, col.field, rawValue)
1032
+ },
1033
+ isEmpty ? "--" : content
1034
+ );
1035
+ }
1036
+ return isEmpty ? "--" : content;
1037
+ };
1038
+ const renderFilterControl2 = (filter) => {
1039
+ const type = filter.type || "select";
1040
+ if (type === "multiselect") {
1041
+ return /* @__PURE__ */ import_react2.default.createElement(
1042
+ import_ui_extensions2.MultiSelect,
1043
+ {
1044
+ key: filter.name,
1045
+ name: `filter-${filter.name}`,
1046
+ label: "",
1047
+ placeholder: filter.placeholder || "All",
1048
+ value: filterValues[filter.name] || [],
1049
+ onChange: (val) => handleFilterChange(filter.name, val),
1050
+ options: filter.options
1051
+ }
1052
+ );
1053
+ }
1054
+ if (type === "dateRange") {
1055
+ const rangeVal = filterValues[filter.name] || { from: null, to: null };
1056
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: filter.name, direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react2.default.createElement(
1057
+ import_ui_extensions2.DateInput,
1058
+ {
1059
+ name: `filter-${filter.name}-from`,
1060
+ label: "",
1061
+ placeholder: resolvedDateFromLabel,
1062
+ format: "medium",
1063
+ value: rangeVal.from,
1064
+ onChange: (val) => handleFilterChange(filter.name, { ...rangeVal, from: val })
1065
+ }
1066
+ ), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "dataSync", size: "sm" }), /* @__PURE__ */ import_react2.default.createElement(
1067
+ import_ui_extensions2.DateInput,
1068
+ {
1069
+ size: "sm",
1070
+ name: `filter-${filter.name}-to`,
1071
+ label: "",
1072
+ placeholder: resolvedDateToLabel,
1073
+ format: "medium",
1074
+ value: rangeVal.to,
1075
+ onChange: (val) => handleFilterChange(filter.name, { ...rangeVal, to: val })
1076
+ }
1077
+ ));
1078
+ }
1079
+ return /* @__PURE__ */ import_react2.default.createElement(
1080
+ import_ui_extensions2.Select,
1081
+ {
1082
+ key: filter.name,
1083
+ name: `filter-${filter.name}`,
1084
+ variant: "transparent",
1085
+ placeholder: filter.placeholder || "All",
1086
+ value: filterValues[filter.name],
1087
+ onChange: (val) => handleFilterChange(filter.name, val),
1088
+ options: [
1089
+ { label: filter.placeholder || "All", value: "" },
1090
+ ...filter.options
1091
+ ]
1092
+ }
1093
+ );
1094
+ };
1095
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs" }, title && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", justify: "between", gap: "sm" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, title), countInTitleRow && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel)), hasToolbarContent && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 3 }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showSearch && searchFields.length > 0 && /* @__PURE__ */ import_react2.default.createElement(
1096
+ import_ui_extensions2.SearchInput,
1097
+ {
1098
+ name: "datatable-search",
1099
+ placeholder: searchPlaceholder,
1100
+ value: internalSearchTerm,
1101
+ onChange: handleSearchChange
1102
+ }
1103
+ ), filters.slice(0, filterInlineLimit).map(renderFilterControl2), filters.length > filterInlineLimit && /* @__PURE__ */ import_react2.default.createElement(
1104
+ import_ui_extensions2.Button,
1105
+ {
1106
+ variant: "transparent",
1107
+ size: "small",
1108
+ onClick: () => setShowMoreFilters((prev) => !prev)
1109
+ },
1110
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "filter", size: "sm" }),
1111
+ " ",
1112
+ resolvedFiltersButtonLabel
1113
+ )), showMoreFilters && filters.length > filterInlineLimit && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, filters.slice(filterInlineLimit).map(renderFilterControl2)), activeChips.length > 0 && (showFilterBadges || resolvedShowClearFiltersButton) && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showFilterBadges && activeChips.map((chip) => /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tag, { key: chip.key, variant: "default", onDelete: () => handleFilterRemove(chip.key) }, chip.label)), resolvedShowClearFiltersButton && /* @__PURE__ */ import_react2.default.createElement(
1114
+ import_ui_extensions2.Button,
1115
+ {
1116
+ variant: "transparent",
1117
+ size: "extra-small",
1118
+ onClick: () => handleFilterRemove("all")
1119
+ },
1120
+ resolvedClearAllLabel
1121
+ )))), countInToolbar && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1, alignSelf: "end" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel)))), showSelectionBar && selectable && selectedIds.size > 0 && (renderSelectionBar ? renderSelectionBar({
1122
+ selectedIds,
1123
+ selectedCount: selectedIds.size,
1124
+ displayCount,
1125
+ countLabel,
1126
+ allSelected: selectedIds.size >= (serverSide ? totalCount || data.length : allRowIds.length),
1127
+ onSelectAll: handleSelectAllRows,
1128
+ onDeselectAll: handleDeselectAll,
1129
+ selectionActions
1130
+ }) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 3 }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "sm", wrap: "nowrap" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { inline: true, format: { fontWeight: "demibold" } }, typeof resolvedSelectedLabel === "function" ? resolvedSelectedLabel(selectedIds.size, countLabel(selectedIds.size)) : resolvedSelectedLabel), selectedIds.size < (serverSide ? totalCount || data.length : allRowIds.length) && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "transparent", size: "extra-small", onClick: handleSelectAllRows }, typeof resolvedSelectAllLabel === "function" ? resolvedSelectAllLabel(displayCount, countLabel(displayCount)) : resolvedSelectAllLabel), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "transparent", size: "extra-small", onClick: handleDeselectAll }, resolvedDeselectAllLabel), selectionActions.map((action, i) => /* @__PURE__ */ import_react2.default.createElement(
1131
+ import_ui_extensions2.Button,
1132
+ {
1133
+ key: i,
1134
+ variant: action.variant || "transparent",
1135
+ size: "extra-small",
1136
+ onClick: () => action.onClick([...selectedIds])
1137
+ },
1138
+ action.icon && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: action.icon, size: "sm" }),
1139
+ " ",
1140
+ action.label
1141
+ )))), showRowCount && displayCount > 0 && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1, alignSelf: "center" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel))))), loading ? renderLoadingState ? renderLoadingState({ label: resolvedLoadingLabel }) : (
1142
+ // Same EmptyState layout as the empty state, just the "building" image
1143
+ // + a loading message — so loading and empty match with no layout shift.
1144
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, null, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.EmptyState, { title: resolvedLoadingLabel, imageName: "building", layout: "vertical" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, null, resolvedLoadingMessage))))
1145
+ ) : error ? renderErrorState ? renderErrorState({
1146
+ error,
1147
+ title: typeof error === "string" ? error : resolvedErrorTitle,
1148
+ message: typeof error === "string" ? resolvedRetryMessage : resolvedErrorMessage
1149
+ }) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.ErrorState, { title: typeof error === "string" ? error : resolvedErrorTitle }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, null, typeof error === "string" ? resolvedRetryMessage : resolvedErrorMessage)) : displayRows.length === 0 ? renderEmptyState ? renderEmptyState({ title: resolvedEmptyTitle, message: resolvedEmptyMessage }) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, null, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.EmptyState, { title: resolvedEmptyTitle, layout: "vertical" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, null, resolvedEmptyMessage)))) : /* @__PURE__ */ import_react2.default.createElement(
1150
+ import_ui_extensions2.Table,
1151
+ {
1152
+ bordered,
1153
+ flush,
1154
+ paginated: pageCount > 1,
1155
+ page: activePage,
1156
+ pageCount,
1157
+ onPageChange: handlePageChange,
1158
+ showFirstLastButtons: showFirstLastButtons != null ? showFirstLastButtons : pageCount > 5,
1159
+ showButtonLabels,
1160
+ ...maxVisiblePageButtons != null ? { maxVisiblePageButtons } : {}
1161
+ },
1162
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TableHead, null, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TableRow, null, selectable && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TableHeader, { width: "min" }, /* @__PURE__ */ import_react2.default.createElement(
1163
+ import_ui_extensions2.Checkbox,
1164
+ {
1165
+ name: "datatable-select-all",
1166
+ "aria-label": "Select all rows",
1167
+ checked: allVisibleSelected,
1168
+ onChange: handleSelectAll
1169
+ }
1170
+ )), columns.map((col) => {
1171
+ const headerAlign = resolvedEditMode === "inline" && col.editable ? void 0 : col.align;
1172
+ return /* @__PURE__ */ import_react2.default.createElement(
1173
+ import_ui_extensions2.TableHeader,
1174
+ {
1175
+ key: col.field,
1176
+ width: getHeaderWidth(col),
1177
+ align: headerAlign,
1178
+ sortDirection: col.sortable ? sortState[col.field] || "none" : "never",
1179
+ onSortChange: col.sortable ? () => handleSortChange(col.field) : void 0
1180
+ },
1181
+ col.description ? /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null, col.label, "\xA0", /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { inline: true, variant: "dark", overlay: /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tooltip, null, col.description) }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "info", screenReaderText: typeof col.description === "string" ? col.description : void 0 }))) : col.label
1182
+ );
1183
+ }), showRowActionsColumn && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TableHeader, { width: "min" }))),
1184
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TableBody, null, displayRows.map(
1185
+ (item, idx) => item.type === "group-header" ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TableRow, { key: `group-${item.group.key}` }, selectable && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TableCell, { width: "min" }), columns.map((col, colIdx) => {
1186
+ var _a, _b, _c;
1187
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TableCell, { key: col.field, width: getCellWidth(col), align: colIdx === 0 ? void 0 : col.align }, colIdx === 0 ? /* @__PURE__ */ import_react2.default.createElement(
1188
+ import_ui_extensions2.Link,
1189
+ {
1190
+ variant: "dark",
1191
+ onClick: () => toggleGroup(item.group.key)
1192
+ },
1193
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: expandedGroups.has(item.group.key) ? "downCarat" : "right" }), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, item.group.label))
1194
+ ) : ((_a = groupBy.aggregations) == null ? void 0 : _a[col.field]) ? groupBy.aggregations[col.field](item.group.rows, item.group.key) : ((_c = (_b = groupBy.groupValues) == null ? void 0 : _b[item.group.key]) == null ? void 0 : _c[col.field]) ?? "");
1195
+ }), showRowActionsColumn && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TableCell, { width: "min" })) : useColumnRendering ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TableRow, { key: item.row[rowIdField] ?? idx }, selectable && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TableCell, { width: "min" }, /* @__PURE__ */ import_react2.default.createElement(
1196
+ import_ui_extensions2.Checkbox,
1197
+ {
1198
+ name: `select-${item.row[rowIdField]}`,
1199
+ "aria-label": "Select row",
1200
+ checked: selectedIds.has(item.row[rowIdField]),
1201
+ onChange: (checked) => handleSelectRow(item.row[rowIdField], checked)
1202
+ }
1203
+ )), columns.map((col) => {
1204
+ const rowId = item.row[rowIdField];
1205
+ const isDiscreteEditing = resolvedEditMode === "discrete" && (editingCell == null ? void 0 : editingCell.rowId) === rowId && (editingCell == null ? void 0 : editingCell.field) === col.field;
1206
+ const isRowEditing = editingRowId != null && rowId === editingRowId && col.editable;
1207
+ const isShowingInput = isDiscreteEditing || isRowEditing || resolvedEditMode === "inline" && col.editable;
1208
+ const cellAlign = isShowingInput ? void 0 : col.align;
1209
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TableCell, { key: col.field, width: isDiscreteEditing || isRowEditing ? "auto" : getCellWidth(col), align: cellAlign }, renderCellContent(item.row, col));
1210
+ }), showRowActionsColumn && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TableCell, { width: "min" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, (() => {
1211
+ const resolvedRowActions = typeof rowActions === "function" ? rowActions(item.row) : rowActions;
1212
+ const actions = Array.isArray(resolvedRowActions) ? resolvedRowActions : [];
1213
+ return actions.map((action, i) => /* @__PURE__ */ import_react2.default.createElement(
1214
+ import_ui_extensions2.Button,
1215
+ {
1216
+ key: i,
1217
+ variant: action.variant || "transparent",
1218
+ size: "extra-small",
1219
+ onClick: () => action.onClick(item.row)
1220
+ },
1221
+ action.icon && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: action.icon, size: "sm" }),
1222
+ action.label && ` ${action.label}`
1223
+ ));
1224
+ })()))) : renderRow(item.row)
1225
+ )),
1226
+ (footer || columns.some((col) => col.footer)) && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TableFooter, null, typeof footer === "function" ? footer(footerData) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TableRow, null, selectable && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TableHeader, { width: "min" }), columns.map((col) => {
1227
+ const footerDef = col.footer;
1228
+ const content = typeof footerDef === "function" ? footerDef(footerData) : footerDef || "";
1229
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TableHeader, { key: col.field, align: col.align }, content);
1230
+ }), showRowActionsColumn && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.TableHeader, { width: "min" })))
1231
+ ));
1232
+ };
1233
+
1234
+ // packages/kanban/src/Kanban.jsx
1235
+ var import_react4 = __toESM(require("react"));
1236
+
1237
+ // src/common-components/StyledText.js
1238
+ var import_react3 = __toESM(require("react"));
1239
+ var import_ui_extensions3 = require("@hubspot/ui-extensions");
1240
+
1241
+ // src/common-components/svgDefaults.js
1242
+ var HS_FONT_FAMILY = '"Lexend Deca", Helvetica, Arial, sans-serif';
1243
+ var HS_TEXT_COLOR = "#33475b";
1244
+ var HS_SUBTLE_BG = "#F5F8FA";
1245
+ var HS_TAG_SUBTLE_BORDER = "#7C98B6";
1246
+ var HS_TAG_TEXT_COLOR = HS_TEXT_COLOR;
1247
+ var HS_TAG_FONT_SIZE = 12;
1248
+ var HS_TAG_LINE_HEIGHT = 22;
1249
+ var HS_TAG_PADDING_X = 8;
1250
+ var HS_TAG_PADDING_Y = 0;
1251
+ var HS_TAG_BORDER_RADIUS = 0;
1252
+ var HS_TAG_BORDER_WIDTH = 1;
1253
+
1254
+ // src/common-components/StyledText.js
1255
+ var VARIANT_PRESETS = {
1256
+ bodytext: { fontSize: 14, lineHeight: 24, fontWeight: 400 },
1257
+ microcopy: { fontSize: 12, lineHeight: 18, fontWeight: 400 }
1258
+ };
1259
+ var WEIGHT_ALIASES = {
1260
+ bold: 700,
1261
+ demibold: 600,
1262
+ regular: 400
1263
+ };
1264
+ var LINE_DECORATION = {
1265
+ strikethrough: "line-through",
1266
+ underline: "underline"
1267
+ };
1268
+ var ORIENTATION_ROTATION = {
1269
+ horizontal: 0,
1270
+ "vertical-up": -90,
1271
+ "vertical-down": 90
1272
+ };
1273
+ var BACKGROUND_PRESETS = {
1274
+ tag: {
1275
+ color: HS_SUBTLE_BG,
1276
+ borderColor: HS_TAG_SUBTLE_BORDER,
1277
+ borderWidth: HS_TAG_BORDER_WIDTH,
1278
+ radius: HS_TAG_BORDER_RADIUS,
1279
+ paddingX: HS_TAG_PADDING_X,
1280
+ paddingY: HS_TAG_PADDING_Y,
1281
+ height: HS_TAG_LINE_HEIGHT,
1282
+ textColor: HS_TAG_TEXT_COLOR,
1283
+ fontSize: HS_TAG_FONT_SIZE,
1284
+ canvasPaddingX: 0,
1285
+ canvasPaddingY: 0
1286
+ }
1287
+ };
1288
+ var TAG_VARIANTS = {
1289
+ default: {
1290
+ color: HS_SUBTLE_BG,
1291
+ borderColor: HS_TAG_SUBTLE_BORDER,
1292
+ textColor: HS_TAG_TEXT_COLOR
1293
+ },
1294
+ success: {
1295
+ color: "#E5F8F6",
1296
+ borderColor: "#00BDA5",
1297
+ textColor: "#00BDA5"
1298
+ },
1299
+ warning: {
1300
+ color: "#FEF8F0",
1301
+ borderColor: "#F5C26B",
1302
+ textColor: "#D39913"
1303
+ },
1304
+ error: {
1305
+ color: "#FDEDEE",
1306
+ borderColor: "#F2545B",
1307
+ textColor: "#F2545B"
1308
+ },
1309
+ danger: {
1310
+ color: "#FDEDEE",
1311
+ borderColor: "#F2545B",
1312
+ textColor: "#F2545B"
1313
+ },
1314
+ info: {
1315
+ color: "#E5F5F8",
1316
+ borderColor: "#00A4BD",
1317
+ textColor: "#00A4BD"
1318
+ }
1319
+ };
1320
+ var escapeSvgText = (s) => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1321
+ var applyTextTransform = (text, transform) => {
1322
+ if (!transform || transform === "none") return String(text);
1323
+ const s = String(text);
1324
+ switch (transform) {
1325
+ case "uppercase":
1326
+ return s.toUpperCase();
1327
+ case "lowercase":
1328
+ return s.toLowerCase();
1329
+ case "capitalize":
1330
+ return s.replace(/\b\w/g, (c) => c.toUpperCase());
1331
+ case "sentenceCase":
1332
+ return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
1333
+ default:
1334
+ return s;
1335
+ }
1336
+ };
1337
+ var estimateTextWidth = (text, fontSize) => Math.max(fontSize, Math.round(String(text).length * fontSize * 0.58));
1338
+ var resolveBackground = (background) => {
1339
+ if (!background) return null;
1340
+ const preset = background.preset ? BACKGROUND_PRESETS[background.preset] : null;
1341
+ const variant = background.preset === "tag" && background.variant ? TAG_VARIANTS[background.variant] || null : null;
1342
+ return {
1343
+ ...preset || {},
1344
+ ...variant || {},
1345
+ ...background
1346
+ };
1347
+ };
1348
+ var buildBackgroundRect = ({ background, x, y, width, height }) => {
1349
+ const radius = (background == null ? void 0 : background.radius) ?? 3;
1350
+ const fill = (background == null ? void 0 : background.color) ?? "transparent";
1351
+ const borderWidth = (background == null ? void 0 : background.borderWidth) ?? 0;
1352
+ const borderColor = background == null ? void 0 : background.borderColor;
1353
+ if (!borderColor || borderWidth <= 0) {
1354
+ return `<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="${radius}" fill="${fill}" />`;
1355
+ }
1356
+ const isTagPreset = (background == null ? void 0 : background.preset) === "tag";
1357
+ const fillRect = `<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="${radius}" fill="${fill}" />`;
1358
+ const strokeInset = borderWidth / 2;
1359
+ const strokeX = x + strokeInset;
1360
+ const strokeY = y + strokeInset;
1361
+ const strokeW = Math.max(0, width - borderWidth);
1362
+ const strokeH = Math.max(0, height - borderWidth);
1363
+ return fillRect + `<rect x="${strokeX}" y="${strokeY}" width="${strokeW}" height="${strokeH}" rx="${Math.max(
1364
+ 0,
1365
+ radius - strokeInset
1366
+ )}" fill="none" stroke="${borderColor}" stroke-width="${borderWidth}"${isTagPreset ? ` shape-rendering="crispEdges"` : ""} />`;
1367
+ };
1368
+ var makeStyledTextDataUri = (text, opts = {}) => {
1369
+ const {
1370
+ variant = "bodytext",
1371
+ format = {},
1372
+ orientation = "horizontal",
1373
+ color: colorProp = HS_TEXT_COLOR,
1374
+ fontFamily = HS_FONT_FAMILY,
1375
+ background: backgroundProp = null,
1376
+ paddingX: paddingXProp = 4,
1377
+ paddingY: paddingYProp = 2,
1378
+ width: widthOverride,
1379
+ height: heightOverride,
1380
+ fontSize: fontSizeOverride
1381
+ } = opts;
1382
+ const preset = VARIANT_PRESETS[variant] || VARIANT_PRESETS.bodytext;
1383
+ const background = resolveBackground(backgroundProp);
1384
+ const fontSize = fontSizeOverride ?? (background == null ? void 0 : background.fontSize) ?? preset.fontSize;
1385
+ const rawWeight = format.fontWeight;
1386
+ const fontWeight = rawWeight ? WEIGHT_ALIASES[rawWeight] ?? rawWeight : preset.fontWeight;
1387
+ const fontStyle = format.italic ? "italic" : "normal";
1388
+ const textDecoration = LINE_DECORATION[format.lineDecoration] || "none";
1389
+ const transformed = applyTextTransform(text, format.textTransform);
1390
+ const lineHeight = (background == null ? void 0 : background.height) ?? preset.lineHeight ?? fontSize;
1391
+ const color = (background == null ? void 0 : background.textColor) ?? colorProp;
1392
+ const paddingX = (background == null ? void 0 : background.canvasPaddingX) ?? paddingXProp;
1393
+ const paddingY = (background == null ? void 0 : background.canvasPaddingY) ?? paddingYProp;
1394
+ const rotate = typeof orientation === "number" ? orientation : ORIENTATION_ROTATION[orientation] ?? 0;
1395
+ const textW = estimateTextWidth(transformed, fontSize);
1396
+ let pillW = 0;
1397
+ let pillH = 0;
1398
+ if (background) {
1399
+ const bgPadX = background.paddingX ?? 6;
1400
+ const bgPadY = background.paddingY ?? 3;
1401
+ pillW = textW + bgPadX * 2;
1402
+ pillH = background.height ?? Math.max(lineHeight, fontSize + bgPadY * 2);
1403
+ }
1404
+ const intrinsicW = (background ? pillW : textW) + paddingX * 2;
1405
+ const intrinsicH = (background ? pillH : lineHeight) + paddingY * 2;
1406
+ const isOrthoRotation = rotate === 90 || rotate === -90 || rotate === 270;
1407
+ const canvasW = widthOverride ?? (isOrthoRotation ? intrinsicH : intrinsicW);
1408
+ const canvasH = heightOverride ?? (isOrthoRotation ? intrinsicW : intrinsicH);
1409
+ const cx = canvasW / 2;
1410
+ const cy = canvasH / 2;
1411
+ const rectX = cx - pillW / 2;
1412
+ const rectY = cy - pillH / 2;
1413
+ const group = (background ? buildBackgroundRect({
1414
+ background,
1415
+ x: rectX,
1416
+ y: rectY,
1417
+ width: pillW,
1418
+ height: pillH
1419
+ }) : "") + `<text x="${cx}" y="${cy}" text-anchor="middle" dominant-baseline="central" font-family="${fontFamily.replace(/"/g, "&quot;")}" font-size="${fontSize}" font-weight="${fontWeight}" font-style="${fontStyle}" text-decoration="${textDecoration}" fill="${color}">${escapeSvgText(transformed)}</text>`;
1420
+ const wrapped = rotate ? `<g transform="rotate(${rotate} ${cx} ${cy})">${group}</g>` : group;
1421
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${canvasW}" height="${canvasH}">` + wrapped + `</svg>`;
1422
+ return {
1423
+ src: `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`,
1424
+ width: canvasW,
1425
+ height: canvasH
1426
+ };
1427
+ };
1428
+
1429
+ // packages/kanban/src/Kanban.jsx
1430
+ var import_ui_extensions4 = require("@hubspot/ui-extensions");
1431
+ var DEFAULT_DENSITY = "compact";
1432
+ var DEFAULT_MAX_CARDS = 10;
1433
+ var DEFAULT_MAX_EXPANDED = 50;
1434
+ var DEFAULT_COLUMN_WIDTH = 350;
1435
+ var MIN_COLUMN_WIDTH = 350;
1436
+ var DEFAULT_FILTER_INLINE_LIMIT = 4;
1437
+ var DEFAULT_SEARCH_DEBOUNCE = 250;
1438
+ var DEFAULT_TITLE_TRUNCATE = 60;
1439
+ var applyTruncate = (value, truncate, fallback) => {
1440
+ if (truncate === false) return value;
1441
+ if (typeof value !== "string") return value;
1442
+ const limit = typeof truncate === "number" ? truncate : truncate === true ? fallback || DEFAULT_TITLE_TRUNCATE : fallback || DEFAULT_TITLE_TRUNCATE;
1443
+ if (value.length <= limit) return value;
1444
+ return value.slice(0, limit).trimEnd() + "\u2026";
1445
+ };
1446
+ var DEFAULT_LABELS = {
1447
+ search: "Search cards...",
1448
+ // Only the total is surfaced — callers asked for a single headline number
1449
+ // rather than a "loaded / total" fraction. Fall back to the bare label when
1450
+ // no total is known.
1451
+ showMore: (_shown, total) => total ? `Show more (${total})` : "Show more",
1452
+ showLess: "Show less",
1453
+ loadMore: (_loaded, total) => total ? `Load more (${total})` : "Load more",
1454
+ loadingMore: "Loading...",
1455
+ retryLoadMore: "Retry",
1456
+ emptyColumn: "\u2014",
1457
+ emptyTitle: "No cards",
1458
+ emptyMessage: "Nothing matches the current filters.",
1459
+ loading: "Loading board...",
1460
+ loadingMessage: "This should only take a moment.",
1461
+ errorTitle: "Something went wrong.",
1462
+ errorMessage: "An error occurred while loading data.",
1463
+ cardCount: (n) => String(n),
1464
+ moveTo: "Move",
1465
+ clearAll: "Clear all",
1466
+ selectAll: (count, label) => `Select all ${count} ${label}`,
1467
+ deselectAll: "Deselect all",
1468
+ selected: (count, label) => `${count}\xA0${label}\xA0selected`,
1469
+ filtersButton: "Filters",
1470
+ dateFrom: "From",
1471
+ dateTo: "To",
1472
+ sortButton: "Sort",
1473
+ sortAscending: "Ascending",
1474
+ sortDescending: "Descending",
1475
+ metricsButton: "Metrics"
1476
+ };
1477
+ var makeRotatedTagDataUri = (label) => makeStyledTextDataUri(label, {
1478
+ variant: "microcopy",
1479
+ format: { fontWeight: "demibold" },
1480
+ orientation: "vertical-down",
1481
+ background: { preset: "tag" }
1482
+ });
1483
+ var makeRotatedLabelDataUri = (label) => makeStyledTextDataUri(label, {
1484
+ variant: "bodytext",
1485
+ format: { fontWeight: "demibold" },
1486
+ orientation: "vertical-down"
1487
+ });
1488
+ var canStageReceiveRow = (stage, row, canMove) => {
1489
+ if (!stage) return false;
1490
+ if (typeof canMove === "function" && !canMove(row, stage.value)) return false;
1491
+ if (typeof stage.canEnter === "function" && !stage.canEnter(row)) return false;
1492
+ return true;
1493
+ };
1494
+ var isFieldDirectionSortOption = (option) => !!(option && option.field && (option.direction === "asc" || option.direction === "desc"));
1495
+ var resolveDividers = (cardDividers, density) => {
1496
+ if (cardDividers === true) {
1497
+ return { afterTitle: true, afterSubtitle: true, afterBody: true, afterFooter: true };
1498
+ }
1499
+ if (cardDividers === false) {
1500
+ return { afterTitle: false, afterSubtitle: false, afterBody: false, afterFooter: false };
1501
+ }
1502
+ if (cardDividers && typeof cardDividers === "object") {
1503
+ return {
1504
+ afterTitle: cardDividers.afterTitle ?? false,
1505
+ afterSubtitle: cardDividers.afterSubtitle ?? false,
1506
+ afterBody: cardDividers.afterBody ?? false,
1507
+ afterFooter: cardDividers.afterFooter ?? false
1508
+ };
1509
+ }
1510
+ if (density === "comfortable") {
1511
+ return { afterTitle: true, afterSubtitle: true, afterBody: true, afterFooter: true };
1512
+ }
1513
+ return { afterTitle: false, afterSubtitle: false, afterBody: true, afterFooter: false };
1514
+ };
1515
+ var partitionFields = (cardFields) => {
1516
+ const buckets = { title: null, subtitle: null, meta: [], body: [], footer: [] };
1517
+ for (const field of cardFields || []) {
1518
+ const placement = field.placement || "body";
1519
+ if (placement === "title" && !buckets.title) buckets.title = field;
1520
+ else if (placement === "subtitle" && !buckets.subtitle) buckets.subtitle = field;
1521
+ else if (placement === "meta") buckets.meta.push(field);
1522
+ else if (placement === "footer") buckets.footer.push(field);
1523
+ else buckets.body.push(field);
1524
+ }
1525
+ return buckets;
1526
+ };
1527
+ var resolveFieldValue = (field, row) => {
1528
+ if (!field) return void 0;
1529
+ if (field.field && row && Object.prototype.hasOwnProperty.call(row, field.field)) {
1530
+ return row[field.field];
1531
+ }
1532
+ return void 0;
1533
+ };
1534
+ var resolveHref = (href, row) => {
1535
+ if (!href) return null;
1536
+ if (typeof href === "function") return href(row);
1537
+ return href;
1538
+ };
1539
+ var KanbanCard = ({
1540
+ row,
1541
+ rowId,
1542
+ stage,
1543
+ stages,
1544
+ fields,
1545
+ density,
1546
+ dividers,
1547
+ bodyAs,
1548
+ maxBodyLines,
1549
+ stageControl,
1550
+ stageControlPlacement,
1551
+ canMove,
1552
+ onStageChangeRequest,
1553
+ isChanging,
1554
+ selectable,
1555
+ selected,
1556
+ onToggleSelect,
1557
+ labels
1558
+ }) => {
1559
+ const titleHref = fields.title ? resolveHref(fields.title.href, row) : null;
1560
+ const rawTitleValue = fields.title ? fields.title.render ? fields.title.render(resolveFieldValue(fields.title, row), row) : resolveFieldValue(fields.title, row) : null;
1561
+ const titleValue = fields.title && typeof rawTitleValue === "string" ? applyTruncate(rawTitleValue, fields.title.truncate, DEFAULT_TITLE_TRUNCATE) : rawTitleValue;
1562
+ const titleNode = titleHref ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Link, { href: titleHref }, titleValue) : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { format: { fontWeight: "demibold" } }, titleValue);
1563
+ const metaNodes = fields.meta.filter((f) => !f.visible || f.visible(row)).map((f) => {
1564
+ const val = resolveFieldValue(f, row);
1565
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { key: f.field || f.label, variant: "microcopy" }, f.render ? f.render(val, row) : val);
1566
+ });
1567
+ const showSubtitle = density === "comfortable" && fields.subtitle;
1568
+ const subtitleNode = showSubtitle ? fields.subtitle.render ? fields.subtitle.render(resolveFieldValue(fields.subtitle, row), row) : resolveFieldValue(fields.subtitle, row) : null;
1569
+ const bodyFields = fields.body.filter((f) => !f.visible || f.visible(row)).slice(0, maxBodyLines);
1570
+ const footerFields = fields.footer.filter((f) => !f.visible || f.visible(row));
1571
+ const footerAlerts = footerFields.slice(0, -1);
1572
+ const footerActionsField = footerFields.length > 0 ? footerFields[footerFields.length - 1] : null;
1573
+ const renderFooterField = (f, idx) => {
1574
+ const val = resolveFieldValue(f, row);
1575
+ const rendered = f.render ? f.render(val, row) : val;
1576
+ const key = f.key || f.field || f.label || `footer-${idx}`;
1577
+ return /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, { key }, rendered);
1578
+ };
1579
+ const stageControlNode = stageControl === "none" ? null : /* @__PURE__ */ import_react4.default.createElement(
1580
+ StageControl,
1581
+ {
1582
+ row,
1583
+ rowId,
1584
+ currentStage: stage,
1585
+ stages,
1586
+ canMove,
1587
+ isChanging,
1588
+ mode: stageControl,
1589
+ onStageChangeRequest,
1590
+ labels
1591
+ }
1592
+ );
1593
+ const titleRow = /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", justify: "between", align: "center", gap: "sm" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Box, { flex: 1 }, titleNode), selectable ? /* @__PURE__ */ import_react4.default.createElement(
1594
+ import_ui_extensions4.Checkbox,
1595
+ {
1596
+ name: `kanban-select-${rowId}`,
1597
+ checked: selected,
1598
+ onChange: () => onToggleSelect(rowId)
1599
+ }
1600
+ ) : null);
1601
+ const metaRow = metaNodes.length === 0 ? null : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", justify: "end", align: "center", gap: "xs" }, metaNodes);
1602
+ const bodyRow = bodyFields.length === 0 ? null : bodyAs === "descriptionList" ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.DescriptionList, { direction: "row" }, bodyFields.map((f, idx) => {
1603
+ const val = resolveFieldValue(f, row);
1604
+ const rendered = f.render ? f.render(val, row) : val ?? "\u2014";
1605
+ const key = f.key || f.field || f.label || `body-${idx}`;
1606
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.DescriptionListItem, { key, label: f.label || "" }, rendered);
1607
+ })) : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: "flush" }, bodyFields.map((f, idx) => {
1608
+ const val = resolveFieldValue(f, row);
1609
+ const rendered = f.render ? f.render(val, row) : val ?? "\u2014";
1610
+ const key = f.key || f.field || f.label || `body-${idx}`;
1611
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { key, variant: "microcopy" }, f.label ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { inline: true, variant: "microcopy" }, `${f.label}: `) : null, rendered);
1612
+ }));
1613
+ const footerAlertsNode = footerAlerts.length === 0 ? null : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: "xs" }, footerAlerts.map((f, idx) => renderFooterField(f, idx)));
1614
+ const footerActionsNode = footerActionsField ? renderFooterField(footerActionsField, footerFields.length - 1) : null;
1615
+ const inlineStageControl = stageControlPlacement === "inline" ? stageControlNode : null;
1616
+ const separateRowStageControl = stageControlPlacement === "separateRow" ? stageControlNode : null;
1617
+ const footerMainRow = inlineStageControl || footerActionsNode ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", justify: "between", align: "start", gap: "sm" }, inlineStageControl ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Box, { alignSelf: "center" }, inlineStageControl) : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Box, null), footerActionsNode ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Box, { flex: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", justify: "end", align: "start" }, footerActionsNode)) : null) : null;
1618
+ const footerRow = !footerAlertsNode && !footerMainRow ? null : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: "xs" }, footerAlertsNode, footerMainRow);
1619
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Tile, { compact: density === "compact" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: density === "compact" ? "xs" : "sm" }, titleRow, dividers.afterTitle && (metaRow || bodyRow || footerRow || separateRowStageControl) ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Divider, null) : null, subtitleNode ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { variant: "microcopy" }, subtitleNode) : null, dividers.afterSubtitle && subtitleNode && (metaRow || bodyRow || footerRow || separateRowStageControl) ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Divider, null) : null, metaRow, bodyRow, dividers.afterBody && bodyRow && (footerRow || separateRowStageControl) ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Divider, null) : null, footerRow, dividers.afterFooter && footerRow && separateRowStageControl ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Divider, null) : null, separateRowStageControl));
1620
+ };
1621
+ var StageControl = ({
1622
+ row,
1623
+ rowId,
1624
+ currentStage,
1625
+ stages,
1626
+ canMove,
1627
+ isChanging,
1628
+ mode,
1629
+ onStageChangeRequest,
1630
+ labels
1631
+ }) => {
1632
+ if (isChanging) {
1633
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.LoadingSpinner, { size: "xs" });
1634
+ }
1635
+ const availableStages = (stages || []).filter(
1636
+ (stage) => stage.value === currentStage.value || canStageReceiveRow(stage, row, canMove)
1637
+ );
1638
+ if (mode === "menu") {
1639
+ const targetStages = availableStages.filter((stage) => stage.value !== currentStage.value);
1640
+ if (targetStages.length === 0) {
1641
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Button, { variant: "transparent", size: "extra-small", disabled: true }, labels.moveTo);
1642
+ }
1643
+ return /* @__PURE__ */ import_react4.default.createElement(
1644
+ import_ui_extensions4.Dropdown,
1645
+ {
1646
+ variant: "transparent",
1647
+ buttonText: labels.moveTo,
1648
+ buttonSize: "xs"
1649
+ },
1650
+ targetStages.map((stage) => /* @__PURE__ */ import_react4.default.createElement(
1651
+ import_ui_extensions4.Dropdown.ButtonItem,
1652
+ {
1653
+ key: stage.value,
1654
+ onClick: () => onStageChangeRequest(row, stage.value, currentStage.value)
1655
+ },
1656
+ stage.shortLabel || stage.label
1657
+ ))
1658
+ );
1659
+ }
1660
+ return /* @__PURE__ */ import_react4.default.createElement(
1661
+ import_ui_extensions4.Select,
1662
+ {
1663
+ name: `stage-${rowId}`,
1664
+ label: "",
1665
+ value: currentStage.value,
1666
+ onChange: (val) => {
1667
+ if (val !== currentStage.value) onStageChangeRequest(row, val, currentStage.value);
1668
+ },
1669
+ options: availableStages.map((stage) => ({
1670
+ label: stage.shortLabel || stage.label,
1671
+ value: stage.value
1672
+ }))
1673
+ }
1674
+ );
1675
+ };
1676
+ var KanbanColumn = ({
1677
+ stage,
1678
+ rows,
1679
+ bucketCount,
1680
+ totalCount,
1681
+ hasMore,
1682
+ loading,
1683
+ error,
1684
+ onLoadMore,
1685
+ expanded,
1686
+ onToggleExpanded,
1687
+ collapsed,
1688
+ onToggleCollapsed,
1689
+ columnFooter,
1690
+ countDisplay,
1691
+ labels,
1692
+ children
1693
+ }) => {
1694
+ const countLabel = labels.cardCount(totalCount != null ? totalCount : bucketCount);
1695
+ const countNode = countDisplay === "text" ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { format: { fontWeight: "demibold" } }, countLabel) : countDisplay === "none" ? null : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Tag, { variant: "default" }, countLabel);
1696
+ if (collapsed) {
1697
+ const rotated = makeRotatedLabelDataUri(stage.label);
1698
+ const rotatedCount = countDisplay === "none" ? null : makeRotatedTagDataUri(countLabel);
1699
+ const stageIdentifier = stage.icon ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Icon, { name: stage.icon, size: "sm", screenReaderText: stage.label }) : /* @__PURE__ */ import_react4.default.createElement(
1700
+ import_ui_extensions4.Image,
1701
+ {
1702
+ src: rotated.src,
1703
+ width: rotated.width,
1704
+ height: rotated.height,
1705
+ alt: stage.label
1706
+ }
1707
+ );
1708
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Tile, { compact: true }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: "xs", align: "center" }, /* @__PURE__ */ import_react4.default.createElement(
1709
+ import_ui_extensions4.Button,
1710
+ {
1711
+ variant: "transparent",
1712
+ size: "sm",
1713
+ onClick: onToggleCollapsed,
1714
+ tooltip: `Expand ${stage.label}`
1715
+ },
1716
+ /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Icon, { name: "right", size: "sm", screenReaderText: `Expand ${stage.label}` })
1717
+ ), stageIdentifier, rotatedCount ? /* @__PURE__ */ import_react4.default.createElement(
1718
+ import_ui_extensions4.Image,
1719
+ {
1720
+ src: rotatedCount.src,
1721
+ width: rotatedCount.width,
1722
+ height: rotatedCount.height,
1723
+ alt: `${bucketCount} items`
1724
+ }
1725
+ ) : null));
1726
+ }
1727
+ const footerContent = stage.footer ? stage.footer(rows) : columnFooter ? columnFooter(rows, stage) : null;
1728
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Tile, { compact: true }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), countNode, loading ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.LoadingSpinner, { size: "xs" }) : null), /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Button, { variant: "transparent", size: "sm", onClick: onToggleCollapsed, tooltip: "Collapse" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Icon, { name: "left", size: "sm", screenReaderText: `Collapse ${stage.label}` }))), footerContent ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { variant: "microcopy" }, footerContent) : null, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Divider, null), children, error ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Alert, { variant: "danger", title: labels.errorTitle }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", gap: "xs", align: "center" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { variant: "microcopy" }, error), onLoadMore ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Button, { variant: "transparent", size: "xs", onClick: () => onLoadMore(stage.value) }, labels.retryLoadMore) : null)) : null, !error && hasMore && onLoadMore && !loading && bucketCount > 0 ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Link, { onClick: () => onLoadMore(stage.value) }, labels.loadMore(bucketCount, totalCount))) : null, !error && loading && hasMore ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.LoadingSpinner, { size: "sm", layout: "centered", label: labels.loadingMore }) : null, !error && !hasMore && bucketCount > rows.length ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Link, { onClick: onToggleExpanded }, expanded ? labels.showLess : labels.showMore(rows.length, bucketCount))) : null, rows.length === 0 && bucketCount === 0 && !loading ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn) : null));
1729
+ };
1730
+ var SortModalBody = ({ sortOptions, sortValue, onSortChange, labels }) => {
1731
+ const hasFieldDirection = Array.isArray(sortOptions) && sortOptions.length > 0 && sortOptions.every(isFieldDirectionSortOption);
1732
+ if (!hasFieldDirection) {
1733
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: "xs" }, sortOptions.map((opt) => /* @__PURE__ */ import_react4.default.createElement(
1734
+ import_ui_extensions4.Button,
1735
+ {
1736
+ key: opt.value,
1737
+ variant: sortValue === opt.value ? "primary" : "secondary",
1738
+ onClick: () => onSortChange(opt.value)
1739
+ },
1740
+ opt.label
1741
+ )));
1742
+ }
1743
+ const currentOption = sortOptions.find((o) => o.value === sortValue) || sortOptions[0];
1744
+ const currentField = currentOption.field;
1745
+ const currentDirection = currentOption.direction;
1746
+ const uniqueFields = [];
1747
+ const seen = /* @__PURE__ */ new Set();
1748
+ for (const opt of sortOptions) {
1749
+ if (!seen.has(opt.field)) {
1750
+ seen.add(opt.field);
1751
+ uniqueFields.push({ value: opt.field, label: opt.fieldLabel || opt.field });
1752
+ }
1753
+ }
1754
+ const fieldOpts = sortOptions.filter((o) => o.field === currentField);
1755
+ const ascOption = fieldOpts.find((o) => o.direction === "asc");
1756
+ const descOption = fieldOpts.find((o) => o.direction === "desc");
1757
+ const handleFieldChange = (newField) => {
1758
+ const next = sortOptions.find((o) => o.field === newField && o.direction === currentDirection) || sortOptions.find((o) => o.field === newField && o.direction === "desc") || sortOptions.find((o) => o.field === newField);
1759
+ if (next) onSortChange(next.value);
1760
+ };
1761
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Inline, { align: "center", gap: "small" }, /* @__PURE__ */ import_react4.default.createElement(
1762
+ import_ui_extensions4.Select,
1763
+ {
1764
+ name: "kanban-sort-field",
1765
+ label: "",
1766
+ value: currentField,
1767
+ onChange: handleFieldChange,
1768
+ options: uniqueFields
1769
+ }
1770
+ ), /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Inline, { align: "center", gap: "flush" }, descOption ? /* @__PURE__ */ import_react4.default.createElement(
1771
+ import_ui_extensions4.Button,
1772
+ {
1773
+ variant: currentDirection === "desc" ? "primary" : "secondary",
1774
+ onClick: () => onSortChange(descOption.value)
1775
+ },
1776
+ labels.sortDescending
1777
+ ) : null, ascOption ? /* @__PURE__ */ import_react4.default.createElement(
1778
+ import_ui_extensions4.Button,
1779
+ {
1780
+ variant: currentDirection === "asc" ? "primary" : "secondary",
1781
+ onClick: () => onSortChange(ascOption.value)
1782
+ },
1783
+ labels.sortAscending
1784
+ ) : null));
1785
+ };
1786
+ var renderFilterControl = ({ filter, value, onChange, labels }) => {
1787
+ const type = filter.type || "select";
1788
+ if (type === "multiselect") {
1789
+ return /* @__PURE__ */ import_react4.default.createElement(
1790
+ import_ui_extensions4.MultiSelect,
1791
+ {
1792
+ key: filter.name,
1793
+ name: `kanban-filter-${filter.name}`,
1794
+ label: "",
1795
+ placeholder: filter.placeholder || "All",
1796
+ value: value || [],
1797
+ onChange: (val) => onChange(filter.name, val),
1798
+ options: filter.options
1799
+ }
1800
+ );
1801
+ }
1802
+ if (type === "dateRange") {
1803
+ const rangeVal = value || { from: null, to: null };
1804
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { key: filter.name, direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react4.default.createElement(
1805
+ import_ui_extensions4.DateInput,
1806
+ {
1807
+ size: "sm",
1808
+ name: `kanban-filter-${filter.name}-from`,
1809
+ label: "",
1810
+ placeholder: labels.dateFrom,
1811
+ format: "medium",
1812
+ value: rangeVal.from,
1813
+ onChange: (val) => onChange(filter.name, { ...rangeVal, from: val })
1814
+ }
1815
+ ), /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Icon, { name: "dataSync", size: "sm" }), /* @__PURE__ */ import_react4.default.createElement(
1816
+ import_ui_extensions4.DateInput,
1817
+ {
1818
+ size: "sm",
1819
+ name: `kanban-filter-${filter.name}-to`,
1820
+ label: "",
1821
+ placeholder: labels.dateTo,
1822
+ format: "medium",
1823
+ value: rangeVal.to,
1824
+ onChange: (val) => onChange(filter.name, { ...rangeVal, to: val })
1825
+ }
1826
+ ));
1827
+ }
1828
+ return /* @__PURE__ */ import_react4.default.createElement(
1829
+ import_ui_extensions4.Select,
1830
+ {
1831
+ key: filter.name,
1832
+ name: `kanban-filter-${filter.name}`,
1833
+ variant: "transparent",
1834
+ placeholder: filter.placeholder || "All",
1835
+ value,
1836
+ onChange: (val) => onChange(filter.name, val),
1837
+ options: [
1838
+ { label: filter.placeholder || "All", value: "" },
1839
+ ...filter.options
1840
+ ]
1841
+ }
1842
+ );
1843
+ };
1844
+ var renderMetricsPanel = (metrics) => {
1845
+ if (!metrics) return null;
1846
+ if (!Array.isArray(metrics)) return metrics;
1847
+ if (metrics.length === 0) return null;
1848
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Statistics, null, metrics.map((m, i) => /* @__PURE__ */ import_react4.default.createElement(
1849
+ import_ui_extensions4.StatisticsItem,
1850
+ {
1851
+ key: m.id || m.label || i,
1852
+ label: m.label,
1853
+ number: m.number != null ? String(m.number) : ""
1854
+ },
1855
+ m.trend ? /* @__PURE__ */ import_react4.default.createElement(
1856
+ import_ui_extensions4.StatisticsTrend,
1857
+ {
1858
+ direction: m.trend.direction || "increase",
1859
+ value: m.trend.value,
1860
+ color: m.trend.color
1861
+ }
1862
+ ) : null
1863
+ )));
1864
+ };
1865
+ var KanbanToolbar = ({
1866
+ showSearch,
1867
+ searchValue,
1868
+ searchPlaceholder,
1869
+ onSearchChange,
1870
+ filters,
1871
+ filterValues,
1872
+ onFilterChange,
1873
+ filterInlineLimit,
1874
+ showFilterBadges,
1875
+ showClearFiltersButton,
1876
+ activeChips,
1877
+ onFilterRemove,
1878
+ sortOptions,
1879
+ sortValue,
1880
+ onSortChange,
1881
+ metrics,
1882
+ showMetrics,
1883
+ onToggleMetrics,
1884
+ labels
1885
+ }) => {
1886
+ const [showMoreFilters, setShowMoreFilters] = (0, import_react4.useState)(false);
1887
+ const inlineFilters = (filters || []).slice(0, filterInlineLimit);
1888
+ const overflowFilters = (filters || []).slice(filterInlineLimit);
1889
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Box, { flex: 3 }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showSearch ? /* @__PURE__ */ import_react4.default.createElement(
1890
+ import_ui_extensions4.SearchInput,
1891
+ {
1892
+ name: "kanban-search",
1893
+ placeholder: searchPlaceholder,
1894
+ value: searchValue,
1895
+ onChange: onSearchChange
1896
+ }
1897
+ ) : null, inlineFilters.map(
1898
+ (filter) => renderFilterControl({
1899
+ filter,
1900
+ value: filterValues[filter.name],
1901
+ onChange: onFilterChange,
1902
+ labels
1903
+ })
1904
+ ), overflowFilters.length > 0 ? /* @__PURE__ */ import_react4.default.createElement(
1905
+ import_ui_extensions4.Button,
1906
+ {
1907
+ variant: "transparent",
1908
+ size: "small",
1909
+ onClick: () => setShowMoreFilters((prev) => !prev)
1910
+ },
1911
+ /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Icon, { name: "filter", size: "sm" }),
1912
+ " ",
1913
+ labels.filtersButton
1914
+ ) : null), showMoreFilters && overflowFilters.length > 0 ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, overflowFilters.map(
1915
+ (filter) => renderFilterControl({
1916
+ filter,
1917
+ value: filterValues[filter.name],
1918
+ onChange: onFilterChange,
1919
+ labels
1920
+ })
1921
+ )) : null, activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showFilterBadges ? activeChips.map((chip) => /* @__PURE__ */ import_react4.default.createElement(
1922
+ import_ui_extensions4.Tag,
1923
+ {
1924
+ key: chip.key,
1925
+ variant: "default",
1926
+ onDelete: () => onFilterRemove(chip.key)
1927
+ },
1928
+ chip.label
1929
+ )) : null, showClearFiltersButton ? /* @__PURE__ */ import_react4.default.createElement(
1930
+ import_ui_extensions4.Button,
1931
+ {
1932
+ variant: "transparent",
1933
+ size: "extra-small",
1934
+ onClick: () => onFilterRemove("all")
1935
+ },
1936
+ labels.clearAll
1937
+ ) : null) : null)), (sortOptions == null ? void 0 : sortOptions.length) > 0 || metrics ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Box, { flex: 1, alignSelf: "start" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", align: "center", gap: "sm", justify: "end" }, sortOptions && sortOptions.length > 0 ? /* @__PURE__ */ import_react4.default.createElement(
1938
+ import_ui_extensions4.Button,
1939
+ {
1940
+ variant: "secondary",
1941
+ size: "small",
1942
+ overlay: /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Modal, { id: "kanban-sort-modal", title: labels.sortButton }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.ModalBody, null, /* @__PURE__ */ import_react4.default.createElement(
1943
+ SortModalBody,
1944
+ {
1945
+ sortOptions,
1946
+ sortValue,
1947
+ onSortChange,
1948
+ labels
1949
+ }
1950
+ )))
1951
+ },
1952
+ /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Icon, { name: "sortAmtDesc", size: "sm" }),
1953
+ " ",
1954
+ labels.sortButton
1955
+ ) : null, metrics ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Button, { variant: "secondary", size: "small", onClick: onToggleMetrics }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Icon, { name: "reports", size: "sm" }), " ", labels.metricsButton) : null)) : null), showMetrics && metrics ? renderMetricsPanel(metrics) : null);
1956
+ };
1957
+ var DefaultSelectionBar = ({
1958
+ selectedIds,
1959
+ selectedCount,
1960
+ displayCount,
1961
+ countLabel,
1962
+ allSelected,
1963
+ onSelectAll,
1964
+ onDeselectAll,
1965
+ selectionActions,
1966
+ labels
1967
+ }) => {
1968
+ const pluralForCount = (n) => countLabel(n);
1969
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Tile, { compact: true }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Inline, { align: "center", justify: "between", gap: "small" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Inline, { align: "center", gap: "small" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, { inline: true, format: { fontWeight: "demibold" } }, typeof labels.selected === "function" ? labels.selected(selectedCount, pluralForCount(selectedCount)) : `${selectedCount} selected`), !allSelected ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Button, { variant: "transparent", size: "extra-small", onClick: onSelectAll }, typeof labels.selectAll === "function" ? labels.selectAll(displayCount, pluralForCount(displayCount)) : labels.selectAll) : null, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Button, { variant: "transparent", size: "extra-small", onClick: onDeselectAll }, labels.deselectAll)), (selectionActions || []).length > 0 ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Inline, { align: "center", gap: "extra-small" }, selectionActions.map((action, i) => /* @__PURE__ */ import_react4.default.createElement(
1970
+ import_ui_extensions4.Button,
1971
+ {
1972
+ key: action.key || action.label || i,
1973
+ variant: action.variant || "transparent",
1974
+ size: "extra-small",
1975
+ onClick: () => action.onClick([...selectedIds])
1976
+ },
1977
+ action.icon ? /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Icon, { name: action.icon, size: "sm" }) : null,
1978
+ " ",
1979
+ action.label
1980
+ ))) : null));
1981
+ };
1982
+ var Kanban = ({
1983
+ // --- Data ---
1984
+ data = [],
1985
+ stages = [],
1986
+ groupBy = "status",
1987
+ rowIdField = "id",
1988
+ // --- Card rendering ---
1989
+ renderCard,
1990
+ cardFields,
1991
+ cardDensity = DEFAULT_DENSITY,
1992
+ cardDividers,
1993
+ cardBodyAs = "descriptionList",
1994
+ maxBodyLines,
1995
+ maxCardsPerColumn = DEFAULT_MAX_CARDS,
1996
+ maxCardsExpanded = DEFAULT_MAX_EXPANDED,
1997
+ expandedStages,
1998
+ onExpandedStagesChange,
1999
+ // --- Per-stage pagination ---
2000
+ stageMeta,
2001
+ onLoadMore,
2002
+ // --- Selection ---
2003
+ selectable = false,
2004
+ selectedIds,
2005
+ onSelectionChange,
2006
+ selectionActions,
2007
+ recordLabel,
2008
+ selectionResetKey,
2009
+ resetSelectionOnQueryChange = true,
2010
+ showSelectionBar = true,
2011
+ renderSelectionBar,
2012
+ // --- Stage transitions ---
2013
+ stageControl,
2014
+ stageControlPlacement,
2015
+ onStageChange,
2016
+ isStageChanging,
2017
+ canMove,
2018
+ // --- Toolbar ---
2019
+ showSearch = true,
2020
+ searchFields,
2021
+ searchPlaceholder,
2022
+ searchDebounce = DEFAULT_SEARCH_DEBOUNCE,
2023
+ fuzzySearch = false,
2024
+ fuzzyOptions,
2025
+ filters,
2026
+ filterInlineLimit = DEFAULT_FILTER_INLINE_LIMIT,
2027
+ showFilterBadges = true,
2028
+ showClearFiltersButton,
2029
+ sortOptions,
2030
+ defaultSort,
2031
+ sort,
2032
+ onSortChange,
2033
+ // --- Column level ---
2034
+ columnFooter,
2035
+ columnWidth = DEFAULT_COLUMN_WIDTH,
2036
+ countDisplay = "tag",
2037
+ collapsedStages,
2038
+ onCollapsedStagesChange,
2039
+ // --- Metrics panel ---
2040
+ metrics,
2041
+ // Array of stat items or a ReactNode for full override
2042
+ showMetrics: controlledShowMetrics,
2043
+ onMetricsToggle,
2044
+ // --- State (controlled) ---
2045
+ searchValue,
2046
+ onSearchChange,
2047
+ filterValues,
2048
+ onFilterChange,
2049
+ onParamsChange,
2050
+ loading = false,
2051
+ error,
2052
+ // --- Labels ---
2053
+ labels: labelsProp,
2054
+ renderEmptyState,
2055
+ renderLoadingState,
2056
+ renderErrorState
2057
+ }) => {
2058
+ var _a;
2059
+ const labels = (0, import_react4.useMemo)(() => ({ ...DEFAULT_LABELS, ...labelsProp || {} }), [labelsProp]);
2060
+ const [internalSearch, setInternalSearch] = (0, import_react4.useState)(searchValue != null ? searchValue : "");
2061
+ const [internalFilters, setInternalFilters] = (0, import_react4.useState)(() => {
2062
+ const init = {};
2063
+ (filters || []).forEach((f) => {
2064
+ init[f.name] = getEmptyFilterValue(f);
2065
+ });
2066
+ return init;
2067
+ });
2068
+ const [internalSort, setInternalSort] = (0, import_react4.useState)(defaultSort || (((_a = sortOptions == null ? void 0 : sortOptions[0]) == null ? void 0 : _a.value) ?? ""));
2069
+ const [internalCollapsed, setInternalCollapsed] = (0, import_react4.useState)([]);
2070
+ const [internalExpanded, setInternalExpanded] = (0, import_react4.useState)([]);
2071
+ const [internalSelection, setInternalSelection] = (0, import_react4.useState)([]);
2072
+ const [internalShowMetrics, setInternalShowMetrics] = (0, import_react4.useState)(false);
2073
+ const [transitionPrompts, setTransitionPrompts] = (0, import_react4.useState)({});
2074
+ const resolvedShowMetrics = controlledShowMetrics != null ? controlledShowMetrics : internalShowMetrics;
2075
+ const toggleMetrics = (0, import_react4.useCallback)(() => {
2076
+ const next = !resolvedShowMetrics;
2077
+ if (onMetricsToggle) onMetricsToggle(next);
2078
+ if (controlledShowMetrics == null) setInternalShowMetrics(next);
2079
+ }, [resolvedShowMetrics, onMetricsToggle, controlledShowMetrics]);
2080
+ const effectiveColumnWidth = Math.max(MIN_COLUMN_WIDTH, columnWidth || DEFAULT_COLUMN_WIDTH);
2081
+ const resolvedSearch = searchValue != null ? searchValue : internalSearch;
2082
+ const searchInputValue = searchDebounce > 0 ? internalSearch : resolvedSearch;
2083
+ const resolvedFilters = filterValues != null ? filterValues : internalFilters;
2084
+ const resolvedSort = sort != null ? sort : internalSort;
2085
+ const resolvedCollapsed = collapsedStages != null ? collapsedStages : internalCollapsed;
2086
+ const resolvedExpanded = expandedStages != null ? expandedStages : internalExpanded;
2087
+ const resolvedSelection = selectedIds != null ? selectedIds : internalSelection;
2088
+ const searchEnabled = showSearch && Array.isArray(searchFields) && searchFields.length > 0;
2089
+ const stagesByValue = (0, import_react4.useMemo)(() => {
2090
+ const map = {};
2091
+ for (const stage of stages || []) {
2092
+ map[stage.value] = stage;
2093
+ }
2094
+ return map;
2095
+ }, [stages]);
2096
+ const fireParamsChange = (0, import_react4.useCallback)((overrides = {}) => {
2097
+ if (!onParamsChange) return;
2098
+ onParamsChange({
2099
+ search: overrides.search != null ? overrides.search : resolvedSearch,
2100
+ filters: overrides.filters != null ? overrides.filters : resolvedFilters,
2101
+ sort: overrides.sort != null ? overrides.sort : resolvedSort || null,
2102
+ collapsedStages: overrides.collapsedStages != null ? overrides.collapsedStages : resolvedCollapsed
2103
+ });
2104
+ }, [onParamsChange, resolvedCollapsed, resolvedFilters, resolvedSearch, resolvedSort]);
2105
+ const lastAppliedSearchRef = (0, import_react4.useRef)(searchValue != null ? searchValue : "");
2106
+ (0, import_react4.useEffect)(() => {
2107
+ if (searchValue == null) return;
2108
+ if (searchValue === lastAppliedSearchRef.current) return;
2109
+ lastAppliedSearchRef.current = searchValue;
2110
+ setInternalSearch(searchValue);
2111
+ }, [searchValue]);
2112
+ const dispatchSearch = (0, import_react4.useCallback)(
2113
+ (val) => {
2114
+ lastAppliedSearchRef.current = val;
2115
+ if (onSearchChange) onSearchChange(val);
2116
+ fireParamsChange({ search: val });
2117
+ },
2118
+ [fireParamsChange, onSearchChange]
2119
+ );
2120
+ const dispatchSearchDebounced = useDebouncedDispatch(internalSearch, searchDebounce, dispatchSearch);
2121
+ const handleSearch = (0, import_react4.useCallback)(
2122
+ (val) => {
2123
+ setInternalSearch(val);
2124
+ dispatchSearchDebounced(val);
2125
+ },
2126
+ [dispatchSearchDebounced]
2127
+ );
2128
+ const handleFilter = (0, import_react4.useCallback)(
2129
+ (name, val) => {
2130
+ const next = { ...resolvedFilters, [name]: val };
2131
+ if (filterValues == null) setInternalFilters(next);
2132
+ if (onFilterChange) onFilterChange(next);
2133
+ fireParamsChange({ filters: next });
2134
+ },
2135
+ [fireParamsChange, onFilterChange, filterValues, resolvedFilters]
2136
+ );
2137
+ const handleFilterRemove = (0, import_react4.useCallback)(
2138
+ (key) => {
2139
+ if (key === "all") {
2140
+ const cleared = {};
2141
+ (filters || []).forEach((f) => {
2142
+ cleared[f.name] = getEmptyFilterValue(f);
2143
+ });
2144
+ if (filterValues == null) setInternalFilters(cleared);
2145
+ if (onFilterChange) onFilterChange(cleared);
2146
+ fireParamsChange({ filters: cleared });
2147
+ return;
2148
+ }
2149
+ const filter = (filters || []).find((f) => f.name === key);
2150
+ const emptyVal = filter ? getEmptyFilterValue(filter) : "";
2151
+ const next = { ...resolvedFilters, [key]: emptyVal };
2152
+ if (filterValues == null) setInternalFilters(next);
2153
+ if (onFilterChange) onFilterChange(next);
2154
+ fireParamsChange({ filters: next });
2155
+ },
2156
+ [filters, filterValues, fireParamsChange, onFilterChange, resolvedFilters]
2157
+ );
2158
+ const handleSort = (0, import_react4.useCallback)(
2159
+ (val) => {
2160
+ if (onSortChange) onSortChange(val);
2161
+ if (sort == null) setInternalSort(val);
2162
+ fireParamsChange({ sort: val });
2163
+ },
2164
+ [fireParamsChange, onSortChange, sort]
2165
+ );
2166
+ const handleCollapsed = (0, import_react4.useCallback)(
2167
+ (stageValue) => {
2168
+ const next = resolvedCollapsed.includes(stageValue) ? resolvedCollapsed.filter((v) => v !== stageValue) : [...resolvedCollapsed, stageValue];
2169
+ if (onCollapsedStagesChange) onCollapsedStagesChange(next);
2170
+ if (collapsedStages == null) setInternalCollapsed(next);
2171
+ fireParamsChange({ collapsedStages: next });
2172
+ },
2173
+ [fireParamsChange, resolvedCollapsed, collapsedStages, onCollapsedStagesChange]
2174
+ );
2175
+ const handleExpanded = (0, import_react4.useCallback)(
2176
+ (stageValue) => {
2177
+ const next = resolvedExpanded.includes(stageValue) ? resolvedExpanded.filter((v) => v !== stageValue) : [...resolvedExpanded, stageValue];
2178
+ if (onExpandedStagesChange) onExpandedStagesChange(next);
2179
+ if (expandedStages == null) setInternalExpanded(next);
2180
+ },
2181
+ [resolvedExpanded, expandedStages, onExpandedStagesChange]
2182
+ );
2183
+ const handleToggleSelect = (0, import_react4.useCallback)(
2184
+ (rowId) => {
2185
+ const next = resolvedSelection.includes(rowId) ? resolvedSelection.filter((id) => id !== rowId) : [...resolvedSelection, rowId];
2186
+ if (onSelectionChange) onSelectionChange(next);
2187
+ if (selectedIds == null) setInternalSelection(next);
2188
+ },
2189
+ [resolvedSelection, selectedIds, onSelectionChange]
2190
+ );
2191
+ const clearTransitionPrompt = (0, import_react4.useCallback)((rowId) => {
2192
+ setTransitionPrompts((prev) => {
2193
+ if (!Object.prototype.hasOwnProperty.call(prev, rowId)) return prev;
2194
+ const next = { ...prev };
2195
+ delete next[rowId];
2196
+ return next;
2197
+ });
2198
+ }, []);
2199
+ const commitStageChange = (0, import_react4.useCallback)(
2200
+ (row, newStage, oldStage, result) => {
2201
+ clearTransitionPrompt(row[rowIdField]);
2202
+ if (onStageChange) onStageChange(row, newStage, oldStage, result);
2203
+ },
2204
+ [clearTransitionPrompt, onStageChange, rowIdField]
2205
+ );
2206
+ const selectionQueryKey = (0, import_react4.useMemo)(() => {
2207
+ if (!resetSelectionOnQueryChange) return "";
2208
+ return toStableKey({
2209
+ search: resolvedSearch,
2210
+ filters: resolvedFilters,
2211
+ sort: resolvedSort || null
2212
+ });
2213
+ }, [resetSelectionOnQueryChange, resolvedFilters, resolvedSearch, resolvedSort]);
2214
+ const combinedSelectionResetKey = (0, import_react4.useMemo)(
2215
+ () => `${selectionQueryKey}::${selectionResetKey == null ? "" : toStableKey(selectionResetKey)}`,
2216
+ [selectionQueryKey, selectionResetKey]
2217
+ );
2218
+ const clearSelection = (0, import_react4.useCallback)(() => setInternalSelection([]), []);
2219
+ useSelectionReset({
2220
+ resetKey: combinedSelectionResetKey,
2221
+ enabled: selectable,
2222
+ isControlled: selectedIds != null,
2223
+ clearSelection
2224
+ });
2225
+ const getStageFor = (0, import_react4.useCallback)(
2226
+ (row) => {
2227
+ if (typeof groupBy === "function") return groupBy(row);
2228
+ return row[groupBy];
2229
+ },
2230
+ [groupBy]
2231
+ );
2232
+ const filteredData = (0, import_react4.useMemo)(() => {
2233
+ let result = filterRows(data, filters, resolvedFilters);
2234
+ const searchLower = (resolvedSearch || "").toLowerCase().trim();
2235
+ if (searchEnabled && searchLower) {
2236
+ result = searchRows(result, searchLower, searchFields, {
2237
+ fuzzy: fuzzySearch,
2238
+ fuzzyOptions
2239
+ });
2240
+ }
2241
+ return result;
2242
+ }, [data, resolvedSearch, resolvedFilters, filters, searchEnabled, searchFields, fuzzySearch, fuzzyOptions]);
2243
+ const buckets = (0, import_react4.useMemo)(() => {
2244
+ const map = {};
2245
+ for (const stage of stages) map[stage.value] = [];
2246
+ for (const row of filteredData) {
2247
+ const key = getStageFor(row);
2248
+ if (map[key]) {
2249
+ map[key].push(row);
2250
+ } else if (stages.length > 0) {
2251
+ if (!map.__unknown) map.__unknown = [];
2252
+ map.__unknown.push(row);
2253
+ }
2254
+ }
2255
+ return map;
2256
+ }, [filteredData, stages, getStageFor]);
2257
+ const sortComparator = (0, import_react4.useMemo)(() => {
2258
+ if (!sortOptions || !resolvedSort) return null;
2259
+ const opt = sortOptions.find((s) => s.value === resolvedSort);
2260
+ return (opt == null ? void 0 : opt.comparator) || null;
2261
+ }, [sortOptions, resolvedSort]);
2262
+ const sortedBuckets = (0, import_react4.useMemo)(() => {
2263
+ if (!sortComparator) return buckets;
2264
+ const out = {};
2265
+ for (const key of Object.keys(buckets)) {
2266
+ out[key] = [...buckets[key]].sort(sortComparator);
2267
+ }
2268
+ return out;
2269
+ }, [buckets, sortComparator]);
2270
+ const activeChips = (0, import_react4.useMemo)(() => {
2271
+ const chips = [];
2272
+ for (const filter of filters || []) {
2273
+ const val = resolvedFilters[filter.name];
2274
+ if (!isFilterActive(filter, val)) continue;
2275
+ const type = filter.type || "select";
2276
+ const prefix = filter.chipLabel || filter.placeholder || filter.name;
2277
+ if (type === "multiselect") {
2278
+ const labelList = val.map((v) => {
2279
+ var _a2;
2280
+ return ((_a2 = filter.options.find((o) => o.value === v)) == null ? void 0 : _a2.label) || v;
2281
+ }).join(", ");
2282
+ chips.push({ key: filter.name, label: `${prefix}: ${labelList}` });
2283
+ } else if (type === "dateRange") {
2284
+ const parts = [];
2285
+ if (val.from) parts.push(`from ${formatDateChip(val.from)}`);
2286
+ if (val.to) parts.push(`to ${formatDateChip(val.to)}`);
2287
+ chips.push({ key: filter.name, label: `${prefix}: ${parts.join(" ")}` });
2288
+ } else {
2289
+ const opt = filter.options.find((o) => o.value === val);
2290
+ chips.push({ key: filter.name, label: `${prefix}: ${(opt == null ? void 0 : opt.label) || val}` });
2291
+ }
2292
+ }
2293
+ return chips;
2294
+ }, [filters, resolvedFilters]);
2295
+ const partitioned = (0, import_react4.useMemo)(() => partitionFields(cardFields || []), [cardFields]);
2296
+ const dividers = (0, import_react4.useMemo)(() => resolveDividers(cardDividers, cardDensity), [cardDividers, cardDensity]);
2297
+ const resolvedMaxBody = maxBodyLines || (cardDensity === "comfortable" ? 5 : 3);
2298
+ const resolvedStageControl = stageControl || (cardDensity === "comfortable" ? "select" : "menu");
2299
+ const resolvedStageControlPlacement = stageControlPlacement || (resolvedStageControl === "menu" ? "inline" : "separateRow");
2300
+ const handleStageChangeRequest = (0, import_react4.useCallback)(
2301
+ (row, newStage, oldStage) => {
2302
+ var _a2;
2303
+ if (!newStage || newStage === oldStage) return;
2304
+ const targetStage = stagesByValue[newStage];
2305
+ if (!targetStage || !canStageReceiveRow(targetStage, row, canMove)) return;
2306
+ const rowId = row[rowIdField];
2307
+ if ((_a2 = targetStage.onEnterRequired) == null ? void 0 : _a2.render) {
2308
+ setTransitionPrompts((prev) => ({
2309
+ ...prev,
2310
+ [rowId]: {
2311
+ row,
2312
+ fromStage: oldStage,
2313
+ toStage: newStage
2314
+ }
2315
+ }));
2316
+ return;
2317
+ }
2318
+ commitStageChange(row, newStage, oldStage);
2319
+ },
2320
+ [canMove, commitStageChange, rowIdField, stagesByValue]
2321
+ );
2322
+ const renderCardNode = (0, import_react4.useCallback)(
2323
+ (row, stage) => {
2324
+ var _a2;
2325
+ const rowId = row[rowIdField];
2326
+ const activePrompt = transitionPrompts[rowId];
2327
+ const promptStage = activePrompt ? stagesByValue[activePrompt.toStage] : null;
2328
+ if ((_a2 = promptStage == null ? void 0 : promptStage.onEnterRequired) == null ? void 0 : _a2.render) {
2329
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Tile, { key: rowId, compact: cardDensity === "compact" }, promptStage.onEnterRequired.render({
2330
+ row: activePrompt.row,
2331
+ fromStage: activePrompt.fromStage,
2332
+ toStage: activePrompt.toStage,
2333
+ onConfirm: (result) => commitStageChange(activePrompt.row, activePrompt.toStage, activePrompt.fromStage, result),
2334
+ onCancel: () => clearTransitionPrompt(rowId)
2335
+ }));
2336
+ }
2337
+ if (renderCard) {
2338
+ return renderCard(row, {
2339
+ stage,
2340
+ isChanging: isStageChanging ? isStageChanging(row) : false,
2341
+ density: cardDensity,
2342
+ onStageChange: (newStage) => handleStageChangeRequest(row, newStage, stage.value)
2343
+ });
2344
+ }
2345
+ return /* @__PURE__ */ import_react4.default.createElement(
2346
+ KanbanCard,
2347
+ {
2348
+ key: rowId,
2349
+ row,
2350
+ rowId,
2351
+ stage,
2352
+ stages,
2353
+ fields: partitioned,
2354
+ density: cardDensity,
2355
+ dividers,
2356
+ bodyAs: cardBodyAs,
2357
+ maxBodyLines: resolvedMaxBody,
2358
+ stageControl: resolvedStageControl,
2359
+ stageControlPlacement: resolvedStageControlPlacement,
2360
+ canMove,
2361
+ onStageChangeRequest: handleStageChangeRequest,
2362
+ isChanging: isStageChanging ? isStageChanging(row) : false,
2363
+ selectable,
2364
+ selected: resolvedSelection.includes(rowId),
2365
+ onToggleSelect: handleToggleSelect,
2366
+ labels
2367
+ }
2368
+ );
2369
+ },
2370
+ [
2371
+ clearTransitionPrompt,
2372
+ commitStageChange,
2373
+ renderCard,
2374
+ rowIdField,
2375
+ partitioned,
2376
+ cardDensity,
2377
+ dividers,
2378
+ resolvedMaxBody,
2379
+ resolvedStageControl,
2380
+ resolvedStageControlPlacement,
2381
+ canMove,
2382
+ isStageChanging,
2383
+ selectable,
2384
+ resolvedSelection,
2385
+ handleStageChangeRequest,
2386
+ handleToggleSelect,
2387
+ labels,
2388
+ stages,
2389
+ stagesByValue,
2390
+ transitionPrompts
2391
+ ]
2392
+ );
2393
+ const totalMatching = filteredData.length;
2394
+ const selectedCount = resolvedSelection.length;
2395
+ const singular = ((recordLabel == null ? void 0 : recordLabel.singular) || "card").toLowerCase();
2396
+ const plural = ((recordLabel == null ? void 0 : recordLabel.plural) || "cards").toLowerCase();
2397
+ const countLabel = (n) => n === 1 ? singular : plural;
2398
+ const resolvedSearchPlaceholder = searchPlaceholder ?? ((recordLabel == null ? void 0 : recordLabel.plural) ? `Search ${plural}...` : labels.search);
2399
+ const selectionBarProps = {
2400
+ selectedIds: resolvedSelection,
2401
+ selectedCount,
2402
+ displayCount: totalMatching,
2403
+ countLabel,
2404
+ allSelected: selectedCount >= totalMatching && totalMatching > 0,
2405
+ onSelectAll: () => {
2406
+ const allIds = filteredData.map((r) => r[rowIdField]);
2407
+ if (onSelectionChange) onSelectionChange(allIds);
2408
+ if (selectedIds == null) setInternalSelection(allIds);
2409
+ },
2410
+ onDeselectAll: () => {
2411
+ if (onSelectionChange) onSelectionChange([]);
2412
+ if (selectedIds == null) setInternalSelection([]);
2413
+ },
2414
+ selectionActions: selectionActions || [],
2415
+ labels
2416
+ };
2417
+ const mainContent = error ? renderErrorState ? renderErrorState({
2418
+ error,
2419
+ title: labels.errorTitle,
2420
+ message: typeof error === "string" ? error : labels.errorMessage
2421
+ }) : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Alert, { variant: "danger", title: labels.errorTitle }, typeof error === "string" ? error : labels.errorMessage) : loading && data.length === 0 ? renderLoadingState ? renderLoadingState({ label: labels.loading }) : (
2422
+ // Same EmptyState layout as the empty state (just the "building" image +
2423
+ // a loading message) so loading and empty match with no layout shift.
2424
+ /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Tile, null, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.EmptyState, { title: labels.loading, imageName: "building", layout: "vertical" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, null, labels.loadingMessage))))
2425
+ ) : filteredData.length === 0 ? renderEmptyState ? renderEmptyState({
2426
+ title: labels.emptyTitle,
2427
+ message: labels.emptyMessage
2428
+ }) : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Tile, null, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.EmptyState, { title: labels.emptyTitle, layout: "vertical" }, /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Text, null, labels.emptyMessage)))) : /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "row", gap: "sm", wrap: "nowrap" }, stages.map((stage) => {
2429
+ const stageRows = sortedBuckets[stage.value] || [];
2430
+ const meta = stageMeta == null ? void 0 : stageMeta[stage.value];
2431
+ const isExpanded = resolvedExpanded.includes(stage.value);
2432
+ const clamp = isExpanded ? maxCardsExpanded : maxCardsPerColumn;
2433
+ const visibleRows = stageRows.slice(0, clamp);
2434
+ const isCollapsed = resolvedCollapsed.includes(stage.value);
2435
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.AutoGrid, { key: stage.value, columnWidth: isCollapsed ? 72 : effectiveColumnWidth }, /* @__PURE__ */ import_react4.default.createElement(
2436
+ KanbanColumn,
2437
+ {
2438
+ stage,
2439
+ rows: visibleRows,
2440
+ bucketCount: stageRows.length,
2441
+ totalCount: meta == null ? void 0 : meta.totalCount,
2442
+ hasMore: meta == null ? void 0 : meta.hasMore,
2443
+ loading: meta == null ? void 0 : meta.loading,
2444
+ error: meta == null ? void 0 : meta.error,
2445
+ onLoadMore,
2446
+ expanded: isExpanded,
2447
+ onToggleExpanded: () => handleExpanded(stage.value),
2448
+ collapsed: isCollapsed,
2449
+ onToggleCollapsed: () => handleCollapsed(stage.value),
2450
+ columnFooter,
2451
+ countDisplay,
2452
+ labels
2453
+ },
2454
+ visibleRows.map((row) => renderCardNode(row, stage))
2455
+ ));
2456
+ }));
2457
+ const resolvedShowClearFiltersButton = showClearFiltersButton ?? showFilterBadges;
2458
+ return /* @__PURE__ */ import_react4.default.createElement(import_ui_extensions4.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react4.default.createElement(
2459
+ KanbanToolbar,
2460
+ {
2461
+ showSearch: searchEnabled,
2462
+ searchValue: searchInputValue,
2463
+ searchPlaceholder: resolvedSearchPlaceholder,
2464
+ onSearchChange: handleSearch,
2465
+ filters,
2466
+ filterValues: resolvedFilters,
2467
+ onFilterChange: handleFilter,
2468
+ filterInlineLimit,
2469
+ showFilterBadges,
2470
+ showClearFiltersButton: resolvedShowClearFiltersButton,
2471
+ activeChips,
2472
+ onFilterRemove: handleFilterRemove,
2473
+ sortOptions,
2474
+ sortValue: resolvedSort,
2475
+ onSortChange: handleSort,
2476
+ metrics,
2477
+ showMetrics: resolvedShowMetrics,
2478
+ onToggleMetrics: toggleMetrics,
2479
+ labels
2480
+ }
2481
+ ), showSelectionBar && selectable && selectedCount > 0 ? renderSelectionBar ? renderSelectionBar(selectionBarProps) : /* @__PURE__ */ import_react4.default.createElement(DefaultSelectionBar, { ...selectionBarProps }) : null, mainContent);
2482
+ };
2483
+
2484
+ // src/utils/objectPath.js
2485
+ var getByPath = (obj, path) => {
2486
+ if (!path) return void 0;
2487
+ if (typeof path === "function") return path(obj);
2488
+ return String(path).split(".").reduce((acc, key) => acc == null ? void 0 : acc[key], obj);
2489
+ };
2490
+
2491
+ // src/utils/crmSearchAdapters.js
2492
+ var EMPTY_ARRAY = [];
2493
+ var EMPTY_OBJECT = {};
2494
+ var EMPTY_CRM_PARAMS = { search: "", filters: {}, sort: null };
2495
+ var isPlainObject = (value) => value != null && Object.prototype.toString.call(value) === "[object Object]";
2496
+ var coerceError = (error) => {
2497
+ if (!error) return false;
2498
+ if (typeof error === "string") return error;
2499
+ if (error.message) return error.message;
2500
+ return true;
2501
+ };
2502
+ var pickArray = (response) => {
2503
+ if (Array.isArray(response)) return response;
2504
+ if (!response) return EMPTY_ARRAY;
2505
+ return response.results || response.data || response.items || response.records || response.objects || EMPTY_ARRAY;
2506
+ };
2507
+ var pickTotal = (response, fallbackLength) => {
2508
+ var _a;
2509
+ if (!response || Array.isArray(response)) return fallbackLength;
2510
+ return response.total ?? response.totalCount ?? response.totalResults ?? ((_a = response.paging) == null ? void 0 : _a.total) ?? fallbackLength;
2511
+ };
2512
+ var normalizeCrmSearchRecord = (record, options = EMPTY_OBJECT) => {
2513
+ const {
2514
+ idField = "id",
2515
+ objectIdField = "objectId",
2516
+ propertiesKey = "properties",
2517
+ flattenProperties = true,
2518
+ propertyValueKey,
2519
+ mapRecord
2520
+ } = options;
2521
+ if (mapRecord) return mapRecord(record);
2522
+ const objectId = (record == null ? void 0 : record.objectId) ?? (record == null ? void 0 : record.id) ?? (record == null ? void 0 : record.hs_object_id) ?? getByPath(record, `${propertiesKey}.hs_object_id`);
2523
+ const properties = (record == null ? void 0 : record[propertiesKey]) || EMPTY_OBJECT;
2524
+ const flattened = {};
2525
+ if (flattenProperties && isPlainObject(properties)) {
2526
+ for (const [key, value] of Object.entries(properties)) {
2527
+ flattened[key] = propertyValueKey && isPlainObject(value) ? value[propertyValueKey] : value;
2528
+ }
2529
+ }
2530
+ return {
2531
+ ...flattenProperties ? flattened : EMPTY_OBJECT,
2532
+ ...record,
2533
+ [idField]: objectId,
2534
+ [objectIdField]: objectId,
2535
+ [propertiesKey]: properties
2536
+ };
2537
+ };
2538
+ var normalizeCrmSearchRows = (response, options = EMPTY_OBJECT) => {
2539
+ const records = pickArray(response);
2540
+ return records.map((record) => normalizeCrmSearchRecord(record, options));
2541
+ };
2542
+ var STABLE_SORT_TIEBREAKER = { propertyName: "hs_object_id", direction: "ASCENDING" };
2543
+ var withStableSort = (sorts) => {
2544
+ const base = Array.isArray(sorts) ? sorts : [];
2545
+ if (base.some((s) => s && s.propertyName === STABLE_SORT_TIEBREAKER.propertyName)) return base;
2546
+ return [...base, STABLE_SORT_TIEBREAKER];
2547
+ };
2548
+ var buildCrmSearchConfig = (params = EMPTY_OBJECT, options = EMPTY_OBJECT) => {
2549
+ const {
2550
+ objectType,
2551
+ properties = EMPTY_ARRAY,
2552
+ query,
2553
+ filterGroups,
2554
+ sorts,
2555
+ pageLength,
2556
+ propertyMap = EMPTY_OBJECT,
2557
+ filterMap,
2558
+ sortMap,
2559
+ baseConfig = EMPTY_OBJECT
2560
+ } = options;
2561
+ const mappedFilters = filterMap ? filterMap(params.filters || EMPTY_OBJECT, params) : filterGroups;
2562
+ const mappedSorts = sortMap ? sortMap(params.sort || EMPTY_OBJECT, params) : sorts;
2563
+ const config = {
2564
+ ...baseConfig,
2565
+ objectType: objectType || baseConfig.objectType,
2566
+ properties: properties.length ? properties : baseConfig.properties,
2567
+ query: query ?? params.search ?? baseConfig.query,
2568
+ filterGroups: mappedFilters ?? baseConfig.filterGroups,
2569
+ sorts: withStableSort(mappedSorts ?? baseConfig.sorts),
2570
+ pageLength: pageLength ?? params.pageLength ?? baseConfig.pageLength
2571
+ };
2572
+ if (propertyMap && Object.keys(propertyMap).length && params.filters) {
2573
+ const filters = Object.entries(params.filters).filter(([, value]) => value !== void 0 && value !== null && value !== "" && !(Array.isArray(value) && value.length === 0)).map(([name, value]) => {
2574
+ const propertyName = propertyMap[name] || name;
2575
+ if (Array.isArray(value)) return { propertyName, operator: "IN", values: value };
2576
+ if (isPlainObject(value) && (value.from || value.to)) {
2577
+ const rangeFilters = [];
2578
+ if (value.from) rangeFilters.push({ propertyName, operator: "GTE", value: value.from });
2579
+ if (value.to) rangeFilters.push({ propertyName, operator: "LTE", value: value.to });
2580
+ return rangeFilters;
2581
+ }
2582
+ return { propertyName, operator: "EQ", value };
2583
+ }).flat();
2584
+ if (filters.length) config.filterGroups = [{ filters }];
2585
+ }
2586
+ Object.keys(config).forEach((key) => config[key] === void 0 && delete config[key]);
2587
+ return config;
2588
+ };
2589
+ var useCrmSearchDataSource = (params = EMPTY_OBJECT, options = EMPTY_OBJECT) => {
2590
+ const {
2591
+ format,
2592
+ row,
2593
+ rowIdField = "id",
2594
+ totalCount,
2595
+ loading,
2596
+ error,
2597
+ mapResponse
2598
+ } = options;
2599
+ const config = (0, import_react5.useMemo)(() => buildCrmSearchConfig(params, options), [params, options]);
2600
+ const response = (0, import_ui_extensions5.useCrmSearch)(config, format);
2601
+ return (0, import_react5.useMemo)(() => {
2602
+ const rows = mapResponse ? mapResponse(response) : normalizeCrmSearchRows(response, { idField: rowIdField, ...row || EMPTY_OBJECT });
2603
+ const resolvedTotal = typeof totalCount === "function" ? totalCount(response) : totalCount ?? pickTotal(response, rows.length);
2604
+ return {
2605
+ data: rows,
2606
+ rows,
2607
+ response,
2608
+ loading: typeof loading === "function" ? loading(response) : loading ?? !!(response == null ? void 0 : response.isLoading),
2609
+ isLoading: typeof loading === "function" ? loading(response) : loading ?? !!(response == null ? void 0 : response.isLoading),
2610
+ error: typeof error === "function" ? error(response) : error ?? coerceError(response == null ? void 0 : response.error),
2611
+ totalCount: resolvedTotal,
2612
+ rowIdField
2613
+ };
2614
+ }, [response, mapResponse, row, rowIdField, totalCount, loading, error]);
2615
+ };
2616
+ var crmSearchResultToOption = (row, options = EMPTY_OBJECT) => {
2617
+ const {
2618
+ label = "name",
2619
+ value = "objectId",
2620
+ description,
2621
+ fallbackLabel = "Untitled record",
2622
+ mapOption
2623
+ } = options;
2624
+ if (mapOption) return mapOption(row);
2625
+ const option = {
2626
+ label: getByPath(row, label) ?? getByPath(row, "properties.name") ?? fallbackLabel,
2627
+ value: getByPath(row, value) ?? getByPath(row, "id") ?? getByPath(row, "objectId")
2628
+ };
2629
+ const desc = getByPath(row, description);
2630
+ if (desc != null && desc !== "") option.description = desc;
2631
+ return option;
2632
+ };
2633
+ var useCrmSearchOptions = (params = EMPTY_OBJECT, options = EMPTY_OBJECT) => {
2634
+ const dataSource = useCrmSearchDataSource(params, options);
2635
+ const optionConfig = options.option || options;
2636
+ return (0, import_react5.useMemo)(() => ({
2637
+ ...dataSource,
2638
+ options: dataSource.rows.map((row) => crmSearchResultToOption(row, optionConfig))
2639
+ }), [dataSource, optionConfig]);
2640
+ };
2641
+ var makeCrmSearchSelectField = (field, searchOptions) => ({
2642
+ type: "select",
2643
+ ...field,
2644
+ options: searchOptions.options || EMPTY_ARRAY,
2645
+ loading: searchOptions.loading || searchOptions.isLoading || (field == null ? void 0 : field.loading)
2646
+ });
2647
+ var makeCrmSearchMultiSelectField = (field, searchOptions) => ({
2648
+ type: "multiselect",
2649
+ ...field,
2650
+ options: searchOptions.options || EMPTY_ARRAY,
2651
+ loading: searchOptions.loading || searchOptions.isLoading || (field == null ? void 0 : field.loading)
2652
+ });
2653
+ var CRM_OBJECT_TYPES = {
2654
+ contact: "0-1",
2655
+ contacts: "0-1",
2656
+ company: "0-2",
2657
+ companies: "0-2",
2658
+ deal: "0-3",
2659
+ deals: "0-3"
2660
+ };
2661
+ var prettifyPropertyName = (name) => String(name || "").replace(/^hs_/, "").replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
2662
+ var inferCrmColumns = (properties = EMPTY_ARRAY) => properties.map((property) => ({
2663
+ field: property,
2664
+ label: prettifyPropertyName(property),
2665
+ sortable: true
2666
+ }));
2667
+ var normalizeAutoFilterFields = (autoFilters, properties = EMPTY_ARRAY) => {
2668
+ if (!autoFilters) return EMPTY_ARRAY;
2669
+ if (Array.isArray(autoFilters)) return autoFilters;
2670
+ if (typeof autoFilters === "object" && Array.isArray(autoFilters.fields)) return autoFilters.fields;
2671
+ return properties.filter((property) => !["id", "objectId", "hs_object_id", "email", "firstname", "lastname", "name", "domain"].includes(property));
2672
+ };
2673
+ var buildAutoFiltersFromRows = ({ rows, fields, labelsRef, maxOptions = 25 }) => {
2674
+ if (!fields.length) return EMPTY_ARRAY;
2675
+ for (const row of rows || EMPTY_ARRAY) {
2676
+ for (const field of fields) {
2677
+ const value = getByPath(row, field);
2678
+ if (value == null || value === "" || Array.isArray(value) || isPlainObject(value)) continue;
2679
+ if (!labelsRef.current[field]) labelsRef.current[field] = /* @__PURE__ */ new Map();
2680
+ const map = labelsRef.current[field];
2681
+ if (map.size < maxOptions || map.has(value)) map.set(value, String(value));
2682
+ }
2683
+ }
2684
+ return fields.map((field) => {
2685
+ const map = labelsRef.current[field];
2686
+ if (!map || map.size === 0 || map.size > maxOptions) return null;
2687
+ return {
2688
+ name: field,
2689
+ label: prettifyPropertyName(field),
2690
+ placeholder: `Any ${prettifyPropertyName(field).toLowerCase()}`,
2691
+ options: Array.from(map.entries()).map(([value, label]) => ({ value, label }))
2692
+ };
2693
+ }).filter(Boolean);
2694
+ };
2695
+ var resolveCrmObjectType = (objectType) => CRM_OBJECT_TYPES[objectType] || objectType;
2696
+ var DEFAULT_CRM_FORMAT = { propertiesToFormat: "all" };
2697
+ var defaultCrmMapRecord = (record) => ({ objectId: record.objectId, ...record.properties });
2698
+ var crmSortsFromState = (sort, propertyMap) => {
2699
+ if (!sort || !sort.field || !sort.direction) return void 0;
2700
+ const propertyName = propertyMap && propertyMap[sort.field] || sort.field;
2701
+ return [{ propertyName, direction: sort.direction === "descending" ? "DESCENDING" : "ASCENDING" }];
2702
+ };
2703
+ var CrmDataTable = ({
2704
+ objectType,
2705
+ properties = EMPTY_ARRAY,
2706
+ columns,
2707
+ title,
2708
+ pageLength = 100,
2709
+ // CRM batch fetched per request (CRM search max)
2710
+ pageSize = 10,
2711
+ // client-side page size
2712
+ // Hybrid model: fetch ONE batch and do everything client-side while the whole
2713
+ // result set fits in the batch (no refetch). Once a fetch comes back capped
2714
+ // (more matches than the batch), search / filter / sort start refetching a
2715
+ // fresh batch server-side so they reach the whole dataset — pagination always
2716
+ // stays client-side (the broken useCrmSearch cursor is never used). Set
2717
+ // `serverSide` to force server-side querying from the first render.
2718
+ serverSide = false,
2719
+ filters,
2720
+ autoFilters = false,
2721
+ autoFilterMaxOptions = 25,
2722
+ filterMap,
2723
+ propertyMap,
2724
+ sortMap,
2725
+ searchFields,
2726
+ searchPlaceholder,
2727
+ format = DEFAULT_CRM_FORMAT,
2728
+ mapRecord,
2729
+ rowIdField = "objectId",
2730
+ dataTableProps = EMPTY_OBJECT,
2731
+ ...props
2732
+ }) => {
2733
+ var _a;
2734
+ const [params, setParams] = (0, import_react5.useState)({ search: "", filters: {}, sort: null });
2735
+ const resolvedProperties = (0, import_react5.useMemo)(() => properties, [properties]);
2736
+ const resolvedColumns = (0, import_react5.useMemo)(
2737
+ () => columns || inferCrmColumns(resolvedProperties),
2738
+ [columns, resolvedProperties]
2739
+ );
2740
+ const resolvedSearchFields = searchFields || resolvedProperties;
2741
+ const autoFilterFields = (0, import_react5.useMemo)(
2742
+ () => normalizeAutoFilterFields(autoFilters, resolvedProperties),
2743
+ [autoFilters, resolvedProperties]
2744
+ );
2745
+ const autoFilterLabelsRef = (0, import_react5.useRef)({});
2746
+ const defaultPropertyMap = (0, import_react5.useMemo)(
2747
+ () => Object.fromEntries(resolvedProperties.map((property) => [property, property])),
2748
+ [resolvedProperties]
2749
+ );
2750
+ const effectivePropertyMap = propertyMap || defaultPropertyMap;
2751
+ const resolvedSortMap = (0, import_react5.useMemo)(
2752
+ () => sortMap || ((sort) => crmSortsFromState(sort, effectivePropertyMap)),
2753
+ [sortMap, effectivePropertyMap]
2754
+ );
2755
+ const resolvedMapRecord = mapRecord || defaultCrmMapRecord;
2756
+ const dataSourceOptions = (0, import_react5.useMemo)(
2757
+ () => ({
2758
+ objectType: resolveCrmObjectType(objectType),
2759
+ properties: resolvedProperties,
2760
+ pageLength,
2761
+ format,
2762
+ filterMap,
2763
+ propertyMap: effectivePropertyMap,
2764
+ sortMap: resolvedSortMap,
2765
+ rowIdField,
2766
+ row: { idField: rowIdField, mapRecord: resolvedMapRecord }
2767
+ }),
2768
+ [objectType, resolvedProperties, pageLength, format, filterMap, effectivePropertyMap, resolvedSortMap, rowIdField, resolvedMapRecord]
2769
+ );
2770
+ const [serverQuerying, setServerQuerying] = (0, import_react5.useState)(!!serverSide);
2771
+ const effectiveParams = serverQuerying ? params : EMPTY_CRM_PARAMS;
2772
+ const dataSource = useCrmSearchDataSource(effectiveParams, dataSourceOptions);
2773
+ (0, import_react5.useEffect)(() => {
2774
+ if (!serverQuerying && typeof dataSource.totalCount === "number" && dataSource.totalCount > dataSource.data.length) {
2775
+ setServerQuerying(true);
2776
+ }
2777
+ }, [serverQuerying, dataSource.totalCount, dataSource.data.length]);
2778
+ const generatedFilters = (0, import_react5.useMemo)(
2779
+ () => buildAutoFiltersFromRows({
2780
+ rows: dataSource.data,
2781
+ fields: autoFilterFields,
2782
+ labelsRef: autoFilterLabelsRef,
2783
+ maxOptions: autoFilterMaxOptions
2784
+ }),
2785
+ [dataSource.data, autoFilterFields, autoFilterMaxOptions]
2786
+ );
2787
+ const resolvedFilters = filters || generatedFilters;
2788
+ const table = import_react5.default.createElement(DataTable, {
2789
+ title: title || `${prettifyPropertyName(objectType)} records`,
2790
+ data: dataSource.data,
2791
+ loading: dataSource.loading || ((_a = dataSource.response) == null ? void 0 : _a.isRefetching),
2792
+ error: dataSource.error,
2793
+ columns: resolvedColumns,
2794
+ rowIdField,
2795
+ pageSize,
2796
+ filters: resolvedFilters,
2797
+ searchFields: resolvedSearchFields,
2798
+ searchPlaceholder: searchPlaceholder || `Search ${prettifyPropertyName(objectType).toLowerCase()}...`,
2799
+ searchDebounce: 300,
2800
+ onParamsChange: (next) => {
2801
+ setParams((prev) => ({ ...prev, search: next.search, filters: next.filters, sort: next.sort }));
2802
+ },
2803
+ ...dataTableProps,
2804
+ ...props
2805
+ });
2806
+ const total = dataSource.totalCount;
2807
+ const capped = typeof total === "number" && total > dataSource.data.length;
2808
+ if (!capped) return table;
2809
+ return import_react5.default.createElement(
2810
+ import_ui_extensions5.Flex,
2811
+ { direction: "column", gap: "xs" },
2812
+ import_react5.default.createElement(
2813
+ import_ui_extensions5.Text,
2814
+ { variant: "microcopy" },
2815
+ `Showing the first ${dataSource.data.length} of ${total} matching. Refine your search or filters to narrow the results.`
2816
+ ),
2817
+ table
2818
+ );
2819
+ };
2820
+ var CrmKanban = ({
2821
+ objectType,
2822
+ properties = EMPTY_ARRAY,
2823
+ groupBy,
2824
+ stages,
2825
+ stageLabels,
2826
+ // object { value: label } or (value) => label
2827
+ title,
2828
+ pageLength = 100,
2829
+ serverSide = false,
2830
+ filters,
2831
+ autoFilters = false,
2832
+ autoFilterMaxOptions = 25,
2833
+ filterMap,
2834
+ propertyMap,
2835
+ sortMap,
2836
+ searchFields,
2837
+ searchPlaceholder,
2838
+ format = DEFAULT_CRM_FORMAT,
2839
+ mapRecord,
2840
+ rowIdField = "objectId",
2841
+ kanbanProps = EMPTY_OBJECT,
2842
+ ...props
2843
+ }) => {
2844
+ var _a;
2845
+ const [params, setParams] = (0, import_react5.useState)(EMPTY_CRM_PARAMS);
2846
+ const resolvedProperties = (0, import_react5.useMemo)(() => properties, [properties]);
2847
+ const resolvedSearchFields = searchFields || resolvedProperties;
2848
+ const autoFilterFields = (0, import_react5.useMemo)(
2849
+ () => normalizeAutoFilterFields(autoFilters, resolvedProperties),
2850
+ [autoFilters, resolvedProperties]
2851
+ );
2852
+ const autoFilterLabelsRef = (0, import_react5.useRef)({});
2853
+ const defaultPropertyMap = (0, import_react5.useMemo)(
2854
+ () => Object.fromEntries(resolvedProperties.map((property) => [property, property])),
2855
+ [resolvedProperties]
2856
+ );
2857
+ const effectivePropertyMap = propertyMap || defaultPropertyMap;
2858
+ const resolvedSortMap = (0, import_react5.useMemo)(
2859
+ () => sortMap || ((sort) => crmSortsFromState(sort, effectivePropertyMap)),
2860
+ [sortMap, effectivePropertyMap]
2861
+ );
2862
+ const resolvedMapRecord = mapRecord || defaultCrmMapRecord;
2863
+ const dataSourceOptions = (0, import_react5.useMemo)(
2864
+ () => ({
2865
+ objectType: resolveCrmObjectType(objectType),
2866
+ properties: resolvedProperties,
2867
+ pageLength,
2868
+ format,
2869
+ filterMap,
2870
+ propertyMap: effectivePropertyMap,
2871
+ sortMap: resolvedSortMap,
2872
+ rowIdField,
2873
+ row: { idField: rowIdField, mapRecord: resolvedMapRecord }
2874
+ }),
2875
+ [objectType, resolvedProperties, pageLength, format, filterMap, effectivePropertyMap, resolvedSortMap, rowIdField, resolvedMapRecord]
2876
+ );
2877
+ const [serverQuerying, setServerQuerying] = (0, import_react5.useState)(!!serverSide);
2878
+ const effectiveParams = serverQuerying ? params : EMPTY_CRM_PARAMS;
2879
+ const dataSource = useCrmSearchDataSource(effectiveParams, dataSourceOptions);
2880
+ (0, import_react5.useEffect)(() => {
2881
+ if (!serverQuerying && typeof dataSource.totalCount === "number" && dataSource.totalCount > dataSource.data.length) {
2882
+ setServerQuerying(true);
2883
+ }
2884
+ }, [serverQuerying, dataSource.totalCount, dataSource.data.length]);
2885
+ const generatedFilters = (0, import_react5.useMemo)(
2886
+ () => buildAutoFiltersFromRows({
2887
+ rows: dataSource.data,
2888
+ fields: autoFilterFields,
2889
+ labelsRef: autoFilterLabelsRef,
2890
+ maxOptions: autoFilterMaxOptions
2891
+ }),
2892
+ [dataSource.data, autoFilterFields, autoFilterMaxOptions]
2893
+ );
2894
+ const resolvedFilters = filters || generatedFilters;
2895
+ const resolvedStages = (0, import_react5.useMemo)(() => {
2896
+ if (stages) return stages;
2897
+ const seen = [];
2898
+ for (const row of dataSource.data) {
2899
+ const value = typeof groupBy === "function" ? groupBy(row) : row[groupBy];
2900
+ if (value != null && value !== "" && !seen.includes(value)) seen.push(value);
2901
+ }
2902
+ return seen.map((value) => ({
2903
+ value,
2904
+ label: typeof stageLabels === "function" ? stageLabels(value) : stageLabels && stageLabels[value] || prettifyPropertyName(String(value))
2905
+ }));
2906
+ }, [stages, stageLabels, dataSource.data, groupBy]);
2907
+ const board = import_react5.default.createElement(Kanban, {
2908
+ title: title || `${prettifyPropertyName(objectType)} board`,
2909
+ data: dataSource.data,
2910
+ loading: dataSource.loading || ((_a = dataSource.response) == null ? void 0 : _a.isRefetching),
2911
+ error: dataSource.error,
2912
+ rowIdField,
2913
+ groupBy,
2914
+ stages: resolvedStages,
2915
+ filters: resolvedFilters,
2916
+ searchFields: resolvedSearchFields,
2917
+ searchPlaceholder: searchPlaceholder || `Search ${prettifyPropertyName(objectType).toLowerCase()}...`,
2918
+ searchDebounce: 300,
2919
+ onParamsChange: (next) => {
2920
+ setParams((prev) => ({ ...prev, search: next.search, filters: next.filters }));
2921
+ },
2922
+ ...kanbanProps,
2923
+ ...props
2924
+ });
2925
+ const total = dataSource.totalCount;
2926
+ const capped = typeof total === "number" && total > dataSource.data.length;
2927
+ if (!capped) return board;
2928
+ return import_react5.default.createElement(
2929
+ import_ui_extensions5.Flex,
2930
+ { direction: "column", gap: "xs" },
2931
+ import_react5.default.createElement(
2932
+ import_ui_extensions5.Text,
2933
+ { variant: "microcopy" },
2934
+ `Showing the first ${dataSource.data.length} of ${total} matching. Refine your search or filters to narrow the results.`
2935
+ ),
2936
+ board
2937
+ );
2938
+ };
2939
+
47
2940
  // src/utils/formatters.js
48
2941
  var DEFAULT_LOCALE = "en-US";
49
2942
  var formatCurrency = (value, { locale = DEFAULT_LOCALE, currency = "USD", maximumFractionDigits = 0, ...options } = {}) => new Intl.NumberFormat(locale, {
@@ -352,8 +3245,12 @@ var deriveCardFieldsFromColumns = (columns, opts = {}) => {
352
3245
  };
353
3246
  // Annotate the CommonJS export names for ESM import in node:
354
3247
  0 && (module.exports = {
3248
+ CrmDataTable,
3249
+ CrmKanban,
3250
+ buildCrmSearchConfig,
355
3251
  buildOptions,
356
3252
  createStatusTagSortComparator,
3253
+ crmSearchResultToOption,
357
3254
  deriveCardFieldsFromColumns,
358
3255
  findOptionLabel,
359
3256
  formatCurrency,
@@ -367,5 +3264,12 @@ var deriveCardFieldsFromColumns = (columns, opts = {}) => {
367
3264
  isDateTimeValueObject,
368
3265
  isDateValueObject,
369
3266
  isTimeValueObject,
370
- sumBy
3267
+ makeCrmSearchMultiSelectField,
3268
+ makeCrmSearchSelectField,
3269
+ normalizeCrmSearchRecord,
3270
+ normalizeCrmSearchRows,
3271
+ resolveCrmObjectType,
3272
+ sumBy,
3273
+ useCrmSearchDataSource,
3274
+ useCrmSearchOptions
371
3275
  });