@rettangoli/ui 0.1.2-rc3 → 0.1.2-rc30

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 (54) hide show
  1. package/dist/rettangoli-iife-layout.min.js +115 -43
  2. package/dist/rettangoli-iife-ui.min.js +187 -67
  3. package/package.json +5 -3
  4. package/src/cli/buildSvg.js +86 -0
  5. package/src/cli/index.js +1 -0
  6. package/src/common.js +19 -0
  7. package/src/components/breadcrumb/breadcrumb.handlers.js +9 -0
  8. package/src/components/breadcrumb/breadcrumb.store.js +29 -0
  9. package/src/components/breadcrumb/breadcrumb.view.yaml +64 -0
  10. package/src/components/dropdownMenu/dropdownMenu.handlers.js +4 -4
  11. package/src/components/dropdownMenu/dropdownMenu.store.js +5 -17
  12. package/src/components/dropdownMenu/dropdownMenu.view.yaml +15 -13
  13. package/src/components/form/form.handlers.js +173 -25
  14. package/src/components/form/form.store.js +176 -22
  15. package/src/components/form/form.view.yaml +217 -33
  16. package/src/components/pageOutline/pageOutline.handlers.js +1 -1
  17. package/src/components/popoverInput/popoverInput.handlers.js +99 -0
  18. package/src/components/popoverInput/popoverInput.store.js +48 -0
  19. package/src/components/popoverInput/popoverInput.view.yaml +55 -0
  20. package/src/components/select/select.handlers.js +116 -11
  21. package/src/components/select/select.store.js +84 -18
  22. package/src/components/select/select.view.yaml +40 -10
  23. package/src/components/sidebar/sidebar.view.yaml +1 -1
  24. package/src/components/sliderInput/sliderInput.handlers.js +41 -0
  25. package/src/components/sliderInput/sliderInput.store.js +18 -0
  26. package/src/components/sliderInput/sliderInput.view.yaml +42 -0
  27. package/src/components/table/table.handlers.js +1 -1
  28. package/src/components/tabs/tabs.handlers.js +10 -0
  29. package/src/components/tabs/tabs.store.js +29 -0
  30. package/src/components/tabs/tabs.view.yaml +64 -0
  31. package/src/components/tooltip/tooltip.handlers.js +0 -0
  32. package/src/components/tooltip/tooltip.store.js +12 -0
  33. package/src/components/tooltip/tooltip.view.yaml +27 -0
  34. package/src/components/waveform/waveform.handlers.js +92 -0
  35. package/src/components/waveform/waveform.store.js +17 -0
  36. package/src/components/waveform/waveform.view.yaml +38 -0
  37. package/src/entry-iife-layout.js +3 -0
  38. package/src/entry-iife-ui.js +4 -0
  39. package/src/index.js +5 -1
  40. package/src/primitives/button.js +10 -0
  41. package/src/primitives/colorPicker.js +9 -0
  42. package/src/primitives/dialog.js +254 -0
  43. package/src/primitives/input.js +41 -11
  44. package/src/primitives/popover.js +280 -0
  45. package/src/primitives/slider.js +18 -9
  46. package/src/primitives/svg.js +2 -0
  47. package/src/primitives/textarea.js +25 -1
  48. package/src/styles/cursorStyles.js +38 -2
  49. package/src/components/dialog/dialog.handlers.js +0 -5
  50. package/src/components/dialog/dialog.store.js +0 -25
  51. package/src/components/dialog/dialog.view.yaml +0 -44
  52. package/src/components/popover/popover.handlers.js +0 -5
  53. package/src/components/popover/popover.store.js +0 -12
  54. package/src/components/popover/popover.view.yaml +0 -57
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rettangoli/ui",
3
- "version": "0.1.2-rc3",
3
+ "version": "0.1.2-rc30",
4
4
  "description": "A UI component library for building web interfaces.",
5
5
  "main": "dist/rettangoli-esm.min.js",
6
6
  "type": "module",
@@ -9,7 +9,8 @@
9
9
  "src"
10
10
  ],
11
11
  "exports": {
12
- ".": "./src/index.js"
12
+ ".": "./src/index.js",
13
+ "./cli": "./src/cli/index.js"
13
14
  },
14
15
  "module": "./src/index.js",
