@excom/quark 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/quark.apply-exports.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/quark.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_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 +4 -0
- package/language.ts +7 -0
- package/package.json +45 -0
- package/rush-logs/quark.apply-exports.cache.log +1 -0
- package/rush-logs/quark.apply-exports.log +1 -0
- package/rush-logs/quark.build_package-metas.cache.log +1 -0
- package/rush-logs/quark.build_package-metas.log +1 -0
- package/src/actions.ts +445 -0
- package/src/ast.ts +187 -0
- package/src/bindings.ts +115 -0
- package/src/builtin-modules.ts +401 -0
- package/src/constants.ts +47 -0
- package/src/cycle-check.ts +121 -0
- package/src/devtools-hook.ts +303 -0
- package/src/element-api.ts +109 -0
- package/src/evaluator.ts +317 -0
- package/src/form-controls.ts +69 -0
- package/src/language-tables.ts +137 -0
- package/src/language.ts +1225 -0
- package/src/observer.ts +110 -0
- package/src/paint.ts +305 -0
- package/src/properties.ts +818 -0
- package/src/props.ts +94 -0
- package/src/quark-element.ts +7 -0
- package/src/quark-internal.ts +365 -0
- package/src/quark.ts +817 -0
- package/src/resolvers.ts +867 -0
- package/src/rule.ts +800 -0
- package/src/scope-id.ts +47 -0
- package/src/selector-utils.ts +430 -0
- package/src/settle.ts +117 -0
- package/src/types.ts +155 -0
- package/src/utils.ts +79 -0
- package/src/variables.ts +359 -0
- package/support/demos/css-variables.html +18 -0
- package/support/demos/dispatch.html +20 -0
- package/support/demos/events.html +9 -0
- package/support/demos/iterate.html +13 -0
- package/support/demos/js-api.html +17 -0
- package/support/demos/provider-list.html +16 -0
- package/support/demos/toggle-content.html +14 -0
- package/support/demos/unset.html +12 -0
- package/support/demos/view-transition.html +36 -0
- package/support/docs/ATTRIBUTES.md +37 -0
- package/support/docs/AT_RULES.md +45 -0
- package/support/docs/BUILTINS.md +57 -0
- package/support/docs/CONTENT.md +40 -0
- package/support/docs/CSS_VARIABLES.md +16 -0
- package/support/docs/DECLARATIONS.md +26 -0
- package/support/docs/DELAY.md +37 -0
- package/support/docs/DIAGNOSTICS.md +32 -0
- package/support/docs/DISPATCH.md +81 -0
- package/support/docs/ELEMENT_PROPERTIES.md +34 -0
- package/support/docs/EXPRESSIONS.md +31 -0
- package/support/docs/INTERNAL.md +8 -0
- package/support/docs/JS_API.md +53 -0
- package/support/docs/JS_WRITES.md +36 -0
- package/support/docs/LIMITATIONS.md +26 -0
- package/support/docs/LOOP_GUARD.md +30 -0
- package/support/docs/METHODS.md +43 -0
- package/support/docs/MODULES.md +111 -0
- package/support/docs/NO_REVERSION.md +18 -0
- package/support/docs/ON.md +87 -0
- package/support/docs/REACTIVITY.md +24 -0
- package/support/docs/README.md +143 -0
- package/support/docs/SELECTORS.md +87 -0
- package/support/docs/SHEETS.md +67 -0
- package/support/docs/SYNTAX.md +36 -0
- package/support/docs/USE.md +24 -0
- package/support/docs/VALUES.md +27 -0
- package/support/docs/VARIABLES.md +47 -0
- package/support/docs/VIEW_TRANSITION.md +64 -0
- package/support/docs-sections.json +65 -0
- package/support/package-meta.json +208 -0
- package/support/scripts/build-language-docs.mjs +196 -0
- package/support/tests/__snapshots__/css-variables.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/dispatch.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/events.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/iterate.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/js-api.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/provider-list.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/quark-features.test.ts.snap +1079 -0
- package/support/tests/__snapshots__/quark-with-providers.test.ts.snap +89 -0
- package/support/tests/__snapshots__/quark.test.ts.snap +221 -0
- package/support/tests/__snapshots__/selectors.test.ts.snap +133 -0
- package/support/tests/__snapshots__/toggle-content.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/unset.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/view-transition.view.test.ts.snap +23 -0
- package/support/tests/actions.test.ts +437 -0
- package/support/tests/builtin-modules.test.ts +337 -0
- package/support/tests/builtins.test.ts +189 -0
- package/support/tests/css-variables.view.test.ts +35 -0
- package/support/tests/delay.test.ts +244 -0
- package/support/tests/devtools-hook.test.ts +496 -0
- package/support/tests/diagnostics.test.ts +224 -0
- package/support/tests/dispatch.view.test.ts +43 -0
- package/support/tests/element-api.test.ts +202 -0
- package/support/tests/evaluator.test.ts +237 -0
- package/support/tests/events.view.test.ts +40 -0
- package/support/tests/form-controls.test.ts +452 -0
- package/support/tests/helpers.ts +354 -0
- package/support/tests/internals.test.ts +568 -0
- package/support/tests/iterate.view.test.ts +40 -0
- package/support/tests/js-api.view.test.ts +45 -0
- package/support/tests/language-docs.test.ts +147 -0
- package/support/tests/lifecycle.test.ts +440 -0
- package/support/tests/loop-guard.test.ts +323 -0
- package/support/tests/provider-list.view.test.ts +47 -0
- package/support/tests/quark-features.test.ts +2887 -0
- package/support/tests/quark-with-providers.test.ts +169 -0
- package/support/tests/quark.test.ts +359 -0
- package/support/tests/resolvers.test.ts +346 -0
- package/support/tests/selector-analysis.test.ts +281 -0
- package/support/tests/selectors.test.ts +670 -0
- package/support/tests/toggle-content.view.test.ts +40 -0
- package/support/tests/unset.view.test.ts +42 -0
- package/support/tests/view-helpers.ts +81 -0
- package/support/tests/view-transition.test.ts +1069 -0
- package/support/tests/view-transition.view.test.ts +76 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
{
|
|
2
|
+
"shortName": "quark",
|
|
3
|
+
"package": {
|
|
4
|
+
"name": "@excom/quark",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"description": "CSS-like DOM orchestration language for binding, rendering, and events",
|
|
7
|
+
"peerDependencies": {},
|
|
8
|
+
"excom": {
|
|
9
|
+
"packageType": "library"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"demos": {
|
|
13
|
+
"css-variables": "<div>\n <details>\n <summary>Open me</summary>\n <p>The border turns blue while open.</p>\n </details>\n <quark-sheet>\n details { --border-color: \"silver\"; }\n details[open] { --border-color: \"#3fa9f5\"; }\n </quark-sheet>\n <style>\n #demo-quark-css-variables > :first-child {\n details {\n border: 2px solid var(--border-color);\n padding: 0.5rem;\n }\n }\n </style>\n</div>\n",
|
|
14
|
+
"dispatch": "<div>\n <button type=\"button\" data-action=\"ping\">Ping the panel</button>\n <section aria-label=\"panel\">\n <output></output>\n </section>\n <quark-sheet>\n button[data-action=\"ping\"] {\n @on click {\n @dispatch demo-ping (target: \"[aria-label='panel']\", detail: (at: event.timeStamp));\n }\n }\n [aria-label=\"panel\"] {\n @on demo-ping {\n $pings: (+attr(\"data-pings\") or 0) + 1;\n data-pings: $pings;\n output { content: \"pinged #{$pings}×\"; }\n }\n }\n </quark-sheet>\n</div>\n",
|
|
15
|
+
"events": "<div>\n <a href=\"https://example.com\">Click me — navigation is prevented</a>\n <output></output>\n <quark-sheet>\n @use \"/demo-utils\" as *;\n\n a { @on click (prevent-default, handle: noteEvent); }\n </quark-sheet>\n</div>\n",
|
|
16
|
+
"iterate": "<div>\n <ul>\n <template>\n <li bind-name></li>\n </template>\n </ul>\n <quark-sheet>\n @use \"/demo-utils\" as *;\n\n ul { content: iterate(getPlanets()); }\n [bind-name] { content: item; }\n </quark-sheet>\n</div>\n",
|
|
17
|
+
"js-api": "<div data-demo-counter>\n <button>+1</button>\n <output></output>\n <quark-sheet>\n @use \"/demo-utils\" as *;\n\n :scope {\n $count: 0;\n }\n button {\n @on click (handle: incrementFromJs(closest(\"[data-demo-counter]\")));\n }\n output {\n content: \"Clicked #{$count} times\";\n }\n </quark-sheet>\n</div>\n",
|
|
18
|
+
"provider-list": "<div>\n <provider-fetch api-url=\"/api/todos?_limit=4\">\n <ul>\n <template>\n <li bind-title></li>\n </template>\n </ul>\n </provider-fetch>\n <quark-sheet>\n provider-fetch[is-success] {\n $todos: prop(\"provision\").body;\n ul { content: iterate($todos); }\n [bind-title] { content: item.title; }\n }\n </quark-sheet>\n</div>\n",
|
|
19
|
+
"toggle-content": "<div>\n <details>\n <summary>Panel <span bind-status></span></summary>\n <p>Panel body.</p>\n </details>\n <quark-sheet>\n details[open] [bind-status] {\n content: \"Open\";\n }\n details:not([open]) [bind-status] {\n content: \"Closed\";\n }\n </quark-sheet>\n</div>\n",
|
|
20
|
+
"unset": "<div>\n <details>\n <summary bind-foo></summary>\n <p>Open me to shadow the host's binding.</p>\n </details>\n <quark-sheet>\n :scope { $foo: 1; }\n details[open] { $foo: 2; }\n details:not([open]) { $foo: unset; }\n [bind-foo] { content: \"$foo resolves to #{$foo}\"; }\n </quark-sheet>\n</div>\n",
|
|
21
|
+
"view-transition": "<div data-count=\"2\">\n <button type=\"button\" data-add>Add planet</button>\n <button type=\"button\" data-remove>Remove planet</button>\n <ul>\n <template>\n <li bind-name></li>\n </template>\n </ul>\n <quark-sheet>\n @use \"/demo-utils\" as *;\n\n :scope {\n $count: +attr(\"data-count\");\n @on click (target: \"[data-add]\") { data-count: $count + 1; }\n @on click (target: \"[data-remove]\") { data-count: $count - 1; }\n }\n [data-add] { disabled: $count == 4; }\n [data-remove] { disabled: $count == 0; }\n ul {\n @view-transition (types: \"planets\") {\n content: iterate(getPlanets().slice(0, $count));\n }\n }\n [bind-name] { content: item; }\n </quark-sheet>\n <style>\n #demo-quark-view-transition > :first-child {\n ul { view-transition-name: demo-planets; }\n li {\n view-transition-name: match-element;\n view-transition-class: demo-planet;\n }\n }\n ::view-transition-group(.demo-planet) { animation-duration: 250ms; }\n </style>\n</div>\n"
|
|
22
|
+
},
|
|
23
|
+
"readme": "<h1 id=\"md-quark\">quark</h1>\n<p>CSS-like orchestration for your HTML — bind attributes, render lists, and wire events without a component tree.</p>\n<p>Quark is a derivative of CSS with CSS-compatible syntax (a CSS parser can tokenize Quark). The language is familiar. Rule properties are HTML attributes. The utilities (at-rules, util functions) are new.</p>\n<p>Prefer <code><quark-sheet></code> for apps; use the <code>Quark</code> class when you need a programmatic host (tests, tooling).</p>\n<h2 id=\"md-features\">Features</h2>\n<ul>\n<li><strong>CSS-like sheets</strong> Selectors + nested rules that mutate the live DOM</li>\n<li><strong><code>$variables</code></strong> Scoped values that nest and resolve in expressions</li>\n<li><strong>JS writes</strong> <code>element.quark.setProperty()</code> writes a <code>$variable</code> from JavaScript, on any element</li>\n<li><strong>CSS variables</strong> Write <code>--custom-props</code> from state; style via <code>var()</code></li>\n<li><strong>Content rendering</strong> <code>content</code>, <code>template()</code>, <code>iterate()</code>, <code>dangerous-html()</code></li>\n<li><strong>Element properties</strong> <code>prop("provision")</code> reads Neutron provisions / any JS property, re-running on assignment</li>\n<li><strong>Events</strong> <code>@on</code> at-rules with delegation, key, timing and host options, plus <code>prevent-default</code> / <code>stop-propagation</code></li>\n<li><strong>View transitions</strong> <code>@view-transition</code> commits a block's writes inside <code>document.startViewTransition()</code>, so CSS animates list changes, removals included</li>\n<li><strong>Delayed writes</strong> <code>@delay 2000 { … }</code> applies a block after a pause — flashes, toasts, undo windows — dropped if the rule stopped matching</li>\n<li><strong>Diagnostics</strong> <code>@warn</code> / <code>@debug</code> / <code>@error</code> report from a rule; the selector is the condition</li>\n<li><strong>Built-in modules</strong> <code>@use "quark:math"</code>, <code>quark:list</code>, <code>quark:map</code>, <code>quark:string</code>, <code>quark:date</code>, <code>quark:url</code>, <code>quark:util</code> — pure helpers, imported like JS modules</li>\n<li><strong>Attribute helpers</strong> <code>dataset</code>, <code>ariaset</code>, <code>class</code>, <code>none</code> to clear</li>\n<li><strong>JS modules</strong> Call app helpers from expressions via <code>@use "/url"</code></li>\n<li><strong>Scoped host</strong> Sheet + targets share a parent; updates follow DOM mutations</li>\n<li><strong>DevTools</strong> <code>Quark.attachDevtools()</code> reports rule applications and <code>$variables</code> to the Nucleus DevTools extension</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>App authors almost always load Quark through <code><quark-sheet></code>:</p>\n<include-content data-language=\"html\"><template><section>\n <quark-sheet>\n details[open] [bind-status] {\n content: \"Open\";\n }\n details:not([open]) [bind-status] {\n content: \"Closed\";\n }\n </quark-sheet>\n <details>\n <summary>Panel</summary>\n <span bind-status></span>\n </details>\n</section></template></include-content>\n<p>Programmatic API (tests / custom hosts):</p>\n<include-content data-language=\"ts\"><template>import { Quark } from \"@excom/quark\";\n\nconst quark = new Quark({\n src: `span { content: \"four times two equals #{twice(4)}\"; }`,\n modules: { dfault: { twice: (n) => n * 2 } },\n});\nquark.register({ sheetElement }); // host = sheetElement.parentElement\n// …\nquark.unregister();</template></include-content>\n<h3 id=\"md-documentation\">Documentation</h3>\n<p>Syntax</p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/quark/sheets\" role=\"link\">Sheets & scoping</spa-a> — <code><quark-sheet></code>, <code>@scope</code>, <code>is-global</code>, what runs</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/syntax\" role=\"link\">Syntax</spa-a> — rules, declarations, literals, operators, <code>if()</code></li>\n</ul>\n<p>Selectors</p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/quark/selectors\" role=\"link\">Selectors</spa-a> — combinators and pseudo-classes, what is observed</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/reactivity\" role=\"link\">Reactivity</spa-a> — when a rule re-runs, loops, timing</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/no_reversion\" role=\"link\">No reversion</spa-a> — write the inverse rule</li>\n</ul>\n<p>Declarations</p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/quark/declarations\" role=\"link\">Declaration kinds</spa-a> — what a key does</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/attributes\" role=\"link\">Attributes</spa-a> — attributes, <code>class</code> / <code>dataset</code> / <code>ariaset</code>, form controls</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/content\" role=\"link\">Content</spa-a> — <code>content</code>, <code>template()</code>, <code>iterate()</code>, <code>dangerous-html()</code></li>\n<li><spa-a route-href=\"/nucleus/packages/quark/css_variables\" role=\"link\">CSS variables</spa-a> — <code>--custom-props</code></li>\n</ul>\n<p>Values & expressions</p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/quark/variables\" role=\"link\">Variables</spa-a> — <code>$variables</code>, cascade, <code>unset</code>, raising state</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/js_writes\" role=\"link\">Writing from JS</spa-a> — <code>element.quark.setProperty()</code></li>\n<li><spa-a route-href=\"/nucleus/packages/quark/values\" role=\"link\">Values & keywords</spa-a> — <code>none</code> / <code>preserve</code> / <code>unset</code>, wipes and no-ops</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/expressions\" role=\"link\">Expressions</spa-a> — name resolution, operators, <code>if()</code>, lists and maps</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/builtins\" role=\"link\">Built-in functions</spa-a> — <code>attr()</code>, <code>prop()</code>, <code>iterate()</code>, <code>event</code>, …</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/methods\" role=\"link\">Allowed methods</spa-a> — <code>.toFixed()</code>, <code>.join()</code>, …</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/modules\" role=\"link\">Built-in modules</spa-a> — <code>@use "quark:math"</code>, <code>quark:list</code>, <code>quark:map</code>, <code>quark:string</code>, <code>quark:date</code>, <code>quark:url</code>, <code>quark:util</code></li>\n<li><spa-a route-href=\"/nucleus/packages/quark/element_properties\" role=\"link\">Element properties</spa-a> — <code>prop("provision")</code>, <code>element</code></li>\n</ul>\n<p>At-rules</p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/quark/at_rules\" role=\"link\">At-rules</spa-a> — the ones that run</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/use\" role=\"link\"><code>@use</code></spa-a> — JS modules</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/on\" role=\"link\"><code>@on</code></spa-a> — events, blocks, options</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/dispatch\" role=\"link\"><code>@dispatch</code> / <code>@command</code></spa-a> — outgoing events and commands</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/view_transition\" role=\"link\"><code>@view-transition</code></spa-a> — animated writes</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/delay\" role=\"link\"><code>@delay</code></spa-a> — deferred writes</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/diagnostics\" role=\"link\"><code>@warn</code> / <code>@debug</code> / <code>@error</code></spa-a> — diagnostics from a rule</li>\n</ul>\n<p>Runtime</p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/quark/js_api\" role=\"link\">JS API</spa-a> — <code>Quark</code>, <code>whenSettled()</code>, DevTools</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/loop_guard\" role=\"link\">Loop guard</spa-a> — runaway chains are cut</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/limitations\" role=\"link\">Limitations</spa-a> — beta limits and pitfalls</li>\n</ul>\n<p>The grammar (EBNF, precedence, AST) is the <code>quark-parser</code> package's <em>Language reference</em>.</p>\n<h3 id=\"md-examples\">Examples</h3>\n<h4 id=\"md-react-to-element-state\">React to element state</h4>\n<p>Native element state drives content — no JS, no listeners:</p>\n<p><include-content data-demo=\"toggle-content\"></include-content></p>\n<h4 id=\"md-list-from-a-provider\">List from a provider</h4>\n<p><code>prop("provision")</code> pulls the fetch payload; <code>iterate()</code> renders a row per item and re-renders on every provision:</p>\n<p><include-content data-demo=\"provider-list\"></include-content></p>\n<h4 id=\"md-call-a-module-helper\">Call a module helper</h4>\n<include-content data-language=\"html\"><template><quark-sheet>\n @use \"/helpers.js\" as *;\n\n #out { content: formatPrice($amount); }\n</quark-sheet></template></include-content>\n<h4 id=\"md-count-clicks-from-js\">Count clicks from JS</h4>\n<p>The sheet hands the owner element to a JS helper; each click calls <code>owner.quark.setProperty("$count", …)</code> and every rule reading <code>$count</code> below the owner re-runs. See <spa-a route-href=\"/nucleus/packages/quark/js_writes\" role=\"link\">Writing from JS</spa-a>.</p>\n<p><include-content data-demo=\"js-api\"></include-content></p>\n",
|
|
24
|
+
"docs": {
|
|
25
|
+
"attributes": "<h1 id=\"md-attributes\">Attributes</h1>\n<p>Any property that is not <code>$…</code> / <code>content</code> / <code>class</code> / <code>dataset</code> / <code>ariaset</code> becomes an attribute of the matched element; <code>none</code> removes it.</p>\n<h2 id=\"md-writing-attributes\">Writing attributes</h2>\n<include-content data-language=\"quark\"><template>dialog[open] input {\n autofocus: \"\";\n}\ndialog:not([open]) input {\n autofocus: none;\n}</template></include-content>\n<p>A string or number is written as text, a boolean writes <code>""</code> (present) or removes the attribute, and a wipe value (<code>none</code>, <code>null</code>, <code>undefined</code>) removes it. Custom attributes always contain a dash (<code>data-is-empty</code>, <code>is-open</code>) so they can never collide with a native one.</p>\n<h2 id=\"md-reading-attributes\">Reading attributes</h2>\n<p>A literal <code>attr("x")</code> in an expression subscribes to <code>x</code> — the rule re-runs when that attribute changes, even if <code>x</code> is not in the selector. <code>attr($name)</code> does not subscribe.</p>\n<h2 id=\"md-attribute-helpers\">Attribute helpers</h2>\n<ul>\n<li><code>class:</code> sets the <code>class</code> attribute from a string (replaces), an array (joined) or an object (<code>{ name: boolean }</code> toggles each class).</li>\n<li><code>dataset:</code> writes one <code>data-*</code> attribute per key (camelCase → dash-case) and removes the <code>data-*</code> attributes this sheet set earlier; <code>ariaset:</code> does the same with the <code>aria-</code> prefix. Unpack a pre-defined object (<code>dataset: item</code>) rather than constructing a map for it.</li>\n</ul>\n<h2 id=\"md-form-controls\">Form controls</h2>\n<p>On <code><input></code> and <code><option></code> the <code>value</code> / <code>checked</code> / <code>selected</code> attributes are only defaults; once the user has touched the control the browser stops mirroring them into the live property. Quark keeps the attribute authoritative: writing <code>value:</code> / <code>checked:</code> on an <code><input></code> or <code>selected:</code> on an <code><option></code> also sets the live property, so an edited control follows the rule whenever it writes (even to the value the attribute already holds). Custom elements are not touched, and <code><select></code> has no <code>value</code> attribute (write <code>selected:</code> on its options). The sync is one-way: typing never updates an attribute and is not observed.</p>\n<include-content data-language=\"quark\"><template>[data-unit=\"f\"] input {\n value: ($celsius * 9 / 5 + 32).toFixed(1); /* shows even after the user typed */\n}</template></include-content>\n<p>A text result painted into a <code><textarea></code> is mirrored the same way — see <spa-a route-href=\"/nucleus/packages/quark/content\" role=\"link\">Content</spa-a>.</p>\n",
|
|
26
|
+
"at_rules": "<h1 id=\"md-at-rules\">At-rules</h1>\n<p>Seven at-rules run: <code>@use</code> imports modules, <code>@scope</code> anchors rules, <code>@on</code> wires listeners, <code>@dispatch</code> / <code>@command</code> send events and commands from an <code>@on</code> block, <code>@view-transition</code> animates a block's writes, <code>@delay</code> defers a block, and <code>@warn</code> / <code>@debug</code> / <code>@error</code> report from a rule.</p>\n<h2 id=\"md-reference\">Reference</h2>\n<p><code>@on</code> takes a comma list of event names, an optional options group (a map: <code>name: value</code> entries and bare flags) and a block applied once per event — or just the options. <code>@dispatch</code> / <code>@command</code> take a name list and options, inside <code>@on</code> blocks only. <code>@delay</code> takes a duration expression and a block; <code>@warn</code> / <code>@debug</code> / <code>@error</code> take an expression. <em>Generated.</em></p>\n<!-- generated:at-rules -->\n<table>\n<thead>\n<tr>\n<th>At-rule</th>\n<th>Effect</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>@use "url" [as name | as *];</code></td>\n<td>Imports a JS module anywhere in the sheet. The namespace defaults to the URL's last path segment without its extension; <code>as *</code> merges exports into the bare scope, last import winning. A <code>with (…)</code> clause is a parse error.</td>\n</tr>\n<tr>\n<td><code>@scope { … }</code></td>\n<td>Rules inside stay anchored to the host in a global sheet (the implicit wrapper of a scoped sheet). It takes no prelude.</td>\n</tr>\n<tr>\n<td><code>@on <event>[, <event>] [(options)] { … }</code></td>\n<td>Inside a rule: listens for the events (bare names such as <code>click</code> or <code>super-form-success</code>, or strings; a comma list shares one listener) on the matched element and applies the block once per event — a one-shot transaction. The block is an ordinary rule body: declarations write the matched element (attributes, <code>$variables</code>, <code>--props</code>, <code>content</code>), nested rules write its matching descendants, or its siblings when the nested selector starts with <code>+</code> / <code>~</code>, <code>@dispatch</code> / <code>@command</code> statements fire after those writes are queued. <code>event</code> names the DOM event and <code>target</code> the delegate (or <code>event.target</code>) inside the block and in its per-event options. <code>@on</code> inside a block is not supported.</td>\n</tr>\n<tr>\n<td><code>@on <event> (option, option: value) …</code></td>\n<td>An options group after the events gates and configures the listener; with it the block is optional (<code>@on submit (prevent-default);</code>). A bare name is a flag. Filters: <code>target: "<selector>"</code> (delegation — fires only when the event target is inside a matching descendant; that element is <code>target</code> in the block), <code>self</code> (only when the event target is the matched element), <code>key: "Escape"</code> / <code>"Shift+K"</code> (keyboard chords; space-separated alternatives). Event flags: <code>prevent-default</code>, <code>stop-propagation</code>, <code>stop-immediate-propagation</code>. Timing: <code>debounce: <ms></code>, <code>throttle: <ms></code>. JS: <code>handle: fn</code> — a function (or a call returning one, or a list <code>(a, b)</code>) called with the event before the block, <code>this</code> being the element. Registration: <code>once</code> (removed after the first event that passes the filters), <code>passive</code>, <code>capture</code>, <code>host: window</code> / <code>host: document</code> (listen there while the element is connected; <code>target</code> then resolves against the whole document). <code>target</code>, <code>key</code>, <code>debounce</code>, <code>throttle</code> and <code>handle</code> are evaluated when the event fires, in the block's scope; the rest once per match. Two <code>@on</code>s for one event may coexist when their options differ.</td>\n</tr>\n<tr>\n<td><code>@dispatch <event>[, <event>] [(options)];</code></td>\n<td>Inside an <code>@on</code> block (or a nested rule / <code>@delay</code> block within one): dispatches a <code>CustomEvent</code> of each name from the block's element after the block's writes are queued — synchronously, before they paint, so the event is an occurrence, not a delivery of State. Options, evaluated per event: <code>detail: <expression></code>; <code>target: "<selector>"</code> (every match in the element's document; <code>:scope</code> = the block's element, not the sheet host — resolved as <code><event-handler target-ref></code> is) or <code>target: <element | list></code> (<code>closest("provider-fetch")</code>); <code>host: window</code> / <code>host: document</code>; <code>form: "<selector>"</code> or <code>form: <form></code> (its field values become the detail, an explicit <code>detail</code> map merges over them); the flags <code>bubbles</code> (default true), <code>cancelable</code> (default true), <code>composed</code> (default false), each settable to <code>false</code>. Dispatching the enclosing <code>@on</code> event is refused; every dispatch is one loop-guard hop, so an event cycle is cut. Not allowed at rule level: a rule matching is not an occurrence.</td>\n</tr>\n<tr>\n<td><code>@command <name>[, <name>] [(target: …)];</code></td>\n<td>Inside an <code>@on</code> block: invokes each command on the target elements (the block's element by default; <code>target</code> as for <code>@dispatch</code>) the way a <code><button command commandfor></code> would — native commands (<code>show-modal</code>, <code>close</code>, <code>request-close</code>, <code>show-popover</code>, <code>hide-popover</code>, <code>toggle-popover</code>) and custom <code>--names</code>, which reach the target as a <code>command</code> event. Where the browser lacks the Invoker Commands API, custom commands are dispatched as a synthetic <code>command</code> event and native ones call the element's method. Only <code>target</code> is an option.</td>\n</tr>\n<tr>\n<td><code>@view-transition [(options)] { … }</code></td>\n<td>Inside a rule, around rules, or inside an <code>@on</code> block: every paint of the writes in the block — its declarations (on the rule's element) and its nested rules' — commits inside <code>document.startViewTransition()</code>, so CSS animates the change (<code>view-transition-name</code>, <code>::view-transition-*</code>). It scopes <em>how</em> writes land, never <em>when</em> rules run. The transition waits for Quark to settle before the new state is captured, so writes that react to these land in the same cut. Committed without a transition: writes that change nothing, the sheet's first render, <code>prefers-reduced-motion: reduce</code>, browsers without the API, and writes while another view transition is active.</td>\n</tr>\n<tr>\n<td><code>@view-transition (option, option: value) { … }</code></td>\n<td><code>types: "a b"</code> names the transition for <code>:active-view-transition-type()</code> (a string or a list). <code>timeout: <ms></code> caps the settle wait (default 300). <code>delay: <ms></code> holds these writes back first. <code>first-render</code> also animates the sheet's first render. <code>if-active: skip | replace</code>: while another transition runs, commit unanimated (default) or start anyway, which skips the running one. <code>until: "<selector>"</code> keeps the transition open until the block's element matches the selector, <code>until: <promise></code> until it settles (default timeout 1000; the page is frozen meanwhile, so for short waits only). Values are evaluated per write.</td>\n</tr>\n<tr>\n<td><code>@delay <ms> { … }</code></td>\n<td>Inside a rule or an <code>@on</code> / <code>@delay</code> block: applies the block once, <code><ms></code> milliseconds (an expression) after the rule applied or the event fired — provided the element is still in the document and the rule still matches; otherwise the block is dropped. Applying the rule again restarts the timer (one per element). The block is an ordinary rule body (declarations write the matched element, nested rules its descendants; <code>event</code> / <code>target</code> are kept inside an <code>@on</code> block). Timers keep the loop guard's causal depth and are cleared when the sheet unregisters.</td>\n</tr>\n<tr>\n<td><code>@warn <expression>; / @debug <expression>; / @error <expression>;</code></td>\n<td>Inside a rule or a block: evaluates the expression on the matched element and reports it — to the console at that level (<code>@debug</code> is silent below debug logging) and to DevTools as <code>quark/diagnostic</code>. The selector is the condition (<code>img:not([alt]) { @warn "img needs alt"; }</code>). <code>@warn</code> / <code>@error</code> speak once per element and rule; <code>@debug</code> speaks on every application, so it re-logs when a binding or <code>prop()</code> it reads changes. A comma list reports one value per item.</td>\n</tr>\n</tbody></table>\n<!-- /generated --><include-content data-language=\"quark\"><template>form {\n @on submit (prevent-default, handle: saveDraft);\n @on keydown (key: \"Escape\") { is-editing: none; }\n @on reset { @dispatch draft-cleared (target: \"#status\"); }\n &:not([is-locked]) { @on input (debounce: 200) { data-draft: event.target.value; } }\n button[data-copy] { @on click { data-copied: \"\"; @delay 2000 { data-copied: none; } } }\n img:not([alt]) { @warn \"img needs alt\"; }\n}</template></include-content>\n<h2 id=\"md-pages\">Pages</h2>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/quark/use\" role=\"link\"><code>@use</code></spa-a> — JS modules</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/on\" role=\"link\"><code>@on</code></spa-a> — events, blocks, options</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/dispatch\" role=\"link\"><code>@dispatch</code> / <code>@command</code></spa-a> — outgoing events and commands</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/view_transition\" role=\"link\"><code>@view-transition</code></spa-a> — animated writes</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/delay\" role=\"link\"><code>@delay</code></spa-a> — deferred writes</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/diagnostics\" role=\"link\"><code>@warn</code> / <code>@debug</code> / <code>@error</code></spa-a> — diagnostics</li>\n<li><code>@scope</code> — see <spa-a route-href=\"/nucleus/packages/quark/sheets\" role=\"link\">Sheets & scoping</spa-a></li>\n</ul>\n<p>That is the whole set. Any other name — CSS's <code>@media</code>, <code>@supports</code>, <code>@keyframes</code>, SCSS's <code>@if</code>, <code>@mixin</code>, … — is a parse error (<code>@media is not a Quark at-rule</code>).</p>\n",
|
|
27
|
+
"builtins": "<h1 id=\"md-built-in-functions\">Built-in functions</h1>\n<p>Available in every expression, after <code>@use</code> exports: element reads, loop context, rendering, listeners, utilities.</p>\n<h2 id=\"md-reference\">Reference</h2>\n<p><em>Generated.</em></p>\n<!-- generated:builtin-functions -->\n<p><strong>Element reads</strong></p>\n<table>\n<thead>\n<tr>\n<th>Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>attr("name")</code></td>\n<td>The matched element's attribute value (<code>null</code> when absent). <code>attr("content")</code> returns its <code>innerHTML</code>. A literal name is observed: the rule re-runs when that attribute changes, even if it is not in the selector. <code>attr($name)</code> reads but does not subscribe.</td>\n</tr>\n<tr>\n<td><code>prop("name")</code></td>\n<td>The matched element's JS property (<code>prop("provision")</code> reads a Neutron provision). A literal name is observed: the rule re-runs when JS assigns <code>element.name</code> (coalesced per microtask). In-place mutation and browser-driven native state are not observed. <code>prop($name)</code> reads but does not subscribe.</td>\n</tr>\n<tr>\n<td><code>closest("selector")</code></td>\n<td><code>element.closest(selector)</code> from the matched element: the nearest ancestor-or-self matching the selector, else <code>null</code>. Not observed.</td>\n</tr>\n<tr>\n<td><code>element</code></td>\n<td>The matched element itself — the node the rule is applied to (inside an <code>@on … { }</code> block the listening element; <code>target</code> is the delegate). Hand it to <code>@use</code> functions that need the node: <code>@on click fire(element)</code>, <code>$chart: mount(element)</code>. Reads through it are not observed — use <code>attr()</code> / <code>prop()</code> for reactive reads.</td>\n</tr>\n</tbody></table>\n<p><strong>Loop context</strong></p>\n<table>\n<thead>\n<tr>\n<th>Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>item</code></td>\n<td>Inside an <code>iterate()</code> row: the current collection item (the value for objects). <code>undefined</code> outside a row.</td>\n</tr>\n<tr>\n<td><code>index</code></td>\n<td>Inside an <code>iterate()</code> row: the current position (the key for objects). <code>undefined</code> outside a row.</td>\n</tr>\n</tbody></table>\n<p><strong>Rendering (<code>content</code>)</strong></p>\n<table>\n<thead>\n<tr>\n<th>Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>iterate(collection, "template-ref"?, "key-property"?)</code></td>\n<td>For <code>content</code>: renders one clone of the element's <code><template></code> child (or the template at <code>template-ref</code>, a selector / URL) per array item or object entry, keyed by <code>key-property</code> (else a content hash) so existing rows are reused. <code>null</code> / <code>undefined</code> wipes the rows; an empty collection clears them; a non-collection no-ops.</td>\n</tr>\n<tr>\n<td><code>template("template-ref"?)</code></td>\n<td>For <code>content</code>: renders one clone of the referenced <code><template></code> (selector or URL; defaults to the element's own <code><template></code> child).</td>\n</tr>\n<tr>\n<td><code>dangerous-html(html)</code></td>\n<td>For <code>content</code>: sets <code>innerHTML</code> to the string. No sanitizing: never pass user-controlled markup.</td>\n</tr>\n</tbody></table>\n<p><strong>Event listeners</strong></p>\n<table>\n<thead>\n<tr>\n<th>Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>event</code></td>\n<td>Inside an <code>@on … { }</code> block, its per-event options and its <code>@dispatch</code> / <code>@command</code> statements: the DOM event being handled (<code>event.target</code>, <code>event.detail</code>, …). <code>undefined</code> elsewhere.</td>\n</tr>\n<tr>\n<td><code>target</code></td>\n<td>Inside an <code>@on … { }</code> block and its per-event options: the element the <code>target:</code> option matched (the delegate), or <code>event.target</code> without that option. <code>undefined</code> elsewhere.</td>\n</tr>\n<tr>\n<td><code>prevent-default</code></td>\n<td>A listener that calls <code>event.preventDefault()</code>, for <code>handle:</code>. The <code>(prevent-default)</code> flag is the shorter form.</td>\n</tr>\n<tr>\n<td><code>stop-propagation</code></td>\n<td>A listener that calls <code>event.stopPropagation()</code>, for <code>handle:</code>. The <code>(stop-propagation)</code> flag is the shorter form.</td>\n</tr>\n</tbody></table>\n<p><strong>Utilities</strong></p>\n<table>\n<thead>\n<tr>\n<th>Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>ternary(condition, whenTrue, whenFalse?)</code></td>\n<td><code>whenTrue</code> if <code>condition</code> is truthy, else <code>whenFalse</code> (<code>null</code> when omitted). Prefer <code>if()</code> for multi-arm conditionals.</td>\n</tr>\n</tbody></table>\n<p><strong>Debugging</strong></p>\n<table>\n<thead>\n<tr>\n<th>Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>log(...values)</code></td>\n<td>Logs the values to the console and returns them as an array.</td>\n</tr>\n<tr>\n<td><code>debug(...values)</code></td>\n<td>Hits a <code>debugger</code> statement and returns the values as an array.</td>\n</tr>\n</tbody></table>\n<!-- /generated --><p>Reading the matched element's JS properties and handing the node to module functions: <spa-a route-href=\"/nucleus/packages/quark/element_properties\" role=\"link\">Element properties</spa-a>. Methods callable on values: <spa-a route-href=\"/nucleus/packages/quark/methods\" role=\"link\">Allowed methods</spa-a>.</p>\n",
|
|
28
|
+
"content": "<h1 id=\"md-content\">Content</h1>\n<p><code>content:</code> replaces an element's rendered children — text, a template clone, one row per item, or raw HTML.</p>\n<h2 id=\"md-text-templates-html\">Text, templates, HTML</h2>\n<include-content data-language=\"quark\"><template>article {\n content: template(\"#article-tmpl\"); /* or template(\"/views/a.html\") */\n}\ndetails:not([open]) p {\n content: none;\n}\nheader {\n content: dangerous-html(getHeaderHtml());\n}</template></include-content>\n<p>A JS callout may also return a <code>Node</code> / <code>NodeList</code> — same paint path as <code>template()</code> / <code>iterate()</code>. A source <code><template></code> child is kept; writing into a <code><template></code> targets its <code>.content</code>; a promise is awaited.</p>\n<p>A text result painted into a <code><textarea></code> is also mirrored to its live <code>.value</code> (the text is only the default value; see <spa-a route-href=\"/nucleus/packages/quark/attributes#md-form-controls\" role=\"link\">Attributes</spa-a>).</p>\n<h2 id=\"md-iterations\">Iterations</h2>\n<p><code>iterate(array)</code> renders one copy of the element's <code><template></code> per item:</p>\n<p><include-content data-demo=\"iterate\"></include-content></p>\n<p><code>item</code> / <code>index</code> are available to matching rules for each row. Objects iterate as key → <code>index</code>, value → <code>item</code>. Pass a key property (<code>iterate($todos, none, "id")</code>) so existing rows are reused when the collection changes.</p>\n<h2 id=\"md-pitfall\">Pitfall</h2>\n<p>Do not render a template that re-matches the same rule — the loop guard cuts it after 50 nested paints, but the fix is the selector:</p>\n<include-content data-language=\"quark\"><template>/* BAD — each new span matches again */\nspan {\n content: template(\"#span-template\");\n}</template></include-content>\n",
|
|
29
|
+
"css_variables": "<h1 id=\"md-css-variables\">CSS variables</h1>\n<p><code>--custom-prop:</code> declarations write CSS custom properties on matched elements (inline style), so stylesheets consume Quark state via <code>var()</code> — computed colors, progress percentages, live theming. Values that attribute selectors can't express.</p>\n<h2 id=\"md-writing-custom-properties\">Writing custom properties</h2>\n<p><include-content data-demo=\"css-variables\"></include-content></p>\n<ul>\n<li>Values are Quark expressions — <strong>CSS literals must be quoted</strong>: <code>--accent: "#ccc"</code>, not <code>--accent: #ccc</code>. Same for keywords, lengths, and functions: <code>"red"</code>, <code>"10px"</code>, <code>"var(--x)"</code>, <code>"red !important"</code> (a trailing <code>!important</code> inside the string maps to the priority argument).</li>\n<li>Numeric expression results work: <code>--progress: "#{($done / $total * 100)}%"</code>.</li>\n<li>Wipe values (<code>none</code> / <code>undefined</code> / <code>null</code>) remove the property; <code>preserve</code> no-ops.</li>\n<li>Write-only: Quark never reads CSS variables back. Own the value in a <code>$variable</code> if rules need to react to it.</li>\n</ul>\n<h2 id=\"md-no-reversion\">No reversion</h2>\n<p>Like every other write, a CSS variable persists after its rule stops matching — write the inverse rule (see <spa-a route-href=\"/nucleus/packages/quark/no_reversion\" role=\"link\">No reversion</spa-a>).</p>\n",
|
|
30
|
+
"declarations": "<h1 id=\"md-declaration-kinds\">Declaration kinds</h1>\n<p>The property key selects what a declaration does: a <code>$variable</code>, a CSS variable, <code>content</code>, one of the attribute helpers, or an attribute of that name.</p>\n<h2 id=\"md-by-key-shape\">By key shape</h2>\n<p>Checked top to bottom; the first match wins. Listeners are at-rules, not declarations. <em>Generated.</em></p>\n<!-- generated:declaration-kinds -->\n<table>\n<thead>\n<tr>\n<th>Key</th>\n<th>Effect</th>\n<th>Accepts</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>$name</code></td>\n<td>Stores a binding on each matched element; consumers resolve it by walking up from their own element (CSS custom-property semantics, shared across sheets).</td>\n<td>Any value. <code>unset</code> deletes the binding.</td>\n</tr>\n<tr>\n<td><code>--name</code></td>\n<td>Writes the CSS custom property <code>--name</code> on the element's inline style. Write-only: Quark never reads CSS variables back. A trailing <code>!important</code> inside the string sets the priority.</td>\n<td>A string or number. CSS literals must be quoted (<code>"#ccc"</code>, <code>"10px"</code>); bare <code>#hex</code> / unit numbers are rejected at build.</td>\n</tr>\n<tr>\n<td><code>content</code></td>\n<td>Replaces the element's rendered children (a source <code><template></code> child is kept). Promises are awaited. Writing into a <code><template></code> targets its <code>.content</code>. On a <code><textarea></code> a text result is also mirrored to the live <code>.value</code> (the text is only the default value).</td>\n<td>A string (text), a <code>Node</code> / <code>NodeList</code>, or the result of <code>template()</code> / <code>iterate()</code> / <code>dangerous-html()</code>. Wipe values clear.</td>\n</tr>\n<tr>\n<td><code>class</code></td>\n<td>Sets the <code>class</code> attribute.</td>\n<td>A string (replaces), an array (joined with spaces), or an object (<code>{ name: boolean }</code> toggles each class). Wipe values remove the attribute.</td>\n</tr>\n<tr>\n<td><code>dataset</code></td>\n<td>Writes one <code>data-*</code> attribute per key (camelCase → dash-case) and removes <code>data-*</code> attributes this sheet set earlier.</td>\n<td>An object. Strings / numbers write as-is, booleans as present / absent, string arrays space-joined, objects and other arrays as their length.</td>\n</tr>\n<tr>\n<td><code>ariaset</code></td>\n<td>Same as <code>dataset</code>, with the <code>aria-</code> prefix.</td>\n<td>An object (same conversions as <code>dataset</code>).</td>\n</tr>\n<tr>\n<td><code><anything else></code></td>\n<td>Sets the attribute of that name on the matched element (<code>none</code> removes it). Always contains a dash in practice; <code>autofocus: ""</code> sets a boolean attribute. On native form controls the attribute is authoritative: <code>value</code> / <code>checked</code> on <code><input></code> and <code>selected</code> on <code><option></code> also set the live property, so a control the user has edited still follows the rule.</td>\n<td>A string or number (written as text), a boolean (<code>true</code> → <code>""</code>, <code>false</code> → removed), or a wipe value.</td>\n</tr>\n</tbody></table>\n<!-- /generated --><h2 id=\"md-details-per-kind\">Details per kind</h2>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/quark/attributes\" role=\"link\">Attributes</spa-a> — the default kind, <code>class</code> / <code>dataset</code> / <code>ariaset</code>, form controls</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/content\" role=\"link\">Content</spa-a> — <code>content</code> with <code>template()</code>, <code>iterate()</code>, <code>dangerous-html()</code></li>\n<li><spa-a route-href=\"/nucleus/packages/quark/css_variables\" role=\"link\">CSS variables</spa-a> — <code>--name</code></li>\n<li><spa-a route-href=\"/nucleus/packages/quark/variables\" role=\"link\">Variables</spa-a> — <code>$name</code></li>\n</ul>\n",
|
|
31
|
+
"delay": "<h1 id=\"md-delay\">@delay</h1>\n<p><code>@delay <ms> { … }</code> applies a block once, after a pause — a "Copied!" flash, a toast that hides itself, an undo window — with no timer in JS.</p>\n<h2 id=\"md-writing-a-delay\">Writing a delay</h2>\n<include-content data-language=\"quark\"><template>button[data-copy] {\n @on click {\n data-copied: \"\";\n @delay 2000 { data-copied: none; } /* rapid clicks restart it */\n }\n}\noutput[is-visible] { @delay 4000 { is-visible: none; } }\ntodo-item[is-pending-delete] {\n @on click (target: \"[data-undo]\") { is-pending-delete: none; }\n @delay 5000 { is-deleted: \"\"; } /* dropped if undo clears the gate first */\n}\n[data-flash] { @delay +attr(\"data-flash-ms\") or 1500 { data-flash: none; } }</template></include-content>\n<p>The duration is any expression — a literal, a binding, an <code>attr()</code> read, arithmetic, a <code>quark:math</code> call — in milliseconds. The block is an ordinary rule body: declarations write the matched element, nested rules write its matching descendants, and it may hold another <code>@delay</code> or a <code>@view-transition</code>. <code>@on</code> inside the block is not supported.</p>\n<h2 id=\"md-when-it-fires\">When it fires</h2>\n<ul>\n<li>The timer starts when the rule applies to the element, or when the event fires inside an <code>@on</code> block, and <strong>restarts on every application</strong> — one timer per element. A rule re-applies when its selector's dependencies change, so a rule that fans out on unrelated attribute changes restarts its delays too.</li>\n<li>When the timer fires, the block applies <strong>only if the element is still in the document and the rule still matches</strong>; otherwise it is dropped. That is what makes the undo idiom above safe: clearing the gate cancels the pending write.</li>\n<li>Inside an <code>@on</code> block, <code>event</code> and <code>target</code> keep the values the block was scheduled with.</li>\n<li>Pending timers are cleared when the sheet unregisters.</li>\n</ul>\n<h2 id=\"md-safety\">Safety</h2>\n<p>The block keeps the <spa-a route-href=\"/nucleus/packages/quark/loop_guard\" role=\"link\">loop guard</spa-a>'s causal depth of the run that scheduled it, so two delays that keep re-triggering each other are cut like any other runaway chain instead of ticking forever.</p>\n<p>Time is otherwise an Adapter's protocol: <code>@delay</code> is for a wait that belongs to one State change, not for clocks, polling or animation — those are elements (<code><timer-…></code>) and CSS.</p>\n<p>DevTools sees every timer as a <code>["quark", "delay"]</code> publication: <code>scheduled</code> (with <code>ms</code>), <code>fired</code>, or <code>dropped</code> (with <code>reason</code>: <code>"disconnected"</code>, <code>"unregistered"</code>, <code>"unmatched"</code>) — see <spa-a route-href=\"/nucleus/packages/quark/js_api\" role=\"link\">JS API</spa-a>.</p>\n",
|
|
32
|
+
"diagnostics": "<h1 id=\"md-warn-debug-error\">@warn / @debug / @error</h1>\n<p>Diagnostics report from inside a rule; the selector is the condition. Nothing is written to the document.</p>\n<h2 id=\"md-writing-diagnostics\">Writing diagnostics</h2>\n<include-content data-language=\"quark\"><template>img:not([alt]) { @warn \"img needs alt\"; }\nform button:not([type]) { @warn \"button defaults to type=submit\"; }\nprovider-fetch[is-error] { @error \"fetch failed\", prop(\"provision\").error; }\ntodo-list {\n $todos: prop(\"provision\").body;\n @debug \"todos\", $todos.length; /* again when $todos changes */\n @on todo-remove { @debug \"remove\", event.detail.id; }\n}</template></include-content>\n<p>Each statement evaluates its expression on the matched element — bindings, <code>attr()</code>, <code>prop()</code>, <code>event</code> inside an <code>@on</code> block, module functions — and reports it. A comma list reports one value per item; a single expression is one value, even when it is a list.</p>\n<h2 id=\"md-when-it-speaks\">When it speaks</h2>\n<ul>\n<li><code>@warn</code> and <code>@error</code> speak <strong>once per element and rule</strong>. A warning repeated on every re-run is noise; a new offending element is its own warning.</li>\n<li><code>@debug</code> speaks <strong>on every application</strong>, so it re-logs when a binding or <code>prop()</code> it reads changes — a trace of the value over time.</li>\n<li>A failing expression reports a <code>quark/error</code> (like any declaration) instead of a diagnostic.</li>\n<li>Outside a rule there is no element to evaluate against: a top-level statement is rejected when the sheet is built.</li>\n</ul>\n<h2 id=\"md-where-it-goes\">Where it goes</h2>\n<ul>\n<li>The console, at the matching logger level: <code>Quark @warn (img:not([alt])): img needs alt</code>. <code>@debug</code> is silent unless Quark logs at debug level (<code>VITE_LOG_LEVEL</code> ≥ 3).</li>\n<li>The DevTools hook, as <code>["quark", "diagnostic"]</code> — <code>level</code>, <code>values</code>, <code>message</code>, <code>expression</code>, the selector, rule and element — so the Nucleus DevTools extension and the agent tools can list them next to the rule. See <spa-a route-href=\"/nucleus/packages/quark/js_api\" role=\"link\">JS API</spa-a>.</li>\n</ul>\n<p>Use <code>@warn</code> for invariants a sheet can state better than a test (<code>img:not([alt])</code>, <code>button:not([type])</code>), <code>@error</code> for States that should never occur, and <code>@debug</code> while developing a rule — then remove it. The <code>log()</code> and <code>debug()</code> built-ins remain for tracing inside an expression.</p>\n",
|
|
33
|
+
"dispatch": "<h1 id=\"md-dispatch-command\">@dispatch / @command</h1>\n<p><code>@dispatch</code> sends a custom event and <code>@command</code> invokes a command, from inside an <code>@on</code> block. They are the outgoing half of <code>@on</code>: the sheet heard an event, wrote State, and now tells another element.</p>\n<include-content data-language=\"quark\"><template>todo-item {\n @on click (target: \"[data-remove]\") {\n @dispatch todo-remove (detail: (id: attr(\"data-id\")));\n }\n}\nprovider-fetch {\n @on super-form-success { @dispatch provider-fetch-trigger; }\n}\nbutton[data-help] {\n @on click { @command toggle-popover (target: element.nextElementSibling); }\n}</template></include-content>\n<p><include-content data-demo=\"dispatch\"></include-content></p>\n<h2 id=\"md-where-they-may-appear\">Where they may appear</h2>\n<p>Inside an <code>@on … { }</code> block, its nested rules (then the event goes out from each matching descendant) and a <code>@delay</code> block within it. Not at rule level and not at sheet level: a rule matching is not an occurrence, so a rule cannot announce one. The runtime logs an error and drops such a statement.</p>\n<h2 id=\"md-when-they-run\">When they run</h2>\n<p>At the end of the block, after every write in it has been queued and before those writes paint — synchronously, like a handler. An event is an occurrence, not a delivery of State: a listener that needs the block's writes should react to the State the block wrote (an attribute the recipient's own rule selects on), not to the event. Dispatching the enclosing <code>@on</code> event type is refused. Every dispatch is one <spa-a route-href=\"/nucleus/packages/quark/loop_guard\" role=\"link\">loop-guard</spa-a> hop, so an event cycle (<code>@on a { @dispatch b } … @on b { @dispatch a }</code>) is cut like a write cycle.</p>\n<h2 id=\"md-dispatch\"><code>@dispatch</code></h2>\n<p><code>@dispatch <event>[, <event>] [(options)];</code> — a <code>CustomEvent</code> per name. Options are evaluated per event, in the block's scope (<code>event</code>, <code>target</code>, <code>element</code>, <code>$bindings</code>):</p>\n<table>\n<thead>\n<tr>\n<th>Option</th>\n<th>Effect</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>detail: <expression></code></td>\n<td>The event's <code>detail</code> — a map <code>(id: $id, at: event.timeStamp)</code>, a <code>$binding</code>, anything.</td>\n</tr>\n<tr>\n<td><code>target: "<selector>"</code></td>\n<td>Dispatch on every element matching the selector in the element's document (or shadow root). <strong><code>:scope</code> is the block's element — the one the <code>@on</code> matched — not the sheet's <code>:scope</code> host.</strong> Resolved exactly as <code><event-handler target-ref></code> is (<code>selectAll</code> from kit-utils): <code>provider-fetch:has(+ :scope)</code> is the <code>provider-fetch</code> right before the element, <code>:scope + dialog</code> the dialog right after it. Default: the block's element.</td>\n</tr>\n<tr>\n<td><code>target: <element></code> / <code><list></code></td>\n<td>An element or list of elements from an expression: <code>closest("provider-fetch")</code>, <code>element.nextElementSibling</code>, <code>closest("section").children</code>.</td>\n</tr>\n<tr>\n<td><code>host: window</code> / <code>host: document</code></td>\n<td>Dispatch on the window / document instead.</td>\n</tr>\n<tr>\n<td><code>form: "<selector>"</code> / <code>form: <form></code></td>\n<td>The form's field values (as <code>formToJson</code> reads them) become the detail; an explicit <code>detail</code> map merges over them.</td>\n</tr>\n<tr>\n<td><code>bubbles</code> / <code>cancelable</code> / <code>composed</code></td>\n<td>Event flags. Bare means <code>true</code>; <code>bubbles: false</code> switches one off. Defaults: bubbles and cancelable on, composed off.</td>\n</tr>\n</tbody></table>\n<p>An unmatched <code>target</code> warns once per element and sends nothing. Events bubble by default, so an ancestor's sheet hears a dispatch from a descendant without any <code>target</code>.</p>\n<h2 id=\"md-command\"><code>@command</code></h2>\n<p><code>@command <name>[, <name>] [(target: …)];</code> — invokes each command on the target elements the way a <code><button command="…" commandfor="…"></code> would. Native commands — <code>show-modal</code>, <code>close</code>, <code>request-close</code>, <code>show-popover</code>, <code>hide-popover</code>, <code>toggle-popover</code> — and custom ones, which start with <code>--</code> and reach the target as a <code>command</code> event (<code>event.command</code>; <code>event.source</code> is the invoker — under the Command API the hidden button Quark clicks, so a recipient should read State from itself or the sheet rather than from <code>source</code>; without the API, the block's element).</p>\n<include-content data-language=\"quark\"><template>[data-open-help] { @on click { @command show-modal (target: \"#help\"); } }\n#help { @on keydown (key: \"Escape\") { @command close; } }\n[data-refresh] { @on click { @command --refresh (target: \"#feed\"); } }</template></include-content>\n<p>Where the browser has the Invoker Commands API, a hidden invoker button carries the command so native behaviour and <code>event.source</code> are exactly the platform's. Elsewhere, custom commands are dispatched as a synthetic <code>command</code> event and native ones call the element's method (<code>showModal()</code>, <code>togglePopover()</code>, …); an unknown native command warns once. <code>target</code> is the only option and resolves like <code>@dispatch</code>'s: the whole document, <code>:scope</code> = the block's element (not the sheet host).</p>\n<include-content data-language=\"quark\"><template>:scope {\n button {\n @on click {\n /* the provider-fetch right before this button */\n @command --fetch (target: \"provider-fetch:has(+ :scope)\");\n }\n }\n}</template></include-content>\n<p>A nested rule starting with a sibling combinator is the other spelling: <code>+ provider-fetch { @command --fetch; }</code> inside the block runs against the <code>provider-fetch</code> right after the button, with no <code>target</code> to resolve.</p>\n<include-content data-language=\"quark\"><template>:scope {\n button {\n @on click {\n + provider-fetch { @command --fetch; }\n }\n }\n}</template></include-content>\n<h2 id=\"md-replacing-lt-event-handler-gt\">Replacing <code><event-handler></code></h2>\n<p><code>@on</code> with <code>@dispatch</code> / <code>@command</code> covers what <code><event-handler></code> wires: <code>listen-for</code> is the event list, <code>selector-filter</code> / <code>keycode-filter</code> / <code>is-debounced</code> / <code>host-ref</code> are options, <code>fire-event</code> + <code>detail-*</code> + <code>form-ref</code> are <code>@dispatch (detail: …, form: …)</code>, <code>target-ref</code> is <code>target:</code>, <code>command-name</code> is <code>@command</code>. Prefer the sheet when the page has one; keep the element for markup without a sheet.</p>\n",
|
|
34
|
+
"element_properties": "<h1 id=\"md-element-properties\">Element properties</h1>\n<p><code>prop("<name>")</code> reads a JS property of the matched element and re-runs when JS assigns it — the way a sheet reads an Adapter's <code>provision</code>.</p>\n<h2 id=\"md-prop\"><code>prop()</code></h2>\n<p><code>prop("<name>")</code> reads a JS property of the <strong>matched element</strong>; it mirrors <code>attr("<name>")</code>. <code>prop("provision")</code> reads a Neutron element's provision:</p>\n<include-content data-language=\"quark\"><template>provider-fetch[is-success] {\n $todo: prop(\"provision\").body;\n [bind-title] { content: $todo.title; }\n}</template></include-content>\n<p>A literal <code>prop("x")</code> subscribes to <code>x</code>: the rule re-runs when JS assigns <code>element.x</code> (assignments coalesce per microtask). In-place mutation of an object is not observed — assign a new object. Changes the browser makes without a JS assignment (a user typing into an <code><input></code>'s <code>value</code>, a <code><details></code> toggling <code>open</code>) are not observed either: select on the reflected attribute / listen for the event. <code>prop($name)</code> reads but does not subscribe.</p>\n<h2 id=\"md-reading-an-ancestor-provider\">Reading an ancestor provider</h2>\n<p><code>prop()</code> reads the matched element only. To read an ancestor provider, publish it as a binding from a rule that matches the provider — in a parent sheet whose host contains it (preferred) or an <code>is-global</code> sheet — and read the <code>$binding</code> from descendants:</p>\n<include-content data-language=\"quark\"><template>provider-fetch[is-success] { $todos: prop(\"provision\").body; }\n[bind-count] { content: $todos.length; }</template></include-content>\n<h2 id=\"md-element\"><code>element</code></h2>\n<p><code>element</code> is the matched element itself — the way to hand the node to a module function (a handler factory, a chart mount) without a selector walk. Reads through it are not observed; use <code>attr()</code> / <code>prop()</code> for those:</p>\n<include-content data-language=\"quark\"><template>[data-chart] { $chart: mountChart(element, $series); }\nbutton[data-sku] { @on click (handle: addToCart(element, event)); } /* evaluated per click */</template></include-content>\n",
|
|
35
|
+
"expressions": "<h1 id=\"md-expressions\">Expressions</h1>\n<p>A value is an expression: names resolve through keywords, modules, built-ins and <code>$bindings</code>; operators follow JS; accessors never throw.</p>\n<h2 id=\"md-evaluation\">Evaluation</h2>\n<ul>\n<li><strong>Names</strong> resolve in order: value keywords → <code>@use</code> exports (bare <code>as *</code> exports, then namespaces) → built-in functions → <code>$bindings</code>. A <code>$binding</code> is read by walking up from the matched element to the nearest ancestor-or-self that holds it; an unbound <code>$name</code> is <code>undefined</code> (a wipe), while an unknown bare identifier is an error (a no-op). A module export shadows a built-in of the same name.</li>\n<li><strong>Literals</strong>: unitless numbers are numbers; a number with a unit (<code>10px</code>, <code>50%</code>) and a color (<code>#ccc</code>) evaluate to strings. Strings unescape <code>\\n</code>, <code>\\t</code>, <code>\\r</code>, and <code>\\x</code> → <code>x</code>. Interpolated strings and <code>url(…)</code> join their parts, with <code>null</code> / <code>undefined</code> parts rendered empty.</li>\n<li><strong><code>&</code></strong> in an expression is the matched element's tag name.</li>\n<li><strong>Accessors</strong> never throw: <code>.field</code> and <code>[index]</code> on <code>null</code> / <code>undefined</code> yield <code>undefined</code>. <code>object.$name</code> reads the property literally named <code>$name</code> (namespaced variables: <code>math.$pi</code>).</li>\n<li><strong>Calls</strong>: a bare callee resolves through the name order above and must be a function. A method call on a value (<code>item.name.trim()</code>) is allowed for own-property functions (a <code>@use</code> namespace, a provided object) and for the <spa-a route-href=\"/nucleus/packages/quark/methods\" role=\"link\">allowed prototype methods</spa-a>; anything else is an error. Calling a method on <code>null</code> / <code>undefined</code> yields <code>undefined</code>. Named arguments are passed positionally (the name is ignored); <code>$list...</code> spreads an array.</li>\n<li><strong>Operators</strong>: <code>and</code> / <code>or</code> short-circuit and return an operand (JS semantics, so <code>$title or preserve</code> works); <code>not</code> returns a boolean; <code>==</code> / <code>!=</code> are loose; <code>+</code> concatenates when either side is a string (prefer <code>#{$x}</code> interpolation for building strings); <code>-</code> <code>*</code> <code>/</code> <code>%</code> and comparisons follow JS.</li>\n<li><strong><code>if()</code></strong> returns the value of the first arm whose condition is truthy; with no match and no <code>else</code> it is <code>undefined</code> (a wipe). <code>ternary()</code> is the two-arm function form.</li>\n<li><strong>Lists</strong> (space or comma separated) evaluate to arrays; <strong>maps</strong> to objects, with bare identifier keys taken literally (<code>(name: "Ada")</code> → <code>{ name: "Ada" }</code>) and other keys stringified.</li>\n<li><strong>Results</strong>: listeners and <code>$variables</code> apply synchronously; attributes, CSS variables, and content are painted in a batch. A <code>content</code> result that is a promise is awaited.</li>\n</ul>\n<h2 id=\"md-example\">Example</h2>\n<include-content data-language=\"quark\"><template>[data-user] {\n $user: prop(\"provision\");\n label: $user.profile.name or \"Anonymous\";\n title: if($user.role == \"admin\": \"Administrator\"; else: $user.role);\n data-tags: $user.tags.join(\", \");\n --progress: \"#{($user.done / $user.total * 100).toFixed(0)}%\";\n}</template></include-content>\n<h2 id=\"md-facts-over-if\">Facts over <code>if()</code></h2>\n<p>Several rules branching on the same condition with <code>if()</code> is a smell: derive the condition once as an attribute (<code>data-is-admin: $user.role == "admin";</code> — a boolean writes <code>""</code> or removes the attribute) and select on it from Quark and CSS.</p>\n",
|
|
36
|
+
"js_api": "<h1 id=\"md-js-api\">JS API</h1>\n<p>The <code>Quark</code> class hosts a sheet programmatically (tests, tooling), settles on demand and reports to the shared DevTools hook.</p>\n<h2 id=\"md-quark\"><code>Quark</code></h2>\n<include-content data-language=\"ts\"><template>import { Quark } from \"@excom/quark\";\n\nconst quark = new Quark({\n src: `span { content: \"four times two equals #{twice(4)}\"; }`,\n modules: { dfault: { twice: (n) => n * 2 } },\n});\nquark.register({ sheetElement }); // host = sheetElement.parentElement\n// …\nquark.unregister();</template></include-content>\n<p><code>isScoped</code> mirrors <code><quark-sheet></code>'s default (see <spa-a route-href=\"/nucleus/packages/quark/sheets\" role=\"link\">Sheets & scoping</spa-a>); <code>modules</code> pre-provides what <code>@use</code> would import; <code>Quark.moduleLoader</code> can be overridden in tests.</p>\n<p><code>Quark.whenSettled({ timeout? })</code> resolves once no rule pass, paint, async <code>content</code> or <code>@use</code> load is pending (<code>"settled"</code>), or after <code>timeout</code> ms (<code>"timeout"</code>, default 1000) — for tests and tools; sheets have no after-render hook.</p>\n<p>Writing <code>$variables</code> from app code goes through <code>element.quark</code> — see <spa-a route-href=\"/nucleus/packages/quark/js_writes\" role=\"link\">Writing from JS</spa-a>.</p>\n<h2 id=\"md-devtools\">DevTools</h2>\n<p>Quark reports to the same global hook as Neutron. The Nucleus DevTools extension installs it at page load; for tests or late attachment call <code>Quark.attachDevtools()</code> (identical to <code>Neutron.attachDevtools()</code>).</p>\n<p>Publications, one per property resolution on a matched element:</p>\n<ul>\n<li><code>["quark", "sheet", "registered" | "unregistered"]</code> on the host — <code>sheetId</code>, <code>ruleCount</code>, <code>isScoped</code></li>\n<li><code>["quark", "apply"]</code> — <code>selector</code>, <code>key</code>, <code>expression</code>, <code>result</code>, <code>runId</code>, <code>isNoop</code> / <code>isWipe</code></li>\n<li><code>["quark", "error"]</code> — a failed expression (<code>errorMessage</code>), which never wipes; also a loop-guard trip (<code>errorName: "LoopGuardDepth"</code> / <code>"LoopGuardBatch"</code>, <code>key</code> = the dropped attribute / binding, see <spa-a route-href=\"/nucleus/packages/quark/loop_guard\" role=\"link\">Loop guard</spa-a>)</li>\n<li><code>["quark", "diagnostic"]</code> — a <code>@warn</code> / <code>@debug</code> / <code>@error</code> statement that spoke: <code>level</code>, <code>values</code>, <code>message</code>, <code>expression</code>, the selector and element (see <spa-a route-href=\"/nucleus/packages/quark/diagnostics\" role=\"link\">Diagnostics</spa-a>)</li>\n<li><code>["quark", "delay"]</code> — a <code>@delay</code> block <code>scheduled</code> (with <code>ms</code>), <code>fired</code>, or <code>dropped</code> (with <code>reason</code>: <code>"disconnected"</code>, <code>"unregistered"</code>, <code>"unmatched"</code>; see <spa-a route-href=\"/nucleus/packages/quark/delay\" role=\"link\"><code>@delay</code></spa-a>)</li>\n<li><code>["quark", "transition"]</code> — a commit holding <code>@view-transition</code> writes: <code>phase: "start"</code>, <code>"settled"</code> (with <code>result</code>: <code>"settled"</code> / <code>"until"</code> / <code>"timeout"</code>) or <code>"skip"</code> (with <code>reason</code>: <code>"unsupported"</code>, <code>"reduced-motion"</code>, <code>"hidden"</code>, <code>"unchanged"</code>, <code>"active"</code>, <code>"error"</code>), plus <code>types</code> and the number of <code>paints</code></li>\n</ul>\n<p>The extension can also paint a whole-document paint-count heatmap from the <code>apply</code> records (toolbar icon → "Paint heatmap"); nothing is written to the page's elements.</p>\n<p>The extension's Element › Orchestration tab lists them, plus the selected element's current <code>$variables</code>, Quark-written attributes and CSS custom properties, listeners (with the <code>@on</code> handlers that attached them) and <code>iterate()</code> row context.</p>\n<include-content data-language=\"ts\"><template>Quark.attachDevtools({\n version: 1,\n publicize: (path, meta) => console.log(path.join(\"/\"), meta),\n});</template></include-content>\n<p>The renderer Quark injects into the hook (<code>hook.inject(renderer)</code>, <code>kind: "quark"</code>) also carries the on-demand queries behind the extension's agent tools: <code>inspect(el)</code> (the snapshot above), <code>sheets()</code> (every registered sheet with host, scope, source and rules), <code>matchingRules(el)</code> (rules whose selector matches <code>el</code> now, scope-aware, in definition order) and <code>evaluate(el, expression, sheetId?)</code> (an expression evaluated as a rule on <code>el</code> would see it, <code>@use</code> modules from <code>sheetId</code> or the first matching sheet). All three are read-only queries over already-built state.</p>\n<h2 id=\"md-language-metadata\">Language metadata</h2>\n<p>The documented language surface (keywords, declaration kinds, at-rules, built-ins, allowed methods, pseudo-class support) is data on <code>@excom/quark/language</code>, off the main entry so its prose stays out of app bundles. The reference tables on these pages are generated from it.</p>\n",
|
|
37
|
+
"js_writes": "<h1 id=\"md-writing-from-js\">Writing from JS</h1>\n<p>Every element carries <code>element.quark</code>, shaped like <code>element.style</code>: the sanctioned way for app JS to write a <code>$variable</code> that rules read.</p>\n<h2 id=\"md-element-quark\"><code>element.quark</code></h2>\n<table>\n<thead>\n<tr>\n<th>Method</th>\n<th>Effect</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>setProperty(name, value)</code></td>\n<td>Stores the binding on that element (it becomes the owner) and re-runs readers below it, in every sheet. Returns <code>true</code> when the value changed.</td>\n</tr>\n<tr>\n<td><code>setProperties({ … })</code></td>\n<td>Stores several bindings, then announces them, so a reader of two names runs once with both.</td>\n</tr>\n<tr>\n<td><code>removeProperty(name)</code></td>\n<td><code>unset</code>: deletes the binding so readers fall through to an ancestor.</td>\n</tr>\n<tr>\n<td><code>getPropertyValue(name)</code></td>\n<td>What a rule on that element would read (nearest owner, self first).</td>\n</tr>\n</tbody></table>\n<p><code>name</code> may be written with or without the <code>$</code>. Change detection is by value (a new object is a change, an in-place mutation is not), the same rule a declaration follows. It is State, not an event: a value written before a sheet registers is read on the sheet's first run.</p>\n<include-content data-language=\"quark\"><template>:scope { $count: 0; }\nbutton { @on click (handle: incrementFromJs(closest(\"[data-demo-counter]\"))); }\noutput { content: \"Clicked #{$count} times\"; }</template></include-content>\n<include-content data-language=\"js\"><template>// JS module\nexport const incrementFromJs = (owner) => () => {\n const current = Number(owner.quark.getPropertyValue(\"$count\") ?? 0);\n owner.quark.setProperty(\"$count\", current + 1);\n};</template></include-content>\n<p><include-content data-demo=\"js-api\"></include-content></p>\n<h2 id=\"md-rules-of-thumb\">Rules of thumb</h2>\n<ul>\n<li>One writer per name per element: a rule that declares <code>$name</code> on the same element rewrites the JS value when its declaration re-runs (last writer wins). Initialize in the sheet and update from JS, or write from JS only.</li>\n<li>Primitives that CSS or a selector should see belong in attributes (<code>setAttribute</code> + <code>attr()</code>, serializable and selectable); <code>element.quark</code> earns its place for rich values and for <code>$name</code> reads across a subtree.</li>\n<li>DevTools shows a JS write as a binding change without a sheet.</li>\n</ul>\n",
|
|
38
|
+
"limitations": "<h1 id=\"md-limitations\">Limitations</h1>\n<p>What the current engine does not observe or support, and the pitfalls that follow from it.</p>\n<h2 id=\"md-beta-limits\">Beta limits</h2>\n<ul>\n<li>Selector features backed by state the DOM does not reflect are not observed: interaction and validity pseudo-classes (<code>:hover</code>, <code>:focus</code>, <code>:checked</code>, <code>:invalid</code>, …) match on the first run only, and the rule logs a warning at build. Everything else the native engine matches is observed — see <spa-a route-href=\"/nucleus/packages/quark/selectors\" role=\"link\">Selectors</spa-a> for the list and the partial cases (<code>:empty</code> ignores text-only changes, <code>:nth-child(… of S)</code> observes only the attributes in <code>S</code>, <code>:open</code> covers <code><details></code> / <code><dialog></code> only, sibling-relative <code>:has(+ …)</code> re-runs the whole rule)</li>\n<li>At-rules are Quark's own — <code>@use</code>, <code>@scope</code>, <code>@on</code>, <code>@dispatch</code> / <code>@command</code>, <code>@view-transition</code>, <code>@delay</code>, <code>@warn</code> / <code>@debug</code> / <code>@error</code> — and any other name is a parse error, so CSS's <code>@media</code> / <code>@keyframes</code> and SCSS's control flow belong in a stylesheet, not a sheet</li>\n<li>Setting attributes is recommended over toggling / mutating classes and ids, since the latter has a heavier impact on Quark's performance: a sheet that names any class wakes on every class change under its host (styling churn included)</li>\n<li>Rules do not revert when they stop matching — see <spa-a route-href=\"/nucleus/packages/quark/no_reversion\" role=\"link\">No reversion</spa-a></li>\n<li><code>@view-transition</code> relies on <code>document.startViewTransition()</code>: one transition per document, no scoped transitions yet; <code>until</code> freezes the page while it waits (short waits only)</li>\n</ul>\n<p>See the related section under <spa-a route-href=\"/nucleus/docs/limitations\" role=\"link\">Limitations</spa-a> in the guides.</p>\n<h2 id=\"md-pitfalls\">Pitfalls</h2>\n<p>Do not render a template that re-matches the same rule — the loop guard cuts it after 50 nested paints, but the fix is the selector:</p>\n<include-content data-language=\"quark\"><template>/* BAD — each new span matches again */\nspan {\n content: template(\"#span-template\");\n}</template></include-content>\n<p>A sheet that registers after an element's connect-time events will not hear them (no replay): react to state attributes, or load the sheet first in its host.</p>\n",
|
|
39
|
+
"loop_guard": "<h1 id=\"md-loop-guard\">Loop guard</h1>\n<p>Runaway write chains are cut, not prevented: one shared guard bounds every chain Quark, Neutron and <code>dom-observer</code> take part in.</p>\n<h2 id=\"md-what-it-cuts\">What it cuts</h2>\n<p>Two rules that flip each other's attributes, a rule and an element effect feeding each other, an event whose listener re-writes the attribute that fired it, or content that re-matches its own paint would otherwise run forever. The Nucleus Stack bounds every such chain with one shared guard, <code>LoopGuard</code> from <code>@excom/kit-utils</code> (Nucleus Kit users: <code>Neutron.DOM.LoopGuard</code>). Each write an engine makes carries the depth of the chain that caused it — Quark attributes, <code>content</code>, <code>$bindings</code>; Neutron attribute reflection and observed property assignments; <code>dom-observer</code> events — and the hop past the limit is <strong>dropped</strong>:</p>\n<ul>\n<li>default limit <strong>50</strong> dependent writes (a legitimate chain is under ten);</li>\n<li>the dropped write is logged once (<code>Loop guard: a chain of 51 dependent writes reached "data-x" on <p> — …</code>) and published to DevTools as <code>quark/error</code>;</li>\n<li>nothing else happens: no event fires, no attribute is wiped, the sheet is not disabled, and the document keeps the state it had before the drop — the cycle's participants stay wherever the chain left them, so treat a trip as a bug report, not a recovery;</li>\n<li>a new external write in a later task (user input, a timer, a fetch, app JS) starts a fresh chain at zero, so streams of updates never trip, however long.</li>\n</ul>\n<h2 id=\"md-api\">API</h2>\n<include-content data-language=\"ts\"><template>import { LoopGuard } from \"@excom/kit-utils\";\n\nLoopGuard.limit; // 50\nLoopGuard.configure({ limit: 100, log: (message) => myLogger.error(message) });\nconst off = LoopGuard.onTrip(({ kind, target, name, depth, limit }) => {\n // kind: \"depth\" (a write chain) | \"batch\" (one Neutron handler re-ran > limit times)\n});</template></include-content>\n<p><code>configure()</code> is global and takes effect immediately. <code>onTrip</code> fires on every trip (the console line is deduplicated per name per task). Rules that gate on attributes they write for each other are named in a warning when the sheet builds, before anything runs.</p>\n<h2 id=\"md-not-covered\">Not covered</h2>\n<p>Writes Quark and Neutron do not route — plain <code>setAttribute</code> / <code>innerHTML</code> in app JS — neither count nor get cut, and a declaration is never re-run by the attribute it wrote (<code>class:</code> excepted), so a rule cannot loop on its own gate (see <spa-a route-href=\"/nucleus/packages/quark/reactivity#md-loops\" role=\"link\">Reactivity</spa-a>).</p>\n",
|
|
40
|
+
"methods": "<h1 id=\"md-allowed-methods\">Allowed methods</h1>\n<p>Prototype methods an expression may call on a value through the dot accessor. Everything else is an error; wrap other logic in a <code>@use</code> module function.</p>\n<h2 id=\"md-reference\">Reference</h2>\n<p><em>Generated.</em></p>\n<!-- generated:allowed-methods -->\n<table>\n<thead>\n<tr>\n<th>Method</th>\n<th>On</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>.toLowerCase()</code></td>\n<td>string</td>\n<td>Lower-cased copy.</td>\n</tr>\n<tr>\n<td><code>.toUpperCase()</code></td>\n<td>string</td>\n<td>Upper-cased copy.</td>\n</tr>\n<tr>\n<td><code>.trim()</code></td>\n<td>string</td>\n<td>Copy without leading / trailing whitespace.</td>\n</tr>\n<tr>\n<td><code>.split(separator, limit?)</code></td>\n<td>string</td>\n<td>Array of substrings.</td>\n</tr>\n<tr>\n<td><code>.charAt(index)</code></td>\n<td>string</td>\n<td>The character at <code>index</code>.</td>\n</tr>\n<tr>\n<td><code>.startsWith(search, position?)</code></td>\n<td>string</td>\n<td>Boolean.</td>\n</tr>\n<tr>\n<td><code>.endsWith(search, length?)</code></td>\n<td>string</td>\n<td>Boolean.</td>\n</tr>\n<tr>\n<td><code>.padStart(length, fill?)</code></td>\n<td>string</td>\n<td>Left-padded copy.</td>\n</tr>\n<tr>\n<td><code>.padEnd(length, fill?)</code></td>\n<td>string</td>\n<td>Right-padded copy.</td>\n</tr>\n<tr>\n<td><code>.replace(search, replacement)</code></td>\n<td>string</td>\n<td>Copy with the first match replaced (string search only).</td>\n</tr>\n<tr>\n<td><code>.replaceAll(search, replacement)</code></td>\n<td>string</td>\n<td>Copy with every match replaced (string search only).</td>\n</tr>\n<tr>\n<td><code>.toString()</code></td>\n<td>string, array, number, date</td>\n<td>String form.</td>\n</tr>\n<tr>\n<td><code>.includes(search)</code></td>\n<td>string, array</td>\n<td>Boolean.</td>\n</tr>\n<tr>\n<td><code>.slice(start?, end?)</code></td>\n<td>string, array</td>\n<td>Sub-range copy.</td>\n</tr>\n<tr>\n<td><code>.indexOf(search)</code></td>\n<td>string, array</td>\n<td>First position, or <code>-1</code>.</td>\n</tr>\n<tr>\n<td><code>.lastIndexOf(search)</code></td>\n<td>string, array</td>\n<td>Last position, or <code>-1</code>.</td>\n</tr>\n<tr>\n<td><code>.concat(...values)</code></td>\n<td>string, array</td>\n<td>Joined copy.</td>\n</tr>\n<tr>\n<td><code>.at(index)</code></td>\n<td>string, array</td>\n<td>Element at <code>index</code>; negative counts from the end.</td>\n</tr>\n<tr>\n<td><code>.join(separator?)</code></td>\n<td>array</td>\n<td>String of the items.</td>\n</tr>\n<tr>\n<td><code>.flat(depth?)</code></td>\n<td>array</td>\n<td>Flattened copy.</td>\n</tr>\n<tr>\n<td><code>.toFixed(digits?)</code></td>\n<td>number</td>\n<td>Fixed-point string.</td>\n</tr>\n<tr>\n<td><code>.toLocaleString(locale?, options?)</code></td>\n<td>number, date, array</td>\n<td>Locale-formatted string.</td>\n</tr>\n<tr>\n<td><code>.toLocaleDateString(locale?, options?)</code></td>\n<td>date</td>\n<td>Locale-formatted date.</td>\n</tr>\n<tr>\n<td><code>.toLocaleTimeString(locale?, options?)</code></td>\n<td>date</td>\n<td>Locale-formatted time.</td>\n</tr>\n<tr>\n<td><code>.toISOString()</code></td>\n<td>date</td>\n<td>ISO 8601 string.</td>\n</tr>\n<tr>\n<td><code>.getAttribute("name")</code></td>\n<td>element</td>\n<td>Attribute value or <code>null</code>. Not observed.</td>\n</tr>\n<tr>\n<td><code>.hasAttribute("name")</code></td>\n<td>element</td>\n<td>Boolean. Not observed.</td>\n</tr>\n<tr>\n<td><code>.matches("selector")</code></td>\n<td>element</td>\n<td>Boolean.</td>\n</tr>\n<tr>\n<td><code>.closest("selector")</code></td>\n<td>element</td>\n<td>Nearest ancestor-or-self matching the selector, or <code>null</code>.</td>\n</tr>\n</tbody></table>\n<!-- /generated --><p>Own-property functions on a value (a <code>@use</code> namespace, a provided object) are always callable; see <spa-a route-href=\"/nucleus/packages/quark/expressions\" role=\"link\">Expressions</spa-a>.</p>\n",
|
|
41
|
+
"modules": "<h1 id=\"md-built-in-modules\">Built-in modules</h1>\n<p>Pure helpers for numbers, lists, maps, strings and dates, imported like JS modules — <code>@use "quark:math" as math;</code> — so nothing is global.</p>\n<h2 id=\"md-using-a-module\">Using a module</h2>\n<include-content data-language=\"quark\"><template>@use \"quark:math\" as math;\n@use \"quark:list\" as *;\n\n[data-progress] { --progress: math.percentage($done / $total); }\n[bind-count] { content: count($todos, \"done\", true); }</template></include-content>\n<p><code>as name</code> namespaces the functions (<code>math.clamp(0, $x, 1)</code>, <code>math.$pi</code>); <code>as *</code> merges them into the bare scope. Every function is pure and null-tolerant: a missing collection reads as empty, a missing value passes through, and results are copies. Collection functions take a dot path (<code>"user.name"</code>) instead of a callback.</p>\n<h2 id=\"md-reference\">Reference</h2>\n<p><em>Generated.</em></p>\n<!-- generated:builtin-modules -->\n<p><strong><code>quark:math</code></strong> — Numbers. Arguments are coerced with <code>parseFloat</code>; the CSS argument order is kept.</p>\n<table>\n<thead>\n<tr>\n<th>Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>math.$pi</code></td>\n<td>π.</td>\n</tr>\n<tr>\n<td><code>math.$e</code></td>\n<td>Euler's number.</td>\n</tr>\n<tr>\n<td><code>min(...values)</code></td>\n<td>The smallest value (lists are flattened).</td>\n</tr>\n<tr>\n<td><code>max(...values)</code></td>\n<td>The largest value (lists are flattened).</td>\n</tr>\n<tr>\n<td><code>clamp(min, value, max)</code></td>\n<td><code>value</code> limited to the range, in CSS argument order.</td>\n</tr>\n<tr>\n<td><code>round(value, digits?)</code></td>\n<td>Rounded to <code>digits</code> decimals (default 0).</td>\n</tr>\n<tr>\n<td><code>floor(value)</code></td>\n<td>Rounded down.</td>\n</tr>\n<tr>\n<td><code>ceil(value)</code></td>\n<td>Rounded up.</td>\n</tr>\n<tr>\n<td><code>abs(value)</code></td>\n<td>Absolute value.</td>\n</tr>\n<tr>\n<td><code>mod(value, divisor)</code></td>\n<td>Wrapping modulo: <code>mod(-1, 3)</code> is <code>2</code> (the <code>%</code> operator keeps the sign). <code>NaN</code> for a zero divisor.</td>\n</tr>\n<tr>\n<td><code>pow(base, exponent)</code></td>\n<td><code>base</code> to the power <code>exponent</code>.</td>\n</tr>\n<tr>\n<td><code>sqrt(value)</code></td>\n<td>Square root.</td>\n</tr>\n<tr>\n<td><code>percentage(fraction)</code></td>\n<td><code>"25%"</code> for <code>0.25</code> — a string, ready for an attribute or CSS variable.</td>\n</tr>\n</tbody></table>\n<p><strong><code>quark:list</code></strong> — Arrays (an object counts as the list of its values). <code>"path"</code> arguments are dot paths into each item; comparisons are loose (<code>==</code>), like <code>find()</code>.</p>\n<table>\n<thead>\n<tr>\n<th>Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>count(list, "path"?, value?)</code></td>\n<td>The number of items; with a path, the items whose value at it is non-empty; with a value too, the items equal to it. <code>0</code> for a missing list.</td>\n</tr>\n<tr>\n<td><code>find(list, "path", value)</code></td>\n<td>The first item whose value at the path equals <code>value</code>, else <code>undefined</code> (was a global built-in before 2026-09-13).</td>\n</tr>\n<tr>\n<td><code>filter(list, "path", value?)</code></td>\n<td>The items whose value at the path equals <code>value</code> — or is non-empty when <code>value</code> is omitted.</td>\n</tr>\n<tr>\n<td><code>reject(list, "path", value?)</code></td>\n<td>The complement of <code>filter</code>.</td>\n</tr>\n<tr>\n<td><code>pluck(list, "path")</code></td>\n<td>The value at the path of every item.</td>\n</tr>\n<tr>\n<td><code>sort-by(list, "path"?, "desc"?)</code></td>\n<td>A sorted copy: numbers numerically, everything else with a locale-aware, numeric-aware comparison; <code>null</code> last. <code>"desc"</code> reverses.</td>\n</tr>\n<tr>\n<td><code>sum(list, "path"?)</code></td>\n<td>The total of the items (or of their value at the path); non-numbers count as 0.</td>\n</tr>\n<tr>\n<td><code>range(end) / range(start, end, step?)</code></td>\n<td><code>range(3)</code> → <code>[0, 1, 2]</code>; <code>range(1, 4)</code> → <code>[1, 2, 3]</code>; counts down when <code>start > end</code>. For skeleton rows and pagination.</td>\n</tr>\n<tr>\n<td><code>unique(list, "path"?)</code></td>\n<td>A copy without duplicates (by the value at the path when given), first occurrence kept.</td>\n</tr>\n<tr>\n<td><code>group-by(list, "path")</code></td>\n<td>A map from each distinct value at the path to the items carrying it.</td>\n</tr>\n<tr>\n<td><code>first(list)</code></td>\n<td>The first item, or <code>undefined</code>.</td>\n</tr>\n<tr>\n<td><code>last(list)</code></td>\n<td>The last item, or <code>undefined</code>.</td>\n</tr>\n<tr>\n<td><code>reverse(list)</code></td>\n<td>A reversed copy (was a global built-in before 2026-09-13).</td>\n</tr>\n<tr>\n<td><code>compact(list)</code></td>\n<td>A copy without <code>null</code>, <code>undefined</code>, <code>""</code>, empty lists and empty maps.</td>\n</tr>\n</tbody></table>\n<p><strong><code>quark:map</code></strong> — Plain objects (<code>(key: value)</code> literals, provisions, <code>dataset</code>-shaped data). Never mutates; returns copies.</p>\n<table>\n<thead>\n<tr>\n<th>Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>get(map, "path", fallback?)</code></td>\n<td>The value at the dot path, or <code>fallback</code> when it is missing.</td>\n</tr>\n<tr>\n<td><code>has-key(map, "path")</code></td>\n<td>Whether the dot path resolves to a value.</td>\n</tr>\n<tr>\n<td><code>keys(map)</code></td>\n<td>The keys, in insertion order.</td>\n</tr>\n<tr>\n<td><code>values(map)</code></td>\n<td>The values, in insertion order.</td>\n</tr>\n<tr>\n<td><code>entries(map)</code></td>\n<td><code>[(key: …, value: …), …]</code> — iterate a map with <code>item.key</code> / <code>item.value</code> in the rows.</td>\n</tr>\n<tr>\n<td><code>merge(...maps)</code></td>\n<td>A shallow merge, later maps winning.</td>\n</tr>\n<tr>\n<td><code>pick(map, ...keys)</code></td>\n<td>A copy holding only the named keys.</td>\n</tr>\n<tr>\n<td><code>omit(map, ...keys)</code></td>\n<td>A copy without the named keys.</td>\n</tr>\n</tbody></table>\n<p><strong><code>quark:string</code></strong> — Text. Values are stringified first; <code>null</code> / <code>undefined</code> read as <code>""</code>.</p>\n<table>\n<thead>\n<tr>\n<th>Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>plural(count, forms, locale?)</code></td>\n<td>The form for <code>count</code> from a map keyed by <code>Intl.PluralRules</code> category (<code>one</code>, <code>other</code>, <code>few</code>, …); <code>#</code> in the form is replaced by the count: <code>plural($n, (one: "# item", other: "# items"))</code>.</td>\n</tr>\n<tr>\n<td><code>escape-html(value)</code></td>\n<td><code>& < > " '</code> escaped, for text that goes through <code>dangerous-html()</code>.</td>\n</tr>\n<tr>\n<td><code>truncate(value, max, suffix?)</code></td>\n<td>Cut to <code>max</code> characters including the suffix (default <code>…</code>).</td>\n</tr>\n<tr>\n<td><code>capitalize(value)</code></td>\n<td>First character upper-cased.</td>\n</tr>\n<tr>\n<td><code>slugify(value)</code></td>\n<td>Lower-case ASCII with dashes: <code>"Héllo World!"</code> → <code>"hello-world"</code>.</td>\n</tr>\n</tbody></table>\n<p><strong><code>quark:date</code></strong> — Dates arrive as strings; these parse and format them. Every function accepts a <code>Date</code>, an ISO string or a timestamp and returns <code>null</code> / <code>""</code> for an unparseable value.</p>\n<table>\n<thead>\n<tr>\n<th>Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>parse(value)</code></td>\n<td>A <code>Date</code>, or <code>null</code> — for the allowed date methods (<code>toLocaleDateString()</code>, <code>toISOString()</code>).</td>\n</tr>\n<tr>\n<td><code>is-valid(value)</code></td>\n<td>Whether the value parses as a date.</td>\n</tr>\n<tr>\n<td><code>format(value, locale?, options?)</code></td>\n<td><code>Intl.DateTimeFormat</code> output: <code>format($when, "en-GB", (dateStyle: "medium"))</code>.</td>\n</tr>\n<tr>\n<td><code>add(value, amount, unit?)</code></td>\n<td>A new date <code>amount</code> units later (<code>"days"</code> by default; seconds … weeks, months, years — months and years step the calendar).</td>\n</tr>\n<tr>\n<td><code>diff(later, earlier, unit?)</code></td>\n<td>Whole units between two dates (<code>"days"</code> by default), negative when <code>later</code> is earlier.</td>\n</tr>\n</tbody></table>\n<p><strong><code>quark:url</code></strong> — Query strings, via <code>URLSearchParams</code>.</p>\n<table>\n<thead>\n<tr>\n<th>Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>query(map)</code></td>\n<td><code>"q=a+b&page=2"</code> from a map; <code>null</code> / <code>""</code> values are dropped, lists repeat the key.</td>\n</tr>\n<tr>\n<td><code>params(url)</code></td>\n<td>The query of a URL (or a bare query string) as a map; repeated keys become lists.</td>\n</tr>\n<tr>\n<td><code>encode(value)</code></td>\n<td><code>encodeURIComponent</code>.</td>\n</tr>\n</tbody></table>\n<p><strong><code>quark:util</code></strong> — Small value helpers.</p>\n<table>\n<thead>\n<tr>\n<th>Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>coalesce(...values)</code></td>\n<td>The first value that is not <code>null</code> / <code>undefined</code> (<code>or</code> also skips <code>0</code>, <code>""</code> and <code>false</code>).</td>\n</tr>\n<tr>\n<td><code>is-empty(value)</code></td>\n<td><code>true</code> for <code>null</code>, <code>undefined</code>, <code>""</code>, an empty list or an empty map.</td>\n</tr>\n<tr>\n<td><code>type-of(value)</code></td>\n<td><code>"string"</code>, <code>"number"</code>, <code>"boolean"</code>, <code>"list"</code>, <code>"map"</code>, <code>"date"</code>, <code>"null"</code>, <code>"undefined"</code> or <code>"function"</code>.</td>\n</tr>\n<tr>\n<td><code>to-json(value, indent?)</code></td>\n<td><code>JSON.stringify</code>, for <code>content</code> or a <code>data-*</code> attribute.</td>\n</tr>\n<tr>\n<td><code>from-json(text)</code></td>\n<td><code>JSON.parse</code>, or <code>null</code> when the text is not JSON.</td>\n</tr>\n</tbody></table>\n<!-- /generated --><p>Functions available without an import: <spa-a route-href=\"/nucleus/packages/quark/builtins\" role=\"link\">Built-in functions</spa-a>.</p>\n",
|
|
42
|
+
"no_reversion": "<h1 id=\"md-no-reversion\">No reversion</h1>\n<p>Quark rules do not undo their writes when they stop matching — the one deliberate divergence from CSS.</p>\n<h2 id=\"md-write-the-inverse-rule\">Write the inverse rule</h2>\n<p>In CSS, a rule's declarations stop applying the moment its selector stops matching. <strong>Quark rules do not revert</strong> — this applies to every property kind: attributes, content, listeners, <code>$variables</code>, and CSS variables persist after the rule that set them stops matching. Write the counter-rule for every state you leave:</p>\n<include-content data-language=\"quark\"><template>details[open] { --border-color: \"red\"; }\ndetails:not([open]) { --border-color: \"transparent\"; }</template></include-content>\n<p>The same applies to <code>:has()</code> facts (<code>:scope:has(…)</code> + <code>:scope:not(:has(…))</code>, see <spa-a route-href=\"/nucleus/packages/quark/selectors\" role=\"link\">Selectors</spa-a>) and to any attribute derived from a condition.</p>\n<h2 id=\"md-why\">Why</h2>\n<p>Quark does not own the document: anything can write an attribute outside a sheet's knowledge, so pretending a rule can be applied and unapplied like CSS would be dishonest. Unmatch reversion may be introduced in a future version.</p>\n",
|
|
43
|
+
"on": "<h1 id=\"md-on\">@on</h1>\n<p><code>@on</code> wires listeners inside a rule: one or more event names, an options group that filters and configures the listener, and a block applied once per event.</p>\n<include-content data-language=\"quark\"><template>form {\n @on submit (prevent-default, handle: saveDraft($draft)) { is-submitted: \"\"; }\n @on input, change (debounce: 300) { data-draft: event.target.value; }\n @on keydown (key: \"Escape\", host: window) { is-open: none; }\n}</template></include-content>\n<p>The prelude says <em>when</em>: which events, which filters. The block says <em>what</em>: writes, <code>@dispatch</code> / <code>@command</code>, <code>@delay</code>. JS callouts are the <code>handle:</code> option. There is no handler list after the event names any more (<code>@on click go;</code> is a parse error that points at <code>handle:</code>).</p>\n<h2 id=\"md-events\">Events</h2>\n<p><code>@on click</code>, <code>@on super-form-success</code>, <code>@on "my:event"</code> — a bare name or a string. A comma list shares one listener and one block: <code>@on input, change { … }</code>. <code>event.type</code> tells them apart inside the block.</p>\n<h2 id=\"md-blocks\">Blocks</h2>\n<p><code>@on <events> [(options)] { … }</code> turns the event into a <strong>one-shot transaction</strong>: the block is an ordinary rule body, evaluated when the event fires instead of when the rule matches. Declarations write the matched element; nested rules write its matching descendants; <code>event</code> is the DOM event; <code>@dispatch</code> / <code>@command</code> statements fire after the writes are queued. It is how typed input, clicks and element events become State without JS:</p>\n<include-content data-language=\"quark\"><template>:scope {\n $count: +attr(\"data-count\");\n [bind-count] { content: $count; }\n @on counter-increment { data-count: $count + 1; }\n @on input {\n data-draft: event.target.value;\n #preview { content: event.target.value or preserve; }\n }\n @on submit (prevent-default) { is-submitted: \"\"; }\n}</template></include-content>\n<p>A nested rule whose selector starts with a sibling combinator writes the element's siblings instead of its descendants — <code>:scope { button { @on click { + provider-fetch { @command --fetch; } } } }</code> invokes <code>--fetch</code> on the <code>provider-fetch</code> right after the button.</p>\n<p>Writes are batched like any rule's; <code>$variables</code> set in a block persist on the element. <code>@on</code> inside a block is not supported.</p>\n<h2 id=\"md-options\">Options</h2>\n<p>The <strong>options group</strong> after the events is a map: <code>name: value</code> entries and bare flags, which mean <code>true</code>. With options the block is optional — <code>@on submit (prevent-default);</code> is a complete statement. These are the same filters <code><event-handler></code> offers, in the sheet:</p>\n<include-content data-language=\"quark\"><template>ul {\n @on click (target: \"li[data-id]\") { data-selected: target.getAttribute(\"data-id\"); }\n @on keydown (key: \"Escape\", host: window) { is-open: none; }\n @on input (debounce: 300) { data-query: event.target.value; }\n @on scroll (throttle: 100, passive, handle: trackScroll);\n @on click (self, once, prevent-default);\n}</template></include-content>\n<table>\n<thead>\n<tr>\n<th>Option</th>\n<th>Effect</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>target: "<selector>"</code></td>\n<td>Delegation: fires only when the event target is inside a descendant matching the selector; that element is <code>target</code> in the block and in the other options (<code>event.target</code> otherwise). With <code>host:</code> the selector is matched document-wide.</td>\n</tr>\n<tr>\n<td><code>self</code></td>\n<td>Fires only when the event target is the matched element itself.</td>\n</tr>\n<tr>\n<td><code>key: "Escape"</code> / <code>"Shift+K"</code></td>\n<td>Keyboard chord; space-separated tokens are alternatives (<code>"Escape Enter"</code>). Listed modifiers (<code>shift</code>, <code>alt</code>, <code>ctrl</code>, <code>meta</code> / <code>cmd</code>) must be held.</td>\n</tr>\n<tr>\n<td><code>prevent-default</code> / <code>stop-propagation</code> / <code>stop-immediate-propagation</code></td>\n<td>Act on the event as soon as it passes the filters, before any timing.</td>\n</tr>\n<tr>\n<td><code>debounce: <ms></code> / <code>throttle: <ms></code></td>\n<td>Wait for a pause / run at most once per window (leading edge). Exclusive.</td>\n</tr>\n<tr>\n<td><code>handle: fn</code></td>\n<td>JS: a function, a call returning one, or a list <code>(a, b)</code> — each called with the event, <code>this</code> being the element, before the block. <code>prevent-default</code> and <code>stop-propagation</code> are also functions, for lists.</td>\n</tr>\n<tr>\n<td><code>once</code></td>\n<td>Detach after the first event that passes the filters.</td>\n</tr>\n<tr>\n<td><code>passive</code> / <code>capture</code></td>\n<td>Native <code>addEventListener</code> options.</td>\n</tr>\n<tr>\n<td><code>host: window</code> / <code>host: document</code></td>\n<td>Register on the window / document while the element is in the document; the listener lets go on the first event after the element is removed.</td>\n</tr>\n</tbody></table>\n<h3 id=\"md-when-values-are-evaluated\">When values are evaluated</h3>\n<p><code>target</code>, <code>key</code>, <code>debounce</code>, <code>throttle</code> and <code>handle</code> are expressions evaluated <strong>when the event fires</strong>, in the block's scope: <code>event</code>, <code>target</code>, <code>element</code> and the element's current <code>$bindings</code> are all in reach.</p>\n<include-content data-language=\"quark\"><template>#list {\n $row: attr(\"data-row-selector\");\n @on click (target: $row, handle: pick(target.getAttribute(\"data-id\"), event)) { … }\n}</template></include-content>\n<p>So nothing about a listener is reactive — a changed <code>$row</code> is read by the next click, and no re-run ever re-registers the DOM listener. The flags, <code>once</code> and <code>host</code> configure the registration and are read once per match; they must be bare words.</p>\n<p>Two <code>@on</code>s for one event may coexist when their options differ (<code>(key: "Escape")</code> and <code>(key: "Enter")</code>).</p>\n<h2 id=\"md-outgoing-events\">Outgoing events</h2>\n<p><code>@dispatch</code> and <code>@command</code> inside the block send events and commands from it — see <spa-a route-href=\"/nucleus/packages/quark/dispatch\" role=\"link\"><code>@dispatch</code> / <code>@command</code></spa-a>.</p>\n<h2 id=\"md-no-off\">No <code>@off</code></h2>\n<p>There is no <code>@off</code>: a listener you would remove is a listener that should not fire — gate it with options or with event data inside its block. Listeners persist like any other write (see <spa-a route-href=\"/nucleus/packages/quark/no_reversion\" role=\"link\">No reversion</spa-a>).</p>\n",
|
|
44
|
+
"reactivity": "<h1 id=\"md-reactivity\">Reactivity</h1>\n<p>A rule runs when an element matches it and re-runs when something its selector or its values depend on changes — nothing more, nothing less.</p>\n<h2 id=\"md-when-a-rule-re-runs\">When a rule re-runs</h2>\n<ul>\n<li>an attribute, class (<code>.x</code>) or id (<code>#x</code>) named in its selector changing on the matched element, on an ancestor or earlier sibling on the selector's path, or on a descendant named in <code>:has()</code> (a class change counts only when it adds or removes a class the sheet names, unless a rule needs the whole value: <code>[class~="x"]</code>, <code>attr("class")</code>);</li>\n<li>a literal <code>attr("x")</code> in one of its values, when <code>x</code> changes on the matched element;</li>\n<li>a <code>$binding</code> one of its values reads, when that binding changes on an ancestor-or-self owner (the nearest owner wins, so a farther change is ignored);</li>\n<li>a literal <code>prop("x")</code> in one of its values, when JS assigns <code>element.x</code>;</li>\n<li>elements being inserted anywhere under the host, whether by Quark, an element, or app JS (a <code>childList</code> MutationObserver): rules matching the new elements run, <code>content</code> rules below the insertion point re-run, and <code>:has()</code> / <code>:empty</code> candidates above it are re-checked;</li>\n<li>elements being removed, only while some rule's match depends on children or sibling position (<code>:has()</code>, <code>:empty</code>, <code>:nth-child()</code>, <code>a + b</code>): the same re-runs as an insertion at that parent. Text-only changes are never observed.</li>\n</ul>\n<h2 id=\"md-not-observed\">Not observed</h2>\n<p>Interaction and validity pseudo-classes (<code>:hover</code>, <code>:focus</code>, <code>:checked</code>, <code>:invalid</code>, … — warned at build), pseudo-elements, <code>attr($dynamic)</code> / <code>prop($dynamic)</code>, <code>closest()</code> and DOM method reads (<code>getAttribute</code>, <code>matches</code>), in-place mutation of an object a <code>prop()</code> or <code>$binding</code> holds, and browser-driven native state that does not go through a JS setter (typing into an <code><input></code>, <code><details></code> toggling). Select on reflected attributes or listen to events for those. The reverse direction is covered: <code>value</code> / <code>checked</code> / <code>selected</code> / textarea <code>content</code> writes also set the live property on native form controls (see <spa-a route-href=\"/nucleus/packages/quark/attributes\" role=\"link\">Attributes</spa-a>).</p>\n<h2 id=\"md-loops\">Loops</h2>\n<p>A declaration is never re-run by the attribute it wrote (the mutation that woke the rule is excluded from that pass), so a rule cannot loop on its own gate. <code>class:</code> is the exception: classes are separate facts, so <code>.is-done { class: (is-struck: true); }</code> applies when <code>is-done</code> arrives. Setting attributes is recommended over toggling / mutating classes and ids, since the latter has a heavier impact on Quark's performance. Cycles through two or more attributes or classes, <code>$bindings</code> across sheets, content that re-matches its own paint, element effects or events are cut by the shared <spa-a route-href=\"/nucleus/packages/quark/loop_guard\" role=\"link\">loop guard</spa-a>: every write carries the depth of the chain that caused it — a deferred paint keeps the depth of the run that scheduled it; an observer record, an <code>attributeChangedCallback</code> or a property-change event inherits the depth stamped on the address it reacts to — and the hop past <code>LoopGuard.limit</code> (50) is dropped, logged once and published as <code>quark/error</code>. Chains restart at zero on every external write in a later task, so a stream of provider updates never trips. Rules that gate on attributes they write for each other are warned about when the sheet builds (<code>Quark: rules gate on attributes they write for each other — …</code>).</p>\n<h2 id=\"md-timing\">Timing</h2>\n<p>Listeners and <code>$variables</code> apply synchronously; attributes, CSS variables and content are painted in a batch one task later, and the rules that depend on those writes run in the next cycle. Writes more than a tick apart are separate facts.</p>\n",
|
|
45
|
+
"readme": "<h1 id=\"md-quark\">quark</h1>\n<p>CSS-like orchestration for your HTML — bind attributes, render lists, and wire events without a component tree.</p>\n<p>Quark is a derivative of CSS with CSS-compatible syntax (a CSS parser can tokenize Quark). The language is familiar. Rule properties are HTML attributes. The utilities (at-rules, util functions) are new.</p>\n<p>Prefer <code><quark-sheet></code> for apps; use the <code>Quark</code> class when you need a programmatic host (tests, tooling).</p>\n<h2 id=\"md-features\">Features</h2>\n<ul>\n<li><strong>CSS-like sheets</strong> Selectors + nested rules that mutate the live DOM</li>\n<li><strong><code>$variables</code></strong> Scoped values that nest and resolve in expressions</li>\n<li><strong>JS writes</strong> <code>element.quark.setProperty()</code> writes a <code>$variable</code> from JavaScript, on any element</li>\n<li><strong>CSS variables</strong> Write <code>--custom-props</code> from state; style via <code>var()</code></li>\n<li><strong>Content rendering</strong> <code>content</code>, <code>template()</code>, <code>iterate()</code>, <code>dangerous-html()</code></li>\n<li><strong>Element properties</strong> <code>prop("provision")</code> reads Neutron provisions / any JS property, re-running on assignment</li>\n<li><strong>Events</strong> <code>@on</code> at-rules with delegation, key, timing and host options, plus <code>prevent-default</code> / <code>stop-propagation</code></li>\n<li><strong>View transitions</strong> <code>@view-transition</code> commits a block's writes inside <code>document.startViewTransition()</code>, so CSS animates list changes, removals included</li>\n<li><strong>Delayed writes</strong> <code>@delay 2000 { … }</code> applies a block after a pause — flashes, toasts, undo windows — dropped if the rule stopped matching</li>\n<li><strong>Diagnostics</strong> <code>@warn</code> / <code>@debug</code> / <code>@error</code> report from a rule; the selector is the condition</li>\n<li><strong>Built-in modules</strong> <code>@use "quark:math"</code>, <code>quark:list</code>, <code>quark:map</code>, <code>quark:string</code>, <code>quark:date</code>, <code>quark:url</code>, <code>quark:util</code> — pure helpers, imported like JS modules</li>\n<li><strong>Attribute helpers</strong> <code>dataset</code>, <code>ariaset</code>, <code>class</code>, <code>none</code> to clear</li>\n<li><strong>JS modules</strong> Call app helpers from expressions via <code>@use "/url"</code></li>\n<li><strong>Scoped host</strong> Sheet + targets share a parent; updates follow DOM mutations</li>\n<li><strong>DevTools</strong> <code>Quark.attachDevtools()</code> reports rule applications and <code>$variables</code> to the Nucleus DevTools extension</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>App authors almost always load Quark through <code><quark-sheet></code>:</p>\n<include-content data-language=\"html\"><template><section>\n <quark-sheet>\n details[open] [bind-status] {\n content: \"Open\";\n }\n details:not([open]) [bind-status] {\n content: \"Closed\";\n }\n </quark-sheet>\n <details>\n <summary>Panel</summary>\n <span bind-status></span>\n </details>\n</section></template></include-content>\n<p>Programmatic API (tests / custom hosts):</p>\n<include-content data-language=\"ts\"><template>import { Quark } from \"@excom/quark\";\n\nconst quark = new Quark({\n src: `span { content: \"four times two equals #{twice(4)}\"; }`,\n modules: { dfault: { twice: (n) => n * 2 } },\n});\nquark.register({ sheetElement }); // host = sheetElement.parentElement\n// …\nquark.unregister();</template></include-content>\n<h3 id=\"md-documentation\">Documentation</h3>\n<p>Syntax</p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/quark/sheets\" role=\"link\">Sheets & scoping</spa-a> — <code><quark-sheet></code>, <code>@scope</code>, <code>is-global</code>, what runs</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/syntax\" role=\"link\">Syntax</spa-a> — rules, declarations, literals, operators, <code>if()</code></li>\n</ul>\n<p>Selectors</p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/quark/selectors\" role=\"link\">Selectors</spa-a> — combinators and pseudo-classes, what is observed</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/reactivity\" role=\"link\">Reactivity</spa-a> — when a rule re-runs, loops, timing</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/no_reversion\" role=\"link\">No reversion</spa-a> — write the inverse rule</li>\n</ul>\n<p>Declarations</p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/quark/declarations\" role=\"link\">Declaration kinds</spa-a> — what a key does</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/attributes\" role=\"link\">Attributes</spa-a> — attributes, <code>class</code> / <code>dataset</code> / <code>ariaset</code>, form controls</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/content\" role=\"link\">Content</spa-a> — <code>content</code>, <code>template()</code>, <code>iterate()</code>, <code>dangerous-html()</code></li>\n<li><spa-a route-href=\"/nucleus/packages/quark/css_variables\" role=\"link\">CSS variables</spa-a> — <code>--custom-props</code></li>\n</ul>\n<p>Values & expressions</p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/quark/variables\" role=\"link\">Variables</spa-a> — <code>$variables</code>, cascade, <code>unset</code>, raising state</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/js_writes\" role=\"link\">Writing from JS</spa-a> — <code>element.quark.setProperty()</code></li>\n<li><spa-a route-href=\"/nucleus/packages/quark/values\" role=\"link\">Values & keywords</spa-a> — <code>none</code> / <code>preserve</code> / <code>unset</code>, wipes and no-ops</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/expressions\" role=\"link\">Expressions</spa-a> — name resolution, operators, <code>if()</code>, lists and maps</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/builtins\" role=\"link\">Built-in functions</spa-a> — <code>attr()</code>, <code>prop()</code>, <code>iterate()</code>, <code>event</code>, …</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/methods\" role=\"link\">Allowed methods</spa-a> — <code>.toFixed()</code>, <code>.join()</code>, …</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/modules\" role=\"link\">Built-in modules</spa-a> — <code>@use "quark:math"</code>, <code>quark:list</code>, <code>quark:map</code>, <code>quark:string</code>, <code>quark:date</code>, <code>quark:url</code>, <code>quark:util</code></li>\n<li><spa-a route-href=\"/nucleus/packages/quark/element_properties\" role=\"link\">Element properties</spa-a> — <code>prop("provision")</code>, <code>element</code></li>\n</ul>\n<p>At-rules</p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/quark/at_rules\" role=\"link\">At-rules</spa-a> — the ones that run</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/use\" role=\"link\"><code>@use</code></spa-a> — JS modules</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/on\" role=\"link\"><code>@on</code></spa-a> — events, blocks, options</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/dispatch\" role=\"link\"><code>@dispatch</code> / <code>@command</code></spa-a> — outgoing events and commands</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/view_transition\" role=\"link\"><code>@view-transition</code></spa-a> — animated writes</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/delay\" role=\"link\"><code>@delay</code></spa-a> — deferred writes</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/diagnostics\" role=\"link\"><code>@warn</code> / <code>@debug</code> / <code>@error</code></spa-a> — diagnostics from a rule</li>\n</ul>\n<p>Runtime</p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/quark/js_api\" role=\"link\">JS API</spa-a> — <code>Quark</code>, <code>whenSettled()</code>, DevTools</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/loop_guard\" role=\"link\">Loop guard</spa-a> — runaway chains are cut</li>\n<li><spa-a route-href=\"/nucleus/packages/quark/limitations\" role=\"link\">Limitations</spa-a> — beta limits and pitfalls</li>\n</ul>\n<p>The grammar (EBNF, precedence, AST) is the <code>quark-parser</code> package's <em>Language reference</em>.</p>\n<h3 id=\"md-examples\">Examples</h3>\n<h4 id=\"md-react-to-element-state\">React to element state</h4>\n<p>Native element state drives content — no JS, no listeners:</p>\n<p><include-content data-demo=\"toggle-content\"></include-content></p>\n<h4 id=\"md-list-from-a-provider\">List from a provider</h4>\n<p><code>prop("provision")</code> pulls the fetch payload; <code>iterate()</code> renders a row per item and re-renders on every provision:</p>\n<p><include-content data-demo=\"provider-list\"></include-content></p>\n<h4 id=\"md-call-a-module-helper\">Call a module helper</h4>\n<include-content data-language=\"html\"><template><quark-sheet>\n @use \"/helpers.js\" as *;\n\n #out { content: formatPrice($amount); }\n</quark-sheet></template></include-content>\n<h4 id=\"md-count-clicks-from-js\">Count clicks from JS</h4>\n<p>The sheet hands the owner element to a JS helper; each click calls <code>owner.quark.setProperty("$count", …)</code> and every rule reading <code>$count</code> below the owner re-runs. See <spa-a route-href=\"/nucleus/packages/quark/js_writes\" role=\"link\">Writing from JS</spa-a>.</p>\n<p><include-content data-demo=\"js-api\"></include-content></p>\n",
|
|
46
|
+
"selectors": "<h1 id=\"md-selectors\">Selectors</h1>\n<p>Quark rides the native selector engine: whatever <code>querySelectorAll</code> matches, a rule matches. What Quark adds is observation — knowing which changes can flip a match and where the subjects are afterwards.</p>\n<h2 id=\"md-what-is-observed\">What is observed</h2>\n<p>Attributes, classes and ids named anywhere in a selector are observed on the element they sit on: the matched element, an ancestor or earlier sibling on the selector's path, or a descendant named in <code>:has()</code>. Structure is observed too: elements inserted or removed under the host re-check the rules whose match depends on children or sibling position. <em>Generated.</em></p>\n<!-- generated:selectors -->\n<p><strong>Combinators</strong></p>\n<table>\n<thead>\n<tr>\n<th>Combinator</th>\n<th>Observed</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>a b</code></td>\n<td>Descendant. An attribute change on <code>a</code> re-runs the rule for the matching <code>b</code>s below it.</td>\n</tr>\n<tr>\n<td><code>a > b</code></td>\n<td>Child. Same observation as the descendant combinator.</td>\n</tr>\n<tr>\n<td><code>a + b</code></td>\n<td>Next sibling. An attribute change on <code>a</code> re-runs the rule from the parent; elements inserted or removed under that parent re-run it too.</td>\n</tr>\n<tr>\n<td><code>a ~ b</code></td>\n<td>Subsequent siblings. Same observation as <code>+</code>.</td>\n</tr>\n</tbody></table>\n<p><strong>Logical</strong></p>\n<table>\n<thead>\n<tr>\n<th>Pseudo-class</th>\n<th>Observed</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>:is(…)</code></td>\n<td>Attributes anywhere in the argument list are observed like the compound's own. A complex argument (<code>:is(section[x] li)</code>) re-runs the rule from the changed element down.</td>\n</tr>\n<tr>\n<td><code>:where(…)</code></td>\n<td>Same as <code>:is()</code> (Quark has no specificity).</td>\n</tr>\n<tr>\n<td><code>:not(…)</code></td>\n<td>Same observation as <code>:is()</code>.</td>\n</tr>\n</tbody></table>\n<p><strong>Relational</strong></p>\n<table>\n<thead>\n<tr>\n<th>Pseudo-class</th>\n<th>Observed</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>:has(…)</code></td>\n<td>Attributes named in the argument are observed on descendants, and elements inserted or removed below a candidate re-check it: the rule re-runs for every matching ancestor of the change. Sibling-relative arguments (<code>:has(+ …)</code>, <code>:has(~ …)</code>) and a <code>:has()</code> nested in a complex <code>:is()</code> / <code>:not()</code> argument re-run the whole rule from the host instead. Without rule reversion, pair it with the inverse <code>:not(:has(…))</code> rule.</td>\n</tr>\n</tbody></table>\n<p><strong>Structural (sibling position, children)</strong></p>\n<table>\n<thead>\n<tr>\n<th>Pseudo-class</th>\n<th>Observed</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>:first-child</code></td>\n<td>Sibling position: re-runs when elements are inserted or removed under the parent.</td>\n</tr>\n<tr>\n<td><code>:last-child</code></td>\n<td>Same as <code>:first-child</code>.</td>\n</tr>\n<tr>\n<td><code>:only-child</code></td>\n<td>Same as <code>:first-child</code>.</td>\n</tr>\n<tr>\n<td><code>:nth-child(An+B [of S])</code></td>\n<td>Same as <code>:first-child</code>. With <code>of S</code>, attributes in <code>S</code> are observed on the siblings and each change re-runs the rule from the parent.</td>\n</tr>\n<tr>\n<td><code>:nth-last-child(An+B [of S])</code></td>\n<td>Same as <code>:nth-child()</code>.</td>\n</tr>\n<tr>\n<td><code>:first-of-type</code></td>\n<td>Same as <code>:first-child</code>.</td>\n</tr>\n<tr>\n<td><code>:last-of-type</code></td>\n<td>Same as <code>:first-child</code>.</td>\n</tr>\n<tr>\n<td><code>:only-of-type</code></td>\n<td>Same as <code>:first-child</code>.</td>\n</tr>\n<tr>\n<td><code>:nth-of-type(An+B)</code></td>\n<td>Same as <code>:first-child</code>.</td>\n</tr>\n<tr>\n<td><code>:nth-last-of-type(An+B)</code></td>\n<td>Same as <code>:first-child</code>.</td>\n</tr>\n<tr>\n<td><code>:empty</code></td>\n<td>Re-checked when elements are inserted or removed below the element. Text-only changes are not observed.</td>\n</tr>\n</tbody></table>\n<p><strong>Attribute-backed</strong></p>\n<table>\n<thead>\n<tr>\n<th>Pseudo-class</th>\n<th>Observed</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>:disabled</code></td>\n<td>Observes <code>disabled</code> on the element and on ancestors (a disabled <code><fieldset></code>).</td>\n</tr>\n<tr>\n<td><code>:enabled</code></td>\n<td>Same as <code>:disabled</code>.</td>\n</tr>\n<tr>\n<td><code>:required</code></td>\n<td>Observes <code>required</code>.</td>\n</tr>\n<tr>\n<td><code>:optional</code></td>\n<td>Same as <code>:required</code>.</td>\n</tr>\n<tr>\n<td><code>:read-only</code></td>\n<td>Observes <code>readonly</code>, <code>disabled</code> and <code>contenteditable</code> on the element and its ancestors.</td>\n</tr>\n<tr>\n<td><code>:read-write</code></td>\n<td>Same as <code>:read-only</code>.</td>\n</tr>\n<tr>\n<td><code>:any-link</code></td>\n<td>Observes <code>href</code>.</td>\n</tr>\n<tr>\n<td><code>:lang(…)</code></td>\n<td>Observes <code>lang</code> on the element and its ancestors.</td>\n</tr>\n<tr>\n<td><code>:open</code></td>\n<td>Observes the <code>open</code> attribute (<code><details></code>, <code><dialog></code>). A <code><select></code> / <code><input></code> picker opening is not observed.</td>\n</tr>\n</tbody></table>\n<p><strong>Static</strong></p>\n<table>\n<thead>\n<tr>\n<th>Pseudo-class</th>\n<th>Observed</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>:scope</code></td>\n<td>The host; never changes.</td>\n</tr>\n<tr>\n<td><code>:root</code></td>\n<td>The document element; never changes.</td>\n</tr>\n</tbody></table>\n<p><strong>Not observed</strong> — <code>:hover</code>, <code>:focus</code>, <code>:focus-within</code>, <code>:focus-visible</code>, <code>:active</code>, <code>:visited</code>, <code>:link</code>, <code>:target</code>, <code>:checked</code>, <code>:indeterminate</code>, <code>:default</code>, <code>:valid</code>, <code>:invalid</code>, <code>:in-range</code>, <code>:out-of-range</code>, <code>:placeholder-shown</code>, <code>:popover-open</code>, <code>:modal</code>, <code>:fullscreen</code>, <code>:defined</code>, <code>:dir(…)</code>: Interaction or browser state with no attribute behind it: matches on the first run only (warned at build). Select on reflected attributes instead. Any pseudo-class not listed above is treated the same way.</p>\n<!-- /generated --><h2 id=\"md-has\"><code>:has()</code></h2>\n<p><code>:has()</code> costs a native subtree scan per candidate on every fan-out that includes the rule, plus one ancestor walk per observed change — keep its arguments shallow and off rules that match many rows. Without rule reversion a <code>:has()</code> rule needs its inverse (<code>:not(:has(…))</code>) like any other state:</p>\n<include-content data-language=\"quark\"><template>/* an aggregate over the rows becomes a fact on the host */\n:scope:has(li[data-is-selected]) { data-has-selection: \"\"; }\n:scope:not(:has(li[data-is-selected])) { data-has-selection: none; }</template></include-content>\n<h2 id=\"md-classes-and-ids\">Classes and ids</h2>\n<p>Class (<code>.x</code>) and id (<code>#x</code>) selectors are observed like attributes (a class change counts only when it adds or removes a class the sheet names). Setting attributes is still recommended over toggling / mutating classes and ids, since the latter has a heavier impact on Quark's performance: a sheet that names any class wakes on every class change under its host (styling churn included).</p>\n<p>When a rule re-runs, and what stays unobserved: <spa-a route-href=\"/nucleus/packages/quark/reactivity\" role=\"link\">Reactivity</spa-a>.</p>\n",
|
|
47
|
+
"sheets": "<h1 id=\"md-sheets-amp-scoping\">Sheets & scoping</h1>\n<p>A sheet lives next to the elements it orchestrates; its rules reach the host's subtree and nothing else, exactly like an inline <code><style></code> with <code>@scope</code>.</p>\n<h2 id=\"md-loading-a-sheet\">Loading a sheet</h2>\n<p>App authors almost always load Quark through <code><quark-sheet></code>:</p>\n<include-content data-language=\"html\"><template><section>\n <quark-sheet>\n details[open] [bind-status] {\n content: \"Open\";\n }\n details:not([open]) [bind-status] {\n content: \"Closed\";\n }\n </quark-sheet>\n <details>\n <summary>Panel</summary>\n <span bind-status></span>\n </details>\n</section></template></include-content>\n<p>Sheets can also be fetched (<code><quark-sheet src-url="/views/app.quark"></code>) or constructed programmatically — see <spa-a route-href=\"/nucleus/packages/quark/js_api\" role=\"link\">JS API</spa-a>.</p>\n<h2 id=\"md-scoping\">Scoping</h2>\n<p>By default (<code><quark-sheet></code> without <code>is-global</code>, or the <code>isScoped</code> constructor option), sheets are implicitly wrapped in <code>@scope { … }</code>, anchored at the host (the sheet's parent element) — the same model and semantics as CSS inline-<code><style></code> scoping. Global sheets (<code><quark-sheet is-global></code>) skip the wrap: top-level rules run in the root context — e.g. to read a provider that is an ancestor of the host — and rules the author nests in an explicit <code>@scope { }</code> block stay host-scoped. Every part of a selector matches strict descendants of the host: an ancestor compound (e.g. the <code>provider-fetch[is-success]</code> in <code>provider-fetch[is-success] h4</code>) is only satisfied by an element <em>inside</em> the host, never by the host itself or anything above it. The host is reachable only via the explicit <code>:scope</code> selector:</p>\n<include-content data-language=\"quark\"><template>:scope { data-app: \"ready\"; } /* mutate the host itself */\n:scope[data-mode=\"on\"] [bind-x] { … } /* react to host state */</template></include-content>\n<p>Registered hosts carry a generated <code>q-scope="<id>"</code> attribute — Quark's scoping anchor. Treat it as internal: don't set, copy, or select on it.</p>\n<h2 id=\"md-what-runs\">What runs</h2>\n<p>A sheet is minified (comments stripped, whitespace collapsed), optionally wrapped in <code>@scope { … }</code> (see above), and parsed. The runtime then executes:</p>\n<ul>\n<li><strong>Rules</strong>, nested to any depth. A nested selector is joined to its parent as a descendant, or spliced over each <code>&</code>.</li>\n<li><strong>Declarations inside rules</strong>, by key shape (see <spa-a route-href=\"/nucleus/packages/quark/declarations\" role=\"link\">Declaration kinds</spa-a>).</li>\n<li><strong><code>@use "url" [as name | as *]</code></strong> anywhere in the sheet. The namespace defaults to the URL's last path segment without its extension (<code>/api-client.js</code> → <code>api-client</code>); <code>as *</code> merges exports into the bare scope, last import winning on clashes. A <code>with (…)</code> clause is a parse error.</li>\n<li><strong><code>@scope { … }</code></strong>, which takes no prelude: rules inside stay anchored to the host in a global sheet.</li>\n<li><strong><code>@on <event> [(options)] …;</code></strong> inside rules: listeners (see <spa-a route-href=\"/nucleus/packages/quark/on\" role=\"link\"><code>@on</code></spa-a>).</li>\n<li><strong><code>@view-transition [(options)] { … }</code></strong> inside rules, around rules or inside <code>@on</code> blocks: the writes inside it commit inside a view transition (see <spa-a route-href=\"/nucleus/packages/quark/view_transition\" role=\"link\"><code>@view-transition</code></spa-a>).</li>\n<li><strong><code>@delay <ms> { … }</code></strong> inside rules and blocks: the block applies once, after the pause, if the rule still matches (see <spa-a route-href=\"/nucleus/packages/quark/delay\" role=\"link\"><code>@delay</code></spa-a>).</li>\n<li><strong><code>@warn</code> / <code>@debug</code> / <code>@error <expression>;</code></strong> inside rules and blocks: report on the matched element (see <spa-a route-href=\"/nucleus/packages/quark/diagnostics\" role=\"link\">Diagnostics</spa-a>).</li>\n</ul>\n<p>That list is the language: any other at-rule, a nested property block, a <code>%placeholder</code> selector, <code>#{…}</code> outside a string, and <code>!important</code> / <code>!default</code> / <code>!global</code> are parse errors, and a sheet that fails to parse does not run (<code><quark-sheet></code> sets <code>is-error</code>). Top-level declarations parse but have no element to write.</p>\n<include-content data-language=\"quark\"><template>@use \"/helpers.js\" as *;\nmain {\n $items: prop(\"provision\").body;\n ul {\n content: iterate($items);\n li { data-id: item.id; &[data-id=\"0\"] { is-first: \"\"; } }\n }\n}</template></include-content>\n<h2 id=\"md-load-order\">Load order</h2>\n<p>Imports (<code>@use</code>) and <code>src-url</code> sheets resolve before the first rule run, so a sheet that registers late misses events fired meanwhile — react to state attributes (<code>is-*</code>) rather than one-shot events, and put the sheet first in its host when it must hear boot-time events.</p>\n",
|
|
48
|
+
"syntax": "<h1 id=\"md-syntax\">Syntax</h1>\n<p>Quark is a derivative of CSS with CSS-compatible syntax. A CSS engine tokenizes a Quark sheet and drops what it does not understand, so a Quark construct never breaks the CSS around it. Nested rules, <code>&</code>, custom properties and <code>/* */</code> comments are CSS's own — Quark inherits them rather than inventing them. On top of CSS it adds <code>$variables</code>, expressions, <code>#{…}</code> interpolation inside strings, and its own at-rules: <code>@use</code>, <code>@scope</code>, <code>@on</code>, <code>@dispatch</code> / <code>@command</code>, <code>@view-transition</code>, <code>@delay</code>, <code>@warn</code> / <code>@debug</code> / <code>@error</code>. It is a derivative, not a superset: those at-rules are the only ones the language has, and every other one — <code>@media</code>, <code>@keyframes</code>, <code>@supports</code>, SCSS's <code>@if</code> / <code>@mixin</code> and the rest — is a parse error, as are <code>%placeholder</code> selectors, <code>#{…}</code> outside a string, <code>!important</code> / <code>!default</code> / <code>!global</code>, and nested property blocks.</p>\n<h2 id=\"md-essentials\">Essentials</h2>\n<p>The full grammar (EBNF, precedence, disambiguation rules) is the <code>quark-parser</code> package's <em>Language reference</em>; the essentials:</p>\n<ul>\n<li><strong>Rules</strong> <code>selector { … }</code> nest. A nested selector is a descendant of its parent unless it uses <code>&</code> (<code>&[open]</code>, <code>&-active</code>). Lists use <code>,</code>; combinators are whitespace, <code>></code>, <code>+</code>, <code>~</code>.</li>\n<li><strong>Declarations</strong> <code>key: value;</code> — the <code>;</code> is optional before <code>}</code>. Keys are attribute names, <code>$variables</code>, <code>--css-vars</code>, <code>content</code>, <code>class</code>, <code>dataset</code>, <code>ariaset</code>.</li>\n<li><strong>At-rules</strong> <code>@use "url" as *;</code> imports; <code>@on click, change (options) { … }</code> wires listeners inside a rule (a comma list of event names, an optional <code>(options)</code> map, then a block applied once per event, or just the options — JS callouts are <code>handle: fn</code> in the map); <code>@dispatch</code> / <code>@command</code> send events from an <code>@on</code> block; <code>@scope { … }</code> anchors rules to the host.</li>\n<li><strong>Literals</strong> <code>"strings"</code> / <code>'strings'</code> with <code>\\</code> escapes and <code>#{$interpolation}</code>; numbers <code>42</code>, <code>1.5</code>, <code>10px</code> (a unit makes it a string); <code>#ccc</code> colors (strings); <code>true</code>, <code>false</code>, <code>null</code>. Bare words are value keywords, <code>@use</code> exports, or built-ins.</li>\n<li><strong>Operators</strong>, loosest to tightest: <code>or</code> · <code>and</code> · <code>not</code> · <code>==</code> <code>!=</code> · <code><</code> <code>></code> <code><=</code> <code>>=</code> · <code>+</code> <code>-</code> · <code>*</code> <code>/</code> <code>%</code> · unary <code>-</code> <code>+</code> · <code>.</code> <code>[…]</code> <code>(…)</code>. Parentheses group.</li>\n<li><strong>Accessors and calls</strong> <code>$obj.field</code>, <code>$list[0]</code>, <code>$obj["key"]</code>, <code>ns.$var</code>; methods <code>item.name.trim()</code>; calls <code>fn($a, $b)</code>, named <code>fn($opt: 1)</code>, spread <code>fn($args...)</code>. Only bare names and member chains are callable, not <code>$variables</code>.</li>\n<li><strong>Conditionals</strong> <code>if($cond: a; $other: b; else: c)</code> or <code>ternary($cond, a, b)</code>.</li>\n<li><strong>Lists and maps</strong> <code>1, 2, 3</code> and <code>1px solid red</code> both evaluate to arrays; <code>(a: 1, b: 2)</code> is a map. Spacing around a sign matters: <code>$x +1</code> is a two-item list, <code>$x + 1</code> / <code>$x+1</code> add.</li>\n<li><strong>Comments</strong> <code>/* … */</code> only, anywhere whitespace is allowed. <code>//</code> is not a comment: a sheet has to stay tokenizable by a CSS engine, and CSS tokenizers read <code>//</code> as text, so a <code>//</code> line is a parse error.</li>\n<li><strong>Not supported</strong> <code>? :</code>, <code>?.</code>, <code>??</code>, <code>===</code>, <code>||</code>, <code>&&</code>, arrow functions — parse errors by design.</li>\n</ul>\n<h2 id=\"md-example\">Example</h2>\n<include-content data-language=\"quark\"><template>/* a comment */\n[data-cart] {\n $items: prop(\"provision\").items;\n $total: $items.length or 0;\n data-is-empty: $total == 0; /* a boolean writes \"\" or removes the attribute */\n [bind-summary] { content: if($total == 1: \"1 item\"; else: \"#{$total} items\"); }\n &[data-is-empty] [bind-summary] { content: \"Empty\"; }\n &[data-is-empty] button[data-action=\"clear\"] { disabled: \"\"; }\n &:not([data-is-empty]) button[data-action=\"clear\"] { disabled: none; }\n button { @on click (prevent-default, handle: clearCart($items)); }\n}</template></include-content>\n<p>How each declaration key is interpreted: <spa-a route-href=\"/nucleus/packages/quark/declarations\" role=\"link\">Declaration kinds</spa-a>. How values evaluate: <spa-a route-href=\"/nucleus/packages/quark/expressions\" role=\"link\">Expressions</spa-a>.</p>\n",
|
|
49
|
+
"use": "<h1 id=\"md-use\">@use</h1>\n<p>Import JS modules directly in the sheet; pure functions are the sanctioned way for logic to enter a sheet.</p>\n<h2 id=\"md-importing\">Importing</h2>\n<p><code>as *</code> exposes exports bare; <code>as name</code> (or the name derived from the url) namespaces them:</p>\n<include-content data-language=\"quark\"><template>@use \"/helpers.js\" as *;\n@use \"/api-client.js\" as api;\n\n#out {\n content: formatPrice(api.getAmount());\n}</template></include-content>\n<p>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 <code>with (…)</code> clause is a parse error.</p>\n<p><code>quark:</code> URLs import Quark's own helpers without a fetch — <code>@use "quark:math";</code> derives the namespace <code>math</code> — see <spa-a route-href=\"/nucleus/packages/quark/modules\" role=\"link\">Built-in modules</spa-a>. Reach for them before writing a module function of your own.</p>\n<h2 id=\"md-writing-module-functions\">Writing module functions</h2>\n<p>Exports are plain functions called from expressions (<code>formatPrice($amount)</code>), as <code>@on</code> handlers (<code>@on click (handle: addToCart)</code>, or a call evaluated per event: <code>@on click (handle: addToCart(element, event))</code>), or handed the matched node through <code>element</code>. Prefer pure functions; a function that needs to write State does it through <spa-a route-href=\"/nucleus/packages/quark/js_writes\" role=\"link\"><code>element.quark</code></spa-a> or by dispatching an event an <code>@on</code> block turns into attributes.</p>\n",
|
|
50
|
+
"values": "<h1 id=\"md-values-amp-keywords\">Values & keywords</h1>\n<p>Three bare words steer what a write does — remove, leave alone, or fall through — and every expression result maps onto one of them.</p>\n<h2 id=\"md-keywords\">Keywords</h2>\n<p>Bare words with a fixed meaning in every expression. <em>Generated.</em></p>\n<!-- generated:value-keywords -->\n<table>\n<thead>\n<tr>\n<th>Keyword</th>\n<th>Meaning</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>none</code></td>\n<td><code>null</code>: removes the attribute / CSS variable, clears content, or stores <code>null</code> in a <code>$variable</code>.</td>\n</tr>\n<tr>\n<td><code>preserve</code></td>\n<td>Explicit no-op: leaves the attribute / content / binding exactly as it is. Idiom for loading states: <code>content: $todo.title or preserve</code>.</td>\n</tr>\n<tr>\n<td><code>unset</code></td>\n<td><code>$variables</code> only: deletes the binding from the matched element so consumers fall through to the next ancestor. On any other target it degrades to a wipe.</td>\n</tr>\n</tbody></table>\n<!-- /generated --><h2 id=\"md-wipes-and-no-ops\">Wipes and no-ops</h2>\n<ul>\n<li><code>none</code> → <code>null</code>: removes the target attribute / clears content.</li>\n<li>An expression resolving to <code>undefined</code> or <code>null</code> also <strong>wipes</strong> its target (a matched declaration that resolves to nothing clears what it manages).</li>\n<li><code>preserve</code>: explicit no-op — leaves the current attribute / content / binding untouched. Handy for loading states: <code>content: $todo.title or preserve;</code></li>\n<li><code>unset</code>: variables only — deletes the binding (falls through to ancestors).</li>\n<li>A failed expression evaluation never wipes; it logs and no-ops — the Orchestrator never destroys state by mistake.</li>\n</ul>\n<h2 id=\"md-literals\">Literals</h2>\n<p>Unitless numbers are numbers; a number with a unit (<code>10px</code>, <code>50%</code>) and a color (<code>#ccc</code>) evaluate to strings; <code>true</code>, <code>false</code>, <code>null</code> are themselves. Strings unescape <code>\\n</code>, <code>\\t</code>, <code>\\r</code>, and <code>\\x</code> → <code>x</code>; interpolated strings (<code>"Hello #{$name}"</code>) and <code>url(…)</code> join their parts, with <code>null</code> / <code>undefined</code> parts rendered empty. Lists (space or comma separated) evaluate to arrays; maps (<code>(name: "Ada")</code>) to objects — see <spa-a route-href=\"/nucleus/packages/quark/expressions\" role=\"link\">Expressions</spa-a>.</p>\n",
|
|
51
|
+
"variables": "<h1 id=\"md-variables\">Variables</h1>\n<p><code>$variables</code> behave like CSS custom properties: stored on the elements a rule matches, resolved up the DOM, shared across sheets.</p>\n<h2 id=\"md-declaring-and-reading\">Declaring and reading</h2>\n<include-content data-language=\"quark\"><template>main {\n $label: \"Ready\";\n $count: twice(21);\n [bind-label] { content: $label; }\n [bind-count] { content: $count; }\n}</template></include-content>\n<p>A <code>$variable</code> declaration stores its value on each element the rule matches, and a consumer resolves it by walking up from its own element to the nearest ancestor (self included) that holds the binding. They cascade across sheets — a sheet can read a binding another sheet set on an ancestor. Nesting rules is still good style, but a sibling rule's (or another sheet's) variable resolves fine as long as the binding lives on a DOM ancestor of the consumer:</p>\n<include-content data-language=\"quark\"><template>main { $theme: \"dark\"; }\n[bind-theme] { content: $theme; } /* resolves if inside <main> */</template></include-content>\n<h2 id=\"md-shadowing-and-namespaces\">Shadowing and namespaces</h2>\n<p>When bindings shadow, the nearest DOM ancestor wins (like CSS inheritance). Because storage is shared per element, same-named writers collide (last writer wins) — namespace app bindings (e.g. <code>$app-theme</code>) to avoid clashes.</p>\n<h2 id=\"md-unset\"><code>unset</code></h2>\n<p>Use <code>unset</code> to delete a binding from the matched elements so consumers fall through to the next ancestor — here the open <code><details></code> shadows the host's binding, and closing it falls back:</p>\n<p><include-content data-demo=\"unset\"></include-content></p>\n<h2 id=\"md-raising-state\">Raising state</h2>\n<p>A <code>$variable</code> declared on a descendant <em>shadows</em> the ancestor's; it does not update it. To change an owner's binding from below, write it <strong>on the owner</strong>: an <code>@on</code> block on the owner rule, with <code>target:</code> delegation when the interaction happens in a descendant:</p>\n<include-content data-language=\"quark\"><template>:scope {\n $count: 0;\n [bind-count] { content: $count; }\n @on click (target: \"button\") { $count: $count + 1; }\n}</template></include-content>\n<p>A row that raises state reflects a dash-named attribute the owner can read: <code>ul { @on click (target: "li[data-id]") { $selected: target.getAttribute("data-id"); } }</code>.</p>\n<p>Writing a binding from JavaScript: <spa-a route-href=\"/nucleus/packages/quark/js_writes\" role=\"link\">Writing from JS</spa-a>.</p>\n",
|
|
52
|
+
"view_transition": "<h1 id=\"md-view-transition\">@view-transition</h1>\n<p><code>@view-transition [(options)] { … }</code> commits the writes inside it inside <code>document.startViewTransition()</code>, so CSS can animate what changed — rows that leave included, which plain CSS transitions cannot reach.</p>\n<h2 id=\"md-a-paint-policy\">A paint policy</h2>\n<p>The block decides <em>how</em> its declarations and its nested rules' writes land, never <em>when</em> rules run.</p>\n<include-content data-language=\"quark\"><template>provider-fetch[is-success] {\n $todos: prop(\"provision\").body;\n @view-transition (types: \"todo-change\") {\n ul { content: iterate($todos, none, \"id\"); }\n [bind-count] { content: $todos.length; }\n }\n}</template></include-content>\n<include-content data-language=\"css\"><template>ul { view-transition-name: todos; }\nli { view-transition-name: match-element; view-transition-class: todo; }\n::view-transition-new(.todo):only-child { animation: todo-in 250ms; }\n::view-transition-old(.todo):only-child { animation: todo-out 200ms; }\n:root:active-view-transition-type(todo-change) ::view-transition-old(root) { animation: none; }</template></include-content>\n<p><include-content data-demo=\"view-transition\"></include-content></p>\n<ul>\n<li><strong>One tick, one cut.</strong> Every Quark write of the same tick lands in the same cut, and writes that react to those writes land in it too as long as Quark settles within <code>timeout</code>. Writes more than a tick later are a separate fact and a separate transition.</li>\n<li><strong>Put the writes of one cut inside the block</strong> (the list, its counter, the empty state). Only a write inside a block starts a transition; writes outside it join the cut when they happen meanwhile. <code>$variables</code> do not paint, so a block holding only <code>$variable</code> writes never starts one.</li>\n<li><strong>Keyed <code>iterate()</code> rows persist</strong>, so named rows move instead of leaving and entering again.</li>\n<li><strong>Styling is CSS's job.</strong> Name elements (<code>view-transition-name</code>, <code>match-element</code>, <code>view-transition-class</code>) and target <code>::view-transition-group</code> / <code>-old</code> / <code>-new</code> and <code>:active-view-transition-type()</code>. While the animation runs, captured elements are drawn from those pseudo-elements, so real-DOM styles only show on elements that are not captured. A name that comes from State is one CSS variable away: <code>li { --vt-name: "row-#{item.id}"; }</code> in the sheet, <code>li { view-transition-name: var(--vt-name); }</code> in the CSS.</li>\n<li><strong>Options belong to the block.</strong> They are evaluated per write, on the block's element — what the rule it sits in matches, or the host for a sheet-level block — so <code>attr()</code>, <code>prop()</code> and <code>item</code> read that element.</li>\n</ul>\n<h2 id=\"md-options\">Options</h2>\n<table>\n<thead>\n<tr>\n<th>Option</th>\n<th>Effect</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>types: "a b"</code></td>\n<td>Names for <code>:active-view-transition-type()</code>: a string (space-separated) or a list; <code>"todo-#{$op}"</code> interpolates. The types of every write in one transition are combined.</td>\n</tr>\n<tr>\n<td><code>timeout: <ms></code></td>\n<td>How long the transition waits for Quark to settle before the new state is captured. Default 300, or 1000 with <code>until</code>. On expiry it captures what is there and warns once per block.</td>\n</tr>\n<tr>\n<td><code>delay: <ms></code></td>\n<td>Hold these writes back first, then commit them in their own transition. Other writes of the same tick are not held and land first.</td>\n</tr>\n<tr>\n<td><code>first-render</code></td>\n<td>Also animate the sheet's first render (off by default, like <code>spa-manager</code>'s <code>transition-first-render</code>).</td>\n</tr>\n<tr>\n<td><code>if-active: skip</code> / <code>replace</code></td>\n<td>While another view transition runs (a route change, or Quark's own still animating): commit unanimated (<code>skip</code>, the default) or start anyway, which ends the running one (<code>replace</code>, the browser's own behavior).</td>\n</tr>\n<tr>\n<td><code>until: "<selector>"</code> / <code>until: <promise></code></td>\n<td>Keep the transition open until the block's element matches the selector (<code>"[is-success], [is-error]"</code>, <code>":not([is-loading])"</code>, <code>":has(li)"</code>) or the promise settles, capped by <code>timeout</code>.</td>\n</tr>\n</tbody></table>\n<h2 id=\"md-until\"><code>until</code></h2>\n<p><strong><code>until</code> is for short waits.</strong> The page stays frozen on the old state while the transition is open, so wait for a template or view that is about to render, never for a data fetch — animate a fetch as two cuts (<code>[is-loading]</code>, then <code>[is-success]</code>) instead. Put the block on the element that owns the fact and nest the writes; a block on a descendant waiting for an ancestor's attribute only times out:</p>\n<include-content data-language=\"quark\"><template>provider-fetch[is-loading] {\n @view-transition (until: \"[is-success], [is-error]\", timeout: 800) {\n ul { content: none; }\n }\n}</template></include-content>\n<h2 id=\"md-when-no-transition-runs\">When no transition runs</h2>\n<p>Committed without a transition: writes that change nothing, the sheet's first render, <code>prefers-reduced-motion: reduce</code>, a hidden document, browsers without the API (the writes land as usual) and, under <code>if-active: skip</code>, writes while another transition is active. A document runs one view transition at a time; scoped (per-element) transitions are not available yet.</p>\n<h2 id=\"md-costs\">Costs</h2>\n<p>Each transition snapshots the page once, its writes land one rendering opportunity later than without the block, and pointer input goes to the transition overlay while it animates — keep animations short. It is the one place Quark stops being "write and forget": a State write waits for a rendering opportunity.</p>\n"
|
|
53
|
+
},
|
|
54
|
+
"docSections": [
|
|
55
|
+
{
|
|
56
|
+
"id": "syntax",
|
|
57
|
+
"title": "Syntax",
|
|
58
|
+
"docs": [
|
|
59
|
+
{
|
|
60
|
+
"name": "sheets",
|
|
61
|
+
"title": "Sheets & scoping"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "syntax",
|
|
65
|
+
"title": "Syntax"
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": "selectors",
|
|
71
|
+
"title": "Selectors",
|
|
72
|
+
"docs": [
|
|
73
|
+
{
|
|
74
|
+
"name": "selectors",
|
|
75
|
+
"title": "Selectors"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"name": "reactivity",
|
|
79
|
+
"title": "Reactivity"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "no_reversion",
|
|
83
|
+
"title": "No reversion"
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"id": "declarations",
|
|
89
|
+
"title": "Declarations",
|
|
90
|
+
"docs": [
|
|
91
|
+
{
|
|
92
|
+
"name": "declarations",
|
|
93
|
+
"title": "Declaration kinds"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"name": "attributes",
|
|
97
|
+
"title": "Attributes"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"name": "content",
|
|
101
|
+
"title": "Content"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"name": "css_variables",
|
|
105
|
+
"title": "CSS variables"
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"id": "values",
|
|
111
|
+
"title": "Values & Expressions",
|
|
112
|
+
"docs": [
|
|
113
|
+
{
|
|
114
|
+
"name": "variables",
|
|
115
|
+
"title": "Variables"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"name": "js_writes",
|
|
119
|
+
"title": "Writing from JS"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"name": "values",
|
|
123
|
+
"title": "Values & keywords"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"name": "expressions",
|
|
127
|
+
"title": "Expressions"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"name": "builtins",
|
|
131
|
+
"title": "Built-in functions"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"name": "modules",
|
|
135
|
+
"title": "Built-in modules"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"name": "methods",
|
|
139
|
+
"title": "Allowed methods"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"name": "element_properties",
|
|
143
|
+
"title": "Element properties"
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"id": "at-rules",
|
|
149
|
+
"title": "At-Rules",
|
|
150
|
+
"docs": [
|
|
151
|
+
{
|
|
152
|
+
"name": "at_rules",
|
|
153
|
+
"title": "At-rules"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"name": "use",
|
|
157
|
+
"title": "@use"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"name": "on",
|
|
161
|
+
"title": "@on"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"name": "dispatch",
|
|
165
|
+
"title": "@dispatch / @command"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"name": "view_transition",
|
|
169
|
+
"title": "@view-transition"
|
|
170
|
+
}
|
|
171
|
+
]
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"id": "runtime",
|
|
175
|
+
"title": "Runtime",
|
|
176
|
+
"docs": [
|
|
177
|
+
{
|
|
178
|
+
"name": "js_api",
|
|
179
|
+
"title": "JS API"
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"name": "loop_guard",
|
|
183
|
+
"title": "Loop guard"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"name": "limitations",
|
|
187
|
+
"title": "Limitations"
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
],
|
|
192
|
+
"installation": {
|
|
193
|
+
"name": "@excom/quark",
|
|
194
|
+
"shortName": "quark",
|
|
195
|
+
"version": "0.1.0",
|
|
196
|
+
"description": "CSS-like DOM orchestration language for binding, rendering, and events",
|
|
197
|
+
"packageType": "library",
|
|
198
|
+
"install": {
|
|
199
|
+
"npm": "npm install @excom/quark"
|
|
200
|
+
},
|
|
201
|
+
"imports": {
|
|
202
|
+
"js": "import { /* … */ } from \"@excom/quark\";"
|
|
203
|
+
},
|
|
204
|
+
"peerDependencies": []
|
|
205
|
+
},
|
|
206
|
+
"elementApis": [],
|
|
207
|
+
"exportedFiles": {}
|
|
208
|
+
}
|