@rettangoli/ui 0.1.2-rc27 → 0.1.2-rc29

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.2-rc27",
3
+ "version": "0.1.2-rc29",
4
4
  "description": "A UI component library for building web interfaces.",
5
5
  "main": "dist/rettangoli-esm.min.js",
6
6
  "type": "module",
@@ -186,3 +186,42 @@ export const handleWaveformClick = (e, deps) => {
186
186
  }),
187
187
  );
188
188
  };
189
+
190
+ export const handleSelectAddOption = (e, deps) => {
191
+ const { store, dispatchEvent } = deps;
192
+ const name = e.currentTarget.id.replace("select-", "");
193
+ dispatchEvent(
194
+ new CustomEvent("action-click", {
195
+ detail: {
196
+ actionId: 'select-options-add',
197
+ name: name,
198
+ formValues: store.selectFormValues(),
199
+ },
200
+ }),
201
+ );
202
+ };
203
+
204
+ export const handleTooltipMouseEnter = (e, deps) => {
205
+ const { store, render, props } = deps;
206
+ const fieldName = e.currentTarget.id.replace('tooltip-icon-', '');
207
+
208
+ // Find the field with matching name to get tooltip content
209
+ const form = props.form;
210
+ const field = form.fields.find(f => f.name === fieldName);
211
+
212
+ if (field && field.tooltip) {
213
+ const rect = e.currentTarget.getBoundingClientRect();
214
+ store.showTooltip({
215
+ x: rect.left + rect.width / 2,
216
+ y: rect.top - 8,
217
+ content: field.tooltip.content
218
+ });
219
+ render();
220
+ }
221
+ };
222
+
223
+ export const handleTooltipMouseLeave = (e, deps) => {
224
+ const { store, render } = deps;
225
+ store.hideTooltip();
226
+ render();
227
+ };
@@ -26,6 +26,12 @@ function pick(obj, keys) {
26
26
 
27
27
  export const INITIAL_STATE = Object.freeze({
28
28
  formValues: {},
29
+ tooltipState: {
30
+ open: false,
31
+ x: 0,
32
+ y: 0,
33
+ content: ''
34
+ },
29
35
  });
30
36
 
31
37
  // Lodash-like utility functions for nested property access
@@ -140,6 +146,7 @@ export const toViewData = ({ state, props, attrs }) => {
140
146
  buttons: [],
141
147
  },
142
148
  formValues: state.formValues,
149
+ tooltipState: state.tooltipState,
143
150
  };
144
151
  };
145
152
 
@@ -174,3 +181,19 @@ export const setFormFieldValue = (state, { name, value, props }) => {
174
181
  );
175
182
  state.formValues = formValues;
176
183
  };
184
+
185
+ export const showTooltip = (state, { x, y, content }) => {
186
+ state.tooltipState = {
187
+ open: true,
188
+ x: x,
189
+ y: y,
190
+ content: content
191
+ };
192
+ };
193
+
194
+ export const hideTooltip = (state) => {
195
+ state.tooltipState = {
196
+ ...state.tooltipState,
197
+ open: false
198
+ };
199
+ };
@@ -33,6 +33,11 @@ propsSchema:
33
33
  const: inputText
34
34
  placeholder:
35
35
  type: string
36
+ tooltip:
37
+ type: object
38
+ properties:
39
+ content:
40
+ type: string
36
41
  required:
37
42
  - name
38
43
  - label
@@ -52,6 +57,11 @@ propsSchema:
52
57
  type: string
53
58
  noClear:
54
59
  type: boolean
60
+ addOption:
61
+ type: object
62
+ properties:
63
+ label:
64
+ type: string
55
65
  options:
56
66
  type: array
57
67
  items:
@@ -64,6 +74,11 @@ propsSchema:
64
74
  required:
65
75
  - label
66
76
  - value
77
+ tooltip:
78
+ type: object
79
+ properties:
80
+ content:
81
+ type: string
67
82
  required:
68
83
  - name
69
84
  - label
