@rettangoli/ui 0.1.2-rc15 → 0.1.2-rc16

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-rc15",
3
+ "version": "0.1.2-rc16",
4
4
  "description": "A UI component library for building web interfaces.",
5
5
  "main": "dist/rettangoli-esm.min.js",
6
6
  "type": "module",
@@ -3,6 +3,18 @@ export const handleBeforeMount = (deps) => {
3
3
  store.setFormValues(props.defaultValues);
4
4
  };
5
5
 
6
+ export const handleOnUpdate = (changes, deps) => {
7
+ const { oldAttrs, newAttrs } = changes
8
+ const { store, props, render } = deps;
9
+
10
+ if (oldAttrs?.key === newAttrs?.key) {
11
+ return;
12
+ }
13
+
14
+ store.setFormValues(props.defaultValues);
15
+ render();
16
+ };
17
+
6
18
  const dispatchFormChange = (name, fieldValue, formValues, dispatchEvent) => {
7
19
  dispatchEvent(
8
20
  new CustomEvent("form-change", {
@@ -41,6 +53,19 @@ export const handleInputChange = (e, deps) => {
41
53
  }
42
54
  };
43
55
 
56
+ export const handlePopoverInputChange = (e, deps) => {
57
+ const { store, dispatchEvent } = deps;
58
+ const name = e.currentTarget.id.replace("popover-input-", "");
59
+ // TODO fix double event
60
+ if (name && e.detail.value !== undefined) {
61
+ store.setFormFieldValue({
62
+ name: name,
63
+ value: e.detail.value,
64
+ });
65
+ dispatchFormChange(name, e.detail.value, store.selectFormValues(), dispatchEvent);
66
+ }
67
+ };
68
+
44
69
  export const handleSelectChange = (e, deps) => {
45
70
  const { store, dispatchEvent } = deps;
46
71
  const name = e.currentTarget.id.replace("select-", "");
@@ -100,3 +125,15 @@ export const handleImageClick = (e, deps) => {
100
125
  }),
101
126
  );
102
127
  };
128
+
129
+ export const handleWaveformClick = (e, deps) => {
130
+ const { dispatchEvent } = deps;
131
+ const name = e.currentTarget.id.replace("waveform-", "");
132
+ dispatchEvent(
133
+ new CustomEvent("extra-event", {
134
+ detail: {
135
+ name: name
136
+ },
137
+ }),
138
+ );
139
+ };
@@ -13,7 +13,7 @@ export const toViewData = ({ state, props, attrs }) => {
13
13
  const fields = structuredClone(props.form.fields || []);
14
14
  const defaultValues = props.defaultValues || {};
15
15
  const fieldResources = props.fieldResources || {};
16
-
16
+
17
17
  fields.forEach((field) => {
18
18
  field.defaultValue = defaultValues[field.name];
19
19
  if (field.inputType === 'image') {
@@ -23,6 +23,13 @@ export const toViewData = ({ state, props, attrs }) => {
23
23
  // Set placeholder text
24
24
  field.placeholderText = field.placeholder || 'No Image';
25
25
  }
26
+ if (field.inputType === 'waveform') {
27
+ const waveformData = fieldResources[field.name]?.waveformData;
28
+ // Only set waveformData if it exists
29
+ field.waveformData = waveformData || null;
30
+ // Set placeholder text
31
+ field.placeholderText = field.placeholder || 'No Waveform';
32
+ }
26
33
  })
27
34
 
28
35
  return {
@@ -153,6 +153,27 @@ propsSchema:
153
153
  - label
154
154
  - inputType
155
155
  additionalProperties: false
156
+ - type: object
157
+ properties:
158
+ name:
159
+ type: string
160
+ label:
161
+ type: string
162
+ description:
163
+ type: string
164
+ inputType:
165
+ const: waveform
166
+ width:
167
+ type: number
168
+ height:
169
+ type: number
170
+ placeholder:
171
+ type: string
172
+ required:
173
+ - name
174
+ - label
175
+ - inputType
176
+ additionalProperties: false
156
177
  actions:
157
178
  type: object
158
179
  properties:
@@ -200,6 +221,14 @@ refs:
200
221
  eventListeners:
201
222
  click:
202
223
  handler: handleImageClick
224
+ waveform-*:
225
+ eventListeners:
226
+ click:
227
+ handler: handleWaveformClick
228
+ popover-input-*:
229
+ eventListeners:
230
+ input-change:
231
+ handler: handlePopoverInputChange
203
232
 
204
233
  events:
205
234
  form-change: {}
@@ -216,8 +245,12 @@ template:
216
245
  - rtgl-view g=sm:
217
246
  - rtgl-text: ${field.label}
218
247
  - rtgl-text s=sm c=mu-fg: ${field.description}
248
+ - $if field.inputType == "read-only-text":
249
+ - rtgl-text s=sm: ${field.defaultValue}
219
250
  - $if field.inputType == "inputText":
220
251
  - rtgl-input#input-${field.name} w=f placeholder=${field.placeholder} value=${field.defaultValue}:
252
+ - $if field.inputType == "popover-input":
253
+ - rtgl-popover-input#popover-input-${field.name} label="${field.label}" .defaultValue=fields[${i}].defaultValue:
221
254
  - $if field.inputType == "select":
222
255
  - rtgl-select#select-${field.name} w=f .options=fields[${i}].options .placeholder=fields[${i}].placeholder .selectedValue=fields[${i}].defaultValue:
223
256
  - $if field.inputType == "colorPicker":
@@ -231,6 +264,11 @@ template:
231
264
  - $if field.inputType == "image" && !field.imageSrc:
232
265
  - rtgl-view#image-${field.name} w=${field.width} h=${field.height} bc=ac bw=sm ah=c av=c cur=p p=md:
233
266
  - rtgl-text c=mu-fg ta=c: ${field.placeholderText}
267
+ - $if field.inputType == "waveform" && field.waveformData:
268
+ - rtgl-waveform#waveform-${field.name} .waveformData=fields[${i}].waveformData w=${field.width} h=${field.height}:
269
+ - $if field.inputType == "waveform" && !field.waveformData:
270
+ - rtgl-view#waveform-${field.name} w=${field.width} h=${field.height} bc=ac bw=sm ah=c av=c cur=p p=md:
271
+ - rtgl-text c=mu-fg ta=c: ${field.placeholderText}
234
272
  - rtgl-view g=sm w=f:
235
273
  - rtgl-view d=h ah=e g=sm w=f:
236
274
  - $for button, i in actions.buttons:
@@ -0,0 +1,99 @@
1
+ export const handleBeforeMount = (deps) => {
2
+ const { store, props } = deps;
3
+
4
+ if (props.value !== undefined || props.defaultValue !== undefined) {
5
+ store.setValue(props.value || props.defaultValue || '');
6
+ }
7
+ }
8
+
9
+ export const handleOnUpdate = (changes, deps) => {
10
+ const { oldProps, newProps} = changes
11
+ const { store, props, render } = deps;
12
+
13
+ if (oldProps.defaultValue !== newProps.defaultValue) {
14
+ store.setValue(props.defaultValue || '');
15
+ }
16
+
17
+ render();
18
+ }
19
+
20
+ export const handleTextClick = (e, deps) => {
21
+ const { store, render, getRefIds, attrs } = deps;
22
+
23
+ const value = store.selectValue();
24
+ store.setTempValue(value)
25
+
26
+ store.openPopover({
27
+ position: {
28
+ x: e.currentTarget.getBoundingClientRect().left,
29
+ y: e.currentTarget.getBoundingClientRect().bottom,
30
+ }
31
+ });
32
+
33
+ const { input } = getRefIds();
34
+ input.elm.value = value;
35
+ render();
36
+
37
+ if (attrs['auto-focus']) {
38
+ setTimeout(() => {
39
+ input.elm.focus();
40
+ }, 50)
41
+ }
42
+ }
43
+
44
+ export const handlePopoverClose = (e, deps) => {
45
+ const { store, render } = deps;
46
+ store.closePopover();
47
+ render();
48
+ }
49
+
50
+ export const handleInputChange = (e, deps) => {
51
+ const { store, render, dispatchEvent } = deps;
52
+ const value = e.detail.value;
53
+
54
+ store.setTempValue(value);
55
+
56
+ dispatchEvent(new CustomEvent('temp-input-change', {
57
+ detail: { value },
58
+ bubbles: true
59
+ }));
60
+
61
+ render();
62
+ }
63
+
64
+ export const handleSubmitClick = (e, deps) => {
65
+ const { store, render, dispatchEvent, getRefIds } = deps;
66
+ const { input } = getRefIds()
67
+ const value = input.elm.value;
68
+
69
+ store.setValue(value)
70
+ store.closePopover();
71
+
72
+ dispatchEvent(new CustomEvent('input-change', {
73
+ detail: { value },
74
+ bubbles: true
75
+ }));
76
+
77
+ render();
78
+ }
79
+
80
+ export const handleInputKeydown = (e, deps) => {
81
+ const { store, render, dispatchEvent, getRefIds } = deps;
82
+
83
+ if (e.key === 'Enter') {
84
+ const { input } = getRefIds()
85
+ const value = input.elm.value;
86
+
87
+ store.closePopover();
88
+ // Dispatch custom event
89
+ dispatchEvent(new CustomEvent('input-change', {
90
+ detail: { value },
91
+ bubbles: true
92
+ }));
93
+
94
+ render();
95
+ } else if (e.key === 'Escape') {
96
+ store.closePopover();
97
+ render();
98
+ }
99
+ }
@@ -0,0 +1,48 @@
1
+ export const INITIAL_STATE = Object.freeze({
2
+ isOpen: false,
3
+ position: {
4
+ x: 0,
5
+ y: 0,
6
+ },
7
+ value: '',
8
+ tempValue: '',
9
+ });
10
+
11
+ export const toViewData = ({ attrs, state, props }) => {
12
+ // Use state's current value if it has been modified, otherwise use props
13
+ const value = state.value || '-';
14
+
15
+ return {
16
+ isOpen: state.isOpen,
17
+ position: state.position,
18
+ value: value ?? '-',
19
+ tempValue: state.tempValue,
20
+ placeholder: props.placeholder ?? '',
21
+ label: attrs.label,
22
+ };
23
+ }
24
+
25
+ export const setTempValue = (state, value) => {
26
+ state.tempValue = value;
27
+ }
28
+
29
+ export const openPopover = (state, payload) => {
30
+ const { position } = payload;
31
+ state.position = position;
32
+ state.isOpen = true;
33
+ // Reset the current value to match the display value when opening
34
+ state.hasUnsavedChanges = false;
35
+ }
36
+
37
+ export const closePopover = (state) => {
38
+ state.isOpen = false;
39
+ state.tempValue = '';
40
+ }
41
+
42
+ export const setValue = (state, value) => {
43
+ state.value = value;
44
+ }
45
+
46
+ export const selectValue = ({ state }) => {
47
+ return state.value;
48
+ }
@@ -0,0 +1,55 @@
1
+ elementName: rtgl-popover-input
2
+
3
+ viewDataSchema:
4
+ type: object
5
+
6
+ attrsSchema:
7
+ type: object
8
+ properties:
9
+ auto-focus:
10
+ type: boolean
11
+
12
+ propsSchema:
13
+ type: object
14
+ properties:
15
+ value:
16
+ type: string
17
+ defaultValue:
18
+ type: string
19
+ placeholder:
20
+ type: string
21
+ onChange:
22
+ type: function
23
+
24
+ refs:
25
+ text-display:
26
+ eventListeners:
27
+ click:
28
+ handler: handleTextClick
29
+ popover:
30
+ eventListeners:
31
+ close:
32
+ handler: handlePopoverClose
33
+ input:
34
+ eventListeners:
35
+ input-change:
36
+ handler: handleInputChange
37
+ keydown:
38
+ handler: handleInputKeydown
39
+ submit:
40
+ eventListeners:
41
+ click:
42
+ handler: handleSubmitClick
43
+
44
+ events:
45
+ input-change: {}
46
+
47
+ template:
48
+ - rtgl-view#text-display w=f cur=p:
49
+ - rtgl-text: ${value}
50
+ - rtgl-popover#popover ?open=${isOpen} x=${position.x} y=${position.y}:
51
+ - rtgl-view g=md w=240 slot=content bgc=background br=md:
52
+ - rtgl-text: ${label}
53
+ - rtgl-input#input w=f placeholder=${placeholder}:
54
+ - rtgl-view w=f ah=e:
55
+ - rtgl-button#submit: Submit
@@ -0,0 +1,86 @@
1
+ export const handleAfterMount = async (deps) => {
2
+ const { props, store, render, getRefIds, } = deps;
3
+ const { waveformData } = props;
4
+
5
+ store.setWaveformData(waveformData);
6
+ render();
7
+
8
+ const canvas = getRefIds().canvas?.elm;
9
+ if (canvas) {
10
+ renderWaveform(waveformData, canvas);
11
+ }
12
+ };
13
+
14
+ export const handleOnUpdate = async (changes, deps) => {
15
+ const { store, render, getRefIds, props } = deps;
16
+ const { waveformData } = props;
17
+ store.setWaveformData(waveformData);
18
+ render();
19
+
20
+ const canvas = getRefIds().canvas?.elm;
21
+ if (canvas) {
22
+ renderWaveform(waveformData, canvas);
23
+ }
24
+ };
25
+
26
+ async function renderWaveform(waveformData, canvas) {
27
+ const ctx = canvas.getContext("2d");
28
+
29
+ // Get the actual display size of the canvas
30
+ const rect = canvas.getBoundingClientRect();
31
+ const displayWidth = rect.width;
32
+ const displayHeight = rect.height;
33
+
34
+ // Set canvas internal resolution to match display size
35
+ canvas.width = displayWidth;
36
+ canvas.height = displayHeight;
37
+
38
+ const width = canvas.width;
39
+ const height = canvas.height;
40
+
41
+ // Clear canvas
42
+ ctx.clearRect(0, 0, width, height);
43
+
44
+ // Dark theme background
45
+ ctx.fillStyle = "#1a1a1a";
46
+ ctx.fillRect(0, 0, width, height);
47
+
48
+ if (!waveformData || !waveformData.data) {
49
+ return;
50
+ }
51
+
52
+ const data = waveformData.data;
53
+ const centerY = height / 2;
54
+
55
+ // Create gradient for waveform
56
+ const gradient = ctx.createLinearGradient(0, 0, 0, height);
57
+ gradient.addColorStop(0, "#404040");
58
+ gradient.addColorStop(0.5, "#A1A1A1");
59
+ gradient.addColorStop(1, "#404040");
60
+
61
+ // Draw waveform bars
62
+ const barWidth = Math.max(1, width / data.length);
63
+ const barSpacing = 0.2; // 20% spacing between bars
64
+
65
+ for (let i = 0; i < data.length; i++) {
66
+ const amplitude = data[i];
67
+ const barHeight = amplitude * (height * 0.85);
68
+ const x = i * barWidth;
69
+ const y = centerY - barHeight / 2;
70
+
71
+ ctx.fillStyle = gradient;
72
+ ctx.fillRect(x, y, Math.max(1, barWidth * (1 - barSpacing)), barHeight);
73
+ }
74
+
75
+ // Draw subtle center line
76
+ ctx.strokeStyle = "rgba(255, 255, 255, 0.1)";
77
+ ctx.lineWidth = 1;
78
+ ctx.beginPath();
79
+ ctx.moveTo(0, centerY);
80
+ ctx.lineTo(width, centerY);
81
+ ctx.stroke();
82
+
83
+ // Add subtle glow effect
84
+ ctx.shadowBlur = 10;
85
+ ctx.shadowColor = "#2196F3";
86
+ }
@@ -0,0 +1,16 @@
1
+ export const INITIAL_STATE = Object.freeze({
2
+ waveformData: null,
3
+ });
4
+
5
+ export const setWaveformData = (state, data) => {
6
+ state.waveformData = data;
7
+ };
8
+
9
+ export const toViewData = ({ state, attrs, props }) => {
10
+ return {
11
+ isLoading: props.isLoading,
12
+ w: attrs.w || "250",
13
+ h: attrs.h || "150",
14
+ waveformData: props.waveformData,
15
+ };
16
+ };
@@ -0,0 +1,35 @@
1
+ elementName: rtgl-waveform
2
+
3
+ attrsSchema:
4
+ type: object
5
+ properties:
6
+ w:
7
+ type: string
8
+ description: Width of the waveform visualizer
9
+ default: '250'
10
+ h:
11
+ type: string
12
+ description: Height of the waveform visualizer
13
+ default: '150'
14
+
15
+ propsSchema:
16
+ type: object
17
+ properties:
18
+ waveformData:
19
+ type: object
20
+ description: File ID of the waveform data in object storage
21
+ isLoading:
22
+ type: boolean
23
+ description: Whether the waveform data is currently being loaded
24
+
25
+ refs:
26
+ canvas:
27
+ selector: canvas
28
+
29
+ template:
30
+ - rtgl-view w=f h=f pos=rel w=${w} h=${h}:
31
+ - $if isLoading:
32
+ - rtgl-view w=f h=f av=c ah=c:
33
+ - rtgl-text c=mu-fg: ...
34
+ $else:
35
+ - 'canvas#canvas style="width:100%; height:100%;"':
@@ -110,6 +110,10 @@ class RettangoliInputElement extends HTMLElement {
110
110
  this._inputElement.value = newValue;
111
111
  }
112
112
 
113
+ focus() {
114
+ this._inputElement.focus();
115
+ }
116
+
113
117
  _onChange = (event) => {
114
118
  this.dispatchEvent(new CustomEvent('input-change', {
115
119
  detail: {