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
package/core/state.ts ADDED
@@ -0,0 +1,142 @@
1
+ import { deepSignal } from 'deepsignal';
2
+ import { EzError } from './EzError.js';
3
+
4
+ export interface ControllerDefinition {
5
+ state?: Record<string, unknown>;
6
+ getState?: (key?: string) => unknown;
7
+ [key: string]: unknown;
8
+ }
9
+
10
+ export interface GridControllerDefinition {
11
+ onInit?: () => void | Promise<void>;
12
+ beforeDataRender?: () => void | Promise<void>;
13
+ afterDataRender?: () => void | Promise<void>;
14
+ [key: string]: unknown;
15
+ }
16
+
17
+ export interface GridBehaviorDefinition {
18
+ onInit?: () => void | Promise<void>;
19
+ [key: string]: unknown;
20
+ }
21
+
22
+ interface EzInstance {
23
+ _controllers: Record<string, ControllerDefinition>;
24
+ _gridControllerDefinitions: Record<string, GridControllerDefinition>;
25
+ _gridBehaviors: Record<string, GridBehaviorDefinition>;
26
+ _loader: {
27
+ resolveController: (name: string) => Promise<void>;
28
+ resolveGridBehavior: (name: string) => Promise<void>;
29
+ };
30
+ _context: {
31
+ route?: string;
32
+ view?: string;
33
+ };
34
+ }
35
+
36
+ export class EzState {
37
+ private ez: EzInstance;
38
+
39
+ constructor(ez: EzInstance) {
40
+ this.ez = ez;
41
+ }
42
+
43
+ defineController(name: string, definition: ControllerDefinition): void {
44
+ definition.state = deepSignal(definition.state || {});
45
+
46
+ definition.getState = (key: string = '') => {
47
+ if (!key) return definition.state;
48
+ return (definition.state as Record<string, unknown>)[key];
49
+ };
50
+
51
+ this.ez._controllers[name] = definition;
52
+ }
53
+
54
+ getController(name: string): ControllerDefinition | Promise<ControllerDefinition> {
55
+ if (this.ez._controllers[name]) return this.ez._controllers[name];
56
+
57
+ return (async () => {
58
+ await this.ez._loader.resolveController(name);
59
+ return this.ez._controllers[name];
60
+ })();
61
+ }
62
+
63
+ getControllerSync(name: string): ControllerDefinition {
64
+ const ctrl = this.ez._controllers[name];
65
+ if (!ctrl) {
66
+ throw new EzError({
67
+ code: 'EZ_CONTROLLER_002',
68
+ source: 'controller',
69
+ message: `Controller "${name}" accessed before being resolved`,
70
+ route: this.ez._context.route,
71
+ view: this.ez._context.view
72
+ });
73
+ }
74
+ return ctrl;
75
+ }
76
+
77
+ defineGridController(name: string, definition: GridControllerDefinition): void {
78
+ if (!name) {
79
+ throw new EzError({
80
+ code: 'EZ_GRID_CTRL_001',
81
+ message: 'defineGridController requires a "gridController" name'
82
+ });
83
+ }
84
+
85
+ if (this.ez._gridControllerDefinitions[name]) {
86
+ throw new EzError({
87
+ code: 'EZ_GRID_CTRL_002',
88
+ message: `GridController "${name}" already defined`
89
+ });
90
+ }
91
+
92
+ this.ez._gridControllerDefinitions[name] = definition;
93
+ }
94
+
95
+ defineGridBehavior(name: string, definition: GridBehaviorDefinition): void {
96
+ if (!name) return;
97
+
98
+ this.ez._gridBehaviors[name] = definition;
99
+ }
100
+
101
+ async getGridBehavior(name: string): Promise<GridBehaviorDefinition | null> {
102
+ if (!name) return null;
103
+
104
+ if (this.ez._gridBehaviors[name]) {
105
+ return this.ez._gridBehaviors[name];
106
+ }
107
+
108
+ await this.ez._loader.resolveGridBehavior(name);
109
+
110
+ const behavior = this.ez._gridBehaviors[name];
111
+
112
+ if (!behavior) {
113
+ throw new EzError({
114
+ code: 'EZ_GRID_BEHAVIOR_INVALID',
115
+ source: 'grid',
116
+ message: `GridBehavior "${name}" was loaded but did not register itself`,
117
+ route: this.ez._context?.route,
118
+ view: this.ez._context?.view
119
+ });
120
+ }
121
+
122
+ return behavior;
123
+ }
124
+
125
+ getDeepValue(obj: Record<string, unknown>, pathArray: string[]): unknown {
126
+ return pathArray.reduce((acc: unknown, key: string) => {
127
+ if (acc && typeof acc === 'object') {
128
+ return (acc as Record<string, unknown>)[key];
129
+ }
130
+ return undefined;
131
+ }, obj);
132
+ }
133
+
134
+ setDeepValue(obj: Record<string, unknown>, props: string[], value: unknown): void {
135
+ let target: Record<string, unknown> = obj;
136
+ for (let i = 0; i < props.length - 1; i++) {
137
+ const key = props[i];
138
+ target = target[key] as Record<string, unknown>;
139
+ }
140
+ target[props[props.length - 1]] = value;
141
+ }
142
+ }
package/core/utils.ts ADDED
@@ -0,0 +1,81 @@
1
+ export type FormatType = 'currency' | 'percentage' | 'number' | 'compact';
2
+
3
+ export interface FormatOptions {
4
+ locale?: string;
5
+ currency?: string;
6
+ decimals?: number;
7
+ }
8
+
9
+ export function format(value: unknown, type?: FormatType, options: FormatOptions = {}): string {
10
+ const { locale = 'en-US', currency = 'USD', decimals } = options;
11
+
12
+ if (value === null || value === undefined) return '';
13
+ if (typeof value !== 'number') return String(value);
14
+
15
+ switch (type) {
16
+ case 'currency':
17
+ return new Intl.NumberFormat(locale, {
18
+ style: 'currency',
19
+ currency,
20
+ minimumFractionDigits: decimals ?? 2,
21
+ maximumFractionDigits: decimals ?? 2
22
+ }).format(value);
23
+
24
+ case 'percentage':
25
+ return new Intl.NumberFormat(locale, {
26
+ style: 'percent',
27
+ minimumFractionDigits: decimals ?? 0,
28
+ maximumFractionDigits: decimals ?? 2
29
+ }).format(value / 100);
30
+
31
+ case 'compact':
32
+ return new Intl.NumberFormat(locale, {
33
+ notation: 'compact',
34
+ compactDisplay: 'short'
35
+ }).format(value);
36
+
37
+ case 'number':
38
+ default:
39
+ return value.toLocaleString(locale, {
40
+ minimumFractionDigits: decimals,
41
+ maximumFractionDigits: decimals
42
+ });
43
+ }
44
+ }
45
+
46
+ export function capitalize(str: string): string {
47
+ if (!str) return '';
48
+ return str.charAt(0).toUpperCase() + str.slice(1);
49
+ }
50
+
51
+ export function capitalizeWords(str: string): string {
52
+ if (!str) return '';
53
+ return str.split(' ').map(word => capitalize(word)).join(' ');
54
+ }
55
+
56
+ export function camelToKebab(str: string): string {
57
+ return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
58
+ }
59
+
60
+ export function kebabToCamel(str: string): string {
61
+ return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
62
+ }
63
+
64
+ export function truncate(str: string, length: number, suffix = '...'): string {
65
+ if (!str || str.length <= length) return str;
66
+ return str.slice(0, length - suffix.length) + suffix;
67
+ }
68
+
69
+ export function pluralize(count: number, singular: string, plural?: string): string {
70
+ return count === 1 ? singular : (plural || singular + 's');
71
+ }
72
+
73
+ export const EzUtils = {
74
+ format,
75
+ capitalize,
76
+ capitalizeWords,
77
+ camelToKebab,
78
+ kebabToCamel,
79
+ truncate,
80
+ pluralize
81
+ };
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "ezfw-core",
3
+ "version": "1.0.0",
4
+ "description": "Ez Framework - A declarative component framework for building modern web applications",
5
+ "type": "module",
6
+ "main": "./core/ez.ts",
7
+ "module": "./core/ez.ts",
8
+ "types": "./core/ez.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./core/ez.ts",
12
+ "types": "./core/ez.ts"
13
+ },
14
+ "./*": "./*"
15
+ },
16
+ "files": [
17
+ "core",
18
+ "components",
19
+ "services",
20
+ "template",
21
+ "themes",
22
+ "types",
23
+ "utils"
24
+ ],
25
+ "keywords": [
26
+ "ez",
27
+ "framework",
28
+ "component",
29
+ "declarative",
30
+ "vite",
31
+ "typescript"
32
+ ],
33
+ "author": "Ez Hard Soft",
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://bitbucket.org/ez-hard-soft/ez.git"
38
+ },
39
+ "dependencies": {
40
+ "@preact/signals": "^2.5.1",
41
+ "deepsignal": "^1.6.0",
42
+ "moment": "^2.30.1"
43
+ },
44
+ "peerDependencies": {
45
+ "vite": ">=5.0.0",
46
+ "sass": ">=1.69.0"
47
+ },
48
+ "engines": {
49
+ "node": ">=18.0.0"
50
+ }
51
+ }
@@ -0,0 +1,17 @@
1
+ export class RouteUIService {
2
+ buildBreadcrumbs({ chain, fullPath }) {
3
+ return chain.map(node => {
4
+ const meta = node.routeDef?.meta || {};
5
+
6
+ const label =
7
+ typeof meta.breadcrumb === 'function'
8
+ ? meta.breadcrumb(node.params)
9
+ : meta.title || node.view;
10
+
11
+ return {
12
+ label,
13
+ route: fullPath
14
+ };
15
+ });
16
+ }
17
+ }
@@ -0,0 +1,64 @@
1
+ let _ready = false;
2
+ let _key = null;
3
+ let _sodium = null;
4
+
5
+ export const CryptoService = {
6
+ async init(secret) {
7
+ if (_ready) return;
8
+
9
+ // Dynamic import - libsodium only loads when init() is called
10
+ const { default: sodium } = await import('libsodium-wrappers');
11
+ _sodium = sodium;
12
+
13
+ await _sodium.ready;
14
+
15
+ if (!secret) {
16
+ throw new Error('[CryptoService] secret is required');
17
+ }
18
+
19
+ // 🔑 SAME KDF AS SERVER
20
+ _key = _sodium.crypto_generichash(
21
+ _sodium.crypto_secretbox_KEYBYTES,
22
+ secret
23
+ );
24
+
25
+ _ready = true;
26
+ },
27
+
28
+ encode(value) {
29
+ if (!_ready) {
30
+ throw new Error('[CryptoService] not initialized');
31
+ }
32
+
33
+ const nonce = _sodium.randombytes_buf(
34
+ _sodium.crypto_secretbox_NONCEBYTES
35
+ );
36
+
37
+ const cipher = _sodium.crypto_secretbox_easy(
38
+ _sodium.from_string(JSON.stringify(value)),
39
+ nonce,
40
+ _key
41
+ );
42
+
43
+ return JSON.stringify({
44
+ nonce: _sodium.to_base64(nonce),
45
+ cipher: _sodium.to_base64(cipher)
46
+ });
47
+ },
48
+
49
+ decode(payload) {
50
+ if (!_ready) {
51
+ throw new Error('[CryptoService] not initialized');
52
+ }
53
+
54
+ const { nonce, cipher } = JSON.parse(payload);
55
+
56
+ const decrypted = _sodium.crypto_secretbox_open_easy(
57
+ _sodium.from_base64(cipher),
58
+ _sodium.from_base64(nonce),
59
+ _key
60
+ );
61
+
62
+ return JSON.parse(_sodium.to_string(decrypted));
63
+ }
64
+ };
@@ -0,0 +1,222 @@
1
+ export class EzDialogService {
2
+ constructor() {
3
+ this._stack = [];
4
+ this._idCounter = 0;
5
+ }
6
+
7
+ get stack() {
8
+ return this._stack;
9
+ }
10
+
11
+ async open(config) {
12
+ const dialogId = `ez-dialog-${++this._idCounter}`;
13
+ let resolvePromise;
14
+ let el;
15
+
16
+ // Create dialog instance with methods
17
+ const dialogInstance = {
18
+ _id: dialogId,
19
+ _el: null,
20
+ _isMaximized: config.maximized || false,
21
+ _isClosed: false,
22
+ _isHidden: false,
23
+
24
+ close: (result = null) => {
25
+ if (dialogInstance._isClosed) return;
26
+ dialogInstance._isClosed = true;
27
+
28
+ document.removeEventListener('keydown', onEscKey);
29
+
30
+ if (dialogInstance._el) {
31
+ dialogInstance._el.classList.remove('visible');
32
+ dialogInstance._el.classList.add('closing');
33
+
34
+ setTimeout(() => {
35
+ dialogInstance._el?.remove();
36
+ }, 200);
37
+ }
38
+
39
+ this.pop(dialogInstance);
40
+
41
+ if (resolvePromise) {
42
+ resolvePromise(result);
43
+ }
44
+
45
+ if (config.onClose) {
46
+ config.onClose(result);
47
+ }
48
+ },
49
+
50
+ hide: () => {
51
+ if (dialogInstance._isHidden || dialogInstance._isClosed) return;
52
+ dialogInstance._isHidden = true;
53
+
54
+ if (dialogInstance._el) {
55
+ dialogInstance._el.classList.remove('visible');
56
+ dialogInstance._el.classList.add('hidden');
57
+ }
58
+ },
59
+
60
+ show: () => {
61
+ if (!dialogInstance._isHidden || dialogInstance._isClosed) return;
62
+ dialogInstance._isHidden = false;
63
+
64
+ if (dialogInstance._el) {
65
+ dialogInstance._el.classList.remove('hidden');
66
+ dialogInstance._el.classList.add('visible');
67
+ }
68
+ },
69
+
70
+ toggleMaximize: () => {
71
+ dialogInstance._isMaximized = !dialogInstance._isMaximized;
72
+ const dialogEl = dialogInstance._el?.querySelector('[class*="dialog"]');
73
+ if (dialogEl) {
74
+ dialogEl.classList.toggle('maximized', dialogInstance._isMaximized);
75
+ }
76
+ },
77
+
78
+ isHidden: () => dialogInstance._isHidden
79
+ };
80
+
81
+ // ESC key handler
82
+ const onEscKey = (e) => {
83
+ if (e.key === 'Escape' && config.closeOnEsc !== false) {
84
+ if (this.getTopDialog() === dialogInstance) {
85
+ dialogInstance.close();
86
+ }
87
+ }
88
+ };
89
+
90
+ // Overlay click handler
91
+ const onOverlayClick = (e) => {
92
+ if (config.closeOnOverlay !== false && e.target === el) {
93
+ dialogInstance.close();
94
+ }
95
+ };
96
+
97
+ // Build dialog config
98
+ const dialogConfig = {
99
+ eztype: 'EzDialog',
100
+ props: {
101
+ ...config,
102
+ _dialogId: dialogId,
103
+ _dialogInstance: dialogInstance
104
+ }
105
+ };
106
+
107
+ // Render through framework
108
+ el = await ez._createElement(dialogConfig);
109
+ dialogInstance._el = el;
110
+
111
+ // Set z-index based on stack
112
+ const level = this._stack.length;
113
+ const zIndex = 1000 + (level * 10);
114
+ el.style.zIndex = zIndex;
115
+
116
+ // Add event listeners
117
+ if (config.closeOnEsc !== false) {
118
+ document.addEventListener('keydown', onEscKey);
119
+ }
120
+ if (config.closeOnOverlay !== false) {
121
+ el.addEventListener('click', onOverlayClick);
122
+ }
123
+
124
+ // Push to stack and append to body
125
+ this.push(dialogInstance);
126
+ document.body.appendChild(el);
127
+
128
+ // Trigger animation
129
+ requestAnimationFrame(() => {
130
+ el.classList.add('visible');
131
+ });
132
+
133
+ // Return promise
134
+ return new Promise((resolve) => {
135
+ resolvePromise = resolve;
136
+ });
137
+ }
138
+
139
+ async alert(messageOrConfig, options = {}) {
140
+ const config = typeof messageOrConfig === 'string'
141
+ ? { message: messageOrConfig, ...options }
142
+ : messageOrConfig;
143
+
144
+ return this.open({
145
+ title: config.title || 'Alert',
146
+ message: config.message,
147
+ icon: config.icon || 'fa-solid fa-circle-info',
148
+ iconVariant: config.iconVariant || 'info',
149
+ size: config.size || 'sm',
150
+ closable: config.closable ?? true,
151
+ closeOnOverlay: config.closeOnOverlay ?? true,
152
+ buttons: [
153
+ {
154
+ text: config.buttonText || 'OK',
155
+ variant: 'primary',
156
+ value: true
157
+ }
158
+ ]
159
+ });
160
+ }
161
+
162
+ async confirm(messageOrConfig, options = {}) {
163
+ const config = typeof messageOrConfig === 'string'
164
+ ? { message: messageOrConfig, ...options }
165
+ : messageOrConfig;
166
+
167
+ const result = await this.open({
168
+ title: config.title || 'Confirm',
169
+ message: config.message,
170
+ icon: config.icon || 'fa-solid fa-circle-question',
171
+ iconVariant: config.iconVariant || 'warning',
172
+ size: config.size || 'sm',
173
+ closable: config.closable ?? false,
174
+ closeOnOverlay: config.closeOnOverlay ?? false,
175
+ closeOnEsc: config.closeOnEsc ?? true,
176
+ buttons: [
177
+ {
178
+ text: config.cancelText || 'Cancel',
179
+ variant: 'secondary',
180
+ value: false
181
+ },
182
+ {
183
+ text: config.confirmText || 'Confirm',
184
+ variant: config.confirmVariant || 'primary',
185
+ value: true
186
+ }
187
+ ]
188
+ });
189
+
190
+ return result === true;
191
+ }
192
+
193
+ async danger(messageOrConfig, options = {}) {
194
+ const config = typeof messageOrConfig === 'string'
195
+ ? { message: messageOrConfig, ...options }
196
+ : messageOrConfig;
197
+
198
+ return this.confirm({
199
+ ...config,
200
+ title: config.title || 'Are you sure?',
201
+ icon: config.icon || 'fa-solid fa-triangle-exclamation',
202
+ iconVariant: 'danger',
203
+ confirmText: config.confirmText || 'Delete',
204
+ confirmVariant: 'danger'
205
+ });
206
+ }
207
+
208
+ push(dialog) {
209
+ this._stack.push(dialog);
210
+ }
211
+
212
+ pop(dialog) {
213
+ const index = this._stack.indexOf(dialog);
214
+ if (index > -1) {
215
+ this._stack.splice(index, 1);
216
+ }
217
+ }
218
+
219
+ getTopDialog() {
220
+ return this._stack.length > 0 ? this._stack[this._stack.length - 1] : null;
221
+ }
222
+ }
@@ -0,0 +1,63 @@
1
+ import {EzError} from "../core/EzError.js";
2
+
3
+ export class FetchApi {
4
+ constructor(config = {}) {
5
+ this.baseUrl = config.baseUrl || '';
6
+ this._headersResolver = null;
7
+ this._responseTransformers = [];
8
+ this._errorHandler = null;
9
+ }
10
+
11
+ setHeadersResolver(fn) {
12
+ this._headersResolver = fn;
13
+ }
14
+
15
+ addResponseTransformer(fn) {
16
+ this._responseTransformers.push(fn);
17
+ }
18
+
19
+ setErrorHandler(fn) {
20
+ this._errorHandler = fn;
21
+ }
22
+
23
+ async request(url, options = {}) {
24
+ const res = await fetch(this.baseUrl + url, {
25
+ method: options.method || 'GET',
26
+ headers: this._headersResolver?.() || {},
27
+ body: options.body
28
+ ? JSON.stringify(options.body)
29
+ : undefined
30
+ });
31
+
32
+ const payload = await res.json().catch(() => null);
33
+
34
+ // 🔴 ERROR PATH (TU CONTRATO REAL)
35
+ if (!res.ok || payload?.success === false) {
36
+ const error = {
37
+ status: payload?.status ?? res.status,
38
+ message: payload?.message ?? 'Unknown error',
39
+ field: payload?.field ?? null
40
+ };
41
+
42
+ if (typeof this._errorHandler === 'function') {
43
+ await this._errorHandler(error, res);
44
+ }
45
+
46
+ throw new EzError({
47
+ code: 'EZ_FETCH_001',
48
+ source: 'fetch',
49
+ message: 'Request failed before reaching server',
50
+ route: ez._context.route,
51
+ cause: error
52
+ });
53
+ }
54
+
55
+ // 🟢 SUCCESS PATH
56
+ let data = payload;
57
+ for (const transform of this._responseTransformers) {
58
+ data = await transform(data, res);
59
+ }
60
+
61
+ return data;
62
+ }
63
+ }
@@ -0,0 +1,30 @@
1
+ export class FirebaseService {
2
+ constructor(config) {
3
+ this.config = config;
4
+ this.app = null;
5
+ this.auth = null;
6
+ this.initialized = false;
7
+ }
8
+
9
+ async init() {
10
+ if (this.initialized) return;
11
+
12
+ const { initializeApp } = await import('firebase/app');
13
+ const { getAuth } = await import('firebase/auth');
14
+
15
+ this.app = initializeApp(this.config);
16
+ this.auth = getAuth(this.app);
17
+ this.initialized = true;
18
+
19
+ console.log('[ez] Firebase initialized');
20
+ }
21
+
22
+ async loginWithEmail(email, password) {
23
+ if (!this.initialized) {
24
+ await this.init();
25
+ }
26
+
27
+ const { signInWithEmailAndPassword } = await import('firebase/auth');
28
+ return signInWithEmailAndPassword(this.auth, email, password);
29
+ }
30
+ }