@@ -82,6 +97,11 @@ propsSchema:
82
97
  const: colorPicker
83
98
  value:
84
99
  type: string
100
+ tooltip:
101
+ type: object
102
+ properties:
103
+ content:
104
+ type: string
85
105
  required:
86
106
  - name
87
107
  - label
@@ -105,6 +125,11 @@ propsSchema:
105
125
  type: number
106
126
  value:
107
127
  type: number
128
+ tooltip:
129
+ type: object
130
+ properties:
131
+ content:
132
+ type: string
108
133
  required:
109
134
  - name
110
135
  - label
@@ -128,6 +153,11 @@ propsSchema:
128
153
  type: number
129
154
  value:
130
155
  type: number
156
+ tooltip:
157
+ type: object
158
+ properties:
159
+ content:
160
+ type: string
131
161
  required:
132
162
  - name
133
163
  - label
@@ -149,6 +179,11 @@ propsSchema:
149
179
  type: number
150
180
  placeholder:
151
181
  type: string
182
+ tooltip:
183
+ type: object
184
+ properties:
185
+ content:
186
+ type: string
152
187
  required:
153
188
  - name
154
189
  - label
@@ -174,6 +209,33 @@ propsSchema:
174
209
  type: object
175
210
  waveformData:
176
211
  type: object
212
+ tooltip:
213
+ type: object
214
+ properties:
215
+ content:
216
+ type: string
217
+ required:
218
+ - name
219
+ - label
220
+ - inputType
221
+ additionalProperties: false
222
+ - type: object
223
+ properties:
224
+ name:
225
+ type: string
226
+ label:
227
+ type: string
228
+ description:
229
+ type: string
230
+ inputType:
231
+ const: popover-input
232
+ placeholder:
233
+ type: string
234
+ tooltip:
235
+ type: object
236
+ properties:
237
+ content:
238
+ type: string
177
239
  required:
178
240
  - name
179
241
  - label
@@ -200,6 +262,12 @@ refs:
200
262
  eventListeners:
201
263
  click:
202
264
  handler: handleActionClick
265
+ tooltip-icon-*:
266
+ eventListeners:
267
+ mouseenter:
268
+ handler: handleTooltipMouseEnter
269
+ mouseleave:
270
+ handler: handleTooltipMouseLeave
203
271
  input-*:
204
272
  eventListeners:
205
273
  input-change:
@@ -208,6 +276,8 @@ refs:
208
276
  eventListeners:
209
277
  select-change:
210
278
  handler: handleSelectChange
279
+ add-option-selected:
280
+ handler: handleSelectAddOption
211
281
  colorpicker-*:
212
282
  eventListeners:
213
283
  colorpicker-change:
@@ -240,6 +310,7 @@ refs:
240
310
  events:
241
311
  form-change: {}
242
312
  extra-event: {}
313
+ action-click: {}
243
314
 
244
315
  template:
245
316
  - rtgl-view w=f p=md g=lg ${containerAttrString}:
@@ -250,7 +321,10 @@ template:
250
321
  - $for field, i in fields:
251
322
  - rtgl-view g=md w=f:
252
323
  - rtgl-view g=sm:
253
- - rtgl-text: ${field.label}
324
+ - rtgl-view d=h g=md av=c:
325
+ - rtgl-text: ${field.label}
326
+ - $if field.tooltip:
327
+ - rtgl-svg#tooltip-icon-${field.name} svg="info" wh=16 c=mu-fg cur=help ml=xs:
254
328
  - rtgl-text s=sm c=mu-fg: ${field.description}
255
329
  - $if field.inputType == "read-only-text":
256
330
  - rtgl-text s=sm: ${field.defaultValue}
@@ -259,7 +333,7 @@ template:
259
333
  - $if field.inputType == "popover-input":
260
334
  - rtgl-popover-input#popover-input-${field.name} label="${field.label}" .defaultValue=fields[${i}].defaultValue:
261
335
  - $if field.inputType == "select":
