@rettangoli/ui 0.1.2-rc15 → 0.1.2-rc18

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-rc18",
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-", "");
@@ -90,12 +115,36 @@ export const handleSliderInputChange = (e, deps) => {
90
115
  };
91
116
 
92
117
  export const handleImageClick = (e, deps) => {
118
+ if (e.type === "contextmenu") {
119
+ e.preventDefault();
120
+ }
93
121
  const { dispatchEvent } = deps;
94
122
  const name = e.currentTarget.id.replace("image-", "");
95
123
  dispatchEvent(
96
124
  new CustomEvent("extra-event", {
97
125
  detail: {
98
- name: name
126
+ name: name,
127
+ x: e.clientX,
128
+ y: e.clientY,
129
+ trigger: e.type
130
+ },
131
+ }),
132
+ );
133
+ };
134
+
135
+ export const handleWaveformClick = (e, deps) => {
136
+ if (e.type === "contextmenu") {
137
+ e.preventDefault();
138
+ }
139
+ const { dispatchEvent } = deps;
140
+ const name = e.currentTarget.id.replace("waveform-", "");
141
+ dispatchEvent(
142
+ new CustomEvent("extra-event", {
143
+ detail: {
144
+ name: name,
145
+ x: e.clientX,
146
+ y: e.clientY,
147
+ trigger: e.type
99
148
  },
100
149
  }),
101
150
  );
@@ -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,18 @@ refs:
200
221
  eventListeners:
201
222
  click:
202
223
  handler: handleImageClick
224
+ contextmenu:
225
+ handler: handleImageClick
226
+ waveform-*:
227
+ eventListeners:
228
+ click:
229
+ handler: handleWaveformClick
230
+ contextmenu:
231
+ handler: handleWaveformClick
232
+ popover-input-*:
233
+ eventListeners:
234
+ input-change:
235
+ handler: handlePopoverInputChange
203
236
 
204
237
  events:
205
238
  form-change: {}
@@ -216,8 +249,12 @@ template:
216
249
  - rtgl-view g=sm:
217
250
  - rtgl-text: ${field.label}
218
251
  - rtgl-text s=sm c=mu-fg: ${field.description}
252
+ - $if field.inputType == "read-only-text":
253
+ - rtgl-text s=sm: ${field.defaultValue}
219
254
  - $if field.inputType == "inputText":
220
255
  - rtgl-input#input-${field.name} w=f placeholder=${field.placeholder} value=${field.defaultValue}:
256
+ - $if field.inputType == "popover-input":
257
+ - rtgl-popover-input#popover-input-${field.name} label="${field.label}" .defaultValue=fields[${i}].defaultValue:
221
258
  - $if field.inputType == "select":
222
259
  - rtgl-select#select-${field.name} w=f .options=fields[${i}].options .placeholder=fields[${i}].placeholder .selectedValue=fields[${i}].defaultValue:
223
260
  - $if field.inputType == "colorPicker":
@@ -231,6 +268,11 @@ template:
231
268
  - $if field.inputType == "image" && !field.imageSrc:
232
269
  - rtgl-view#image-${field.name} w=${field.width} h=${field.height} bc=ac bw=sm ah=c av=c cur=p p=md:
233
270
  - rtgl-text c=mu-fg ta=c: ${field.placeholderText}
271
+ - $if field.inputType == "waveform" && field.waveformData:
272
+ - rtgl-waveform#waveform-${field.name} .waveformData=fields[${i}].waveformData w=${field.width} h=${field.height} cur=p:
273
+ - $if field.inputType == "waveform" && !field.waveformData:
274
+ - rtgl-view#waveform-${field.name} w=${field.width} h=${field.height} bc=ac bw=sm ah=c av=c cur=p p=md:
275
+ - rtgl-text c=mu-fg ta=c: ${field.placeholderText}
234
276
  - rtgl-view g=sm w=f:
235
277
  - rtgl-view d=h ah=e g=sm w=f:
236
278
  - $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,17 @@
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
+ cur: attrs.cur,
15
+ waveformData: props.waveformData,
16
+ };
17
+ };
@@ -0,0 +1,38 @@
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
+ cur:
15
+ type: string
16
+ description: cursor
17
+
18
+ propsSchema:
19
+ type: object
20
+ properties:
21
+ waveformData:
22
+ type: object
23
+ description: File ID of the waveform data in object storage
24
+ isLoading:
25
+ type: boolean
26
+ description: Whether the waveform data is currently being loaded
27
+
28
+ refs:
29
+ canvas:
30
+ selector: canvas
31
+
32
+ template:
33
+ - rtgl-view w=f h=f pos=rel w=${w} h=${h} cur=${cur}:
34
+ - $if isLoading:
35
+ - rtgl-view w=f h=f av=c ah=c:
36
+ - rtgl-text c=mu-fg: ...
37
+ $else:
38
+ - '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: {