15
16
  "repository": {
@@ -48,10 +49,11 @@
48
49
  "homepage": "https://github.com/yuusoft-org/rettangoli#readme",
49
50
  "dependencies": {
50
51
  "@floating-ui/dom": "^1.6.13",
52
+ "@rettangoli/fe": "0.0.7-rc12",
51
53
  "commander": "^13.1.0",
54
+ "jempl": "0.1.4-rc1",
52
55
  "js-yaml": "^4.1.0",
53
56
  "liquidjs": "^10.21.0",
54
- "@rettangoli/fe": "0.0.7-rc4",
55
57
  "snabbdom": "^3.6.2"
56
58
  }
57
59
  }
@@ -0,0 +1,86 @@
1
+ import { readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
2
+ import { join, basename } from 'node:path';
3
+
4
+ function getAllSvgFiles(dir) {
5
+ const svgFiles = [];
6
+
7
+ function traverse(currentDir) {
8
+ const files = readdirSync(currentDir);
9
+
10
+ for (const file of files) {
11
+ const fullPath = join(currentDir, file);
12
+ const stats = statSync(fullPath);
13
+
14
+ if (stats.isDirectory()) {
15
+ traverse(fullPath);
16
+ } else if (file.endsWith('.svg')) {
17
+ svgFiles.push(fullPath);
18
+ }
19
+ }
20
+ }
21
+
22
+ traverse(dir);
23
+ return svgFiles;
24
+ }
25
+
26
+ function removeFrontmatter(content) {
27
+ // Look for a line containing only '---'
28
+ const lines = content.split('\n');
29
+
30
+ for (let i = 0; i < lines.length; i++) {
31
+ if (lines[i].trim() === '---') {
32
+ // Found the separator, return everything after it
33
+ return lines.slice(i + 1).join('\n').trim();
34
+ }
35
+ }
36
+
37
+ // No frontmatter separator found, return original content
38
+ return content;
39
+ }
40
+
41
+ const buildSvgIcons = (options) => {
42
+ const { dir, outfile } = options;
43
+
44
+ if (!dir || !outfile) {
45
+ console.error('Error: Both dir and outfile options are required');
46
+ return;
47
+ }
48
+
49
+ console.log(`Scanning for SVG files in: ${dir}`);
50
+
51
+ const svgFiles = getAllSvgFiles(dir);
52
+
53
+ if (svgFiles.length === 0) {
54
+ console.log('No SVG files found');
55
+ return;
56
+ }
57
+
58
+ console.log(`Found ${svgFiles.length} SVG files`);
59
+
60
+ const icons = {};
61
+
62
+ for (const filePath of svgFiles) {
63
+ const fileName = basename(filePath, '.svg');
64
+ let content = readFileSync(filePath, 'utf8');
65
+
66
+ content = removeFrontmatter(content);
67
+
68
+ content = content.replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
69
+
70
+ icons[fileName] = content;
71
+ }
72
+
73
+ // Convert to custom format with single quotes for keys and backticks for values
74
+ const iconEntries = Object.entries(icons).map(([key, value]) => {
75
+ return ` '${key}': \`${value}\``;
76
+ });
77
+
78
+ const output = `window.rtglIcons = {\n${iconEntries.join(',\n')}\n};`;
79
+
80
+ writeFileSync(outfile, output);
81
+
82
+ console.log(`SVG icons written to: ${outfile}`);
83
+ console.log(`Total icons: ${Object.keys(icons).length}`);
84
+ };
85
+
86
+ export default buildSvgIcons;
@@ -0,0 +1 @@
1
+ export { default as buildSvg } from './buildSvg.js';
package/src/common.js CHANGED
@@ -180,6 +180,25 @@ function convertObjectToCssString(styleObject, selector = ':host') {
180
180
  return result;
181
181
  }
182
182
 
183
+ // Deep equality helper for comparing values (including objects)
184
+ export const deepEqual = (a, b) => {
185
+ if (a === b) return true;
186
+ if (a == null || b == null) return false;
187
+ if (typeof a !== 'object' || typeof b !== 'object') return false;
188
+
189
+ const keysA = Object.keys(a);
190
+ const keysB = Object.keys(b);
191
+
192
+ if (keysA.length !== keysB.length) return false;
193
+
194
+ for (const key of keysA) {
195
+ if (!keysB.includes(key)) return false;
196
+ if (!deepEqual(a[key], b[key])) return false;
197
+ }
198
+
199
+ return true;
200
+ };
201
+
183
202
  export {
184
203
  css,
185
204
  generateCSS,
@@ -0,0 +1,9 @@
1
+ export const handleClickItem = (e, deps) => {
2
+ const { dispatchEvent } = deps;
3
+ const id = e.currentTarget.dataset.id;
4
+ dispatchEvent(new CustomEvent('item-click', {
5
+ detail: {
6
+ id
7
+ }
8
+ }));
9
+ }
@@ -0,0 +1,29 @@
1
+ export const INITIAL_STATE = Object.freeze({});
2
+
3
+ const blacklistedAttrs = ['id', 'class', 'style', 'slot'];
4
+
5
+ const stringifyAttrs = (attrs) => {
6
+ return Object.entries(attrs).filter(([key]) => !blacklistedAttrs.includes(key)).map(([key, value]) => `${key}=${value}`).join(' ');
7
+ }
8
+
9
+ export const toViewData = ({ props, attrs }) => {
10
+ const containerAttrString = stringifyAttrs(attrs);
11
+
12
+ const items = props.items || [];
13
+ const separator = props.separator || 'breadcrumb-arrow';
14
+
15
+ // Add separators between items, but not after the last one
16
+ const itemsWithSeparators = [];
17
+ items.forEach((item, index) => {
18
+ itemsWithSeparators.push(item);
19
+ if (index < items.length - 1) {
20
+ itemsWithSeparators.push({ isSeparator: true });
21
+ }
22
+ });
23
+
24
+ return {
25
+ containerAttrString,
26
+ items: itemsWithSeparators,
27
+ separator
28
+ };
29
+ }
@@ -0,0 +1,64 @@
1
+ elementName: rtgl-breadcrumb
2
+
3
+ viewDataSchema:
4
+ type: object
5
+ properties:
6
+ containerAttrString:
7
+ type: string
8
+ items:
9
+ type: array
10
+ items:
11
+ type: object
12
+ properties:
13
+ label:
14
+ type: string
15
+ id:
16
+ type: string
17
+ separator:
18
+ type: string
19
+
20
+ propsSchema:
21
+ type: object
22
+ properties:
23
+ items:
24
+ type: array
25
+ items:
26
+ type: object
27
+ properties:
28
+ label:
29
+ type: string
30
+ id:
31
+ type: string
32
+ separator:
33
+ type: string
34
+ default: "breadcrumb-arrow"
35
+
36
+ refs:
37
+ item-*:
38
+ eventListeners:
39
+ click:
40
+ handler: handleClickItem
41
+ events:
42
+ item-click:
43
+ type: object
44
+ properties:
45
+ item:
46
+ type: object
47
+ properties:
48
+ label:
49
+ type: string
50
+ id:
51
+ type: string
52
+ index:
53
+ type: number
54
+
55
+ template:
56
+ - rtgl-view d=h av=c g=md p=sm ${containerAttrString}:
57
+ - $for item in items:
58
+ - $if item.isSeparator:
59
+ - rtgl-svg wh=16 svg=${separator} c=mu-fg:
60
+ $elif item.id:
61
+ - rtgl-view#item-${item.id} data-id=${item.id} cur=p:
62
+ - rtgl-text s=sm c=mu-fg: "${item.label}"
63
+ $else:
64
+ - rtgl-text s=sm c=mu-fg: "${item.label}"
@@ -1,18 +1,18 @@
1
1
 
2
- export const handleClickPopoverOverlay = (e, deps) => {
2
+ export const handleClosePopover = (e, deps) => {
3
3
  const { dispatchEvent } = deps;
4
- dispatchEvent(new CustomEvent('click-overlay'));
4
+ dispatchEvent(new CustomEvent('close'));
5
5
  }
6
6
 
7
7
  export const handleClickMenuItem = (e, deps) => {
8
8
  const { dispatchEvent } = deps;
9
9
  const index = parseInt(e.currentTarget.id.replace('option-', ''));
10
10
  const item = deps.props.items[index];
11
-
11
+
12
12
  dispatchEvent(new CustomEvent('click-item', {
13
13
  detail: {
14
14
  index,
15
15
  item
16
16
  }
17
17
  }));
18
- }
18
+ }
@@ -2,24 +2,12 @@
2
2
  export const INITIAL_STATE = Object.freeze({
3
3
  });
4
4
 
5
- export const toViewData = ({ state, props }) => {
5
+ export const toViewData = ({ props, attrs }) => {
6
6
  return {
7
7
  items: props.items || [],
8
- isOpen: props.isOpen || false,
9
- position: props.position || {
10
- x: 0,
11
- y: 0,
12
- },
8
+ open: !!attrs.open,
9
+ x: attrs.x,
10
+ y: attrs.y,
11
+ placement: attrs.placement
13
12
  };
14
13
  }
15
-
16
- export const selectState = ({ state }) => {
17
- return state;
18
- }
19
-
20
- export const setState = (state) => {
21
- // do doSomething
22
- }
23
-
24
-
25
-
@@ -19,28 +19,31 @@ propsSchema:
19
19
  - label
20
20
  - item
21
21
  - separator
22
- isOpen:
23
- type: boolean
24
- position:
25
- type: object
26
- properties:
27
- x:
28
- type: number
29
- y:
30
- type: number
22
+
23
+ attrsSchema:
24
+ type: object
25
+ properties:
26
+ open:
27
+ type: string
28
+ x:
29
+ type: string
30
+ y:
31
+ type: string
32
+ placement:
33
+ type: string
31
34
 
32
35
  refs:
33
36
  popover:
34
37
  eventListeners:
35
- click-overlay:
36
- handler: handleClickPopoverOverlay
38
+ close:
39
+ handler: handleClosePopover
37
40
  option-*:
38
41
  eventListeners:
39
42
  click:
40
43
  handler: handleClickMenuItem
41
44
 
42
45
  template:
43
- - rtgl-popover#popover .isOpen=isOpen .position=position:
46
+ - rtgl-popover#popover ?open=${open} x=${x} y=${y} placement=${placement}:
44
47
  - rtgl-view wh=300 g=xs slot=content bgc=background br=md:
45
48
  - $for item, i in items:
46
49
  - $if item.type == 'label':
@@ -51,4 +54,3 @@ template:
51
54
  - rtgl-text: ${item.label}
52
55
  $elif item.type == 'separator':
53
56
  - rtgl-view w=f h=1 ph=lg mv=md bgc=bo:
54
-
@@ -1,13 +1,25 @@
1
- export const handleOnMount = (deps) => {
1
+ export const handleBeforeMount = (deps) => {
2
2
  const { store, props } = deps;
3
- store.setDefaultValues(props.defaultValues);
3
+ store.setFormValues(props.defaultValues);
4
4
  };
5
5
 
6
- const dispatchFormChange = (fieldName, fieldValue, formValues, dispatchEvent) => {
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
+
18
+ const dispatchFormChange = (name, fieldValue, formValues, dispatchEvent) => {
7
19
  dispatchEvent(
8
20
  new CustomEvent("form-change", {
9
21
  detail: {
10
- fieldName,
22
+ name,
11
23
  fieldValue,
12
24
  formValues,
13
25
  },
@@ -29,51 +41,187 @@ export const handleActionClick = (e, deps) => {
29
41
  };
30
42
 
31
43
  export const handleInputChange = (e, deps) => {
32
- const { store, dispatchEvent } = deps;
33
- const id = e.currentTarget.id.replace("input-", "");
44
+ const { store, dispatchEvent, props } = deps;
45
+ const name = e.currentTarget.id.replace("input-", "");
46
+ // TODO fix double event
47
+ if (name && e.detail.value !== undefined) {
48
+ store.setFormFieldValue({
49
+ name: name,
50
+ value: e.detail.value,
51
+ props,
52
+ });
53
+ dispatchFormChange(
54
+ name,
55
+ e.detail.value,
56
+ store.selectFormValues(),
57
+ dispatchEvent,
58
+ );
59
+ }
60
+ };
61
+
62
+ export const handlePopoverInputChange = (e, deps) => {
63
+ const { store, dispatchEvent, props } = deps;
64
+ const name = e.currentTarget.id.replace("popover-input-", "");
34
65
  // TODO fix double event
35
- if (id && e.detail.value !== undefined) {
66
+ if (name && e.detail.value !== undefined) {
36
67
  store.setFormFieldValue({
37
- // TODO user field name instead of id
38
- fieldName: id,
68
+ name: name,
39
69
  value: e.detail.value,
70
+ props,
40
71
  });
41
- dispatchFormChange(id, e.detail.value, store.selectFormValues(), dispatchEvent);
72
+ dispatchFormChange(
73
+ name,
74
+ e.detail.value,
75
+ store.selectFormValues(),
76
+ dispatchEvent,
77
+ );
42
78
  }
43
79
  };
44
80
 
45
81
  export const handleSelectChange = (e, deps) => {
46
- const { store, dispatchEvent } = deps;
47
- const id = e.currentTarget.id.replace("select-", "");
48
- if (id && e.detail.selectedValue !== undefined) {
82
+ const { store, dispatchEvent, render, props } = deps;
83
+ const name = e.currentTarget.id.replace("select-", "");
84
+ if (name && e.detail.selectedValue !== undefined) {
49
85
  store.setFormFieldValue({
50
- fieldName: id,
86
+ name: name,
51
87
  value: e.detail.selectedValue,
88
+ props,
52
89
  });
53
- dispatchFormChange(id, e.detail.selectedValue, store.selectFormValues(), dispatchEvent);
90
+ dispatchFormChange(
91
+ name,
92
+ e.detail.selectedValue,
93
+ store.selectFormValues(),
94
+ dispatchEvent,
95
+ );
96
+ render();
54
97
  }
55
98
  };
56
99
 
57
100
  export const handleColorPickerChange = (e, deps) => {
58
- const { store, dispatchEvent } = deps;
59
- const id = e.currentTarget.id.replace("colorpicker-", "");
60
- if (id && e.detail.value !== undefined) {
101
+ const { store, dispatchEvent, props } = deps;
102
+ const name = e.currentTarget.id.replace("colorpicker-", "");
103
+ if (name && e.detail.value !== undefined) {
61
104
  store.setFormFieldValue({
62
- fieldName: id,
105
+ name: name,
63
106
  value: e.detail.value,
107
+ props,
64
108
  });
65
- dispatchFormChange(id, e.detail.value, store.selectFormValues(), dispatchEvent);
109
+ dispatchFormChange(
110
+ name,
111
+ e.detail.value,
112
+ store.selectFormValues(),
113
+ dispatchEvent,
114
+ );
66
115
  }
67
116
  };
68
117
 
69
118
  export const handleSliderChange = (e, deps) => {
70
- const { store, dispatchEvent } = deps;
71
- const id = e.currentTarget.id.replace("slider-", "");
72
- if (id && e.detail.value !== undefined) {
119
+ const { store, dispatchEvent, props } = deps;
120
+ const name = e.currentTarget.id.replace("slider-", "");
121
+ if (name && e.detail.value !== undefined) {
73
122
  store.setFormFieldValue({
74
- fieldName: id,
123
+ name: name,
75
124
  value: e.detail.value,
125
+ props,
126
+ });
127
+ dispatchFormChange(
128
+ name,
129
+ e.detail.value,
130
+ store.selectFormValues(),
131
+ dispatchEvent,
132
+ );
133
+ }
134
+ };
135
+
136
+ export const handleSliderInputChange = (e, deps) => {
137
+ const { store, dispatchEvent, props } = deps;
138
+ const name = e.currentTarget.id.replace("slider-input-", "");
139
+ if (name && e.detail.value !== undefined) {
140
+ store.setFormFieldValue({
141
+ name: name,
142
+ value: e.detail.value,
143
+ props,
144
+ });
145
+ dispatchFormChange(
146
+ name,
147
+ e.detail.value,
148
+ store.selectFormValues(),
149
+ dispatchEvent,
150
+ );
151
+ }
152
+ };
153
+
154
+ export const handleImageClick = (e, deps) => {
155
+ if (e.type === "contextmenu") {
156
+ e.preventDefault();
157
+ }
158
+ const { dispatchEvent } = deps;
159
+ const name = e.currentTarget.id.replace("image-", "");
160
+ dispatchEvent(
161
+ new CustomEvent("extra-event", {
162
+ detail: {
163
+ name: name,
164
+ x: e.clientX,
165
+ y: e.clientY,
166
+ trigger: e.type,
167
+ },
168
+ }),
169
+ );
170
+ };
171
+
172
+ export const handleWaveformClick = (e, deps) => {
173
+ if (e.type === "contextmenu") {
174
+ e.preventDefault();
175
+ }
176
+ const { dispatchEvent } = deps;
177
+ const name = e.currentTarget.id.replace("waveform-", "");
178
+ dispatchEvent(
179
+ new CustomEvent("extra-event", {
180
+ detail: {
181
+ name: name,
182
+ x: e.clientX,
183
+ y: e.clientY,
184
+ trigger: e.type,
185
+ },
186
+ }),
187
+ );
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
76
218
  });
77
- dispatchFormChange(id, e.detail.value, store.selectFormValues(), dispatchEvent);
219
+ render();
78
220
  }
79
221
  };
222
+
223
+ export const handleTooltipMouseLeave = (e, deps) => {
224
+ const { store, render } = deps;
225
+ store.hideTooltip();
226
+ render();
227
+ };