@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; }

@use

Import JS modules into a sheet: pure functions, values in and a value out, are the sanctioned way for logic to enter a sheet.

Importing

as * exposes exports bare; as name (or the name derived from the url) namespaces them:

@use "/helpers.js" as *;
@use "/pricing.js" as pricing;
​
[bind-total] {
  content: formatPrice(pricing.total($items, $tax-rate));
}

A default export is default() with as *, or pricing.default() with a namespace, never the function's own name.

Imports start as soon as the sheet is parsed and load in parallel; the first rule run waits until they resolve. A failed import is logged and skipped.

A path (/x.js, ./x.js, x.js) resolves against the site root, not the sheet; an absolute http(s) URL loads as it is, and a module from another origin needs CORS. Any scheme besides http(s) and the page's own is refused (data:, blob:).

quark: URLs import Quark's own helpers without a fetch — @use "quark:math"; derives the namespace math — see Built-in modules. Reach for them before writing a module function of your own.

Writing module functions

A module should be pure business logic: simple functions that take values and return one. It is also the exit for anything Quark cannot yet declare (until the Nucleus stack is out of Beta).

/* pricing.js */
export const total = (items, taxRate) =>
  items.reduce((sum, { price, quantity }) => sum + price * quantity, 0) * (1 + taxRate);

A function called from an expression should not read or write the document around it. Bridging a protocol (the network, storage, a sensor, the clock, user devices) belongs to an Adapter: an element that does the work and carries the outcome as attributes and a provision, which the sheet reads with prop("provision"). Quark leans that way on purpose: it calls module functions synchronously and does not await what they return. This is a deliberate design to make logic - that should belong to Adapters - feel awkward in a Quark module.

Two narrow shapes go beyond values:

  • A node it owns. A function may create an element, render into it and return it for content: to place, which is how a third-party renderer reaches the page: [bind-chart] { content: createChart($series); } (see Content). It should still leave the document around it alone.
  • A listener, for imperative DOM work Quark has no declaration for, such as moving focus once content renders: @on include-content-did-render (handle: focusInput);. It receives the event, this being the element (see @on).

Asynchronous work

Quark does not await what a module function returns, so asynchronous work takes one of two paths:

  • Setup that needs no per-element input, such as importing and configuring a library, is an await at the top level of the module. The first rule run waits for @use imports, so rules start with the setup done.
  • Work that depends on an argument returns at once an element the function created, and fills it later. An event on that element announces the outcome, and an @on block turns it into State.
/* diagram.js */
const announce = (node, name) =>
  node.dispatchEvent(new Event(name, { bubbles: true }));
​
const startMermaidRender = async (figure, source) => {
  try {
    const { default: mermaid } = await import("mermaid");
    const { svg } = await mermaid.render(`diagram-${crypto.randomUUID()}`, source);
    figure.innerHTML = svg;
    announce(figure, "diagram-ready");
  } catch (_) {
    announce(figure, "diagram-error");
  }
};
​
export const renderDiagram = (source) => {
  const figure = document.createElement("figure");
  startMermaidRender(figure, source);
  return figure;
};
@use "/diagram.js" as *;
​
[data-diagram] {
  content: renderDiagram(attr("data-source"));
  @on diagram-ready { data-is-rendered: ""; }
  @on diagram-error { data-did-fail: ""; }
}

Logic heavier than this, such as a widget that needs teardown or incremental updates (a live chart, a map, an editor), is better served by a dedicated custom element.

Handing rendering to a framework

When a sheet hands a region to a rendering framework that requires sole authority over its DOM (React and similar), that region is the right place for a boundary. The function creates the host element, calls .attachShadow({ mode: "open" }) on it, gives the shadow root to the framework and returns the host for content: to place (content: renderCalendar($events);):

export const renderCalendar = (events) => {
  const host = document.createElement("div");
  createRoot(host.attachShadow({ mode: "open" })).render(createElement(Calendar, { events }));
  return host;
};

Quark never enters a shadow root, so the framework never meets a write it did not make. See May or may not play well with others in the guides.

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.