262
- - rtgl-select#select-${field.name} key=${key} w=f .options=fields[${i}].options .placeholder=fields[${i}].placeholder .selectedValue=fields[${i}].defaultValue ?no-clear=fields[${i}].noClear:
336
+ - rtgl-select#select-${field.name} key=${key} w=f .options=fields[${i}].options .placeholder=fields[${i}].placeholder .selectedValue=fields[${i}].defaultValue ?no-clear=fields[${i}].noClear .addOption=fields[${i}].addOption:
263
337
  - $if field.inputType == "colorPicker":
264
338
  - rtgl-color-picker#colorpicker-${field.name} key=${key} value=${field.defaultValue}:
265
339
  - $if field.inputType == "slider":
@@ -282,3 +356,4 @@ template:
282
356
  - rtgl-view d=h ah=e g=sm w=f:
283
357
  - $for button, i in actions.buttons:
284
358
  - rtgl-button#action-${button.id}: ${button.content}
359
+ - rtgl-tooltip ?open=${tooltipState.open} x=${tooltipState.x} y=${tooltipState.y} placement="top" content="${tooltipState.content}":
@@ -138,3 +138,29 @@ export const handleClearClick = (e, deps) => {
138
138
 
139
139
  render();
140
140
  }
141
+
142
+ export const handleAddOptionClick = (e, deps) => {
143
+ const { store, render, dispatchEvent } = deps;
144
+
145
+ // Close the popover
146
+ store.closeOptionsPopover();
147
+
148
+ // Dispatch custom event for add option (no detail)
149
+ dispatchEvent(new CustomEvent('add-option-selected', {
150
+ bubbles: true
151
+ }));
152
+
153
+ render();
154
+ }
155
+
156
+ export const handleAddOptionMouseEnter = (e, deps) => {
157
+ const { store, render } = deps;
158
+ store.setHoveredAddOption(true);
159
+ render();
160
+ }
161
+
162
+ export const handleAddOptionMouseLeave = (e, deps) => {
163
+ const { store, render } = deps;
164
+ store.setHoveredAddOption(false);
165
+ render();
166
+ }
@@ -31,6 +31,7 @@ export const INITIAL_STATE = Object.freeze({
31
31
  },
32
32
  selectedValue: null,
33
33
  hoveredOptionId: null,
34
+ hoveredAddOption: false,
34
35
  });
35
36
 
36
37
  export const toViewData = ({ state, props, attrs }) => {
@@ -72,7 +73,10 @@ export const toViewData = ({ state, props, attrs }) => {
72
73
  selectedLabelColor: isPlaceholderLabel ? "mu-fg" : "fg",
73
74
  placeholder: props.placeholder || 'Select an option',
74
75
  hasValue: currentValue !== null && currentValue !== undefined,
75
- showClear: !attrs['no-clear'] && !props['no-clear'] && (currentValue !== null && currentValue !== undefined)
76
+ showClear: !attrs['no-clear'] && !props['no-clear'] && (currentValue !== null && currentValue !== undefined),
77
+ showAddOption: !!props.addOption,
78
+ addOptionLabel: props.addOption?.label ? `+ ${props.addOption.label}` : '+ Add',
79
+ addOptionBgc: state.hoveredAddOption ? 'ac' : ''
76
80
  };
77
81
  }
78
82
 
@@ -119,5 +123,9 @@ export const clearSelectedValue = (state) => {
119
123
  state.selectedValue = undefined;
120
124
  }
121
125
 
126
+ export const setHoveredAddOption = (state, isHovered) => {
127
+ state.hoveredAddOption = isHovered;
128
+ }
129
+
122
130
 
123
131
 
@@ -23,6 +23,11 @@ propsSchema:
23
23
  type: function
24
24
  no-clear:
25
25
  type: boolean
26
+ addOption:
27
+ type: object
28
+ properties:
29
+ label:
30
+ type: string
26
31
 
27
32
  refs:
28
33
  select-button:
@@ -45,6 +50,14 @@ refs:
45
50
  handler: handleOptionMouseEnter
