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.
- package/components/EzBaseComponent.ts +648 -0
- package/components/EzComponent.ts +89 -0
- package/components/EzInput.module.scss +183 -0
- package/components/EzInput.ts +104 -0
- package/components/EzLabel.ts +22 -0
- package/components/EzOutlet.ts +181 -0
- package/components/HtmlWrapper.ts +305 -0
- package/components/avatar/EzAvatar.module.scss +200 -0
- package/components/avatar/EzAvatar.ts +130 -0
- package/components/badge/EzBadge.module.scss +202 -0
- package/components/badge/EzBadge.ts +77 -0
- package/components/button/EzButton.module.scss +402 -0
- package/components/button/EzButton.ts +175 -0
- package/components/button/EzButtonGroup.ts +48 -0
- package/components/card/EzCard.module.scss +71 -0
- package/components/card/EzCard.ts +120 -0
- package/components/chart/EzBarChart.ts +47 -0
- package/components/chart/EzChart.module.scss +14 -0
- package/components/chart/EzChart.ts +279 -0
- package/components/chart/EzDoughnutChart.ts +47 -0
- package/components/chart/EzLineChart.ts +53 -0
- package/components/checkbox/EzCheckbox.module.scss +145 -0
- package/components/checkbox/EzCheckbox.ts +115 -0
- package/components/dataview/EzDataView.module.scss +115 -0
- package/components/dataview/EzDataView.ts +355 -0
- package/components/dataview/modes/EzDataViewCards.ts +322 -0
- package/components/dataview/modes/EzDataViewGrid.ts +76 -0
- package/components/datepicker/EzDatePicker.module.scss +348 -0
- package/components/datepicker/EzDatePicker.ts +519 -0
- package/components/dialog/EzDialog.module.scss +180 -0
- package/components/dropdown/EzDropdown.module.scss +107 -0
- package/components/dropdown/EzDropdown.ts +235 -0
- package/components/feed/EzActivityFeed.module.scss +90 -0
- package/components/feed/EzActivityFeed.ts +78 -0
- package/components/form/EzForm.ts +364 -0
- package/components/form/EzValidators.test.js +421 -0
- package/components/form/EzValidators.ts +202 -0
- package/components/grid/EzGrid.scss +88 -0
- package/components/grid/EzGrid.ts +1085 -0
- package/components/grid/EzGridContainer.ts +104 -0
- package/components/grid/body/EzGridBody.scss +283 -0
- package/components/grid/body/EzGridBody.ts +549 -0
- package/components/grid/body/EzGridCell.ts +211 -0
- package/components/grid/body/EzGridRow.ts +196 -0
- package/components/grid/filter/EzGridFilters.scss +78 -0
- package/components/grid/filter/EzGridFilters.ts +285 -0
- package/components/grid/footer/EzGridFooter.scss +136 -0
- package/components/grid/footer/EzGridFooter.ts +448 -0
- package/components/grid/header/EzGridHeader.scss +199 -0
- package/components/grid/header/EzGridHeader.ts +430 -0
- package/components/grid/query/EzGridQuery.ts +81 -0
- package/components/grid/state/EzGridColumns.ts +155 -0
- package/components/grid/state/EzGridController.ts +470 -0
- package/components/grid/state/EzGridLifecycle.ts +136 -0
- package/components/grid/state/EzGridNormalizers.test.js +273 -0
- package/components/grid/state/EzGridNormalizers.ts +162 -0
- package/components/grid/state/EzGridParts.ts +233 -0
- package/components/grid/state/EzGridPersistence.ts +140 -0
- package/components/grid/state/EzGridRemote.test.js +573 -0
- package/components/grid/state/EzGridRemote.ts +335 -0
- package/components/grid/state/EzGridSelection.ts +231 -0
- package/components/grid/state/EzGridSort.ts +286 -0
- package/components/grid/title/EzGridActionBar.ts +98 -0
- package/components/grid/title/EzGridTitle.ts +114 -0
- package/components/grid/title/EzGridTitleBar.scss +65 -0
- package/components/grid/title/EzGridTitleBar.ts +87 -0
- package/components/grid/types.ts +607 -0
- package/components/panel/EzPanel.module.scss +133 -0
- package/components/panel/EzPanel.ts +147 -0
- package/components/radio/EzRadio.module.scss +190 -0
- package/components/radio/EzRadio.ts +149 -0
- package/components/select/EzSelect.module.scss +153 -0
- package/components/select/EzSelect.ts +238 -0
- package/components/skeleton/EzSkeleton.module.scss +95 -0
- package/components/skeleton/EzSkeleton.ts +70 -0
- package/components/store/EzStore.ts +344 -0
- package/components/switch/EzSwitch.module.scss +164 -0
- package/components/switch/EzSwitch.ts +117 -0
- package/components/tabs/EzTabPanel.module.scss +181 -0
- package/components/tabs/EzTabPanel.ts +402 -0
- package/components/textarea/EzTextarea.module.scss +131 -0
- package/components/textarea/EzTextarea.ts +161 -0
- package/components/timepicker/EzTimePicker.module.scss +282 -0
- package/components/timepicker/EzTimePicker.ts +540 -0
- package/components/toast/EzToast.module.scss +291 -0
- package/components/tooltip/EzTooltip.module.scss +124 -0
- package/components/tooltip/EzTooltip.ts +153 -0
- package/core/EzComponentTypes.ts +693 -0
- package/core/EzError.ts +63 -0
- package/core/EzModel.ts +268 -0
- package/core/EzTypes.ts +328 -0
- package/core/eventBus.ts +284 -0
- package/core/ez.ts +617 -0
- package/core/loader.ts +725 -0
- package/core/renderer.ts +1010 -0
- package/core/router.ts +490 -0
- package/core/services.ts +124 -0
- package/core/state.ts +142 -0
- package/core/utils.ts +81 -0
- package/package.json +51 -0
- package/services/RouteUI.js +17 -0
- package/services/crypto.js +64 -0
- package/services/dialog.js +222 -0
- package/services/fetchApi.js +63 -0
- package/services/firebase.js +30 -0
- package/services/toast.js +214 -0
- package/template/doc/EzDocs.js +15 -0
- package/template/doc/EzDocs.module.scss +627 -0
- package/template/doc/EzDocsController.js +164 -0
- package/template/doc/data/activityfeed/EzActivityFeedDoc.js +42 -0
- package/template/doc/data/avatar/EzAvatarDoc.js +71 -0
- package/template/doc/data/badge/EzBadgeDoc.js +92 -0
- package/template/doc/data/button/EzButtonDoc.js +77 -0
- package/template/doc/data/buttongroup/EzButtonGroupDoc.js +102 -0
- package/template/doc/data/card/EzCardDoc.js +39 -0
- package/template/doc/data/chart/EzChartDoc.js +60 -0
- package/template/doc/data/checkbox/EzCheckboxDoc.js +67 -0
- package/template/doc/data/component/EzComponentDoc.js +34 -0
- package/template/doc/data/cssmodules/CSSModulesDoc.js +70 -0
- package/template/doc/data/datepicker/EzDatePickerDoc.js +126 -0
- package/template/doc/data/dialog/EzDialogDoc.js +217 -0
- package/template/doc/data/dropdown/EzDropdownDoc.js +178 -0
- package/template/doc/data/form/EzFormDoc.js +90 -0
- package/template/doc/data/grid/EzGridDoc.js +99 -0
- package/template/doc/data/input/EzInputDoc.js +92 -0
- package/template/doc/data/label/EzLabelDoc.js +40 -0
- package/template/doc/data/model/EzModelDoc.js +53 -0
- package/template/doc/data/outlet/EzOutletDoc.js +63 -0
- package/template/doc/data/panel/EzPanelDoc.js +214 -0
- package/template/doc/data/radio/EzRadioDoc.js +174 -0
- package/template/doc/data/router/EzRouterDoc.js +75 -0
- package/template/doc/data/select/EzSelectDoc.js +37 -0
- package/template/doc/data/skeleton/EzSkeletonDoc.js +149 -0
- package/template/doc/data/switch/EzSwitchDoc.js +82 -0
- package/template/doc/data/tabpanel/EzTabPanelDoc.js +44 -0
- package/template/doc/data/textarea/EzTextareaDoc.js +131 -0
- package/template/doc/data/timepicker/EzTimePickerDoc.js +107 -0
- package/template/doc/data/tooltip/EzTooltipDoc.js +193 -0
- package/template/doc/data/validators/EzValidatorsDoc.js +37 -0
- package/template/doc/sidebar/EzDocsSidebar.js +32 -0
- package/template/doc/sidebar/category/EzDocsCategory.js +33 -0
- package/template/doc/sidebar/item/EzDocsComponentItem.js +24 -0
- package/template/doc/viewer/EzDocsViewer.js +18 -0
- package/template/doc/viewer/codepanel/EzDocsCodePanel.js +51 -0
- package/template/doc/viewer/content/EzDocsContent.js +315 -0
- package/template/doc/viewer/header/EzDocsViewerHeader.js +46 -0
- package/template/doc/viewer/showcase/EzDocsShowcase.js +59 -0
- package/template/doc/viewer/showcase/EzDocsShowcaseSection.js +25 -0
- package/template/doc/viewer/showcase/EzDocsVariantItem.js +29 -0
- package/template/doc/welcome/EzDocsWelcome.js +48 -0
- package/themes/ez-theme.scss +179 -0
- package/themes/nature-fresh.scss +169 -0
- package/types/global.d.ts +21 -0
- 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
|
+
}
|