ezfw-core 1.0.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.
Files changed (154) hide show
  1. package/components/EzBaseComponent.ts +648 -0
  2. package/components/EzComponent.ts +89 -0
  3. package/components/EzInput.module.scss +183 -0
  4. package/components/EzInput.ts +104 -0
  5. package/components/EzLabel.ts +22 -0
  6. package/components/EzOutlet.ts +181 -0
  7. package/components/HtmlWrapper.ts +305 -0
  8. package/components/avatar/EzAvatar.module.scss +200 -0
  9. package/components/avatar/EzAvatar.ts +130 -0
  10. package/components/badge/EzBadge.module.scss +202 -0
  11. package/components/badge/EzBadge.ts +77 -0
  12. package/components/button/EzButton.module.scss +402 -0
  13. package/components/button/EzButton.ts +175 -0
  14. package/components/button/EzButtonGroup.ts +48 -0
  15. package/components/card/EzCard.module.scss +71 -0
  16. package/components/card/EzCard.ts +120 -0
  17. package/components/chart/EzBarChart.ts +47 -0
  18. package/components/chart/EzChart.module.scss +14 -0
  19. package/components/chart/EzChart.ts +279 -0
  20. package/components/chart/EzDoughnutChart.ts +47 -0
  21. package/components/chart/EzLineChart.ts +53 -0
  22. package/components/checkbox/EzCheckbox.module.scss +145 -0
  23. package/components/checkbox/EzCheckbox.ts +115 -0
  24. package/components/dataview/EzDataView.module.scss +115 -0
  25. package/components/dataview/EzDataView.ts +355 -0
  26. package/components/dataview/modes/EzDataViewCards.ts +322 -0
  27. package/components/dataview/modes/EzDataViewGrid.ts +76 -0
  28. package/components/datepicker/EzDatePicker.module.scss +348 -0
  29. package/components/datepicker/EzDatePicker.ts +519 -0
  30. package/components/dialog/EzDialog.module.scss +180 -0
  31. package/components/dropdown/EzDropdown.module.scss +107 -0
  32. package/components/dropdown/EzDropdown.ts +235 -0
  33. package/components/feed/EzActivityFeed.module.scss +90 -0
  34. package/components/feed/EzActivityFeed.ts +78 -0
  35. package/components/form/EzForm.ts +364 -0
  36. package/components/form/EzValidators.test.js +421 -0
  37. package/components/form/EzValidators.ts +202 -0
  38. package/components/grid/EzGrid.scss +88 -0
  39. package/components/grid/EzGrid.ts +1085 -0
  40. package/components/grid/EzGridContainer.ts +104 -0
  41. package/components/grid/body/EzGridBody.scss +283 -0
  42. package/components/grid/body/EzGridBody.ts +549 -0
  43. package/components/grid/body/EzGridCell.ts +211 -0
  44. package/components/grid/body/EzGridRow.ts +196 -0
  45. package/components/grid/filter/EzGridFilters.scss +78 -0
  46. package/components/grid/filter/EzGridFilters.ts +285 -0
  47. package/components/grid/footer/EzGridFooter.scss +136 -0
  48. package/components/grid/footer/EzGridFooter.ts +448 -0
  49. package/components/grid/header/EzGridHeader.scss +199 -0
  50. package/components/grid/header/EzGridHeader.ts +430 -0
  51. package/components/grid/query/EzGridQuery.ts +81 -0
  52. package/components/grid/state/EzGridColumns.ts +155 -0
  53. package/components/grid/state/EzGridController.ts +470 -0
  54. package/components/grid/state/EzGridLifecycle.ts +136 -0
  55. package/components/grid/state/EzGridNormalizers.test.js +273 -0
  56. package/components/grid/state/EzGridNormalizers.ts +162 -0
  57. package/components/grid/state/EzGridParts.ts +233 -0
  58. package/components/grid/state/EzGridPersistence.ts +140 -0
  59. package/components/grid/state/EzGridRemote.test.js +573 -0
  60. package/components/grid/state/EzGridRemote.ts +335 -0
  61. package/components/grid/state/EzGridSelection.ts +231 -0
  62. package/components/grid/state/EzGridSort.ts +286 -0
  63. package/components/grid/title/EzGridActionBar.ts +98 -0
  64. package/components/grid/title/EzGridTitle.ts +114 -0
  65. package/components/grid/title/EzGridTitleBar.scss +65 -0
  66. package/components/grid/title/EzGridTitleBar.ts +87 -0
  67. package/components/grid/types.ts +607 -0
  68. package/components/panel/EzPanel.module.scss +133 -0
  69. package/components/panel/EzPanel.ts +147 -0
  70. package/components/radio/EzRadio.module.scss +190 -0
  71. package/components/radio/EzRadio.ts +149 -0
  72. package/components/select/EzSelect.module.scss +153 -0
  73. package/components/select/EzSelect.ts +238 -0
  74. package/components/skeleton/EzSkeleton.module.scss +95 -0
  75. package/components/skeleton/EzSkeleton.ts +70 -0
  76. package/components/store/EzStore.ts +344 -0
  77. package/components/switch/EzSwitch.module.scss +164 -0
  78. package/components/switch/EzSwitch.ts +117 -0
  79. package/components/tabs/EzTabPanel.module.scss +181 -0
  80. package/components/tabs/EzTabPanel.ts +402 -0
  81. package/components/textarea/EzTextarea.module.scss +131 -0
  82. package/components/textarea/EzTextarea.ts +161 -0
  83. package/components/timepicker/EzTimePicker.module.scss +282 -0
  84. package/components/timepicker/EzTimePicker.ts +540 -0
  85. package/components/toast/EzToast.module.scss +291 -0
  86. package/components/tooltip/EzTooltip.module.scss +124 -0
  87. package/components/tooltip/EzTooltip.ts +153 -0
  88. package/core/EzComponentTypes.ts +693 -0
  89. package/core/EzError.ts +63 -0
  90. package/core/EzModel.ts +268 -0
  91. package/core/EzTypes.ts +328 -0
  92. package/core/eventBus.ts +284 -0
  93. package/core/ez.ts +617 -0
  94. package/core/loader.ts +725 -0
  95. package/core/renderer.ts +1010 -0
  96. package/core/router.ts +490 -0
  97. package/core/services.ts +124 -0
  98. package/core/state.ts +142 -0
  99. package/core/utils.ts +81 -0
  100. package/package.json +51 -0
  101. package/services/RouteUI.js +17 -0
  102. package/services/crypto.js +64 -0
  103. package/services/dialog.js +222 -0
  104. package/services/fetchApi.js +63 -0
  105. package/services/firebase.js +30 -0
  106. package/services/toast.js +214 -0
  107. package/template/doc/EzDocs.js +15 -0
  108. package/template/doc/EzDocs.module.scss +627 -0
  109. package/template/doc/EzDocsController.js +164 -0
  110. package/template/doc/data/activityfeed/EzActivityFeedDoc.js +42 -0
  111. package/template/doc/data/avatar/EzAvatarDoc.js +71 -0
  112. package/template/doc/data/badge/EzBadgeDoc.js +92 -0
  113. package/template/doc/data/button/EzButtonDoc.js +77 -0
  114. package/template/doc/data/buttongroup/EzButtonGroupDoc.js +102 -0
  115. package/template/doc/data/card/EzCardDoc.js +39 -0
  116. package/template/doc/data/chart/EzChartDoc.js +60 -0
  117. package/template/doc/data/checkbox/EzCheckboxDoc.js +67 -0
  118. package/template/doc/data/component/EzComponentDoc.js +34 -0
  119. package/template/doc/data/cssmodules/CSSModulesDoc.js +70 -0
  120. package/template/doc/data/datepicker/EzDatePickerDoc.js +126 -0
  121. package/template/doc/data/dialog/EzDialogDoc.js +217 -0
  122. package/template/doc/data/dropdown/EzDropdownDoc.js +178 -0
  123. package/template/doc/data/form/EzFormDoc.js +90 -0
  124. package/template/doc/data/grid/EzGridDoc.js +99 -0
  125. package/template/doc/data/input/EzInputDoc.js +92 -0
  126. package/template/doc/data/label/EzLabelDoc.js +40 -0
  127. package/template/doc/data/model/EzModelDoc.js +53 -0
  128. package/template/doc/data/outlet/EzOutletDoc.js +63 -0
  129. package/template/doc/data/panel/EzPanelDoc.js +214 -0
  130. package/template/doc/data/radio/EzRadioDoc.js +174 -0
  131. package/template/doc/data/router/EzRouterDoc.js +75 -0
  132. package/template/doc/data/select/EzSelectDoc.js +37 -0
  133. package/template/doc/data/skeleton/EzSkeletonDoc.js +149 -0
  134. package/template/doc/data/switch/EzSwitchDoc.js +82 -0
  135. package/template/doc/data/tabpanel/EzTabPanelDoc.js +44 -0
  136. package/template/doc/data/textarea/EzTextareaDoc.js +131 -0
  137. package/template/doc/data/timepicker/EzTimePickerDoc.js +107 -0
  138. package/template/doc/data/tooltip/EzTooltipDoc.js +193 -0
  139. package/template/doc/data/validators/EzValidatorsDoc.js +37 -0
  140. package/template/doc/sidebar/EzDocsSidebar.js +32 -0
  141. package/template/doc/sidebar/category/EzDocsCategory.js +33 -0
  142. package/template/doc/sidebar/item/EzDocsComponentItem.js +24 -0
  143. package/template/doc/viewer/EzDocsViewer.js +18 -0
  144. package/template/doc/viewer/codepanel/EzDocsCodePanel.js +51 -0
  145. package/template/doc/viewer/content/EzDocsContent.js +315 -0
  146. package/template/doc/viewer/header/EzDocsViewerHeader.js +46 -0
  147. package/template/doc/viewer/showcase/EzDocsShowcase.js +59 -0
  148. package/template/doc/viewer/showcase/EzDocsShowcaseSection.js +25 -0
  149. package/template/doc/viewer/showcase/EzDocsVariantItem.js +29 -0
  150. package/template/doc/welcome/EzDocsWelcome.js +48 -0
  151. package/themes/ez-theme.scss +179 -0
  152. package/themes/nature-fresh.scss +169 -0
  153. package/types/global.d.ts +21 -0
  154. package/utils/cssModules.js +81 -0
