@rettangoli/ui 0.1.4 → 0.1.6

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": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "A UI component library for building web interfaces.",
5
5
  "main": "dist/rettangoli-esm.min.js",
6
6
  "type": "module",
@@ -23,10 +23,10 @@
23
23
  },
24
24
  "license": "MIT",
25
25
  "scripts": {
26
- "build:dev": "rtgl fe build && bun run esbuild-dev.js",
26
+ "build:dev": "rtgl fe build -d && bun run esbuild-dev.js",
27
27
  "build": "rtgl fe build && bun run esbuild.js",
28
- "vt:generate": "rtgl fe build && bun run esbuild-dev.js && rtgl vt generate --skip-screenshots",
29
- "vt:report": "bun run build:dev && rtgl vt generate && rtgl vt report",
28
+ "vt:generate": "rtgl fe build -d && bun run esbuild-dev.js && rtgl vt generate --skip-screenshots",
29
+ "vt:report": "bun run build:dev && rtgl vt generate --screenshot-wait-time 500 && rtgl vt report",
30
30
  "vt:accept": "rtgl vt accept",
31
31
  "serve": "bunx serve .rettangoli/vt/_site"
32
32
  },
@@ -5,10 +5,10 @@ export const handleClosePopover = (deps, payload) => {
5
5
  }
6
6
 
