@fusion.dev/helix-docs 0.205.2 → 0.207.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 (42) hide show
  1. package/README.md +22 -2
  2. package/content/angular.mdx +37 -4
  3. package/content/browser-support.mdx +3 -3
  4. package/content/building-on-the-headless-tier.mdx +84 -21
  5. package/content/charting.mdx +50 -0
  6. package/content/coding-agents.mdx +2 -1
  7. package/content/color-modes.mdx +2 -2
  8. package/content/color.mdx +35 -5
  9. package/content/diagnostics.mdx +255 -0
  10. package/content/graphs.mdx +6 -0
  11. package/content/iconography.mdx +31 -13
  12. package/content/installation.mdx +34 -10
  13. package/content/intent-reference.mdx +27 -3
  14. package/content/introduction.mdx +3 -2
  15. package/content/localization.mdx +86 -4
  16. package/content/maps.mdx +65 -13
  17. package/content/metrics.mdx +252 -249
  18. package/content/other-frameworks.mdx +52 -12
  19. package/content/overriding-a-component.mdx +11 -10
  20. package/content/react.mdx +15 -1
  21. package/content/recipes.mdx +18 -2
  22. package/content/sandbox.mdx +75 -25
  23. package/content/server-rendering.mdx +2 -2
  24. package/content/state-and-control.mdx +7 -1
  25. package/content/surfaces-and-separation.mdx +2 -1
  26. package/content/svelte.mdx +54 -8
  27. package/content/theme-builder.mdx +15 -3
  28. package/content/theming.mdx +5 -1
  29. package/content/tiers.mdx +15 -14
  30. package/content/using-helix-with-an-agent.mdx +12 -5
  31. package/content/using-the-elements.mdx +24 -23
  32. package/content/versioning.mdx +15 -8
  33. package/content/visual-axes.mdx +11 -4
  34. package/content/vue.mdx +28 -1
  35. package/content/without-a-bundler.mdx +9 -7
  36. package/dist/index.d.ts +1 -3
  37. package/dist/index.js +0 -1
  38. package/dist/pages.generated.js +7 -1
  39. package/dist/reference.d.ts +70 -6
  40. package/dist/reference.js +13 -2
  41. package/dist/sources.generated.js +1343 -458
  42. package/package.json +13 -12
package/README.md CHANGED
@@ -1,15 +1,35 @@
1
1
  # @fusion.dev/helix-docs
2
2
 
3
- The Helix documentation as content: the pages, and the registry that names them.
3
+ The Helix documentation as content: the pages, the registry that names them,
4
+ and the renderer for the component reference.
4
5
 
5
6
  ```sh
6
7
  npm install @fusion.dev/helix-docs
7
8
  ```
8
9
 
9
10
  ```js
10
- import { catalog, findInCatalog, reference } from '@fusion.dev/helix-docs'
11
+ import { docSources, findPage, navigation, reference } from '@fusion.dev/helix-docs'
11
12
  ```
12
13
 
14
+ - `pages` is every page, each with its `slug`, `title`, `section`, and
15
+ `description`. `findPage(slug)` returns one page.
16
+ - `navigation()` returns the sidebar: each section in order, the pages under it
17
+ in the order a sidebar renders them, and each page's children.
18
+ - `sections` is the sections in order, and `findSection(title)` returns one.
19
+ - `docSources` holds each page's MDX, keyed by slug.
20
+ - `contentPath(page)` takes a page from `pages` and returns the path of its MDX
21
+ in the package, such as `content/theming.mdx`. Import the file as
22
+ `@fusion.dev/helix-docs/content/theming.mdx`.
23
+ - `reference(manifest, options)` renders a `custom-elements.json` as the
24
+ markdown component reference. `referenceFamilies(manifest)` returns each
25
+ family and its parts in the order the reference prints them.
26
+
27
+ The component catalog ships in each tier as `api.json` and `examples.json`,
28
+ such as `@fusion.dev/helix/api.json`: every family with its elements, imports,
29
+ and examples. An add-on package ships them too, listing the elements it adds
30
+ to a family: `@fusion.dev/helix-sandbox/api.json` lists its editor under the
31
+ Sandbox family.
32
+
13
33
  Published as a package rather than served only from a site so that the
