@jay-framework/jay-stack-cli 0.11.0 → 0.13.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/README.md +58 -0
- package/agent-kit-template/INSTRUCTIONS.md +120 -0
- package/agent-kit-template/cli-commands.md +229 -0
- package/agent-kit-template/contracts-and-plugins.md +293 -0
- package/agent-kit-template/jay-html-syntax.md +312 -0
- package/agent-kit-template/project-structure.md +242 -0
- package/agent-kit-template/routing.md +112 -0
- package/dist/index.d.ts +82 -2
- package/dist/index.js +2620 -629
- package/lib/vendors/README.md +510 -0
- package/lib/vendors/figma/README.md +396 -0
- package/package.json +13 -8
- package/test/vendors/figma/fixtures/README.md +164 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# Contracts and Plugins
|
|
2
|
+
|
|
3
|
+
## Discovery: Plugins Index
|
|
4
|
+
|
|
5
|
+
After running `jay-stack agent-kit`, read `materialized-contracts/plugins-index.yaml`:
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
materialized_at: '2026-02-09T...'
|
|
9
|
+
jay_stack_version: '1.0.0'
|
|
10
|
+
plugins:
|
|
11
|
+
- name: wix-stores
|
|
12
|
+
path: ./node_modules/@wix/stores
|
|
13
|
+
contracts:
|
|
14
|
+
- name: product-page
|
|
15
|
+
type: static
|
|
16
|
+
path: ./node_modules/@wix/stores/lib/contracts/product-page.jay-contract
|
|
17
|
+
- name: product-search
|
|
18
|
+
type: static
|
|
19
|
+
path: ./node_modules/@wix/stores/lib/contracts/product-search.jay-contract
|
|
20
|
+
actions:
|
|
21
|
+
- name: searchProducts
|
|
22
|
+
description: Search products with text/filter/sort/pagination
|
|
23
|
+
path: ./node_modules/@wix/stores/lib/actions/search-products.jay-action
|
|
24
|
+
- name: getProductBySlug
|
|
25
|
+
description: Get a single product by URL slug
|
|
26
|
+
path: ./node_modules/@wix/stores/lib/actions/get-product-by-slug.jay-action
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Fields:
|
|
30
|
+
|
|
31
|
+
- `name` — plugin name (use in `plugin="..."` attributes in jay-html)
|
|
32
|
+
- `path` — path to plugin root (relative to project root)
|
|
33
|
+
- `contracts[].name` — contract name (use in `contract="..."` attributes)
|
|
34
|
+
- `contracts[].type` — `static` (defined in source) or `dynamic` (generated at runtime)
|
|
35
|
+
- `contracts[].path` — path to the `.jay-contract` file you can read
|
|
36
|
+
- `actions[].name` — action name (use with `jay-stack action <plugin>/<action>`)
|
|
37
|
+
- `actions[].description` — short description of what the action does
|
|
38
|
+
- `actions[].path` — path to the `.jay-action` file with full input/output schemas
|
|
39
|
+
|
|
40
|
+
## Discovery: Contracts Index
|
|
41
|
+
|
|
42
|
+
`materialized-contracts/contracts-index.yaml` lists all contracts across all plugins:
|
|
43
|
+
|
|
44
|
+
```yaml
|
|
45
|
+
contracts:
|
|
46
|
+
- plugin: wix-stores
|
|
47
|
+
name: product-page
|
|
48
|
+
type: static
|
|
49
|
+
path: ./node_modules/@wix/stores/lib/contracts/product-page.jay-contract
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Reading plugin.yaml
|
|
53
|
+
|
|
54
|
+
Each plugin has a `plugin.yaml` at its root (the `path` from plugins-index):
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
name: wix-stores
|
|
58
|
+
contracts:
|
|
59
|
+
- name: product-page
|
|
60
|
+
contract: product-page.jay-contract
|
|
61
|
+
component: productPage
|
|
62
|
+
description: Complete headless product page with server-side rendering
|
|
63
|
+
- name: product-search
|
|
64
|
+
contract: product-search.jay-contract
|
|
65
|
+
component: productSearch
|
|
66
|
+
description: Headless product search page
|
|
67
|
+
actions:
|
|
68
|
+
- name: searchProducts
|
|
69
|
+
action: search-products.jay-action
|
|
70
|
+
- name: getProductBySlug
|
|
71
|
+
action: get-product-by-slug.jay-action
|
|
72
|
+
- name: getCategories
|
|
73
|
+
action: get-categories.jay-action
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Key fields:
|
|
77
|
+
|
|
78
|
+
- `contracts[].name` — use in `contract="..."` in jay-html
|
|
79
|
+
- `contracts[].description` — what the component does (helps you decide which to use)
|
|
80
|
+
- `actions[].name` — action name (use with `jay-stack action <plugin>/<action>`)
|
|
81
|
+
- `actions[].action` — path to `.jay-action` file with full metadata (description, input/output schemas)
|
|
82
|
+
|
|
83
|
+
## Reading .jay-contract Files
|
|
84
|
+
|
|
85
|
+
Contracts define the data shape (ViewState), interaction points (Refs), and rendering phases.
|
|
86
|
+
|
|
87
|
+
### Tag Types
|
|
88
|
+
|
|
89
|
+
| Type | Purpose | Jay-HTML Usage |
|
|
90
|
+
| --------------------------------- | -------------------------------- | --------------------------------- |
|
|
91
|
+
| `data` | Read-only data value | `{tagName}` binding |
|
|
92
|
+
| `variant` | Enum or boolean for conditions | `if="tagName===value"` |
|
|
93
|
+
| `interactive` | Element ref for user interaction | `ref="tagName"` |
|
|
94
|
+
| `[data, interactive]` | Both data and interactive | `{tagName}` + `ref="tagName"` |
|
|
95
|
+
| `sub-contract` | Nested object | `{parent.child}` |
|
|
96
|
+
| `sub-contract` + `repeated: true` | Array for loops | `forEach="tagName" trackBy="..."` |
|
|
97
|
+
|
|
98
|
+
### Phases
|
|
99
|
+
|
|
100
|
+
| Phase | When | Example |
|
|
101
|
+
| ------------------ | ------------------------ | ----------------------------------------- |
|
|
102
|
+
| `slow` | Build time (SSG) | Product name, description, static content |
|
|
103
|
+
| `fast` | Request time (SSR) | Live pricing, stock status |
|
|
104
|
+
| `fast+interactive` | Request + client updates | Price that updates on variant selection |
|
|
105
|
+
| _(no phase)_ | All phases | Available everywhere |
|
|
106
|
+
|
|
107
|
+
### Props
|
|
108
|
+
|
|
109
|
+
Components that accept props:
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
props:
|
|
113
|
+
- name: productId
|
|
114
|
+
type: string
|
|
115
|
+
required: true
|
|
116
|
+
description: The ID of the product to display
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Use in jay-html: `<jay:contract-name productId="value">`.
|
|
120
|
+
|
|
121
|
+
### Params
|
|
122
|
+
|
|
123
|
+
Page components with dynamic routes:
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
params:
|
|
127
|
+
slug: string
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Params are always strings. Discover values with `jay-stack params`.
|
|
131
|
+
|
|
132
|
+
### Linked Sub-Contracts
|
|
133
|
+
|
|
134
|
+
A sub-contract can reference another contract file:
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
- tag: mediaGallery
|
|
138
|
+
type: sub-contract
|
|
139
|
+
phase: fast+interactive
|
|
140
|
+
link: ./media-gallery # refers to media-gallery.jay-contract in same directory
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Read the linked file to see the nested tags.
|
|
144
|
+
|
|
145
|
+
## Contract Examples
|
|
146
|
+
|
|
147
|
+
**Simple component with props:**
|
|
148
|
+
|
|
149
|
+
```yaml
|
|
150
|
+
name: ProductWidget
|
|
151
|
+
props:
|
|
152
|
+
- name: productId
|
|
153
|
+
type: string
|
|
154
|
+
required: true
|
|
155
|
+
tags:
|
|
156
|
+
- tag: name
|
|
157
|
+
type: data
|
|
158
|
+
dataType: string
|
|
159
|
+
phase: slow
|
|
160
|
+
- tag: price
|
|
161
|
+
type: data
|
|
162
|
+
dataType: number
|
|
163
|
+
phase: slow
|
|
164
|
+
- tag: inStock
|
|
165
|
+
type: variant
|
|
166
|
+
dataType: boolean
|
|
167
|
+
phase: fast+interactive
|
|
168
|
+
- tag: addToCart
|
|
169
|
+
type: interactive
|
|
170
|
+
elementType: HTMLButtonElement
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Page with nested loops:**
|
|
174
|
+
|
|
175
|
+
```yaml
|
|
176
|
+
name: product-page
|
|
177
|
+
tags:
|
|
178
|
+
- tag: productName
|
|
179
|
+
type: data
|
|
180
|
+
dataType: string
|
|
181
|
+
- tag: price
|
|
182
|
+
type: data
|
|
183
|
+
dataType: string
|
|
184
|
+
phase: fast+interactive
|
|
185
|
+
- tag: addToCartButton
|
|
186
|
+
type: interactive
|
|
187
|
+
elementType: HTMLButtonElement
|
|
188
|
+
- tag: options
|
|
189
|
+
type: sub-contract
|
|
190
|
+
repeated: true
|
|
191
|
+
trackBy: _id
|
|
192
|
+
tags:
|
|
193
|
+
- tag: _id
|
|
194
|
+
type: data
|
|
195
|
+
dataType: string
|
|
196
|
+
- tag: name
|
|
197
|
+
type: data
|
|
198
|
+
dataType: string
|
|
199
|
+
- tag: choices
|
|
200
|
+
type: sub-contract
|
|
201
|
+
repeated: true
|
|
202
|
+
trackBy: choiceId
|
|
203
|
+
tags:
|
|
204
|
+
- tag: choiceId
|
|
205
|
+
type: data
|
|
206
|
+
dataType: string
|
|
207
|
+
- tag: name
|
|
208
|
+
type: data
|
|
209
|
+
dataType: string
|
|
210
|
+
- tag: isSelected
|
|
211
|
+
type: variant
|
|
212
|
+
dataType: boolean
|
|
213
|
+
phase: fast+interactive
|
|
214
|
+
- tag: choiceButton
|
|
215
|
+
type: interactive
|
|
216
|
+
elementType: HTMLButtonElement
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Reading .jay-action Files
|
|
220
|
+
|
|
221
|
+
Actions with `.jay-action` files have rich metadata: name, description, and typed input/output schemas. Read the file at the `path` from `plugins-index.yaml` (or from `plugin.yaml`'s `actions[].action` field).
|
|
222
|
+
|
|
223
|
+
### .jay-action Format
|
|
224
|
+
|
|
225
|
+
```yaml
|
|
226
|
+
name: searchProducts
|
|
227
|
+
description: Search products with text/filter/sort/pagination
|
|
228
|
+
|
|
229
|
+
import:
|
|
230
|
+
productCard: product-card.jay-contract
|
|
231
|
+
|
|
232
|
+
inputSchema:
|
|
233
|
+
query: string
|
|
234
|
+
filters?:
|
|
235
|
+
inStockOnly?: boolean
|
|
236
|
+
minPrice?: number
|
|
237
|
+
maxPrice?: number
|
|
238
|
+
sortBy?: enum(relevance | price_asc | price_desc)
|
|
239
|
+
pageSize?: number
|
|
240
|
+
|
|
241
|
+
outputSchema:
|
|
242
|
+
products:
|
|
243
|
+
- productCard
|
|
244
|
+
totalCount: number
|
|
245
|
+
hasMore: boolean
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Jay-Type Notation
|
|
249
|
+
|
|
250
|
+
Schemas use a compact type notation:
|
|
251
|
+
|
|
252
|
+
| Notation | Meaning |
|
|
253
|
+
| --- | --- |
|
|
254
|
+
| `propName: string` | Required string property |
|
|
255
|
+
| `propName?: number` | Optional number property |
|
|
256
|
+
| `propName: boolean` | Required boolean |
|
|
257
|
+
| `propName: enum(a \| b \| c)` | Required enum |
|
|
258
|
+
| `propName:` + nested block | Nested object |
|
|
259
|
+
| `propName:` + `- childProp: type` | Array of objects (YAML list) |
|
|
260
|
+
| `propName: importedName` | Type from `import:` block (references a `.jay-contract`) |
|
|
261
|
+
| `- importedName` | Array of imported type |
|
|
262
|
+
|
|
263
|
+
### Using Action Metadata
|
|
264
|
+
|
|
265
|
+
1. **Discovery** — read `plugins-index.yaml` for action names and descriptions
|
|
266
|
+
2. **Details** — read the `.jay-action` file at the path for full input/output schemas
|
|
267
|
+
3. **Run** — use `jay-stack action <plugin>/<action> --input '{...}'` with a JSON body matching the inputSchema
|
|
268
|
+
|
|
269
|
+
## From Contract to Jay-HTML
|
|
270
|
+
|
|
271
|
+
### Step by step
|
|
272
|
+
|
|
273
|
+
1. **Read the contract** — identify tags, their types, and phases
|
|
274
|
+
2. **Map `data` tags** → `{tagName}` bindings
|
|
275
|
+
3. **Map `variant` tags** → `if="tagName===value"` conditions
|
|
276
|
+
4. **Map `interactive` tags** → `ref="tagName"` on appropriate element types
|
|
277
|
+
5. **Map `sub-contract` + `repeated: true`** → `forEach="tagName" trackBy="..."` loops
|
|
278
|
+
6. **Map nested `sub-contract`** → dotted paths or nested context inside forEach
|
|
279
|
+
7. **Respect phases** — don't assume fast-only data is available at build time
|
|
280
|
+
|
|
281
|
+
### Quick mapping
|
|
282
|
+
|
|
283
|
+
| Contract Tag | Jay-HTML |
|
|
284
|
+
| ---------------------------------------------------------------- | --------------------------------------------- |
|
|
285
|
+
| `{tag: title, type: data}` | `<h1>{title}</h1>` |
|
|
286
|
+
| `{tag: active, type: variant, dataType: boolean}` | `<span if="active">Active</span>` |
|
|
287
|
+
| `{tag: status, type: variant, dataType: "enum (A \| B)"}` | `<div if="status===A">...</div>` |
|
|
288
|
+
| `{tag: btn, type: interactive, elementType: HTMLButtonElement}` | `<button ref="btn">Click</button>` |
|
|
289
|
+
| `{tag: link, type: interactive, elementType: HTMLAnchorElement}` | `<a ref="link">Go</a>` |
|
|
290
|
+
| `{tag: input, type: interactive, elementType: HTMLInputElement}` | `<input ref="input" value="{val}" />` |
|
|
291
|
+
| `{tag: sel, type: interactive, elementType: HTMLSelectElement}` | `<select ref="sel">...</select>` |
|
|
292
|
+
| `{tag: items, type: sub-contract, repeated: true, trackBy: id}` | `<div forEach="items" trackBy="id">...</div>` |
|
|
293
|
+
| `{tag: detail, type: sub-contract}` | `{detail.fieldName}` |
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# Jay-HTML Syntax Reference
|
|
2
|
+
|
|
3
|
+
## File Structure
|
|
4
|
+
|
|
5
|
+
A `.jay-html` file is standard HTML with jay-specific extensions.
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<html>
|
|
9
|
+
<head>
|
|
10
|
+
<!-- Page contract (optional — defines page-level data) -->
|
|
11
|
+
<script type="application/jay-data" contract="./page.jay-contract"></script>
|
|
12
|
+
|
|
13
|
+
<!-- Headless component imports -->
|
|
14
|
+
<script type="application/jay-headless" plugin="..." contract="..." key="..."></script>
|
|
15
|
+
|
|
16
|
+
<!-- Styles -->
|
|
17
|
+
<style>
|
|
18
|
+
/* inline CSS */
|
|
19
|
+
</style>
|
|
20
|
+
<link rel="stylesheet" href="../../styles/theme.css" />
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<!-- Template with data bindings -->
|
|
24
|
+
<h1>{title}</h1>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Data Binding
|
|
30
|
+
|
|
31
|
+
Use `{expression}` to bind contract data:
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<h1>{productName}</h1>
|
|
35
|
+
<!-- simple -->
|
|
36
|
+
<span>{product.price}</span>
|
|
37
|
+
<!-- nested via key -->
|
|
38
|
+
<div style="color: {textColor}">{msg}</div>
|
|
39
|
+
<!-- in attributes -->
|
|
40
|
+
<a href="/products/{slug}">{name}</a>
|
|
41
|
+
<!-- interpolated in attr values -->
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Conditional Rendering
|
|
45
|
+
|
|
46
|
+
Use the `if` attribute:
|
|
47
|
+
|
|
48
|
+
```html
|
|
49
|
+
<span if="inStock">In Stock</span>
|
|
50
|
+
<span if="!inStock">Out of Stock</span>
|
|
51
|
+
<div if="type===physical">Ships to your door</div>
|
|
52
|
+
<div if="type===virtual">Instant download</div>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Rules:
|
|
56
|
+
|
|
57
|
+
- Boolean: `if="tagName"` / `if="!tagName"`
|
|
58
|
+
- Enum variant: `if="tagName===value"` / `if="tagName!==value"` (no quotes around value)
|
|
59
|
+
- Negation: `!` prefix
|
|
60
|
+
|
|
61
|
+
## Loops (forEach / trackBy)
|
|
62
|
+
|
|
63
|
+
Iterate over repeated sub-contracts:
|
|
64
|
+
|
|
65
|
+
```html
|
|
66
|
+
<li forEach="products" trackBy="id">
|
|
67
|
+
<a href="/products/{slug}">
|
|
68
|
+
<div>{name}</div>
|
|
69
|
+
<div>{price}</div>
|
|
70
|
+
</a>
|
|
71
|
+
</li>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- `forEach` — the repeated tag name from the contract
|
|
75
|
+
- `trackBy` — stable unique key for each item (must match contract's trackBy)
|
|
76
|
+
- Inside the loop, bindings resolve to the **current item's** tags
|
|
77
|
+
|
|
78
|
+
**Nested loops:**
|
|
79
|
+
|
|
80
|
+
```html
|
|
81
|
+
<div forEach="options" trackBy="_id">
|
|
82
|
+
<h3>{name}</h3>
|
|
83
|
+
<div forEach="choices" trackBy="choiceId">
|
|
84
|
+
<button ref="choiceButton">{name}</button>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Refs (Interactions)
|
|
90
|
+
|
|
91
|
+
Map elements to contract `interactive` tags using `ref`:
|
|
92
|
+
|
|
93
|
+
```html
|
|
94
|
+
<button ref="addToCart">Add to Cart</button>
|
|
95
|
+
<input value="{quantity}" ref="quantityInput" />
|
|
96
|
+
<a ref="productLink" href="/products/{slug}">{name}</a>
|
|
97
|
+
<select ref="sizeSelector">
|
|
98
|
+
...
|
|
99
|
+
</select>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Match the element type to the contract's `elementType`:
|
|
103
|
+
|
|
104
|
+
- `HTMLButtonElement` → `<button>`
|
|
105
|
+
- `HTMLInputElement` → `<input>`
|
|
106
|
+
- `HTMLAnchorElement` → `<a>`
|
|
107
|
+
- `HTMLSelectElement` → `<select>`
|
|
108
|
+
|
|
109
|
+
**Key-based headless refs** — prefix with the key:
|
|
110
|
+
|
|
111
|
+
```html
|
|
112
|
+
<button ref="rating.submitButton">Submit</button> <button ref="mt.happy">+1 Happy</button>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Refs inside forEach** — use the tag path from the contract:
|
|
116
|
+
|
|
117
|
+
```html
|
|
118
|
+
<div forEach="options" trackBy="_id">
|
|
119
|
+
<div forEach="choices" trackBy="choiceId">
|
|
120
|
+
<button ref="choiceButton">{name}</button>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Headless Components
|
|
126
|
+
|
|
127
|
+
### Pattern 1: Key-Based Import
|
|
128
|
+
|
|
129
|
+
Data merged into parent ViewState under a key. Use when you have **one instance** of a component per page.
|
|
130
|
+
|
|
131
|
+
Declare in `<head>` with a `key` attribute:
|
|
132
|
+
|
|
133
|
+
```html
|
|
134
|
+
<head>
|
|
135
|
+
<script
|
|
136
|
+
type="application/jay-headless"
|
|
137
|
+
plugin="wix-stores"
|
|
138
|
+
contract="product-page"
|
|
139
|
+
key="productPage"
|
|
140
|
+
></script>
|
|
141
|
+
</head>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Access data and refs with the key prefix:
|
|
145
|
+
|
|
146
|
+
```html
|
|
147
|
+
<h1>{productPage.productName}</h1>
|
|
148
|
+
<span>{productPage.price}</span>
|
|
149
|
+
<button ref="productPage.addToCartButton">Add to Cart</button>
|
|
150
|
+
|
|
151
|
+
<!-- Nested repeated sub-contracts -->
|
|
152
|
+
<div forEach="productPage.options" trackBy="_id">
|
|
153
|
+
<h3>{name}</h3>
|
|
154
|
+
<div forEach="choices" trackBy="choiceId">
|
|
155
|
+
<button ref="choiceButton">{name}</button>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Pattern 2: Instance-Based (jay: prefix)
|
|
161
|
+
|
|
162
|
+
Multiple instances with props and inline templates. Use when you need **multiple instances** or need to pass **props**.
|
|
163
|
+
|
|
164
|
+
Declare in `<head>` **without** a `key`:
|
|
165
|
+
|
|
166
|
+
```html
|
|
167
|
+
<head>
|
|
168
|
+
<script
|
|
169
|
+
type="application/jay-headless"
|
|
170
|
+
plugin="product-widget"
|
|
171
|
+
contract="product-widget"
|
|
172
|
+
></script>
|
|
173
|
+
</head>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Use `<jay:contract-name>` tags with props:
|
|
177
|
+
|
|
178
|
+
```html
|
|
179
|
+
<!-- Static props — each instance renders independently -->
|
|
180
|
+
<jay:product-widget productId="prod-1">
|
|
181
|
+
<h3>{name}</h3>
|
|
182
|
+
<div>${price}</div>
|
|
183
|
+
<button ref="addToCart">Add</button>
|
|
184
|
+
</jay:product-widget>
|
|
185
|
+
|
|
186
|
+
<jay:product-widget productId="prod-2">
|
|
187
|
+
<h3>{name}</h3>
|
|
188
|
+
<button ref="addToCart">Add</button>
|
|
189
|
+
</jay:product-widget>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**With forEach** (dynamic props from parent data):
|
|
193
|
+
|
|
194
|
+
```html
|
|
195
|
+
<div forEach="featuredProducts" trackBy="_id">
|
|
196
|
+
<jay:product-widget productId="{_id}">
|
|
197
|
+
<h3>{name}</h3>
|
|
198
|
+
<div>${price}</div>
|
|
199
|
+
<button ref="addToCart">Add</button>
|
|
200
|
+
</jay:product-widget>
|
|
201
|
+
</div>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Inside `<jay:...>`, bindings resolve to **that instance's** contract tags (not the parent).
|
|
205
|
+
|
|
206
|
+
## Page-Level Contract
|
|
207
|
+
|
|
208
|
+
A page can define its own data contract:
|
|
209
|
+
|
|
210
|
+
```html
|
|
211
|
+
<script type="application/jay-data" contract="./page.jay-contract"></script>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Tags from the page contract are bound directly (no key prefix).
|
|
215
|
+
|
|
216
|
+
## Styling
|
|
217
|
+
|
|
218
|
+
**Inline `<style>`:**
|
|
219
|
+
|
|
220
|
+
```html
|
|
221
|
+
<head>
|
|
222
|
+
<style>
|
|
223
|
+
.product-card {
|
|
224
|
+
border: 1px solid #ccc;
|
|
225
|
+
padding: 16px;
|
|
226
|
+
}
|
|
227
|
+
.price {
|
|
228
|
+
font-weight: bold;
|
|
229
|
+
color: #2d7d2d;
|
|
230
|
+
}
|
|
231
|
+
</style>
|
|
232
|
+
</head>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**External stylesheets:**
|
|
236
|
+
|
|
237
|
+
```html
|
|
238
|
+
<link rel="stylesheet" href="../../styles/theme.css" />
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Dynamic style bindings:**
|
|
242
|
+
|
|
243
|
+
```html
|
|
244
|
+
<div style="color: {textColor}; width: {width}px">styled</div>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Complete Example
|
|
248
|
+
|
|
249
|
+
A homepage with key-based and instance-based headless components:
|
|
250
|
+
|
|
251
|
+
```html
|
|
252
|
+
<html>
|
|
253
|
+
<head>
|
|
254
|
+
<script
|
|
255
|
+
type="application/jay-headless"
|
|
256
|
+
plugin="mood-tracker"
|
|
257
|
+
contract="mood-tracker"
|
|
258
|
+
key="mt"
|
|
259
|
+
></script>
|
|
260
|
+
<script
|
|
261
|
+
type="application/jay-headless"
|
|
262
|
+
plugin="product-widget"
|
|
263
|
+
contract="product-widget"
|
|
264
|
+
></script>
|
|
265
|
+
<script type="application/jay-data" contract="./page.jay-contract"></script>
|
|
266
|
+
<style>
|
|
267
|
+
.section {
|
|
268
|
+
margin: 20px 0;
|
|
269
|
+
padding: 10px;
|
|
270
|
+
}
|
|
271
|
+
.product-card {
|
|
272
|
+
border: 1px solid #ccc;
|
|
273
|
+
padding: 10px;
|
|
274
|
+
display: inline-block;
|
|
275
|
+
}
|
|
276
|
+
</style>
|
|
277
|
+
</head>
|
|
278
|
+
<body>
|
|
279
|
+
<h1>Homepage</h1>
|
|
280
|
+
|
|
281
|
+
<!-- Key-based: mood tracker -->
|
|
282
|
+
<div class="section">
|
|
283
|
+
<div>Happy: {mt.happy} <button ref="mt.happy">more</button></div>
|
|
284
|
+
<span if="mt.currentMood === happy">:)</span>
|
|
285
|
+
<span if="mt.currentMood === sad">:(</span>
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
<!-- Instance-based: static product widgets -->
|
|
289
|
+
<div class="section">
|
|
290
|
+
<jay:product-widget productId="1">
|
|
291
|
+
<h3>{name}</h3>
|
|
292
|
+
<div>${price}</div>
|
|
293
|
+
<span if="inStock">In Stock</span>
|
|
294
|
+
<button ref="addToCart">Add</button>
|
|
295
|
+
</jay:product-widget>
|
|
296
|
+
</div>
|
|
297
|
+
|
|
298
|
+
<!-- Instance-based: dynamic from forEach -->
|
|
299
|
+
<div class="section">
|
|
300
|
+
<div forEach="featuredProducts" trackBy="_id">
|
|
301
|
+
<div class="product-card">
|
|
302
|
+
<jay:product-widget productId="{_id}">
|
|
303
|
+
<h3>{name}</h3>
|
|
304
|
+
<div>${price}</div>
|
|
305
|
+
<button ref="addToCart">Add</button>
|
|
306
|
+
</jay:product-widget>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
</body>
|
|
311
|
+
</html>
|
|
312
|
+
```
|