@use "/shell" as *; @use "quark:list" as list; #release-notice { /* opens once: sheet re-runs restart a delay, so gate it on the fact it writes */ &:not([data-did-open]) { @delay 7000 { is-open: ""; data-did-open: ""; } } /* prevent clicks from bubbling to the sheet */ @on mouseup, click (stop-propagation); } provider-fetch[api-url*="package-metas/index.json"][is-success] { $package-indices: prop("provision").body; /* `excom.navGroup` moves a package out of its type's list, into that group */ $ungrouped: list.reject($package-indices.packages, "navGroup"); .package-links { [bind-elements] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "kit-element")); } [bind-element-bases] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "element-base")); } [bind-tools] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "tool")); } /* the standalone libraries: same rows, rendered flat (shell.css) */ [bind-libraries] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "library")); } [bind-library-group] ul:not(ul ul) { content: iterate(list.filter($package-indices.packages, "navGroup", "libraries")); } ul:not(ul ul) > li { $pkg: item.shortName; > spa-a { route-href: "/packages/#{item.shortName}"; } } /* one `content` rule per link: a second one would rewrite the first's text on every pass */ ul:not(ul ul, [bind-libraries] ul) > li > spa-a { content: item.shortName; } [bind-libraries] ul:not(ul ul) > li > spa-a { content: displayName(item.shortName); } /* packages whose docs span several pages: one collapsible group per section */ [bind-sections] { content: iterate(item.docSections); summary { content: item.title; } details > ul { content: iterate(item.docs); spa-a { route-href: "/packages/#{$pkg}/#{item.name}"; content: item.title; } } details:not(:has(spa-a[is-active])) { open: none; } } } /* page titles: the page, then the site. The docs home keeps the title in the document head; the other routes carry theirs in markup */ spa-route[is-active] { /* a site guide */ &[route-href$=":name"] { $title-guide: list.find($package-indices.docs, "name", $route.params.name); document-title: "#{$title-guide.title or $route.params.name} · Nucleus · docs"; } /* a package README */ &[route-href$=":packageName"] { document-title: "#{displayName($route.params.packageName)} · Nucleus · docs"; } /* one page of a package's docs */ &[route-href$=":docName"] { $title-package: list.find($package-indices.packages, "shortName", $route.params.packageName); document-title: "#{docTitle($title-package, $route.params.docName)} · #{displayName($route.params.packageName)} · Nucleus · docs"; } } /* the page's markdown file, for the footer link: the docs home and each guide the index lists, and a package's page whose index entry says `markdown` (build-docs-index sets it; the dev server's index has none, so no link there). siteDocHref and the path check say which URL is the page's own: the 404 and a package's doc pages have none */ > spa-manager[active-url] { $page-path: attr("active-url").split("#").at(0).split("?").at(0); $page-name: if($page-path == SITE_HOME: SITE_HOME_DOC; else: $page-path.split("/").at(-1)); $page-guide: list.find($package-indices.docs, "name", $page-name); $page-package: list.find($package-indices.packages, "shortName", $page-name); $page-markdown: if($page-guide and siteDocHref($page-guide.name) == $page-path: "/docs/#{$page-guide.name}.md"; $page-package.markdown and "#{SITE_BASE}/packages/#{$page-package.shortName}" == $page-path: "/#{$page-package.shortName}.md"); footer [bind-page-markdown] { href: $page-markdown; content: ternary($page-markdown, "Markdown version of this page"); } } } spa-route { $route: prop("provision"); /* paramless routes name their guide in markup (the docs home, `/`); /docs/:name gets it from params */ $route-doc-name: attr("data-doc-name"); } /* the desktop aside and the mobile sheet stamp the same nav template */ [data-site-nav] { details:has(spa-a[is-active]) { /* not kosher */ open: ""; } spa-a[is-active] { aria-current: ""; } spa-a:not([is-active]) { aria-current: none; } } /* mobile sheet: Escape / back gesture while open; a tapped link closes it */ #site-menu { &[is-open] dismiss-watcher { is-active: ""; } &:not([is-open]) dismiss-watcher { is-active: none; } @on click (target: "spa-a") { is-open: none; } } #site-menu-gesture { &:has(> #site-menu[is-open]) { progress-offset: 1; } &:not(:has(> #site-menu[is-open])) { progress-offset: 0; } @on gesture-handler-start { #site-menu { is-scrubbing: ""; } } @on gesture-handler-end { #site-menu { is-open: event.detail.snap == 1; is-scrubbing: none; } } } main { @on copy-source (handle: copySource); } #search-dialog[open] { > include-content { is-active: ""; /* for some reason this is necessary on first render */ @on include-content-did-render (handle: focusInput); } input[type="search"] { /* works on all subsequent opens */ autofocus: ""; } } #search-dialog:not([open]) input[type="search"] { autofocus: none; }

