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,448 @@
1
+ // EzGrid/footer/EzGridFooter.ts
2
+
3
+ import './EzGridFooter.scss';
4
+ import { EzGridContainer, EzGridContainerConfig } from '../EzGridContainer.js';
5
+ import type { NormalizedColumn, EzGridController } from '../types.js';
6
+
7
+ declare const ez: {
8
+ _createElement(config: any): Promise<HTMLElement | null>;
9
+ };
10
+
11
+ export interface EzGridFooterRef {
12
+ controller?: EzGridController;
13
+ getVisibleColumns?: () => NormalizedColumn[];
14
+ columns?: NormalizedColumn[];
15
+ refreshBody?: () => Promise<void>;
16
+ }
17
+
18
+ export interface EzGridFooterConfig extends EzGridContainerConfig {
19
+ grid?: EzGridFooterRef;
20
+ }
21
+
22
+ export class EzGridFooter extends EzGridContainer {
23
+ declare config: EzGridFooterConfig;
24
+
25
+ grid: EzGridFooterRef | undefined;
26
+ private _pageSizeOptions: number[];
27
+ private _infoEl: HTMLElement | null;
28
+ private _pageIndicatorEl: HTMLElement | null;
29
+ private _btnFirst: HTMLButtonElement | null;
30
+ private _btnPrev: HTMLButtonElement | null;
31
+ private _btnNext: HTMLButtonElement | null;
32
+ private _btnLast: HTMLButtonElement | null;
33
+ private _btnReload: HTMLButtonElement | null;
34
+ private _pageSizeInstance: any;
35
+ private _statsEl: HTMLElement | null;
36
+
37
+ constructor(config: EzGridFooterConfig = {}) {
38
+ super(config);
39
+
40
+ this.grid = config.grid;
41
+
42
+ this.config.cls = (this.config.cls || '') + ' ez-grid-footer';
43
+ this.config.layout = 'hbox';
44
+
45
+ this._pageSizeOptions = [10, 25, 50, 100];
46
+ this._infoEl = null;
47
+ this._pageIndicatorEl = null;
48
+ this._btnFirst = null;
49
+ this._btnPrev = null;
50
+ this._btnNext = null;
51
+ this._btnLast = null;
52
+ this._btnReload = null;
53
+ this._pageSizeInstance = null;
54
+ this._statsEl = null;
55
+
56
+ this._buildItems();
57
+ }
58
+
59
+ private _buildItems(): void {
60
+ const items: any[] = [];
61
+
62
+ // Stats row (if any column has summary)
63
+ const statsRow = this._buildStatsRow();
64
+ if (statsRow) {
65
+ items.push(statsRow);
66
+ }
67
+
68
+ // Pagination row
69
+ items.push({
70
+ eztype: 'EzComponent',
71
+ cls: 'ez-grid-footer-pagination',
72
+ layout: 'hbox',
73
+ flex: 1,
74
+ items: [
75
+ // Navigation buttons (left)
76
+ this._buildNavigationButtons(),
77
+
78
+ // Spacer
79
+ { eztype: 'EzComponent', flex: 1 },
80
+
81
+ // Page size selector
82
+ this._buildPageSizeSelector(),
83
+
84
+ // Page info (right)
85
+ this._buildPageInfo()
86
+ ]
87
+ });
88
+
89
+ this.config.items = items;
90
+ }
91
+
92
+ private _buildStatsRow(): any | null {
93
+ const columns = this._getColumns();
94
+
95
+ // Check if any column has summary
96
+ const hasSummary = columns.some(col => (col as any).summary);
97
+ if (!hasSummary) return null;
98
+
99
+ return {
100
+ eztype: 'EzComponent',
101
+ cls: 'ez-grid-footer-stats',
102
+ layout: 'hbox',
103
+ _ezAfterRender: (el: HTMLElement) => {
104
+ this._statsEl = el;
105
+ },
106
+ items: columns.map(col => this._buildStatCell(col))
107
+ };
108
+ }
109
+
110
+ private _buildStatCell(col: NormalizedColumn): any {
111
+ // Width / flex - same as header/body cells
112
+ const cellStyle = col.width != null
113
+ ? { width: `${col.width}px` }
114
+ : undefined;
115
+
116
+ const cellFlex = col.width != null
117
+ ? undefined
118
+ : (col.flex ?? 1);
119
+
120
+ // No summary - empty cell
121
+ if (!(col as any).summary) {
122
+ return {
123
+ eztype: 'EzComponent',
124
+ cls: 'ez-grid-footer-stat-cell',
125
+ style: cellStyle,
126
+ flex: cellFlex
127
+ };
128
+ }
129
+
130
+ return {
131
+ eztype: 'EzComponent',
132
+ cls: 'ez-grid-footer-stat-cell',
133
+ style: cellStyle,
134
+ flex: cellFlex,
135
+ attrs: {
136
+ 'data-col-id': col._id
137
+ },
138
+ _ezAfterRender: (el: HTMLElement) => {
139
+ this._updateStatCell(el, col);
140
+ }
141
+ };
142
+ }
143
+
144
+ private _updateStatCell(el: HTMLElement, col: NormalizedColumn): void {
145
+ if (!el || !(col as any).summary) return;
146
+
147
+ const value = this._calculateStat(col);
148
+ const label = this._getStatLabel((col as any).summary);
149
+
150
+ el.textContent = `${label}: ${value}`;
151
+ }
152
+
153
+ private _calculateStat(col: NormalizedColumn): string | number {
154
+ const data = this.grid?.controller?.state?.data ?? [];
155
+ const field = col.index;
156
+
157
+ if (!field || data.length === 0) {
158
+ return '-';
159
+ }
160
+
161
+ const values = data
162
+ .map(row => row[field])
163
+ .filter(v => v != null && v !== '');
164
+
165
+ const numericValues = values
166
+ .map(v => parseFloat(String(v)))
167
+ .filter(n => !isNaN(n));
168
+
169
+ const summaryType = typeof (col as any).summary === 'string'
170
+ ? (col as any).summary
171
+ : (col as any).summary?.type ?? 'count';
172
+
173
+ switch (summaryType.toLowerCase()) {
174
+ case 'count':
175
+ return values.length;
176
+
177
+ case 'sum':
178
+ return numericValues.length
179
+ ? this._formatNumber(numericValues.reduce((a, b) => a + b, 0))
180
+ : '-';
181
+
182
+ case 'avg':
183
+ case 'average':
184
+ return numericValues.length
185
+ ? this._formatNumber(numericValues.reduce((a, b) => a + b, 0) / numericValues.length)
186
+ : '-';
187
+
188
+ case 'min':
189
+ return numericValues.length
190
+ ? this._formatNumber(Math.min(...numericValues))
191
+ : '-';
192
+
193
+ case 'max':
194
+ return numericValues.length
195
+ ? this._formatNumber(Math.max(...numericValues))
196
+ : '-';
197
+
198
+ default:
199
+ return values.length;
200
+ }
201
+ }
202
+
203
+ private _getStatLabel(summary: any): string {
204
+ const type = typeof summary === 'string'
205
+ ? summary
206
+ : summary?.type ?? 'count';
207
+
208
+ const labels: Record<string, string> = {
209
+ count: 'Count',
210
+ sum: 'Sum',
211
+ avg: 'Avg',
212
+ average: 'Avg',
213
+ min: 'Min',
214
+ max: 'Max'
215
+ };
216
+
217
+ return labels[type.toLowerCase()] ?? 'Count';
218
+ }
219
+
220
+ private _formatNumber(num: number): string {
221
+ if (Number.isInteger(num)) {
222
+ return num.toLocaleString();
223
+ }
224
+ return num.toLocaleString(undefined, {
225
+ minimumFractionDigits: 0,
226
+ maximumFractionDigits: 2
227
+ });
228
+ }
229
+
230
+ private _getColumns(): NormalizedColumn[] {
231
+ const rawColumns = this.grid?.getVisibleColumns?.()
232
+ ?? this.grid?.columns
233
+ ?? [];
234
+
235
+ return typeof rawColumns === 'function'
236
+ ? (rawColumns as () => NormalizedColumn[])()
237
+ : rawColumns;
238
+ }
239
+
240
+ private _refreshStats(): void {
241
+ if (!this._statsEl) return;
242
+
243
+ const columns = this._getColumns();
244
+
245
+ for (const col of columns) {
246
+ if (!(col as any).summary) continue;
247
+
248
+ const cell = this._statsEl.querySelector(`[data-col-id="${col._id}"]`) as HTMLElement | null;
249
+ if (cell) {
250
+ this._updateStatCell(cell, col);
251
+ }
252
+ }
253
+ }
254
+
255
+ private _buildPageInfo(): any {
256
+ return {
257
+ eztype: 'EzComponent',
258
+ cls: 'ez-grid-footer-info',
259
+ _ezAfterRender: (el: HTMLElement) => {
260
+ this._infoEl = el;
261
+ this._updatePageInfo();
262
+ }
263
+ };
264
+ }
265
+
266
+ private _updatePageInfo(): void {
267
+ if (!this._infoEl) return;
268
+
269
+ const state = this.grid?.controller?.state;
270
+ if (!state) {
271
+ this._infoEl.textContent = '';
272
+ return;
273
+ }
274
+
275
+ const { page, pageSize, total } = state;
276
+ const start = total === 0 ? 0 : (page - 1) * pageSize + 1;
277
+ const end = Math.min(page * pageSize, total);
278
+
279
+ this._infoEl.textContent = `${start} - ${end} of ${total}`;
280
+ }
281
+
282
+ private _buildPageSizeSelector(): any {
283
+ const currentSize = this.grid?.controller?.state?.pageSize ?? 25;
284
+
285
+ return {
286
+ eztype: 'EzComponent',
287
+ cls: 'ez-grid-footer-pagesize',
288
+ layout: 'hbox',
289
+ items: [
290
+ {
291
+ eztype: 'EzLabel',
292
+ text: 'Per page:',
293
+ cls: 'ez-grid-footer-pagesize-label'
294
+ },
295
+ {
296
+ eztype: 'EzSelect',
297
+ cls: 'ez-grid-footer-pagesize-select',
298
+ options: this._pageSizeOptions,
299
+ value: currentSize,
300
+ onChange: (newSize: string) => {
301
+ this.grid?.controller?.setPageSize?.(parseInt(newSize, 10));
302
+ this.refresh();
303
+ },
304
+ _ezAfterRender: (el: HTMLElement, instance: any) => {
305
+ this._pageSizeInstance = instance;
306
+ }
307
+ }
308
+ ]
309
+ };
310
+ }
311
+
312
+ private _buildNavigationButtons(): any {
313
+ return {
314
+ eztype: 'EzComponent',
315
+ cls: 'ez-grid-footer-nav',
316
+ layout: 'hbox',
317
+ items: [
318
+ {
319
+ eztype: 'button',
320
+ cls: 'ez-grid-footer-nav-btn',
321
+ text: '«',
322
+ attrs: { title: 'First page' },
323
+ _ezAfterRender: (el: HTMLButtonElement) => {
324
+ this._btnFirst = el;
325
+ el.addEventListener('click', () => this._goToPage(1));
326
+ }
327
+ },
328
+ {
329
+ eztype: 'button',
330
+ cls: 'ez-grid-footer-nav-btn',
331
+ text: '‹',
332
+ attrs: { title: 'Previous page' },
333
+ _ezAfterRender: (el: HTMLButtonElement) => {
334
+ this._btnPrev = el;
335
+ el.addEventListener('click', () => {
336
+ const page = this.grid?.controller?.state?.page ?? 1;
337
+ this._goToPage(page - 1);
338
+ });
339
+ }
340
+ },
341
+ {
342
+ eztype: 'EzComponent',
343
+ cls: 'ez-grid-footer-page-indicator',
344
+ _ezAfterRender: (el: HTMLElement) => {
345
+ this._pageIndicatorEl = el;
346
+ this._updatePageIndicator();
347
+ }
348
+ },
349
+ {
350
+ eztype: 'button',
351
+ cls: 'ez-grid-footer-nav-btn',
352
+ text: '›',
353
+ attrs: { title: 'Next page' },
354
+ _ezAfterRender: (el: HTMLButtonElement) => {
355
+ this._btnNext = el;
356
+ el.addEventListener('click', () => {
357
+ const page = this.grid?.controller?.state?.page ?? 1;
358
+ this._goToPage(page + 1);
359
+ });
360
+ }
361
+ },
362
+ {
363
+ eztype: 'button',
364
+ cls: 'ez-grid-footer-nav-btn',
365
+ text: '»',
366
+ attrs: { title: 'Last page' },
367
+ _ezAfterRender: (el: HTMLButtonElement) => {
368
+ this._btnLast = el;
369
+ el.addEventListener('click', () => {
370
+ const totalPages = this._getTotalPages();
371
+ this._goToPage(totalPages);
372
+ });
373
+ }
374
+ },
375
+ {
376
+ eztype: 'button',
377
+ cls: 'ez-grid-footer-nav-btn ez-grid-footer-reload-btn',
378
+ text: '↻',
379
+ attrs: { title: 'Reload' },
380
+ _ezAfterRender: (el: HTMLButtonElement) => {
381
+ this._btnReload = el;
382
+ el.addEventListener('click', () => {
383
+ void this.grid?.refreshBody?.();
384
+ });
385
+ }
386
+ }
387
+ ]
388
+ };
389
+ }
390
+
391
+ private _goToPage(page: number): void {
392
+ const totalPages = this._getTotalPages();
393
+ const safePage = Math.max(1, Math.min(page, totalPages));
394
+
395
+ this.grid?.controller?.setPage?.(safePage);
396
+ this.refresh();
397
+ }
398
+
399
+ private _getTotalPages(): number {
400
+ const state = this.grid?.controller?.state;
401
+ if (!state) return 1;
402
+
403
+ const { pageSize, total } = state;
404
+ return Math.max(1, Math.ceil(total / pageSize));
405
+ }
406
+
407
+ private _updatePageIndicator(): void {
408
+ if (!this._pageIndicatorEl) return;
409
+
410
+ const state = this.grid?.controller?.state;
411
+ if (!state) {
412
+ this._pageIndicatorEl.textContent = '1 / 1';
413
+ return;
414
+ }
415
+
416
+ const { page } = state;
417
+ const totalPages = this._getTotalPages();
418
+
419
+ this._pageIndicatorEl.textContent = `${page} / ${totalPages}`;
420
+ }
421
+
422
+ private _updateButtonStates(): void {
423
+ const state = this.grid?.controller?.state;
424
+ const page = state?.page ?? 1;
425
+ const totalPages = this._getTotalPages();
426
+
427
+ const isFirst = page <= 1;
428
+ const isLast = page >= totalPages;
429
+
430
+ if (this._btnFirst) this._btnFirst.disabled = isFirst;
431
+ if (this._btnPrev) this._btnPrev.disabled = isFirst;
432
+ if (this._btnNext) this._btnNext.disabled = isLast;
433
+ if (this._btnLast) this._btnLast.disabled = isLast;
434
+ }
435
+
436
+ refresh(): void {
437
+ this._updatePageInfo();
438
+ this._updatePageIndicator();
439
+ this._updateButtonStates();
440
+ this._refreshStats();
441
+
442
+ // Update pageSize select
443
+ if (this._pageSizeInstance) {
444
+ const currentSize = this.grid?.controller?.state?.pageSize ?? 25;
445
+ this._pageSizeInstance.setValue(currentSize);
446
+ }
447
+ }
448
+ }
@@ -0,0 +1,199 @@
1
+ // ==========================================================
2
+ // EzGridHeader
3
+ // ==========================================================
4
+
5
+ .ez-grid-header {
6
+ display: flex;
7
+ align-items: stretch;
8
+
9
+ background-color: var(--ez-grid-surface);
10
+ border-bottom: 1px solid var(--ez-grid-border);
11
+
12
+ min-height: var(--ez-grid-header-height);
13
+
14
+ // ----------------------------------------------------------
15
+ // Header cells
16
+ // ----------------------------------------------------------
17
+ .ez-grid-header-cell {
18
+ display: flex;
19
+ align-items: center;
20
+ position: relative;
21
+
22
+ padding: var(--ez-grid-cell-padding);
23
+ height: var(--ez-grid-header-height);
24
+ box-sizing: border-box;
25
+
26
+ font-size: var(--ez-grid-font-size);
27
+ font-weight: var(--ez-grid-font-weight-semibold);
28
+ color: var(--ez-grid-text-secondary);
29
+
30
+ white-space: nowrap;
31
+ overflow: hidden;
32
+ text-overflow: ellipsis;
33
+
34
+ user-select: none;
35
+
36
+ // Subtle separator between columns
37
+ &:not(:last-child)::after {
38
+ content: '';
39
+ position: absolute;
40
+ top: 10px;
41
+ right: 0;
42
+ width: 1px;
43
+ height: calc(100% - 20px);
44
+ background-color: var(--ez-grid-border-light);
45
+ }
46
+
47
+ // Hover state
48
+ &:hover {
49
+ background-color: var(--ez-grid-surface-hover);
50
+ cursor: pointer;
51
+ }
52
+
53
+ // --------------------------------------------------------
54
+ // Alignment helpers
55
+ // --------------------------------------------------------
56
+ &.align-left {
57
+ justify-content: flex-start;
58
+
59
+ .ez-grid-header-text {
60
+ text-align: left;
61
+ }
62
+ }
63
+
64
+ &.align-center {
65
+ justify-content: center;
66
+
67
+ .ez-grid-header-text {
68
+ text-align: center;
69
+ }
70
+ }
71
+
72
+ &.align-right {
73
+ justify-content: flex-end;
74
+
75
+ .ez-grid-header-text {
76
+ text-align: right;
77
+ }
78
+ }
79
+
80
+ // --------------------------------------------------------
81
+ // Header text
82
+ // --------------------------------------------------------
83
+ .ez-grid-header-text {
84
+ flex: 1;
85
+ overflow: hidden;
86
+ text-overflow: ellipsis;
87
+ }
88
+
89
+ // --------------------------------------------------------
90
+ // Sort icon
91
+ // --------------------------------------------------------
92
+ .ez-grid-header-sort-icon {
93
+ margin-left: 6px;
94
+ font-size: 11px;
95
+ opacity: 0.4;
96
+ color: var(--ez-grid-text-muted);
97
+
98
+ transition: opacity 0.15s ease,
99
+ transform 0.15s ease,
100
+ color 0.15s ease;
101
+ }
102
+
103
+ // --------------------------------------------------------
104
+ // Sorted states
105
+ // --------------------------------------------------------
106
+ &.is-sorted {
107
+ background-color: var(--ez-grid-surface-hover);
108
+ color: var(--ez-grid-text);
109
+
110
+ .ez-grid-header-sort-icon {
111
+ opacity: 1;
112
+ color: var(--ez-grid-primary);
113
+ }
114
+ }
115
+
116
+ &.is-sort-asc {
117
+ .ez-grid-header-sort-icon {
118
+ transform: translateY(-1px);
119
+ }
120
+ }
121
+
122
+ &.is-sort-desc {
123
+ .ez-grid-header-sort-icon {
124
+ transform: translateY(1px);
125
+ }
126
+ }
127
+
128
+ // --------------------------------------------------------
129
+ // Resize handle
130
+ // --------------------------------------------------------
131
+ &.is-resizable {
132
+ .ez-grid-resize-handle {
133
+ position: absolute;
134
+ top: 0;
135
+ right: 0;
136
+ width: 6px;
137
+ height: 100%;
138
+ cursor: col-resize;
139
+ z-index: 10;
140
+
141
+ // Hover indicator
142
+ &::after {
143
+ content: '';
144
+ position: absolute;
145
+ top: 8px;
146
+ right: 2px;
147
+ width: 2px;
148
+ height: calc(100% - 16px);
149
+ background-color: transparent;
150
+ border-radius: 1px;
151
+ transition: background-color 0.15s ease;
152
+ }
153
+
154
+ &:hover::after {
155
+ background-color: var(--ez-grid-primary);
156
+ }
157
+ }
158
+
159
+ // Hide separator when resizable
160
+ &:not(:last-child)::after {
161
+ display: none;
162
+ }
163
+ }
164
+
165
+ // --------------------------------------------------------
166
+ // Drag & Drop reorder
167
+ // --------------------------------------------------------
168
+ &.is-reorderable {
169
+ cursor: grab;
170
+
171
+ &:active {
172
+ cursor: grabbing;
173
+ }
174
+ }
175
+
176
+ &.is-dragging {
177
+ opacity: 0.5;
178
+ cursor: grabbing;
179
+ }
180
+
181
+ &.drop-before {
182
+ box-shadow: inset 3px 0 0 var(--ez-grid-primary);
183
+ }
184
+
185
+ &.drop-after {
186
+ box-shadow: inset -3px 0 0 var(--ez-grid-primary);
187
+ }
188
+ }
189
+ }
190
+
191
+ // Global resize cursor (applied to body during resize)
192
+ body.ez-grid-resizing {
193
+ cursor: col-resize;
194
+ user-select: none;
195
+
196
+ * {
197
+ cursor: col-resize;
198
+ }
199
+ }