7
7
  export const handleClickMenuItem = (deps, payload) => {
8
- const { dispatchEvent } = deps;
8
+ const { dispatchEvent, props } = deps;
9
9
  const event = payload._event;
10
10
  const index = parseInt(event.currentTarget.id.replace('option-', ''));
11
- const item = deps.props.items[index];
11
+ const item = props.items[index];
12
12
 
13
13
  dispatchEvent(new CustomEvent('click-item', {
14
14
  detail: {
@@ -44,13 +44,13 @@ refs:
44
44
 
45
45
  template:
46
46
  - rtgl-popover#popover ?open=${open} x=${x} y=${y} placement=${placement}:
47
- - rtgl-view wh=300 g=xs slot=content bgc=background br=md:
48
- - $for item, i in items:
49
- - $if item.type == 'label':
50
- - rtgl-view w=f ph=lg pv=md:
51
- - rtgl-text s=sm c=mu-fg: ${item.label}
52
- $elif item.type == 'item':
53
- - rtgl-view#option-${i} w=f h-bgc=ac ph=lg pv=md cur=p br=md bgc=mu:
54
- - rtgl-text: ${item.label}
55
- $elif item.type == 'separator':
56
- - rtgl-view w=f h=1 ph=lg mv=md bgc=bo:
47
+ - rtgl-view wh=300 sv g=xs slot=content bgc=background br=md:
48
+ - $for item, i in items:
49
+ - $if item.type == 'label':
50
+ - rtgl-view w=f ph=lg pv=md:
51
+ - rtgl-text s=sm c=mu-fg: ${item.label}
52
+ $elif item.type == 'item':
53
+ - rtgl-view#option-${i} w=f h-bgc=ac ph=lg pv=md cur=p br=md bgc=mu:
54
+ - rtgl-text: ${item.label}
55
+ $elif item.type == 'separator':
56
+ - rtgl-view w=f h=1 ph=lg mv=md bgc=bo:
@@ -103,7 +103,7 @@ export const selectForm = ({ state, props }) => {
103
103
 
104
104
  export const selectViewData = ({ state, props, attrs }) => {
105
105
  const containerAttrString = stringifyAttrs(attrs);
106
- const defaultValues = props.defaultValues || {};
106
+ const defaultValues = state.formValues;
107
107
 
108
108
  const form = selectForm({ state, props });
109
109
  const fields = structuredClone(form.fields || []);
@@ -21,12 +21,48 @@ export const handleCancel = (deps, payload) => {
21
21
  globalUI.emit('event', false);
22
22
  };
23
23
 
24
+ export const handleDropdownClose = (deps, payload) => {
25
+ const { store, render, globalUI } = deps;
26
+
27
+ // Always process close events - the framework will handle if it's already closed
28
+ store.closeDialog();
29
+ render();
30
+ globalUI.emit('event', null);
31
+ };
32
+
33
+ export const handleDropdownItemClick = (deps, payload) => {
34
+ const { store, render, globalUI } = deps;
35
+ const event = payload._event;
36
+ const { index, item } = event.detail;
37
+
38
+ // Always process click events - the framework will handle if it's already closed
39
+ store.closeDialog();
40
+ render();
41
+ globalUI.emit('event', { index, item });
42
+ };
43
+
44
+ /**
45
+ * Shows an alert dialog with the specified options.
46
+ * Closes any existing dialog or dropdown before showing the alert.
47
+ *
48
+ * @param {Object} deps - Dependencies object
49
+ * @param {Object} deps.store - The globalUI store instance
50
+ * @param {Function} deps.render - Function to trigger re-rendering
51
+ * @param {Object} payload - Alert configuration options
52
+ * @param {string} payload.message - The alert message (required)
53
+ * @param {string} [payload.title] - Optional alert title
54
+ * @param {('info'|'warning'|'error')} [payload.status] - Optional status type
55
+ * @param {string} [payload.confirmText] - Text for the confirm button (default: "OK")
56
+ * @returns {void}
57
+ */
24
58
  export const showAlert = (deps, payload) => {
25
59
  const { store, render } = deps;
26
60
  const options = payload;
27
61
 
62
+ // Close any existing dialog/dropdown menu first
28
63
  if (store.selectIsOpen()) {
29
- throw new Error("A dialog is already open");
64
+ store.closeDialog();
65
+ render();
30
66
  }
31
67
 
32
68
  store.setAlertConfig(options);
@@ -37,8 +73,10 @@ export const showConfirm = async (deps, payload) => {
37
73
  const { store, render, globalUI } = deps;
38
74
  const options = payload;
39
75
 
76
+ // Close any existing dialog/dropdown menu first
40
77
  if (store.selectIsOpen()) {
41
- throw new Error("A dialog is already open");
78
+ store.closeDialog();
79
+ render();
42
80
  }
43
81
 
44
82
  store.setConfirmConfig(options);
@@ -50,4 +88,42 @@ export const showConfirm = async (deps, payload) => {
50
88
  resolve(result)
51
89
  });
52
90
  });
91
+ };
92
+
93
+ /**
94
+ * Shows a dropdown menu at the specified position with the given items.
95
+ * Closes any existing dialog or dropdown menu before showing the dropdown menu.
96
+ *
97
+ * @param {Object} deps - Dependencies object
98
+ * @param {Object} deps.store - The globalUI store instance
99
+ * @param {Function} deps.render - Function to trigger re-rendering
100
+ * @param {Object} deps.globalUI - The globalUI event emitter
101
+ * @param {Object} payload - Dropdown menu configuration options
102
+ * @param {Array<Object>} payload.items - Array of dropdown menu items (required)
103
+ * @param {number} payload.x - X coordinate position (required)
104
+ * @param {number} payload.y - Y coordinate position (required)
105
+ * @param {string} [payload.placement] - Dropdown menu placement (default: "bottom-start")
106
+ * @returns {Promise<Object|null>} Promise that resolves with clicked item info or null if closed without selection
107
+ * @returns {Object} [result.index] - Index of the clicked item
108
+ * @returns {Object} [result.item] - The clicked item object
109
+ */
110
+ export const showDropdownMenu = async (deps, payload) => {
111
+ const { store, render, globalUI } = deps;
112
+ const options = payload;
113
+
114
+ // Close any existing dialog/dropdown menu first
115
+ if (store.selectIsOpen()) {
116
+ store.closeDialog();
117
+ render();
118
+ }
119
+
120
+ store.setDropdownConfig(options);
121
+ render();
122
+
123
+ return new Promise((resolve) => {
124
+ globalUI.once('event', (result) => {
125
+ // result contains info of clicked item or null if closed without selection
126
+ resolve(result)
127
+ });
128
+ });
53
129
  };
@@ -1,5 +1,6 @@
1
1
  export const createInitialState = () => Object.freeze({
2
2
  isOpen: false,
3
+ uiType: "dialog", // "dialog" | "dropdown"
3
4
  config: {
4
5
  status: undefined, // undefined | info | warning | error
5
6
  title: "",
@@ -8,13 +9,19 @@ export const createInitialState = () => Object.freeze({
8
9
  cancelText: "Cancel",
9
10
  mode: "alert", // alert | confirm
10
11
  },
12
+ dropdownConfig: {
13
+ items: [],
14
+ x: 0,
15
+ y: 0,
16
+ placement: "bottom-start",
17
+ },
11
18
  });
12
19
 
13
20
  export const setAlertConfig = (state, options) => {
14
21
  if (!options.message) {
15
22
  throw new Error("message is required for showAlert");
16
23
  }
17
-
24
+
18
25
  state.config = {
19
26
  status: options.status || undefined,
20
27
  title: options.title || "",
@@ -23,6 +30,7 @@ export const setAlertConfig = (state, options) => {
23
30
  cancelText: "",
24
31
  mode: "alert",
25
32
  };
33
+ state.uiType = "dialog";
26
34
  state.isOpen = true;
27
35
  };
28
36
 
@@ -30,7 +38,7 @@ export const setConfirmConfig = (state, options) => {
30
38
  if (!options.message) {
31
39
  throw new Error("message is required for showConfirm");
32
40
  }
33
-
41
+
34
42
  state.config = {
35
43
  status: options.status || undefined,
36
44
  title: options.title || "",
@@ -39,19 +47,47 @@ export const setConfirmConfig = (state, options) => {
39
47
  cancelText: options.cancelText || "Cancel",
40
48
  mode: "confirm",
41
49
  };
50
+ state.uiType = "dialog";
51
+ state.isOpen = true;
52
+ };
53
+
54
+ export const setDropdownConfig = (state, options) => {
55
+ if (!options.items || !Array.isArray(options.items)) {
56
+ throw new Error("items array is required for showDropdown");
57
+ }
58
+
59
+ state.dropdownConfig = {
60
+ items: options.items,
61
+ x: options.x || 0,
62
+ y: options.y || 0,
63
+ placement: options.placement || "bottom-start",
64
+ };
65
+ state.uiType = "dropdown";
42
66
  state.isOpen = true;
43
67
  };
44
68
 
45
69
  export const closeDialog = (state) => {
46
70
  state.isOpen = false;
71
+ state.uiType = "dialog"; // Reset to default type
47
72
  };
48
73
 
49
74
  export const selectConfig = ({ state }) => state.config;
75
+ export const selectDropdownConfig = ({ state }) => state.dropdownConfig;
76
+ export const selectUiType = ({ state }) => state.uiType;
50
77
  export const selectIsOpen = ({ state }) => state.isOpen;
51
78
 
52
79
  export const selectViewData = ({ state }) => {
53
80
  return {
54
81
  isOpen: state.isOpen,
82
+ uiType: state.uiType,
55
83
  config: state.config,
84
+ dropdownConfig: {
85
+ items: state.dropdownConfig?.items || [],
86
+ x: state.dropdownConfig?.x || 0,
87
+ y: state.dropdownConfig?.y || 0,
88
+ placement: state.dropdownConfig?.placement || 'bottom-start',
89
+ },
90
+ isDialogOpen: state.isOpen && state.uiType === 'dialog',
91
+ isDropdownOpen: state.isOpen && state.uiType === 'dropdown',
56
92
  };
57
93
  };
@@ -5,6 +5,8 @@ viewDataSchema:
5
5
  properties:
6
6
  isOpen:
7
7
  type: boolean
8
+ uiType:
9
+ type: string
8
10
  config:
9
11
  type: object
10
12
  properties:
@@ -20,6 +22,17 @@ viewDataSchema:
20
22
  type: string
21
23
  mode:
22
24
  type: string
25
+ dropdownConfig:
26
+ type: object
27
+ properties:
28
+ items:
29
+ type: array
30
+ x:
31
+ type: number
32
+ y:
33
+ type: number
34
+ placement:
35
+ type: string
23
36
 
24
37
  refs:
25
38
  dialog:
@@ -34,17 +47,24 @@ refs:
34
47
  eventListeners:
35
48
  click:
36
49
  handler: handleCancel
50
+ dropdown-menu:
51
+ eventListeners:
52
+ close:
53
+ handler: handleDropdownClose
54
+ click-item:
55
+ handler: handleDropdownItemClick
37
56
 
38
57
  template:
39
- - rtgl-dialog#dialog ?open=${isOpen} s=sm:
58
+ - rtgl-dialog#dialog ?open=${isDialogOpen} s=sm:
40
59
  - rtgl-view slot=content g=lg p=lg:
41
- - rtgl-view d=h g=md:
42
- - rtgl-view d=h ah=c av=c g=md:
43
- - rtgl-view g=md:
44
- - rtgl-view h=24 av=c:
45
- - rtgl-text fw=600: ${config.title}
46
- - rtgl-text: ${config.message}
47
- - rtgl-view d=h g=md mt=lg w=f ah=e:
48
- - $if config.mode == 'confirm':
49
- - rtgl-button#cancel-button v=se: ${config.cancelText}
50
- - rtgl-button#confirm-button v=pr: ${config.confirmText}
60
+ - rtgl-view d=h g=md:
61
+ - rtgl-view d=h ah=c av=c g=md:
62
+ - rtgl-view g=md:
63
+ - rtgl-view h=24 av=c:
64
+ - rtgl-text fw=600: ${config.title}
65
+ - rtgl-text: ${config.message}
66
+ - rtgl-view d=h g=md mt=lg w=f ah=e:
67
+ - $if config.mode == 'confirm':
68
+ - rtgl-button#cancel-button v=se: ${config.cancelText}
69
+ - rtgl-button#confirm-button v=pr: ${config.confirmText}
70
+ - rtgl-dropdown-menu#dropdown-menu ?open=${isDropdownOpen} x=${dropdownConfig.x} y=${dropdownConfig.y} placement=${dropdownConfig.placement} .items=dropdownConfig.items key=dropdown-${isDropdownOpen}:
@@ -1,7 +1,27 @@
1
+ /**
2
+ * Creates a GlobalUI manager instance for controlling global UI components.
3
+ * Provides methods for showing alerts, confirm dialogs, and dropdown menus.
4
+ *
5
+ * @param {HTMLElement} globalUIElement - The globalUI component element
6
+ * @returns {Object} GlobalUI manager instance
7
+ * @returns {Function} returns.once - Register a one-time event listener
8
+ * @returns {Function} returns.emit - Emit an event to registered listeners
9
+ * @returns {Function} returns.showAlert - Show an alert dialog
10
+ * @returns {Function} returns.showConfirm - Show a confirmation dialog
11
+ * @returns {Function} returns.showDropdownMenu - Show a dropdown menu
12
+ */
1
13
  const createGlobalUI = (globalUIElement) => {
2
14
  let listeners = {};
3
15
 
4
16
  return {
17
+ /**
18
+ * Registers a one-time event listener for the specified event.
19
+ * The listener will be automatically removed after the first event.
20
+ *
21
+ * @param {string} event - The event name to listen for
22
+ * @param {Function} callback - The callback function to execute
23
+ * @returns {void}
24
+ */
5
25
  once: (event, callback) => {
6
26
  if (!listeners[event]) {
7
27
  listeners[event] = [];
@@ -12,6 +32,14 @@ const createGlobalUI = (globalUIElement) => {
12
32
  }
13
33
  listeners[event].push(onceCallback);
14
34
  },
35
+
36
+ /**
37
+ * Emits an event to all registered listeners for the specified event type.
38
+ *
39
+ * @param {string} event - The event name to emit
40
+ * @param {...any} args - Arguments to pass to the event listeners
41
+ * @returns {void}
42
+ */
15
43
  emit: (event, ...args) => {
16
44
  if (listeners[event]) {
17
45
  listeners[event].forEach(callback => {
@@ -19,6 +47,19 @@ const createGlobalUI = (globalUIElement) => {
19
47
  });
20
48
  }
21
49
  },
50
+
51
+ /**
52
+ * Shows an alert dialog with the specified options.
53
+ * The alert displays a message with a single OK button.
54
+ *
55
+ * @param {Object} options - Alert configuration options
56
+ * @param {string} options.message - The alert message (required)
57
+ * @param {string} [options.title] - Optional alert title
58
+ * @param {('info'|'warning'|'error')} [options.status] - Optional status type
59
+ * @param {string} [options.confirmText] - Text for the confirm button (default: "OK")
60
+ * @returns {Promise<void>} Promise that resolves when the alert is closed
61
+ * @throws {Error} If globalUIElement is not initialized
62
+ */
22
63
  showAlert: async (options) => {
23
64
  if(!globalUIElement)
24
65
  {
@@ -26,12 +67,48 @@ const createGlobalUI = (globalUIElement) => {
26
67
  }
27
68
  globalUIElement.transformedHandlers.showAlert(options);
28
69
  },
70
+
71
+ /**
72
+ * Shows a confirmation dialog with the specified options.
73
+ * The dialog displays a message with confirm and cancel buttons.
74
+ *
75
+ * @param {Object} options - Confirmation dialog configuration options
76
+ * @param {string} options.message - The confirmation message (required)
77
+ * @param {string} [options.title] - Optional dialog title
78
+ * @param {('info'|'warning'|'error')} [options.status] - Optional status type
79
+ * @param {string} [options.confirmText] - Text for the confirm button (default: "Yes")
80
+ * @param {string} [options.cancelText] - Text for the cancel button (default: "Cancel")
81
+ * @returns {Promise<boolean>} Promise that resolves to true if confirmed, false if cancelled
82
+ * @throws {Error} If globalUIElement is not initialized
83
+ */
29
84
  showConfirm: async (options) => {
30
85
  if(!globalUIElement)
31
86
  {
32
87
  throw new Error("globalUIElement is not set. Make sure to initialize the global UI component and pass it to createGlobalUIManager.");
33
88
  }
34
89
  return globalUIElement.transformedHandlers.showConfirm(options);
90
+ },
91
+
92
+ /**
93
+ * Shows a dropdown menu at the specified position with the given items.
94
+ * The dropdown can contain various item types including labels, items, and separators.
95
+ *
96
+ * @param {Object} options - Dropdown menu configuration options
97
+ * @param {Array<Object>} options.items - Array of dropdown menu items (required)
98
+ * @param {number} options.x - X coordinate position (required)
99
+ * @param {number} options.y - Y coordinate position (required)
100
+ * @param {string} [options.placement] - Dropdown menu placement (default: "bottom-start")
101
+ * @returns {Promise<Object|null>} Promise that resolves with clicked item info or null if closed without selection
102
+ * @returns {Object} [result.index] - Index of the clicked item
103
+ * @returns {Object} [result.item] - The clicked item object
104
+ * @throws {Error} If globalUIElement is not initialized
105
+ */
106
+ showDropdownMenu: async (options) => {
107
+ if(!globalUIElement)
108
+ {
109
+ throw new Error("globalUIElement is not set. Make sure to initialize the global UI component and pass it to createGlobalUIManager.");
110
+ }
111
+ return globalUIElement.transformedHandlers.showDropdownMenu(options);
35
112
  }
36
113
  };
37
114
  }