@fusion.dev/helix-core 0.0.0-dev → 0.0.3
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/custom-elements.json +156 -124
- package/dist/controllers/aria-forward-controller/aria-forward-controller.d.ts +2 -0
- package/dist/controllers/aria-forward-controller/aria-forward-controller.js +44 -17
- package/dist/controllers/chart-pointer-controller/chart-pointer-controller.d.ts +5 -6
- package/dist/controllers/chart-pointer-controller/chart-pointer-controller.js +5 -6
- package/dist/controllers/command-controller/command-controller.d.ts +5 -7
- package/dist/controllers/command-controller/command-controller.js +5 -7
- package/dist/controllers/copy-controller/copy-controller.d.ts +2 -4
- package/dist/controllers/copy-controller/copy-controller.js +7 -9
- package/dist/controllers/disabled-cascade-controller/disabled-cascade-controller.d.ts +10 -17
- package/dist/controllers/disabled-cascade-controller/disabled-cascade-controller.js +11 -18
- package/dist/controllers/dismiss-controller/dismiss-controller.d.ts +44 -6
- package/dist/controllers/dismiss-controller/dismiss-controller.js +77 -10
- package/dist/controllers/drilldown-controller/drilldown-controller.d.ts +3 -6
- package/dist/controllers/drilldown-controller/drilldown-controller.js +3 -6
- package/dist/controllers/floating-controller/floating-controller.d.ts +7 -8
- package/dist/controllers/floating-controller/floating-controller.js +69 -38
- package/dist/controllers/focus-trap-controller/focus-trap-controller.js +7 -10
- package/dist/controllers/indicator-controller/indicator-controller.d.ts +17 -29
- package/dist/controllers/indicator-controller/indicator-controller.js +11 -21
- package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.d.ts +3 -13
- package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.js +11 -24
- package/dist/controllers/popover-controller/popover-controller.d.ts +2 -7
- package/dist/controllers/popover-controller/popover-controller.js +3 -9
- package/dist/controllers/source-controller/source-controller.d.ts +2 -5
- package/dist/controllers/source-controller/source-controller.js +2 -5
- package/dist/controllers/token-color-controller/token-color-controller.d.ts +2 -4
- package/dist/controllers/token-color-controller/token-color-controller.js +2 -4
- package/dist/controllers/virtualize-controller/virtualize-controller.d.ts +1 -1
- package/dist/controllers/virtualize-controller/virtualize-controller.js +15 -15
- package/dist/events/element-events.js +0 -5
- package/dist/events/events.d.ts +55 -93
- package/dist/events/events.js +68 -105
- package/dist/helix-element/helix-element.d.ts +26 -27
- package/dist/helix-element/helix-element.js +83 -100
- package/dist/helix-form-element/helix-form-element.d.ts +11 -17
- package/dist/helix-form-element/helix-form-element.js +15 -21
- package/dist/mixins/mixin-chart-source/mixin-chart-source.d.ts +6 -14
- package/dist/mixins/mixin-chart-source/mixin-chart-source.js +12 -25
- package/dist/mixins/mixin-collection/mixin-collection.d.ts +1 -7
- package/dist/mixins/mixin-collection/mixin-collection.js +16 -23
- package/dist/mixins/mixin-controller-active/mixin-controller-active.d.ts +15 -16
- package/dist/mixins/mixin-controller-active/mixin-controller-active.js +28 -42
- package/dist/mixins/mixin-controller-checked/mixin-controller-checked.d.ts +15 -17
- package/dist/mixins/mixin-controller-checked/mixin-controller-checked.js +33 -45
- package/dist/mixins/mixin-controller-open/mixin-controller-open.d.ts +4 -4
- package/dist/mixins/mixin-controller-open/mixin-controller-open.js +29 -45
- package/dist/mixins/mixin-controller-value/mixin-controller-value.d.ts +10 -18
- package/dist/mixins/mixin-controller-value/mixin-controller-value.js +14 -31
- package/dist/mixins/mixin-dismissible/mixin-dismissible.d.ts +10 -15
- package/dist/mixins/mixin-dismissible/mixin-dismissible.js +10 -15
- package/dist/mixins/mixin-field/mixin-field.d.ts +10 -12
- package/dist/mixins/mixin-field/mixin-field.js +48 -64
- package/dist/utils/capture-pointer/capture-pointer.d.ts +1 -4
- package/dist/utils/capture-pointer/capture-pointer.js +1 -4
- package/dist/utils/closest-instance/closest-instance.d.ts +6 -10
- package/dist/utils/closest-instance/closest-instance.js +6 -10
- package/dist/utils/create-sheet/create-sheet.d.ts +1 -1
- package/dist/utils/create-sheet/create-sheet.js +1 -1
- package/dist/utils/custom-element/custom-element.d.ts +0 -21
- package/dist/utils/custom-element/custom-element.js +1 -1
- package/dist/utils/draw-icon/draw-icon.d.ts +3 -3
- package/dist/utils/draw-icon/draw-icon.js +3 -3
- package/dist/utils/elapsed-time/elapsed-time.js +1 -3
- package/dist/utils/focusable-elements/focusable-elements.d.ts +3 -4
- package/dist/utils/focusable-elements/focusable-elements.js +9 -11
- package/dist/utils/format-numbers/format-numbers.js +1 -4
- package/dist/utils/host-aria/host-aria.d.ts +2 -2
- package/dist/utils/host-aria/host-aria.js +2 -2
- package/dist/utils/line-path/line-path.js +1 -1
- package/dist/utils/on-unless-false/on-unless-false.d.ts +0 -5
- package/dist/utils/on-unless-false/on-unless-false.js +0 -5
- package/dist/utils/read-series/read-series.d.ts +26 -0
- package/dist/utils/read-series/read-series.js +26 -29
- package/dist/utils/relative-time/relative-time.js +5 -6
- package/dist/utils/request-change/request-change.d.ts +9 -20
- package/dist/utils/resolve-active-item/resolve-active-item.d.ts +3 -3
- package/dist/utils/resolve-active-item/resolve-active-item.js +3 -3
- package/dist/utils/restore-value/restore-value.js +1 -6
- package/dist/utils/scroll-item-into-view/scroll-item-into-view.js +4 -5
- package/dist/utils/slot-filled/slot-filled.d.ts +2 -4
- package/dist/utils/slot-filled/slot-filled.js +2 -4
- package/dist/utils/tell-disclosure/tell-disclosure.d.ts +15 -18
- package/dist/utils/tell-disclosure/tell-disclosure.js +15 -18
- package/dist/utils/tone-marks/tone-marks.d.ts +10 -16
- package/dist/utils/tone-marks/tone-marks.js +6 -10
- package/dist/utils/value-as/value-as.d.ts +0 -28
- package/dist/utils/value-as/value-as.js +5 -10
- package/package.json +6 -6
|
@@ -8,15 +8,15 @@ import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
|
8
8
|
* every consumer of any component that renders one ships all of the generated
|
|
9
9
|
* path data: measured at 50,814 bytes raw and 15,010 gzipped for 102 icons, and
|
|
10
10
|
* growing about 147 gzipped bytes per icon added. A named import ships one path
|
|
11
|
-
* and is checked when the package compiles rather than
|
|
11
|
+
* and is checked when the package compiles rather than by a name lookup later.
|
|
12
12
|
*
|
|
13
13
|
* That element still earns its place as *public* API, where a consumer writes a
|
|
14
14
|
* name in markup and a lookup is the only thing that can work. What it should
|
|
15
15
|
* not be is how this library draws its own marks.
|
|
16
16
|
*
|
|
17
17
|
* **There is no `part` option, and there cannot be.** A part name behind an
|
|
18
|
-
* interpolation is a part the manifest cannot see, so
|
|
19
|
-
*
|
|
18
|
+
* interpolation is a part the manifest cannot see, so the component's own
|
|
19
|
+
* stylesheet matches nothing and the part stops being
|
|
20
20
|
* API. `Trend` needs one and therefore writes its `<svg>` out with a literal
|
|
21
21
|
* `part="icon"`, which is the honest cost: a helper can carry everything except
|
|
22
22
|
* the thing static analysis has to read.
|
|
@@ -82,9 +82,7 @@ export const elapsedTime = (locale) => {
|
|
|
82
82
|
if (size < 60_000) {
|
|
83
83
|
/* Floored like every other branch. At 59,999 milliseconds the remainder is
|
|
84
84
|
one, and a caller handed a one is re-armed inside the callback that
|
|
85
|
-
fired it.
|
|
86
|
-
and its own `Relative.next` claimed to have it; only the shared ticker's
|
|
87
|
-
own `Math.max(1000, ...)` was standing between that and a busy loop. */
|
|
85
|
+
fired it. */
|
|
88
86
|
return { next: Math.max(1000, 60_000 - size), text: zero.format(0, 'second') };
|
|
89
87
|
}
|
|
90
88
|
/* A date, and it will not change again. `Infinity` is the whole point of
|
|
@@ -26,11 +26,10 @@ export declare const deepActiveElement: () => Element | null;
|
|
|
26
26
|
* `deepActiveElement`, which returns exactly such a node, that pairing is a trap
|
|
27
27
|
* of its own: the two look like they belong together and one of them crosses.
|
|
28
28
|
*
|
|
29
|
-
* `
|
|
30
|
-
* and it is why every dialog and drawer in the library leaked. Focus on a
|
|
29
|
+
* `contains(deepActiveElement())` is the shape to avoid: focus on a
|
|
31
30
|
* `helix-button` reports as the `<button>` in its shadow root, which is in no
|
|
32
|
-
* light-DOM container anywhere, so
|
|
33
|
-
* outside it.
|
|
31
|
+
* light-DOM container anywhere, so a trap written that way reads every reader
|
|
32
|
+
* as already outside it.
|
|
34
33
|
*
|
|
35
34
|
* `host` is reached without naming `ShadowRoot`, which the server has no global
|
|
36
35
|
* for. This runs from a keydown handler and would never execute there, but a
|
|
@@ -30,13 +30,12 @@ const NATIVE_FOCUSABLE = [
|
|
|
30
30
|
export const focusableElements = (root) =>
|
|
31
31
|
/* One pass asking both questions, rather than two passes concatenated.
|
|
32
32
|
*
|
|
33
|
-
* Two lists joined end to end is not document order
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* did not wrap. Focus left the dialog.
|
|
33
|
+
* Two lists joined end to end is not document order: every native element
|
|
34
|
+
* followed by every delegating host comes out `b d a c` for a tree holding
|
|
35
|
+
* `a b c d`, measured. The trap reads `items[0]` and `items.at(-1)` as its
|
|
36
|
+
* boundaries, so a surface whose last control is a plain `<button>` and whose
|
|
37
|
+
* first is a Helix component has a last that is not last, forward Tab off the
|
|
38
|
+
* real end does not wrap, and focus leaves the dialog.
|
|
40
39
|
*
|
|
41
40
|
* It is invisible in a set that is all one kind, which is what every example
|
|
42
41
|
* and every spec had. A single filtered walk cannot express the fault, and
|
|
@@ -65,11 +64,10 @@ export const deepActiveElement = () => {
|
|
|
65
64
|
* `deepActiveElement`, which returns exactly such a node, that pairing is a trap
|
|
66
65
|
* of its own: the two look like they belong together and one of them crosses.
|
|
67
66
|
*
|
|
68
|
-
* `
|
|
69
|
-
* and it is why every dialog and drawer in the library leaked. Focus on a
|
|
67
|
+
* `contains(deepActiveElement())` is the shape to avoid: focus on a
|
|
70
68
|
* `helix-button` reports as the `<button>` in its shadow root, which is in no
|
|
71
|
-
* light-DOM container anywhere, so
|
|
72
|
-
* outside it.
|
|
69
|
+
* light-DOM container anywhere, so a trap written that way reads every reader
|
|
70
|
+
* as already outside it.
|
|
73
71
|
*
|
|
74
72
|
* `host` is reached without naming `ShadowRoot`, which the server has no global
|
|
75
73
|
* for. This runs from a keydown handler and would never execute there, but a
|
|
@@ -3,10 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `Intl.NumberFormat` is expensive to construct and cheap to call, and a
|
|
5
5
|
* pagination renders its count on every update, so building one per render is
|
|
6
|
-
* the whole cost of the component.
|
|
7
|
-
* out separately and each kept its own two-line cache, which is three places to
|
|
8
|
-
* find the day the caching rule changes and three chances for one of them to be
|
|
9
|
-
* subtly different.
|
|
6
|
+
* the whole cost of the component.
|
|
10
7
|
*
|
|
11
8
|
* Keyed on the locale, so a component that never changes locale builds one
|
|
12
9
|
* formatter for its life and one that does builds a second and drops the first.
|
|
@@ -20,8 +20,8 @@ export interface HostAria {
|
|
|
20
20
|
* **Empty means absent, and that is the trap this exists for.** Assigning `''`
|
|
21
21
|
* to `ariaLabel` is not the same as not naming the element: it is a name of no
|
|
22
22
|
* characters, which a screen reader announces as an unnamed control while every
|
|
23
|
-
* check for "is it labeled" passes.
|
|
24
|
-
*
|
|
23
|
+
* check for "is it labeled" passes. Clearing is `null`, and this is the one
|
|
24
|
+
* place that has to remember it.
|
|
25
25
|
*
|
|
26
26
|
* **Element references are feature-tested rather than assumed.** They are the
|
|
27
27
|
* only way a host in the light DOM can point at a node in its own shadow root,
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* **Empty means absent, and that is the trap this exists for.** Assigning `''`
|
|
10
10
|
* to `ariaLabel` is not the same as not naming the element: it is a name of no
|
|
11
11
|
* characters, which a screen reader announces as an unnamed control while every
|
|
12
|
-
* check for "is it labeled" passes.
|
|
13
|
-
*
|
|
12
|
+
* check for "is it labeled" passes. Clearing is `null`, and this is the one
|
|
13
|
+
* place that has to remember it.
|
|
14
14
|
*
|
|
15
15
|
* **Element references are feature-tested rather than assumed.** They are the
|
|
16
16
|
* only way a host in the light DOM can point at a node in its own shadow root,
|
|
@@ -13,7 +13,7 @@ const round = (value) => Math.round(value * 100) / 100;
|
|
|
13
13
|
*
|
|
14
14
|
* Two rules do it. A tangent at a turning point is flat, so the curve arrives
|
|
15
15
|
* and leaves level rather than carrying on past the peak; and where a tangent
|
|
16
|
-
* is steep enough relative to its
|
|
16
|
+
* is steep enough relative to its neighbors to bulge anyway, the pair is
|
|
17
17
|
* scaled back until it cannot.
|
|
18
18
|
*/
|
|
19
19
|
const tangents = (xs, ys) => {
|
|
@@ -9,11 +9,6 @@ import type { ComplexAttributeConverter } from 'lit';
|
|
|
9
9
|
* attribute that turns it off at all, and a consumer who writes the obvious
|
|
10
10
|
* thing gets the opposite of what they asked for, silently.
|
|
11
11
|
*
|
|
12
|
-
* Found on `Drawer.modal`, where it mattered: a tour cannot point inside a
|
|
13
|
-
* modal, so `modal="false"` is exactly what a consumer reaches for, and it left
|
|
14
|
-
* the drawer modal. Two others have the same shape, `Video.playsInline` and the
|
|
15
|
-
* sandbox source's `tabIndents`.
|
|
16
|
-
*
|
|
17
12
|
* So: absent is the default, present is on, and the one string that means off
|
|
18
13
|
* is the one a consumer would write. `"false"` only, and not `"0"` or `"no"`,
|
|
19
14
|
* because a converter that accepts a vocabulary invites a consumer to guess at
|
|
@@ -8,11 +8,6 @@
|
|
|
8
8
|
* attribute that turns it off at all, and a consumer who writes the obvious
|
|
9
9
|
* thing gets the opposite of what they asked for, silently.
|
|
10
10
|
*
|
|
11
|
-
* Found on `Drawer.modal`, where it mattered: a tour cannot point inside a
|
|
12
|
-
* modal, so `modal="false"` is exactly what a consumer reaches for, and it left
|
|
13
|
-
* the drawer modal. Two others have the same shape, `Video.playsInline` and the
|
|
14
|
-
* sandbox source's `tabIndents`.
|
|
15
|
-
*
|
|
16
11
|
* So: absent is the default, present is on, and the one string that means off
|
|
17
12
|
* is the one a consumer would write. `"false"` only, and not `"0"` or `"no"`,
|
|
18
13
|
* because a converter that accepts a vocabulary invites a consumer to guess at
|
|
@@ -12,6 +12,10 @@ export interface SeriesPoint {
|
|
|
12
12
|
* The machine-readable instant, if the row header carries a `<time>`.
|
|
13
13
|
*/
|
|
14
14
|
when?: string;
|
|
15
|
+
/**
|
|
16
|
+
* The tone the cell asked for through `data-tone`, where it carries one.
|
|
17
|
+
*/
|
|
18
|
+
tone?: string;
|
|
15
19
|
}
|
|
16
20
|
/** One column of a table, read as a series. */
|
|
17
21
|
export interface Series {
|
|
@@ -25,6 +29,28 @@ export interface Series {
|
|
|
25
29
|
*/
|
|
26
30
|
points: SeriesPoint[];
|
|
27
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Reads a table as series, one per data column.
|
|
34
|
+
*
|
|
35
|
+
* **The table is the chart's accessible representation, not a fallback for
|
|
36
|
+
* one.** Every chart here takes its numbers from real markup rather than from a
|
|
37
|
+
* property, so the page is readable with no script at all, sortable by anything
|
|
38
|
+
* that sorts tables, and correct in a screen reader without a parallel
|
|
39
|
+
* description that has to be kept in step by hand. A `values` array would be
|
|
40
|
+
* easier to pass and would make the accessible version the thing nobody
|
|
41
|
+
* maintains.
|
|
42
|
+
*
|
|
43
|
+
* The shape is the ordinary one: a header row naming the series, a first column
|
|
44
|
+
* of row headers, and numbers in the rest. A single-column table has one series
|
|
45
|
+
* and takes its name from the caption, since a header row of one is usually
|
|
46
|
+
* absent.
|
|
47
|
+
*
|
|
48
|
+
* `data-value` wins over the cell's text, because `$1.2k` is what a reader
|
|
49
|
+
* should see and is not a number. `Number()` rather than `parseFloat`, because
|
|
50
|
+
* the second reads `12abc` as 12: a value that is nearly a number is a mistake
|
|
51
|
+
* worth dropping rather than rounding off, and a dropped point is visible where
|
|
52
|
+
* a silently truncated one is not.
|
|
53
|
+
*/
|
|
28
54
|
export declare const readSeries: (table: Element) => Series[];
|
|
29
55
|
/** One cell of a row, as a reader sees it and as a chart measures it. */
|
|
30
56
|
export interface RowCell {
|
|
@@ -1,25 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reads a table as series, one per data column.
|
|
3
|
-
*
|
|
4
|
-
* **The table is the chart's accessible representation, not a fallback for
|
|
5
|
-
* one.** Every chart here takes its numbers from real markup rather than from a
|
|
6
|
-
* property, so the page is readable with no script at all, sortable by anything
|
|
7
|
-
* that sorts tables, and correct in a screen reader without a parallel
|
|
8
|
-
* description that has to be kept in step by hand. A `values` array would be
|
|
9
|
-
* easier to pass and would make the accessible version the thing nobody
|
|
10
|
-
* maintains.
|
|
11
|
-
*
|
|
12
|
-
* The shape is the ordinary one: a header row naming the series, a first column
|
|
13
|
-
* of row headers, and numbers in the rest. A single-column table has one series
|
|
14
|
-
* and takes its name from the caption, since a header row of one is usually
|
|
15
|
-
* absent.
|
|
16
|
-
*
|
|
17
|
-
* `data-value` wins over the cell's text, because `$1.2k` is what a reader
|
|
18
|
-
* should see and is not a number. `Number()` rather than `parseFloat`, because
|
|
19
|
-
* the second reads `12abc` as 12: a value that is nearly a number is a mistake
|
|
20
|
-
* worth dropping rather than rounding off, and a dropped point is visible where
|
|
21
|
-
* a silently truncated one is not.
|
|
22
|
-
*/
|
|
23
1
|
/* A grid's own rows and cells, by role and by tag.
|
|
24
2
|
*
|
|
25
3
|
* **Both, because `helix-table` sets its roles through `ElementInternals`.**
|
|
@@ -84,14 +62,33 @@ const readGrid = (grid) => {
|
|
|
84
62
|
/* A column with no numbers in it is not a series.
|
|
85
63
|
*
|
|
86
64
|
* Both readers build one entry per data column and then drop the rows whose
|
|
87
|
-
* cells will not parse, so a column of notes
|
|
88
|
-
* and no points. Nothing
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* Found the moment tables grew columns a tooltip wanted and a drawing did not,
|
|
93
|
-
* which is the arrangement `readRows` exists for. */
|
|
65
|
+
* cells will not parse, so a column of notes arrives here as a series with a
|
|
66
|
+
* name and no points. Nothing draws it, so it is invisible until `Legend` names
|
|
67
|
+
* it anyway: a swatch in the palette against a column no chart plotted, and
|
|
68
|
+
* every series after it shifted a color. */
|
|
94
69
|
const plotted = (series) => series.filter((one) => one.points.length > 0);
|
|
70
|
+
/**
|
|
71
|
+
* Reads a table as series, one per data column.
|
|
72
|
+
*
|
|
73
|
+
* **The table is the chart's accessible representation, not a fallback for
|
|
74
|
+
* one.** Every chart here takes its numbers from real markup rather than from a
|
|
75
|
+
* property, so the page is readable with no script at all, sortable by anything
|
|
76
|
+
* that sorts tables, and correct in a screen reader without a parallel
|
|
77
|
+
* description that has to be kept in step by hand. A `values` array would be
|
|
78
|
+
* easier to pass and would make the accessible version the thing nobody
|
|
79
|
+
* maintains.
|
|
80
|
+
*
|
|
81
|
+
* The shape is the ordinary one: a header row naming the series, a first column
|
|
82
|
+
* of row headers, and numbers in the rest. A single-column table has one series
|
|
83
|
+
* and takes its name from the caption, since a header row of one is usually
|
|
84
|
+
* absent.
|
|
85
|
+
*
|
|
86
|
+
* `data-value` wins over the cell's text, because `$1.2k` is what a reader
|
|
87
|
+
* should see and is not a number. `Number()` rather than `parseFloat`, because
|
|
88
|
+
* the second reads `12abc` as 12: a value that is nearly a number is a mistake
|
|
89
|
+
* worth dropping rather than rounding off, and a dropped point is visible where
|
|
90
|
+
* a silently truncated one is not.
|
|
91
|
+
*/
|
|
95
92
|
export const readSeries = (table) => {
|
|
96
93
|
if (isGrid(table)) {
|
|
97
94
|
return plotted(readGrid(table));
|
|
@@ -57,12 +57,11 @@ export const relativeTime = (locale) => {
|
|
|
57
57
|
nothing: nobody acts differently at seven seconds than at four. */
|
|
58
58
|
const size = Math.abs(elapsed);
|
|
59
59
|
if (size < 60_000) {
|
|
60
|
-
/* Floored,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
would have had the busy loop the type promised it would not. */
|
|
60
|
+
/* Floored, as `Relative.next` says every branch is: at 59,999
|
|
61
|
+
milliseconds the remainder is one, so a caller scheduling for itself is
|
|
62
|
+
re-armed inside the callback that fired it. The shared ticker's own
|
|
63
|
+
`Math.max(1000, ...)` hides this from the two readers that go through
|
|
64
|
+
it. */
|
|
66
65
|
return { next: Math.max(1000, 60_000 - size), text: found.format(0, 'second') };
|
|
67
66
|
}
|
|
68
67
|
let chosen = UNITS[1];
|
|
@@ -10,30 +10,19 @@
|
|
|
10
10
|
* anything moves. A component that mutated itself and then announced it would
|
|
11
11
|
* leave a consumer reacting to a decision already made.
|
|
12
12
|
*
|
|
13
|
-
* **Control is an action, not a mode.**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* 294 for the argument, which is that a mode forks the event vocabulary by prop
|
|
19
|
-
* presence, has no ergonomic spelling in HTML, and is invisible enough to need
|
|
20
|
-
* an audit to make it audible.
|
|
21
|
-
*
|
|
22
|
-
* **The audit went with it, and that is not a loss.** It existed to warn that
|
|
23
|
-
* `value="Ada"` looks like the native seed and was not one. It is one now, so
|
|
24
|
-
* the trap it detected cannot happen and the machinery to detect it, two
|
|
25
|
-
* `WeakMap`s and a warning judged on the second request, has nothing to do.
|
|
13
|
+
* **Control is an action, not a mode.** The element always owns its state and
|
|
14
|
+
* always applies it, and a consumer takes over for one interaction by refusing
|
|
15
|
+
* here and assigning their own. A mode instead would fork the event vocabulary
|
|
16
|
+
* by prop presence, has no ergonomic spelling in HTML, and is invisible enough
|
|
17
|
+
* to need an audit to make it audible.
|
|
26
18
|
*
|
|
27
19
|
* Returns whether the change was applied, so a caller that needs to know (a key
|
|
28
20
|
* handler deciding whether to also move focus) can ask.
|
|
29
21
|
*
|
|
30
|
-
* **There is one channel, and it is the event.**
|
|
31
|
-
* `
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* React takes any `on[A-Z]` prop into its own branch and calls
|
|
35
|
-
* `addEventListener('CheckedChange', …)` with it, an event nothing dispatches,
|
|
36
|
-
* so no consumer writing JSX could reach one.
|
|
22
|
+
* **There is one channel, and it is the event.** A callback property beside it
|
|
23
|
+
* (`onCheckedChange` and its kind) is unreachable from JSX: React takes any
|
|
24
|
+
* `on[A-Z]` prop into its own branch and calls
|
|
25
|
+
* `addEventListener('CheckedChange', …)` with it, an event nothing dispatches.
|
|
37
26
|
*/
|
|
38
27
|
export interface ChangeRequestOptions<T> {
|
|
39
28
|
/** Applies the change. Called only if no listener refused it. */
|
|
@@ -16,9 +16,9 @@ export interface ActivatableItem {
|
|
|
16
16
|
* The fallback skips disabled items and the match does not. A consumer who
|
|
17
17
|
* names a disabled item meant it, and quietly moving to another would be the
|
|
18
18
|
* component overruling them; but falling back *onto* a disabled item is the
|
|
19
|
-
* component choosing, and it should not choose something out of play.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* component choosing, and it should not choose something out of play. Taking
|
|
20
|
+
* `items[0]` either way opens a set on a disabled first item and shows it as
|
|
21
|
+
* current.
|
|
22
22
|
*
|
|
23
23
|
* A plain function rather than only a method on `MixinControllerActive`,
|
|
24
24
|
* because the components that need it are split across two mixins: a set whose
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* The fallback skips disabled items and the match does not. A consumer who
|
|
10
10
|
* names a disabled item meant it, and quietly moving to another would be the
|
|
11
11
|
* component overruling them; but falling back *onto* a disabled item is the
|
|
12
|
-
* component choosing, and it should not choose something out of play.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* component choosing, and it should not choose something out of play. Taking
|
|
13
|
+
* `items[0]` either way opens a set on a disabled first item and shows it as
|
|
14
|
+
* current.
|
|
15
15
|
*
|
|
16
16
|
* A plain function rather than only a method on `MixinControllerActive`,
|
|
17
17
|
* because the components that need it are split across two mixins: a set whose
|
|
@@ -7,12 +7,7 @@
|
|
|
7
7
|
* how that is done, and per spec assigning a *different* string moves the text
|
|
8
8
|
* entry cursor to the end. Measured: caret at index three in `abcdef`, assign
|
|
9
9
|
* `abcXdef`, caret at seven. Assigning the same string leaves it alone, which
|
|
10
|
-
* is why this
|
|
11
|
-
*
|
|
12
|
-
* So a React consumer holding the value in state, which commits after the
|
|
13
|
-
* event, put the caret at the end of the field after every single keystroke.
|
|
14
|
-
* Typing in the middle of a word was impossible, and the class JSDoc claimed
|
|
15
|
-
* the caret was the platform's business.
|
|
10
|
+
* is why this only shows against a consumer that is slow to answer.
|
|
16
11
|
*
|
|
17
12
|
* The offset is adjusted by the length difference rather than clamped, because
|
|
18
13
|
* the common case is exactly one character wide: the reader typed, the element
|
|
@@ -23,13 +23,12 @@
|
|
|
23
23
|
/**
|
|
24
24
|
* The nearest ancestor that actually scrolls, crossing shadow boundaries.
|
|
25
25
|
*
|
|
26
|
-
*
|
|
26
|
+
* **Not `item.offsetParent`**, which is the nearest *positioned* ancestor and
|
|
27
27
|
* has nothing whatever to do with overflow. The two coincide often enough to
|
|
28
28
|
* look right, because a scroller frequently is positioned, and they come apart
|
|
29
29
|
* exactly where it matters: put anything `position: relative` between the item
|
|
30
|
-
* and the box that scrolls, and the util
|
|
31
|
-
* does not scroll, and
|
|
32
|
-
* it had the default on every keypress.
|
|
30
|
+
* and the box that scrolls, and the util scrolls the thing in between, which
|
|
31
|
+
* does not scroll, and reports nothing.
|
|
33
32
|
*
|
|
34
33
|
* The walk crosses shadow boundaries through the host, because the item is
|
|
35
34
|
* almost always in one and `parentElement` is null at the root. Without that
|
|
@@ -83,7 +82,7 @@ export const scrollItemIntoView = (item, options = {}) => {
|
|
|
83
82
|
};
|
|
84
83
|
/* Whichever edge it is past, and nothing when it is past neither. A scroll of
|
|
85
84
|
zero still fires a `scroll` event and still cancels a smooth one already
|
|
86
|
-
running, so the early return is behavior rather than an
|
|
85
|
+
running, so the early return is behavior rather than an optimization. */
|
|
87
86
|
const inline = before.inline > 0 ? -before.inline : after.inline > 0 ? after.inline : 0;
|
|
88
87
|
const block = before.block > 0 ? -before.block : after.block > 0 ? after.block : 0;
|
|
89
88
|
if (inline === 0 && block === 0) {
|
|
@@ -10,10 +10,8 @@
|
|
|
10
10
|
* The answer matters because an empty wrapper is not free. A region that
|
|
11
11
|
* generates a box is a flex item or a grid item whether or not it holds
|
|
12
12
|
* anything, so it takes the container's gap: an announcement with no actions
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* every quote. `display: none` is the only fix, because only a box that is not
|
|
16
|
-
* generated has no gap beside it.
|
|
13
|
+
* pays the row gap twice. `display: none` is the only fix, because only a box
|
|
14
|
+
* that is not generated has no gap beside it.
|
|
17
15
|
*
|
|
18
16
|
* Text or an element, because neither alone is the test. An element on its own
|
|
19
17
|
* counts a whitespace-only span as content; text on its own counts an icon as
|
|
@@ -10,10 +10,8 @@
|
|
|
10
10
|
* The answer matters because an empty wrapper is not free. A region that
|
|
11
11
|
* generates a box is a flex item or a grid item whether or not it holds
|
|
12
12
|
* anything, so it takes the container's gap: an announcement with no actions
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* every quote. `display: none` is the only fix, because only a box that is not
|
|
16
|
-
* generated has no gap beside it.
|
|
13
|
+
* pays the row gap twice. `display: none` is the only fix, because only a box
|
|
14
|
+
* that is not generated has no gap beside it.
|
|
17
15
|
*
|
|
18
16
|
* Text or an element, because neither alone is the test. An element on its own
|
|
19
17
|
* counts a whitespace-only span as content; text on its own counts an icon as
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tells a slotted trigger that it opens something, and whether it is open.
|
|
3
3
|
*
|
|
4
|
-
* **
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* one whose output a React consumer's hydration disagrees with.
|
|
4
|
+
* **Writing `aria-expanded` onto the consumer's element is a hydration
|
|
5
|
+
* mismatch, so it is not written.** The gap that answer was reaching for is
|
|
6
|
+
* real: a consumer who has to mirror `open` back onto their own trigger is a
|
|
7
|
+
* consumer who will forget, and the failure is silent to everyone who can see
|
|
8
|
+
* the screen. It is still the wrong answer, because a component decorating a
|
|
9
|
+
* consumer's markup after upgrading is one whose output a React consumer's
|
|
10
|
+
* hydration disagrees with.
|
|
12
11
|
*
|
|
13
|
-
* **
|
|
12
|
+
* **Measured 28 Aug 2026 against React 19.2.8.** Four
|
|
14
13
|
* families were server-rendered as React emits them and hydrated with the
|
|
15
14
|
* element definitions loaded either side of `hydrateRoot`:
|
|
16
15
|
*
|
|
@@ -28,14 +27,12 @@
|
|
|
28
27
|
* and rebuilding it, which is what the carousel's own note records blinding an
|
|
29
28
|
* `<helix-aura>` two sections away.
|
|
30
29
|
*
|
|
31
|
-
* **A property crosses the gap that an attribute cannot
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* were protecting: nothing is written onto the consumer's element, and the
|
|
38
|
-
* reader is still told what the control does.
|
|
30
|
+
* **A property crosses the gap that an attribute cannot.** React diffs the
|
|
31
|
+
* attributes it rendered and never looks at a property, so the state reaches
|
|
32
|
+
* the trigger, lands as `aria-expanded` inside *that element's* shadow root,
|
|
33
|
+
* and the consumer's markup is exactly what they served. Both positions get
|
|
34
|
+
* what they were protecting: nothing is written onto the consumer's element,
|
|
35
|
+
* and the reader is still told what the control does.
|
|
39
36
|
*
|
|
40
37
|
* **Set before upgrade is safe**, which is what lets this run without waiting
|
|
41
38
|
* for a definition: `ReactiveElement` captures an instance property assigned
|
|
@@ -46,6 +43,6 @@
|
|
|
46
43
|
* `<button slot="trigger">` has no shadow root to keep the state out of the
|
|
47
44
|
* markup and no property to take it, and a consumer cannot mirror it themselves
|
|
48
45
|
* because the open state belongs to the component. Saying so once is the same
|
|
49
|
-
* answer `Tooltip` gives about the link it will not write for them.
|
|
46
|
+
* answer `Tooltip` gives about the link it will not write for them.
|
|
50
47
|
*/
|
|
51
48
|
export declare const tellDisclosure: (trigger: Element | null | undefined, open: boolean) => void;
|
|
@@ -3,16 +3,15 @@ const warned = new WeakSet();
|
|
|
3
3
|
/**
|
|
4
4
|
* Tells a slotted trigger that it opens something, and whether it is open.
|
|
5
5
|
*
|
|
6
|
-
* **
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* one whose output a React consumer's hydration disagrees with.
|
|
6
|
+
* **Writing `aria-expanded` onto the consumer's element is a hydration
|
|
7
|
+
* mismatch, so it is not written.** The gap that answer was reaching for is
|
|
8
|
+
* real: a consumer who has to mirror `open` back onto their own trigger is a
|
|
9
|
+
* consumer who will forget, and the failure is silent to everyone who can see
|
|
10
|
+
* the screen. It is still the wrong answer, because a component decorating a
|
|
11
|
+
* consumer's markup after upgrading is one whose output a React consumer's
|
|
12
|
+
* hydration disagrees with.
|
|
14
13
|
*
|
|
15
|
-
* **
|
|
14
|
+
* **Measured 28 Aug 2026 against React 19.2.8.** Four
|
|
16
15
|
* families were server-rendered as React emits them and hydrated with the
|
|
17
16
|
* element definitions loaded either side of `hydrateRoot`:
|
|
18
17
|
*
|
|
@@ -30,14 +29,12 @@ const warned = new WeakSet();
|
|
|
30
29
|
* and rebuilding it, which is what the carousel's own note records blinding an
|
|
31
30
|
* `<helix-aura>` two sections away.
|
|
32
31
|
*
|
|
33
|
-
* **A property crosses the gap that an attribute cannot
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* were protecting: nothing is written onto the consumer's element, and the
|
|
40
|
-
* reader is still told what the control does.
|
|
32
|
+
* **A property crosses the gap that an attribute cannot.** React diffs the
|
|
33
|
+
* attributes it rendered and never looks at a property, so the state reaches
|
|
34
|
+
* the trigger, lands as `aria-expanded` inside *that element's* shadow root,
|
|
35
|
+
* and the consumer's markup is exactly what they served. Both positions get
|
|
36
|
+
* what they were protecting: nothing is written onto the consumer's element,
|
|
37
|
+
* and the reader is still told what the control does.
|
|
41
38
|
*
|
|
42
39
|
* **Set before upgrade is safe**, which is what lets this run without waiting
|
|
43
40
|
* for a definition: `ReactiveElement` captures an instance property assigned
|
|
@@ -48,7 +45,7 @@ const warned = new WeakSet();
|
|
|
48
45
|
* `<button slot="trigger">` has no shadow root to keep the state out of the
|
|
49
46
|
* markup and no property to take it, and a consumer cannot mirror it themselves
|
|
50
47
|
* because the open state belongs to the component. Saying so once is the same
|
|
51
|
-
* answer `Tooltip` gives about the link it will not write for them.
|
|
48
|
+
* answer `Tooltip` gives about the link it will not write for them.
|
|
52
49
|
*/
|
|
53
50
|
export const tellDisclosure = (trigger, open) => {
|
|
54
51
|
if (!trigger) {
|
|
@@ -2,12 +2,10 @@ import { type HelixIcon } from '@fusion.dev/helix-icons';
|
|
|
2
2
|
/**
|
|
3
3
|
* The four status tones, as a type anything branching on them can take.
|
|
4
4
|
*
|
|
5
|
-
* **A subset of `tone` rather than an axis of its own
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* and not four values of their own. The components moved with #158; the
|
|
10
|
-
* vocabulary underneath them was still saying the old word.
|
|
5
|
+
* **A subset of `tone` rather than an axis of its own.** A `severity` beside it
|
|
6
|
+
* would be a sixth spelling of the axis for what a thing *means*: `Callout` and
|
|
7
|
+
* `Toast.Item` report a status and never a brand role, so they take four of the
|
|
8
|
+
* seven values and not four values of their own.
|
|
11
9
|
*
|
|
12
10
|
* `Status` stays in the name because that is the half of `tone` these are: there
|
|
13
11
|
* is no mark for `primary` or `accent`, a brand role having nothing to announce.
|
|
@@ -23,17 +21,13 @@ export interface ToneMark {
|
|
|
23
21
|
/**
|
|
24
22
|
* The one status vocabulary.
|
|
25
23
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* meets these marks in a passage and in a message and has to read them as the
|
|
29
|
-
* same four things, so there is one place they come from.
|
|
24
|
+
* A reader meets these marks in a passage and in a message and has to read them
|
|
25
|
+
* as the same four things, so there is one place they come from.
|
|
30
26
|
*
|
|
31
|
-
* **Filled at every tone**,
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* read at a glance and at a small size, where an outline at this weight loses
|
|
36
|
-
* its interior.
|
|
27
|
+
* **Filled at every tone**, or a set meant to be read as one vocabulary has a
|
|
28
|
+
* member drawn a different way. Filled because a tone mark is a status
|
|
29
|
+
* indicator and not an action: it wants to read at a glance and at a small
|
|
30
|
+
* size, where an outline at this weight loses its interior.
|
|
37
31
|
*
|
|
38
32
|
* **The label is not the tone name.** `danger` is announced as "Error",
|
|
39
33
|
* because that is the word a reader understands and `danger` is the word the API
|
|
@@ -2,17 +2,13 @@ import { checkCircleFill, infoFill, warningFill, xCircleFill, } from '@fusion.de
|
|
|
2
2
|
/**
|
|
3
3
|
* The one status vocabulary.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* meets these marks in a passage and in a message and has to read them as the
|
|
8
|
-
* same four things, so there is one place they come from.
|
|
5
|
+
* A reader meets these marks in a passage and in a message and has to read them
|
|
6
|
+
* as the same four things, so there is one place they come from.
|
|
9
7
|
*
|
|
10
|
-
* **Filled at every tone**,
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* read at a glance and at a small size, where an outline at this weight loses
|
|
15
|
-
* its interior.
|
|
8
|
+
* **Filled at every tone**, or a set meant to be read as one vocabulary has a
|
|
9
|
+
* member drawn a different way. Filled because a tone mark is a status
|
|
10
|
+
* indicator and not an action: it wants to read at a glance and at a small
|
|
11
|
+
* size, where an outline at this weight loses its interior.
|
|
16
12
|
*
|
|
17
13
|
* **The label is not the tone name.** `danger` is announced as "Error",
|
|
18
14
|
* because that is the word a reader understands and `danger` is the word the API
|