@redsift/table-pro 12.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CONTRIBUTING.md +435 -0
  2. package/_internal/BaseComponents.js +3 -0
  3. package/_internal/BaseComponents.js.map +1 -0
  4. package/_internal/BaseIconButton.js +126 -0
  5. package/_internal/BaseIconButton.js.map +1 -0
  6. package/_internal/BaseTextField.js +26 -0
  7. package/_internal/BaseTextField.js.map +1 -0
  8. package/_internal/DataGrid.js +2 -0
  9. package/_internal/DataGrid.js.map +1 -0
  10. package/_internal/DataGrid2.js +507 -0
  11. package/_internal/DataGrid2.js.map +1 -0
  12. package/_internal/GridToolbarFilterSemanticField.js +2 -0
  13. package/_internal/GridToolbarFilterSemanticField.js.map +1 -0
  14. package/_internal/GridToolbarFilterSemanticField2.js +6962 -0
  15. package/_internal/GridToolbarFilterSemanticField2.js.map +1 -0
  16. package/_internal/Pagination.js +2 -0
  17. package/_internal/Pagination.js.map +1 -0
  18. package/_internal/ServerSideControlledPagination.js +324 -0
  19. package/_internal/ServerSideControlledPagination.js.map +1 -0
  20. package/_internal/StatefulDataGrid.js +2 -0
  21. package/_internal/StatefulDataGrid.js.map +1 -0
  22. package/_internal/StatefulDataGrid2.js +3237 -0
  23. package/_internal/StatefulDataGrid2.js.map +1 -0
  24. package/_internal/TextCell.js +2 -0
  25. package/_internal/TextCell.js.map +1 -0
  26. package/_internal/TextCell2.js +66 -0
  27. package/_internal/TextCell2.js.map +1 -0
  28. package/_internal/Toolbar.js +2 -0
  29. package/_internal/Toolbar.js.map +1 -0
  30. package/_internal/Toolbar2.js +102 -0
  31. package/_internal/Toolbar2.js.map +1 -0
  32. package/_internal/ToolbarWrapper.js +2 -0
  33. package/_internal/ToolbarWrapper.js.map +1 -0
  34. package/_internal/ToolbarWrapper2.js +53 -0
  35. package/_internal/ToolbarWrapper2.js.map +1 -0
  36. package/_internal/_rollupPluginBabelHelpers.js +93 -0
  37. package/_internal/_rollupPluginBabelHelpers.js.map +1 -0
  38. package/_internal/useControlledDatagridState.js +170 -0
  39. package/_internal/useControlledDatagridState.js.map +1 -0
  40. package/index.d.ts +1223 -0
  41. package/index.js +721 -0
  42. package/index.js.map +1 -0
  43. package/package.json +96 -0
  44. package/style/index.css +7 -0
