@fusion.dev/helix-docs 0.104.1 → 0.105.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 (48) hide show
  1. package/content/accessibility.mdx +92 -71
  2. package/content/angular.mdx +26 -24
  3. package/content/browser-support.mdx +61 -58
  4. package/content/building-on-the-headless-tier.mdx +100 -74
  5. package/content/charting.mdx +98 -79
  6. package/content/coding-agents.mdx +87 -69
  7. package/content/color-modes.mdx +40 -37
  8. package/content/color.mdx +67 -54
  9. package/content/composition.mdx +85 -83
  10. package/content/elevation-and-material.mdx +32 -29
  11. package/content/events.mdx +77 -65
  12. package/content/forms.mdx +69 -58
  13. package/content/iconography.mdx +75 -71
  14. package/content/installation.mdx +77 -68
  15. package/content/intent-reference.mdx +59 -54
  16. package/content/introduction.mdx +25 -23
  17. package/content/localization.mdx +122 -82
  18. package/content/maps.mdx +222 -0
  19. package/content/metrics.mdx +294 -297
  20. package/content/motion.mdx +44 -36
  21. package/content/other-frameworks.mdx +33 -31
  22. package/content/overlays.mdx +72 -54
  23. package/content/overriding-a-component.mdx +85 -74
  24. package/content/react.mdx +72 -64
  25. package/content/recipes.mdx +64 -47
  26. package/content/routing.mdx +100 -93
  27. package/content/sandbox.mdx +39 -39
  28. package/content/server-rendering.mdx +95 -84
  29. package/content/shape-and-borders.mdx +44 -37
  30. package/content/size-and-density.mdx +51 -46
  31. package/content/space-and-size.mdx +41 -34
  32. package/content/state-and-control.mdx +63 -51
  33. package/content/surfaces-and-separation.mdx +55 -53
  34. package/content/svelte.mdx +20 -19
  35. package/content/theme-builder.mdx +42 -31
  36. package/content/theming.mdx +58 -51
  37. package/content/tiers.mdx +69 -61
  38. package/content/tokens.mdx +72 -53
  39. package/content/typography.mdx +47 -47
  40. package/content/using-helix-with-an-agent.mdx +147 -115
  41. package/content/using-the-elements.mdx +55 -50
  42. package/content/versioning.mdx +42 -35
  43. package/content/visual-axes.mdx +168 -146
  44. package/content/vue.mdx +24 -24
  45. package/content/without-a-bundler.mdx +40 -23
  46. package/dist/pages.generated.js +49 -43
  47. package/dist/sources.generated.js +3337 -2680
  48. package/package.json +3 -3
@@ -1,64 +1,68 @@
1
1
  ---
2
- description: Families, parts and groups, and which to reach for.
2
+ description: How Helix is organized into families, parts, and groups, and which one to use.
3
3
  section: Essentials
4
4
  title: Composition
5
5
  ---
6
6
 
7
7
  # Composition
8
8
 
9
- Helix has families, parts and groups, and they are not stylistic choices. Each
10
- answers a different question, and knowing which you are looking at tells you
11
- what to import and what to write.
9
+ Helix components come in families, parts, and groups. Each kind has a different
10
+ purpose. When you know which kind you are looking at, you know what to import
11
+ and what to write.
12
12
 
13
- ## Namespaces mean composition
13
+ ## A namespace means one component goes inside another
14
14
 
15
- `Tabs.Root` contains `Tabs.Tab`. The namespace says *this goes inside that*.
15
+ `Tabs.Root` contains `Tabs.Tab`. A namespace means the part goes inside the
16
+ family's root.
16
17
 
17
- It never says *this is a version of that*. An icon-only button is not a part of
18
- `Button`, so `IconButton` is top-level. The rule falls out of one line:
18
+ A namespace never marks a version of a component. An icon-only button is its
19
+ own component, so `IconButton` is top-level. One rule decides this:
19
20
 
20
21
  > Variants are props. Alternatives are separate components.
21
22
 
