@fusion.dev/helix-core 0.205.2 → 0.207.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -13
- package/custom-elements.json +1 -37171
- package/dist/controllers/aria-forward-controller/aria-forward-controller.js +5 -6
- package/dist/controllers/category-axis-controller/category-axis-controller.d.ts +75 -0
- package/dist/controllers/category-axis-controller/category-axis-controller.js +128 -0
- package/dist/controllers/source-controller/source-controller.d.ts +2 -0
- package/dist/controllers/source-controller/source-controller.js +5 -2
- package/dist/controllers/virtualize-controller/virtualize-controller.d.ts +9 -0
- package/dist/controllers/virtualize-controller/virtualize-controller.js +10 -8
- package/dist/helix-element/helix-element.js +3 -21
- package/dist/helix-element/react-bridge.d.ts +17 -0
- package/dist/helix-element/react-bridge.js +54 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +8 -2
- package/dist/mixins/mixin-chart-source/mixin-chart-source.d.ts +28 -0
- package/dist/mixins/mixin-chart-source/mixin-chart-source.js +76 -9
- package/dist/mixins/mixin-field/mixin-field.js +2 -0
- package/dist/mixins/mixin-tokens/mixin-tokens.js +2 -1
- package/dist/mixins/mixin-viewport/mixin-viewport.js +3 -2
- package/dist/utils/audit-children/audit-children.d.ts +1 -0
- package/dist/utils/audit-children/audit-children.js +2 -2
- package/dist/utils/audit-name/audit-name.js +2 -2
- package/dist/utils/audit-number/audit-number-off.d.ts +12 -0
- package/dist/utils/audit-number/audit-number-off.js +2 -0
- package/dist/utils/audit-number/audit-number.d.ts +23 -0
- package/dist/utils/audit-number/audit-number.js +21 -0
- package/dist/utils/category-labels/category-labels.d.ts +74 -0
- package/dist/utils/category-labels/category-labels.js +230 -0
- package/dist/utils/closest-name/closest-name.d.ts +11 -0
- package/dist/utils/closest-name/closest-name.js +35 -0
- package/dist/utils/custom-element/custom-element.js +11 -6
- package/dist/utils/draw-tooltip/draw-tooltip.js +3 -2
- package/dist/utils/host-words/host-words.js +2 -1
- package/dist/utils/how-to-register/how-to-register.d.ts +20 -0
- package/dist/utils/how-to-register/how-to-register.js +3 -0
- package/dist/utils/labels-of/labels-of.d.ts +23 -0
- package/dist/utils/labels-of/labels-of.js +8 -0
- package/dist/utils/line-path/line-path.d.ts +18 -0
- package/dist/utils/line-path/line-path.js +36 -0
- package/dist/utils/locale-of/locale-of.d.ts +40 -2
- package/dist/utils/locale-of/locale-of.js +30 -16
- package/dist/utils/number-converter/number-converter.d.ts +44 -0
- package/dist/utils/number-converter/number-converter.js +34 -0
- package/dist/utils/read-data/read-data.d.ts +1 -1
- package/dist/utils/read-data/read-data.js +23 -10
- package/dist/utils/read-series/read-series.js +9 -2
- package/dist/utils/report-runtime/report-runtime.d.ts +8 -0
- package/dist/utils/scale/scale.d.ts +7 -1
- package/dist/utils/scale/scale.js +9 -4
- package/dist/utils/scroll-item-into-view/scroll-item-into-view.d.ts +5 -0
- package/dist/utils/scroll-item-into-view/scroll-item-into-view.js +3 -2
- package/dist/utils/watch-labels/watch-labels.d.ts +4 -2
- package/dist/utils/watch-labels/watch-labels.js +14 -14
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/words/held.d.ts +11 -3
- package/dist/words/words-locale.js +20 -8
- package/dist/words/words.js +10 -5
- package/package.json +16 -4
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { auditName } from '@fusion.dev/helix-core/audits/name';
|
|
2
2
|
import { elementById } from '../../utils/element-by-id/element-by-id.js';
|
|
3
|
+
import { labelsOf } from '../../utils/labels-of/labels-of.js';
|
|
3
4
|
import { nameText } from '../../utils/name-text/name-text.js';
|
|
4
5
|
const RELATIONSHIPS = [
|
|
5
6
|
['aria-describedby', 'ariaDescribedByElements'],
|
|
@@ -38,16 +39,14 @@ export class AriaForwardController {
|
|
|
38
39
|
if (!this.forwarded.has('aria-labelledby') && target.hasAttribute('aria-labelledby')) {
|
|
39
40
|
return true;
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
return [...(own ?? [])].some((label) => nameText(label).trim() !== '');
|
|
42
|
+
return labelsOf(target).some((label) => nameText(label).trim() !== '');
|
|
43
43
|
}
|
|
44
44
|
hostLabels(target) {
|
|
45
|
-
const labels = this.host
|
|
46
|
-
|
|
47
|
-
if (!labels || labels.length === 0 || named) {
|
|
45
|
+
const labels = labelsOf(this.host);
|
|
46
|
+
if (labels.length === 0 || (this.named ? this.named() : this.namedByItself(target))) {
|
|
48
47
|
return [];
|
|
49
48
|
}
|
|
50
|
-
return
|
|
49
|
+
return labels.filter((label) => label instanceof HTMLElement);
|
|
51
50
|
}
|
|
52
51
|
forward() {
|
|
53
52
|
const target = this.target();
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
|
2
|
+
import { type Category, type PlacedLabel } from '../../utils/category-labels/category-labels.js';
|
|
3
|
+
/** What the controller needs of its host: a Lit element and where it drew itself. */
|
|
4
|
+
export interface CategoryAxisHost extends ReactiveControllerHost, HTMLElement {
|
|
5
|
+
/**
|
|
6
|
+
* Where the chart drew itself, which is where the `labels` part is looked for.
|
|
7
|
+
*/
|
|
8
|
+
readonly renderRoot: DocumentFragment | HTMLElement;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Measures a chart's category axis and says which of its names fit.
|
|
12
|
+
*
|
|
13
|
+
* **A controller because the answer moves with the box.** `placeLabels` is
|
|
14
|
+
* the arithmetic; what it needs is the axis' length and every name's length
|
|
15
|
+
* on it, and both change without the chart changing: a card resized, a font
|
|
16
|
+
* that finished loading. So the `labels` part is watched with a
|
|
17
|
+
* `ResizeObserver`, a name's font is read from a drawn name, and the host is
|
|
18
|
+
* asked to update when either moves.
|
|
19
|
+
*
|
|
20
|
+
* **Measured on a canvas in the name's own font**, so a name is measured
|
|
21
|
+
* without being built: an axis of 10,000 readings measures the few dozen names
|
|
22
|
+
* that are candidates to be drawn, and builds only the ones that are. The
|
|
23
|
+
* widths are held by name until the font changes.
|
|
24
|
+
*
|
|
25
|
+
* **The first update is unmeasured**, the box not having been laid out yet,
|
|
26
|
+
* and draws every name up to `UNMEASURED_LABELS`. The observer's first report
|
|
27
|
+
* comes after that layout and before the frame is painted, so the frame shows
|
|
28
|
+
* the measured axis.
|
|
29
|
+
*
|
|
30
|
+
* **The box it watches never changes size because of what it draws.** Along
|
|
31
|
+
* the line of text that is the `labels` part, whose width is the plot's and
|
|
32
|
+
* whose height is one line whichever names are in it. Down the side, as a
|
|
33
|
+
* horizontal bar chart's names are, the names set the part's width, so the
|
|
34
|
+
* host is watched there and the part's height is read when it reports.
|
|
35
|
+
* Watching a box the update resizes is a loop the browser reports as an error.
|
|
36
|
+
*/
|
|
37
|
+
export declare class CategoryAxisController implements ReactiveController {
|
|
38
|
+
private readonly host;
|
|
39
|
+
private readonly along;
|
|
40
|
+
private sizes;
|
|
41
|
+
/** The `labels` part the observer last saw, so a new one is watched. */
|
|
42
|
+
private labels;
|
|
43
|
+
/** The axis' length in pixels, or 0 while it has not been measured. */
|
|
44
|
+
private length;
|
|
45
|
+
private setting;
|
|
46
|
+
/** Each name's length in the current font, by its text. */
|
|
47
|
+
private widths;
|
|
48
|
+
private held;
|
|
49
|
+
constructor(host: CategoryAxisHost, along?: () => 'block' | 'inline');
|
|
50
|
+
/**
|
|
51
|
+
* The axis' length in pixels, or 0 before it is measured, which is the
|
|
52
|
+
* plot's length along it: a chart draws a line of more readings than it has
|
|
53
|
+
* pixels with only the points those pixels show. See `columnPoints`.
|
|
54
|
+
*/
|
|
55
|
+
get measured(): number;
|
|
56
|
+
/**
|
|
57
|
+
* The names to draw for these readings, placed along the axis.
|
|
58
|
+
*
|
|
59
|
+
* Held while the readings, the axis and the font are the ones it was worked
|
|
60
|
+
* out for, so a render the pointer asks for costs nothing here. The
|
|
61
|
+
* readings are compared by identity, which is how a chart holds its rows
|
|
62
|
+
* between edits.
|
|
63
|
+
*/
|
|
64
|
+
place(categories: readonly Category[]): PlacedLabel[];
|
|
65
|
+
/** The axis and its names in pixels, or nothing while either cannot be measured. */
|
|
66
|
+
private lengths;
|
|
67
|
+
hostConnected(): void;
|
|
68
|
+
hostDisconnected(): void;
|
|
69
|
+
/** Watches the `labels` part the update drew, where it is a new one. */
|
|
70
|
+
hostUpdated(): void;
|
|
71
|
+
/** Reads the axis' length and its names' font, and asks for an update where either moved. */
|
|
72
|
+
private readonly measure;
|
|
73
|
+
/** A font finished loading, so every name is measured again in it. */
|
|
74
|
+
private readonly refont;
|
|
75
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { placeLabels, } from '../../utils/category-labels/category-labels.js';
|
|
2
|
+
let pen;
|
|
3
|
+
const penOf = () => {
|
|
4
|
+
if (pen === undefined) {
|
|
5
|
+
try {
|
|
6
|
+
pen =
|
|
7
|
+
typeof document === 'undefined' ? null : document.createElement('canvas').getContext('2d');
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
pen = null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return pen;
|
|
14
|
+
};
|
|
15
|
+
const cased = (text, transform) => transform === 'uppercase'
|
|
16
|
+
? text.toUpperCase()
|
|
17
|
+
: transform === 'lowercase'
|
|
18
|
+
? text.toLowerCase()
|
|
19
|
+
: transform === 'capitalize'
|
|
20
|
+
? text.replace(/(^|\s)(\S)/g, (_, space, letter) => space + letter.toUpperCase())
|
|
21
|
+
: text;
|
|
22
|
+
export class CategoryAxisController {
|
|
23
|
+
constructor(host, along = () => 'inline') {
|
|
24
|
+
this.labels = null;
|
|
25
|
+
this.length = 0;
|
|
26
|
+
this.widths = new Map();
|
|
27
|
+
this.measure = () => {
|
|
28
|
+
const labels = this.labels;
|
|
29
|
+
const name = labels?.querySelector('[part~="label"]');
|
|
30
|
+
if (!labels || !name) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const box = labels.getBoundingClientRect();
|
|
34
|
+
const length = this.along() === 'block' ? box.height : box.width;
|
|
35
|
+
const style = getComputedStyle(name);
|
|
36
|
+
const size = Number.parseFloat(style.fontSize) || 0;
|
|
37
|
+
const spacing = Number.parseFloat(style.letterSpacing) || 0;
|
|
38
|
+
const font = `${style.fontStyle} ${style.fontWeight} ${style.fontSize} ${style.fontFamily}`;
|
|
39
|
+
const setting = this.setting;
|
|
40
|
+
const same = setting?.font === font &&
|
|
41
|
+
setting.spacing === spacing &&
|
|
42
|
+
setting.transform === style.textTransform;
|
|
43
|
+
if (!same) {
|
|
44
|
+
this.setting = { font, gap: size / 2, size, spacing, transform: style.textTransform };
|
|
45
|
+
this.widths.clear();
|
|
46
|
+
}
|
|
47
|
+
if (!same || length !== this.length) {
|
|
48
|
+
this.length = length;
|
|
49
|
+
this.host.requestUpdate();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
this.refont = () => {
|
|
53
|
+
this.widths.clear();
|
|
54
|
+
this.setting = this.setting && { ...this.setting };
|
|
55
|
+
this.host.requestUpdate();
|
|
56
|
+
};
|
|
57
|
+
this.host = host;
|
|
58
|
+
this.along = along;
|
|
59
|
+
host.addController(this);
|
|
60
|
+
}
|
|
61
|
+
get measured() {
|
|
62
|
+
return this.length;
|
|
63
|
+
}
|
|
64
|
+
place(categories) {
|
|
65
|
+
const held = this.held;
|
|
66
|
+
if (held &&
|
|
67
|
+
held.categories === categories &&
|
|
68
|
+
held.length === this.length &&
|
|
69
|
+
held.setting === this.setting) {
|
|
70
|
+
return held.placed;
|
|
71
|
+
}
|
|
72
|
+
if (held?.categories !== categories) {
|
|
73
|
+
this.widths.clear();
|
|
74
|
+
}
|
|
75
|
+
const placed = placeLabels(categories, this.lengths(categories));
|
|
76
|
+
this.held = { categories, length: this.length, placed, setting: this.setting };
|
|
77
|
+
return placed;
|
|
78
|
+
}
|
|
79
|
+
lengths(categories) {
|
|
80
|
+
const setting = this.setting;
|
|
81
|
+
const pen = penOf();
|
|
82
|
+
if (this.length <= 0 || !setting || !pen) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
if (this.along() === 'block') {
|
|
86
|
+
return { gap: setting.size / 8, length: this.length, name: () => setting.size };
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
gap: setting.gap,
|
|
90
|
+
length: this.length,
|
|
91
|
+
name: (at) => {
|
|
92
|
+
const text = categories[at]?.label ?? '';
|
|
93
|
+
let width = this.widths.get(text);
|
|
94
|
+
if (width === undefined) {
|
|
95
|
+
pen.font = setting.font;
|
|
96
|
+
width =
|
|
97
|
+
pen.measureText(cased(text, setting.transform)).width + setting.spacing * text.length;
|
|
98
|
+
this.widths.set(text, width);
|
|
99
|
+
}
|
|
100
|
+
return width;
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
hostConnected() {
|
|
105
|
+
globalThis.document?.fonts?.addEventListener?.('loadingdone', this.refont);
|
|
106
|
+
}
|
|
107
|
+
hostDisconnected() {
|
|
108
|
+
globalThis.document?.fonts?.removeEventListener?.('loadingdone', this.refont);
|
|
109
|
+
this.sizes?.disconnect();
|
|
110
|
+
this.sizes = undefined;
|
|
111
|
+
this.labels = null;
|
|
112
|
+
}
|
|
113
|
+
hostUpdated() {
|
|
114
|
+
const labels = this.host.renderRoot.querySelector('[part="labels"]');
|
|
115
|
+
if (labels === this.labels) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
this.labels = labels;
|
|
119
|
+
if (typeof ResizeObserver === 'undefined') {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
this.sizes ??= new ResizeObserver(this.measure);
|
|
123
|
+
this.sizes.disconnect();
|
|
124
|
+
if (labels) {
|
|
125
|
+
this.sizes.observe(this.along() === 'block' ? this.host : labels);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -63,6 +63,8 @@ export declare class SourceController implements ReactiveController {
|
|
|
63
63
|
* table, and `for` naming another one.
|
|
64
64
|
*/
|
|
65
65
|
private version;
|
|
66
|
+
/** The last `data` checked for a table's shape, and whether it had it. */
|
|
67
|
+
private checked;
|
|
66
68
|
private heldRows;
|
|
67
69
|
private heldSeries;
|
|
68
70
|
constructor(host: SourceHost);
|
|
@@ -29,7 +29,10 @@ export class SourceController {
|
|
|
29
29
|
if (host.data === undefined || typeof host.closest !== 'function' || this.hydrating) {
|
|
30
30
|
return undefined;
|
|
31
31
|
}
|
|
32
|
-
if (
|
|
32
|
+
if (this.checked?.data !== host.data) {
|
|
33
|
+
this.checked = { data: host.data, fits: isChartData(host.data) };
|
|
34
|
+
}
|
|
35
|
+
if (!this.checked.fits) {
|
|
33
36
|
auditData(host);
|
|
34
37
|
return undefined;
|
|
35
38
|
}
|
|
@@ -63,7 +66,7 @@ export class SourceController {
|
|
|
63
66
|
: null;
|
|
64
67
|
}
|
|
65
68
|
get series() {
|
|
66
|
-
this.heldSeries = this.parsed(this.heldSeries, (table, data) => table ? readSeries(table) : data ? seriesFromData(data
|
|
69
|
+
this.heldSeries = this.parsed(this.heldSeries, (table, data) => table ? readSeries(table) : data ? seriesFromData(data) : []);
|
|
67
70
|
return this.heldSeries.value;
|
|
68
71
|
}
|
|
69
72
|
get rows() {
|
|
@@ -496,6 +496,15 @@ export declare class VirtualizeController implements ReactiveController {
|
|
|
496
496
|
*
|
|
497
497
|
* Once per controller, because a warning per frame is one people filter out
|
|
498
498
|
* and this one has to be believed the day it is right.
|
|
499
|
+
*
|
|
500
|
+
* **The usual cause is a scrolling box with no height of its own.** Its
|
|
501
|
+
* `clientHeight` is then a function of the rows just drawn, so measuring
|
|
502
|
+
* moves what is being measured and the two never agree. The other is rows
|
|
503
|
+
* that report no height, such as a row drawn with `display: contents`, which
|
|
504
|
+
* is a fault in the component that draws them. The message names the
|
|
505
|
+
* element and the part the box is, which is what a consumer's stylesheet
|
|
506
|
+
* reaches: the report prints only the message, and a class name or "the
|
|
507
|
+
* container" told a reader nothing they could select.
|
|
499
508
|
*/
|
|
500
509
|
private report;
|
|
501
510
|
}
|
|
@@ -451,16 +451,18 @@ export class VirtualizeController {
|
|
|
451
451
|
}
|
|
452
452
|
this.warned = true;
|
|
453
453
|
const host = this.host;
|
|
454
|
+
const tag = typeof host.localName === 'string' ? host.localName : undefined;
|
|
455
|
+
const part = this.options.container()?.getAttribute('part')?.split(' ')[0];
|
|
454
456
|
reportRuntime({
|
|
455
457
|
kind: 'unsettled-window',
|
|
456
|
-
message:
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
tag
|
|
458
|
+
message: tag && part
|
|
459
|
+
? `The number of rows that fit in the \`${part}\` part of <${tag}> did not settle, which ` +
|
|
460
|
+
'usually means the part has no block size of its own. ' +
|
|
461
|
+
`Give ${tag}::part(${part}) a block size and an overflow.`
|
|
462
|
+
: `The number of rows that fit in ${tag ? `<${tag}>` : 'a windowed list'} did not settle, ` +
|
|
463
|
+
'which usually means the box they scroll in has no block size of its own. Give that ' +
|
|
464
|
+
'box a block size and an overflow.',
|
|
465
|
+
tag,
|
|
464
466
|
});
|
|
465
467
|
}
|
|
466
468
|
}
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { LitElement, unsafeCSS } from 'lit';
|
|
2
|
-
import { auditReactProps } from '@fusion.dev/helix-core/audits/react';
|
|
3
2
|
import { measuring, reportRender } from '../utils/report-render/report-render.js';
|
|
4
3
|
import { noteVersion, reportUsage } from '../utils/report-usage/report-usage.js';
|
|
5
|
-
import { applyReactProperties, bindReactHandler,
|
|
4
|
+
import { applyReactProperties, bindReactHandler, lookForReact } from './react-bridge.js';
|
|
6
5
|
const BOX_SIZING = '*,::before,::after{box-sizing:border-box}';
|
|
7
6
|
const HIDDEN = ":host([hidden]:not([hidden='until-found'])){display:none}";
|
|
8
7
|
const SETTLED = ':host(:not(:state(settled))),:host(:not(:state(settled))) *{transition:none!important}';
|
|
9
8
|
const BASE = unsafeCSS(BOX_SIZING + HIDDEN + SETTLED);
|
|
10
|
-
const LOOK_EVERY = 16;
|
|
11
|
-
const LOOK_FOR = 96;
|
|
12
9
|
export class HelixElement extends LitElement {
|
|
13
10
|
static { this.tier = 'helix-headless-'; }
|
|
14
11
|
tag(name) {
|
|
@@ -112,26 +109,11 @@ export class HelixElement extends LitElement {
|
|
|
112
109
|
firstUpdated(changed) {
|
|
113
110
|
super.firstUpdated(changed);
|
|
114
111
|
reportUsage(this);
|
|
115
|
-
|
|
112
|
+
const applied = applyReactProperties(this);
|
|
116
113
|
requestAnimationFrame(() => {
|
|
117
114
|
this.toggleState('settled', true);
|
|
118
115
|
this.resolveSettled();
|
|
119
|
-
|
|
120
|
-
const look = () => {
|
|
121
|
-
const props = reactPropsOf(this);
|
|
122
|
-
if (props === undefined) {
|
|
123
|
-
if (waited < LOOK_FOR) {
|
|
124
|
-
waited += LOOK_EVERY;
|
|
125
|
-
setTimeout(look, LOOK_EVERY);
|
|
126
|
-
}
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
if (!applied) {
|
|
130
|
-
applied = applyReactProperties(this);
|
|
131
|
-
}
|
|
132
|
-
auditReactProps(this, props);
|
|
133
|
-
};
|
|
134
|
-
look();
|
|
116
|
+
lookForReact(this, applied);
|
|
135
117
|
});
|
|
136
118
|
}
|
|
137
119
|
}
|
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import type { LitElement } from 'lit';
|
|
2
2
|
/** The props React is holding for a node, if React is holding any. */
|
|
3
3
|
export declare const reactPropsOf: (node: EventTarget) => Record<string, unknown> | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* Looks for React's props a frame after an element's first update, and goes
|
|
6
|
+
* on looking for `LOOK_FOR` where they have not arrived.
|
|
7
|
+
*
|
|
8
|
+
* **The ordering this is for**: a definition evaluated with the client bundle
|
|
9
|
+
* upgrades before React hydrates, so `firstUpdated` found no bag to read and
|
|
10
|
+
* there may be none for a frame or two yet. `applied` says whether that first
|
|
11
|
+
* look assigned the properties already.
|
|
12
|
+
*
|
|
13
|
+
* **Audited here as well as at dispatch, in a development build, and this is
|
|
14
|
+
* the half that catches the real mistake.** From inside `dispatchEvent` alone,
|
|
15
|
+
* an element holding a handler prop and sending nothing is never looked at,
|
|
16
|
+
* and a prop on the wrong element is exactly the case where that element has
|
|
17
|
+
* no reason to dispatch. Once per element either way: the audit holds its own
|
|
18
|
+
* set.
|
|
19
|
+
*/
|
|
20
|
+
export declare const lookForReact: (host: LitElement, applied: boolean) => void;
|
|
4
21
|
/**
|
|
5
22
|
* The prop React would be holding for this event, if it is holding one.
|
|
6
23
|
*
|
|
@@ -13,6 +13,60 @@ export const reactPropsOf = (node) => {
|
|
|
13
13
|
reactPropsKey = found;
|
|
14
14
|
return stash[found];
|
|
15
15
|
};
|
|
16
|
+
const REACT_LISTENING = '_reactListening';
|
|
17
|
+
const REACT_EVENTS = '__reactEvents$';
|
|
18
|
+
const reactOnPage = (node) => Object.keys(node.ownerDocument ?? node).some((key) => key.startsWith(REACT_LISTENING) || key.startsWith(REACT_EVENTS));
|
|
19
|
+
const LOOK_EVERY = 16;
|
|
20
|
+
const LOOK_FOR = 96;
|
|
21
|
+
const looking = [];
|
|
22
|
+
let ticks = 0;
|
|
23
|
+
let booked = false;
|
|
24
|
+
const take = (host, applied) => {
|
|
25
|
+
const props = reactPropsOf(host);
|
|
26
|
+
if (props === undefined) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
if (!applied) {
|
|
30
|
+
applyReactProperties(host);
|
|
31
|
+
}
|
|
32
|
+
auditReactProps(host, props);
|
|
33
|
+
return true;
|
|
34
|
+
};
|
|
35
|
+
const tick = () => {
|
|
36
|
+
booked = false;
|
|
37
|
+
ticks += 1;
|
|
38
|
+
const now = performance.now();
|
|
39
|
+
let page;
|
|
40
|
+
let react = false;
|
|
41
|
+
let kept = 0;
|
|
42
|
+
for (const one of looking) {
|
|
43
|
+
const own = one.host.ownerDocument ?? one.host;
|
|
44
|
+
if (own !== page) {
|
|
45
|
+
page = own;
|
|
46
|
+
react = reactOnPage(one.host);
|
|
47
|
+
}
|
|
48
|
+
const done = (react && take(one.host, false)) ||
|
|
49
|
+
(ticks - one.after >= LOOK_FOR / LOOK_EVERY && now - one.since >= LOOK_FOR);
|
|
50
|
+
if (!done) {
|
|
51
|
+
looking[kept] = one;
|
|
52
|
+
kept += 1;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
looking.length = kept;
|
|
56
|
+
book();
|
|
57
|
+
};
|
|
58
|
+
const book = () => {
|
|
59
|
+
if (!booked && looking.length > 0) {
|
|
60
|
+
booked = true;
|
|
61
|
+
setTimeout(tick, LOOK_EVERY);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
export const lookForReact = (host, applied) => {
|
|
65
|
+
if (!(reactOnPage(host) && take(host, applied))) {
|
|
66
|
+
looking.push({ after: ticks, host, since: performance.now() });
|
|
67
|
+
book();
|
|
68
|
+
}
|
|
69
|
+
};
|
|
16
70
|
const REACT_WIRES = new Set(['scroll', 'scrollend']);
|
|
17
71
|
export const reactPropFor = (event) => {
|
|
18
72
|
if (event.type.includes('-')) {
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { auditNesting } from '@fusion.dev/helix-core/audits/nesting';
|
|
|
4
4
|
export { auditServed } from '@fusion.dev/helix-core/audits/served';
|
|
5
5
|
export { AriaForwardController, type AriaForwardOptions, } from './controllers/aria-forward-controller/aria-forward-controller.js';
|
|
6
6
|
export { ArrivalController, type ArrivalOptions, } from './controllers/arrival-controller/arrival-controller.js';
|
|
7
|
+
export { CategoryAxisController, type CategoryAxisHost, } from './controllers/category-axis-controller/category-axis-controller.js';
|
|
7
8
|
export { ChartPointerController, type ChartPointerHost, type ReadingHeard, } from './controllers/chart-pointer-controller/chart-pointer-controller.js';
|
|
8
9
|
export { ChildrenController } from './controllers/children-controller/children-controller.js';
|
|
9
10
|
export { CommandController, type CommandOptions, HelixFallbackCommandEvent, supportsInvokers, } from './controllers/command-controller/command-controller.js';
|
|
@@ -43,7 +44,7 @@ export { HelixElement, whenHydrated } from './helix-element/helix-element.js';
|
|
|
43
44
|
export { MIRROR_RULE, SCROLLER_RULE, SPOKEN_RULE, VIRTUAL_RULE, } from './helix-element/mark-rules.js';
|
|
44
45
|
export { HelixFormElement } from './helix-form-element/helix-form-element.js';
|
|
45
46
|
export { HelixFormElementBase } from './helix-form-element/helix-form-element-base.js';
|
|
46
|
-
export { MixinChartSource, type MixinChartSourceInterface, PALETTE, PLOT_HEIGHT, PLOT_WIDTH, POSITION_LABEL, seriesIndex, seriesNames, } from './mixins/mixin-chart-source/mixin-chart-source.js';
|
|
47
|
+
export { MixinChartSource, type MixinChartSourceInterface, PALETTE, PLOT_HEIGHT, PLOT_WIDTH, POSITION_LABEL, READINGS_PAGE, seriesIndex, seriesNames, } from './mixins/mixin-chart-source/mixin-chart-source.js';
|
|
47
48
|
export { MixinChartStatus, type MixinChartStatusInterface, } from './mixins/mixin-chart-status/mixin-chart-status.js';
|
|
48
49
|
export { type CollectionElement, MixinCollection, } from './mixins/mixin-collection/mixin-collection.js';
|
|
49
50
|
export { MixinControllerActive, type MixinControllerActiveInterface, } from './mixins/mixin-controller-active/mixin-controller-active.js';
|
|
@@ -66,6 +67,7 @@ export { type DrawAutofillOptions, drawAutofill, } from './utils/autofill-select
|
|
|
66
67
|
export { type BoxAxisText, type BoxLabel, type BoxLabelSizes, type BoxPlace, boxLabels, type SeenPlace, } from './utils/box-labels/box-labels.js';
|
|
67
68
|
export { addDays, addMonths, type Barring, barredBy, type CalendarParts, type CalendarView, calendarLocale, type DateCalendar, localToday, MONTH_CALENDARS, type MonthCalendar, type MonthSystem, monthOf, monthOut, monthSystem, type PeriodCell, type PeriodPage, periodCells, periodKey, periodMove, sameMonth, todayIn, weekendOf, weekStartOf, YEARS_SHOWN, yearOut, yearPageOf, } from './utils/calendar/calendar.js';
|
|
68
69
|
export { capturePointer } from './utils/capture-pointer/capture-pointer.js';
|
|
70
|
+
export { type AxisLengths, type Category, type PlacedLabel, placeLabels, UNMEASURED_LABELS, } from './utils/category-labels/category-labels.js';
|
|
69
71
|
export { centerInView } from './utils/center-in-view/center-in-view.js';
|
|
70
72
|
export { ariaChord, type ChordPress, chordParts, matchesChord, printableChord, selectsAll, } from './utils/chord/chord.js';
|
|
71
73
|
export { type KeyNames, keyNames } from './utils/chord/key-names.js';
|
|
@@ -73,6 +75,7 @@ export { chosenValues } from './utils/chosen-values/chosen-values.js';
|
|
|
73
75
|
export { clamp } from './utils/clamp/clamp.js';
|
|
74
76
|
export { clockLocale, dayPeriod, type HourCycle, isTwelveHour, nearestTime, offeredTimes, type TimeFault, type TimeRules, timeBarred, timeFault, wheelParts, wholeHour, } from './utils/clock/clock.js';
|
|
75
77
|
export { closestInstance } from './utils/closest-instance/closest-instance.js';
|
|
78
|
+
export { closestName } from './utils/closest-name/closest-name.js';
|
|
76
79
|
export { type ColorWords, colorName, hueName } from './utils/color-name/color-name.js';
|
|
77
80
|
export { columnStep } from './utils/column-step/column-step.js';
|
|
78
81
|
export { contextBudget, type Holder, LIVE, pageBudget, } from './utils/context-budget/context-budget.js';
|
|
@@ -103,18 +106,21 @@ export { headingIn, headingsIn } from './utils/heading-in/heading-in.js';
|
|
|
103
106
|
export { headingLevel } from './utils/heading-level/heading-level.js';
|
|
104
107
|
export { type HostAria, hostAria } from './utils/host-aria/host-aria.js';
|
|
105
108
|
export { hostWords } from './utils/host-words/host-words.js';
|
|
109
|
+
export { howToRegister, type Unregistered } from './utils/how-to-register/how-to-register.js';
|
|
106
110
|
export { defaultButton, submitImplicitly, submits, submitsOnEnter, } from './utils/implicit-submit/implicit-submit.js';
|
|
107
111
|
export { type DisableableItem, forgetDisabled, inheritDisabled, } from './utils/inherit-disabled/inherit-disabled.js';
|
|
108
112
|
export { inlineStep } from './utils/inline-step/inline-step.js';
|
|
109
113
|
export { isRtl } from './utils/is-rtl/is-rtl.js';
|
|
110
114
|
export { keyClaimed } from './utils/key-claimed/key-claimed.js';
|
|
111
|
-
export {
|
|
115
|
+
export { labelsOf } from './utils/labels-of/labels-of.js';
|
|
116
|
+
export { type Curve, columnPoints, linePath, type Point } from './utils/line-path/line-path.js';
|
|
112
117
|
export { type EndInSight, endInSight } from './utils/load-more/load-more.js';
|
|
113
118
|
export { localeOf, runtimeLocale } from './utils/locale-of/locale-of.js';
|
|
114
119
|
export { markRegion, type RegionOptions } from './utils/mark-region/mark-region.js';
|
|
115
120
|
export { copiesOf, type MediaCopy, sameCopies, } from './utils/media-copy/media-copy.js';
|
|
116
121
|
export { nameText } from './utils/name-text/name-text.js';
|
|
117
122
|
export { nearest } from './utils/nearest/nearest.js';
|
|
123
|
+
export { numberConverter, timeConverter } from './utils/number-converter/number-converter.js';
|
|
118
124
|
export { formatNumber, formatPercent, parseNumber, readingText, } from './utils/number-format/number-format.js';
|
|
119
125
|
export { onUnlessFalse } from './utils/on-unless-false/on-unless-false.js';
|
|
120
126
|
export { applyOwnRule, type OwnRule } from './utils/own-rule/own-rule.js';
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export { auditNesting } from '@fusion.dev/helix-core/audits/nesting';
|
|
|
4
4
|
export { auditServed } from '@fusion.dev/helix-core/audits/served';
|
|
5
5
|
export { AriaForwardController, } from './controllers/aria-forward-controller/aria-forward-controller.js';
|
|
6
6
|
export { ArrivalController, } from './controllers/arrival-controller/arrival-controller.js';
|
|
7
|
+
export { CategoryAxisController, } from './controllers/category-axis-controller/category-axis-controller.js';
|
|
7
8
|
export { ChartPointerController, } from './controllers/chart-pointer-controller/chart-pointer-controller.js';
|
|
8
9
|
export { ChildrenController } from './controllers/children-controller/children-controller.js';
|
|
9
10
|
export { CommandController, HelixFallbackCommandEvent, supportsInvokers, } from './controllers/command-controller/command-controller.js';
|
|
@@ -43,7 +44,7 @@ export { HelixElement, whenHydrated } from './helix-element/helix-element.js';
|
|
|
43
44
|
export { MIRROR_RULE, SCROLLER_RULE, SPOKEN_RULE, VIRTUAL_RULE, } from './helix-element/mark-rules.js';
|
|
44
45
|
export { HelixFormElement } from './helix-form-element/helix-form-element.js';
|
|
45
46
|
export { HelixFormElementBase } from './helix-form-element/helix-form-element-base.js';
|
|
46
|
-
export { MixinChartSource, PALETTE, PLOT_HEIGHT, PLOT_WIDTH, POSITION_LABEL, seriesIndex, seriesNames, } from './mixins/mixin-chart-source/mixin-chart-source.js';
|
|
47
|
+
export { MixinChartSource, PALETTE, PLOT_HEIGHT, PLOT_WIDTH, POSITION_LABEL, READINGS_PAGE, seriesIndex, seriesNames, } from './mixins/mixin-chart-source/mixin-chart-source.js';
|
|
47
48
|
export { MixinChartStatus, } from './mixins/mixin-chart-status/mixin-chart-status.js';
|
|
48
49
|
export { MixinCollection, } from './mixins/mixin-collection/mixin-collection.js';
|
|
49
50
|
export { MixinControllerActive, } from './mixins/mixin-controller-active/mixin-controller-active.js';
|
|
@@ -66,6 +67,7 @@ export { drawAutofill, } from './utils/autofill-select/autofill-select.js';
|
|
|
66
67
|
export { boxLabels, } from './utils/box-labels/box-labels.js';
|
|
67
68
|
export { addDays, addMonths, barredBy, calendarLocale, localToday, MONTH_CALENDARS, monthOf, monthOut, monthSystem, periodCells, periodKey, periodMove, sameMonth, todayIn, weekendOf, weekStartOf, YEARS_SHOWN, yearOut, yearPageOf, } from './utils/calendar/calendar.js';
|
|
68
69
|
export { capturePointer } from './utils/capture-pointer/capture-pointer.js';
|
|
70
|
+
export { placeLabels, UNMEASURED_LABELS, } from './utils/category-labels/category-labels.js';
|
|
69
71
|
export { centerInView } from './utils/center-in-view/center-in-view.js';
|
|
70
72
|
export { ariaChord, chordParts, matchesChord, printableChord, selectsAll, } from './utils/chord/chord.js';
|
|
71
73
|
export { keyNames } from './utils/chord/key-names.js';
|
|
@@ -73,6 +75,7 @@ export { chosenValues } from './utils/chosen-values/chosen-values.js';
|
|
|
73
75
|
export { clamp } from './utils/clamp/clamp.js';
|
|
74
76
|
export { clockLocale, dayPeriod, isTwelveHour, nearestTime, offeredTimes, timeBarred, timeFault, wheelParts, wholeHour, } from './utils/clock/clock.js';
|
|
75
77
|
export { closestInstance } from './utils/closest-instance/closest-instance.js';
|
|
78
|
+
export { closestName } from './utils/closest-name/closest-name.js';
|
|
76
79
|
export { colorName, hueName } from './utils/color-name/color-name.js';
|
|
77
80
|
export { columnStep } from './utils/column-step/column-step.js';
|
|
78
81
|
export { contextBudget, LIVE, pageBudget, } from './utils/context-budget/context-budget.js';
|
|
@@ -103,18 +106,21 @@ export { headingIn, headingsIn } from './utils/heading-in/heading-in.js';
|
|
|
103
106
|
export { headingLevel } from './utils/heading-level/heading-level.js';
|
|
104
107
|
export { hostAria } from './utils/host-aria/host-aria.js';
|
|
105
108
|
export { hostWords } from './utils/host-words/host-words.js';
|
|
109
|
+
export { howToRegister } from './utils/how-to-register/how-to-register.js';
|
|
106
110
|
export { defaultButton, submitImplicitly, submits, submitsOnEnter, } from './utils/implicit-submit/implicit-submit.js';
|
|
107
111
|
export { forgetDisabled, inheritDisabled, } from './utils/inherit-disabled/inherit-disabled.js';
|
|
108
112
|
export { inlineStep } from './utils/inline-step/inline-step.js';
|
|
109
113
|
export { isRtl } from './utils/is-rtl/is-rtl.js';
|
|
110
114
|
export { keyClaimed } from './utils/key-claimed/key-claimed.js';
|
|
111
|
-
export {
|
|
115
|
+
export { labelsOf } from './utils/labels-of/labels-of.js';
|
|
116
|
+
export { columnPoints, linePath } from './utils/line-path/line-path.js';
|
|
112
117
|
export { endInSight } from './utils/load-more/load-more.js';
|
|
113
118
|
export { localeOf, runtimeLocale } from './utils/locale-of/locale-of.js';
|
|
114
119
|
export { markRegion } from './utils/mark-region/mark-region.js';
|
|
115
120
|
export { copiesOf, sameCopies, } from './utils/media-copy/media-copy.js';
|
|
116
121
|
export { nameText } from './utils/name-text/name-text.js';
|
|
117
122
|
export { nearest } from './utils/nearest/nearest.js';
|
|
123
|
+
export { numberConverter, timeConverter } from './utils/number-converter/number-converter.js';
|
|
118
124
|
export { formatNumber, formatPercent, parseNumber, readingText, } from './utils/number-format/number-format.js';
|
|
119
125
|
export { onUnlessFalse } from './utils/on-unless-false/on-unless-false.js';
|
|
120
126
|
export { applyOwnRule } from './utils/own-rule/own-rule.js';
|
|
@@ -143,6 +143,11 @@ export declare class MixinChartSourceInterface {
|
|
|
143
143
|
* cells are shown as the table writes them, whatever this says.
|
|
144
144
|
*/
|
|
145
145
|
locale: string;
|
|
146
|
+
/**
|
|
147
|
+
* The name of the button that shows the next page of readings in the table
|
|
148
|
+
* drawn from `data`, which holds a page at a time past `READINGS_PAGE`.
|
|
149
|
+
*/
|
|
150
|
+
nextReadingsLabel: string;
|
|
146
151
|
/**
|
|
147
152
|
* Where a reading the keys moved to stands among the readings, said after
|
|
148
153
|
* it. `{position}` and `{count}` are replaced with the numbers, written in
|
|
@@ -152,6 +157,17 @@ export declare class MixinChartSourceInterface {
|
|
|
152
157
|
* {count}`, or to nothing to say no position.
|
|
153
158
|
*/
|
|
154
159
|
positionLabel: string;
|
|
160
|
+
/**
|
|
161
|
+
* The name of the button that shows the previous page of readings in the
|
|
162
|
+
* table drawn from `data`.
|
|
163
|
+
*/
|
|
164
|
+
previousReadingsLabel: string;
|
|
165
|
+
/**
|
|
166
|
+
* Which readings the page of the table drawn from `data` holds, said beside
|
|
167
|
+
* its buttons and as each page arrives. `{first}`, `{last}` and `{count}`
|
|
168
|
+
* are replaced with the numbers, written in `locale`.
|
|
169
|
+
*/
|
|
170
|
+
readingsLabel: string;
|
|
155
171
|
/**
|
|
156
172
|
* Whether the readings have been looked for: false on the server and while
|
|
157
173
|
* hydrating, where no table is resolved and no data is read.
|
|
@@ -209,6 +225,18 @@ export declare class MixinChartSourceInterface {
|
|
|
209
225
|
*/
|
|
210
226
|
protected renderTable(): TemplateResult | typeof nothing;
|
|
211
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* How many readings the table drawn from `data` holds at once.
|
|
230
|
+
*
|
|
231
|
+
* **A table of every reading cost four elements a reading**, so a chart of
|
|
232
|
+
* 10,000 readings built 40,000 nodes before its first frame and rebuilt them
|
|
233
|
+
* on every new set of data, and a screen reader was handed a table it would
|
|
234
|
+
* take an afternoon to read. Past this many the table holds a page of them,
|
|
235
|
+
* says which, and takes buttons to the next and the previous page, which a
|
|
236
|
+
* screen reader presses as it reads. The keys on the plot still walk every
|
|
237
|
+
* reading.
|
|
238
|
+
*/
|
|
239
|
+
export declare const READINGS_PAGE = 200;
|
|
212
240
|
/**
|
|
213
241
|
* The words a chart says a reading's place in by default, such as "2 of 12":
|
|
214
242
|
* every chart's `position-label`, and Sparkline's, which is no chart source.
|