@relax.js/core 1.0.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.
Files changed (194) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +188 -0
  3. package/dist/DataLoader.d.ts +51 -0
  4. package/dist/DependencyInjection.d.ts +271 -0
  5. package/dist/DependencyInjectionOld.d.ts +35 -0
  6. package/dist/Metadata.d.ts +8 -0
  7. package/dist/SequentialId.d.ts +47 -0
  8. package/dist/_alt/src/MustardEngine.d.ts +30 -0
  9. package/dist/_alt/src/MustardParser.d.ts +63 -0
  10. package/dist/_alt/src/MustardParser2.d.ts +35 -0
  11. package/dist/_alt/src/pipes.d.ts +93 -0
  12. package/dist/_alt/src/template.d.ts +166 -0
  13. package/dist/_alt/src/tools.d.ts +4 -0
  14. package/dist/_alt/tests/pipes.tests.d.ts +1 -0
  15. package/dist/_alt/tests/template.tests.d.ts +1 -0
  16. package/dist/_alt/vitest.config.d.ts +2 -0
  17. package/dist/collections/Index.d.ts +1 -0
  18. package/dist/collections/LinkedList.d.ts +75 -0
  19. package/dist/collections/Pager.d.ts +15 -0
  20. package/dist/collections/index.js +2 -0
  21. package/dist/collections/index.js.map +7 -0
  22. package/dist/collections/index.mjs +2 -0
  23. package/dist/collections/index.mjs.map +7 -0
  24. package/dist/components/Table.d.ts +13 -0
  25. package/dist/components/index.d.ts +4 -0
  26. package/dist/components/index.js +128 -0
  27. package/dist/components/index.js.map +7 -0
  28. package/dist/components/index.mjs +128 -0
  29. package/dist/components/index.mjs.map +7 -0
  30. package/dist/components/lists/Table.d.ts +59 -0
  31. package/dist/components/lists/TreeView.d.ts +67 -0
  32. package/dist/components/lists/index.d.ts +2 -0
  33. package/dist/components/loader.d.ts +60 -0
  34. package/dist/components/menus/MenuItem.d.ts +30 -0
  35. package/dist/components/menus/TopMenu.d.ts +16 -0
  36. package/dist/components/menus/index.d.ts +2 -0
  37. package/dist/components/panels/tabs.d.ts +15 -0
  38. package/dist/di/index.d.ts +1 -0
  39. package/dist/di/index.js +2 -0
  40. package/dist/di/index.js.map +7 -0
  41. package/dist/di/index.mjs +2 -0
  42. package/dist/di/index.mjs.map +7 -0
  43. package/dist/elements/CopyAttributes.d.ts +2 -0
  44. package/dist/elements/dom.d.ts +18 -0
  45. package/dist/elements/index.d.ts +2 -0
  46. package/dist/elements/index.js +2 -0
  47. package/dist/elements/index.js.map +7 -0
  48. package/dist/elements/index.mjs +2 -0
  49. package/dist/elements/index.mjs.map +7 -0
  50. package/dist/errors.d.ts +71 -0
  51. package/dist/forms/FormReader.d.ts +182 -0
  52. package/dist/forms/FormValidator.d.ts +114 -0
  53. package/dist/forms/ValidationRules.d.ts +103 -0
  54. package/dist/forms/index.d.ts +4 -0
  55. package/dist/forms/index.js +2 -0
  56. package/dist/forms/index.js.map +7 -0
  57. package/dist/forms/index.mjs +2 -0
  58. package/dist/forms/index.mjs.map +7 -0
  59. package/dist/forms/setFormData.d.ts +49 -0
  60. package/dist/getParentComponent.d.ts +43 -0
  61. package/dist/html/TableRenderer.d.ts +44 -0
  62. package/dist/html/TreeBinder.d.ts +9 -0
  63. package/dist/html/html.d.ts +55 -0
  64. package/dist/html/index.d.ts +5 -0
  65. package/dist/html/index.js +2 -0
  66. package/dist/html/index.js.map +7 -0
  67. package/dist/html/index.mjs +2 -0
  68. package/dist/html/index.mjs.map +7 -0
  69. package/dist/html/template.d.ts +167 -0
  70. package/dist/http/ServerSentEvents.d.ts +116 -0
  71. package/dist/http/SimpleWebSocket.d.ts +153 -0
  72. package/dist/http/http.d.ts +177 -0
  73. package/dist/http/index.d.ts +3 -0
  74. package/dist/http/index.js +2 -0
  75. package/dist/http/index.js.map +7 -0
  76. package/dist/http/index.mjs +2 -0
  77. package/dist/http/index.mjs.map +7 -0
  78. package/dist/i18n/i18n.d.ts +105 -0
  79. package/dist/i18n/icu.d.ts +64 -0
  80. package/dist/i18n/index.d.ts +2 -0
  81. package/dist/i18n/index.js +2 -0
  82. package/dist/i18n/index.js.map +7 -0
  83. package/dist/i18n/index.mjs +2 -0
  84. package/dist/i18n/index.mjs.map +7 -0
  85. package/dist/index.d.ts +16 -0
  86. package/dist/index.js +5 -0
  87. package/dist/index.js.map +7 -0
  88. package/dist/index.mjs +5 -0
  89. package/dist/index.mjs.map +7 -0
  90. package/dist/lib/DataLoader.d.ts +51 -0
  91. package/dist/lib/DependencyInjection.d.ts +271 -0
  92. package/dist/lib/InvokeParent.d.ts +10 -0
  93. package/dist/lib/Pipes.d.ts +236 -0
  94. package/dist/lib/SequentialId.d.ts +47 -0
  95. package/dist/lib/collections/Index.d.ts +1 -0
  96. package/dist/lib/collections/LinkedList.d.ts +75 -0
  97. package/dist/lib/collections/Pager.d.ts +15 -0
  98. package/dist/lib/collections/TableRenderer.d.ts +44 -0
  99. package/dist/lib/di/index.d.ts +1 -0
  100. package/dist/lib/elements/CopyAttributes.d.ts +2 -0
  101. package/dist/lib/elements/dom.d.ts +18 -0
  102. package/dist/lib/elements/index.d.ts +2 -0
  103. package/dist/lib/errors.d.ts +71 -0
  104. package/dist/lib/forms/FormReader.d.ts +182 -0
  105. package/dist/lib/forms/FormValidator.d.ts +114 -0
  106. package/dist/lib/forms/ValidationRules.d.ts +103 -0
  107. package/dist/lib/forms/index.d.ts +4 -0
  108. package/dist/lib/forms/setFormData.d.ts +49 -0
  109. package/dist/lib/getParentComponent.d.ts +43 -0
  110. package/dist/lib/html/TableRenderer.d.ts +44 -0
  111. package/dist/lib/html/TreeBinder.d.ts +9 -0
  112. package/dist/lib/html/html.d.ts +55 -0
  113. package/dist/lib/html/html2.d.ts +55 -0
  114. package/dist/lib/html/index.d.ts +5 -0
  115. package/dist/lib/html/m.d.ts +167 -0
  116. package/dist/lib/html/m2.d.ts +8 -0
  117. package/dist/lib/html/m3.d.ts +0 -0
  118. package/dist/lib/html/template.d.ts +167 -0
  119. package/dist/lib/http/HttpClient.d.ts +153 -0
  120. package/dist/lib/http/ServerSentEvents.d.ts +116 -0
  121. package/dist/lib/http/SimpleWebSocket.d.ts +153 -0
  122. package/dist/lib/http/http.d.ts +177 -0
  123. package/dist/lib/http/index.d.ts +3 -0
  124. package/dist/lib/i18n/i18n.d.ts +105 -0
  125. package/dist/lib/i18n/icu.d.ts +64 -0
  126. package/dist/lib/i18n/index.d.ts +2 -0
  127. package/dist/lib/index.d.ts +16 -0
  128. package/dist/lib/routing/NavigateRouteEvent.d.ts +52 -0
  129. package/dist/lib/routing/RouteLink.d.ts +7 -0
  130. package/dist/lib/routing/Routing.d.ts +270 -0
  131. package/dist/lib/routing/RoutingTarget.d.ts +22 -0
  132. package/dist/lib/routing/index.d.ts +7 -0
  133. package/dist/lib/routing/navigation.d.ts +70 -0
  134. package/dist/lib/routing/routeMatching.d.ts +21 -0
  135. package/dist/lib/routing/routeTargetRegistry.d.ts +23 -0
  136. package/dist/lib/routing/types.d.ts +130 -0
  137. package/dist/lib/templates/NodeTemplate.d.ts +38 -0
  138. package/dist/lib/templates/accessorParser.d.ts +87 -0
  139. package/dist/lib/templates/parseTemplate.d.ts +6 -0
  140. package/dist/lib/templates/tokenizer.d.ts +76 -0
  141. package/dist/lib/tools.d.ts +30 -0
  142. package/dist/lib/utils/index.d.ts +4 -0
  143. package/dist/pipes.d.ts +236 -0
  144. package/dist/routing/NavigateRouteEvent.d.ts +52 -0
  145. package/dist/routing/RouteLink.d.ts +7 -0
  146. package/dist/routing/RoutingTarget.d.ts +22 -0
  147. package/dist/routing/index.d.ts +7 -0
  148. package/dist/routing/index.js +5 -0
  149. package/dist/routing/index.js.map +7 -0
  150. package/dist/routing/index.mjs +5 -0
  151. package/dist/routing/index.mjs.map +7 -0
  152. package/dist/routing/navigation.d.ts +70 -0
  153. package/dist/routing/routeMatching.d.ts +21 -0
  154. package/dist/routing/routeTargetRegistry.d.ts +23 -0
  155. package/dist/routing/types.d.ts +130 -0
  156. package/dist/templates/NodeTemplate.d.ts +38 -0
  157. package/dist/templates/accessorParser.d.ts +87 -0
  158. package/dist/templates/parseTemplate.d.ts +6 -0
  159. package/dist/templates/tokenizer.d.ts +76 -0
  160. package/dist/tools.d.ts +30 -0
  161. package/dist/utils/index.d.ts +4 -0
  162. package/dist/utils/index.js +2 -0
  163. package/dist/utils/index.js.map +7 -0
  164. package/dist/utils/index.mjs +2 -0
  165. package/dist/utils/index.mjs.map +7 -0
  166. package/docs/Architecture.md +333 -0
  167. package/docs/DependencyInjection.md +237 -0
  168. package/docs/Errors.md +87 -0
  169. package/docs/GettingStarted.md +231 -0
  170. package/docs/Pipes.md +211 -0
  171. package/docs/Translations.md +312 -0
  172. package/docs/WhyRelaxjs.md +336 -0
  173. package/docs/elements/dom.md +102 -0
  174. package/docs/forms/creating-form-components.md +924 -0
  175. package/docs/forms/form-api.md +94 -0
  176. package/docs/forms/forms.md +99 -0
  177. package/docs/forms/patterns.md +311 -0
  178. package/docs/forms/reading-writing.md +365 -0
  179. package/docs/forms/validation.md +351 -0
  180. package/docs/html/TableRenderer.md +292 -0
  181. package/docs/html/html.md +175 -0
  182. package/docs/html/index.md +54 -0
  183. package/docs/html/template.md +422 -0
  184. package/docs/http/HttpClient.md +459 -0
  185. package/docs/http/ServerSentEvents.md +184 -0
  186. package/docs/http/index.md +109 -0
  187. package/docs/i18n/i18n.md +309 -0
  188. package/docs/i18n/intl-standard.md +178 -0
  189. package/docs/routing/RouteLink.md +98 -0
  190. package/docs/routing/Routing.md +332 -0
  191. package/docs/routing/RoutingTarget.md +136 -0
  192. package/docs/routing/layouts.md +207 -0
  193. package/docs/utilities.md +143 -0
  194. package/package.json +93 -0
