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,421 @@
1
+ // ==========================================================
2
+ // EzValidators - Unit Tests
3
+ // ==========================================================
4
+ // Para correr: npm test EzValidators
5
+ // ==========================================================
6
+
7
+ import { describe, it, expect } from 'vitest';
8
+ import { EzValidators, validateField, runValidator, defaultMessages } from './EzValidators.js';
9
+
10
+ // ==========================================================
11
+ // EzValidators.required
12
+ // ==========================================================
13
+
14
+ describe('EzValidators.required', () => {
15
+
16
+ it('should return false for null', () => {
17
+ expect(EzValidators.required(null)).toBe(false);
18
+ });
19
+
20
+ it('should return false for undefined', () => {
21
+ expect(EzValidators.required(undefined)).toBe(false);
22
+ });
23
+
24
+ it('should return false for empty string', () => {
25
+ expect(EzValidators.required('')).toBe(false);
26
+ });
27
+
28
+ it('should return false for whitespace only', () => {
29
+ expect(EzValidators.required(' ')).toBe(false);
30
+ });
31
+
32
+ it('should return true for non-empty string', () => {
33
+ expect(EzValidators.required('hello')).toBe(true);
34
+ });
35
+
36
+ it('should return true for zero', () => {
37
+ expect(EzValidators.required(0)).toBe(true);
38
+ });
39
+
40
+ it('should return false for empty array', () => {
41
+ expect(EzValidators.required([])).toBe(false);
42
+ });
43
+
44
+ it('should return true for non-empty array', () => {
45
+ expect(EzValidators.required([1, 2])).toBe(true);
46
+ });
47
+
48
+ });
49
+
50
+ // ==========================================================
51
+ // EzValidators.minLength
52
+ // ==========================================================
53
+
54
+ describe('EzValidators.minLength', () => {
55
+
56
+ it('should return true for empty value (not required)', () => {
57
+ const validator = EzValidators.minLength(5);
58
+ expect(validator('')).toBe(true);
59
+ expect(validator(null)).toBe(true);
60
+ });
61
+
62
+ it('should return false if length < min', () => {
63
+ const validator = EzValidators.minLength(5);
64
+ expect(validator('abc')).toBe(false);
65
+ });
66
+
67
+ it('should return true if length >= min', () => {
68
+ const validator = EzValidators.minLength(5);
69
+ expect(validator('abcde')).toBe(true);
70
+ expect(validator('abcdef')).toBe(true);
71
+ });
72
+
73
+ });
74
+
75
+ // ==========================================================
76
+ // EzValidators.maxLength
77
+ // ==========================================================
78
+
79
+ describe('EzValidators.maxLength', () => {
80
+
81
+ it('should return true for empty value', () => {
82
+ const validator = EzValidators.maxLength(5);
83
+ expect(validator('')).toBe(true);
84
+ });
85
+
86
+ it('should return true if length <= max', () => {
87
+ const validator = EzValidators.maxLength(5);
88
+ expect(validator('abc')).toBe(true);
89
+ expect(validator('abcde')).toBe(true);
90
+ });
91
+
92
+ it('should return false if length > max', () => {
93
+ const validator = EzValidators.maxLength(5);
94
+ expect(validator('abcdef')).toBe(false);
95
+ });
96
+
97
+ });
98
+
99
+ // ==========================================================
100
+ // EzValidators.min / max (numeric)
101
+ // ==========================================================
102
+
103
+ describe('EzValidators.min', () => {
104
+
105
+ it('should return true for empty value', () => {
106
+ const validator = EzValidators.min(10);
107
+ expect(validator('')).toBe(true);
108
+ });
109
+
110
+ it('should return false if value < min', () => {
111
+ const validator = EzValidators.min(10);
112
+ expect(validator(5)).toBe(false);
113
+ expect(validator('5')).toBe(false);
114
+ });
115
+
116
+ it('should return true if value >= min', () => {
117
+ const validator = EzValidators.min(10);
118
+ expect(validator(10)).toBe(true);
119
+ expect(validator(15)).toBe(true);
120
+ });
121
+
122
+ });
123
+
124
+ describe('EzValidators.max', () => {
125
+
126
+ it('should return true for empty value', () => {
127
+ const validator = EzValidators.max(100);
128
+ expect(validator('')).toBe(true);
129
+ });
130
+
131
+ it('should return true if value <= max', () => {
132
+ const validator = EzValidators.max(100);
133
+ expect(validator(50)).toBe(true);
134
+ expect(validator(100)).toBe(true);
135
+ });
136
+
137
+ it('should return false if value > max', () => {
138
+ const validator = EzValidators.max(100);
139
+ expect(validator(150)).toBe(false);
140
+ });
141
+
142
+ });
143
+
144
+ // ==========================================================
145
+ // EzValidators.email
146
+ // ==========================================================
147
+
148
+ describe('EzValidators.email', () => {
149
+
150
+ it('should return true for empty value', () => {
151
+ expect(EzValidators.email('')).toBe(true);
152
+ });
153
+
154
+ it('should return true for valid email', () => {
155
+ expect(EzValidators.email('test@example.com')).toBe(true);
156
+ expect(EzValidators.email('user.name@domain.org')).toBe(true);
157
+ });
158
+
159
+ it('should return false for invalid email', () => {
160
+ expect(EzValidators.email('invalid')).toBe(false);
161
+ expect(EzValidators.email('test@')).toBe(false);
162
+ expect(EzValidators.email('@domain.com')).toBe(false);
163
+ });
164
+
165
+ });
166
+
167
+ // ==========================================================
168
+ // EzValidators.pattern
169
+ // ==========================================================
170
+
171
+ describe('EzValidators.pattern', () => {
172
+
173
+ it('should return true for empty value', () => {
174
+ const validator = EzValidators.pattern(/^\d+$/);
175
+ expect(validator('')).toBe(true);
176
+ });
177
+
178
+ it('should return true if pattern matches', () => {
179
+ const validator = EzValidators.pattern(/^\d+$/);
180
+ expect(validator('12345')).toBe(true);
181
+ });
182
+
183
+ it('should return false if pattern does not match', () => {
184
+ const validator = EzValidators.pattern(/^\d+$/);
185
+ expect(validator('abc')).toBe(false);
186
+ });
187
+
188
+ it('should accept string pattern', () => {
189
+ const validator = EzValidators.pattern('^[A-Z]+$');
190
+ expect(validator('ABC')).toBe(true);
191
+ expect(validator('abc')).toBe(false);
192
+ });
193
+
194
+ });
195
+
196
+ // ==========================================================
197
+ // EzValidators.match
198
+ // ==========================================================
199
+
200
+ describe('EzValidators.match', () => {
201
+
202
+ it('should return true for empty value', () => {
203
+ const validator = EzValidators.match('password');
204
+ expect(validator('', { password: 'secret' })).toBe(true);
205
+ });
206
+
207
+ it('should return true if values match', () => {
208
+ const validator = EzValidators.match('password');
209
+ expect(validator('secret', { password: 'secret' })).toBe(true);
210
+ });
211
+
212
+ it('should return false if values do not match', () => {
213
+ const validator = EzValidators.match('password');
214
+ expect(validator('different', { password: 'secret' })).toBe(false);
215
+ });
216
+
217
+ });
218
+
219
+ // ==========================================================
220
+ // EzValidators.numeric
221
+ // ==========================================================
222
+
223
+ describe('EzValidators.numeric', () => {
224
+
225
+ it('should return true for empty value', () => {
226
+ expect(EzValidators.numeric('')).toBe(true);
227
+ });
228
+
229
+ it('should return true for valid numbers', () => {
230
+ expect(EzValidators.numeric('123')).toBe(true);
231
+ expect(EzValidators.numeric(456)).toBe(true);
232
+ expect(EzValidators.numeric('3.14')).toBe(true);
233
+ });
234
+
235
+ it('should return false for non-numeric', () => {
236
+ expect(EzValidators.numeric('abc')).toBe(false);
237
+ expect(EzValidators.numeric('12abc')).toBe(false);
238
+ });
239
+
240
+ });
241
+
242
+ // ==========================================================
243
+ // EzValidators.integer
244
+ // ==========================================================
245
+
246
+ describe('EzValidators.integer', () => {
247
+
248
+ it('should return true for empty value', () => {
249
+ expect(EzValidators.integer('')).toBe(true);
250
+ });
251
+
252
+ it('should return true for integers', () => {
253
+ expect(EzValidators.integer(123)).toBe(true);
254
+ expect(EzValidators.integer('456')).toBe(true);
255
+ });
256
+
257
+ it('should return false for decimals', () => {
258
+ expect(EzValidators.integer(3.14)).toBe(false);
259
+ expect(EzValidators.integer('3.14')).toBe(false);
260
+ });
261
+
262
+ });
263
+
264
+ // ==========================================================
265
+ // EzValidators.url
266
+ // ==========================================================
267
+
268
+ describe('EzValidators.url', () => {
269
+
270
+ it('should return true for empty value', () => {
271
+ expect(EzValidators.url('')).toBe(true);
272
+ });
273
+
274
+ it('should return true for valid URL', () => {
275
+ expect(EzValidators.url('https://example.com')).toBe(true);
276
+ expect(EzValidators.url('http://localhost:3000')).toBe(true);
277
+ });
278
+
279
+ it('should return false for invalid URL', () => {
280
+ expect(EzValidators.url('not-a-url')).toBe(false);
281
+ expect(EzValidators.url('example.com')).toBe(false);
282
+ });
283
+
284
+ });
285
+
286
+ // ==========================================================
287
+ // EzValidators.phone
288
+ // ==========================================================
289
+
290
+ describe('EzValidators.phone', () => {
291
+
292
+ it('should return true for empty value', () => {
293
+ expect(EzValidators.phone('')).toBe(true);
294
+ });
295
+
296
+ it('should return true for valid phone formats', () => {
297
+ expect(EzValidators.phone('1234567890')).toBe(true);
298
+ expect(EzValidators.phone('+1 (555) 123-4567')).toBe(true);
299
+ expect(EzValidators.phone('555-123-4567')).toBe(true);
300
+ });
301
+
302
+ it('should return false for too short', () => {
303
+ expect(EzValidators.phone('123')).toBe(false);
304
+ });
305
+
306
+ it('should return false for letters', () => {
307
+ expect(EzValidators.phone('555-CALL-ME')).toBe(false);
308
+ });
309
+
310
+ });
311
+
312
+ // ==========================================================
313
+ // validateField
314
+ // ==========================================================
315
+
316
+ describe('validateField', () => {
317
+
318
+ it('should validate with object config', () => {
319
+ const result = validateField('', { required: true });
320
+ expect(result.valid).toBe(false);
321
+ expect(result.message).toBe('This field is required');
322
+ });
323
+
324
+ it('should validate multiple rules and stop at first error', () => {
325
+ const result = validateField('ab', {
326
+ required: true,
327
+ minLength: 5
328
+ });
329
+ expect(result.valid).toBe(false);
330
+ expect(result.message).toContain('5');
331
+ });
332
+
333
+ it('should pass when all rules pass', () => {
334
+ const result = validateField('hello@test.com', {
335
+ required: true,
336
+ email: true
337
+ });
338
+ expect(result.valid).toBe(true);
339
+ expect(result.message).toBe(null);
340
+ });
341
+
342
+ it('should use custom message', () => {
343
+ const result = validateField('', {
344
+ required: true,
345
+ message: 'Custom error message'
346
+ });
347
+ expect(result.valid).toBe(false);
348
+ expect(result.message).toBe('Custom error message');
349
+ });
350
+
351
+ it('should handle custom validation function', () => {
352
+ const customValidator = (value) => {
353
+ if (value !== 'expected') return 'Value must be "expected"';
354
+ return true;
355
+ };
356
+
357
+ expect(validateField('wrong', customValidator).valid).toBe(false);
358
+ expect(validateField('expected', customValidator).valid).toBe(true);
359
+ });
360
+
361
+ it('should pass formData to match validator', () => {
362
+ const result = validateField('different', {
363
+ match: 'password'
364
+ }, { password: 'secret' });
365
+
366
+ expect(result.valid).toBe(false);
367
+ });
368
+
369
+ });
370
+
371
+ // ==========================================================
372
+ // runValidator
373
+ // ==========================================================
374
+
375
+ describe('runValidator', () => {
376
+
377
+ it('should handle simple boolean rule', () => {
378
+ const result = runValidator('required', true, '');
379
+ expect(result.valid).toBe(false);
380
+ });
381
+
382
+ it('should handle parameterized rule', () => {
383
+ const result = runValidator('minLength', 5, 'abc');
384
+ expect(result.valid).toBe(false);
385
+ expect(result.message).toContain('5');
386
+ });
387
+
388
+ it('should handle object rule with custom message', () => {
389
+ const result = runValidator('minLength', { value: 5, message: 'Too short!' }, 'abc');
390
+ expect(result.valid).toBe(false);
391
+ expect(result.message).toBe('Too short!');
392
+ });
393
+
394
+ it('should return valid for unknown validator', () => {
395
+ const result = runValidator('unknownRule', true, 'value');
396
+ expect(result.valid).toBe(true);
397
+ });
398
+
399
+ });
400
+
401
+ // ==========================================================
402
+ // defaultMessages
403
+ // ==========================================================
404
+
405
+ describe('defaultMessages', () => {
406
+
407
+ it('should have message for required', () => {
408
+ expect(defaultMessages.required).toBe('This field is required');
409
+ });
410
+
411
+ it('should have function for minLength', () => {
412
+ expect(typeof defaultMessages.minLength).toBe('function');
413
+ expect(defaultMessages.minLength(5)).toContain('5');
414
+ });
415
+
416
+ it('should have function for maxLength', () => {
417
+ expect(typeof defaultMessages.maxLength).toBe('function');
418
+ expect(defaultMessages.maxLength(10)).toContain('10');
419
+ });
420
+
421
+ });
@@ -0,0 +1,202 @@
1
+ type FormData = Record<string, unknown>;
2
+
3
+ interface ValidationResult {
4
+ valid: boolean;
5
+ message: string | null;
6
+ }
7
+
8
+ interface RuleConfig {
9
+ value?: unknown;
10
+ message?: string;
11
+ }
12
+
13
+ type RuleValue = boolean | number | string | RegExp | RuleConfig;
14
+
15
+ interface ValidateConfig {
16
+ message?: string;
17
+ [key: string]: RuleValue | string | undefined;
18
+ }
19
+
20
+ type ValidateInput = ValidateConfig | ((value: unknown, formData: FormData) => boolean | string | null);
21
+
22
+ export const defaultMessages: Record<string, string | ((param: unknown) => string)> = {
23
+ required: 'This field is required',
24
+ minLength: (min: unknown) => `Minimum ${min} characters required`,
25
+ maxLength: (max: unknown) => `Maximum ${max} characters allowed`,
26
+ min: (n: unknown) => `Value must be at least ${n}`,
27
+ max: (n: unknown) => `Value must be at most ${n}`,
28
+ email: 'Please enter a valid email address',
29
+ pattern: 'Invalid format',
30
+ match: (field: unknown) => `Must match ${field}`,
31
+ };
32
+
33
+ export const EzValidators = {
34
+ required(value: unknown): boolean {
35
+ if (value == null) return false;
36
+ if (typeof value === 'string') return value.trim() !== '';
37
+ if (Array.isArray(value)) return value.length > 0;
38
+ return true;
39
+ },
40
+
41
+ minLength(min: number) {
42
+ return (value: unknown): boolean => {
43
+ if (value == null || value === '') return true;
44
+ return String(value).length >= min;
45
+ };
46
+ },
47
+
48
+ maxLength(max: number) {
49
+ return (value: unknown): boolean => {
50
+ if (value == null || value === '') return true;
51
+ return String(value).length <= max;
52
+ };
53
+ },
54
+
55
+ min(n: number) {
56
+ return (value: unknown): boolean => {
57
+ if (value == null || value === '') return true;
58
+ return Number(value) >= n;
59
+ };
60
+ },
61
+
62
+ max(n: number) {
63
+ return (value: unknown): boolean => {
64
+ if (value == null || value === '') return true;
65
+ return Number(value) <= n;
66
+ };
67
+ },
68
+
69
+ email(value: unknown): boolean {
70
+ if (value == null || value === '') return true;
71
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
72
+ return emailRegex.test(String(value));
73
+ },
74
+
75
+ pattern(regex: RegExp | string) {
76
+ const re = typeof regex === 'string' ? new RegExp(regex) : regex;
77
+ return (value: unknown): boolean => {
78
+ if (value == null || value === '') return true;
79
+ return re.test(String(value));
80
+ };
81
+ },
82
+
83
+ match(fieldName: string) {
84
+ return (value: unknown, formData: FormData = {}): boolean => {
85
+ if (value == null || value === '') return true;
86
+ return value === formData[fieldName];
87
+ };
88
+ },
89
+
90
+ numeric(value: unknown): boolean {
91
+ if (value == null || value === '') return true;
92
+ return !isNaN(Number(value)) && isFinite(Number(value));
93
+ },
94
+
95
+ integer(value: unknown): boolean {
96
+ if (value == null || value === '') return true;
97
+ return Number.isInteger(Number(value));
98
+ },
99
+
100
+ url(value: unknown): boolean {
101
+ if (value == null || value === '') return true;
102
+ try {
103
+ new URL(String(value));
104
+ return true;
105
+ } catch {
106
+ return false;
107
+ }
108
+ },
109
+
110
+ phone(value: unknown): boolean {
111
+ if (value == null || value === '') return true;
112
+ const phoneRegex = /^[\d\s\-\(\)\+]+$/;
113
+ const str = String(value);
114
+ return phoneRegex.test(str) && str.replace(/\D/g, '').length >= 7;
115
+ }
116
+ } as const;
117
+
118
+ type ValidatorName = keyof typeof EzValidators;
119
+
120
+ export function runValidator(
121
+ ruleName: string,
122
+ ruleValue: RuleValue,
123
+ fieldValue: unknown,
124
+ formData: FormData = {}
125
+ ): ValidationResult {
126
+ const validator = EzValidators[ruleName as ValidatorName];
127
+ if (!validator) {
128
+ console.warn(`[EzValidators] Unknown validator: ${ruleName}`);
129
+ return { valid: true, message: null };
130
+ }
131
+
132
+ let isValid: boolean;
133
+ let message: string | undefined;
134
+
135
+ if (ruleValue === true) {
136
+ const result = (validator as (value: unknown, formData?: FormData) => boolean)(fieldValue, formData);
137
+ isValid = result;
138
+ const defaultMsg = defaultMessages[ruleName];
139
+ message = typeof defaultMsg === 'function'
140
+ ? defaultMsg(undefined)
141
+ : defaultMsg;
142
+ } else if (typeof ruleValue === 'number' || typeof ruleValue === 'string' || ruleValue instanceof RegExp) {
143
+ const validateFn = (validator as (param: unknown) => (value: unknown, formData?: FormData) => boolean)(ruleValue);
144
+ isValid = validateFn(fieldValue, formData);
145
+ const defaultMsg = defaultMessages[ruleName];
146
+ message = typeof defaultMsg === 'function'
147
+ ? defaultMsg(ruleValue)
148
+ : defaultMsg;
149
+ } else if (typeof ruleValue === 'object' && ruleValue !== null) {
150
+ const { value, message: customMessage } = ruleValue as RuleConfig;
151
+ let validateFn: (value: unknown, formData?: FormData) => boolean;
152
+ if (value !== undefined) {
153
+ validateFn = (validator as (param: unknown) => (value: unknown, formData?: FormData) => boolean)(value);
154
+ } else {
155
+ validateFn = validator as (value: unknown, formData?: FormData) => boolean;
156
+ }
157
+ isValid = validateFn(fieldValue, formData);
158
+ const defaultMsg = defaultMessages[ruleName];
159
+ message = customMessage || (typeof defaultMsg === 'function'
160
+ ? defaultMsg(value)
161
+ : defaultMsg);
162
+ } else {
163
+ isValid = true;
164
+ message = undefined;
165
+ }
166
+
167
+ return {
168
+ valid: isValid,
169
+ message: isValid ? null : (message || 'Invalid value')
170
+ };
171
+ }
172
+
173
+ export function validateField(
174
+ value: unknown,
175
+ validate: ValidateInput,
176
+ formData: FormData = {}
177
+ ): ValidationResult {
178
+ if (typeof validate === 'function') {
179
+ const result = validate(value, formData);
180
+ if (result === true || result == null) {
181
+ return { valid: true, message: null };
182
+ }
183
+ return { valid: false, message: typeof result === 'string' ? result : 'Invalid value' };
184
+ }
185
+
186
+ if (typeof validate === 'object' && validate !== null) {
187
+ for (const [ruleName, ruleValue] of Object.entries(validate)) {
188
+ if (ruleName === 'message') continue;
189
+ if (ruleValue === undefined) continue;
190
+
191
+ const result = runValidator(ruleName, ruleValue as RuleValue, value, formData);
192
+ if (!result.valid) {
193
+ return {
194
+ valid: false,
195
+ message: (validate.message as string) || result.message
196
+ };
197
+ }
198
+ }
199
+ }
200
+
201
+ return { valid: true, message: null };
202
+ }
@@ -0,0 +1,88 @@
1
+ // ==========================================================
2
+ // EzGrid - Base Styles & Design Tokens
3
+ // ==========================================================
4
+
5
+ .ez-grid {
6
+ // ----------------------------------------------------------
7
+ // Design Tokens (CSS Custom Properties)
8
+ // ----------------------------------------------------------
9
+
10
+ // Colors - Surfaces (inherit from theme)
11
+ --ez-grid-bg: var(--ez-surface-secondary, #ffffff);
12
+ --ez-grid-surface: var(--ez-surface-primary, #f8fafc);
13
+ --ez-grid-surface-hover: var(--ez-surface-tertiary, #f1f5f9);
14
+ --ez-grid-surface-active: var(--ez-border, #e2e8f0);
15
+
16
+ // Colors - Borders (inherit from theme)
17
+ --ez-grid-border: var(--ez-border, #e2e8f0);
18
+ --ez-grid-border-light: var(--ez-surface-tertiary, #f1f5f9);
19
+
20
+ // Colors - Text (inherit from theme)
21
+ --ez-grid-text: var(--ez-text-primary, #1e293b);
22
+ --ez-grid-text-secondary: var(--ez-text-secondary, #475569);
23
+ --ez-grid-text-muted: var(--ez-text-tertiary, #94a3b8);
24
+
25
+ // Colors - Primary (inherit from theme)
26
+ --ez-grid-primary: var(--ez-primary, #005871);
27
+ --ez-grid-primary-light: var(--ez-primary-light, rgba(0, 88, 113, 0.15));
28
+ --ez-grid-primary-border: var(--ez-primary, #005871);
29
+
30
+ // Colors - Selection (inherit from theme)
31
+ --ez-grid-row-selected: var(--ez-primary-light, rgba(0, 88, 113, 0.08));
32
+ --ez-grid-row-hover: var(--ez-surface-primary, #f8fafc);
33
+
34
+ // Colors - Danger (inherit from theme)
35
+ --ez-grid-danger: var(--ez-danger, #dc2626);
36
+ --ez-grid-danger-light: var(--ez-danger-light, rgba(220, 38, 38, 0.08));
37
+
38
+ // Typography
39
+ --ez-grid-font-size: 13px;
40
+ --ez-grid-font-size-sm: 12px;
41
+ --ez-grid-font-size-lg: 15px;
42
+ --ez-grid-font-weight: 400;
43
+ --ez-grid-font-weight-medium: 500;
44
+ --ez-grid-font-weight-semibold: 600;
45
+
46
+ // Spacing
47
+ --ez-grid-cell-padding: 0 12px;
48
+ --ez-grid-row-height: 40px;
49
+ --ez-grid-header-height: 40px;
50
+
51
+ // Radius
52
+ --ez-grid-radius: 8px;
53
+ --ez-grid-radius-sm: 4px;
54
+
55
+ // ----------------------------------------------------------
56
+ // Base Layout
57
+ // ----------------------------------------------------------
58
+ background: var(--ez-grid-bg);
59
+ border-radius: var(--ez-grid-radius);
60
+ overflow: hidden;
61
+ display: flex;
62
+ flex-direction: column;
63
+ min-height: 0;
64
+
65
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
66
+ font-size: var(--ez-grid-font-size);
67
+ color: var(--ez-grid-text);
68
+
69
+ // ----------------------------------------------------------
70
+ // Variants
71
+ // ----------------------------------------------------------
72
+ &.ez-grid--flat {
73
+ border: none;
74
+ box-shadow: none;
75
+ }
76
+
77
+ &.ez-grid--outlined {
78
+ border: 1px solid var(--ez-grid-border);
79
+ box-shadow: none;
80
+ }
81
+
82
+ &.ez-grid--elevated {
83
+ border: 1px solid var(--ez-grid-border);
84
+ box-shadow:
85
+ 0 1px 3px rgba(0, 0, 0, 0.04),
86
+ 0 4px 12px rgba(0, 0, 0, 0.03);
87
+ }
88
+ }