hs-uix 1.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/index.js ADDED
@@ -0,0 +1,2272 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
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
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/index.js
30
+ var src_exports = {};
31
+ __export(src_exports, {
32
+ DataTable: () => DataTable,
33
+ FormBuilder: () => FormBuilder,
34
+ useFormPrefill: () => useFormPrefill
35
+ });
36
+ module.exports = __toCommonJS(src_exports);
37
+
38
+ // packages/datatable/src/DataTable.jsx
39
+ var import_react = __toESM(require("react"));
40
+ var import_fuse = __toESM(require("fuse.js"));
41
+ var import_ui_extensions = require("@hubspot/ui-extensions");
42
+ var formatDateChip = (dateObj) => {
43
+ if (!dateObj) return "";
44
+ const { year, month, date } = dateObj;
45
+ return new Intl.DateTimeFormat("en-US", {
46
+ month: "short",
47
+ day: "numeric",
48
+ year: "numeric"
49
+ }).format(new Date(year, month, date));
50
+ };
51
+ var dateToTimestamp = (dateObj) => {
52
+ if (!dateObj) return null;
53
+ return new Date(dateObj.year, dateObj.month, dateObj.date).getTime();
54
+ };
55
+ var NARROW_EDIT_TYPES = /* @__PURE__ */ new Set(["checkbox", "toggle"]);
56
+ var DATE_PATTERN = /^\d{4}[-/]\d{2}[-/]\d{2}/;
57
+ var BOOL_VALUES = /* @__PURE__ */ new Set(["true", "false", "yes", "no", "0", "1"]);
58
+ var SORT_DIRECTIONS = /* @__PURE__ */ new Set(["ascending", "descending", "none"]);
59
+ var normalizeSortState = (columns, sort) => {
60
+ const normalized = {};
61
+ columns.forEach((col) => {
62
+ if (col.sortable) normalized[col.field] = "none";
63
+ });
64
+ if (!sort) return normalized;
65
+ if (sort.field && SORT_DIRECTIONS.has(sort.direction) && sort.field in normalized) {
66
+ normalized[sort.field] = sort.direction;
67
+ return normalized;
68
+ }
69
+ Object.keys(normalized).forEach((field) => {
70
+ const direction = sort[field];
71
+ if (SORT_DIRECTIONS.has(direction)) normalized[field] = direction;
72
+ });
73
+ return normalized;
74
+ };
75
+ var serializeSortState = (sortState) => {
76
+ const activeField = Object.keys(sortState).find((field) => sortState[field] !== "none");
77
+ if (!activeField) return null;
78
+ return { field: activeField, direction: sortState[activeField] };
79
+ };
80
+ var toStableKey = (value) => {
81
+ try {
82
+ return JSON.stringify(value);
83
+ } catch (_error) {
84
+ return String(value);
85
+ }
86
+ };
87
+ var computeAutoWidths = (columns, data) => {
88
+ if (!data || data.length === 0) return {};
89
+ const sample = data.slice(0, 50);
90
+ const results = {};
91
+ columns.forEach((col) => {
92
+ if (col.width && col.cellWidth) return;
93
+ const values = sample.map((row) => row[col.field]).filter((v) => v != null);
94
+ const strings = values.map((v) => String(v));
95
+ let widthHint = null;
96
+ let cellWidthHint = null;
97
+ if (col.editable && col.editType && NARROW_EDIT_TYPES.has(col.editType)) {
98
+ cellWidthHint = "min";
99
+ }
100
+ if (strings.length > 0) {
101
+ const lengths = strings.map((s) => s.length);
102
+ const maxLen = Math.max(...lengths);
103
+ const uniqueCount = new Set(strings).size;
104
+ if (values.every((v) => typeof v === "boolean") || strings.every((s) => BOOL_VALUES.has(s.toLowerCase()))) {
105
+ widthHint = widthHint || "min";
106
+ cellWidthHint = cellWidthHint || "min";
107
+ } else if (strings.every((s) => DATE_PATTERN.test(s))) {
108
+ widthHint = widthHint || "min";
109
+ cellWidthHint = cellWidthHint || "auto";
110
+ } else if (values.every((v) => typeof v === "number")) {
111
+ widthHint = widthHint || "auto";
112
+ cellWidthHint = cellWidthHint || "auto";
113
+ } else if (uniqueCount <= 5 && maxLen <= 15) {
114
+ widthHint = widthHint || "min";
115
+ cellWidthHint = cellWidthHint || "auto";
116
+ } else {
117
+ widthHint = widthHint || "auto";
118
+ cellWidthHint = cellWidthHint || "auto";
119
+ }
120
+ }
121
+ if (col.editable && !NARROW_EDIT_TYPES.has(col.editType) && widthHint === "min") {
122
+ widthHint = "auto";
123
+ }
124
+ results[col.field] = {
125
+ width: widthHint || "auto",
126
+ cellWidth: cellWidthHint || "auto"
127
+ };
128
+ });
129
+ return results;
130
+ };
131
+ var getEmptyFilterValue = (filter) => {
132
+ const type = filter.type || "select";
133
+ if (type === "multiselect") return [];
134
+ if (type === "dateRange") return { from: null, to: null };
135
+ return "";
136
+ };
137
+ var BOOLEAN_SELECT_OPTIONS = [
138
+ { label: "Yes", value: true },
139
+ { label: "No", value: false }
140
+ ];
141
+ var resolveEditOptions = (col, data) => {
142
+ if (col.editOptions && col.editOptions.length > 0) return col.editOptions;
143
+ const sample = data.find((row) => row[col.field] != null);
144
+ if (sample && typeof sample[col.field] === "boolean") return BOOLEAN_SELECT_OPTIONS;
145
+ return [];
146
+ };
147
+ var isFilterActive = (filter, value) => {
148
+ const type = filter.type || "select";
149
+ if (type === "multiselect") return Array.isArray(value) && value.length > 0;
150
+ if (type === "dateRange") return value && (value.from || value.to);
151
+ return !!value;
152
+ };
153
+ var DataTable = ({
154
+ // Data
155
+ data,
156
+ columns,
157
+ renderRow,
158
+ // Search
159
+ searchFields = [],
160
+ searchPlaceholder = "Search...",
161
+ fuzzySearch = false,
162
+ // enable fuzzy matching via Fuse.js
163
+ fuzzyOptions,
164
+ // custom Fuse.js options (threshold, distance, etc.)
165
+ // Filters
166
+ filters = [],
167
+ showFilterBadges = true,
168
+ // show active filter chips/badges
169
+ showClearFiltersButton = true,
170
+ // show "Clear all" filters reset button
171
+ // Pagination
172
+ pageSize = 10,
173
+ maxVisiblePageButtons,
174
+ // max page number buttons to show
175
+ showButtonLabels = true,
176
+ // show First/Prev/Next/Last text labels
177
+ showFirstLastButtons,
178
+ // show First/Last page buttons (default: auto when pageCount > 5)
179
+ // Row count
180
+ showRowCount = true,
181
+ // show "X records" / "X of Y records" text
182
+ rowCountBold = false,
183
+ // bold the row count text
184
+ rowCountText,
185
+ // custom formatter: (shownOnPage, totalMatching) => string
186
+ // Table appearance
187
+ bordered = true,
188
+ // show table borders
189
+ flush = true,
190
+ // remove bottom margin
191
+ scrollable = false,
192
+ // allow horizontal overflow with scrollbar
193
+ // Sorting
194
+ defaultSort = {},
195
+ // Grouping
196
+ groupBy,
197
+ // Footer
198
+ footer,
199
+ // Empty state
200
+ emptyTitle,
201
+ emptyMessage,
202
+ // -----------------------------------------------------------------------
203
+ // Server-side mode
204
+ // -----------------------------------------------------------------------
205
+ serverSide = false,
206
+ loading = false,
207
+ // show loading spinner over the table
208
+ error,
209
+ // error message string or boolean — shows ErrorState
210
+ totalCount,
211
+ // server total (server-side only)
212
+ page: externalPage,
213
+ // controlled page (server-side only)
214
+ searchValue,
215
+ // controlled search term (server-side only)
216
+ filterValues: externalFilterValues,
217
+ // controlled filter values (server-side only)
218
+ sort: externalSort,
219
+ // controlled sort state, e.g. { field: "ascending" }
220
+ searchDebounce = 0,
221
+ // ms to debounce onSearchChange callback
222
+ resetPageOnChange = true,
223
+ // auto-reset to page 1 on search/filter/sort change
224
+ onSearchChange,
225
+ // (searchTerm) => void
226
+ onFilterChange,
227
+ // (filterValues) => void
228
+ onSortChange,
229
+ // (field, direction) => void
230
+ onPageChange,
231
+ // (page) => void
232
+ onParamsChange,
233
+ // ({ search, filters, sort, page }) => void
234
+ // -----------------------------------------------------------------------
235
+ // Row selection
236
+ // -----------------------------------------------------------------------
237
+ selectable = false,
238
+ rowIdField = "id",
239
+ // field name used as unique row identifier
240
+ selectedIds: externalSelectedIds,
241
+ // controlled selection — array of row IDs
242
+ onSelectionChange,
243
+ // (selectedIds[]) => void
244
+ onSelectAllRequest,
245
+ // server-side: ({ selectedIds, pageIds, totalCount }) => void
246
+ selectionActions = [],
247
+ // [{ label, onClick(selectedIds[]), icon?, variant? }]
248
+ selectionResetKey,
249
+ // optional key to force clear uncontrolled selection memory
250
+ resetSelectionOnQueryChange = true,
251
+ // clear uncontrolled selection on search/filter/sort changes
252
+ recordLabel,
253
+ // { singular: "Contact", plural: "Contacts" } — defaults to Record/Records
254
+ // -----------------------------------------------------------------------
255
+ // Row actions
256
+ // -----------------------------------------------------------------------
257
+ rowActions,
258
+ // [{ label, onClick(row), icon?, variant? }] or (row) => actions[]
259
+ hideRowActionsWhenSelectionActive = false,
260
+ // hide row action column while selected-row action bar is visible
261
+ // -----------------------------------------------------------------------
262
+ // Inline editing
263
+ // -----------------------------------------------------------------------
264
+ editMode,
265
+ // "discrete" (click-to-edit) | "inline" (always show inputs)
266
+ editingRowId,
267
+ // controlled — row ID currently in full-row edit mode
268
+ onRowEdit,
269
+ // (row, field, newValue) => void
270
+ onRowEditInput,
271
+ // optional live-input callback: (row, field, inputValue) => void
272
+ // -----------------------------------------------------------------------
273
+ // Auto-width
274
+ // -----------------------------------------------------------------------
275
+ autoWidth = true
276
+ // auto-compute column widths from content analysis
277
+ }) => {
278
+ const initialSortState = (0, import_react.useMemo)(() => {
279
+ return normalizeSortState(columns, defaultSort);
280
+ }, [columns, defaultSort]);
281
+ const [internalSearchTerm, setInternalSearchTerm] = (0, import_react.useState)("");
282
+ const [internalFilterValues, setInternalFilterValues] = (0, import_react.useState)(() => {
283
+ const init = {};
284
+ filters.forEach((f) => {
285
+ init[f.name] = getEmptyFilterValue(f);
286
+ });
287
+ return init;
288
+ });
289
+ const [internalSortState, setInternalSortState] = (0, import_react.useState)(initialSortState);
290
+ const [currentPage, setCurrentPage] = (0, import_react.useState)(1);
291
+ const [showMoreFilters, setShowMoreFilters] = (0, import_react.useState)(false);
292
+ const searchTerm = serverSide && searchValue != null ? searchValue : internalSearchTerm;
293
+ const filterValues = serverSide && externalFilterValues != null ? externalFilterValues : internalFilterValues;
294
+ const externalSortState = (0, import_react.useMemo)(
295
+ () => normalizeSortState(columns, externalSort),
296
+ [columns, externalSort]
297
+ );
298
+ const sortState = serverSide && externalSort != null ? externalSortState : internalSortState;
299
+ const activePage = serverSide && externalPage != null ? externalPage : currentPage;
300
+ (0, import_react.useEffect)(() => {
301
+ if (!serverSide) setCurrentPage(1);
302
+ }, [internalSearchTerm, internalFilterValues, internalSortState, serverSide]);
303
+ const debounceRef = (0, import_react.useRef)(null);
304
+ const fireSearchCallback = (0, import_react.useCallback)((term) => {
305
+ if (serverSide && onSearchChange) onSearchChange(term);
306
+ }, [serverSide, onSearchChange]);
307
+ const fireParamsChange = (0, import_react.useCallback)((overrides) => {
308
+ if (!onParamsChange) return;
309
+ const nextSortState = overrides.sort != null ? normalizeSortState(columns, overrides.sort) : sortState;
310
+ onParamsChange({
311
+ search: overrides.search != null ? overrides.search : searchTerm,
312
+ filters: overrides.filters != null ? overrides.filters : filterValues,
313
+ sort: serializeSortState(nextSortState),
314
+ page: overrides.page != null ? overrides.page : activePage
315
+ });
316
+ }, [onParamsChange, columns, searchTerm, filterValues, sortState, activePage]);
317
+ const resetPage = (0, import_react.useCallback)(() => {
318
+ if (resetPageOnChange) {
319
+ setCurrentPage(1);
320
+ if (serverSide && onPageChange) onPageChange(1);
321
+ }
322
+ }, [resetPageOnChange, serverSide, onPageChange]);
323
+ const handleSearchChange = (0, import_react.useCallback)((term) => {
324
+ setInternalSearchTerm(term);
325
+ resetPage();
326
+ if (searchDebounce > 0) {
327
+ if (debounceRef.current) clearTimeout(debounceRef.current);
328
+ debounceRef.current = setTimeout(() => {
329
+ fireSearchCallback(term);
330
+ fireParamsChange({ search: term, page: resetPageOnChange ? 1 : void 0 });
331
+ }, searchDebounce);
332
+ } else {
333
+ fireSearchCallback(term);
334
+ fireParamsChange({ search: term, page: resetPageOnChange ? 1 : void 0 });
335
+ }
336
+ }, [searchDebounce, fireSearchCallback, fireParamsChange, resetPage, resetPageOnChange]);
337
+ (0, import_react.useEffect)(() => () => {
338
+ if (debounceRef.current) clearTimeout(debounceRef.current);
339
+ }, []);
340
+ const handleFilterChange = (0, import_react.useCallback)((name, value) => {
341
+ const next = { ...filterValues, [name]: value };
342
+ setInternalFilterValues(next);
343
+ if (serverSide && onFilterChange) onFilterChange(next);
344
+ resetPage();
345
+ fireParamsChange({ filters: next, page: resetPageOnChange ? 1 : void 0 });
346
+ }, [filterValues, serverSide, onFilterChange, fireParamsChange, resetPage, resetPageOnChange]);
347
+ const handleSortChange = (0, import_react.useCallback)((field) => {
348
+ const current = sortState[field] || "none";
349
+ const nextDirection = current === "none" ? "ascending" : current === "ascending" ? "descending" : "none";
350
+ const reset = {};
351
+ columns.forEach((col) => {
352
+ if (col.sortable) reset[col.field] = "none";
353
+ });
354
+ const next = nextDirection === "none" ? reset : { ...reset, [field]: nextDirection };
355
+ setInternalSortState(next);
356
+ if (serverSide && onSortChange) onSortChange(field, nextDirection);
357
+ resetPage();
358
+ fireParamsChange({ sort: next, page: resetPageOnChange ? 1 : void 0 });
359
+ }, [sortState, columns, serverSide, onSortChange, fireParamsChange, resetPage, resetPageOnChange]);
360
+ const handlePageChange = (0, import_react.useCallback)((page) => {
361
+ setCurrentPage(page);
362
+ if (serverSide && onPageChange) onPageChange(page);
363
+ fireParamsChange({ page });
364
+ }, [serverSide, onPageChange, fireParamsChange]);
365
+ const filteredData = (0, import_react.useMemo)(() => {
366
+ if (serverSide) return data;
367
+ let result = data;
368
+ filters.forEach((filter) => {
369
+ const value = filterValues[filter.name];
370
+ if (!isFilterActive(filter, value)) return;
371
+ const type = filter.type || "select";
372
+ if (filter.filterFn) {
373
+ result = result.filter((row) => filter.filterFn(row, value));
374
+ } else if (type === "multiselect") {
375
+ result = result.filter((row) => value.includes(row[filter.name]));
376
+ } else if (type === "dateRange") {
377
+ const fromTs = dateToTimestamp(value.from);
378
+ const toTs = value.to ? dateToTimestamp(value.to) + 864e5 - 1 : null;
379
+ result = result.filter((row) => {
380
+ const rowTs = new Date(row[filter.name]).getTime();
381
+ if (Number.isNaN(rowTs)) return false;
382
+ if (fromTs && rowTs < fromTs) return false;
383
+ if (toTs && rowTs > toTs) return false;
384
+ return true;
385
+ });
386
+ } else {
387
+ result = result.filter((row) => row[filter.name] === value);
388
+ }
389
+ });
390
+ if (searchTerm && searchFields.length > 0) {
391
+ if (fuzzySearch) {
392
+ const fuse = new import_fuse.default(result, {
393
+ keys: searchFields,
394
+ threshold: 0.4,
395
+ distance: 100,
396
+ ignoreLocation: true,
397
+ ...fuzzyOptions
398
+ });
399
+ result = fuse.search(searchTerm).map((r) => r.item);
400
+ } else {
401
+ const term = searchTerm.toLowerCase();
402
+ result = result.filter(
403
+ (row) => searchFields.some((field) => {
404
+ const val = row[field];
405
+ return val && String(val).toLowerCase().includes(term);
406
+ })
407
+ );
408
+ }
409
+ }
410
+ return result;
411
+ }, [data, filterValues, searchTerm, filters, searchFields, serverSide, fuzzySearch, fuzzyOptions]);
412
+ const sortedData = (0, import_react.useMemo)(() => {
413
+ if (serverSide) return filteredData;
414
+ const activeField = Object.keys(sortState).find((k) => sortState[k] !== "none");
415
+ if (!activeField) return filteredData;
416
+ return [...filteredData].sort((a, b) => {
417
+ const dir = sortState[activeField] === "ascending" ? 1 : -1;
418
+ const aVal = a[activeField];
419
+ const bVal = b[activeField];
420
+ if (aVal == null && bVal == null) return 0;
421
+ if (aVal == null) return 1;
422
+ if (bVal == null) return -1;
423
+ if (aVal < bVal) return -dir;
424
+ if (aVal > bVal) return dir;
425
+ return 0;
426
+ });
427
+ }, [filteredData, sortState, serverSide]);
428
+ const groupedData = (0, import_react.useMemo)(() => {
429
+ if (!groupBy) return null;
430
+ const source = serverSide ? data : sortedData;
431
+ const groups = {};
432
+ source.forEach((row) => {
433
+ const key = row[groupBy.field] ?? "--";
434
+ if (!groups[key]) groups[key] = [];
435
+ groups[key].push(row);
436
+ });
437
+ let groupKeys = Object.keys(groups);
438
+ if (groupBy.sort) {
439
+ if (typeof groupBy.sort === "function") {
440
+ groupKeys.sort(groupBy.sort);
441
+ } else {
442
+ const dir = groupBy.sort === "desc" ? -1 : 1;
443
+ groupKeys.sort((a, b) => a < b ? -dir : a > b ? dir : 0);
444
+ }
445
+ }
446
+ return groupKeys.map((key) => ({
447
+ key,
448
+ label: groupBy.label ? groupBy.label(key, groups[key]) : key,
449
+ rows: groups[key]
450
+ }));
451
+ }, [sortedData, data, groupBy, serverSide]);
452
+ const [expandedGroups, setExpandedGroups] = (0, import_react.useState)(() => {
453
+ if (!groupBy) return /* @__PURE__ */ new Set();
454
+ const defaultExpanded = groupBy.defaultExpanded !== false;
455
+ if (defaultExpanded && groupedData) {
456
+ return new Set(groupedData.map((g) => g.key));
457
+ }
458
+ return /* @__PURE__ */ new Set();
459
+ });
460
+ (0, import_react.useEffect)(() => {
461
+ if (!groupedData) return;
462
+ const defaultExpanded = (groupBy == null ? void 0 : groupBy.defaultExpanded) !== false;
463
+ if (defaultExpanded) {
464
+ setExpandedGroups((prev) => {
465
+ const next = new Set(prev);
466
+ groupedData.forEach((g) => next.add(g.key));
467
+ return next;
468
+ });
469
+ }
470
+ }, [groupedData, groupBy]);
471
+ const toggleGroup = (0, import_react.useCallback)((key) => {
472
+ setExpandedGroups((prev) => {
473
+ const next = new Set(prev);
474
+ if (next.has(key)) next.delete(key);
475
+ else next.add(key);
476
+ return next;
477
+ });
478
+ }, []);
479
+ const flatRows = (0, import_react.useMemo)(() => {
480
+ if (!groupedData) return (serverSide ? data : sortedData).map((row) => ({ type: "data", row }));
481
+ const flat = [];
482
+ groupedData.forEach((group) => {
483
+ flat.push({ type: "group-header", group });
484
+ if (expandedGroups.has(group.key)) {
485
+ group.rows.forEach((row) => flat.push({ type: "data", row }));
486
+ }
487
+ });
488
+ return flat;
489
+ }, [groupedData, sortedData, data, serverSide, expandedGroups]);
490
+ const totalItems = serverSide ? totalCount || data.length : flatRows.length;
491
+ const pageCount = Math.ceil(totalItems / pageSize);
492
+ let displayRows;
493
+ if (serverSide) {
494
+ displayRows = groupBy ? flatRows : data.map((row) => ({ type: "data", row }));
495
+ } else {
496
+ displayRows = flatRows.slice(
497
+ (activePage - 1) * pageSize,
498
+ activePage * pageSize
499
+ );
500
+ }
501
+ const footerData = serverSide ? data : filteredData;
502
+ const activeChips = (0, import_react.useMemo)(() => {
503
+ const chips = [];
504
+ filters.forEach((filter) => {
505
+ const value = filterValues[filter.name];
506
+ if (!isFilterActive(filter, value)) return;
507
+ const type = filter.type || "select";
508
+ const prefix = filter.chipLabel || filter.placeholder || filter.name;
509
+ if (type === "multiselect") {
510
+ const labels = value.map((v) => {
511
+ var _a;
512
+ return ((_a = filter.options.find((o) => o.value === v)) == null ? void 0 : _a.label) || v;
513
+ }).join(", ");
514
+ chips.push({ key: filter.name, label: `${prefix}: ${labels}` });
515
+ } else if (type === "dateRange") {
516
+ const parts = [];
517
+ if (value.from) parts.push(`from ${formatDateChip(value.from)}`);
518
+ if (value.to) parts.push(`to ${formatDateChip(value.to)}`);
519
+ chips.push({ key: filter.name, label: `${prefix}: ${parts.join(" ")}` });
520
+ } else {
521
+ const option = filter.options.find((o) => o.value === value);
522
+ chips.push({ key: filter.name, label: `${prefix}: ${(option == null ? void 0 : option.label) || value}` });
523
+ }
524
+ });
525
+ return chips;
526
+ }, [filterValues, filters]);
527
+ const handleFilterRemove = (0, import_react.useCallback)((key) => {
528
+ if (key === "all") {
529
+ const cleared = {};
530
+ filters.forEach((f) => {
531
+ cleared[f.name] = getEmptyFilterValue(f);
532
+ });
533
+ setInternalFilterValues(cleared);
534
+ if (serverSide && onFilterChange) onFilterChange(cleared);
535
+ resetPage();
536
+ fireParamsChange({ filters: cleared, page: resetPageOnChange ? 1 : void 0 });
537
+ } else {
538
+ const filter = filters.find((f) => f.name === key);
539
+ const emptyVal = filter ? getEmptyFilterValue(filter) : "";
540
+ const next = { ...filterValues, [key]: emptyVal };
541
+ setInternalFilterValues(next);
542
+ if (serverSide && onFilterChange) onFilterChange(next);
543
+ resetPage();
544
+ fireParamsChange({ filters: next, page: resetPageOnChange ? 1 : void 0 });
545
+ }
546
+ }, [filters, filterValues, serverSide, onFilterChange, resetPage, fireParamsChange, resetPageOnChange]);
547
+ const displayCount = serverSide ? totalCount || data.length : filteredData.length;
548
+ const totalDataCount = serverSide ? totalCount || data.length : data.length;
549
+ const shownOnPageCount = displayRows.filter((item) => item.type === "data").length;
550
+ const pluralLabel = ((recordLabel == null ? void 0 : recordLabel.plural) || "records").toLowerCase();
551
+ const singularLabel = ((recordLabel == null ? void 0 : recordLabel.singular) || "record").toLowerCase();
552
+ const countLabel = (n) => n === 1 ? singularLabel : pluralLabel;
553
+ const resolvedEmptyTitle = emptyTitle || "No results found";
554
+ const resolvedEmptyMessage = emptyMessage || `No ${pluralLabel} match your search or filter criteria.`;
555
+ const resolvedLoadingLabel = `Loading ${pluralLabel}...`;
556
+ const recordCountLabel = rowCountText ? rowCountText(shownOnPageCount, displayCount) : displayCount === totalDataCount ? `${totalDataCount} ${countLabel(totalDataCount)}` : `${displayCount} of ${totalDataCount} ${countLabel(totalDataCount)}`;
557
+ const [internalSelectedIds, setInternalSelectedIds] = (0, import_react.useState)(/* @__PURE__ */ new Set());
558
+ const selectionResetRef = (0, import_react.useRef)("");
559
+ (0, import_react.useEffect)(() => {
560
+ if (externalSelectedIds != null) {
561
+ setInternalSelectedIds(new Set(externalSelectedIds));
562
+ }
563
+ }, [externalSelectedIds]);
564
+ const selectionQueryKey = (0, import_react.useMemo)(() => {
565
+ if (!resetSelectionOnQueryChange) return "";
566
+ return toStableKey({
567
+ search: searchTerm,
568
+ filters: filterValues,
569
+ sort: serializeSortState(sortState)
570
+ });
571
+ }, [searchTerm, filterValues, sortState, resetSelectionOnQueryChange]);
572
+ const combinedSelectionResetKey = (0, import_react.useMemo)(
573
+ () => `${selectionQueryKey}::${selectionResetKey == null ? "" : toStableKey(selectionResetKey)}`,
574
+ [selectionQueryKey, selectionResetKey]
575
+ );
576
+ (0, import_react.useEffect)(() => {
577
+ if (!selectable || externalSelectedIds != null) {
578
+ selectionResetRef.current = combinedSelectionResetKey;
579
+ return;
580
+ }
581
+ if (selectionResetRef.current && selectionResetRef.current !== combinedSelectionResetKey) {
582
+ setInternalSelectedIds(/* @__PURE__ */ new Set());
583
+ }
584
+ selectionResetRef.current = combinedSelectionResetKey;
585
+ }, [combinedSelectionResetKey, selectable, externalSelectedIds]);
586
+ const selectedIds = externalSelectedIds != null ? new Set(externalSelectedIds) : internalSelectedIds;
587
+ const showRowActionsColumn = !!rowActions && !(hideRowActionsWhenSelectionActive && selectable && selectedIds.size > 0);
588
+ const applySelection = (0, import_react.useCallback)((nextSet) => {
589
+ if (externalSelectedIds == null) {
590
+ setInternalSelectedIds(nextSet);
591
+ }
592
+ if (onSelectionChange) onSelectionChange([...nextSet]);
593
+ }, [externalSelectedIds, onSelectionChange]);
594
+ const pageRowIds = (0, import_react.useMemo)(() => {
595
+ if (serverSide) {
596
+ return data.map((row) => row[rowIdField]).filter((id) => id != null);
597
+ }
598
+ return displayRows.filter((r) => r.type === "data").map((r) => r.row[rowIdField]).filter((id) => id != null);
599
+ }, [serverSide, data, displayRows, rowIdField]);
600
+ const allRowIds = (0, import_react.useMemo)(
601
+ () => flatRows.filter((r) => r.type === "data").map((r) => r.row[rowIdField]).filter((id) => id != null),
602
+ [flatRows, rowIdField]
603
+ );
604
+ const handleSelectRow = (0, import_react.useCallback)((rowId, checked) => {
605
+ const next = new Set(selectedIds);
606
+ if (checked) next.add(rowId);
607
+ else next.delete(rowId);
608
+ applySelection(next);
609
+ }, [selectedIds, applySelection]);
610
+ const handleSelectAll = (0, import_react.useCallback)((checked) => {
611
+ const next = new Set(selectedIds);
612
+ pageRowIds.forEach((id) => {
613
+ if (checked) next.add(id);
614
+ else next.delete(id);
615
+ });
616
+ applySelection(next);
617
+ }, [selectedIds, pageRowIds, applySelection]);
618
+ const allVisibleSelected = (0, import_react.useMemo)(() => {
619
+ return pageRowIds.length > 0 && pageRowIds.every((id) => selectedIds.has(id));
620
+ }, [pageRowIds, selectedIds]);
621
+ const handleSelectAllRows = (0, import_react.useCallback)(() => {
622
+ const idsToAdd = serverSide ? pageRowIds : allRowIds;
623
+ const next = new Set(selectedIds);
624
+ idsToAdd.forEach((id) => next.add(id));
625
+ applySelection(next);
626
+ if (serverSide && onSelectAllRequest) {
627
+ onSelectAllRequest({
628
+ selectedIds: [...next],
629
+ pageIds: pageRowIds,
630
+ totalCount: totalCount || data.length
631
+ });
632
+ }
633
+ }, [serverSide, pageRowIds, allRowIds, selectedIds, applySelection, onSelectAllRequest, totalCount, data.length]);
634
+ const handleDeselectAll = (0, import_react.useCallback)(() => {
635
+ applySelection(/* @__PURE__ */ new Set());
636
+ }, [applySelection]);
637
+ const [editingCell, setEditingCell] = (0, import_react.useState)(null);
638
+ const [editValue, setEditValue] = (0, import_react.useState)(null);
639
+ const [editError, setEditError] = (0, import_react.useState)(null);
640
+ const startEditing = (0, import_react.useCallback)((rowId, field, currentValue) => {
641
+ setEditingCell({ rowId, field });
642
+ setEditValue(currentValue);
643
+ setEditError(null);
644
+ }, []);
645
+ const commitEdit = (0, import_react.useCallback)((row, field, value) => {
646
+ const col = columns.find((c) => c.field === field);
647
+ if (col == null ? void 0 : col.editValidate) {
648
+ const result = col.editValidate(value, row);
649
+ if (result !== true && result !== void 0 && result !== null) {
650
+ setEditError(typeof result === "string" ? result : "Invalid value");
651
+ return;
652
+ }
653
+ }
654
+ if (onRowEdit) onRowEdit(row, field, value);
655
+ setEditingCell(null);
656
+ setEditValue(null);
657
+ setEditError(null);
658
+ }, [onRowEdit, columns]);
659
+ const renderEditControl = (col, row) => {
660
+ const type = col.editType || "text";
661
+ const rowId = row[rowIdField];
662
+ const fieldName = `edit-${rowId}-${col.field}`;
663
+ const commit = (val) => commitEdit(row, col.field, val);
664
+ const exitEdit = () => {
665
+ if (editError) return;
666
+ setEditingCell(null);
667
+ setEditValue(null);
668
+ };
669
+ const extra = col.editProps || {};
670
+ const validate = col.editValidate;
671
+ const validationProps = validate && editError ? { error: true, validationMessage: editError } : {};
672
+ const onInputValidate = validate ? (val) => {
673
+ const result = validate(val, row);
674
+ if (result !== true && result !== void 0 && result !== null) {
675
+ setEditError(typeof result === "string" ? result : "Invalid value");
676
+ } else {
677
+ setEditError(null);
678
+ }
679
+ } : void 0;
680
+ const handleInput = (val) => {
681
+ setEditValue(val);
682
+ if (onInputValidate) onInputValidate(val);
683
+ if (onRowEditInput) onRowEditInput(row, col.field, val);
684
+ };
685
+ const maybeExitDatetimeEdit = () => {
686
+ if (typeof document === "undefined") return;
687
+ setTimeout(() => {
688
+ var _a, _b;
689
+ const activeName = (_b = (_a = document.activeElement) == null ? void 0 : _a.getAttribute) == null ? void 0 : _b.call(_a, "name");
690
+ if (activeName !== `${fieldName}-date` && activeName !== `${fieldName}-time`) {
691
+ exitEdit();
692
+ }
693
+ }, 0);
694
+ };
695
+ switch (type) {
696
+ case "textarea":
697
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TextArea, { ...extra, name: fieldName, label: "", value: editValue ?? "", onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
698
+ case "number":
699
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.NumberInput, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
700
+ case "currency":
701
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.CurrencyInput, { currencyCode: "USD", ...extra, name: fieldName, label: "", value: editValue, onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
702
+ case "stepper":
703
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.StepperInput, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
704
+ case "select":
705
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Select, { variant: "transparent", ...extra, name: fieldName, label: "", value: editValue, onChange: commit, options: resolveEditOptions(col, data) });
706
+ case "multiselect":
707
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.MultiSelect, { ...extra, name: fieldName, label: "", value: editValue || [], onChange: commit, options: resolveEditOptions(col, data) });
708
+ case "date":
709
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.DateInput, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit });
710
+ case "time":
711
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TimeInput, { ...extra, name: fieldName, label: "", value: editValue, onChange: commit });
712
+ case "datetime":
713
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.DateInput, { ...extra, name: `${fieldName}-date`, label: "", value: editValue == null ? void 0 : editValue.date, onChange: (val) => {
714
+ const next = { ...editValue, date: val };
715
+ setEditValue(next);
716
+ if (onRowEdit) onRowEdit(row, col.field, next);
717
+ }, onBlur: maybeExitDatetimeEdit }), /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: "", value: editValue == null ? void 0 : editValue.time, onChange: (val) => {
718
+ const next = { ...editValue, time: val };
719
+ setEditValue(next);
720
+ if (onRowEdit) onRowEdit(row, col.field, next);
721
+ }, onBlur: maybeExitDatetimeEdit }));
722
+ case "toggle":
723
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Toggle, { ...extra, name: fieldName, label: "", checked: !!editValue, onChange: commit });
724
+ case "checkbox":
725
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Checkbox, { ...extra, name: fieldName, checked: !!editValue, onChange: commit });
726
+ default:
727
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Input, { ...extra, name: fieldName, label: "", value: editValue ?? "", onChange: commit, onBlur: exitEdit, ...validationProps, onInput: handleInput });
728
+ }
729
+ };
730
+ const resolvedEditMode = editMode || (columns.some((col) => col.editable) ? "discrete" : null);
731
+ const useColumnRendering = selectable || !!resolvedEditMode || editingRowId != null || showRowActionsColumn || !renderRow;
732
+ const autoWidths = (0, import_react.useMemo)(
733
+ () => autoWidth ? computeAutoWidths(columns, data) : {},
734
+ [columns, data, autoWidth]
735
+ );
736
+ const defaultWidth = scrollable ? "min" : "auto";
737
+ const getHeaderWidth = (col) => {
738
+ var _a;
739
+ return col.width || ((_a = autoWidths[col.field]) == null ? void 0 : _a.width) || defaultWidth;
740
+ };
741
+ const getCellWidth = (col) => {
742
+ var _a;
743
+ return col.cellWidth || col.width || ((_a = autoWidths[col.field]) == null ? void 0 : _a.cellWidth) || defaultWidth;
744
+ };
745
+ const [inlineErrors, setInlineErrors] = (0, import_react.useState)({});
746
+ const renderInlineControl = (col, row) => {
747
+ const type = col.editType || "text";
748
+ const rowId = row[rowIdField];
749
+ const fieldName = `inline-${rowId}-${col.field}`;
750
+ const cellKey = `${rowId}-${col.field}`;
751
+ const value = row[col.field];
752
+ const validate = col.editValidate;
753
+ const fire = (val) => {
754
+ if (validate) {
755
+ const result = validate(val, row);
756
+ if (result !== true && result !== void 0 && result !== null) {
757
+ setInlineErrors((prev) => ({ ...prev, [cellKey]: typeof result === "string" ? result : "Invalid value" }));
758
+ return;
759
+ }
760
+ setInlineErrors((prev) => {
761
+ const next = { ...prev };
762
+ delete next[cellKey];
763
+ return next;
764
+ });
765
+ }
766
+ if (onRowEdit) onRowEdit(row, col.field, val);
767
+ };
768
+ const extra = col.editProps || {};
769
+ const cellError = inlineErrors[cellKey];
770
+ const validationProps = cellError ? { error: true, validationMessage: cellError } : {};
771
+ const onInputValidate = validate ? (val) => {
772
+ const result = validate(val, row);
773
+ if (result !== true && result !== void 0 && result !== null) {
774
+ setInlineErrors((prev) => ({ ...prev, [cellKey]: typeof result === "string" ? result : "Invalid value" }));
775
+ } else {
776
+ setInlineErrors((prev) => {
777
+ const next = { ...prev };
778
+ delete next[cellKey];
779
+ return next;
780
+ });
781
+ }
782
+ } : void 0;
783
+ const emitInput = (val) => {
784
+ if (onInputValidate) onInputValidate(val);
785
+ if (onRowEditInput) onRowEditInput(row, col.field, val);
786
+ };
787
+ switch (type) {
788
+ case "textarea":
789
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TextArea, { ...extra, name: fieldName, label: "", value: value ?? "", onChange: fire, ...validationProps, onInput: emitInput });
790
+ case "number":
791
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.NumberInput, { ...extra, name: fieldName, label: "", value, onChange: fire, ...validationProps, onInput: emitInput });
792
+ case "currency":
793
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.CurrencyInput, { currencyCode: "USD", ...extra, name: fieldName, label: "", value, onChange: fire, ...validationProps, onInput: emitInput });
794
+ case "stepper":
795
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.StepperInput, { ...extra, name: fieldName, label: "", value, onChange: fire, ...validationProps, onInput: emitInput });
796
+ case "select":
797
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Select, { ...extra, name: fieldName, label: "", value, onChange: fire, options: resolveEditOptions(col, data) });
798
+ case "multiselect":
799
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.MultiSelect, { ...extra, name: fieldName, label: "", value: value || [], onChange: fire, options: resolveEditOptions(col, data) });
800
+ case "date":
801
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.DateInput, { ...extra, name: fieldName, label: "", value, onChange: fire });
802
+ case "time":
803
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TimeInput, { ...extra, name: fieldName, label: "", value, onChange: fire });
804
+ case "datetime":
805
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.DateInput, { ...extra, name: `${fieldName}-date`, label: "", value: value == null ? void 0 : value.date, onChange: (val) => {
806
+ fire({ ...value, date: val });
807
+ } }), /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: "", value: value == null ? void 0 : value.time, onChange: (val) => {
808
+ fire({ ...value, time: val });
809
+ } }));
810
+ case "toggle":
811
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Toggle, { ...extra, name: fieldName, label: "", checked: !!value, onChange: fire });
812
+ case "checkbox":
813
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Checkbox, { ...extra, name: fieldName, checked: !!value, onChange: fire });
814
+ default:
815
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Input, { ...extra, name: fieldName, label: "", value: value ?? "", onChange: fire, ...validationProps, onInput: emitInput });
816
+ }
817
+ };
818
+ const renderCellContent = (row, col) => {
819
+ const rowId = row[rowIdField];
820
+ if (resolvedEditMode === "inline" && col.editable) {
821
+ return renderInlineControl(col, row);
822
+ }
823
+ if (editingRowId != null && rowId === editingRowId && col.editable) {
824
+ return renderInlineControl(col, row);
825
+ }
826
+ const isEditing = (editingCell == null ? void 0 : editingCell.rowId) === rowId && (editingCell == null ? void 0 : editingCell.field) === col.field;
827
+ if (isEditing && col.editable) return renderEditControl(col, row);
828
+ const rawValue = row[col.field];
829
+ const rawStr = String(rawValue ?? "");
830
+ if (col.truncate && rawStr.length > 0) {
831
+ if (col.truncate === true) {
832
+ const content2 = col.renderCell ? col.renderCell(rawValue, row) : rawStr;
833
+ if (col.editable) {
834
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { truncate: { tooltipText: rawStr } }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--"));
835
+ }
836
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { truncate: { tooltipText: rawStr } }, content2);
837
+ }
838
+ const maxLen = col.truncate.maxLength || 100;
839
+ if (rawStr.length > maxLen) {
840
+ const truncatedStr = rawStr.slice(0, maxLen) + "\u2026";
841
+ const truncatedContent = col.renderCell ? col.renderCell(truncatedStr, row) : truncatedStr;
842
+ if (col.editable) {
843
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, truncatedContent || "--");
844
+ }
845
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { truncate: { tooltipText: rawStr } }, truncatedContent || "--");
846
+ }
847
+ }
848
+ const content = col.renderCell ? col.renderCell(rawValue, row) : rawValue;
849
+ const isEmpty = content == null || content === "";
850
+ if (col.editable) {
851
+ return /* @__PURE__ */ import_react.default.createElement(
852
+ import_ui_extensions.Link,
853
+ {
854
+ variant: "dark",
855
+ onClick: () => startEditing(rowId, col.field, rawValue)
856
+ },
857
+ isEmpty ? "--" : content
858
+ );
859
+ }
860
+ return isEmpty ? "--" : content;
861
+ };
862
+ const renderFilterControl = (filter) => {
863
+ const type = filter.type || "select";
864
+ if (type === "multiselect") {
865
+ return /* @__PURE__ */ import_react.default.createElement(
866
+ import_ui_extensions.MultiSelect,
867
+ {
868
+ key: filter.name,
869
+ name: `filter-${filter.name}`,
870
+ label: "",
871
+ placeholder: filter.placeholder || "All",
872
+ value: filterValues[filter.name] || [],
873
+ onChange: (val) => handleFilterChange(filter.name, val),
874
+ options: filter.options
875
+ }
876
+ );
877
+ }
878
+ if (type === "dateRange") {
879
+ const rangeVal = filterValues[filter.name] || { from: null, to: null };
880
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { key: filter.name, direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react.default.createElement(
881
+ import_ui_extensions.DateInput,
882
+ {
883
+ name: `filter-${filter.name}-from`,
884
+ label: "",
885
+ placeholder: "From",
886
+ format: "medium",
887
+ value: rangeVal.from,
888
+ onChange: (val) => handleFilterChange(filter.name, { ...rangeVal, from: val })
889
+ }
890
+ ), /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Icon, { name: "dataSync", size: "sm" }), /* @__PURE__ */ import_react.default.createElement(
891
+ import_ui_extensions.DateInput,
892
+ {
893
+ size: "sm",
894
+ name: `filter-${filter.name}-to`,
895
+ label: "",
896
+ placeholder: "To",
897
+ format: "medium",
898
+ value: rangeVal.to,
899
+ onChange: (val) => handleFilterChange(filter.name, { ...rangeVal, to: val })
900
+ }
901
+ ));
902
+ }
903
+ return /* @__PURE__ */ import_react.default.createElement(
904
+ import_ui_extensions.Select,
905
+ {
906
+ key: filter.name,
907
+ name: `filter-${filter.name}`,
908
+ variant: "transparent",
909
+ placeholder: filter.placeholder || "All",
910
+ value: filterValues[filter.name],
911
+ onChange: (val) => handleFilterChange(filter.name, val),
912
+ options: [
913
+ { label: filter.placeholder || "All", value: "" },
914
+ ...filter.options
915
+ ]
916
+ }
917
+ );
918
+ };
919
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: 3 }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, searchFields.length > 0 && /* @__PURE__ */ import_react.default.createElement(
920
+ import_ui_extensions.SearchInput,
921
+ {
922
+ name: "datatable-search",
923
+ placeholder: searchPlaceholder,
924
+ value: searchTerm,
925
+ onChange: handleSearchChange
926
+ }
927
+ ), filters.slice(0, 2).map(renderFilterControl), filters.length > 2 && /* @__PURE__ */ import_react.default.createElement(
928
+ import_ui_extensions.Button,
929
+ {
930
+ variant: "transparent",
931
+ size: "small",
932
+ onClick: () => setShowMoreFilters((prev) => !prev)
933
+ },
934
+ /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Icon, { name: "filter", size: "sm" }),
935
+ " Filters"
936
+ )), showMoreFilters && filters.length > 2 && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "end", gap: "sm", wrap: "wrap" }, filters.slice(2).map(renderFilterControl)), activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showFilterBadges && activeChips.map((chip) => /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Tag, { key: chip.key, variant: "default", onDelete: () => handleFilterRemove(chip.key) }, chip.label)), showClearFiltersButton && /* @__PURE__ */ import_react.default.createElement(
937
+ import_ui_extensions.Button,
938
+ {
939
+ variant: "transparent",
940
+ size: "extra-small",
941
+ onClick: () => handleFilterRemove("all")
942
+ },
943
+ "Clear all"
944
+ )))), showRowCount && displayCount > 0 && !(selectable && selectedIds.size > 0) && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: 1, alignSelf: "end" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel)))), selectable && selectedIds.size > 0 && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: 3 }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "sm", wrap: "nowrap" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { inline: true, format: { fontWeight: "demibold" } }, selectedIds.size, "\xA0", countLabel(selectedIds.size), "\xA0selected"), /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Button, { variant: "transparent", size: "extra-small", onClick: handleSelectAllRows }, "Select all ", displayCount, " ", countLabel(displayCount)), /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Button, { variant: "transparent", size: "extra-small", onClick: handleDeselectAll }, "Deselect all"), selectionActions.map((action, i) => /* @__PURE__ */ import_react.default.createElement(
945
+ import_ui_extensions.Button,
946
+ {
947
+ key: i,
948
+ variant: action.variant || "transparent",
949
+ size: "extra-small",
950
+ onClick: () => action.onClick([...selectedIds])
951
+ },
952
+ action.icon && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Icon, { name: action.icon, size: "sm" }),
953
+ " ",
954
+ action.label
955
+ )))), showRowCount && displayCount > 0 && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: 1, alignSelf: "center" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel)))), loading ? /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.LoadingSpinner, { label: resolvedLoadingLabel, layout: "centered" }) : error ? /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.ErrorState, { title: typeof error === "string" ? error : "Something went wrong." }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, null, typeof error === "string" ? "Please try again." : "An error occurred while loading data.")) : displayRows.length === 0 ? /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.EmptyState, { title: resolvedEmptyTitle, layout: "vertical" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, null, resolvedEmptyMessage))) : /* @__PURE__ */ import_react.default.createElement(
956
+ import_ui_extensions.Table,
957
+ {
958
+ bordered,
959
+ flush,
960
+ paginated: pageCount > 1,
961
+ page: activePage,
962
+ pageCount,
963
+ onPageChange: handlePageChange,
964
+ showFirstLastButtons: showFirstLastButtons != null ? showFirstLastButtons : pageCount > 5,
965
+ showButtonLabels,
966
+ ...maxVisiblePageButtons != null ? { maxVisiblePageButtons } : {}
967
+ },
968
+ /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableHead, null, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableRow, null, selectable && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableHeader, { width: "min" }, /* @__PURE__ */ import_react.default.createElement(
969
+ import_ui_extensions.Checkbox,
970
+ {
971
+ name: "datatable-select-all",
972
+ "aria-label": "Select all rows",
973
+ checked: allVisibleSelected,
974
+ onChange: handleSelectAll
975
+ }
976
+ )), columns.map((col) => {
977
+ const headerAlign = resolvedEditMode === "inline" && col.editable ? void 0 : col.align;
978
+ return /* @__PURE__ */ import_react.default.createElement(
979
+ import_ui_extensions.TableHeader,
980
+ {
981
+ key: col.field,
982
+ width: getHeaderWidth(col),
983
+ align: headerAlign,
984
+ sortDirection: col.sortable ? sortState[col.field] || "none" : "never",
985
+ onSortChange: col.sortable ? () => handleSortChange(col.field) : void 0
986
+ },
987
+ col.label
988
+ );
989
+ }), showRowActionsColumn && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableHeader, { width: "min" }))),
990
+ /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableBody, null, displayRows.map(
991
+ (item, idx) => item.type === "group-header" ? /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableRow, { key: `group-${item.group.key}` }, selectable && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableCell, { width: "min" }), columns.map((col, colIdx) => {
992
+ var _a, _b, _c;
993
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableCell, { key: col.field, width: getCellWidth(col), align: colIdx === 0 ? void 0 : col.align }, colIdx === 0 ? /* @__PURE__ */ import_react.default.createElement(
994
+ import_ui_extensions.Link,
995
+ {
996
+ variant: "dark",
997
+ onClick: () => toggleGroup(item.group.key)
998
+ },
999
+ /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Icon, { name: expandedGroups.has(item.group.key) ? "downCarat" : "right" }), /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { format: { fontWeight: "demibold" } }, item.group.label))
1000
+ ) : ((_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]) ?? "");
1001
+ }), showRowActionsColumn && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableCell, { width: "min" })) : useColumnRendering ? /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableRow, { key: item.row[rowIdField] ?? idx }, selectable && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableCell, { width: "min" }, /* @__PURE__ */ import_react.default.createElement(
1002
+ import_ui_extensions.Checkbox,
1003
+ {
1004
+ name: `select-${item.row[rowIdField]}`,
1005
+ "aria-label": "Select row",
1006
+ checked: selectedIds.has(item.row[rowIdField]),
1007
+ onChange: (checked) => handleSelectRow(item.row[rowIdField], checked)
1008
+ }
1009
+ )), columns.map((col) => {
1010
+ const rowId = item.row[rowIdField];
1011
+ const isDiscreteEditing = resolvedEditMode === "discrete" && (editingCell == null ? void 0 : editingCell.rowId) === rowId && (editingCell == null ? void 0 : editingCell.field) === col.field;
1012
+ const isRowEditing = editingRowId != null && rowId === editingRowId && col.editable;
1013
+ const isShowingInput = isDiscreteEditing || isRowEditing || resolvedEditMode === "inline" && col.editable;
1014
+ const cellAlign = isShowingInput ? void 0 : col.align;
1015
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableCell, { key: col.field, width: isDiscreteEditing || isRowEditing ? "auto" : getCellWidth(col), align: cellAlign }, renderCellContent(item.row, col));
1016
+ }), showRowActionsColumn && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableCell, { width: "min" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, (() => {
1017
+ const resolvedRowActions = typeof rowActions === "function" ? rowActions(item.row) : rowActions;
1018
+ const actions = Array.isArray(resolvedRowActions) ? resolvedRowActions : [];
1019
+ return actions.map((action, i) => /* @__PURE__ */ import_react.default.createElement(
1020
+ import_ui_extensions.Button,
1021
+ {
1022
+ key: i,
1023
+ variant: action.variant || "transparent",
1024
+ size: "extra-small",
1025
+ onClick: () => action.onClick(item.row)
1026
+ },
1027
+ action.icon && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Icon, { name: action.icon, size: "sm" }),
1028
+ action.label && ` ${action.label}`
1029
+ ));
1030
+ })()))) : renderRow(item.row)
1031
+ )),
1032
+ (footer || columns.some((col) => col.footer)) && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableFooter, null, typeof footer === "function" ? footer(footerData) : /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableRow, null, selectable && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableHeader, { width: "min" }), columns.map((col) => {
1033
+ const footerDef = col.footer;
1034
+ const content = typeof footerDef === "function" ? footerDef(footerData) : footerDef || "";
1035
+ return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableHeader, { key: col.field, align: col.align }, content);
1036
+ }), showRowActionsColumn && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TableHeader, { width: "min" })))
1037
+ ));
1038
+ };
1039
+
1040
+ // packages/form/src/FormBuilder.jsx
1041
+ var import_react2 = __toESM(require("react"));
1042
+ var import_ui_extensions2 = require("@hubspot/ui-extensions");
1043
+ var import_crm = require("@hubspot/ui-extensions/crm");
1044
+ var getEmptyValue = (field) => {
1045
+ switch (field.type) {
1046
+ case "toggle":
1047
+ case "checkbox":
1048
+ return false;
1049
+ case "multiselect":
1050
+ case "checkboxGroup":
1051
+ return [];
1052
+ case "number":
1053
+ case "stepper":
1054
+ case "currency":
1055
+ return void 0;
1056
+ case "date":
1057
+ case "time":
1058
+ case "datetime":
1059
+ return void 0;
1060
+ case "display":
1061
+ case "crmPropertyList":
1062
+ case "crmAssociationPropertyList":
1063
+ return void 0;
1064
+ // these field types have no form value
1065
+ case "repeater":
1066
+ return [];
1067
+ default:
1068
+ return "";
1069
+ }
1070
+ };
1071
+ var isValueEmpty = (value, field) => {
1072
+ if (value === void 0 || value === null) return true;
1073
+ if (typeof value === "string" && value.trim() === "") return true;
1074
+ if (Array.isArray(value) && value.length === 0) return true;
1075
+ if ((field.type === "toggle" || field.type === "checkbox") && value === false) return true;
1076
+ return false;
1077
+ };
1078
+ var runValidators = (value, field, allValues, fieldTypes) => {
1079
+ if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") return null;
1080
+ const isRequired = resolveRequired(field, allValues);
1081
+ const plugin = fieldTypes && fieldTypes[field.type];
1082
+ const empty = plugin && plugin.isEmpty ? plugin.isEmpty(value) : isValueEmpty(value, field);
1083
+ if (isRequired && empty) {
1084
+ return `${field.label} is required`;
1085
+ }
1086
+ if (empty) return null;
1087
+ if (field.pattern && typeof value === "string") {
1088
+ if (!field.pattern.test(value)) {
1089
+ return field.patternMessage || "Invalid format";
1090
+ }
1091
+ }
1092
+ if (typeof value === "string") {
1093
+ if (field.minLength != null && value.length < field.minLength) {
1094
+ return `Must be at least ${field.minLength} characters`;
1095
+ }
1096
+ if (field.maxLength != null && value.length > field.maxLength) {
1097
+ return `Must be no more than ${field.maxLength} characters`;
1098
+ }
1099
+ }
1100
+ if (typeof value === "number") {
1101
+ if (field.min != null && value < field.min) {
1102
+ return `Must be at least ${field.min}`;
1103
+ }
1104
+ if (field.max != null && value > field.max) {
1105
+ return `Must be no more than ${field.max}`;
1106
+ }
1107
+ }
1108
+ if (field.validate) {
1109
+ const result = field.validate(value, allValues);
1110
+ if (result && typeof result.then === "function") return null;
1111
+ if (result !== true && result) return result;
1112
+ }
1113
+ return null;
1114
+ };
1115
+ var resolveRequired = (field, allValues) => {
1116
+ if (typeof field.required === "function") return field.required(allValues);
1117
+ return !!field.required;
1118
+ };
1119
+ var resolveOptions = (field, allValues) => {
1120
+ if (typeof field.options === "function") return field.options(allValues);
1121
+ return field.options || [];
1122
+ };
1123
+ var useFormPrefill = (properties, mapping) => {
1124
+ return (0, import_react2.useMemo)(() => {
1125
+ if (!properties) return {};
1126
+ if (!mapping) {
1127
+ const result2 = {};
1128
+ for (const [key, value] of Object.entries(properties)) {
1129
+ if (value !== void 0) result2[key] = value;
1130
+ }
1131
+ return result2;
1132
+ }
1133
+ const result = {};
1134
+ for (const [formField, crmProp] of Object.entries(mapping)) {
1135
+ if (properties[crmProp] !== void 0) {
1136
+ result[formField] = properties[crmProp];
1137
+ }
1138
+ }
1139
+ return result;
1140
+ }, [properties, mapping]);
1141
+ };
1142
+ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref) {
1143
+ const {
1144
+ fields,
1145
+ // FormBuilderField[] — field definitions
1146
+ onSubmit,
1147
+ // (values, { reset, rawValues }) => void | Promise
1148
+ transformValues,
1149
+ // (values) => values — reshape before submit
1150
+ onBeforeSubmit,
1151
+ // (values) => boolean | Promise<boolean> — intercept submit
1152
+ onSubmitSuccess,
1153
+ // (result, { reset, values }) => void
1154
+ onSubmitError,
1155
+ // (error, { values }) => void
1156
+ resetOnSuccess = false
1157
+ // auto-reset after successful submit
1158
+ } = props;
1159
+ const {
1160
+ initialValues,
1161
+ // Record<string, unknown> — starting values (uncontrolled)
1162
+ values,
1163
+ // Record<string, unknown> — controlled values
1164
+ onChange,
1165
+ // (values) => void — called on any field change (controlled)
1166
+ onFieldChange
1167
+ // (name, value, allValues) => void — per-field change callback
1168
+ } = props;
1169
+ const {
1170
+ validateOnChange = false,
1171
+ // validate on keystroke (onInput)
1172
+ validateOnBlur = true,
1173
+ // validate on blur
1174
+ validateOnSubmit = true,
1175
+ // validate all before onSubmit
1176
+ onValidationChange
1177
+ // (errors) => void
1178
+ } = props;
1179
+ const {
1180
+ steps,
1181
+ // FormBuilderStep[] — enables multi-step mode
1182
+ step: controlledStep,
1183
+ // number — controlled current step (0-based)
1184
+ onStepChange,
1185
+ // (step) => void
1186
+ showStepIndicator = true,
1187
+ // show StepIndicator component
1188
+ validateStepOnNext = true
1189
+ // validate current step fields before Next
1190
+ } = props;
1191
+ const {
1192
+ submitLabel = "Submit",
1193
+ // submit button text
1194
+ submitVariant = "primary",
1195
+ // submit button variant
1196
+ showCancel = false,
1197
+ // show cancel button
1198
+ cancelLabel = "Cancel",
1199
+ // cancel button text
1200
+ onCancel,
1201
+ // () => void
1202
+ submitPosition = "bottom",
1203
+ // "bottom" | "none"
1204
+ loading: controlledLoading,
1205
+ // controlled loading state
1206
+ disabled = false
1207
+ // disable entire form
1208
+ } = props;
1209
+ const {
1210
+ columns = 1,
1211
+ // number of grid columns (1 = full-width stack)
1212
+ columnWidth,
1213
+ // AutoGrid columnWidth — responsive layout (overrides columns)
1214
+ layout,
1215
+ // explicit row layout array (overrides columns + columnWidth)
1216
+ sections,
1217
+ // FormBuilderSection[] — accordion field grouping
1218
+ gap = "sm",
1219
+ // gap between fields
1220
+ showRequiredIndicator = true,
1221
+ // show * on required fields
1222
+ noFormWrapper = false,
1223
+ // skip HubSpot <Form> wrapper
1224
+ fieldTypes
1225
+ // Record<string, FieldTypePlugin> — custom field type registry
1226
+ } = props;
1227
+ const {
1228
+ error: formError,
1229
+ // string | boolean — form-level error alert
1230
+ success: formSuccess,
1231
+ // string — form-level success alert
1232
+ readOnly: formReadOnly = false,
1233
+ // boolean — lock all fields
1234
+ readOnlyMessage
1235
+ // string — warning alert when readOnly
1236
+ } = props;
1237
+ const {
1238
+ onDirtyChange,
1239
+ // (isDirty: boolean) => void
1240
+ autoSave
1241
+ // { debounce: number, onAutoSave: (values) => void }
1242
+ } = props;
1243
+ const computeInitialValues = () => {
1244
+ const vals = {};
1245
+ for (const field of fields) {
1246
+ if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
1247
+ const plugin = fieldTypes && fieldTypes[field.type];
1248
+ const emptyValue = plugin && plugin.getEmptyValue ? plugin.getEmptyValue() : getEmptyValue(field);
1249
+ const init = initialValues && initialValues[field.name] !== void 0 ? initialValues[field.name] : field.defaultValue !== void 0 ? field.defaultValue : emptyValue;
1250
+ vals[field.name] = init;
1251
+ }
1252
+ return vals;
1253
+ };
1254
+ const [internalValues, setInternalValues] = (0, import_react2.useState)(computeInitialValues);
1255
+ const [internalErrors, setInternalErrors] = (0, import_react2.useState)({});
1256
+ const [internalStep, setInternalStep] = (0, import_react2.useState)(0);
1257
+ const [internalLoading, setInternalLoading] = (0, import_react2.useState)(false);
1258
+ const [touchedFields, setTouchedFields] = (0, import_react2.useState)({});
1259
+ const [validatingFields, setValidatingFields] = (0, import_react2.useState)({});
1260
+ const asyncValidationRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
1261
+ const debounceTimersRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
1262
+ const initialSnapshot = (0, import_react2.useRef)(null);
1263
+ if (initialSnapshot.current === null) {
1264
+ initialSnapshot.current = JSON.stringify(computeInitialValues());
1265
+ }
1266
+ const formValues = values != null ? values : internalValues;
1267
+ const currentStep = controlledStep != null ? controlledStep : internalStep;
1268
+ const isLoading = controlledLoading != null ? controlledLoading : internalLoading;
1269
+ const isMultiStep = Array.isArray(steps) && steps.length > 0;
1270
+ (0, import_react2.useEffect)(() => {
1271
+ return () => {
1272
+ for (const timer of debounceTimersRef.current.values()) clearTimeout(timer);
1273
+ };
1274
+ }, []);
1275
+ const isDirty = (0, import_react2.useMemo)(() => {
1276
+ return JSON.stringify(formValues) !== initialSnapshot.current;
1277
+ }, [formValues]);
1278
+ const prevDirtyRef = (0, import_react2.useRef)(false);
1279
+ (0, import_react2.useEffect)(() => {
1280
+ if (isDirty !== prevDirtyRef.current) {
1281
+ prevDirtyRef.current = isDirty;
1282
+ if (onDirtyChange) onDirtyChange(isDirty);
1283
+ }
1284
+ }, [isDirty, onDirtyChange]);
1285
+ const autoSaveTimerRef = (0, import_react2.useRef)(null);
1286
+ (0, import_react2.useEffect)(() => {
1287
+ if (!autoSave || !autoSave.onAutoSave || !isDirty) return;
1288
+ if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
1289
+ autoSaveTimerRef.current = setTimeout(() => {
1290
+ autoSaveTimerRef.current = null;
1291
+ autoSave.onAutoSave(formValues);
1292
+ }, autoSave.debounce || 1e3);
1293
+ return () => {
1294
+ if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
1295
+ };
1296
+ }, [formValues, isDirty, autoSave]);
1297
+ const visibleFields = (0, import_react2.useMemo)(() => {
1298
+ let filtered = fields.filter((f) => {
1299
+ if (f.visible && !f.visible(formValues)) return false;
1300
+ return true;
1301
+ });
1302
+ if (isMultiStep && steps[currentStep] && steps[currentStep].fields) {
1303
+ const stepFieldNames = new Set(steps[currentStep].fields);
1304
+ filtered = filtered.filter((f) => stepFieldNames.has(f.name));
1305
+ }
1306
+ return filtered;
1307
+ }, [fields, formValues, isMultiStep, steps, currentStep]);
1308
+ const validateField = (0, import_react2.useCallback)(
1309
+ (name, value) => {
1310
+ const field = fields.find((f) => f.name === name);
1311
+ if (!field) return null;
1312
+ if (field.visible && !field.visible(formValues)) return null;
1313
+ return runValidators(value != null ? value : formValues[name], field, formValues, fieldTypes);
1314
+ },
1315
+ [fields, formValues, fieldTypes]
1316
+ );
1317
+ const validateVisibleFields = (0, import_react2.useCallback)(
1318
+ (fieldSubset) => {
1319
+ const toValidate = fieldSubset || visibleFields;
1320
+ const errors = {};
1321
+ let hasErrors = false;
1322
+ for (const field of toValidate) {
1323
+ const err = runValidators(formValues[field.name], field, formValues, fieldTypes);
1324
+ if (err) {
1325
+ errors[field.name] = err;
1326
+ hasErrors = true;
1327
+ }
1328
+ }
1329
+ return { errors, hasErrors };
1330
+ },
1331
+ [visibleFields, formValues]
1332
+ );
1333
+ const updateErrors = (0, import_react2.useCallback)(
1334
+ (newErrors) => {
1335
+ setInternalErrors((prev) => {
1336
+ const merged = { ...prev, ...newErrors };
1337
+ for (const key of Object.keys(merged)) {
1338
+ if (newErrors[key] === null || newErrors[key] === void 0) {
1339
+ delete merged[key];
1340
+ }
1341
+ }
1342
+ if (onValidationChange) onValidationChange(merged);
1343
+ return merged;
1344
+ });
1345
+ },
1346
+ [onValidationChange]
1347
+ );
1348
+ const runAsyncValidation = (0, import_react2.useCallback)(
1349
+ (name, value) => {
1350
+ const field = fields.find((f) => f.name === name);
1351
+ if (!field || !field.validate) return;
1352
+ const val = value != null ? value : formValues[name];
1353
+ const syncError = runValidators(val, field, formValues, fieldTypes);
1354
+ if (syncError) return;
1355
+ const result = field.validate(val, formValues);
1356
+ if (!result || typeof result.then !== "function") return;
1357
+ const validationPromise = result.then(
1358
+ (asyncResult) => {
1359
+ if (asyncValidationRef.current.get(name) !== validationPromise) return;
1360
+ asyncValidationRef.current.delete(name);
1361
+ setValidatingFields((prev) => {
1362
+ const next = { ...prev };
1363
+ delete next[name];
1364
+ return next;
1365
+ });
1366
+ const err = asyncResult !== true && asyncResult ? asyncResult : null;
1367
+ updateErrors({ [name]: err });
1368
+ },
1369
+ (rejection) => {
1370
+ if (asyncValidationRef.current.get(name) !== validationPromise) return;
1371
+ asyncValidationRef.current.delete(name);
1372
+ setValidatingFields((prev) => {
1373
+ const next = { ...prev };
1374
+ delete next[name];
1375
+ return next;
1376
+ });
1377
+ updateErrors({ [name]: (rejection == null ? void 0 : rejection.message) || "Validation failed" });
1378
+ }
1379
+ );
1380
+ asyncValidationRef.current.set(name, validationPromise);
1381
+ setValidatingFields((prev) => ({ ...prev, [name]: true }));
1382
+ },
1383
+ [fields, formValues, updateErrors]
1384
+ );
1385
+ const triggerAsyncValidation = (0, import_react2.useCallback)(
1386
+ (name, value) => {
1387
+ const field = fields.find((f) => f.name === name);
1388
+ if (!field || !field.validate) return;
1389
+ const debounceMs = field.validateDebounce;
1390
+ if (debounceMs && debounceMs > 0) {
1391
+ const existing = debounceTimersRef.current.get(name);
1392
+ if (existing) clearTimeout(existing);
1393
+ const timer = setTimeout(() => {
1394
+ debounceTimersRef.current.delete(name);
1395
+ runAsyncValidation(name, value);
1396
+ }, debounceMs);
1397
+ debounceTimersRef.current.set(name, timer);
1398
+ } else {
1399
+ runAsyncValidation(name, value);
1400
+ }
1401
+ },
1402
+ [fields, runAsyncValidation]
1403
+ );
1404
+ const setFieldValueSilent = (0, import_react2.useCallback)(
1405
+ (name, value) => {
1406
+ if (values != null) {
1407
+ if (onChange) onChange({ ...formValues, [name]: value });
1408
+ } else {
1409
+ setInternalValues((prev) => ({ ...prev, [name]: value }));
1410
+ }
1411
+ },
1412
+ [values, onChange, formValues]
1413
+ );
1414
+ const handleFieldChange = (0, import_react2.useCallback)(
1415
+ (name, value) => {
1416
+ const newValues = { ...formValues, [name]: value };
1417
+ if (values != null) {
1418
+ if (onChange) onChange(newValues);
1419
+ } else {
1420
+ setInternalValues(newValues);
1421
+ }
1422
+ if (onFieldChange) onFieldChange(name, value, newValues);
1423
+ if (internalErrors[name]) {
1424
+ updateErrors({ [name]: null });
1425
+ }
1426
+ const field = fields.find((f) => f.name === name);
1427
+ if (field && field.onFieldChange) {
1428
+ field.onFieldChange(value, newValues, {
1429
+ setFieldValue: setFieldValueSilent,
1430
+ setFieldError: (fieldName, message) => updateErrors({ [fieldName]: message })
1431
+ });
1432
+ }
1433
+ },
1434
+ [formValues, values, onChange, onFieldChange, internalErrors, updateErrors, fields, setFieldValueSilent]
1435
+ );
1436
+ const inputDebounceRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
1437
+ const handleDebouncedFieldChange = (0, import_react2.useCallback)(
1438
+ (name, value) => {
1439
+ const field = fields.find((f) => f.name === name);
1440
+ const debounceMs = field && field.debounce;
1441
+ if (debounceMs && debounceMs > 0) {
1442
+ const existing = inputDebounceRef.current.get(name);
1443
+ if (existing) clearTimeout(existing);
1444
+ const timer = setTimeout(() => {
1445
+ inputDebounceRef.current.delete(name);
1446
+ handleFieldChange(name, value);
1447
+ }, debounceMs);
1448
+ inputDebounceRef.current.set(name, timer);
1449
+ } else {
1450
+ handleFieldChange(name, value);
1451
+ }
1452
+ },
1453
+ [fields, handleFieldChange]
1454
+ );
1455
+ const handleFieldInput = (0, import_react2.useCallback)(
1456
+ (name, value) => {
1457
+ if (validateOnChange) {
1458
+ const err = validateField(name, value);
1459
+ updateErrors({ [name]: err });
1460
+ }
1461
+ },
1462
+ [validateOnChange, validateField, updateErrors]
1463
+ );
1464
+ const handleFieldBlur = (0, import_react2.useCallback)(
1465
+ (name, value) => {
1466
+ setTouchedFields((prev) => ({ ...prev, [name]: true }));
1467
+ if (validateOnBlur) {
1468
+ const err = validateField(name, value != null ? value : formValues[name]);
1469
+ updateErrors({ [name]: err });
1470
+ if (!err) {
1471
+ triggerAsyncValidation(name, value != null ? value : formValues[name]);
1472
+ }
1473
+ }
1474
+ },
1475
+ [validateOnBlur, validateField, updateErrors, formValues, triggerAsyncValidation]
1476
+ );
1477
+ const handleSubmit = (0, import_react2.useCallback)(
1478
+ async (e) => {
1479
+ if (e && e.preventDefault) e.preventDefault();
1480
+ if (validateOnSubmit) {
1481
+ const allVisible = fields.filter((f) => !f.visible || f.visible(formValues));
1482
+ const { errors, hasErrors } = validateVisibleFields(allVisible);
1483
+ if (hasErrors) {
1484
+ setInternalErrors(errors);
1485
+ if (onValidationChange) onValidationChange(errors);
1486
+ return;
1487
+ }
1488
+ if (asyncValidationRef.current.size > 0) {
1489
+ await Promise.all(asyncValidationRef.current.values());
1490
+ const currentErrors = { ...internalErrors };
1491
+ const hasAsyncErrors = Object.keys(currentErrors).length > 0;
1492
+ if (hasAsyncErrors) return;
1493
+ }
1494
+ }
1495
+ const reset = () => {
1496
+ const fresh = computeInitialValues();
1497
+ if (values == null) setInternalValues(fresh);
1498
+ setInternalErrors({});
1499
+ setTouchedFields({});
1500
+ initialSnapshot.current = JSON.stringify(fresh);
1501
+ };
1502
+ const rawValues = {};
1503
+ for (const key of Object.keys(formValues)) {
1504
+ const f = fields.find((fd) => fd.name === key);
1505
+ if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList")) continue;
1506
+ rawValues[key] = formValues[key];
1507
+ }
1508
+ const submitValues = transformValues ? transformValues(rawValues) : rawValues;
1509
+ if (onBeforeSubmit) {
1510
+ const proceed = await onBeforeSubmit(submitValues);
1511
+ if (proceed === false) return;
1512
+ }
1513
+ if (controlledLoading == null) setInternalLoading(true);
1514
+ try {
1515
+ const result = await onSubmit(submitValues, { reset, rawValues });
1516
+ if (resetOnSuccess) reset();
1517
+ if (onSubmitSuccess) onSubmitSuccess(result, { reset, values: submitValues });
1518
+ } catch (err) {
1519
+ if (onSubmitError) {
1520
+ onSubmitError(err, { values: submitValues });
1521
+ } else {
1522
+ throw err;
1523
+ }
1524
+ } finally {
1525
+ if (controlledLoading == null) setInternalLoading(false);
1526
+ }
1527
+ },
1528
+ [validateOnSubmit, fields, formValues, validateVisibleFields, onValidationChange, onSubmit, values, controlledLoading, internalErrors, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess]
1529
+ );
1530
+ const handleNext = (0, import_react2.useCallback)(() => {
1531
+ if (!isMultiStep) return;
1532
+ if (validateStepOnNext && steps[currentStep] && steps[currentStep].fields) {
1533
+ const stepFields = fields.filter(
1534
+ (f) => steps[currentStep].fields.includes(f.name) && (!f.visible || f.visible(formValues))
1535
+ );
1536
+ const { errors, hasErrors } = validateVisibleFields(stepFields);
1537
+ if (hasErrors) {
1538
+ setInternalErrors((prev) => ({ ...prev, ...errors }));
1539
+ if (onValidationChange) onValidationChange({ ...internalErrors, ...errors });
1540
+ return;
1541
+ }
1542
+ }
1543
+ if (steps[currentStep] && steps[currentStep].validate) {
1544
+ const result = steps[currentStep].validate(formValues);
1545
+ if (result !== true && result) {
1546
+ setInternalErrors((prev) => ({ ...prev, ...result }));
1547
+ return;
1548
+ }
1549
+ }
1550
+ const nextStep = Math.min(currentStep + 1, steps.length - 1);
1551
+ if (controlledStep != null) {
1552
+ if (onStepChange) onStepChange(nextStep);
1553
+ } else {
1554
+ setInternalStep(nextStep);
1555
+ }
1556
+ }, [isMultiStep, validateStepOnNext, steps, currentStep, fields, formValues, validateVisibleFields, onValidationChange, internalErrors, controlledStep, onStepChange]);
1557
+ const handleBack = (0, import_react2.useCallback)(() => {
1558
+ if (!isMultiStep) return;
1559
+ const prevStep = Math.max(currentStep - 1, 0);
1560
+ if (controlledStep != null) {
1561
+ if (onStepChange) onStepChange(prevStep);
1562
+ } else {
1563
+ setInternalStep(prevStep);
1564
+ }
1565
+ }, [isMultiStep, currentStep, controlledStep, onStepChange]);
1566
+ const handleGoTo = (0, import_react2.useCallback)(
1567
+ (stepIndex) => {
1568
+ if (!isMultiStep) return;
1569
+ const clamped = Math.max(0, Math.min(stepIndex, steps.length - 1));
1570
+ if (controlledStep != null) {
1571
+ if (onStepChange) onStepChange(clamped);
1572
+ } else {
1573
+ setInternalStep(clamped);
1574
+ }
1575
+ },
1576
+ [isMultiStep, steps, controlledStep, onStepChange]
1577
+ );
1578
+ (0, import_react2.useImperativeHandle)(ref, () => ({
1579
+ submit: handleSubmit,
1580
+ validate: () => {
1581
+ const allVisible = fields.filter((f) => !f.visible || f.visible(formValues));
1582
+ const { errors, hasErrors } = validateVisibleFields(allVisible);
1583
+ setInternalErrors(errors);
1584
+ return { valid: !hasErrors, errors };
1585
+ },
1586
+ reset: () => {
1587
+ const fresh = computeInitialValues();
1588
+ if (values == null) setInternalValues(fresh);
1589
+ setInternalErrors({});
1590
+ setTouchedFields({});
1591
+ initialSnapshot.current = JSON.stringify(fresh);
1592
+ },
1593
+ getValues: () => formValues,
1594
+ isDirty: () => isDirty,
1595
+ setFieldValue: (name, value) => handleFieldChange(name, value),
1596
+ setFieldError: (name, message) => updateErrors({ [name]: message }),
1597
+ setErrors: (errors) => {
1598
+ setInternalErrors(errors);
1599
+ if (onValidationChange) onValidationChange(errors);
1600
+ }
1601
+ }));
1602
+ const renderField = (field) => {
1603
+ const fieldValue = formValues[field.name];
1604
+ const fieldError = internalErrors[field.name] || null;
1605
+ const hasError = !!fieldError;
1606
+ const isRequired = showRequiredIndicator && resolveRequired(field, formValues);
1607
+ const isReadOnly = field.readOnly || disabled || formReadOnly;
1608
+ const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
1609
+ if (field.type === "display") {
1610
+ if (field.render) {
1611
+ return field.render({ allValues: formValues });
1612
+ }
1613
+ return null;
1614
+ }
1615
+ if (field.type === "crmPropertyList") {
1616
+ return /* @__PURE__ */ import_react2.default.createElement(
1617
+ import_crm.CrmPropertyList,
1618
+ {
1619
+ properties: field.properties,
1620
+ direction: field.direction,
1621
+ ...field.objectId ? { objectId: field.objectId } : {},
1622
+ ...field.objectTypeId ? { objectTypeId: field.objectTypeId } : {},
1623
+ ...field.fieldProps || {}
1624
+ }
1625
+ );
1626
+ }
1627
+ if (field.type === "crmAssociationPropertyList") {
1628
+ return /* @__PURE__ */ import_react2.default.createElement(
1629
+ import_crm.CrmAssociationPropertyList,
1630
+ {
1631
+ objectTypeId: field.objectTypeId,
1632
+ properties: field.properties,
1633
+ ...field.associationLabels ? { associationLabels: field.associationLabels } : {},
1634
+ ...field.filters ? { filters: field.filters } : {},
1635
+ ...field.sort ? { sort: field.sort } : {},
1636
+ ...field.fieldProps || {}
1637
+ }
1638
+ );
1639
+ }
1640
+ if (field.render) {
1641
+ return field.render({
1642
+ value: fieldValue,
1643
+ onChange: fieldOnChange,
1644
+ error: hasError,
1645
+ allValues: formValues
1646
+ });
1647
+ }
1648
+ const plugin = fieldTypes && fieldTypes[field.type];
1649
+ if (plugin && plugin.render) {
1650
+ return plugin.render({
1651
+ value: fieldValue,
1652
+ onChange: fieldOnChange,
1653
+ error: hasError,
1654
+ field,
1655
+ allValues: formValues
1656
+ });
1657
+ }
1658
+ const commonProps = {
1659
+ name: field.name,
1660
+ label: field.label,
1661
+ description: field.description,
1662
+ placeholder: field.placeholder,
1663
+ tooltip: field.tooltip,
1664
+ required: isRequired,
1665
+ readOnly: isReadOnly,
1666
+ error: hasError,
1667
+ validationMessage: fieldError || void 0,
1668
+ ...field.loading || validatingFields[field.name] ? { loading: true } : {},
1669
+ ...field.fieldProps || {}
1670
+ };
1671
+ const options = resolveOptions(field, formValues);
1672
+ switch (field.type) {
1673
+ case "text":
1674
+ case "password":
1675
+ return /* @__PURE__ */ import_react2.default.createElement(
1676
+ import_ui_extensions2.Input,
1677
+ {
1678
+ ...commonProps,
1679
+ type: field.type === "password" ? "password" : "text",
1680
+ value: fieldValue || "",
1681
+ onChange: fieldOnChange,
1682
+ onInput: (v) => handleFieldInput(field.name, v),
1683
+ onBlur: (v) => handleFieldBlur(field.name, v)
1684
+ }
1685
+ );
1686
+ case "textarea":
1687
+ return /* @__PURE__ */ import_react2.default.createElement(
1688
+ import_ui_extensions2.TextArea,
1689
+ {
1690
+ ...commonProps,
1691
+ value: fieldValue || "",
1692
+ rows: field.rows,
1693
+ cols: field.cols,
1694
+ resize: field.resize,
1695
+ maxLength: field.maxLength,
1696
+ onChange: fieldOnChange,
1697
+ onInput: (v) => handleFieldInput(field.name, v),
1698
+ onBlur: (v) => handleFieldBlur(field.name, v)
1699
+ }
1700
+ );
1701
+ case "number":
1702
+ return /* @__PURE__ */ import_react2.default.createElement(
1703
+ import_ui_extensions2.NumberInput,
1704
+ {
1705
+ ...commonProps,
1706
+ value: fieldValue,
1707
+ min: field.min,
1708
+ max: field.max,
1709
+ precision: field.precision,
1710
+ formatStyle: field.formatStyle,
1711
+ onChange: fieldOnChange,
1712
+ onBlur: (v) => handleFieldBlur(field.name, v)
1713
+ }
1714
+ );
1715
+ case "stepper":
1716
+ return /* @__PURE__ */ import_react2.default.createElement(
1717
+ import_ui_extensions2.StepperInput,
1718
+ {
1719
+ ...commonProps,
1720
+ value: fieldValue,
1721
+ min: field.min,
1722
+ max: field.max,
1723
+ precision: field.precision,
1724
+ formatStyle: field.formatStyle,
1725
+ stepSize: field.stepSize,
1726
+ minValueReachedTooltip: field.minValueReachedTooltip,
1727
+ maxValueReachedTooltip: field.maxValueReachedTooltip,
1728
+ onChange: fieldOnChange,
1729
+ onBlur: (v) => handleFieldBlur(field.name, v)
1730
+ }
1731
+ );
1732
+ case "currency":
1733
+ return /* @__PURE__ */ import_react2.default.createElement(
1734
+ import_ui_extensions2.CurrencyInput,
1735
+ {
1736
+ ...commonProps,
1737
+ currency: field.currency || "USD",
1738
+ value: fieldValue,
1739
+ min: field.min,
1740
+ max: field.max,
1741
+ precision: field.precision,
1742
+ onChange: fieldOnChange,
1743
+ onBlur: (v) => handleFieldBlur(field.name, v)
1744
+ }
1745
+ );
1746
+ case "date":
1747
+ return /* @__PURE__ */ import_react2.default.createElement(
1748
+ import_ui_extensions2.DateInput,
1749
+ {
1750
+ ...commonProps,
1751
+ value: fieldValue,
1752
+ format: field.format,
1753
+ min: field.min,
1754
+ max: field.max,
1755
+ timezone: field.timezone,
1756
+ clearButtonLabel: field.clearButtonLabel,
1757
+ todayButtonLabel: field.todayButtonLabel,
1758
+ minValidationMessage: field.minValidationMessage,
1759
+ maxValidationMessage: field.maxValidationMessage,
1760
+ onChange: fieldOnChange,
1761
+ onBlur: (v) => handleFieldBlur(field.name, v)
1762
+ }
1763
+ );
1764
+ case "time":
1765
+ return /* @__PURE__ */ import_react2.default.createElement(
1766
+ import_ui_extensions2.TimeInput,
1767
+ {
1768
+ ...commonProps,
1769
+ value: fieldValue,
1770
+ interval: field.interval,
1771
+ min: field.min,
1772
+ max: field.max,
1773
+ timezone: field.timezone,
1774
+ onChange: fieldOnChange,
1775
+ onBlur: (v) => handleFieldBlur(field.name, v)
1776
+ }
1777
+ );
1778
+ case "datetime": {
1779
+ const dateVal = fieldValue ? fieldValue.date || fieldValue : void 0;
1780
+ const timeVal = fieldValue ? fieldValue.time || void 0 : void 0;
1781
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1 }, /* @__PURE__ */ import_react2.default.createElement(
1782
+ import_ui_extensions2.DateInput,
1783
+ {
1784
+ ...commonProps,
1785
+ name: `${field.name}-date`,
1786
+ label: field.label,
1787
+ format: field.format,
1788
+ value: dateVal,
1789
+ min: field.min,
1790
+ max: field.max,
1791
+ timezone: field.timezone,
1792
+ clearButtonLabel: field.clearButtonLabel,
1793
+ todayButtonLabel: field.todayButtonLabel,
1794
+ minValidationMessage: field.minValidationMessage,
1795
+ maxValidationMessage: field.maxValidationMessage,
1796
+ onChange: (v) => {
1797
+ handleFieldChange(field.name, { ...fieldValue, date: v, time: timeVal });
1798
+ }
1799
+ }
1800
+ )), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1 }, /* @__PURE__ */ import_react2.default.createElement(
1801
+ import_ui_extensions2.TimeInput,
1802
+ {
1803
+ name: `${field.name}-time`,
1804
+ label: "Time",
1805
+ description: field.description,
1806
+ tooltip: field.tooltip,
1807
+ readOnly: isReadOnly,
1808
+ error: hasError,
1809
+ value: timeVal,
1810
+ interval: field.interval,
1811
+ timezone: field.timezone,
1812
+ onChange: (v) => {
1813
+ handleFieldChange(field.name, { ...fieldValue, date: dateVal, time: v });
1814
+ }
1815
+ }
1816
+ )));
1817
+ }
1818
+ case "select":
1819
+ return /* @__PURE__ */ import_react2.default.createElement(
1820
+ import_ui_extensions2.Select,
1821
+ {
1822
+ ...commonProps,
1823
+ value: fieldValue,
1824
+ options,
1825
+ variant: field.variant,
1826
+ onChange: fieldOnChange
1827
+ }
1828
+ );
1829
+ case "multiselect":
1830
+ return /* @__PURE__ */ import_react2.default.createElement(
1831
+ import_ui_extensions2.MultiSelect,
1832
+ {
1833
+ ...commonProps,
1834
+ value: fieldValue || [],
1835
+ options,
1836
+ onChange: fieldOnChange
1837
+ }
1838
+ );
1839
+ case "toggle":
1840
+ return /* @__PURE__ */ import_react2.default.createElement(
1841
+ import_ui_extensions2.Toggle,
1842
+ {
1843
+ name: field.name,
1844
+ label: field.label,
1845
+ checked: !!fieldValue,
1846
+ size: field.size || "md",
1847
+ labelDisplay: field.labelDisplay || "top",
1848
+ textChecked: field.textChecked,
1849
+ textUnchecked: field.textUnchecked,
1850
+ readonly: isReadOnly,
1851
+ onChange: fieldOnChange,
1852
+ ...field.fieldProps || {}
1853
+ }
1854
+ );
1855
+ case "checkbox":
1856
+ return /* @__PURE__ */ import_react2.default.createElement(
1857
+ import_ui_extensions2.Checkbox,
1858
+ {
1859
+ name: field.name,
1860
+ checked: !!fieldValue,
1861
+ description: field.description,
1862
+ readOnly: isReadOnly,
1863
+ inline: field.inline,
1864
+ variant: field.variant,
1865
+ onChange: fieldOnChange,
1866
+ ...field.fieldProps || {}
1867
+ },
1868
+ field.label
1869
+ );
1870
+ case "checkboxGroup":
1871
+ return /* @__PURE__ */ import_react2.default.createElement(
1872
+ import_ui_extensions2.ToggleGroup,
1873
+ {
1874
+ ...commonProps,
1875
+ toggleType: "checkboxList",
1876
+ value: fieldValue || [],
1877
+ options,
1878
+ inline: field.inline,
1879
+ variant: field.variant,
1880
+ onChange: fieldOnChange
1881
+ }
1882
+ );
1883
+ case "radioGroup":
1884
+ return /* @__PURE__ */ import_react2.default.createElement(
1885
+ import_ui_extensions2.ToggleGroup,
1886
+ {
1887
+ ...commonProps,
1888
+ toggleType: "radioButtonList",
1889
+ value: fieldValue,
1890
+ options,
1891
+ inline: field.inline,
1892
+ variant: field.variant,
1893
+ onChange: fieldOnChange
1894
+ }
1895
+ );
1896
+ case "repeater": {
1897
+ const rows = Array.isArray(fieldValue) ? fieldValue : [];
1898
+ const subFields = field.fields || [];
1899
+ const minRows = field.min || 0;
1900
+ const maxRows = field.max || Infinity;
1901
+ const canAdd = rows.length < maxRows && !isReadOnly;
1902
+ const canRemove = rows.length > minRows && !isReadOnly;
1903
+ const addRow = () => {
1904
+ const emptyRow = {};
1905
+ for (const sf of subFields) {
1906
+ emptyRow[sf.name] = sf.defaultValue !== void 0 ? sf.defaultValue : getEmptyValue(sf);
1907
+ }
1908
+ handleFieldChange(field.name, [...rows, emptyRow]);
1909
+ };
1910
+ const removeRow = (idx) => {
1911
+ handleFieldChange(field.name, rows.filter((_, i) => i !== idx));
1912
+ };
1913
+ const updateRow = (idx, subName, subValue) => {
1914
+ const updated = rows.map(
1915
+ (row, i) => i === idx ? { ...row, [subName]: subValue } : row
1916
+ );
1917
+ handleFieldChange(field.name, updated);
1918
+ };
1919
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs" }, field.label && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, field.label, isRequired ? " *" : ""), field.description && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, field.description), rows.map((row, rowIdx) => /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: rowIdx, direction: "row", gap: "xs", align: "end" }, subFields.map((sf) => {
1920
+ const sfValue = row[sf.name];
1921
+ const sfLabel = rowIdx === 0 ? sf.label : void 0;
1922
+ const sfOptions = resolveOptions(sf, formValues);
1923
+ const sfProps = {
1924
+ name: `${field.name}-${rowIdx}-${sf.name}`,
1925
+ label: sfLabel,
1926
+ placeholder: sf.placeholder,
1927
+ readOnly: isReadOnly,
1928
+ ...sf.fieldProps || {}
1929
+ };
1930
+ let sfElement;
1931
+ switch (sf.type) {
1932
+ case "select":
1933
+ sfElement = /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Select, { ...sfProps, value: sfValue, options: sfOptions, onChange: (v) => updateRow(rowIdx, sf.name, v) });
1934
+ break;
1935
+ case "number":
1936
+ sfElement = /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.NumberInput, { ...sfProps, value: sfValue, onChange: (v) => updateRow(rowIdx, sf.name, v) });
1937
+ break;
1938
+ case "checkbox":
1939
+ sfElement = /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Checkbox, { ...sfProps, checked: !!sfValue, onChange: (v) => updateRow(rowIdx, sf.name, v) }, sf.label);
1940
+ break;
1941
+ default:
1942
+ sfElement = /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Input, { ...sfProps, value: sfValue || "", onChange: (v) => updateRow(rowIdx, sf.name, v) });
1943
+ }
1944
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { key: sf.name, flex: 1 }, sfElement);
1945
+ }), canRemove && /* @__PURE__ */ import_react2.default.createElement(
1946
+ import_ui_extensions2.Button,
1947
+ {
1948
+ variant: "secondary",
1949
+ size: "xs",
1950
+ onClick: () => removeRow(rowIdx)
1951
+ },
1952
+ "Remove"
1953
+ ))), canAdd && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "secondary", size: "sm", onClick: addRow }, "+ Add"), hasError && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, fieldError));
1954
+ }
1955
+ default:
1956
+ return /* @__PURE__ */ import_react2.default.createElement(
1957
+ import_ui_extensions2.Input,
1958
+ {
1959
+ ...commonProps,
1960
+ value: fieldValue || "",
1961
+ onChange: fieldOnChange,
1962
+ onInput: (v) => handleFieldInput(field.name, v),
1963
+ onBlur: (v) => handleFieldBlur(field.name, v)
1964
+ }
1965
+ );
1966
+ }
1967
+ };
1968
+ const getFieldColSpan = (field) => {
1969
+ if (field.colSpan != null) return Math.min(field.colSpan, columns);
1970
+ if (field.width === "full" && columns > 1) return columns;
1971
+ return 1;
1972
+ };
1973
+ const getDependents = (parentField) => visibleFields.filter((f) => f.dependsOn === parentField.name && f.name !== parentField.name);
1974
+ const isDependent = (field) => field.dependsOn && visibleFields.some((f) => f.name === field.dependsOn && f.name !== field.name);
1975
+ const renderDependentGroup = (parentField, dependents) => {
1976
+ const firstWithLabel = dependents.find((f) => f.dependsOnLabel) || dependents[0];
1977
+ const firstWithMessage = dependents.find((f) => f.dependsOnMessage) || dependents[0];
1978
+ const groupLabel = firstWithLabel.dependsOnLabel || "Dependent properties";
1979
+ const rawMessage = firstWithMessage.dependsOnMessage;
1980
+ const tooltipMessage = typeof rawMessage === "function" ? rawMessage(parentField.label) : rawMessage || "";
1981
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, { key: `dep-${parentField.name}`, compact: true }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, groupLabel, " ", tooltipMessage && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { inline: true, variant: "dark", overlay: /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tooltip, null, tooltipMessage) }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "info" })))), dependents.map((dep) => /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, { key: dep.name }, renderField(dep)))));
1982
+ };
1983
+ const renderGridLayout = (fieldSubset) => {
1984
+ const fieldList = fieldSubset || visibleFields;
1985
+ const elements = [];
1986
+ let currentRow = [];
1987
+ let currentRowSpan = 0;
1988
+ const flushRow = () => {
1989
+ if (currentRow.length === 0) return;
1990
+ const totalSpan = currentRow.reduce((s, f) => s + getFieldColSpan(f), 0);
1991
+ const remainder = columns - totalSpan;
1992
+ elements.push(
1993
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: `row-${currentRow[0].name}`, direction: "row", gap }, currentRow.map((f) => /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { key: f.name, flex: getFieldColSpan(f) }, renderField(f))), remainder > 0 && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: remainder }))
1994
+ );
1995
+ currentRow = [];
1996
+ currentRowSpan = 0;
1997
+ };
1998
+ for (const field of fieldList) {
1999
+ if (isDependent(field)) continue;
2000
+ const span = getFieldColSpan(field);
2001
+ if (span >= columns) {
2002
+ flushRow();
2003
+ elements.push(
2004
+ /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, { key: field.name }, renderField(field))
2005
+ );
2006
+ } else {
2007
+ if (currentRowSpan + span > columns) flushRow();
2008
+ currentRow.push(field);
2009
+ currentRowSpan += span;
2010
+ if (currentRowSpan >= columns) flushRow();
2011
+ }
2012
+ const dependents = getDependents(field);
2013
+ if (dependents.length > 0) {
2014
+ flushRow();
2015
+ elements.push(renderDependentGroup(field, dependents));
2016
+ }
2017
+ }
2018
+ flushRow();
2019
+ return elements;
2020
+ };
2021
+ const renderExplicitLayout = () => {
2022
+ const elements = [];
2023
+ const renderedNames = /* @__PURE__ */ new Set();
2024
+ for (let rowIdx = 0; rowIdx < layout.length; rowIdx++) {
2025
+ const row = layout[rowIdx];
2026
+ const rowFields = [];
2027
+ for (const entry of row) {
2028
+ const fieldName = typeof entry === "string" ? entry : entry.field;
2029
+ const flexValue = typeof entry === "string" ? 1 : entry.flex || 1;
2030
+ const field = visibleFields.find((f) => f.name === fieldName);
2031
+ if (!field) continue;
2032
+ rowFields.push({ field, flex: flexValue });
2033
+ renderedNames.add(fieldName);
2034
+ }
2035
+ if (rowFields.length === 0) continue;
2036
+ if (rowFields.length === 1) {
2037
+ elements.push(
2038
+ /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, { key: rowFields[0].field.name }, renderField(rowFields[0].field))
2039
+ );
2040
+ } else {
2041
+ elements.push(
2042
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: `layout-row-${rowIdx}`, direction: "row", gap }, rowFields.map(({ field, flex }) => /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { key: field.name, flex }, renderField(field))))
2043
+ );
2044
+ }
2045
+ for (const { field } of rowFields) {
2046
+ const dependents = getDependents(field).filter((d) => !renderedNames.has(d.name));
2047
+ if (dependents.length > 0) {
2048
+ elements.push(renderDependentGroup(field, dependents));
2049
+ for (const dep of dependents) renderedNames.add(dep.name);
2050
+ }
2051
+ }
2052
+ }
2053
+ for (const field of visibleFields) {
2054
+ if (renderedNames.has(field.name)) continue;
2055
+ if (isDependent(field)) continue;
2056
+ elements.push(
2057
+ /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, { key: field.name }, renderField(field))
2058
+ );
2059
+ renderedNames.add(field.name);
2060
+ const dependents = getDependents(field).filter((d) => !renderedNames.has(d.name));
2061
+ if (dependents.length > 0) {
2062
+ elements.push(renderDependentGroup(field, dependents));
2063
+ for (const dep of dependents) renderedNames.add(dep.name);
2064
+ }
2065
+ }
2066
+ return elements;
2067
+ };
2068
+ const renderLegacyLayout = (fieldSubset) => {
2069
+ const fieldList = fieldSubset || visibleFields;
2070
+ const rows = [];
2071
+ let i = 0;
2072
+ while (i < fieldList.length) {
2073
+ const field = fieldList[i];
2074
+ if (field.width === "half" && i + 1 < fieldList.length && fieldList[i + 1].width === "half" && !field.dependsOn) {
2075
+ rows.push({ type: "pair", fields: [fieldList[i], fieldList[i + 1]] });
2076
+ i += 2;
2077
+ } else {
2078
+ rows.push({ type: "single", field });
2079
+ i++;
2080
+ }
2081
+ }
2082
+ const elements = [];
2083
+ const processedDeps = /* @__PURE__ */ new Set();
2084
+ for (const row of rows) {
2085
+ if (row.type === "pair") {
2086
+ elements.push(
2087
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: `pair-${row.fields[0].name}`, direction: "row", gap: "sm" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1 }, renderField(row.fields[0])), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1 }, renderField(row.fields[1])))
2088
+ );
2089
+ } else {
2090
+ const field = row.field;
2091
+ if (processedDeps.has(field.name)) continue;
2092
+ elements.push(
2093
+ /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, { key: field.name }, renderField(field))
2094
+ );
2095
+ const dependents = getDependents(field);
2096
+ if (dependents.length > 0) {
2097
+ for (const dep of dependents) processedDeps.add(dep.name);
2098
+ elements.push(renderDependentGroup(field, dependents));
2099
+ }
2100
+ }
2101
+ }
2102
+ return elements;
2103
+ };
2104
+ const renderAutoGridLayout = (fieldSubset) => {
2105
+ const fieldList = fieldSubset || visibleFields;
2106
+ const elements = [];
2107
+ let batch = [];
2108
+ const flushBatch = () => {
2109
+ if (batch.length === 0) return;
2110
+ elements.push(
2111
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.AutoGrid, { key: `ag-${batch[0].name}`, columnWidth, flexible: true, gap }, batch.map((f) => /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, { key: f.name }, renderField(f))))
2112
+ );
2113
+ batch = [];
2114
+ };
2115
+ for (const field of fieldList) {
2116
+ if (isDependent(field)) continue;
2117
+ batch.push(field);
2118
+ const dependents = getDependents(field);
2119
+ if (dependents.length > 0) {
2120
+ flushBatch();
2121
+ elements.push(renderDependentGroup(field, dependents));
2122
+ }
2123
+ }
2124
+ flushBatch();
2125
+ return elements;
2126
+ };
2127
+ const wrapWithGroups = (fieldList, renderFn) => {
2128
+ const hasGroups = fieldList.some((f) => f.group);
2129
+ if (!hasGroups) return renderFn(fieldList);
2130
+ const chunks = [];
2131
+ let currentGroup = void 0;
2132
+ let currentChunk = [];
2133
+ for (const field of fieldList) {
2134
+ const fieldGroup = field.group || void 0;
2135
+ if (fieldGroup !== currentGroup && currentChunk.length > 0) {
2136
+ chunks.push({ group: currentGroup, fields: [...currentChunk] });
2137
+ currentChunk = [];
2138
+ }
2139
+ currentGroup = fieldGroup;
2140
+ currentChunk.push(field);
2141
+ }
2142
+ if (currentChunk.length > 0) {
2143
+ chunks.push({ group: currentGroup, fields: currentChunk });
2144
+ }
2145
+ const elements = [];
2146
+ for (let i = 0; i < chunks.length; i++) {
2147
+ const chunk = chunks[i];
2148
+ if (i > 0) {
2149
+ elements.push(/* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Divider, { key: `group-div-${i}` }));
2150
+ }
2151
+ if (chunk.group) {
2152
+ elements.push(
2153
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { key: `group-label-${i}`, format: { fontWeight: "demibold" } }, chunk.group)
2154
+ );
2155
+ }
2156
+ const chunkElements = renderFn(chunk.fields);
2157
+ if (Array.isArray(chunkElements)) {
2158
+ elements.push(...chunkElements);
2159
+ } else {
2160
+ elements.push(chunkElements);
2161
+ }
2162
+ }
2163
+ return elements;
2164
+ };
2165
+ const renderFieldSubset = (fieldSubset) => {
2166
+ if (layout && fieldSubset === visibleFields) return renderExplicitLayout();
2167
+ if (columnWidth) return renderAutoGridLayout(fieldSubset);
2168
+ if (columns > 1) return renderGridLayout(fieldSubset);
2169
+ return renderLegacyLayout(fieldSubset);
2170
+ };
2171
+ const renderSections = () => {
2172
+ const hasSections = Array.isArray(sections) && sections.length > 0;
2173
+ if (!hasSections) return null;
2174
+ const sectionFieldNames = /* @__PURE__ */ new Set();
2175
+ for (const sec of sections) {
2176
+ if (sec.fields) for (const name of sec.fields) sectionFieldNames.add(name);
2177
+ }
2178
+ const elements = [];
2179
+ for (const sec of sections) {
2180
+ const sectionFields = sec.fields ? visibleFields.filter((f) => sec.fields.includes(f.name)) : [];
2181
+ if (sectionFields.length === 0) continue;
2182
+ const accordionContent = /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap }, renderFieldSubset(sectionFields));
2183
+ const accordion = /* @__PURE__ */ import_react2.default.createElement(
2184
+ import_ui_extensions2.Accordion,
2185
+ {
2186
+ key: sec.id,
2187
+ title: sec.label,
2188
+ size: "sm",
2189
+ defaultOpen: sec.defaultOpen !== false
2190
+ },
2191
+ accordionContent
2192
+ );
2193
+ if (sec.info) {
2194
+ elements.push(
2195
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: sec.id, direction: "row", align: "start", gap: "flush" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1 }, accordion), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { overlay: /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tooltip, null, sec.info) }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "info", size: "sm", screenReaderText: sec.info })))
2196
+ );
2197
+ } else {
2198
+ elements.push(accordion);
2199
+ }
2200
+ }
2201
+ const unsectionedFields = visibleFields.filter(
2202
+ (f) => !sectionFieldNames.has(f.name)
2203
+ );
2204
+ if (unsectionedFields.length > 0) {
2205
+ elements.push(...renderFieldSubset(unsectionedFields));
2206
+ }
2207
+ return elements;
2208
+ };
2209
+ const renderFieldLayout = () => {
2210
+ const hasSections = Array.isArray(sections) && sections.length > 0;
2211
+ if (hasSections) return renderSections();
2212
+ const hasGroups = visibleFields.some((f) => f.group);
2213
+ if (hasGroups && !layout) {
2214
+ return wrapWithGroups(visibleFields, renderFieldSubset);
2215
+ }
2216
+ if (layout) return renderExplicitLayout();
2217
+ return renderFieldSubset(visibleFields);
2218
+ };
2219
+ const renderButtons = () => {
2220
+ if (submitPosition === "none" || formReadOnly) return null;
2221
+ const isLastStep = !isMultiStep || currentStep === steps.length - 1;
2222
+ const isFirstStep = !isMultiStep || currentStep === 0;
2223
+ if (isMultiStep) {
2224
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: "between", align: "center" }, !isFirstStep ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "secondary", onClick: handleBack, disabled }, "Back") : showCancel ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "secondary", onClick: onCancel, disabled }, cancelLabel) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, null, " "), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Inline, { gap: "small" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, "Step ", currentStep + 1, " of ", steps.length), isLastStep ? /* @__PURE__ */ import_react2.default.createElement(
2225
+ import_ui_extensions2.LoadingButton,
2226
+ {
2227
+ variant: submitVariant,
2228
+ loading: isLoading,
2229
+ onClick: handleSubmit,
2230
+ disabled
2231
+ },
2232
+ submitLabel
2233
+ ) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "primary", onClick: handleNext, disabled }, "Next")));
2234
+ }
2235
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: showCancel ? "between" : "start", gap: "sm" }, showCancel && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "secondary", onClick: onCancel, disabled }, cancelLabel), /* @__PURE__ */ import_react2.default.createElement(
2236
+ import_ui_extensions2.LoadingButton,
2237
+ {
2238
+ variant: submitVariant,
2239
+ type: noFormWrapper ? "button" : "submit",
2240
+ loading: isLoading,
2241
+ onClick: noFormWrapper ? handleSubmit : void 0,
2242
+ disabled
2243
+ },
2244
+ submitLabel
2245
+ ));
2246
+ };
2247
+ const formContent = /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap }, isMultiStep && showStepIndicator && /* @__PURE__ */ import_react2.default.createElement(
2248
+ import_ui_extensions2.StepIndicator,
2249
+ {
2250
+ currentStep,
2251
+ stepNames: steps.map((s) => s.title)
2252
+ }
2253
+ ), formReadOnly && readOnlyMessage && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Alert, { title: "Read Only", variant: "warning" }, readOnlyMessage), formError && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Alert, { title: "Error", variant: "danger" }, typeof formError === "string" ? formError : void 0), formSuccess && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Alert, { title: "Success", variant: "success" }, formSuccess), isMultiStep && steps[currentStep] && steps[currentStep].render ? steps[currentStep].render({
2254
+ values: formValues,
2255
+ goNext: handleNext,
2256
+ goBack: handleBack,
2257
+ goTo: handleGoTo
2258
+ }) : (
2259
+ /* Field layout */
2260
+ renderFieldLayout()
2261
+ ), renderButtons());
2262
+ if (noFormWrapper) {
2263
+ return formContent;
2264
+ }
2265
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Form, { onSubmit: handleSubmit, autoComplete: props.autoComplete }, formContent);
2266
+ });
2267
+ // Annotate the CommonJS export names for ESM import in node:
2268
+ 0 && (module.exports = {
2269
+ DataTable,
2270
+ FormBuilder,
2271
+ useFormPrefill
2272
+ });