@fusion.dev/helix-core 0.147.0 → 0.148.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/custom-elements.json +1741 -133
  2. package/dist/controllers/children-controller/children-controller.d.ts +28 -0
  3. package/dist/controllers/children-controller/children-controller.js +18 -0
  4. package/dist/controllers/countdown-controller/countdown-controller.d.ts +15 -1
  5. package/dist/controllers/countdown-controller/countdown-controller.js +27 -2
  6. package/dist/controllers/floating-controller/floating-controller.d.ts +12 -1
  7. package/dist/controllers/floating-controller/floating-controller.js +2 -1
  8. package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.d.ts +25 -0
  9. package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.js +8 -0
  10. package/dist/controllers/open-phase-controller/open-phase-controller.d.ts +10 -0
  11. package/dist/controllers/open-phase-controller/open-phase-controller.js +7 -4
  12. package/dist/controllers/option-window-controller/option-window-controller.d.ts +96 -0
  13. package/dist/controllers/option-window-controller/option-window-controller.js +81 -0
  14. package/dist/controllers/popover-controller/popover-controller.js +1 -1
  15. package/dist/helix-element/helix-element.d.ts +14 -5
  16. package/dist/helix-element/helix-element.js +4 -2
  17. package/dist/index.d.ts +24 -10
  18. package/dist/index.js +22 -8
  19. package/dist/mixins/mixin-chart-source/mixin-chart-source.d.ts +5 -0
  20. package/dist/mixins/mixin-chart-source/mixin-chart-source.js +3 -0
  21. package/dist/mixins/mixin-viewport/mixin-viewport.js +8 -7
  22. package/dist/utils/assign-slots/assign-slots.d.ts +18 -0
  23. package/dist/utils/assign-slots/assign-slots.js +14 -0
  24. package/dist/utils/autofill-select/autofill-select.d.ts +40 -0
  25. package/dist/utils/autofill-select/autofill-select.js +14 -0
  26. package/dist/utils/center-in-view/center-in-view.d.ts +18 -0
  27. package/dist/utils/center-in-view/center-in-view.js +14 -0
  28. package/dist/utils/chord/key-names.d.ts +30 -0
  29. package/dist/utils/chord/key-names.js +52 -0
  30. package/dist/utils/column-step/column-step.d.ts +13 -0
  31. package/dist/utils/column-step/column-step.js +9 -0
  32. package/dist/utils/data-options/data-options.d.ts +60 -0
  33. package/dist/utils/data-options/data-options.js +46 -0
  34. package/dist/utils/draw-tooltip/draw-tooltip.d.ts +7 -2
  35. package/dist/utils/draw-tooltip/draw-tooltip.js +1 -1
  36. package/dist/utils/draw-tooltip/spoken-reading.d.ts +3 -2
  37. package/dist/utils/draw-tooltip/spoken-reading.js +3 -2
  38. package/dist/utils/export-parts/export-parts.d.ts +14 -0
  39. package/dist/utils/export-parts/export-parts.js +1 -0
  40. package/dist/utils/fill-template/fill-template.d.ts +9 -0
  41. package/dist/utils/fill-template/fill-template.js +3 -0
  42. package/dist/utils/filter-marks/filter-marks.d.ts +36 -0
  43. package/dist/utils/filter-marks/filter-marks.js +41 -0
  44. package/dist/utils/focus-target/focus-target.d.ts +12 -0
  45. package/dist/utils/focus-target/focus-target.js +9 -0
  46. package/dist/utils/focusable-elements/focusable-elements.d.ts +17 -0
  47. package/dist/utils/focusable-elements/focusable-elements.js +8 -0
  48. package/dist/utils/grid-move/grid-move.d.ts +56 -0
  49. package/dist/utils/grid-move/grid-move.js +28 -0
  50. package/dist/utils/number-format/number-format.d.ts +7 -0
  51. package/dist/utils/number-format/number-format.js +1 -0
  52. package/dist/utils/report-runtime/report-runtime.d.ts +7 -1
  53. package/dist/utils/report-values/report-values.d.ts +16 -4
  54. package/dist/utils/report-values/report-values.js +2 -2
  55. package/dist/utils/request-change/request-change.d.ts +11 -0
  56. package/dist/utils/request-change/request-change.js +10 -0
  57. package/dist/utils/scroll-item-into-view/scroll-item-into-view.d.ts +17 -0
  58. package/dist/utils/scroll-item-into-view/scroll-item-into-view.js +1 -0
  59. package/dist/utils/slot-filled/slot-filled.d.ts +8 -0
  60. package/dist/utils/slot-filled/slot-filled.js +1 -0
  61. package/dist/utils/toolbar-around/toolbar-around.d.ts +18 -0
  62. package/dist/utils/toolbar-around/toolbar-around.js +5 -0
  63. package/dist/version.d.ts +1 -1
  64. package/dist/version.js +1 -1
  65. package/package.json +4 -4