@@ -0,0 +1,422 @@
1
+ # compileTemplate
2
+
3
+ A full-featured template engine for web components. Compiles HTML templates with mustache-style expressions into reactive render functions.
4
+
5
+ ## Features
6
+
7
+ - **Text Interpolation** - `{{expression}}` syntax with nested path support
8
+ - **Attribute Binding** - Dynamic attribute values
9
+ - **Pipes** - Transform values with `{{value | uppercase | shorten:20}}`
10
+ - **Function Calls** - `{{formatDate(createdAt)}}`, `{{add(5, 3)}}`
11
+ - **Array Indexing** - `{{items[0]}}`, `{{users[1].name}}`
12
+ - **Conditional Rendering** - `if` and `unless` attributes
13
+ - **Loop Rendering** - `loop="item in items"` directive
14
+ - **Type-Safe** - Full TypeScript support
15
+ - **Performance Optimized** - Expression caching and memoized re-renders
16
+
17
+ ## Basic Usage
18
+
19
+ ```typescript
20
+ import { compileTemplate } from 'relaxjs/html';
21
+
22
+ // 1. Compile the template
23
+ const { content, render } = compileTemplate(`
24
+ <div class="user-card">
25
+ <h2>Hello, {{user.name}}</h2>
26
+ <p if="user.isAdmin">Admin User</p>
27
+ <ul>
28
+ <li loop="permission in user.permissions">{{permission}}</li>
29
+ </ul>
30
+ </div>
31
+ `);
32
+
33
+ // 2. Render with data
34
+ render({
35
+ user: {
36
+ name: 'John Smith',
37
+ isAdmin: true,
38
+ permissions: ['read', 'write', 'delete']
39
+ }
40
+ });
41
+
42
+ // 3. Append to DOM
43
+ document.getElementById('app').appendChild(content);
44
+
45
+ // 4. Update with new data (DOM updates automatically)
46
+ render({
47
+ user: {
48
+ name: 'Jane Doe',
49
+ isAdmin: false,
50
+ permissions: ['read']
51
+ }
52
+ });
53
+ ```
54
+
55
+ ## Template Syntax
56
+
57
+ ### Text Interpolation
58
+
59
+ Embed data expressions in text nodes:
60
+
61
+ ```html
62
+ <p>Hello, {{user.name}}! Your score is {{user.score}}.</p>
63
+ <p>Welcome to {{app.name}} - {{app.version}}</p>
64
+ ```
65
+
66
+ ### Attribute Binding
67
+
68
+ Set attribute values dynamically:
69
+
70
+ ```html
71
+ <img src="/avatars/{{user.id}}.png" alt="Avatar for {{user.name}}">
72
+ <a href="/users/{{user.id}}" class="{{linkClass}}">View Profile</a>
73
+ ```
74
+
75
+ ### Array Indexing
76
+
77
+ Access array elements by index:
78
+
79
+ ```html
80
+ <p>First item: {{items[0]}}</p>
81
+ <p>Second user: {{users[1].name}}</p>
82
+ <p>Nested: {{data.rows[0].cells[1].value}}</p>
83
+ ```
84
+
85
+ ### Pipes
86
+
87
+ Transform values using pipe functions:
88
+
89
+ ```html
90
+ <span>{{name | uppercase}}</span>
91
+ <span>{{price | currency}}</span>
92
+ <span>{{text | trim | uppercase | shorten:20}}</span>
93
+ <span>{{createdAt | daysAgo}}</span>
94
+ ```
95
+
96
+ See [Pipes](../Pipes.md) for the full list of built-in pipes.
97
+
98
+ ```typescript
99
+ import { compileTemplate } from 'relaxjs/html';
100
+ import { createPipeRegistry } from 'relaxjs/utils';
101
+
102
+ const pipeRegistry = createPipeRegistry();
103
+ const { content, render } = compileTemplate(
104
+ '<span>{{name | uppercase}}</span>',
105
+ { strict: false, pipeRegistry }
106
+ );
107
+ ```
108
+
109
+ ### Function Calls
110
+
111
+ Call functions defined in the functions context:
112
+
113
+ ```html
114
+ <span>Today: {{getDate()}}</span>
115
+ <span>Sum: {{add(5, 3)}}</span>
116
+ <span>{{greet("World")}}</span>
117
+ <span>{{formatPrice(item.price)}}</span>
118
+ <span>{{format(user.name, "bold")}}</span>
119
+ ```
120
+
121
+ ```typescript
122
+ const { content, render } = compileTemplate(`
123
+ <div>
124
+ <p>{{formatDate(createdAt)}}</p>
125
+ <p>Total: {{calculateTotal(items)}}</p>
126
+ </div>
127
+ `);
128
+
129
+ render(
130
+ { createdAt: new Date(), items: [10, 20, 30] },
131
+ {
132
+ formatDate: (d) => d.toLocaleDateString(),
133
+ calculateTotal: (arr) => arr.reduce((a, b) => a + b, 0)
134
+ }
135
+ );
136
+ ```
137
+
138
+ Function arguments can be:
139
+ - String literals: `"hello"` or `'hello'`
140
+ - Number literals: `42`, `3.14`
141
+ - Context paths: `user.name`, `items[0]`
142
+
143
+ ### Conditional Rendering
144
+
145
+ Show or hide elements with the `if` and `unless` directive attributes.
146
+ When hidden, the element is removed from the DOM entirely and replaced by an invisible comment placeholder.
147
+ When shown again, a fresh clone is inserted and its inner bindings are re-compiled with the latest data.
148
+
149
+ #### `if`
150
+
151
+ Renders the element only when the expression resolves to a truthy value:
152
+
153
+ ```html
154
+ <div if="user.isLoggedIn">
155
+ Welcome back, {{user.name}}!
156
+ </div>
157
+
158
+ <button if="cart.items">Checkout</button>
159
+ ```
160
+
161
+ #### `unless`
162
+
163
+ The inverse of `if`. Renders the element only when the expression is falsy:
164
+
165
+ ```html
166
+ <div unless="user.isLoggedIn">
167
+ Please <a href="/login">sign in</a> to continue.
168
+ </div>
169
+ ```
170
+
171
+ #### Truthiness Rules
172
+
173
+ The condition expression is resolved from the context and evaluated with JavaScript truthiness:
174
+
175
+ | Value | `if` shows? | `unless` shows? |
176
+ |-------|-------------|-----------------|
177
+ | `true` | yes | no |
178
+ | `false` | no | yes |
179
+ | `"hello"` (non-empty string) | yes | no |
180
+ | `""` (empty string) | no | yes |
181
+ | `1` (positive number) | yes | no |
182
+ | `0` | no | yes |
183
+ | `undefined` / missing | no | yes |
184
+ | `null` | no | yes |
185
+ | `{ ... }` (object) | yes | no |
186
+
187
+ #### Updates Across Renders
188
+
189
+ The element toggles in and out of the DOM as the condition changes across `render()` calls.
190
+
191
+ Inner bindings (text, attributes, nested conditionals, loops) are re-evaluated on every `render()` call while the element is visible, not just when first inserted:
192
+
193
+ ```typescript
194
+ const { content, render } = compileTemplate(`
195
+ <span if="show">{{name}}</span>
196
+ `);
197
+
198
+ render({ show: true, name: 'Alice' });
199
+ // → <span>Alice</span>
200
+
201
+ render({ show: false, name: 'Alice' });
202
+ // → (element removed)
203
+
204
+ render({ show: true, name: 'Bob' });
205
+ // → <span>Bob</span> (picks up latest data)
206
+
207
+ render({ show: true, name: 'Charlie' });
208
+ // → <span>Charlie</span> (inner content updates while visible)
209
+ ```
210
+
211
+ #### Nesting Conditionals
212
+
213
+ `if` and `unless` can be nested. Inner conditionals react independently to their own expressions:
214
+
215
+ ```html
216
+ <div if="hasPermission">
217
+ <p>You have access.</p>
218
+ <div if="isAdmin">
219
+ <button>Delete All</button>
220
+ </div>
221
+ </div>
222
+ ```
223
+
224
+ ```typescript
225
+ render({ hasPermission: true, isAdmin: true });
226
+ // → both div and button visible
227
+
228
+ render({ hasPermission: true, isAdmin: false });
229
+ // → outer div visible, button removed
230
+
231
+ render({ hasPermission: false, isAdmin: true });
232
+ // → everything removed (outer controls inner)
233
+ ```
234
+
235
+ #### Multiple Independent Conditionals
236
+
237
+ Sibling `if` elements toggle independently and preserve their position among siblings:
238
+
239
+ ```html
240
+ <span>Always</span>
241
+ <span if="showA">A</span>
242
+ <span if="showB">B</span>
243
+ <span>End</span>
244
+ ```
245
+
246
+ ### Loop Rendering
247
+
248
+ Repeat elements for arrays with `loop`:
249
+
250
+ ```html
251
+ <ul>
252
+ <li loop="item in cart.items">
253
+ {{item.name}} - ${{item.price}}
254
+ </li>
255
+ </ul>
256
+ ```
257
+
258
+ Combine with conditionals:
259
+
260
+ ```html
261
+ <table>
262
+ <tr loop="user in users">
263
+ <td>{{user.name}}</td>
264
+ <td if="user.isAdmin">Administrator</td>
265
+ <td unless="user.isAdmin">Regular User</td>
266
+ </tr>
267
+ </table>
268
+ ```
269
+
270
+ Loop with array indexing:
271
+
272
+ ```html
273
+ <li loop="item in data.lists[0]">{{item}}</li>
274
+ ```
275
+
276
+ ## Configuration
277
+
278
+ ```typescript
279
+ import { compileTemplate, EngineConfig } from 'relaxjs/html';
280
+ import { createPipeRegistry } from 'relaxjs/utils';
281
+
282
+ const config: EngineConfig = {
283
+ strict: true,
284
+ onError: (msg) => console.error(msg),
285
+ pipeRegistry: createPipeRegistry()
286
+ };
287
+
288
+ const { content, render } = compileTemplate(template, config);
289
+ ```
290
+
291
+ | Option | Type | Default | Description |
292
+ |--------|------|---------|-------------|
293
+ | `strict` | boolean | `false` | Throw errors for missing paths/functions |
294
+ | `onError` | function | `undefined` | Callback for error messages |
295
+ | `pipeRegistry` | PipeRegistry | `defaultPipes` | Custom pipe registry |
296
+
297
+ ## API Reference
298
+
299
+ ### compileTemplate(templateStr, config?)
300
+
301
+ Compiles an HTML template string into a render function.
302
+
303
+ ```typescript
304
+ function compileTemplate(
305
+ templateStr: string,
306
+ config?: EngineConfig
307
+ ): CompiledTemplate;
308
+
309
+ interface CompiledTemplate {
310
+ content: DocumentFragment | HTMLElement;
311
+ render: (ctx: Context, fns?: FunctionsContext) => void;
312
+ }
313
+ ```
314
+
315
+ ### Context
316
+
317
+ Data object passed to `render()`:
318
+
319
+ ```typescript
320
+ interface Context {
321
+ [key: string]: ContextValue;
322
+ }
323
+
324
+ // Example
325
+ render({
326
+ user: { name: 'John', age: 30 },
327
+ items: ['a', 'b', 'c'],
328
+ isActive: true
329
+ });
330
+ ```
331
+
332
+ ### FunctionsContext
333
+
334
+ Functions object passed as second argument to `render()`:
335
+
336
+ ```typescript
337
+ interface FunctionsContext {
338
+ [key: string]: (...args: any[]) => any;
339
+ }
340
+
341
+ // Example
342
+ render(data, {
343
+ formatDate: (d) => d.toLocaleDateString(),
344
+ add: (a, b) => a + b,
345
+ greet: (name) => `Hello, ${name}!`
346
+ });
347
+ ```
348
+
349
+ ## Web Component Integration
350
+
351
+ ```typescript
352
+ class UserCard extends HTMLElement {
353
+ private template: CompiledTemplate;
354
+ private _user: any = null;
355
+
356
+ constructor() {
357
+ super();
358
+ this.attachShadow({ mode: 'open' });
359
+
360
+ this.template = compileTemplate(`
361
+ <div class="card">
362
+ <h3>{{user.name}}</h3>
363
+ <p>{{user.email}}</p>
364
+ <p if="user.isAdmin">Administrator</p>
365
+ </div>
366
+ `);
367
+
368
+ this.shadowRoot!.appendChild(this.template.content);
369
+ }
370
+
371
+ set user(value) {
372
+ this._user = value;
373
+ this.template.render({ user: this._user });
374
+ }
375
+ }
376
+
377
+ customElements.define('user-card', UserCard);
378
+ ```
379
+
380
+ ## Complete Example
381
+
382
+ ```typescript
383
+ import { compileTemplate } from 'relaxjs/html';
384
+ import { createPipeRegistry } from 'relaxjs/utils';
385
+
386
+ const pipeRegistry = createPipeRegistry();
387
+
388
+ const { content, render } = compileTemplate(`
389
+ <div class="product-list">
390
+ <h1>{{title | uppercase}}</h1>
391
+ <p if="showIntro">{{getIntro()}}</p>
392
+
393
+ <ul>
394
+ <li loop="product in products" if="product.inStock">
395
+ <span>{{product.name | capitalize}}</span>
396
+ <span>{{formatPrice(product.price)}}</span>
397
+ <span>{{product.stock}} in stock</span>
398
+ </li>
399
+ </ul>
400
+
401
+ <p unless="products">No products available</p>
402
+ </div>
403
+ `, { strict: false, pipeRegistry });
404
+
405
+ render(
406
+ {
407
+ title: 'our products',
408
+ showIntro: true,
409
+ products: [
410
+ { name: 'apple', price: 1.50, stock: 10, inStock: true },
411
+ { name: 'banana', price: 0.75, stock: 0, inStock: false },
412
+ { name: 'orange', price: 2.00, stock: 5, inStock: true }
413
+ ]
414
+ },
415
+ {
416
+ getIntro: () => 'Welcome to our store!',
417
+ formatPrice: (p) => `$${p.toFixed(2)}`
418
+ }
419
+ );
420
+
421
+ document.body.appendChild(content);
422
+ ```