@jay-framework/compiler-jay-html 0.6.10 → 0.7.0
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/dist/index.d.cts +2 -1
- package/dist/index.js +1048 -281
- package/docs/jay-html-docs.md +136 -0
- package/package.json +8 -8
- package/readme.md +52 -1
- package/test/fixtures/recursive-html/README.md +142 -0
package/docs/jay-html-docs.md
CHANGED
|
@@ -176,3 +176,139 @@ Since Jay uses immutable view state, the `trackBy` attribute is essential for pr
|
|
|
176
176
|
```
|
|
177
177
|
|
|
178
178
|
In this example, each `ProductCard` component receives the entire product object as its context.
|
|
179
|
+
|
|
180
|
+
## Recursive Rendering
|
|
181
|
+
|
|
182
|
+
Jay-HTML supports recursive component structures where elements can reference themselves, enabling the rendering of tree-like and nested data structures.
|
|
183
|
+
|
|
184
|
+
### Defining Recursive Types
|
|
185
|
+
|
|
186
|
+
Recursive types are defined in the data contract using the `$/data` or `$/data/path` syntax:
|
|
187
|
+
|
|
188
|
+
```yaml
|
|
189
|
+
data:
|
|
190
|
+
title: string
|
|
191
|
+
tree:
|
|
192
|
+
id: string
|
|
193
|
+
name: string
|
|
194
|
+
children: $/data/tree
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The `$/data/tree` reference creates a recursive type where `children` has the same type as `tree` itself.
|
|
198
|
+
|
|
199
|
+
### Nested Recursive References
|
|
200
|
+
|
|
201
|
+
You can reference nested types deeper in the data structure:
|
|
202
|
+
|
|
203
|
+
```yaml
|
|
204
|
+
data:
|
|
205
|
+
root:
|
|
206
|
+
value: number
|
|
207
|
+
nested:
|
|
208
|
+
id: string
|
|
209
|
+
children: $/data/root/nested
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
This creates a recursive type at `root.nested` where `children` has the same type as the parent `nested` object.
|
|
213
|
+
|
|
214
|
+
### Creating Recursive Regions
|
|
215
|
+
|
|
216
|
+
Use the `ref` attribute to mark an element as a recursive region, then use `<recurse>` to trigger recursion:
|
|
217
|
+
|
|
218
|
+
```html
|
|
219
|
+
<div class="tree-node" ref="treeNode">
|
|
220
|
+
<div class="node-name">{name}</div>
|
|
221
|
+
<div if="children">
|
|
222
|
+
<recurse ref="treeNode" accessor="children" />
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
The compiler generates a recursive function for the referenced element, and `<recurse>` calls that function with the specified data.
|
|
228
|
+
|
|
229
|
+
### Context Switching with `<with-data>`
|
|
230
|
+
|
|
231
|
+
When your recursive data is nested within a parent structure, use `<with-data>` to switch the view state context:
|
|
232
|
+
|
|
233
|
+
```yaml
|
|
234
|
+
data:
|
|
235
|
+
title: string
|
|
236
|
+
description: string
|
|
237
|
+
btree:
|
|
238
|
+
value: number
|
|
239
|
+
left: $/data/btree
|
|
240
|
+
right: $/data/btree
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
```html
|
|
244
|
+
<div class="tree-container">
|
|
245
|
+
<h1>{title}</h1>
|
|
246
|
+
<p>{description}</p>
|
|
247
|
+
<with-data accessor="btree">
|
|
248
|
+
<div class="tree-node" ref="treeNode">
|
|
249
|
+
<div>{value}</div>
|
|
250
|
+
<div if="left">
|
|
251
|
+
<recurse ref="treeNode" accessor="left" />
|
|
252
|
+
</div>
|
|
253
|
+
<div if="right">
|
|
254
|
+
<recurse ref="treeNode" accessor="right" />
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
</with-data>
|
|
258
|
+
</div>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
The `<with-data>` element:
|
|
262
|
+
|
|
263
|
+
- Accepts an `accessor` attribute specifying a property path
|
|
264
|
+
- Changes the view state context for its children
|
|
265
|
+
- Must have exactly one child element
|
|
266
|
+
- Works with both object and array types
|
|
267
|
+
|
|
268
|
+
This allows the recursive region to operate on a consistent type (`btree`) whether it's at the root level or in a recursive call.
|
|
269
|
+
|
|
270
|
+
### Identity Accessor with `forEach`
|
|
271
|
+
|
|
272
|
+
Within a `<with-data>` context, you can use `forEach="."` to iterate over the current context:
|
|
273
|
+
|
|
274
|
+
```html
|
|
275
|
+
<with-data accessor="tree">
|
|
276
|
+
<ul ref="menuItem">
|
|
277
|
+
<li forEach="." trackBy="id">
|
|
278
|
+
<span>{name}</span>
|
|
279
|
+
<div if="children">
|
|
280
|
+
<recurse ref="menuItem" accessor="children" />
|
|
281
|
+
</div>
|
|
282
|
+
</li>
|
|
283
|
+
</ul>
|
|
284
|
+
</with-data>
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
The `forEach="."` syntax means "iterate over the current context", which is useful when `<with-data>` has already narrowed the context to an array.
|
|
288
|
+
|
|
289
|
+
### Recursion Guards
|
|
290
|
+
|
|
291
|
+
Recursion requires proper guards to prevent infinite loops:
|
|
292
|
+
|
|
293
|
+
- **Recursion with accessor** (e.g., `<recurse accessor="children"/>`) uses `withData` which includes a built-in null check
|
|
294
|
+
- **Recursion without accessor** (e.g., `<recurse/>` in a forEach loop) must be inside a `forEach` or conditional to prevent infinite recursion
|
|
295
|
+
|
|
296
|
+
Example with forEach (no accessor needed):
|
|
297
|
+
|
|
298
|
+
```html
|
|
299
|
+
<ul ref="list">
|
|
300
|
+
<li forEach="items" trackBy="id">
|
|
301
|
+
{text}
|
|
302
|
+
<recurse ref="list" />
|
|
303
|
+
</li>
|
|
304
|
+
</ul>
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Example with accessor (built-in guard):
|
|
308
|
+
|
|
309
|
+
```html
|
|
310
|
+
<div ref="node">
|
|
311
|
+
{value}
|
|
312
|
+
<recurse ref="node" accessor="child" />
|
|
313
|
+
</div>
|
|
314
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/compiler-jay-html",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
},
|
|
35
35
|
"author": "",
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@jay-framework/compiler-analyze-exported-types": "^0.
|
|
38
|
-
"@jay-framework/compiler-shared": "^0.
|
|
39
|
-
"@jay-framework/component": "^0.
|
|
40
|
-
"@jay-framework/runtime": "^0.
|
|
41
|
-
"@jay-framework/secure": "^0.
|
|
37
|
+
"@jay-framework/compiler-analyze-exported-types": "^0.7.0",
|
|
38
|
+
"@jay-framework/compiler-shared": "^0.7.0",
|
|
39
|
+
"@jay-framework/component": "^0.7.0",
|
|
40
|
+
"@jay-framework/runtime": "^0.7.0",
|
|
41
|
+
"@jay-framework/secure": "^0.7.0",
|
|
42
42
|
"@types/js-yaml": "^4.0.9",
|
|
43
43
|
"change-case": "^4.1.2",
|
|
44
44
|
"js-yaml": "^4.1.0",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@caiogondim/strip-margin": "^1.0.0",
|
|
53
|
-
"@jay-framework/4-react": "^0.
|
|
54
|
-
"@jay-framework/dev-environment": "^0.
|
|
53
|
+
"@jay-framework/4-react": "^0.7.0",
|
|
54
|
+
"@jay-framework/dev-environment": "^0.7.0",
|
|
55
55
|
"@testing-library/jest-dom": "^6.2.0",
|
|
56
56
|
"@types/js-beautify": "^1",
|
|
57
57
|
"@types/node": "^20.11.5",
|
package/readme.md
CHANGED
|
@@ -26,7 +26,7 @@ Here's a simple Jay-HTML file:
|
|
|
26
26
|
|
|
27
27
|
## Key Differences from Standard HTML
|
|
28
28
|
|
|
29
|
-
Jay-HTML extends HTML with
|
|
29
|
+
Jay-HTML extends HTML with eight main features:
|
|
30
30
|
|
|
31
31
|
1. **Component and type imports** - Import reusable components and type definitions
|
|
32
32
|
2. **Data contract definition** - Define component interfaces using YAML
|
|
@@ -35,6 +35,7 @@ Jay-HTML extends HTML with seven main features:
|
|
|
35
35
|
5. **Data binding** - Bind component data to HTML using `{}` syntax
|
|
36
36
|
6. **Conditional rendering** - Show/hide elements based on conditions
|
|
37
37
|
7. **List rendering** - Iterate over arrays with `forEach` and `trackBy`
|
|
38
|
+
8. **Async rendering** - Handle promises with loading, resolved, and error states
|
|
38
39
|
|
|
39
40
|
## Component Import System
|
|
40
41
|
|
|
@@ -104,6 +105,56 @@ Headless components provide only the contract and logic:
|
|
|
104
105
|
></script>
|
|
105
106
|
```
|
|
106
107
|
|
|
108
|
+
## Async Rendering
|
|
109
|
+
|
|
110
|
+
Jay-HTML provides built-in support for handling asynchronous data with dedicated conditional rendering attributes. Async data can be primitive types (like strings or numbers), objects, or arrays.
|
|
111
|
+
|
|
112
|
+
### Async Data Declaration
|
|
113
|
+
|
|
114
|
+
```html
|
|
115
|
+
<script type="application/yaml-jay">
|
|
116
|
+
data:
|
|
117
|
+
title: string
|
|
118
|
+
async status: string <!-- Primitive type -->
|
|
119
|
+
async userProfile: <!-- Object type -->
|
|
120
|
+
name: string
|
|
121
|
+
email: string
|
|
122
|
+
async posts: <!-- Array type -->
|
|
123
|
+
- id: string
|
|
124
|
+
title: string
|
|
125
|
+
</script>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Async State Handling
|
|
129
|
+
|
|
130
|
+
```html
|
|
131
|
+
<body>
|
|
132
|
+
<h1>{title}</h1>
|
|
133
|
+
|
|
134
|
+
<!-- Loading state -->
|
|
135
|
+
<div when-loading="userProfile">Loading user profile...</div>
|
|
136
|
+
|
|
137
|
+
<!-- Success state -->
|
|
138
|
+
<div when-resolved="userProfile">
|
|
139
|
+
<h2>Welcome, {name}!</h2>
|
|
140
|
+
<p>{email}</p>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<!-- Error state -->
|
|
144
|
+
<div when-rejected="userProfile">
|
|
145
|
+
Failed to load profile: {message}
|
|
146
|
+
<!-- Error properties available: {name}, {message}, {stack} -->
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<!-- Async arrays -->
|
|
150
|
+
<div when-resolved="posts">
|
|
151
|
+
<article forEach="." trackBy="id">
|
|
152
|
+
<h3>{title}</h3>
|
|
153
|
+
</article>
|
|
154
|
+
</div>
|
|
155
|
+
</body>
|
|
156
|
+
```
|
|
157
|
+
|
|
107
158
|
## Documentation
|
|
108
159
|
|
|
109
160
|
### Jay-HTML Syntax
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Recursive Jay-HTML Test Fixtures
|
|
2
|
+
|
|
3
|
+
These fixtures test the new recursive jay-html feature as designed in Design Log 46.
|
|
4
|
+
|
|
5
|
+
## Feature Overview
|
|
6
|
+
|
|
7
|
+
The recursive jay-html feature allows marking HTML subtrees as recursive regions using:
|
|
8
|
+
|
|
9
|
+
- `ref="regionName"` on an element to mark it as a recursive region
|
|
10
|
+
- `<recurse ref="regionName" />` to trigger recursion at that point
|
|
11
|
+
- `array<$/data>` type syntax to define recursive type references
|
|
12
|
+
|
|
13
|
+
## Test Cases
|
|
14
|
+
|
|
15
|
+
### 1. `simple-tree/`
|
|
16
|
+
|
|
17
|
+
**Tests:** Basic direct recursion with a simple tree structure
|
|
18
|
+
|
|
19
|
+
**Key Features:**
|
|
20
|
+
|
|
21
|
+
- Direct recursion: `children: array<$/data>`
|
|
22
|
+
- Recursive region marked with `ref="treeNode"`
|
|
23
|
+
- Recursion triggered in `forEach` with `<recurse ref="treeNode" />`
|
|
24
|
+
- Has a ref within the recursive region (`nodeHeader`)
|
|
25
|
+
- Conditional rendering with `if="open"`
|
|
26
|
+
|
|
27
|
+
### 2. `indirect-recursion/`
|
|
28
|
+
|
|
29
|
+
**Tests:** Indirect recursion through a nested container object
|
|
30
|
+
|
|
31
|
+
**Key Features:**
|
|
32
|
+
|
|
33
|
+
- Indirect recursion: `submenu.items: array<$/data>`
|
|
34
|
+
- The recursion doesn't happen directly on the root type
|
|
35
|
+
- Demonstrates type generation for nested containers with recursive arrays
|
|
36
|
+
- Combined conditional: `if="hasSubmenu && isOpen"`
|
|
37
|
+
|
|
38
|
+
### 3. `tree-with-conditional/`
|
|
39
|
+
|
|
40
|
+
**Tests:** Recursive structure with enum types and complex conditionals
|
|
41
|
+
|
|
42
|
+
**Key Features:**
|
|
43
|
+
|
|
44
|
+
- Enum type usage: `type: enum (file | folder)`
|
|
45
|
+
- Conditional recursion based on enum: `if="type == folder && isExpanded"`
|
|
46
|
+
- Demonstrates enum code generation in recursive contexts
|
|
47
|
+
- Shows how enum comparisons work in recursive regions
|
|
48
|
+
|
|
49
|
+
### 4. `nested-comments/`
|
|
50
|
+
|
|
51
|
+
**Tests:** Classic nested comments/replies pattern
|
|
52
|
+
|
|
53
|
+
**Key Features:**
|
|
54
|
+
|
|
55
|
+
- Simple direct recursion for comment threads
|
|
56
|
+
- Button ref within recursive region (`toggleReplies`)
|
|
57
|
+
- Demonstrates real-world use case (threaded comments)
|
|
58
|
+
- Clean parent-child relationship pattern
|
|
59
|
+
|
|
60
|
+
### 5. `linked-list/`
|
|
61
|
+
|
|
62
|
+
**Tests:** Recursive conditional without arrays (single optional child)
|
|
63
|
+
|
|
64
|
+
**Key Features:**
|
|
65
|
+
|
|
66
|
+
- Non-array recursion: `next: $/data` (not an array!)
|
|
67
|
+
- Linked list pattern with optional next node
|
|
68
|
+
- Recursion guard is conditional only (no forEach)
|
|
69
|
+
- Type generation: `next: LinkedListViewState | null`
|
|
70
|
+
- Demonstrates single-child recursive structures
|
|
71
|
+
|
|
72
|
+
### 6. `binary-tree/`
|
|
73
|
+
|
|
74
|
+
**Tests:** Multiple recursive conditionals (left/right children)
|
|
75
|
+
|
|
76
|
+
**Key Features:**
|
|
77
|
+
|
|
78
|
+
- Multiple non-array recursive references: `left: $/data`, `right: $/data`
|
|
79
|
+
- Binary tree pattern with optional left and right children
|
|
80
|
+
- Two separate conditional recursions in same region
|
|
81
|
+
- Type generation: nullable optional children
|
|
82
|
+
- Demonstrates branching recursive structures
|
|
83
|
+
|
|
84
|
+
## Expected Generated Code Pattern
|
|
85
|
+
|
|
86
|
+
Each test expects code generation following this pattern:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// 1. Recursive ViewState type with self-reference
|
|
90
|
+
export interface TreeViewState {
|
|
91
|
+
// ... properties
|
|
92
|
+
children: Array<TreeViewState>; // Self-referencing array
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 2. Internal recursive render function
|
|
96
|
+
function renderRecursiveRegion_<refName>(data: TreeViewState) {
|
|
97
|
+
return e('div', {}, [
|
|
98
|
+
// ... element structure
|
|
99
|
+
forEach(
|
|
100
|
+
(vs) => vs.children,
|
|
101
|
+
(childData) => {
|
|
102
|
+
return e('li', {}, [
|
|
103
|
+
renderRecursiveRegion_<refName>(childData), // Recursive call
|
|
104
|
+
]);
|
|
105
|
+
},
|
|
106
|
+
'trackByKey',
|
|
107
|
+
),
|
|
108
|
+
]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 3. Main render function that calls the recursive function
|
|
112
|
+
const render = (viewState: TreeViewState) =>
|
|
113
|
+
ConstructContext.withRootContext(viewState, refManager, () =>
|
|
114
|
+
e('div', {}, [
|
|
115
|
+
// Non-recursive content
|
|
116
|
+
renderRecursiveRegion_<refName>(viewState), // Initial call
|
|
117
|
+
]),
|
|
118
|
+
) as TreeElement;
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Validation Rules Being Tested
|
|
122
|
+
|
|
123
|
+
1. **Recursion Guards**: Each `<recurse>` must be inside a `forEach` or conditional
|
|
124
|
+
- forEach guard: Tests 1-4 (array-based recursion)
|
|
125
|
+
- Conditional guard: Tests 5-6 (non-array recursion)
|
|
126
|
+
2. **Type Consistency**: The array/property type must match the recursive type reference
|
|
127
|
+
- Array recursion: `array<$/data>` → `Array<ViewState>`
|
|
128
|
+
- Single recursion: `$/data` → `ViewState | null`
|
|
129
|
+
3. **Valid Region References**: `<recurse ref="name">` must reference an existing `ref="name"` element
|
|
130
|
+
4. **Descendant Requirement**: `<recurse>` must be a descendant of the referenced region
|
|
131
|
+
|
|
132
|
+
## Not Yet Tested
|
|
133
|
+
|
|
134
|
+
These fixtures do **not** yet test:
|
|
135
|
+
|
|
136
|
+
- Multiple recursive regions in one component
|
|
137
|
+
- Referencing nested types (`array<$/data/metadata>`)
|
|
138
|
+
- Error cases (missing guards, invalid references, etc.)
|
|
139
|
+
- React target generation for recursive structures
|
|
140
|
+
- Contract file recursion with `link: $/`
|
|
141
|
+
|
|
142
|
+
Additional test fixtures should be added for these scenarios.
|