@@ -0,0 +1,28 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ /**
3
+ * Calls back whenever a child arrives at its host or leaves it.
4
+ *
5
+ * **A root that assigns its slots by hand needs this, and nothing else
6
+ * does.** `slotchange` fires when an assignment changes, and under
7
+ * `slotAssignment: 'manual'` the only thing that changes one is the host
8
+ * calling `assign`. A child appended after the first render reaches no slot,
9
+ * fires no `slotchange` and schedules no render, so it is drawn nowhere and
10
+ * counted by nothing: a notification added to a feed of three was never shown
11
+ * until some property changed. A default slot takes whatever is there and
12
+ * needs no watcher.
13
+ *
14
+ * **`childList` and not `subtree`.** A child rewriting its own text, a
15
+ * streaming chat turn on every frame, is no arrival, and a subtree watch would
16
+ * render the whole host again for each change.
17
+ *
18
+ * Asks the host for an update unless given something else to do. Watches
19
+ * from connection to disconnection, and again after a move.
20
+ */
21
+ export declare class ChildrenController implements ReactiveController {
22
+ private readonly host;
23
+ private readonly changed;
24
+ private watcher;
25
+ constructor(host: ReactiveControllerHost & Node, changed?: () => void);
26
+ hostConnected(): void;
27
+ hostDisconnected(): void;
28
+ }
@@ -0,0 +1,18 @@
1
+ export class ChildrenController {
2
+ constructor(host, changed = () => host.requestUpdate()) {
3
+ this.host = host;
4
+ this.changed = changed;
5
+ this.watcher = undefined;
6
+ host.addController(this);
7
+ }
8
+ hostConnected() {
9
+ if (typeof MutationObserver === 'undefined') {
10
+ return;
11
+ }
12
+ this.watcher ??= new MutationObserver(() => this.changed());
13
+ this.watcher.observe(this.host, { childList: true });
14
+ }
15
+ hostDisconnected() {
16
+ this.watcher?.disconnect();
17
+ }
18
+ }
@@ -3,6 +3,13 @@ import type { ReactiveController, ReactiveControllerHost } from 'lit';
3
3
  export interface CountdownOptions {
4
4
  /** Runs once the time is spent. */
5
5
  done: () => void;
6
+ /**
7
+ * Whether a hidden page holds the clock, as a pointer on the message does,
8
+ * so a timed message is not spent in a background tab. Counted as one hold
9
+ * however often the page hides, and a clock started while the page is
10
+ * hidden waits for it to show. Defaults to `false`.
11
+ */
12
+ holdWhileHidden?: boolean;
6
13
  /** Runs with `true` when the clock stops partway and `false` when it runs again. */
7
14
  paused?: (paused: boolean) => void;
8
15
  }
