@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.
- package/.rush/temp/chunked-rush-logs/renderable-element.apply-exports.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/renderable-element.build_docs.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/renderable-element.build_package-metas.chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/all.log +1 -0
- package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/state.json +3 -0
- package/.rush/temp/operation/build_docs/all.log +1 -0
- package/.rush/temp/operation/build_docs/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_docs/state.json +3 -0
- package/.rush/temp/operation/build_package-metas/all.log +1 -0
- package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_package-metas/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +3 -0
- package/config/rig.json +6 -0
- package/index.ts +638 -0
- package/package.json +48 -0
- package/rush-logs/renderable-element.apply-exports.cache.log +1 -0
- package/rush-logs/renderable-element.apply-exports.log +1 -0
- package/rush-logs/renderable-element.build_docs.cache.log +1 -0
- package/rush-logs/renderable-element.build_docs.log +1 -0
- package/rush-logs/renderable-element.build_package-metas.cache.log +1 -0
- package/rush-logs/renderable-element.build_package-metas.log +1 -0
- package/src/index.css +11 -0
- package/support/custom-elements.json +371 -0
- package/support/demos/host-iframe.html +26 -0
- package/support/demos/host-selector.html +29 -0
- package/support/demos/host-shadow.html +31 -0
- package/support/demos/persist-content.html +57 -0
- package/support/demos/render-event.html +66 -0
- package/support/dist-docs/renderable-element.md +499 -0
- package/support/docs/README.md +146 -0
- package/support/package-meta.json +206 -0
- package/support/tests/host-iframe.view.test.ts +22 -0
- package/support/tests/host-selector.view.test.ts +19 -0
- package/support/tests/host-shadow.view.test.ts +19 -0
- package/support/tests/persist-content.view.test.ts +31 -0
- package/support/tests/render-event.view.test.ts +24 -0
- package/support/tests/render-lifecycle.test.ts +961 -0
- package/support/tests/renderable-element.test.ts +222 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
{
|
|
2
|
+
"shortName": "renderable-element",
|
|
3
|
+
"package": {
|
|
4
|
+
"name": "@excom/renderable-element",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"description": "RenderableElement base for Neutron elements",
|
|
7
|
+
"peerDependencies": {},
|
|
8
|
+
"excom": {
|
|
9
|
+
"packageType": "element-base"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"demos": {
|
|
13
|
+
"host-iframe": "<include-content is-active host-ref=\"iframe\">\n <template>\n <style>\n body { font-family: system-ui; padding: 12px; color: #333; }\n h4 { color: navy; margin: 0 0 4px; }\n </style>\n <h4>Hello from inside the iframe</h4>\n <p>Rendered into <code>iframe.contentDocument.body</code>.</p>\n </template>\n <iframe data-render-host\n srcdoc=\"<!doctype html><html><body></body></html>\"></iframe>\n <style>\n #demo-renderable-element-host-iframe > :first-child {\n display: block;\n border: 1px dashed currentColor;\n padding: 8px;\n\n > iframe {\n width: 100%;\n height: 140px;\n border: none;\n background: white;\n }\n }\n </style>\n</include-content>\n",
|
|
14
|
+
"host-selector": "<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>\n\n<style>\n .selector-demo {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px;\n }\n .selector-demo .mount-point {\n border: 1px dashed currentColor;\n padding: 8px;\n min-height: 80px;\n }\n</style>\n\n<div class=\"selector-demo\">\n <div>\n <strong>Source element:</strong>\n <include-content is-active host-ref=\".sidebar-mount\">\n <template>\n <p>I render into <code>.sidebar-mount</code> →</p>\n </template>\n </include-content>\n </div>\n <div>\n <strong>Mount target:</strong>\n <div class=\"mount-point sidebar-mount\"></div>\n </div>\n</div>\n",
|
|
15
|
+
"host-shadow": "<p>Two <code><include-content></code> elements rendering the same template into different hosts. The shadow-root version is style-isolated.</p>\n\n<style>\n .host-demo include-content {\n display: block;\n border: 1px dashed currentColor;\n padding: 8px;\n margin: 8px 0;\n }\n /* this rule cannot reach into a shadow root */\n .host-demo h4 { color: rebeccapurple; }\n</style>\n\n<div class=\"host-demo\">\n <strong>Light DOM (default):</strong>\n <include-content is-active>\n <template>\n <h4>I'm rebeccapurple</h4>\n <p>Light-DOM children inherit the host page's CSS.</p>\n </template>\n </include-content>\n\n <strong>Shadow DOM (<code>host-ref=\"shadow\"</code>):</strong>\n <include-content is-active host-ref=\"shadow\">\n <template>\n <style>h4 { color: tomato; }</style>\n <h4>I'm tomato — outer styles can't touch me</h4>\n <p>Encapsulated inside an attached shadow root.</p>\n </template>\n </include-content>\n</div>\n",
|
|
16
|
+
"persist-content": "<p>Type into both inputs, then toggle them off and on. The right side\nkeeps its value because <code>persist-content</code> reuses the same\ntemplate node instead of cloning a fresh copy each render.</p>\n\n<style>\n .persist-demo {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px;\n }\n .persist-demo include-content {\n display: block;\n border: 1px dashed currentColor;\n padding: 8px;\n margin-top: 4px;\n }\n</style>\n\n<div class=\"persist-demo\" id=\"persist-host\">\n <div>\n <strong>Default (cloned each render):</strong>\n <label>\n <input type=\"checkbox\" data-target=\"#cloned\" checked />\n Active\n </label>\n <include-content id=\"cloned\" is-active>\n <template>\n <input type=\"text\" placeholder=\"Type, then toggle off + on\" />\n </template>\n </include-content>\n </div>\n\n <div>\n <strong><code>persist-content</code>:</strong>\n <label>\n <input type=\"checkbox\" data-target=\"#persisted\" checked />\n Active\n </label>\n <include-content id=\"persisted\" is-active persist-content>\n <template>\n <input type=\"text\" placeholder=\"Type, then toggle off + on\" />\n </template>\n </include-content>\n </div>\n</div>\n\n<script>\n (() => {\n document.querySelectorAll(\"#persist-host input[type=checkbox]\").forEach((cb) => {\n cb.addEventListener(\"change\", () => {\n document\n .querySelector(cb.dataset.target)\n .toggleAttribute(\"is-active\", cb.checked);\n });\n });\n })();\n</script>\n",
|
|
17
|
+
"render-event": "<p>Toggle the checkbox to activate the element. The cancelable\n<code>include-content-render</code> event is intercepted, the actual\nDOM mutation is wrapped in <code>document.startViewTransition()</code>,\nand a parent listener can defer or skip the render entirely.</p>\n\n<style>\n .render-event-demo {\n border: 1px solid var(--border, #444);\n padding: 12px;\n border-radius: 4px;\n }\n .render-event-demo include-content {\n display: block;\n margin-top: 8px;\n padding: 8px;\n border: 1px dashed currentColor;\n }\n .render-event-demo include-content > article {\n background: rgba(255, 200, 80, 0.15);\n padding: 12px;\n view-transition-name: render-demo-card;\n }\n ::view-transition-old(render-demo-card),\n ::view-transition-new(render-demo-card) {\n animation-duration: 350ms;\n }\n</style>\n\n<div class=\"render-event-demo\" id=\"render-event-host\">\n <label>\n <input type=\"checkbox\" id=\"toggle-active\" />\n Active\n </label>\n\n <include-content id=\"render-target\">\n <template>\n <article>\n <h4>Just rendered (with a view transition)</h4>\n <p>The host swallowed the default render and ran it through <code>startViewTransition</code>.</p>\n </article>\n </template>\n </include-content>\n</div>\n\n<script>\n (() => {\n const host = document.querySelector(\"#render-event-host\");\n const target = host.querySelector(\"#render-target\");\n const toggle = host.querySelector(\"#toggle-active\");\n\n target.addEventListener(\"include-content-render\", (e) => {\n if (!document.startViewTransition) return;\n e.preventDefault();\n document.startViewTransition(() => e.detail());\n });\n target.addEventListener(\"include-content-unrender\", (e) => {\n if (!document.startViewTransition) return;\n e.preventDefault();\n document.startViewTransition(() => e.detail());\n });\n\n toggle.addEventListener(\"change\", () => {\n target.toggleAttribute(\"is-active\", toggle.checked);\n });\n })();\n</script>\n"
|
|
18
|
+
},
|
|
19
|
+
"readme": "<h1 id=\"md-renderable-element\">renderable-element</h1>\n<p>Composition base for Neutron elements that defer rendering a <code><template></code>\nuntil the right moment. It owns the load + render lifecycle so subclasses\nonly decide <em>when</em> to flip <code>is-active</code>. Used by <code><include-content></code>,\n<code><spa-route></code>, and any custom element you compose yourself.</p>\n<p>The demos below use <code><include-content></code> (the simplest concrete subclass)\nto exercise behavior that comes straight from this mixin.</p>\n<p><include-content data-demo=\"persist-content\"></include-content></p>\n<h2 id=\"md-features\">Features</h2>\n<ul>\n<li><strong>Template resolution</strong> via <code>template-ref</code> in-document selectors or remote URLs</li>\n<li><strong>Prefetch strategies</strong> <code>lazy</code> (default), <code>eager</code>, or <code>idle</code></li>\n<li><strong>Configurable render host</strong> light DOM, shadow, author iframe, or any selector</li>\n<li><strong>Cancelable render / unrender</strong> parents can wrap updates in view transitions</li>\n<li><strong>Persistable content</strong> keep live subtree state across unrender / render cycles</li>\n<li><strong>Ready coordination</strong> <code>ready-on</code> + <code>delaying-ready</code> for paint-synced reveals</li>\n</ul>\n<h2 id=\"md-installation\">Installation</h2>\n<p><include-content is-active template-ref=\"/views/install-section/install-section.html\"></include-content></p>\n<h2 id=\"md-usage\">Usage</h2>\n<p>Compose <code>RenderableElement</code> into a Neutron class and toggle <code>isActive</code>\nfrom whatever signal makes sense — a media query, a websocket message,\nan experiment flag, etc. Everything else (template fetch, caching, host\nresolution, lifecycle events) is inherited.</p>\n<include-content data-language=\"ts\"><template>import { Neutron } from \"@excom/neutron\";\nimport { RenderableElement } from \"@excom/renderable-element\";\n\nexport const MediaGated = Neutron.compose([\n RenderableElement,\n Neutron({\n tag: \"media-gated\",\n props: {\n mediaQuery: String,\n },\n }),\n])\n .onPropChanged(\"mediaQuery\", (el, prev) => {\n prev.mediaQuery && el._mql?.removeEventListener(\"change\", el._sync);\n if (!el.mediaQuery) return { isActive: false };\n el._mql = window.matchMedia(el.mediaQuery);\n el._sync = () => (el.isActive = el._mql.matches);\n el._mql.addEventListener(\"change\", el._sync);\n el._sync();\n });\n\nMediaGated.define();</template></include-content>\n<include-content data-language=\"html\"><template><media-gated media-query=\"(min-width: 900px)\">\n <template>\n <wide-screen-only></wide-screen-only>\n </template>\n</media-gated></template></include-content>\n<p>Event names are prefixed with the concrete tag (shown as <code>{tag}-…</code> in the\nAPI). For <code><include-content></code> that means <code>include-content-render</code>, etc.</p>\n<h3 id=\"md-api-reference\">API Reference</h3>\n<p><include-content is-active template-ref=\"/views/api-reference/api-reference.html\"></include-content></p>\n<h3 id=\"md-examples\">Examples</h3>\n<h4 id=\"md-choosing-a-render-host\">Choosing a render host</h4>\n<p>Unset <code>host-ref</code> renders into the element's light DOM. <code>"shadow"</code> attaches\nan open shadow root for style isolation:</p>\n<p><include-content data-demo=\"host-shadow\"></include-content></p>\n<p><code>"iframe"</code> paints into a child <code><iframe data-render-host></code> you provide —\nsandbox styles / scripts / document context. Only nodes move: custom\nelements upgrade there only if the iframe document loads their definitions:</p>\n<p><include-content data-demo=\"host-iframe\"></include-content></p>\n<p>Any other value is a CSS selector — the template lands in whatever\nelement it resolves to:</p>\n<p><include-content data-demo=\"host-selector\"></include-content></p>\n<h4 id=\"md-hooking-render-with-view-transitions\">Hooking render with view transitions</h4>\n<p><code>render</code> and <code>unrender</code> are cancelable; <code>event.detail</code> is the update\nthunk. A parent can <code>preventDefault()</code> and run the mutation inside\n<code>document.startViewTransition()</code> — the same pattern <code><spa-manager></code>\nuses to batch sibling routes.</p>\n<p><include-content data-demo=\"render-event\"></include-content></p>\n<p>The thunk's returned Promise resolves when the view is ready (immediately, or when <code>ready-on</code> fires). If <code>is-active</code> is unset while still loading / <code>delaying-ready</code>, teardown rejects that Promise and emits <code>aborted</code> instead of <code>unrender</code>.</p>\n<p>Pair with <code>ready-on</code> so <code>delaying-ready</code> stays set until your transition\nhas committed:</p>\n<include-content data-language=\"html\"><template><media-gated ready-on=\"my-app-paint\" media-query=\"(min-width: 900px)\">\n <template>...</template>\n</media-gated></template></include-content>\n<include-content data-language=\"css\"><template>media-gated[delaying-ready] {\n display: none;\n}</template></include-content>\n<h4 id=\"md-persisting-content-across-cycles\">Persisting content across cycles</h4>\n<p>Once a template resolves, <code>did-load</code> stays set so consumers know later\ntoggles are warm — URL <code>template-ref</code>s reuse the shared fetch cache in\nkit-utils. Without <code>persist-content</code> (the default), each activation\nre-resolves and imports a fresh clone — subtree state is lost on\nunrender. With it, the same live nodes are held across toggles:</p>\n<p><include-content data-demo=\"persist-content\"></include-content></p>\n<h4 id=\"md-loading-strategies\">Loading strategies</h4>\n<p><code>pre-fetch</code> controls <em>when</em> the template is fetched, separately from\nwhen it is rendered:</p>\n<include-content data-language=\"html\"><template><!-- default: fetch on first activation -->\n<my-el></my-el>\n\n<!-- pre-warm immediately on attribute set -->\n<my-el pre-fetch=\"eager\"></my-el>\n\n<!-- backfill on idle -->\n<my-el pre-fetch=\"idle\" template-ref=\"/fragments/hero.html\"></my-el></template></include-content>\n<p>Pair with <code>bypass-cache</code> for revalidation when the element activates\nmultiple times. Invoke the <code>--reload</code> command to force a refresh.</p>\n",
|
|
20
|
+
"docs": {
|
|
21
|
+
"readme": "<h1 id=\"md-renderable-element\">renderable-element</h1>\n<p>Composition base for Neutron elements that defer rendering a <code><template></code>\nuntil the right moment. It owns the load + render lifecycle so subclasses\nonly decide <em>when</em> to flip <code>is-active</code>. Used by <code><include-content></code>,\n<code><spa-route></code>, and any custom element you compose yourself.</p>\n<p>The demos below use <code><include-content></code> (the simplest concrete subclass)\nto exercise behavior that comes straight from this mixin.</p>\n<p><include-content data-demo=\"persist-content\"></include-content></p>\n<h2 id=\"md-features\">Features</h2>\n<ul>\n<li><strong>Template resolution</strong> via <code>template-ref</code> in-document selectors or remote URLs</li>\n<li><strong>Prefetch strategies</strong> <code>lazy</code> (default), <code>eager</code>, or <code>idle</code></li>\n<li><strong>Configurable render host</strong> light DOM, shadow, author iframe, or any selector</li>\n<li><strong>Cancelable render / unrender</strong> parents can wrap updates in view transitions</li>\n<li><strong>Persistable content</strong> keep live subtree state across unrender / render cycles</li>\n<li><strong>Ready coordination</strong> <code>ready-on</code> + <code>delaying-ready</code> for paint-synced reveals</li>\n</ul>\n<h2 id=\"md-installation\">Installation</h2>\n<p><include-content is-active template-ref=\"/views/install-section/install-section.html\"></include-content></p>\n<h2 id=\"md-usage\">Usage</h2>\n<p>Compose <code>RenderableElement</code> into a Neutron class and toggle <code>isActive</code>\nfrom whatever signal makes sense — a media query, a websocket message,\nan experiment flag, etc. Everything else (template fetch, caching, host\nresolution, lifecycle events) is inherited.</p>\n<include-content data-language=\"ts\"><template>import { Neutron } from \"@excom/neutron\";\nimport { RenderableElement } from \"@excom/renderable-element\";\n\nexport const MediaGated = Neutron.compose([\n RenderableElement,\n Neutron({\n tag: \"media-gated\",\n props: {\n mediaQuery: String,\n },\n }),\n])\n .onPropChanged(\"mediaQuery\", (el, prev) => {\n prev.mediaQuery && el._mql?.removeEventListener(\"change\", el._sync);\n if (!el.mediaQuery) return { isActive: false };\n el._mql = window.matchMedia(el.mediaQuery);\n el._sync = () => (el.isActive = el._mql.matches);\n el._mql.addEventListener(\"change\", el._sync);\n el._sync();\n });\n\nMediaGated.define();</template></include-content>\n<include-content data-language=\"html\"><template><media-gated media-query=\"(min-width: 900px)\">\n <template>\n <wide-screen-only></wide-screen-only>\n </template>\n</media-gated></template></include-content>\n<p>Event names are prefixed with the concrete tag (shown as <code>{tag}-…</code> in the\nAPI). For <code><include-content></code> that means <code>include-content-render</code>, etc.</p>\n<h3 id=\"md-api-reference\">API Reference</h3>\n<p><include-content is-active template-ref=\"/views/api-reference/api-reference.html\"></include-content></p>\n<h3 id=\"md-examples\">Examples</h3>\n<h4 id=\"md-choosing-a-render-host\">Choosing a render host</h4>\n<p>Unset <code>host-ref</code> renders into the element's light DOM. <code>"shadow"</code> attaches\nan open shadow root for style isolation:</p>\n<p><include-content data-demo=\"host-shadow\"></include-content></p>\n<p><code>"iframe"</code> paints into a child <code><iframe data-render-host></code> you provide —\nsandbox styles / scripts / document context. Only nodes move: custom\nelements upgrade there only if the iframe document loads their definitions:</p>\n<p><include-content data-demo=\"host-iframe\"></include-content></p>\n<p>Any other value is a CSS selector — the template lands in whatever\nelement it resolves to:</p>\n<p><include-content data-demo=\"host-selector\"></include-content></p>\n<h4 id=\"md-hooking-render-with-view-transitions\">Hooking render with view transitions</h4>\n<p><code>render</code> and <code>unrender</code> are cancelable; <code>event.detail</code> is the update\nthunk. A parent can <code>preventDefault()</code> and run the mutation inside\n<code>document.startViewTransition()</code> — the same pattern <code><spa-manager></code>\nuses to batch sibling routes.</p>\n<p><include-content data-demo=\"render-event\"></include-content></p>\n<p>The thunk's returned Promise resolves when the view is ready (immediately, or when <code>ready-on</code> fires). If <code>is-active</code> is unset while still loading / <code>delaying-ready</code>, teardown rejects that Promise and emits <code>aborted</code> instead of <code>unrender</code>.</p>\n<p>Pair with <code>ready-on</code> so <code>delaying-ready</code> stays set until your transition\nhas committed:</p>\n<include-content data-language=\"html\"><template><media-gated ready-on=\"my-app-paint\" media-query=\"(min-width: 900px)\">\n <template>...</template>\n</media-gated></template></include-content>\n<include-content data-language=\"css\"><template>media-gated[delaying-ready] {\n display: none;\n}</template></include-content>\n<h4 id=\"md-persisting-content-across-cycles\">Persisting content across cycles</h4>\n<p>Once a template resolves, <code>did-load</code> stays set so consumers know later\ntoggles are warm — URL <code>template-ref</code>s reuse the shared fetch cache in\nkit-utils. Without <code>persist-content</code> (the default), each activation\nre-resolves and imports a fresh clone — subtree state is lost on\nunrender. With it, the same live nodes are held across toggles:</p>\n<p><include-content data-demo=\"persist-content\"></include-content></p>\n<h4 id=\"md-loading-strategies\">Loading strategies</h4>\n<p><code>pre-fetch</code> controls <em>when</em> the template is fetched, separately from\nwhen it is rendered:</p>\n<include-content data-language=\"html\"><template><!-- default: fetch on first activation -->\n<my-el></my-el>\n\n<!-- pre-warm immediately on attribute set -->\n<my-el pre-fetch=\"eager\"></my-el>\n\n<!-- backfill on idle -->\n<my-el pre-fetch=\"idle\" template-ref=\"/fragments/hero.html\"></my-el></template></include-content>\n<p>Pair with <code>bypass-cache</code> for revalidation when the element activates\nmultiple times. Invoke the <code>--reload</code> command to force a refresh.</p>\n"
|
|
22
|
+
},
|
|
23
|
+
"installation": {
|
|
24
|
+
"name": "@excom/renderable-element",
|
|
25
|
+
"shortName": "renderable-element",
|
|
26
|
+
"version": "0.1.0",
|
|
27
|
+
"description": "RenderableElement base for Neutron elements",
|
|
28
|
+
"packageType": "element-base",
|
|
29
|
+
"install": {
|
|
30
|
+
"npm": "npm install @excom/renderable-element"
|
|
31
|
+
},
|
|
32
|
+
"imports": {
|
|
33
|
+
"js": "import { /* … */ } from \"@excom/renderable-element\";"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": []
|
|
36
|
+
},
|
|
37
|
+
"elementApis": [
|
|
38
|
+
{
|
|
39
|
+
"kind": "mixin",
|
|
40
|
+
"attributes": [
|
|
41
|
+
{
|
|
42
|
+
"name": "bypass-cache",
|
|
43
|
+
"type": "boolean",
|
|
44
|
+
"description": "Skip the in-memory response cache (URL <code>template-ref</code> only).",
|
|
45
|
+
"fieldName": "bypassCache",
|
|
46
|
+
"surface": "option"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "host-ref",
|
|
50
|
+
"type": "string",
|
|
51
|
+
"description": "Where rendered children land. Unset = this element's light DOM. <code>shadow</code> attaches an open shadow root. <code>iframe</code> paints into a child <code><iframe data-render-host></code> body (you supply the iframe — useful for sandboxed / third-party document isolation). Any other value is a portal selector.",
|
|
52
|
+
"fieldName": "hostRef",
|
|
53
|
+
"surface": "option",
|
|
54
|
+
"values": "\"shadow\" | \"iframe\" | <CSS Selector>"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "persist-content",
|
|
58
|
+
"type": "boolean",
|
|
59
|
+
"description": "Reuse the same live nodes across unrender / render (held on <code>_persistedTree</code>) so form values, scroll position, and subtree state survive toggles.",
|
|
60
|
+
"fieldName": "persistContent",
|
|
61
|
+
"surface": "option"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "pre-fetch",
|
|
65
|
+
"type": "string",
|
|
66
|
+
"description": "When to fetch the template, independent of when it renders. <code>""</code> aliases <code>eager</code>.",
|
|
67
|
+
"fieldName": "preFetch",
|
|
68
|
+
"surface": "option",
|
|
69
|
+
"default": "\"lazy\"",
|
|
70
|
+
"values": "\"\" | \"eager\" | \"idle\" | \"lazy\""
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "ready-on",
|
|
74
|
+
"type": "string",
|
|
75
|
+
"description": "Event name that marks rendered children "ready". Until it fires, <code>delaying-ready</code> is set so CSS can hide the host for a coordinated paint / view transition.",
|
|
76
|
+
"fieldName": "readyOn",
|
|
77
|
+
"surface": "option",
|
|
78
|
+
"values": "<Event Name>"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"name": "template-ref",
|
|
82
|
+
"type": "string",
|
|
83
|
+
"description": "Source <code><template></code> — in-document selector or remote URL. Changing mid-flight aborts and reloads. Can use <code>:scope</code> to relatively select elements: e.g. <code>main:has(:scope) > template</code>",
|
|
84
|
+
"fieldName": "templateRef",
|
|
85
|
+
"surface": "option",
|
|
86
|
+
"default": "\":scope > template\"",
|
|
87
|
+
"values": "<CSS Selector> | <URL>"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"name": "is-active",
|
|
91
|
+
"type": "boolean",
|
|
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
|
+
"surface": "hybrid"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "delaying-ready",
|
|
98
|
+
"type": "boolean",
|
|
99
|
+
"description": "Between <code>render</code> and the matching <code>ready-on</code> event. Hook with CSS for coordinated paints / view transitions.",
|
|
100
|
+
"fieldName": "delayingReady",
|
|
101
|
+
"surface": "state"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"name": "did-load",
|
|
105
|
+
"type": "boolean",
|
|
106
|
+
"description": "Template resolved at least once. Stays set across <code>is-active</code> toggles so consumers know later paints are warm (URL refs reuse the shared fetch cache in kit-utils). Cleared when <code>template-ref</code> changes or <code>--reload</code> forces a fresh resolve.",
|
|
107
|
+
"fieldName": "didLoad",
|
|
108
|
+
"surface": "state"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "is-error",
|
|
112
|
+
"type": "boolean",
|
|
113
|
+
"description": "Latest template fetch rejected (excluding abort). Fires with the <code>error</code> event.",
|
|
114
|
+
"fieldName": "isError",
|
|
115
|
+
"surface": "state"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"name": "is-loading",
|
|
119
|
+
"type": "boolean",
|
|
120
|
+
"description": "Template fetch in flight.",
|
|
121
|
+
"fieldName": "isLoading",
|
|
122
|
+
"surface": "state"
|
|
123
|
+
}
|
|
124
|
+
],
|
|
125
|
+
"events": [
|
|
126
|
+
{
|
|
127
|
+
"name": "{tag}-aborted",
|
|
128
|
+
"description": "Dispatched when an in-flight load / ready wait is canceled because <code>is-active</code> was unset (via <code>startTeardown</code>).",
|
|
129
|
+
"type": "RenderableAbortedEvent",
|
|
130
|
+
"typeExpanded": "CustomEvent & { type: \"{tag}-aborted\"; detail: void; bubbles: true; cancelable: true; composed: true }"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"name": "{tag}-did-render",
|
|
134
|
+
"description": "Dispatched after the template content has actually been placed into the host.",
|
|
135
|
+
"type": "RenderableDidRenderEvent",
|
|
136
|
+
"typeExpanded": "CustomEvent & { type: \"{tag}-did-render\"; detail: void; bubbles: true; cancelable: true; composed: true }"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"name": "{tag}-did-unrender",
|
|
140
|
+
"description": "Dispatched after rendered children have been removed from the host.",
|
|
141
|
+
"type": "RenderableDidUnrenderEvent",
|
|
142
|
+
"typeExpanded": "CustomEvent & { type: \"{tag}-did-unrender\"; detail: void; bubbles: true; cancelable: true; composed: true }"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"name": "{tag}-error",
|
|
146
|
+
"description": "Dispatched when the template promise rejects with anything other than an <code>AbortError</code>.",
|
|
147
|
+
"type": "RenderableErrorEvent",
|
|
148
|
+
"typeExpanded": "CustomEvent & { type: \"{tag}-error\"; detail: void; bubbles: true; cancelable: true; composed: true }"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"name": "{tag}-render",
|
|
152
|
+
"description": "Cancelable. Dispatched when the element becomes active and is about to place template content into the host. <code>event.detail</code> is a thunk that performs the load (if not already loaded) and renders the children, returning a Promise that resolves once the corresponding <code>ready-on</code> event fires (or immediately if <code>ready-on</code> is unset). The promise rejects if the element is torn down mid-flight (<code>startTeardown</code> while loading / <code>delaying-ready</code>). Call <code>preventDefault()</code> to defer rendering and invoke <code>event.detail()</code> later.",
|
|
153
|
+
"type": "RenderableRenderEvent",
|
|
154
|
+
"typeExpanded": "CustomEvent & { type: \"{tag}-render\"; detail: () => Promise<void>; bubbles: true; cancelable: true; composed: true }",
|
|
155
|
+
"defaultAction": "Invokes <code>event.detail()</code> to load (if needed) and render the template into the host."
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"name": "{tag}-unrender",
|
|
159
|
+
"description": "Cancelable. Dispatched when the element becomes inactive and content is already painted. <code>event.detail</code> is a thunk that removes the rendered children. Call <code>preventDefault()</code> to defer the removal. Not fired when teardown cancels an in-flight load — that path emits <code>aborted</code> instead.",
|
|
160
|
+
"type": "RenderableUnrenderEvent",
|
|
161
|
+
"typeExpanded": "CustomEvent & { type: \"{tag}-unrender\"; detail: () => void; bubbles: true; cancelable: true; composed: true }",
|
|
162
|
+
"defaultAction": "Invokes <code>event.detail()</code> to remove rendered children from the host."
|
|
163
|
+
}
|
|
164
|
+
],
|
|
165
|
+
"slots": [],
|
|
166
|
+
"cssProperties": [],
|
|
167
|
+
"cssClasses": [],
|
|
168
|
+
"cssAliases": [],
|
|
169
|
+
"listens": [],
|
|
170
|
+
"commands": [
|
|
171
|
+
{
|
|
172
|
+
"name": "--reload",
|
|
173
|
+
"description": "Aborts any in-flight fetch and re-resolves the template, bypassing the cache for URL refs (useful after remote content changes)."
|
|
174
|
+
}
|
|
175
|
+
],
|
|
176
|
+
"defaultActions": [
|
|
177
|
+
{
|
|
178
|
+
"name": "{tag}-render",
|
|
179
|
+
"description": "Invokes <code>event.detail()</code> to load (if needed) and render the template into the host."
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"name": "{tag}-unrender",
|
|
183
|
+
"description": "Invokes <code>event.detail()</code> to remove rendered children from the host."
|
|
184
|
+
}
|
|
185
|
+
],
|
|
186
|
+
"expectedChildren": [
|
|
187
|
+
{
|
|
188
|
+
"name": "child:iframe[data-render-host]",
|
|
189
|
+
"relationship": "child",
|
|
190
|
+
"selector": "iframe[data-render-host]",
|
|
191
|
+
"required": false,
|
|
192
|
+
"description": "Required when <code>host-ref="iframe"</code>. Content paints into <code>iframe.contentDocument.body</code>. Provide your own iframe (e.g. with <code>srcdoc</code>); the element will not create one."
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"name": "child:template",
|
|
196
|
+
"relationship": "child",
|
|
197
|
+
"selector": "template",
|
|
198
|
+
"required": false,
|
|
199
|
+
"description": "Optional immediate <code><template></code> child used when <code>template-ref</code> is the default <code>":scope > template"</code>. Not required when <code>template-ref</code> points at a selector or URL elsewhere."
|
|
200
|
+
}
|
|
201
|
+
],
|
|
202
|
+
"provisions": []
|
|
203
|
+
}
|
|
204
|
+
],
|
|
205
|
+
"exportedFiles": {}
|
|
206
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import "@excom/include-content";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
8
|
+
import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers";
|
|
9
|
+
|
|
10
|
+
describe("host-iframe view", () => {
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
document.body.innerHTML = "";
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("mounts an iframe-host include-content", async () => {
|
|
16
|
+
const { root } = await mountView(readDemo(import.meta.url, "host-iframe"));
|
|
17
|
+
expect(root.matches("include-content")).toBe(true);
|
|
18
|
+
expect(root.hasAttribute("is-active")).toBe(true);
|
|
19
|
+
expect(root.getAttribute("host-ref")).toBe("iframe");
|
|
20
|
+
expect(root.querySelector("iframe")).toBeTruthy();
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import "@excom/include-content";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
8
|
+
import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers";
|
|
9
|
+
|
|
10
|
+
describe("host-selector view", () => {
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
document.body.innerHTML = "";
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("renders into the selector mount point", async () => {
|
|
16
|
+
const { root } = await mountView(readDemo(import.meta.url, "host-selector"));
|
|
17
|
+
expect(root.querySelector(".sidebar-mount")?.textContent).toBeTruthy();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import "@excom/include-content";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
8
|
+
import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers";
|
|
9
|
+
|
|
10
|
+
describe("host-shadow view", () => {
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
document.body.innerHTML = "";
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("mounts a shadow-host include-content", async () => {
|
|
16
|
+
const { root } = await mountView(readDemo(import.meta.url, "host-shadow"));
|
|
17
|
+
expect(root.querySelector("include-content")).toBeTruthy();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import "@excom/include-content";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
8
|
+
import { flush, mountView, readDemo } from "@excom/quark/support/tests/view-helpers";
|
|
9
|
+
|
|
10
|
+
describe("persist-content view", () => {
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
document.body.innerHTML = "";
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("keeps the persisted input across toggle", async () => {
|
|
16
|
+
const { root } = await mountView(readDemo(import.meta.url, "persist-content"));
|
|
17
|
+
const persisted = root.querySelector("#persisted") as HTMLElement;
|
|
18
|
+
const input = persisted.querySelector("input")!;
|
|
19
|
+
input.value = "kept";
|
|
20
|
+
const toggle = root.querySelector<HTMLInputElement>(
|
|
21
|
+
'input[data-target="#persisted"]',
|
|
22
|
+
)!;
|
|
23
|
+
toggle.checked = false;
|
|
24
|
+
toggle.dispatchEvent(new Event("change", { bubbles: true }));
|
|
25
|
+
await flush();
|
|
26
|
+
toggle.checked = true;
|
|
27
|
+
toggle.dispatchEvent(new Event("change", { bubbles: true }));
|
|
28
|
+
await flush();
|
|
29
|
+
expect(persisted.querySelector("input")?.value).toBe("kept");
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import "@excom/include-content";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
8
|
+
import { flush, mountView, readDemo } from "@excom/quark/support/tests/view-helpers";
|
|
9
|
+
|
|
10
|
+
describe("render-event view", () => {
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
document.body.innerHTML = "";
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("activates include-content from the checkbox", async () => {
|
|
16
|
+
const { root } = await mountView(readDemo(import.meta.url, "render-event"));
|
|
17
|
+
const include = root.querySelector("include-content")!;
|
|
18
|
+
const checkbox = root.querySelector<HTMLInputElement>("input[type='checkbox']")!;
|
|
19
|
+
checkbox.checked = true;
|
|
20
|
+
checkbox.dispatchEvent(new Event("change", { bubbles: true }));
|
|
21
|
+
await flush();
|
|
22
|
+
expect(include.hasAttribute("is-active") || include.textContent).toBeTruthy();
|
|
23
|
+
});
|
|
24
|
+
});
|