@excom/renderable-element 0.1.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 (40) hide show
  1. package/.rush/temp/chunked-rush-logs/renderable-element.apply-exports.chunks.jsonl +1 -0
  2. package/.rush/temp/chunked-rush-logs/renderable-element.build_docs.chunks.jsonl +1 -0
  3. package/.rush/temp/chunked-rush-logs/renderable-element.build_package-metas.chunks.jsonl +1 -0
  4. package/.rush/temp/operation/apply-exports/all.log +1 -0
  5. package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
  6. package/.rush/temp/operation/apply-exports/state.json +3 -0
  7. package/.rush/temp/operation/build_docs/all.log +1 -0
  8. package/.rush/temp/operation/build_docs/log-chunks.jsonl +1 -0
  9. package/.rush/temp/operation/build_docs/state.json +3 -0
  10. package/.rush/temp/operation/build_package-metas/all.log +1 -0
  11. package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
  12. package/.rush/temp/operation/build_package-metas/state.json +3 -0
  13. package/.rush/temp/shrinkwrap-deps.json +3 -0
  14. package/config/rig.json +6 -0
  15. package/index.ts +638 -0
  16. package/package.json +48 -0
  17. package/rush-logs/renderable-element.apply-exports.cache.log +1 -0
  18. package/rush-logs/renderable-element.apply-exports.log +1 -0
  19. package/rush-logs/renderable-element.build_docs.cache.log +1 -0
  20. package/rush-logs/renderable-element.build_docs.log +1 -0
  21. package/rush-logs/renderable-element.build_package-metas.cache.log +1 -0
  22. package/rush-logs/renderable-element.build_package-metas.log +1 -0
  23. package/src/index.css +11 -0
  24. package/support/custom-elements.json +371 -0
  25. package/support/demos/host-iframe.html +26 -0
  26. package/support/demos/host-selector.html +29 -0
  27. package/support/demos/host-shadow.html +31 -0
  28. package/support/demos/persist-content.html +57 -0
  29. package/support/demos/render-event.html +66 -0
  30. package/support/dist-docs/renderable-element.md +499 -0
  31. package/support/docs/README.md +146 -0
  32. package/support/package-meta.json +206 -0
  33. package/support/tests/host-iframe.view.test.ts +22 -0
  34. package/support/tests/host-selector.view.test.ts +19 -0
  35. package/support/tests/host-shadow.view.test.ts +19 -0
  36. package/support/tests/persist-content.view.test.ts +31 -0
  37. package/support/tests/render-event.view.test.ts +24 -0
  38. package/support/tests/render-lifecycle.test.ts +961 -0
  39. package/support/tests/renderable-element.test.ts +222 -0
  40. package/tsconfig.json +5 -0
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@excom/renderable-element",
3
+ "version": "0.1.0",
4
+ "description": "RenderableElement base for Neutron elements",
5
+ "license": "MIT",
6
+ "engines": {
7
+ "node": ">=24.13.0"
8
+ },
9
+ "type": "module",
10
+ "dependencies": {
11
+ "@excom/kit-logger": "^0.1.0",
12
+ "@excom/abortable-element": "^0.1.0",
13
+ "@excom/kit-shims": "^0.1.0",
14
+ "@excom/kit-utils": "^0.1.0",
15
+ "@excom/neutron": "^0.1.0"
16
+ },
17
+ "peerDependencies": {},
18
+ "devDependencies": {
19
+ "@excom/heft-rig": "^0.1.0"
20
+ },
21
+ "repository": {
22
+ "url": "excom-dev/nucleus",
23
+ "directory": "packages/renderable-element"
24
+ },
25
+ "homepage": "https://github.com/excom-dev/nucleus/tree/main/packages/renderable-element/support/docs/README.md",
26
+ "bugs": "https://github.com/excom-dev/nucleus/issues",
27
+ "keywords": [
28
+ "RenderableElement",
29
+ "renderable-element",
30
+ "neutron",
31
+ "kit-element-base",
32
+ "custom-elements"
33
+ ],
34
+ "excom": {
35
+ "packageType": "element-base"
36
+ },
37
+ "scripts": {
38
+ "build": "node node_modules/@excom/heft-rig/scripts/vite-build.mjs",
39
+ "build:watch": "node node_modules/@excom/heft-rig/scripts/vite-build-watch.mjs",
40
+ "format": "node node_modules/@excom/heft-rig/scripts/format.mjs",
41
+ "test": "node node_modules/@excom/heft-rig/scripts/vitest.mjs",
42
+ "coverage": "node node_modules/@excom/heft-rig/scripts/coverage.mjs",
43
+ "dev": "node node_modules/@excom/heft-rig/scripts/vite-dev.mjs",
44
+ "preview": "node node_modules/@excom/heft-rig/scripts/vite-preview.mjs",
45
+ "build:package-metas": "node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs",
46
+ "build:docs": "node node_modules/@excom/heft-rig/scripts/build-docs.mjs"
47
+ }
48
+ }
@@ -0,0 +1 @@
1
+ Caching has been disabled for this project's "apply-exports" command.
@@ -0,0 +1 @@
1
+ Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
@@ -0,0 +1 @@
1
+ This project does not define the caching behavior of the "build:docs" command, so caching has been disabled.
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
@@ -0,0 +1 @@
1
+ This project does not define the caching behavior of the "build:package-metas" command, so caching has been disabled.
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
package/src/index.css ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Shared styles for `RenderableElement` hosts. Hide while
3
+ * `delaying-ready` (waiting on `ready-on`).
4
+ */
5
+
6
+ @define-mixin renderable-element {
7
+ &[delaying-ready] {
8
+ /* Always hide, even against later display rules */
9
+ display: none !important;
10
+ }
11
+ }
@@ -0,0 +1,371 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "modules": [
4
+ {
5
+ "kind": "javascript-module",
6
+ "path": "index.ts",
7
+ "declarations": [
8
+ {
9
+ "kind": "mixin",
10
+ "name": "RenderableElement",
11
+ "mixins": [
12
+ {
13
+ "name": "AbortableElement",
14
+ "package": "@excom/abortable-element"
15
+ }
16
+ ],
17
+ "description": "Iframes waiting on `load` so their body can be `renderHost`.",
18
+ "attributes": [
19
+ {
20
+ "name": "template-ref",
21
+ "type": {
22
+ "text": "string"
23
+ },
24
+ "description": "Source `<template>` — in-document selector or remote URL. Changing mid-flight aborts and reloads. Can use `:scope` to relatively select elements: e.g. `main:has(:scope) > template`",
25
+ "fieldName": "templateRef",
26
+ "default": ":scope > template",
27
+ "values": [
28
+ "<CSS Selector>",
29
+ "<URL>"
30
+ ]
31
+ },
32
+ {
33
+ "name": "bypass-cache",
34
+ "type": {
35
+ "text": "boolean"
36
+ },
37
+ "description": "Skip the in-memory response cache (URL `template-ref` only).",
38
+ "fieldName": "bypassCache"
39
+ },
40
+ {
41
+ "name": "pre-fetch",
42
+ "type": {
43
+ "text": "string"
44
+ },
45
+ "description": "When to fetch the template, independent of when it renders. `\"\"` aliases `eager`.",
46
+ "fieldName": "preFetch",
47
+ "default": "lazy",
48
+ "values": [
49
+ "",
50
+ "eager",
51
+ "idle",
52
+ "lazy"
53
+ ]
54
+ },
55
+ {
56
+ "name": "persist-content",
57
+ "type": {
58
+ "text": "boolean"
59
+ },
60
+ "description": "Reuse the same live nodes across unrender / render (held on `_persistedTree`) so form values, scroll position, and subtree state survive toggles.",
61
+ "fieldName": "persistContent"
62
+ },
63
+ {
64
+ "name": "host-ref",
65
+ "type": {
66
+ "text": "string"
67
+ },
68
+ "description": "Where rendered children land. Unset = this element's light DOM. `shadow` attaches an open shadow root. `iframe` paints into a child `<iframe data-render-host>` body (you supply the iframe — useful for sandboxed / third-party document isolation). Any other value is a portal selector.",
69
+ "fieldName": "hostRef",
70
+ "values": [
71
+ "shadow",
72
+ "iframe",
73
+ "<CSS Selector>"
74
+ ]
75
+ },
76
+ {
77
+ "name": "ready-on",
78
+ "type": {
79
+ "text": "string"
80
+ },
81
+ "description": "Event name that marks rendered children \"ready\". Until it fires, `delaying-ready` is set so CSS can hide the host for a coordinated paint / view transition.",
82
+ "fieldName": "readyOn",
83
+ "values": [
84
+ "<Event Name>"
85
+ ]
86
+ },
87
+ {
88
+ "name": "is-active",
89
+ "type": {
90
+ "text": "boolean"
91
+ },
92
+ "description": "Master switch. Set to load (if needed) and render; unset to unrender. Drive from visibility, route match, hover, etc.",
93
+ "fieldName": "isActive"
94
+ },
95
+ {
96
+ "name": "is-loading",
97
+ "type": {
98
+ "text": "boolean"
99
+ },
100
+ "description": "Template fetch in flight.",
101
+ "fieldName": "isLoading"
102
+ },
103
+ {
104
+ "name": "did-load",
105
+ "type": {
106
+ "text": "boolean"
107
+ },
108
+ "description": "Template resolved at least once. Stays set across `is-active` toggles so consumers know later paints are warm (URL refs reuse the shared fetch cache in kit-utils). Cleared when `template-ref` changes or `--reload` forces a fresh resolve.",
109
+ "fieldName": "didLoad"
110
+ },
111
+ {
112
+ "name": "is-error",
113
+ "type": {
114
+ "text": "boolean"
115
+ },
116
+ "description": "Latest template fetch rejected (excluding abort). Fires with the `error` event.",
117
+ "fieldName": "isError"
118
+ },
119
+ {
120
+ "name": "delaying-ready",
121
+ "type": {
122
+ "text": "boolean"
123
+ },
124
+ "description": "Between `render` and the matching `ready-on` event. Hook with CSS for coordinated paints / view transitions.",
125
+ "fieldName": "delayingReady"
126
+ }
127
+ ],
128
+ "members": [
129
+ {
130
+ "kind": "field",
131
+ "name": "templateRef",
132
+ "type": {
133
+ "text": "string"
134
+ },
135
+ "privacy": "public",
136
+ "readonly": false,
137
+ "description": "Source `<template>` — in-document selector or remote URL. Changing mid-flight aborts and reloads. Can use `:scope` to relatively select elements: e.g. `main:has(:scope) > template`",
138
+ "default": ":scope > template",
139
+ "_neutron": {
140
+ "surface": "option"
141
+ }
142
+ },
143
+ {
144
+ "kind": "field",
145
+ "name": "bypassCache",
146
+ "type": {
147
+ "text": "boolean"
148
+ },
149
+ "privacy": "public",
150
+ "readonly": false,
151
+ "description": "Skip the in-memory response cache (URL `template-ref` only).",
152
+ "_neutron": {
153
+ "surface": "option"
154
+ }
155
+ },
156
+ {
157
+ "kind": "field",
158
+ "name": "preFetch",
159
+ "type": {
160
+ "text": "string"
161
+ },
162
+ "privacy": "public",
163
+ "readonly": false,
164
+ "description": "When to fetch the template, independent of when it renders. `\"\"` aliases `eager`.",
165
+ "default": "lazy",
166
+ "_neutron": {
167
+ "surface": "option"
168
+ }
169
+ },
170
+ {
171
+ "kind": "field",
172
+ "name": "persistContent",
173
+ "type": {
174
+ "text": "boolean"
175
+ },
176
+ "privacy": "public",
177
+ "readonly": false,
178
+ "description": "Reuse the same live nodes across unrender / render (held on `_persistedTree`) so form values, scroll position, and subtree state survive toggles.",
179
+ "_neutron": {
180
+ "surface": "option"
181
+ }
182
+ },
183
+ {
184
+ "kind": "field",
185
+ "name": "hostRef",
186
+ "type": {
187
+ "text": "string"
188
+ },
189
+ "privacy": "public",
190
+ "readonly": false,
191
+ "description": "Where rendered children land. Unset = this element's light DOM. `shadow` attaches an open shadow root. `iframe` paints into a child `<iframe data-render-host>` body (you supply the iframe — useful for sandboxed / third-party document isolation). Any other value is a portal selector.",
192
+ "_neutron": {
193
+ "surface": "option"
194
+ }
195
+ },
196
+ {
197
+ "kind": "field",
198
+ "name": "readyOn",
199
+ "type": {
200
+ "text": "string"
201
+ },
202
+ "privacy": "public",
203
+ "readonly": false,
204
+ "description": "Event name that marks rendered children \"ready\". Until it fires, `delaying-ready` is set so CSS can hide the host for a coordinated paint / view transition.",
205
+ "_neutron": {
206
+ "surface": "option"
207
+ }
208
+ },
209
+ {
210
+ "kind": "field",
211
+ "name": "isActive",
212
+ "type": {
213
+ "text": "boolean"
214
+ },
215
+ "privacy": "public",
216
+ "readonly": false,
217
+ "description": "Master switch. Set to load (if needed) and render; unset to unrender. Drive from visibility, route match, hover, etc.",
218
+ "_neutron": {
219
+ "surface": "hybrid"
220
+ }
221
+ },
222
+ {
223
+ "kind": "field",
224
+ "name": "isLoading",
225
+ "type": {
226
+ "text": "boolean"
227
+ },
228
+ "privacy": "public",
229
+ "readonly": true,
230
+ "description": "Template fetch in flight.",
231
+ "_neutron": {
232
+ "surface": "state"
233
+ }
234
+ },
235
+ {
236
+ "kind": "field",
237
+ "name": "didLoad",
238
+ "type": {
239
+ "text": "boolean"
240
+ },
241
+ "privacy": "public",
242
+ "readonly": true,
243
+ "description": "Template resolved at least once. Stays set across `is-active` toggles so consumers know later paints are warm (URL refs reuse the shared fetch cache in kit-utils). Cleared when `template-ref` changes or `--reload` forces a fresh resolve.",
244
+ "_neutron": {
245
+ "surface": "state"
246
+ }
247
+ },
248
+ {
249
+ "kind": "field",
250
+ "name": "isError",
251
+ "type": {
252
+ "text": "boolean"
253
+ },
254
+ "privacy": "public",
255
+ "readonly": true,
256
+ "description": "Latest template fetch rejected (excluding abort). Fires with the `error` event.",
257
+ "_neutron": {
258
+ "surface": "state"
259
+ }
260
+ },
261
+ {
262
+ "kind": "field",
263
+ "name": "delayingReady",
264
+ "type": {
265
+ "text": "boolean"
266
+ },
267
+ "privacy": "public",
268
+ "readonly": true,
269
+ "description": "Between `render` and the matching `ready-on` event. Hook with CSS for coordinated paints / view transitions.",
270
+ "_neutron": {
271
+ "surface": "state"
272
+ }
273
+ }
274
+ ],
275
+ "events": [
276
+ {
277
+ "name": "{tag}-render",
278
+ "description": "Cancelable. Dispatched when the element becomes active and is about to place template content into the host. `event.detail` is a thunk that performs the load (if not already loaded) and renders the children, returning a Promise that resolves once the corresponding `ready-on` event fires (or immediately if `ready-on` is unset). The promise rejects if the element is torn down mid-flight (`startTeardown` while loading / `delaying-ready`). Call `preventDefault()` to defer rendering and invoke `event.detail()` later.",
279
+ "type": {
280
+ "text": "RenderableRenderEvent",
281
+ "expanded": "CustomEvent & { type: \"{tag}-render\"; detail: () => Promise<void>; bubbles: true; cancelable: true; composed: true }"
282
+ }
283
+ },
284
+ {
285
+ "name": "{tag}-unrender",
286
+ "description": "Cancelable. Dispatched when the element becomes inactive and content is already painted. `event.detail` is a thunk that removes the rendered children. Call `preventDefault()` to defer the removal. Not fired when teardown cancels an in-flight load — that path emits `aborted` instead.",
287
+ "type": {
288
+ "text": "RenderableUnrenderEvent",
289
+ "expanded": "CustomEvent & { type: \"{tag}-unrender\"; detail: () => void; bubbles: true; cancelable: true; composed: true }"
290
+ }
291
+ },
292
+ {
293
+ "name": "{tag}-did-render",
294
+ "description": "Dispatched after the template content has actually been placed into the host.",
295
+ "type": {
296
+ "text": "RenderableDidRenderEvent",
297
+ "expanded": "CustomEvent & { type: \"{tag}-did-render\"; detail: void; bubbles: true; cancelable: true; composed: true }"
298
+ }
299
+ },
300
+ {
301
+ "name": "{tag}-did-unrender",
302
+ "description": "Dispatched after rendered children have been removed from the host.",
303
+ "type": {
304
+ "text": "RenderableDidUnrenderEvent",
305
+ "expanded": "CustomEvent & { type: \"{tag}-did-unrender\"; detail: void; bubbles: true; cancelable: true; composed: true }"
306
+ }
307
+ },
308
+ {
309
+ "name": "{tag}-error",
310
+ "description": "Dispatched when the template promise rejects with anything other than an `AbortError`.",
311
+ "type": {
312
+ "text": "RenderableErrorEvent",
313
+ "expanded": "CustomEvent & { type: \"{tag}-error\"; detail: void; bubbles: true; cancelable: true; composed: true }"
314
+ }
315
+ },
316
+ {
317
+ "name": "{tag}-aborted",
318
+ "description": "Dispatched when an in-flight load / ready wait is canceled because `is-active` was unset (via `startTeardown`).",
319
+ "type": {
320
+ "text": "RenderableAbortedEvent",
321
+ "expanded": "CustomEvent & { type: \"{tag}-aborted\"; detail: void; bubbles: true; cancelable: true; composed: true }"
322
+ }
323
+ }
324
+ ],
325
+ "_neutron": {
326
+ "commands": [
327
+ {
328
+ "name": "--reload",
329
+ "description": "Aborts any in-flight fetch and re-resolves the template, bypassing the cache for URL refs (useful after remote content changes)."
330
+ }
331
+ ],
332
+ "defaultActions": [
333
+ {
334
+ "name": "{tag}-render",
335
+ "description": "Invokes `event.detail()` to load (if needed) and render the template into the host."
336
+ },
337
+ {
338
+ "name": "{tag}-unrender",
339
+ "description": "Invokes `event.detail()` to remove rendered children from the host."
340
+ }
341
+ ],
342
+ "expectedChildren": [
343
+ {
344
+ "relationship": "child",
345
+ "selector": "template",
346
+ "required": false,
347
+ "description": "Optional immediate `<template>` child used when `template-ref` is the default `\":scope > template\"`. Not required when `template-ref` points at a selector or URL elsewhere."
348
+ },
349
+ {
350
+ "relationship": "child",
351
+ "selector": "iframe[data-render-host]",
352
+ "required": false,
353
+ "description": "Required when `host-ref=\"iframe\"`. Content paints into `iframe.contentDocument.body`. Provide your own iframe (e.g. with `srcdoc`); the element will not create one."
354
+ }
355
+ ]
356
+ }
357
+ }
358
+ ],
359
+ "exports": [
360
+ {
361
+ "kind": "js",
362
+ "name": "RenderableElement",
363
+ "declaration": {
364
+ "name": "RenderableElement",
365
+ "module": "index.ts"
366
+ }
367
+ }
368
+ ]
369
+ }
370
+ ]
371
+ }
@@ -0,0 +1,26 @@
1
+ <include-content is-active host-ref="iframe">
2
+ <template>
3
+ <style>
4
+ body { font-family: system-ui; padding: 12px; color: #333; }
5
+ h4 { color: navy; margin: 0 0 4px; }
6
+ </style>
7
+ <h4>Hello from inside the iframe</h4>
8
+ <p>Rendered into <code>iframe.contentDocument.body</code>.</p>
9
+ </template>
10
+ <iframe data-render-host
11
+ srcdoc="<!doctype html><html><body></body></html>"></iframe>
12
+ <style>
13
+ #demo-renderable-element-host-iframe > :first-child {
14
+ display: block;
15
+ border: 1px dashed currentColor;
16
+ padding: 8px;
17
+
18
+ > iframe {
19
+ width: 100%;
20
+ height: 140px;
21
+ border: none;
22
+ background: white;
23
+ }
24
+ }
25
+ </style>
26
+ </include-content>
@@ -0,0 +1,29 @@
1
+ <p>Any other <code>host-ref</code> value is treated as a CSS selector. The element renders its template into whatever the selector resolves to.</p>
2
+
3
+ <style>
4
+ .selector-demo {
5
+ display: grid;
6
+ grid-template-columns: 1fr 1fr;
7
+ gap: 12px;
8
+ }
9
+ .selector-demo .mount-point {
10
+ border: 1px dashed currentColor;
11
+ padding: 8px;
12
+ min-height: 80px;
13
+ }
14
+ </style>
15
+
16
+ <div class="selector-demo">
17
+ <div>
18
+ <strong>Source element:</strong>
19
+ <include-content is-active host-ref=".sidebar-mount">
20
+ <template>
21
+ <p>I render into <code>.sidebar-mount</code> →</p>
22
+ </template>
23
+ </include-content>
24
+ </div>
25
+ <div>
26
+ <strong>Mount target:</strong>
27
+ <div class="mount-point sidebar-mount"></div>
28
+ </div>
29
+ </div>
@@ -0,0 +1,31 @@
1
+ <p>Two <code>&lt;include-content&gt;</code> elements rendering the same template into different hosts. The shadow-root version is style-isolated.</p>
2
+
3
+ <style>
4
+ .host-demo include-content {
5
+ display: block;
6
+ border: 1px dashed currentColor;
7
+ padding: 8px;
8
+ margin: 8px 0;
9
+ }
10
+ /* this rule cannot reach into a shadow root */
11
+ .host-demo h4 { color: rebeccapurple; }
12
+ </style>
13
+
14
+ <div class="host-demo">
15
+ <strong>Light DOM (default):</strong>
16
+ <include-content is-active>
17
+ <template>
18
+ <h4>I'm rebeccapurple</h4>
19
+ <p>Light-DOM children inherit the host page's CSS.</p>
20
+ </template>
21
+ </include-content>
22
+
23
+ <strong>Shadow DOM (<code>host-ref="shadow"</code>):</strong>
24
+ <include-content is-active host-ref="shadow">
25
+ <template>
26
+ <style>h4 { color: tomato; }</style>
27
+ <h4>I'm tomato — outer styles can't touch me</h4>
28
+ <p>Encapsulated inside an attached shadow root.</p>
29
+ </template>
30
+ </include-content>
31
+ </div>
@@ -0,0 +1,57 @@
1
+ <p>Type into both inputs, then toggle them off and on. The right side
2
+ keeps its value because <code>persist-content</code> reuses the same
3
+ template node instead of cloning a fresh copy each render.</p>
4
+
5
+ <style>
6
+ .persist-demo {
7
+ display: grid;
8
+ grid-template-columns: 1fr 1fr;
9
+ gap: 12px;
10
+ }
11
+ .persist-demo include-content {
12
+ display: block;
13
+ border: 1px dashed currentColor;
14
+ padding: 8px;
15
+ margin-top: 4px;
16
+ }
17
+ </style>
18
+
19
+ <div class="persist-demo" id="persist-host">
20
+ <div>
21
+ <strong>Default (cloned each render):</strong>
22
+ <label>
23
+ <input type="checkbox" data-target="#cloned" checked />
24
+ Active
25
+ </label>
26
+ <include-content id="cloned" is-active>
27
+ <template>
28
+ <input type="text" placeholder="Type, then toggle off + on" />
29
+ </template>
30
+ </include-content>
31
+ </div>
32
+
33
+ <div>
34
+ <strong><code>persist-content</code>:</strong>
35
+ <label>
36
+ <input type="checkbox" data-target="#persisted" checked />
37
+ Active
38
+ </label>
39
+ <include-content id="persisted" is-active persist-content>
40
+ <template>
41
+ <input type="text" placeholder="Type, then toggle off + on" />
42
+ </template>
43
+ </include-content>
44
+ </div>
45
+ </div>
46
+
47
+ <script>
48
+ (() => {
49
+ document.querySelectorAll("#persist-host input[type=checkbox]").forEach((cb) => {
50
+ cb.addEventListener("change", () => {
51
+ document
52
+ .querySelector(cb.dataset.target)
53
+ .toggleAttribute("is-active", cb.checked);
54
+ });
55
+ });
56
+ })();
57
+ </script>
@@ -0,0 +1,66 @@
1
+ <p>Toggle the checkbox to activate the element. The cancelable
2
+ <code>include-content-render</code> event is intercepted, the actual
3
+ DOM mutation is wrapped in <code>document.startViewTransition()</code>,
4
+ and a parent listener can defer or skip the render entirely.</p>
5
+
6
+ <style>
7
+ .render-event-demo {
8
+ border: 1px solid var(--border, #444);
9
+ padding: 12px;
10
+ border-radius: 4px;
11
+ }
12
+ .render-event-demo include-content {
13
+ display: block;
14
+ margin-top: 8px;
15
+ padding: 8px;
16
+ border: 1px dashed currentColor;
17
+ }
18
+ .render-event-demo include-content > article {
19
+ background: rgba(255, 200, 80, 0.15);
20
+ padding: 12px;
21
+ view-transition-name: render-demo-card;
22
+ }
23
+ ::view-transition-old(render-demo-card),
24
+ ::view-transition-new(render-demo-card) {
25
+ animation-duration: 350ms;
26
+ }
27
+ </style>
28
+
29
+ <div class="render-event-demo" id="render-event-host">
30
+ <label>
31
+ <input type="checkbox" id="toggle-active" />
32
+ Active
33
+ </label>
34
+
35
+ <include-content id="render-target">
36
+ <template>
37
+ <article>
38
+ <h4>Just rendered (with a view transition)</h4>
39
+ <p>The host swallowed the default render and ran it through <code>startViewTransition</code>.</p>
40
+ </article>
41
+ </template>
42
+ </include-content>
43
+ </div>
44
+
45
+ <script>
46
+ (() => {
47
+ const host = document.querySelector("#render-event-host");
48
+ const target = host.querySelector("#render-target");
49
+ const toggle = host.querySelector("#toggle-active");
50
+
51
+ target.addEventListener("include-content-render", (e) => {
52
+ if (!document.startViewTransition) return;
53
+ e.preventDefault();
54
+ document.startViewTransition(() => e.detail());
55
+ });
56
+ target.addEventListener("include-content-unrender", (e) => {
57
+ if (!document.startViewTransition) return;
58
+ e.preventDefault();
59
+ document.startViewTransition(() => e.detail());
60
+ });
61
+
62
+ toggle.addEventListener("change", () => {
63
+ target.toggleAttribute("is-active", toggle.checked);
64
+ });
65
+ })();
66
+ </script>