@rettangoli/ui 1.0.3 → 1.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rettangoli/ui",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "A UI component library for building web interfaces.",
5
5
  "main": "dist/rettangoli-esm.min.js",
6
6
  "type": "module",
@@ -1,33 +1,85 @@
1
- export const handleDialogClose = (deps, payload) => {
2
- const { store, render } = deps;
1
+ const getDismissResult = (store) => {
2
+ const uiType = store.selectUiType();
3
3
 
4
- store.closeAll();
5
- render();
4
+ if (uiType === "dropdown" || uiType === "formDialog") {
5
+ return null;
6
+ }
7
+
8
+ const config = store.selectConfig();
9
+ if (config.mode === "confirm") {
10
+ return false;
11
+ }
12
+
13
+ return null;
6
14
  };
7
15
 
8
- export const handleConfirm = (deps, payload) => {
9
- const { store, render, globalUI } = deps;
16
+ const closeCurrentUi = ({ store, render, globalUI, emitResult = false, result }) => {
17
+ if (!store.selectIsOpen()) {
18
+ return;
19
+ }
10
20
 
21
+ const resolvedResult = result !== undefined ? result : getDismissResult(store);
11
22
  store.closeAll();
12
23
  render();
13
- globalUI.emit('event', true);
24
+
25
+ if (emitResult) {
26
+ globalUI.emit("event", resolvedResult);
27
+ }
14
28
  };
15
29
 
16
- export const handleCancel = (deps, payload) => {
30
+ const closeExistingUiBeforeShow = ({ store, render, globalUI }) => {
31
+ if (store.selectIsOpen()) {
32
+ closeCurrentUi({ store, render, globalUI, emitResult: true });
33
+ }
34
+ };
35
+
36
+ const scheduleFormDialogMount = (deps, expectedKey) => {
37
+ setTimeout(() => {
38
+ const { store, refs } = deps;
39
+
40
+ if (!store.selectIsOpen() || store.selectUiType() !== "formDialog") {
41
+ return;
42
+ }
43
+
44
+ const formDialogConfig = store.selectFormDialogConfig?.();
45
+ if (!formDialogConfig || formDialogConfig.key !== expectedKey) {
46
+ return;
47
+ }
48
+
49
+ if (typeof formDialogConfig.mount !== "function") {
50
+ return;
51
+ }
52
+
53
+ const formEl = refs.formDialog;
54
+ if (!formEl) {
55
+ return;
56
+ }
57
+
58
+ formDialogConfig.mount(formEl);
59
+ }, 0);
60
+ };
61
+
62
+ export const handleDialogClose = (deps) => {
17
63
  const { store, render, globalUI } = deps;
64
+ closeCurrentUi({ store, render, globalUI, emitResult: true });
65
+ };
18
66
 
19
- store.closeAll();
20
- render();
21
- globalUI.emit('event', false);
67
+ export const handleConfirm = (deps) => {
68
+ const { store, render, globalUI } = deps;
69
+ const config = store.selectConfig();
70
+ const result = config.mode === "confirm" ? true : null;
71
+
72
+ closeCurrentUi({ store, render, globalUI, emitResult: true, result });
22
73
  };
23
74
 
24
- export const handleDropdownClose = (deps, payload) => {
75
+ export const handleCancel = (deps) => {
25
76
  const { store, render, globalUI } = deps;
77
+ closeCurrentUi({ store, render, globalUI, emitResult: true, result: false });
78
+ };
26
79
 
27
- // Always process close events - the framework will handle if it's already closed
28
- store.closeAll();
29
- render();
30
- globalUI.emit('event', null);
80
+ export const handleDropdownClose = (deps) => {
81
+ const { store, render, globalUI } = deps;
82
+ closeCurrentUi({ store, render, globalUI, emitResult: true, result: null });
31
83
  };
32
84
 
33
85
  export const handleDropdownItemClick = (deps, payload) => {
@@ -35,10 +87,13 @@ export const handleDropdownItemClick = (deps, payload) => {
35
87
  const event = payload._event;
36
88
  const { index, item } = event.detail;
37
89
 
38
- // Always process click events - the framework will handle if it's already closed
39
- store.closeAll();
40
- render();
41
- globalUI.emit('event', { index, item });
90
+ closeCurrentUi({
91
+ store,
92
+ render,
93
+ globalUI,
94
+ emitResult: true,
95
+ result: { index, item },
96
+ });
42
97
  };
43
98
 
44
99
  /**
@@ -53,31 +108,29 @@ export const handleDropdownItemClick = (deps, payload) => {
53
108
  * @param {string} [payload.title] - Optional alert title
54
109
  * @param {('info'|'warning'|'error')} [payload.status] - Optional status type
55
110
  * @param {string} [payload.confirmText] - Text for the confirm button (default: "OK")
56
- * @returns {void}
111
+ * @returns {Promise<void>}
57
112
  */
58
113
  export const handleShowAlert = (deps, payload) => {
59
- const { store, render } = deps;
114
+ const { store, render, globalUI } = deps;
60
115
  const options = payload;
61
116
 
62
- // Close any existing dialog/dropdown menu first
63
- if (store.selectIsOpen()) {
64
- store.closeAll();
65
- render();
66
- }
117
+ closeExistingUiBeforeShow({ store, render, globalUI });
67
118
 
68
119
  store.setAlertConfig(options);
69
120
  render();
121
+
122
+ return new Promise((resolve) => {
123
+ globalUI.once("event", () => {
124
+ resolve();
125
+ });
126
+ });
70
127
  };
71
128
 
72
129
  export const handleShowConfirm = async (deps, payload) => {
73
130
  const { store, render, globalUI } = deps;
74
131
  const options = payload;
75
132
 
76
- // Close any existing dialog/dropdown menu first
77
- if (store.selectIsOpen()) {
78
- store.closeAll();
79
- render();
80
- }
133
+ closeExistingUiBeforeShow({ store, render, globalUI });
81
134
 
82
135
  store.setConfirmConfig(options);
83
136
  render();
@@ -111,11 +164,7 @@ export const handleShowDropdownMenu = async (deps, payload) => {
111
164
  const { store, render, globalUI } = deps;
112
165
  const options = payload;
113
166
 
114
- // Close any existing dialog/dropdown menu first
115
- if (store.selectIsOpen()) {
116
- store.closeAll();
117
- render();
118
- }
167
+ closeExistingUiBeforeShow({ store, render, globalUI });
119
168
 
120
169
  store.setDropdownConfig(options);
121
170
  render();
@@ -128,6 +177,55 @@ export const handleShowDropdownMenu = async (deps, payload) => {
128
177
  });
129
178
  };
130
179
 
180
+ export const handleShowFormDialog = async (deps, payload) => {
181
+ const { store, render, globalUI } = deps;
182
+ const options = payload;
183
+
184
+ closeExistingUiBeforeShow({ store, render, globalUI });
185
+
186
+ store.setFormDialogConfig(options);
187
+ render();
188
+
189
+ const expectedKey = store.selectFormDialogConfig?.().key;
190
+ scheduleFormDialogMount(deps, expectedKey);
191
+
192
+ return new Promise((resolve) => {
193
+ globalUI.once("event", (result) => {
194
+ resolve(result);
195
+ });
196
+ });
197
+ };
198
+
199
+ export const handleFormAction = (deps, payload) => {
200
+ const { store, render, globalUI } = deps;
201
+ const detail = payload._event?.detail || {};
202
+
203
+ if (detail.valid === false) {
204
+ return;
205
+ }
206
+
207
+ closeCurrentUi({
208
+ store,
209
+ render,
210
+ globalUI,
211
+ emitResult: true,
212
+ result: detail,
213
+ });
214
+ };
215
+
216
+ export const handleFormFieldEvent = (deps, payload) => {
217
+ const { store, refs } = deps;
218
+ const detail = payload._event?.detail || {};
219
+ const formDialogConfig = store.selectFormDialogConfig?.();
220
+
221
+ if (typeof formDialogConfig?.onFieldEvent === "function") {
222
+ formDialogConfig.onFieldEvent({
223
+ detail,
224
+ formEl: refs.formDialog || null,
225
+ });
226
+ }
227
+ };
228
+
131
229
  /**
132
230
  * Triggers a global event to close all UI components.
133
231
  * This function will:
@@ -141,11 +239,6 @@ export const handleShowDropdownMenu = async (deps, payload) => {
141
239
  * @returns {void}
142
240
  */
143
241
  export const handleCloseAll = (deps) => {
144
- const { store, render } = deps;
145
-
146
- // Close global UI dialogs/dropdowns
147
- if (store.selectIsOpen()) {
148
- store.closeAll();
149
- render();
150
- }
242
+ const { store, render, globalUI } = deps;
243
+ closeCurrentUi({ store, render, globalUI, emitResult: true });
151
244
  };
@@ -1,6 +1,19 @@
1
+ const VALID_DIALOG_SIZES = new Set(["sm", "md", "lg", "f"]);
2
+
3
+ const normalizeObject = (value) => {
4
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
5
+ return {};
6
+ }
7
+ return value;
8
+ };
9
+
10
+ const normalizeDialogSize = (value, fallback = "md") => {
11
+ return VALID_DIALOG_SIZES.has(value) ? value : fallback;
12
+ };
13
+
1
14
  export const createInitialState = () => Object.freeze({
2
15
  isOpen: false,
3
- uiType: "dialog", // "dialog" | "dropdown"
16
+ uiType: "dialog", // "dialog" | "dropdown" | "formDialog"
4
17
  config: {
5
18
  status: undefined, // undefined | info | warning | error
6
19
  title: "",
@@ -15,6 +28,16 @@ export const createInitialState = () => Object.freeze({
15
28
  y: 0,
16
29
  place: "bs",
17
30
  },
31
+ formDialogConfig: {
32
+ form: null,
33
+ defaultValues: {},
34
+ context: {},
35
+ disabled: false,
36
+ size: "md",
37
+ key: 0,
38
+ onFieldEvent: null,
39
+ mount: null,
40
+ },
18
41
  });
19
42
 
20
43
  export const setAlertConfig = ({ state }, options = {}) => {
@@ -66,6 +89,27 @@ export const setDropdownConfig = ({ state }, options = {}) => {
66
89
  state.isOpen = true;
67
90
  };
68
91
 
92
+ export const setFormDialogConfig = ({ state }, options = {}) => {
93
+ if (!options.form || typeof options.form !== "object" || Array.isArray(options.form)) {
94
+ throw new Error("form object is required for showFormDialog");
95
+ }
96
+
97
+ const prevKey = state.formDialogConfig?.key || 0;
98
+
99
+ state.formDialogConfig = {
100
+ form: options.form,
101
+ defaultValues: normalizeObject(options.defaultValues),
102
+ context: normalizeObject(options.context),
103
+ disabled: !!options.disabled,
104
+ size: normalizeDialogSize(options.size, "md"),
105
+ key: prevKey + 1,
106
+ onFieldEvent: typeof options.onFieldEvent === "function" ? options.onFieldEvent : null,
107
+ mount: typeof options.mount === "function" ? options.mount : null,
108
+ };
109
+ state.uiType = "formDialog";
110
+ state.isOpen = true;
111
+ };
112
+
69
113
  export const closeAll = ({ state }) => {
70
114
  state.isOpen = false;
71
115
  state.uiType = "dialog"; // Reset to default type
@@ -73,10 +117,14 @@ export const closeAll = ({ state }) => {
73
117
 
74
118
  export const selectConfig = ({ state }) => state.config;
75
119
  export const selectDropdownConfig = ({ state }) => state.dropdownConfig;
120
+ export const selectFormDialogConfig = ({ state }) => state.formDialogConfig;
76
121
  export const selectUiType = ({ state }) => state.uiType;
77
122
  export const selectIsOpen = ({ state }) => state.isOpen;
78
123
 
79
124
  export const selectViewData = ({ state }) => {
125
+ const isDialogOpen = state.isOpen && state.uiType === "dialog";
126
+ const isFormDialogOpen = state.isOpen && state.uiType === "formDialog";
127
+
80
128
  return {
81
129
  isOpen: state.isOpen,
82
130
  uiType: state.uiType,
@@ -87,7 +135,20 @@ export const selectViewData = ({ state }) => {
87
135
  y: state.dropdownConfig?.y || 0,
88
136
  place: state.dropdownConfig?.place || 'bs',
89
137
  },
90
- isDialogOpen: state.isOpen && state.uiType === 'dialog',
138
+ formDialogConfig: {
139
+ form: state.formDialogConfig?.form || { fields: [], actions: { buttons: [] } },
140
+ defaultValues: state.formDialogConfig?.defaultValues || {},
141
+ context: state.formDialogConfig?.context || {},
142
+ disabled: !!state.formDialogConfig?.disabled,
143
+ size: normalizeDialogSize(state.formDialogConfig?.size, "md"),
144
+ key: state.formDialogConfig?.key || 0,
145
+ },
146
+ isDialogOpen,
147
+ isFormDialogOpen,
148
+ isDialogContainerOpen: isDialogOpen || isFormDialogOpen,
91
149
  isDropdownOpen: state.isOpen && state.uiType === 'dropdown',
150
+ dialogSize: isFormDialogOpen
151
+ ? normalizeDialogSize(state.formDialogConfig?.size, "md")
152
+ : "sm",
92
153
  };
93
154
  };
@@ -11,6 +11,12 @@ refs:
11
11
  eventListeners:
12
12
  click:
13
13
  handler: handleCancel
14
+ formDialog:
15
+ eventListeners:
16
+ form-action:
17
+ handler: handleFormAction
18
+ form-field-event:
19
+ handler: handleFormFieldEvent
14
20
  dropdownMenu:
15
21
  eventListeners:
16
22
  close:
@@ -18,16 +24,19 @@ refs:
18
24
  item-click:
19
25
  handler: handleDropdownItemClick
20
26
  template:
21
- - rtgl-dialog#dialog ?open=${isDialogOpen} s=sm:
22
- - rtgl-view slot=content g=lg p=lg:
23
- - rtgl-view d=h g=md:
24
- - rtgl-view d=h ah=c av=c g=md:
25
- - rtgl-view g=md: null
26
- - rtgl-view h=24 av=c:
27
- - rtgl-text s=lg: ${config.title}
28
- - rtgl-text: ${config.message}
29
- - rtgl-view d=h g=md mt=lg w=f ah=e:
30
- - $if config.mode == 'confirm':
31
- - rtgl-button#cancelButton v=se: ${config.cancelText}
32
- - rtgl-button#confirmButton v=pr: ${config.confirmText}
27
+ - rtgl-dialog#dialog ?open=${isDialogContainerOpen} s=${dialogSize}:
28
+ - $if isFormDialogOpen:
29
+ - rtgl-form#formDialog slot=content :form=formDialogConfig.form :defaultValues=formDialogConfig.defaultValues :context=formDialogConfig.context ?disabled=${formDialogConfig.disabled} key=form-dialog-${formDialogConfig.key}: null
30
+ - $if !isFormDialogOpen:
31
+ - rtgl-view slot=content g=lg p=lg:
32
+ - rtgl-view d=h g=md:
33
+ - rtgl-view d=h ah=c av=c g=md:
34
+ - rtgl-view g=md: null
35
+ - rtgl-view h=24 av=c:
36
+ - rtgl-text s=lg: ${config.title}
37
+ - rtgl-text: ${config.message}
38
+ - rtgl-view d=h g=md mt=lg w=f ah=e:
39
+ - $if config.mode == 'confirm':
40
+ - rtgl-button#cancelButton v=se: ${config.cancelText}
41
+ - rtgl-button#confirmButton v=pr: ${config.confirmText}
33
42
  - rtgl-dropdown-menu#dropdownMenu ?open=${isDropdownOpen} x=${dropdownConfig.x} y=${dropdownConfig.y} place=${dropdownConfig.place} :items=dropdownConfig.items key=dropdown-${isDropdownOpen}: null
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Creates a GlobalUI manager instance for controlling global UI components.
3
- * Provides methods for showing alerts, confirm dialogs, dropdown menus, and closing all UI components.
3
+ * Provides methods for showing alerts, confirm dialogs, form dialogs,
4
+ * dropdown menus, and closing all UI components.
4
5
  *
5
6
  * @param {HTMLElement} globalUIElement - The globalUI component element
6
7
  * @returns {Object} GlobalUI manager instance
@@ -8,6 +9,7 @@
8
9
  * @returns {Function} returns.emit - Emit an event to registered listeners
9
10
  * @returns {Function} returns.showAlert - Show an alert dialog
10
11
  * @returns {Function} returns.showConfirm - Show a confirmation dialog
12
+ * @returns {Function} returns.showFormDialog - Show a form dialog
11
13
  * @returns {Function} returns.showDropdownMenu - Show a dropdown menu
12
14
  * @returns {Function} returns.closeAll - General-purpose function to close all currently open UI components
13
15
  */
@@ -66,7 +68,7 @@ const createGlobalUI = (globalUIElement) => {
66
68
  {
67
69
  throw new Error("globalUIElement is not set. Make sure to initialize the global UI component and pass it to createGlobalUIManager.");
68
70
  }
69
- globalUIElement.transformedHandlers.handleShowAlert(options);
71
+ return globalUIElement.transformedHandlers.handleShowAlert(options);
70
72
  },
71
73
 
72
74
  /**
@@ -90,6 +92,28 @@ const createGlobalUI = (globalUIElement) => {
90
92
  return globalUIElement.transformedHandlers.handleShowConfirm(options);
91
93
  },
92
94
 
95
+ /**
96
+ * Shows a dialog containing an embedded rtgl-form.
97
+ *
98
+ * @param {Object} options - Form dialog configuration options
99
+ * @param {Object} options.form - rtgl-form schema (required)
100
+ * @param {Object} [options.defaultValues] - Initial form values
101
+ * @param {Object} [options.context] - Context used by rtgl-form conditional rendering
102
+ * @param {boolean} [options.disabled] - Whether the form should be disabled
103
+ * @param {('sm'|'md'|'lg'|'f')} [options.size] - Dialog size token (default: "md")
104
+ * @param {Function} [options.onFieldEvent] - Called with `{ detail, formEl }` for form field events
105
+ * @param {Function} [options.mount] - Called once after the form mounts, useful for slot content
106
+ * @returns {Promise<Object|null>} Resolves with form-action detail or null on dismiss
107
+ * @throws {Error} If globalUIElement is not initialized
108
+ */
109
+ showFormDialog: async (options) => {
110
+ if(!globalUIElement)
111
+ {
112
+ throw new Error("globalUIElement is not set. Make sure to initialize the global UI component and pass it to createGlobalUIManager.");
113
+ }
114
+ return globalUIElement.transformedHandlers.handleShowFormDialog(options);
115
+ },
116
+
93
117
  /**
94
118
  * Shows a dropdown menu at the specified position with the given items.
95
119
  * The dropdown can contain various item types including labels, items, and separators.