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
package/core/EzError.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export interface EzErrorConfig {
|
|
2
|
+
code: string;
|
|
3
|
+
message: string;
|
|
4
|
+
source?: string;
|
|
5
|
+
route?: string | null;
|
|
6
|
+
view?: string | null;
|
|
7
|
+
details?: unknown;
|
|
8
|
+
cause?: Error | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class EzError extends Error {
|
|
12
|
+
name: string = 'EzError';
|
|
13
|
+
code: string;
|
|
14
|
+
source: string;
|
|
15
|
+
route: string | null;
|
|
16
|
+
view: string | null;
|
|
17
|
+
details: unknown;
|
|
18
|
+
cause: Error | null;
|
|
19
|
+
|
|
20
|
+
constructor({
|
|
21
|
+
code,
|
|
22
|
+
message,
|
|
23
|
+
source = 'framework',
|
|
24
|
+
route = null,
|
|
25
|
+
view = null,
|
|
26
|
+
details = null,
|
|
27
|
+
cause = null
|
|
28
|
+
}: EzErrorConfig) {
|
|
29
|
+
super(message);
|
|
30
|
+
|
|
31
|
+
this.code = code;
|
|
32
|
+
this.source = source;
|
|
33
|
+
this.route = route;
|
|
34
|
+
this.view = view;
|
|
35
|
+
this.details = details;
|
|
36
|
+
this.cause = cause;
|
|
37
|
+
|
|
38
|
+
this.handleFrameworkError(this);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
handleFrameworkError(error: EzError): void {
|
|
42
|
+
console.group(`🧱 EZ Framework Error — ${error.code}`);
|
|
43
|
+
console.error(error.message);
|
|
44
|
+
|
|
45
|
+
console.info('Source:', error.source);
|
|
46
|
+
if (error.route) console.info('Route:', error.route);
|
|
47
|
+
if (error.view) console.info('View:', error.view);
|
|
48
|
+
|
|
49
|
+
if (error.details) {
|
|
50
|
+
console.group('Details');
|
|
51
|
+
console.dir(error.details);
|
|
52
|
+
console.groupEnd();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (error.cause) {
|
|
56
|
+
console.group('Cause');
|
|
57
|
+
console.error(error.cause);
|
|
58
|
+
console.groupEnd();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.groupEnd();
|
|
62
|
+
}
|
|
63
|
+
}
|
package/core/EzModel.ts
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { EzError } from './EzError.js';
|
|
2
|
+
|
|
3
|
+
type TypeCoercer = (value: unknown) => unknown;
|
|
4
|
+
|
|
5
|
+
export interface FieldDefinition {
|
|
6
|
+
name: string;
|
|
7
|
+
type?: string;
|
|
8
|
+
default?: unknown | (() => unknown);
|
|
9
|
+
primaryKey?: boolean;
|
|
10
|
+
compute?: (record: Record<string, unknown>) => unknown;
|
|
11
|
+
nullable?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ModelDefinition {
|
|
15
|
+
fields: FieldDefinition[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface NormalizedField {
|
|
19
|
+
name: string;
|
|
20
|
+
type: string;
|
|
21
|
+
default: unknown | (() => unknown) | undefined;
|
|
22
|
+
primaryKey: boolean;
|
|
23
|
+
compute: ((record: Record<string, unknown>) => unknown) | null;
|
|
24
|
+
nullable: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ValidationError {
|
|
28
|
+
field: string;
|
|
29
|
+
error: string;
|
|
30
|
+
message: string;
|
|
31
|
+
received?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ValidationResult {
|
|
35
|
+
valid: boolean;
|
|
36
|
+
errors: ValidationError[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface Model {
|
|
40
|
+
name: string;
|
|
41
|
+
fields: NormalizedField[];
|
|
42
|
+
fieldMap: Record<string, NormalizedField>;
|
|
43
|
+
primaryKey: string | null;
|
|
44
|
+
coerce: (record: Record<string, unknown>) => Record<string, unknown>;
|
|
45
|
+
validate: (record: Record<string, unknown>) => ValidationResult;
|
|
46
|
+
applyDefaults: (record: Record<string, unknown>) => Record<string, unknown>;
|
|
47
|
+
applyComputed: (record: Record<string, unknown>) => Record<string, unknown>;
|
|
48
|
+
process: (record: Record<string, unknown>) => Record<string, unknown>;
|
|
49
|
+
processAll: (records: Record<string, unknown>[]) => Record<string, unknown>[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface EzInstance {
|
|
53
|
+
_models: Record<string, Model>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class EzModel {
|
|
57
|
+
private ez: EzInstance;
|
|
58
|
+
|
|
59
|
+
static TYPES: Record<string, TypeCoercer> = {
|
|
60
|
+
int: (v) => v == null ? null : parseInt(String(v), 10),
|
|
61
|
+
float: (v) => v == null ? null : parseFloat(String(v)),
|
|
62
|
+
string: (v) => v == null ? null : String(v),
|
|
63
|
+
bool: (v) => {
|
|
64
|
+
if (v == null) return null;
|
|
65
|
+
if (typeof v === 'string') {
|
|
66
|
+
return v.toLowerCase() === 'true' || v === '1';
|
|
67
|
+
}
|
|
68
|
+
return Boolean(v);
|
|
69
|
+
},
|
|
70
|
+
date: (v) => {
|
|
71
|
+
if (v == null) return null;
|
|
72
|
+
if (v instanceof Date) return v;
|
|
73
|
+
const d = new Date(v as string | number);
|
|
74
|
+
return isNaN(d.getTime()) ? null : d;
|
|
75
|
+
},
|
|
76
|
+
array: (v) => {
|
|
77
|
+
if (v == null) return [];
|
|
78
|
+
return Array.isArray(v) ? v : [v];
|
|
79
|
+
},
|
|
80
|
+
object: (v) => v == null ? {} : (typeof v === 'object' ? v : {}),
|
|
81
|
+
any: (v) => v
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
constructor(ez: EzInstance) {
|
|
85
|
+
this.ez = ez;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
defineModel(name: string, definition: ModelDefinition): void {
|
|
89
|
+
if (this.ez._models[name]) {
|
|
90
|
+
throw new EzError({
|
|
91
|
+
code: 'EZ_MODEL_DUPLICATE',
|
|
92
|
+
source: 'model',
|
|
93
|
+
message: `Model "${name}" is already defined`
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const fields = (definition.fields || []).map(f => this._normalizeField(f));
|
|
98
|
+
|
|
99
|
+
const model: Model = {
|
|
100
|
+
name,
|
|
101
|
+
fields,
|
|
102
|
+
fieldMap: Object.fromEntries(fields.map(f => [f.name, f])),
|
|
103
|
+
primaryKey: fields.find(f => f.primaryKey)?.name || null,
|
|
104
|
+
|
|
105
|
+
coerce: (record) => this._coerceRecord(fields, record),
|
|
106
|
+
validate: (record) => this._validateRecord(fields, record),
|
|
107
|
+
applyDefaults: (record) => this._applyDefaults(fields, record),
|
|
108
|
+
applyComputed: (record) => this._applyComputed(fields, record),
|
|
109
|
+
process: (record) => this._processRecord(fields, record),
|
|
110
|
+
processAll: (records) => this._processAll(fields, records)
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
this.ez._models[name] = model;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private _normalizeField(field: FieldDefinition): NormalizedField {
|
|
117
|
+
return {
|
|
118
|
+
name: field.name,
|
|
119
|
+
type: field.type || 'any',
|
|
120
|
+
default: field.default,
|
|
121
|
+
primaryKey: field.primaryKey || false,
|
|
122
|
+
compute: field.compute || null,
|
|
123
|
+
nullable: field.nullable !== false
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
getModel(name: string): Model | null {
|
|
128
|
+
return this.ez._models[name] || null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
getModelSync(name: string): Model {
|
|
132
|
+
const model = this.ez._models[name];
|
|
133
|
+
if (!model) {
|
|
134
|
+
throw new EzError({
|
|
135
|
+
code: 'EZ_MODEL_001',
|
|
136
|
+
source: 'model',
|
|
137
|
+
message: `Model "${name}" not found`,
|
|
138
|
+
details: {
|
|
139
|
+
available: Object.keys(this.ez._models)
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return model;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private _processRecord(fields: NormalizedField[], record: Record<string, unknown>): Record<string, unknown> {
|
|
147
|
+
if (!record || typeof record !== 'object') {
|
|
148
|
+
return record;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let result = { ...record };
|
|
152
|
+
result = this._applyDefaults(fields, result);
|
|
153
|
+
result = this._coerceRecord(fields, result);
|
|
154
|
+
result = this._applyComputed(fields, result);
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private _processAll(fields: NormalizedField[], records: Record<string, unknown>[]): Record<string, unknown>[] {
|
|
159
|
+
if (!Array.isArray(records)) {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
return records.map(r => this._processRecord(fields, r));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private _applyDefaults(fields: NormalizedField[], record: Record<string, unknown>): Record<string, unknown> {
|
|
166
|
+
const result = { ...record };
|
|
167
|
+
|
|
168
|
+
for (const field of fields) {
|
|
169
|
+
if (result[field.name] === undefined && field.default !== undefined) {
|
|
170
|
+
result[field.name] = typeof field.default === 'function'
|
|
171
|
+
? field.default()
|
|
172
|
+
: field.default;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private _coerceRecord(fields: NormalizedField[], record: Record<string, unknown>): Record<string, unknown> {
|
|
180
|
+
const result = { ...record };
|
|
181
|
+
|
|
182
|
+
for (const field of fields) {
|
|
183
|
+
if (field.compute) continue;
|
|
184
|
+
|
|
185
|
+
if (result[field.name] !== undefined) {
|
|
186
|
+
const coercer = EzModel.TYPES[field.type] || EzModel.TYPES.any;
|
|
187
|
+
result[field.name] = coercer(result[field.name]);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private _applyComputed(fields: NormalizedField[], record: Record<string, unknown>): Record<string, unknown> {
|
|
195
|
+
const result = { ...record };
|
|
196
|
+
|
|
197
|
+
for (const field of fields) {
|
|
198
|
+
if (field.compute && typeof field.compute === 'function') {
|
|
199
|
+
try {
|
|
200
|
+
result[field.name] = field.compute(result);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
console.warn(`[EzModel] Error computing field "${field.name}":`, err);
|
|
203
|
+
result[field.name] = null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private _validateRecord(fields: NormalizedField[], record: Record<string, unknown>): ValidationResult {
|
|
212
|
+
const errors: ValidationError[] = [];
|
|
213
|
+
|
|
214
|
+
for (const field of fields) {
|
|
215
|
+
const value = record[field.name];
|
|
216
|
+
|
|
217
|
+
if (field.compute) continue;
|
|
218
|
+
|
|
219
|
+
if (value == null && !field.nullable) {
|
|
220
|
+
errors.push({
|
|
221
|
+
field: field.name,
|
|
222
|
+
error: 'required',
|
|
223
|
+
message: `Field "${field.name}" is required`
|
|
224
|
+
});
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (value != null) {
|
|
229
|
+
const isValid = this._validateType(field.type, value);
|
|
230
|
+
if (!isValid) {
|
|
231
|
+
errors.push({
|
|
232
|
+
field: field.name,
|
|
233
|
+
error: 'type',
|
|
234
|
+
message: `Field "${field.name}" must be of type ${field.type}`,
|
|
235
|
+
received: typeof value
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
valid: errors.length === 0,
|
|
243
|
+
errors
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private _validateType(type: string, value: unknown): boolean {
|
|
248
|
+
switch (type) {
|
|
249
|
+
case 'int':
|
|
250
|
+
return Number.isInteger(value) || (typeof value === 'string' && /^-?\d+$/.test(value));
|
|
251
|
+
case 'float':
|
|
252
|
+
return typeof value === 'number' || (typeof value === 'string' && !isNaN(parseFloat(value)));
|
|
253
|
+
case 'string':
|
|
254
|
+
return typeof value === 'string';
|
|
255
|
+
case 'bool':
|
|
256
|
+
return typeof value === 'boolean' || value === 'true' || value === 'false' || value === 0 || value === 1;
|
|
257
|
+
case 'date':
|
|
258
|
+
return value instanceof Date || (typeof value === 'string' && !isNaN(Date.parse(value)));
|
|
259
|
+
case 'array':
|
|
260
|
+
return Array.isArray(value);
|
|
261
|
+
case 'object':
|
|
262
|
+
return typeof value === 'object' && !Array.isArray(value);
|
|
263
|
+
case 'any':
|
|
264
|
+
default:
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
package/core/EzTypes.ts
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import type { EzItemConfig } from './EzComponentTypes.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for component items (children)
|
|
5
|
+
* For component-specific autocomplete, use EzItemConfig instead
|
|
6
|
+
*/
|
|
7
|
+
export interface ItemConfig {
|
|
8
|
+
/**
|
|
9
|
+
* Component type to render
|
|
10
|
+
* @example eztype: 'EzButton'
|
|
11
|
+
* @example eztype: 'div' // native HTML element
|
|
12
|
+
*/
|
|
13
|
+
eztype?: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* CSS class(es) to apply to the element
|
|
17
|
+
* @example cls: 'header'
|
|
18
|
+
* @example cls: ['card', 'primary', 'shadow']
|
|
19
|
+
*/
|
|
20
|
+
cls?: string | string[];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* CSS module name - framework auto-loads {css}.module.scss
|
|
24
|
+
* @example css: 'UserCard' // loads UserCard.module.scss
|
|
25
|
+
*/
|
|
26
|
+
css?: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Text content for the element
|
|
30
|
+
* @example text: 'Hello World'
|
|
31
|
+
* @example text: user.name
|
|
32
|
+
*/
|
|
33
|
+
text?: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Raw HTML content (use with caution - XSS risk)
|
|
37
|
+
* @example html: '<strong>Bold</strong>'
|
|
38
|
+
*/
|
|
39
|
+
html?: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Child items to render inside this element
|
|
43
|
+
* @example items: [{ eztype: 'span', text: 'Child 1' }]
|
|
44
|
+
*/
|
|
45
|
+
items?: ItemConfig[];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Flex grow value for flexbox layouts
|
|
49
|
+
* @example flex: 1 // takes remaining space
|
|
50
|
+
* @example flex: 2 // takes 2x space relative to flex: 1
|
|
51
|
+
*/
|
|
52
|
+
flex?: number;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Inline CSS styles
|
|
56
|
+
* @example style: { padding: '16px', gap: '8px' }
|
|
57
|
+
*/
|
|
58
|
+
style?: Record<string, string | number>;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Props passed to template-based components
|
|
62
|
+
* @example props: { user: userData, showAvatar: true }
|
|
63
|
+
*/
|
|
64
|
+
props?: Record<string, unknown>;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Data bindings for reactive updates
|
|
68
|
+
* @example bind: { text: 'userName', visible: 'isLoggedIn' }
|
|
69
|
+
*/
|
|
70
|
+
bind?: BindConfig;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Reference name for accessing via ez._refs
|
|
74
|
+
* @example ref: 'submitBtn' // access as ez._refs.submitBtn
|
|
75
|
+
*/
|
|
76
|
+
ref?: string;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Click event handler - method name (string) or function
|
|
80
|
+
* @example onClick: 'handleClick' // calls controller.handleClick()
|
|
81
|
+
* @example onClick: (e) => console.log('clicked')
|
|
82
|
+
*/
|
|
83
|
+
onClick?: string | ((e: Event) => void);
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Change event handler (for inputs)
|
|
87
|
+
* @example onChange: 'onSelectChange'
|
|
88
|
+
*/
|
|
89
|
+
onChange?: string | ((e: Event) => void);
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Input event handler (fires on every keystroke)
|
|
93
|
+
* @example onInput: 'onSearchInput'
|
|
94
|
+
*/
|
|
95
|
+
onInput?: string | ((e: Event) => void);
|
|
96
|
+
|
|
97
|
+
/** Any additional properties */
|
|
98
|
+
[key: string]: unknown;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Data binding configuration for reactive updates
|
|
103
|
+
*/
|
|
104
|
+
export interface BindConfig {
|
|
105
|
+
/**
|
|
106
|
+
* Bind to array data for list rendering with itemRender
|
|
107
|
+
* @example
|
|
108
|
+
* bind: { data: 'users' },
|
|
109
|
+
* itemRender: (user) => ({ eztype: 'UserCard', props: { user } })
|
|
110
|
+
*/
|
|
111
|
+
data?: string;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Bind text content to a state property
|
|
115
|
+
* @example bind: { text: 'userName' } // displays state.userName
|
|
116
|
+
*/
|
|
117
|
+
text?: string;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Conditionally show/hide element based on state
|
|
121
|
+
* @example bind: { visible: 'isLoading' }
|
|
122
|
+
* @example bind: { visible: () => ctrl.state.items.length > 0 }
|
|
123
|
+
*/
|
|
124
|
+
visible?: string | (() => boolean);
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Dynamically set CSS classes based on state
|
|
128
|
+
* @example bind: { cls: 'activeClass' } // uses state.activeClass
|
|
129
|
+
* @example bind: { cls: () => ctrl.state.isActive ? 'active' : 'inactive' }
|
|
130
|
+
*/
|
|
131
|
+
cls?: string | (() => string | string[]);
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Bind input value to state (two-way binding)
|
|
135
|
+
* @example bind: { value: 'formData.email' }
|
|
136
|
+
*/
|
|
137
|
+
value?: string;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Bind disabled state
|
|
141
|
+
* @example bind: { disabled: 'isSubmitting' }
|
|
142
|
+
*/
|
|
143
|
+
disabled?: string | (() => boolean);
|
|
144
|
+
|
|
145
|
+
/** Any additional attribute bindings */
|
|
146
|
+
[key: string]: unknown;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Component definition for ez.define()
|
|
151
|
+
*/
|
|
152
|
+
export interface ComponentDefinition {
|
|
153
|
+
/**
|
|
154
|
+
* Base component type to extend
|
|
155
|
+
* @example eztype: 'EzComponent'
|
|
156
|
+
* @example eztype: 'EzForm'
|
|
157
|
+
*/
|
|
158
|
+
eztype?: string;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* CSS module name for automatic style loading
|
|
162
|
+
* The framework will load `{css}.module.scss` automatically
|
|
163
|
+
* @example css: 'StatCard' // loads StatCard.module.scss
|
|
164
|
+
*/
|
|
165
|
+
css?: string;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Controller name for state management
|
|
169
|
+
* @example controller: 'UserDetail'
|
|
170
|
+
*/
|
|
171
|
+
controller?: string;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* CSS class(es) to apply to the root element
|
|
175
|
+
* @example cls: 'myComponent'
|
|
176
|
+
* @example cls: ['card', 'primary']
|
|
177
|
+
*/
|
|
178
|
+
cls?: string | string[];
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Layout direction for child items
|
|
182
|
+
* @example layout: 'hbox' // horizontal
|
|
183
|
+
* @example layout: 'vbox' // vertical
|
|
184
|
+
*/
|
|
185
|
+
layout?: 'hbox' | 'vbox';
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Inline styles for the root element
|
|
189
|
+
*/
|
|
190
|
+
style?: Record<string, string | number>;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Static child items
|
|
194
|
+
* @example items: [{ eztype: 'EzButton', text: 'Click' }]
|
|
195
|
+
*/
|
|
196
|
+
items?: ItemConfig[];
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Template function for dynamic rendering
|
|
200
|
+
* Receives props and returns component configuration
|
|
201
|
+
* @example
|
|
202
|
+
* template({ name, age }) {
|
|
203
|
+
* return {
|
|
204
|
+
* eztype: 'div',
|
|
205
|
+
* items: [
|
|
206
|
+
* { eztype: 'span', text: name },
|
|
207
|
+
* { eztype: 'span', text: `Age: ${age}` }
|
|
208
|
+
* ]
|
|
209
|
+
* };
|
|
210
|
+
* }
|
|
211
|
+
*/
|
|
212
|
+
template?: (props: Record<string, unknown>) => ItemConfig;
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Data bindings for reactive updates
|
|
216
|
+
* @example bind: { data: 'users', visible: 'hasUsers' }
|
|
217
|
+
*/
|
|
218
|
+
bind?: BindConfig;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Render function for each item when using bind.data
|
|
222
|
+
* @example
|
|
223
|
+
* itemRender: (user) => ({
|
|
224
|
+
* eztype: 'UserCard',
|
|
225
|
+
* props: { user }
|
|
226
|
+
* })
|
|
227
|
+
*/
|
|
228
|
+
itemRender?: (item: unknown, index?: number) => ItemConfig;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Method name to call before rendering (async data loading)
|
|
232
|
+
* The method must exist on the controller
|
|
233
|
+
* @example onLoad: 'loadUserData'
|
|
234
|
+
*/
|
|
235
|
+
onLoad?: string;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Component to show while onLoad is executing
|
|
239
|
+
* @example
|
|
240
|
+
* fallback: {
|
|
241
|
+
* eztype: 'EzSkeleton',
|
|
242
|
+
* variant: 'card'
|
|
243
|
+
* }
|
|
244
|
+
*/
|
|
245
|
+
fallback?: ItemConfig;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Reference name for accessing this component via ez._refs
|
|
249
|
+
* @example ref: 'myGrid' // access as ez._refs.myGrid
|
|
250
|
+
*/
|
|
251
|
+
ref?: string;
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Flex grow value for the component
|
|
255
|
+
*/
|
|
256
|
+
flex?: number;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Route parameters passed by the router
|
|
260
|
+
*/
|
|
261
|
+
routeParams?: Record<string, string>;
|
|
262
|
+
|
|
263
|
+
/** Any additional properties */
|
|
264
|
+
[key: string]: unknown;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Controller definition for ez.defineController()
|
|
269
|
+
*/
|
|
270
|
+
export interface ControllerDef {
|
|
271
|
+
/**
|
|
272
|
+
* Reactive state object - automatically wrapped with deepSignal
|
|
273
|
+
* Access in methods via this.state
|
|
274
|
+
* @example
|
|
275
|
+
* state: {
|
|
276
|
+
* users: [],
|
|
277
|
+
* loading: false,
|
|
278
|
+
* filters: { search: '', status: 'all' }
|
|
279
|
+
* }
|
|
280
|
+
*/
|
|
281
|
+
state?: Record<string, unknown>;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Called once when controller is first initialized
|
|
285
|
+
* Use for initial data loading
|
|
286
|
+
* @example
|
|
287
|
+
* async onInit() {
|
|
288
|
+
* this.state.loading = true;
|
|
289
|
+
* this.state.users = await api.getUsers();
|
|
290
|
+
* this.state.loading = false;
|
|
291
|
+
* }
|
|
292
|
+
*/
|
|
293
|
+
onInit?: () => void | Promise<void>;
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Called when route changes (for view controllers)
|
|
297
|
+
* @param params.fullPath - Current route path
|
|
298
|
+
* @param params.chain - Remaining route chain for nested views
|
|
299
|
+
* @example
|
|
300
|
+
* async onRouteChange({ fullPath, chain }) {
|
|
301
|
+
* const userId = chain[0]?.params?.id;
|
|
302
|
+
* if (userId) await this.loadUser(userId);
|
|
303
|
+
* }
|
|
304
|
+
*/
|
|
305
|
+
onRouteChange?: (params: { fullPath: string; chain: unknown[] }) => void | Promise<void>;
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Called when controller is being destroyed
|
|
309
|
+
* Use for cleanup (event listeners, timers, etc.)
|
|
310
|
+
* @example
|
|
311
|
+
* onDestroy() {
|
|
312
|
+
* clearInterval(this.refreshTimer);
|
|
313
|
+
* }
|
|
314
|
+
*/
|
|
315
|
+
onDestroy?: () => void;
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Custom methods - access state via this.state
|
|
319
|
+
* @example
|
|
320
|
+
* async loadUsers() {
|
|
321
|
+
* this.state.users = await api.get('/users');
|
|
322
|
+
* },
|
|
323
|
+
* addUser(user) {
|
|
324
|
+
* this.state.users.push(user);
|
|
325
|
+
* }
|
|
326
|
+
*/
|
|
327
|
+
[key: string]: unknown;
|
|
328
|
+
}
|