@rettangoli/ui 1.0.0-rc13 → 1.0.0-rc15

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 (37) hide show
  1. package/dist/rettangoli-iife-layout.min.js +81 -49
  2. package/dist/rettangoli-iife-ui.min.js +150 -49
  3. package/package.json +10 -5
  4. package/src/common/dimensions.js +85 -0
  5. package/src/common/responsive.js +72 -0
  6. package/src/common.js +6 -4
  7. package/src/components/dropdownMenu/dropdownMenu.schema.yaml +1 -1
  8. package/src/components/form/form.handlers.js +328 -152
  9. package/src/components/form/form.methods.js +205 -0
  10. package/src/components/form/form.schema.yaml +16 -271
  11. package/src/components/form/form.store.js +535 -95
  12. package/src/components/form/form.view.yaml +73 -52
  13. package/src/components/globalUi/globalUi.handlers.js +4 -4
  14. package/src/components/popoverInput/popoverInput.handlers.js +64 -50
  15. package/src/components/popoverInput/popoverInput.schema.yaml +3 -1
  16. package/src/components/popoverInput/popoverInput.store.js +9 -3
  17. package/src/components/popoverInput/popoverInput.view.yaml +4 -4
  18. package/src/components/select/select.handlers.js +15 -19
  19. package/src/components/select/select.schema.yaml +2 -0
  20. package/src/components/select/select.store.js +8 -6
  21. package/src/components/select/select.view.yaml +4 -4
  22. package/src/components/sliderInput/sliderInput.handlers.js +15 -1
  23. package/src/components/sliderInput/sliderInput.schema.yaml +3 -0
  24. package/src/components/sliderInput/sliderInput.store.js +2 -1
  25. package/src/components/sliderInput/sliderInput.view.yaml +2 -2
  26. package/src/components/tooltip/tooltip.schema.yaml +1 -1
  27. package/src/deps/createGlobalUI.js +4 -4
  28. package/src/entry-iife-layout.js +6 -0
  29. package/src/entry-iife-ui.js +8 -0
  30. package/src/index.js +8 -0
  31. package/src/primitives/checkbox.js +295 -0
  32. package/src/primitives/input-date.js +31 -0
  33. package/src/primitives/input-datetime.js +31 -0
  34. package/src/primitives/input-time.js +31 -0
  35. package/src/primitives/input.js +43 -1
  36. package/src/primitives/textarea.js +3 -0
  37. package/src/primitives/view.js +6 -2
@@ -16,23 +16,15 @@ refs:
16
16
  field*:
17
17
  eventListeners:
18
18
  value-input:
19
- handler: handleInputChange
19
+ handler: handleValueInput
20
20
  value-change:
21
- handler: handleInputChange
22
- add-option-click:
23
- handler: handleSelectAddOption
21
+ handler: handleValueChange
24
22
  image*:
25
23
  eventListeners:
26
24
  click:
27
25
  handler: handleImageClick
28
26
  contextmenu:
29
27
  handler: handleImageClick
30
- waveform*:
31
- eventListeners:
32
- click:
33
- handler: handleWaveformClick
34
- contextmenu:
35
- handler: handleWaveformClick
36
28
  template:
37
29
  - rtgl-view#formContainer w=f p=md g=lg ${containerAttrString}:
38
30
  - rtgl-view g=sm w=f:
@@ -41,49 +33,78 @@ template:
41
33
  - $if description:
42
34
  - rtgl-text c=mu-fg: ${description}
43
35
  - rtgl-view g=lg w=f:
44
- - $for field, i in fields:
45
- - rtgl-view g=md w=f:
46
- - $if field.label || field.description:
36
+ - $for field, i in flatFields:
37
+ - $if field._isSection:
38
+ - rtgl-view g=md w=f:
47
39
  - rtgl-view g=sm:
48
- - rtgl-view d=h g=md av=c:
49
- - $if field.label:
50
- - rtgl-text: ${field.label}
51
- - $if field.label && field.tooltip:
52
- - rtgl-svg#tooltipIcon${i} data-field-name=${field.name} svg="info" wh=16 c=mu-fg cur=help ml=xs: null
40
+ - $if field.label:
41
+ - rtgl-text s=md: ${field.label}
53
42
  - $if field.description:
54
43
  - rtgl-text s=sm c=mu-fg: ${field.description}
