@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,14 @@
|
|
|
1
|
+
<div>
|
|
2
|
+
<details>
|
|
3
|
+
<summary>Panel <span bind-status></span></summary>
|
|
4
|
+
<p>Panel body.</p>
|
|
5
|
+
</details>
|
|
6
|
+
<quark-sheet>
|
|
7
|
+
details[open] [bind-status] {
|
|
8
|
+
content: "Open";
|
|
9
|
+
}
|
|
10
|
+
details:not([open]) [bind-status] {
|
|
11
|
+
content: "Closed";
|
|
12
|
+
}
|
|
13
|
+
</quark-sheet>
|
|
14
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<div>
|
|
2
|
+
<details>
|
|
3
|
+
<summary bind-foo></summary>
|
|
4
|
+
<p>Open me to shadow the host's binding.</p>
|
|
5
|
+
</details>
|
|
6
|
+
<quark-sheet>
|
|
7
|
+
:scope { $foo: 1; }
|
|
8
|
+
details[open] { $foo: 2; }
|
|
9
|
+
details:not([open]) { $foo: unset; }
|
|
10
|
+
[bind-foo] { content: "$foo resolves to #{$foo}"; }
|
|
11
|
+
</quark-sheet>
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<div data-count="2">
|
|
2
|
+
<button type="button" data-add>Add planet</button>
|
|
3
|
+
<button type="button" data-remove>Remove planet</button>
|
|
4
|
+
<ul>
|
|
5
|
+
<template>
|
|
6
|
+
<li bind-name></li>
|
|
7
|
+
</template>
|
|
8
|
+
</ul>
|
|
9
|
+
<quark-sheet>
|
|
10
|
+
@use "/demo-utils" as *;
|
|
11
|
+
|
|
12
|
+
:scope {
|
|
13
|
+
$count: +attr("data-count");
|
|
14
|
+
@on click (target: "[data-add]") { data-count: $count + 1; }
|
|
15
|
+
@on click (target: "[data-remove]") { data-count: $count - 1; }
|
|
16
|
+
}
|
|
17
|
+
[data-add] { disabled: $count == 4; }
|
|
18
|
+
[data-remove] { disabled: $count == 0; }
|
|
19
|
+
ul {
|
|
20
|
+
@view-transition (types: "planets") {
|
|
21
|
+
content: iterate(getPlanets().slice(0, $count));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
[bind-name] { content: item; }
|
|
25
|
+
</quark-sheet>
|
|
26
|
+
<style>
|
|
27
|
+
#demo-quark-view-transition > :first-child {
|
|
28
|
+
ul { view-transition-name: demo-planets; }
|
|
29
|
+
li {
|
|
30
|
+
view-transition-name: match-element;
|
|
31
|
+
view-transition-class: demo-planet;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
::view-transition-group(.demo-planet) { animation-duration: 250ms; }
|
|
35
|
+
</style>
|
|
36
|
+
</div>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Attributes
|
|
2
|
+
|
|
3
|
+
Any property that is not `$…` / `content` / `class` / `dataset` / `ariaset` becomes an attribute of the matched element; `none` removes it.
|
|
4
|
+
|
|
5
|
+
## Writing attributes
|
|
6
|
+
|
|
7
|
+
```quark
|
|
8
|
+
dialog[open] input {
|
|
9
|
+
autofocus: "";
|
|
10
|
+
}
|
|
11
|
+
dialog:not([open]) input {
|
|
12
|
+
autofocus: none;
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
A string or number is written as text, a boolean writes `""` (present) or removes the attribute, and a wipe value (`none`, `null`, `undefined`) removes it. Custom attributes always contain a dash (`data-is-empty`, `is-open`) so they can never collide with a native one.
|
|
17
|
+
|
|
18
|
+
## Reading attributes
|
|
19
|
+
|
|
20
|
+
A literal `attr("x")` in an expression subscribes to `x` — the rule re-runs when that attribute changes, even if `x` is not in the selector. `attr($name)` does not subscribe.
|
|
21
|
+
|
|
22
|
+
## Attribute helpers
|
|
23
|
+
|
|
24
|
+
- `class:` sets the `class` attribute from a string (replaces), an array (joined) or an object (`{ name: boolean }` toggles each class).
|
|
25
|
+
- `dataset:` writes one `data-*` attribute per key (camelCase → dash-case) and removes the `data-*` attributes this sheet set earlier; `ariaset:` does the same with the `aria-` prefix. Unpack a pre-defined object (`dataset: item`) rather than constructing a map for it.
|
|
26
|
+
|
|
27
|
+
## Form controls
|
|
28
|
+
|
|
29
|
+
On `<input>` and `<option>` the `value` / `checked` / `selected` 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 `value:` / `checked:` on an `<input>` or `selected:` on an `<option>` 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 `<select>` has no `value` attribute (write `selected:` on its options). The sync is one-way: typing never updates an attribute and is not observed.
|
|
30
|
+
|
|
31
|
+
```quark
|
|
32
|
+
[data-unit="f"] input {
|
|
33
|
+
value: ($celsius * 9 / 5 + 32).toFixed(1); /* shows even after the user typed */
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
A text result painted into a `<textarea>` is mirrored the same way — see [Content](./CONTENT.md).
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# At-rules
|
|
2
|
+
|
|
3
|
+
Seven at-rules run: `@use` imports modules, `@scope` anchors rules, `@on` wires listeners, `@dispatch` / `@command` send events and commands from an `@on` block, `@view-transition` animates a block's writes, `@delay` defers a block, and `@warn` / `@debug` / `@error` report from a rule.
|
|
4
|
+
|
|
5
|
+
## Reference
|
|
6
|
+
|
|
7
|
+
`@on` takes a comma list of event names, an optional options group (a map: `name: value` entries and bare flags) and a block applied once per event — or just the options. `@dispatch` / `@command` take a name list and options, inside `@on` blocks only. `@delay` takes a duration expression and a block; `@warn` / `@debug` / `@error` take an expression. *Generated.*
|
|
8
|
+
|
|
9
|
+
<!-- generated:at-rules -->
|
|
10
|
+
| At-rule | Effect |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| `@use "url" [as name \| as *];` | Imports a JS module anywhere in the sheet. The namespace defaults to the URL's last path segment without its extension; `as *` merges exports into the bare scope, last import winning. A `with (…)` clause is a parse error. |
|
|
13
|
+
| `@scope { … }` | Rules inside stay anchored to the host in a global sheet (the implicit wrapper of a scoped sheet). It takes no prelude. |
|
|
14
|
+
| `@on <event>[, <event>] [(options)] { … }` | Inside a rule: listens for the events (bare names such as `click` or `super-form-success`, 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, `$variables`, `--props`, `content`), nested rules write its matching descendants, or its siblings when the nested selector starts with `+` / `~`, `@dispatch` / `@command` statements fire after those writes are queued. `event` names the DOM event and `target` the delegate (or `event.target`) inside the block and in its per-event options. `@on` inside a block is not supported. |
|
|
15
|
+
| `@on <event> (option, option: value) …` | An options group after the events gates and configures the listener; with it the block is optional (`@on submit (prevent-default);`). A bare name is a flag. Filters: `target: "<selector>"` (delegation — fires only when the event target is inside a matching descendant; that element is `target` in the block), `self` (only when the event target is the matched element), `key: "Escape"` / `"Shift+K"` (keyboard chords; space-separated alternatives). Event flags: `prevent-default`, `stop-propagation`, `stop-immediate-propagation`. Timing: `debounce: <ms>`, `throttle: <ms>`. JS: `handle: fn` — a function (or a call returning one, or a list `(a, b)`) called with the event before the block, `this` being the element. Registration: `once` (removed after the first event that passes the filters), `passive`, `capture`, `host: window` / `host: document` (listen there while the element is connected; `target` then resolves against the whole document). `target`, `key`, `debounce`, `throttle` and `handle` are evaluated when the event fires, in the block's scope; the rest once per match. Two `@on`s for one event may coexist when their options differ. |
|
|
16
|
+
| `@dispatch <event>[, <event>] [(options)];` | Inside an `@on` block (or a nested rule / `@delay` block within one): dispatches a `CustomEvent` 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: `detail: <expression>`; `target: "<selector>"` (every match in the element's document; `:scope` = the block's element, not the sheet host — resolved as `<event-handler target-ref>` is) or `target: <element \| list>` (`closest("provider-fetch")`); `host: window` / `host: document`; `form: "<selector>"` or `form: <form>` (its field values become the detail, an explicit `detail` map merges over them); the flags `bubbles` (default true), `cancelable` (default true), `composed` (default false), each settable to `false`. Dispatching the enclosing `@on` 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. |
|
|
17
|
+
| `@command <name>[, <name>] [(target: …)];` | Inside an `@on` block: invokes each command on the target elements (the block's element by default; `target` as for `@dispatch`) the way a `<button command commandfor>` would — native commands (`show-modal`, `close`, `request-close`, `show-popover`, `hide-popover`, `toggle-popover`) and custom `--names`, which reach the target as a `command` event. Where the browser lacks the Invoker Commands API, custom commands are dispatched as a synthetic `command` event and native ones call the element's method. Only `target` is an option. |
|
|
18
|
+
| `@view-transition [(options)] { … }` | Inside a rule, around rules, or inside an `@on` block: every paint of the writes in the block — its declarations (on the rule's element) and its nested rules' — commits inside `document.startViewTransition()`, so CSS animates the change (`view-transition-name`, `::view-transition-*`). It scopes *how* writes land, never *when* 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, `prefers-reduced-motion: reduce`, browsers without the API, and writes while another view transition is active. |
|
|
19
|
+
| `@view-transition (option, option: value) { … }` | `types: "a b"` names the transition for `:active-view-transition-type()` (a string or a list). `timeout: <ms>` caps the settle wait (default 300). `delay: <ms>` holds these writes back first. `first-render` also animates the sheet's first render. `if-active: skip \| replace`: while another transition runs, commit unanimated (default) or start anyway, which skips the running one. `until: "<selector>"` keeps the transition open until the block's element matches the selector, `until: <promise>` until it settles (default timeout 1000; the page is frozen meanwhile, so for short waits only). Values are evaluated per write. |
|
|
20
|
+
| `@delay <ms> { … }` | Inside a rule or an `@on` / `@delay` block: applies the block once, `<ms>` 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; `event` / `target` are kept inside an `@on` block). Timers keep the loop guard's causal depth and are cleared when the sheet unregisters. |
|
|
21
|
+
| `@warn <expression>; / @debug <expression>; / @error <expression>;` | Inside a rule or a block: evaluates the expression on the matched element and reports it — to the console at that level (`@debug` is silent below debug logging) and to DevTools as `quark/diagnostic`. The selector is the condition (`img:not([alt]) { @warn "img needs alt"; }`). `@warn` / `@error` speak once per element and rule; `@debug` speaks on every application, so it re-logs when a binding or `prop()` it reads changes. A comma list reports one value per item. |
|
|
22
|
+
<!-- /generated -->
|
|
23
|
+
|
|
24
|
+
```quark
|
|
25
|
+
form {
|
|
26
|
+
@on submit (prevent-default, handle: saveDraft);
|
|
27
|
+
@on keydown (key: "Escape") { is-editing: none; }
|
|
28
|
+
@on reset { @dispatch draft-cleared (target: "#status"); }
|
|
29
|
+
&:not([is-locked]) { @on input (debounce: 200) { data-draft: event.target.value; } }
|
|
30
|
+
button[data-copy] { @on click { data-copied: ""; @delay 2000 { data-copied: none; } } }
|
|
31
|
+
img:not([alt]) { @warn "img needs alt"; }
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Pages
|
|
36
|
+
|
|
37
|
+
- [`@use`](./USE.md) — JS modules
|
|
38
|
+
- [`@on`](./ON.md) — events, blocks, options
|
|
39
|
+
- [`@dispatch` / `@command`](./DISPATCH.md) — outgoing events and commands
|
|
40
|
+
- [`@view-transition`](./VIEW_TRANSITION.md) — animated writes
|
|
41
|
+
- [`@delay`](./DELAY.md) — deferred writes
|
|
42
|
+
- [`@warn` / `@debug` / `@error`](./DIAGNOSTICS.md) — diagnostics
|
|
43
|
+
- `@scope` — see [Sheets & scoping](./SHEETS.md)
|
|
44
|
+
|
|
45
|
+
That is the whole set. Any other name — CSS's `@media`, `@supports`, `@keyframes`, SCSS's `@if`, `@mixin`, … — is a parse error (`@media is not a Quark at-rule`).
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Built-in functions
|
|
2
|
+
|
|
3
|
+
Available in every expression, after `@use` exports: element reads, loop context, rendering, listeners, utilities.
|
|
4
|
+
|
|
5
|
+
## Reference
|
|
6
|
+
|
|
7
|
+
*Generated.*
|
|
8
|
+
|
|
9
|
+
<!-- generated:builtin-functions -->
|
|
10
|
+
**Element reads**
|
|
11
|
+
|
|
12
|
+
| Name | Description |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| `attr("name")` | The matched element's attribute value (`null` when absent). `attr("content")` returns its `innerHTML`. A literal name is observed: the rule re-runs when that attribute changes, even if it is not in the selector. `attr($name)` reads but does not subscribe. |
|
|
15
|
+
| `prop("name")` | The matched element's JS property (`prop("provision")` reads a Neutron provision). A literal name is observed: the rule re-runs when JS assigns `element.name` (coalesced per microtask). In-place mutation and browser-driven native state are not observed. `prop($name)` reads but does not subscribe. |
|
|
16
|
+
| `closest("selector")` | `element.closest(selector)` from the matched element: the nearest ancestor-or-self matching the selector, else `null`. Not observed. |
|
|
17
|
+
| `element` | The matched element itself — the node the rule is applied to (inside an `@on … { }` block the listening element; `target` is the delegate). Hand it to `@use` functions that need the node: `@on click fire(element)`, `$chart: mount(element)`. Reads through it are not observed — use `attr()` / `prop()` for reactive reads. |
|
|
18
|
+
|
|
19
|
+
**Loop context**
|
|
20
|
+
|
|
21
|
+
| Name | Description |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| `item` | Inside an `iterate()` row: the current collection item (the value for objects). `undefined` outside a row. |
|
|
24
|
+
| `index` | Inside an `iterate()` row: the current position (the key for objects). `undefined` outside a row. |
|
|
25
|
+
|
|
26
|
+
**Rendering (`content`)**
|
|
27
|
+
|
|
28
|
+
| Name | Description |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| `iterate(collection, "template-ref"?, "key-property"?)` | For `content`: renders one clone of the element's `<template>` child (or the template at `template-ref`, a selector / URL) per array item or object entry, keyed by `key-property` (else a content hash) so existing rows are reused. `null` / `undefined` wipes the rows; an empty collection clears them; a non-collection no-ops. |
|
|
31
|
+
| `template("template-ref"?)` | For `content`: renders one clone of the referenced `<template>` (selector or URL; defaults to the element's own `<template>` child). |
|
|
32
|
+
| `dangerous-html(html)` | For `content`: sets `innerHTML` to the string. No sanitizing: never pass user-controlled markup. |
|
|
33
|
+
|
|
34
|
+
**Event listeners**
|
|
35
|
+
|
|
36
|
+
| Name | Description |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `event` | Inside an `@on … { }` block, its per-event options and its `@dispatch` / `@command` statements: the DOM event being handled (`event.target`, `event.detail`, …). `undefined` elsewhere. |
|
|
39
|
+
| `target` | Inside an `@on … { }` block and its per-event options: the element the `target:` option matched (the delegate), or `event.target` without that option. `undefined` elsewhere. |
|
|
40
|
+
| `prevent-default` | A listener that calls `event.preventDefault()`, for `handle:`. The `(prevent-default)` flag is the shorter form. |
|
|
41
|
+
| `stop-propagation` | A listener that calls `event.stopPropagation()`, for `handle:`. The `(stop-propagation)` flag is the shorter form. |
|
|
42
|
+
|
|
43
|
+
**Utilities**
|
|
44
|
+
|
|
45
|
+
| Name | Description |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| `ternary(condition, whenTrue, whenFalse?)` | `whenTrue` if `condition` is truthy, else `whenFalse` (`null` when omitted). Prefer `if()` for multi-arm conditionals. |
|
|
48
|
+
|
|
49
|
+
**Debugging**
|
|
50
|
+
|
|
51
|
+
| Name | Description |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `log(...values)` | Logs the values to the console and returns them as an array. |
|
|
54
|
+
| `debug(...values)` | Hits a `debugger` statement and returns the values as an array. |
|
|
55
|
+
<!-- /generated -->
|
|
56
|
+
|
|
57
|
+
Reading the matched element's JS properties and handing the node to module functions: [Element properties](./ELEMENT_PROPERTIES.md). Methods callable on values: [Allowed methods](./METHODS.md).
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Content
|
|
2
|
+
|
|
3
|
+
`content:` replaces an element's rendered children — text, a template clone, one row per item, or raw HTML.
|
|
4
|
+
|
|
5
|
+
## Text, templates, HTML
|
|
6
|
+
|
|
7
|
+
```quark
|
|
8
|
+
article {
|
|
9
|
+
content: template("#article-tmpl"); /* or template("/views/a.html") */
|
|
10
|
+
}
|
|
11
|
+
details:not([open]) p {
|
|
12
|
+
content: none;
|
|
13
|
+
}
|
|
14
|
+
header {
|
|
15
|
+
content: dangerous-html(getHeaderHtml());
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
A JS callout may also return a `Node` / `NodeList` — same paint path as `template()` / `iterate()`. A source `<template>` child is kept; writing into a `<template>` targets its `.content`; a promise is awaited.
|
|
20
|
+
|
|
21
|
+
A text result painted into a `<textarea>` is also mirrored to its live `.value` (the text is only the default value; see [Attributes](./ATTRIBUTES.md#md-form-controls)).
|
|
22
|
+
|
|
23
|
+
## Iterations
|
|
24
|
+
|
|
25
|
+
`iterate(array)` renders one copy of the element's `<template>` per item:
|
|
26
|
+
|
|
27
|
+
<include-content data-demo="iterate"></include-content>
|
|
28
|
+
|
|
29
|
+
`item` / `index` are available to matching rules for each row. Objects iterate as key → `index`, value → `item`. Pass a key property (`iterate($todos, none, "id")`) so existing rows are reused when the collection changes.
|
|
30
|
+
|
|
31
|
+
## Pitfall
|
|
32
|
+
|
|
33
|
+
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:
|
|
34
|
+
|
|
35
|
+
```quark
|
|
36
|
+
/* BAD — each new span matches again */
|
|
37
|
+
span {
|
|
38
|
+
content: template("#span-template");
|
|
39
|
+
}
|
|
40
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# CSS variables
|
|
2
|
+
|
|
3
|
+
`--custom-prop:` declarations write CSS custom properties on matched elements (inline style), so stylesheets consume Quark state via `var()` — computed colors, progress percentages, live theming. Values that attribute selectors can't express.
|
|
4
|
+
|
|
5
|
+
## Writing custom properties
|
|
6
|
+
|
|
7
|
+
<include-content data-demo="css-variables"></include-content>
|
|
8
|
+
|
|
9
|
+
- Values are Quark expressions — **CSS literals must be quoted**: `--accent: "#ccc"`, not `--accent: #ccc`. Same for keywords, lengths, and functions: `"red"`, `"10px"`, `"var(--x)"`, `"red !important"` (a trailing `!important` inside the string maps to the priority argument).
|
|
10
|
+
- Numeric expression results work: `--progress: "#{($done / $total * 100)}%"`.
|
|
11
|
+
- Wipe values (`none` / `undefined` / `null`) remove the property; `preserve` no-ops.
|
|
12
|
+
- Write-only: Quark never reads CSS variables back. Own the value in a `$variable` if rules need to react to it.
|
|
13
|
+
|
|
14
|
+
## No reversion
|
|
15
|
+
|
|
16
|
+
Like every other write, a CSS variable persists after its rule stops matching — write the inverse rule (see [No reversion](./NO_REVERSION.md)).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Declaration kinds
|
|
2
|
+
|
|
3
|
+
The property key selects what a declaration does: a `$variable`, a CSS variable, `content`, one of the attribute helpers, or an attribute of that name.
|
|
4
|
+
|
|
5
|
+
## By key shape
|
|
6
|
+
|
|
7
|
+
Checked top to bottom; the first match wins. Listeners are at-rules, not declarations. *Generated.*
|
|
8
|
+
|
|
9
|
+
<!-- generated:declaration-kinds -->
|
|
10
|
+
| Key | Effect | Accepts |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| `$name` | Stores a binding on each matched element; consumers resolve it by walking up from their own element (CSS custom-property semantics, shared across sheets). | Any value. `unset` deletes the binding. |
|
|
13
|
+
| `--name` | Writes the CSS custom property `--name` on the element's inline style. Write-only: Quark never reads CSS variables back. A trailing `!important` inside the string sets the priority. | A string or number. CSS literals must be quoted (`"#ccc"`, `"10px"`); bare `#hex` / unit numbers are rejected at build. |
|
|
14
|
+
| `content` | Replaces the element's rendered children (a source `<template>` child is kept). Promises are awaited. Writing into a `<template>` targets its `.content`. On a `<textarea>` a text result is also mirrored to the live `.value` (the text is only the default value). | A string (text), a `Node` / `NodeList`, or the result of `template()` / `iterate()` / `dangerous-html()`. Wipe values clear. |
|
|
15
|
+
| `class` | Sets the `class` attribute. | A string (replaces), an array (joined with spaces), or an object (`{ name: boolean }` toggles each class). Wipe values remove the attribute. |
|
|
16
|
+
| `dataset` | Writes one `data-*` attribute per key (camelCase → dash-case) and removes `data-*` attributes this sheet set earlier. | An object. Strings / numbers write as-is, booleans as present / absent, string arrays space-joined, objects and other arrays as their length. |
|
|
17
|
+
| `ariaset` | Same as `dataset`, with the `aria-` prefix. | An object (same conversions as `dataset`). |
|
|
18
|
+
| `<anything else>` | Sets the attribute of that name on the matched element (`none` removes it). Always contains a dash in practice; `autofocus: ""` sets a boolean attribute. On native form controls the attribute is authoritative: `value` / `checked` on `<input>` and `selected` on `<option>` also set the live property, so a control the user has edited still follows the rule. | A string or number (written as text), a boolean (`true` → `""`, `false` → removed), or a wipe value. |
|
|
19
|
+
<!-- /generated -->
|
|
20
|
+
|
|
21
|
+
## Details per kind
|
|
22
|
+
|
|
23
|
+
- [Attributes](./ATTRIBUTES.md) — the default kind, `class` / `dataset` / `ariaset`, form controls
|
|
24
|
+
- [Content](./CONTENT.md) — `content` with `template()`, `iterate()`, `dangerous-html()`
|
|
25
|
+
- [CSS variables](./CSS_VARIABLES.md) — `--name`
|
|
26
|
+
- [Variables](./VARIABLES.md) — `$name`
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# @delay
|
|
2
|
+
|
|
3
|
+
`@delay <ms> { … }` applies a block once, after a pause — a "Copied!" flash, a toast that hides itself, an undo window — with no timer in JS.
|
|
4
|
+
|
|
5
|
+
## Writing a delay
|
|
6
|
+
|
|
7
|
+
```quark
|
|
8
|
+
button[data-copy] {
|
|
9
|
+
@on click {
|
|
10
|
+
data-copied: "";
|
|
11
|
+
@delay 2000 { data-copied: none; } /* rapid clicks restart it */
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
output[is-visible] { @delay 4000 { is-visible: none; } }
|
|
15
|
+
todo-item[is-pending-delete] {
|
|
16
|
+
@on click (target: "[data-undo]") { is-pending-delete: none; }
|
|
17
|
+
@delay 5000 { is-deleted: ""; } /* dropped if undo clears the gate first */
|
|
18
|
+
}
|
|
19
|
+
[data-flash] { @delay +attr("data-flash-ms") or 1500 { data-flash: none; } }
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The duration is any expression — a literal, a binding, an `attr()` read, arithmetic, a `quark:math` 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 `@delay` or a `@view-transition`. `@on` inside the block is not supported.
|
|
23
|
+
|
|
24
|
+
## When it fires
|
|
25
|
+
|
|
26
|
+
- The timer starts when the rule applies to the element, or when the event fires inside an `@on` block, and **restarts on every application** — 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.
|
|
27
|
+
- When the timer fires, the block applies **only if the element is still in the document and the rule still matches**; otherwise it is dropped. That is what makes the undo idiom above safe: clearing the gate cancels the pending write.
|
|
28
|
+
- Inside an `@on` block, `event` and `target` keep the values the block was scheduled with.
|
|
29
|
+
- Pending timers are cleared when the sheet unregisters.
|
|
30
|
+
|
|
31
|
+
## Safety
|
|
32
|
+
|
|
33
|
+
The block keeps the [loop guard](./LOOP_GUARD.md)'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.
|
|
34
|
+
|
|
35
|
+
Time is otherwise an Adapter's protocol: `@delay` is for a wait that belongs to one State change, not for clocks, polling or animation — those are elements (`<timer-…>`) and CSS.
|
|
36
|
+
|
|
37
|
+
DevTools sees every timer as a `["quark", "delay"]` publication: `scheduled` (with `ms`), `fired`, or `dropped` (with `reason`: `"disconnected"`, `"unregistered"`, `"unmatched"`) — see [JS API](./JS_API.md).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# @warn / @debug / @error
|
|
2
|
+
|
|
3
|
+
Diagnostics report from inside a rule; the selector is the condition. Nothing is written to the document.
|
|
4
|
+
|
|
5
|
+
## Writing diagnostics
|
|
6
|
+
|
|
7
|
+
```quark
|
|
8
|
+
img:not([alt]) { @warn "img needs alt"; }
|
|
9
|
+
form button:not([type]) { @warn "button defaults to type=submit"; }
|
|
10
|
+
provider-fetch[is-error] { @error "fetch failed", prop("provision").error; }
|
|
11
|
+
todo-list {
|
|
12
|
+
$todos: prop("provision").body;
|
|
13
|
+
@debug "todos", $todos.length; /* again when $todos changes */
|
|
14
|
+
@on todo-remove { @debug "remove", event.detail.id; }
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Each statement evaluates its expression on the matched element — bindings, `attr()`, `prop()`, `event` inside an `@on` 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.
|
|
19
|
+
|
|
20
|
+
## When it speaks
|
|
21
|
+
|
|
22
|
+
- `@warn` and `@error` speak **once per element and rule**. A warning repeated on every re-run is noise; a new offending element is its own warning.
|
|
23
|
+
- `@debug` speaks **on every application**, so it re-logs when a binding or `prop()` it reads changes — a trace of the value over time.
|
|
24
|
+
- A failing expression reports a `quark/error` (like any declaration) instead of a diagnostic.
|
|
25
|
+
- Outside a rule there is no element to evaluate against: a top-level statement is rejected when the sheet is built.
|
|
26
|
+
|
|
27
|
+
## Where it goes
|
|
28
|
+
|
|
29
|
+
- The console, at the matching logger level: `Quark @warn (img:not([alt])): img needs alt`. `@debug` is silent unless Quark logs at debug level (`VITE_LOG_LEVEL` ≥ 3).
|
|
30
|
+
- The DevTools hook, as `["quark", "diagnostic"]` — `level`, `values`, `message`, `expression`, the selector, rule and element — so the Nucleus DevTools extension and the agent tools can list them next to the rule. See [JS API](./JS_API.md).
|
|
31
|
+
|
|
32
|
+
Use `@warn` for invariants a sheet can state better than a test (`img:not([alt])`, `button:not([type])`), `@error` for States that should never occur, and `@debug` while developing a rule — then remove it. The `log()` and `debug()` built-ins remain for tracing inside an expression.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# @dispatch / @command
|
|
2
|
+
|
|
3
|
+
`@dispatch` sends a custom event and `@command` invokes a command, from inside an `@on` block. They are the outgoing half of `@on`: the sheet heard an event, wrote State, and now tells another element.
|
|
4
|
+
|
|
5
|
+
```quark
|
|
6
|
+
todo-item {
|
|
7
|
+
@on click (target: "[data-remove]") {
|
|
8
|
+
@dispatch todo-remove (detail: (id: attr("data-id")));
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
provider-fetch {
|
|
12
|
+
@on super-form-success { @dispatch provider-fetch-trigger; }
|
|
13
|
+
}
|
|
14
|
+
button[data-help] {
|
|
15
|
+
@on click { @command toggle-popover (target: element.nextElementSibling); }
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
<include-content data-demo="dispatch"></include-content>
|
|
20
|
+
|
|
21
|
+
## Where they may appear
|
|
22
|
+
|
|
23
|
+
Inside an `@on … { }` block, its nested rules (then the event goes out from each matching descendant) and a `@delay` 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.
|
|
24
|
+
|
|
25
|
+
## When they run
|
|
26
|
+
|
|
27
|
+
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 `@on` event type is refused. Every dispatch is one [loop-guard](./LOOP_GUARD.md) hop, so an event cycle (`@on a { @dispatch b } … @on b { @dispatch a }`) is cut like a write cycle.
|
|
28
|
+
|
|
29
|
+
## `@dispatch`
|
|
30
|
+
|
|
31
|
+
`@dispatch <event>[, <event>] [(options)];` — a `CustomEvent` per name. Options are evaluated per event, in the block's scope (`event`, `target`, `element`, `$bindings`):
|
|
32
|
+
|
|
33
|
+
| Option | Effect |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| `detail: <expression>` | The event's `detail` — a map `(id: $id, at: event.timeStamp)`, a `$binding`, anything. |
|
|
36
|
+
| `target: "<selector>"` | Dispatch on every element matching the selector in the element's document (or shadow root). **`:scope` is the block's element — the one the `@on` matched — not the sheet's `:scope` host.** Resolved exactly as `<event-handler target-ref>` is (`selectAll` from kit-utils): `provider-fetch:has(+ :scope)` is the `provider-fetch` right before the element, `:scope + dialog` the dialog right after it. Default: the block's element. |
|
|
37
|
+
| `target: <element>` / `<list>` | An element or list of elements from an expression: `closest("provider-fetch")`, `element.nextElementSibling`, `closest("section").children`. |
|
|
38
|
+
| `host: window` / `host: document` | Dispatch on the window / document instead. |
|
|
39
|
+
| `form: "<selector>"` / `form: <form>` | The form's field values (as `formToJson` reads them) become the detail; an explicit `detail` map merges over them. |
|
|
40
|
+
| `bubbles` / `cancelable` / `composed` | Event flags. Bare means `true`; `bubbles: false` switches one off. Defaults: bubbles and cancelable on, composed off. |
|
|
41
|
+
|
|
42
|
+
An unmatched `target` warns once per element and sends nothing. Events bubble by default, so an ancestor's sheet hears a dispatch from a descendant without any `target`.
|
|
43
|
+
|
|
44
|
+
## `@command`
|
|
45
|
+
|
|
46
|
+
`@command <name>[, <name>] [(target: …)];` — invokes each command on the target elements the way a `<button command="…" commandfor="…">` would. Native commands — `show-modal`, `close`, `request-close`, `show-popover`, `hide-popover`, `toggle-popover` — and custom ones, which start with `--` and reach the target as a `command` event (`event.command`; `event.source` 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 `source`; without the API, the block's element).
|
|
47
|
+
|
|
48
|
+
```quark
|
|
49
|
+
[data-open-help] { @on click { @command show-modal (target: "#help"); } }
|
|
50
|
+
#help { @on keydown (key: "Escape") { @command close; } }
|
|
51
|
+
[data-refresh] { @on click { @command --refresh (target: "#feed"); } }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Where the browser has the Invoker Commands API, a hidden invoker button carries the command so native behaviour and `event.source` are exactly the platform's. Elsewhere, custom commands are dispatched as a synthetic `command` event and native ones call the element's method (`showModal()`, `togglePopover()`, …); an unknown native command warns once. `target` is the only option and resolves like `@dispatch`'s: the whole document, `:scope` = the block's element (not the sheet host).
|
|
55
|
+
|
|
56
|
+
```quark
|
|
57
|
+
:scope {
|
|
58
|
+
button {
|
|
59
|
+
@on click {
|
|
60
|
+
/* the provider-fetch right before this button */
|
|
61
|
+
@command --fetch (target: "provider-fetch:has(+ :scope)");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
A nested rule starting with a sibling combinator is the other spelling: `+ provider-fetch { @command --fetch; }` inside the block runs against the `provider-fetch` right after the button, with no `target` to resolve.
|
|
68
|
+
|
|
69
|
+
```quark
|
|
70
|
+
:scope {
|
|
71
|
+
button {
|
|
72
|
+
@on click {
|
|
73
|
+
+ provider-fetch { @command --fetch; }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Replacing `<event-handler>`
|
|
80
|
+
|
|
81
|
+
`@on` with `@dispatch` / `@command` covers what `<event-handler>` wires: `listen-for` is the event list, `selector-filter` / `keycode-filter` / `is-debounced` / `host-ref` are options, `fire-event` + `detail-*` + `form-ref` are `@dispatch (detail: …, form: …)`, `target-ref` is `target:`, `command-name` is `@command`. Prefer the sheet when the page has one; keep the element for markup without a sheet.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Element properties
|
|
2
|
+
|
|
3
|
+
`prop("<name>")` reads a JS property of the matched element and re-runs when JS assigns it — the way a sheet reads an Adapter's `provision`.
|
|
4
|
+
|
|
5
|
+
## `prop()`
|
|
6
|
+
|
|
7
|
+
`prop("<name>")` reads a JS property of the **matched element**; it mirrors `attr("<name>")`. `prop("provision")` reads a Neutron element's provision:
|
|
8
|
+
|
|
9
|
+
```quark
|
|
10
|
+
provider-fetch[is-success] {
|
|
11
|
+
$todo: prop("provision").body;
|
|
12
|
+
[bind-title] { content: $todo.title; }
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
A literal `prop("x")` subscribes to `x`: the rule re-runs when JS assigns `element.x` (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 `<input>`'s `value`, a `<details>` toggling `open`) are not observed either: select on the reflected attribute / listen for the event. `prop($name)` reads but does not subscribe.
|
|
17
|
+
|
|
18
|
+
## Reading an ancestor provider
|
|
19
|
+
|
|
20
|
+
`prop()` 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 `is-global` sheet — and read the `$binding` from descendants:
|
|
21
|
+
|
|
22
|
+
```quark
|
|
23
|
+
provider-fetch[is-success] { $todos: prop("provision").body; }
|
|
24
|
+
[bind-count] { content: $todos.length; }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## `element`
|
|
28
|
+
|
|
29
|
+
`element` 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 `attr()` / `prop()` for those:
|
|
30
|
+
|
|
31
|
+
```quark
|
|
32
|
+
[data-chart] { $chart: mountChart(element, $series); }
|
|
33
|
+
button[data-sku] { @on click (handle: addToCart(element, event)); } /* evaluated per click */
|
|
34
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Expressions
|
|
2
|
+
|
|
3
|
+
A value is an expression: names resolve through keywords, modules, built-ins and `$bindings`; operators follow JS; accessors never throw.
|
|
4
|
+
|
|
5
|
+
## Evaluation
|
|
6
|
+
|
|
7
|
+
- **Names** resolve in order: value keywords → `@use` exports (bare `as *` exports, then namespaces) → built-in functions → `$bindings`. A `$binding` is read by walking up from the matched element to the nearest ancestor-or-self that holds it; an unbound `$name` is `undefined` (a wipe), while an unknown bare identifier is an error (a no-op). A module export shadows a built-in of the same name.
|
|
8
|
+
- **Literals**: unitless numbers are numbers; a number with a unit (`10px`, `50%`) and a color (`#ccc`) evaluate to strings. Strings unescape `\n`, `\t`, `\r`, and `\x` → `x`. Interpolated strings and `url(…)` join their parts, with `null` / `undefined` parts rendered empty.
|
|
9
|
+
- **`&`** in an expression is the matched element's tag name.
|
|
10
|
+
- **Accessors** never throw: `.field` and `[index]` on `null` / `undefined` yield `undefined`. `object.$name` reads the property literally named `$name` (namespaced variables: `math.$pi`).
|
|
11
|
+
- **Calls**: a bare callee resolves through the name order above and must be a function. A method call on a value (`item.name.trim()`) is allowed for own-property functions (a `@use` namespace, a provided object) and for the [allowed prototype methods](./METHODS.md); anything else is an error. Calling a method on `null` / `undefined` yields `undefined`. Named arguments are passed positionally (the name is ignored); `$list...` spreads an array.
|
|
12
|
+
- **Operators**: `and` / `or` short-circuit and return an operand (JS semantics, so `$title or preserve` works); `not` returns a boolean; `==` / `!=` are loose; `+` concatenates when either side is a string (prefer `#{$x}` interpolation for building strings); `-` `*` `/` `%` and comparisons follow JS.
|
|
13
|
+
- **`if()`** returns the value of the first arm whose condition is truthy; with no match and no `else` it is `undefined` (a wipe). `ternary()` is the two-arm function form.
|
|
14
|
+
- **Lists** (space or comma separated) evaluate to arrays; **maps** to objects, with bare identifier keys taken literally (`(name: "Ada")` → `{ name: "Ada" }`) and other keys stringified.
|
|
15
|
+
- **Results**: listeners and `$variables` apply synchronously; attributes, CSS variables, and content are painted in a batch. A `content` result that is a promise is awaited.
|
|
16
|
+
|
|
17
|
+
## Example
|
|
18
|
+
|
|
19
|
+
```quark
|
|
20
|
+
[data-user] {
|
|
21
|
+
$user: prop("provision");
|
|
22
|
+
label: $user.profile.name or "Anonymous";
|
|
23
|
+
title: if($user.role == "admin": "Administrator"; else: $user.role);
|
|
24
|
+
data-tags: $user.tags.join(", ");
|
|
25
|
+
--progress: "#{($user.done / $user.total * 100).toFixed(0)}%";
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Facts over `if()`
|
|
30
|
+
|
|
31
|
+
Several rules branching on the same condition with `if()` is a smell: derive the condition once as an attribute (`data-is-admin: $user.role == "admin";` — a boolean writes `""` or removes the attribute) and select on it from Quark and CSS.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Limitations:
|
|
2
|
+
- Interaction / validity pseudo-classes (`:hover`, `:focus`, `:checked`, `:invalid`, …) and pseudo-elements are not observed — the rule matches on its first run only (warned at build; see README *Selectors*)
|
|
3
|
+
- `:has(+ …)` / `:has(~ …)` and a `:has()` nested in a complex `:is()` / `:not()` argument re-run the whole rule from the host
|
|
4
|
+
- `:empty` ignores text-only changes; `:nth-child(… of S)` observes only the attributes in `S`; `:open` covers `<details>` / `<dialog>` only
|
|
5
|
+
- At-rules other than `@use` / `@scope` / `@on` / `@view-transition` / `@delay` / `@warn` (`@debug`, `@error`) are not supported yet
|
|
6
|
+
- `@view-transition` relies on `document.startViewTransition()`: one transition per document, no scoped transitions yet; `until` freezes the page while it waits (short waits only)
|
|
7
|
+
- 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)
|
|
8
|
+
- Runaway cycles (attribute ↔ attribute, `$binding` ↔ `$binding` across sheets, content re-matching its own paint, rule ↔ element effect, attribute ↔ event) are cut by the shared loop guard after `LoopGuard.limit` (50) dependent writes, not prevented; writers Quark / Neutron do not route (plain `setAttribute` in app JS) are invisible to it
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# JS API
|
|
2
|
+
|
|
3
|
+
The `Quark` class hosts a sheet programmatically (tests, tooling), settles on demand and reports to the shared DevTools hook.
|
|
4
|
+
|
|
5
|
+
## `Quark`
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Quark } from "@excom/quark";
|
|
9
|
+
|
|
10
|
+
const quark = new Quark({
|
|
11
|
+
src: `span { content: "four times two equals #{twice(4)}"; }`,
|
|
12
|
+
modules: { dfault: { twice: (n) => n * 2 } },
|
|
13
|
+
});
|
|
14
|
+
quark.register({ sheetElement }); // host = sheetElement.parentElement
|
|
15
|
+
// …
|
|
16
|
+
quark.unregister();
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`isScoped` mirrors `<quark-sheet>`'s default (see [Sheets & scoping](./SHEETS.md)); `modules` pre-provides what `@use` would import; `Quark.moduleLoader` can be overridden in tests.
|
|
20
|
+
|
|
21
|
+
`Quark.whenSettled({ timeout? })` resolves once no rule pass, paint, async `content` or `@use` load is pending (`"settled"`), or after `timeout` ms (`"timeout"`, default 1000) — for tests and tools; sheets have no after-render hook.
|
|
22
|
+
|
|
23
|
+
Writing `$variables` from app code goes through `element.quark` — see [Writing from JS](./JS_WRITES.md).
|
|
24
|
+
|
|
25
|
+
## DevTools
|
|
26
|
+
|
|
27
|
+
Quark reports to the same global hook as Neutron. The Nucleus DevTools extension installs it at page load; for tests or late attachment call `Quark.attachDevtools()` (identical to `Neutron.attachDevtools()`).
|
|
28
|
+
|
|
29
|
+
Publications, one per property resolution on a matched element:
|
|
30
|
+
|
|
31
|
+
- `["quark", "sheet", "registered" | "unregistered"]` on the host — `sheetId`, `ruleCount`, `isScoped`
|
|
32
|
+
- `["quark", "apply"]` — `selector`, `key`, `expression`, `result`, `runId`, `isNoop` / `isWipe`
|
|
33
|
+
- `["quark", "error"]` — a failed expression (`errorMessage`), which never wipes; also a loop-guard trip (`errorName: "LoopGuardDepth"` / `"LoopGuardBatch"`, `key` = the dropped attribute / binding, see [Loop guard](./LOOP_GUARD.md))
|
|
34
|
+
- `["quark", "diagnostic"]` — a `@warn` / `@debug` / `@error` statement that spoke: `level`, `values`, `message`, `expression`, the selector and element (see [Diagnostics](./DIAGNOSTICS.md))
|
|
35
|
+
- `["quark", "delay"]` — a `@delay` block `scheduled` (with `ms`), `fired`, or `dropped` (with `reason`: `"disconnected"`, `"unregistered"`, `"unmatched"`; see [`@delay`](./DELAY.md))
|
|
36
|
+
- `["quark", "transition"]` — a commit holding `@view-transition` writes: `phase: "start"`, `"settled"` (with `result`: `"settled"` / `"until"` / `"timeout"`) or `"skip"` (with `reason`: `"unsupported"`, `"reduced-motion"`, `"hidden"`, `"unchanged"`, `"active"`, `"error"`), plus `types` and the number of `paints`
|
|
37
|
+
|
|
38
|
+
The extension can also paint a whole-document paint-count heatmap from the `apply` records (toolbar icon → "Paint heatmap"); nothing is written to the page's elements.
|
|
39
|
+
|
|
40
|
+
The extension's Element › Orchestration tab lists them, plus the selected element's current `$variables`, Quark-written attributes and CSS custom properties, listeners (with the `@on` handlers that attached them) and `iterate()` row context.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
Quark.attachDevtools({
|
|
44
|
+
version: 1,
|
|
45
|
+
publicize: (path, meta) => console.log(path.join("/"), meta),
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The renderer Quark injects into the hook (`hook.inject(renderer)`, `kind: "quark"`) also carries the on-demand queries behind the extension's agent tools: `inspect(el)` (the snapshot above), `sheets()` (every registered sheet with host, scope, source and rules), `matchingRules(el)` (rules whose selector matches `el` now, scope-aware, in definition order) and `evaluate(el, expression, sheetId?)` (an expression evaluated as a rule on `el` would see it, `@use` modules from `sheetId` or the first matching sheet). All three are read-only queries over already-built state.
|
|
50
|
+
|
|
51
|
+
## Language metadata
|
|
52
|
+
|
|
53
|
+
The documented language surface (keywords, declaration kinds, at-rules, built-ins, allowed methods, pseudo-class support) is data on `@excom/quark/language`, off the main entry so its prose stays out of app bundles. The reference tables on these pages are generated from it.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Writing from JS
|
|
2
|
+
|
|
3
|
+
Every element carries `element.quark`, shaped like `element.style`: the sanctioned way for app JS to write a `$variable` that rules read.
|
|
4
|
+
|
|
5
|
+
## `element.quark`
|
|
6
|
+
|
|
7
|
+
| Method | Effect |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `setProperty(name, value)` | Stores the binding on that element (it becomes the owner) and re-runs readers below it, in every sheet. Returns `true` when the value changed. |
|
|
10
|
+
| `setProperties({ … })` | Stores several bindings, then announces them, so a reader of two names runs once with both. |
|
|
11
|
+
| `removeProperty(name)` | `unset`: deletes the binding so readers fall through to an ancestor. |
|
|
12
|
+
| `getPropertyValue(name)` | What a rule on that element would read (nearest owner, self first). |
|
|
13
|
+
|
|
14
|
+
`name` may be written with or without the `$`. 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.
|
|
15
|
+
|
|
16
|
+
```quark
|
|
17
|
+
:scope { $count: 0; }
|
|
18
|
+
button { @on click (handle: incrementFromJs(closest("[data-demo-counter]"))); }
|
|
19
|
+
output { content: "Clicked #{$count} times"; }
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
// JS module
|
|
24
|
+
export const incrementFromJs = (owner) => () => {
|
|
25
|
+
const current = Number(owner.quark.getPropertyValue("$count") ?? 0);
|
|
26
|
+
owner.quark.setProperty("$count", current + 1);
|
|
27
|
+
};
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
<include-content data-demo="js-api"></include-content>
|
|
31
|
+
|
|
32
|
+
## Rules of thumb
|
|
33
|
+
|
|
34
|
+
- One writer per name per element: a rule that declares `$name` 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.
|
|
35
|
+
- Primitives that CSS or a selector should see belong in attributes (`setAttribute` + `attr()`, serializable and selectable); `element.quark` earns its place for rich values and for `$name` reads across a subtree.
|
|
36
|
+
- DevTools shows a JS write as a binding change without a sheet.
|