22
- A button that is filled instead of outlined is the same component with a
23
- different `emphasis`. A button that holds only an icon has a different
24
- accessible-name requirement and a different shape, so it is its own component.
23
+ A filled button and an outlined button are the same component with a different
24
+ `emphasis`. A button that holds only an icon has different requirements for its
25
+ accessible name and a different shape, so it is its own component.
25
26
 
26
- ## When a region becomes an element
27
+ ## When a region is an element and when it is a slot
27
28
 
28
- When it has behavior, state or API. Otherwise it is a slot.
29
+ A region is an element when it has behavior, state, or an API. Otherwise it is a
30
+ slot.
29
31
 
30
- `Card`'s header, body and footer carried none of the three, so five elements
31
- became one and a title became a real `<h2>` the consumer writes. `Tabs.Tab`,
32
- `Select.Option` and `Accordion.Item` do carry state, so they stay elements.
32
+ `Card`'s header, body, and footer had none of the three. Five elements became
33
+ one, and a title became a real `<h2>` that you write. `Tabs.Tab`,
34
+ `Select.Option`, and `Accordion.Item` have state, so they stay elements.
33
35
 
34
- **An empty region still costs layout.** A `<slot>` is `display: contents`, so an unassigned one generates no
35
- box: no grid row, no flex item, no gap. A wrapper generates one either way.
36
- Measured on Card with identical content: wrappers present but empty rendered
37
- 85px against an ideal of 53px. Bare slots rendered 53px with no hiding logic at
38
- all.
36
+ An empty wrapper element still takes up layout space. A `<slot>` is
37
+ `display: contents`, so an unassigned slot generates no box. It adds no grid
38
+ row, no flex item, and no gap. A wrapper generates a box either way. We measured
39
+ `Card` with identical content. With empty wrappers present, it rendered at 85px,
40
+ and the ideal is 53px. With bare slots, it rendered at 53px with no hiding logic
41
+ at all.
39
42
 
40
- ## When a group is a part
43
+ ## When a group is a part of a family
41
44
 
42
- The test is whether the group reports a form value.
45
+ A group is a part of a family when it reports a form value.
43
46
 
44
- **`Radio.Group` and `Toggle.Group` do.** They own the `name`, the `value` and
45
- the validity, and a lone radio is not a control at all: it is an answer to one.
46
- The group is what the family is *about*, so it is a part of it, and the member
47
- has nowhere else to be.
47
+ `Radio.Group` and `Toggle.Group` report a form value. They own the `name`, the
48
+ `value`, and the validity. A single radio is an answer, and the group is the
49
+ control it answers. The family is about the group, so the group is a part of the
50
+ family, and the member belongs nowhere else.
48
51
 
49
- **`Checkbox Group` and `Button Group` do not.** Neither calls `setFormValue`.
50
- Each checkbox is form-associated and submits itself; the group adds
51
- `role="group"`, a shared name, and one place to read the set. A button needs no
52
- group at all. Both are their own families, because their members are complete
53
- controls that the family does not need the group to explain.
52
+ `Checkbox Group` and `Button Group` do not report a form value. Neither calls
53
+ `setFormValue`. Each checkbox is form-associated and submits itself. The group
54
+ adds `role="group"`, a shared name, and one place to read the set. A button
55
+ needs no group at all. Both groups are their own families, because their members
56
+ are complete controls without the group.
54
57
 
55
- A container holding mixed content is a family for the same reason. `Toolbar`
56
- holds buttons, dropdowns and inputs, and is about none of them.
58
+ A container that holds mixed content is its own family for the same reason.
59
+ `Toolbar` holds buttons, dropdowns, and inputs, so it is not a part of any of
60
+ their families.
57
61
 
58
- ## A content property is its slot's fallback
62
+ ## A content property is the fallback for its slot
59
63
 
60
- Where a property carries content rather than configuration, it is the fallback
61
- content of a slot of the same name, never a second thing rendered beside it.
64
+ A property that holds content, such as a tooltip's text, is the fallback content
65
+ for a slot of the same name. Helix never renders both.
62
66
 