@@ -0,0 +1,549 @@
1
+ // EzGrid/body/EzGridBody.ts
2
+
3
+ import './EzGridBody.scss';
4
+ import { EzGridContainer, EzGridContainerConfig } from '../EzGridContainer.js';
5
+ import type {
6
+ NormalizedColumn,
7
+ EzGridController,
8
+ RowData,
9
+ RowKeyFn,
10
+ RowCallback,
11
+ RowContextMenuCallback,
12
+ RowClickContext
13
+ } from '../types.js';
14
+ import type { EzGridSelection } from '../state/EzGridSelection.js';
15
+
16
+ declare const ez: {
17
+ _createElement(config: unknown): Promise<HTMLElement | null>;
18
+ };
19
+
20
+ export interface EzGridBodyRef {
21
+ selectAll?: () => void;
22
+ clearSelection?: () => void;
23
+ selectRange?: (toIndex: number) => void;
24
+ toggleRow?: (row: RowData, index: number) => void;
25
+ isSelected?: (row: RowData) => boolean;
26
+ selection?: EzGridSelection;
27
+ controller?: EzGridController;
28
+ }
29
+
30
+ export interface EzGridBodyConfig extends Omit<EzGridContainerConfig, 'controller'> {
31
+ grid?: EzGridBodyRef;
32
+ controller?: EzGridController;
33
+ columns?: (() => NormalizedColumn[]) | NormalizedColumn[];
34
+ rowKey?: RowKeyFn;
35
+ selection?: EzGridSelection;
36
+ rowHeight?: number;
37
+ virtual?: boolean;
38
+ bufferRows?: number;
39
+ keyboard?: boolean;
40
+ emptyText?: string;
41
+ onRowClick?: RowCallback;
42
+ onRowDoubleClick?: RowCallback;
43
+ onRowContextMenu?: RowContextMenuCallback;
44
+ }
45
+
46
+ export class EzGridBody extends EzGridContainer {
47
+ declare config: EzGridBodyConfig;
48
+ declare el: HTMLElement | null;
49
+
50
+ data: RowData[];
51
+ private _isLoading: boolean;
52
+ private _error: Error | string | null;
53
+ private _focusedIndex: number;
54
+ private _keyboardEnabled: boolean;
55
+ private _virtual: boolean;
56
+ private _rowHeight: number;
57
+ private _bufferRows: number;
58
+ private _visibleStart: number;
59
+ private _visibleEnd: number;
60
+ private _scrollRAF: number | null;
61
+ private _loadingEl: HTMLElement | null;
62
+ private _errorEl: HTMLElement | null;
63
+ private _emptyEl: HTMLElement | null;
64
+ private _virtualContainer: HTMLElement | null;
65
+
66
+ constructor(config: EzGridBodyConfig = {}) {
67
+ super(config as EzGridContainerConfig);
68
+
69
+ this.config.cls = (this.config.cls || '') + ' ez-grid-body';
70
+
71
+ this.data = [];
72
+
73
+ // State tracking for loading/error UI
74
+ this._isLoading = false;
75
+ this._error = null;
76
+
77
+ // Keyboard navigation state
78
+ this._focusedIndex = -1;
79
+ this._keyboardEnabled = this.config.keyboard !== false;
80
+
81
+ // Virtual scrolling config
82
+ this._virtual = this.config.virtual === true;
83
+ this._rowHeight = this.config.rowHeight ?? 40;
84
+ this._bufferRows = this.config.bufferRows ?? 5;
85
+ this._visibleStart = 0;
86
+ this._visibleEnd = 0;
87
+ this._scrollRAF = null;
88
+
89
+ // UI element refs
90
+ this._loadingEl = null;
91
+ this._errorEl = null;
92
+ this._emptyEl = null;
93
+ this._virtualContainer = null;
94
+ }
95
+
96
+ async render(): Promise<HTMLDivElement> {
97
+ const el = document.createElement('div');
98
+
99
+ // layout
100
+ el.style.display = 'flex';
101
+ el.style.flexDirection = 'column';
102
+ el.style.flex = String(this.config.flex ?? '1');
103
+ el.style.minHeight = '0';
104
+ el.style.overflowY = 'auto';
105
+
106
+ if (this.config.cls) {
107
+ el.className = Array.isArray(this.config.cls)
108
+ ? this.config.cls.join(' ')
109
+ : String(this.config.cls);
110
+ }
111
+
112
+ // Make focusable for keyboard navigation
113
+ if (this._keyboardEnabled) {
114
+ el.setAttribute('tabindex', '0');
115
+ el.setAttribute('role', 'grid');
116
+ this._bindKeyboardEvents(el);
117
+ }
118
+
119
+ // Virtual scrolling setup
120
+ if (this._virtual) {
121
+ el.classList.add('ez-grid-body--virtual');
122
+ this._bindScrollEvents(el);
123
+ }
124
+
125
+ this.el = el;
126
+
127
+ return el;
128
+ }
129
+
130
+ private _bindScrollEvents(el: HTMLElement): void {
131
+ el.addEventListener('scroll', () => {
132
+ if (this._scrollRAF) return;
133
+
134
+ this._scrollRAF = requestAnimationFrame(() => {
135
+ this._scrollRAF = null;
136
+ this._onVirtualScroll();
137
+ });
138
+ }, { passive: true });
139
+ }
140
+
141
+ private _onVirtualScroll(): void {
142
+ if (!this._virtual || !this.data?.length) return;
143
+
144
+ const scrollTop = this.el!.scrollTop;
145
+ const viewportHeight = this.el!.clientHeight;
146
+
147
+ const startIndex = Math.floor(scrollTop / this._rowHeight);
148
+ const visibleCount = Math.ceil(viewportHeight / this._rowHeight);
149
+
150
+ const newStart = Math.max(0, startIndex - this._bufferRows);
151
+ const newEnd = Math.min(
152
+ this.data.length,
153
+ startIndex + visibleCount + this._bufferRows
154
+ );
155
+
156
+ // Only re-render if visible range changed
157
+ if (newStart !== this._visibleStart || newEnd !== this._visibleEnd) {
158
+ this._visibleStart = newStart;
159
+ this._visibleEnd = newEnd;
160
+ this._renderVirtualRows();
161
+ }
162
+ }
163
+
164
+ private async _renderVirtualRows(): Promise<void> {
165
+ if (!this._virtualContainer) return;
166
+
167
+ // Clear current rows
168
+ this._virtualContainer.innerHTML = '';
169
+
170
+ // Render visible rows
171
+ for (let i = this._visibleStart; i < this._visibleEnd; i++) {
172
+ const row = this.data[i];
173
+ if (!row) continue;
174
+
175
+ const rowCfg = this._createRowConfig(row, i);
176
+ const rowEl = await ez._createElement(rowCfg);
177
+
178
+ if (rowEl) {
179
+ // Position row absolutely
180
+ rowEl.style.position = 'absolute';
181
+ rowEl.style.top = `${i * this._rowHeight}px`;
182
+ rowEl.style.left = '0';
183
+ rowEl.style.right = '0';
184
+ rowEl.style.height = `${this._rowHeight}px`;
185
+
186
+ this._virtualContainer.appendChild(rowEl);
187
+ }
188
+ }
189
+ }
190
+
191
+ private _bindKeyboardEvents(el: HTMLElement): void {
192
+ el.addEventListener('keydown', (e: KeyboardEvent) => {
193
+ this._handleKeyDown(e);
194
+ });
195
+ }
196
+
197
+ private _handleKeyDown(e: KeyboardEvent): void {
198
+ if (!this.data?.length) return;
199
+
200
+ const maxIndex = this.data.length - 1;
201
+
202
+ switch (e.key) {
203
+ case 'ArrowDown':
204
+ e.preventDefault();
205
+ this._moveFocus(1);
206
+ break;
207
+
208
+ case 'ArrowUp':
209
+ e.preventDefault();
210
+ this._moveFocus(-1);
211
+ break;
212
+
213
+ case 'Home':
214
+ e.preventDefault();
215
+ this._setFocusedIndex(0);
216
+ break;
217
+
218
+ case 'End':
219
+ e.preventDefault();
220
+ this._setFocusedIndex(maxIndex);
221
+ break;
222
+
223
+ case 'PageDown':
224
+ e.preventDefault();
225
+ this._moveFocus(10);
226
+ break;
227
+
228
+ case 'PageUp':
229
+ e.preventDefault();
230
+ this._moveFocus(-10);
231
+ break;
232
+
233
+ case 'Enter':
234
+ case ' ':
235
+ e.preventDefault();
236
+ this._selectFocusedRow(e);
237
+ break;
238
+
239
+ case 'a':
240
+ case 'A':
241
+ if (e.ctrlKey || e.metaKey) {
242
+ e.preventDefault();
243
+ this.config.grid?.selectAll?.();
244
+ }
245
+ break;
246
+
247
+ case 'Escape':
248
+ e.preventDefault();
249
+ this.config.grid?.clearSelection?.();
250
+ this._setFocusedIndex(-1);
251
+ break;
252
+ }
253
+ }
254
+
255
+ private _moveFocus(delta: number): void {
256
+ const maxIndex = this.data.length - 1;
257
+ let newIndex = this._focusedIndex + delta;
258
+
259
+ // Clamp to valid range
260
+ newIndex = Math.max(0, Math.min(newIndex, maxIndex));
261
+
262
+ this._setFocusedIndex(newIndex);
263
+ }
264
+
265
+ private _setFocusedIndex(index: number): void {
266
+ const prevIndex = this._focusedIndex;
267
+ this._focusedIndex = index;
268
+
269
+ // Update visual focus
270
+ if (prevIndex >= 0) {
271
+ const prevRow = this._getRowElement(prevIndex);
272
+ prevRow?.classList.remove('is-focused');
273
+ }
274
+
275
+ if (index >= 0) {
276
+ const row = this._getRowElement(index);
277
+ if (row) {
278
+ row.classList.add('is-focused');
279
+ this._scrollRowIntoView(row);
280
+ }
281
+ }
282
+ }
283
+
284
+ private _getRowElement(index: number): HTMLElement | null {
285
+ if (!this.el) return null;
286
+ return this.el.querySelector(`.ez-grid-row:nth-child(${index + 1})`);
287
+ }
288
+
289
+ private _scrollRowIntoView(row: HTMLElement): void {
290
+ if (!row || !this.el) return;
291
+
292
+ const bodyRect = this.el.getBoundingClientRect();
293
+ const rowRect = row.getBoundingClientRect();
294
+
295
+ if (rowRect.top < bodyRect.top) {
296
+ row.scrollIntoView({ block: 'start', behavior: 'smooth' });
297
+ } else if (rowRect.bottom > bodyRect.bottom) {
298
+ row.scrollIntoView({ block: 'end', behavior: 'smooth' });
299
+ }
300
+ }
301
+
302
+ private _selectFocusedRow(e: KeyboardEvent): void {
303
+ if (this._focusedIndex < 0 || !this.data) return;
304
+
305
+ const row = this.data[this._focusedIndex];
306
+ const grid = this.config.grid;
307
+
308
+ if (!grid || !row) return;
309
+
310
+ if (e.shiftKey) {
311
+ grid.selectRange?.(this._focusedIndex);
312
+ } else if (e.ctrlKey || e.metaKey) {
313
+ // Toggle selection without clearing others
314
+ grid.toggleRow?.(row, this._focusedIndex);
315
+ } else {
316
+ grid.toggleRow?.(row, this._focusedIndex);
317
+ }
318
+ }
319
+
320
+ setLoading(loading: boolean): void {
321
+ this._isLoading = !!loading;
322
+
323
+ if (!this.el) return;
324
+
325
+ if (this._isLoading) {
326
+ this._showLoadingOverlay();
327
+ } else {
328
+ this._hideLoadingOverlay();
329
+ }
330
+ }
331
+
332
+ setError(error: Error | string | null): void {
333
+ this._error = error;
334
+
335
+ if (!this.el) return;
336
+
337
+ if (this._error) {
338
+ this._showErrorState();
339
+ } else {
340
+ this._hideErrorState();
341
+ }
342
+ }
343
+
344
+ private _showLoadingOverlay(): void {
345
+ if (this._loadingEl) return;
346
+
347
+ this._loadingEl = document.createElement('div');
348
+ this._loadingEl.className = 'ez-grid-loading';
349
+ this._loadingEl.innerHTML = `
350
+ <div class="ez-grid-loading-spinner"></div>
351
+ <span class="ez-grid-loading-text">Loading...</span>
352
+ `;
353
+
354
+ this.el!.appendChild(this._loadingEl);
355
+ }
356
+
357
+ private _hideLoadingOverlay(): void {
358
+ if (this._loadingEl) {
359
+ this._loadingEl.remove();
360
+ this._loadingEl = null;
361
+ }
362
+ }
363
+
364
+ private _showErrorState(): void {
365
+ // Remove existing error if any
366
+ this._hideErrorState();
367
+
368
+ const message = this._error instanceof Error
369
+ ? this._error.message
370
+ : String(this._error);
371
+
372
+ this._errorEl = document.createElement('div');
373
+ this._errorEl.className = 'ez-grid-error';
374
+ this._errorEl.innerHTML = `
375
+ <div class="ez-grid-error-icon">⚠</div>
376
+ <div class="ez-grid-error-message">${this._escapeHtml(message)}</div>
377
+ <button class="ez-grid-error-retry">Retry</button>
378
+ `;
379
+
380
+ // Retry button handler
381
+ const retryBtn = this._errorEl.querySelector('.ez-grid-error-retry');
382
+ retryBtn?.addEventListener('click', () => {
383
+ (this.config.grid as any)?.controller?.reload?.();
384
+ });
385
+
386
+ this.el!.innerHTML = '';
387
+ this.el!.appendChild(this._errorEl);
388
+ }
389
+
390
+ private _hideErrorState(): void {
391
+ if (this._errorEl) {
392
+ this._errorEl.remove();
393
+ this._errorEl = null;
394
+ }
395
+ }
396
+
397
+ private _showEmptyState(): void {
398
+ const emptyText = this.config.emptyText ?? 'No data available';
399
+
400
+ this._emptyEl = document.createElement('div');
401
+ this._emptyEl.className = 'ez-grid-empty';
402
+ this._emptyEl.textContent = emptyText;
403
+
404
+ this.el!.appendChild(this._emptyEl);
405
+ }
406
+
407
+ private _escapeHtml(str: string): string {
408
+ const div = document.createElement('div');
409
+ div.textContent = str;
410
+ return div.innerHTML;
411
+ }
412
+
413
+ updateSelectionVisuals(): void {
414
+ if (!this.el) return;
415
+
416
+ const grid = this.config.grid;
417
+ if (!grid) return;
418
+
419
+ const rows = this.el.querySelectorAll('.ez-grid-row[data-ez-row-id]');
420
+
421
+ rows.forEach(rowEl => {
422
+ const rowId = rowEl.getAttribute('data-ez-row-id');
423
+ const isSelected = grid.selection?.selected?.has?.(String(rowId));
424
+
425
+ if (isSelected) {
426
+ rowEl.classList.add('is-selected');
427
+ } else {
428
+ rowEl.classList.remove('is-selected');
429
+ }
430
+
431
+ // Also update checkbox if exists
432
+ const checkbox = rowEl.querySelector('input[type="checkbox"]') as HTMLInputElement | null;
433
+ if (checkbox) {
434
+ checkbox.checked = !!isSelected;
435
+ }
436
+ });
437
+ }
438
+
439
+ async refreshData(data: any[]): Promise<void> {
440
+ this.data = Array.isArray(data) ? data : [];
441
+
442
+ // Reset focus when data changes
443
+ this._focusedIndex = -1;
444
+
445
+ // Clear current content
446
+ if (this.el) {
447
+ this.el.innerHTML = '';
448
+ this._loadingEl = null;
449
+ this._errorEl = null;
450
+ this._emptyEl = null;
451
+ this._virtualContainer = null;
452
+ }
453
+
454
+ // Show loading if active
455
+ if (this._isLoading) {
456
+ this._showLoadingOverlay();
457
+ return;
458
+ }
459
+
460
+ // Show error if present
461
+ if (this._error) {
462
+ this._showErrorState();
463
+ return;
464
+ }
465
+
466
+ // Show empty state if no data
467
+ if (this.data.length === 0) {
468
+ this._showEmptyState();
469
+ return;
470
+ }
471
+
472
+ // Virtual scrolling mode
473
+ if (this._virtual) {
474
+ await this._renderVirtual();
475
+ return;
476
+ }
477
+
478
+ // Standard rendering - all rows
479
+ for (let i = 0; i < this.data.length; i++) {
480
+ const row = this.data[i];
481
+
482
+ const rowCfg = this._createRowConfig(row, i);
483
+ const rowEl = await ez._createElement(rowCfg);
484
+
485
+ if (rowEl) {
486
+ this.el!.appendChild(rowEl);
487
+ }
488
+ }
489
+ }
490
+
491
+ private async _renderVirtual(): Promise<void> {
492
+ // Create spacer for total height
493
+ const totalHeight = this.data.length * this._rowHeight;
494
+
495
+ // Virtual container with absolute positioning
496
+ this._virtualContainer = document.createElement('div');
497
+ this._virtualContainer.className = 'ez-grid-virtual-container';
498
+ this._virtualContainer.style.position = 'relative';
499
+ this._virtualContainer.style.height = `${totalHeight}px`;
500
+ this._virtualContainer.style.width = '100%';
501
+
502
+ this.el!.appendChild(this._virtualContainer);
503
+
504
+ // Calculate initial visible range
505
+ const viewportHeight = this.el!.clientHeight || 400;
506
+ const visibleCount = Math.ceil(viewportHeight / this._rowHeight);
507
+
508
+ this._visibleStart = 0;
509
+ this._visibleEnd = Math.min(this.data.length, visibleCount + this._bufferRows);
510
+
511
+ // Render initial rows
512
+ await this._renderVirtualRows();
513
+ }
514
+
515
+ private _createRowConfig(row: any, index: number): any {
516
+
517
+ return {
518
+ eztype: 'EzGridRow',
519
+ row,
520
+ index,
521
+ rowCount: this.data.length,
522
+ columns: typeof this.config.columns === 'function'
523
+ ? this.config.columns()
524
+ : this.config.columns,
525
+ rowKey: this.config.rowKey,
526
+ selection: this.config.selection,
527
+ rowHeight: this.config.rowHeight,
528
+ onRowClick: this.config.onRowClick,
529
+ onRowDoubleClick: this.config.onRowDoubleClick,
530
+ onRowContextMenu: this.config.onRowContextMenu,
531
+
532
+ onClick: (e: MouseEvent) => {
533
+ const grid = this.config.grid;
534
+
535
+ if (!grid) return;
536
+
537
+ if (e.shiftKey) {
538
+ grid.selectRange?.(index);
539
+ } else {
540
+ grid.toggleRow?.(row, index);
541
+ }
542
+ },
543
+
544
+ controller: this.config.controller,
545
+ grid: this.config.grid
546
+ };
547
+
548
+ }
549
+ }