@fusion.dev/helix-core 0.206.0 → 0.207.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +22 -13
  2. package/custom-elements.json +1 -37171
  3. package/dist/controllers/aria-forward-controller/aria-forward-controller.js +5 -6
  4. package/dist/controllers/category-axis-controller/category-axis-controller.d.ts +75 -0
  5. package/dist/controllers/category-axis-controller/category-axis-controller.js +128 -0
  6. package/dist/controllers/source-controller/source-controller.d.ts +2 -0
  7. package/dist/controllers/source-controller/source-controller.js +5 -2
  8. package/dist/controllers/virtualize-controller/virtualize-controller.d.ts +9 -0
  9. package/dist/controllers/virtualize-controller/virtualize-controller.js +10 -8
  10. package/dist/helix-element/helix-element.js +3 -21
  11. package/dist/helix-element/react-bridge.d.ts +17 -0
  12. package/dist/helix-element/react-bridge.js +54 -0
  13. package/dist/index.d.ts +8 -2
  14. package/dist/index.js +8 -2
  15. package/dist/mixins/mixin-chart-source/mixin-chart-source.d.ts +28 -0
  16. package/dist/mixins/mixin-chart-source/mixin-chart-source.js +76 -9
  17. package/dist/mixins/mixin-field/mixin-field.js +2 -0
  18. package/dist/mixins/mixin-tokens/mixin-tokens.js +2 -1
  19. package/dist/mixins/mixin-viewport/mixin-viewport.js +3 -2
  20. package/dist/utils/audit-children/audit-children.d.ts +1 -0
  21. package/dist/utils/audit-children/audit-children.js +2 -2
  22. package/dist/utils/audit-name/audit-name.js +2 -2
  23. package/dist/utils/audit-number/audit-number-off.d.ts +12 -0
  24. package/dist/utils/audit-number/audit-number-off.js +2 -0
  25. package/dist/utils/audit-number/audit-number.d.ts +23 -0
  26. package/dist/utils/audit-number/audit-number.js +21 -0
  27. package/dist/utils/category-labels/category-labels.d.ts +74 -0
  28. package/dist/utils/category-labels/category-labels.js +230 -0
  29. package/dist/utils/closest-name/closest-name.d.ts +11 -0
  30. package/dist/utils/closest-name/closest-name.js +35 -0
  31. package/dist/utils/custom-element/custom-element.js +11 -6
  32. package/dist/utils/draw-tooltip/draw-tooltip.js +3 -2
  33. package/dist/utils/host-words/host-words.js +2 -1
  34. package/dist/utils/how-to-register/how-to-register.d.ts +20 -0
  35. package/dist/utils/how-to-register/how-to-register.js +3 -0
  36. package/dist/utils/labels-of/labels-of.d.ts +23 -0
  37. package/dist/utils/labels-of/labels-of.js +8 -0
  38. package/dist/utils/line-path/line-path.d.ts +18 -0
  39. package/dist/utils/line-path/line-path.js +36 -0
  40. package/dist/utils/locale-of/locale-of.d.ts +40 -2
  41. package/dist/utils/locale-of/locale-of.js +30 -16
  42. package/dist/utils/number-converter/number-converter.d.ts +44 -0
  43. package/dist/utils/number-converter/number-converter.js +34 -0
  44. package/dist/utils/read-data/read-data.d.ts +1 -1
  45. package/dist/utils/read-data/read-data.js +23 -10
  46. package/dist/utils/read-series/read-series.js +9 -2
  47. package/dist/utils/report-runtime/report-runtime.d.ts +8 -0
  48. package/dist/utils/scale/scale.d.ts +7 -1
  49. package/dist/utils/scale/scale.js +9 -4
  50. package/dist/utils/scroll-item-into-view/scroll-item-into-view.d.ts +5 -0
  51. package/dist/utils/scroll-item-into-view/scroll-item-into-view.js +3 -2
  52. package/dist/utils/watch-labels/watch-labels.d.ts +4 -2
  53. package/dist/utils/watch-labels/watch-labels.js +14 -14
  54. package/dist/version.d.ts +1 -1
  55. package/dist/version.js +1 -1
  56. package/dist/words/held.d.ts +11 -3
  57. package/dist/words/words-locale.js +20 -8
  58. package/dist/words/words.js +10 -5
  59. 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
- const own = target.labels;
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.labels;
46
- const named = this.named ? this.named() : this.namedByItself(target);
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 [...labels].filter((label) => label instanceof HTMLElement);
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 (!isChartData(host.data)) {
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, wordsLocale(this.host)) : []);
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: `VirtualizeController: the window did not settle in ${UNSTABLE} passes, so it is holding ` +
457
- 'the last one. That happens when the scrolling box has no height of its own: its ' +
458
- '`clientHeight` is then a function of the rows just drawn, so measuring moves what is ' +
459
- 'being measured and the two never agree. Give the container a block size and an ' +
460
- 'overflow, which is what the styled tier does and what a headless consumer has to. ' +
461
- 'It also happens when the rows report no height, such as a row drawn with ' +
462
- '`display: contents`, which is a fault in the component that draws them.',
463
- tag: typeof host.localName === 'string' ? host.localName : undefined,
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, reactPropsOf } from './react-bridge.js';
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
- let applied = applyReactProperties(this);
112
+ const applied = applyReactProperties(this);
116
113
  requestAnimationFrame(() => {
117
114
  this.toggleState('settled', true);
118
115
  this.resolveSettled();
119
- let waited = 0;
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 { type Curve, linePath, type Point } from './utils/line-path/line-path.js';
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 { linePath } from './utils/line-path/line-path.js';
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.