@hyperfixi/engine 3.3.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +921 -0
  2. package/LICENSE +20 -0
  3. package/README.md +272 -0
  4. package/dist/additions.d.ts +30 -0
  5. package/dist/ast.d.ts +277 -0
  6. package/dist/commands/animation.d.ts +34 -0
  7. package/dist/commands/control.d.ts +36 -0
  8. package/dist/commands/dom-more.d.ts +68 -0
  9. package/dist/commands/dom.d.ts +50 -0
  10. package/dist/commands/events.d.ts +25 -0
  11. package/dist/commands/js.d.ts +22 -0
  12. package/dist/commands/loops.d.ts +34 -0
  13. package/dist/commands/misc.d.ts +36 -0
  14. package/dist/commands/morph.d.ts +18 -0
  15. package/dist/commands/pick.d.ts +22 -0
  16. package/dist/commands/platform.d.ts +44 -0
  17. package/dist/commands/setters.d.ts +38 -0
  18. package/dist/common.js +3020 -0
  19. package/dist/common.min.js +17 -0
  20. package/dist/conversions.d.ts +6 -0
  21. package/dist/cookies.d.ts +10 -0
  22. package/dist/core.js +2190 -0
  23. package/dist/core.min.js +17 -0
  24. package/dist/engine.d.ts +79 -0
  25. package/dist/everything.d.ts +2 -0
  26. package/dist/expressions-extra.d.ts +2 -0
  27. package/dist/expressions.d.ts +51 -0
  28. package/dist/features.d.ts +47 -0
  29. package/dist/full.js +5673 -0
  30. package/dist/full.min.js +19 -0
  31. package/dist/hyperfixi-hs.dev.js +5676 -0
  32. package/dist/hyperfixi-hs.js +19 -0
  33. package/dist/index.d.ts +39 -0
  34. package/dist/index.js +5737 -0
  35. package/dist/live-templates.d.ts +3 -0
  36. package/dist/minimal.js +2580 -0
  37. package/dist/minimal.min.js +17 -0
  38. package/dist/on.d.ts +39 -0
  39. package/dist/parser.d.ts +97 -0
  40. package/dist/reactivity.d.ts +30 -0
  41. package/dist/runtime.d.ts +160 -0
  42. package/dist/statements.d.ts +24 -0
  43. package/dist/templates.d.ts +61 -0
  44. package/dist/tokenizer.d.ts +25 -0
  45. package/dist/util.d.ts +34 -0
  46. package/package.json +60 -0
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 LokaScript Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,272 @@
1
+ # @hyperfixi/engine
2
+
3
+ A hyperscript engine written against upstream `_hyperscript`'s source as the specification and
4
+ upstream's own test suite as the acceptance oracle. It is typed (`tsc --strict`, no `any`),
5
+ synchronous until a script really waits on something, and built from modules, so a bundle
6
+ contains the commands it registers and nothing else.
7
+
8
+ It is meant to replace the engine in `packages/core`, and is published as its own package
9
+ (since 2026-10-02). Nothing else in the repository depends on it at run time yet; the migration
10
+ plan is `~/.claude/plans/engine-replaces-core.md`.
11
+
12
+ ## Install
13
+
14
+ The script-tag bundle, hyperscript and nothing else (34.1 KB gzipped). It installs as
15
+ `window._hyperscript` and `window.hyperfixi`, and reads the document when it is ready:
16
+
17
+ ```html
18
+ <script src="https://unpkg.com/@hyperfixi/engine/dist/hyperfixi-hs.js"></script>
19
+ <button _="on click toggle .active on me">Toggle</button>
20
+ ```
21
+
22
+ Or, with a bundler, build an engine from the modules a page needs:
23
+
24
+ ```sh
25
+ npm install @hyperfixi/engine
26
+ ```
27
+
28
+ ## Use
29
+
30
+ ```ts
31
+ import { register, boot, on, add, remove, toggle } from '@hyperfixi/engine';
32
+
33
+ register(on, add, remove, toggle); // this engine knows `on`, `add`, `remove`, `toggle`
34
+ boot(); // install as window._hyperscript and initialise the document
35
+ ```
36
+
37
+ A module is a function `(g: Grammar) => void` that adds its rules to the grammar: a command, a
38
+ feature, or an optional expression kind. A rule that is not registered does not exist, and a
39
+ script that uses it fails with a parse error that names the token. `src/bundles/full.ts` is the
40
+ list of every module.
41
+
42
+ The public object is shaped like upstream's `_hyperscript` (`evaluate`, `parse`, `process`,
43
+ `config`, `use(plugin)`, `addBeforeProcessHook`), so a plugin written for upstream's public API
44
+ can be used on it; the multilingual adapter is. It adds one hook upstream lacks:
45
+
46
+ ```ts
47
+ _hyperscript.addSourceTransform((source, element) => english);
48
+ ```
49
+
50
+ A script is rewritten as it is read. The element keeps the text its author wrote, and a parse
51
+ error in the rewritten script carries that text (`error.written`). `@lokascript/hyperscript-adapter`
52
+ uses the hook when the host has it.
53
+
54
+ ## Layout
55
+
56
+ | File | What it holds |
57
+ | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
58
+ | `src/index.ts` | The library entry: the engine functions, the node types, every module |
59
+ | `src/tokenizer.ts`, `src/parser.ts` | Lexer, token stream, grammar registry, parse errors |
60
+ | `src/expressions.ts` | The expression chain and the core expression kinds |
61
+ | `src/expressions-extra.ts`, `src/conversions.ts` | Optional modules: positional, `closest`, collection operators, type checks; the less common `as` conversions |
62
+ | `src/statements.ts` | Commands, command lists, programs |
63
+ | `src/runtime.ts`, `src/engine.ts` | Contexts, scopes, collections, block execution; DOM init, cleanup, public API |
64
+ | `src/on.ts`, `src/features.ts` | Features: `on`; `def`, `init`, `behavior`, `install`, top-level `set` |
65
+ | `src/reactivity.ts`, `src/templates.ts`, `src/live-templates.ts` | Optional: `when` / `live` / `bind`; `render` and template text; live templates |
66
+ | `src/commands/*` | 52 command keywords, one module export each |
67
+ | `src/additions.ts` | The two forms upstream does not have: `new X()` and `toggle <element>` |
68
+ | `src/bundles/*` | Browser bundle entries. `hyperfixi-hs` is the script-tag bundle; `full` is the same modules, for the gate; `core`, `minimal`, `common` exist to measure size |
69
+ | `upstream-suite/` | The acceptance gate: upstream's Playwright suite (vendored) against a bundle |
70
+ | `tests/` | This package's own tests (`src/additions.ts`, and regressions upstream's tests did not show), in the form of upstream's and on the same fixtures |
71
+ | `tools/probe.mts` | One source on this engine and on upstream, side by side, on the value matrix's fixture |
72
+
73
+ A node is typed data plus the closure that runs it, bound when the node is parsed: an
74
+ expression has `ev`, a command has `run`, a feature has `install`. Control flow is a value a
75
+ command returns (`return`, `break`, `continue`), not an exception.
76
+
77
+ ## Commands
78
+
79
+ ```bash
80
+ npm run build --prefix packages/engine # dist/: library, bundles, declarations; prints sizes
81
+ npm run typecheck --prefix packages/engine
82
+ npm run test:upstream --prefix packages/engine # the gate (needs a build; about 30 s)
83
+ npm run test:own --prefix packages/engine # additions and regressions (needs a build; 2 s)
84
+ npm run cost --prefix packages/engine # what each module costs in the full bundle
85
+
86
+ # Any engine bundle can be scored on the same suite:
87
+ node packages/engine/upstream-suite/run.mjs --bundle <bundle.js> --fails
88
+
89
+ # One source on this engine and on upstream, side by side:
90
+ npx tsx packages/engine/tools/probe.mts '<source>'
91
+
92
+ # What this engine reads of the sources this repository ships, beside core and upstream:
93
+ npx tsx packages/engine/tools/shipped-sources.mts
94
+ ```
95
+
96
+ ## The gates
97
+
98
+ **Upstream's suite.** `test:upstream` runs upstream's own tests (`upstream-suite/vendor/`,
99
+ release 0.9.93, the one this repository's other gates use) against `dist/full.js`. The tests
100
+ that fail must be exactly the ones listed in `upstream-suite/known-failures.json`. A new failure
101
+ fails the gate. So does a listed test that now passes: prune it with
102
+ `npm run test:upstream:update` in the same change, so the list stays what does not pass. CI runs
103
+ it in the `browser-tests` job.
104
+
105
+ **The package's own tests.** `test:own` runs `tests/` against the same bundle, for the two forms
106
+ upstream has no tests for. Every one must pass. On upstream's own bundle 2 of the 21 pass (the
107
+ two that check an upstream form still reads as it did), which is what shows the rest test the
108
+ additions. `tests/regressions.js` holds the other kind: places where this engine differed from
109
+ upstream and upstream's tests did not show it (each of those passes on upstream). CI runs both
110
+ after the upstream suite.
111
+
112
+ **The multilingual gates**, in the packages that own them:
113
+
114
+ - The value matrix (`packages/testing-framework`, `value-matrix.ts`) has an `eng` lane, the
115
+ English source on this engine, and a `<lang>/eng` lane per language: the adapter's English,
116
+ the same string the `/up` lane runs on upstream, on this engine.
117
+ - `engine-parser-parity.test.ts` (same package) puts every string the canonical-validity gates
118
+ ask upstream's parser about to this engine's parser; the two must agree.
119
+ - `packages/hyperscript-adapter/test/engine-host.test.ts` hosts the real plugin on this engine.
120
+
121
+ **The shipped-sources gates**, also in `packages/testing-framework`:
122
+
123
+ - `shipped-sources-engine.test.ts` lists the sources this repository ships (`examples/`, the doc
124
+ trees) that `packages/core` compiles and this engine rejects
125
+ (`baselines/shipped-sources-engine.json`). A new one fails; so does a listed one that now
126
+ parses. It is empty when replacing core's engine breaks no shipped page.
127
+ - `shipped-examples-execution.test.ts` has an engine lane: every example handler upstream accepts
128
+ is run on upstream and on this engine, in jsdom, and the DOM effects must match. Measured
129
+ 2026-10-01: 134 handlers, 89 matches with an effect, 44 with none on either, 1 difference
130
+ (upstream reads a stale `document` in that harness). `packages/core` differs on 31.
131
+
132
+ ## Measured (2026-10-01, upstream 0.9.93)
133
+
134
+ | | this engine (`full` bundle) | `hyperfixi.js` | upstream |
135
+ | ---------------------- | --------------------------- | ----------------------- | -------- |
136
+ | Whole suite (1,467) | 1,401 | 826 | 1,466 |
137
+ | Command tests (516) | 516 | 306 | 516 |
138
+ | Expression tests (459) | 457 | 371 | 459 |
139
+ | Feature tests (254) | 237 | 100 | 253 |
140
+ | Core tests (190) | 143 | 49 | 190 |
141
+ | Template tests (48) | 48 | 0 | 48 |
142
+ | Size, gzipped | 34.1 KB with every module | 92 KB engine-only build | 45.8 KB |
143
+
144
+ Bundle sizes, gzipped: `minimal` (`on` + add / remove / toggle) 16.3 KB, `common` (15 everyday
145
+ commands) 18.4 KB, `full` 34.1 KB, `core` (no commands) 13.8 KB.
146
+
147
+ ## The script-tag bundle: `hyperfixi-hs.js`
148
+
149
+ `dist/hyperfixi-hs.js` (minified, 34.1 KB gzipped; `hyperfixi-hs.dev.js` is the readable build) is the first product on this engine:
150
+ hyperscript and nothing else, every module, no htmx attributes, English only. It installs
151
+ `window._hyperscript`, as upstream does, and the same object as `window.hyperfixi`. The name
152
+ pairs with `hyperfixi-hx.js` (hyperscript plus htmx); `hyperfixi.js` is everything.
153
+
154
+ Thirty-six example pages load it instead of `packages/core/dist/hyperfixi.js` (2026-10-02):
155
+ the pages that need only hyperscript. (Twenty-one by a script tag; fifteen through the
156
+ examples' loader, `data-default="hs"`, so that `?bundle=` still switches them.) Core's
157
+ Playwright suites run them, and the bundle is a column of the bundle-compatibility matrix
158
+ (`?bundle=hs` in the examples' loader). No page needed an API shim: `processNode` is upstream's
159
+ name too, and the other `hyperfixi.*` calls were core debugging code. The multilingual demo
160
+ pages are among the thirty-six: their handlers are English, and what they translate for display
161
+ comes from the i18n or semantic bundle, loaded beside the engine. What keeps the other pages on
162
+ `hyperfixi.js`: the behaviors resolver (4), the intent element (4), core's own debug pages (2),
163
+ `<script type="text/hyperscript" for="…">` (1), partial validation (1), htmx attributes (1),
164
+ and the history commands. One behavior of core's is not in this engine, by decision
165
+ (2026-10-02): `increment #count` on core counts in the element's text, where upstream and this
166
+ engine want `increment #count's textContent`. The examples write the second, which every
167
+ bundle runs to the same count (the bundle matrix's Counter row).
168
+
169
+ The 66 known failures: upstream's internal-API surface (`internals.tokenizer`,
170
+ `evalStatically`, source info, error collection: 47), sockets and workers (17, upstream
171
+ extensions that are not built), and two collection-expression tests that need upstream's
172
+ component extension. Upstream's one failure is a test that expects its worker extension to be
173
+ absent.
174
+
175
+ As a host for the multilingual text path (semantic renders a translation, the adapter's
176
+ `preprocess` turns it back into English, the engine runs the English):
177
+
178
+ - **Value matrix.** 4,205 cells. English on this engine gives the oracle's value in every cell.
179
+ Through the adapter in 23 languages, 96,640 of 96,643 (cell, language) pairs do, and the two
180
+ hosts agree on every pair: the three that miss are the accepted Italian `di` ambiguity, and
181
+ they miss on upstream too. (`packages/core` misses 8 cells in English and 187 pairs on its
182
+ direct path.)
183
+ - **Parser parity.** Of the 273 distinct English strings the canonical-validity gates put to
184
+ upstream's parser, the two parsers disagree on none.
185
+ - Of the 168 corpus rows, 151 parse on both engines and 17 on neither; none parses on one
186
+ engine only. Fifteen of the 17 are markup or extension rows (components, `sse-*` / `ws-*` /
187
+ `hx-live`, sockets, workers, event sources, `intercept`) and one is valid nowhere. Two are
188
+ syntax only `packages/core` has: `as FormData` (fetch-formdata) and `swap … using view
189
+ transition` (swap-view-transition). Nine more were, until the rows were rewritten in
190
+ upstream's spelling on 2026-10-01.
191
+ - **The adapter's plugin** in six languages: the script runs and the attribute stays as
192
+ written. On upstream the plugin has to rewrite the attribute.
193
+
194
+ ## Against what this repository ships
195
+
196
+ `npx tsx packages/engine/tools/shipped-sources.mts` puts every hyperscript source in
197
+ `examples/` and the doc trees (the shipped-sources gate's collection: 386 sources, 336
198
+ distinct) to three parsers. Measured 2026-10-01:
199
+
200
+ - upstream accepts 272, this engine 309, `packages/core` 320;
201
+ - **12 sources that core accepts are rejected here**: the eleven handlers that use core's
202
+ history commands (`push url`, `replace url`), which wait for the htmx decision, and the
203
+ `hyperfixi-hx.js` example in `docs/BROWSER_BUNDLES.md`, which is written in the hybrid
204
+ parser's own dialect (`on click.debounce(300)`, `me has .loading`).
205
+
206
+ It was 78 when first measured. The examples and docs were moved to upstream's spellings where
207
+ upstream has one (`put … into` for core's `swap` strategies, `debounced at 300ms`,
208
+ `start view transition … end`, `morph … to`, `toggle *display of`, `descending`, …: each
209
+ rewrite was run on core, upstream and this engine, to the same DOM), and two forms were added
210
+ here (below).
211
+
212
+ A parse-level count understates a difference: a source can parse and run differently.
213
+ `set t to new Date().getDay()` parses on upstream (`new` is a variable there, the call a second
214
+ command) and fails when it runs; `swap afterBegin of #list with html` is upstream's exchange of
215
+ two values and inserts nothing. The comparison that counts is the DOM a handler leaves
216
+ (`shipped-examples-execution` in `packages/testing-framework`).
217
+
218
+ ## Differences from upstream
219
+
220
+ Three, all recorded here and nowhere silent.
221
+
222
+ **Two additions** (`src/additions.ts`, decided 2026-10-01), forms `packages/core` has and the
223
+ shipped examples use. Each is its own module, and neither changes how an upstream script reads:
224
+ upstream's suite passes the same tests with them registered as without.
225
+
226
+ - `new Date()`, `new Intl.NumberFormat('en')`, `(new Date()).getDay()`: a constructor call as
227
+ an expression. `new` is still an ordinary name unless a constructor call follows it. Upstream
228
+ has the `make a Date` command and no expression. 0.20 KB.
229
+ - `toggle #dialog`, `toggle #dialog modal` (or `as modal`), `toggle #details`: open what is
230
+ closed and close what is open, for a dialog, a details element (or its summary), a popover
231
+ and a select. Upstream rejects `toggle <expression>` unless `between` follows; that form is
232
+ unchanged. 0.21 KB.
233
+
234
+ **One looser reading**, found by the measurement above: `my @id as String` (an attribute read
235
+ through `my` / `its` / `your`, then a conversion or any further access). Upstream rejects it,
236
+ because its attribute-access rule does not continue the expression chain as every other access
237
+ does; the possessive form `#a's @title as String` works on both. This engine continues the
238
+ chain. It is kept because the stricter reading looks like an upstream slip and a shipped
239
+ example uses the form (`its @data-stock as Number > 0`).
240
+
241
+ Considered and not added: core's `swap` strategies (upstream's `swap` means something else),
242
+ `copy`, a `? :` conditional, `.debounce(n)`, `has`, prefix `unless`, `set @a to v on <target>`,
243
+ `sorted by … desc`. `push url` / `replace url` are undecided.
244
+
245
+ ## Where the bytes are
246
+
247
+ `npm run cost` rebuilds the full bundle without each registered module and prints the
248
+ difference. Of the 34.1 KB: the fixed core (tokenizer, parser, the core expression kinds,
249
+ runtime, engine) is 12.2 KB; `on` is 1.6; the optional modules add up to 15.1; code that
250
+ several modules share is 5.2. The largest modules: `expressionsExtra` 1.5, `reactivity` 1.3,
251
+ `render` 1.0, `repeat` 0.8, `toggle` 0.8, `fetch` 0.7, `pick` 0.7. A simple command costs 20 to
252
+ 190 bytes (`throw` 18, `get` 53, `log` 73, `send` 101, `append` 189).
253
+
254
+ Three ideas for the command layer were measured. None is worth doing for size:
255
+
256
+ | Idea | Minified | Gzipped |
257
+ | ----------------------------------------------------------------- | --------- | --------------------------------------------------------------------------- |
258
+ | Share the class / attribute handling of `add`, `remove`, `toggle` | −188 | −4 (`minimal`), +6 (all) |
259
+ | Drop the descriptive fields from node literals (65 of them) | −1,459 | −566 (all), −177 (`minimal`) |
260
+ | A table-driven grammar for the simple commands | not built | an estimate: under 200, since the eight simplest commands cost 700 in total |
261
+
262
+ Gzip already removes repeated text, so removing duplication between commands shrinks the
263
+ minified file and not the download. The first row is kept because it is less code. The second
264
+ saves 1.7 % and would cost the typed half of each node. The third would have to parse those
265
+ commands for nothing to break even.
266
+
267
+ The fixed core is three quarters of a small bundle. The comparison operators (`is`, `matches`,
268
+ `is greater than`, `starts with`, …) are 1.25 KB of it and the math operators 0.15 KB, measured
269
+ by stubbing them out. They stay in the core by decision (2026-10-01): a bundle without them
270
+ would reject `when it matches .x`, and simpler bundles were preferred to smaller ones.
271
+
272
+ Source: 8,100 lines, one documented type assertion (`num` in `src/util.ts`).
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Syntax upstream `_hyperscript` does not have. Everything else in this engine
3
+ * follows upstream's source; these two forms come from `packages/core`, are used
4
+ * by the examples this repository ships, and were kept by decision (2026-10-01).
5
+ * Each is an optional module, and neither changes how an upstream script reads:
6
+ *
7
+ * - `construct`: `new Date()`, `new Intl.NumberFormat('en')`. Upstream has the
8
+ * `make a Date` command and no expression; there, `set t to new Date().getDay()`
9
+ * parses (`new` is a variable, the call a second command) and fails when it runs.
10
+ * - `toggleElement`: `toggle #dialog`, `toggle #dialog modal`, `toggle #details`.
11
+ * Upstream rejects `toggle <expression>` without `between`.
12
+ */
13
+ import type { Cmd, Expr } from './ast';
14
+ import type { Grammar } from './parser';
15
+ export interface ConstructNode extends Expr {
16
+ type: 'newExpression';
17
+ /** `Intl.NumberFormat` is `['Intl', 'NumberFormat']`. */
18
+ path: string[];
19
+ args: Expr[];
20
+ }
21
+ /** `new <Constructor>(<args>)`: construct an object, as JavaScript's `new` does. */
22
+ export declare function construct(g: Grammar): void;
23
+ export interface ToggleElementNode extends Cmd {
24
+ type: 'toggleElementCommand';
25
+ target: Expr;
26
+ /** `toggle #d modal` / `toggle #d as modal`: open with `showModal()`. */
27
+ modal: boolean;
28
+ }
29
+ /** `toggle <element> [modal | as modal]`: the form `toggle` takes when no `between` follows. */
30
+ export declare function toggleElement(g: Grammar): void;
package/dist/ast.d.ts ADDED
@@ -0,0 +1,277 @@
1
+ /**
2
+ * The engine's node types.
3
+ *
4
+ * A node is plain typed data plus the closure that runs it. The closure is bound
5
+ * once, when the node is parsed, so execution never re-reads syntax: an
6
+ * expression has `ev`, a command has `run`, a feature has `install`. Every
7
+ * member of the unions below is discriminated by `type`.
8
+ */
9
+ import type { MaybeP } from './util';
10
+ export interface Meta {
11
+ /** The element whose script this is. */
12
+ owner: unknown;
13
+ feature?: Feature;
14
+ /** The event, while an `on …[filter]` expression is being evaluated. */
15
+ context?: unknown;
16
+ returned?: boolean;
17
+ returnValue?: unknown;
18
+ /** While a template renders: its output so far, and what each of its loops iterates over. */
19
+ template?: {
20
+ out: string[];
21
+ loops: Record<string, LoopScope>;
22
+ };
23
+ }
24
+ /** A loop in a template, kept so an element in its output can take the loop's variables. */
25
+ export interface LoopScope {
26
+ identifier?: string;
27
+ indexIdentifier?: string;
28
+ source: unknown;
29
+ }
30
+ export interface Ctx {
31
+ me: unknown;
32
+ you: unknown;
33
+ result: unknown;
34
+ /** The element under test inside a `when` / `where` clause; `it` reads it first. */
35
+ beingTested: unknown;
36
+ event: unknown;
37
+ target: unknown;
38
+ detail: unknown;
39
+ sender: unknown;
40
+ body: unknown;
41
+ locals: Record<string, unknown>;
42
+ meta: Meta;
43
+ }
44
+ export type Signal = {
45
+ k: 'return';
46
+ value: unknown;
47
+ } | {
48
+ k: 'break';
49
+ } | {
50
+ k: 'continue';
51
+ };
52
+ export type Completion = Signal | void;
53
+ export interface Node {
54
+ type: string;
55
+ /** Source offsets, `[start, end)`. */
56
+ start: number;
57
+ end: number;
58
+ }
59
+ /**
60
+ * What every expression has. The optional members are the small protocol other
61
+ * grammar rules read without knowing the concrete kind: a selector-like value
62
+ * exposes `css`, a chained access exposes its `root`, and an assignable
63
+ * expression has `lhs` (evaluate what the write needs) and `put` (do the write).
64
+ */
65
+ export interface Expr extends Node {
66
+ ev(ctx: Ctx): unknown;
67
+ lhs?(ctx: Ctx): unknown;
68
+ put?(ctx: Ctx, lhs: unknown, value: unknown): void;
69
+ del?(ctx: Ctx, lhs: unknown): void;
70
+ /** The value, for an expression that needs no context (a literal, `200ms`). */
71
+ stat?(): unknown;
72
+ css?: string;
73
+ root?: Expr;
74
+ /** Symbol, attribute or style name. */
75
+ name?: string;
76
+ /** Property name of a property access. */
77
+ prop?: string;
78
+ /** On a `where` clause inside `for x in …`: the loop variable each item is bound to. */
79
+ varName?: string;
80
+ }
81
+ export type Scope = 'local' | 'element' | 'global' | 'inherited';
82
+ export interface SymbolNode extends Expr {
83
+ type: 'symbol';
84
+ name: string;
85
+ scope: Scope;
86
+ }
87
+ export interface LiteralNode extends Expr {
88
+ type: 'boolean' | 'null' | 'number';
89
+ value: boolean | null | number;
90
+ }
91
+ export interface StringNode extends Expr {
92
+ type: 'string';
93
+ value: string;
94
+ /** Present on a backtick string: literal text interleaved with expressions. */
95
+ parts?: (string | Expr)[];
96
+ }
97
+ export interface ArrayNode extends Expr {
98
+ type: 'arrayLiteral';
99
+ values: Expr[];
100
+ }
101
+ export interface ObjectNode extends Expr {
102
+ type: 'objectLiteral';
103
+ keys: (string | Expr)[];
104
+ values: Expr[];
105
+ }
106
+ export interface NamedArgsNode extends Expr {
107
+ type: 'namedArgumentList';
108
+ names: string[];
109
+ values: Expr[];
110
+ }
111
+ export interface ParenNode extends Expr {
112
+ type: 'parenthesized';
113
+ expr: Expr;
114
+ }
115
+ export interface IdRefNode extends Expr {
116
+ type: 'idRef';
117
+ /** Absent on `#{expr}`. */
118
+ value?: string;
119
+ expr?: Expr;
120
+ }
121
+ export interface ClassRefNode extends Expr {
122
+ type: 'classRef';
123
+ /** Absent on `.{expr}`. */
124
+ className?: string;
125
+ expr?: Expr;
126
+ }
127
+ export interface QueryRefNode extends Expr {
128
+ type: 'queryRef';
129
+ css: string;
130
+ parts?: (string | Expr)[];
131
+ }
132
+ export interface AttributeRefNode extends Expr {
133
+ type: 'attributeRef';
134
+ name: string;
135
+ css: string;
136
+ value?: string;
137
+ }
138
+ export interface StyleRefNode extends Expr {
139
+ type: 'styleRef' | 'computedStyleRef';
140
+ name: string;
141
+ }
142
+ export interface StyleLiteralNode extends Expr {
143
+ type: 'styleLiteral';
144
+ parts: string[];
145
+ exprs: Expr[];
146
+ }
147
+ export interface ImplicitMeNode extends Expr {
148
+ type: 'implicitMeTarget';
149
+ }
150
+ export interface PathNode extends Expr {
151
+ type: 'dotOrColonPath' | 'eventName';
152
+ value: string;
153
+ }
154
+ export interface PropertyAccessNode extends Expr {
155
+ type: 'propertyAccess';
156
+ root: Expr;
157
+ prop: string;
158
+ }
159
+ export interface OfNode extends Expr {
160
+ type: 'ofExpression';
161
+ root: Expr;
162
+ name: string;
163
+ kind: 'property' | 'attribute' | 'style' | 'computed';
164
+ }
165
+ export interface PossessiveNode extends Expr {
166
+ type: 'possessive';
167
+ root: Expr;
168
+ prop?: string;
169
+ attribute?: AttributeRefNode | StyleRefNode;
170
+ }
171
+ export interface InNode extends Expr {
172
+ type: 'inExpression';
173
+ root: Expr;
174
+ target: Expr;
175
+ }
176
+ export interface AsNode extends Expr {
177
+ type: 'asExpression';
178
+ root: Expr;
179
+ conversion: string;
180
+ }
181
+ export interface CallNode extends Expr {
182
+ type: 'functionCall';
183
+ root: Expr;
184
+ args: Expr[];
185
+ }
186
+ export interface AttributeAccessNode extends Expr {
187
+ type: 'attributeRefAccess';
188
+ root: Expr;
189
+ attribute: AttributeRefNode;
190
+ }
191
+ export interface IndexNode extends Expr {
192
+ type: 'arrayIndex';
193
+ root: Expr;
194
+ first: Expr;
195
+ second?: Expr;
196
+ andBefore: boolean;
197
+ andAfter: boolean;
198
+ }
199
+ export interface UnaryNode extends Expr {
200
+ type: 'logicalNot' | 'negativeNumber' | 'noExpression' | 'some' | 'beepExpression';
201
+ root: Expr;
202
+ }
203
+ export interface PostfixNode extends Expr {
204
+ type: 'timeExpression' | 'stringPostfixExpression' | 'typeCheckExpression';
205
+ root: Expr;
206
+ /** Time factor in ms, CSS unit, or type name. */
207
+ suffix: number | string;
208
+ }
209
+ export interface BinaryNode extends Expr {
210
+ type: 'mathOperator' | 'logicalOperator';
211
+ operator: string;
212
+ left: Expr;
213
+ right: Expr;
214
+ }
215
+ export interface ComparisonNode extends Expr {
216
+ type: 'comparisonOperator';
217
+ operator: string;
218
+ left: Expr;
219
+ right?: Expr;
220
+ right2?: Expr;
221
+ typeName?: string;
222
+ ignoringCase: boolean;
223
+ }
224
+ export interface BlockLiteralNode extends Expr {
225
+ type: 'blockLiteral';
226
+ params: string[];
227
+ expr: Expr;
228
+ }
229
+ export interface PositionalNode extends Expr {
230
+ type: 'positionalExpression';
231
+ operator: string;
232
+ root: Expr;
233
+ }
234
+ export interface RelativeNode extends Expr {
235
+ type: 'relativePositionalExpression';
236
+ operator: string;
237
+ thing: Expr;
238
+ from: Expr;
239
+ inElt?: Expr;
240
+ withinElt?: Expr;
241
+ wrapping: boolean;
242
+ }
243
+ export interface ClosestNode extends Expr {
244
+ type: 'closestExpr';
245
+ css: string;
246
+ parentSearch: boolean;
247
+ to: Expr;
248
+ }
249
+ export interface CollectionNode extends Expr {
250
+ type: 'collectionExpression';
251
+ operator: 'where' | 'sorted' | 'mapped' | 'split' | 'joined';
252
+ root: Expr;
253
+ operand: Expr;
254
+ descending?: boolean;
255
+ }
256
+ export type AnyExpr = SymbolNode | LiteralNode | StringNode | ArrayNode | ObjectNode | NamedArgsNode | ParenNode | IdRefNode | ClassRefNode | QueryRefNode | AttributeRefNode | StyleRefNode | StyleLiteralNode | ImplicitMeNode | PathNode | PropertyAccessNode | OfNode | PossessiveNode | InNode | AsNode | CallNode | AttributeAccessNode | IndexNode | UnaryNode | PostfixNode | BinaryNode | ComparisonNode | BlockLiteralNode | PositionalNode | RelativeNode | ClosestNode | CollectionNode;
257
+ /** A command: its own typed fields (declared by the module that parses it) plus `run`. */
258
+ export interface Cmd extends Node {
259
+ run(ctx: Ctx): MaybeP<Completion>;
260
+ }
261
+ export interface Feature extends Node {
262
+ /** `on click or keyup`, for traces. */
263
+ displayName?: string;
264
+ /** Set on the features of a `behavior`: they share that behavior's variable scope. */
265
+ behavior?: string;
266
+ install(target: unknown, source: unknown): void;
267
+ }
268
+ /** The optional `catch` / `finally` blocks of a handler or a function. */
269
+ export interface Handlers {
270
+ errorSymbol?: string;
271
+ errorHandler?: Cmd[];
272
+ finallyHandler?: Cmd[];
273
+ }
274
+ export interface Program extends Node {
275
+ type: 'hyperscript';
276
+ features: Feature[];
277
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `transition`, `settle`, `start a view transition`. Follows upstream
3
+ * `parsetree/commands/animations.js`.
4
+ */
5
+ import type { Cmd, Expr } from '../ast';
6
+ import type { Grammar } from '../parser';
7
+ export interface SettleNode extends Cmd {
8
+ type: 'settleCommand';
9
+ target: Expr;
10
+ }
11
+ /** `settle [<target>]`: wait for a running CSS transition to finish. */
12
+ export declare function settle(g: Grammar): void;
13
+ export interface TransitionNode extends Cmd {
14
+ type: 'transitionCommand';
15
+ properties: Expr[];
16
+ from: (Expr | undefined)[];
17
+ /** A value, or `'initial'` for what the property was before the first transition. */
18
+ to: (Expr | 'initial')[];
19
+ over?: Expr;
20
+ using?: Expr;
21
+ }
22
+ /** `transition <property> [from <value>] to <value> … [over <time> | using <css transition>]`. */
23
+ export declare function transition(g: Grammar): void;
24
+ export interface ViewTransitionNode extends Cmd {
25
+ type: 'viewTransitionCommand';
26
+ body: Cmd[];
27
+ transitionType?: string;
28
+ }
29
+ /**
30
+ * `start [a] view transition [using "<type>"] <commands> end`. The body runs as
31
+ * the transition's update; leaving it early (`return`, `halt`, `break`) skips
32
+ * the transition.
33
+ */
34
+ export declare function viewTransition(g: Grammar): void;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * `if`, `halt`, `return`, `exit`, `throw`. Follows upstream
3
+ * `parsetree/commands/controlflow.js` and `basic.js`.
4
+ */
5
+ import type { Cmd, Expr } from '../ast';
6
+ import type { Grammar } from '../parser';
7
+ export interface IfNode extends Cmd {
8
+ type: 'ifCommand';
9
+ condition: Expr;
10
+ trueBranch: Cmd[];
11
+ falseBranch?: Cmd[];
12
+ }
13
+ /**
14
+ * `if <condition> [then] <commands> [else|otherwise <commands>] [end]`.
15
+ * The block runs to `end` (or `else`, or the end of the script) wherever the
16
+ * lines break; `else if` on one line chains without needing its own `end`.
17
+ */
18
+ export declare function ifCommand(g: Grammar): void;
19
+ export interface HaltNode extends Cmd {
20
+ type: 'haltCommand';
21
+ /** `halt the event`: stop the event but keep running the handler. */
22
+ keepExecuting: boolean;
23
+ bubbling: boolean;
24
+ haltDefault: boolean;
25
+ }
26
+ export declare function halt(g: Grammar): void;
27
+ export interface ReturnNode extends Cmd {
28
+ type: 'returnCommand' | 'exitCommand';
29
+ value?: Expr;
30
+ }
31
+ export declare function returnCommand(g: Grammar): void;
32
+ export interface ThrowNode extends Cmd {
33
+ type: 'throwCommand';
34
+ value: Expr;
35
+ }
36
+ export declare function throwCommand(g: Grammar): void;