@shival99/z-ui 2.2.1 → 2.2.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shival99/z-ui",
3
- "version": "2.2.1",
3
+ "version": "2.2.3",
4
4
  "description": "Z-UI: Modern Angular UI Component Library - A comprehensive, high-performance design system built with Angular 22, featuring 40+ customizable components with dark mode, accessibility, and enterprise-ready features.",
5
5
  "keywords": [
6
6
  "angular",
@@ -1,3 +1,4 @@
1
+ import * as _tanstack_angular_virtual from '@tanstack/angular-virtual';
1
2
  import * as _angular_core from '@angular/core';
2
3
  import { OnDestroy } from '@angular/core';
3
4
  import { ZIcon } from '@shival99/z-ui/components/z-icon';
@@ -81,9 +82,22 @@ declare class ZChatComponent implements OnDestroy {
81
82
  private readonly _messagesScrollbarRef;
82
83
  private readonly _chatInputRef;
83
84
  private readonly _fileInputRef;
85
+ private readonly _virtualMessageRefs;
86
+ /** Signal, not a field: requesting a pin must itself wake `_pinToNewest`. */
87
+ private readonly _pinAttemptsLeft;
84
88
  private _mockReplyTimer;
85
89
  private _rippleTimer;
86
- private _scrollTimer;
90
+ private readonly _measureMessages;
91
+ protected readonly messageVirtualizer: _tanstack_angular_virtual.AngularVirtualizer<HTMLElement, Element>;
92
+ /**
93
+ * Drives a requested pin to completion across renders.
94
+ *
95
+ * One `scrollToEnd()` is not enough on a cold list: the end is computed from estimates, and it
96
+ * moves as rows are measured. The scroll box also arrives late — ng-scrollbar reports its
97
+ * viewport only after init, and a scroll issued before then leaves the virtualizer at offset 0
98
+ * with no event to resync it. Re-asserting each render until `isAtEnd` holds covers both.
99
+ */
100
+ private readonly _pinToNewest;
87
101
  protected readonly wrapperClasses: _angular_core.Signal<string>;
88
102
  protected readonly wrapperTop: _angular_core.Signal<string | null>;
89
103
  protected readonly wrapperRight: _angular_core.Signal<string | null>;
@@ -94,6 +108,8 @@ declare class ZChatComponent implements OnDestroy {
94
108
  protected readonly isTyping: _angular_core.Signal<boolean>;
95
109
  protected readonly canSend: _angular_core.Signal<boolean>;
96
110
  protected readonly hasMessages: _angular_core.Signal<boolean>;
111
+ /** Only the newest bubble plays the push-in animation; recycled rows must not replay it. */
112
+ protected readonly newestMessageId: _angular_core.Signal<string | null>;
97
113
  protected readonly showEmptyState: _angular_core.Signal<boolean>;
98
114
  protected readonly previewImageSizeLabel: _angular_core.Signal<string>;
99
115
  ngOnDestroy(): void;
@@ -120,9 +136,16 @@ declare class ZChatComponent implements OnDestroy {
120
136
  private _triggerRipple;
121
137
  protected onMessagesScrollbarInit(): void;
122
138
  protected onEscapeKey(): void;
123
- private _scheduleScrollToBottom;
139
+ /**
140
+ * Requests a pin to the newest message; `_pinToNewest` performs it once rendering can honour it.
141
+ *
142
+ * Never scroll synchronously from here. Callers fire right after mutating `zMessages` or
143
+ * `zOpen`, before change detection has fed the new length into the virtualizer, so the scroll
144
+ * would target the previous last index and strand the list mid-history.
145
+ */
124
146
  private _ensureBottomVisible;
125
- private _scrollToBottom;
147
+ /** ng-scrollbar owns the real scrolling box; the virtualizer must observe that, not the host. */
148
+ private _getMessagesViewportElement;
126
149
  private _focusInputSoon;
127
150
  private _releaseAttachmentUrls;
128
151
  private _appendFiles;
@@ -458,6 +458,13 @@ interface ZTableGroupingConfig<T = unknown> {
458
458
  allowHeaderMenu?: boolean;
459
459
  defaultExpanded?: boolean;
460
460
  showCount?: boolean;
461
+ /**
462
+ * Ghim group header đang active lên đầu vùng cuộn (chạy ở cả virtual lẫn non-virtual).
463
+ * Header của group hiện tại dính ở đầu bảng và bị group kế tiếp đẩy đi khi cuộn tới.
464
+ * Multi-level grouping thì header con xếp chồng ngay dưới header cha.
465
+ * @default false
466
+ */
467
+ stickyHeader?: boolean;
461
468
  /** Cấu hình các cột cần hiển thị tổng trên group row. */
462
469
  totals?: ZTableGroupTotalConfig<T>[];
463
470
  /**
@@ -1115,6 +1122,7 @@ declare class ZTableComponent<T> implements AfterViewInit {
1115
1122
  private readonly _zTranslate;
1116
1123
  /** Prevents recursive scroll sync between thead/tbody/tfoot */
1117
1124
  private readonly _isSyncingScroll;
1125
+ private _lastSyncedScrollLeft;
1118
1126
  /** Preserves horizontal scroll position across loading states */
1119
1127
  private readonly _savedScrollLeft;
1120
1128
  /** Observes tbody container height changes for skeleton row count calculation */
@@ -1150,6 +1158,7 @@ declare class ZTableComponent<T> implements AfterViewInit {
1150
1158
  protected readonly tfootWrapper: _angular_core.Signal<ElementRef<HTMLDivElement> | undefined>;
1151
1159
  protected readonly expandedRowTemplate: _angular_core.Signal<TemplateRef<unknown> | undefined>;
1152
1160
  protected readonly virtualRowElements: _angular_core.Signal<readonly ElementRef<HTMLDivElement>[]>;
1161
+ protected readonly groupRowElements: _angular_core.Signal<readonly ElementRef<HTMLTableRowElement>[]>;
1153
1162
  protected readonly columnPinning: _angular_core.WritableSignal<ColumnPinningState>;
1154
1163
  protected readonly columnVisibility: _angular_core.WritableSignal<ColumnVisibilityState>;
1155
1164
  protected readonly columnOrder: _angular_core.WritableSignal<ColumnOrderState>;
@@ -1241,6 +1250,21 @@ declare class ZTableComponent<T> implements AfterViewInit {
1241
1250
  protected readonly hasScrollTop: _angular_core.WritableSignal<boolean>;
1242
1251
  /** True khi bottom-pinned rows đang đè lên nội dung body còn scroll xuống được */
1243
1252
  protected readonly hasScrollBottom: _angular_core.WritableSignal<boolean>;
1253
+ /**
1254
+ * Group header (non-virtual) đang ghim ở mép dưới cùng của chồng sticky — chỉ mình nó đổ shadow.
1255
+ * Nhiều header ghim chung một chỗ, mỗi cái một shadow thì alpha cộng dồn thành vệt đen đậm dần.
1256
+ * Chỉ dùng cho shadow: class này bám theo scroll nên luôn trễ một frame, chấp nhận được với shadow
1257
+ * nhưng không dùng được cho z-index (xem `tr.z-group-row-sticky`).
1258
+ */
1259
+ protected readonly shadowGroupRowId: _angular_core.WritableSignal<string | null>;
1260
+ /** Chiều cao thật của group header row (non-virtual), dùng để xếp chồng sticky theo depth. */
1261
+ private readonly _groupRowHeight;
1262
+ /**
1263
+ * Tổng chiều cao khối pinned top rows (non-virtual), đo trực tiếp từ DOM sau mỗi render.
1264
+ * Không dùng `pinnedRowHeights` vì signal đó chỉ refresh trong `_checkScrollState`, nên có thể
1265
+ * còn giữ số đo cũ khi bảng vừa đổi cấu hình — sticky group header sẽ hở một khoảng vài px.
1266
+ */
1267
+ private readonly _nonVirtualPinnedTopHeight;
1244
1268
  /** Maps row IDs to measured DOM heights for pinned row offset calculations */
1245
1269
  protected readonly pinnedRowHeights: _angular_core.WritableSignal<Record<string, number>>;
1246
1270
  protected readonly virtualMeasuredLeftSpacerWidth: _angular_core.WritableSignal<number>;
@@ -1357,6 +1381,40 @@ declare class ZTableComponent<T> implements AfterViewInit {
1357
1381
  protected readonly renderedBodyRows: _angular_core.Signal<Row<T>[]>;
1358
1382
  protected readonly bodyRowSpanRows: _angular_core.Signal<Row<T>[]>;
1359
1383
  protected readonly virtualGroupedFlatItems: _angular_core.Signal<ZVirtualFlatItem<T>[]>;
1384
+ /** Sticky group header đang bật (config cho phép và đang thực sự group theo cột nào đó). */
1385
+ protected readonly isStickyGroupHeaderEnabled: _angular_core.Signal<boolean>;
1386
+ /** Flow layout: một transform cho cả khối thay vì mỗi row một cái. Sticky group header phải
1387
+ * dùng absolute vì rangeExtractor làm tập render không liền mạch. */
1388
+ protected readonly usesFlowVirtualRows: _angular_core.Signal<boolean>;
1389
+ protected readonly virtualFlowOffset: _angular_core.Signal<number>;
1390
+ /**
1391
+ * `top` (px) của sticky group header theo depth ở chế độ non-virtual.
1392
+ * Ở đây mọi group header đều là `<tr>` thật trong cùng một `<tbody>`, nên chỉ cần `position: sticky`:
1393
+ * header sau (đứng sau trong DOM) tự đè lên header trước khi trôi tới rồi lộ lại khi cuộn ngược —
1394
+ * không cần rangeExtractor như bên virtual (cơ chế đó chỉ để giữ row đã ra khỏi range).
1395
+ * Header con xếp chồng ngay dưới header cha nên cộng thêm `depth * chiều cao group row`.
1396
+ */
1397
+ protected readonly stickyGroupTopByDepth: _angular_core.Signal<number[]>;
1398
+ /**
1399
+ * Lookup phục vụ sticky group header trong virtual scroll:
1400
+ * - `nearest[i]`: index group header gần nhất tại hoặc trước `i` (-1 nếu chưa có).
1401
+ * - `parent`: index group header cha của một header (multi-level grouping), -1 nếu là cấp gốc.
1402
+ * Trả `null` khi tắt sticky hoặc không ở chế độ virtual + grouping.
1403
+ */
1404
+ private readonly _stickyGroupHeaderMeta;
1405
+ /** Chuỗi header (gốc → sâu nhất) cần ghim khi viewport bắt đầu tại `startIndex`. */
1406
+ private _buildStickyGroupChain;
1407
+ /**
1408
+ * Trạng thái sticky group header theo virtual index:
1409
+ * - `tops`: offset `top` (px) của từng header đang ghim; header cấp sâu hơn xếp chồng bên dưới
1410
+ * header cha nên cộng dồn chiều cao.
1411
+ * - `shadowIndex`: header cuối chuỗi mang shadow (giống pinned row), -1 khi chưa có row nào
1412
+ * cuộn xuống dưới nó — lúc đó header vẫn ở đúng vị trí thật nên không cần shadow.
1413
+ */
1414
+ protected readonly stickyGroupHeaderState: _angular_core.Signal<{
1415
+ shadowIndex: number;
1416
+ tops: Record<number, number>;
1417
+ }>;
1360
1418
  protected _getVirtualGroupHeader(item: ZVirtualFlatItem<T> | undefined): ZTableGroupRowItem | null;
1361
1419
  protected _isVirtualGroupChild(item: ZVirtualFlatItem<T> | undefined): boolean;
1362
1420
  protected _getVirtualGroupDepth(item: ZVirtualFlatItem<T> | undefined): number;
@@ -1629,6 +1687,16 @@ declare class ZTableComponent<T> implements AfterViewInit {
1629
1687
  rowSelectionFeature: _tanstack_angular_table.TableFeature;
1630
1688
  rowSortingFeature: _tanstack_angular_table.TableFeature;
1631
1689
  }, T & Record<string, unknown>>[]>;
1690
+ /**
1691
+ * Id row ở hai biên của mỗi khối pinned, theo THỨ TỰ HIỂN THỊ (pinned bottom render ngược lại).
1692
+ * Dùng signal thay cho `zTableRow` pipe: pipe là pure, chỉ chạy lại khi một tham số đổi reference.
1693
+ * Khi ghim thêm/bớt row thì `row` và `table` giữ nguyên reference, nên row từng là biên vẫn giữ
1694
+ * class cũ — pin bottom lộ rõ nhất vì shadow ở lại row cũ và row mới cũng có shadow.
1695
+ */
1696
+ protected readonly firstTopPinnedRowId: _angular_core.Signal<string>;
1697
+ protected readonly lastTopPinnedRowId: _angular_core.Signal<string>;
1698
+ protected readonly firstBottomPinnedRowId: _angular_core.Signal<string>;
1699
+ protected readonly lastBottomPinnedRowId: _angular_core.Signal<string>;
1632
1700
  protected readonly leftHeaderRow: _angular_core.Signal<_tanstack_angular_table.Header<{
1633
1701
  expandedRowModel: (table: _tanstack_angular_table.Table<any, any>) => () => _tanstack_angular_table.RowModel<any, any>;
1634
1702
  facetedMinMaxValues: (table: _tanstack_angular_table.Table<_tanstack_angular_table.TableFeatures, any>, columnId: string) => () => undefined | [number, number];
@@ -1795,9 +1863,43 @@ declare class ZTableComponent<T> implements AfterViewInit {
1795
1863
  protected readonly effectiveDynamicRowSizing: _angular_core.Signal<boolean>;
1796
1864
  /** Virtualizer instance — operates on groups rather than individual rows */
1797
1865
  protected readonly virtualizer: _tanstack_angular_virtual.AngularVirtualizer<HTMLElement, Element>;
1866
+ /**
1867
+ * Nhận diện group header đang ghim sau MỖI render, không chỉ khi scroll.
1868
+ * Đổi virtual/non-virtual, đổi cột group hay pin/unpin row đều giữ nguyên scrollOffset nhưng dựng
1869
+ * lại DOM và đổi đường sticky — nếu chỉ trông vào scroll handler thì header đang ghim sẽ thiếu
1870
+ * class `z-group-row-stuck` cho tới lần cuộn kế tiếp, và bị data row đè trong khoảng đó.
1871
+ * `_refreshStuckGroupRows` đọc `groupRowElements` + `stickyGroupTopByDepth` trước mọi early-return
1872
+ * nên effect này tự chạy lại khi hai nguồn đó đổi (kể cả khi số đo tới muộn hơn một pass).
1873
+ */
1874
+ private readonly _trackStuckGroupRows;
1875
+ /** Measures in the render `read` phase so corrected offsets land before paint, not a frame later. */
1798
1876
  private readonly _measureVirtualItems;
1799
- /** Learn estimate height from measured rows (reads the measurement cache, no reflow). Locks once. */
1800
- private _learnDynamicEstimate;
1877
+ private _stuckGroupRowsFrame;
1878
+ /** Gom nhiều lần scroll trong cùng một frame — mỗi lần chạy đều đọc rect nên phải giới hạn tần suất. */
1879
+ private _scheduleStuckGroupRowsRefresh;
1880
+ private _cancelStuckGroupRowsRefresh;
1881
+ /**
1882
+ * Tìm group header (non-virtual) đang ghim ở mép dưới cùng của chồng sticky để trao shadow.
1883
+ * Header đã ghim có `rect.top` đúng bằng đường sticky của depth đó; header chưa tới thì lớn hơn.
1884
+ * Ưu tiên đường sticky thấp nhất (multi-level: header con), tie thì lấy cái sau cùng trong DOM vì
1885
+ * đó là cái đang nằm trên khi nhiều header chồng nhau.
1886
+ */
1887
+ private _refreshStuckGroupRows;
1888
+ /** Đo các mốc mà `stickyGroupTopByDepth` cần (non-virtual): chiều cao group row và khối pinned top. */
1889
+ private _refreshStickyGroupMetrics;
1890
+ private _hasWarnedOverflowingFixedRows;
1891
+ /**
1892
+ * Cảnh báo một lần mỗi bảng khi body row cao hơn `virtual.size`.
1893
+ * CSS table layout coi `height` trên `<tr>`/`<td>` là min-height, nên nội dung cell không nén được
1894
+ * (tag, button, nhiều dòng…) sẽ đẩy row vượt slot của virtualizer; row kế tiếp định vị tuyệt đối
1895
+ * đúng theo `virtual.size` nên đè lên phần dôi ra — nhìn như mỗi row bị hụt mất vài px.
1896
+ * Không tự sửa: chiều cao đúng phụ thuộc dữ liệu, chọn tăng `size` hay bật `dynamicSize` là của app.
1897
+ */
1898
+ private _warnOnOverflowingFixedRows;
1899
+ private _dynamicEstimateSampleCount;
1900
+ /** Averages the rows measured so far to estimate the ones not yet rendered.
1901
+ * Tính lại theo bậc ~1.5x để tổng công việc là O(N) thay vì O(N²) khi cuộn hết danh sách dài. */
1902
+ private _refreshDynamicEstimate;
1801
1903
  private readonly _rowDragAutoScroll;
1802
1904
  private readonly _rowDragCellSelectionCleanup;
1803
1905
  constructor();
@@ -2039,11 +2141,11 @@ declare class ZTableFilterComponent<T> {
2039
2141
  protected readonly sortOptions: _angular_core.Signal<ZSelectOption<string>[]>;
2040
2142
  protected readonly draftFilters: _angular_core.WritableSignal<ZTableDraftFilterCondition[]>;
2041
2143
  protected readonly draftLegacyFilterValue: _angular_core.WritableSignal<unknown>;
2042
- protected readonly draftSortState: _angular_core.WritableSignal<false | "asc" | "desc">;
2144
+ protected readonly draftSortState: _angular_core.WritableSignal<false | "desc" | "asc">;
2043
2145
  protected readonly columnFilterValue: _angular_core.Signal<unknown>;
2044
2146
  protected readonly effectiveFilterValue: _angular_core.Signal<unknown>;
2045
- protected readonly sortState: _angular_core.Signal<false | "asc" | "desc">;
2046
- protected readonly effectiveSortState: _angular_core.Signal<false | "asc" | "desc">;
2147
+ protected readonly sortState: _angular_core.Signal<false | "desc" | "asc">;
2148
+ protected readonly effectiveSortState: _angular_core.Signal<false | "desc" | "asc">;
2047
2149
  protected readonly canSort: _angular_core.Signal<boolean>;
2048
2150
  protected readonly canFilter: _angular_core.Signal<boolean>;
2049
2151
  protected readonly filterUi: _angular_core.Signal<ZTableFilterUi>;
@@ -2061,7 +2163,7 @@ declare class ZTableFilterComponent<T> {
2061
2163
  protected readonly filterBadgeCount: _angular_core.Signal<number>;
2062
2164
  protected readonly hasFilterValue: _angular_core.Signal<boolean>;
2063
2165
  protected readonly isActive: _angular_core.Signal<boolean>;
2064
- protected readonly draftSortValue: _angular_core.Signal<"asc" | "desc" | "none">;
2166
+ protected readonly draftSortValue: _angular_core.Signal<"none" | "desc" | "asc">;
2065
2167
  protected readonly rangeMinValue: _angular_core.Signal<any>;
2066
2168
  protected readonly rangeMaxValue: _angular_core.Signal<any>;
2067
2169
  protected readonly dateValue: _angular_core.Signal<Date | null>;