@@ -23,7 +30,10 @@ export interface CountdownOptions {
23
30
  * **Leaving the page pauses it**, keeping the time already spent, and drops
24
31
  * the holds, since a pointer or focus on a detached element never leaves it.
25
32
  * Coming back does not resume on its own: the host says whether it should,
26
- * such as only while it is still showing. `DelayController` is the other wait
33
+ * such as only while it is still showing.
34
+ *
35
+ * **A hidden page holds it too, where the host asks** with `holdWhileHidden`:
36
+ * RAC's toast queue stops its timers while the window is in the background. `DelayController` is the other wait
27
37
  * in core, for a wait that starts over and forgets itself on leaving.
28
38
  */
29
39
  export declare class CountdownController implements ReactiveController {
@@ -35,6 +45,10 @@ export declare class CountdownController implements ReactiveController {
35
45
  private timer;
36
46
  /** How many reasons there are to hold the clock. */
37
47
  private holds;
48
+ /** Whether a hidden page is one of the holds, with `holdWhileHidden`. */
49
+ private hidden;
50
+ /** Holds the clock as the page hides, and lets it go as the page shows. */
51
+ private readonly handleVisibility;
38
52
  constructor(host: ReactiveControllerHost, options: CountdownOptions);
39
53
  /** Whether the clock is running now. */
40
54
  get running(): boolean;
@@ -2,15 +2,37 @@ export class CountdownController {
2
2
  constructor(host, options) {
3
3
  this.options = options;
4
4
  this.holds = 0;
5
+ this.hidden = false;
6
+ this.handleVisibility = () => {
7
+ if (document.hidden === this.hidden) {
8
+ return;
9
+ }
10
+ this.hidden = document.hidden;
11
+ if (this.hidden) {
12
+ this.hold();
13
+ }
14
+ else {
15
+ this.release();
16
+ }
17
+ };
5
18
  host.addController(this);
6
19
  }
7
20
  get running() {
8
21
  return this.timer !== undefined;
9
22
  }
10
- hostConnected() { }
23
+ hostConnected() {
24
+ if (this.options.holdWhileHidden && typeof document !== 'undefined') {
25
+ document.addEventListener('visibilitychange', this.handleVisibility);
26
+ this.handleVisibility();
27
+ }
28
+ }
11
29
  hostDisconnected() {
12
30
  this.pause();
13
31
  this.holds = 0;
32
+ this.hidden = false;
33
+ if (typeof document !== 'undefined') {
34
+ document.removeEventListener('visibilitychange', this.handleVisibility);
35
+ }
14
36
  }
15
37
  start(ms) {
16
38
  if (ms === undefined || !(ms > 0)) {
@@ -30,7 +52,10 @@ export class CountdownController {
30
52
  this.options.paused?.(true);
31
53
  }
32
54
  resume() {
33
- if (this.timer !== undefined || this.remaining === undefined || this.remaining <= 0) {
55
+ if (this.hidden ||
56
+ this.timer !== undefined ||
57
+ this.remaining === undefined ||
58
+ this.remaining <= 0) {
34
59
  return;
35
60
  }
36
61
  this.startedAt = Date.now();
@@ -97,6 +97,17 @@ export interface FloatingOptions {
97
97
  */
98
98
  within?: () => Element | null | undefined;
99
99
  }
100
+ /**
101
+ * How wide a panel that matches its anchor is drawn: the anchor's width, no
102
+ * wider than the room the panel has inside the window's margins.
103
+ *
104
+ * **A trigger as wide as the window gave a list as wide as the window**, and
105
+ * `shift` keeps a margin it then cannot keep: a Select spanning a phone with no
106
+ * gutter opened its list 8 pixels past the end edge, on both sides of the
107
+ * window at once. The room is Floating UI's `availableWidth` after the shift,
108
+ * so a panel that fits is the anchor's width exactly, as before.
109
+ */
110
+ export declare const matchedInlineSize: (anchor: number, room: number) => number;
100
111
  /**
101
112
  * Places one element against another, and keeps it there.
102
113
  *
@@ -123,7 +134,7 @@ export interface FloatingOptions {
123
134
  * package and none of them is private. the `hooks` rule pairs a documented
124
135
  * hook with a rule that reads one, in both directions.
125
136
  *
126
- * @cssproperty --floating-anchor-inline-size - The anchor's width, for a panel that matches it.
137
+ * @cssproperty --floating-anchor-inline-size - The anchor's width, for a panel that matches it, and never wider than the room the panel has inside the window's margins.
127
138
  * @cssproperty --floating-arrow-x - Where the arrow meets the anchor, from the panel's left edge in both text directions.
128
139
  * @cssproperty --floating-arrow-y - Where the arrow meets the anchor, from the panel's top edge.
129
140
  * @cssproperty --floating-available-block-size - How much room is left below, for a panel that scrolls.
@@ -20,6 +20,7 @@ const corner = (floating) => {
20
20
  }
21
21
  return largest;
22
22
  };
23
+ export const matchedInlineSize = (anchor, room) => Math.round(Math.max(0, Math.min(anchor, room)));
23
24
  export class FloatingController {
24
25
  constructor(host, options) {
25
26
  this.run = 0;
@@ -53,7 +54,7 @@ export class FloatingController {
53
54
  apply: ({ availableHeight, availableWidth, elements, rects }) => {
54
55
  room = availableWidth;
55
56
  elements.floating.style.setProperty('--floating-available-block-size', `${Math.round(availableHeight)}px`);
56
- elements.floating.style.setProperty('--floating-anchor-inline-size', `${Math.round(rects.reference.width)}px`);
57
+ elements.floating.style.setProperty('--floating-anchor-inline-size', `${matchedInlineSize(rects.reference.width, availableWidth)}px`);
57
58
  },
58
59
  padding: 8,
59
60
  }));
@@ -25,6 +25,31 @@ export interface NavigableItem {
25
25
  */
26
26
  readonly words?: string;
27
27
  }
28
+ /** What `focusItemOf` reads of an element, where its defaults do not fit. */
29
+ export interface FocusItemOptions {
30
+ /** Whether the item is skipped. Defaults to `false`. */
31
+ disabled?: boolean;
32
+ /** Moves focus to the item. Defaults to the element's own `focus()`. */
33
+ focus?: () => void;
34
+ /**
35
+ * Whether a node is the item, for `origin: 'focus'`. Defaults to the element
36
+ * and anything inside it; an item whose children are content of their own,
37
+ * an accordion section's panel, answers for itself alone.
38
+ */
39
+ owns?: (node: Node | null) => boolean;
40
+ /** The words a reader types the start of. */
41
+ words?: string;
42
+ }
43
+ /**
44
+ * An item that is only ever focused: arrows move between such items and
45
+ * nothing is chosen on arrival, the shape of a toolbar, a megamenu bar or a
46
+ * legend.
47
+ *
48
+ * Eight families wrote the same object literal by hand, `select` a no-op and
49
+ * `selected` false, and an item that forgot one of them type-checked
50
+ * wherever its `contains` came from an element.
51
+ */
52
+ export declare const focusItemOf: (element: HTMLElement, options?: FocusItemOptions) => NavigableItem;
28
53
  /** Where a move starts from: the item that has focus, or the chosen item. */
29
54
  export type KeyboardOrigin = 'focus' | 'selection';
30
55
  /** How the controller is told what it is moving through. */
@@ -1,6 +1,14 @@
1
1
  import { inlineStep } from '../../utils/inline-step/inline-step.js';
2
2
  import { keyClaimed } from '../../utils/key-claimed/key-claimed.js';
3
3
  import { typesCharacter } from '../../utils/typeahead/typeahead.js';
4
+ export const focusItemOf = (element, options = {}) => ({
5
+ contains: options.owns ?? ((node) => element.contains(node)),
6
+ disabled: options.disabled ?? false,
7
+ focusItem: options.focus ?? (() => element.focus()),
8
+ select: () => { },
9
+ selected: false,
10
+ ...(options.words === undefined ? {} : { words: options.words }),
11
+ });
4
12
  const KEYS = new Set(['ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'End', 'Home']);
5
13
  export class KeyboardNavigationController {
6
14
  constructor(host, { items, origin = 'selection', reserved = () => [], selects = () => true, typeahead, wrap = true, }) {
@@ -14,6 +14,16 @@ export interface OpenPhaseOptions {
14
14
  /** The element whose animations are the entrance and the exit. */
15
15
  surface: () => Element | null | undefined;
16
16
  }
17
+ /**
18
+ * Resolves once every animation running on an element and inside it has
19
+ * finished, or at once where none is running.
20
+ *
21
+ * **Asked now, with no frame waited first**, for an exit that is already
22
+ * running when the caller asks: a section closing, a message leaving. A
23
+ * canceled animation counts as finished. `animationsSettled` waits two frames
24
+ * first, for an entrance that has not started yet.
25
+ */
26
+ export declare const animationsFinished: (element: Element | null | undefined) => Promise<void>;
17
27
  /**
18
28
  * Resolves once an element's animations have finished, or two frames on where
19
29
  * it has none.
@@ -1,3 +1,9 @@
1
+ export const animationsFinished = async (element) => {
2
+ if (!element || typeof element.getAnimations !== 'function') {
3
+ return;
4
+ }
5
+ await Promise.allSettled(element.getAnimations({ subtree: true }).map((one) => one.finished));
6
+ };
1
7
  export const animationsSettled = async (element) => {
2
8
  const frame = () => new Promise((resolve) => {
3
9
  if (typeof requestAnimationFrame === 'function') {
@@ -9,10 +15,7 @@ export const animationsSettled = async (element) => {
9
15
  });
10
16
  await frame();
11
17
  await frame();
12
- if (!element || typeof element.getAnimations !== 'function') {
13
- return;
14
- }
15
- await Promise.allSettled(element.getAnimations({ subtree: true }).map((one) => one.finished));
18
+ await animationsFinished(element);
16
19
  };
17
20
  export class OpenPhaseController {
18
21
  constructor(host, options) {
@@ -0,0 +1,96 @@
1
+ import { type ReactiveController, type ReactiveControllerHost, type TemplateResult } from 'lit';
2
+ import { type DataOption, type DataOptionItem } from '../../utils/data-options/data-options.js';
3
+ /** What the controller needs of a list whose options can come as data. */
4
+ export interface OptionWindowOptions {
5
+ /** The value of the option the keyboard is on, which is kept drawn. */
6
+ current: () => string;
7
+ /** The options as data, or nothing for a list of child elements. */
8
+ items: () => readonly DataOptionItem[] | undefined;
9
+ }
10
+ /**
11
+ * Draws the options of a list handed over as data, a window of them at a
12
+ * time, for a list too long to draw whole.
13
+ *
14
+ * **The list keeps its logic and this keeps the drawing.** `records` are the
15
+ * options as the list reads them, built once per `items` array: the keyboard,
16
+ * the typeahead, the filter and the value work over all of them, and the list
17
+ * writes `hidden`, `selected` and `active` onto them as it would onto its
18
+ * option elements. This draws the ones in the window as option elements of
19
+ * the host's tier, between two spacers that hold the room of the rest, through
20
+ * `VirtualizeController`.
21
+ *
22
+ * **The option the keyboard is on is always drawn**, wherever the window is,
23
+ * because `aria-activedescendant` points at an element. `follow` brings it into
24
+ * view when it moves, in two steps where it is far: the arithmetic scrolls the
25
+ * window to it, and the host scrolls the drawn element into view on the next
26
+ * update, as it does for a list of children.
27
+ *
28
+ * **The scrolling box is the element `render` was drawn into**, so the host
29
+ * says nothing about it: it puts the template inside its box. A host's
30
+ * `isDisabled`, or else its `disabled`, holds every drawn option down.
31
+ *
32
+ * Listbox uses it, and so every list that holds a Listbox can.
33
+ */
34
+ export declare class OptionWindowController implements ReactiveController {
35
+ private readonly host;
36
+ private readonly options;
37
+ /** The option elements drawn on the last update, in order. */
38
+ drawn: HTMLElement[];
39
+ /** The `items` array the records were built from. */
40
+ private source;
41
+ private built;
42
+ /** Which slice is drawn, made the first time there are records. */
43
+ private virtualize;
44
+ /** Where in the shown records the highlight is: `-1` for nowhere. */
45
+ private kept;
46
+ /** Whether the highlight is still being brought into view. */
47
+ private following;
48
+ /**
49
+ * The window as it was last drawn, which `follow` reads: the controller's
50
+ * own may have moved since, measured after the draw, and an option is in
51
+ * its place only in the window that drew it.
52
+ */
53
+ private span;
54
+ constructor(host: ReactiveControllerHost & HTMLElement & {
55
+ disabled?: boolean;
56
+ isDisabled?: boolean;
57
+ }, options: OptionWindowOptions);
58
+ /**
59
+ * The options as the list reads them, or undefined for a list of children.
60
+ *
61
+ * Built when `items` is a new array, so a page that changes the set hands
62
+ * over a new one, as a property of any framework is set. The windowing is
63
+ * made then too, and only then: a list of children observes no size and
64
+ * listens to no scroll it does not need. Its estimate of 32 pixels is
65
+ * corrected by the rows it draws, on first sight.
66
+ */
67
+ get records(): DataOption[] | undefined;
68
+ /** The box the window is drawn into, inside the scrolling one. */
69
+ private box;
70
+ /** The records a filter has left showing. */
71
+ private get shown();
72
+ /**
73
+ * The spacers and the box the window is drawn in, for the host's scrolling
74
+ * box, or undefined for a list of children, which draws its slot instead.
75
+ *
76
+ * A spacer never shrinks: in a column of rows that flexes, it shrank to
77
+ * nothing, and the list scrolled for a screenful.
78
+ */
79
+ render(): TemplateResult | undefined;
80
+ /**
81
+ * Draws the window once the render has put its box in place, then measures
82
+ * it again. Before `VirtualizeController`'s own pass, which reads the rows.
83
+ */
84
+ hostUpdated(): void;
85
+ /**
86
+ * Brings the highlight into view where `moved` says it moved, and answers
87
+ * whether its drawn element is in place for the host to scroll to now.
88
+ *
89
+ * Only when it moved, or the filter did: the window moves on every scroll,
90
+ * and following then would pull a reader scrolling with a wheel back to the
91
+ * highlight.
92
+ */
93
+ follow(moved: boolean): boolean;
94
+ /** Scrolls the box to put the option of `value` in its middle. */
95
+ center(value: string): void;
96
+ }
@@ -0,0 +1,81 @@
1
+ import { html, } from 'lit';
2
+ import { dataOptions, drawOptions, windowOf, } from '../../utils/data-options/data-options.js';
3
+ import { VirtualizeController } from '../virtualize-controller/virtualize-controller.js';
4
+ export class OptionWindowController {
5
+ constructor(host, options) {
6
+ this.drawn = [];
7
+ this.kept = -1;
8
+ this.following = false;
9
+ this.span = { first: 0, last: 0 };
10
+ this.host = host;
11
+ this.options = options;
12
+ host.addController(this);
13
+ }
14
+ get records() {
15
+ const items = this.options.items();
16
+ if (items !== this.source) {
17
+ this.source = items;
18
+ this.built = items && dataOptions(items);
19
+ if (items) {
20
+ this.virtualize ??= new VirtualizeController(this.host, {
21
+ container: () => this.box()?.parentElement,
22
+ count: () => this.shown.length,
23
+ estimate: () => 32,
24
+ measured: () => false,
25
+ rows: () => this.drawn.map((element) => ({
26
+ element,
27
+ index: Number(element.getAttribute('aria-posinset')) - 1,
28
+ })),
29
+ });
30
+ }
31
+ }
32
+ return this.built;
33
+ }
34
+ box() {
35
+ return this.host.shadowRoot?.querySelector('[data-rows]');
36
+ }
37
+ get shown() {
38
+ return this.records?.filter((record) => !record.hidden) ?? [];
39
+ }
40
+ render() {
41
+ if (!this.records || !this.virtualize) {
42
+ return undefined;
43
+ }
44
+ const current = this.options.current();
45
+ this.kept = this.shown.findIndex((record) => record.value === current);
46
+ const { after, before } = this.virtualize.spacers(this.kept < 0 ? undefined : this.kept);
47
+ return html `<div style="flex: none; block-size: ${before}px"></div>
48
+ <div data-rows></div>
49
+ <div style="flex: none; block-size: ${after}px"></div>`;
50
+ }
51
+ hostUpdated() {
52
+ const box = this.box();
53
+ if (!this.records || !this.virtualize || !box) {
54
+ return;
55
+ }
56
+ const { first, last } = this.virtualize.window;
57
+ const shown = this.shown;
58
+ this.span = { first, last };
59
+ this.drawn = drawOptions(box, `${this.host.constructor.tier}option`, shown, windowOf(first, last, shown.length, this.kept), Boolean(this.host.isDisabled ?? this.host.disabled));
60
+ this.virtualize.measure();
61
+ }
62
+ follow(moved) {
63
+ this.following ||= moved;
64
+ if (!this.virtualize || !this.following || this.kept < 0) {
65
+ return false;
66
+ }
67
+ const { first, last } = this.span;
68
+ if (this.kept < first || this.kept >= last) {
69
+ this.virtualize.reveal(this.kept);
70
+ return false;
71
+ }
72
+ this.following = false;
73
+ return true;
74
+ }
75
+ center(value) {
76
+ const at = this.shown.findIndex((record) => record.value === value);
77
+ if (at >= 0) {
78
+ this.virtualize?.reveal(at, 0, { align: 'center' });
79
+ }
80
+ }
81
+ }
@@ -44,7 +44,7 @@ export class PopoverController {
44
44
  }
45
45
  show() {
46
46
  const surface = this.options.surface();
47
- if (this.shown || !surface) {
47
+ if (this.shown || !surface?.isConnected) {
48
48
  return;
49
49
  }
50
50
  this.shown = true;
@@ -1,5 +1,5 @@
1
1
  import { type CSSResultOrNative, LitElement, type PropertyValues } from 'lit';
2
- import { type StaticValue } from 'lit/static-html.js';
2
+ import type { StaticValue } from 'lit/static-html.js';
3
3
  /**
4
4
  * @summary Base class every Helix element extends.
5
5
  *
@@ -46,10 +46,19 @@ export declare class HelixElement extends LitElement {
46
46
  /**
47
47
  * One composed tag, as a static value a template can interpolate.
48
48
  *
49
- * `unsafeStatic` is safe by construction: the name is a literal in our own
50
- * source and the prefix is one of two strings this class defines. Called per
51
- * render and not cached, which costs nothing, because `static-html` keys its
52
- * template cache on the joined string rather than on the object.
49
+ * What `unsafeStatic` returns, safe by construction: the name is a literal in
50
+ * our own source and the prefix is one of two strings this class defines.
51
+ * Called per render and not cached, which costs nothing, because
52
+ * `static-html` keys its template cache on the joined string rather than on
53
+ * the object.
54
+ *
55
+ * **Written out rather than imported, for every element that never renders
56
+ * one.** `lit/static-html.js` declares no side effects, so importing
57
+ * `unsafeStatic` alone kept the module's three `withStatic` calls in every
58
+ * bundle: about 210 bytes gzipped on each of the families that compose no
59
+ * tag, Divider and Badge among them. The value is lit's cross-copy protocol,
60
+ * keyed by `Symbol.for('')` so that two copies of lit on a page read each
61
+ * other's, and a spec holds it equal to `unsafeStatic`'s.
53
62
  */
54
63
  protected tag(name: string): StaticValue;
55
64
  /**
@@ -1,5 +1,4 @@
1
1
  import { LitElement, unsafeCSS } from 'lit';
2
- import { unsafeStatic } from 'lit/static-html.js';
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
4
  import { applyReactProperties, auditReactProps, bindReactHandler, reactPropsOf, } from './react-bridge.js';
@@ -15,7 +14,10 @@ const LOOK_FOR = 96;
15
14
  export class HelixElement extends LitElement {
16
15
  static { this.tier = 'helix-headless-'; }
17
16
  tag(name) {
18
- return unsafeStatic(`${this.constructor.tier}${name}`);
17
+ return {
18
+ _$litStatic$: `${this.constructor.tier}${name}`,
19
+ r: Symbol.for(''),
20
+ };
19
21
  }
20
22
  partNamed(name) {
21
23
  const root = this.renderRoot;
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { auditName } from '#audit-name';
2
2
  export { AriaForwardController, type AriaForwardOptions, } from './controllers/aria-forward-controller/aria-forward-controller.js';
3
3
  export { ArrivalController, type ArrivalOptions, } from './controllers/arrival-controller/arrival-controller.js';
4
4
  export { ChartPointerController, type ChartPointerHost, } from './controllers/chart-pointer-controller/chart-pointer-controller.js';
5
+ export { ChildrenController } from './controllers/children-controller/children-controller.js';
5
6
  export { CommandController, type CommandOptions, HelixFallbackCommandEvent, supportsInvokers, } from './controllers/command-controller/command-controller.js';
6
7
  export { CopyController, type CopyOptions, } from './controllers/copy-controller/copy-controller.js';
7
8
  export { CountdownController, type CountdownOptions, } from './controllers/countdown-controller/countdown-controller.js';
@@ -16,9 +17,10 @@ export { FullscreenController } from './controllers/fullscreen-controller/fullsc
16
17
  export { HostClickController } from './controllers/host-click-controller/host-click-controller.js';
17
18
  export { IdleController } from './controllers/idle-controller/idle-controller.js';
18
19
  export { IndicatorController, type IndicatorOptions, } from './controllers/indicator-controller/indicator-controller.js';
19
- export { KeyboardNavigationController, type KeyboardNavigationOptions, type KeyboardOrigin, type NavigableItem, } from './controllers/keyboard-navigation-controller/keyboard-navigation-controller.js';
20
+ export { type FocusItemOptions, focusItemOf, KeyboardNavigationController, type KeyboardNavigationOptions, type KeyboardOrigin, type NavigableItem, } from './controllers/keyboard-navigation-controller/keyboard-navigation-controller.js';
20
21
  export { MediaSessionController, type SessionHandlers, SKIP_SECONDS, } from './controllers/media-session-controller/media-session-controller.js';
21
- export { animationsSettled, type OpenPhase, OpenPhaseController, type OpenPhaseOptions, } from './controllers/open-phase-controller/open-phase-controller.js';
22
+ export { animationsFinished, animationsSettled, type OpenPhase, OpenPhaseController, type OpenPhaseOptions, } from './controllers/open-phase-controller/open-phase-controller.js';
23
+ export { OptionWindowController, type OptionWindowOptions, } from './controllers/option-window-controller/option-window-controller.js';
22
24
  export { MAX_FRAME_STEP, PaintGateController, type PaintGateOptions, } from './controllers/paint-gate-controller/paint-gate-controller.js';
23
25
  export { PlacementController } from './controllers/placement-controller/placement-controller.js';
24
26
  export { PopoverController, type PopoverOptions, } from './controllers/popover-controller/popover-controller.js';
@@ -50,27 +52,37 @@ export { MixinTokens, type MixinTokensInterface } from './mixins/mixin-tokens/mi
50
52
  export { MixinViewport, type ViewportControlsPlacement, type ViewportElement, type ViewportGestureLabels, type ViewportGestureMode, type ViewportGestures, type ViewportWay, } from './mixins/mixin-viewport/mixin-viewport.js';
51
53
  export { anchorElement } from './utils/anchor-element/anchor-element.js';
52
54
  export { arrowReach, sideOf } from './utils/arrow/arrow.js';
55
+ export { assignSlots } from './utils/assign-slots/assign-slots.js';
56
+ export { type DrawAutofillOptions, drawAutofill, } from './utils/autofill-select/autofill-select.js';
53
57
  export { addDays, addMonths, type Barring, barredBy, type CalendarView, localToday, monthOf, monthOut, type PeriodCell, type PeriodPage, periodCells, periodKey, periodMove, sameMonth, weekStartOf, YEARS_SHOWN, yearOut, yearPageOf, } from './utils/calendar/calendar.js';
54
58
  export { capturePointer } from './utils/capture-pointer/capture-pointer.js';
59
+ export { centerInView } from './utils/center-in-view/center-in-view.js';
55
60
  export { ariaChord, type ChordPress, chordParts, matchesChord, printableChord, selectsAll, } from './utils/chord/chord.js';
61
+ export { type KeyNames, keyNames } from './utils/chord/key-names.js';
56
62
  export { clamp } from './utils/clamp/clamp.js';
57
63
  export { clockLocale, dayPeriod, type HourCycle, isTwelveHour, nearestTime, offeredTimes, type TimeFault, type TimeRules, timeBarred, timeFault, wheelParts, wholeHour, } from './utils/clock/clock.js';
58
64
  export { closestInstance } from './utils/closest-instance/closest-instance.js';
59
65
  export { type ColorWords, colorName, hueName } from './utils/color-name/color-name.js';
66
+ export { columnStep } from './utils/column-step/column-step.js';
60
67
  export { countLabel } from './utils/count-label/count-label.js';
61
68
  export { createSheet } from './utils/create-sheet/create-sheet.js';
62
69
  export { type Color, type ColorFormat, formatColor, parseColor, toCss, toHsl, toRgb, } from './utils/css-color/css-color.js';
63
70
  export { customElement, definedTags, duplicateRegistrations, } from './utils/custom-element/custom-element.js';
71
+ export { type DataOption, type DataOptionItem, dataOptions, drawOptions, windowOf, } from './utils/data-options/data-options.js';
64
72
  export { segmentName } from './utils/date-segments/date-segments.js';
65
73
  export { type DrawDismissOptions, drawDismiss, drawDismissGlyph, } from './utils/draw-dismiss/draw-dismiss.js';
66
74
  export { type DrawIconOptions, drawIcon } from './utils/draw-icon/draw-icon.js';
67
- export { CHART_PLOT, drawTooltip } from './utils/draw-tooltip/draw-tooltip.js';
75
+ export { CHART_PLOT, drawTooltip, type ReadingCursor } from './utils/draw-tooltip/draw-tooltip.js';
68
76
  export { elapsedTime } from './utils/elapsed-time/elapsed-time.js';
69
77
  export { type ElementByIdOptions, elementById, searchableById, } from './utils/element-by-id/element-by-id.js';
70
- export { fillTemplate } from './utils/fill-template/fill-template.js';
71
- export { deepActiveElement, deepContains, focusableElements, } from './utils/focusable-elements/focusable-elements.js';
78
+ export { exportParts } from './utils/export-parts/export-parts.js';
79
+ export { fillTemplate, labelWith } from './utils/fill-template/fill-template.js';
80
+ export { FILTER_MARK, markMatches, rangeOf } from './utils/filter-marks/filter-marks.js';
81
+ export { focusTarget } from './utils/focus-target/focus-target.js';
82
+ export { deepActiveElement, deepContains, focusableElements, PRESSABLE, pressedOwnControl, } from './utils/focusable-elements/focusable-elements.js';
72
83
  export { afterLinkFollowed, followLink, type LinkModifiers, type LinkTarget, } from './utils/follow-link/follow-link.js';
73
84
  export { formatNumbers } from './utils/format-numbers/format-numbers.js';
85
+ export { type GridBounds, type GridKeys, type GridPlace, gridMove, pageRows, } from './utils/grid-move/grid-move.js';
74
86
  export { headingIn, headingsIn } from './utils/heading-in/heading-in.js';
75
87
  export { headingLevel } from './utils/heading-level/heading-level.js';
76
88
  export { type HostAria, hostAria } from './utils/host-aria/host-aria.js';
@@ -87,7 +99,7 @@ export { markRegion, type RegionOptions } from './utils/mark-region/mark-region.
87
99
  export { copiesOf, type MediaCopy, sameCopies, } from './utils/media-copy/media-copy.js';
88
100
  export { nameText } from './utils/name-text/name-text.js';
89
101
  export { nearest } from './utils/nearest/nearest.js';
90
- export { formatNumber, parseNumber, readingText } from './utils/number-format/number-format.js';
102
+ export { formatNumber, formatPercent, parseNumber, readingText, } from './utils/number-format/number-format.js';
91
103
  export { onUnlessFalse } from './utils/on-unless-false/on-unless-false.js';
92
104
  export { applyOwnRule, type OwnRule } from './utils/own-rule/own-rule.js';
93
105
  export { plainClick } from './utils/plain-click/plain-click.js';
@@ -101,8 +113,8 @@ export { type Relative, type RelativeOptions, relativeTime, } from './utils/rela
101
113
  export { measuring, RENDER_SLOT, reportRender } from './utils/report-render/report-render.js';
102
114
  export { RUNTIME_LOG, RUNTIME_SLOT, type RuntimeEvent, type RuntimeKind, reportRuntime, } from './utils/report-runtime/report-runtime.js';
103
115
  export { noteVersion, reportUsage, USAGE_SLOT, VERSIONS_SLOT, } from './utils/report-usage/report-usage.js';
104
- export { reportValues } from './utils/report-values/report-values.js';
105
- export { type ChangeRequestOptions, requestChange, } from './utils/request-change/request-change.js';
116
+ export { type ReportValuesOptions, reportValues } from './utils/report-values/report-values.js';
117
+ export { type ChangeRequestOptions, requestChange, requestValue, } from './utils/request-change/request-change.js';
106
118
  export { type ActivatableItem, resolveActiveItem, } from './utils/resolve-active-item/resolve-active-item.js';
107
119
  export { holdCaret, restoreValue } from './utils/restore-value/restore-value.js';
108
120
  export { type RovingOptions, rovingStop } from './utils/roving-stop/roving-stop.js';
@@ -110,9 +122,9 @@ export { valueAt } from './utils/row-data/row-data.js';
110
122
  export { type Arranged, type Axis, arranged, cycle, type DataRow, filter, format, type Ordered, ordered, parse, type Sort, sort, window_, } from './utils/row-order/row-order.js';
111
123
  export { extent, linear, type Scale } from './utils/scale/scale.js';
112
124
  export { ticks } from './utils/scale/ticks.js';
113
- export { type ScrollerOptions, type ScrollItemOptions, scrollItemIntoView, scrollingAncestor, } from './utils/scroll-item-into-view/scroll-item-into-view.js';
125
+ export { type ScrollerOptions, type ScrollItemOptions, type ScrollRoomOptions, scrollItemIntoView, scrollingAncestor, scrollRoom, } from './utils/scroll-item-into-view/scroll-item-into-view.js';
114
126
  export { lockScroll, scrollHolders, unlockScroll } from './utils/scroll-lock/scroll-lock.js';
115
- export { slotFilled } from './utils/slot-filled/slot-filled.js';
127
+ export { slotFilled, slotNamed } from './utils/slot-filled/slot-filled.js';
116
128
  export { SlottedGlyph } from './utils/slotted-glyph/slotted-glyph.js';
117
129
  export { slottedWords } from './utils/slotted-words/slotted-words.js';
118
130
  export { splitValues } from './utils/split-values/split-values.js';
@@ -120,9 +132,11 @@ export { edgeOffset, FLING, parseSnapPoints, type SwipeAxis, type SwipeEdge, typ
120
132
  export { tellDisclosure } from './utils/tell-disclosure/tell-disclosure.js';
121
133
  export { subscribe } from './utils/ticker/ticker.js';
122
134
  export { type HelixStatusTone, TONE_MARKS, type ToneMark, toneMark, } from './utils/tone-marks/tone-marks.js';
135
+ export { toolbarAround, toolbarAxis } from './utils/toolbar-around/toolbar-around.js';
123
136
  export { type TreeArrow, type TreeArrowRow, treeArrow } from './utils/tree-arrow/tree-arrow.js';
124
137
  export { Typeahead, type TypeaheadBy, typeaheadBy, typeaheadByWords, typesCharacter, } from './utils/typeahead/typeahead.js';
125
138
  export { dateAsValue, isClockValue, isDateValue, isTimeValue, minutesAsValue, numberAsValue, secondsAsValue, timeAsValue, valueAsDate, valueAsMinutes, valueAsNumber, valueAsSeconds, valueAsTime, } from './utils/value-as/value-as.js';
139
+ export { type PickerVerdict, verdictFlags } from './utils/verdict-flags/verdict-flags.js';
126
140
  export { watchTheme } from './utils/watch-theme/watch-theme.js';
127
141
  export { wordsConverter } from './utils/words-converter/words-converter.js';
128
142
  export { writeClock, writeTime } from './utils/write-time/write-time.js';