63
67
  ```html
64
68
  <!-- The property -->
@@ -70,21 +74,21 @@ content of a slot of the same name, never a second thing rendered beside it.
70
74
  </helix-tooltip>
71
75
  ```
72
76
 
73
- Text stays an attribute, because that is what most tooltips and most avatars
74
- hold. Anything richer replaces it by being slotted. Both can never show at once,
75
- and no component has to detect which you did.
77
+ Text stays an attribute, because most tooltips and most avatars hold only text.
78
+ To use richer content, put it in the slot, and it replaces the attribute. The
79
+ two never show at once, so no component has to check which one you used.
76
80
 
77
- One trap: **a whitespace-only text node is assigned to the default slot and
78
- defeats its fallback.** A newline between the tags is enough. That is
79
- why some examples close on the same line they open.
81
+ A text node that holds only whitespace is assigned to the default slot and hides
82
+ its fallback. A newline between the tags is enough. For this reason, some
83
+ examples put the closing tag on the same line as the opening tag.
80
84
 
81
- ## Width comes from the layout
85
+ ## Set a button's width with the layout
82
86
 
83
- There is no `width` or `fill` property on a button, for the same reason there is
84
- no `size` one: how wide a control is says something about the region it is in.
87
+ A button has no `width` or `fill` property, for the same reason it has no `size`
88
+ property. How wide a control is depends on the region it is in.
85
89
 
86
- A button already stretches in a column flex or a grid, because that is what a
87
- stretch-aligned parent does to its children:
90
+ A button already stretches in a column flex or a grid, because a stretch-aligned
91
+ parent stretches its children:
88
92
 
89
93
  ```html
90
94
  <helix-flex direction="column">
@@ -95,7 +99,7 @@ stretch-aligned parent does to its children:
95
99
  </helix-flex>
96
100
  ```
97
101
 
98
- To make one hug its label instead, tell the layout:
102
+ To make a button only as wide as its label, set that on the layout:
99
103
 
100
104
  ```css
101
105
  .hug {
@@ -107,13 +111,13 @@ To make one hug its label instead, tell the layout:
107
111
  <helix-button class="hug">Hugs its label</helix-button>
108
112
  ```
109
113
 
110
- The default surprises people: dropping a button into a column makes it full
111
- width, and the instinct is to look for a property that turns that off.
114
+ This default can surprise you. A button in a column is full width, and you might
115
+ look for a property to turn that off. The layout controls it.
112
116
 
113
- **A fixed width is a different question, and it belongs to your site.** A
114
- marketing page whose hero buttons are all the same width is a real and reusable
115
- practice, but the number is a fact about that page's design, not about buttons.
116
- Name it once where your other page constants live:
117
+ **A fixed width belongs to your site's styles.** A marketing page whose hero
118
+ buttons all share one width is a common and reusable practice. The number is
119
+ part of that page's design, so it does not belong to the button component. Name
120
+ it once, where your other page constants are defined:
117
121
 
118
122
  ```css
119
123
  :root {
@@ -125,12 +129,12 @@ Name it once where your other page constants live:
125
129
  }
126
130
  ```
127
131
 
128
- The same reasoning puts a hero's type size outside the component scale: a
129
- marketing hero is not a component, so its measurements are the site's own.
132
+ For the same reason, a hero's type size is outside the component scale. A
133
+ marketing hero is not a component, so its measurements belong to your site.
130
134
 
131
- ## One property per intent
135
+ ## Name a field control with `label` or `aria-label`
132
136
 
133
- Every field control names itself the same way, and there are exactly two cases.
137
+ Every field control is named the same way, and there are two cases:
134
138
 
135
139
  ```html
136
140
  <helix-input label="Files"></helix-input> <!-- a visible label -->
@@ -138,34 +142,32 @@ Every field control names itself the same way, and there are exactly two cases.
138
142
  <helix-input></helix-input> <!-- unnamed. avoid -->
139
143
  ```