55
- - $if field.inputType == "read-only-text":
56
- - rtgl-text s=sm: ${field.defaultValue}
57
- - $if field.inputType == "input-text":
58
- - rtgl-input#field${i} data-field-name=${field.name} w=f min=${field.min} max=${field.max} step=${field.step} data-testid=${field.testId}: null
59
- - $if field.inputType == "input-number":
60
- - rtgl-input-number#field${i} data-field-name=${field.name} w=f data-testid=${field.testId}: null
61
- - $if field.inputType == "input-textarea":
62
- - rtgl-textarea#field${i} data-field-name=${field.name} w=f rows=${field.rows} data-testid=${field.testId}: null
63
- - $if field.inputType == "popover-input":
64
- - rtgl-popover-input#field${i} data-field-name=${field.name} label="${field.label}" data-testid=${field.testId}: null
65
- - $if field.inputType == "select":
66
- - rtgl-select#field${i} data-field-name=${field.name} key=${key} w=f :options=fields[${i}].options ?no-clear=fields[${i}].noClear :addOption=fields[${i}].addOption :selectedValue=#{field.selectedValue} :placeholder=#{field.placeholder} data-testid=${field.testId}: null
67
- - $if field.inputType == "color-picker":
68
- - rtgl-color-picker#field${i} data-field-name=${field.name} key=${key} data-testid=${field.testId}: null
69
- - $if field.inputType == "slider":
70
- - rtgl-slider#field${i} data-field-name=${field.name} key=${key} w=f min=${field.min} max=${field.max} step=${field.step} data-testid=${field.testId}: null
71
- - $if field.inputType == "slider-input":
72
- - rtgl-slider-input#field${i} data-field-name=${field.name} w=f min=${field.min} max=${field.max} step=${field.step} data-testid=${field.testId}: null
73
- - $if field.inputType == "image" && field.imageSrc:
74
- - rtgl-image#image${i} data-field-name=${field.name} src=${field.imageSrc} w=${field.width} h=${field.height} cur=pointer data-testid=${field.testId}: null
75
- - $if field.inputType == "image" && !field.imageSrc:
76
- - rtgl-view#image${i} data-field-name=${field.name} w=${field.width} h=${field.height} bc=ac bw=sm ah=c av=c cur=pointer p=md data-testid=${field.testId}:
77
- - rtgl-text c=mu-fg ta=c: ${field.placeholderText}
78
- - $if field.inputType == "waveform" && field.waveformData:
79
- - rtgl-waveform#waveform${i} data-field-name=${field.name} :waveformData=fields[${i}].waveformData w=${field.width} h=${field.height} cur=pointer data-testid=${field.testId}: null
80
- - $if field.inputType == "waveform" && !field.waveformData:
81
- - rtgl-view#waveform${i} data-field-name=${field.name} w=${field.width} h=${field.height} bc=ac bw=sm ah=c av=c cur=pointer p=md data-testid=${field.testId}:
82
- - rtgl-text c=mu-fg ta=c: ${field.placeholder}
83
- - $if field.inputType == "slot":
84
- - 'slot#fieldSlot${i} data-field-name=${field.name} name=${field.slot} style="display: contents;"': null
85
- - rtgl-view g=sm w=f:
86
- - rtgl-view d=h ah=e g=sm w=f:
87
- - $for button, i in actions.buttons:
88
- - rtgl-button#action${i} data-action-id=${button.id} data-testid=${button.testId}: ${button.content}
44
+ - $if !field._isSection:
45
+ - rtgl-view g=md w=f:
46
+ - $if field.label || field.description:
47
+ - rtgl-view g=sm:
48
+ - rtgl-view d=h g=md av=c:
49
+ - $if field.label:
50
+ - rtgl-text: ${field.label}
51
+ - $if field.required:
52
+ - rtgl-text c=fg s=sm: "*"
53
+ - $if field.tooltip:
54
+ - rtgl-svg#tooltipIcon${field._idx} data-field-name=${field.name} svg="info" wh=16 c=mu-fg cur=help ml=xs: null
55
+ - $if field.description:
56
+ - rtgl-text s=sm c=mu-fg: ${field.description}
57
+ - $if field.type == "input-text":
58
+ - rtgl-input#field${field._idx} data-field-name=${field.name} w=f type=${field._inputType} placeholder=${field.placeholder} ?disabled=${field._disabled}: null
59
+ - $if field.type == "input-date":
60
+ - rtgl-input-date#field${field._idx} data-field-name=${field.name} w=f min=${field.min} max=${field.max} placeholder=${field.placeholder} ?disabled=${field._disabled}: null
61
+ - $if field.type == "input-time":
62
+ - rtgl-input-time#field${field._idx} data-field-name=${field.name} w=f min=${field.min} max=${field.max} step=${field.step} placeholder=${field.placeholder} ?disabled=${field._disabled}: null
63
+ - $if field.type == "input-datetime":
64
+ - rtgl-input-datetime#field${field._idx} data-field-name=${field.name} w=f min=${field.min} max=${field.max} step=${field.step} placeholder=${field.placeholder} ?disabled=${field._disabled}: null
65
+ - $if field.type == "input-number":
66
+ - rtgl-input-number#field${field._idx} data-field-name=${field.name} w=f min=${field.min} max=${field.max} step=${field.step} placeholder=${field.placeholder} ?disabled=${field._disabled}: null
67
+ - $if field.type == "input-textarea":
68
+ - rtgl-textarea#field${field._idx} data-field-name=${field.name} w=f rows=${field.rows} placeholder=${field.placeholder} ?disabled=${field._disabled}: null
69
+ - $if field.type == "select":
70
+ - rtgl-select#field${field._idx} data-field-name=${field.name} w=f :options=flatFields[${field._arrIdx}].options ?no-clear=flatFields[${field._arrIdx}].noClear :selectedValue=#{field._selectedValue} :placeholder=#{field.placeholder} ?disabled=${field._disabled}: null
71
+ - $if field.type == "color-picker":
72
+ - rtgl-color-picker#field${field._idx} data-field-name=${field.name} ?disabled=${field._disabled}: null
73
+ - $if field.type == "slider":
74
+ - rtgl-slider#field${field._idx} data-field-name=${field.name} w=f min=${field.min} max=${field.max} step=${field.step} ?disabled=${field._disabled}: null
75
+ - $if field.type == "slider-with-input":
76
+ - rtgl-slider-input#field${field._idx} data-field-name=${field.name} w=f min=${field.min} max=${field.max} step=${field.step} ?disabled=${field._disabled}: null
77
+ - $if field.type == "popover-input":
78
+ - rtgl-popover-input#field${field._idx} data-field-name=${field.name} label="${field.label}" placeholder=${field.placeholder} ?disabled=${field._disabled}: null
79
+ - $if field.type == "checkbox":
80
+ - rtgl-checkbox#field${field._idx} data-field-name=${field.name} label="${field._checkboxText}" ?disabled=${field._disabled}: null
81
+ - $if field.type == "image" && field._imageSrc:
82
+ - rtgl-image#image${field._idx} data-field-name=${field.name} src=${field._imageSrc} w=${field.width} h=${field.height} cur=pointer: null
83
+ - $if field.type == "image" && !field._imageSrc:
84
+ - rtgl-view#image${field._idx} data-field-name=${field.name} w=${field.width} h=${field.height} bc=ac bw=sm ah=c av=c cur=pointer p=md:
85
+ - rtgl-text c=mu-fg ta=c: ${field.placeholderText}
86
+ - $if field.type == "read-only-text":
87
+ - rtgl-text s=sm: ${field.content}
88
+ - $if field.type == "slot":
89
+ - 'slot#fieldSlot${field._idx} name=${field.slot} style="display: contents;"': null
90
+ - $if field._error:
91
+ - rtgl-text s=sm c=de: ${field._error}
92
+ - $if actions.buttons.length > 0:
93
+ - $if actions._layout == "split":
94
+ - rtgl-view d=h w=f g=sm:
95
+ - rtgl-view d=h g=sm:
96
+ - $for button, i in actions._leftButtons:
97
+ - rtgl-button#action${button._globalIdx} data-action-id=${button.id} v=${button.variant} ?disabled=${button._disabled} pre=${button.pre} suf=${button.suf}: ${button.label}
98
+ - rtgl-view d=h g=sm ah=e w=1fg:
99
+ - $for button, i in actions._rightButtons:
100
+ - rtgl-button#action${button._globalIdx} data-action-id=${button.id} v=${button.variant} ?disabled=${button._disabled} pre=${button.pre} suf=${button.suf}: ${button.label}
101
+ - $if actions._layout == "vertical":
102
+ - rtgl-view g=sm w=f:
103
+ - $for button, i in actions._allButtons:
104
+ - rtgl-button#action${button._globalIdx} data-action-id=${button.id} v=${button.variant} w=f ?disabled=${button._disabled} pre=${button.pre} suf=${button.suf}: ${button.label}
105
+ - $if actions._layout == "stretch":
106
+ - rtgl-view d=h g=sm w=f:
107
+ - $for button, i in actions._allButtons:
108
+ - rtgl-view w=1fg:
109
+ - rtgl-button#action${button._globalIdx} data-action-id=${button.id} v=${button.variant} w=f ?disabled=${button._disabled} pre=${button.pre} suf=${button.suf}: ${button.label}
89
110
  - rtgl-tooltip ?open=${tooltipState.open} x=${tooltipState.x} y=${tooltipState.y} place="t" content="${tooltipState.content}": null
