@undevy-org/tablewright 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/hooks/table-orchestration/use-column-filter-stats.js +38 -0
- package/dist/es/hooks/table-orchestration/use-column-gaps.js +31 -0
- package/dist/es/hooks/table-orchestration/use-filtered-rows.js +32 -0
- package/dist/es/hooks/table-orchestration/use-orchestration-actions.js +182 -0
- package/dist/es/hooks/table-orchestration/use-responsive-columns.js +35 -0
- package/dist/es/hooks/useTableOrchestration.js +69 -264
- package/dist/es/index.js +2 -1
- package/dist/es/lib/column-gaps.js +5 -1
- package/dist/tablewright.cjs +390 -289
- package/dist/types/hooks/table-orchestration/use-column-filter-stats.d.ts +6 -0
- package/dist/types/hooks/table-orchestration/use-column-gaps.d.ts +15 -0
- package/dist/types/hooks/table-orchestration/use-filtered-rows.d.ts +15 -0
- package/dist/types/hooks/table-orchestration/use-orchestration-actions.d.ts +49 -0
- package/dist/types/hooks/table-orchestration/use-responsive-columns.d.ts +10 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/lib/column-gaps.d.ts +6 -0
- package/package.json +1 -1
package/dist/tablewright.cjs
CHANGED
|
@@ -4271,90 +4271,6 @@ function FilterFormToggle({
|
|
|
4271
4271
|
}
|
|
4272
4272
|
);
|
|
4273
4273
|
}
|
|
4274
|
-
function computeColumnGaps(orderedColumnIds, columnVisibility) {
|
|
4275
|
-
const gaps = [];
|
|
4276
|
-
let lastVisibleId = null;
|
|
4277
|
-
let pendingHidden = [];
|
|
4278
|
-
for (const columnId of orderedColumnIds) {
|
|
4279
|
-
if (columnVisibility[columnId] === false) {
|
|
4280
|
-
pendingHidden.push(columnId);
|
|
4281
|
-
} else {
|
|
4282
|
-
if (pendingHidden.length > 0) {
|
|
4283
|
-
gaps.push({ afterColumnId: lastVisibleId, hiddenIds: pendingHidden });
|
|
4284
|
-
pendingHidden = [];
|
|
4285
|
-
}
|
|
4286
|
-
lastVisibleId = columnId;
|
|
4287
|
-
}
|
|
4288
|
-
}
|
|
4289
|
-
if (pendingHidden.length > 0) {
|
|
4290
|
-
gaps.push({ afterColumnId: lastVisibleId, hiddenIds: pendingHidden });
|
|
4291
|
-
}
|
|
4292
|
-
return gaps;
|
|
4293
|
-
}
|
|
4294
|
-
function getField(row, fieldName) {
|
|
4295
|
-
return row[fieldName];
|
|
4296
|
-
}
|
|
4297
|
-
function computeColumnStats(rows, fieldName, config) {
|
|
4298
|
-
switch (config.type) {
|
|
4299
|
-
case "number-range": {
|
|
4300
|
-
let min = Infinity;
|
|
4301
|
-
let max = -Infinity;
|
|
4302
|
-
for (const row of rows) {
|
|
4303
|
-
const val = getField(row, fieldName);
|
|
4304
|
-
if (typeof val === "number" && Number.isFinite(val)) {
|
|
4305
|
-
if (val < min) min = val;
|
|
4306
|
-
if (val > max) max = val;
|
|
4307
|
-
}
|
|
4308
|
-
}
|
|
4309
|
-
if (!Number.isFinite(min) || !Number.isFinite(max)) return null;
|
|
4310
|
-
return { type: "number-range", min, max };
|
|
4311
|
-
}
|
|
4312
|
-
case "text": {
|
|
4313
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4314
|
-
const samples = [];
|
|
4315
|
-
for (const row of rows) {
|
|
4316
|
-
const val = getField(row, fieldName);
|
|
4317
|
-
if (typeof val === "string" && val.length > 0) {
|
|
4318
|
-
if (!seen.has(val)) {
|
|
4319
|
-
seen.add(val);
|
|
4320
|
-
if (samples.length < 3) samples.push(val);
|
|
4321
|
-
}
|
|
4322
|
-
}
|
|
4323
|
-
}
|
|
4324
|
-
if (seen.size === 0) return null;
|
|
4325
|
-
return { type: "text", samples, totalUnique: seen.size };
|
|
4326
|
-
}
|
|
4327
|
-
case "date": {
|
|
4328
|
-
const valueField = `${fieldName}Value`;
|
|
4329
|
-
let min = Infinity;
|
|
4330
|
-
let max = -Infinity;
|
|
4331
|
-
for (const row of rows) {
|
|
4332
|
-
const val = getField(row, valueField);
|
|
4333
|
-
if (typeof val === "number" && val > 0) {
|
|
4334
|
-
if (val < min) min = val;
|
|
4335
|
-
if (val > max) max = val;
|
|
4336
|
-
}
|
|
4337
|
-
}
|
|
4338
|
-
if (!Number.isFinite(min) || !Number.isFinite(max)) return null;
|
|
4339
|
-
const toDateStr = (ts) => new Date(ts).toISOString().slice(0, 10);
|
|
4340
|
-
return { type: "date", min: toDateStr(min), max: toDateStr(max) };
|
|
4341
|
-
}
|
|
4342
|
-
case "enum": {
|
|
4343
|
-
const counts = {};
|
|
4344
|
-
if (config.options) {
|
|
4345
|
-
for (const opt of config.options) counts[opt.value] = 0;
|
|
4346
|
-
}
|
|
4347
|
-
for (const row of rows) {
|
|
4348
|
-
const val = getField(row, fieldName);
|
|
4349
|
-
const key = typeof val === "boolean" ? String(val) : typeof val === "string" ? val : null;
|
|
4350
|
-
if (key !== null && key in counts) counts[key]++;
|
|
4351
|
-
}
|
|
4352
|
-
return { type: "enum", counts };
|
|
4353
|
-
}
|
|
4354
|
-
default:
|
|
4355
|
-
return null;
|
|
4356
|
-
}
|
|
4357
|
-
}
|
|
4358
4274
|
const CUSTOM_VIEW_KEY = "custom";
|
|
4359
4275
|
const defaultValueForFilter = (cfg) => {
|
|
4360
4276
|
if (cfg.type === "compound") return {};
|
|
@@ -4515,209 +4431,134 @@ function createTableOrchestrationStore({
|
|
|
4515
4431
|
};
|
|
4516
4432
|
});
|
|
4517
4433
|
}
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
initialColumnWidths,
|
|
4532
|
-
columnPriorities,
|
|
4533
|
-
wideBreakpoint = 1280,
|
|
4534
|
-
compactBreakpoint = 768
|
|
4535
|
-
} = config;
|
|
4536
|
-
const { getRowId, filterRow, getSortValue, getSearchText } = config;
|
|
4537
|
-
const [store] = React.useState(
|
|
4538
|
-
() => createTableOrchestrationStore({
|
|
4539
|
-
defaultViewKey,
|
|
4540
|
-
initialColumnVisibility,
|
|
4541
|
-
initialRowsPerPage: rowsPerPageOptions[0]
|
|
4542
|
-
})
|
|
4543
|
-
);
|
|
4544
|
-
const searchInput = zustand.useStore(store, (s) => s.searchInput);
|
|
4545
|
-
const searchQuery = zustand.useStore(store, (s) => s.searchQuery);
|
|
4546
|
-
const columnFilters = zustand.useStore(store, (s) => s.columnFilters);
|
|
4547
|
-
const newestFilterColumnId = zustand.useStore(store, (s) => s.newestFilterColumnId);
|
|
4548
|
-
const requestOpenFilterId = zustand.useStore(store, (s) => s.requestOpenFilterId);
|
|
4549
|
-
const currentSort = zustand.useStore(store, (s) => s.currentSort);
|
|
4550
|
-
const columnVisibility = zustand.useStore(store, (s) => s.columnVisibility);
|
|
4551
|
-
const storedPage = zustand.useStore(store, (s) => s.page);
|
|
4552
|
-
const rowsPerPage = zustand.useStore(store, (s) => s.rowsPerPage);
|
|
4553
|
-
const density = zustand.useStore(store, (s) => s.density);
|
|
4554
|
-
const isRefreshing = zustand.useStore(store, (s) => s.isRefreshing);
|
|
4555
|
-
const storedSelectedIds = zustand.useStore(store, (s) => s.selectedIds);
|
|
4556
|
-
const activeView = zustand.useStore(store, (s) => s.activeView);
|
|
4557
|
-
const columnsMenuOpen = zustand.useStore(store, (s) => s.columnsMenuOpen);
|
|
4558
|
-
const storedSelectedRowId = zustand.useStore(store, (s) => s.selectedRowId);
|
|
4559
|
-
const hasPriorities = Boolean(columnPriorities && Object.keys(columnPriorities).length > 0);
|
|
4560
|
-
const [isWide, setIsWide] = React.useState(true);
|
|
4561
|
-
const [isCompact, setIsCompact] = React.useState(false);
|
|
4562
|
-
React.useEffect(() => {
|
|
4563
|
-
if (!hasPriorities || typeof window === "undefined") return;
|
|
4564
|
-
const mql = window.matchMedia(`(min-width: ${wideBreakpoint}px)`);
|
|
4565
|
-
const onChange = (e) => setIsWide(e.matches);
|
|
4566
|
-
setIsWide(mql.matches);
|
|
4567
|
-
mql.addEventListener("change", onChange);
|
|
4568
|
-
return () => mql.removeEventListener("change", onChange);
|
|
4569
|
-
}, [hasPriorities, wideBreakpoint]);
|
|
4570
|
-
React.useEffect(() => {
|
|
4571
|
-
if (typeof window === "undefined") return;
|
|
4572
|
-
const mql = window.matchMedia(`(max-width: ${compactBreakpoint - 1}px)`);
|
|
4573
|
-
const onChange = (e) => setIsCompact(e.matches);
|
|
4574
|
-
setIsCompact(mql.matches);
|
|
4575
|
-
mql.addEventListener("change", onChange);
|
|
4576
|
-
return () => mql.removeEventListener("change", onChange);
|
|
4577
|
-
}, [compactBreakpoint]);
|
|
4578
|
-
const responsiveHiddenColumnIds = React.useMemo(() => {
|
|
4579
|
-
if (isWide || !columnPriorities) return EMPTY_ID_SET;
|
|
4580
|
-
const hidden = /* @__PURE__ */ new Set();
|
|
4581
|
-
for (const [id, priority] of Object.entries(columnPriorities)) {
|
|
4582
|
-
if (priority === 3) hidden.add(id);
|
|
4583
|
-
}
|
|
4584
|
-
return hidden;
|
|
4585
|
-
}, [isWide, columnPriorities]);
|
|
4586
|
-
const selectedIdsRef = React.useRef(storedSelectedIds);
|
|
4587
|
-
const headerCheckStateRef = React.useRef(false);
|
|
4588
|
-
const sortState = React.useMemo(() => {
|
|
4589
|
-
if (!currentSort) return {};
|
|
4590
|
-
return { [currentSort.columnId]: currentSort.direction };
|
|
4591
|
-
}, [currentSort]);
|
|
4592
|
-
const { widths, onPointerDown, setWidths } = useColumnResize(columnMeta, initialColumnWidths);
|
|
4593
|
-
const activePreset = React.useMemo(() => {
|
|
4594
|
-
if (activeView === "custom") {
|
|
4595
|
-
return {
|
|
4596
|
-
key: "custom",
|
|
4597
|
-
label: "Custom",
|
|
4598
|
-
filters: [],
|
|
4599
|
-
sort: null,
|
|
4600
|
-
columnVisibility: {}
|
|
4601
|
-
};
|
|
4602
|
-
}
|
|
4603
|
-
return viewPresets.find((v) => v.key === activeView) ?? viewPresets[0];
|
|
4604
|
-
}, [activeView, viewPresets]);
|
|
4605
|
-
const allColumnFilterStats = React.useMemo(() => {
|
|
4606
|
-
const result = {};
|
|
4607
|
-
for (const [columnId, cfg] of Object.entries(filterConfigs)) {
|
|
4608
|
-
if (cfg.type === "compound" && cfg.subFilters) {
|
|
4609
|
-
for (const sub of cfg.subFilters) {
|
|
4610
|
-
const s = computeColumnStats(rows, sub.field, {
|
|
4611
|
-
type: sub.type,
|
|
4612
|
-
label: sub.label,
|
|
4613
|
-
options: sub.options
|
|
4614
|
-
});
|
|
4615
|
-
if (s) result[`${columnId}:${sub.key}`] = s;
|
|
4434
|
+
function getField(row, fieldName) {
|
|
4435
|
+
return row[fieldName];
|
|
4436
|
+
}
|
|
4437
|
+
function computeColumnStats(rows, fieldName, config) {
|
|
4438
|
+
switch (config.type) {
|
|
4439
|
+
case "number-range": {
|
|
4440
|
+
let min = Infinity;
|
|
4441
|
+
let max = -Infinity;
|
|
4442
|
+
for (const row of rows) {
|
|
4443
|
+
const val = getField(row, fieldName);
|
|
4444
|
+
if (typeof val === "number" && Number.isFinite(val)) {
|
|
4445
|
+
if (val < min) min = val;
|
|
4446
|
+
if (val > max) max = val;
|
|
4616
4447
|
}
|
|
4617
|
-
} else {
|
|
4618
|
-
const s = computeColumnStats(rows, cfg.fieldName ?? columnId, cfg);
|
|
4619
|
-
if (s) result[columnId] = s;
|
|
4620
4448
|
}
|
|
4449
|
+
if (!Number.isFinite(min) || !Number.isFinite(max)) return null;
|
|
4450
|
+
return { type: "number-range", min, max };
|
|
4621
4451
|
}
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
const { columnId, direction } = currentSort;
|
|
4633
|
-
return [...filtered].sort((a, b) => {
|
|
4634
|
-
const aVal = getSortValue(a, columnId);
|
|
4635
|
-
const bVal = getSortValue(b, columnId);
|
|
4636
|
-
if (aVal < bVal) return direction === "asc" ? -1 : 1;
|
|
4637
|
-
if (aVal > bVal) return direction === "asc" ? 1 : -1;
|
|
4638
|
-
return 0;
|
|
4639
|
-
});
|
|
4640
|
-
}, [
|
|
4641
|
-
rows,
|
|
4642
|
-
searchQuery,
|
|
4643
|
-
columnFilters,
|
|
4644
|
-
currentSort,
|
|
4645
|
-
activePreset,
|
|
4646
|
-
getSearchText,
|
|
4647
|
-
filterRow,
|
|
4648
|
-
getSortValue
|
|
4649
|
-
]);
|
|
4650
|
-
const filteredColumnFilterStats = React.useMemo(() => {
|
|
4651
|
-
if (columnFilters.length === 0 && searchQuery.trim().length === 0) return null;
|
|
4652
|
-
const result = {};
|
|
4653
|
-
for (const [columnId, cfg] of Object.entries(filterConfigs)) {
|
|
4654
|
-
if (cfg.type === "compound" && cfg.subFilters) {
|
|
4655
|
-
for (const sub of cfg.subFilters) {
|
|
4656
|
-
const s = computeColumnStats(filteredRows, sub.field, {
|
|
4657
|
-
type: sub.type,
|
|
4658
|
-
label: sub.label,
|
|
4659
|
-
options: sub.options
|
|
4660
|
-
});
|
|
4661
|
-
if (s) result[`${columnId}:${sub.key}`] = s;
|
|
4452
|
+
case "text": {
|
|
4453
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4454
|
+
const samples = [];
|
|
4455
|
+
for (const row of rows) {
|
|
4456
|
+
const val = getField(row, fieldName);
|
|
4457
|
+
if (typeof val === "string" && val.length > 0) {
|
|
4458
|
+
if (!seen.has(val)) {
|
|
4459
|
+
seen.add(val);
|
|
4460
|
+
if (samples.length < 3) samples.push(val);
|
|
4461
|
+
}
|
|
4662
4462
|
}
|
|
4663
|
-
} else {
|
|
4664
|
-
const s = computeColumnStats(filteredRows, cfg.fieldName ?? columnId, cfg);
|
|
4665
|
-
if (s) result[columnId] = s;
|
|
4666
4463
|
}
|
|
4464
|
+
if (seen.size === 0) return null;
|
|
4465
|
+
return { type: "text", samples, totalUnique: seen.size };
|
|
4667
4466
|
}
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
if (!all) continue;
|
|
4678
|
-
result[key] = { all, filtered: filteredColumnFilterStats?.[key] ?? null };
|
|
4467
|
+
case "date": {
|
|
4468
|
+
const valueField = `${fieldName}Value`;
|
|
4469
|
+
let min = Infinity;
|
|
4470
|
+
let max = -Infinity;
|
|
4471
|
+
for (const row of rows) {
|
|
4472
|
+
const val = getField(row, valueField);
|
|
4473
|
+
if (typeof val === "number" && val > 0) {
|
|
4474
|
+
if (val < min) min = val;
|
|
4475
|
+
if (val > max) max = val;
|
|
4679
4476
|
}
|
|
4680
|
-
} else {
|
|
4681
|
-
const all = allColumnFilterStats[columnId];
|
|
4682
|
-
if (!all) continue;
|
|
4683
|
-
result[columnId] = { all, filtered: filteredColumnFilterStats?.[columnId] ?? null };
|
|
4684
4477
|
}
|
|
4478
|
+
if (!Number.isFinite(min) || !Number.isFinite(max)) return null;
|
|
4479
|
+
const toDateStr = (ts) => new Date(ts).toISOString().slice(0, 10);
|
|
4480
|
+
return { type: "date", min: toDateStr(min), max: toDateStr(max) };
|
|
4685
4481
|
}
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
const
|
|
4704
|
-
const
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4482
|
+
case "enum": {
|
|
4483
|
+
const counts = {};
|
|
4484
|
+
if (config.options) {
|
|
4485
|
+
for (const opt of config.options) counts[opt.value] = 0;
|
|
4486
|
+
}
|
|
4487
|
+
for (const row of rows) {
|
|
4488
|
+
const val = getField(row, fieldName);
|
|
4489
|
+
const key = typeof val === "boolean" ? String(val) : typeof val === "string" ? val : null;
|
|
4490
|
+
if (key !== null && key in counts) counts[key]++;
|
|
4491
|
+
}
|
|
4492
|
+
return { type: "enum", counts };
|
|
4493
|
+
}
|
|
4494
|
+
default:
|
|
4495
|
+
return null;
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
function computeFilterStats(rows, filterConfigs) {
|
|
4499
|
+
const result = {};
|
|
4500
|
+
for (const [columnId, cfg] of Object.entries(filterConfigs)) {
|
|
4501
|
+
if (cfg.type === "compound" && cfg.subFilters) {
|
|
4502
|
+
for (const sub of cfg.subFilters) {
|
|
4503
|
+
const s = computeColumnStats(rows, sub.field, {
|
|
4504
|
+
type: sub.type,
|
|
4505
|
+
label: sub.label,
|
|
4506
|
+
options: sub.options
|
|
4507
|
+
});
|
|
4508
|
+
if (s) result[`${columnId}:${sub.key}`] = s;
|
|
4509
|
+
}
|
|
4510
|
+
} else {
|
|
4511
|
+
const s = computeColumnStats(rows, cfg.fieldName ?? columnId, cfg);
|
|
4512
|
+
if (s) result[columnId] = s;
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4515
|
+
return result;
|
|
4516
|
+
}
|
|
4517
|
+
function useColumnFilterStats(rows, filteredRows, filterConfigs, isFiltered) {
|
|
4518
|
+
const allStats = React.useMemo(() => computeFilterStats(rows, filterConfigs), [rows, filterConfigs]);
|
|
4519
|
+
const filteredStats = React.useMemo(
|
|
4520
|
+
() => isFiltered ? computeFilterStats(filteredRows, filterConfigs) : null,
|
|
4521
|
+
[filteredRows, filterConfigs, isFiltered]
|
|
4718
4522
|
);
|
|
4719
|
-
|
|
4720
|
-
|
|
4523
|
+
return React.useMemo(() => {
|
|
4524
|
+
const result = {};
|
|
4525
|
+
for (const [key, all] of Object.entries(allStats)) {
|
|
4526
|
+
result[key] = { all, filtered: filteredStats?.[key] ?? null };
|
|
4527
|
+
}
|
|
4528
|
+
return result;
|
|
4529
|
+
}, [allStats, filteredStats]);
|
|
4530
|
+
}
|
|
4531
|
+
function gapColumnId(afterColumnId) {
|
|
4532
|
+
return `__gap_after_${afterColumnId ?? "start"}`;
|
|
4533
|
+
}
|
|
4534
|
+
function computeColumnGaps(orderedColumnIds, columnVisibility) {
|
|
4535
|
+
const gaps = [];
|
|
4536
|
+
let lastVisibleId = null;
|
|
4537
|
+
let pendingHidden = [];
|
|
4538
|
+
for (const columnId of orderedColumnIds) {
|
|
4539
|
+
if (columnVisibility[columnId] === false) {
|
|
4540
|
+
pendingHidden.push(columnId);
|
|
4541
|
+
} else {
|
|
4542
|
+
if (pendingHidden.length > 0) {
|
|
4543
|
+
gaps.push({ afterColumnId: lastVisibleId, hiddenIds: pendingHidden });
|
|
4544
|
+
pendingHidden = [];
|
|
4545
|
+
}
|
|
4546
|
+
lastVisibleId = columnId;
|
|
4547
|
+
}
|
|
4548
|
+
}
|
|
4549
|
+
if (pendingHidden.length > 0) {
|
|
4550
|
+
gaps.push({ afterColumnId: lastVisibleId, hiddenIds: pendingHidden });
|
|
4551
|
+
}
|
|
4552
|
+
return gaps;
|
|
4553
|
+
}
|
|
4554
|
+
const GAP_WIDTH = 32;
|
|
4555
|
+
function useColumnGaps({
|
|
4556
|
+
columnGroups,
|
|
4557
|
+
toggleableColumnIds,
|
|
4558
|
+
columnVisibility,
|
|
4559
|
+
columnMeta,
|
|
4560
|
+
widths
|
|
4561
|
+
}) {
|
|
4721
4562
|
const columnGaps = React.useMemo(
|
|
4722
4563
|
() => columnGroups ? computeColumnGaps(toggleableColumnIds, columnVisibility) : [],
|
|
4723
4564
|
[columnGroups, toggleableColumnIds, columnVisibility]
|
|
@@ -4725,25 +4566,56 @@ function useTableOrchestration(config) {
|
|
|
4725
4566
|
const extendedColumnMeta = React.useMemo(() => {
|
|
4726
4567
|
if (columnGaps.length === 0) return columnMeta;
|
|
4727
4568
|
const meta = { ...columnMeta };
|
|
4728
|
-
for (const gap of columnGaps) {
|
|
4729
|
-
meta[`__gap_after_${gap.afterColumnId ?? "start"}`] = { minW: 32 };
|
|
4730
|
-
}
|
|
4569
|
+
for (const gap of columnGaps) meta[gapColumnId(gap.afterColumnId)] = { minW: GAP_WIDTH };
|
|
4731
4570
|
return meta;
|
|
4732
4571
|
}, [columnGaps, columnMeta]);
|
|
4733
4572
|
const extendedWidths = React.useMemo(() => {
|
|
4734
4573
|
if (columnGaps.length === 0) return widths;
|
|
4735
4574
|
const merged = { ...widths };
|
|
4736
|
-
for (const gap of columnGaps)
|
|
4737
|
-
merged[`__gap_after_${gap.afterColumnId ?? "start"}`] = 32;
|
|
4738
|
-
}
|
|
4575
|
+
for (const gap of columnGaps) merged[gapColumnId(gap.afterColumnId)] = GAP_WIDTH;
|
|
4739
4576
|
return merged;
|
|
4740
4577
|
}, [widths, columnGaps]);
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4578
|
+
return { columnGaps, extendedColumnMeta, extendedWidths };
|
|
4579
|
+
}
|
|
4580
|
+
function useFilteredRows({
|
|
4581
|
+
rows,
|
|
4582
|
+
activePreset,
|
|
4583
|
+
searchQuery,
|
|
4584
|
+
columnFilters,
|
|
4585
|
+
currentSort,
|
|
4586
|
+
filterRow,
|
|
4587
|
+
getSearchText,
|
|
4588
|
+
getSortValue
|
|
4589
|
+
}) {
|
|
4590
|
+
return React.useMemo(() => {
|
|
4591
|
+
const search = searchQuery.trim().toLowerCase();
|
|
4592
|
+
const filtered = rows.filter((row) => {
|
|
4593
|
+
if (activePreset.customFilter && !activePreset.customFilter(row)) return false;
|
|
4594
|
+
if (search.length > 0 && !getSearchText(row).includes(search)) return false;
|
|
4595
|
+
return columnFilters.every((filter) => filterRow(row, filter));
|
|
4596
|
+
});
|
|
4597
|
+
if (!currentSort) return filtered;
|
|
4598
|
+
const { columnId, direction } = currentSort;
|
|
4599
|
+
return [...filtered].sort((a, b) => {
|
|
4600
|
+
const aVal = getSortValue(a, columnId);
|
|
4601
|
+
const bVal = getSortValue(b, columnId);
|
|
4602
|
+
if (aVal < bVal) return direction === "asc" ? -1 : 1;
|
|
4603
|
+
if (aVal > bVal) return direction === "asc" ? 1 : -1;
|
|
4604
|
+
return 0;
|
|
4605
|
+
});
|
|
4606
|
+
}, [rows, searchQuery, columnFilters, currentSort, activePreset, getSearchText, filterRow, getSortValue]);
|
|
4607
|
+
}
|
|
4608
|
+
function useOrchestrationActions({
|
|
4609
|
+
store,
|
|
4610
|
+
viewPresets,
|
|
4611
|
+
filterConfigs,
|
|
4612
|
+
columnMeta,
|
|
4613
|
+
setWidths,
|
|
4614
|
+
defaultViewKey,
|
|
4615
|
+
paginatedRows,
|
|
4616
|
+
allOnPageSelected,
|
|
4617
|
+
getRowId
|
|
4618
|
+
}) {
|
|
4747
4619
|
const handleSearchSubmit = React.useCallback(
|
|
4748
4620
|
(e) => {
|
|
4749
4621
|
e.preventDefault();
|
|
@@ -4879,6 +4751,234 @@ function useTableOrchestration(config) {
|
|
|
4879
4751
|
(v) => store.getState().setSelectedRowId(v),
|
|
4880
4752
|
[store]
|
|
4881
4753
|
);
|
|
4754
|
+
return {
|
|
4755
|
+
handleSearchSubmit,
|
|
4756
|
+
handleHeaderSort,
|
|
4757
|
+
handleViewChange,
|
|
4758
|
+
handleAddColumnFilter,
|
|
4759
|
+
handleOpenExistingFilter,
|
|
4760
|
+
handleRequestOpenHandled,
|
|
4761
|
+
handleRemoveColumnFilter,
|
|
4762
|
+
handleChangeColumnFilter,
|
|
4763
|
+
handleSetColumnFilter,
|
|
4764
|
+
handleHeaderFilterClick,
|
|
4765
|
+
handleHideColumn,
|
|
4766
|
+
handleResetColumns,
|
|
4767
|
+
handleRemoveSort,
|
|
4768
|
+
handleRemoveCustomFilter,
|
|
4769
|
+
handleClearAll,
|
|
4770
|
+
handleRefresh,
|
|
4771
|
+
handleToggleRowSelection,
|
|
4772
|
+
handleToggleHeaderCheck,
|
|
4773
|
+
handleExpandRow,
|
|
4774
|
+
handleExpandGap,
|
|
4775
|
+
handlePageChange,
|
|
4776
|
+
handleRowsPerPageChange,
|
|
4777
|
+
setSearchInput,
|
|
4778
|
+
setColumnVisibility,
|
|
4779
|
+
setDensity,
|
|
4780
|
+
setSelectedIds,
|
|
4781
|
+
setColumnsMenuOpen,
|
|
4782
|
+
setSelectedRowId
|
|
4783
|
+
};
|
|
4784
|
+
}
|
|
4785
|
+
const EMPTY_ID_SET = /* @__PURE__ */ new Set();
|
|
4786
|
+
function useResponsiveColumns(columnPriorities, wideBreakpoint, compactBreakpoint) {
|
|
4787
|
+
const hasPriorities = Boolean(columnPriorities && Object.keys(columnPriorities).length > 0);
|
|
4788
|
+
const [isWide, setIsWide] = React.useState(true);
|
|
4789
|
+
const [isCompact, setIsCompact] = React.useState(false);
|
|
4790
|
+
React.useEffect(() => {
|
|
4791
|
+
if (!hasPriorities || typeof window === "undefined") return;
|
|
4792
|
+
const mql = window.matchMedia(`(min-width: ${wideBreakpoint}px)`);
|
|
4793
|
+
const onChange = (e) => setIsWide(e.matches);
|
|
4794
|
+
setIsWide(mql.matches);
|
|
4795
|
+
mql.addEventListener("change", onChange);
|
|
4796
|
+
return () => mql.removeEventListener("change", onChange);
|
|
4797
|
+
}, [hasPriorities, wideBreakpoint]);
|
|
4798
|
+
React.useEffect(() => {
|
|
4799
|
+
if (typeof window === "undefined") return;
|
|
4800
|
+
const mql = window.matchMedia(`(max-width: ${compactBreakpoint - 1}px)`);
|
|
4801
|
+
const onChange = (e) => setIsCompact(e.matches);
|
|
4802
|
+
setIsCompact(mql.matches);
|
|
4803
|
+
mql.addEventListener("change", onChange);
|
|
4804
|
+
return () => mql.removeEventListener("change", onChange);
|
|
4805
|
+
}, [compactBreakpoint]);
|
|
4806
|
+
const responsiveHiddenColumnIds = React.useMemo(() => {
|
|
4807
|
+
if (isWide || !columnPriorities) return EMPTY_ID_SET;
|
|
4808
|
+
const hidden = /* @__PURE__ */ new Set();
|
|
4809
|
+
for (const [id, priority] of Object.entries(columnPriorities)) {
|
|
4810
|
+
if (priority === 3) hidden.add(id);
|
|
4811
|
+
}
|
|
4812
|
+
return hidden;
|
|
4813
|
+
}, [isWide, columnPriorities]);
|
|
4814
|
+
return { responsiveHiddenColumnIds, isCompact };
|
|
4815
|
+
}
|
|
4816
|
+
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
|
|
4817
|
+
function useTableOrchestration(config) {
|
|
4818
|
+
const {
|
|
4819
|
+
rows,
|
|
4820
|
+
filterConfigs,
|
|
4821
|
+
columnMeta,
|
|
4822
|
+
toggleableColumnIds,
|
|
4823
|
+
rowsPerPageOptions,
|
|
4824
|
+
viewPresets,
|
|
4825
|
+
defaultViewKey,
|
|
4826
|
+
columnGroups,
|
|
4827
|
+
initialColumnVisibility,
|
|
4828
|
+
initialColumnWidths,
|
|
4829
|
+
columnPriorities,
|
|
4830
|
+
wideBreakpoint = 1280,
|
|
4831
|
+
compactBreakpoint = 768
|
|
4832
|
+
} = config;
|
|
4833
|
+
const { getRowId, filterRow, getSortValue, getSearchText } = config;
|
|
4834
|
+
const [store] = React.useState(
|
|
4835
|
+
() => createTableOrchestrationStore({
|
|
4836
|
+
defaultViewKey,
|
|
4837
|
+
initialColumnVisibility,
|
|
4838
|
+
initialRowsPerPage: rowsPerPageOptions[0]
|
|
4839
|
+
})
|
|
4840
|
+
);
|
|
4841
|
+
const searchInput = zustand.useStore(store, (s) => s.searchInput);
|
|
4842
|
+
const searchQuery = zustand.useStore(store, (s) => s.searchQuery);
|
|
4843
|
+
const columnFilters = zustand.useStore(store, (s) => s.columnFilters);
|
|
4844
|
+
const newestFilterColumnId = zustand.useStore(store, (s) => s.newestFilterColumnId);
|
|
4845
|
+
const requestOpenFilterId = zustand.useStore(store, (s) => s.requestOpenFilterId);
|
|
4846
|
+
const currentSort = zustand.useStore(store, (s) => s.currentSort);
|
|
4847
|
+
const columnVisibility = zustand.useStore(store, (s) => s.columnVisibility);
|
|
4848
|
+
const storedPage = zustand.useStore(store, (s) => s.page);
|
|
4849
|
+
const rowsPerPage = zustand.useStore(store, (s) => s.rowsPerPage);
|
|
4850
|
+
const density = zustand.useStore(store, (s) => s.density);
|
|
4851
|
+
const isRefreshing = zustand.useStore(store, (s) => s.isRefreshing);
|
|
4852
|
+
const storedSelectedIds = zustand.useStore(store, (s) => s.selectedIds);
|
|
4853
|
+
const activeView = zustand.useStore(store, (s) => s.activeView);
|
|
4854
|
+
const columnsMenuOpen = zustand.useStore(store, (s) => s.columnsMenuOpen);
|
|
4855
|
+
const storedSelectedRowId = zustand.useStore(store, (s) => s.selectedRowId);
|
|
4856
|
+
const { responsiveHiddenColumnIds, isCompact } = useResponsiveColumns(
|
|
4857
|
+
columnPriorities,
|
|
4858
|
+
wideBreakpoint,
|
|
4859
|
+
compactBreakpoint
|
|
4860
|
+
);
|
|
4861
|
+
const selectedIdsRef = React.useRef(storedSelectedIds);
|
|
4862
|
+
const headerCheckStateRef = React.useRef(false);
|
|
4863
|
+
const sortState = React.useMemo(() => {
|
|
4864
|
+
if (!currentSort) return {};
|
|
4865
|
+
return { [currentSort.columnId]: currentSort.direction };
|
|
4866
|
+
}, [currentSort]);
|
|
4867
|
+
const { widths, onPointerDown, setWidths } = useColumnResize(columnMeta, initialColumnWidths);
|
|
4868
|
+
const activePreset = React.useMemo(() => {
|
|
4869
|
+
if (activeView === "custom") {
|
|
4870
|
+
return {
|
|
4871
|
+
key: "custom",
|
|
4872
|
+
label: "Custom",
|
|
4873
|
+
filters: [],
|
|
4874
|
+
sort: null,
|
|
4875
|
+
columnVisibility: {}
|
|
4876
|
+
};
|
|
4877
|
+
}
|
|
4878
|
+
return viewPresets.find((v) => v.key === activeView) ?? viewPresets[0];
|
|
4879
|
+
}, [activeView, viewPresets]);
|
|
4880
|
+
const filteredRows = useFilteredRows({
|
|
4881
|
+
rows,
|
|
4882
|
+
activePreset,
|
|
4883
|
+
searchQuery,
|
|
4884
|
+
columnFilters,
|
|
4885
|
+
currentSort,
|
|
4886
|
+
filterRow,
|
|
4887
|
+
getSearchText,
|
|
4888
|
+
getSortValue
|
|
4889
|
+
});
|
|
4890
|
+
const columnFilterStats = useColumnFilterStats(
|
|
4891
|
+
rows,
|
|
4892
|
+
filteredRows,
|
|
4893
|
+
filterConfigs,
|
|
4894
|
+
columnFilters.length > 0 || searchQuery.trim().length > 0
|
|
4895
|
+
);
|
|
4896
|
+
const totalPages = Math.max(1, Math.ceil(filteredRows.length / rowsPerPage));
|
|
4897
|
+
const safePage = Math.min(storedPage, totalPages);
|
|
4898
|
+
const page = safePage;
|
|
4899
|
+
const pageNumbers = React.useMemo(
|
|
4900
|
+
() => getVisiblePageNumbers(safePage, totalPages),
|
|
4901
|
+
[safePage, totalPages]
|
|
4902
|
+
);
|
|
4903
|
+
const paginatedRows = React.useMemo(() => {
|
|
4904
|
+
const start = (safePage - 1) * rowsPerPage;
|
|
4905
|
+
return filteredRows.slice(start, start + rowsPerPage);
|
|
4906
|
+
}, [filteredRows, rowsPerPage, safePage]);
|
|
4907
|
+
const filteredIdSet = React.useMemo(
|
|
4908
|
+
() => new Set(filteredRows.map(getRowId)),
|
|
4909
|
+
[filteredRows, getRowId]
|
|
4910
|
+
);
|
|
4911
|
+
const selectedRowId = storedSelectedRowId && filteredIdSet.has(storedSelectedRowId) ? storedSelectedRowId : null;
|
|
4912
|
+
const selectedIds = React.useMemo(() => {
|
|
4913
|
+
const pruned = new Set([...storedSelectedIds].filter((id) => filteredIdSet.has(id)));
|
|
4914
|
+
return pruned.size === storedSelectedIds.size ? storedSelectedIds : pruned;
|
|
4915
|
+
}, [storedSelectedIds, filteredIdSet]);
|
|
4916
|
+
useIsomorphicLayoutEffect(() => {
|
|
4917
|
+
const state = store.getState();
|
|
4918
|
+
if (state.page !== safePage) state.setPage(safePage);
|
|
4919
|
+
if (state.selectedRowId !== selectedRowId) state.setSelectedRowId(selectedRowId);
|
|
4920
|
+
if (state.selectedIds !== selectedIds) state.setSelectedIds(selectedIds);
|
|
4921
|
+
}, [store, storedPage, safePage, storedSelectedRowId, selectedRowId, storedSelectedIds, selectedIds]);
|
|
4922
|
+
const pageIds = React.useMemo(() => new Set(paginatedRows.map(getRowId)), [paginatedRows, getRowId]);
|
|
4923
|
+
const selectedOnPage = React.useMemo(
|
|
4924
|
+
() => [...selectedIds].filter((id) => pageIds.has(id)).length,
|
|
4925
|
+
[selectedIds, pageIds]
|
|
4926
|
+
);
|
|
4927
|
+
const allOnPageSelected = pageIds.size > 0 && selectedOnPage === pageIds.size;
|
|
4928
|
+
const headerCheckState = allOnPageSelected ? true : selectedOnPage > 0 ? "indeterminate" : false;
|
|
4929
|
+
const { columnGaps, extendedColumnMeta, extendedWidths } = useColumnGaps({
|
|
4930
|
+
columnGroups,
|
|
4931
|
+
toggleableColumnIds,
|
|
4932
|
+
columnVisibility,
|
|
4933
|
+
columnMeta,
|
|
4934
|
+
widths
|
|
4935
|
+
});
|
|
4936
|
+
const hiddenColumnsCount = React.useMemo(() => {
|
|
4937
|
+
return toggleableColumnIds.filter((id) => columnVisibility[id] === false).length;
|
|
4938
|
+
}, [toggleableColumnIds, columnVisibility]);
|
|
4939
|
+
const activeCustomFilterChip = activePreset.customFilterChip ?? null;
|
|
4940
|
+
selectedIdsRef.current = selectedIds;
|
|
4941
|
+
headerCheckStateRef.current = headerCheckState;
|
|
4942
|
+
const {
|
|
4943
|
+
handleSearchSubmit,
|
|
4944
|
+
handleHeaderSort,
|
|
4945
|
+
handleViewChange,
|
|
4946
|
+
handleAddColumnFilter,
|
|
4947
|
+
handleOpenExistingFilter,
|
|
4948
|
+
handleRequestOpenHandled,
|
|
4949
|
+
handleRemoveColumnFilter,
|
|
4950
|
+
handleChangeColumnFilter,
|
|
4951
|
+
handleSetColumnFilter,
|
|
4952
|
+
handleHeaderFilterClick,
|
|
4953
|
+
handleHideColumn,
|
|
4954
|
+
handleResetColumns,
|
|
4955
|
+
handleRemoveSort,
|
|
4956
|
+
handleRemoveCustomFilter,
|
|
4957
|
+
handleClearAll,
|
|
4958
|
+
handleRefresh,
|
|
4959
|
+
handleToggleRowSelection,
|
|
4960
|
+
handleToggleHeaderCheck,
|
|
4961
|
+
handleExpandRow,
|
|
4962
|
+
handleExpandGap,
|
|
4963
|
+
handlePageChange,
|
|
4964
|
+
handleRowsPerPageChange,
|
|
4965
|
+
setSearchInput,
|
|
4966
|
+
setColumnVisibility,
|
|
4967
|
+
setDensity,
|
|
4968
|
+
setSelectedIds,
|
|
4969
|
+
setColumnsMenuOpen,
|
|
4970
|
+
setSelectedRowId
|
|
4971
|
+
} = useOrchestrationActions({
|
|
4972
|
+
store,
|
|
4973
|
+
viewPresets,
|
|
4974
|
+
filterConfigs,
|
|
4975
|
+
columnMeta,
|
|
4976
|
+
setWidths,
|
|
4977
|
+
defaultViewKey,
|
|
4978
|
+
paginatedRows,
|
|
4979
|
+
allOnPageSelected,
|
|
4980
|
+
getRowId
|
|
4981
|
+
});
|
|
4882
4982
|
const getSelectedIds = React.useCallback(() => selectedIdsRef.current, []);
|
|
4883
4983
|
const getHeaderCheckState = React.useCallback(() => headerCheckStateRef.current, []);
|
|
4884
4984
|
return {
|
|
@@ -5444,6 +5544,7 @@ exports.displayTimeZone = displayTimeZone;
|
|
|
5444
5544
|
exports.formatDateParts = formatDateParts;
|
|
5445
5545
|
exports.formatEnumLabel = formatEnumLabel;
|
|
5446
5546
|
exports.formatLiveTimestamp = formatLiveTimestamp;
|
|
5547
|
+
exports.gapColumnId = gapColumnId;
|
|
5447
5548
|
exports.getCompoundValue = getCompoundValue;
|
|
5448
5549
|
exports.getDateRange = getDateRange;
|
|
5449
5550
|
exports.getNumberRange = getNumberRange;
|