14
34
  documentation carries the version of the library it documents. A docs package at
15
35
  version x describes Helix at version x, which is why versioning here is per
@@ -31,7 +31,8 @@ typos, in your other components.
31
31
 
32
32
  ```html
33
33
  <helix-input
34
- (change)="name = $event.target.value"
34
+ #field
35
+ (change)="name = field.value"
35
36
  [value]="name"
36
37
  />
37
38
  ```
@@ -39,6 +40,10 @@ typos, in your other components.
39
40
  `[prop]` sets a property, which a value that is not a string needs. `(event)`
40
41
  binds with `addEventListener`, so use the event's own name.
41
42
 
43
+ `#field` gives the template a reference to the element, typed as its class once
44
+ you import its family. Under `strictTemplates`, `$event.target` is an
45
+ `EventTarget | null`, which has no `value`.
46
+
42
47
  **Do not use `attr.` for a stateful property.** `[attr.value]="name"` writes the
43
48
  attribute. The attribute seeds the control and is out of date as soon as someone
44
49
  types in it. The property holds the current value.
@@ -50,9 +55,17 @@ checking that typings would feed, so Angular would not enforce a generated file.
50
55
  We publish the manifest as `@fusion.dev/helix/custom-elements.json`, so you can
51
56
  generate your own files from it.
52
57
 
53
- As a result, a misspelled property fails without an error in Angular. In React,
54
- Vue, and Svelte, the same mistake is a compile error. Check property names in
55
- code review.
58
+ As a result, a misspelled property fails without an error in Angular. Check
59
+ property names in code review.
60
+
61
+ The other frameworks check a misspelled name to different degrees:
62
+
63
+ - In React, TypeScript reports a misspelled prop unless its name has a hyphen.
64
+ See [React](./react).
65
+ - In Vue, `vue-tsc` reports one only when `checkUnknownProps` is on. See
66
+ [Vue](./vue).
67
+ - In Svelte, `svelte-check` reports an attribute that the Helix tag does not
68
+ declare.
56
69
 
57
70
  ## Refuse a change
58
71
 
@@ -60,3 +73,23 @@ code review.
60
73
  value you set and then applies its own changes. To refuse a change, listen for
61
74
  the `helix-before-*` event and call `preventDefault()`. See
62
75
  [State and control](./state-and-control).
76
+
77
+ To write your own value into a field you type into, set your signal and then
78
+ call `detectChanges()` on the component's `ChangeDetectorRef`:
79
+
80
+ ```ts
81
+ private readonly view = inject(ChangeDetectorRef)
82
+
83
+ protected takeHandle(event: Event): void {
84
+ event.preventDefault()
85
+ const { value } = (event as CustomEvent<{ value: string }>).detail
86
+ this.handle.set(clean(value))
87
+ this.view.detectChanges()
88
+ }
89
+ ```
90
+
91
+ Without zone.js, Angular renders a signal change in a later task. A key that
92
+ arrives before that task lands in the text from before your value, and the keys
93
+ typed in between are lost. A barcode scanner, a password manager, and a test
94
+ send keys that fast. `detectChanges()` renders the change before your handler
95
+ returns.
@@ -13,7 +13,7 @@ support in older browsers. Your browser needs every feature Helix uses.
13
13
 
14
14
  ## What we test
15
15
 
16
- We test in Chromium, through Playwright. The DOM specs run in it, and 83 pixel
16
+ We test in Chromium, through Playwright. The DOM specs run in it, and the pixel
17
17
  baselines are captured in it, on Linux, with the network blocked.
18
18
 
19
19
  We do not run the tests in Firefox or Safari. The rest of this page lists the
@@ -27,9 +27,9 @@ something broken, please report it. Our tests would not have caught it.
27
27
  | Custom elements and shadow DOM | Everything |
