@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.
Files changed (36) hide show
  1. package/assets/css/base.css +0 -16
  2. package/fesm2022/shival99-z-ui-components-z-calendar.mjs.map +1 -1
  3. package/fesm2022/shival99-z-ui-components-z-drawer.mjs +7 -2
  4. package/fesm2022/shival99-z-ui-components-z-drawer.mjs.map +1 -1
  5. package/fesm2022/shival99-z-ui-components-z-filter.mjs +150 -3
  6. package/fesm2022/shival99-z-ui-components-z-filter.mjs.map +1 -1
  7. package/fesm2022/shival99-z-ui-components-z-kanban.mjs +2 -2
  8. package/fesm2022/shival99-z-ui-components-z-kanban.mjs.map +1 -1
  9. package/fesm2022/shival99-z-ui-components-z-modal.mjs +13 -6
  10. package/fesm2022/shival99-z-ui-components-z-modal.mjs.map +1 -1
  11. package/fesm2022/shival99-z-ui-components-z-select.mjs +4 -3
  12. package/fesm2022/shival99-z-ui-components-z-select.mjs.map +1 -1
  13. package/fesm2022/shival99-z-ui-components-z-table.mjs +219 -0
  14. package/fesm2022/shival99-z-ui-components-z-table.mjs.map +1 -1
  15. package/fesm2022/shival99-z-ui-components-z-timeline.mjs +43 -261
  16. package/fesm2022/shival99-z-ui-components-z-timeline.mjs.map +1 -1
  17. package/fesm2022/shival99-z-ui-components-z-upload.mjs +1 -4
  18. package/fesm2022/shival99-z-ui-components-z-upload.mjs.map +1 -1
  19. package/fesm2022/shival99-z-ui-providers.mjs +6 -2
  20. package/fesm2022/shival99-z-ui-providers.mjs.map +1 -1
  21. package/fesm2022/shival99-z-ui-services.mjs +71 -4
  22. package/fesm2022/shival99-z-ui-services.mjs.map +1 -1
  23. package/package.json +1 -1
  24. package/types/shival99-z-ui-components-z-autocomplete.d.ts +1 -1
  25. package/types/shival99-z-ui-components-z-calendar.d.ts +4 -4
  26. package/types/shival99-z-ui-components-z-drawer.d.ts +2 -0
  27. package/types/shival99-z-ui-components-z-editor.d.ts +1 -1
  28. package/types/shival99-z-ui-components-z-filter.d.ts +17 -0
  29. package/types/shival99-z-ui-components-z-modal.d.ts +5 -2
  30. package/types/shival99-z-ui-components-z-popover.d.ts +1 -1
  31. package/types/shival99-z-ui-components-z-select.d.ts +1 -1
  32. package/types/shival99-z-ui-components-z-table.d.ts +205 -1
  33. package/types/shival99-z-ui-components-z-timeline.d.ts +20 -62
  34. package/types/shival99-z-ui-components-z-upload.d.ts +3 -3
  35. package/types/shival99-z-ui-providers.d.ts +6 -2
  36. 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) {