@fusion.dev/helix-docs 0.223.1 → 0.224.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/content/building-on-the-headless-tier.mdx +6 -8
- package/content/events.mdx +151 -49
- package/content/graphs.mdx +7 -5
- package/content/introduction.mdx +2 -2
- package/content/maps.mdx +1 -1
- package/content/metrics.mdx +288 -288
- package/content/other-frameworks.mdx +1 -1
- package/content/routing.mdx +17 -10
- package/content/sandbox.mdx +1 -1
- package/content/using-helix-with-an-agent.mdx +1 -1
- package/content/using-the-elements.mdx +30 -5
- package/content/visual-testing.mdx +59 -2
- package/dist/pages.generated.js +1 -1
- package/dist/sources.generated.js +564 -373
- package/package.json +14 -14
|
@@ -24,7 +24,7 @@ element.items = tabs
|
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
**Bind events with `addEventListener`, using the event's own name.** Helix event
|
|
27
|
-
names, such as `helix-before-open`, `helix-before-change`, and `helix-select`,
|
|
27
|
+
names, such as `helix-before-open`, `helix-before-change`, and `helix-before-select`,
|
|
28
28
|
are lowercase and hyphenated. They have no camel-case variant. Your framework's
|
|
29
29
|
`on*` syntax needs to pass that exact name through.
|
|
30
30
|
|
package/content/routing.mdx
CHANGED
|
@@ -6,15 +6,15 @@ title: Routing and navigation
|
|
|
6
6
|
|
|
7
7
|
# Routing and navigation
|
|
8
8
|
|
|
9
|
-
Some components navigate, such as a tree used as a sidebar, a
|
|
10
|
-
another page, or a tab that is a page. Each of these should be a link. A
|
|
9
|
+
Some components navigate, such as a tree used as a sidebar, a menu item that
|
|
10
|
+
opens another page, or a tab that is a page. Each of these should be a link. A
|
|
11
11
|
framework router does not handle a link inside a shadow root the way it handles
|
|
12
12
|
its own links.
|
|
13
13
|
|
|
14
14
|
## A component that navigates renders its own anchor
|
|
15
15
|
|
|
16
|
-
`Tree.Item` and `
|
|
17
|
-
press *is* an `<a>`:
|
|
16
|
+
`Tree.Item`, `Tab`, and `Menu.Item` take an `href`. With an `href`, the element
|
|
17
|
+
you press *is* an `<a>`:
|
|
18
18
|
|
|
19
19
|
```html
|
|
20
20
|
<helix-tree-item
|
|
@@ -103,9 +103,11 @@ changes the structure of the markup as well as adding an attribute. Without an
|
|
|
103
103
|
`href`, nothing moves. The wrapper stays the `treeitem` and contains its group,
|
|
104
104
|
the same way an `<li>` contains a `<ul>`.
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
A tab with an `href` renders `role="tab"` on the `<a>` itself, so a press,
|
|
107
|
+
Enter, and the browser's own link gestures follow it. It sends `helix-before-select`
|
|
108
|
+
first, and canceling that keeps the browser from following, so a client-side
|
|
109
|
+
router can follow it instead. Web Awesome drops the anchor and navigates in
|
|
110
|
+
JavaScript, which loses every item in the list above.
|
|
109
111
|
|
|
110
112
|
## Use client-side routing with real anchors
|
|
111
113
|
|
|
@@ -166,9 +168,14 @@ That is expected.
|
|
|
166
168
|
|
|
167
169
|
| Component | Notes |
|
|
168
170
|
|---|---|
|
|
169
|
-
| `Button` | Renders an `<a>` styled as a button. |
|
|
170
171
|
| `Tree.Item` | Changes the ARIA shape, as above. A linked branch navigates from its row and opens from its chevron. |
|
|
171
172
|
| `Menu.Item` | The row becomes the anchor and carries `menuitem`. Takes `target`, `rel` and `download`. |
|
|
173
|
+
| `Tab` | The tab becomes the anchor and carries `tab`. Takes `target` and `rel`. `helix-before-select` comes first, and canceling it keeps the browser from following. |
|
|
174
|
+
|
|
175
|
+
`Button` never renders an anchor, because a router renders its own `<a>` and
|
|
176
|
+
cannot render one inside the button's shadow root. For a link that looks like a
|
|
177
|
+
button, put the `data-button` recipe on your own anchor, such as
|
|
178
|
+
`<Link data-button="solid" href="/pricing">`, which draws the same control.
|
|
172
179
|
|
|
173
180
|
Other components that should navigate do not take an `href` yet. To make one
|
|
174
181
|
navigate, slot a link into it. `Tree.Item` follows a slotted anchor when you
|
|
@@ -204,8 +211,8 @@ it cannot work here.
|
|
|
204
211
|
|
|
205
212
|
No arrangement of the markup meets both of these constraints.
|
|
206
213
|
|
|
207
|
-
**In a command palette, navigate from the `helix-select` event.** Listen for
|
|
208
|
-
`helix-select` on `helix-command` and call your router. A palette entry is a
|
|
214
|
+
**In a command palette, navigate from the `helix-before-select` event.** Listen for
|
|
215
|
+
`helix-before-select` on `helix-command` and call your router. A palette entry is a
|
|
209
216
|
command, and "go to Settings" is a command that navigates. Command palettes in
|
|
210
217
|
other applications work the same way. You lose the gestures that only a real
|
|
211
218
|
anchor has:
|
package/content/sandbox.mdx
CHANGED
|
@@ -182,7 +182,7 @@ preview, and `helix-caret` from it fills the status bar's position. A wrapper
|
|
|
182
182
|
around CodeMirror:
|
|
183
183
|
|
|
184
184
|
```ts
|
|
185
|
-
import { HelixBeforeEditEvent } from '@fusion.dev/helix
|
|
185
|
+
import { HelixBeforeEditEvent } from '@fusion.dev/helix/sandbox/events'
|
|
186
186
|
import { EditorView, basicSetup } from 'codemirror'
|
|
187
187
|
|
|
188
188
|
class CodeEditor extends HTMLElement {
|
|
@@ -122,7 +122,7 @@ casing must match. Outside React, use
|
|
|
122
122
|
|
|
123
123
|
**Lifecycle events do not bubble. Coordination events do.**
|
|
124
124
|
`helix-before-open` and `helix-before-close` reach the element and stop there,
|
|
125
|
-
so a menu closing inside a dialog does not close the dialog. `helix-select`,
|
|
125
|
+
so a menu closing inside a dialog does not close the dialog. `helix-before-select`,
|
|
126
126
|
`helix-before-change`, and `helix-dismiss` cross shadow boundaries, so you can
|
|
127
127
|
listen for them on a container. If a listener on a wrapper receives nothing, the
|
|
128
128
|
event is a lifecycle event. Put the listener on the element.
|
|
@@ -36,12 +36,37 @@ The bug is [facebook/react#35446](https://github.com/facebook/react/issues/35446
|
|
|
36
36
|
and it is still open. The elements work around it for you.
|
|
37
37
|
|
|
38
38
|
Every Helix element binds the handler itself, just before it dispatches any
|
|
39
|
-
event, using the function React already holds for it.
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
event, using the function React already holds for it. On a hydrated page, the
|
|
40
|
+
first event reaches the handler. After React binds its own listener, no event
|
|
41
|
+
is delivered twice.
|
|
42
42
|
|
|
43
43
|
You do not need to do anything, and nothing changes when React fixes the bug.
|
|
44
44
|
|
|
45
|
+
We measured this in Chromium against React 19.3.1 on October 25, 2026. Each
|
|
46
|
+
version below rendered a page with `renderToString` and hydrated it with
|
|
47
|
+
`hydrateRoot`:
|
|
48
|
+
|
|
49
|
+
| React | Bug present | Helix handles it |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| 19.0.9 | Yes | Yes |
|
|
52
|
+
| 19.2.9 | Yes | Yes |
|
|
53
|
+
| 19.3.1 | Yes | Yes |
|
|
54
|
+
| 19.3.0 canary of October 23, 2026 | Yes | Yes |
|
|
55
|
+
| 18.3.1 | Yes | The first event only |
|
|
56
|
+
|
|
57
|
+
Helix supports React 19. React 18 binds no `on` prop on a custom element at
|
|
58
|
+
all. Helix binds the first handler, but each render that passes a new function
|
|
59
|
+
adds another listener, so a handler runs once for every render it has been
|
|
60
|
+
through.
|
|
61
|
+
|
|
62
|
+
In a development build, the page logs an error when React has a root and no
|
|
63
|
+
Helix element finds the props React holds for it. This means the installed
|
|
64
|
+
React stores its props under a key Helix does not read, and every `onhelix-*`
|
|
65
|
+
handler on a server-rendered page is dead. Pin React to a version in the table,
|
|
66
|
+
and open an issue at
|
|
67
|
+
[github.com/project-fusion/fusion](https://github.com/project-fusion/fusion/issues)
|
|
68
|
+
that names the version you installed.
|
|
69
|
+
|
|
45
70
|
React's own events work when they bubble. `onClick` on a `<helix-button>` goes
|
|
46
71
|
through React's synthetic event system, which hydration sets up.
|
|
47
72
|
|
|
@@ -63,8 +88,8 @@ written.
|
|
|
63
88
|
- `onhelix-sample-start` reaches `helix-sample-start`. ✅
|
|
64
89
|
- `onHelixOpen` binds an event called `HelixOpen`, which is never dispatched. ❌
|
|
65
90
|
|
|
66
|
-
The number of hyphens does not matter. We measured this in
|
|
67
|
-
19.
|
|
91
|
+
The number of hyphens does not matter. We measured this in Chromium against
|
|
92
|
+
React 19.3.1.
|
|
68
93
|
|
|
69
94
|
## Register the elements in one client component
|
|
70
95
|
|
|
@@ -23,7 +23,13 @@ npx playwright install --with-deps chromium
|
|
|
23
23
|
|
|
24
24
|
Capture runs in Playwright, which you install beside the command line. The
|
|
25
25
|
rest of `helix` needs no browser, so Playwright is an optional peer, and
|
|
26
|
-
`helix visual test` names it when it is missing.
|
|
26
|
+
`helix visual test` names it when it is missing. A config in TypeScript needs
|
|
27
|
+
Node 22.18 or later. On an older Node, write it as `visual.config.mjs`.
|
|
28
|
+
|
|
29
|
+
**On macOS and Windows, a run skips until you choose where baselines are taken.**
|
|
30
|
+
`test` and `update` say so, compare nothing, and exit 0, because text draws
|
|
31
|
+
differently on each platform. Take baselines on Linux, or set `platform: 'any'`
|
|
32
|
+
to compare on the machine in front of you.
|
|
27
33
|
|
|
28
34
|
## Commands
|
|
29
35
|
|
|
@@ -54,6 +60,38 @@ Each run ends with one line that says what it compared and what it skipped.
|
|
|
54
60
|
`helix --json visual …` prints one JSON document on stdout and moves the
|
|
55
61
|
sentences to stderr.
|
|
56
62
|
|
|
63
|
+
## Picture a site
|
|
64
|
+
|
|
65
|
+
Picture a production build, because a dev server draws its own overlays, and they
|
|
66
|
+
differ from run to run. `visual.config.ts`, for a Next.js site:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { defineConfig } from '@fusion.dev/helix-cli/visual'
|
|
70
|
+
|
|
71
|
+
export default defineConfig({
|
|
72
|
+
sources: [
|
|
73
|
+
{
|
|
74
|
+
baselines: 'visual/baselines',
|
|
75
|
+
kind: 'server',
|
|
76
|
+
name: 'site',
|
|
77
|
+
routes: ['/', '/pricing', { name: 'not-found', path: '/nope', status: 404 }],
|
|
78
|
+
start: 'npm run start -- --port 3100',
|
|
79
|
+
url: 'http://localhost:3100',
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
npm run build
|
|
87
|
+
npx helix visual update
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`update` writes the baselines and the fingerprint, and its report shows each
|
|
91
|
+
picture it wrote. Look at them, commit them, and run `helix visual test` from then
|
|
92
|
+
on. Any page that answers 400 or above fails the entry, unless its route names that
|
|
93
|
+
`status`.
|
|
94
|
+
|
|
57
95
|
## The config
|
|
58
96
|
|
|
59
97
|
`visual.config.ts`, in the directory you run `helix visual` from:
|
|
@@ -90,9 +128,28 @@ export default defineConfig({
|
|
|
90
128
|
width, and dark when you give none. Each baseline is written beside its
|
|
91
129
|
entry as `<stem>.<name>.png`.
|
|
92
130
|
- **`serve`** is URL paths served from disk, relative to the config.
|
|
131
|
+
- **`baselines`** is, on a server source, the directory its baselines are written
|
|
132
|
+
in, relative to the config.
|
|
133
|
+
- **`platform`** is where baselines are taken and compared: `linux` unless you set
|
|
134
|
+
`any`.
|
|
135
|
+
- **`network`** is what a page may reach. `origin`, the default, refuses every
|
|
136
|
+
request to another host and names each host in the log and in `run.json`.
|
|
137
|
+
`{ allow: ['tiles.example.com'] }` lets those hosts through, and `open` lets every
|
|
138
|
+
host through.
|
|
139
|
+
- **`mask`** is selectors whose elements are painted over in every picture, for
|
|
140
|
+
what changes on its own, such as a clock. A mask paints over the element's box and
|
|
141
|
+
anything drawn on top of it. The run names a mask that matches nothing on a page,
|
|
142
|
+
or only elements with no box.
|
|
143
|
+
- **`style`** is a stylesheet applied while each picture is taken, to hold still
|
|
144
|
+
what `mask` cannot.
|
|
145
|
+
- **`settle.selector`** names what a page matches once it has drawn what it draws
|
|
146
|
+
late, such as a map's tiles. The entry fails when nothing matches within
|
|
147
|
+
`settle.timeout`, 30 seconds unless you set it.
|
|
93
148
|
- **`settle.readyProperty`** names a property your elements carry holding a
|
|
94
149
|
promise that resolves once they have rendered. The run awaits it before each
|
|
95
|
-
shot.
|
|
150
|
+
shot, for 5 seconds, and then names the tags still waiting.
|
|
151
|
+
- **`settle.elements`** limits the custom elements a run waits for to one tag
|
|
152
|
+
prefix, for a framework that writes hyphenated tags it never defines.
|
|
96
153
|
- **`aria`** keeps each entry's accessibility snapshot beside its baselines
|
|
97
154
|
and compares it as text, so a lost label fails even when no pixel moved.
|
|
98
155
|
- **`checks`** adds judgments that need no baseline: `code` fails a code block
|
package/dist/pages.generated.js
CHANGED
|
@@ -112,7 +112,7 @@ export const docPages = [
|
|
|
112
112
|
title: 'Intent reference',
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
|
-
description: 'What Helix is, and the packages it includes.',
|
|
115
|
+
description: 'What Fusion Helix is, and the packages it includes.',
|
|
116
116
|
order: 1,
|
|
117
117
|
section: 'Getting started',
|
|
118
118
|
slug: 'introduction',
|