rettangoli-ui 0.1.0-rc2 → 0.1.0-rc4

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/README.md +16 -27
  2. package/dist/rettangoli-iife-layout.min.js +118 -45
  3. package/dist/rettangoli-iife-ui.min.js +86 -65
  4. package/package.json +11 -4
  5. package/src/common/BaseElement.js +182 -0
  6. package/src/common.js +190 -0
  7. package/src/components/dialog/dialog.handlers.js +5 -0
  8. package/src/components/dialog/dialog.store.js +24 -0
  9. package/src/components/dialog/dialog.view.yaml +41 -0
  10. package/src/components/dropdownMenu/dropdownMenu.handlers.js +5 -0
  11. package/src/components/dropdownMenu/dropdownMenu.store.js +25 -0
  12. package/src/components/dropdownMenu/dropdownMenu.view.yaml +52 -0
  13. package/src/components/form/form.handlers.js +30 -0
  14. package/src/components/form/form.store.js +45 -0
  15. package/src/components/form/form.view.yaml +47 -0
  16. package/src/components/navbar/navbar.examples.yaml +86 -0
  17. package/src/components/navbar/navbar.handlers.js +10 -0
  18. package/src/components/navbar/navbar.store.js +46 -0
  19. package/src/components/navbar/navbar.view.yaml +74 -0
  20. package/src/components/pageOutline/pageOutline.handlers.js +69 -0
  21. package/src/components/pageOutline/pageOutline.store.js +40 -0
  22. package/src/components/pageOutline/pageOutline.view.yaml +34 -0
  23. package/src/components/popover/popover.handlers.js +5 -0
  24. package/src/components/popover/popover.store.js +12 -0
  25. package/src/components/popover/popover.view.yaml +57 -0
  26. package/src/components/select/select.handlers.js +46 -0
  27. package/src/components/select/select.store.js +65 -0
  28. package/src/components/select/select.view.yaml +50 -0
  29. package/src/components/sidebar/sidebar.handlers.js +36 -0
  30. package/src/components/sidebar/sidebar.store.js +139 -0
  31. package/src/components/sidebar/sidebar.view.yaml +190 -0
  32. package/src/entry-iife-layout.js +15 -0
  33. package/src/entry-iife-ui.js +18 -0
  34. package/src/index.js +17 -0
  35. package/src/lib/uhtml.js +9 -0
  36. package/src/primitives/button.js +306 -0
  37. package/src/primitives/image.js +234 -0
  38. package/src/primitives/input.js +208 -0
  39. package/src/primitives/svg.js +95 -0
  40. package/src/primitives/text.js +141 -0
  41. package/src/primitives/textarea.js +103 -0
  42. package/src/primitives/view.js +217 -0
  43. package/src/setup.js +16 -0
  44. package/src/styles/anchorStyles.js +13 -0
  45. package/src/styles/buttonMarginStyles.js +84 -0
  46. package/src/styles/cursorStyles.js +12 -0
  47. package/src/styles/flexChildStyles.js +43 -0
  48. package/src/styles/flexDirectionStyles.js +74 -0
  49. package/src/styles/marginStyles.js +13 -0
  50. package/src/styles/paddingSvgStyles.js +120 -0
  51. package/src/styles/scrollStyles.js +22 -0
  52. package/src/styles/textColorStyles.js +14 -0
  53. package/src/styles/textStyles.js +62 -0
  54. package/src/styles/viewStyles.js +119 -0
