@dashforge/tw 0.7.0-beta → 0.8.1-beta
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/CHANGELOG.md +255 -0
- package/dist/index.esm.js +1057 -74
- package/dist/src/components/DataGrid/DataGrid.d.ts.map +1 -1
- package/dist/src/components/DataGrid/dataGrid.types.d.ts +43 -0
- package/dist/src/components/DataGrid/dataGrid.types.d.ts.map +1 -1
- package/dist/src/components/DataGrid/dataGrid.variants.d.ts +6 -0
- package/dist/src/components/DataGrid/dataGrid.variants.d.ts.map +1 -1
- package/dist/src/components/DataGrid/filters/ColumnFilters.d.ts +23 -0
- package/dist/src/components/DataGrid/filters/ColumnFilters.d.ts.map +1 -0
- package/dist/src/components/DataGrid/reorder/useColumnReorder.d.ts +50 -0
- package/dist/src/components/DataGrid/reorder/useColumnReorder.d.ts.map +1 -0
- package/dist/src/components/DataGrid/resize/useColumnResize.d.ts +32 -0
- package/dist/src/components/DataGrid/resize/useColumnResize.d.ts.map +1 -0
- package/dist/src/components/DataGrid/visibility/ColumnVisibilityMenu.d.ts +40 -0
- package/dist/src/components/DataGrid/visibility/ColumnVisibilityMenu.d.ts.map +1 -0
- package/dist/src/components/Table/Table.d.ts.map +1 -1
- package/dist/src/components/Table/table.types.d.ts +67 -5
- package/dist/src/components/Table/table.types.d.ts.map +1 -1
- package/dist/src/components/_shared/data/useTableFilter.d.ts +30 -7
- package/dist/src/components/_shared/data/useTableFilter.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/components/DataGrid/DataGrid.test.tsx +418 -1
- package/src/components/DataGrid/DataGrid.tsx +416 -53
- package/src/components/DataGrid/dataGrid.types.ts +50 -1
- package/src/components/DataGrid/dataGrid.variants.ts +9 -1
- package/src/components/DataGrid/filters/ColumnFilters.tsx +432 -0
- package/src/components/DataGrid/reorder/useColumnReorder.test.ts +38 -0
- package/src/components/DataGrid/reorder/useColumnReorder.ts +144 -0
- package/src/components/DataGrid/resize/useColumnResize.ts +106 -0
- package/src/components/DataGrid/visibility/ColumnVisibilityMenu.tsx +169 -0
- package/src/components/Table/Table.tsx +11 -0
- package/src/components/Table/table.types.ts +72 -5
- package/src/components/_shared/data/useTableFilter.test.ts +166 -0
- package/src/components/_shared/data/useTableFilter.ts +97 -16
- package/src/index.ts +2 -0
package/dist/index.esm.js
CHANGED
|
@@ -8259,28 +8259,99 @@ const DEFAULT_PAGE_SIZE_OPTIONS = [
|
|
|
8259
8259
|
}
|
|
8260
8260
|
|
|
8261
8261
|
/**
|
|
8262
|
-
*
|
|
8263
|
-
*
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8262
|
+
* Coerce a value to a number for range comparison. Returns
|
|
8263
|
+
* `null` if not coercible.
|
|
8264
|
+
*/ function toNumber(value) {
|
|
8265
|
+
if (value == null) return null;
|
|
8266
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value : null;
|
|
8267
|
+
if (typeof value === 'bigint') return Number(value);
|
|
8268
|
+
if (typeof value === 'string') {
|
|
8269
|
+
const n = Number(value);
|
|
8270
|
+
return Number.isFinite(n) ? n : null;
|
|
8271
|
+
}
|
|
8272
|
+
return null;
|
|
8273
|
+
}
|
|
8274
|
+
/**
|
|
8275
|
+
* Coerce a value to a Date's epoch ms for date-range comparison.
|
|
8276
|
+
* Accepts `Date` instances + ISO 8601 strings. Returns `null` if
|
|
8277
|
+
* not coercible.
|
|
8278
|
+
*/ function toDateMs(value) {
|
|
8279
|
+
if (value == null) return null;
|
|
8280
|
+
if (value instanceof Date) return value.getTime();
|
|
8281
|
+
if (typeof value === 'string') {
|
|
8282
|
+
const d = new Date(value);
|
|
8283
|
+
const t = d.getTime();
|
|
8284
|
+
return Number.isNaN(t) ? null : t;
|
|
8285
|
+
}
|
|
8286
|
+
return null;
|
|
8287
|
+
}
|
|
8288
|
+
/**
|
|
8289
|
+
* Test one row × one filter. Returns `true` if the row passes the
|
|
8290
|
+
* filter. The operator decides the predicate:
|
|
8291
|
+
*
|
|
8292
|
+
* - `contains` → substring match on stringified value (text filter)
|
|
8293
|
+
* - `equals` → strict equality (boolean filter mostly)
|
|
8294
|
+
* - `between` → `value: [min, max]` tuple. Each end can be `null`
|
|
8295
|
+
* for an open range. Used by number range + date range filters.
|
|
8296
|
+
*
|
|
8297
|
+
* Empty filter is a no-op (passes every row).
|
|
8298
|
+
*/ function passesFilter(rowValue, op, filterValue) {
|
|
8299
|
+
switch(op){
|
|
8300
|
+
case 'contains':
|
|
8301
|
+
{
|
|
8302
|
+
const needle = typeof filterValue === 'string' ? filterValue.trim().toLowerCase() : '';
|
|
8303
|
+
if (!needle) return true;
|
|
8304
|
+
return stringifyForSearch(rowValue).toLowerCase().includes(needle);
|
|
8305
|
+
}
|
|
8306
|
+
case 'equals':
|
|
8307
|
+
{
|
|
8308
|
+
if (filterValue == null) return true;
|
|
8309
|
+
return rowValue === filterValue;
|
|
8310
|
+
}
|
|
8311
|
+
case 'between':
|
|
8312
|
+
{
|
|
8313
|
+
if (!Array.isArray(filterValue) || filterValue.length !== 2) return true;
|
|
8314
|
+
const [min, max] = filterValue;
|
|
8315
|
+
if (min == null && max == null) return true;
|
|
8316
|
+
// Try number range first.
|
|
8317
|
+
const rowNum = toNumber(rowValue);
|
|
8318
|
+
const minNum = min == null ? null : toNumber(min);
|
|
8319
|
+
const maxNum = max == null ? null : toNumber(max);
|
|
8320
|
+
if (rowNum != null && (minNum != null || maxNum != null)) {
|
|
8321
|
+
if (minNum != null && rowNum < minNum) return false;
|
|
8322
|
+
if (maxNum != null && rowNum > maxNum) return false;
|
|
8323
|
+
return true;
|
|
8324
|
+
}
|
|
8325
|
+
// Fall through to date range.
|
|
8326
|
+
const rowMs = toDateMs(rowValue);
|
|
8327
|
+
const minMs = min == null ? null : toDateMs(min);
|
|
8328
|
+
const maxMs = max == null ? null : toDateMs(max);
|
|
8329
|
+
if (rowMs != null && (minMs != null || maxMs != null)) {
|
|
8330
|
+
if (minMs != null && rowMs < minMs) return false;
|
|
8331
|
+
if (maxMs != null && rowMs > maxMs) return false;
|
|
8332
|
+
return true;
|
|
8333
|
+
}
|
|
8334
|
+
// If neither coercion worked, let the row pass (defensive).
|
|
8335
|
+
return true;
|
|
8336
|
+
}
|
|
8337
|
+
default:
|
|
8338
|
+
return true;
|
|
8339
|
+
}
|
|
8340
|
+
}
|
|
8341
|
+
/**
|
|
8342
|
+
* Filters `rows` by the per-column filter model. AND semantics
|
|
8343
|
+
* across columns — a row must pass EVERY active filter.
|
|
8344
|
+
*
|
|
8345
|
+
* Each filter item specifies an `op` (`'contains'` / `'equals'` /
|
|
8346
|
+
* `'between'`) and a `value` whose shape matches the operator
|
|
8347
|
+
* (string for contains; primitive for equals; `[min, max]` tuple
|
|
8348
|
+
* for between).
|
|
8270
8349
|
*/ function useTableFilter(rows, filterModel) {
|
|
8271
8350
|
return useMemo(()=>{
|
|
8272
8351
|
if (!filterModel || filterModel.length === 0) return rows;
|
|
8273
8352
|
return rows.filter((row)=>filterModel.every((filter)=>{
|
|
8274
8353
|
const value = getNestedValue(row, filter.field);
|
|
8275
|
-
|
|
8276
|
-
const needle = filter.value.trim().toLowerCase();
|
|
8277
|
-
if (!needle) return true; // empty filter == no-op
|
|
8278
|
-
switch(filter.op){
|
|
8279
|
-
case 'contains':
|
|
8280
|
-
return stringValue.includes(needle);
|
|
8281
|
-
default:
|
|
8282
|
-
return true;
|
|
8283
|
-
}
|
|
8354
|
+
return passesFilter(value, filter.op, filter.value);
|
|
8284
8355
|
}));
|
|
8285
8356
|
}, [
|
|
8286
8357
|
rows,
|
|
@@ -8592,10 +8663,21 @@ const DEFAULT_LABELS$1 = {
|
|
|
8592
8663
|
filterColumn: 'Filter',
|
|
8593
8664
|
filterApply: 'Apply',
|
|
8594
8665
|
filterClear: 'Clear',
|
|
8666
|
+
filterMin: 'Min',
|
|
8667
|
+
filterMax: 'Max',
|
|
8668
|
+
filterFrom: 'From',
|
|
8669
|
+
filterTo: 'To',
|
|
8670
|
+
filterAll: 'All',
|
|
8671
|
+
filterTrue: 'True',
|
|
8672
|
+
filterFalse: 'False',
|
|
8595
8673
|
density: 'Density',
|
|
8596
8674
|
densityCompact: 'Compact',
|
|
8597
8675
|
densityComfortable: 'Comfortable',
|
|
8598
|
-
densitySpacious: 'Spacious'
|
|
8676
|
+
densitySpacious: 'Spacious',
|
|
8677
|
+
columnsButton: 'Columns',
|
|
8678
|
+
columnsTitle: 'Toggle columns',
|
|
8679
|
+
columnsShowAll: 'Show all',
|
|
8680
|
+
columnsHideAll: 'Hide all'
|
|
8599
8681
|
};
|
|
8600
8682
|
const DEFAULT_GET_ROW_ID = (_row, index)=>String(index);
|
|
8601
8683
|
/**
|
|
@@ -9536,7 +9618,7 @@ function RowActionMenuItem(props) {
|
|
|
9536
9618
|
'border-t border-neutral-200',
|
|
9537
9619
|
'bg-neutral-50 shadow-md'
|
|
9538
9620
|
],
|
|
9539
|
-
// Sticky
|
|
9621
|
+
// Sticky LEFT column — uses `position: sticky; left: 0` + z-index
|
|
9540
9622
|
// so the column stays pinned during horizontal scroll. The
|
|
9541
9623
|
// `bg-neutral-50` ensures the column doesn't bleed when content
|
|
9542
9624
|
// scrolls underneath.
|
|
@@ -9544,6 +9626,12 @@ function RowActionMenuItem(props) {
|
|
|
9544
9626
|
// Sticky left × header intersection (top-left corner): higher
|
|
9545
9627
|
// z-index than both header (`z-10`) and sticky col (`z-[1]`).
|
|
9546
9628
|
stickyLeftHeaderCell: 'sticky left-0 z-20 bg-neutral-50',
|
|
9629
|
+
// Sticky RIGHT column — mirror of stickyLeftCell. The first
|
|
9630
|
+
// right-sticky column gets a `border-l` to separate it visually
|
|
9631
|
+
// from the scrollable area (the consumer can override via
|
|
9632
|
+
// slotProps if they don't want it).
|
|
9633
|
+
stickyRightCell: 'sticky right-0 z-[1] bg-neutral-50 border-l border-neutral-200',
|
|
9634
|
+
stickyRightHeaderCell: 'sticky right-0 z-20 bg-neutral-50 border-l border-neutral-200',
|
|
9547
9635
|
paginationFooter: 'flex justify-end mt-3'
|
|
9548
9636
|
},
|
|
9549
9637
|
variants: {
|
|
@@ -9748,6 +9836,661 @@ function RowActionMenuItem(props) {
|
|
|
9748
9836
|
};
|
|
9749
9837
|
}
|
|
9750
9838
|
|
|
9839
|
+
/**
|
|
9840
|
+
* Per-column filter UI for DataGrid.
|
|
9841
|
+
*
|
|
9842
|
+
* `<ColumnFilterTrigger>` renders the filter icon button in the
|
|
9843
|
+
* column header — when clicked, opens a Popover with the
|
|
9844
|
+
* appropriate filter UI for the column type:
|
|
9845
|
+
*
|
|
9846
|
+
* - `text` → `<TextFilter>` with single input (op: `contains`)
|
|
9847
|
+
* - `number` → `<NumberRangeFilter>` with min/max inputs (op: `between`)
|
|
9848
|
+
* - `boolean` → `<BooleanFilter>` with true/false/all radio (op: `equals`)
|
|
9849
|
+
* - `date` → `<DateRangeFilter>` with min/max date inputs (op: `between`)
|
|
9850
|
+
*
|
|
9851
|
+
* Each filter UI has Apply + Clear actions; Apply commits the
|
|
9852
|
+
* filter to the model, Clear removes any existing filter for that
|
|
9853
|
+
* column.
|
|
9854
|
+
*/ // ───── Helper: read/write filter model for a single column ─────
|
|
9855
|
+
function findFilter(model, field) {
|
|
9856
|
+
return model.find((f)=>f.field === field);
|
|
9857
|
+
}
|
|
9858
|
+
function setFilter(model, field, next) {
|
|
9859
|
+
const rest = model.filter((f)=>f.field !== field);
|
|
9860
|
+
if (next == null) return rest;
|
|
9861
|
+
return [
|
|
9862
|
+
...rest,
|
|
9863
|
+
next
|
|
9864
|
+
];
|
|
9865
|
+
}
|
|
9866
|
+
const DEFAULT_FILTER_LABELS = {
|
|
9867
|
+
filterColumn: 'Filter',
|
|
9868
|
+
filterApply: 'Apply',
|
|
9869
|
+
filterClear: 'Clear',
|
|
9870
|
+
filterMin: 'Min',
|
|
9871
|
+
filterMax: 'Max',
|
|
9872
|
+
filterFrom: 'From',
|
|
9873
|
+
filterTo: 'To',
|
|
9874
|
+
filterAll: 'All',
|
|
9875
|
+
filterTrue: 'True',
|
|
9876
|
+
filterFalse: 'False'
|
|
9877
|
+
};
|
|
9878
|
+
function ColumnFilterTrigger(props) {
|
|
9879
|
+
const { field, filterType, filterModel, onFilterChange, disabled, labels: labelsProp } = props;
|
|
9880
|
+
const labels = _extends({}, DEFAULT_FILTER_LABELS, labelsProp);
|
|
9881
|
+
const [open, setOpen] = useState(false);
|
|
9882
|
+
const active = findFilter(filterModel, field) != null;
|
|
9883
|
+
return jsx(Popover, {
|
|
9884
|
+
open: open,
|
|
9885
|
+
onOpenChange: setOpen,
|
|
9886
|
+
side: "bottom",
|
|
9887
|
+
align: "end",
|
|
9888
|
+
content: jsx(FilterPanel, {
|
|
9889
|
+
field: field,
|
|
9890
|
+
filterType: filterType,
|
|
9891
|
+
filterModel: filterModel,
|
|
9892
|
+
onFilterChange: (next)=>{
|
|
9893
|
+
onFilterChange(next);
|
|
9894
|
+
setOpen(false);
|
|
9895
|
+
},
|
|
9896
|
+
labels: labels
|
|
9897
|
+
}),
|
|
9898
|
+
children: jsx("button", {
|
|
9899
|
+
type: "button",
|
|
9900
|
+
"aria-label": labels.filterColumn,
|
|
9901
|
+
"aria-pressed": active,
|
|
9902
|
+
disabled: disabled,
|
|
9903
|
+
className: cn('inline-flex items-center justify-center h-6 w-6 rounded', 'text-neutral-500 hover:text-neutral-900', active && 'text-primary-700', 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500', 'transition-colors motion-reduce:transition-none', 'disabled:opacity-50 disabled:cursor-not-allowed'),
|
|
9904
|
+
children: jsx(FilterIcon, {})
|
|
9905
|
+
})
|
|
9906
|
+
});
|
|
9907
|
+
}
|
|
9908
|
+
// ───── Panel — dispatches to the right filter UI ─────
|
|
9909
|
+
function FilterPanel(props) {
|
|
9910
|
+
const { field, filterType, filterModel, onFilterChange, labels } = props;
|
|
9911
|
+
const current = findFilter(filterModel, field);
|
|
9912
|
+
const commit = (next)=>{
|
|
9913
|
+
onFilterChange(setFilter(filterModel, field, next));
|
|
9914
|
+
};
|
|
9915
|
+
switch(filterType){
|
|
9916
|
+
case 'number':
|
|
9917
|
+
return jsx(NumberRangeFilter, {
|
|
9918
|
+
field: field,
|
|
9919
|
+
current: current,
|
|
9920
|
+
onCommit: commit,
|
|
9921
|
+
labels: labels
|
|
9922
|
+
});
|
|
9923
|
+
case 'boolean':
|
|
9924
|
+
return jsx(BooleanFilter, {
|
|
9925
|
+
field: field,
|
|
9926
|
+
current: current,
|
|
9927
|
+
onCommit: commit,
|
|
9928
|
+
labels: labels
|
|
9929
|
+
});
|
|
9930
|
+
case 'date':
|
|
9931
|
+
return jsx(DateRangeFilter, {
|
|
9932
|
+
field: field,
|
|
9933
|
+
current: current,
|
|
9934
|
+
onCommit: commit,
|
|
9935
|
+
labels: labels
|
|
9936
|
+
});
|
|
9937
|
+
case 'text':
|
|
9938
|
+
default:
|
|
9939
|
+
return jsx(TextFilter, {
|
|
9940
|
+
field: field,
|
|
9941
|
+
current: current,
|
|
9942
|
+
onCommit: commit,
|
|
9943
|
+
labels: labels
|
|
9944
|
+
});
|
|
9945
|
+
}
|
|
9946
|
+
}
|
|
9947
|
+
// ───── Text filter ─────
|
|
9948
|
+
function TextFilter(props) {
|
|
9949
|
+
const { field, current, onCommit, labels } = props;
|
|
9950
|
+
const [value, setValue] = useState(typeof (current == null ? void 0 : current.value) === 'string' ? current.value : '');
|
|
9951
|
+
return jsxs("div", {
|
|
9952
|
+
className: "flex flex-col gap-2 min-w-[200px]",
|
|
9953
|
+
children: [
|
|
9954
|
+
jsx("input", {
|
|
9955
|
+
type: "text",
|
|
9956
|
+
value: value,
|
|
9957
|
+
onChange: (e)=>setValue(e.target.value),
|
|
9958
|
+
placeholder: "\u2026",
|
|
9959
|
+
autoFocus: true,
|
|
9960
|
+
className: "rounded-md border border-neutral-300 bg-neutral-50 px-2 py-1.5 text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
|
|
9961
|
+
}),
|
|
9962
|
+
jsx(FilterActions, {
|
|
9963
|
+
onApply: ()=>onCommit(value.trim() ? {
|
|
9964
|
+
field,
|
|
9965
|
+
op: 'contains',
|
|
9966
|
+
value
|
|
9967
|
+
} : null),
|
|
9968
|
+
onClear: ()=>{
|
|
9969
|
+
setValue('');
|
|
9970
|
+
onCommit(null);
|
|
9971
|
+
},
|
|
9972
|
+
labels: labels
|
|
9973
|
+
})
|
|
9974
|
+
]
|
|
9975
|
+
});
|
|
9976
|
+
}
|
|
9977
|
+
// ───── Number range filter ─────
|
|
9978
|
+
function NumberRangeFilter(props) {
|
|
9979
|
+
const { field, current, onCommit, labels } = props;
|
|
9980
|
+
const currentValue = current == null ? void 0 : current.value;
|
|
9981
|
+
const currentTuple = Array.isArray(currentValue) ? currentValue : [
|
|
9982
|
+
null,
|
|
9983
|
+
null
|
|
9984
|
+
];
|
|
9985
|
+
const [min, setMin] = useState(currentTuple[0] != null ? String(currentTuple[0]) : '');
|
|
9986
|
+
const [max, setMax] = useState(currentTuple[1] != null ? String(currentTuple[1]) : '');
|
|
9987
|
+
const apply = ()=>{
|
|
9988
|
+
const minN = min.trim() ? Number(min) : null;
|
|
9989
|
+
const maxN = max.trim() ? Number(max) : null;
|
|
9990
|
+
if ((minN == null || Number.isFinite(minN)) && (maxN == null || Number.isFinite(maxN))) {
|
|
9991
|
+
if (minN == null && maxN == null) {
|
|
9992
|
+
onCommit(null);
|
|
9993
|
+
} else {
|
|
9994
|
+
onCommit({
|
|
9995
|
+
field,
|
|
9996
|
+
op: 'between',
|
|
9997
|
+
value: [
|
|
9998
|
+
minN,
|
|
9999
|
+
maxN
|
|
10000
|
+
]
|
|
10001
|
+
});
|
|
10002
|
+
}
|
|
10003
|
+
}
|
|
10004
|
+
};
|
|
10005
|
+
return jsxs("div", {
|
|
10006
|
+
className: "flex flex-col gap-2 min-w-[220px]",
|
|
10007
|
+
children: [
|
|
10008
|
+
jsxs("div", {
|
|
10009
|
+
className: "grid grid-cols-2 gap-2",
|
|
10010
|
+
children: [
|
|
10011
|
+
jsxs("label", {
|
|
10012
|
+
className: "flex flex-col gap-1 text-xs text-neutral-700",
|
|
10013
|
+
children: [
|
|
10014
|
+
jsx("span", {
|
|
10015
|
+
children: labels.filterMin
|
|
10016
|
+
}),
|
|
10017
|
+
jsx("input", {
|
|
10018
|
+
type: "number",
|
|
10019
|
+
value: min,
|
|
10020
|
+
onChange: (e)=>setMin(e.target.value),
|
|
10021
|
+
autoFocus: true,
|
|
10022
|
+
className: "rounded-md border border-neutral-300 bg-neutral-50 px-2 py-1.5 text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
|
|
10023
|
+
})
|
|
10024
|
+
]
|
|
10025
|
+
}),
|
|
10026
|
+
jsxs("label", {
|
|
10027
|
+
className: "flex flex-col gap-1 text-xs text-neutral-700",
|
|
10028
|
+
children: [
|
|
10029
|
+
jsx("span", {
|
|
10030
|
+
children: labels.filterMax
|
|
10031
|
+
}),
|
|
10032
|
+
jsx("input", {
|
|
10033
|
+
type: "number",
|
|
10034
|
+
value: max,
|
|
10035
|
+
onChange: (e)=>setMax(e.target.value),
|
|
10036
|
+
className: "rounded-md border border-neutral-300 bg-neutral-50 px-2 py-1.5 text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
|
|
10037
|
+
})
|
|
10038
|
+
]
|
|
10039
|
+
})
|
|
10040
|
+
]
|
|
10041
|
+
}),
|
|
10042
|
+
jsx(FilterActions, {
|
|
10043
|
+
onApply: apply,
|
|
10044
|
+
onClear: ()=>{
|
|
10045
|
+
setMin('');
|
|
10046
|
+
setMax('');
|
|
10047
|
+
onCommit(null);
|
|
10048
|
+
},
|
|
10049
|
+
labels: labels
|
|
10050
|
+
})
|
|
10051
|
+
]
|
|
10052
|
+
});
|
|
10053
|
+
}
|
|
10054
|
+
// ───── Boolean filter ─────
|
|
10055
|
+
function BooleanFilter(props) {
|
|
10056
|
+
const { field, current, onCommit, labels } = props;
|
|
10057
|
+
const initial = (current == null ? void 0 : current.value) === true ? 'true' : (current == null ? void 0 : current.value) === false ? 'false' : 'all';
|
|
10058
|
+
const [value, setValue] = useState(initial);
|
|
10059
|
+
const apply = ()=>{
|
|
10060
|
+
if (value === 'all') onCommit(null);
|
|
10061
|
+
else onCommit({
|
|
10062
|
+
field,
|
|
10063
|
+
op: 'equals',
|
|
10064
|
+
value: value === 'true'
|
|
10065
|
+
});
|
|
10066
|
+
};
|
|
10067
|
+
return jsxs("div", {
|
|
10068
|
+
className: "flex flex-col gap-2 min-w-[160px]",
|
|
10069
|
+
children: [
|
|
10070
|
+
jsx("fieldset", {
|
|
10071
|
+
className: "flex flex-col gap-1",
|
|
10072
|
+
children: [
|
|
10073
|
+
'all',
|
|
10074
|
+
'true',
|
|
10075
|
+
'false'
|
|
10076
|
+
].map((opt)=>jsxs("label", {
|
|
10077
|
+
className: "flex items-center gap-2 text-sm cursor-pointer",
|
|
10078
|
+
children: [
|
|
10079
|
+
jsx("input", {
|
|
10080
|
+
type: "radio",
|
|
10081
|
+
name: `filter-${field}`,
|
|
10082
|
+
value: opt,
|
|
10083
|
+
checked: value === opt,
|
|
10084
|
+
onChange: ()=>setValue(opt),
|
|
10085
|
+
className: "cursor-pointer"
|
|
10086
|
+
}),
|
|
10087
|
+
jsx("span", {
|
|
10088
|
+
children: opt === 'all' ? labels.filterAll : opt === 'true' ? labels.filterTrue : labels.filterFalse
|
|
10089
|
+
})
|
|
10090
|
+
]
|
|
10091
|
+
}, opt))
|
|
10092
|
+
}),
|
|
10093
|
+
jsx(FilterActions, {
|
|
10094
|
+
onApply: apply,
|
|
10095
|
+
onClear: ()=>{
|
|
10096
|
+
setValue('all');
|
|
10097
|
+
onCommit(null);
|
|
10098
|
+
},
|
|
10099
|
+
labels: labels
|
|
10100
|
+
})
|
|
10101
|
+
]
|
|
10102
|
+
});
|
|
10103
|
+
}
|
|
10104
|
+
// ───── Date range filter ─────
|
|
10105
|
+
function DateRangeFilter(props) {
|
|
10106
|
+
const { field, current, onCommit, labels } = props;
|
|
10107
|
+
const currentValue = current == null ? void 0 : current.value;
|
|
10108
|
+
const currentTuple = Array.isArray(currentValue) ? currentValue : [
|
|
10109
|
+
null,
|
|
10110
|
+
null
|
|
10111
|
+
];
|
|
10112
|
+
const [from, setFrom] = useState(typeof currentTuple[0] === 'string' ? currentTuple[0] : '');
|
|
10113
|
+
const [to, setTo] = useState(typeof currentTuple[1] === 'string' ? currentTuple[1] : '');
|
|
10114
|
+
const apply = ()=>{
|
|
10115
|
+
if (!from && !to) {
|
|
10116
|
+
onCommit(null);
|
|
10117
|
+
} else {
|
|
10118
|
+
onCommit({
|
|
10119
|
+
field,
|
|
10120
|
+
op: 'between',
|
|
10121
|
+
value: [
|
|
10122
|
+
from || null,
|
|
10123
|
+
to || null
|
|
10124
|
+
]
|
|
10125
|
+
});
|
|
10126
|
+
}
|
|
10127
|
+
};
|
|
10128
|
+
return jsxs("div", {
|
|
10129
|
+
className: "flex flex-col gap-2 min-w-[220px]",
|
|
10130
|
+
children: [
|
|
10131
|
+
jsxs("div", {
|
|
10132
|
+
className: "grid grid-cols-2 gap-2",
|
|
10133
|
+
children: [
|
|
10134
|
+
jsxs("label", {
|
|
10135
|
+
className: "flex flex-col gap-1 text-xs text-neutral-700",
|
|
10136
|
+
children: [
|
|
10137
|
+
jsx("span", {
|
|
10138
|
+
children: labels.filterFrom
|
|
10139
|
+
}),
|
|
10140
|
+
jsx("input", {
|
|
10141
|
+
type: "date",
|
|
10142
|
+
value: from,
|
|
10143
|
+
onChange: (e)=>setFrom(e.target.value),
|
|
10144
|
+
autoFocus: true,
|
|
10145
|
+
className: "rounded-md border border-neutral-300 bg-neutral-50 px-2 py-1.5 text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
|
|
10146
|
+
})
|
|
10147
|
+
]
|
|
10148
|
+
}),
|
|
10149
|
+
jsxs("label", {
|
|
10150
|
+
className: "flex flex-col gap-1 text-xs text-neutral-700",
|
|
10151
|
+
children: [
|
|
10152
|
+
jsx("span", {
|
|
10153
|
+
children: labels.filterTo
|
|
10154
|
+
}),
|
|
10155
|
+
jsx("input", {
|
|
10156
|
+
type: "date",
|
|
10157
|
+
value: to,
|
|
10158
|
+
onChange: (e)=>setTo(e.target.value),
|
|
10159
|
+
className: "rounded-md border border-neutral-300 bg-neutral-50 px-2 py-1.5 text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
|
|
10160
|
+
})
|
|
10161
|
+
]
|
|
10162
|
+
})
|
|
10163
|
+
]
|
|
10164
|
+
}),
|
|
10165
|
+
jsx(FilterActions, {
|
|
10166
|
+
onApply: apply,
|
|
10167
|
+
onClear: ()=>{
|
|
10168
|
+
setFrom('');
|
|
10169
|
+
setTo('');
|
|
10170
|
+
onCommit(null);
|
|
10171
|
+
},
|
|
10172
|
+
labels: labels
|
|
10173
|
+
})
|
|
10174
|
+
]
|
|
10175
|
+
});
|
|
10176
|
+
}
|
|
10177
|
+
// ───── Apply + Clear actions ─────
|
|
10178
|
+
function FilterActions(props) {
|
|
10179
|
+
return jsxs("div", {
|
|
10180
|
+
className: "flex justify-end gap-2 pt-1 border-t border-neutral-200",
|
|
10181
|
+
children: [
|
|
10182
|
+
jsx("button", {
|
|
10183
|
+
type: "button",
|
|
10184
|
+
onClick: props.onClear,
|
|
10185
|
+
className: "text-xs text-neutral-600 hover:text-neutral-900 underline-offset-2 hover:underline focus:outline-none focus-visible:underline px-2 py-1",
|
|
10186
|
+
children: props.labels.filterClear
|
|
10187
|
+
}),
|
|
10188
|
+
jsx("button", {
|
|
10189
|
+
type: "button",
|
|
10190
|
+
onClick: props.onApply,
|
|
10191
|
+
className: "text-xs bg-primary-500 text-white rounded-md hover:bg-primary-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 px-3 py-1",
|
|
10192
|
+
children: props.labels.filterApply
|
|
10193
|
+
})
|
|
10194
|
+
]
|
|
10195
|
+
});
|
|
10196
|
+
}
|
|
10197
|
+
// ───── Icon ─────
|
|
10198
|
+
function FilterIcon() {
|
|
10199
|
+
return jsx("svg", {
|
|
10200
|
+
width: "0.875em",
|
|
10201
|
+
height: "0.875em",
|
|
10202
|
+
viewBox: "0 0 16 16",
|
|
10203
|
+
fill: "none",
|
|
10204
|
+
stroke: "currentColor",
|
|
10205
|
+
strokeWidth: "1.5",
|
|
10206
|
+
strokeLinecap: "round",
|
|
10207
|
+
strokeLinejoin: "round",
|
|
10208
|
+
"aria-hidden": "true",
|
|
10209
|
+
children: jsx("path", {
|
|
10210
|
+
d: "M2 3h12M4 8h8M6 13h4"
|
|
10211
|
+
})
|
|
10212
|
+
});
|
|
10213
|
+
}
|
|
10214
|
+
|
|
10215
|
+
const DEFAULT_VISIBILITY_LABELS = {
|
|
10216
|
+
columnsButton: 'Columns',
|
|
10217
|
+
columnsTitle: 'Toggle columns',
|
|
10218
|
+
columnsShowAll: 'Show all',
|
|
10219
|
+
columnsHideAll: 'Hide all'
|
|
10220
|
+
};
|
|
10221
|
+
function ColumnVisibilityTrigger(props) {
|
|
10222
|
+
const { cols, hiddenColumns, onChange, disabled, labels: labelsProp } = props;
|
|
10223
|
+
const labels = _extends({}, DEFAULT_VISIBILITY_LABELS, labelsProp);
|
|
10224
|
+
const [open, setOpen] = useState(false);
|
|
10225
|
+
// Hideable cols are the only ones the user can toggle. `hideable`
|
|
10226
|
+
// defaults to `true` — a column is hideable unless explicitly opted
|
|
10227
|
+
// out with `hideable: false` (structurally required column).
|
|
10228
|
+
const hideableCols = cols.filter((c)=>c.hideable !== false);
|
|
10229
|
+
const toggle = (field)=>{
|
|
10230
|
+
onChange(hiddenColumns.includes(field) ? hiddenColumns.filter((f)=>f !== field) : [
|
|
10231
|
+
...hiddenColumns,
|
|
10232
|
+
field
|
|
10233
|
+
]);
|
|
10234
|
+
};
|
|
10235
|
+
const showAll = ()=>onChange([]);
|
|
10236
|
+
const hideAll = ()=>onChange(hideableCols.map((c)=>c.field));
|
|
10237
|
+
return jsx(Popover, {
|
|
10238
|
+
open: open,
|
|
10239
|
+
onOpenChange: setOpen,
|
|
10240
|
+
side: "bottom",
|
|
10241
|
+
align: "end",
|
|
10242
|
+
content: jsxs("div", {
|
|
10243
|
+
className: "flex flex-col gap-2 min-w-[220px]",
|
|
10244
|
+
children: [
|
|
10245
|
+
jsxs("div", {
|
|
10246
|
+
className: "flex items-center justify-between pb-2 border-b border-neutral-200",
|
|
10247
|
+
children: [
|
|
10248
|
+
jsx("span", {
|
|
10249
|
+
className: "text-xs font-semibold text-neutral-700 uppercase tracking-wide",
|
|
10250
|
+
children: labels.columnsTitle
|
|
10251
|
+
}),
|
|
10252
|
+
jsxs("div", {
|
|
10253
|
+
className: "flex items-center gap-2",
|
|
10254
|
+
children: [
|
|
10255
|
+
jsx("button", {
|
|
10256
|
+
type: "button",
|
|
10257
|
+
onClick: showAll,
|
|
10258
|
+
className: "text-xs text-neutral-600 hover:text-neutral-900 underline-offset-2 hover:underline focus:outline-none focus-visible:underline",
|
|
10259
|
+
children: labels.columnsShowAll
|
|
10260
|
+
}),
|
|
10261
|
+
jsx("span", {
|
|
10262
|
+
className: "text-neutral-300",
|
|
10263
|
+
"aria-hidden": "true",
|
|
10264
|
+
children: "\u00B7"
|
|
10265
|
+
}),
|
|
10266
|
+
jsx("button", {
|
|
10267
|
+
type: "button",
|
|
10268
|
+
onClick: hideAll,
|
|
10269
|
+
className: "text-xs text-neutral-600 hover:text-neutral-900 underline-offset-2 hover:underline focus:outline-none focus-visible:underline",
|
|
10270
|
+
children: labels.columnsHideAll
|
|
10271
|
+
})
|
|
10272
|
+
]
|
|
10273
|
+
})
|
|
10274
|
+
]
|
|
10275
|
+
}),
|
|
10276
|
+
jsx("ul", {
|
|
10277
|
+
className: "flex flex-col gap-0.5 max-h-72 overflow-auto",
|
|
10278
|
+
children: hideableCols.map((col)=>{
|
|
10279
|
+
const field = col.field;
|
|
10280
|
+
const hidden = hiddenColumns.includes(field);
|
|
10281
|
+
const header = typeof col.header === 'function' ? col.header() : col.header;
|
|
10282
|
+
return jsx("li", {
|
|
10283
|
+
children: jsxs("label", {
|
|
10284
|
+
className: "flex items-center gap-2 cursor-pointer py-1 px-1 rounded hover:bg-neutral-100",
|
|
10285
|
+
children: [
|
|
10286
|
+
jsx("input", {
|
|
10287
|
+
type: "checkbox",
|
|
10288
|
+
checked: !hidden,
|
|
10289
|
+
onChange: ()=>toggle(field),
|
|
10290
|
+
className: "cursor-pointer"
|
|
10291
|
+
}),
|
|
10292
|
+
jsx("span", {
|
|
10293
|
+
className: "text-sm text-neutral-900",
|
|
10294
|
+
children: header
|
|
10295
|
+
})
|
|
10296
|
+
]
|
|
10297
|
+
})
|
|
10298
|
+
}, field);
|
|
10299
|
+
})
|
|
10300
|
+
})
|
|
10301
|
+
]
|
|
10302
|
+
}),
|
|
10303
|
+
children: jsxs("button", {
|
|
10304
|
+
type: "button",
|
|
10305
|
+
disabled: disabled,
|
|
10306
|
+
className: cn('inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-md', 'text-sm text-neutral-700 hover:text-neutral-900', 'border border-neutral-300 bg-neutral-50 hover:bg-neutral-100', 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500', 'transition-colors motion-reduce:transition-none', 'disabled:opacity-50 disabled:cursor-not-allowed'),
|
|
10307
|
+
children: [
|
|
10308
|
+
jsx(ColumnsIcon, {}),
|
|
10309
|
+
jsx("span", {
|
|
10310
|
+
children: labels.columnsButton
|
|
10311
|
+
})
|
|
10312
|
+
]
|
|
10313
|
+
})
|
|
10314
|
+
});
|
|
10315
|
+
}
|
|
10316
|
+
// ───── Icon ─────
|
|
10317
|
+
function ColumnsIcon() {
|
|
10318
|
+
return jsxs("svg", {
|
|
10319
|
+
width: "0.875em",
|
|
10320
|
+
height: "0.875em",
|
|
10321
|
+
viewBox: "0 0 16 16",
|
|
10322
|
+
fill: "none",
|
|
10323
|
+
stroke: "currentColor",
|
|
10324
|
+
strokeWidth: "1.5",
|
|
10325
|
+
strokeLinecap: "round",
|
|
10326
|
+
strokeLinejoin: "round",
|
|
10327
|
+
"aria-hidden": "true",
|
|
10328
|
+
children: [
|
|
10329
|
+
jsx("rect", {
|
|
10330
|
+
x: "2",
|
|
10331
|
+
y: "3",
|
|
10332
|
+
width: "3.5",
|
|
10333
|
+
height: "10",
|
|
10334
|
+
rx: "0.5"
|
|
10335
|
+
}),
|
|
10336
|
+
jsx("rect", {
|
|
10337
|
+
x: "6.25",
|
|
10338
|
+
y: "3",
|
|
10339
|
+
width: "3.5",
|
|
10340
|
+
height: "10",
|
|
10341
|
+
rx: "0.5"
|
|
10342
|
+
}),
|
|
10343
|
+
jsx("rect", {
|
|
10344
|
+
x: "10.5",
|
|
10345
|
+
y: "3",
|
|
10346
|
+
width: "3.5",
|
|
10347
|
+
height: "10",
|
|
10348
|
+
rx: "0.5"
|
|
10349
|
+
})
|
|
10350
|
+
]
|
|
10351
|
+
});
|
|
10352
|
+
}
|
|
10353
|
+
|
|
10354
|
+
function useColumnResize(args) {
|
|
10355
|
+
const { widths, onChange, minWidth = 40, maxWidth = 1200 } = args;
|
|
10356
|
+
// We need the LATEST widths inside the pointermove closure. Stick
|
|
10357
|
+
// them in a ref so the closure captured at pointerdown stays valid
|
|
10358
|
+
// for the duration of the drag.
|
|
10359
|
+
const widthsRef = useRef(widths);
|
|
10360
|
+
widthsRef.current = widths;
|
|
10361
|
+
const startResize = useCallback((field, startWidth, clampMin, clampMax)=>{
|
|
10362
|
+
return (e)=>{
|
|
10363
|
+
// Only respond to the primary mouse button (or touch / pen).
|
|
10364
|
+
if (e.pointerType === 'mouse' && e.button !== 0) return;
|
|
10365
|
+
e.preventDefault();
|
|
10366
|
+
e.stopPropagation();
|
|
10367
|
+
const startX = e.clientX;
|
|
10368
|
+
const target = e.currentTarget;
|
|
10369
|
+
try {
|
|
10370
|
+
target.setPointerCapture(e.pointerId);
|
|
10371
|
+
} catch (unused) {
|
|
10372
|
+
// jsdom or older browsers — fine to fall back to global listeners.
|
|
10373
|
+
}
|
|
10374
|
+
const effectiveMin = Math.max(minWidth, clampMin);
|
|
10375
|
+
const effectiveMax = Math.min(maxWidth, clampMax);
|
|
10376
|
+
const handleMove = (ev)=>{
|
|
10377
|
+
const delta = ev.clientX - startX;
|
|
10378
|
+
const next = clamp(startWidth + delta, effectiveMin, effectiveMax);
|
|
10379
|
+
onChange(_extends({}, widthsRef.current, {
|
|
10380
|
+
[field]: next
|
|
10381
|
+
}));
|
|
10382
|
+
};
|
|
10383
|
+
const handleEnd = (ev)=>{
|
|
10384
|
+
try {
|
|
10385
|
+
target.releasePointerCapture(ev.pointerId);
|
|
10386
|
+
} catch (unused) {
|
|
10387
|
+
/* noop */ }
|
|
10388
|
+
target.removeEventListener('pointermove', handleMove);
|
|
10389
|
+
target.removeEventListener('pointerup', handleEnd);
|
|
10390
|
+
target.removeEventListener('pointercancel', handleEnd);
|
|
10391
|
+
};
|
|
10392
|
+
target.addEventListener('pointermove', handleMove);
|
|
10393
|
+
target.addEventListener('pointerup', handleEnd);
|
|
10394
|
+
target.addEventListener('pointercancel', handleEnd);
|
|
10395
|
+
};
|
|
10396
|
+
}, [
|
|
10397
|
+
onChange,
|
|
10398
|
+
minWidth,
|
|
10399
|
+
maxWidth
|
|
10400
|
+
]);
|
|
10401
|
+
return {
|
|
10402
|
+
startResize
|
|
10403
|
+
};
|
|
10404
|
+
}
|
|
10405
|
+
function clamp(n, min, max) {
|
|
10406
|
+
if (n < min) return min;
|
|
10407
|
+
if (n > max) return max;
|
|
10408
|
+
return n;
|
|
10409
|
+
}
|
|
10410
|
+
|
|
10411
|
+
function useColumnReorder(args) {
|
|
10412
|
+
const { order, onChange } = args;
|
|
10413
|
+
const sourceRef = useRef(null);
|
|
10414
|
+
const [dropTarget, setDropTarget] = useState(null);
|
|
10415
|
+
const [draggingField, setDraggingField] = useState(null);
|
|
10416
|
+
const dragStart = useCallback((field)=>(e)=>{
|
|
10417
|
+
sourceRef.current = field;
|
|
10418
|
+
setDraggingField(field);
|
|
10419
|
+
try {
|
|
10420
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
10421
|
+
// Some browsers refuse to start a drag if no data is set.
|
|
10422
|
+
e.dataTransfer.setData('text/plain', field);
|
|
10423
|
+
} catch (unused) {
|
|
10424
|
+
/* noop — jsdom doesn't implement dataTransfer fully. */ }
|
|
10425
|
+
}, []);
|
|
10426
|
+
const dragOver = useCallback((field)=>(e)=>{
|
|
10427
|
+
if (sourceRef.current == null) return;
|
|
10428
|
+
if (field === sourceRef.current) return;
|
|
10429
|
+
e.preventDefault();
|
|
10430
|
+
e.dataTransfer.dropEffect = 'move';
|
|
10431
|
+
// Determine LEFT vs RIGHT half of the target.
|
|
10432
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
10433
|
+
const midpoint = rect.left + rect.width / 2;
|
|
10434
|
+
const side = e.clientX < midpoint ? 'left' : 'right';
|
|
10435
|
+
setDropTarget((prev)=>prev && prev.field === field && prev.side === side ? prev : {
|
|
10436
|
+
field,
|
|
10437
|
+
side
|
|
10438
|
+
});
|
|
10439
|
+
}, []);
|
|
10440
|
+
const drop = useCallback((field)=>(e)=>{
|
|
10441
|
+
e.preventDefault();
|
|
10442
|
+
const source = sourceRef.current;
|
|
10443
|
+
sourceRef.current = null;
|
|
10444
|
+
setDraggingField(null);
|
|
10445
|
+
const target = dropTarget;
|
|
10446
|
+
setDropTarget(null);
|
|
10447
|
+
if (!source || source === field) return;
|
|
10448
|
+
const side = (target == null ? void 0 : target.field) === field ? target.side : 'right';
|
|
10449
|
+
const next = moveColumn(order, source, field, side);
|
|
10450
|
+
if (!arraysEqual(next, order)) onChange(next);
|
|
10451
|
+
}, [
|
|
10452
|
+
order,
|
|
10453
|
+
onChange,
|
|
10454
|
+
dropTarget
|
|
10455
|
+
]);
|
|
10456
|
+
const dragEnd = useCallback(()=>{
|
|
10457
|
+
sourceRef.current = null;
|
|
10458
|
+
setDraggingField(null);
|
|
10459
|
+
setDropTarget(null);
|
|
10460
|
+
}, []);
|
|
10461
|
+
return {
|
|
10462
|
+
dropTarget,
|
|
10463
|
+
draggingField,
|
|
10464
|
+
dragStart,
|
|
10465
|
+
dragOver,
|
|
10466
|
+
drop,
|
|
10467
|
+
dragEnd
|
|
10468
|
+
};
|
|
10469
|
+
}
|
|
10470
|
+
/**
|
|
10471
|
+
* Move `source` next to `target` in the `order` list.
|
|
10472
|
+
* `side='left'` inserts before; `side='right'` inserts after.
|
|
10473
|
+
* Exposed for unit testing.
|
|
10474
|
+
*/ function moveColumn(order, source, target, side) {
|
|
10475
|
+
const withoutSource = order.filter((f)=>f !== source);
|
|
10476
|
+
const targetIndex = withoutSource.indexOf(target);
|
|
10477
|
+
if (targetIndex === -1) return order;
|
|
10478
|
+
const insertAt = side === 'left' ? targetIndex : targetIndex + 1;
|
|
10479
|
+
return [
|
|
10480
|
+
...withoutSource.slice(0, insertAt),
|
|
10481
|
+
source,
|
|
10482
|
+
...withoutSource.slice(insertAt)
|
|
10483
|
+
];
|
|
10484
|
+
}
|
|
10485
|
+
function arraysEqual(a, b) {
|
|
10486
|
+
if (a === b) return true;
|
|
10487
|
+
if (a.length !== b.length) return false;
|
|
10488
|
+
for(let i = 0; i < a.length; i++){
|
|
10489
|
+
if (a[i] !== b[i]) return false;
|
|
10490
|
+
}
|
|
10491
|
+
return true;
|
|
10492
|
+
}
|
|
10493
|
+
|
|
9751
10494
|
const DEFAULT_LABELS = {
|
|
9752
10495
|
searchPlaceholder: 'Search...',
|
|
9753
10496
|
noData: 'No data',
|
|
@@ -9764,10 +10507,21 @@ const DEFAULT_LABELS = {
|
|
|
9764
10507
|
filterColumn: 'Filter',
|
|
9765
10508
|
filterApply: 'Apply',
|
|
9766
10509
|
filterClear: 'Clear',
|
|
10510
|
+
filterMin: 'Min',
|
|
10511
|
+
filterMax: 'Max',
|
|
10512
|
+
filterFrom: 'From',
|
|
10513
|
+
filterTo: 'To',
|
|
10514
|
+
filterAll: 'All',
|
|
10515
|
+
filterTrue: 'True',
|
|
10516
|
+
filterFalse: 'False',
|
|
9767
10517
|
density: 'Density',
|
|
9768
10518
|
densityCompact: 'Compact',
|
|
9769
10519
|
densityComfortable: 'Comfortable',
|
|
9770
|
-
densitySpacious: 'Spacious'
|
|
10520
|
+
densitySpacious: 'Spacious',
|
|
10521
|
+
columnsButton: 'Columns',
|
|
10522
|
+
columnsTitle: 'Toggle columns',
|
|
10523
|
+
columnsShowAll: 'Show all',
|
|
10524
|
+
columnsHideAll: 'Hide all'
|
|
9771
10525
|
};
|
|
9772
10526
|
/**
|
|
9773
10527
|
* Dashforge TW `<DataGrid>` — virtualized data table.
|
|
@@ -9796,7 +10550,7 @@ const DEFAULT_LABELS = {
|
|
|
9796
10550
|
* - `'visible'` — toggles only the current viewport window
|
|
9797
10551
|
*/ function DataGrid(props) {
|
|
9798
10552
|
var _slotProps_root, _slotProps_toolbar, _slotProps_search, _slotProps_scroll, _slotProps_table, _slotProps_thead, _slotProps_headerRow, _slotProps_headerCell, _slotProps_selectionCell, _slotProps_headerCell1, _slotProps_tbody, _slotProps_row, _slotProps_cell, _slotProps_emptyState, _slotProps_row1, _slotProps_cell1, _slotProps_cell2, _slotProps_selectionCell1, _slotProps_cell3, _slotProps_rowActionsCell, _slotProps_bulkActionFooter, _slotProps_pagination;
|
|
9799
|
-
const { rows, cols, getRowId, totalCount: totalCountProp, rowHeight, overscan = 5, height, sortModel: sortModelProp, onSortChange, defaultSortModel, serverSideSort = false, enableSearch = false, searchQuery: searchQueryProp, onSearchQueryChange, searchPlaceholder, searchDebounceMs = 200, serverSideSearch = false, filterModel: filterModelProp, onFilterChange, serverSideFilter = false, rowSelection = 'none', selectedRowIds: selectedRowIdsProp, onSelectionChange, bulkActions, selectAllScope = 'allLoaded', pagination, serverSidePagination = false, rowActions, loading = false, loadingRowCount = 8, emptyState, stickyHeader = true, caption, showCaption = false, access: gridAccess, labels: labelsProp, variant, size, density, sx, slotProps } = props;
|
|
10553
|
+
const { rows, cols, getRowId, totalCount: totalCountProp, rowHeight, overscan = 5, height, sortModel: sortModelProp, onSortChange, defaultSortModel, serverSideSort = false, enableSearch = false, searchQuery: searchQueryProp, onSearchQueryChange, searchPlaceholder, searchDebounceMs = 200, serverSideSearch = false, filterModel: filterModelProp, onFilterChange, serverSideFilter = false, hiddenColumns: hiddenColumnsProp, onHiddenColumnsChange, enableColumnVisibility = true, columnWidths: columnWidthsProp, onColumnWidthsChange, enableColumnResize = true, columnOrder: columnOrderProp, onColumnOrderChange, enableColumnReorder = true, rowSelection = 'none', selectedRowIds: selectedRowIdsProp, onSelectionChange, bulkActions, selectAllScope = 'allLoaded', pagination, serverSidePagination = false, rowActions, loading = false, loadingRowCount = 8, emptyState, stickyHeader = true, caption, showCaption = false, access: gridAccess, labels: labelsProp, variant, size, density, sx, slotProps } = props;
|
|
9800
10554
|
const labels = _extends({}, DEFAULT_LABELS, labelsProp);
|
|
9801
10555
|
const gridAccessState = useAccessState(gridAccess);
|
|
9802
10556
|
// ───── Controllable state ─────
|
|
@@ -9810,16 +10564,109 @@ const DEFAULT_LABELS = {
|
|
|
9810
10564
|
defaultValue: '',
|
|
9811
10565
|
onChange: onSearchQueryChange
|
|
9812
10566
|
});
|
|
9813
|
-
|
|
10567
|
+
// filterModel: in v1-bis the per-column header UI commits filter
|
|
10568
|
+
// items to this controllable state. Consumer can lift it via
|
|
10569
|
+
// filterModelProp + onFilterChange to drive server-side filtering.
|
|
10570
|
+
const [filterModel, setFilterModel] = useControllableState({
|
|
10571
|
+
controlledValue: filterModelProp,
|
|
10572
|
+
defaultValue: [],
|
|
10573
|
+
onChange: onFilterChange
|
|
10574
|
+
});
|
|
9814
10575
|
const [selectedRowIds, setSelectedRowIds] = useControllableState({
|
|
9815
10576
|
controlledValue: selectedRowIdsProp,
|
|
9816
10577
|
defaultValue: [],
|
|
9817
10578
|
onChange: onSelectionChange
|
|
9818
10579
|
});
|
|
9819
|
-
// ─────
|
|
9820
|
-
|
|
10580
|
+
// ───── Column visibility ─────
|
|
10581
|
+
// Seed default hidden set from `defaultHidden` flag on columns.
|
|
10582
|
+
// Memoized to avoid the seed shifting on every render — the
|
|
10583
|
+
// controllable state keeps internal state stable thereafter.
|
|
10584
|
+
const defaultHiddenColumns = useMemo(()=>cols.filter((c)=>c.defaultHidden === true).map((c)=>c.field), [
|
|
9821
10585
|
cols
|
|
9822
10586
|
]);
|
|
10587
|
+
const [hiddenColumns, setHiddenColumns] = useControllableState({
|
|
10588
|
+
controlledValue: hiddenColumnsProp,
|
|
10589
|
+
defaultValue: defaultHiddenColumns,
|
|
10590
|
+
onChange: onHiddenColumnsChange
|
|
10591
|
+
});
|
|
10592
|
+
// ───── Column widths (resize state) ─────
|
|
10593
|
+
// Seed from any `col.width` declared statically. Internal state is
|
|
10594
|
+
// applied only when the user actually starts dragging; until then
|
|
10595
|
+
// we fall back to the static width from the column def.
|
|
10596
|
+
const defaultColumnWidths = useMemo(()=>{
|
|
10597
|
+
const map = {};
|
|
10598
|
+
for (const c of cols){
|
|
10599
|
+
if (typeof c.width === 'number') {
|
|
10600
|
+
map[c.field] = c.width;
|
|
10601
|
+
}
|
|
10602
|
+
}
|
|
10603
|
+
return map;
|
|
10604
|
+
}, [
|
|
10605
|
+
cols
|
|
10606
|
+
]);
|
|
10607
|
+
const [columnWidths, setColumnWidths] = useControllableState({
|
|
10608
|
+
controlledValue: columnWidthsProp,
|
|
10609
|
+
defaultValue: defaultColumnWidths,
|
|
10610
|
+
onChange: onColumnWidthsChange
|
|
10611
|
+
});
|
|
10612
|
+
const { startResize } = useColumnResize({
|
|
10613
|
+
widths: columnWidths,
|
|
10614
|
+
onChange: setColumnWidths
|
|
10615
|
+
});
|
|
10616
|
+
// ───── Column order (reorder state) ─────
|
|
10617
|
+
const defaultColumnOrder = useMemo(()=>cols.map((c)=>c.field), [
|
|
10618
|
+
cols
|
|
10619
|
+
]);
|
|
10620
|
+
const [columnOrder, setColumnOrder] = useControllableState({
|
|
10621
|
+
controlledValue: columnOrderProp,
|
|
10622
|
+
defaultValue: defaultColumnOrder,
|
|
10623
|
+
onChange: onColumnOrderChange
|
|
10624
|
+
});
|
|
10625
|
+
const reorderHandlers = useColumnReorder({
|
|
10626
|
+
order: columnOrder,
|
|
10627
|
+
onChange: setColumnOrder
|
|
10628
|
+
});
|
|
10629
|
+
// Apply the active order to the columns list. Cols not in the
|
|
10630
|
+
// order array (e.g. newly-added columns at runtime) are appended
|
|
10631
|
+
// in their original position from `cols`.
|
|
10632
|
+
const orderedCols = useMemo(()=>{
|
|
10633
|
+
if (!columnOrder || columnOrder.length === 0) return cols;
|
|
10634
|
+
const byField = new Map(cols.map((c)=>[
|
|
10635
|
+
c.field,
|
|
10636
|
+
c
|
|
10637
|
+
]));
|
|
10638
|
+
const seen = new Set();
|
|
10639
|
+
const result = [];
|
|
10640
|
+
for (const field of columnOrder){
|
|
10641
|
+
const c = byField.get(field);
|
|
10642
|
+
if (c) {
|
|
10643
|
+
result.push(c);
|
|
10644
|
+
seen.add(field);
|
|
10645
|
+
}
|
|
10646
|
+
}
|
|
10647
|
+
for (const c of cols){
|
|
10648
|
+
if (!seen.has(c.field)) result.push(c);
|
|
10649
|
+
}
|
|
10650
|
+
return result;
|
|
10651
|
+
}, [
|
|
10652
|
+
cols,
|
|
10653
|
+
columnOrder
|
|
10654
|
+
]);
|
|
10655
|
+
// ───── RBAC + visibility + reorder: derive final column list ─────
|
|
10656
|
+
// - `access.onUnauthorized === 'hide'` removes a column entirely.
|
|
10657
|
+
// - `hiddenColumns` is the user-toggled hidden set from the
|
|
10658
|
+
// column-visibility dialog.
|
|
10659
|
+
// - `orderedCols` is the user-reordered version of `cols`.
|
|
10660
|
+
// NOTE: filtering happens on `orderedCols` so the active order is
|
|
10661
|
+
// preserved through visibility changes.
|
|
10662
|
+
const visibleCols = useMemo(()=>orderedCols.filter((col)=>{
|
|
10663
|
+
if (col.access && col.access.onUnauthorized === 'hide') return false;
|
|
10664
|
+
if (hiddenColumns.includes(col.field)) return false;
|
|
10665
|
+
return true;
|
|
10666
|
+
}), [
|
|
10667
|
+
orderedCols,
|
|
10668
|
+
hiddenColumns
|
|
10669
|
+
]);
|
|
9823
10670
|
// ───── Smart-default column types ─────
|
|
9824
10671
|
const columnTypes = useColumnAutoDetect(rows, visibleCols);
|
|
9825
10672
|
// ───── Pipeline: search → filter → sort (skip when server-side) ─────
|
|
@@ -9901,28 +10748,43 @@ const DEFAULT_LABELS = {
|
|
|
9901
10748
|
className: cn(v.root(), sx, slotProps == null ? void 0 : (_slotProps_root = slotProps.root) == null ? void 0 : _slotProps_root.className),
|
|
9902
10749
|
"data-disabled": !isInteractive ? 'true' : undefined,
|
|
9903
10750
|
children: [
|
|
9904
|
-
enableSearch &&
|
|
10751
|
+
(enableSearch || enableColumnVisibility) && jsxs("div", {
|
|
9905
10752
|
className: cn(v.toolbar(), slotProps == null ? void 0 : (_slotProps_toolbar = slotProps.toolbar) == null ? void 0 : _slotProps_toolbar.className),
|
|
9906
|
-
children:
|
|
9907
|
-
|
|
9908
|
-
|
|
9909
|
-
|
|
9910
|
-
|
|
9911
|
-
|
|
9912
|
-
|
|
9913
|
-
|
|
9914
|
-
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
9918
|
-
|
|
9919
|
-
|
|
9920
|
-
|
|
9921
|
-
|
|
9922
|
-
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
|
|
10753
|
+
children: [
|
|
10754
|
+
enableSearch && jsxs("label", {
|
|
10755
|
+
className: cn(v.search(), slotProps == null ? void 0 : (_slotProps_search = slotProps.search) == null ? void 0 : _slotProps_search.className),
|
|
10756
|
+
children: [
|
|
10757
|
+
jsx("span", {
|
|
10758
|
+
className: "sr-only",
|
|
10759
|
+
children: labels.searchPlaceholder
|
|
10760
|
+
}),
|
|
10761
|
+
jsx(SearchIcon, {
|
|
10762
|
+
className: "ml-2 shrink-0 text-neutral-500"
|
|
10763
|
+
}),
|
|
10764
|
+
jsx("input", {
|
|
10765
|
+
type: "text",
|
|
10766
|
+
value: searchQuery,
|
|
10767
|
+
onChange: (e)=>setSearchQuery(e.target.value),
|
|
10768
|
+
placeholder: searchPlaceholder != null ? searchPlaceholder : labels.searchPlaceholder,
|
|
10769
|
+
disabled: !isInteractive,
|
|
10770
|
+
className: cn('flex-1 bg-transparent border-0 outline-none', 'px-2 py-1.5', 'placeholder:text-neutral-400', 'disabled:opacity-50 disabled:cursor-not-allowed')
|
|
10771
|
+
})
|
|
10772
|
+
]
|
|
10773
|
+
}),
|
|
10774
|
+
enableColumnVisibility && jsx(ColumnVisibilityTrigger, {
|
|
10775
|
+
cols: cols.filter((c)=>// RBAC-hidden columns are never offered in the menu.
|
|
10776
|
+
!c.access || c.access.onUnauthorized !== 'hide'),
|
|
10777
|
+
hiddenColumns: hiddenColumns,
|
|
10778
|
+
onChange: setHiddenColumns,
|
|
10779
|
+
disabled: !isInteractive,
|
|
10780
|
+
labels: {
|
|
10781
|
+
columnsButton: labels.columnsButton,
|
|
10782
|
+
columnsTitle: labels.columnsTitle,
|
|
10783
|
+
columnsShowAll: labels.columnsShowAll,
|
|
10784
|
+
columnsHideAll: labels.columnsHideAll
|
|
10785
|
+
}
|
|
10786
|
+
})
|
|
10787
|
+
]
|
|
9926
10788
|
}),
|
|
9927
10789
|
jsx("div", {
|
|
9928
10790
|
ref: scrollRef,
|
|
@@ -9965,8 +10827,15 @@ const DEFAULT_LABELS = {
|
|
|
9965
10827
|
sortModel: sortModel,
|
|
9966
10828
|
onSortClick: (e)=>handleSortClick(col, e, sortModel, setSortModel),
|
|
9967
10829
|
inferredType: columnTypes.get(col.field),
|
|
10830
|
+
filterModel: filterModel,
|
|
10831
|
+
onFilterChange: setFilterModel,
|
|
10832
|
+
effectiveWidth: resolveColumnWidth(col, columnWidths),
|
|
10833
|
+
resizable: enableColumnResize && col.resizable !== false,
|
|
10834
|
+
startResize: startResize,
|
|
10835
|
+
reorderable: enableColumnReorder && col.reorderable !== false,
|
|
10836
|
+
reorderHandlers: reorderHandlers,
|
|
9968
10837
|
labels: labels,
|
|
9969
|
-
className: cn(v.headerCell(), col.sticky === 'left' && v.stickyLeftHeaderCell(), slotProps == null ? void 0 : (_slotProps_headerCell = slotProps.headerCell) == null ? void 0 : _slotProps_headerCell.className),
|
|
10838
|
+
className: cn(v.headerCell(), col.sticky === 'left' && v.stickyLeftHeaderCell(), col.sticky === 'right' && v.stickyRightHeaderCell(), slotProps == null ? void 0 : (_slotProps_headerCell = slotProps.headerCell) == null ? void 0 : _slotProps_headerCell.className),
|
|
9970
10839
|
buttonClassName: v.headerCellButton(),
|
|
9971
10840
|
disabled: !isInteractive
|
|
9972
10841
|
}, col.field);
|
|
@@ -9999,6 +10868,7 @@ const DEFAULT_LABELS = {
|
|
|
9999
10868
|
renderedRows,
|
|
10000
10869
|
visibleCols,
|
|
10001
10870
|
columnTypes,
|
|
10871
|
+
columnWidths,
|
|
10002
10872
|
selection,
|
|
10003
10873
|
rowSelection,
|
|
10004
10874
|
showSelectionColumn,
|
|
@@ -10012,6 +10882,7 @@ const DEFAULT_LABELS = {
|
|
|
10012
10882
|
cell: cn(v.cell(), slotProps == null ? void 0 : (_slotProps_cell1 = slotProps.cell) == null ? void 0 : _slotProps_cell1.className),
|
|
10013
10883
|
selectionCell: cn(v.cell(), v.selectionCell(), v.stickyLeftCell(), slotProps == null ? void 0 : (_slotProps_cell2 = slotProps.cell) == null ? void 0 : _slotProps_cell2.className, slotProps == null ? void 0 : (_slotProps_selectionCell1 = slotProps.selectionCell) == null ? void 0 : _slotProps_selectionCell1.className),
|
|
10014
10884
|
stickyLeftCell: v.stickyLeftCell(),
|
|
10885
|
+
stickyRightCell: v.stickyRightCell(),
|
|
10015
10886
|
rowActionsCell: cn(v.cell(), v.rowActionsCell(), slotProps == null ? void 0 : (_slotProps_cell3 = slotProps.cell) == null ? void 0 : _slotProps_cell3.className, slotProps == null ? void 0 : (_slotProps_rowActionsCell = slotProps.rowActionsCell) == null ? void 0 : _slotProps_rowActionsCell.className)
|
|
10016
10887
|
}
|
|
10017
10888
|
})
|
|
@@ -10058,50 +10929,160 @@ const DEFAULT_LABELS = {
|
|
|
10058
10929
|
}
|
|
10059
10930
|
// ───── Header cell ─────
|
|
10060
10931
|
function HeaderCell(props) {
|
|
10061
|
-
const { col, sortModel, onSortClick, inferredType, labels, className, buttonClassName, disabled } = props;
|
|
10932
|
+
const { col, sortModel, onSortClick, inferredType, filterModel, onFilterChange, effectiveWidth, resizable, startResize, reorderable, reorderHandlers, labels, className, buttonClassName, disabled } = props;
|
|
10062
10933
|
const align = resolveAlign(col, inferredType);
|
|
10063
10934
|
const alignClass = align === 'right' ? 'text-right' : align === 'center' ? 'text-center' : 'text-left';
|
|
10064
10935
|
const sortEntry = sortModel.find((s)=>s.field === col.field);
|
|
10065
10936
|
const ariaSort = (sortEntry == null ? void 0 : sortEntry.direction) === 'asc' ? 'ascending' : (sortEntry == null ? void 0 : sortEntry.direction) === 'desc' ? 'descending' : 'none';
|
|
10066
10937
|
const headerContent = typeof col.header === 'function' ? col.header() : col.header;
|
|
10067
10938
|
const cellStyle = {
|
|
10068
|
-
width:
|
|
10939
|
+
width: effectiveWidth != null ? `${effectiveWidth}px` : undefined,
|
|
10069
10940
|
minWidth: col.minWidth != null ? `${col.minWidth}px` : undefined,
|
|
10070
|
-
flex: col.flex
|
|
10941
|
+
flex: effectiveWidth != null ? undefined : col.flex,
|
|
10942
|
+
position: resizable || reorderable ? 'relative' : undefined
|
|
10071
10943
|
};
|
|
10944
|
+
// Drop indicator for reorder: a 2px vertical bar at the LEFT or
|
|
10945
|
+
// RIGHT edge of this th when it's the active drop target.
|
|
10946
|
+
const dropTarget = reorderHandlers.dropTarget;
|
|
10947
|
+
const dropIndicatorClass = dropTarget != null && dropTarget.field === col.field ? dropTarget.side === 'left' ? 'before:absolute before:top-0 before:bottom-0 before:left-0 before:w-0.5 before:bg-primary-500' : 'after:absolute after:top-0 after:bottom-0 after:right-0 after:w-0.5 after:bg-primary-500' : '';
|
|
10948
|
+
const isDragging = reorderHandlers.draggingField === col.field;
|
|
10949
|
+
// Drag attributes only attach when reorderable; we DO NOT make the
|
|
10950
|
+
// entire th `draggable` if it isn't — keeps the keyboard focus
|
|
10951
|
+
// behavior unchanged.
|
|
10952
|
+
const dragAttrs = reorderable ? {
|
|
10953
|
+
draggable: true,
|
|
10954
|
+
onDragStart: reorderHandlers.dragStart(col.field),
|
|
10955
|
+
onDragOver: reorderHandlers.dragOver(col.field),
|
|
10956
|
+
onDrop: reorderHandlers.drop(col.field),
|
|
10957
|
+
onDragEnd: reorderHandlers.dragEnd
|
|
10958
|
+
} : {};
|
|
10959
|
+
const resizeHandle = resizable ? jsx("span", {
|
|
10960
|
+
role: "separator",
|
|
10961
|
+
"aria-orientation": "vertical",
|
|
10962
|
+
"aria-label": `Resize ${typeof col.header === 'string' ? col.header : col.field}`,
|
|
10963
|
+
onPointerDown: (e)=>{
|
|
10964
|
+
var _ref, _col_minWidth, _col_maxWidth;
|
|
10965
|
+
const handle = e.currentTarget;
|
|
10966
|
+
const th = handle.parentElement;
|
|
10967
|
+
const startWidth = (_ref = effectiveWidth != null ? effectiveWidth : th == null ? void 0 : th.getBoundingClientRect().width) != null ? _ref : 120;
|
|
10968
|
+
const min = (_col_minWidth = col.minWidth) != null ? _col_minWidth : 40;
|
|
10969
|
+
const max = (_col_maxWidth = col.maxWidth) != null ? _col_maxWidth : 1200;
|
|
10970
|
+
startResize(col.field, startWidth, min, max)(e);
|
|
10971
|
+
},
|
|
10972
|
+
className: cn('absolute top-0 bottom-0 right-0 w-1.5', 'cursor-col-resize select-none touch-none', 'hover:bg-primary-300 active:bg-primary-400', 'transition-colors motion-reduce:transition-none')
|
|
10973
|
+
}) : null;
|
|
10974
|
+
const filterType = col.filterable ? resolveFilterType(col, inferredType) : null;
|
|
10975
|
+
const filterTrigger = col.filterable && filterType ? jsx(ColumnFilterTrigger, {
|
|
10976
|
+
field: col.field,
|
|
10977
|
+
filterType: filterType,
|
|
10978
|
+
filterModel: filterModel,
|
|
10979
|
+
onFilterChange: onFilterChange,
|
|
10980
|
+
disabled: disabled,
|
|
10981
|
+
labels: {
|
|
10982
|
+
filterColumn: labels.filterColumn,
|
|
10983
|
+
filterApply: labels.filterApply,
|
|
10984
|
+
filterClear: labels.filterClear,
|
|
10985
|
+
filterMin: labels.filterMin,
|
|
10986
|
+
filterMax: labels.filterMax,
|
|
10987
|
+
filterFrom: labels.filterFrom,
|
|
10988
|
+
filterTo: labels.filterTo,
|
|
10989
|
+
filterAll: labels.filterAll,
|
|
10990
|
+
filterTrue: labels.filterTrue,
|
|
10991
|
+
filterFalse: labels.filterFalse
|
|
10992
|
+
}
|
|
10993
|
+
}) : null;
|
|
10994
|
+
// Layout strategy:
|
|
10995
|
+
// - sortable only → sort button is the cell content
|
|
10996
|
+
// - filterable only → header text + filter trigger in a flex row
|
|
10997
|
+
// - both → sort button + filter trigger in a flex row
|
|
10998
|
+
// - neither → plain header text
|
|
10999
|
+
const wrapperJustify = align === 'right' ? 'justify-end' : align === 'center' ? 'justify-center' : 'justify-start';
|
|
10072
11000
|
if (col.sortable) {
|
|
10073
|
-
|
|
11001
|
+
const sortButton = jsxs("button", {
|
|
11002
|
+
type: "button",
|
|
11003
|
+
onClick: onSortClick,
|
|
11004
|
+
disabled: disabled,
|
|
11005
|
+
"aria-label": ariaSort === 'ascending' ? labels.ariaSortAscending : ariaSort === 'descending' ? labels.ariaSortDescending : labels.ariaSortNone,
|
|
11006
|
+
className: cn(buttonClassName, align === 'right' && 'flex-row-reverse', 'disabled:cursor-not-allowed disabled:opacity-50'),
|
|
11007
|
+
children: [
|
|
11008
|
+
jsx("span", {
|
|
11009
|
+
children: headerContent
|
|
11010
|
+
}),
|
|
11011
|
+
jsx(SortIcon, {
|
|
11012
|
+
direction: sortEntry == null ? void 0 : sortEntry.direction
|
|
11013
|
+
})
|
|
11014
|
+
]
|
|
11015
|
+
});
|
|
11016
|
+
return jsxs("th", _extends({
|
|
10074
11017
|
scope: "col",
|
|
10075
11018
|
"aria-sort": ariaSort,
|
|
10076
|
-
className: cn(className, alignClass),
|
|
10077
|
-
style: cellStyle
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
11019
|
+
className: cn(className, alignClass, dropIndicatorClass, isDragging && 'opacity-50'),
|
|
11020
|
+
style: cellStyle
|
|
11021
|
+
}, dragAttrs, {
|
|
11022
|
+
children: [
|
|
11023
|
+
filterTrigger ? jsxs("div", {
|
|
11024
|
+
className: cn('flex items-center gap-1', wrapperJustify),
|
|
11025
|
+
children: [
|
|
11026
|
+
sortButton,
|
|
11027
|
+
filterTrigger
|
|
11028
|
+
]
|
|
11029
|
+
}) : sortButton,
|
|
11030
|
+
resizeHandle
|
|
11031
|
+
]
|
|
11032
|
+
}));
|
|
11033
|
+
}
|
|
11034
|
+
return jsxs("th", _extends({
|
|
11035
|
+
scope: "col",
|
|
11036
|
+
className: cn(className, alignClass, dropIndicatorClass, isDragging && 'opacity-50'),
|
|
11037
|
+
style: cellStyle
|
|
11038
|
+
}, dragAttrs, {
|
|
11039
|
+
children: [
|
|
11040
|
+
filterTrigger ? jsxs("div", {
|
|
11041
|
+
className: cn('flex items-center gap-1', wrapperJustify),
|
|
10084
11042
|
children: [
|
|
10085
11043
|
jsx("span", {
|
|
10086
11044
|
children: headerContent
|
|
10087
11045
|
}),
|
|
10088
|
-
|
|
10089
|
-
direction: sortEntry == null ? void 0 : sortEntry.direction
|
|
10090
|
-
})
|
|
11046
|
+
filterTrigger
|
|
10091
11047
|
]
|
|
10092
|
-
})
|
|
10093
|
-
|
|
11048
|
+
}) : headerContent,
|
|
11049
|
+
resizeHandle
|
|
11050
|
+
]
|
|
11051
|
+
}));
|
|
11052
|
+
}
|
|
11053
|
+
/**
|
|
11054
|
+
* Resolve the effective width (px) for a column. User-driven resize
|
|
11055
|
+
* (`columnWidths[field]`) wins over the static `col.width`. Returns
|
|
11056
|
+
* `undefined` if neither source has a width set — the consumer falls
|
|
11057
|
+
* back to the column's `flex` value or browser auto-layout.
|
|
11058
|
+
*/ function resolveColumnWidth(col, columnWidths) {
|
|
11059
|
+
const resized = columnWidths[col.field];
|
|
11060
|
+
if (resized != null) return resized;
|
|
11061
|
+
return col.width;
|
|
11062
|
+
}
|
|
11063
|
+
/**
|
|
11064
|
+
* Resolve the filter UI type for a column. Explicit `col.filterType`
|
|
11065
|
+
* wins; otherwise we map the auto-detected primitive type:
|
|
11066
|
+
* - `number` → number range
|
|
11067
|
+
* - `boolean` → true/false/all select
|
|
11068
|
+
* - `date` → date range
|
|
11069
|
+
* - anything else (string / unknown) → text contains
|
|
11070
|
+
*/ function resolveFilterType(col, inferred) {
|
|
11071
|
+
if (col.filterType) return col.filterType;
|
|
11072
|
+
switch(inferred){
|
|
11073
|
+
case 'number':
|
|
11074
|
+
return 'number';
|
|
11075
|
+
case 'boolean':
|
|
11076
|
+
return 'boolean';
|
|
11077
|
+
case 'date':
|
|
11078
|
+
return 'date';
|
|
11079
|
+
default:
|
|
11080
|
+
return 'text';
|
|
10094
11081
|
}
|
|
10095
|
-
return jsx("th", {
|
|
10096
|
-
scope: "col",
|
|
10097
|
-
className: cn(className, alignClass),
|
|
10098
|
-
style: cellStyle,
|
|
10099
|
-
children: headerContent
|
|
10100
|
-
});
|
|
10101
11082
|
}
|
|
10102
11083
|
// ───── Virtualized body ─────
|
|
10103
11084
|
function renderVirtualizedBody(args) {
|
|
10104
|
-
const { vWindow, renderedRows, visibleCols, columnTypes, selection, rowSelection, showSelectionColumn, showRowActionsColumn, rowActions, getRowId, isInteractive, labels, classes } = args;
|
|
11085
|
+
const { vWindow, renderedRows, visibleCols, columnTypes, columnWidths, selection, rowSelection, showSelectionColumn, showRowActionsColumn, rowActions, getRowId, isInteractive, labels, classes } = args;
|
|
10105
11086
|
return jsxs(Fragment$1, {
|
|
10106
11087
|
children: [
|
|
10107
11088
|
vWindow.paddingTop > 0 && jsx("tr", {
|
|
@@ -10136,8 +11117,10 @@ function renderVirtualizedBody(args) {
|
|
|
10136
11117
|
rowIndex: absoluteIdx,
|
|
10137
11118
|
isSelected: isSelected,
|
|
10138
11119
|
inferredType: columnTypes.get(col.field),
|
|
11120
|
+
effectiveWidth: resolveColumnWidth(col, columnWidths),
|
|
10139
11121
|
cellClass: classes.cell,
|
|
10140
|
-
stickyLeftClass: classes.stickyLeftCell
|
|
11122
|
+
stickyLeftClass: classes.stickyLeftCell,
|
|
11123
|
+
stickyRightClass: classes.stickyRightCell
|
|
10141
11124
|
}, col.field)),
|
|
10142
11125
|
showRowActionsColumn && rowActions && jsx("td", {
|
|
10143
11126
|
className: classes.rowActionsCell,
|
|
@@ -10157,7 +11140,7 @@ function renderVirtualizedBody(args) {
|
|
|
10157
11140
|
}
|
|
10158
11141
|
// ───── Data cell ─────
|
|
10159
11142
|
function DataCell(props) {
|
|
10160
|
-
const { col, row, rowIndex, isSelected, inferredType, cellClass, stickyLeftClass } = props;
|
|
11143
|
+
const { col, row, rowIndex, isSelected, inferredType, effectiveWidth, cellClass, stickyLeftClass, stickyRightClass } = props;
|
|
10161
11144
|
const value = getNestedValue(row, col.field);
|
|
10162
11145
|
const align = resolveAlign(col, inferredType);
|
|
10163
11146
|
const tabularNums = resolveTabularNums(col, inferredType);
|
|
@@ -10177,9 +11160,9 @@ function DataCell(props) {
|
|
|
10177
11160
|
access: columnAccessState
|
|
10178
11161
|
}) : renderDefaultCell(value);
|
|
10179
11162
|
return jsx("td", {
|
|
10180
|
-
className: cn(cellClass, alignClass, tabularNums && 'tabular-nums', monospace && 'font-mono', col.sticky === 'left' && stickyLeftClass),
|
|
11163
|
+
className: cn(cellClass, alignClass, tabularNums && 'tabular-nums', monospace && 'font-mono', col.sticky === 'left' && stickyLeftClass, col.sticky === 'right' && stickyRightClass),
|
|
10181
11164
|
style: {
|
|
10182
|
-
width:
|
|
11165
|
+
width: effectiveWidth != null ? `${effectiveWidth}px` : undefined,
|
|
10183
11166
|
minWidth: col.minWidth != null ? `${col.minWidth}px` : undefined
|
|
10184
11167
|
},
|
|
10185
11168
|
children: content
|