include-content

One-stop shop for rendering a view when & where you need it — lazy-loading/unloading, conditional rendering, portalling… no app JS required.

Features

  • Zero JS Sophisticated UX from a simple HTML-only API, as with all NucleusKit elements.
  • Lazy (un)load Lazy load and lazy unload your views
  • Eager / idle Prioritize critical content; defer the rest
  • Prefetch Warm templates so they're ready on activate
  • Shared / remote templates Point at a DOM <template> or URL
  • Choose the host Portal into light DOM, shadow, author iframe, or any selector
  • Keep state Reuse the same tree across toggles
  • Animatable Built-in fade, or bring your own with .instant

Installation

This package is available in the NucleusKit. Or it can be used by itself:

CDN Package Manager
<script src="https://unpkg.com/@excom/kit-utils@0.3.0/dist/index.umd.min.js"></script>
<script src="https://unpkg.com/@excom/neutron@0.2.0/dist/index.umd.min.js"></script>
<script src="https://unpkg.com/@excom/include-content@0.1.4/dist/index.umd.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@excom/include-content@0.1.4/dist/index.css">
npm install @excom/include-content
HTML Imports JS / CSS Imports
<!-- import path to `node_modules` will depend on your build setup -->
<script type="module" src="/node_modules/@excom/include-content"></script>
<link rel="stylesheet" href="/node_modules/@excom/include-content">
import "@excom/include-content";
@import "@excom/include-content/index.css";
Peer dependencies (0)

Packages a consumer must install alongside this one. Workspace deps are bundled.

Package Version
View Dist Files

All exports:

.
  • types: ./dist/index.d.ts
  • import: ./dist/index.js
  • default: ./dist/index.js
./include-content
  • types: ./dist/include-content.d.ts
  • import: ./dist/include-content.js
  • default: ./dist/include-content.js
./include-content.js
  • types: ./dist/include-content.d.ts
  • import: ./dist/include-content.js
  • default: ./dist/include-content.js
./include-content.min
  • types: ./dist/include-content.d.ts
  • import: ./dist/include-content.min.js
  • default: ./dist/include-content.min.js
./include-content.min.js
  • types: ./dist/include-content.d.ts
  • import: ./dist/include-content.min.js
  • default: ./dist/include-content.min.js
./include-content.umd.min
  • types: ./dist/include-content.d.ts
  • default: ./dist/include-content.umd.min.js
./include-content.umd.min.js
  • types: ./dist/include-content.d.ts
  • default: ./dist/include-content.umd.min.js
./index
  • types: ./dist/index.d.ts
  • import: ./dist/index.js
  • default: ./dist/index.js
./index.bundle.min.css
  • default: ./dist/index.bundle.min.css
./index.css
  • default: ./dist/index.css
./index.js
  • types: ./dist/index.d.ts
  • import: ./dist/index.js
  • default: ./dist/index.js
./index.min
  • types: ./dist/index.d.ts
  • import: ./dist/index.min.js
  • default: ./dist/index.min.js