46
51
  mouseleave:
47
52
  handler: handleOptionMouseLeave
53
+ option-add:
54
+ eventListeners:
55
+ click:
56
+ handler: handleAddOptionClick
57
+ mouseenter:
58
+ handler: handleAddOptionMouseEnter
59
+ mouseleave:
60
+ handler: handleAddOptionMouseLeave
48
61
 
49
62
  events: {}
50
63
 
@@ -61,3 +74,7 @@ template:
61
74
  - $for option, i in options:
62
75
  - rtgl-view#option-${i} w=f ph=lg pv=md cur=p br=md bgc=${option.bgc}:
63
76
  - rtgl-text: ${option.label}
77
+ - $if showAddOption:
78
+ - rtgl-view w=f bw=xs bc=mu-bg bt=sm:
79
+ - rtgl-view#option-add w=f ph=lg pv=md cur=p br=md bgc=${addOptionBgc}:
80
+ - rtgl-text c=ac: ${addOptionLabel}
File without changes
@@ -0,0 +1,12 @@
1
+ export const INITIAL_STATE = Object.freeze({
2
+ });
3
+
4
+ export const toViewData = ({ attrs }) => {
5
+ return {
6
+ open: !!attrs.open,
7
+ x: attrs.x || 0,
8
+ y: attrs.y || 0,
9
+ placement: attrs.placement || 'top',
10
+ content: attrs.content || ''
11
+ };
12
+ }
@@ -0,0 +1,27 @@
1
+ elementName: rtgl-tooltip
2
+
3
+ viewDataSchema:
4
+ type: object
5
+
6
+ attrsSchema:
7
+ type: object
8
+ properties:
9
+ open:
10
+ type: string
11
+ x:
12
+ type: string
13
+ y:
14
+ type: string
15
+ placement:
16
+ type: string
17
+ content:
18
+ type: string
19
+
20
+ refs:
21
+ popover:
22
+
23
+ template:
24
+ - rtgl-popover#popover ?open=${open} x=${x} y=${y} placement=${placement} no-overlay:
25
+ - rtgl-view slot=content bgc=background bc=border br=md p=sm ah=c av=c:
26
+ - rtgl-text ta=c s=sm c=foreground: ${content}
27
+
@@ -296,6 +296,16 @@ class RettangoliButtonElement extends HTMLElement {
296
296
  this._buttonElement.style.maxWidth = "";
297
297
  }
298
298
  }
299
+
300
+ // Public method to get the actual button's bounding rect
301
+ // This is needed because the host element has display: contents
302
+ getBoundingClientRect() {
303
+ if (this._buttonElement) {
304
+ return this._buttonElement.getBoundingClientRect();
305
+ }
306
+ // Fallback to host element
307
+ return super.getBoundingClientRect();
308
+ }
299
309
  }
300
310
 
301
311
  // Export factory function to maintain API compatibility
@@ -28,7 +28,7 @@ class RettangoliPopoverElement extends HTMLElement {
28
28
  outline: none;
29
29
  }
30
30
 
31
- :host([open]) .popover-overlay {
31
+ :host([open]:not([no-overlay])) .popover-overlay {
32
32
  display: block;
33
33
  }
34
34
 
@@ -36,6 +36,11 @@ class RettangoliPopoverElement extends HTMLElement {
36
36
  display: block;
37
37
  visibility: hidden;
38
38
  }
39
+
40
+ /* For no-overlay mode, make the container non-interactive */
41
+ :host([no-overlay]) .popover-container {
42
+ pointer-events: none;
43
+ }
39
44
 
40
45
  :host([open][positioned]) .popover-container {
41
46
  visibility: visible;
@@ -96,7 +101,7 @@ class RettangoliPopoverElement extends HTMLElement {
96
101
  }
97
102
 
98
103
  static get observedAttributes() {
99
- return ["open", "x", "y", "placement"];
104
+ return ["open", "x", "y", "placement", "no-overlay"];
100
105
  }
101
106
 
102
107
  connectedCallback() {