140
144
 
141
- `label` means a label. `aria-label` means a name. Neither means a control a
142
- screen reader announces as "edit text", which is almost never what you want.
143
- If the page already makes the purpose plain, that is the `aria-label` case, not
144
- the empty case.
145
+ `label` gives the control a visible label. `aria-label` gives it a name and
146
+ draws nothing. With neither, a screen reader announces the control as "edit
147
+ text", which is almost never what you want. If the page already makes the
148
+ purpose plain, use `aria-label`. Do not leave the control unnamed.
145
149
 
146
- **`aria-label` works here because the component forwards it**, which is not true
147
- of custom elements in general. A custom element
148
- cannot delegate a label to the control inside its shadow root: an `aria-label`
149
- you write on the host sits on a wrapper and never reaches the `<input>` that
150
- actually takes focus. Measured on this library before it was fixed, the inner
151
- input came back with no label and no name at all.
150
+ `aria-label` works on a Helix field because the component forwards it. Most
151
+ custom elements do not. A custom element cannot delegate a label to the control
152
+ inside its shadow root. An `aria-label` you write on the host stays on a wrapper
153
+ and never reaches the `<input>` that takes focus. We measured this in Helix
154
+ before we fixed it: the inner input had no label and no name at all.
152
155
 
153
- So the field renders the label and clips it, and the name arrives through the
154
- same `<label for>` a visible one does.
156
+ The field renders the label and clips it. The name then reaches the input
157
+ through the same `<label for>` that a visible label uses.
155
158
 
156
- There used to be a `hide-label` boolean beside `label`. It is gone: one
157
- attribute supplying a label and a second saying not to show it is the absence of
158
- the first, spelled twice.
159
+ `hide-label` was a boolean beside `label`, and we removed it. Setting a label
160
+ and then a second attribute to hide it is the same as having no visible label.
159
161
 
160
- ## Shared behavior becomes its own family
162
+ ## Shared behavior is its own family
161
163
 
162
- `Option`, `Listbox` and `Select` are three families rather than one because the
163
- keyboard, the filtering, the multi-select rules and the active-item tracking
164
- should be written once. `Select` renders a `Listbox` internally; a future
165
- combobox will; a command palette would.
164
+ `Option`, `Listbox`, and `Select` are three families so that the keyboard
165
+ handling, the filtering, the multi-select rules, and the active-item tracking
166
+ are written once. `Select` renders a `Listbox` internally. A future combobox
167
+ will too, and so would a command palette.
166
168
 
167
- The same reasoning made `DrilldownController`: `Menu` had held a page stack and
168
- derived its rows since it was written, and none of that was about menus. `Menu`
169
- and `Drilldown` share it now.
169
+ For the same reason, we wrote `DrilldownController`. Since it was first written,
170
+ `Menu` had held a page stack and derived its rows, and none of that code
171
+ was specific to menus. `Menu` and `Drilldown` now share it.
170
172
 
171
- If you are about to write the second copy of something, that is the signal.
173
+ If you are about to write a second copy of some behavior, make it shared.
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Five layers, four shadows, and the glass the material tokens make possible.
2
+ description: The layer, shadow, and material tokens for stacking, lifting, and translucent surfaces.
3
3
  section: Foundations
4
4
  title: Elevation and material
5
5
  ---
@@ -21,9 +21,9 @@ title: Elevation and material
21
21
 