./index.min.css
  • default: ./dist/index.min.css
./index.min.js
  • types: ./dist/index.d.ts
  • import: ./dist/index.min.js
  • default: ./dist/index.min.js
./index.umd.min
  • types: ./dist/index.d.ts
  • default: ./dist/index.umd.min.js
./index.umd.min.js
  • types: ./dist/index.d.ts
  • default: ./dist/index.umd.min.js

Usage

Put a <template> inside (or set template-ref) and pick when it should appear. For conditional rendering, use Quark or JS to toggle is-active.

<include-content lazy-load template-ref="/path/to/view.html"></include-content>

API Reference

include-content

Attributes (18) Role — option is configurable, state is managed by the element (read-only), or hybrid which is both. Role Attribute Type Values Prop Default option bypass-cache
Skip the in-memory response cache (URL template-ref only). bypassCache renderable-element
boolean false
option host-ref
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. hostRef renderable-element
string "shadow" | "iframe" | <CSS Selector> null
option idle-load
Activate after first paint, when the browser is idle. Useful for below-the-fold / secondary views that should not compete with critical content. idleLoad
boolean false
option lazy-load
Activate when this element is on screen. Pair with lazy-unload for lazy load and lazy unload of views. lazyLoad
boolean false
option lazy-unload
Deactivate (unrender) when no longer on screen. Use with lazy-load to free DOM for off-screen views. lazyUnload
boolean false
option observer-delay
Minimum time visible before activating (ms). Ignored where unsupported. Useful for ensuring lazy load is not triggered when a programmatic smooth scroll zips the user right past the element. observerDelay
number 0
option observer-root
Scroll container for lazy load / unload. Defaults to the viewport. observerRoot
string <CSS Selector> null
option observer-root-margin
How far outside the root counts as "visible". Expand (e.g. 500px) to lazy-load a view before it enters the viewport so users never see an empty slot; shrink (default -1px) so edge-flush elements wait until they truly enter. observerRootMargin
string <length> | <percentage> "-1px -1px -1px -1px"
option observer-threshold
Fraction of the element that must be visible (0–1) before activating. observerThreshold
number null
option persist-content
Reuse the same live nodes across unrender / render (held on _persistedTree) so form values, scroll position, and subtree state survive toggles. persistContent renderable-element
boolean false
option pre-fetch
When to fetch the template, independent of when it renders. "" aliases eager. idle never runs in a prerender. preFetch renderable-element
string "" | "eager" | "idle" | "lazy" "lazy"
option ready-on
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. Prerendered content kept at hydration is ready at once, never hidden. readyOn renderable-element
string <Event Name> null
option template-ref
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 templateRef renderable-element
string <CSS Selector> | <URL> ":scope > template"
hybrid is-active
Master switch. Set to load (if needed) and render; unset to unrender. Drive from visibility, route match, hover, etc. isActive renderable-element
boolean false
state delaying-ready
Between render and the matching ready-on event (or a failed load). Hook with CSS for coordinated paints / view transitions. delayingReady renderable-element
boolean false
state did-load
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. didLoad renderable-element
boolean false
state is-error
Latest template fetch rejected (excluding abort). Fires with the error event. isError renderable-element
boolean false
state is-loading
Template fetch in flight. isLoading renderable-element
boolean false
Provision (0) This provision property lives on the DOM node. Read / watch it with Quark’s prop("provision"), or listen for the neutron-provision event from app JS. Property Type
Events (7)
Dispatches — events that include-content fires and their default actions. Dispatch Default Action
A "default action" is subsequent logic executed by the element if e.preventDefault() is not synchronously called on the event.
Name include-content-aborted
Dispatched when an in-flight load / ready wait is canceled because is-active was unset (via startTeardown). renderable-element

Type RenderableAbortedEvent
Name include-content-did-render
Dispatched after the template content has actually been placed into the host. renderable-element