28
28
  | `ElementInternals` | Form participation, and ARIA that has to cross a shadow boundary |
29
29
  | Popover API | Every overlay: menu, tooltip, select, combobox, dialog |
30
- | `light-dark()` | 156 declarations in the shipped stylesheets, 71 of them in the token layer |
30
+ | `light-dark()` | Every color token, and each component color that differs between the modes |
31
31
  | `oklch()` | Every color in the system |
32
- | `:has()` | 11 rules, mostly relational styling of slotted content |
32
+ | `:has()` | Relational styling, mostly of slotted content |
33
33
  | `@starting-style` | Entry transitions on overlays |
34
34
  | `:state()` | Every boolean state the library publishes: `open`, `checked`, `selected`, `invalid`, `read`, `has-label`, `settled`, `empty`, `controls` |
35
35
  | `CSS.highlights` | Filter-match highlighting, in `Command` and `Combobox` |
@@ -47,9 +47,10 @@ headless manifest carries the static, so you can find it there.
47
47
  **When a component renders another component, register both.**
48
48
 
49
49
  If you register `acme-combobox` without `acme-listbox`, the panel renders a tag
50
- that is not defined. The browser shows no error or warning. An unregistered
51
- custom element is an inert, unstyled inline box, so the combobox appears not to
52
- open.
50
+ that is not defined. An unregistered custom element is an inert, unstyled inline
51
+ box, so the panel shows every option with no list around them, and typing
52
+ filters nothing. In development, the combobox says so once in the console and
53
+ names the tag to define.
53
54
 
