@rettangoli/fe 0.0.7-rc5 → 0.0.7-rc6

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/README.md CHANGED
@@ -14,13 +14,18 @@ A modern frontend framework that uses YAML for view definitions, web components
14
14
 
15
15
  ## Quick Start
16
16
 
17
- **Production usage** (when rtgl is installed globally):
18
17
  ```bash
19
18
  rtgl fe build # Build components
20
19
  rtgl fe watch # Start dev server
21
- rtgl fe scaffold # Create new component
22
20
  ```
23
21
 
22
+ ## Documentation
23
+
24
+ - **[Developer Quickstart](./docs/overview.md)** - Complete introduction and examples
25
+ - **[View System](./docs/view.md)** - Complete YAML syntax
26
+ - **[Store Management](./docs/store.md)** - State patterns
27
+ - **[Event Handlers](./docs/handlers.md)** - Event handling
28
+
24
29
  ## Architecture
25
30
 
26
31
  ### Technology Stack
@@ -84,335 +89,6 @@ src/
84
89
  └── index.js # Main exports
85
90
  ```
86
91
 
87
- # Usage
88
-
89
- ## Component Structure
90
-
91
- Each component consists of three files:
92
-
93
- ```
94
- component-name/
95
- ├── component-name.handlers.js # Event handlers
96
- ├── component-name.store.js # State management
97
- └── component-name.view.yaml # UI structure and styling
98
- ```
99
-
100
-
101
- ## View Layer (.view.yaml)
102
-
103
- Views are written in YAML and compiled to virtual DOM at build time.
104
-
105
- ### Basic HTML Structure
106
-
107
- ```yaml
108
- template:
109
- - div#myid.class1.class2 custom-attribute=abcd:
110
- - rtgl-text: "Hello World"
111
- - rtgl-button: "Click Me"
112
- ```
113
-
114
- Compiles to:
115
- ```html
116
- <div id="myid" class="class1 class2" custom-attribute="abcd">
117
- <rtgl-text>Hello World</rtgl-text>
118
- <rtgl-button>Click Me</rtgl-button>
119
- </div>
120
- ```
121
-
122
- ### Component Definition
123
-
124
- ```yaml
125
- elementName: my-custom-component
126
-
127
- template:
128
- - rtgl-view:
129
- - rtgl-text: "My Component"
130
- ```
131
-
132
- ### Attributes vs Props
133
-
134
- When passing data to components, there's an important distinction:
135
-
136
- ```yaml
137
- template:
138
- - custom-component title=Hello .items=items
139
- ```
140
-
141
- - **Attributes** (`title=Hello`): Always string values, passed as HTML attributes
142
- - **Props** (`.items=items`): JavaScript values from viewData, passed as component properties
143
-
144
- Attributes become HTML attributes, while props are JavaScript objects/arrays/functions passed directly to the component.
145
-
146
- ### Variable Expressions
147
-
148
- Views do not support complex variable expressions like `${myValue || 4}`. All values must be pre-computed in the `toViewData` store function:
149
-
150
- ❌ **Don't do this:**
151
- ```yaml
152
- template:
153
- - rtgl-text: "${user.name || 'Guest'}"
154
- - rtgl-view class="${isActive ? 'active' : 'inactive'}"
155
- ```
156
-
157
- ✅ **Do this instead:**
158
- ```js
159
- // In your .store.js file
160
- export const toViewData = ({ state, props, attrs }) => {
161
- return {
162
- ...state,
163
- displayName: state.user.name || 'Guest',
164
- statusClass: state.isActive ? 'active' : 'inactive'
165
- };
166
- };
167
- ```
168
-
169
- ```yaml
170
- template:
171
- - rtgl-text: "${displayName}"
172
- - rtgl-view class="${statusClass}"
173
- ```
174
-
175
-
176
-
177
- ### Styling
178
-
179
- ```yaml
180
- styles:
181
- '#title':
182
- font-size: 24px
183
- color: blue
184
- '@media (min-width: 768px)':
185
- '#title':
186
- font-size: 32px
187
- ```
188
-
189
- ### Event Handling
190
-
191
- ```yaml
192
- refs:
193
- submitButton:
194
- eventListeners:
195
- click:
196
- handler: handleSubmit
197
-
198
- template:
199
- - rtgl-button#submitButton: "Submit"
200
- ```
201
-
202
- ### Templating with Jempl
203
-
204
- **Loops:**
205
- ```yaml
206
- template:
207
- - rtgl-view:
208
- projects:
209
- $for project, index in projects:
210
- - rtgl-view#project-${project.id}:
211
- - rtgl-text: "${project.name}"
212
- - rtgl-text: "${project.description}"
213
- - rtgl-text: "Item ${index}"
214
- ```
215
-
216
-
217
- #### Props caveats
218
-
219
- ❌ This will not work. Prop references can only be taken from viewDate, not from loop variables
220
-
221
- ```yaml
222
-
223
- template:
224
- - rtgl-view:
225
- - $for project, index in projects:
226
- - rtgl-view#project-${project.id}:
227
- - custom-component .item=project:
228
- ```
229
-
230
- ✅ This is the workaround
231
-
232
- ```yaml
233
- template:
234
- - rtgl-view:
235
- - $for project, index in projects:
236
- - rtgl-view#project-${project.id}:
237
- - custom-component .item=projects[${index}]:
238
- ```
239
-
240
- **Conditionals:**
241
- ```yaml
242
- template:
243
- - rtgl-view:
244
- $if isLoggedIn:
245
- - user-dashboard: []
246
- $else:
247
- - login-form: []
248
-
249
- # Multiple conditions with logical operators
250
- template:
251
- - rtgl-view:
252
- $if user.age >= 18 && user.verified:
253
- - admin-panel: []
254
- $elif user.age >= 13:
255
- - teen-dashboard: []
256
- $else:
257
- - kid-dashboard: []
258
- ```
259
-
260
- For more advanced templating features, see the [Jempl documentation](https://github.com/yuusoft-org/jempl).
261
-
262
- ### Data Schemas
263
-
264
- Define component interfaces with JSON Schema:
265
-
266
- ```yaml
267
- viewDataSchema:
268
- type: object
269
- properties:
270
- title:
271
- type: string
272
- default: "My Component"
273
- items:
274
- type: array
275
- items:
276
- type: object
277
-
278
- propsSchema:
279
- type: object
280
- properties:
281
- onSelect:
282
- type: function
283
-
284
- attrsSchema:
285
- type: object
286
- properties:
287
- variant:
288
- type: string
289
- enum: [primary, secondary]
290
- ```
291
-
292
- ## State Management (.store.js)
293
-
294
- ### Initial State
295
-
296
- ```js
297
- export const INITIAL_STATE = Object.freeze({
298
- title: "My App",
299
- items: [],
300
- loading: false
301
- });
302
- ```
303
-
304
- ### View Data Transformation
305
-
306
- ```js
307
- export const toViewData = ({ state, props, attrs }) => {
308
- return {
309
- ...state,
310
- itemCount: state.items.length,
311
- hasItems: state.items.length > 0
312
- };
313
- };
314
- ```
315
-
316
- ### Selectors
317
-
318
- ```js
319
- export const selectItems = (state) => state.items;
320
- export const selectIsLoading = (state) => state.loading;
321
- ```
322
-
323
- ### Actions
324
-
325
- ```js
326
- export const setLoading = (state, isLoading) => {
327
- state.loading = isLoading; // Immer makes this immutable
328
- };
329
-
330
- export const addItem = (state, item) => {
331
- state.items.push(item);
332
- };
333
-
334
- export const removeItem = (state, itemId) => {
335
- const index = state.items.findIndex(item => item.id === itemId);
336
- if (index !== -1) {
337
- state.items.splice(index, 1);
338
- }
339
- };
340
- ```
341
-
342
- ## Event Handlers (.handlers.js)
343
-
344
- ### Special Handlers
345
-
346
- ```js
347
- // Called when component mounts
348
- export const handleOnMount = (deps) => {
349
- const { store, render } = deps;
350
-
351
- // Load initial data
352
- store.setLoading(true);
353
- loadData().then(data => {
354
- store.setItems(data);
355
- store.setLoading(false);
356
- render();
357
- });
358
-
359
- // Return cleanup function
360
- return () => {
361
- // Cleanup code here
362
- };
363
- };
364
- ```
365
-
366
- ### Event Handlers
367
-
368
- ```js
369
- export const handleSubmit = async (event, deps) => {
370
- const { store, render, attrs, props } = deps;
371
-
372
- event.preventDefault();
373
-
374
- const formData = new FormData(event.target);
375
- const newItem = Object.fromEntries(formData);
376
-
377
- store.addItem(newItem);
378
- render();
379
-
380
- // Dispatch custom event
381
- deps.dispatchEvent(new CustomEvent('item-added', {
382
- detail: { item: newItem }
383
- }));
384
- };
385
-
386
- export const handleItemClick = (event, deps) => {
387
- const itemId = event.target.id.replace('item-', '');
388
- console.log('Item clicked:', itemId);
389
- };
390
- ```
391
-
392
- ### Dependency Injection
393
-
394
- ```js
395
- // In your setup.js file
396
- const componentDependencies = {
397
- apiClient: new ApiClient(),
398
- router: new Router()
399
- };
400
-
401
- export const deps = {
402
- components: componentDependencies,
403
- pages: {}
404
- };
405
- ```
406
-
407
- Access in handlers:
408
- ```js
409
- export const handleLoadData = async (event, deps) => {
410
- const { apiClient } = deps.components;
411
- const data = await apiClient.fetchItems();
412
- // ... handle data
413
- };
414
- ```
415
-
416
92
  ## Configuration
417
93
 
418
94
  Create a `rettangoli.config.yaml` file in your project root:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rettangoli/fe",
3
- "version": "0.0.7-rc5",
3
+ "version": "0.0.7-rc6",
4
4
  "description": "Frontend framework for building reactive web components",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -4,7 +4,7 @@ import { parseView } from "./parser.js";
4
4
  /**
5
5
  * covert this format of json into raw css strings
6
6
  * notice if propoperty starts with \@, it will need to nest it
7
- *
7
+ *
8
8
  ':host':
9
9
  display: contents
10
10
  'button':
@@ -26,8 +26,8 @@ import { parseView } from "./parser.js";
26
26
  '@media (min-width: 768px)':
27
27
  'button':
28
28
  height: 40px
29
- * @param {*} styleObject
30
- * @returns
29
+ * @param {*} styleObject
30
+ * @returns
31
31
  */
32
32
  const yamlToCss = (elementName, styleObject) => {
33
33
  if (!styleObject || typeof styleObject !== "object") {
@@ -313,15 +313,24 @@ class BaseComponent extends HTMLElement {
313
313
  };
314
314
  });
315
315
 
316
- if (this.handlers?.subscriptions) {
317
- this.unsubscribeAll = subscribeAll(this.handlers.subscriptions(deps));
318
- }
316
+ if (this.handlers?.handleBeforeMount) {
317
+ this._unmountCallback = this.handlers?.handleBeforeMount(deps);
319
318
 
320
- if (this.handlers?.handleOnMount) {
321
- this._unmountCallback = this.handlers?.handleOnMount(deps);
319
+ // Validate that handleBeforeMount doesn't return a Promise
320
+ if (this._unmountCallback && typeof this._unmountCallback.then === 'function') {
321
+ throw new Error('handleBeforeMount must be synchronous and cannot return a Promise.');
322
+ }
322
323
  }
323
324
 
324
325
  this.render();
326
+
327
+ if (this.handlers?.handleAfterMount) {
328
+ this.handlers?.handleAfterMount(deps);
329
+ }
330
+
331
+ if (this.handlers?.subscriptions) {
332
+ this.unsubscribeAll = subscribeAll(this.handlers.subscriptions(deps));
333
+ }
325
334
  }
326
335
 
327
336
  disconnectedCallback() {
package/src/parser.js CHANGED
@@ -113,7 +113,7 @@ export const createVirtualDom = ({
113
113
 
114
114
  const entries = Object.entries(item);
115
115
  if (entries.length === 0) {
116
- console.warn("Skipping empty object item:", item);
116
+ // skipping empty object item
117
117
  return null;
118
118
  }
119
119