Type RenderableDidRenderEvent
Name include-content-did-unrender
Dispatched after rendered children have been removed from the host. renderable-element

Type RenderableDidUnrenderEvent
Name include-content-error
Dispatched when the template promise rejects with anything other than an AbortError. renderable-element

Type RenderableErrorEvent
Name include-content-render
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 template fails to load, host-ref resolves to no host (nor an author iframe still loading), or the element is torn down mid-flight (startTeardown while loading / delaying-ready). Call preventDefault() to defer rendering and invoke event.detail() later. renderable-element

Type RenderableRenderEvent
Invokes event.detail() to load (if needed) and render the template into the host.
Name include-content-unrender
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. renderable-element

Type RenderableUnrenderEvent
Invokes event.detail() to remove rendered children from the host.
Listeners — events that include-content listens for which will trigger subsequent actions. Listener Type Action
Commands — verbs include-content accepts as a command event (HTML Command API): from a <button command commandfor>, an <event-handler command-name>, or a CommandEvent. Command Action --reload Stops waiting for any in-flight load and re-resolves the template, bypassing the cache for URL refs (useful after remote content changes). A URL request is shared by the page, so the one in flight is not cancelled. renderable-element
Recognized Elements (2) Child or descendant elements are recognized by include-content and relevant to its functionality. Element Relationship Required iframe[data-render-host]
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. renderable-element
child false
template
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. renderable-element
child false
Styles (9)
Classes — optional classes that change the appearance of include-content. Class Description .instant Skip the built-in fade — bring your own animation / view transition. Also opts-out if the element is portalling content elsewhere ([host-ref])
Variables — public CSS variables for theming include-content. Opt-out:
If you wish to opt-out of these styles on a case-to-case basis, use property all: revert-layer. Valence.css ships this as .unstyled and .unstyled-all for the entire tree.
Variable Syntax Default --include-content-transition-duration <time> 0.15s --include-content-transition-ease * ease-in
Aliases — @custom-selector synonyms so custom elements can share semantic styles. Element aliases match the host tag / .tag-* class; state aliases match attributes (nested under the element alias). Alias Kind Matches :--include-content element include-content, .tag-include-content :--include-content--did-load state [did-load], [data-did-load] :--include-content--host-shadow state [host-ref="shadow"], [data-host-ref="shadow"] :--include-content--is-active state [is-active], [aria-current] :--include-content--lazy-load state [lazy-load], [data-lazy-load] :--include-content--lazy-unload state [lazy-unload], [data-lazy-unload]
Release notes (1)

0.1.1

  • Ship only dist (and declared extras) in the npm tarball; drop build logs, tests and sources
View Source

Examples

Template include

Break your app into smaller views. Point template-ref at a <template> or a URL with is-active to render it immediately. This demo does both. A <script> inside a URL template does not run; stylesheets, <style> and <quark-sheet> do.

Lazy Load & Unload

lazy-load waits until on screen; lazy-unload removes it when it leaves. Give the host a real min-height so the trigger isn't ambiguous. Tune with observer-root, observer-root-margin, observer-threshold, and observer-delay — e.g. expand the margin to pre-render before the user scrolls to it. If remote template, pair with pre-fetch="idle" to warm the cache early.

Portal elsewhere

By default content lands in the element's light DOM. Set host-ref to shadow, iframe (with a child <iframe data-render-host>), or any CSS selector to render somewhere else. The iframe host only moves nodes: custom elements inside it upgrade only if that document loads their definitions.

Keep tree state

Toggling is-active off leaves did-load set (warm re-resolve; URL refs hit the shared fetch cache). persist-content goes further and reuses the same live nodes so implicit state (form values, open details, etc.) survives toggles.

Beta. The Nucleus Stack is in beta for a few weeks until features are stabilized and optimized.

Thanks — we'll email you when it ships.

Something went wrong. Please try again.