22
22
  [//]: # (/tokens)
23
23
 
24
- Five, named after what lives at each rather than numbered. A numbered scale
25
- invites `z-index: 1001`, and the number nobody can justify is how stacking
26
- context bugs start.
24
+ The five main layers are named after what sits at each one. A numbered scale
25
+ leads to values such as `z-index: 1001` that nobody can explain, and those
26
+ values cause stacking context bugs.
27
27
 
28
28
  | Layer | What is at it |
29
29
  |---|---|
@@ -33,10 +33,13 @@ context bugs start.
33
33
  | `overlay` | Menus, dialogs, popovers, tooltips |
34
34
  | `toast` | Above everything, including a modal |
35
35
 
36
- **Most overlays do not need these**, because they use the popover API and render
37
- in the browser's top layer, which is above every z-index by construction. The
38
- tokens are for the cases the top layer does not cover: your own sticky header,
39
- your own dragged element, and anything that has to sit between two of ours.
36
+ **Most overlays do not need these tokens.** They use the popover API and render
37
+ in the browser's top layer, which is always above every z-index. Use the tokens
38
+ for the cases the top layer does not cover:
39
+
40
+ - your own sticky header
41
+ - your own dragged element
42
+ - anything that has to sit between two of our layers
40
43
 
41
44
  ## Shadows
42
45
 
@@ -53,41 +56,41 @@ your own dragged element, and anything that has to sit between two of ours.
53
56
 
54
57
  [//]: # (/tokens)
55
58
 
56
- The first four are the values. The last two are the *decision*, and they are
57
- what a theme sets to make the system shadow-first or border-first. See [Surfaces
58
- and separation](./surfaces-and-separation).
59
+ The first four are the values. The last two are policy tokens. A theme sets
60
+ them to make your interface shadow-first or border-first. See [Surfaces and
61
+ separation](./surfaces-and-separation).
59
62
 
60
63
  ## Material
61
64
 
62
- Eight tokens describing translucency rather than color:
65
+ Eight tokens describe translucency:
63
66
 
64
67
  [//]: # (tokens:material)
65
68
 
66
69
  | Token | Value | For |
67
70
  | --- | --- | --- |
68
- | `--helix-material-backdrop` | `blur(var(--helix-material-blur-md)) saturate(1.5)` | The wash behind a modal. |
71
+ | `--helix-material-backdrop` | `blur(var(--helix-material-blur-md)) saturate(1.5)` | The tint behind a modal. |
69
72
  | `--helix-material-opacity` | `1` | How opaque a translucent surface is. |
70
73
  | `--helix-material-blur-sm` | `8px` | The lightest backdrop blur. |
71
- | `--helix-material-blur-md` | `16px` | The backdrop blur a modal takes. |
74
+ | `--helix-material-blur-md` | `16px` | The backdrop blur behind a modal. |
72
75
  | `--helix-material-blur-lg` | `32px` | The heaviest backdrop blur. |
73
- | `--helix-material-translucent` | `color-mix(in oklab, light-dark(oklch(1 0 0), oklch(0.182 var(--helix-seed-neutral-chroma, 0) var(--helix-seed-neutral-hue, 258))) 30%, transparent)` | The translucent ground, for a surface that admits what is behind it. |
74
- | `--helix-material-translucent-strong` | `color-mix(in oklab, light-dark(oklch(1 0 0), oklch(0.182 var(--helix-seed-neutral-chroma, 0) var(--helix-seed-neutral-hue, 258))) 88%, transparent)` | The heavier translucent ground, for one that admits less. |
75
- | `--helix-material-border` | `light-dark(color-mix(in oklab, oklch(0.15 var(--helix-seed-neutral-chroma, 0) var(--helix-seed-neutral-hue, 258)) 14%, transparent), color-mix(in oklab, oklch(1 0 0) 12%, transparent))` | The edge a translucent surface needs to have any at all. |
76
+ | `--helix-material-translucent` | `color-mix(in oklab, light-dark(oklch(1 0 0), oklch(0.182 var(--helix-seed-neutral-chroma, 0) var(--helix-seed-neutral-hue, 258))) 30%, transparent)` | The translucent background, for a surface that shows what is behind it. |
77
+ | `--helix-material-translucent-strong` | `color-mix(in oklab, light-dark(oklch(1 0 0), oklch(0.182 var(--helix-seed-neutral-chroma, 0) var(--helix-seed-neutral-hue, 258))) 88%, transparent)` | The stronger translucent background, for a surface that shows less of what is behind it. |
78
+ | `--helix-material-border` | `light-dark(color-mix(in oklab, oklch(0.15 var(--helix-seed-neutral-chroma, 0) var(--helix-seed-neutral-hue, 258)) 14%, transparent), color-mix(in oklab, oklch(1 0 0) 12%, transparent))` | The border a translucent surface needs to keep a visible edge. |
76
79
 
77
80
  [//]: # (/tokens)
78
81
 
79
- These are what the `material` axis renders: `frosted`, `glass` and `transparent`
80
- are three points of one optical language, and they are an axis of their own
81
- rather than values of `emphasis`, because a panel can be glass *and* outlined
82
- and two things that can be true at once are two axes. See [Visual
82
+ The `material` axis draws its three values, `frosted`, `glass`, and
83
+ `transparent`, with these tokens. Material is an axis of its own, separate from
84
+ `emphasis`, because a panel can be glass and outlined at the same time. Two
85
+ properties that can apply at once belong on two axes. See [Visual
83
86
  axes](./visual-axes).
84
87
 
85
- **Blur is expensive and is the one thing here worth being deliberate about.** A
86
- `backdrop-filter` forces the compositor to re-read what is behind the element on
87
- every frame it moves. A frosted header that is always on screen pays that
88
- continuously. Reach for `translucent` without the blur when the effect is about
89
- depth rather than about the texture.
88
+ **Use blur with care, because it is expensive.** A `backdrop-filter` makes the
89
+ compositor read what is behind the element again on every frame the element
90
+ moves. A frosted header that is always on screen pays that cost all the time.
91
+ When you want depth without the frosted texture, use `translucent` without the
92
+ blur.
90
93
 
91
- **`material-border` is not decoration.** A translucent surface takes its color
92
- from what is behind it, so on a busy background it has no edge at all. The
93
- border token is what keeps it a shape.
94
+ A translucent surface takes its color from what is behind it, so on a busy
95
+ background it has no visible edge. `material-border` gives it one, so it keeps
96
+ its shape.
@@ -1,12 +1,13 @@
1
1
  ---
2
- description: The request and the notification, why one name is prefixed and the other is not.
2
+ description: The two events each state change sends, and why one name has a prefix and the other does not.
3
3
  section: Essentials
4
4
  title: Events
5
5
  ---
6
6
 
7
7
  # Events
8
8
 
9
- **Every state change is announced twice, and the two are for different people.**
9
+ Each state change sends two events. The first is a request that you can cancel.
10
+ The second is a notification that the change happened.
10
11
 
11
12
  ```ts
12
13
  // The request. Cancelable, carries what is being proposed, and fires before
@@ -24,59 +25,67 @@ element.addEventListener('change', (event) => {
24
25
  })
25
26
  ```
26
27
 
27
- Most consumers want the second. Bind `onChange` in React or `onchange=` in HTML
28
- and read the target, exactly as you would from an `<input>`. Reach for the
29
- prefixed one when you mean to refuse.
28
+ Most of the time you need the notification. Bind `onChange` in React or
29
+ `onchange=` in HTML and read the target, as you would for an `<input>`. Use the
30
+ prefixed event when you want to cancel the change.
30
31
 
31
- The class exists rather than a bare `CustomEvent` for three reasons, all about
32
- the type reaching you: the class name is in the manifest, which is what lets the
33
- framework typings emit a typed callback instead of `(detail: unknown)`; the
34
- package augments `HTMLElementEventMap`, which types `addEventListener` for
35
- people writing plain HTML; and the name lives on the class, so a dispatch and a
36
- listener cannot disagree about the string.
32
+ Each event has its own class. The class gets the event's type to you in three
33
+ ways:
37
34
 
38
- ## Why the request is prefixed and the notification is not
35
+ - The class name is in the manifest, so the framework typings can emit a typed
36
+ callback. A bare `CustomEvent` would give `(detail: unknown)`.
37
+ - The package augments `HTMLElementEventMap`, which types `addEventListener`
38
+ when you write plain HTML.
39
+ - The name is defined on the class, so a dispatch and a listener always use the
40
+ same string.
39
41
 
40
- **Retargeting.** An event dispatched inside a shadow root is retargeted to look
41
- like it came from the host, so a bare `input` from a `<helix-input>` is
42
- indistinguishable from the inner control's own. Prefixed, it cannot be mistaken
43
- for either.
42
+ ## Why the request has a prefix and the notification does not
44
43
 
45
- **React's claimed verbs.** React's synthetic system owns `change`, `input`,
46
- `submit`, `copy`, `focus` and the rest, and a prop matching one of those is
47
- wired to the native event instead. `onhelix-before-change` matches none of them
48
- and reaches us, which is exactly what a request needs.
44
+ ### Retargeting
49
45
 
50
- **And the notification wants the opposite.** It is not prefixed precisely
51
- because React claims the verb: `onChange` on a Helix element goes through the
52
- synthetic system to the native `change` the element sends, which is why the
53
- ordinary path costs a consumer nothing to learn.
46
+ An event dispatched inside a shadow root is retargeted so that it appears to
47
+ come from the host. A bare `input` from a `<helix-input>` would look the same
48
+ as the `input` from the control inside it. A prefixed event cannot be mistaken
49
+ for either one.
54
50
 
55
- **The type map is global.** `HTMLElementEventMap` is shared with every element
56
- in your application. Our classes extend `Event`, so an entry named `select`
57
- would not fail to compile: it would silently retype
58
- `textarea.addEventListener('select', …)` as carrying our detail, which at
59
- runtime is `undefined`. Measured. An unprefixed name is not a collision we would
60
- notice, it is one you would.
51
+ ### React's event names
61
52
 
62
- ## Propagation follows the event's role
53
+ React's synthetic event system handles `change`, `input`, `submit`, `copy`,
54
+ `focus`, and its other event names. A prop that matches one of those names is
55
+ connected to the native event of that name. `onhelix-before-change` matches none
56
+ of them, so it reaches our event. A request needs exactly that.
63
57
 
64
- **Lifecycle does not bubble and is not composed.** `helix-before-open` and
65
- `helix-before-close`. Overlays nest, and an inner one's lifecycle must never reach the
66
- outer one that contains it: a menu closing inside a dialog is not the dialog
67
- closing.
58
+ ### The notification uses the native name
68
59
 
69
- **Coordination bubbles and is composed.** `helix-select`, `helix-dismiss`,
70
- `helix-before-change`. A parent, or a router, can intercept one across a shadow
71
- boundary, which is what makes a `Tree` usable as navigation.
60
+ The notification has no prefix for the same reason. `onChange` on a Helix
61
+ element goes through React's synthetic system to the native `change` event the
62
+ element sends. You use it the way you already use it on any input.
72
63
 
73
- If you are listening on a container and hearing nothing, the event is probably a
74
- lifecycle one and the listener belongs on the element itself.
64
+ ### The type map is global
75
65
 
76
- ## Canceling
66
+ `HTMLElementEventMap` is shared by every element in your application. Our event
67
+ classes extend `Event`, so an entry named `select` would still compile. It would
68
+ silently retype `textarea.addEventListener('select', …)` as carrying our detail,
69
+ which is `undefined` at runtime. We measured this. We would not see a collision
70
+ from an unprefixed name, and you would.
77
71
 
78
- The events that precede a state change are cancelable, and that is the only way
79
- to refuse one:
72
+ ## Which events bubble
73
+
74
+ - **Lifecycle events** do not bubble and are not composed. These are
75
+ `helix-before-open` and `helix-before-close`. Overlays nest, and an inner
76
+ overlay's lifecycle must never reach the outer overlay that contains it. A
77
+ menu that closes inside a dialog does not close the dialog.
78
+ - **Coordination events** bubble and are composed. These are `helix-select`,
79
+ `helix-dismiss`, and `helix-before-change`. A parent or a router can intercept
80
+ them across a shadow boundary, so you can use a `Tree` as navigation.
81
+
82
+ If you listen on a container and receive nothing, the event is probably a
83
+ lifecycle event. Add the listener to the element itself.
84
+
85
+ ## Cancel a change
86
+
87
+ You can cancel the events sent before a state change. Canceling is the only way
88
+ to stop the change:
80
89
 
81
90
  ```ts
82
91
  dialog.addEventListener('helix-before-close', (event) => {
@@ -86,18 +95,18 @@ dialog.addEventListener('helix-before-close', (event) => {
86
95
  })
87
96
  ```
88
97
 
89
- Every dispatched event is held to a declaration and every declaration to a
90
- dispatch, so an event you can listen for is an event that is documented.
98
+ Every dispatched event is checked against a declaration, and every declaration
99
+ against a dispatch, so every event you can listen for is documented.
91
100
 
92
- ## The set
101
+ ## Helix events
93
102
 
94
- | Event | Roughly |
103
+ | Event | Meaning |
95
104
  |---|---|
96
- | `helix-before-change` | A value about to move, refusable |
97
- | `helix-before-toggle` | A checked state about to flip, refusable |
98
- | `helix-before-open`, `helix-before-close` | An open state about to move, refusable |
99
- | `helix-select` | An item chosen from a set |
100
- | `helix-dismiss` | The reader asked for something to go away |
105
+ | `helix-before-change` | A value is about to change, cancelable |
106
+ | `helix-before-toggle` | A checked state is about to flip, cancelable |
107
+ | `helix-before-open`, `helix-before-close` | An open state is about to change, cancelable |
108
+ | `helix-select` | An item was chosen from a set |
109
+ | `helix-dismiss` | A user asked for something to close |
101
110
  | `helix-next`, `helix-previous` | Movement through a sequence |
102
111
  | `helix-filter` | A query changed in a filtering control |
103
112
  | `helix-navigate` | A component is about to navigate |
@@ -105,21 +114,24 @@ dispatch, so an event you can listen for is an event that is documented.
105
114
  | `helix-caret` | The cursor moved in a text control |
106
115
  | `helix-complete` | A control that fills up has filled |
107
116
  | `helix-create` | A new item was made from a query |
108
- | `helix-console` | The sandbox relaying a log |
117
+ | `helix-console` | The sandbox passes on a log message |
109
118
  | `helix-tour-ask`, `helix-sample-start`, `helix-sample-end` | Tour and color sampling |
110
119
 
111
- And the native ones every stateful component sends once the change has landed:
112
- `input` and `change` on a value control, `toggle` on anything that opens,
113
- carrying `newState` and `oldState` the way `<details>` does, `close` on a dialog,
114
- and `load` and `error` on an image.
120
+ Every stateful component also sends native events once the change is applied:
121
+
122
+ - `input` and `change` on a value control
123
+ - `toggle` on anything that opens, carrying `newState` and `oldState` as
124
+ `<details>` does
125
+ - `close` on a dialog
126
+ - `load` and `error` on an image
115
127
 
116
- Each component's page lists the events it actually dispatches, with the class
117
- and the detail.
128
+ Each component's page lists the events it dispatches, with the class and the
129
+ detail.
118
130
 
119
- ## In a framework
131
+ ## Events in a framework
120
132
 
121
- A native event is `onChange`, `onInput` or `onToggle`, which is what React and
122
- every framework already understand. A prefixed one is `on` and the name,
123
- verbatim: `onhelix-before-change`. Nothing strips the prefix and nothing
124
- camel-cases it, which is React's own behavior rather than a convention here. See [React](./react), [Vue](./vue) and
125
- [Svelte](./svelte).
133
+ Native events use their usual props, such as `onChange`, `onInput`, and
134
+ `onToggle`, which every framework already understands. A prefixed event uses
135
+ `on` followed by the exact name, such as `onhelix-before-change`. Nothing
136
+ removes the prefix or converts it to camel case. That is React's own behavior.
137
+ See [React](./react), [Vue](./vue), and [Svelte](./svelte).