@shival99/z-ui 1.9.11 → 1.9.13
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/assets/css/base.css +0 -16
- package/fesm2022/shival99-z-ui-components-z-calendar.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-components-z-drawer.mjs +7 -2
- package/fesm2022/shival99-z-ui-components-z-drawer.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-components-z-filter.mjs +150 -3
- package/fesm2022/shival99-z-ui-components-z-filter.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-components-z-kanban.mjs +2 -2
- package/fesm2022/shival99-z-ui-components-z-kanban.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-components-z-modal.mjs +13 -6
- package/fesm2022/shival99-z-ui-components-z-modal.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-components-z-select.mjs +4 -3
- package/fesm2022/shival99-z-ui-components-z-select.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-components-z-table.mjs +219 -0
- package/fesm2022/shival99-z-ui-components-z-table.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-components-z-timeline.mjs +43 -261
- package/fesm2022/shival99-z-ui-components-z-timeline.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-components-z-upload.mjs +1 -4
- package/fesm2022/shival99-z-ui-components-z-upload.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-providers.mjs +6 -2
- package/fesm2022/shival99-z-ui-providers.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-services.mjs +71 -4
- package/fesm2022/shival99-z-ui-services.mjs.map +1 -1
- package/package.json +1 -1
- package/types/shival99-z-ui-components-z-autocomplete.d.ts +1 -1
- package/types/shival99-z-ui-components-z-calendar.d.ts +4 -4
- package/types/shival99-z-ui-components-z-drawer.d.ts +2 -0
- package/types/shival99-z-ui-components-z-editor.d.ts +1 -1
- package/types/shival99-z-ui-components-z-filter.d.ts +17 -0
- package/types/shival99-z-ui-components-z-modal.d.ts +5 -2
- package/types/shival99-z-ui-components-z-popover.d.ts +1 -1
- package/types/shival99-z-ui-components-z-select.d.ts +1 -1
- package/types/shival99-z-ui-components-z-table.d.ts +205 -1
- package/types/shival99-z-ui-components-z-timeline.d.ts +20 -62
- package/types/shival99-z-ui-components-z-upload.d.ts +3 -3
- package/types/shival99-z-ui-providers.d.ts +6 -2
- package/types/shival99-z-ui-services.d.ts +26 -1
|
@@ -33,14 +33,24 @@ import { ZSelectComponent } from '@shival99/z-ui/components/z-select';
|
|
|
33
33
|
import { ZSwitchComponent } from '@shival99/z-ui/components/z-switch';
|
|
34
34
|
import { Subject, debounceTime, distinctUntilChanged } from 'rxjs';
|
|
35
35
|
|
|
36
|
+
// ─── Default Constants ───────────────────────────────────────────────────────
|
|
37
|
+
/** Default row height in pixels for virtual scroll estimation */
|
|
36
38
|
const Z_DEFAULT_ROW_HEIGHT = 40;
|
|
39
|
+
/** How many extra rows to render outside the visible viewport (virtual scroll buffer) */
|
|
37
40
|
const Z_DEFAULT_VIRTUAL_OVERSCAN = 5;
|
|
41
|
+
/** Number of rows per virtual group — used for rowSpan grouping in virtual mode */
|
|
38
42
|
const Z_DEFAULT_GROUP_SIZE = 1;
|
|
43
|
+
/** Minimum column width in pixels; prevents columns from becoming too narrow */
|
|
39
44
|
const Z_DEFAULT_COLUMN_MIN_SIZE = 100;
|
|
45
|
+
/** Debounce delay (ms) for async state updates (filtering/sorting transitions) */
|
|
40
46
|
const Z_DEFAULT_DEBOUNCE_TIME = 150;
|
|
47
|
+
/** Row height used for skeleton loading placeholders */
|
|
41
48
|
const Z_SKELETON_ROW_HEIGHT = 60;
|
|
49
|
+
/** Width in pixels of each action button in the actions column */
|
|
42
50
|
const Z_TABLE_DEFAULT_ACTION_BUTTON_WIDTH = 32;
|
|
51
|
+
/** Max visible action buttons before overflow into dropdown */
|
|
43
52
|
const Z_TABLE_DEFAULT_MAX_VISIBLE_ACTIONS = 3;
|
|
53
|
+
/** Default size variant for the overflow dropdown trigger button */
|
|
44
54
|
const Z_TABLE_DEFAULT_DROPDOWN_BUTTON_SIZE = 'sm';
|
|
45
55
|
|
|
46
56
|
class ZTableActionsComponent {
|
|
@@ -246,6 +256,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
246
256
|
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}\n"] }]
|
|
247
257
|
}], propDecorators: { zConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "zConfig", required: true }] }], zRow: [{ type: i0.Input, args: [{ isSignal: true, alias: "zRow", required: true }] }], zRowId: [{ type: i0.Input, args: [{ isSignal: true, alias: "zRowId", required: true }] }], zDropdownButtonSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "zDropdownButtonSize", required: false }] }], zActionClick: [{ type: i0.Output, args: ["zActionClick"] }] } });
|
|
248
258
|
|
|
259
|
+
// ─── Column Visibility Pre-filter ────────────────────────────────────────────
|
|
260
|
+
/**
|
|
261
|
+
* Recursively filters columns based on their `visible` property.
|
|
262
|
+
* This runs BEFORE TanStack table creation — columns excluded here
|
|
263
|
+
* won't generate ColumnDef entries at all (unlike columnVisibility state
|
|
264
|
+
* which hides columns but keeps them in the column model).
|
|
265
|
+
*/
|
|
249
266
|
const filterVisibleColumns = (columns) => columns
|
|
250
267
|
.filter(col => {
|
|
251
268
|
const { visible } = col;
|
|
@@ -263,15 +280,29 @@ const filterVisibleColumns = (columns) => columns
|
|
|
263
280
|
}
|
|
264
281
|
return col;
|
|
265
282
|
});
|
|
283
|
+
// ─── Config Type Guards ──────────────────────────────────────────────────────
|
|
284
|
+
/**
|
|
285
|
+
* Checks if a config value is a plain object (config struct) vs a primitive/template.
|
|
286
|
+
* Used to distinguish shorthand content (string/TemplateRef) from full config objects
|
|
287
|
+
* like ZTableHeaderColumnConfig, ZTableBodyColumnConfig, etc.
|
|
288
|
+
*/
|
|
266
289
|
const isObjectConfig = (config) => {
|
|
267
290
|
if (!config || typeof config !== 'object') {
|
|
268
291
|
return false;
|
|
269
292
|
}
|
|
270
293
|
return config.constructor === Object;
|
|
271
294
|
};
|
|
295
|
+
/** Type guard: is this a full header config object (not just content shorthand)? */
|
|
272
296
|
const isHeaderConfig = (config) => isObjectConfig(config);
|
|
297
|
+
/** Type guard: is this a full body config object (not just content shorthand)? */
|
|
273
298
|
const isBodyConfig = (config) => isObjectConfig(config);
|
|
299
|
+
/** Type guard: is this a full footer config object (not just content shorthand)? */
|
|
274
300
|
const isFooterConfig = (config) => isObjectConfig(config);
|
|
301
|
+
// ─── Config Extractors ───────────────────────────────────────────────────────
|
|
302
|
+
/**
|
|
303
|
+
* Internal helper that normalizes header/footer config into a consistent shape.
|
|
304
|
+
* Handles both shorthand (just content) and full config objects.
|
|
305
|
+
*/
|
|
275
306
|
const getHeaderOrFooterConfigInternal = (col, type) => {
|
|
276
307
|
const empty = {
|
|
277
308
|
content: undefined,
|
|
@@ -305,7 +336,12 @@ const getHeaderOrFooterConfigInternal = (col, type) => {
|
|
|
305
336
|
contentStyle: typedConfig.contentStyle,
|
|
306
337
|
};
|
|
307
338
|
};
|
|
339
|
+
/** Extract and normalize header config from a column definition */
|
|
308
340
|
const getHeaderConfig = (col) => getHeaderOrFooterConfigInternal(col, 'header');
|
|
341
|
+
/**
|
|
342
|
+
* Extract and normalize body config from a column definition.
|
|
343
|
+
* Resolves dynamic properties (class, style, rowSpan, etc.) when CellContext is provided.
|
|
344
|
+
*/
|
|
309
345
|
const getBodyConfig = (col, ctx) => {
|
|
310
346
|
const empty = {
|
|
311
347
|
content: undefined,
|
|
@@ -345,6 +381,8 @@ const getBodyConfig = (col, ctx) => {
|
|
|
345
381
|
};
|
|
346
382
|
};
|
|
347
383
|
const getFooterConfig = (col) => getHeaderOrFooterConfigInternal(col, 'footer');
|
|
384
|
+
// ─── Shortcut Accessors ──────────────────────────────────────────────────────
|
|
385
|
+
// These convenience functions extract a single property from the relevant config.
|
|
348
386
|
const getHeaderContent = (col) => getHeaderConfig(col).content;
|
|
349
387
|
const getBodyContent = (col) => {
|
|
350
388
|
if (!col?.body) {
|
|
@@ -359,6 +397,11 @@ const getHeaderRowSpan = (col) => getHeaderConfig(col).rowSpan;
|
|
|
359
397
|
const getHeaderColSpan = (col) => getHeaderConfig(col).colSpan;
|
|
360
398
|
const getFooterRowSpan = (col) => getFooterConfig(col).rowSpan;
|
|
361
399
|
const getFooterColSpan = (col) => getFooterConfig(col).colSpan;
|
|
400
|
+
// ─── Icon Syntax Parsing ─────────────────────────────────────────────────────
|
|
401
|
+
/**
|
|
402
|
+
* Parses inline icon syntax: `"Total [icon:lucideTrendingUp|size:16|class:text-green] Revenue"`
|
|
403
|
+
* Returns an array of text and icon segments for rendering by ZTableIconTextComponent.
|
|
404
|
+
*/
|
|
362
405
|
function parseIconString(content) {
|
|
363
406
|
if (!content || typeof content !== 'string') {
|
|
364
407
|
return [{ type: 'text', value: content || '' }];
|
|
@@ -398,18 +441,22 @@ function parseIconString(content) {
|
|
|
398
441
|
}
|
|
399
442
|
return parts.length > 0 ? parts : [{ type: 'text', value: content }];
|
|
400
443
|
}
|
|
444
|
+
/** Removes all `[icon:...]` syntax from a string, returning plain text */
|
|
401
445
|
function stripIconSyntax(content) {
|
|
402
446
|
if (!content || typeof content !== 'string') {
|
|
403
447
|
return content || '';
|
|
404
448
|
}
|
|
405
449
|
return content.replace(/\[icon:[^\]]+\]/g, '').trim();
|
|
406
450
|
}
|
|
451
|
+
/** Returns true if the content string contains at least one `[icon:...]` segment */
|
|
407
452
|
function hasIconSyntax(content) {
|
|
408
453
|
if (!content || typeof content !== 'string') {
|
|
409
454
|
return false;
|
|
410
455
|
}
|
|
411
456
|
return /\[icon:[^\]]+\]/.test(content);
|
|
412
457
|
}
|
|
458
|
+
// ─── Column Lookup ───────────────────────────────────────────────────────────
|
|
459
|
+
/** Recursively search for a column config by ID within a (possibly nested) column array */
|
|
413
460
|
const findColumnConfig = (columnId, columns) => {
|
|
414
461
|
for (const col of columns) {
|
|
415
462
|
if (col.id === columnId) {
|
|
@@ -424,9 +471,16 @@ const findColumnConfig = (columnId, columns) => {
|
|
|
424
471
|
}
|
|
425
472
|
return undefined;
|
|
426
473
|
};
|
|
474
|
+
// ─── Span Calculation ────────────────────────────────────────────────────────
|
|
475
|
+
/**
|
|
476
|
+
* Walk down a chain of placeholder headers to find the deepest real header.
|
|
477
|
+
* Used to calculate how many rows a header cell should span in multi-level headers.
|
|
478
|
+
* Returns null if the header is already the deepest (no spanning needed).
|
|
479
|
+
*/
|
|
427
480
|
const deepestHeader = (header) => {
|
|
428
481
|
let last = header;
|
|
429
482
|
while (true) {
|
|
483
|
+
// Follow single-child placeholder chains (placeholders with colSpan=1)
|
|
430
484
|
const next = last.isPlaceholder && last.colSpan === 1 && last.subHeaders.length === 1 ? last.subHeaders[0] : null;
|
|
431
485
|
if (!next) {
|
|
432
486
|
return last === header ? null : last;
|
|
@@ -434,12 +488,20 @@ const deepestHeader = (header) => {
|
|
|
434
488
|
last = next;
|
|
435
489
|
}
|
|
436
490
|
};
|
|
491
|
+
/** Calculate rowSpan for a TanStack header based on its depth vs deepest sub-header */
|
|
437
492
|
const tableHeaderRowSpan = (header) => {
|
|
438
493
|
const deepest = deepestHeader(header);
|
|
439
494
|
const rowSpan = (deepest ? deepest.depth - header.depth : 0) + 1;
|
|
495
|
+
// If the header is more than 1 level above its column's natural depth, it's been
|
|
496
|
+
// consumed by a parent's rowSpan — return null to skip rendering
|
|
440
497
|
const above = header.depth - header.column.depth;
|
|
441
498
|
return above > 1 ? null : rowSpan;
|
|
442
499
|
};
|
|
500
|
+
/**
|
|
501
|
+
* Calculate rowSpan for body cells with static or dynamic rowSpan config.
|
|
502
|
+
* For static rowSpan: uses modular arithmetic (filteredIndex % rowSpan === 0)
|
|
503
|
+
* to determine which rows get the merged cell vs which are skipped.
|
|
504
|
+
*/
|
|
443
505
|
const calculateCellRowSpan = (cell, columnConfig, allRows) => {
|
|
444
506
|
if (!columnConfig?.body || !isBodyConfig(columnConfig.body)) {
|
|
445
507
|
return null;
|
|
@@ -565,6 +627,15 @@ const calculateFooterColSpan = (header, columnConfig) => {
|
|
|
565
627
|
}
|
|
566
628
|
return header.colSpan > 1 ? header.colSpan : null;
|
|
567
629
|
};
|
|
630
|
+
// ─── Render-Skip Logic ───────────────────────────────────────────────────────
|
|
631
|
+
//
|
|
632
|
+
// These functions determine which cells should NOT be rendered because they
|
|
633
|
+
// are "covered" by a neighboring cell's rowSpan or colSpan. This prevents
|
|
634
|
+
// duplicate DOM elements in merged cell scenarios.
|
|
635
|
+
/**
|
|
636
|
+
* Checks if a body cell should be rendered based on rowSpan rules.
|
|
637
|
+
* Returns false for cells that fall within another cell's rowSpan range.
|
|
638
|
+
*/
|
|
568
639
|
const shouldCellRenderUtil = (cell, columnConfig, allRows) => {
|
|
569
640
|
if (!columnConfig?.body || !isBodyConfig(columnConfig.body)) {
|
|
570
641
|
return true;
|
|
@@ -587,6 +658,11 @@ const shouldCellRenderUtil = (cell, columnConfig, allRows) => {
|
|
|
587
658
|
}
|
|
588
659
|
return true;
|
|
589
660
|
};
|
|
661
|
+
/**
|
|
662
|
+
* Shared logic for determining if a header or footer cell should render.
|
|
663
|
+
* A cell should NOT render if a previous cell in the same row has a colSpan
|
|
664
|
+
* that covers this cell's position. Handles pinned column edge cases.
|
|
665
|
+
*/
|
|
590
666
|
const shouldHeaderOrFooterRender = (header, allHeaders, columns, type) => {
|
|
591
667
|
const currentIndex = allHeaders.findIndex(h => h.id === header.id);
|
|
592
668
|
if (currentIndex <= 0) {
|
|
@@ -643,6 +719,18 @@ const shouldBodyCellRenderColSpan = (cell, allCells, columns) => {
|
|
|
643
719
|
};
|
|
644
720
|
const shouldFooterRender = (header, allHeaders, columns) => shouldHeaderOrFooterRender(header, allHeaders, columns, 'footer');
|
|
645
721
|
const shouldHeaderRender = (header, allHeaders, columns) => shouldHeaderOrFooterRender(header, allHeaders, columns, 'header');
|
|
722
|
+
// ─── Column Config → TanStack ColumnDef Conversion ──────────────────────────
|
|
723
|
+
/**
|
|
724
|
+
* Converts the consumer-facing ZTableColumnConfig into a TanStack ColumnDef.
|
|
725
|
+
* This is the bridge between the z-table API and TanStack Table internals.
|
|
726
|
+
*
|
|
727
|
+
* Handles:
|
|
728
|
+
* - Accessor mapping (accessorKey / accessorFn)
|
|
729
|
+
* - Header/body/footer content resolution
|
|
730
|
+
* - Sort and filter function wiring (local vs server mode)
|
|
731
|
+
* - Built-in filter functions for each filter type
|
|
732
|
+
* - Recursive nested column conversion
|
|
733
|
+
*/
|
|
646
734
|
function columnConfigToColumnDef(config) {
|
|
647
735
|
const sortConfig = typeof config.sort === 'boolean' ? (config.sort ? { enabled: true } : undefined) : config.sort;
|
|
648
736
|
const filterConfig = typeof config.filter === 'boolean' ? (config.filter ? { enabled: true } : undefined) : config.filter;
|
|
@@ -822,12 +910,15 @@ function columnConfigToColumnDef(config) {
|
|
|
822
910
|
}
|
|
823
911
|
return columnDef;
|
|
824
912
|
}
|
|
913
|
+
// ─── Generic Utility Helpers ─────────────────────────────────────────────────
|
|
914
|
+
/** Resolve a value that may be static or a function of the row data */
|
|
825
915
|
const resolveConfigValue = (config, row, defaultValue) => {
|
|
826
916
|
if (config === undefined) {
|
|
827
917
|
return defaultValue;
|
|
828
918
|
}
|
|
829
919
|
return typeof config === 'function' ? config(row) : config;
|
|
830
920
|
};
|
|
921
|
+
/** Pick only defined (non-undefined) keys from an object — used to spread optional virtualizer config */
|
|
831
922
|
const pickDefined = (obj, keys) => keys.reduce((acc, key) => {
|
|
832
923
|
if (obj[key] !== undefined) {
|
|
833
924
|
acc[key] = obj[key];
|
|
@@ -2962,22 +3053,64 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
2962
3053
|
}] });
|
|
2963
3054
|
|
|
2964
3055
|
/* eslint-disable @stylistic/indent */
|
|
3056
|
+
/**
|
|
3057
|
+
* Z-Table Component
|
|
3058
|
+
*
|
|
3059
|
+
* Feature-rich data table built on @tanstack/angular-table. Supports:
|
|
3060
|
+
* - Local and server-side sorting, filtering, and pagination
|
|
3061
|
+
* - Virtual scrolling with rowSpan-aware grouping
|
|
3062
|
+
* - Column pinning (left/right) with scroll shadow indicators
|
|
3063
|
+
* - Column resizing, reordering, and visibility toggling
|
|
3064
|
+
* - Inline cell editing (text, number, select, date, etc.)
|
|
3065
|
+
* - Row selection (single/multi with sub-row support)
|
|
3066
|
+
* - Row expansion with custom templates
|
|
3067
|
+
* - Row pinning (top/bottom)
|
|
3068
|
+
* - Action buttons column with overflow dropdown
|
|
3069
|
+
* - Settings drawer for column management
|
|
3070
|
+
* - Persistent settings via ZCacheService (keyed by zKey)
|
|
3071
|
+
* - Skeleton loading placeholder
|
|
3072
|
+
* - Search bar with debounce
|
|
3073
|
+
*
|
|
3074
|
+
* Architecture notes:
|
|
3075
|
+
* - All template computations use pipes (OnPush compliance, no method calls in templates)
|
|
3076
|
+
* - Scroll sync between thead/tbody/tfoot is manual (independent scrollable containers)
|
|
3077
|
+
* - Virtual scrolling groups rows to handle rowSpan correctly
|
|
3078
|
+
* - Each column's sort/filter can independently be 'local' or 'server' mode
|
|
3079
|
+
*/
|
|
2965
3080
|
class ZTableComponent {
|
|
3081
|
+
// ─── Outputs ──────────────────────────────────────────────────────────────
|
|
3082
|
+
/** Unified change event — parent listens to this for all table interactions */
|
|
2966
3083
|
zChange = output();
|
|
3084
|
+
/** Emits an imperative control handle for programmatic table manipulation */
|
|
2967
3085
|
zControl = output();
|
|
3086
|
+
// ─── Inputs ───────────────────────────────────────────────────────────────
|
|
3087
|
+
/** Extra CSS classes merged onto the table container */
|
|
2968
3088
|
zClass = input('', ...(ngDevMode ? [{ debugName: "zClass" }] : []));
|
|
3089
|
+
/** Main configuration — data, columns, mode, features */
|
|
2969
3090
|
zConfig = input({ data: [], columns: [], mode: 'local' }, ...(ngDevMode ? [{ debugName: "zConfig" }] : []));
|
|
3091
|
+
/** External loading state (complementary to config.loading) */
|
|
2970
3092
|
zLoading = input(false, ...(ngDevMode ? [{ debugName: "zLoading" }] : []));
|
|
3093
|
+
/** Cache key for persisting column settings; no persistence when empty */
|
|
2971
3094
|
zKey = input('', ...(ngDevMode ? [{ debugName: "zKey" }] : []));
|
|
3095
|
+
/** Visual variant: 'default' has card shadow, 'borderless' has no container border */
|
|
2972
3096
|
zVariant = input('default', ...(ngDevMode ? [{ debugName: "zVariant" }] : []));
|
|
3097
|
+
// ─── Private State ────────────────────────────────────────────────────────
|
|
2973
3098
|
_destroy$ = injectDestroy();
|
|
2974
3099
|
_zTranslate = inject(ZTranslateService);
|
|
3100
|
+
/** Prevents recursive scroll sync between thead/tbody/tfoot */
|
|
2975
3101
|
_isSyncingScroll = signal(false, ...(ngDevMode ? [{ debugName: "_isSyncingScroll" }] : []));
|
|
3102
|
+
/** Preserves horizontal scroll position across loading states */
|
|
2976
3103
|
_savedScrollLeft = signal(0, ...(ngDevMode ? [{ debugName: "_savedScrollLeft" }] : []));
|
|
3104
|
+
/** Observes tbody container height changes for skeleton row count calculation */
|
|
2977
3105
|
_resizeObserver = null;
|
|
3106
|
+
/** Debounces rapid settings changes (visibility/pin toggles) */
|
|
2978
3107
|
_settingsDebounceTimeout = null;
|
|
3108
|
+
/** Debounces filter change emissions to server */
|
|
2979
3109
|
_filterEmitDebounceTimeout = null;
|
|
3110
|
+
// ─── Template-bound State ─────────────────────────────────────────────────
|
|
3111
|
+
/** Merged loading state from both zLoading input and config.loading */
|
|
2980
3112
|
isLoading = computed(() => this.zConfig().loading ?? this.zLoading(), ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
3113
|
+
/** True during debounced async state transitions (sort/filter processing) */
|
|
2981
3114
|
isProcessing = signal(false, ...(ngDevMode ? [{ debugName: "isProcessing" }] : []));
|
|
2982
3115
|
theadWrapper = viewChild('theadWrapper', ...(ngDevMode ? [{ debugName: "theadWrapper" }] : []));
|
|
2983
3116
|
tbodyContainer = viewChild('tbodyContainer', ...(ngDevMode ? [{ debugName: "tbodyContainer" }] : []));
|
|
@@ -2986,6 +3119,9 @@ class ZTableComponent {
|
|
|
2986
3119
|
tfootWrapper = viewChild('tfootWrapper', ...(ngDevMode ? [{ debugName: "tfootWrapper" }] : []));
|
|
2987
3120
|
expandedRowTemplate = viewChild('expandedRowTemplate', ...(ngDevMode ? [{ debugName: "expandedRowTemplate" }] : []));
|
|
2988
3121
|
virtualRowElements = viewChildren('virtualRow', ...(ngDevMode ? [{ debugName: "virtualRowElements" }] : []));
|
|
3122
|
+
// ─── TanStack Table State Signals ─────────────────────────────────────────
|
|
3123
|
+
// These signals are the source of truth for table state. They are passed into
|
|
3124
|
+
// createAngularTable() and updated via the on*Change callbacks.
|
|
2989
3125
|
columnPinning = signal({ left: [], right: [] }, ...(ngDevMode ? [{ debugName: "columnPinning" }] : []));
|
|
2990
3126
|
columnVisibility = signal({}, ...(ngDevMode ? [{ debugName: "columnVisibility" }] : []));
|
|
2991
3127
|
columnOrder = signal([], ...(ngDevMode ? [{ debugName: "columnOrder" }] : []));
|
|
@@ -2994,10 +3130,17 @@ class ZTableComponent {
|
|
|
2994
3130
|
rowPinning = signal({ top: [], bottom: [] }, ...(ngDevMode ? [{ debugName: "rowPinning" }] : []));
|
|
2995
3131
|
columnFilters = signal([], ...(ngDevMode ? [{ debugName: "columnFilters" }] : []));
|
|
2996
3132
|
globalFilter = signal('', ...(ngDevMode ? [{ debugName: "globalFilter" }] : []));
|
|
3133
|
+
/** Note: pageIndex is 1-based here; converted to 0-based for TanStack via _tanstackPagination */
|
|
2997
3134
|
pagination = signal({ pageIndex: 1, pageSize: 10 }, ...(ngDevMode ? [{ debugName: "pagination" }] : []));
|
|
2998
3135
|
sorting = signal([], ...(ngDevMode ? [{ debugName: "sorting" }] : []));
|
|
3136
|
+
/**
|
|
3137
|
+
* Caches the last server-provided total to avoid flickering to 0
|
|
3138
|
+
* during loading transitions in server-side pagination mode.
|
|
3139
|
+
*/
|
|
2999
3140
|
_serverPaginationTotal = signal(0, ...(ngDevMode ? [{ debugName: "_serverPaginationTotal" }] : []));
|
|
3141
|
+
/** Container CSS classes combining variant + user-provided classes */
|
|
3000
3142
|
classTable = computed(() => zMergeClasses('z-table-container', this.zVariant() === 'default' ? 'shadow-card' : 'z-table-borderless', this.zClass()), ...(ngDevMode ? [{ debugName: "classTable" }] : []));
|
|
3143
|
+
/** Convert 1-based pagination to TanStack's 0-based pageIndex */
|
|
3001
3144
|
_tanstackPagination = computed(() => ({
|
|
3002
3145
|
...this.pagination(),
|
|
3003
3146
|
pageIndex: Math.max(0, this.pagination().pageIndex - 1),
|
|
@@ -3026,19 +3169,32 @@ class ZTableComponent {
|
|
|
3026
3169
|
}
|
|
3027
3170
|
return this.table.getFilteredRowModel().rows.length;
|
|
3028
3171
|
}, ...(ngDevMode ? [{ debugName: "paginationTotal" }] : []));
|
|
3172
|
+
// ─── Scroll & Border State ────────────────────────────────────────────────
|
|
3029
3173
|
hasVerticalScroll = signal(false, ...(ngDevMode ? [{ debugName: "hasVerticalScroll" }] : []));
|
|
3030
3174
|
hasHorizontalScroll = signal(false, ...(ngDevMode ? [{ debugName: "hasHorizontalScroll" }] : []));
|
|
3175
|
+
/** True when the last row touches or extends past the container bottom (no gap) */
|
|
3031
3176
|
lastRowTouchesBottom = signal(true, ...(ngDevMode ? [{ debugName: "lastRowTouchesBottom" }] : []));
|
|
3032
3177
|
showSettingsDrawer = signal(false, ...(ngDevMode ? [{ debugName: "showSettingsDrawer" }] : []));
|
|
3033
3178
|
showHorizontalBorder = signal(true, ...(ngDevMode ? [{ debugName: "showHorizontalBorder" }] : []));
|
|
3034
3179
|
showVerticalBorder = signal(true, ...(ngDevMode ? [{ debugName: "showVerticalBorder" }] : []));
|
|
3035
3180
|
showHeaderFooterShadow = signal(true, ...(ngDevMode ? [{ debugName: "showHeaderFooterShadow" }] : []));
|
|
3181
|
+
/** True when table body is scrolled away from left edge (shows left pin shadow) */
|
|
3036
3182
|
hasScrollLeft = signal(false, ...(ngDevMode ? [{ debugName: "hasScrollLeft" }] : []));
|
|
3183
|
+
/** True when table has right-pinned columns and body isn't scrolled to the end */
|
|
3037
3184
|
hasScrollRight = signal(false, ...(ngDevMode ? [{ debugName: "hasScrollRight" }] : []));
|
|
3185
|
+
/** Maps row IDs to measured DOM heights for pinned row offset calculations */
|
|
3038
3186
|
pinnedRowHeights = signal({}, ...(ngDevMode ? [{ debugName: "pinnedRowHeights" }] : []));
|
|
3187
|
+
// ─── Internal Change Tracking ──────────────────────────────────────────────
|
|
3188
|
+
/** Bumped to force recomputation of pinned column IDs after programmatic pin changes */
|
|
3039
3189
|
_columnPinVersion = signal(0, ...(ngDevMode ? [{ debugName: "_columnPinVersion" }] : []));
|
|
3190
|
+
/** Bumped to trigger data refresh (e.g., after addItem/deleteItem via control API) */
|
|
3040
3191
|
_dataForceUpdate = signal(0, ...(ngDevMode ? [{ debugName: "_dataForceUpdate" }] : []));
|
|
3192
|
+
/** Set when a single row is updated via control API (for targeted re-render) */
|
|
3041
3193
|
_rowUpdate = signal(null, ...(ngDevMode ? [{ debugName: "_rowUpdate" }] : []));
|
|
3194
|
+
/**
|
|
3195
|
+
* Column config lookup cache — cleared when column reference changes.
|
|
3196
|
+
* Avoids repeated recursive searches during render cycles.
|
|
3197
|
+
*/
|
|
3042
3198
|
_columnConfigCache = new Map();
|
|
3043
3199
|
_lastColumnsRef = null;
|
|
3044
3200
|
pinnedColumnIds = computed(() => {
|
|
@@ -3054,6 +3210,10 @@ class ZTableComponent {
|
|
|
3054
3210
|
pendingVisibleColumns = signal([], ...(ngDevMode ? [{ debugName: "pendingVisibleColumns" }] : []));
|
|
3055
3211
|
pendingColumnOrder = signal([], ...(ngDevMode ? [{ debugName: "pendingColumnOrder" }] : []));
|
|
3056
3212
|
pendingShowHeaderFooterShadow = signal(true, ...(ngDevMode ? [{ debugName: "pendingShowHeaderFooterShadow" }] : []));
|
|
3213
|
+
// ─── Feature Detection Computeds ──────────────────────────────────────────
|
|
3214
|
+
// These computed signals scan the column config tree to determine which
|
|
3215
|
+
// features are active, avoiding feature-gating at template render time.
|
|
3216
|
+
/** True if ANY body column uses rowSpan — affects virtual grouping and expand column behavior */
|
|
3057
3217
|
hasBodyRowSpan = computed(() => {
|
|
3058
3218
|
const columns = this.zConfig().columns ?? [];
|
|
3059
3219
|
const checkRowSpan = (col) => {
|
|
@@ -3254,10 +3414,17 @@ class ZTableComponent {
|
|
|
3254
3414
|
const config = this.searchConfig();
|
|
3255
3415
|
return config !== null && config.enabled;
|
|
3256
3416
|
}, ...(ngDevMode ? [{ debugName: "isSearchEnabled" }] : []));
|
|
3417
|
+
// ─── Core TanStack Data Signals ───────────────────────────────────────────
|
|
3418
|
+
/** Data signal with force-update support for control API mutations */
|
|
3257
3419
|
_data = computed(() => {
|
|
3258
3420
|
this._dataForceUpdate();
|
|
3259
3421
|
return this.zConfig().data ?? [];
|
|
3260
3422
|
}, ...(ngDevMode ? [{ debugName: "_data" }] : []));
|
|
3423
|
+
/**
|
|
3424
|
+
* Converts ZTableColumnConfig[] into TanStack ColumnDef[].
|
|
3425
|
+
* Handles special columns (select, expand, actionRowPin), action column sizing,
|
|
3426
|
+
* and rowSpan/expand compatibility (expand column is removed when rowSpan is used).
|
|
3427
|
+
*/
|
|
3261
3428
|
_columns = computed(() => {
|
|
3262
3429
|
let cols = this.zConfig().columns ?? [];
|
|
3263
3430
|
cols = filterVisibleColumns(cols);
|
|
@@ -3310,6 +3477,7 @@ class ZTableComponent {
|
|
|
3310
3477
|
return colDef;
|
|
3311
3478
|
});
|
|
3312
3479
|
}, ...(ngDevMode ? [{ debugName: "_columns" }] : []));
|
|
3480
|
+
// ─── Virtual Scrolling ────────────────────────────────────────────────────
|
|
3313
3481
|
isVirtual = computed(() => {
|
|
3314
3482
|
const { virtual } = this.zConfig();
|
|
3315
3483
|
if (!virtual) {
|
|
@@ -3320,6 +3488,7 @@ class ZTableComponent {
|
|
|
3320
3488
|
}
|
|
3321
3489
|
return virtual.enabled;
|
|
3322
3490
|
}, ...(ngDevMode ? [{ debugName: "isVirtual" }] : []));
|
|
3491
|
+
/** Normalized virtual config with defaults applied */
|
|
3323
3492
|
_virtualConfig = computed(() => {
|
|
3324
3493
|
const { virtual } = this.zConfig();
|
|
3325
3494
|
if (!virtual || typeof virtual === 'boolean') {
|
|
@@ -3351,6 +3520,11 @@ class ZTableComponent {
|
|
|
3351
3520
|
groupSize = computed(() => this._virtualConfig().groupSize ?? Z_DEFAULT_GROUP_SIZE, ...(ngDevMode ? [{ debugName: "groupSize" }] : []));
|
|
3352
3521
|
groupHeight = computed(() => this.groupSize() * this.virtualRowHeight(), ...(ngDevMode ? [{ debugName: "groupHeight" }] : []));
|
|
3353
3522
|
_dynamicGroupsVersion = signal(0, ...(ngDevMode ? [{ debugName: "_dynamicGroupsVersion" }] : []));
|
|
3523
|
+
/**
|
|
3524
|
+
* Groups rows for virtual scrolling. When rowSpan is used, rows that share
|
|
3525
|
+
* a merged cell are grouped together so the virtualizer treats them as
|
|
3526
|
+
* a single unit, preserving the visual merge across scroll boundaries.
|
|
3527
|
+
*/
|
|
3354
3528
|
dynamicGroups = computed(() => {
|
|
3355
3529
|
if (!this.isVirtual()) {
|
|
3356
3530
|
return [];
|
|
@@ -3561,6 +3735,11 @@ class ZTableComponent {
|
|
|
3561
3735
|
}
|
|
3562
3736
|
return colSizes;
|
|
3563
3737
|
}, ...(ngDevMode ? [{ debugName: "columnSizeVars" }] : []));
|
|
3738
|
+
// ─── TanStack Table Instance ──────────────────────────────────────────────
|
|
3739
|
+
//
|
|
3740
|
+
// The central table instance. All state signals flow in, and on*Change
|
|
3741
|
+
// callbacks flow out. Features (sorting, filtering, pagination) are
|
|
3742
|
+
// conditionally enabled based on column configs and table mode.
|
|
3564
3743
|
table = createAngularTable(() => {
|
|
3565
3744
|
const config = this.zConfig();
|
|
3566
3745
|
const isServerMode = config.mode === 'server';
|
|
@@ -3726,6 +3905,7 @@ class ZTableComponent {
|
|
|
3726
3905
|
const totalRows = untracked(() => this.table.getRowModel().rows.length);
|
|
3727
3906
|
return Math.ceil(totalRows / this.groupSize());
|
|
3728
3907
|
}, ...(ngDevMode ? [{ debugName: "_virtualGroupCount" }] : []));
|
|
3908
|
+
/** Virtualizer instance — operates on groups rather than individual rows */
|
|
3729
3909
|
virtualizer = injectVirtualizer(() => {
|
|
3730
3910
|
const groups = this.dynamicGroups();
|
|
3731
3911
|
const rowHeight = this.virtualRowHeight();
|
|
@@ -3767,7 +3947,13 @@ class ZTableComponent {
|
|
|
3767
3947
|
this.virtualizer.measureElement(el.nativeElement);
|
|
3768
3948
|
}
|
|
3769
3949
|
}, ...(ngDevMode ? [{ debugName: "_measureVirtualItems" }] : []));
|
|
3950
|
+
// ─── Constructor Effects ──────────────────────────────────────────────────
|
|
3951
|
+
//
|
|
3952
|
+
// Effects run in the constructor because Angular signals + effects don't
|
|
3953
|
+
// have a separate "init" lifecycle. Each effect is responsible for one
|
|
3954
|
+
// concern (data sync, config loading, scroll reset, settings persistence, etc.)
|
|
3770
3955
|
constructor() {
|
|
3956
|
+
// Force data refresh when language changes (re-evaluates translations in cells)
|
|
3771
3957
|
explicitEffect([this._zTranslate.currentLang], () => {
|
|
3772
3958
|
this._dataForceUpdate.update(v => v + 1);
|
|
3773
3959
|
});
|
|
@@ -3961,6 +4147,7 @@ class ZTableComponent {
|
|
|
3961
4147
|
this._saveConfig();
|
|
3962
4148
|
}, { defer: true });
|
|
3963
4149
|
}
|
|
4150
|
+
// ─── Lifecycle ────────────────────────────────────────────────────────────
|
|
3964
4151
|
ngAfterViewInit() {
|
|
3965
4152
|
queueMicrotask(() => this._checkScrollState());
|
|
3966
4153
|
this.zControl.emit({
|
|
@@ -4033,6 +4220,12 @@ class ZTableComponent {
|
|
|
4033
4220
|
},
|
|
4034
4221
|
});
|
|
4035
4222
|
}
|
|
4223
|
+
// ─── Selection Helpers ────────────────────────────────────────────────────
|
|
4224
|
+
/**
|
|
4225
|
+
* Syncs parent row selection state based on children.
|
|
4226
|
+
* If all children are selected, the parent is auto-selected;
|
|
4227
|
+
* if no children are selected, the parent is deselected.
|
|
4228
|
+
*/
|
|
4036
4229
|
_handleRowSelectionWithParents(newState) {
|
|
4037
4230
|
const stateRecord = newState;
|
|
4038
4231
|
const finalState = { ...stateRecord };
|
|
@@ -4057,6 +4250,12 @@ class ZTableComponent {
|
|
|
4057
4250
|
data: { selection: finalState },
|
|
4058
4251
|
});
|
|
4059
4252
|
}
|
|
4253
|
+
// ─── Async State Helpers ──────────────────────────────────────────────────
|
|
4254
|
+
/**
|
|
4255
|
+
* Wraps state mutations with a debounced processing indicator.
|
|
4256
|
+
* Shows a brief loading state to prevent UI flicker during
|
|
4257
|
+
* filter/sort recomputation on large datasets.
|
|
4258
|
+
*/
|
|
4060
4259
|
_runAsyncStateUpdate(updateFn, forceLocal = false) {
|
|
4061
4260
|
const isServerMode = this.zConfig().mode === 'server';
|
|
4062
4261
|
const hasLocalFiltering = this.hasLocalFiltering();
|
|
@@ -4213,6 +4412,7 @@ class ZTableComponent {
|
|
|
4213
4412
|
hasRightPinnedColumns() {
|
|
4214
4413
|
return this.table.getRightLeafColumns().length > 0;
|
|
4215
4414
|
}
|
|
4415
|
+
// ─── Scroll Event Handlers ────────────────────────────────────────────────
|
|
4216
4416
|
onTbodyScroll(event) {
|
|
4217
4417
|
if (this._isSyncingScroll()) {
|
|
4218
4418
|
return;
|
|
@@ -4241,6 +4441,8 @@ class ZTableComponent {
|
|
|
4241
4441
|
this._isSyncingScroll.set(false);
|
|
4242
4442
|
});
|
|
4243
4443
|
}
|
|
4444
|
+
// ─── Sort / Pagination / Search Handlers ──────────────────────────────────
|
|
4445
|
+
/** Handles sort click; auto-adds shiftKey for multi-sort when enabled */
|
|
4244
4446
|
handleSort(event, handler) {
|
|
4245
4447
|
event.preventDefault();
|
|
4246
4448
|
if (this.zConfig().enableMultiSort) {
|
|
@@ -4329,6 +4531,7 @@ class ZTableComponent {
|
|
|
4329
4531
|
},
|
|
4330
4532
|
});
|
|
4331
4533
|
}
|
|
4534
|
+
// ─── Settings Drawer ─────────────────────────────────────────────────────
|
|
4332
4535
|
openSettingsDrawer() {
|
|
4333
4536
|
if (this.columnOrder().length === 0) {
|
|
4334
4537
|
this.columnOrder.set(this.table.getAllLeafColumns().map(col => col.id));
|
|
@@ -4484,6 +4687,8 @@ class ZTableComponent {
|
|
|
4484
4687
|
const movableColumns = order.filter(id => id !== 'select' && id !== 'expand' && id !== 'actions');
|
|
4485
4688
|
return movableColumns.length > 0 && movableColumns[movableColumns.length - 1] === columnId;
|
|
4486
4689
|
}
|
|
4690
|
+
// ─── Settings Persistence ─────────────────────────────────────────────────
|
|
4691
|
+
/** Saves current column layout, sizing, pinning, and visibility to ZCacheService */
|
|
4487
4692
|
_saveConfig() {
|
|
4488
4693
|
const key = this.zKey();
|
|
4489
4694
|
if (!key) {
|
|
@@ -4527,6 +4732,7 @@ class ZTableComponent {
|
|
|
4527
4732
|
collect(columns);
|
|
4528
4733
|
return result;
|
|
4529
4734
|
}
|
|
4735
|
+
/** Loads cached config from ZCacheService; validates schema compatibility first */
|
|
4530
4736
|
_loadConfigCache(key) {
|
|
4531
4737
|
if (!key) {
|
|
4532
4738
|
return false;
|
|
@@ -4568,6 +4774,11 @@ class ZTableComponent {
|
|
|
4568
4774
|
return false;
|
|
4569
4775
|
}
|
|
4570
4776
|
}
|
|
4777
|
+
/**
|
|
4778
|
+
* Validates cached column info against current config.
|
|
4779
|
+
* Returns false if columns have been added/removed/renamed since cache was saved,
|
|
4780
|
+
* which triggers cache invalidation.
|
|
4781
|
+
*/
|
|
4571
4782
|
_isColumnConfigValid(cachedColumnInfo) {
|
|
4572
4783
|
if (!cachedColumnInfo || cachedColumnInfo.length === 0) {
|
|
4573
4784
|
return true;
|
|
@@ -4593,6 +4804,9 @@ class ZTableComponent {
|
|
|
4593
4804
|
}
|
|
4594
4805
|
return true;
|
|
4595
4806
|
}
|
|
4807
|
+
// ─── Filter / Sort Mode Helpers ───────────────────────────────────────────
|
|
4808
|
+
// These helpers determine whether a column's sort/filter changes should be
|
|
4809
|
+
// processed locally by TanStack or emitted to the parent for server-side handling.
|
|
4596
4810
|
_getChangedFilterColumnIds(oldState, newState) {
|
|
4597
4811
|
const changedIds = [];
|
|
4598
4812
|
const oldMap = new Map(oldState.map(f => [f.id, f.value]));
|
|
@@ -4651,6 +4865,11 @@ class ZTableComponent {
|
|
|
4651
4865
|
_filterServerModeSorting(sorting) {
|
|
4652
4866
|
return sorting.filter(sort => !this._isColumnSortModeLocal(sort.id));
|
|
4653
4867
|
}
|
|
4868
|
+
// ─── Column Config Lookup (Cached) ────────────────────────────────────────
|
|
4869
|
+
/**
|
|
4870
|
+
* Recursively finds a column config by ID with caching.
|
|
4871
|
+
* Cache is invalidated when the column array reference changes.
|
|
4872
|
+
*/
|
|
4654
4873
|
_findColumnConfig(columnId) {
|
|
4655
4874
|
const { columns } = this.zConfig();
|
|
4656
4875
|
if (columns !== this._lastColumnsRef) {
|