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

quark-formatter

Prettier-style formatting for Quark sheets — one call, one canonical style, nothing to configure.

import { format } from "@excom/quark-formatter";
​
format(`provider-fetch[is-success]{$todos:prop("provision").body;ul{content:iterate($todos)}}`);
// provider-fetch[is-success] {
//   $todos: prop("provision").body;
//   ul {
//     content: iterate($todos);
//   }
// }

Features

  • One style Two-space indent, 80-column wrapping, one selector per line, single blank lines between groups
  • Safe Invalid Quark throws instead of rewriting; comments stay where they were written; formatting is idempotent
  • Editor / CLI ready Powers Format Document in the Nucleus & Quark extension and the monorepo format script
  • Self-contained Parser bundled in; runs in Node, bundlers and browsers

Installation

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

CDN Package Manager
<script src="https://unpkg.com/@excom/quark-formatter@0.1.4/dist/index.umd.min.js"></script>
npm install @excom/quark-formatter
HTML Imports JS / CSS Imports
import { /* … */ } from "@excom/quark-formatter";
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
./index
  • types: ./dist/index.d.ts
  • import: ./dist/index.js
  • default: ./dist/index.js
./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.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

format(source, options?) returns the formatted sheet as a string. The only option is indent (default two spaces).

import { format } from "@excom/quark-formatter";
​
const pretty = format(source, { indent: "\t" });

Invalid input throws QuarkParseError (from @excom/quark-parser) with the line and column, so callers leave the original file untouched:

try {
  fs.writeFileSync(file, format(fs.readFileSync(file, "utf8")));
} catch (error) {
  console.error(`${file}: ${error.message}`);
}

What gets normalized

  • Whitespace, indentation and blank lines (at most one preserved between statements)
  • Selector lists: one per line at rule heads, , -joined inside :not() / :is()
  • Operator spacing, with only the parentheses the expression needs: ($a or $b) and $c
  • Accessors and call chains print compactly: $todo.title, $row["id"], closest("li").getAttribute("id")
  • Comments (/* … */ only) keep their position, including trailing same-line comments
  • Lines wrap at 80 columns the way prettier wraps CSS: maps, call arguments, if() arms, @on / @dispatch / @command / @view-transition options and @delay durations that overflow break one item per line, operator chains wrap like text, a comma list of multi-word values breaks one value per line
content: formatPrice(
  pricing.total($items, $tax-rate) - pricing.discount($items, $coupon-code),
  $currency
);
data-mode: if(
  event.target.name == "data-mode": event.target.value;
  else: preserve
);

Strings, selectors and interpolations never wrap, so a long content: "…#{…}…" stays on one line. Listener at-rules print as @on input, change (target: "[name]", debounce: 300) { — the event list as written, the options group like a map — and @dispatch / @command statements the same way; @view-transition (types: "todo-change") { prints its options like @on's and always opens a block, as does @scope {.

Quark is a derivative of CSS with at-rules of its own, and the formatter prints those — @use, @scope, @on, @dispatch, @command, @view-transition, @delay, @warn, @debug, @error — and nothing else. A sheet the parser rejects, such as one holding @media or !important, throws rather than being reformatted.

In the editor

Install the Nucleus & Quark Syntax Highlighter to format .quark files with Format Document and inline <quark-sheet> blocks with a command.

Release notes (3)

0.1.3

  • Fix the published type declarations: index.d.ts pointed at a folder that is not in the tarball, so consumers got any for named re-exports and missing-member errors for export * instead of the real types

0.1.2

  • Declare the MIT license in package.json (was ISC), matching the repo LICENSE and every other package

0.1.1

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

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.