@@ -55,7 +55,7 @@ export const handleDropdownItemClick = (deps, payload) => {
55
55
  * @param {string} [payload.confirmText] - Text for the confirm button (default: "OK")
56
56
  * @returns {void}
57
57
  */
58
- export const showAlert = (deps, payload) => {
58
+ export const handleShowAlert = (deps, payload) => {
59
59
  const { store, render } = deps;
60
60
  const options = payload;
61
61
 
@@ -69,7 +69,7 @@ export const showAlert = (deps, payload) => {
69
69
  render();
70
70
  };
71
71
 
72
- export const showConfirm = async (deps, payload) => {
72
+ export const handleShowConfirm = async (deps, payload) => {
73
73
  const { store, render, globalUI } = deps;
74
74
  const options = payload;
75
75
 
@@ -107,7 +107,7 @@ export const showConfirm = async (deps, payload) => {
107
107
  * @returns {Object} [result.index] - Index of the clicked item
108
108
  * @returns {Object} [result.item] - The clicked item object
109
109
  */
110
- export const showDropdownMenu = async (deps, payload) => {
110
+ export const handleShowDropdownMenu = async (deps, payload) => {
111
111
  const { store, render, globalUI } = deps;
112
112
  const options = payload;
113
113
 
@@ -140,7 +140,7 @@ export const showDropdownMenu = async (deps, payload) => {
140
140
  * @param {Object} deps.globalUI - The globalUI event emitter
141
141
  * @returns {void}
142
142
  */
143
- export const closeAll = (deps) => {
143
+ export const handleCloseAll = (deps) => {
144
144
  const { store, render } = deps;
145
145
 
146
146
  // Close global UI dialogs/dropdowns
@@ -1,18 +1,46 @@
1
+ const normalizePopoverValue = (value) => {
2
+ if (value === undefined || value === null || value === true) {
3
+ return "";
4
+ }
5
+ return String(value);
6
+ };
7
+
8
+ const commitValue = (deps, value) => {
9
+ const { store, render, dispatchEvent } = deps;
10
+ const nextValue = normalizePopoverValue(value);
11
+
12
+ store.setValue({ value: nextValue });
13
+ store.closePopover({});
14
+
15
+ dispatchEvent(new CustomEvent("value-change", {
16
+ detail: { value: nextValue },
17
+ bubbles: true,
18
+ }));
19
+
20
+ render();
21
+ };
22
+
1
23
  export const handleBeforeMount = (deps) => {
2
24
  const { store, props } = deps;
3
25
 
4
26
  if (props.value !== undefined) {
5
- store.setValue({ value: props.value || '' });
27
+ const value = normalizePopoverValue(props.value);
28
+ store.setValue({ value });
29
+ store.setTempValue({ value });
6
30
  }
7
31
  }
8
32
 
9
33
  export const handleOnUpdate = (deps, payload) => {
10
34
  const { oldProps, newProps } = payload;
11
35
  const { store, render } = deps;
36
+ const valueChanged = oldProps?.value !== newProps?.value;
12
37
 
13
- if (oldProps?.value !== newProps?.value) {
14
- const value = newProps?.value ?? '';
38
+ if (valueChanged) {
39
+ const value = normalizePopoverValue(newProps?.value);
15
40
  store.setValue({ value });
41
+ if (!store.getState().isOpen) {
42
+ store.setTempValue({ value });
43
+ }
16
44
  }
17
45
 
18
46
  render();
@@ -20,10 +48,14 @@ export const handleOnUpdate = (deps, payload) => {
20
48
 
21
49
  export const handleTextClick = (deps, payload) => {
22
50
  const { store, render, refs, props } = deps;
51
+ if (props.disabled) {
52
+ return;
53
+ }
23
54
  const event = payload._event;
24
55
 
25
- const value = store.selectValue();
26
- store.setTempValue({ value })
56
+ const value = normalizePopoverValue(props.value);
57
+ store.setValue({ value });
58
+ store.setTempValue({ value });
27
59
 
28
60
  store.openPopover({
29
61
  position: {
@@ -31,16 +63,18 @@ export const handleTextClick = (deps, payload) => {
31
63
  y: event.currentTarget.getBoundingClientRect().bottom,
32
64
  }
33
65
  });
34
-
35
- const { input } = refs;
36
- input.value = value;
37
66
  render();
38
67
 
39
- if (props.autoFocus) {
40
- setTimeout(() => {
41
- input.focus();
42
- }, 50)
43
- }
68
+ setTimeout(() => {
69
+ const { input } = refs;
70
+ if (!input) return;
71
+ input.value = value;
72
+ input.focus();
73
+ const innerInput = input.shadowRoot?.querySelector("input, textarea");
74
+ if (innerInput && typeof innerInput.focus === "function") {
75
+ innerInput.focus();
76
+ }
77
+ }, 50);
44
78
  }
45
79
 
46
80
  export const handlePopoverClose = (deps, payload) => {
@@ -50,54 +84,34 @@ export const handlePopoverClose = (deps, payload) => {
50
84
  }
51
85
 
52
86
  export const handleInputChange = (deps, payload) => {
53
- const { store, render, dispatchEvent } = deps;
87
+ const { store } = deps;
54
88
  const event = payload._event;
55
- const value = event.detail.value;
89
+ const value = normalizePopoverValue(event.detail.value);
56
90
 
57
91
  store.setTempValue({ value });
58
-
59
- dispatchEvent(new CustomEvent('value-input', {
60
- detail: { value },
61
- bubbles: true,
62
- }));
63
-
64
- render();
65
92
  }
66
93
 
67
94
  export const handleSubmitClick = (deps) => {
68
- const { store, render, dispatchEvent, refs } = deps;
69
- const { input } = refs
70
- const value = input.value;
71
-
72
- store.setValue({ value });
73
- store.closePopover({});
74
-
75
- dispatchEvent(new CustomEvent('value-change', {
76
- detail: { value },
77
- bubbles: true,
78
- }));
79
-
80
- render();
95
+ const { store, refs } = deps;
96
+ const { input } = refs;
97
+ const value = input ? input.value : store.getState().tempValue;
98
+ commitValue(deps, value);
81
99
  }
82
100
 
83
101
  export const handleInputKeydown = (deps, payload) => {
84
- const { store, render, dispatchEvent, refs } = deps;
102
+ const { store, refs } = deps;
85
103
  const event = payload._event;
86
104
 
87
- if (event.key === 'Enter') {
88
- const { input } = refs
89
- const value = input.value;
90
-
91
- store.closePopover({});
92
- // Dispatch custom event
93
- dispatchEvent(new CustomEvent('value-change', {
94
- detail: { value },
95
- bubbles: true,
96
- }));
97
-
98
- render();
99
- } else if (event.key === 'Escape') {
105
+ if (event.key === "Enter") {
106
+ event.preventDefault();
107
+ event.stopPropagation();
108
+ const { input } = refs;
109
+ const value = input ? input.value : store.getState().tempValue;
110
+ commitValue(deps, value);
111
+ } else if (event.key === "Escape") {
112
+ event.preventDefault();
113
+ event.stopPropagation();
100
114
  store.closePopover({});
101
- render();
115
+ deps.render();
102
116
  }
103
117
  }
@@ -10,8 +10,10 @@ propsSchema:
10
10
  type: string
11
11
  autoFocus:
12
12
  type: boolean
13
+ disabled:
14
+ type: boolean
15
+ default: false
13
16
  events:
14
- value-input: {}
15
17
  value-change: {}
16
18
  methods:
17
19
  type: object
@@ -9,15 +9,21 @@ export const createInitialState = () => Object.freeze({
9
9
  });
10
10
 
11
11
  export const selectViewData = ({ props, state }) => {
12
- const value = state.value || '-';
12
+ const hasValue = typeof state.value === "string" && state.value.length > 0;
13
+ const value = hasValue ? state.value : "-";
14
+ const placeholder = typeof props.placeholder === "string" ? props.placeholder : "";
15
+ const label = typeof props.label === "string" ? props.label : "";
16
+ const disabled = Boolean(props.disabled);
13
17
 
14
18
  return {
15
19
  isOpen: state.isOpen,
16
20
  position: state.position,
17
21
  value: value,
22
+ valueColor: hasValue ? "fg" : "mu-fg",
18
23
  tempValue: state.tempValue,
19
- placeholder: props.placeholder ?? '',
20
- label: props.label,
24
+ placeholder,
25
+ label,
26
+ disabled,
21
27
  };
22
28
  }
23
29
 
@@ -19,10 +19,10 @@ refs:
19
19
  handler: handleSubmitClick
20
20
  template:
21
21
  - rtgl-view#textDisplay w=f cur=pointer:
22
- - rtgl-text: ${value}
22
+ - rtgl-text c=${valueColor}: ${value}
23
23
  - rtgl-popover#popover ?open=${isOpen} x=${position.x} y=${position.y}:
24
- - rtgl-view g=md w=240 slot=content bgc=bg br=md:
24
+ - rtgl-view g=md w=240 slot=content bgc=mu br=md:
25
25
  - rtgl-text: ${label}
26
- - rtgl-input#input w=f placeholder=${placeholder}: null
26
+ - rtgl-input#input w=f placeholder=${placeholder} ?disabled=${disabled}: null
27
27
  - rtgl-view w=f ah=e:
28
- - rtgl-button#submit: Submit
28
+ - rtgl-button#submit ?disabled=${disabled}: Submit
@@ -17,33 +17,26 @@ export const handleBeforeMount = (deps) => {
17
17
  export const handleOnUpdate = (deps, payload) => {
18
18
  const { oldProps, newProps } = payload;
19
19
  const { store, render } = deps;
20
+ let shouldRender = false;
20
21
 
21
- // Check if key changed
22
- if (oldProps?.key !== newProps?.key && newProps?.key) {
23
- // Clear current state using store action
24
- store.resetSelection({});
25
-
26
- // Re-apply the prop value if available
27
- const selectedValue = newProps?.selectedValue;
28
- const options = newProps?.options || [];
29
-
30
- if (selectedValue !== null && selectedValue !== undefined && options) {
31
- const selectedOption = options.find(opt => deepEqual(opt.value, selectedValue));
32
- if (selectedOption) {
33
- store.updateSelectedValue({
34
- value: selectedOption.value
35
- });
36
- }
37
- }
38
- render();
39
- } else if (oldProps.selectedValue !== newProps.selectedValue) {
22
+ if (!!newProps?.disabled && !oldProps?.disabled) {
23
+ store.closeOptionsPopover({});
24
+ shouldRender = true;
25
+ }
26
+
27
+ if (oldProps.selectedValue !== newProps.selectedValue) {
40
28
  store.updateSelectedValue({ value: newProps.selectedValue });
29
+ shouldRender = true;
30
+ }
31
+
32
+ if (shouldRender) {
41
33
  render();
42
34
  }
43
35
  }
44
36
 
45
37
  export const handleButtonClick = (deps, payload) => {
46
38
  const { store, render, refs, props } = deps;
39
+ if (props.disabled) return;
47
40
  const event = payload._event;
48
41
  event.stopPropagation();
49
42
 
@@ -80,6 +73,7 @@ export const handleClickOptionsPopoverOverlay = (deps) => {
80
73
 
81
74
  export const handleOptionClick = (deps, payload) => {
82
75
  const { render, dispatchEvent, props, store } = deps;
76
+ if (props.disabled) return;
83
77
  const event = payload._event;
84
78
  event.stopPropagation();
85
79
  const id = event.currentTarget.id.slice('option'.length);
@@ -124,6 +118,7 @@ export const handleOptionMouseLeave = (deps, payload) => {
124
118
 
125
119
  export const handleClearClick = (deps, payload) => {
126
120
  const { store, render, dispatchEvent, props } = deps;
121
+ if (props.disabled) return;
127
122
  const event = payload._event;
128
123
 
129
124
  event.stopPropagation();
@@ -150,6 +145,7 @@ export const handleClearClick = (deps, payload) => {
150
145
  }
151
146
 
152
147
  export const handleAddOptionClick = (deps, payload) => {
148
+ if (deps.props.disabled) return;
153
149
  const { store, render, dispatchEvent } = deps;
154
150
  const { _event: event } = payload;
155
151
  event.stopPropagation();
@@ -26,6 +26,8 @@ propsSchema:
26
26
  properties:
27
27
  label:
28
28
  type: string
29
+ disabled:
30
+ type: boolean
29
31
  w:
30
32
  type: string
31
33
  events:
@@ -14,6 +14,7 @@ const blacklistedProps = [
14
14
  "options",
15
15
  "noClear",
16
16
  "addOption",
17
+ "disabled",
17
18
  ];
18
19
 
19
20
  const stringifyProps = (props = {}) => {
@@ -37,9 +38,11 @@ export const createInitialState = () => Object.freeze({
37
38
  export const selectViewData = ({ state, props }) => {
38
39
  // Generate container attribute string
39
40
  const containerAttrString = stringifyProps(props);
41
+ const isDisabled = !!props.disabled;
40
42
 
41
- // Use state's selected value if available, otherwise use props.selectedValue
42
- const currentValue = state.selectedValue !== null ? state.selectedValue : props.selectedValue;
43
+ // Treat selectedValue as a controlled prop when provided by parent.
44
+ const hasControlledValue = Object.prototype.hasOwnProperty.call(props || {}, "selectedValue");
45
+ const currentValue = hasControlledValue ? props.selectedValue : state.selectedValue;
43
46
 
44
47
  // Calculate display label from value
45
48
  let displayLabel = props.placeholder || "Select an option";
@@ -65,6 +68,7 @@ export const selectViewData = ({ state, props }) => {
65
68
 
66
69
  return {
67
70
  containerAttrString,
71
+ isDisabled,
68
72
  isOpen: state.isOpen,
69
73
  position: state.position,
70
74
  options: optionsWithSelection,
@@ -72,8 +76,8 @@ export const selectViewData = ({ state, props }) => {
72
76
  selectedLabel: displayLabel,
73
77
  selectedLabelColor: isPlaceholderLabel ? "mu-fg" : "fg",
74
78
  hasValue: currentValue !== null && currentValue !== undefined,
75
- showClear: !props.noClear && (currentValue !== null && currentValue !== undefined),
76
- showAddOption: !!props.addOption,
79
+ showClear: !isDisabled && !props.noClear && (currentValue !== null && currentValue !== undefined),
80
+ showAddOption: !isDisabled && !!props.addOption,
77
81
  addOptionLabel: props.addOption?.label ? `+ ${props.addOption.label}` : "+ Add",
78
82
  addOptionBgc: state.hoveredAddOption ? "ac" : "",
79
83
  };
@@ -125,5 +129,3 @@ export const clearSelectedValue = ({ state }) => {
125
129
  export const setHoveredAddOption = ({ state }, payload = {}) => {
126
130
  state.hoveredAddOption = !!payload.isHovered;
127
131
  };
128
-
129
-
@@ -28,9 +28,9 @@ refs:
28
28
  mouseleave:
29
29
  handler: handleAddOptionMouseLeave
30
30
  template:
31
- - rtgl-button#selectButton v=ol ${containerAttrString} data-testid="select-button":
32
- - rtgl-view d=h av=c w=f:
33
- - rtgl-text w=1fg c=${selectedLabelColor} ellipsis: ${selectedLabel}
31
+ - rtgl-button#selectButton v=ol ${containerAttrString} ?disabled=${isDisabled} data-testid="select-button":
32
+ - rtgl-view d=h av=c ah=s w=f:
33
+ - rtgl-text w=1fg ta=s c=${selectedLabelColor} ellipsis: ${selectedLabel}
34
34
  - $if showClear:
35
35
  - rtgl-svg#clearButton ml=md svg=x wh=16 c=mu-fg cur=pointer data-testid="select-clear-button": null
36
36
  - rtgl-svg ml=md svg=chevronDown wh=16 c=mu-fg: null
@@ -38,7 +38,7 @@ template:
38
38
  - rtgl-view wh=300 g=xs slot=content bgc=mu br=md sv=true:
39
39
  - $for option, i in options:
40
40
  - rtgl-view#option${i} w=f ph=lg pv=md cur=pointer br=md bgc=${option.bgc} data-testid=${option.testId}:
41
- - rtgl-text: ${option.label}
41
+ - rtgl-text ta=s: ${option.label}
42
42
  - $if showAddOption:
43
43
  - rtgl-view w=f bw=xs bc=mu bwt=sm: null
44
44
  - rtgl-view#optionAdd w=f ph=lg pv=md cur=pointer br=md bgc=${addOptionBgc} data-testid="select-add-option":
@@ -6,8 +6,10 @@ export const handleBeforeMount = (deps) => {
6
6
  export const handleOnUpdate = (deps, payload) => {
7
7
  const { oldProps, newProps } = payload;
8
8
  const { store, render } = deps;
9
+ const keyChanged = oldProps?.key !== newProps?.key;
10
+ const valueChanged = oldProps?.value !== newProps?.value;
9
11
 
10
- if (oldProps?.value !== newProps?.value) {
12
+ if (keyChanged || valueChanged) {
11
13
  const value = newProps?.value ?? 0;
12
14
  store.setValue({ value });
13
15
  render();
@@ -18,8 +20,14 @@ export const handleValueChange = (deps, payload) => {
18
20
  const { store, render, dispatchEvent } = deps;
19
21
  const event = payload._event;
20
22
  const newValue = Number(event.detail.value);
23
+ const path = typeof event.composedPath === "function" ? event.composedPath() : [];
24
+ const host = path.find((node) => node?.tagName === "RTGL-SLIDER-INPUT")
25
+ || event.currentTarget?.getRootNode?.()?.host;
21
26
 
22
27
  store.setValue({ value: newValue });
28
+ if (host && typeof host.setAttribute === "function") {
29
+ host.setAttribute("value", String(newValue));
30
+ }
23
31
 
24
32
  // Re-render to sync slider and input
25
33
  render();
@@ -39,8 +47,14 @@ export const handleValueInput = (deps, payload) => {
39
47
  const { store, render, dispatchEvent } = deps;
40
48
  const event = payload._event;
41
49
  const newValue = Number(event.detail.value);
50
+ const path = typeof event.composedPath === "function" ? event.composedPath() : [];
51
+ const host = path.find((node) => node?.tagName === "RTGL-SLIDER-INPUT")
52
+ || event.currentTarget?.getRootNode?.()?.host;
42
53
 
43
54
  store.setValue({ value: newValue });
55
+ if (host && typeof host.setAttribute === "function") {
56
+ host.setAttribute("value", String(newValue));
57
+ }
44
58
 
45
59
  // Re-render to sync slider and input
46
60
  render();
@@ -19,6 +19,9 @@ propsSchema:
19
19
  step:
20
20
  type: string
21
21
  default: '1'
22
+ disabled:
23
+ type: boolean
24
+ default: false
22
25
  events:
23
26
  value-input: {}
24
27
  value-change: {}
@@ -9,7 +9,8 @@ export const selectViewData = ({ state, props }) => {
9
9
  w: props.w || '',
10
10
  min: props.min || 0,
11
11
  max: props.max || 100,
12
- step: props.step || 1
12
+ step: props.step || 1,
13
+ disabled: Boolean(props.disabled),
13
14
  };
14
15
  }
15
16
 
@@ -13,6 +13,6 @@ refs:
13
13
  handler: handleValueInput
14
14
  template:
15
15
  - rtgl-view d=h av=c g=md w=${w}:
16
- - rtgl-slider#slider key=${key} w=f type=range min=${min} max=${max} step=${step} value=${value}: null
16
+ - rtgl-slider#slider key=${key} w=f type=range min=${min} max=${max} step=${step} value=${value} ?disabled=${disabled}: null
17
17
  - rtgl-view w=84:
18
- - rtgl-input#input key=${key} w=f type=number min=${min} max=${max} step=${step} value=${value}: null
18
+ - rtgl-input-number#input key=${key} w=f min=${min} max=${max} step=${step} value=${value} ?disabled=${disabled}: null
@@ -3,7 +3,7 @@ propsSchema:
3
3
  type: object
4
4
  properties:
5
5
  open:
6
- type: string
6
+ type: boolean
7
7
  x:
8
8
  type: string
9
9
  'y':