@fusion.dev/helix-docs 0.206.0 → 0.207.1
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/README.md +22 -2
- package/content/angular.mdx +37 -4
- package/content/browser-support.mdx +3 -3
- package/content/building-on-the-headless-tier.mdx +84 -21
- package/content/charting.mdx +50 -0
- package/content/coding-agents.mdx +2 -1
- package/content/color-modes.mdx +2 -2
- package/content/color.mdx +35 -5
- package/content/diagnostics.mdx +255 -0
- package/content/graphs.mdx +6 -0
- package/content/iconography.mdx +31 -13
- package/content/installation.mdx +34 -10
- package/content/intent-reference.mdx +27 -3
- package/content/introduction.mdx +3 -2
- package/content/localization.mdx +86 -4
- package/content/maps.mdx +68 -14
- package/content/metrics.mdx +252 -249
- package/content/other-frameworks.mdx +52 -12
- package/content/overriding-a-component.mdx +11 -10
- package/content/react.mdx +15 -1
- package/content/recipes.mdx +18 -2
- package/content/sandbox.mdx +75 -25
- package/content/server-rendering.mdx +2 -2
- package/content/state-and-control.mdx +7 -1
- package/content/surfaces-and-separation.mdx +2 -1
- package/content/svelte.mdx +54 -8
- package/content/theme-builder.mdx +15 -3
- package/content/theming.mdx +5 -1
- package/content/tiers.mdx +15 -14
- package/content/using-helix-with-an-agent.mdx +12 -5
- package/content/using-the-elements.mdx +24 -23
- package/content/versioning.mdx +15 -8
- package/content/visual-axes.mdx +11 -4
- package/content/vue.mdx +28 -1
- package/content/without-a-bundler.mdx +9 -7
- package/dist/index.d.ts +1 -3
- package/dist/index.js +0 -1
- package/dist/pages.generated.js +7 -1
- package/dist/reference.d.ts +70 -6
- package/dist/reference.js +13 -2
- package/dist/sources.generated.js +1346 -459
- package/package.json +13 -12
|
@@ -8,9 +8,9 @@ trailing: true
|
|
|
8
8
|
|
|
9
9
|
# Other frameworks
|
|
10
10
|
|
|
11
|
-
This page covers Solid, Qwik, Lit, Ember, Alpine, htmx, and any other
|
|
12
|
-
that puts elements in a document. They all follow the same three
|
|
13
|
-
nothing else.
|
|
11
|
+
This page covers Preact, Solid, Qwik, Lit, Ember, Alpine, htmx, and any other
|
|
12
|
+
framework that puts elements in a document. They all follow the same three
|
|
13
|
+
rules and need nothing else.
|
|
14
14
|
|
|
15
15
|
## The three rules
|
|
16
16
|
|
|
@@ -44,11 +44,12 @@ in full.
|
|
|
44
44
|
|
|
45
45
|
| Framework | Notes |
|
|
46
46
|
|---|---|
|
|
47
|
-
| Solid | Sets properties on custom elements by default. Use `prop:` to force one and `attr:` to force the other. `on:helix-before-change` binds the literal name |
|
|
48
|
-
| Qwik | Works as JSX. Bind with `document:on…` or a ref, since a plain `on*` prop is normalized |
|
|
49
|
-
| Lit | `.prop=`, `?bool=`, and `@helix-before-change=` work with no setup |
|
|
50
|
-
| Ember | Angle-bracket invocation passes attributes only, so set properties from a modifier |
|
|
51
47
|
| Alpine, htmx | Markup and attributes only, which work with every component. Refusing a change needs script |
|
|
48
|
+
| Ember | Angle-bracket invocation passes attributes only, so set properties from a modifier |
|
|
49
|
+
| Lit | `.prop=`, `?bool=`, and `@helix-before-change=` work with no setup |
|
|
50
|
+
| Preact | Sets a property when the element has one, and an attribute otherwise. `onhelix-before-change` binds the literal name. [Typings](#typings) says how to type the tags |
|
|
51
|
+
| Qwik | Works as JSX. Bind with `document:on…` or a ref, since a plain `on*` prop is normalized |
|
|
52
|
+
| Solid | Sets properties on custom elements by default. Use `prop:` to force one and `attr:` to force the other. `on:helix-before-change` binds the literal name. [Typings](#typings) says how to type the tags |
|
|
52
53
|
|
|
53
54
|
We do not test these frameworks, so they share one page. If your framework needs
|
|
54
55
|
something the three rules do not cover, tell us. It is a gap in this page or in
|
|
@@ -56,11 +57,50 @@ the library.
|
|
|
56
57
|
|
|
57
58
|
## Typings
|
|
58
59
|
|
|
59
|
-
`@fusion.dev/helix/jsx`
|
|
60
|
-
the
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
`@fusion.dev/helix/jsx` types the tags for React, Preact, and Solid, and it is
|
|
61
|
+
the base for typing any other framework that uses JSX. The Svelte typings read
|
|
62
|
+
their props from it. It needs only the types of the framework you use, and it
|
|
63
|
+
compiles with `skipLibCheck: false`.
|
|
64
|
+
|
|
65
|
+
`ref` and `style` take the types of the framework the tag is written in:
|
|
66
|
+
|
|
67
|
+
- React: React's `Ref` and `CSSProperties`.
|
|
68
|
+
- Preact: Preact's `Ref`, and the `style` a `<div>` takes, which is a string or
|
|
69
|
+
an object.
|
|
70
|
+
- Solid: the element or a callback that receives it, and the `style` a `<div>`
|
|
71
|
+
takes, which is a string or an object.
|
|
72
|
+
- Any other framework: the element or a callback that receives it, and an
|
|
73
|
+
object of style properties.
|
|
74
|
+
|
|
75
|
+
In Preact and Solid, import the typings once, anywhere TypeScript sees it:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import '@fusion.dev/helix/jsx'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Preact reads the tags with `jsxImportSource: "preact"` and with `h` as the JSX
|
|
82
|
+
factory. If your `paths` alias `react` to `preact/compat`, the tags take `ref`
|
|
83
|
+
and `style` as `preact/compat` types React's, so `style` takes an object.
|
|
84
|
+
|
|
85
|
+
Solid reads the tags with `jsxImportSource: "solid-js"`, and each tag also
|
|
86
|
+
takes `prop:`, `attr:`, and `on:` forms of its props and events.
|
|
87
|
+
|
|
88
|
+
With `jsxImportSource: "solid-js/h"`, Solid runs without its compiler, and the
|
|
89
|
+
same import types the tags. Each prop also takes a function that returns its
|
|
90
|
+
value, and `h` calls that function again when a signal it reads changes. A
|
|
91
|
+
property whose value is a function, such as a slider's `formatter`, takes only a
|
|
92
|
+
function that returns it, because `h` calls any function it is given:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<helix-slider disabled={() => busy()} formatter={() => format} label="Volume" />
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`ref`, `children`, and the handlers named `on…`, such as
|
|
99
|
+
`on:helix-before-change`, take their values as they are.
|
|
100
|
+
|
|
101
|
+
If your framework needs a different format, use the manifest at
|
|
102
|
+
`@fusion.dev/helix/custom-elements.json`. It describes every tag, prop, event,
|
|
103
|
+
and part, and we generate all our typings from it.
|
|
64
104
|
|
|
65
105
|
## Register the elements
|
|
66
106
|
|
|
@@ -69,7 +69,7 @@ helix-select::part(trigger) {
|
|
|
69
69
|
font-variant-numeric: tabular-nums;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
helix-dialog::part(
|
|
72
|
+
helix-dialog::part(surface) {
|
|
73
73
|
max-inline-size: 40rem;
|
|
74
74
|
}
|
|
75
75
|
```
|
|
@@ -99,16 +99,17 @@ helix-video::part(overflow-header) { … }
|
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
`<helix-date-input>` renders a date picker in the `picker` role, so the picker's
|
|
102
|
-
`cell` part is `picker-cell`.
|
|
103
|
-
|
|
104
|
-
`tooltip
|
|
102
|
+
`cell` part is `picker-cell`. The tooltip inside `<helix-copy-button>` is in the
|
|
103
|
+
`tooltip` role, so its `surface` is `tooltip-surface` and its arrow is
|
|
104
|
+
`tooltip-arrow`. When the child's part is the role itself, the forwarded name is
|
|
105
|
+
the role alone: each line inside `<helix-log>` is `line`.
|
|
105
106
|
|
|
106
107
|
A part that changes with a state has a second name while the state is active,
|
|
107
108
|
and that name is forwarded too. A tooltip placed below its button is also
|
|
108
|
-
`tooltip-bottom`, and its arrow is also `tooltip-arrow-bottom`. Your
|
|
109
|
-
can use these names to style each side, which it cannot detect any
|
|
110
|
-
The arrow already faces the button on every side. Use the names for
|
|
111
|
-
add on top.
|
|
109
|
+
`tooltip-surface-bottom`, and its arrow is also `tooltip-arrow-bottom`. Your
|
|
110
|
+
stylesheet can use these names to style each side, which it cannot detect any
|
|
111
|
+
other way. The arrow already faces the button on every side. Use the names for
|
|
112
|
+
styles you add on top.
|
|
112
113
|
|
|
113
114
|
**The prefix keeps part names unique.** A component and its child can use the
|
|
114
115
|
same word. This happened once: `Command` forwarded the listbox's `search-field`
|
|
@@ -138,8 +139,8 @@ so you learn each name once:
|
|
|
138
139
|
| `container` | An element's own outer box. |
|
|
139
140
|
| `content` | A region you fill. |
|
|
140
141
|
| `panel` | A region switched between, or a sheet anchored to an edge. |
|
|
141
|
-
| `surface` | A floating panel drawn over the page. |
|
|
142
|
-
| `viewport` |
|
|
142
|
+
| `surface` | A floating panel drawn over the page, such as a menu, a popover, a tooltip, or a dialog. |
|
|
143
|
+
| `viewport` | The box that shows part of a larger area: a scrollport, or the frame a map or a graph is panned and zoomed in. |
|
|
143
144
|
|
|
144
145
|
A part that names something specific to one component, such as `thumb`,
|
|
145
146
|
`swatch`, or `weekday`, is listed on that component's page.
|
package/content/react.mdx
CHANGED
|
@@ -137,6 +137,8 @@ starts with that value and keeps applying its own changes, so the markup below
|
|
|
137
137
|
works without a handler.
|
|
138
138
|
|
|
139
139
|
```tsx
|
|
140
|
+
import { flushSync } from 'react-dom'
|
|
141
|
+
|
|
140
142
|
// Seeded: the element owns it from there.
|
|
141
143
|
<helix-input default-value="Ada" label="Name" />
|
|
142
144
|
|
|
@@ -148,7 +150,7 @@ works without a handler.
|
|
|
148
150
|
label="Name"
|
|
149
151
|
onhelix-before-change={(event) => {
|
|
150
152
|
event.preventDefault()
|
|
151
|
-
setName(clean(event.detail.value))
|
|
153
|
+
flushSync(() => setName(clean(event.detail.value)))
|
|
152
154
|
}}
|
|
153
155
|
value={name}
|
|
154
156
|
/>
|
|
@@ -159,6 +161,18 @@ refuse it. React restores a native `<input>` whose state did not change, and it
|
|
|
159
161
|
does not do this for a custom element. A handler that does not call `setState`
|
|
160
162
|
leaves the element with the new value.
|
|
161
163
|
|
|
164
|
+
**Set the state inside `flushSync` when you write your own value.** React sets
|
|
165
|
+
the priority of a state update from the type of the event being handled.
|
|
166
|
+
`helix-before-change` is not one of the types React lists, so the update gets
|
|
167
|
+
the default priority and renders in a later task. A key that arrives before that
|
|
168
|
+
task lands in the text from before the update, and the keys typed in between are
|
|
169
|
+
lost. A barcode scanner, a password manager, and a test send keys that fast:
|
|
170
|
+
Playwright typing `Ada L!` with no delay into a field that keeps lowercase
|
|
171
|
+
letters left it empty. `flushSync` renders the update before your handler
|
|
172
|
+
returns, so the element holds your value when the next key arrives. The same
|
|
173
|
+
applies to every field you type into, such as `helix-textarea`,
|
|
174
|
+
`helix-number-input`, and `helix-pin-input`.
|
|
175
|
+
|
|
162
176
|
*Until August 26, 2026, this page described the opposite behavior, and the typings
|
|
163
177
|
enforced it. Writing `value` made you the owner of the state. The element
|
|
164
178
|
applied no change until you set the value, and `<helix-input value="Ada" />`
|
package/content/recipes.mdx
CHANGED
|
@@ -140,7 +140,7 @@ Three Markdown constructs need components, because HTML alone cannot hold what
|
|
|
140
140
|
they need:
|
|
141
141
|
|
|
142
142
|
- **Fenced code** becomes `helix-code-block`, with the fence's language as its
|
|
143
|
-
`label`. A `<pre>` has no label or copy button.
|
|
143
|
+
`label` and its `language`. A `<pre>` has no label or copy button.
|
|
144
144
|
- **Alerts** such as `> [!WARNING]` become `helix-callout`. A `<blockquote>` has no
|
|
145
145
|
tone. `NOTE` becomes `tone="info"`, `TIP` becomes `success`, `WARNING` becomes
|
|
146
146
|
`warning`, and `CAUTION` becomes `danger`. `IMPORTANT` has no tone. Each
|
|
@@ -155,13 +155,29 @@ The `rehype` plugin from `@fusion.dev/helix/rehype` makes all three changes in t
|
|
|
155
155
|
tree your Markdown library has already built. It parses nothing, so your
|
|
156
156
|
library, its dialect, and your sanitizing stay yours. Add it to any pipeline
|
|
157
157
|
that reaches rehype, such as `remark` with `remark-rehype`, `react-markdown`, or
|
|
158
|
-
MDX
|
|
158
|
+
MDX.
|
|
159
|
+
|
|
160
|
+
Tables are GitHub Flavored Markdown, so your pipeline needs `remark-gfm` to
|
|
161
|
+
parse them. Without it, a table stays a paragraph of pipes. For a `remark`
|
|
162
|
+
pipeline, install:
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
bun add unified remark-parse remark-gfm remark-rehype rehype-stringify
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Then add the plugin after `remark-rehype`:
|
|
159
169
|
|
|
160
170
|
```js
|
|
161
171
|
import rehypeHelix from '@fusion.dev/helix/rehype'
|
|
172
|
+
import rehypeStringify from 'rehype-stringify'
|
|
173
|
+
import remarkGfm from 'remark-gfm'
|
|
174
|
+
import remarkParse from 'remark-parse'
|
|
175
|
+
import remarkRehype from 'remark-rehype'
|
|
176
|
+
import { unified } from 'unified'
|
|
162
177
|
|
|
163
178
|
const html = await unified()
|
|
164
179
|
.use(remarkParse)
|
|
180
|
+
.use(remarkGfm)
|
|
165
181
|
.use(remarkRehype)
|
|
166
182
|
.use(rehypeHelix)
|
|
167
183
|
.use(rehypeStringify)
|
package/content/sandbox.mdx
CHANGED
|
@@ -12,8 +12,10 @@ You can edit every example on a component's page. When you change the markup,
|
|
|
12
12
|
the preview redraws. When you change the stylesheet beside it, the preview
|
|
13
13
|
restyles.
|
|
14
14
|
|
|
15
|
-
To build a playground of your own,
|
|
16
|
-
|
|
15
|
+
To build a playground of your own, use the same elements: the sandbox from
|
|
16
|
+
`@fusion.dev/helix/sandbox`, and the editor from `@fusion.dev/helix-sandbox`.
|
|
17
|
+
The isolation described below makes it safe to run markup that someone else
|
|
18
|
+
wrote.
|
|
17
19
|
|
|
18
20
|
## How the preview is isolated
|
|
19
21
|
|
|
@@ -160,7 +162,7 @@ preview, and `helix-caret` from it fills the status bar's position. A wrapper
|
|
|
160
162
|
around CodeMirror:
|
|
161
163
|
|
|
162
164
|
```ts
|
|
163
|
-
import { HelixBeforeEditEvent } from '@fusion.dev/helix'
|
|
165
|
+
import { HelixBeforeEditEvent } from '@fusion.dev/helix-core'
|
|
164
166
|
import { EditorView, basicSetup } from 'codemirror'
|
|
165
167
|
|
|
166
168
|
class CodeEditor extends HTMLElement {
|
|
@@ -217,16 +219,18 @@ own message from `:state(failed)`.
|
|
|
217
219
|
|
|
218
220
|
## The frame document
|
|
219
221
|
|
|
220
|
-
`helix-sandbox-preview`
|
|
221
|
-
document
|
|
222
|
-
to the preview through `postMessage`.
|
|
222
|
+
`helix-sandbox-preview` runs each example in a sandboxed frame. The frame
|
|
223
|
+
document either comes with the preview or is one you host.
|
|
223
224
|
|
|
224
|
-
Without `src`, the preview loads a built-in document
|
|
225
|
-
HTML, CSS, and scripts, and reports the
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
225
|
+
**Without `src`, the preview loads a built-in document**, so a sandbox needs
|
|
226
|
+
nothing hosted. It runs the example's HTML, CSS, and scripts, and reports the
|
|
227
|
+
console, uncaught errors, the height, and presses. It defines no element, so
|
|
228
|
+
set `src` to a document of your own when your examples use Helix components.
|
|
229
|
+
Its script is inline, so a page whose Content Security Policy refuses inline
|
|
230
|
+
scripts needs a `src` too.
|
|
231
|
+
|
|
232
|
+
**With `src`, the preview loads your document.** It loads the styles and the
|
|
233
|
+
elements your examples use, and it talks to the preview through `postMessage`.
|
|
230
234
|
|
|
231
235
|
The messages:
|
|
232
236
|
|
|
@@ -250,9 +254,18 @@ it was given. The types are exported from `@fusion.dev/helix/sandbox` as
|
|
|
250
254
|
you. Call it once in the frame document, after your elements are defined. It
|
|
251
255
|
writes each render into `#preview-root`, runs the example's scripts, relays
|
|
252
256
|
its console, its uncaught errors, and its unhandled promise rejections,
|
|
253
|
-
reports its height and its presses, and adds the rules for the four layouts.
|
|
254
|
-
|
|
255
|
-
|
|
257
|
+
reports its height and its presses, and adds the rules for the four layouts.
|
|
258
|
+
|
|
259
|
+
The frame's own script must be a classic script, bundled into one file. A
|
|
260
|
+
module does not load there. The frame's origin is opaque, so a module import
|
|
261
|
+
back to your site is a cross-origin request, which fails unless your server
|
|
262
|
+
answers it with CORS headers. A stylesheet `<link>` is not checked, so the
|
|
263
|
+
styles load as they are.
|
|
264
|
+
|
|
265
|
+
A frame follows. `/frame.js` is the bundle. `/helix.css` is the stylesheet
|
|
266
|
+
from `@fusion.dev/helix-styles`, and it imports `primitives.css`, `tokens.css`,
|
|
267
|
+
`base.css`, and `elements.css` from beside it, so serve the five files from
|
|
268
|
+
the package's `dist` folder together:
|
|
256
269
|
|
|
257
270
|
```html
|
|
258
271
|
<!doctype html>
|
|
@@ -284,16 +297,53 @@ Logs from the example appear in the console panel. Logs from the page around it
|
|
|
284
297
|
do not. We patch the console inside the frame on purpose, so the panel shows
|
|
285
298
|
what the example logs and nothing else.
|
|
286
299
|
|
|
287
|
-
## The
|
|
300
|
+
## The editor and the highlighter
|
|
301
|
+
|
|
302
|
+
`@fusion.dev/helix-sandbox` is the editor the component pages use, and the
|
|
303
|
+
highlighter that colors their code. Install it beside the library:
|
|
304
|
+
|
|
305
|
+
```sh
|
|
306
|
+
npm install @fusion.dev/helix @fusion.dev/helix-styles @fusion.dev/helix-sandbox
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Each entry does one thing:
|
|
310
|
+
|
|
311
|
+
- `@fusion.dev/helix-sandbox/editor` registers `helix-sandbox-editor`, a
|
|
312
|
+
source pane with CodeMirror behind it.
|
|
313
|
+
- `@fusion.dev/helix-sandbox/highlight` turns a snippet into highlighted HTML,
|
|
314
|
+
on a server or in a page, with no editor.
|
|
315
|
+
- `@fusion.dev/helix-sandbox/code-block` colors every Code Block on the page.
|
|
316
|
+
|
|
317
|
+
The editor is a source pane, so it goes in the `source` slot, with a `name`
|
|
318
|
+
the sandbox reads:
|
|
319
|
+
|
|
320
|
+
```html
|
|
321
|
+
<helix-sandbox>
|
|
322
|
+
<helix-sandbox-editor
|
|
323
|
+
language="html"
|
|
324
|
+
name="html"
|
|
325
|
+
slot="source"
|
|
326
|
+
value="<p>Hello</p>"
|
|
327
|
+
></helix-sandbox-editor>
|
|
328
|
+
<helix-sandbox-preview slot="preview"></helix-sandbox-preview>
|
|
329
|
+
</helix-sandbox>
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
import '@fusion.dev/helix/sandbox'
|
|
334
|
+
import '@fusion.dev/helix-sandbox/editor'
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
The [Sandbox reference](/helix/components/sandbox#helix-sandbox-editor) lists
|
|
338
|
+
the editor's properties, events, methods, and states.
|
|
288
339
|
|
|
289
|
-
|
|
290
|
-
|
|
340
|
+
`highlight` reads the same grammars as the editor. By default each token is a
|
|
341
|
+
span with Lezer's `tok-` class, for a stylesheet of your own. Pass
|
|
342
|
+
`syntaxHighlighter` for the editor's classes, and put `syntaxRules` in the
|
|
343
|
+
stylesheet where the result is drawn for the editor's colors:
|
|
291
344
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
- The build depends on this repository's layout.
|
|
345
|
+
```ts
|
|
346
|
+
import { highlight, syntaxHighlighter } from '@fusion.dev/helix-sandbox/highlight'
|
|
295
347
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
have not filled, and we have no policy against filling it. Tell us if you need
|
|
299
|
-
it.
|
|
348
|
+
const markup = highlight('const total = 1', 'ts', syntaxHighlighter)
|
|
349
|
+
```
|
|
@@ -85,8 +85,8 @@ Run your page's own scripts after the definitions load. Hydration expects that
|
|
|
85
85
|
order. A property you set before hydration is one the server did not render
|
|
86
86
|
with.
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
Chromium, and
|
|
88
|
+
We render every component example in both tiers with Lit SSR, hydrate it in
|
|
89
|
+
Chromium, and compare it with the same example rendered on a client.
|
|
90
90
|
|
|
91
91
|
React server components do not use declarative shadow DOM. Their output is
|
|
92
92
|
replayed through DOM APIs, and declarative shadow DOM does not activate for
|
|
@@ -49,12 +49,18 @@ state yourself, cancel the change and assign your own value:
|
|
|
49
49
|
label="Name"
|
|
50
50
|
onhelix-before-change={(event) => {
|
|
51
51
|
event.preventDefault()
|
|
52
|
-
setName(sanitize(event.detail.value))
|
|
52
|
+
flushSync(() => setName(sanitize(event.detail.value)))
|
|
53
53
|
}}
|
|
54
54
|
value={name}
|
|
55
55
|
/>
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
Assign your value before the next key arrives, or that key lands in the text
|
|
59
|
+
from before your value. Vue, Svelte, and Solid apply a bound value in time. In
|
|
60
|
+
React, set the state inside `flushSync`, as [React](./react) explains. In
|
|
61
|
+
Angular without zone.js, call `detectChanges()` after you set the signal, as
|
|
62
|
+
[Angular](./angular) explains.
|
|
63
|
+
|
|
58
64
|
The platform uses the same pattern. You call `preventDefault()` to cancel:
|
|
59
65
|
|
|
60
66
|
- a form submit
|
|
@@ -98,4 +98,5 @@ its own components, and the same applies to anything you build with it.
|
|
|
98
98
|
The theme engine measures each ink against the ground it is actually painted
|
|
99
99
|
on. The ladder records which pairs occur. For example, `foreground` on
|
|
100
100
|
`surface-overlay` is a real pair, and `foreground-on-primary` on `surface` is
|
|
101
|
-
not.
|
|
101
|
+
not. A field's fill, `well`, is translucent, so the text in a field is measured
|
|
102
|
+
against the well over each surface. See [Color](./color).
|
package/content/svelte.mdx
CHANGED
|
@@ -12,30 +12,74 @@ import '@fusion.dev/helix/svelte'
|
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
Add this types-only import once. It augments `SvelteHTMLElements`, so every
|
|
15
|
-
Helix tag is typed in a component.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
cannot disagree.
|
|
15
|
+
Helix tag is typed in a component. Svelte binds a handler as
|
|
16
|
+
`onhelix-before-change={…}`, which is the shape the JSX typings already
|
|
17
|
+
describe, so the Svelte typings read each tag's props from
|
|
18
|
+
`@fusion.dev/helix/jsx`. The two cannot disagree.
|
|
19
|
+
|
|
20
|
+
The typings need only Svelte's own types, and they compile with
|
|
21
|
+
`skipLibCheck: false`.
|
|
19
22
|
|
|
20
23
|
## Pass props and bind events
|
|
21
24
|
|
|
22
25
|
```svelte
|
|
23
|
-
<helix-input
|
|
26
|
+
<helix-input
|
|
27
|
+
label="Name"
|
|
28
|
+
onchange={(event) => (name = event.currentTarget.value)}
|
|
29
|
+
value={name}
|
|
30
|
+
></helix-input>
|
|
24
31
|
```
|
|
25
32
|
|
|
26
33
|
Svelte sets a property when the element has one, and an attribute otherwise. It
|
|
27
34
|
binds `on*` with `addEventListener`. Svelte shows no unknown-element warning,
|
|
28
35
|
and you do not need a compiler flag.
|
|
29
36
|
|
|
37
|
+
Read the value from `event.currentTarget`, which is typed as the element.
|
|
38
|
+
`event.target` is an `EventTarget | null`, which has no `value`.
|
|
39
|
+
|
|
30
40
|
Svelte passes objects and arrays as properties on its own:
|
|
31
41
|
|
|
32
42
|
```svelte
|
|
33
|
-
<helix-data-table label="People" rows={people}
|
|
43
|
+
<helix-data-table label="People" rows={people}></helix-data-table>
|
|
34
44
|
```
|
|
35
45
|
|
|
36
46
|
Svelte's own attributes are typed on every Helix tag as well, so `onclick`,
|
|
37
47
|
`class`, and a `style` string check as they do on a `div`.
|
|
38
48
|
|
|
49
|
+
## Hold the element
|
|
50
|
+
|
|
51
|
+
`bind:this` holds the element, typed as its class:
|
|
52
|
+
|
|
53
|
+
```svelte
|
|
54
|
+
<script lang="ts">
|
|
55
|
+
import type { HelixInput } from '@fusion.dev/helix'
|
|
56
|
+
|
|
57
|
+
let input: HelixInput | undefined = $state()
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<helix-input bind:this={input} label="Name"></helix-input>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Type a handler in the script
|
|
64
|
+
|
|
65
|
+
A handler written on the tag takes its event type from the tag. A handler
|
|
66
|
+
written in the script names its event type, which `@fusion.dev/helix/jsx`
|
|
67
|
+
exports for each element and event:
|
|
68
|
+
|
|
69
|
+
```svelte
|
|
70
|
+
<script lang="ts">
|
|
71
|
+
import type { HelixInputHelixBeforeChangeElementEvent } from '@fusion.dev/helix/jsx'
|
|
72
|
+
|
|
73
|
+
const refuseLong = (event: HelixInputHelixBeforeChangeElementEvent) => {
|
|
74
|
+
if (event.detail.value.length > 40) {
|
|
75
|
+
event.preventDefault()
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<helix-input label="Handle" onhelix-before-change={refuseLong}></helix-input>
|
|
81
|
+
```
|
|
82
|
+
|
|
39
83
|
## Ignore the click warnings on a control
|
|
40
84
|
|
|
41
85
|
`svelte-check` warns about an `onclick` on any Helix element. Its accessibility
|
|
@@ -55,8 +99,10 @@ right. Use a control there.
|
|
|
55
99
|
|
|
56
100
|
These two rules apply in every framework.
|
|
57
101
|
|
|
58
|
-
|
|
59
|
-
two-way binding convention to opt into.
|
|
102
|
+
Two-way bindings do not work on a Helix control, because custom elements have
|
|
103
|
+
no two-way binding convention to opt into. Svelte accepts `bind:value` only on
|
|
104
|
+
`<input>`, `<textarea>`, and `<select>`, and `bind:checked` only on `<input>`.
|
|
105
|
+
Read the value from the event.
|
|
60
106
|
|
|
61
107
|
A declared `value`, `open`, `checked`, or `active` is a seed. The element starts
|
|
62
108
|
with that value and then applies its own changes. To refuse a change, cancel the
|
|
@@ -73,9 +73,10 @@ its target. Its three tabs are:
|
|
|
73
73
|
- **Contrast.** The pairs that fail, or a line saying that every pair passes.
|
|
74
74
|
- **Collisions.** Colors too close to each other to tell apart, such as the
|
|
75
75
|
primary color and a status color.
|
|
76
|
-
- **Unverifiable.** Pairs whose background is translucent
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
- **Unverifiable.** Pairs whose background is translucent over something the
|
|
77
|
+
engine cannot know, such as a photograph or the page under a translucent
|
|
78
|
+
material, so there is no known color to measure against. This is the cost of
|
|
79
|
+
a translucent material, and the theme builder reports it.
|
|
79
80
|
|
|
80
81
|
## What the theme builder gives you
|
|
81
82
|
|
|
@@ -89,6 +90,17 @@ stylesheet:
|
|
|
89
90
|
}
|
|
90
91
|
```
|
|
91
92
|
|
|
93
|
+
The block holds one mode, the one the preview shows, under the scope
|
|
94
|
+
`[data-theme="preview"]`. Put that attribute on the element to theme, or change
|
|
95
|
+
the selector. Under any selector other than `:root`, also add
|
|
96
|
+
`data-helix-theme` to the element. The block sets the seeds the stylesheet
|
|
97
|
+
derives the chart colors, the spacing, and the corner radii from, and the
|
|
98
|
+
stylesheet reads seeds only at `:root` or on an element with
|
|
99
|
+
`data-helix-theme`. See [Two ways to theme](./theming#two-ways-to-theme).
|
|
100
|
+
|
|
101
|
+
For a page with both modes, derive the intent with `deriveModes` or
|
|
102
|
+
`helix theme build`, as [Derive both modes](./theming#derive-both-modes) shows.
|
|
103
|
+
|
|
92
104
|
The page's link holds every input you changed, so a link is the theme. To derive
|
|
93
105
|
the theme in your own build, write the same inputs as an intent object:
|
|
94
106
|
|
package/content/theming.mdx
CHANGED
|
@@ -29,7 +29,11 @@ changing them one at a time. A change to one value can break the contrast of a
|
|
|
29
29
|
component elsewhere, and nothing tells you.
|
|
30
30
|
|
|
31
31
|
`@fusion.dev/helix-theme` computes a complete token set from a few intent
|
|
32
|
-
inputs
|
|
32
|
+
inputs. It solves each text color against the backgrounds it lands on, measures
|
|
33
|
+
every pair in its surface model, and reports any pair that misses its contrast
|
|
34
|
+
target. `foreground-subtle` is not in the model. It is a fixed step of the
|
|
35
|
+
stylesheet's gray ramp, and the engine does not solve it, so its contrast is not
|
|
36
|
+
guaranteed.
|
|
33
37
|
|
|
34
38
|
## Two ways to theme
|
|
35
39
|
|
package/content/tiers.mdx
CHANGED
|
@@ -21,8 +21,8 @@ depends only on the packages below it.
|
|
|
21
21
|
Neither is a tier, and you do not need either one to render a component.
|
|
22
22
|
|
|
23
23
|
There is no React package. We generate the framework typings into
|
|
24
|
-
`@fusion.dev/helix`, and the bridge that
|
|
25
|
-
events is in `@fusion.dev/helix-core`. See [React](./react).
|
|
24
|
+
`@fusion.dev/helix` and `@fusion.dev/helix-headless`, and the bridge that
|
|
25
|
+
repairs React's hydration of custom events is in `@fusion.dev/helix-core`. See [React](./react).
|
|
26
26
|
|
|
27
27
|
## Why the tiers are separate packages
|
|
28
28
|
|
|
@@ -64,7 +64,7 @@ export class HelixHeadlessAccordionItem extends MixinControllerOpen(HelixElement
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
//
|
|
67
|
+
// styled/src/accordion/root/accordion-root.ts
|
|
68
68
|
// extends the headless one, adds presentation only
|
|
69
69
|
import { styles } from './accordion-root.css.js'
|
|
70
70
|
|
|
@@ -78,7 +78,7 @@ export class HelixAccordion extends HelixHeadlessAccordion {
|
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
```css
|
|
81
|
-
/*
|
|
81
|
+
/* styled/src/accordion/item/accordion-item.css */
|
|
82
82
|
[part='trigger'] {
|
|
83
83
|
min-block-size: var(--helix-size-control);
|
|
84
84
|
padding: var(--helix-space-sm);
|
|
@@ -115,8 +115,8 @@ as unstyled text and then changes into a card. The delay is short. It is most
|
|
|
115
115
|
visible on a server-rendered page, where the markup arrives long before the
|
|
116
116
|
JavaScript.
|
|
117
117
|
|
|
118
|
-
`@fusion.dev/helix` includes `elements.css`, which hides each of
|
|
119
|
-
the tag's definition loads:
|
|
118
|
+
`@fusion.dev/helix-styles` includes `elements.css`, which hides each tag of the
|
|
119
|
+
styled tier until the tag's definition loads:
|
|
120
120
|
|
|
121
121
|
```css
|
|
122
122
|
helix-card:not(:defined) {
|
|
@@ -124,7 +124,7 @@ helix-card:not(:defined) {
|
|
|
124
124
|
}
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
**The
|
|
127
|
+
**The headless tier ships no such file on purpose.** There are three reasons, and
|
|
128
128
|
the third one affects you most.
|
|
129
129
|
|
|
130
130
|
First, hiding an element is a design choice. Below the styled tier, we ship only
|
|
@@ -138,20 +138,21 @@ inside, because there is no shadow root before the upgrade. The rule has to
|
|
|
138
138
|
live in your stylesheet, so the choice is yours.
|
|
139
139
|
|
|
140
140
|
Third, one file for every tag would not fit your project. You import only the
|
|
141
|
-
|
|
141
|
+
headless elements you use, so a generated list would include tags you never ship.
|
|
142
142
|
Write the rule for the elements you have:
|
|
143
143
|
|
|
144
144
|
```css
|
|
145
|
-
helix-
|
|
146
|
-
helix-
|
|
145
|
+
helix-headless-accordion:not(:defined),
|
|
146
|
+
helix-headless-dialog:not(:defined) {
|
|
147
147
|
visibility: hidden;
|
|
148
148
|
}
|
|
149
149
|
```
|
|
150
150
|
|
|
151
|
-
Use `visibility
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
151
|
+
Use `visibility`, so a hidden element keeps the size of its unstyled content.
|
|
152
|
+
The layout still settles when the element upgrades and takes its own size.
|
|
153
|
+
`display: none` collapses the box, so the element takes no space until the
|
|
154
|
+
definition arrives, and then everything below it shifts by the element's full
|
|
155
|
+
size.
|
|
155
156
|
|
|
156
157
|
You may want to hide nothing. If your first paint matters more than the final
|
|
157
158
|
layout, or your page has to be readable with JavaScript turned off, let the
|
|
@@ -43,8 +43,11 @@ writes where an installed family stands in for it, as `native-element`.
|
|
|
43
43
|
|
|
44
44
|
Each family's API is in the packages you installed, and on this site:
|
|
45
45
|
|
|
46
|
-
- `helix api family <family>`, or the MCP tool `helix_family
|
|
47
|
-
|
|
46
|
+
- `helix api family <family>`, or the MCP tool `helix_family`, which read every
|
|
47
|
+
Helix package you installed
|
|
48
|
+
- `node_modules/@fusion.dev/helix/api.json`, and the `api.json` of each other
|
|
49
|
+
Helix package you installed, which lists the elements it adds to a family,
|
|
50
|
+
such as `helix-sandbox-editor` in `@fusion.dev/helix-sandbox`
|
|
48
51
|
- `https://fusion.dev/helix/components/<family>.md` and `.json`
|
|
49
52
|
|
|
50
53
|
Each lists the import lines, every tag, every property with its literal union,
|
|
@@ -98,12 +101,15 @@ block a change, call `preventDefault()` on the matching `helix-before-*` event:
|
|
|
98
101
|
label="Name"
|
|
99
102
|
onhelix-before-change={(event) => {
|
|
100
103
|
event.preventDefault()
|
|
101
|
-
setName(clean(event.detail.value))
|
|
104
|
+
flushSync(() => setName(clean(event.detail.value)))
|
|
102
105
|
}}
|
|
103
106
|
value={name}
|
|
104
107
|
/>
|
|
105
108
|
```
|
|
106
109
|
|
|
110
|
+
Set the state inside `flushSync` from `react-dom`. Without it, React renders the
|
|
111
|
+
update in a later task, and keys typed faster than that are lost.
|
|
112
|
+
|
|
107
113
|
A form reset returns the element to `default-value` and the matching attributes
|
|
108
114
|
for the other states. See [State and control](./state-and-control).
|
|
109
115
|
|
|
@@ -213,12 +219,13 @@ Run these after you write Helix code, and fix every finding at `error` and
|
|
|
213
219
|
|
|
214
220
|
| Command | MCP tool | What it finds |
|
|
215
221
|
|---|---|---|
|
|
216
|
-
| `helix scan` | `helix_check` | Unknown tags, attributes, values, slots, parts, hooks, events, and tokens, a native element a Helix family replaces, a missing import, and the rest, each with its file and line |
|
|
222
|
+
| `helix scan` | `helix_check` | Unknown tags, attributes, values, slots, parts, hooks, events, and tokens, a native element a Helix family replaces, a missing import, a button inside a button, `disabled="false"`, and the rest, each with its file and line |
|
|
217
223
|
| `helix diagnostics --once <url>` | `helix_check_page` | The same on the running page, and what the library logged as it ran |
|
|
218
224
|
| `helix theme audit` | `helix_audit_theme` | Every foreground and background pair in your theme that misses its contrast target |
|
|
219
225
|
|
|
220
226
|
`helix scan --file <path>` reports one file's findings and still reads the whole
|
|
221
|
-
project, so an import in another file counts.
|
|
227
|
+
project, so an import in another file counts. [Diagnostics](./diagnostics)
|
|
228
|
+
lists every kind of finding, and what the checks do not cover.
|
|
222
229
|
|
|
223
230
|
## What this guide does not cover
|
|
224
231
|
|