54
55
  [//]: # (renders)
55
56
 
@@ -89,7 +90,8 @@ everything those families render in turn. For example, `command` reaches
89
90
  **The elements you write inside a component are yours to register as well.**
90
91
  A select renders none of the options you write in it, so `acme-select` needs
91
92
  your `acme-option` registered beside it. In development, a component holding a
92
- child of its tier that nobody registered says so once, with the import to add.
93
+ child of its tier that nobody registered says so once and names the tag to
94
+ define.
93
95
  A listbox given `items` and a filter input given a `source` draw options of
94
96
  your tier from data, and those need `acme-option` registered too.
95
97
 
@@ -102,7 +104,7 @@ generated table is always current.
102
104
 
103
105
  ### A `:not(:defined)` rule
104
106
 
105
- `@fusion.dev/helix` ships an `elements.css` that hides each of its tags until
107
+ `@fusion.dev/helix-styles` ships an `elements.css` that hides each of its tags until
106
108
  the tag's definition loads. Your page then does not show unstyled text that
107
109
  changes into components. The headless package ships no such file, and no tool
108
110
  can generate one for tags you have not named yet. Write the rule for your own
@@ -115,26 +117,85 @@ acme-listbox:not(:defined) {
115
117
  }
116
118
  ```
117
119
 
118
- Use `visibility` so the page does not reflow when the definitions arrive.
120
+ The rule hides each element until its definition loads, and the layout
121
+ settles when the elements upgrade.
122
+
123
+ ### A rule that hides the `live` parts
124
+
125
+ A `live` part is a live region. It holds words only a screen reader should
126
+ read, such as how many options a filter left, and the headless tier draws it
127
+ as ordinary text. Hide each one visually with a clip, which keeps it in the
128
+ accessibility tree:
129
+
130
+ ```css
131
+ acme-combobox::part(live),
132
+ acme-combobox::part(listbox-live) {
133
+ block-size: 1px;
134
+ clip-path: inset(50%);
135
+ inline-size: 1px;
136
+ overflow: hidden;
137
+ position: absolute;
138
+ white-space: nowrap;
139
+ }
140
+ ```
141
+
142
+ `display: none` and `visibility: hidden` take a region out of the
143
+ accessibility tree as well, and a screen reader then hears nothing. A
144
+ component forwards the `live` part of a component it renders under a name of
145
+ its own, such as `listbox-live` on Combobox. The styled tier hides every one
146
+ of them.
119
147
 
120
148
  ### Typings for your tags
121
149
 
122
- Your typings come from a manifest of your package. `helix manifest build`, in
123
- `@fusion.dev/helix-cli`, writes one that includes every member your subclasses
124
- inherit from ours. Name each Helix package you extend and where its manifest is:
150
+ Your typings come from a custom elements manifest of your package, written by
151
+ `@custom-elements-manifest/analyzer`:
125
152
 
126
153
  ```sh
127
- helix manifest build . --tier styled \
128
- --dependency @fusion.dev/helix=node_modules/@fusion.dev/helix/custom-elements.json \
129
- --dependency @fusion.dev/helix-headless=node_modules/@fusion.dev/helix-headless/custom-elements.json \
130
- --dependency @fusion.dev/helix-core=node_modules/@fusion.dev/helix-core/custom-elements.json \
131
- > custom-elements.json
154
+ npx @custom-elements-manifest/analyzer analyze --litelement --dependencies \
155
+ --globs "src/**/*.ts"
132
156
  ```
133
157
 
134
- Then run a typings generator over it. Our own typings start from
158
+ Each flag does one job, and the manifest is incomplete without either:
159
+
160
+ - `--dependencies` reads the `custom-elements.json` each Helix package ships
161
+ and names in its `package.json`, so `acme-combobox` gets the members,
162
+ attributes, events, slots and parts it inherits from ours.
163
+ - `--litelement` reads Lit's decorators, so the properties you declare with
164
+ `@property` are attributes too.
165
+
166
+ Then run a typings generator over the manifest. Our own typings start from
135
167
  `@wc-toolkit/jsx-types` and `@wc-toolkit/vuejs-types`. The typings for
136
- `acme-combobox` then include the hundred or so members it inherits, as well as
137
- the two you wrote.
168
+ `acme-combobox` then include the members it inherits, as well as the ones you
169
+ wrote.
170
+
171
+ The generated file names Helix's event classes, such as
172
+ `HelixBeforeChangeEvent`, without importing them. Add one line at its top that
173
+ imports the names TypeScript reports as missing:
174
+
175
+ ```ts
176
+ import type { HelixBeforeChangeEvent, HelixFilterEvent } from '@fusion.dev/helix-core'
177
+ ```
178
+
179
+ Without that line, a project that sets `skipLibCheck` gets no error, and each
180
+ of those event handlers is typed `any`.
181
+
182
+ For a few tags in React, you can skip the generator. Each headless family
183
+ publishes its props in `@fusion.dev/helix-headless/jsx`, and your tag takes
184
+ them:
185
+
186
+ ```ts
187
+ import type { HelixHeadlessComboboxProps } from '@fusion.dev/helix-headless/jsx'
188
+
189
+ declare module 'react' {
190
+ namespace JSX {
191
+ interface IntrinsicElements {
192
+ 'acme-combobox': HelixHeadlessComboboxProps
193
+ }
194
+ }
195
+ }
196
+ ```
197
+
198
+ A subclass that adds properties joins its own to them with `&`.
138
199
 
139
200
  `@fusion.dev/helix-cem`, the step that runs those generators for our packages
140
201
  and corrects what they write, is not published. It is build tooling for this
@@ -202,10 +263,12 @@ findings as data. The rules check what your components document and write:
202
263
 
203
264
  ### Tree-shaking
204
265
 
205
- `@fusion.dev/helix-headless` declares `sideEffects: ["./dist/**/*.js"]`. The
206
- declaration is accurate, because the modules register elements when you import
207
- them. A bundler that treated them as free of side effects would drop a
208
- registration, and a tag would never upgrade.
266
+ `@fusion.dev/helix-headless` declares `"sideEffects": true`, because its
267
+ modules register elements when you import them. A bundler that treated them as
268
+ free of side effects would drop a registration, and a tag would never upgrade.
269
+ Import each family from its own entry, such as
270
+ `@fusion.dev/helix-headless/combobox`, so your bundle carries only the families
271
+ you use.
209
272
 
210
273
  ### Documented parts
211
274
 
@@ -169,6 +169,56 @@ bar there, and a heatmap draws the cell with no tone. The readings after the gap
169
169
  stay under their own labels, and the tooltip for that row still shows the
170
170
  row's cells.
171
171
 
172
+ A cell holds a number when its whole text reads as one, such as `1200` or
173
+ `-3.5`. A formatted number, such as `1,200` or `$1.2k`, is a gap. To chart a
174
+ formatted cell, write its number in `data-value`:
175
+
176
+ ```html
177
+ <tr><th>Q1</th><td data-value="1200">$1.2k</td><td data-value="800">$800</td></tr>
178
+ ```
179
+
180
+ The chart reads `data-value` when a cell has one, and the cell's text otherwise.
181
+ The table and the tooltip still show `$1.2k`.
182
+
183
+ ## Charts with many readings
184
+
185
+ A chart builds what it can show, and that is bounded by its size. The readings
186
+ themselves are read again each time the table or the data changes.
187
+
188
+ - **Names on the axis.** `Line Chart`, `Area Chart`, and `Bar Chart` draw every
189
+ reading's name where every name fits. Where they do not, the chart draws a
190
+ regular share of them, and always the first and the last. Where every row
191
+ header holds a `<time>`, the names drawn start a period, such as the first
192
+ reading of each month or each year. Where the names are numbers in order,
193
+ such as years, they are round numbers. Other names are drawn every so many
194
+ readings, counted from the first. The chart works out the share again when it
195
+ is resized. The tooltip and the keys still say each reading's own name.
196
+ - **Gridlines.** `gridlines="category"` rules the readings whose names are
197
+ drawn.
198
+ - **Lines.** Where a line has more readings than the plot has pixels across, it
199
+ is drawn through the points those pixels can show: in each column of pixels,
200
+ the first reading, the lowest, the highest, and the last.
201
+ - **Markers.** `markers` marks every reading, with one path for each series.
202
+ - **Columns.** `Bar Chart` draws each column as an element while every column
203
+ and the space between two categories are at least a pixel wide. Past that, it
204
+ draws each series' columns as one path, each column of pixels as tall as the
205
+ tallest column in it, and draws the category the tooltip describes again over
206
+ them, at least two pixels thick.
207
+ - **Points.** `Scatter Chart` draws each series' points as paths. Points that
208
+ overlap are drawn in separate groups, up to four deep, so each keeps its edge
209
+ and a cluster reads darker. The point the keys rest on is drawn again over
210
+ them.
211
+ - **The table drawn from `data`.** Past a few hundred readings, the hidden table
212
+ holds one page of them at a time. Its markup gives a screen reader the number
213
+ of rows in the whole table and the place of each row, and the words after it
214
+ say which readings the page holds. Two buttons after it show the previous and
215
+ the next page. You press them with a screen reader as you read. They are left
216
+ out of the tab order, so the plot stays the chart's one tab stop. The keys on
217
+ the plot move through every reading.
218
+ - **A table on the page.** A chart reads the whole table that `for` names, and
219
+ the table is part of your page, however many rows it has. Where the readings
220
+ are too many to put in the document, draw the chart from `data`.
221
+
172
222
  ## Series colors
173
223
 
174
224
  Helix has twenty-four series color tokens, in order, from
@@ -163,7 +163,8 @@ agent uses most:
163
163
  `helix scan`, `helix diagnostics --once`, and `helix theme audit` check what an
164
164
  agent wrote, and the MCP server runs the same checks.
165
165
  [Using Helix with an agent](./using-helix-with-an-agent#check-the-code-you-wrote)
166
- lists what each finds.
166
+ lists what each finds, and [Diagnostics](./diagnostics) sets the checks up in
167
+ your app and in CI.
167
168
 
168
169
  ## The index
169
170
 
@@ -22,8 +22,8 @@ class, or a second copy of any value.
22
22
  Each token is written once, with its light and dark values in `light-dark()`.
23
23
  The `color-scheme` property alone decides which value applies. `color-scheme`
24
24
  inherits, so one property sets the mode for an element and everything inside
25
- it. The shipped stylesheets use `light-dark()` in 156 declarations, 71 of them
26
- in the token layer.
25
+ it. A component that sets a color of its own for each mode uses `light-dark()`
26
+ the same way.
27
27
 
28
28
  ## Set the mode
29
29
 
package/content/color.mdx CHANGED
@@ -34,11 +34,20 @@ Choose a surface by where the element sits relative to the page. This is the
34
34
  choice people most often get wrong. See [Surfaces and
35
35
  separation](./surfaces-and-separation).
36
36
 
37
+ A field is filled with `well`: 5% of `foreground` laid over whatever the field
38
+ sits on, so its color depends on the surface under the field.
39
+
37
40
  ### Inks
38
41
 
39
42
  Inks are the colors that go on grounds. The base ink is `foreground`.
40
43
 
41
- - `foreground-muted` and `foreground-subtle` are for text that recedes.
44
+ - `foreground-muted` is for text that recedes and is still read, such as a
45
+ placeholder, a hint, or a caption. The engine solves it to the contrast
46
+ target.
47
+ - `foreground-subtle` recedes further. It is for marks that are not text, such
48
+ as a separator, a chevron, or a grip, and for the text of a disabled control.
49
+ It is a fixed step of the gray ramp, and the engine does not solve it, so its
50
+ contrast is not guaranteed. Use `foreground-muted` for text that is read.
42
51
  - Text on a filled color takes the ink named for that fill:
43
52
  - `foreground-on-primary` on `primary-surface`
44
53
  - `foreground-on-accent` on `accent-surface`
@@ -52,8 +61,16 @@ Intent colors show what something means.
52
61
  `-subtle` variants. With no brand color set, both are ink: near-black in
53
62
  light mode and near-white in dark mode.
54
63
  - `info`, `success`, `warning`, and `danger` are for status. Each has a
55
- `-surface` and a `-surface-hover` variant for the tinted box that holds the
56
- message.
64
+ `-surface` variant, the filled ground that carries `foreground-on-status`,
65
+ such as a danger button or a badge in a status tone, and a `-surface-hover`
66
+ variant for that fill under the pointer.
67
+
68
+ ### Rise and fall
69
+
70
+ `rise` and `fall` color a value by which way it moved, such as a price. They
71
+ are `success` and `danger` unless you set them. Mainland China, Taiwan, and
72
+ Japan draw a rise in red, so a page for one of those markets swaps the two. See
73
+ [Rise and fall colors](./localization#rise-and-fall-colors).
57
74
 
58
75
  ### Series
59
76
 
@@ -98,8 +115,21 @@ OKLCH lets the theme engine *solve* for a palette from your brand color. See
98
115
 
99
116
  The engine measures each ink against the ground it will be painted on. A brand
100
117
  color that already meets the target there is used as it is. For any other color,
101
- the engine searches for the lightness that meets the target. Every pair it
102
- produces meets the target.
118
+ the engine searches for the lightness that meets the target. A pair that cannot
119
+ meet its target, such as one with a color you pinned, is reported in
120
+ `violations`.
121
+
122
+ `foreground` and `foreground-muted` are also written in fields, so the engine
123
+ solves them against the well over the page, a card, a raised surface, a dialog,
124
+ and a sunken surface. The report names each of these grounds as the well over
125
+ its surface, such as `color-well over color-surface`.
126
+
127
+ `foreground-subtle` is the one ink the engine does not solve. The range of
128
+ readable grays has room for one quiet ink, and `foreground-muted` is that ink, so
129
+ `foreground-subtle` stays a fixed step of the stylesheet's gray ramp and its
130
+ contrast is not guaranteed. A component paints its quiet text in
131
+ `foreground-muted`, and keeps `foreground-subtle` for marks that are not text and
132
+ for the text of a disabled control.
103
133
 
104
134
  The engine can solve only the pairs it knows about, so we check two things:
105
135