package/index.js ADDED
@@ -0,0 +1,721 @@
1
+ import { o as operatorList, D as DOES_NOT_CONTAIN, a as DOES_NOT_EQUAL, b as DOES_NOT_START_WITH, c as DOES_NOT_END_WITH, I as IS_ANY_OF, d as IS_NOT_ANY_OF, C as CONTAINS_ANY_OF, e as DOES_NOT_CONTAIN_ANY_OF, S as STARTS_WITH_ANY_OF, f as DOES_NOT_START_WITH_ANY_OF, E as ENDS_WITH_ANY_OF, g as DOES_NOT_END_WITH_ANY_OF, h as IS_BETWEEN, i as IS_WITH_SELECT, j as IS_NOT_WITH_SELECT, k as IS_ANY_OF_WITH_SELECT, l as IS_NOT_ANY_OF_WITH_SELECT, A as ARRAY_IS_EMPTY, m as ARRAY_IS_NOT_EMPTY, H as HAS_WITH_SELECT, n as DOES_NOT_HAVE_WITH_SELECT, p as HAS_ANY_OF_WITH_SELECT, q as HAS_ALL_OF_WITH_SELECT, r as DOES_NOT_HAVE_ANY_OF_WITH_SELECT, s as HAS_ONLY_WITH_SELECT, t as HAS, u as DOES_NOT_HAVE, v as HAS_ANY_OF, w as HAS_ALL_OF, x as DOES_NOT_HAVE_ANY_OF, y as HAS_ONLY } from './_internal/StatefulDataGrid2.js';
2
+ export { A as ARRAY_IS_EMPTY, m as ARRAY_IS_NOT_EMPTY, W as CATEGORIES, U as COLUMN_ORDER_MODEL_KEY, C as CONTAINS_ANY_OF, T as DENSITY_MODEL_KEY, Q as DIMENSION_MODEL_KEY, D as DOES_NOT_CONTAIN, e as DOES_NOT_CONTAIN_ANY_OF, c as DOES_NOT_END_WITH, g as DOES_NOT_END_WITH_ANY_OF, a as DOES_NOT_EQUAL, u as DOES_NOT_HAVE, x as DOES_NOT_HAVE_ANY_OF, r as DOES_NOT_HAVE_ANY_OF_WITH_SELECT, n as DOES_NOT_HAVE_WITH_SELECT, b as DOES_NOT_START_WITH, f as DOES_NOT_START_WITH_ANY_OF, E as ENDS_WITH_ANY_OF, M as FILTER_MODEL_KEY, R as FILTER_SEARCH_KEY, t as HAS, w as HAS_ALL_OF, q as HAS_ALL_OF_WITH_SELECT, v as HAS_ANY_OF, p as HAS_ANY_OF_WITH_SELECT, y as HAS_ONLY, s as HAS_ONLY_WITH_SELECT, H as HAS_WITH_SELECT, B as IS, I as IS_ANY_OF, k as IS_ANY_OF_WITH_SELECT, h as IS_BETWEEN, F as IS_NOT, d as IS_NOT_ANY_OF, l as IS_NOT_ANY_OF_WITH_SELECT, j as IS_NOT_WITH_SELECT, i as IS_WITH_SELECT, P as PAGINATION_MODEL_KEY, O as PINNED_COLUMNS, N as SORT_MODEL_KEY, S as STARTS_WITH_ANY_OF, av as StatefulDataGrid, V as VISIBILITY_MODEL_KEY, au as areFilterModelsEquivalent, a3 as areSearchStringsEqual, a2 as buildQueryParamsString, X as buildStorageKey, Z as clearAllVersionStorage, Y as clearPreviousVersionStorage, a0 as convertFromDisplayFormat, $ as convertToDisplayFormat, a4 as decodeValue, a5 as encodeValue, ap as getColumnOrderFromString, ah as getColumnVisibilityFromString, a1 as getDecodedSearchFromUrl, am as getDensityFromString, ao as getDensityModel, ab as getFilterModelFromString, ar as getFinalSearch, z as getGridNumericOperators, J as getGridStringArrayOperators, K as getGridStringArrayOperatorsWithSelect, L as getGridStringArrayOperatorsWithSelectOnStringArrayColumns, G as getGridStringOperators, as as getModelsParsedOrUpdateLocalStorage, af as getPaginationFromString, aj as getPinnedColumnsFromString, aq as getSearchParamsFromColumnOrder, ai as getSearchParamsFromColumnVisibility, an as getSearchParamsFromDensity, ac as getSearchParamsFromFilterModel, ag as getSearchParamsFromPagination, ak as getSearchParamsFromPinnedColumns, ae as getSearchParamsFromSorting, al as getSearchParamsFromTab, ad as getSortingFromString, a9 as isOperatorValueValid, aa as isValueValid, a8 as numberOperatorDecoder, a7 as numberOperatorEncoder, o as operatorList, _ as resetStatefulDataGridState, at as updateUrl, a6 as urlSearchParamsToString } from './_internal/StatefulDataGrid2.js';
3
+ import { GRID_DETAIL_PANEL_TOGGLE_COL_DEF, GridFilterInputMultipleValue, GridFilterInputMultipleSingleSelect, getGridDateOperators, getGridStringOperators, getGridNumericOperators } from '@mui/x-data-grid-pro';
4
+ export { getGridBooleanOperators, getGridDateOperators, getGridSingleSelectOperators } from '@mui/x-data-grid-pro';
5
+ import { _ as _objectSpread2 } from './_internal/_rollupPluginBabelHelpers.js';
6
+ import * as React from 'react';
7
+ import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
8
+ import Box from '@mui/material/Box';
9
+ import TextField from '@mui/material/TextField';
10
+ import { Icon } from '@redsift/design-system';
11
+ import { mdiSync } from '@redsift/icons';
12
+ export { D as DEFAULT_OPERATORS, G as GridToolbarFilterSemanticField, g as getCompletion } from './_internal/GridToolbarFilterSemanticField2.js';
13
+ export { C as ControlledPagination, E as EMPTY_ROW_SELECTION_MODEL, S as ServerSideControlledPagination, c as createRowSelectionModel, f as fixServerSideHeaderCheckboxSelection, b as getSelectableRowIdsInPage, a as getSelectedIds, g as getSelectionCount, i as isRowSelected, n as normalizeRowSelectionModel, o as onServerSideSelectionStatusChange } from './_internal/ServerSideControlledPagination.js';
14
+ export { B as BaseButton, a as BaseCheckbox, b as BaseIcon, c as BaseIconButton, m as muiIconToDSIcon } from './_internal/BaseIconButton.js';
15
+ export { B as BaseTextField } from './_internal/BaseTextField.js';
16
+ export { D as DataGrid } from './_internal/DataGrid2.js';
17
+ export { T as TextCell } from './_internal/TextCell2.js';
18
+ export { T as Toolbar } from './_internal/Toolbar2.js';
19
+ export { T as ToolbarWrapper } from './_internal/ToolbarWrapper2.js';
20
+
21
+ // Don't use a spread operator there or it will break the build due to MUI internal components
22
+ const DETAIL_PANEL_TOGGLE_COL_DEF = GRID_DETAIL_PANEL_TOGGLE_COL_DEF;
23
+ DETAIL_PANEL_TOGGLE_COL_DEF.type = 'actions';
24
+
25
+ const isAnyOfIOperator = {
26
+ label: 'is any of (case-insensitive)',
27
+ value: 'isAnyOfI',
28
+ getApplyFilterFn: filterItem => {
29
+ if (!filterItem.field || !filterItem.value || !filterItem.operator) {
30
+ return null;
31
+ }
32
+ const lowerCaseFilterValues = filterItem.value.map(v => String(v).toLowerCase());
33
+ return value => {
34
+ if (filterItem.value.length === 0) {
35
+ return true;
36
+ }
37
+ if (value == null) {
38
+ return false;
39
+ }
40
+ const paramValues = Array.isArray(value) ? value : [value];
41
+ for (const paramValue of paramValues) {
42
+ if (lowerCaseFilterValues.includes(String(paramValue).toLowerCase())) {
43
+ return true;
44
+ }
45
+ }
46
+ return false;
47
+ };
48
+ },
49
+ InputComponent: GridFilterInputMultipleValue
50
+ };
51
+ const IS_ANY_OF_I = isAnyOfIOperator;
52
+ const IS_ANY_OF_I_WITH_SELECT = _objectSpread2(_objectSpread2({}, IS_ANY_OF_I), {}, {
53
+ InputComponent: GridFilterInputMultipleSingleSelect
54
+ });
55
+
56
+ const SUBMIT_FILTER_STROKE_TIME = 500;
57
+ const InputDateInterval = props => {
58
+ var _item$value, _filterValueState$, _filterValueState$2;
59
+ const {
60
+ item,
61
+ applyValue,
62
+ focusElementRef = null
63
+ } = props;
64
+ const filterTimeout = React.useRef();
65
+ const [filterValueState, setFilterValueState] = React.useState((_item$value = item.value) !== null && _item$value !== void 0 ? _item$value : ['', '']);
66
+ const [applying, setIsApplying] = React.useState(false);
67
+ React.useEffect(() => {
68
+ return () => {
69
+ clearTimeout(filterTimeout.current);
70
+ };
71
+ }, []);
72
+ React.useEffect(() => {
73
+ var _item$value2;
74
+ const itemValue = (_item$value2 = item.value) !== null && _item$value2 !== void 0 ? _item$value2 : ['', ''];
75
+ setFilterValueState(itemValue);
76
+ }, [item.value]);
77
+ const updateFilterValue = (from, to) => {
78
+ clearTimeout(filterTimeout.current);
79
+ setFilterValueState([from, to]);
80
+ setIsApplying(true);
81
+ filterTimeout.current = setTimeout(() => {
82
+ setIsApplying(false);
83
+ applyValue(_objectSpread2(_objectSpread2({}, item), {}, {
84
+ value: [from, to]
85
+ }));
86
+ }, SUBMIT_FILTER_STROKE_TIME);
87
+ };
88
+ const handleFromChange = event => {
89
+ const newFrom = event.target.value;
90
+ updateFilterValue(newFrom, filterValueState[1]);
91
+ };
92
+ const handleToChange = event => {
93
+ const newTo = event.target.value;
94
+ updateFilterValue(filterValueState[0], newTo);
95
+ };
96
+ return /*#__PURE__*/React.createElement(Box, {
97
+ sx: {
98
+ display: 'inline-flex',
99
+ flexDirection: 'row',
100
+ alignItems: 'end',
101
+ height: 48,
102
+ pl: '20px'
103
+ }
104
+ }, /*#__PURE__*/React.createElement(TextField, {
105
+ name: "date-from-input",
106
+ placeholder: "From",
107
+ label: "From",
108
+ variant: "standard",
109
+ value: (_filterValueState$ = filterValueState[0]) !== null && _filterValueState$ !== void 0 ? _filterValueState$ : '',
110
+ onChange: handleFromChange,
111
+ type: "date",
112
+ inputRef: focusElementRef,
113
+ sx: {
114
+ mr: 2
115
+ },
116
+ InputLabelProps: {
117
+ shrink: true
118
+ }
119
+ }), /*#__PURE__*/React.createElement(TextField, {
120
+ name: "date-to-input",
121
+ placeholder: "To",
122
+ label: "To",
123
+ variant: "standard",
124
+ value: (_filterValueState$2 = filterValueState[1]) !== null && _filterValueState$2 !== void 0 ? _filterValueState$2 : '',
125
+ onChange: handleToChange,
126
+ type: "date",
127
+ InputProps: applying ? {
128
+ endAdornment: /*#__PURE__*/React.createElement(Icon, {
129
+ icon: mdiSync
130
+ })
131
+ } : {},
132
+ InputLabelProps: {
133
+ shrink: true
134
+ }
135
+ }));
136
+ };
137
+
138
+ const isBetweenOperator = {
139
+ label: 'is between',
140
+ value: 'isBetween',
141
+ getApplyFilterFn: filterItem => {
142
+ if (!filterItem.field || !filterItem.value || !filterItem.operator) {
143
+ return null;
144
+ }
145
+ if (!Array.isArray(filterItem.value) || filterItem.value.length !== 2) {
146
+ return null;
147
+ }
148
+ if (!filterItem.value[0] || !filterItem.value[1]) {
149
+ return null;
150
+ }
151
+
152
+ // Parse as local time (not UTC) since the date input produces YYYY-MM-DD
153
+ // strings representing dates in the user's local timezone.
154
+ const [fromYear, fromMonth, fromDay] = filterItem.value[0].split('-').map(Number);
155
+ const [toYear, toMonth, toDay] = filterItem.value[1].split('-').map(Number);
156
+ const fromTime = new Date(fromYear, fromMonth - 1, fromDay).getTime();
157
+ // Set upper bound to end-of-day so dateTime values later that day are included.
158
+ const toTime = new Date(toYear, toMonth - 1, toDay, 23, 59, 59, 999).getTime();
159
+ if (isNaN(fromTime) || isNaN(toTime)) {
160
+ return null;
161
+ }
162
+ return value => {
163
+ if (value == null) {
164
+ return false;
165
+ }
166
+ const cellTime = value instanceof Date ? value.getTime() : new Date(value).getTime();
167
+ if (isNaN(cellTime)) {
168
+ return false;
169
+ }
170
+ return cellTime >= fromTime && cellTime <= toTime;
171
+ };
172
+ },
173
+ InputComponent: InputDateInterval
174
+ };
175
+ const DATE_IS_BETWEEN = isBetweenOperator;
176
+
177
+ const getGridDateOperatorsExtended = function () {
178
+ let showTime = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
179
+ return [...getGridDateOperators(showTime), DATE_IS_BETWEEN];
180
+ };
181
+
182
+ const lowercaseValue = value => {
183
+ if (typeof value === 'string') {
184
+ return value.toLowerCase();
185
+ }
186
+ if (Array.isArray(value)) {
187
+ return value.map(lowercaseValue);
188
+ }
189
+ return value;
190
+ };
191
+
192
+ /**
193
+ * Wraps a filter operator's `getApplyFilterFn` to lowercase both the cell value
194
+ * and the filter value before comparison, making the operator case-insensitive.
195
+ *
196
+ * Only affects string-based values — numbers, dates, and booleans pass through unchanged.
197
+ */
198
+ const wrapCaseInsensitive = operator => {
199
+ const originalGetApplyFilterFn = operator.getApplyFilterFn;
200
+ return _objectSpread2(_objectSpread2({}, operator), {}, {
201
+ getApplyFilterFn: (filterItem, column) => {
202
+ const caseInsensitiveFilterItem = _objectSpread2(_objectSpread2({}, filterItem), {}, {
203
+ value: lowercaseValue(filterItem.value)
204
+ });
205
+ const originalFn = originalGetApplyFilterFn(caseInsensitiveFilterItem, column);
206
+ if (!originalFn) {
207
+ return null;
208
+ }
209
+ return (value, row, column, apiRef) => {
210
+ const lowercasedValue = lowercaseValue(value);
211
+ return originalFn(lowercasedValue, row, column, apiRef);
212
+ };
213
+ }
214
+ });
215
+ };
216
+
217
+ /**
218
+ * Applies case-insensitive wrapping to an array of filter operators.
219
+ */
220
+ const makeCaseInsensitive = operators => {
221
+ return operators.map(wrapCaseInsensitive);
222
+ };
223
+
224
+ const getRsStringColumnType = () => {
225
+ return {
226
+ type: 'string',
227
+ filterOperators: operatorList.rsString
228
+ };
229
+ };
230
+ const getRsNumberColumnType = () => {
231
+ return {
232
+ type: 'number',
233
+ filterOperators: operatorList.rsNumber
234
+ };
235
+ };
236
+ const getRsSingleSelectColumnType = () => {
237
+ return {
238
+ type: 'singleSelect',
239
+ filterOperators: operatorList.rsSingleSelect
240
+ };
241
+ };
242
+ const getRsSingleSelectWithShortOperatorListColumnType = () => {
243
+ return {
244
+ type: 'singleSelect',
245
+ filterOperators: operatorList.rsSingleSelectWithShortOperatorList
246
+ };
247
+ };
248
+ const getRsMultipleSelectColumnType = () => {
249
+ return {
250
+ type: 'singleSelect',
251
+ filterOperators: operatorList.rsMultipleSelect
252
+ };
253
+ };
254
+ const getRsMultipleSelectWithShortOperatorListColumnType = () => {
255
+ return {
256
+ type: 'singleSelect',
257
+ filterOperators: operatorList.rsMultipleSelectWithShortOperatorList
258
+ };
259
+ };
260
+ const customColumnTypes = {
261
+ rsString: getRsStringColumnType(),
262
+ rsNumber: getRsNumberColumnType(),
263
+ rsSingleSelect: getRsSingleSelectColumnType(),
264
+ rsSingleSelectWithShortOperatorList: getRsSingleSelectWithShortOperatorListColumnType(),
265
+ rsMultipleSelect: getRsMultipleSelectColumnType(),
266
+ rsMultipleSelectWithShortOperatorList: getRsMultipleSelectWithShortOperatorListColumnType()
267
+ };
268
+
269
+ // ---------------------------------------------------------------------------
270
+ // Operator name unions (for typed `exclude`)
271
+ // ---------------------------------------------------------------------------
272
+ // ---------------------------------------------------------------------------
273
+ // Column type
274
+ // ---------------------------------------------------------------------------
275
+ // ---------------------------------------------------------------------------
276
+ // Options: discriminated union (built-in vs override)
277
+ // ---------------------------------------------------------------------------
278
+ // ---------------------------------------------------------------------------
279
+ // Full & compact operator lists
280
+ // ---------------------------------------------------------------------------
281
+ const getStringOperatorsFull = () => [...getGridStringOperators().filter(op => !['isAnyOf'].includes(op.value)), DOES_NOT_CONTAIN, DOES_NOT_EQUAL, DOES_NOT_START_WITH, DOES_NOT_END_WITH, IS_ANY_OF, IS_NOT_ANY_OF, CONTAINS_ANY_OF, DOES_NOT_CONTAIN_ANY_OF, STARTS_WITH_ANY_OF, DOES_NOT_START_WITH_ANY_OF, ENDS_WITH_ANY_OF, DOES_NOT_END_WITH_ANY_OF];
282
+ const getStringOperatorsCompact = () => [...getGridStringOperators().filter(op => !['isAnyOf', 'startsWith', 'endsWith'].includes(op.value)), DOES_NOT_CONTAIN, DOES_NOT_EQUAL, IS_ANY_OF];
283
+ const getNumberOperatorsFull = () => [...getGridNumericOperators(), IS_BETWEEN];
284
+ const getNumberOperatorsCompact = () => getGridNumericOperators();
285
+ const getDateOperatorsFull = showTime => [...getGridDateOperators(showTime), DATE_IS_BETWEEN];
286
+ const getDateOperatorsCompact = showTime => getGridDateOperators(showTime);
287
+ const getSingleSelectOperatorsFull = () => [IS_WITH_SELECT, IS_NOT_WITH_SELECT, IS_ANY_OF_WITH_SELECT, IS_NOT_ANY_OF_WITH_SELECT, ARRAY_IS_EMPTY, ARRAY_IS_NOT_EMPTY];
288
+ const getSingleSelectOperatorsCompact = () => [IS_WITH_SELECT, IS_NOT_WITH_SELECT, IS_ANY_OF_WITH_SELECT];
289
+ const getMultiSelectOperatorsFull = () => [HAS_WITH_SELECT, DOES_NOT_HAVE_WITH_SELECT, HAS_ANY_OF_WITH_SELECT, HAS_ALL_OF_WITH_SELECT, DOES_NOT_HAVE_ANY_OF_WITH_SELECT, HAS_ONLY_WITH_SELECT, ARRAY_IS_EMPTY, ARRAY_IS_NOT_EMPTY];
290
+ const getMultiSelectOperatorsCompact = () => [HAS_WITH_SELECT, DOES_NOT_HAVE_WITH_SELECT, HAS_ANY_OF_WITH_SELECT];
291
+ const getTagsOperatorsFull = () => [HAS, DOES_NOT_HAVE, HAS_ANY_OF, HAS_ALL_OF, DOES_NOT_HAVE_ANY_OF, HAS_ONLY, CONTAINS_ANY_OF, DOES_NOT_CONTAIN_ANY_OF, ARRAY_IS_EMPTY, ARRAY_IS_NOT_EMPTY];
292
+ const getTagsOperatorsCompact = () => [HAS, DOES_NOT_HAVE, HAS_ANY_OF];
293
+
294
+ // ---------------------------------------------------------------------------
295
+ // Helpers
296
+ // ---------------------------------------------------------------------------
297
+
298
+ const isOverrideOptions = options => {
299
+ return options && 'operators' in options;
300
+ };
301
+ const arraySortComparator = (a, b) => {
302
+ const arrA = Array.isArray(a) ? a : [];
303
+ const arrB = Array.isArray(b) ? b : [];
304
+ if (arrA.length !== arrB.length) {
305
+ return arrA.length - arrB.length;
306
+ }
307
+ const strA = arrA.map(String).sort().join(',');
308
+ const strB = arrB.map(String).sort().join(',');
309
+ return strA.localeCompare(strB);
310
+ };
311
+ const arrayValueFormatter = value => {
312
+ if (Array.isArray(value)) {
313
+ return value.map(String).join(', ');
314
+ }
315
+ return value != null ? String(value) : '';
316
+ };
317
+
318
+ // ---------------------------------------------------------------------------
319
+ // Operator type strings that support case-insensitive wrapping
320
+ // ---------------------------------------------------------------------------
321
+
322
+ const CASE_INSENSITIVE_TYPES = ['string', 'singleSelect', 'tags'];
323
+
324
+ // ---------------------------------------------------------------------------
325
+ // createColumn()
326
+ // ---------------------------------------------------------------------------
327
+
328
+ /**
329
+ * Creates a column configuration with the appropriate MUI type and filter operators
330
+ * for the given column type. Spread the result into your column definition.
331
+ *
332
+ * @example
333
+ * ```tsx
334
+ * const columns = [
335
+ * { ...createColumn('string'), field: 'name', headerName: 'Name' },
336
+ * { ...createColumn('number'), field: 'price', headerName: 'Price' },
337
+ * { ...createColumn('singleSelect', { compact: true }), field: 'status', valueOptions: ['active', 'inactive'] },
338
+ * { ...createColumn('multiSelect'), field: 'tags', valueOptions: ['a', 'b', 'c'] },
339
+ * { ...createColumn('tags'), field: 'labels', headerName: 'Labels' },
340
+ * ];
341
+ * ```
342
+ */
343
+ function createColumn(type, options) {
344
+ var _builtInOpts$compact, _builtInOpts$caseSens;
345
+ // Override mode: consumer provides full operator list
346
+ if (options && isOverrideOptions(options)) {
347
+ const result = {
348
+ type: getMuiType(type),
349
+ filterOperators: options.operators
350
+ };
351
+ if (type === 'multiSelect' || type === 'tags') {
352
+ result.sortComparator = arraySortComparator;
353
+ result.valueFormatter = arrayValueFormatter;
354
+ }
355
+ return result;
356
+ }
357
+
358
+ // Built-in mode
359
+ const builtInOpts = options !== null && options !== void 0 ? options : {};
360
+ const compact = (_builtInOpts$compact = builtInOpts.compact) !== null && _builtInOpts$compact !== void 0 ? _builtInOpts$compact : false;
361
+ const caseSensitive = (_builtInOpts$caseSens = builtInOpts.caseSensitive) !== null && _builtInOpts$caseSens !== void 0 ? _builtInOpts$caseSens : false;
362
+ let operators = getOperatorsForType(type, compact);
363
+
364
+ // Apply exclude
365
+ if (builtInOpts.exclude && builtInOpts.exclude.length > 0) {
366
+ const excludeSet = new Set(builtInOpts.exclude);
367
+ operators = operators.filter(op => !excludeSet.has(op.value));
368
+ }
369
+
370
+ // Apply case-insensitive wrapping
371
+ if (!caseSensitive && CASE_INSENSITIVE_TYPES.includes(type)) {
372
+ operators = makeCaseInsensitive(operators);
373
+ }
374
+
375
+ // Apply extend
376
+ if (builtInOpts.extend && builtInOpts.extend.length > 0) {
377
+ operators = [...operators, ...builtInOpts.extend];
378
+ }
379
+ const result = {
380
+ type: getMuiType(type),
381
+ filterOperators: operators
382
+ };
383
+ if (type === 'multiSelect' || type === 'tags') {
384
+ result.sortComparator = arraySortComparator;
385
+ result.valueFormatter = arrayValueFormatter;
386
+ }
387
+ return result;
388
+ }
389
+ function getMuiType(type) {
390
+ switch (type) {
391
+ case 'multiSelect':
392
+ case 'singleSelect':
393
+ return 'singleSelect';
394
+ case 'tags':
395
+ return 'string';
396
+ default:
397
+ return type;
398
+ }
399
+ }
400
+ function getOperatorsForType(type, compact) {
401
+ switch (type) {
402
+ case 'string':
403
+ return compact ? getStringOperatorsCompact() : getStringOperatorsFull();
404
+ case 'number':
405
+ return compact ? getNumberOperatorsCompact() : getNumberOperatorsFull();
406
+ case 'date':
407
+ return compact ? getDateOperatorsCompact(false) : getDateOperatorsFull(false);
408
+ case 'dateTime':
409
+ return compact ? getDateOperatorsCompact(true) : getDateOperatorsFull(true);
410
+ case 'singleSelect':
411
+ return compact ? getSingleSelectOperatorsCompact() : getSingleSelectOperatorsFull();
412
+ case 'multiSelect':
413
+ return compact ? getMultiSelectOperatorsCompact() : getMultiSelectOperatorsFull();
414
+ case 'tags':
415
+ return compact ? getTagsOperatorsCompact() : getTagsOperatorsFull();
416
+ }
417
+ }
418
+
419
+ /**
420
+ * A React Router v5 / connected-react-router adapter for `StatefulDataGrid.useRouter`.
421
+ *
422
+ * Defers `history.replace` via `queueMicrotask` to avoid the
423
+ * "Cannot update during an existing state transition" warning
424
+ * that connected-react-router triggers when the grid synchronises
425
+ * URL state during render.
426
+ *
427
+ * Also coalesces multiple `historyReplace` calls within the same microtask
428
+ * by computing per-call deltas against the captured `search` snapshot and
429
+ * merging them into the live URL. This prevents handler stomping when the
430
+ * grid fires several model-change handlers in the same render (e.g. a
431
+ * pivot change re-emits a synthetic column-visibility change alongside a
432
+ * filter change): without coalescing, each handler would compute a full
433
+ * new search from the same captured snapshot, and the second
434
+ * `history.replace` would clobber the first — dropping any keys (such as
435
+ * the filter just typed) that the second handler didn't know about.
436
+ *
437
+ * @example
438
+ * ```tsx
439
+ * import { createReactRouterV5Adapter } from '@redsift/table-pro';
440
+ *
441
+ * const useRouter = createReactRouterV5Adapter(history);
442
+ *
443
+ * <StatefulDataGrid useRouter={useRouter} … />
444
+ * ```
445
+ */
446
+
447
+ /** Minimal subset of React Router v5 `history` used by the adapter. */
448
+
449
+ /**
450
+ * Create a `useRouter` hook compatible with `StatefulDataGrid` from a
451
+ * React Router v5 `history` object.
452
+ */
453
+ const createReactRouterV5Adapter = history => {
454
+ // Module-instance state shared across all `historyReplace` calls scheduled
455
+ // within the same microtask. Each `useRouter()` invocation captures its own
456
+ // `search` snapshot for delta computation; `pending` accumulates the deltas
457
+ // and is flushed once per microtask onto the live URL.
458
+ let pending = null;
459
+ let flushScheduled = false;
460
+ const scheduleFlush = () => {
461
+ if (flushScheduled) return;
462
+ flushScheduled = true;
463
+ queueMicrotask(() => {
464
+ flushScheduled = false;
465
+ const flush = pending;
466
+ pending = null;
467
+ if (flush === null) return;
468
+ const serialized = flush.toString();
469
+ const nextSearch = serialized ? `?${serialized}` : '';
470
+ if (nextSearch === history.location.search) return;
471
+ history.replace({
472
+ pathname: history.location.pathname,
473
+ search: nextSearch
474
+ });
475
+ });
476
+ };
477
+ return () => {
478
+ // Capture the search snapshot AT THE TIME useRouter() is called. The
479
+ // grid hands this string to its change handlers, which build a complete
480
+ // new search string from it. Diffing the handler's `newSearch` against
481
+ // this captured snapshot tells us which keys the handler intentionally
482
+ // changed (vs. which are stale passthrough from sibling handlers' models).
483
+ const captured = history.location.search.replace(/^\?/, '');
484
+ const capturedParams = new URLSearchParams(captured);
485
+ const capturedKeys = new Set(capturedParams.keys());
486
+ return {
487
+ pathname: history.location.pathname,
488
+ search: captured,
489
+ historyReplace: newSearch => {
490
+ const newParams = new URLSearchParams(newSearch);
491
+ const newKeys = new Set(newParams.keys());
492
+
493
+ // Lazy-init `pending` from the LIVE URL — not the captured snapshot —
494
+ // so deltas from earlier microtask-scheduled calls (which may have
495
+ // already been flushed, or were scheduled by a previous render and
496
+ // are now reflected in the live URL) are preserved.
497
+ if (pending === null) {
498
+ pending = new URLSearchParams(history.location.search.replace(/^\?/, ''));
499
+ }
500
+
501
+ // Apply only keys this handler actually changed. A handler builds a
502
+ // complete new search from a captured snapshot, but only some keys
503
+ // reflect its intent — the rest are passthrough of the snapshot.
504
+ // When two handlers fire in the same render and one was triggered
505
+ // by a model the other doesn't care about, passthrough keys would
506
+ // otherwise stomp pending state from earlier handlers.
507
+ for (const key of newKeys) {
508
+ const newValues = newParams.getAll(key);
509
+ const capturedValues = capturedParams.getAll(key);
510
+ const unchanged = newValues.length === capturedValues.length && newValues.every((v, i) => v === capturedValues[i]);
511
+ if (unchanged) continue;
512
+ pending.delete(key);
513
+ for (const value of newValues) {
514
+ pending.append(key, value);
515
+ }
516
+ }
517
+ // Keys that were in the captured snapshot but absent from newSearch
518
+ // were intentionally removed by this handler.
519
+ for (const key of capturedKeys) {
520
+ if (!newKeys.has(key)) pending.delete(key);
521
+ }
522
+ scheduleFlush();
523
+ }
524
+ };
525
+ };
526
+ };
527
+
528
+ const EMPTY_FILTER_MODEL = {
529
+ items: []
530
+ };
531
+ const sameItems = (a, b) => JSON.stringify(a.items) === JSON.stringify(b.items);
532
+ const filterOutField = (model, field) => model.items.filter(item => item.field !== field);
533
+ const nextItemId = (() => {
534
+ let id = 1;
535
+ return () => id++;
536
+ })();
537
+
538
+ /**
539
+ * Bridge a DataGrid `filterModel` and the DataCards that drive (and reflect) it.
540
+ *
541
+ * Without this hook, every drilldowned datagrid page hand-rolls the same plumbing:
542
+ * derive selections from `filterModel`, write them back via raw `setFilterModel`
543
+ * mutations, debounce panel changes, and track which fields the panel "owns" so
544
+ * cards don't fight the panel for control. SOFA-6 shipped this logic ad hoc and
545
+ * got it wrong (no debounce, prop-drilled state, no panel/card reconciliation).
546
+ *
547
+ * @example
548
+ * const filter = useLinkedFilterModel();
549
+ *
550
+ * <DataCard.Listbox
551
+ * values={filter.getSelected('Category')}
552
+ * onChange={(values) => filter.setMultiSelect('Category', values)}
553
+ * isDisabled={filter.isFieldExternallyControlled('Category')}
554
+ * >
555
+ * {…}
556
+ * </DataCard.Listbox>
557
+ *
558
+ * <DataGrid filterModel={filter.filterModel} onFilterModelChange={filter.onFilterModelChange} />
559
+ */
560
+ function useLinkedFilterModel() {
561
+ let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
562
+ const {
563
+ initialFilterModel = EMPTY_FILTER_MODEL,
564
+ debounceMs = 300,
565
+ onChange
566
+ } = options;
567
+ const [filterModel, setFilterModel] = useState(initialFilterModel);
568
+
569
+ // Fields whose current filter was set by setMultiSelect / setBoolean (not the panel).
570
+ const cardOwnedFields = useRef(new Set());
571
+ // Marks the next onFilterModelChange as originating from a card setter so we skip debouncing.
572
+ const skipNextDebounce = useRef(false);
573
+ const debounceTimer = useRef(null);
574
+ const onChangeRef = useRef(onChange);
575
+ useEffect(() => {
576
+ onChangeRef.current = onChange;
577
+ }, [onChange]);
578
+ useEffect(() => {
579
+ return () => {
580
+ if (debounceTimer.current) clearTimeout(debounceTimer.current);
581
+ };
582
+ }, []);
583
+ const commit = useCallback(next => {
584
+ setFilterModel(prev => {
585
+ var _onChangeRef$current;
586
+ if (sameItems(prev, next)) return prev;
587
+ (_onChangeRef$current = onChangeRef.current) === null || _onChangeRef$current === void 0 ? void 0 : _onChangeRef$current.call(onChangeRef, next);
588
+ return next;
589
+ });
590
+ }, []);
591
+ const onFilterModelChange = useCallback(model => {
592
+ if (skipNextDebounce.current) {
593
+ skipNextDebounce.current = false;
594
+ // If the incoming model matches the current state, this is a DataGrid echo
595
+ // of a card setter — commit (no-op) and preserve card ownership.
596
+ if (sameItems(filterModel, model)) {
597
+ commit(model);
598
+ return;
599
+ }
600
+ // Otherwise a real panel change arrived — fall through to ownership logic.
601
+ }
602
+
603
+ // Panel change — drop card ownership for fields the panel now controls / has cleared,
604
+ // then debounce the commit.
605
+ const panelFields = new Set(model.items.map(item => item.field));
606
+ for (const field of Array.from(cardOwnedFields.current)) {
607
+ if (!panelFields.has(field)) cardOwnedFields.current.delete(field);
608
+ }
609
+ // If the panel touched a field the card previously owned, transfer ownership to the panel.
610
+ for (const field of panelFields) {
611
+ cardOwnedFields.current.delete(field);
612
+ }
613
+ if (debounceMs <= 0) {
614
+ commit(model);
615
+ return;
616
+ }
617
+ if (debounceTimer.current) clearTimeout(debounceTimer.current);
618
+ debounceTimer.current = setTimeout(() => {
619
+ debounceTimer.current = null;
620
+ commit(model);
621
+ }, debounceMs);
622
+ }, [commit, debounceMs, filterModel]);
623
+ const getSelected = useCallback(field => {
624
+ const item = filterModel.items.find(i => i.field === field);
625
+ if (!item || item.value === undefined || item.value === null) return [];
626
+ if (item.operator === 'isAnyOf' || item.operator === 'hasAnyOf') {
627
+ return Array.isArray(item.value) ? item.value : [String(item.value)];
628
+ }
629
+ return [];
630
+ }, [filterModel]);
631
+ const getValue = useCallback(field => {
632
+ var _filterModel$items$fi;
633
+ return (_filterModel$items$fi = filterModel.items.find(i => i.field === field)) === null || _filterModel$items$fi === void 0 ? void 0 : _filterModel$items$fi.value;
634
+ }, [filterModel]);
635
+ const setMultiSelect = useCallback(function (field, values) {
636
+ let operator = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'isAnyOf';
637
+ skipNextDebounce.current = true;
638
+ setFilterModel(prev => {
639
+ var _onChangeRef$current2;
640
+ const items = filterOutField(prev, field);
641
+ if (values.length > 0) {
642
+ items.push({
643
+ field,
644
+ id: nextItemId(),
645
+ operator,
646
+ value: values
647
+ });
648
+ cardOwnedFields.current.add(field);
649
+ } else {
650
+ cardOwnedFields.current.delete(field);
651
+ }
652
+ const next = _objectSpread2(_objectSpread2({}, prev), {}, {
653
+ items
654
+ });
655
+ if (sameItems(prev, next)) return prev;
656
+ (_onChangeRef$current2 = onChangeRef.current) === null || _onChangeRef$current2 === void 0 ? void 0 : _onChangeRef$current2.call(onChangeRef, next);
657
+ return next;
658
+ });
659
+ }, []);
660
+ const setBoolean = useCallback((field, value) => {
661
+ skipNextDebounce.current = true;
662
+ setFilterModel(prev => {
663
+ var _onChangeRef$current3;
664
+ const items = filterOutField(prev, field);
665
+ if (value !== null) {
666
+ items.push({
667
+ field,
668
+ id: nextItemId(),
669
+ operator: 'is',
670
+ value: String(value)
671
+ });
672
+ cardOwnedFields.current.add(field);
673
+ } else {
674
+ cardOwnedFields.current.delete(field);
675
+ }
676
+ const next = _objectSpread2(_objectSpread2({}, prev), {}, {
677
+ items
678
+ });
679
+ if (sameItems(prev, next)) return prev;
680
+ (_onChangeRef$current3 = onChangeRef.current) === null || _onChangeRef$current3 === void 0 ? void 0 : _onChangeRef$current3.call(onChangeRef, next);
681
+ return next;
682
+ });
683
+ }, []);
684
+ const clearField = useCallback(field => {
685
+ skipNextDebounce.current = true;
686
+ setFilterModel(prev => {
687
+ var _onChangeRef$current4;
688
+ const items = filterOutField(prev, field);
689
+ cardOwnedFields.current.delete(field);
690
+ const next = _objectSpread2(_objectSpread2({}, prev), {}, {
691
+ items
692
+ });
693
+ if (sameItems(prev, next)) return prev;
694
+ (_onChangeRef$current4 = onChangeRef.current) === null || _onChangeRef$current4 === void 0 ? void 0 : _onChangeRef$current4.call(onChangeRef, next);
695
+ return next;
696
+ });
697
+ }, []);
698
+ const reset = useCallback(() => {
699
+ skipNextDebounce.current = true;
700
+ cardOwnedFields.current.clear();
701
+ commit(initialFilterModel);
702
+ }, [commit, initialFilterModel]);
703
+ const isFieldExternallyControlled = useCallback(field => {
704
+ const hasFilter = filterModel.items.some(item => item.field === field);
705
+ return hasFilter && !cardOwnedFields.current.has(field);
706
+ }, [filterModel]);
707
+ return useMemo(() => ({
708
+ filterModel,
709
+ onFilterModelChange,
710
+ getSelected,
711
+ getValue,
712
+ setMultiSelect,
713
+ setBoolean,
714
+ clearField,
715
+ reset,
716
+ isFieldExternallyControlled
717
+ }), [filterModel, onFilterModelChange, getSelected, getValue, setMultiSelect, setBoolean, clearField, reset, isFieldExternallyControlled]);
718
+ }
719
+
720
+ export { DATE_IS_BETWEEN, DETAIL_PANEL_TOGGLE_COL_DEF, IS_ANY_OF_I, IS_ANY_OF_I_WITH_SELECT, createColumn, createReactRouterV5Adapter, customColumnTypes, getGridDateOperatorsExtended, getRsMultipleSelectColumnType, getRsMultipleSelectWithShortOperatorListColumnType, getRsNumberColumnType, getRsSingleSelectColumnType, getRsSingleSelectWithShortOperatorListColumnType, getRsStringColumnType, makeCaseInsensitive, useLinkedFilterModel, wrapCaseInsensitive };
721
+ //# sourceMappingURL=index.js.map