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,430 @@
1
+ // EzGrid/header/EzGridHeader.ts
2
+
3
+ import './EzGridHeader.scss';
4
+ import { EzGridContainer, EzGridContainerConfig } from '../EzGridContainer.js';
5
+ import type {
6
+ NormalizedColumn,
7
+ HeaderConfig,
8
+ SortDirection,
9
+ EzGridSelectionRef
10
+ } from '../types.js';
11
+
12
+ declare const ez: {
13
+ _createElement(config: unknown): Promise<HTMLElement | null>;
14
+ };
15
+
16
+ export interface EzGridHeaderRef {
17
+ selection?: EzGridSelectionRef;
18
+ isAllSelected: () => boolean;
19
+ clearSelection: () => void;
20
+ selectAll: () => void;
21
+ isColumnSorted?: (colId: string) => boolean;
22
+ getColumnSortDirection?: (colId: string) => SortDirection | null;
23
+ toggleSort?: (colId: string) => void;
24
+ getVisibleColumns?: () => NormalizedColumn[];
25
+ columns?: NormalizedColumn[];
26
+ setColumnWidth?: (colId: string, width: number) => void;
27
+ moveColumn?: (colId: string, toIndex: number) => void;
28
+ }
29
+
30
+ export interface EzGridHeaderConfig extends EzGridContainerConfig {
31
+ header?: HeaderConfig;
32
+ columns?: (() => NormalizedColumn[]) | NormalizedColumn[];
33
+ grid?: EzGridHeaderRef;
34
+ reorderable?: boolean;
35
+ }
36
+
37
+ export class EzGridHeader extends EzGridContainer {
38
+ declare config: EzGridHeaderConfig;
39
+ declare el: HTMLElement | null;
40
+
41
+ private _resizing: boolean;
42
+ private _resizeCol: NormalizedColumn | null;
43
+ private _resizeStartX: number;
44
+ private _resizeStartWidth: number;
45
+ private _reorderable: boolean;
46
+ private _dragging: boolean;
47
+ private _dragCol: NormalizedColumn | null;
48
+ private _dragOverCol: NormalizedColumn | null;
49
+ private _defaultAlign: 'left' | 'center' | 'right';
50
+ private _onMouseMove?: (e: MouseEvent) => void;
51
+ private _onMouseUp?: () => void;
52
+
53
+ constructor(config: EzGridHeaderConfig = {}) {
54
+ super(config);
55
+
56
+ const { header } = config;
57
+
58
+ this.config.cls = (this.config.cls || '') + ' ez-grid-header';
59
+ this.config.layout = 'hbox';
60
+
61
+ // Resize state
62
+ this._resizing = false;
63
+ this._resizeCol = null;
64
+ this._resizeStartX = 0;
65
+ this._resizeStartWidth = 0;
66
+
67
+ // Drag reorder state
68
+ this._reorderable = config.reorderable !== false;
69
+ this._dragging = false;
70
+ this._dragCol = null;
71
+ this._dragOverCol = null;
72
+
73
+ // Apply global header config
74
+ this._defaultAlign = header?.align ?? 'left';
75
+
76
+ if (header?.cls) {
77
+ this.config.cls += ` ${header.cls}`;
78
+ }
79
+
80
+ if (header?.height) {
81
+ this.config.style = {
82
+ ...(this.config.style || {}),
83
+ minHeight: `${header.height}px`
84
+ };
85
+ }
86
+
87
+ if (header?.visible === false) {
88
+ this.config.style = {
89
+ ...(this.config.style || {}),
90
+ display: 'none'
91
+ };
92
+ }
93
+
94
+ this._buildColumns();
95
+ }
96
+
97
+ private _buildColumns(): void {
98
+ const rawColumns = this.config.columns;
99
+
100
+ const columns: NormalizedColumn[] =
101
+ typeof rawColumns === 'function'
102
+ ? rawColumns()
103
+ : (rawColumns || []);
104
+
105
+ const grid = this.config.grid;
106
+
107
+ const hasSelection = !!grid?.selection;
108
+
109
+ this.config.items = columns.map(col => {
110
+
111
+ // NOTE: Sort state must be queried via the Grid API.
112
+ // Header must not read legacy sortState or internal structures.
113
+ const isSorted = grid?.isColumnSorted?.(col._id) === true;
114
+ const dir = grid?.getColumnSortDirection?.(col._id);
115
+
116
+ // SELECT ALL
117
+ if (col.type === 'selection' && hasSelection) {
118
+ const checked = grid.isAllSelected();
119
+ const selectionWidth = col.width ?? 36;
120
+
121
+ return {
122
+ eztype: 'EzComponent',
123
+ cls: 'ez-grid-header-cell ez-grid-header-select-all',
124
+ style: { width: `${selectionWidth}px`, justifyContent: 'center' },
125
+
126
+ items: [{
127
+ eztype: 'input',
128
+ attrs: {
129
+ type: 'checkbox',
130
+ checked: checked ? true : null
131
+ },
132
+ onClick: (e: MouseEvent) => {
133
+ e.stopPropagation();
134
+
135
+ if (grid.isAllSelected()) {
136
+ grid.clearSelection();
137
+ } else {
138
+ grid.selectAll();
139
+ }
140
+ }
141
+ }]
142
+ };
143
+ }
144
+
145
+ if (col.type === 'actions') {
146
+ return {
147
+ eztype: 'EzComponent',
148
+ cls: 'ez-grid-header-cell align-center ez-grid-header-actions',
149
+ style: { width: `${col.width}px` }
150
+ };
151
+ }
152
+
153
+ // width / flex - same logic as EzGridCell
154
+ const cellStyle = col.width != null
155
+ ? { width: `${col.width}px` }
156
+ : undefined;
157
+
158
+ const cellFlex = col.width != null
159
+ ? undefined
160
+ : (col.flex ?? 1);
161
+
162
+ // Check if column is resizable (default true unless explicitly false)
163
+ const resizable = col.resizable !== false && col.type !== 'selection';
164
+
165
+ // Check if column is reorderable
166
+ const canReorder = this._reorderable &&
167
+ col.reorderable !== false &&
168
+ col.type !== 'selection';
169
+
170
+ // Column-level align overrides global header.align
171
+ const cellAlign = col.header?.align ?? this._defaultAlign;
172
+
173
+ // Build cell style with alignment
174
+ const alignJustify = cellAlign === 'center' ? 'center'
175
+ : cellAlign === 'right' ? 'flex-end'
176
+ : 'flex-start';
177
+
178
+ const mergedStyle = {
179
+ ...cellStyle,
180
+ justifyContent: alignJustify
181
+ };
182
+
183
+ const items: any[] = [
184
+ {
185
+ eztype: 'EzComponent',
186
+ cls: 'ez-grid-header-text',
187
+ text: col.text ?? '',
188
+ style: { textAlign: cellAlign }
189
+ },
190
+ {
191
+ eztype: 'i',
192
+ cls: [
193
+ 'ez-grid-header-sort-icon',
194
+ dir === 'ASC'
195
+ ? 'fa fa-chevron-up'
196
+ : dir === 'DESC'
197
+ ? 'fa fa-chevron-down'
198
+ : ''
199
+ ].filter(Boolean).join(' ')
200
+ }
201
+ ];
202
+
203
+ // Add resize handle if resizable
204
+ if (resizable) {
205
+ items.push({
206
+ eztype: 'EzComponent',
207
+ cls: 'ez-grid-resize-handle',
208
+ attrs: {
209
+ 'data-col-id': col._id
210
+ },
211
+ onMouseDown: (e: MouseEvent) => {
212
+ e.stopPropagation();
213
+ this._startResize(e, col);
214
+ }
215
+ });
216
+ }
217
+
218
+ return {
219
+ eztype: 'EzComponent',
220
+ cls: [
221
+ 'ez-grid-header-cell',
222
+ `align-${cellAlign}`,
223
+ isSorted && 'is-sorted',
224
+ dir === 'ASC' && 'is-sort-asc',
225
+ dir === 'DESC' && 'is-sort-desc',
226
+ resizable && 'is-resizable',
227
+ canReorder && 'is-reorderable'
228
+ ].filter(Boolean).join(' '),
229
+
230
+ style: mergedStyle,
231
+ flex: cellFlex,
232
+ attrs: {
233
+ 'data-col-id': col._id,
234
+ draggable: canReorder ? 'true' : undefined
235
+ },
236
+
237
+ items,
238
+
239
+ onClick: () => {
240
+ // Don't trigger sort while resizing or dragging
241
+ if (this._resizing || this._dragging) return;
242
+
243
+ // WARNING: Header must toggle sort state, not force a direction.
244
+ // sortBy() is an imperative API and always resets to ASC.
245
+ grid?.toggleSort?.(col._id);
246
+ },
247
+
248
+ onDragStart: canReorder ? (e: DragEvent) => {
249
+ this._handleDragStart(e, col);
250
+ } : undefined,
251
+
252
+ onDragOver: canReorder ? (e: DragEvent) => {
253
+ this._handleDragOver(e, col);
254
+ } : undefined,
255
+
256
+ onDragLeave: canReorder ? (e: DragEvent) => {
257
+ this._handleDragLeave(e);
258
+ } : undefined,
259
+
260
+ onDrop: canReorder ? (e: DragEvent) => {
261
+ this._handleDrop(e, col);
262
+ } : undefined,
263
+
264
+ onDragEnd: canReorder ? () => {
265
+ this._handleDragEnd();
266
+ } : undefined
267
+ };
268
+ });
269
+ }
270
+
271
+ // ==========================================================
272
+ // Column Reorder (Drag & Drop)
273
+ // ==========================================================
274
+
275
+ private _handleDragStart(e: DragEvent, col: NormalizedColumn): void {
276
+ this._dragging = true;
277
+ this._dragCol = col;
278
+
279
+ // Set drag data
280
+ e.dataTransfer!.effectAllowed = 'move';
281
+ e.dataTransfer!.setData('text/plain', col._id);
282
+
283
+ // Add dragging class
284
+ const cell = (e.target as HTMLElement).closest('.ez-grid-header-cell');
285
+ if (cell) {
286
+ cell.classList.add('is-dragging');
287
+ }
288
+ }
289
+
290
+ private _handleDragOver(e: DragEvent, col: NormalizedColumn): void {
291
+ if (!this._dragging || !this._dragCol) return;
292
+ if (this._dragCol._id === col._id) return;
293
+
294
+ e.preventDefault();
295
+ e.dataTransfer!.dropEffect = 'move';
296
+
297
+ this._dragOverCol = col;
298
+
299
+ // Add visual indicator
300
+ const cell = (e.target as HTMLElement).closest('.ez-grid-header-cell');
301
+ if (cell) {
302
+ // Determine drop position (before or after)
303
+ const rect = cell.getBoundingClientRect();
304
+ const midX = rect.left + rect.width / 2;
305
+
306
+ cell.classList.remove('drop-before', 'drop-after');
307
+ cell.classList.add(e.clientX < midX ? 'drop-before' : 'drop-after');
308
+ }
309
+ }
310
+
311
+ private _handleDragLeave(e: DragEvent): void {
312
+ const cell = (e.target as HTMLElement).closest('.ez-grid-header-cell');
313
+ if (cell) {
314
+ cell.classList.remove('drop-before', 'drop-after');
315
+ }
316
+ }
317
+
318
+ private _handleDrop(e: DragEvent, col: NormalizedColumn): void {
319
+ e.preventDefault();
320
+
321
+ if (!this._dragging || !this._dragCol || !col) return;
322
+ if (this._dragCol._id === col._id) return;
323
+
324
+ const grid = this.config.grid;
325
+ if (!grid) return;
326
+
327
+ // Determine target index
328
+ const columns = grid.getVisibleColumns?.() ?? grid.columns ?? [];
329
+ const targetIndex = columns.findIndex(c => c._id === col._id);
330
+
331
+ if (targetIndex === -1) return;
332
+
333
+ // Determine if before or after based on drop position
334
+ const cell = (e.target as HTMLElement).closest('.ez-grid-header-cell');
335
+ const dropAfter = cell?.classList.contains('drop-after');
336
+
337
+ const finalIndex = dropAfter ? targetIndex + 1 : targetIndex;
338
+
339
+ // Move column via grid API
340
+ grid.moveColumn?.(this._dragCol._id, finalIndex);
341
+
342
+ // Clean up
343
+ this._handleDragEnd();
344
+ }
345
+
346
+ private _handleDragEnd(): void {
347
+ // Remove all drag classes
348
+ this.el?.querySelectorAll('.ez-grid-header-cell').forEach(cell => {
349
+ cell.classList.remove('is-dragging', 'drop-before', 'drop-after');
350
+ });
351
+
352
+ // Reset state (delay to prevent click)
353
+ setTimeout(() => {
354
+ this._dragging = false;
355
+ this._dragCol = null;
356
+ this._dragOverCol = null;
357
+ }, 50);
358
+ }
359
+
360
+ private _startResize(e: MouseEvent, col: NormalizedColumn): void {
361
+ e.preventDefault();
362
+
363
+ this._resizing = true;
364
+ this._resizeCol = col;
365
+ this._resizeStartX = e.clientX;
366
+
367
+ // Get current width from element or column config
368
+ const headerCell = this.el?.querySelector(`[data-col-id="${col._id}"]`) as HTMLElement | null;
369
+ this._resizeStartWidth = headerCell?.offsetWidth ?? col.width ?? 100;
370
+
371
+ // Add document-level listeners
372
+ this._onMouseMove = this._handleResizeMove.bind(this);
373
+ this._onMouseUp = this._endResize.bind(this);
374
+
375
+ document.addEventListener('mousemove', this._onMouseMove);
376
+ document.addEventListener('mouseup', this._onMouseUp);
377
+
378
+ // Add resizing class to body for cursor
379
+ document.body.classList.add('ez-grid-resizing');
380
+ }
381
+
382
+ private _handleResizeMove(e: MouseEvent): void {
383
+ if (!this._resizing || !this._resizeCol) return;
384
+
385
+ const delta = e.clientX - this._resizeStartX;
386
+ const newWidth = Math.max(50, this._resizeStartWidth + delta);
387
+
388
+ // Update column width via grid API
389
+ const grid = this.config.grid;
390
+ grid?.setColumnWidth?.(this._resizeCol._id, newWidth);
391
+ }
392
+
393
+ private _endResize(): void {
394
+ // Remove listeners
395
+ if (this._onMouseMove) {
396
+ document.removeEventListener('mousemove', this._onMouseMove);
397
+ }
398
+ if (this._onMouseUp) {
399
+ document.removeEventListener('mouseup', this._onMouseUp);
400
+ }
401
+
402
+ // Remove resizing class
403
+ document.body.classList.remove('ez-grid-resizing');
404
+
405
+ // Reset state after a small delay to prevent click event
406
+ setTimeout(() => {
407
+ this._resizing = false;
408
+ this._resizeCol = null;
409
+ }, 50);
410
+ }
411
+
412
+ async refreshColumns(): Promise<void> {
413
+ this._buildColumns();
414
+
415
+ // Force re-render
416
+ if (this.el) {
417
+ this.el.innerHTML = '';
418
+
419
+ // Create all elements in parallel but preserve order
420
+ const children = await Promise.all(
421
+ (this.config.items || []).map(item => ez._createElement(item))
422
+ );
423
+
424
+ for (const child of children) {
425
+ if (child) this.el.appendChild(child);
426
+ }
427
+ }
428
+ }
429
+
430
+ }
@@ -0,0 +1,81 @@
1
+ // EzGrid/query/EzGridQuery.ts
2
+
3
+ export type SortDirection = 'ASC' | 'DESC';
4
+
5
+ export interface SortEntry {
6
+ field: string;
7
+ direction: SortDirection;
8
+ }
9
+
10
+ export interface FilterRule {
11
+ field?: string;
12
+ operator?: string;
13
+ value?: any;
14
+ and?: FilterRule[];
15
+ or?: FilterRule[];
16
+ }
17
+
18
+ // ---- sort helpers ----
19
+ export function createSort(field: string, direction: SortDirection = 'ASC'): SortEntry[] {
20
+ return [
21
+ {
22
+ field,
23
+ direction
24
+ }
25
+ ];
26
+ }
27
+
28
+ export function clearSort(): null {
29
+ return null;
30
+ }
31
+
32
+ // ---- filter helpers ----
33
+ export function eq(field: string, value: any): FilterRule {
34
+ return { field, operator: '=', value };
35
+ }
36
+
37
+ export function ne(field: string, value: any): FilterRule {
38
+ return { field, operator: '!=', value };
39
+ }
40
+
41
+ export function gt(field: string, value: any): FilterRule {
42
+ return { field, operator: '>', value };
43
+ }
44
+
45
+ export function gte(field: string, value: any): FilterRule {
46
+ return { field, operator: '>=', value };
47
+ }
48
+
49
+ export function lt(field: string, value: any): FilterRule {
50
+ return { field, operator: '<', value };
51
+ }
52
+
53
+ export function lte(field: string, value: any): FilterRule {
54
+ return { field, operator: '<=', value };
55
+ }
56
+
57
+ export function like(field: string, value: any): FilterRule {
58
+ return { field, operator: 'LIKE', value };
59
+ }
60
+
61
+ export function and(...rules: FilterRule[]): FilterRule {
62
+ return { and: rules };
63
+ }
64
+
65
+ export function or(...rules: FilterRule[]): FilterRule {
66
+ return { or: rules };
67
+ }
68
+
69
+ export function between(field: string, from: any, to: any): FilterRule {
70
+ if (import.meta?.env?.DEV && arguments.length !== 3) {
71
+ console.warn(
72
+ '[EzGridQuery] between() expects (field, from, to)'
73
+ );
74
+ }
75
+
76
+ return {
77
+ field,
78
+ operator: 'BETWEEN',
79
+ value: [from, to]
80
+ };
81
+ }
@@ -0,0 +1,155 @@
1
+ // EzGrid/state/EzGridColumns.ts
2
+
3
+ import type {
4
+ NormalizedColumn,
5
+ ColumnsSnapshot
6
+ } from '../types.js';
7
+
8
+ export type EzGridColumn = NormalizedColumn;
9
+
10
+ export interface ColumnsState {
11
+ visibility: Record<string, boolean>;
12
+ order: string[] | null;
13
+ widths: Record<string, number>;
14
+ }
15
+
16
+ export interface EzGridColumnsRef {
17
+ _onColumnsChanged: () => void;
18
+ }
19
+
20
+ // Re-export for backwards compatibility
21
+ export type { ColumnsSnapshot } from '../types.js';
22
+
23
+ export class EzGridColumns {
24
+ grid: EzGridColumnsRef;
25
+ columns: EzGridColumn[];
26
+ state: ColumnsState;
27
+
28
+ constructor(grid: EzGridColumnsRef, initialColumns: EzGridColumn[] = []) {
29
+ this.grid = grid;
30
+
31
+ // WARNING: Column state must be fully owned by this module.
32
+ // EzGrid should never mutate these objects directly.
33
+ this.columns = initialColumns;
34
+
35
+ this.state = {
36
+ visibility: Object.create(null),
37
+ order: null,
38
+ widths: Object.create(null)
39
+ };
40
+ }
41
+
42
+ getVisibleColumns(): EzGridColumn[] {
43
+ const { visibility, order, widths } = this.state;
44
+
45
+ let cols = this.columns;
46
+
47
+ // 1) Order
48
+ if (Array.isArray(order)) {
49
+ const map = new Map(cols.map(c => [c._id, c]));
50
+ cols = order.map(id => map.get(id)).filter((c): c is EzGridColumn => Boolean(c));
51
+ }
52
+
53
+ // 2) Visibility
54
+ cols = cols.filter(col => visibility[col._id] !== false);
55
+
56
+ // 3) Width override (derived)
57
+ return cols.map(col => {
58
+ const w = widths[col._id];
59
+
60
+ if (w != null) {
61
+ return {
62
+ ...col,
63
+ width: w,
64
+ flex: null
65
+ };
66
+ }
67
+
68
+ return col;
69
+ });
70
+ }
71
+
72
+ hide(colId: string): void {
73
+ this.state.visibility[colId] = false;
74
+ this._changed();
75
+ }
76
+
77
+ show(colId: string): void {
78
+ this.state.visibility[colId] = true;
79
+ this._changed();
80
+ }
81
+
82
+ toggle(colId: string): void {
83
+ const current = this.state.visibility[colId];
84
+ this.state.visibility[colId] = current === false;
85
+ this._changed();
86
+ }
87
+
88
+ setWidth(colId: string, width: number): void {
89
+ if (typeof width !== 'number' || width <= 0) return;
90
+
91
+ this.state.widths[colId] = width;
92
+ this._changed();
93
+ }
94
+
95
+ resetWidth(colId: string): void {
96
+ if (this.state.widths[colId] != null) {
97
+ delete this.state.widths[colId];
98
+ this._changed();
99
+ }
100
+ }
101
+
102
+ move(colId: string, toIndex: number): void {
103
+ const ids = this.columns.map(c => c._id);
104
+
105
+ const order = this.state.order
106
+ ? [...this.state.order]
107
+ : [...ids];
108
+
109
+ const fromIndex = order.indexOf(colId);
110
+ if (fromIndex === -1) return;
111
+
112
+ // WARNING: Index is clamped to valid range
113
+ const target =
114
+ Math.max(0, Math.min(toIndex, order.length - 1));
115
+
116
+ order.splice(fromIndex, 1);
117
+ order.splice(target, 0, colId);
118
+
119
+ this.state.order = order;
120
+ this._changed();
121
+ }
122
+
123
+ snapshot(): ColumnsSnapshot {
124
+ const { visibility, order, widths } = this.state;
125
+
126
+ return {
127
+ visibility: { ...visibility },
128
+ order: order ? [...order] : null,
129
+ widths: { ...widths }
130
+ };
131
+ }
132
+
133
+ restore(snapshot: ColumnsSnapshot | null): void {
134
+ if (!snapshot) return;
135
+
136
+ const { visibility, order, widths } = snapshot;
137
+
138
+ if (visibility && typeof visibility === 'object') {
139
+ this.state.visibility = { ...visibility };
140
+ }
141
+
142
+ if (Array.isArray(order)) {
143
+ this.state.order = [...order];
144
+ }
145
+
146
+ if (widths && typeof widths === 'object') {
147
+ this.state.widths = { ...widths };
148
+ }
149
+ }
150
+
151
+ _changed(): void {
152
+ // NOTE: Delegates side effects to the grid.
153
+ this.grid._onColumnsChanged();
154
+ }
155
+ }