package/package.json CHANGED
@@ -1,12 +1,17 @@
1
1
  {
2
2
  "name": "rettangoli-ui",
3
- "version": "0.1.0-rc2",
3
+ "version": "0.1.0-rc4",
4
4
  "description": "A UI component library for building web interfaces.",
5
5
  "main": "dist/rettangoli-esm.min.js",
6
6
  "type": "module",
7
7
  "files": [
8
- "dist"
8
+ "dist",
9
+ "src"
9
10
  ],
11
+ "exports": {
12
+ ".": "./src/index.js"
13
+ },
14
+ "module": "./src/index.js",
10
15
  "repository": {
11
16
  "type": "git",
12
17
  "url": "git+https://github.com/yuusoft-org/rettangoli.git"
@@ -17,12 +22,14 @@
17
22
  },
18
23
  "license": "MIT",
19
24
  "scripts": {
20
- "build": "bun run ../rettangoli-cli/cli.js fe build -d ./src -o ./viz/static/public/main.js && bun run esbuild.js"
25
+ "build:dev": "bun run ../rettangoli-cli/cli.js fe build && bun run esbuild-dev.js",
26
+ "build": "bun run ../rettangoli-cli/cli.js fe build && bun run esbuild.js"
21
27
  },
22
28
  "devDependencies": {
23
29
  "esbuild": "^0.20.0",
24
- "playwright": "^1.46.0",
30
+ "looks-same": "^9.0.1",
25
31
  "pixelmatch": "^6.0.0",
32
+ "playwright": "^1.46.0",
26
33
  "pngjs": "^7.0.0"
27
34
  },
28
35
  "keywords": [
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Subscribes to all observables and returns a functionthat will unsubscribe
3
+ * from all observables when called
4
+ * @param {*} observables
5
+ * @returns
6
+ */
7
+ const subscribeAll = (observables) => {
8
+ // Subscribe to all observables and store the subscription objects
9
+ const subscriptions = observables.map((observable) => observable.subscribe());
10
+
11
+ // Return a function that will unsubscribe from all observables when called
12
+ return () => {
13
+ for (const subscription of subscriptions) {
14
+ if (subscription && typeof subscription.unsubscribe === "function") {
15
+ subscription.unsubscribe();
16
+ }
17
+ }
18
+ };
19
+ };
20
+
21
+ /**
22
+ * Creates a base web component element to be used in environments
23
+ * where the output is strings such as server side rendering environments
24
+ * @param {Object} param
25
+ * @param {Function} param.render - The render function to be used in the element. compliant with uhtml.render
26
+ * @returns
27
+ */
28
+ export const createSsrBaseElement = ({ render }) => {
29
+
30
+ class CSSStyleSheet {
31
+ _css = "";
32
+
33
+ get css() {
34
+ return this._css;
35
+ }
36
+
37
+ replaceSync(css) {
38
+ this._css = css;
39
+ }
40
+ }
41
+ class SsrRenderTarget {
42
+ innerHTML = "";
43
+ adoptedStyleSheets = [];
44
+ write(html) {
45
+ this.innerHTML = html;
46
+ }
47
+ }
48
+ class BaseBaseSsrElement {
49
+ renderTarget = new SsrRenderTarget();
50
+
51
+ constructor() {
52
+ const styleSheet = new CSSStyleSheet();
53
+ styleSheet.replaceSync(`:host { display: contents; }`);
54
+ this.renderTarget.adoptedStyleSheets = [styleSheet];
55
+ }
56
+ }
57
+ return class BaseSsrElement extends createGenericBaseElement({
58
+ BaseElement: BaseBaseSsrElement,
59
+ render,
60
+ skipOnMount: true,
61
+ }) {};
62
+ };
63
+
64
+ /**
65
+ * Creates a base web component element to be used in web browser environments
66
+ * @param {Object} param
67
+ * @param {Function} param.render - The render function to be used in the element. compliant with uhtml.render
68
+ * @returns
69
+ */
70
+ export const createWebComponentBaseElement = ({ render, styleSheet }) => {
71
+ let actualStyleSheet;
72
+
73
+ if (styleSheet) {
74
+ actualStyleSheet = styleSheet;
75
+ } else {
76
+ actualStyleSheet = new CSSStyleSheet();
77
+ actualStyleSheet.replaceSync(`:host { display: contents; }`);
78
+ }
79
+
80
+ return class BaseWebComponentElement extends createGenericBaseElement({
81
+ BaseElement: HTMLElement,
82
+ render,
83
+ skipOnMount: false,
84
+ }) {
85
+ static get observedAttributes() {
86
+ return ["key"];
87
+ }
88
+
89
+ constructor() {
90
+ super();
91
+ this.renderTarget = this.attachShadow({ mode: "closed" });
92
+ }
93
+
94
+ connectedCallback() {
95
+ this.renderTarget.adoptedStyleSheets = [actualStyleSheet];
96
+ this.baseOnMount();
97
+ }
98
+
99
+ disconnectedCallback() {
100
+ this.baseOnUnmount();
101
+ }
102
+
103
+ attributeChangedCallback() {
104
+ setTimeout(() => {
105
+ this.reRender();
106
+ }, 0);
107
+ }
108
+ };
109
+ };
110
+
111
+ /**
112
+ * Creates a base web component element to be used in environments
113
+ * where the output is strings such as server side rendering environments
114
+ * @param {Object} param
115
+ * @param {Object} param.BaseElement - The base element to be used in the element.
116
+ * @param {Function} param.render - The render function to be used in the element. compliant with uhtml.render
117
+ * @param {Boolean} param.skipOnMount - Whether to skip the onMount callback. Default is false.
118
+ * @returns
119
+ */
120
+ export const createGenericBaseElement = ({ BaseElement, render, skipOnMount = false }) => {
121
+ class GenericBaseElement extends BaseElement {
122
+
123
+ _renderKey = 0;
124
+ _unmountCallback;
125
+ // renderTarget;
126
+ disableFirstAutomatedRender = false;
127
+
128
+ get renderKey() {
129
+ return String(this._renderKey);
130
+ }
131
+
132
+ baseOnMount = () => {
133
+ if (!skipOnMount && this.onMount) {
134
+ this._unmountCallback = this.onMount();
135
+ }
136
+
137
+ if (!this.disableFirstAutomatedRender) {
138
+ this.reRender();
139
+ }
140
+
141
+ if (this.attachedObservables) {
142
+ if (Array.isArray(this.attachedObservables)) {
143
+ this.unsubscribe = subscribeAll(this.attachedObservables);
144
+ } else {
145
+ this.unsubscribe = subscribeAll(this.attachedObservables());
146
+ }
147
+ } else if (this.subscriptions) {
148
+ this.unsubscribe = subscribeAll(this.subscriptions);
149
+ }
150
+ };
151
+
152
+ baseOnUnmount = () => {
153
+ if (this._unmountCallback) {
154
+ this._unmountCallback();
155
+ }
156
+ if (this.onUnmount) {
157
+ this.onUnmount();
158
+ }
159
+ if (this.unsubscribe) {
160
+ this.unsubscribe();
161
+ }
162
+ };
163
+
164
+ /**
165
+ * @deprecated
166
+ * @see reRender
167
+ */
168
+ triggerRender = () => {
169
+ this.reRender();
170
+ };
171
+
172
+ /**
173
+ * ReRenders the component
174
+ */
175
+ reRender = () => {
176
+ this._renderKey++;
177
+ render(this.renderTarget, this.render());
178
+ };
179
+ }
180
+
181
+ return GenericBaseElement;
182
+ };
package/src/common.js ADDED
@@ -0,0 +1,190 @@
1
+ function css(strings, ...values) {
2
+ // Combine the strings and values back into a single string
3
+ let str = "";
4
+ strings.forEach((string, i) => {
5
+ str += string + (values[i] || "");
6
+ });
7
+ return str;
8
+ }
9
+
10
+ const breakpoints = ["xs", "sm", "md", "lg", "xl"];
11
+
12
+ const styleMap = {
13
+ mt: "margin-top",
14
+ mr: "margin-right",
15
+ mb: "margin-bottom",
16
+ ml: "margin-left",
17
+ m: "margin",
18
+ mh: "margin-left margin-right",
19
+ mv: "margin-top margin-bottom",
20
+ pt: "padding-top",
21
+ pr: "padding-right",
22
+ pb: "padding-bottom",
23
+ pl: "padding-left",
24
+ p: "padding",
25
+ ph: "padding-left padding-right",
26
+ pv: "padding-top padding-bottom",
27
+ g: "gap",
28
+ gv: "row-gap",
29
+ gh: "column-gap",
30
+ bw: "border-width",
31
+ bwt: "border-top-width",
32
+ bwr: "border-right-width",
33
+ bwb: "border-bottom-width",
34
+ bwl: "border-left-width",
35
+ bc: "border-color",
36
+ br: "border-radius",
37
+ pos: "position",
38
+ shadow: "box-shadow",
39
+ ta: "text-align",
40
+ c: "color",
41
+ cur: "cursor",
42
+ };
43
+
44
+ export const styleMapKeys = Object.keys(styleMap);
45
+
46
+ export const permutateBreakpoints = (keys) => {
47
+ return keys.concat(
48
+ breakpoints.flatMap((breakpoint) =>
49
+ keys.map((key) => `${breakpoint}-${key}`)
50
+ )
51
+ );
52
+ };
53
+
54
+ const mediaQueries = {
55
+ default: undefined,
56
+ xl: "@media only screen and (max-width: 1280px)",
57
+ lg: "@media only screen and (max-width: 1024px)",
58
+ md: "@media only screen and (max-width: 768px)",
59
+ sm: "@media only screen and (max-width: 640px)",
60
+ };
61
+
62
+ const generateCSS = (styles, descendants = {}) => {
63
+ let css = "";
64
+
65
+ for (const [size, mediaQuery] of Object.entries(mediaQueries)) {
66
+ if (size !== "default") {
67
+ css += `${mediaQuery} {`;
68
+ }
69
+ for (const [attr, values] of Object.entries(styles)) {
70
+ const dscendant = descendants[attr] ? ` ${descendants[attr]} ` : " ";
71
+ for (const [value, rule] of Object.entries(values)) {
72
+ const cssProperties = styleMap[attr];
73
+ const cssRule = rule.startsWith("--") ? `var(${rule})` : rule;
74
+
75
+ const attributeWithBreakpoint =
76
+ size === "default" ? attr : `${size}-${attr}`;
77
+ const hoverAttributeWithBreakpoint =
78
+ size === "default" ? `h-${attr}` : `${size}-h-${attr}`;
79
+
80
+ if (cssProperties) {
81
+ // Handle multiple properties if mapped in styleMap
82
+ const properties = cssProperties.split(" ");
83
+ let propertyRules = properties
84
+ .map((property) => `${property}: ${cssRule};`)
85
+ .join(" ");
86
+
87
+ css += `
88
+ :host([${attributeWithBreakpoint}="${value}"])${dscendant}{
89
+ ${propertyRules}
90
+ }
91
+ :host([${hoverAttributeWithBreakpoint}="${value}"]:hover)${dscendant}{
92
+ ${propertyRules}
93
+ }
94
+ `;
95
+ } else {
96
+ // Attribute is not mapped, handle directly
97
+ css += `
98
+ :host([${attributeWithBreakpoint}="${value}"])${dscendant}{
99
+ ${rule}
100
+ }
101
+ :host([${hoverAttributeWithBreakpoint}="${value}"]:hover)${dscendant}{
102
+ ${rule}
103
+ }
104
+ `;
105
+ }
106
+ }
107
+ }
108
+ if (size !== "default") {
109
+ css += `}`;
110
+ }
111
+ }
112
+ return css;
113
+ };
114
+
115
+ function endsWithDigit(inputValue) {
116
+ if (inputValue === null) {
117
+ return false;
118
+ }
119
+ if (inputValue.includes("/")) {
120
+ return false;
121
+ }
122
+ // Convert the input value to a string if it's not already one.
123
+ const inputStr = String(inputValue);
124
+ // Check if the last character of the string is a digit.
125
+ return /[0-9]$/.test(inputStr);
126
+ }
127
+
128
+ const endsWithPercentage = (inputStr) => {
129
+ return /%$/.test(inputStr);
130
+ };
131
+
132
+ const dimensionWithUnit = (dimension) => {
133
+ if (dimension === undefined) {
134
+ return;
135
+ }
136
+
137
+ if (endsWithPercentage(dimension)) {
138
+ return dimension;
139
+ }
140
+
141
+ if (endsWithDigit(dimension)) {
142
+ return `${dimension}px`;
143
+ }
144
+
145
+ if (Object.keys(spacing).includes(dimension)) {
146
+ return `var(${spacing[dimension]})`;
147
+ }
148
+
149
+ return dimension;
150
+ };
151
+
152
+ const spacing = {
153
+ xs: "--spacing-xs",
154
+ sm: "--spacing-sm",
155
+ md: "--spacing-md",
156
+ lg: "--spacing-lg",
157
+ xl: "--spacing-xl",
158
+ };
159
+
160
+ function convertObjectToCssString(styleObject, selector = ':host') {
161
+ let result = "";
162
+ for (const [size, mediaQuery] of Object.entries(mediaQueries)) {
163
+ if (size !== "default") {
164
+ result += `${mediaQuery} {\n`;
165
+ }
166
+ let cssString = "";
167
+ for (const [key, value] of Object.entries(styleObject[size])) {
168
+ if (value !== undefined && value !== null) {
169
+ cssString += `${key}: ${value};\n`;
170
+ }
171
+ }
172
+ result += `${selector} {
173
+ ${cssString.trim()}
174
+ }\n`;
175
+
176
+ if (size !== "default") {
177
+ result += `}\n`;
178
+ }
179
+ }
180
+ return result;
181
+ }
182
+
183
+ export {
184
+ css,
185
+ generateCSS,
186
+ dimensionWithUnit,
187
+ spacing,
188
+ convertObjectToCssString,
189
+ mediaQueries,
190
+ };
@@ -0,0 +1,5 @@
1
+
2
+ export const handleClickDialogueOverlay = (e, deps) => {
3
+ const { dispatchEvent } = deps;
4
+ dispatchEvent(new CustomEvent('close-dialogue'));
5
+ }
@@ -0,0 +1,24 @@
1
+ export const INITIAL_STATE = Object.freeze({
2
+ //
3
+ });
4
+
5
+ export const toViewData = ({ state, props }) => {
6
+ return {
7
+ isOpen: props.isOpen,
8
+ position: {
9
+ x: 0,
10
+ y: 0
11
+ }
12
+ };
13
+ }
14
+
15
+ export const selectState = ({ state }) => {
16
+ return state;
17
+ }
18
+
19
+ export const setState = (state) => {
20
+ // do doSomething
21
+ }
22
+
23
+
24
+
@@ -0,0 +1,41 @@
1
+ elementName: rtgl-dialog
2
+
3
+ viewDataSchema:
4
+ type: object
5
+
6
+ propsSchema:
7
+ type: object
8
+ properties:
9
+ isOpen:
10
+ type: boolean
11
+
12
+ refs:
13
+ dialog-overlay:
14
+ eventListeners:
15
+ click:
16
+ handler: handleClickDialogueOverlay
17
+
18
+ events:
19
+ close-dialogue:
20
+ type: object
21
+ properties: {}
22
+
23
+ styles:
24
+ # '@keyframes dialog-in':
25
+ # from:
26
+ # opacity: 0
27
+ # transform: scale(0.95)
28
+ # to:
29
+ # opacity: 1
30
+ # transform: scale(1)
31
+
32
+ # '#dialog-container':
33
+ # animation: dialog-in 150ms cubic-bezier(0.16, 1, 0.3, 1)
34
+ # transform-origin: top
35
+
36
+ template:
37
+ - $if isOpen:
38
+ - rtgl-view pos=fix cor=full ah=c av=c:
39
+ - 'rtgl-view#dialog-overlay pos=fix cor=full ah=c av=c bgc=bg op=0.5':
40
+ - rtgl-view#dialog-container pos=fix bw=xs p=lg bgc=bg w=600 br=sm:
41
+ - slot name=content:
@@ -0,0 +1,5 @@
1
+
2
+ export const handleClickPopoverOverlay = (e, deps) => {
3
+ const { dispatchEvent } = deps;
4
+ dispatchEvent(new CustomEvent('click-overlay'));
5
+ }
@@ -0,0 +1,25 @@
1
+
2
+ export const INITIAL_STATE = Object.freeze({
3
+ });
4
+
5
+ export const toViewData = ({ state, props }) => {
6
+ return {
7
+ items: props.items || [],
8
+ isOpen: props.isOpen || false,
9
+ position: props.position || {
10
+ x: 0,
11
+ y: 0,
12
+ },
13
+ };
14
+ }
15
+
16
+ export const selectState = ({ state }) => {
17
+ return state;
18
+ }
19
+
20
+ export const setState = (state) => {
21
+ // do doSomething
22
+ }
23
+
24
+
25
+
@@ -0,0 +1,52 @@
1
+ elementName: rtgl-dropdown-menu
2
+
3
+ viewDataSchema:
4
+ type: object
5
+
6
+ propsSchema:
7
+ type: object
8
+ properties:
9
+ items:
10
+ type: array
11
+ items:
12
+ type: object
13
+ properties:
14
+ label:
15
+ type: string
16
+ type:
17
+ type: string
18
+ enum:
19
+ - label
20
+ - item
21
+ - separator
22
+ isOpen:
23
+ type: boolean
24
+ position:
25
+ type: object
26
+ properties:
27
+ x:
28
+ type: number
29
+ y:
30
+ type: number
31
+
32
+ refs:
33
+ popover:
34
+ eventListeners:
35
+ click-overlay:
36
+ handler: handleClickPopoverOverlay
37
+
38
+ events: {}
39
+
40
+ template:
41
+ - rtgl-popover#popover .isOpen=isOpen .position=position:
42
+ - rtgl-view wh=300 g=xs slot=content bgc=background br=md:
43
+ - $for item, i in items:
44
+ - $if item.type == 'label':
45
+ - rtgl-view w=f ph=lg pv=md:
46
+ - rtgl-text s=sm c=mu-fg: ${item.label}
47
+ $elif item.type == 'item':
48
+ - rtgl-view#option-${i} w=f h-bgc=ac ph=lg pv=md cur=p br=md bgc=mu:
49
+ - rtgl-text: ${item.label}
50
+ $elif item.type == 'separator':
51
+ - rtgl-view w=f h=1 ph=lg mv=md bgc=bo:
52
+
@@ -0,0 +1,30 @@
1
+ export const handleOnMount = (deps) => {
2
+ const { store, props } = deps;
3
+ store.setDefaultValues(props.defaultValues);
4
+ };
5
+
6
+ export const handleActionClick = (e, deps) => {
7
+ const { store, dispatchEvent } = deps;
8
+ const id = e.currentTarget.id.replace("action-", "");
9
+ dispatchEvent(
10
+ new CustomEvent("action-click", {
11
+ detail: {
12
+ actionId: id,
13
+ formValues: store.selectFormValues(),
14
+ },
15
+ }),
16
+ );
17
+ };
18
+
19
+ export const handleInputChange = (e, deps) => {
20
+ const { store } = deps;
21
+ const id = e.currentTarget.id.replace("input-", "");
22
+ // TODO fix double event
23
+ if (id && e.detail.value !== undefined) {
24
+ store.setFormFieldValue({
25
+ // TODO user field name instead of id
26
+ fieldName: id,
27
+ value: e.detail.value,
28
+ });
29
+ }
30
+ };
@@ -0,0 +1,45 @@
1
+ export const INITIAL_STATE = Object.freeze({
2
+ formValues: {}
3
+ });
4
+
5
+ const blacklistedAttrs = ['id', 'class', 'style', 'slot'];
6
+
7
+ const stringifyAttrs = (attrs) => {
8
+ return Object.entries(attrs).filter(([key]) => !blacklistedAttrs.includes(key)).map(([key, value]) => `${key}=${value}`).join(' ');
9
+ }
10
+
11
+ export const toViewData = ({ state, props, attrs }) => {
12
+ const containerAttrString = stringifyAttrs(attrs);
13
+ return {
14
+ containerAttrString,
15
+ title: props.form?.title || '',
16
+ description: props?.form?.description || '',
17
+ fields: props?.form?.fields || [],
18
+ actions: props?.form?.actions || {
19
+ buttons: []
20
+ },
21
+ // TODO fix default values
22
+ formValues: state.formValues,
23
+ };
24
+ }
25
+
26
+ export const selectState = ({ state }) => {
27
+ return state;
28
+ }
29
+
30
+ export const selectFormValues = ({ state }) => {
31
+ return state.formValues;
32
+ }
33
+
34
+ export const setState = (state) => {
35
+ // do doSomething
36
+ }
37
+
38
+ export const setDefaultValues = (state, defaultValues) => {
39
+ state.formValues = defaultValues || {};
40
+ }
41
+
42
+ export const setFormFieldValue = (state, { fieldName, value }) => {
43
+ state.formValues[fieldName] = value;
44
+ }
45
+
@@ -0,0 +1,47 @@
1
+ elementName: rtgl-form
2
+
3
+ viewDataSchema:
4
+ type: object
5
+
6
+ propsSchema:
7
+ type: object
8
+ properties:
9
+ defaultValues:
10
+ type: object
11
+ properties: {}
12
+ form:
13
+ type: object
14
+ properties:
15
+ title:
16
+ type: string
17
+ description:
18
+ type: string
19
+
20
+ refs:
21
+ action-*:
22
+ eventListeners:
23
+ click:
24
+ handler: handleActionClick
25
+ input-*:
26
+ eventListeners:
27
+ input-keydown:
28
+ handler: handleInputChange
29
+
30
+ events: {}
31
+
32
+ template:
33
+ - rtgl-view w=f h=f p=md g=lg ${containerAttrString}:
34
+ - rtgl-view g=sm w=f:
35
+ - rtgl-text s=lg: ${title}
36
+ - rtgl-text c=mu-fg: ${description}
37
+ - rtgl-view g=sm w=f:
38
+ - $for field, i in fields:
39
+ - rtgl-view g=sm w=f:
40
+ - rtgl-text: ${field.label}
41
+ - rtgl-text s=sm c=mu-fg: ${field.description}
42
+ - $if field.inputType == "inputText":
43
+ - rtgl-input#input-${field.id} w=f:
44
+ - rtgl-view g=sm w=f:
45
+ - $for button, i in actions.buttons:
46
+ - rtgl-view d=h ah=e g=sm w=f:
47
+ - rtgl-button#action-${button.id}: ${button.content}