@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
|
@@ -1,4 +1,50 @@
|
|
|
1
1
|
import { arrow, autoUpdate, computePosition, flip, offset, shift, size, } from '@floating-ui/dom';
|
|
2
|
+
/**
|
|
3
|
+
* The panel's largest corner radius, which is how far its arrow has to stay
|
|
4
|
+
* from either end.
|
|
5
|
+
*
|
|
6
|
+
* **Without it the arrow is correctly aimed and looks wrong.** Floating UI
|
|
7
|
+
* centers the arrow on the anchor, and on a small trigger that center is inside
|
|
8
|
+
* the panel's own corner: the arrow's base meets the curve instead of a straight
|
|
9
|
+
* run and its outer flank is cut by it. Measured against a 4px radius and an 8px
|
|
10
|
+
* arrow, a 14px trigger put the arrow's near flank 1.5px from the panel's edge
|
|
11
|
+
* and a 20px trigger put it at 4.3px, so anything narrower than the arrow plus
|
|
12
|
+
* two radii is broken by construction. An icon beside a heading is the ordinary
|
|
13
|
+
* way to be under that.
|
|
14
|
+
*
|
|
15
|
+
* `padding` is what Floating UI's `arrow` middleware takes for exactly this, and
|
|
16
|
+
* its own guidance for a rounded surface is the border radius.
|
|
17
|
+
*
|
|
18
|
+
* **Measured rather than named**, so a family that rounds itself differently is
|
|
19
|
+
* right without telling this controller. Reading a token here would put a value
|
|
20
|
+
* from `helix/styles` into a tier that must not name one, and would still be
|
|
21
|
+
* wrong for any component that overrides its own radius. The four corners rather
|
|
22
|
+
* than one, because they can differ and the arrow travels along two of them; the
|
|
23
|
+
* largest clears all of them.
|
|
24
|
+
*
|
|
25
|
+
* A percentage is refused rather than guessed at: it resolves against the box,
|
|
26
|
+
* which is the thing being positioned.
|
|
27
|
+
*/
|
|
28
|
+
const corner = (floating) => {
|
|
29
|
+
const style = getComputedStyle(floating);
|
|
30
|
+
const corners = [
|
|
31
|
+
style.borderTopLeftRadius,
|
|
32
|
+
style.borderTopRightRadius,
|
|
33
|
+
style.borderBottomRightRadius,
|
|
34
|
+
style.borderBottomLeftRadius,
|
|
35
|
+
];
|
|
36
|
+
let largest = 0;
|
|
37
|
+
for (const each of corners) {
|
|
38
|
+
if (!each.endsWith('px')) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const value = Number.parseFloat(each);
|
|
42
|
+
if (Number.isFinite(value) && value > largest) {
|
|
43
|
+
largest = value;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return largest;
|
|
47
|
+
};
|
|
2
48
|
/**
|
|
3
49
|
* Places one element against another, and keeps it there.
|
|
4
50
|
*
|
|
@@ -21,9 +67,8 @@ import { arrow, autoUpdate, computePosition, flip, offset, shift, size, } from '
|
|
|
21
67
|
* **The custom properties are declared here because this is what writes them.**
|
|
22
68
|
* Each carries a measurement taken against the viewport that no stylesheet can
|
|
23
69
|
* take for itself, and `core` ships no CSS, so there is no reader inside this
|
|
24
|
-
* package and none of them is private. `
|
|
25
|
-
* with a rule that reads one, in both directions
|
|
26
|
-
* to carry was what exempted them from it.
|
|
70
|
+
* package and none of them is private. the `hooks` rule pairs a documented
|
|
71
|
+
* hook with a rule that reads one, in both directions.
|
|
27
72
|
*
|
|
28
73
|
* @cssproperty --floating-anchor-inline-size - How wide the anchor is, for a panel that matches it.
|
|
29
74
|
* @cssproperty --floating-arrow-x - Where the pointer meets the anchor, along the inline axis.
|
|
@@ -36,11 +81,11 @@ export class FloatingController {
|
|
|
36
81
|
* Which run of placements the results coming back belong to.
|
|
37
82
|
*
|
|
38
83
|
* `computePosition` is async, so a measurement started before `stop()` still
|
|
39
|
-
* resolves after it and
|
|
40
|
-
* taken down: the inline `position` and `translate`
|
|
41
|
-
* `data-placed`, which is the attribute five families gate visibility
|
|
42
|
-
* Opening and closing inside one task is all it takes, since the write
|
|
43
|
-
* a microtask later.
|
|
84
|
+
* resolves after it and would write its result onto a surface that had just
|
|
85
|
+
* been taken down: the inline `position` and `translate` back on, and with
|
|
86
|
+
* them `data-placed`, which is the attribute five families gate visibility
|
|
87
|
+
* on. Opening and closing inside one task is all it takes, since the write
|
|
88
|
+
* lands a microtask later.
|
|
44
89
|
*
|
|
45
90
|
* A counter rather than an `AbortController`, because there is nothing to
|
|
46
91
|
* abort: the work is already done by the time the answer arrives and the only
|
|
@@ -74,10 +119,9 @@ export class FloatingController {
|
|
|
74
119
|
middleware.push(size({
|
|
75
120
|
/* The height and not the width, which is asymmetric on purpose. A
|
|
76
121
|
panel that runs out of room grows a scrollbar downwards and every
|
|
77
|
-
rule reading this asks for a `max-block-size`; nothing
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
nobody. `check:hooks` is what says so out loud: declared and never
|
|
122
|
+
rule reading this asks for a `max-block-size`; nothing asks for the
|
|
123
|
+
inline twin, so writing it would be a `setProperty` on every
|
|
124
|
+
reposition of every floating element for nobody. Declared and never
|
|
81
125
|
read is a promise on the API page with nothing behind it. It comes
|
|
82
126
|
back the day a rule wants it, together with that rule. */
|
|
83
127
|
apply: ({ availableHeight, elements, rects }) => {
|
|
@@ -95,7 +139,7 @@ export class FloatingController {
|
|
|
95
139
|
}));
|
|
96
140
|
}
|
|
97
141
|
if (pointer) {
|
|
98
|
-
middleware.push(arrow({ element: pointer }));
|
|
142
|
+
middleware.push(arrow({ element: pointer, padding: corner(floating) }));
|
|
99
143
|
}
|
|
100
144
|
computePosition(anchor, floating, {
|
|
101
145
|
middleware,
|
|
@@ -133,29 +177,17 @@ export class FloatingController {
|
|
|
133
177
|
*
|
|
134
178
|
* `computePosition` returns a position in the viewport and this element
|
|
135
179
|
* is `position: fixed`, so the two agree and the translate is the answer
|
|
136
|
-
* unmodified.
|
|
137
|
-
*
|
|
138
|
-
* **A correction used to sit here and it was answering the wrong
|
|
139
|
-
* question.** A chart's bubble was landing hundreds of pixels from its
|
|
140
|
-
* anchor, which looked like an ancestor stealing the containing block:
|
|
141
|
-
* `transform`, `filter` and `backdrop-filter` all do that to a `fixed`
|
|
142
|
-
* descendant, and the page's cards are glass. Measured, the bubble with
|
|
143
|
-
* a zeroed translate sat at (799, 439) rather than the origin, which
|
|
144
|
-
* seemed to confirm it.
|
|
145
|
-
*
|
|
146
|
-
* It was not that. 799 is `(1728 - 130) / 2` and 439 is near
|
|
147
|
-
* `(1003 - 73) / 2`: the bubble was centered in the viewport, which is
|
|
148
|
-
* what the UA stylesheet does to a `popover` through `inset: 0` and
|
|
149
|
-
* `margin: auto`. The chart was rendering the *headless* tooltip, which
|
|
150
|
-
* ships no styles to overrule that; `Tooltip` sets `margin: 0` and the
|
|
151
|
-
* controller writes the insets, so a styled one was never centered and
|
|
152
|
-
* never needed correcting. Fixing the tier fixed the position, and the
|
|
153
|
-
* correction then subtracted a real ancestor's origin from an already
|
|
154
|
-
* correct number and threw every tooltip on the page off by it.
|
|
180
|
+
* unmodified. **Never subtract an ancestor's origin from it.**
|
|
155
181
|
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
182
|
+
* A surface landing hundreds of pixels from its anchor looks like an
|
|
183
|
+
* ancestor stealing the containing block, which `transform`, `filter`
|
|
184
|
+
* and `backdrop-filter` all do to a `fixed` descendant. Check the tier
|
|
185
|
+
* first: the UA stylesheet centers a `popover` through `inset: 0` and
|
|
186
|
+
* `margin: auto`, and the headless tooltip ships no styles to overrule
|
|
187
|
+
* it, so it sits mid-viewport with a zeroed translate while `Tooltip`,
|
|
188
|
+
* which sets `margin: 0`, never does. A correction added for that
|
|
189
|
+
* subtracts a real ancestor's origin from an already correct number and
|
|
190
|
+
* throws every tooltip on the page off by it. */
|
|
159
191
|
translate: `${Math.round(x)}px ${Math.round(y)}px`,
|
|
160
192
|
});
|
|
161
193
|
floating.setAttribute('data-placement', placement);
|
|
@@ -168,9 +200,8 @@ export class FloatingController {
|
|
|
168
200
|
the first open, and looked correct on every open after, because the
|
|
169
201
|
inline styles from last time were still on it.
|
|
170
202
|
|
|
171
|
-
A panel that wants to fade in on placement selects on this. Nothing
|
|
172
|
-
|
|
173
|
-
pair, so a component that ignores both is exactly as it was. */
|
|
203
|
+
A panel that wants to fade in on placement selects on this. Nothing
|
|
204
|
+
has to. */
|
|
174
205
|
floating.setAttribute('data-placed', '');
|
|
175
206
|
const point = middlewareData.arrow;
|
|
176
207
|
if (pointer && point) {
|
|
@@ -34,11 +34,9 @@ export class FocusTrapController {
|
|
|
34
34
|
* `focusableElements` returns hosts: a `helix-button` delegates focus, so
|
|
35
35
|
* the list holds the component and `deepActiveElement` returns the
|
|
36
36
|
* `<button>` in its shadow root. They are never equal, and the container
|
|
37
|
-
* never `contains` the second, so
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* from anywhere and forward Tab never wrap at all, which is a trap that
|
|
41
|
-
* leaks in one direction and cannot be walked backwards in the other.
|
|
37
|
+
* never `contains` the second, so an identity check reads "outside the
|
|
38
|
+
* trap" on every element: against the two branches below, shift-Tab jumps
|
|
39
|
+
* to the last item from anywhere and forward Tab never wraps at all.
|
|
42
40
|
*
|
|
43
41
|
* An index also gives the outside-entirely case for free: -1 wraps to
|
|
44
42
|
* whichever end the reader was heading for. */
|
|
@@ -64,12 +62,11 @@ export class FocusTrapController {
|
|
|
64
62
|
/* After render, so the container has something to focus, and *held*, so a
|
|
65
63
|
release before the frame runs can cancel it.
|
|
66
64
|
*
|
|
67
|
-
Unheld, an overlay opened and closed inside one frame
|
|
68
|
-
itself a frame after it
|
|
65
|
+
Unheld, an overlay opened and closed inside one frame pulls focus into
|
|
66
|
+
itself a frame after it has gone: the reader is looking at the page, the
|
|
69
67
|
surface is not on it, and their next key goes somewhere they cannot see.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
toggling `open` twice in one task. */
|
|
68
|
+
A menu that closes on the same activation that opened it does exactly
|
|
69
|
+
this, and so does any consumer toggling `open` twice in one task. */
|
|
73
70
|
this.frame = requestAnimationFrame(() => {
|
|
74
71
|
this.frame = undefined;
|
|
75
72
|
const items = focusableElements(container);
|
|
@@ -20,28 +20,20 @@ export interface IndicatorOptions {
|
|
|
20
20
|
/**
|
|
21
21
|
* Measures the marked child and writes the geometry onto a traveling mark.
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* a position onto an indicator, and two of them had bugs the others did not.
|
|
26
|
-
* `Outline`'s own comment counted the repetition rather than resolving it, and
|
|
27
|
-
* the audit found the same shape independently in all four.
|
|
28
|
-
*
|
|
29
|
-
* What the copies disagreed about, and what this settles:
|
|
23
|
+
* Serves `Navigation`, `Tabs`, `Toggle.Group` and `Outline`. The two things
|
|
24
|
+
* hand-written copies of this get wrong:
|
|
30
25
|
*
|
|
31
26
|
* **The inline axis is signed.** In a right-to-left document the distance is
|
|
32
27
|
* measured from the surface's right edge and applied as a negative translation,
|
|
33
28
|
* because the mark travels from the inline start and the inline start is on the
|
|
34
|
-
* right.
|
|
35
|
-
* inline-start edge
|
|
36
|
-
* in RTL. That is the bug this extraction exists to stop repeating.
|
|
29
|
+
* right. Measuring from the physical left and applying an unsigned offset from
|
|
30
|
+
* the inline-start edge sends the mark the wrong way in RTL.
|
|
37
31
|
*
|
|
38
32
|
* **The border has to come off both axes.** `getBoundingClientRect` gives the
|
|
39
33
|
* border box and the containing block is the padding box, so a surface with a
|
|
40
|
-
* border of its own puts the mark one border-width out. `
|
|
41
|
-
* `
|
|
42
|
-
*
|
|
43
|
-
* are the border widths, already resolved, which is why they are used here
|
|
44
|
-
* rather than parsing `borderLeftWidth`.
|
|
34
|
+
* border of its own puts the mark one border-width out. `clientLeft` and
|
|
35
|
+
* `clientTop` are the border widths, already resolved, which is why they are
|
|
36
|
+
* used here rather than parsing `borderLeftWidth`.
|
|
45
37
|
*
|
|
46
38
|
* **Nothing is written until there is something to measure.** A zero-width box
|
|
47
39
|
* is a component whose children have not upgraded yet, and writing zeroes would
|
|
@@ -53,12 +45,10 @@ export interface IndicatorOptions {
|
|
|
53
45
|
* is its own business, which is how one controller serves a bar under a tab, a
|
|
54
46
|
* pill filling it, and a rule down the side of a contents page.
|
|
55
47
|
*
|
|
56
|
-
* **Declared here because this is what writes them.**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* one, in both directions, and the underscore these used to carry was what
|
|
61
|
-
* exempted them from it.
|
|
48
|
+
* **Declared here because this is what writes them.** `core` ships no CSS, so
|
|
49
|
+
* every reader is outside this package and none of these is private.
|
|
50
|
+
* the `hooks` rule pairs a documented hook with a rule that reads one, in both
|
|
51
|
+
* directions.
|
|
62
52
|
*
|
|
63
53
|
* @cssproperty --indicator-block-size - How tall the active item is, in pixels.
|
|
64
54
|
* @cssproperty --indicator-inline-size - How wide the active item is, in pixels.
|
|
@@ -81,14 +71,12 @@ export declare class IndicatorController implements ReactiveController {
|
|
|
81
71
|
/**
|
|
82
72
|
* The listener currently waiting, so the next move can take it off first.
|
|
83
73
|
*
|
|
84
|
-
* **Without this, a move inside a second orphans a listener.**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* an element that lives as long as the page. The people who asked for less
|
|
91
|
-
* work were the ones accumulating it.
|
|
74
|
+
* **Without this, a move inside a second orphans a listener.** Clearing the
|
|
75
|
+
* timer is not enough on its own: the `transitionend` that would remove the
|
|
76
|
+
* listener never arrives under reduced motion, where there is no transition
|
|
77
|
+
* to end, so a reader changing tabs quickly, or any reader at all with motion
|
|
78
|
+
* reduced, leaves one dead listener per selection on an element that lives as
|
|
79
|
+
* long as the page.
|
|
92
80
|
*/
|
|
93
81
|
private waiting;
|
|
94
82
|
place(): void;
|
|
@@ -1,28 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Measures the marked child and writes the geometry onto a traveling mark.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* a position onto an indicator, and two of them had bugs the others did not.
|
|
7
|
-
* `Outline`'s own comment counted the repetition rather than resolving it, and
|
|
8
|
-
* the audit found the same shape independently in all four.
|
|
9
|
-
*
|
|
10
|
-
* What the copies disagreed about, and what this settles:
|
|
4
|
+
* Serves `Navigation`, `Tabs`, `Toggle.Group` and `Outline`. The two things
|
|
5
|
+
* hand-written copies of this get wrong:
|
|
11
6
|
*
|
|
12
7
|
* **The inline axis is signed.** In a right-to-left document the distance is
|
|
13
8
|
* measured from the surface's right edge and applied as a negative translation,
|
|
14
9
|
* because the mark travels from the inline start and the inline start is on the
|
|
15
|
-
* right.
|
|
16
|
-
* inline-start edge
|
|
17
|
-
* in RTL. That is the bug this extraction exists to stop repeating.
|
|
10
|
+
* right. Measuring from the physical left and applying an unsigned offset from
|
|
11
|
+
* the inline-start edge sends the mark the wrong way in RTL.
|
|
18
12
|
*
|
|
19
13
|
* **The border has to come off both axes.** `getBoundingClientRect` gives the
|
|
20
14
|
* border box and the containing block is the padding box, so a surface with a
|
|
21
|
-
* border of its own puts the mark one border-width out. `
|
|
22
|
-
* `
|
|
23
|
-
*
|
|
24
|
-
* are the border widths, already resolved, which is why they are used here
|
|
25
|
-
* rather than parsing `borderLeftWidth`.
|
|
15
|
+
* border of its own puts the mark one border-width out. `clientLeft` and
|
|
16
|
+
* `clientTop` are the border widths, already resolved, which is why they are
|
|
17
|
+
* used here rather than parsing `borderLeftWidth`.
|
|
26
18
|
*
|
|
27
19
|
* **Nothing is written until there is something to measure.** A zero-width box
|
|
28
20
|
* is a component whose children have not upgraded yet, and writing zeroes would
|
|
@@ -34,12 +26,10 @@
|
|
|
34
26
|
* is its own business, which is how one controller serves a bar under a tab, a
|
|
35
27
|
* pill filling it, and a rule down the side of a contents page.
|
|
36
28
|
*
|
|
37
|
-
* **Declared here because this is what writes them.**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* one, in both directions, and the underscore these used to carry was what
|
|
42
|
-
* exempted them from it.
|
|
29
|
+
* **Declared here because this is what writes them.** `core` ships no CSS, so
|
|
30
|
+
* every reader is outside this package and none of these is private.
|
|
31
|
+
* the `hooks` rule pairs a documented hook with a rule that reads one, in both
|
|
32
|
+
* directions.
|
|
43
33
|
*
|
|
44
34
|
* @cssproperty --indicator-block-size - How tall the active item is, in pixels.
|
|
45
35
|
* @cssproperty --indicator-inline-size - How wide the active item is, in pixels.
|
|
@@ -85,19 +85,9 @@ export interface KeyboardNavigationOptions {
|
|
|
85
85
|
* Disabled items are skipped rather than stopped at, so a set with a gap in it
|
|
86
86
|
* still moves in one press.
|
|
87
87
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* had not diverged only because nothing had touched them yet.
|
|
92
|
-
*
|
|
93
|
-
* **`Tabs` is the third, and it is what `origin` and `selects` are for.** It had
|
|
94
|
-
* diverged, which is the case the paragraph above was worried about: it started
|
|
95
|
-
* from focus rather than from the selection, and it moved without choosing when
|
|
96
|
-
* `manual` was set. Both are real and neither is a tabs-only idea, so they are
|
|
97
|
-
* options here rather than a reason to keep a fourth copy. What it does *not*
|
|
98
|
-
* keep is its own key set: it bound the horizontal pair only, and both axes is
|
|
99
|
-
* the house position, argued two paragraphs up. A reader on a tab who presses
|
|
100
|
-
* Down now moves along the row instead of getting nothing.
|
|
88
|
+
* A set that starts from focus rather than from the selection, or that moves
|
|
89
|
+
* without choosing, says so through `origin` and `selects`. Neither is a reason
|
|
90
|
+
* to keep a hand-written copy, and neither exempts a set from both axes.
|
|
101
91
|
*/
|
|
102
92
|
export declare class KeyboardNavigationController implements ReactiveController {
|
|
103
93
|
private readonly host;
|
|
@@ -18,19 +18,9 @@ const KEYS = new Set(['ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'End',
|
|
|
18
18
|
* Disabled items are skipped rather than stopped at, so a set with a gap in it
|
|
19
19
|
* still moves in one press.
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* had not diverged only because nothing had touched them yet.
|
|
25
|
-
*
|
|
26
|
-
* **`Tabs` is the third, and it is what `origin` and `selects` are for.** It had
|
|
27
|
-
* diverged, which is the case the paragraph above was worried about: it started
|
|
28
|
-
* from focus rather than from the selection, and it moved without choosing when
|
|
29
|
-
* `manual` was set. Both are real and neither is a tabs-only idea, so they are
|
|
30
|
-
* options here rather than a reason to keep a fourth copy. What it does *not*
|
|
31
|
-
* keep is its own key set: it bound the horizontal pair only, and both axes is
|
|
32
|
-
* the house position, argued two paragraphs up. A reader on a tab who presses
|
|
33
|
-
* Down now moves along the row instead of getting nothing.
|
|
21
|
+
* A set that starts from focus rather than from the selection, or that moves
|
|
22
|
+
* without choosing, says so through `origin` and `selects`. Neither is a reason
|
|
23
|
+
* to keep a hand-written copy, and neither exempts a set from both axes.
|
|
34
24
|
*/
|
|
35
25
|
export class KeyboardNavigationController {
|
|
36
26
|
constructor(host, { items, origin = 'selection', reserved = () => [], selects = () => true, wrap = true, }) {
|
|
@@ -68,18 +58,15 @@ export class KeyboardNavigationController {
|
|
|
68
58
|
else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
|
|
69
59
|
/* From nothing, backwards is the last, which the arithmetic did not give.
|
|
70
60
|
*
|
|
71
|
-
* `(-1 - 1 + length) % length` is `length - 2`: the *second* to last
|
|
72
|
-
* reader arrowing backwards into an unanswered group
|
|
73
|
-
* the end
|
|
74
|
-
*
|
|
75
|
-
* that is not an index.
|
|
61
|
+
* `(-1 - 1 + length) % length` is `length - 2`: the *second* to last, so
|
|
62
|
+
* a reader arrowing backwards into an unanswered group lands one short of
|
|
63
|
+
* the end. The wrapping form is right for every real index and wrong for
|
|
64
|
+
* the one that is not an index.
|
|
76
65
|
*
|
|
77
|
-
* And the clamp is `Math.max` against the forward branch's `Math.min
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* first star of a rating got silence, where holding Right on the last one
|
|
82
|
-
* holds its ground. */
|
|
66
|
+
* And the clamp is `Math.max` against the forward branch's `Math.min`.
|
|
67
|
+
* Unclamped this is `-1` on the first item, and `items[-1]` is undefined,
|
|
68
|
+
* so nothing is focused and nothing chosen on a press that
|
|
69
|
+
* `preventDefault` has already swallowed. */
|
|
83
70
|
next =
|
|
84
71
|
current < 0
|
|
85
72
|
? last
|
|
@@ -64,13 +64,8 @@ export interface PopoverOptions {
|
|
|
64
64
|
/**
|
|
65
65
|
* A surface that floats against something, in the top layer, until dismissed.
|
|
66
66
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* Individually each is small, which is exactly why the copies drifted rather
|
|
70
|
-
* than being noticed. **`Select` never called `showPopover` at all**, so its
|
|
71
|
-
* list was an ordinary positioned panel and any ancestor with `overflow:
|
|
72
|
-
* hidden` clipped it, while a menu two folders away in the same position did
|
|
73
|
-
* not. That is not a difference anyone chose.
|
|
67
|
+
* Bundles the three things that always travel together: the popover API, a
|
|
68
|
+
* `FloatingController` and a `DismissController`.
|
|
74
69
|
*
|
|
75
70
|
* **The top layer is the whole point.** A floating panel has to escape every
|
|
76
71
|
* clipping ancestor between it and the page, and `position: fixed` does not do
|
|
@@ -3,13 +3,8 @@ import { FloatingController } from '../floating-controller/floating-controller.j
|
|
|
3
3
|
/**
|
|
4
4
|
* A surface that floats against something, in the top layer, until dismissed.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Individually each is small, which is exactly why the copies drifted rather
|
|
9
|
-
* than being noticed. **`Select` never called `showPopover` at all**, so its
|
|
10
|
-
* list was an ordinary positioned panel and any ancestor with `overflow:
|
|
11
|
-
* hidden` clipped it, while a menu two folders away in the same position did
|
|
12
|
-
* not. That is not a difference anyone chose.
|
|
6
|
+
* Bundles the three things that always travel together: the popover API, a
|
|
7
|
+
* `FloatingController` and a `DismissController`.
|
|
13
8
|
*
|
|
14
9
|
* **The top layer is the whole point.** A floating panel has to escape every
|
|
15
10
|
* clipping ancestor between it and the page, and `position: fixed` does not do
|
|
@@ -87,8 +82,7 @@ export class PopoverController {
|
|
|
87
82
|
element with no `popover` attribute throws `NotSupportedError`, from
|
|
88
83
|
inside the promise the host called this in, so it surfaces as an
|
|
89
84
|
unhandled rejection naming a method rather than as the thing that is
|
|
90
|
-
actually wrong.
|
|
91
|
-
broken: no error at all, just a panel that could be clipped. */
|
|
85
|
+
actually wrong. */
|
|
92
86
|
if (!surface.hasAttribute('popover')) {
|
|
93
87
|
throw new Error('PopoverController: the surface needs a `popover` attribute to reach the top layer');
|
|
94
88
|
}
|
|
@@ -10,11 +10,8 @@ export interface SourceHost extends ReactiveControllerHost, HTMLElement {
|
|
|
10
10
|
/**
|
|
11
11
|
* Resolves the element a chart reads from, and follows it.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* called it `#source`, for the same method with the same body. Every chart that
|
|
16
|
-
* takes a `for` wants exactly this, and wants it to behave identically, so it is
|
|
17
|
-
* a controller rather than a paragraph in each of them.
|
|
13
|
+
* Every chart that takes a `for` wants exactly this, and wants it to behave
|
|
14
|
+
* identically, so it is a controller rather than a paragraph in each of them.
|
|
18
15
|
*
|
|
19
16
|
* A controller and not a pure function, by the test the rules set: it reacts to
|
|
20
17
|
* the host's lifecycle. It has to start watching when the host connects, stop
|
|
@@ -2,11 +2,8 @@ import { readSeries } from '../../utils/read-series/read-series.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Resolves the element a chart reads from, and follows it.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* called it `#source`, for the same method with the same body. Every chart that
|
|
8
|
-
* takes a `for` wants exactly this, and wants it to behave identically, so it is
|
|
9
|
-
* a controller rather than a paragraph in each of them.
|
|
5
|
+
* Every chart that takes a `for` wants exactly this, and wants it to behave
|
|
6
|
+
* identically, so it is a controller rather than a paragraph in each of them.
|
|
10
7
|
*
|
|
11
8
|
* A controller and not a pure function, by the test the rules set: it reacts to
|
|
12
9
|
* the host's lifecycle. It has to start watching when the host connects, stop
|
|
@@ -12,10 +12,8 @@ export type TokenColor = [number, number, number];
|
|
|
12
12
|
* white. Parsing that with a regular expression for numbers reads the three
|
|
13
13
|
* channels as 100, 0 and 0, which divided by 255 is a dark red.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* survived review because a warm cast on a saturated gradient is also what a
|
|
18
|
-
* screenshot's chroma subsampling produces, so the picture agreed with the bug.
|
|
15
|
+
* A screenshot cannot catch that: a warm cast on a saturated gradient is also
|
|
16
|
+
* what chroma subsampling produces, so the picture agrees with the bug.
|
|
19
17
|
*
|
|
20
18
|
* The conversion is therefore the browser's own: fill a pixel and read it back.
|
|
21
19
|
* That holds for `lab()`, `oklch()`, `color-mix()` and whatever a token is
|
|
@@ -9,10 +9,8 @@
|
|
|
9
9
|
* white. Parsing that with a regular expression for numbers reads the three
|
|
10
10
|
* channels as 100, 0 and 0, which divided by 255 is a dark red.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* survived review because a warm cast on a saturated gradient is also what a
|
|
15
|
-
* screenshot's chroma subsampling produces, so the picture agreed with the bug.
|
|
12
|
+
* A screenshot cannot catch that: a warm cast on a saturated gradient is also
|
|
13
|
+
* what chroma subsampling produces, so the picture agrees with the bug.
|
|
16
14
|
*
|
|
17
15
|
* The conversion is therefore the browser's own: fill a pixel and read it back.
|
|
18
16
|
* That holds for `lab()`, `oklch()`, `color-mix()` and whatever a token is
|
|
@@ -57,7 +57,7 @@ export interface Window {
|
|
|
57
57
|
* and the note in the rules that objects and functions have that bug and are
|
|
58
58
|
* not fixed).
|
|
59
59
|
*
|
|
60
|
-
* **A third is open
|
|
60
|
+
* **A third channel is open.** Data as *text*
|
|
61
61
|
* in the markup, a `<script type="application/json">` child or an attribute, is
|
|
62
62
|
* neither a property nor a node per row: React's hydration bug cannot touch text
|
|
63
63
|
* it renders as a child, a server emits it, and the node count is one. The
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* How many times an update this asked for may change the answer again.
|
|
3
3
|
*
|
|
4
|
-
* **A measurement that its own render moves has no fixed point,
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* **A measurement that its own render moves has no fixed point, so something
|
|
5
|
+
* has to stop looking for one.** `clientHeight` is meant to be a fact about the
|
|
6
|
+
* box, true of any real scroller, and false of a box with no height: there it
|
|
7
|
+
* is a function of the rows just drawn, so `measure` renders, the render
|
|
8
|
+
* changes the height, and the next measurement disagrees.
|
|
9
9
|
*
|
|
10
10
|
* It does not even diverge, which is what made it hard to see. Measured on the
|
|
11
11
|
* headless tier with 500 rows and no height on the viewport, it converged
|
|
@@ -51,7 +51,7 @@ const UNSTABLE = 10;
|
|
|
51
51
|
* and the note in the rules that objects and functions have that bug and are
|
|
52
52
|
* not fixed).
|
|
53
53
|
*
|
|
54
|
-
* **A third is open
|
|
54
|
+
* **A third channel is open.** Data as *text*
|
|
55
55
|
* in the markup, a `<script type="application/json">` child or an attribute, is
|
|
56
56
|
* neither a property nor a node per row: React's hydration bug cannot touch text
|
|
57
57
|
* it renders as a child, a server emits it, and the node count is one. The
|
|
@@ -103,15 +103,15 @@ export class VirtualizeController {
|
|
|
103
103
|
/* Nothing is observed here, and that is deliberate. `hostConnected` runs
|
|
104
104
|
before the first render, so the container query returns null and an
|
|
105
105
|
observer attached to nothing observes nothing for the life of the
|
|
106
|
-
element.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
106
|
+
element. It is an invisible failure: the first measurement is right, so
|
|
107
|
+
the only symptom is a window that never moves again. */
|
|
108
|
+
/* **The count is deliberately not cleared here**, which is the obvious
|
|
109
|
+
thing to do and does not work. A resize looks like an outside cause and
|
|
110
|
+
is not one when the box has no height of its own: the render this
|
|
111
|
+
controller asked for is what changed the size, so the observer fires on
|
|
112
|
+
every pass and resets the very counter meant to notice that. Measured:
|
|
113
|
+
the guard made no difference at all, 7776 passes in twenty-five seconds,
|
|
114
|
+
identical to having none.
|
|
115
115
|
|
|
116
116
|
Nothing is lost by leaving it. A genuine resize changes the window once
|
|
117
117
|
and the next measurement agrees, which clears the count through `same`. A
|
|
@@ -8,11 +8,6 @@
|
|
|
8
8
|
* That is the likelier mistake of the two, because it happens while writing a
|
|
9
9
|
* *correct* event name.
|
|
10
10
|
*
|
|
11
|
-
* Measured 25 Aug 2026: `onhelix-resize` was written on `<helix-sandbox>` where
|
|
12
|
-
* the dispatcher is the `<helix-sandbox-preview>` inside it. Nothing warned, the
|
|
13
|
-
* handler never ran, and the page it was sizing sat at its floor looking exactly
|
|
14
|
-
* like the fixed layout it replaced.
|
|
15
|
-
*
|
|
16
11
|
* **Registered rather than imported, because the direction of the tiers forbids
|
|
17
12
|
* the other way round.** This lives in `core`, which is below the packages that
|
|
18
13
|
* define elements and cannot name them. Each package ships a generated call
|