@fusion.dev/helix-core 0.217.0 → 0.219.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.
@@ -19,6 +19,16 @@ export interface PopoverOptions {
19
19
  * itself instead, so a press on its trigger is inside rather than out.
20
20
  */
21
21
  boundary?: () => Element | readonly (Element | null | undefined)[] | null | undefined;
22
+ /**
23
+ * What the open surface does to a tooltip outside it.
24
+ *
25
+ * `hide`, the default, closes a tooltip showing outside the surface as it
26
+ * opens, and keeps tooltips outside it closed until it closes. A tooltip
27
+ * inside it still shows. `keep` leaves them alone, for a surface that is a
28
+ * tooltip itself or that stays up while the reader goes on using the page,
29
+ * such as a tour's stop.
30
+ */
31
+ hints?: 'hide' | 'keep';
22
32
  /** Space between the anchor and the surface, in pixels. */
23
33
  offset?: number | (() => number);
24
34
  /** Whether the surface may move to the other side to fit. On by default. See `FloatingController`. */
@@ -93,6 +103,19 @@ export interface PopoverOptions {
93
103
  /** The panel that floats. */
94
104
  surface: () => HTMLElement | null | undefined;
95
105
  }
106
+ /**
107
+ * Whether a tooltip at `element` may show: no surface is open, or the last one
108
+ * to open holds it. A tooltip outside an open menu or popover would sit over
109
+ * the panel the reader is using.
110
+ */
111
+ export declare function hintMayShow(element: Element): boolean;
112
+ /**
113
+ * Calls `watcher` with each surface that opens and hides the tooltips outside
114
+ * it. Returns the call that stops it.
115
+ */
116
+ export declare function onPanelOpen(watcher: (surface: HTMLElement) => void): () => void;
117
+ /** Whether `element` is drawn inside `surface`, following slots. */
118
+ export declare function hintInside(surface: HTMLElement, element: Element): boolean;
96
119
  /**
97
120
  * A surface that floats against something, in the top layer, until dismissed.
98
121
  *
@@ -128,6 +151,8 @@ export declare class PopoverController implements ReactiveController {
128
151
  private readonly dismisser;
129
152
  /** Whether the surface is currently shown, so the calls can be idempotent. */
130
153
  private shown;
154
+ /** The surface tooltips outside it yield to, while it is open. */
155
+ private held;
131
156
  private readonly host;
132
157
  constructor(host: ReactiveControllerHost & HTMLElement, options: PopoverOptions);
133
158
  /** Whether the surface is showing. */
@@ -144,6 +169,8 @@ export declare class PopoverController implements ReactiveController {
144
169
  * track of which one got there first.
145
170
  */
146
171
  show(): void;
172
+ /** Takes the surface off the list of open ones, so tooltips may show again. */
173
+ private freeHints;
147
174
  /** Hides the surface and stops everything watching it. */
148
175
  hide(): void;
149
176
  /** Whichever of the two the surface is not already doing. */
@@ -1,5 +1,46 @@
1
1
  import { DismissController } from '../dismiss-controller/dismiss-controller.js';
2
2
  import { FloatingController } from '../floating-controller/floating-controller.js';
3
+ const PANELS = Symbol.for('fusion.helix.open-panels');
4
+ const openPanels = () => {
5
+ const globals = globalThis;
6
+ globals[PANELS] ??= { open: new Set(), watchers: new Set() };
7
+ return globals[PANELS];
8
+ };
9
+ const slotOf = (node) => {
10
+ const element = node;
11
+ if (element.assignedSlot) {
12
+ return element.assignedSlot;
13
+ }
14
+ const root = node.parentNode?.shadowRoot;
15
+ if (!root || typeof element.getAttribute !== 'function') {
16
+ return null;
17
+ }
18
+ const name = element.getAttribute('slot');
19
+ return root.querySelector(name ? `slot[name="${name.replaceAll('"', '\\"')}"]` : 'slot:not([name])');
20
+ };
21
+ const drawnInside = (container, node) => {
22
+ let at = node;
23
+ while (at) {
24
+ if (at === container) {
25
+ return true;
26
+ }
27
+ at = slotOf(at) ?? at.parentNode ?? at.host ?? null;
28
+ }
29
+ return false;
30
+ };
31
+ export function hintMayShow(element) {
32
+ const open = [...openPanels().open];
33
+ const last = open.at(-1);
34
+ return last === undefined || drawnInside(last, element);
35
+ }
36
+ export function onPanelOpen(watcher) {
37
+ const { watchers } = openPanels();
38
+ watchers.add(watcher);
39
+ return () => watchers.delete(watcher);
40
+ }
41
+ export function hintInside(surface, element) {
42
+ return drawnInside(surface, element);
43
+ }
3
44
  export class PopoverController {
4
45
  constructor(host, options) {
5
46
  this.shown = false;
@@ -64,6 +105,20 @@ export class PopoverController {
64
105
  this.floating.release();
65
106
  }
66
107
  this.dismisser.activate();
108
+ if (this.options.hints !== 'keep') {
109
+ const panels = openPanels();
110
+ this.held = surface;
111
+ panels.open.add(surface);
112
+ for (const watcher of [...panels.watchers]) {
113
+ watcher(surface);
114
+ }
115
+ }
116
+ }
117
+ freeHints() {
118
+ if (this.held) {
119
+ openPanels().open.delete(this.held);
120
+ this.held = undefined;
121
+ }
67
122
  }
68
123
  hide() {
69
124
  const surface = this.options.surface();
@@ -71,6 +126,7 @@ export class PopoverController {
71
126
  return;
72
127
  }
73
128
  this.shown = false;
129
+ this.freeHints();
74
130
  this.dismisser.deactivate();
75
131
  this.floating.stop();
76
132
  surface?.hidePopover?.();
@@ -88,6 +144,7 @@ export class PopoverController {
88
144
  }
89
145
  hostDisconnected() {
90
146
  this.shown = false;
147
+ this.freeHints();
91
148
  }
92
149
  hostConnected() {
93
150
  if (this.options.open?.()) {
@@ -3,12 +3,14 @@ import { keyClaimed } from '../../utils/key-claimed/key-claimed.js';
3
3
  import { Typeahead, typesCharacter } from '../../utils/typeahead/typeahead.js';
4
4
  const KEYS = new Set(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End']);
5
5
  const ownsTabStop = (node) => 'focusable' in node;
6
+ const disabledOut = (node) => node.hasAttribute('disabled') &&
7
+ node.focusableWhenDisabled !== true;
6
8
  const NATIVE = 'a[href], button, input, select, textarea, [tabindex], [contenteditable=""], [contenteditable="true"]';
7
9
  const TABBABLE = NATIVE.replace('[tabindex]', '[tabindex]:not([tabindex="-1"])');
8
10
  const innerStops = new WeakMap();
9
11
  const takesFocus = (node) => {
10
12
  if (!(node instanceof HTMLElement) ||
11
- node.hasAttribute('disabled') ||
13
+ disabledOut(node) ||
12
14
  node.getAttribute('aria-hidden') === 'true') {
13
15
  return false;
14
16
  }
@@ -138,7 +140,7 @@ export class ToolbarController {
138
140
  const controls = [];
139
141
  for (const child of this.members) {
140
142
  if (!(child instanceof HTMLElement) ||
141
- child.hasAttribute('disabled') ||
143
+ disabledOut(child) ||
142
144
  child.getAttribute('aria-hidden') === 'true') {
143
145
  continue;
144
146
  }
@@ -314,7 +316,7 @@ export class ToolbarController {
314
316
  const watched = this.options.watch?.() ?? this.host;
315
317
  this.watcher = new MutationObserver(() => this.restore());
316
318
  this.watcher.observe(watched, {
317
- attributeFilter: ['disabled', 'aria-hidden'],
319
+ attributeFilter: ['disabled', 'focusable-when-disabled', 'aria-hidden'],
318
320
  attributes: true,
319
321
  subtree: true,
320
322
  });
@@ -128,6 +128,7 @@ import { HelixShortcutEvent } from './shortcut.js';
128
128
  import { HelixSortEvent } from './sort.js';
129
129
  import { HelixSortedEvent } from './sorted.js';
130
130
  import { HelixSplitEvent } from './split.js';
131
+ import { HelixStopEvent } from './stop.js';
131
132
  import { HelixStrandsFallbackEvent } from './strands-fallback.js';
132
133
  import { HelixTerminalBellEvent } from './terminal-bell.js';
133
134
  import { HelixTerminalInputEvent } from './terminal-input.js';
@@ -141,7 +142,7 @@ import { HelixTourStatusEvent } from './tour-status.js';
141
142
  import { HelixTypingEvent } from './typing.js';
142
143
  export type { ActivateDetail, AuraFallbackDetail, BandDetail, CanvasView, CaretDetail, ChangeDetail, CompleteDetail, ConnectDetail, ConsoleDetail, CopyDetail, CreateDetail, DeviceButton, DeviceButtonDetail, DeviceKeyDetail, DeviceScrollDetail, DeviceSwipeDetail, DeviceTapDetail, DeviceTextDetail, DeviceTouchDetail, DismissDetail, FacetDetail, FilterDetail, GraphFilterDetail, GraphLayoutDetail, GraphPartChangeDetail, GraphSignalDetail, ListenDetail, MapClusterDetail, MapPanDetail, MapPartChangeDetail, MapRotateDetail, MapTiltDetail, MapZoomDetail, MoveDetail, NavigateDetail, OpenDetail, PasteDetail, PlotChangeDetail, PlotTurnDetail, PressDetail, PressPointerType, RangeDetail, ReadDetail, ReadingDetail, ReconnectDetail, Refusal, RefusedFile, RejectDetail, RelabelDetail, RemoveDetail, ReorderDetail, ResizeDetail, ScrolledDetail, SelectDetail, SendDetail, ShortcutDetail, SkipDetail, SortDetail, StrandsFallbackDetail, TerminalInputDetail, TerminalLinkDetail, TerminalResizeDetail, TerminalTitleDetail, ToggleDetail, TourAskDetail, TourStatusDetail, TypingDetail, ViewportPanDetail, ViewportZoomDetail, } from './details.js';
143
144
  export { helixEventNames } from './names.js';
144
- export { HelixActionEvent, HelixActivateEvent, HelixAuraFallbackEvent, HelixBandEvent, HelixBeforeActivateEvent, HelixBeforeChangeEvent, HelixBeforeCloseEvent, HelixBeforeCollapseEvent, HelixBeforeEditEvent, HelixBeforeListenEvent, HelixBeforeMoveEvent, HelixBeforeOpenEvent, HelixBeforePanelResizeEvent, HelixBeforePasteEvent, HelixBeforePinEvent, HelixBeforeReconnectEvent, HelixBeforeRelabelEvent, HelixBeforeReorderEvent, HelixBeforeResizeEvent, HelixBeforeSplitEvent, HelixBeforeToggleEvent, HelixCanvasEditEvent, HelixCanvasPanEvent, HelixCanvasViewEvent, HelixCanvasZoomEvent, HelixCaretEvent, HelixClearEvent, HelixCollapseEvent, HelixCompleteEvent, HelixConnectEvent, HelixConsoleEvent, HelixCopyErrorEvent, HelixCopyEvent, HelixCreateEvent, HelixDeviceButtonEvent, HelixDeviceKeyEvent, HelixDeviceScreenshotEvent, HelixDeviceScrollEvent, HelixDeviceSwipeEvent, HelixDeviceTapEvent, HelixDeviceTextEvent, HelixDeviceTouchEvent, HelixDismissEvent, HelixEditEvent, HelixEndEvent, HelixExpandEvent, HelixFacetEvent, HelixFilterEvent, HelixFilteredEvent, HelixGraphFilterEvent, HelixGraphLayoutEvent, HelixGraphPanEvent, HelixGraphPartChangeEvent, HelixGraphReadyEvent, HelixGraphSignalEvent, HelixGraphZoomEvent, HelixHistoryEvent, HelixListenEvent, HelixLoadEvent, HelixLoadMoreEvent, HelixMapClusterEvent, HelixMapPanEvent, HelixMapPartChangeEvent, HelixMapReadyEvent, HelixMapRotateEvent, HelixMapTiltEvent, HelixMapZoomEvent, HelixMoveEvent, HelixNavigateEvent, HelixNextEvent, HelixPanelResizeEvent, HelixPastedEvent, HelixPinEvent, HelixPlotChangeEvent, HelixPlotTurnEvent, HelixPressEvent, HelixPreviewEvent, HelixPreviousEvent, HelixRangeChangeEvent, HelixReadEvent, HelixReadingChangeEvent, HelixReadingPressEvent, HelixReconnectEvent, HelixRejectEvent, HelixRelabelEvent, HelixRemoveEvent, HelixReorderEvent, HelixReorderedEvent, HelixResizedEvent, HelixResizeEvent, HelixSampleEndEvent, HelixSampleStartEvent, HelixScrolledEvent, HelixSelectEvent, HelixSelectedEvent, HelixSendEvent, HelixShortcutEvent, HelixSortEvent, HelixSortedEvent, HelixSplitEvent, HelixStrandsFallbackEvent, HelixTerminalBellEvent, HelixTerminalInputEvent, HelixTerminalLinkEvent, HelixTerminalResizeEvent, HelixTerminalRestartEvent, HelixTerminalTitleEvent, HelixToggleEvent, HelixTourAskEvent, HelixTourStatusEvent, HelixTypingEvent, };
145
+ export { HelixActionEvent, HelixActivateEvent, HelixAuraFallbackEvent, HelixBandEvent, HelixBeforeActivateEvent, HelixBeforeChangeEvent, HelixBeforeCloseEvent, HelixBeforeCollapseEvent, HelixBeforeEditEvent, HelixBeforeListenEvent, HelixBeforeMoveEvent, HelixBeforeOpenEvent, HelixBeforePanelResizeEvent, HelixBeforePasteEvent, HelixBeforePinEvent, HelixBeforeReconnectEvent, HelixBeforeRelabelEvent, HelixBeforeReorderEvent, HelixBeforeResizeEvent, HelixBeforeSplitEvent, HelixBeforeToggleEvent, HelixCanvasEditEvent, HelixCanvasPanEvent, HelixCanvasViewEvent, HelixCanvasZoomEvent, HelixCaretEvent, HelixClearEvent, HelixCollapseEvent, HelixCompleteEvent, HelixConnectEvent, HelixConsoleEvent, HelixCopyErrorEvent, HelixCopyEvent, HelixCreateEvent, HelixDeviceButtonEvent, HelixDeviceKeyEvent, HelixDeviceScreenshotEvent, HelixDeviceScrollEvent, HelixDeviceSwipeEvent, HelixDeviceTapEvent, HelixDeviceTextEvent, HelixDeviceTouchEvent, HelixDismissEvent, HelixEditEvent, HelixEndEvent, HelixExpandEvent, HelixFacetEvent, HelixFilterEvent, HelixFilteredEvent, HelixGraphFilterEvent, HelixGraphLayoutEvent, HelixGraphPanEvent, HelixGraphPartChangeEvent, HelixGraphReadyEvent, HelixGraphSignalEvent, HelixGraphZoomEvent, HelixHistoryEvent, HelixListenEvent, HelixLoadEvent, HelixLoadMoreEvent, HelixMapClusterEvent, HelixMapPanEvent, HelixMapPartChangeEvent, HelixMapReadyEvent, HelixMapRotateEvent, HelixMapTiltEvent, HelixMapZoomEvent, HelixMoveEvent, HelixNavigateEvent, HelixNextEvent, HelixPanelResizeEvent, HelixPastedEvent, HelixPinEvent, HelixPlotChangeEvent, HelixPlotTurnEvent, HelixPressEvent, HelixPreviewEvent, HelixPreviousEvent, HelixRangeChangeEvent, HelixReadEvent, HelixReadingChangeEvent, HelixReadingPressEvent, HelixReconnectEvent, HelixRejectEvent, HelixRelabelEvent, HelixRemoveEvent, HelixReorderEvent, HelixReorderedEvent, HelixResizedEvent, HelixResizeEvent, HelixSampleEndEvent, HelixSampleStartEvent, HelixScrolledEvent, HelixSelectEvent, HelixSelectedEvent, HelixSendEvent, HelixShortcutEvent, HelixSortEvent, HelixSortedEvent, HelixSplitEvent, HelixStopEvent, HelixStrandsFallbackEvent, HelixTerminalBellEvent, HelixTerminalInputEvent, HelixTerminalLinkEvent, HelixTerminalResizeEvent, HelixTerminalRestartEvent, HelixTerminalTitleEvent, HelixToggleEvent, HelixTourAskEvent, HelixTourStatusEvent, HelixTypingEvent, };
145
146
  /**
146
147
  * Every event class, and the one place a new one is registered: a class in a
147
148
  * module of its own, imported above, and named here and in `names.ts`.
@@ -255,6 +256,7 @@ declare const registry: {
255
256
  readonly HelixSortEvent: typeof HelixSortEvent;
256
257
  readonly HelixSortedEvent: typeof HelixSortedEvent;
257
258
  readonly HelixSplitEvent: typeof HelixSplitEvent;
259
+ readonly HelixStopEvent: typeof HelixStopEvent;
258
260
  readonly HelixStrandsFallbackEvent: typeof HelixStrandsFallbackEvent;
259
261
  readonly HelixTerminalBellEvent: typeof HelixTerminalBellEvent;
260
262
  readonly HelixTerminalInputEvent: typeof HelixTerminalInputEvent;
@@ -98,6 +98,7 @@ import { HelixShortcutEvent } from './shortcut.js';
98
98
  import { HelixSortEvent } from './sort.js';
99
99
  import { HelixSortedEvent } from './sorted.js';
100
100
  import { HelixSplitEvent } from './split.js';
101
+ import { HelixStopEvent } from './stop.js';
101
102
  import { HelixStrandsFallbackEvent } from './strands-fallback.js';
102
103
  import { HelixTerminalBellEvent } from './terminal-bell.js';
103
104
  import { HelixTerminalInputEvent } from './terminal-input.js';
@@ -110,7 +111,7 @@ import { HelixTourAskEvent } from './tour-ask.js';
110
111
  import { HelixTourStatusEvent } from './tour-status.js';
111
112
  import { HelixTypingEvent } from './typing.js';
112
113
  export { helixEventNames } from './names.js';
113
- export { HelixActionEvent, HelixActivateEvent, HelixAuraFallbackEvent, HelixBandEvent, HelixBeforeActivateEvent, HelixBeforeChangeEvent, HelixBeforeCloseEvent, HelixBeforeCollapseEvent, HelixBeforeEditEvent, HelixBeforeListenEvent, HelixBeforeMoveEvent, HelixBeforeOpenEvent, HelixBeforePanelResizeEvent, HelixBeforePasteEvent, HelixBeforePinEvent, HelixBeforeReconnectEvent, HelixBeforeRelabelEvent, HelixBeforeReorderEvent, HelixBeforeResizeEvent, HelixBeforeSplitEvent, HelixBeforeToggleEvent, HelixCanvasEditEvent, HelixCanvasPanEvent, HelixCanvasViewEvent, HelixCanvasZoomEvent, HelixCaretEvent, HelixClearEvent, HelixCollapseEvent, HelixCompleteEvent, HelixConnectEvent, HelixConsoleEvent, HelixCopyErrorEvent, HelixCopyEvent, HelixCreateEvent, HelixDeviceButtonEvent, HelixDeviceKeyEvent, HelixDeviceScreenshotEvent, HelixDeviceScrollEvent, HelixDeviceSwipeEvent, HelixDeviceTapEvent, HelixDeviceTextEvent, HelixDeviceTouchEvent, HelixDismissEvent, HelixEditEvent, HelixEndEvent, HelixExpandEvent, HelixFacetEvent, HelixFilterEvent, HelixFilteredEvent, HelixGraphFilterEvent, HelixGraphLayoutEvent, HelixGraphPanEvent, HelixGraphPartChangeEvent, HelixGraphReadyEvent, HelixGraphSignalEvent, HelixGraphZoomEvent, HelixHistoryEvent, HelixListenEvent, HelixLoadEvent, HelixLoadMoreEvent, HelixMapClusterEvent, HelixMapPanEvent, HelixMapPartChangeEvent, HelixMapReadyEvent, HelixMapRotateEvent, HelixMapTiltEvent, HelixMapZoomEvent, HelixMoveEvent, HelixNavigateEvent, HelixNextEvent, HelixPanelResizeEvent, HelixPastedEvent, HelixPinEvent, HelixPlotChangeEvent, HelixPlotTurnEvent, HelixPressEvent, HelixPreviewEvent, HelixPreviousEvent, HelixRangeChangeEvent, HelixReadEvent, HelixReadingChangeEvent, HelixReadingPressEvent, HelixReconnectEvent, HelixRejectEvent, HelixRelabelEvent, HelixRemoveEvent, HelixReorderEvent, HelixReorderedEvent, HelixResizedEvent, HelixResizeEvent, HelixSampleEndEvent, HelixSampleStartEvent, HelixScrolledEvent, HelixSelectEvent, HelixSelectedEvent, HelixSendEvent, HelixShortcutEvent, HelixSortEvent, HelixSortedEvent, HelixSplitEvent, HelixStrandsFallbackEvent, HelixTerminalBellEvent, HelixTerminalInputEvent, HelixTerminalLinkEvent, HelixTerminalResizeEvent, HelixTerminalRestartEvent, HelixTerminalTitleEvent, HelixToggleEvent, HelixTourAskEvent, HelixTourStatusEvent, HelixTypingEvent, };
114
+ export { HelixActionEvent, HelixActivateEvent, HelixAuraFallbackEvent, HelixBandEvent, HelixBeforeActivateEvent, HelixBeforeChangeEvent, HelixBeforeCloseEvent, HelixBeforeCollapseEvent, HelixBeforeEditEvent, HelixBeforeListenEvent, HelixBeforeMoveEvent, HelixBeforeOpenEvent, HelixBeforePanelResizeEvent, HelixBeforePasteEvent, HelixBeforePinEvent, HelixBeforeReconnectEvent, HelixBeforeRelabelEvent, HelixBeforeReorderEvent, HelixBeforeResizeEvent, HelixBeforeSplitEvent, HelixBeforeToggleEvent, HelixCanvasEditEvent, HelixCanvasPanEvent, HelixCanvasViewEvent, HelixCanvasZoomEvent, HelixCaretEvent, HelixClearEvent, HelixCollapseEvent, HelixCompleteEvent, HelixConnectEvent, HelixConsoleEvent, HelixCopyErrorEvent, HelixCopyEvent, HelixCreateEvent, HelixDeviceButtonEvent, HelixDeviceKeyEvent, HelixDeviceScreenshotEvent, HelixDeviceScrollEvent, HelixDeviceSwipeEvent, HelixDeviceTapEvent, HelixDeviceTextEvent, HelixDeviceTouchEvent, HelixDismissEvent, HelixEditEvent, HelixEndEvent, HelixExpandEvent, HelixFacetEvent, HelixFilterEvent, HelixFilteredEvent, HelixGraphFilterEvent, HelixGraphLayoutEvent, HelixGraphPanEvent, HelixGraphPartChangeEvent, HelixGraphReadyEvent, HelixGraphSignalEvent, HelixGraphZoomEvent, HelixHistoryEvent, HelixListenEvent, HelixLoadEvent, HelixLoadMoreEvent, HelixMapClusterEvent, HelixMapPanEvent, HelixMapPartChangeEvent, HelixMapReadyEvent, HelixMapRotateEvent, HelixMapTiltEvent, HelixMapZoomEvent, HelixMoveEvent, HelixNavigateEvent, HelixNextEvent, HelixPanelResizeEvent, HelixPastedEvent, HelixPinEvent, HelixPlotChangeEvent, HelixPlotTurnEvent, HelixPressEvent, HelixPreviewEvent, HelixPreviousEvent, HelixRangeChangeEvent, HelixReadEvent, HelixReadingChangeEvent, HelixReadingPressEvent, HelixReconnectEvent, HelixRejectEvent, HelixRelabelEvent, HelixRemoveEvent, HelixReorderEvent, HelixReorderedEvent, HelixResizedEvent, HelixResizeEvent, HelixSampleEndEvent, HelixSampleStartEvent, HelixScrolledEvent, HelixSelectEvent, HelixSelectedEvent, HelixSendEvent, HelixShortcutEvent, HelixSortEvent, HelixSortedEvent, HelixSplitEvent, HelixStopEvent, HelixStrandsFallbackEvent, HelixTerminalBellEvent, HelixTerminalInputEvent, HelixTerminalLinkEvent, HelixTerminalResizeEvent, HelixTerminalRestartEvent, HelixTerminalTitleEvent, HelixToggleEvent, HelixTourAskEvent, HelixTourStatusEvent, HelixTypingEvent, };
114
115
  const registry = {
115
116
  HelixActionEvent,
116
117
  HelixActivateEvent,
@@ -212,6 +213,7 @@ const registry = {
212
213
  HelixSortEvent,
213
214
  HelixSortedEvent,
214
215
  HelixSplitEvent,
216
+ HelixStopEvent,
215
217
  HelixStrandsFallbackEvent,
216
218
  HelixTerminalBellEvent,
217
219
  HelixTerminalInputEvent,
@@ -11,7 +11,7 @@ export const helixEventNames = new Set(('action activate aura-fallback band befo
11
11
  'preview previous range-change read reading-change reading-press reconnect reject ' +
12
12
  'relabel remove reorder ' +
13
13
  'reordered resize resized sample-end sample-start scrolled select selected send ' +
14
- 'shortcut sort sorted split strands-fallback terminal-bell terminal-input terminal-link ' +
14
+ 'shortcut sort sorted split stop strands-fallback terminal-bell terminal-input terminal-link ' +
15
15
  'terminal-resize terminal-restart terminal-title toggle tour-ask tour-status typing')
16
16
  .split(' ')
17
17
  .map((name) => `helix-${name}`));
@@ -0,0 +1,12 @@
1
+ /**
2
+ * A reader asked for what is under way to stop: the stop control a chat
3
+ * composer shows in place of its send button while a reply is on its way.
4
+ *
5
+ * Carries nothing, because what stops is the listener's to know. Not
6
+ * cancelable: the reader has asked, and the composer changes nothing by
7
+ * itself, so there is nothing for a listener to refuse.
8
+ */
9
+ export declare class HelixStopEvent extends Event {
10
+ static readonly eventName = "helix-stop";
11
+ constructor();
12
+ }
@@ -0,0 +1,6 @@
1
+ export class HelixStopEvent extends Event {
2
+ static { this.eventName = 'helix-stop'; }
3
+ constructor() {
4
+ super(HelixStopEvent.eventName, { bubbles: true, cancelable: false, composed: true });
5
+ }
6
+ }
@@ -298,6 +298,25 @@ export declare class HelixElement extends LitElement {
298
298
  * cost, and a property read where it did not: see `reportRender`.
299
299
  */
300
300
  protected performUpdate(): void;
301
+ /**
302
+ * Whether the first update found React's props, where it looked before the
303
+ * render. See `willUpdate`.
304
+ */
305
+ private reactApplied;
306
+ /**
307
+ * **The first look for React's props, and the one that costs nothing.** It
308
+ * succeeds whenever the definition landed after React hydrated, which is the
309
+ * ordering a deferred or lazily imported element has: the bag is already on
310
+ * the node, so the value is in the first render.
311
+ *
312
+ * **Before the render, because `firstUpdated` is too late.** A value
313
+ * assigned there schedules a second update as the first one finishes, which
314
+ * Lit's development build reports as `change-in-update`: Command's
315
+ * `filterMatch` did in the Hartley app until October 14, 2026. A hydrating
316
+ * render has to be the server's, so that element looks in `firstUpdated`,
317
+ * where `finishHydration` asks for the update after it anyway.
318
+ */
319
+ protected willUpdate(changed: PropertyValues): void;
301
320
  /**
302
321
  * Marks the component's first style resolution as done, so `SETTLED` stops
303
322
  * suppressing transitions.
@@ -107,10 +107,16 @@ export class HelixElement extends LitElement {
107
107
  reportRender(this, start, performance.now() - start);
108
108
  }
109
109
  }
110
+ willUpdate(changed) {
111
+ super.willUpdate(changed);
112
+ if (!this.hasUpdated && !this.hydrating) {
113
+ this.reactApplied = applyReactProperties(this);
114
+ }
115
+ }
110
116
  firstUpdated(changed) {
111
117
  super.firstUpdated(changed);
112
118
  reportUsage(this);
113
- const applied = applyReactProperties(this);
119
+ const applied = this.reactApplied ?? applyReactProperties(this);
114
120
  requestAnimationFrame(() => {
115
121
  this.toggleState('settled', true);
116
122
  this.resolveSettled();
package/dist/index.d.ts CHANGED
@@ -28,7 +28,7 @@ export { animationsFinished, animationsSettled, type OpenPhase, OpenPhaseControl
28
28
  export { OptionWindowController, type OptionWindowOptions, } from './controllers/option-window-controller/option-window-controller.js';
29
29
  export { MAX_FRAME_STEP, PaintGateController, type PaintGateOptions, } from './controllers/paint-gate-controller/paint-gate-controller.js';
30
30
  export { PlacementController } from './controllers/placement-controller/placement-controller.js';
31
- export { PopoverController, type PopoverOptions, } from './controllers/popover-controller/popover-controller.js';
31
+ export { hintInside, hintMayShow, onPanelOpen, PopoverController, type PopoverOptions, } from './controllers/popover-controller/popover-controller.js';
32
32
  export { RelativeClockController, type RelativeClockOptions, } from './controllers/relative-clock-controller/relative-clock-controller.js';
33
33
  export { RemotePlaybackController, type RemoteReport, } from './controllers/remote-playback-controller/remote-playback-controller.js';
34
34
  export { ReorderController, type ReorderMove, type ReorderOptions, type ReorderWords, requestReorder, } from './controllers/reorder-controller/reorder-controller.js';
@@ -41,7 +41,7 @@ export { UpgradeController, type UpgradeOptions, } from './controllers/upgrade-c
41
41
  export { type Rendered, VirtualizeController, type VirtualizeOptions, type Window as VirtualizeWindow, } from './controllers/virtualize-controller/virtualize-controller.js';
42
42
  export { Waterfall } from './controllers/virtualize-controller/waterfall.js';
43
43
  export { eventsForTag, registerElementEvents } from './events/element-events.js';
44
- export { type ActivateDetail, type AuraFallbackDetail, type BandDetail, type CanvasView, type CaretDetail, type ChangeDetail, type CompleteDetail, type ConnectDetail, type ConsoleDetail, type CopyDetail, type CreateDetail, type DeviceButton, type DeviceButtonDetail, type DeviceKeyDetail, type DeviceScrollDetail, type DeviceSwipeDetail, type DeviceTapDetail, type DeviceTextDetail, type DeviceTouchDetail, type DismissDetail, type FacetDetail, type FilterDetail, type GraphFilterDetail, type GraphLayoutDetail, type GraphPartChangeDetail, type GraphSignalDetail, HelixActionEvent, HelixActivateEvent, HelixAuraFallbackEvent, HelixBandEvent, HelixBeforeActivateEvent, HelixBeforeChangeEvent, HelixBeforeCloseEvent, HelixBeforeCollapseEvent, HelixBeforeEditEvent, HelixBeforeListenEvent, HelixBeforeMoveEvent, HelixBeforeOpenEvent, HelixBeforePanelResizeEvent, HelixBeforePasteEvent, HelixBeforePinEvent, HelixBeforeReconnectEvent, HelixBeforeRelabelEvent, HelixBeforeReorderEvent, HelixBeforeResizeEvent, HelixBeforeSplitEvent, HelixBeforeToggleEvent, HelixCanvasEditEvent, HelixCanvasPanEvent, HelixCanvasViewEvent, HelixCanvasZoomEvent, HelixCaretEvent, HelixClearEvent, HelixCollapseEvent, HelixCompleteEvent, HelixConnectEvent, HelixConsoleEvent, HelixCopyErrorEvent, HelixCopyEvent, HelixCreateEvent, HelixDeviceButtonEvent, HelixDeviceKeyEvent, HelixDeviceScreenshotEvent, HelixDeviceScrollEvent, HelixDeviceSwipeEvent, HelixDeviceTapEvent, HelixDeviceTextEvent, HelixDeviceTouchEvent, HelixDismissEvent, HelixEditEvent, HelixEndEvent, HelixExpandEvent, HelixFacetEvent, HelixFilterEvent, HelixFilteredEvent, HelixGraphFilterEvent, HelixGraphLayoutEvent, HelixGraphPanEvent, HelixGraphPartChangeEvent, HelixGraphReadyEvent, HelixGraphSignalEvent, HelixGraphZoomEvent, HelixHistoryEvent, HelixListenEvent, HelixLoadEvent, HelixLoadMoreEvent, HelixMapClusterEvent, HelixMapPanEvent, HelixMapPartChangeEvent, HelixMapReadyEvent, HelixMapRotateEvent, HelixMapTiltEvent, HelixMapZoomEvent, HelixMoveEvent, HelixNavigateEvent, HelixNextEvent, HelixPanelResizeEvent, HelixPastedEvent, HelixPinEvent, HelixPlotChangeEvent, HelixPlotTurnEvent, HelixPressEvent, HelixPreviewEvent, HelixPreviousEvent, HelixRangeChangeEvent, HelixReadEvent, HelixReadingChangeEvent, HelixReadingPressEvent, HelixReconnectEvent, HelixRejectEvent, HelixRelabelEvent, HelixRemoveEvent, HelixReorderEvent, HelixReorderedEvent, HelixResizedEvent, HelixResizeEvent, HelixSampleEndEvent, HelixSampleStartEvent, HelixScrolledEvent, HelixSelectEvent, HelixSelectedEvent, HelixSendEvent, HelixShortcutEvent, HelixSortEvent, HelixSortedEvent, HelixSplitEvent, HelixStrandsFallbackEvent, HelixTerminalBellEvent, HelixTerminalInputEvent, HelixTerminalLinkEvent, HelixTerminalResizeEvent, HelixTerminalRestartEvent, HelixTerminalTitleEvent, HelixToggleEvent, HelixTourAskEvent, HelixTourStatusEvent, HelixTypingEvent, type ListenDetail, type MapClusterDetail, type MapPanDetail, type MapPartChangeDetail, type MapRotateDetail, type MapTiltDetail, type MapZoomDetail, type MoveDetail, type NavigateDetail, type OpenDetail, type PasteDetail, type PlotChangeDetail, type PlotTurnDetail, type PressDetail, type PressPointerType, type RangeDetail, type ReadDetail, type ReadingDetail, type ReconnectDetail, type Refusal, type RefusedFile, type RejectDetail, type RelabelDetail, type RemoveDetail, type ReorderDetail, type ResizeDetail, type ScrolledDetail, type SelectDetail, type SendDetail, type ShortcutDetail, type SkipDetail, type SortDetail, type StrandsFallbackDetail, type TerminalInputDetail, type TerminalLinkDetail, type TerminalResizeDetail, type TerminalTitleDetail, type ToggleDetail, type TourStatusDetail, type TypingDetail, type ViewportPanDetail, type ViewportZoomDetail, } from './events/events.js';
44
+ export { type ActivateDetail, type AuraFallbackDetail, type BandDetail, type CanvasView, type CaretDetail, type ChangeDetail, type CompleteDetail, type ConnectDetail, type ConsoleDetail, type CopyDetail, type CreateDetail, type DeviceButton, type DeviceButtonDetail, type DeviceKeyDetail, type DeviceScrollDetail, type DeviceSwipeDetail, type DeviceTapDetail, type DeviceTextDetail, type DeviceTouchDetail, type DismissDetail, type FacetDetail, type FilterDetail, type GraphFilterDetail, type GraphLayoutDetail, type GraphPartChangeDetail, type GraphSignalDetail, HelixActionEvent, HelixActivateEvent, HelixAuraFallbackEvent, HelixBandEvent, HelixBeforeActivateEvent, HelixBeforeChangeEvent, HelixBeforeCloseEvent, HelixBeforeCollapseEvent, HelixBeforeEditEvent, HelixBeforeListenEvent, HelixBeforeMoveEvent, HelixBeforeOpenEvent, HelixBeforePanelResizeEvent, HelixBeforePasteEvent, HelixBeforePinEvent, HelixBeforeReconnectEvent, HelixBeforeRelabelEvent, HelixBeforeReorderEvent, HelixBeforeResizeEvent, HelixBeforeSplitEvent, HelixBeforeToggleEvent, HelixCanvasEditEvent, HelixCanvasPanEvent, HelixCanvasViewEvent, HelixCanvasZoomEvent, HelixCaretEvent, HelixClearEvent, HelixCollapseEvent, HelixCompleteEvent, HelixConnectEvent, HelixConsoleEvent, HelixCopyErrorEvent, HelixCopyEvent, HelixCreateEvent, HelixDeviceButtonEvent, HelixDeviceKeyEvent, HelixDeviceScreenshotEvent, HelixDeviceScrollEvent, HelixDeviceSwipeEvent, HelixDeviceTapEvent, HelixDeviceTextEvent, HelixDeviceTouchEvent, HelixDismissEvent, HelixEditEvent, HelixEndEvent, HelixExpandEvent, HelixFacetEvent, HelixFilterEvent, HelixFilteredEvent, HelixGraphFilterEvent, HelixGraphLayoutEvent, HelixGraphPanEvent, HelixGraphPartChangeEvent, HelixGraphReadyEvent, HelixGraphSignalEvent, HelixGraphZoomEvent, HelixHistoryEvent, HelixListenEvent, HelixLoadEvent, HelixLoadMoreEvent, HelixMapClusterEvent, HelixMapPanEvent, HelixMapPartChangeEvent, HelixMapReadyEvent, HelixMapRotateEvent, HelixMapTiltEvent, HelixMapZoomEvent, HelixMoveEvent, HelixNavigateEvent, HelixNextEvent, HelixPanelResizeEvent, HelixPastedEvent, HelixPinEvent, HelixPlotChangeEvent, HelixPlotTurnEvent, HelixPressEvent, HelixPreviewEvent, HelixPreviousEvent, HelixRangeChangeEvent, HelixReadEvent, HelixReadingChangeEvent, HelixReadingPressEvent, HelixReconnectEvent, HelixRejectEvent, HelixRelabelEvent, HelixRemoveEvent, HelixReorderEvent, HelixReorderedEvent, HelixResizedEvent, HelixResizeEvent, HelixSampleEndEvent, HelixSampleStartEvent, HelixScrolledEvent, HelixSelectEvent, HelixSelectedEvent, HelixSendEvent, HelixShortcutEvent, HelixSortEvent, HelixSortedEvent, HelixSplitEvent, HelixStopEvent, HelixStrandsFallbackEvent, HelixTerminalBellEvent, HelixTerminalInputEvent, HelixTerminalLinkEvent, HelixTerminalResizeEvent, HelixTerminalRestartEvent, HelixTerminalTitleEvent, HelixToggleEvent, HelixTourAskEvent, HelixTourStatusEvent, HelixTypingEvent, type ListenDetail, type MapClusterDetail, type MapPanDetail, type MapPartChangeDetail, type MapRotateDetail, type MapTiltDetail, type MapZoomDetail, type MoveDetail, type NavigateDetail, type OpenDetail, type PasteDetail, type PlotChangeDetail, type PlotTurnDetail, type PressDetail, type PressPointerType, type RangeDetail, type ReadDetail, type ReadingDetail, type ReconnectDetail, type Refusal, type RefusedFile, type RejectDetail, type RelabelDetail, type RemoveDetail, type ReorderDetail, type ResizeDetail, type ScrolledDetail, type SelectDetail, type SendDetail, type ShortcutDetail, type SkipDetail, type SortDetail, type StrandsFallbackDetail, type TerminalInputDetail, type TerminalLinkDetail, type TerminalResizeDetail, type TerminalTitleDetail, type ToggleDetail, type TourStatusDetail, type TypingDetail, type ViewportPanDetail, type ViewportZoomDetail, } from './events/events.js';
45
45
  export { HelixElement, whenHydrated } from './helix-element/helix-element.js';
46
46
  export { LAYERS_RULE, MIRROR_RULE, SCROLLER_RULE, SPOKEN_RULE, TOUCHES_RULE, VIRTUAL_RULE, } from './helix-element/mark-rules.js';
47
47
  export { HelixFormElement } from './helix-form-element/helix-form-element.js';
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ export { animationsFinished, animationsSettled, OpenPhaseController, } from './c
28
28
  export { OptionWindowController, } from './controllers/option-window-controller/option-window-controller.js';
29
29
  export { MAX_FRAME_STEP, PaintGateController, } from './controllers/paint-gate-controller/paint-gate-controller.js';
30
30
  export { PlacementController } from './controllers/placement-controller/placement-controller.js';
31
- export { PopoverController, } from './controllers/popover-controller/popover-controller.js';
31
+ export { hintInside, hintMayShow, onPanelOpen, PopoverController, } from './controllers/popover-controller/popover-controller.js';
32
32
  export { RelativeClockController, } from './controllers/relative-clock-controller/relative-clock-controller.js';
33
33
  export { RemotePlaybackController, } from './controllers/remote-playback-controller/remote-playback-controller.js';
34
34
  export { ReorderController, requestReorder, } from './controllers/reorder-controller/reorder-controller.js';
@@ -41,7 +41,7 @@ export { UpgradeController, } from './controllers/upgrade-controller/upgrade-con
41
41
  export { VirtualizeController, } from './controllers/virtualize-controller/virtualize-controller.js';
42
42
  export { Waterfall } from './controllers/virtualize-controller/waterfall.js';
43
43
  export { eventsForTag, registerElementEvents } from './events/element-events.js';
44
- export { HelixActionEvent, HelixActivateEvent, HelixAuraFallbackEvent, HelixBandEvent, HelixBeforeActivateEvent, HelixBeforeChangeEvent, HelixBeforeCloseEvent, HelixBeforeCollapseEvent, HelixBeforeEditEvent, HelixBeforeListenEvent, HelixBeforeMoveEvent, HelixBeforeOpenEvent, HelixBeforePanelResizeEvent, HelixBeforePasteEvent, HelixBeforePinEvent, HelixBeforeReconnectEvent, HelixBeforeRelabelEvent, HelixBeforeReorderEvent, HelixBeforeResizeEvent, HelixBeforeSplitEvent, HelixBeforeToggleEvent, HelixCanvasEditEvent, HelixCanvasPanEvent, HelixCanvasViewEvent, HelixCanvasZoomEvent, HelixCaretEvent, HelixClearEvent, HelixCollapseEvent, HelixCompleteEvent, HelixConnectEvent, HelixConsoleEvent, HelixCopyErrorEvent, HelixCopyEvent, HelixCreateEvent, HelixDeviceButtonEvent, HelixDeviceKeyEvent, HelixDeviceScreenshotEvent, HelixDeviceScrollEvent, HelixDeviceSwipeEvent, HelixDeviceTapEvent, HelixDeviceTextEvent, HelixDeviceTouchEvent, HelixDismissEvent, HelixEditEvent, HelixEndEvent, HelixExpandEvent, HelixFacetEvent, HelixFilterEvent, HelixFilteredEvent, HelixGraphFilterEvent, HelixGraphLayoutEvent, HelixGraphPanEvent, HelixGraphPartChangeEvent, HelixGraphReadyEvent, HelixGraphSignalEvent, HelixGraphZoomEvent, HelixHistoryEvent, HelixListenEvent, HelixLoadEvent, HelixLoadMoreEvent, HelixMapClusterEvent, HelixMapPanEvent, HelixMapPartChangeEvent, HelixMapReadyEvent, HelixMapRotateEvent, HelixMapTiltEvent, HelixMapZoomEvent, HelixMoveEvent, HelixNavigateEvent, HelixNextEvent, HelixPanelResizeEvent, HelixPastedEvent, HelixPinEvent, HelixPlotChangeEvent, HelixPlotTurnEvent, HelixPressEvent, HelixPreviewEvent, HelixPreviousEvent, HelixRangeChangeEvent, HelixReadEvent, HelixReadingChangeEvent, HelixReadingPressEvent, HelixReconnectEvent, HelixRejectEvent, HelixRelabelEvent, HelixRemoveEvent, HelixReorderEvent, HelixReorderedEvent, HelixResizedEvent, HelixResizeEvent, HelixSampleEndEvent, HelixSampleStartEvent, HelixScrolledEvent, HelixSelectEvent, HelixSelectedEvent, HelixSendEvent, HelixShortcutEvent, HelixSortEvent, HelixSortedEvent, HelixSplitEvent, HelixStrandsFallbackEvent, HelixTerminalBellEvent, HelixTerminalInputEvent, HelixTerminalLinkEvent, HelixTerminalResizeEvent, HelixTerminalRestartEvent, HelixTerminalTitleEvent, HelixToggleEvent, HelixTourAskEvent, HelixTourStatusEvent, HelixTypingEvent, } from './events/events.js';
44
+ export { HelixActionEvent, HelixActivateEvent, HelixAuraFallbackEvent, HelixBandEvent, HelixBeforeActivateEvent, HelixBeforeChangeEvent, HelixBeforeCloseEvent, HelixBeforeCollapseEvent, HelixBeforeEditEvent, HelixBeforeListenEvent, HelixBeforeMoveEvent, HelixBeforeOpenEvent, HelixBeforePanelResizeEvent, HelixBeforePasteEvent, HelixBeforePinEvent, HelixBeforeReconnectEvent, HelixBeforeRelabelEvent, HelixBeforeReorderEvent, HelixBeforeResizeEvent, HelixBeforeSplitEvent, HelixBeforeToggleEvent, HelixCanvasEditEvent, HelixCanvasPanEvent, HelixCanvasViewEvent, HelixCanvasZoomEvent, HelixCaretEvent, HelixClearEvent, HelixCollapseEvent, HelixCompleteEvent, HelixConnectEvent, HelixConsoleEvent, HelixCopyErrorEvent, HelixCopyEvent, HelixCreateEvent, HelixDeviceButtonEvent, HelixDeviceKeyEvent, HelixDeviceScreenshotEvent, HelixDeviceScrollEvent, HelixDeviceSwipeEvent, HelixDeviceTapEvent, HelixDeviceTextEvent, HelixDeviceTouchEvent, HelixDismissEvent, HelixEditEvent, HelixEndEvent, HelixExpandEvent, HelixFacetEvent, HelixFilterEvent, HelixFilteredEvent, HelixGraphFilterEvent, HelixGraphLayoutEvent, HelixGraphPanEvent, HelixGraphPartChangeEvent, HelixGraphReadyEvent, HelixGraphSignalEvent, HelixGraphZoomEvent, HelixHistoryEvent, HelixListenEvent, HelixLoadEvent, HelixLoadMoreEvent, HelixMapClusterEvent, HelixMapPanEvent, HelixMapPartChangeEvent, HelixMapReadyEvent, HelixMapRotateEvent, HelixMapTiltEvent, HelixMapZoomEvent, HelixMoveEvent, HelixNavigateEvent, HelixNextEvent, HelixPanelResizeEvent, HelixPastedEvent, HelixPinEvent, HelixPlotChangeEvent, HelixPlotTurnEvent, HelixPressEvent, HelixPreviewEvent, HelixPreviousEvent, HelixRangeChangeEvent, HelixReadEvent, HelixReadingChangeEvent, HelixReadingPressEvent, HelixReconnectEvent, HelixRejectEvent, HelixRelabelEvent, HelixRemoveEvent, HelixReorderEvent, HelixReorderedEvent, HelixResizedEvent, HelixResizeEvent, HelixSampleEndEvent, HelixSampleStartEvent, HelixScrolledEvent, HelixSelectEvent, HelixSelectedEvent, HelixSendEvent, HelixShortcutEvent, HelixSortEvent, HelixSortedEvent, HelixSplitEvent, HelixStopEvent, HelixStrandsFallbackEvent, HelixTerminalBellEvent, HelixTerminalInputEvent, HelixTerminalLinkEvent, HelixTerminalResizeEvent, HelixTerminalRestartEvent, HelixTerminalTitleEvent, HelixToggleEvent, HelixTourAskEvent, HelixTourStatusEvent, HelixTypingEvent, } from './events/events.js';
45
45
  export { HelixElement, whenHydrated } from './helix-element/helix-element.js';
46
46
  export { LAYERS_RULE, MIRROR_RULE, SCROLLER_RULE, SPOKEN_RULE, TOUCHES_RULE, VIRTUAL_RULE, } from './helix-element/mark-rules.js';
47
47
  export { HelixFormElement } from './helix-form-element/helix-form-element.js';
@@ -292,12 +292,14 @@ export const MixinViewport = (Base) => {
292
292
  }
293
293
  const [up, start, end, down] = ['up', 'start', 'end', 'down'].map((way) => this.panGlyph(way));
294
294
  const button = this.controlTag;
295
+ const tip = this.tag('tooltip');
295
296
  const { material, raised } = this.controlAxes;
296
297
  const ground = material ?? nothing;
297
298
  return staticHtml `<div part="controls" role="toolbar" aria-label=${this.controlsLabel}>
298
299
  ${pan
299
300
  ? staticHtml `<${button}
300
301
  part="pan-up"
302
+ id="pan-up"
301
303
  exportparts="button: pan-up-button, live: pan-up-live, spinner: pan-up-spinner, text: pan-up-text"
302
304
  label=${this.panLabel('up')}
303
305
  material=${ground}
@@ -306,9 +308,10 @@ export const MixinViewport = (Base) => {
306
308
  @helix-press=${stopPress}
307
309
  >
308
310
  <slot name="pan-up-icon">${up}</slot>
309
- </${button}>
311
+ </${button}><${tip} for="pan-up" content=${this.panLabel('up')} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>
310
312
  <${button}
311
313
  part="pan-start"
314
+ id="pan-start"
312
315
  exportparts="button: pan-start-button, live: pan-start-live, spinner: pan-start-spinner, text: pan-start-text"
313
316
  label=${this.panLabel('start')}
314
317
  material=${ground}
@@ -317,9 +320,10 @@ export const MixinViewport = (Base) => {
317
320
  @helix-press=${stopPress}
318
321
  >
319
322
  <slot name="pan-start-icon">${start}</slot>
320
- </${button}>
323
+ </${button}><${tip} for="pan-start" content=${this.panLabel('start')} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>
321
324
  <${button}
322
325
  part="pan-end"
326
+ id="pan-end"
323
327
  exportparts="button: pan-end-button, live: pan-end-live, spinner: pan-end-spinner, text: pan-end-text"
324
328
  label=${this.panLabel('end')}
325
329
  material=${ground}
@@ -328,9 +332,10 @@ export const MixinViewport = (Base) => {
328
332
  @helix-press=${stopPress}
329
333
  >
330
334
  <slot name="pan-end-icon">${end}</slot>
331
- </${button}>
335
+ </${button}><${tip} for="pan-end" content=${this.panLabel('end')} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>
332
336
  <${button}
333
337
  part="pan-down"
338
+ id="pan-down"
334
339
  exportparts="button: pan-down-button, live: pan-down-live, spinner: pan-down-spinner, text: pan-down-text"
335
340
  label=${this.panLabel('down')}
336
341
  material=${ground}
@@ -339,11 +344,12 @@ export const MixinViewport = (Base) => {
339
344
  @helix-press=${stopPress}
340
345
  >
341
346
  <slot name="pan-down-icon">${down}</slot>
342
- </${button}>`
347
+ </${button}><${tip} for="pan-down" content=${this.panLabel('down')} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>`
343
348
  : nothing}
344
349
  ${zoom
345
350
  ? staticHtml `<${button}
346
351
  part="zoom-in"
352
+ id="zoom-in"
347
353
  exportparts="button: zoom-in-button, live: zoom-in-live, spinner: zoom-in-spinner, text: zoom-in-text"
348
354
  label=${this.zoomInLabel}
349
355
  material=${ground}
@@ -352,9 +358,10 @@ export const MixinViewport = (Base) => {
352
358
  @helix-press=${stopPress}
353
359
  >
354
360
  <slot name="zoom-in-icon">+</slot>
355
- </${button}>
361
+ </${button}><${tip} for="zoom-in" content=${this.zoomInLabel} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>
356
362
  <${button}
357
363
  part="zoom-out"
364
+ id="zoom-out"
358
365
  exportparts="button: zoom-out-button, live: zoom-out-live, spinner: zoom-out-spinner, text: zoom-out-text"
359
366
  label=${this.zoomOutLabel}
360
367
  material=${ground}
@@ -363,12 +370,13 @@ export const MixinViewport = (Base) => {
363
370
  @helix-press=${stopPress}
364
371
  >
365
372
  <slot name="zoom-out-icon">−</slot>
366
- </${button}>`
373
+ </${button}><${tip} for="zoom-out" content=${this.zoomOutLabel} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>`
367
374
  : nothing}
368
375
  ${more}
369
376
  ${fullscreen
370
377
  ? staticHtml `<${button}
371
378
  part="fullscreen"
379
+ id="fullscreen"
372
380
  exportparts="button: fullscreen-button, live: fullscreen-live, spinner: fullscreen-spinner, text: fullscreen-text"
373
381
  aria-pressed=${this.filled ? 'true' : 'false'}
374
382
  label=${this.fullscreenLabel}
@@ -378,7 +386,7 @@ export const MixinViewport = (Base) => {
378
386
  @helix-press=${stopPress}
379
387
  >
380
388
  <slot name="fullscreen-icon">⤢</slot>
381
- </${button}>`
389
+ </${button}><${tip} for="fullscreen" content=${this.fullscreenLabel} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>`
382
390
  : nothing}
383
391
  </div>`;
384
392
  }
@@ -8,5 +8,7 @@ import type { auditChildren as audit, auditEntry as entry } from './audit-childr
8
8
  * builds. See `audit-children.ts`.
9
9
  */
10
10
  export declare const auditChildren: typeof audit;
11
+ /** A production build has no audit, so nothing waits. See `QUIET`. */
12
+ export declare const QUIET = 0;
11
13
  /** The missing-entry warning in a production build: nothing. See `auditEntry`. */
12
14
  export declare const auditEntry: typeof entry;
@@ -1,2 +1,3 @@
1
1
  export const auditChildren = () => { };
2
+ export const QUIET = 0;
2
3
  export const auditEntry = () => { };
@@ -1,3 +1,14 @@
1
+ /**
2
+ * How long, in milliseconds, the page goes without loading a resource or
3
+ * registering a Helix family before a child still undefined counts as missing.
4
+ *
5
+ * **An app that imports its families with `import()` registers them after
6
+ * the page has loaded**, each as its chunk arrives, so a container can be
7
+ * defined and painted while the chunk holding its children is still in
8
+ * flight. Measured in the Hartley desktop app on October 14, 2026: Command was
9
+ * defined 90 ms before Option, and its families arrived over 230 ms.
10
+ */
11
+ export declare const QUIET = 500;
1
12
  /**
2
13
  * Reports a child from another family that a container holds and nobody
3
14
  * registered, naming the entry to import.
@@ -9,8 +20,10 @@
9
20
  * This says so, once for each container tag and child tag on the page, with
10
21
  * the line to add.
11
22
  *
12
- * Asked once the page has loaded and painted, so an entry a later module
13
- * imports is not taken for a missing one.
23
+ * Asked once the page has loaded and painted. A child still undefined then
24
+ * is reported once the page has loaded and registered nothing for `QUIET`
25
+ * milliseconds and the tag is still undefined, so an entry a later module or
26
+ * a later chunk imports is not taken for a missing one.
14
27
  *
15
28
  * **In a development build only**, as `missing-name` is: every container would
16
29
  * pay for a sentence meant for the person writing the page. Core imports it as
@@ -40,6 +53,8 @@ export declare const auditChildren: (host: HTMLElement, part: string, entry: str
40
53
  * `attribute` is the one the page set, `entry` the entry after the tier's
41
54
  * package, such as `menu/filter`, and `consequence` what the missing entry
42
55
  * costs, after "so". `missing` is asked once the page has loaded and painted,
43
- * so an entry a later module imports is not taken for a missing one.
56
+ * and again once the page has loaded and registered nothing for `QUIET`
57
+ * milliseconds, so an entry a later module or a later chunk imports is not
58
+ * taken for a missing one.
44
59
  */
45
60
  export declare const auditEntry: (host: HTMLElement, attribute: string, entry: string, consequence: string, missing: () => boolean) => void;
@@ -1,3 +1,4 @@
1
+ import { definedTags } from '../custom-element/custom-element.js';
1
2
  import { howToRegister } from '../how-to-register/how-to-register.js';
2
3
  import { reportRuntime } from '../report-runtime/report-runtime.js';
3
4
  const reported = new Set();
@@ -12,6 +13,27 @@ const afterPaint = (check) => {
12
13
  window.addEventListener('load', frame, { once: true });
13
14
  }
14
15
  };
16
+ export const QUIET = 500;
17
+ const activity = () => definedTags().length + (performance.getEntriesByType?.('resource').length ?? 0);
18
+ const whenQuiet = () => new Promise((resolve) => {
19
+ let seen = activity();
20
+ let periods = 0;
21
+ const wait = () => {
22
+ setTimeout(() => {
23
+ const now = activity();
24
+ periods += 1;
25
+ if (now === seen || periods >= 20) {
26
+ resolve();
27
+ }
28
+ else {
29
+ seen = now;
30
+ wait();
31
+ }
32
+ }, QUIET);
33
+ };
34
+ wait();
35
+ });
36
+ const settled = (tag) => Promise.race([customElements.whenDefined(tag), whenQuiet()]);
15
37
  export const auditChildren = (host, part, entry, consequence, drawn = false) => {
16
38
  if (typeof window === 'undefined') {
17
39
  return;
@@ -22,11 +44,16 @@ export const auditChildren = (host, part, entry, consequence, drawn = false) =>
22
44
  if (reported.has(key)) {
23
45
  return;
24
46
  }
25
- const check = () => {
26
- if (reported.has(key) ||
27
- !host.isConnected ||
28
- customElements.get(tag) !== undefined ||
29
- (!drawn && host.querySelector(tag) === null)) {
47
+ const fine = () => reported.has(key) ||
48
+ !host.isConnected ||
49
+ customElements.get(tag) !== undefined ||
50
+ (!drawn && host.querySelector(tag) === null);
51
+ const check = async () => {
52
+ if (fine()) {
53
+ return;
54
+ }
55
+ await settled(tag);
56
+ if (fine()) {
30
57
  return;
31
58
  }
32
59
  reported.add(key);
@@ -36,7 +63,7 @@ export const auditChildren = (host, part, entry, consequence, drawn = false) =>
36
63
  tag: host.localName,
37
64
  });
38
65
  };
39
- afterPaint(check);
66
+ afterPaint(() => void check());
40
67
  };
41
68
  export const auditEntry = (host, attribute, entry, consequence, missing) => {
42
69
  if (typeof window === 'undefined') {
@@ -46,8 +73,13 @@ export const auditEntry = (host, attribute, entry, consequence, missing) => {
46
73
  if (reported.has(key)) {
47
74
  return;
48
75
  }
49
- afterPaint(() => {
50
- if (reported.has(key) || !host.isConnected || !missing()) {
76
+ const fine = () => reported.has(key) || !host.isConnected || !missing();
77
+ const check = async () => {
78
+ if (fine()) {
79
+ return;
80
+ }
81
+ await whenQuiet();
82
+ if (fine()) {
51
83
  return;
52
84
  }
53
85
  reported.add(key);
@@ -58,5 +90,6 @@ export const auditEntry = (host, attribute, entry, consequence, missing) => {
58
90
  message: `<${host.localName}> has \`${attribute}\` and its entry is not imported, so ${consequence}. Import '${pkg}/${entry}'.`,
59
91
  tag: host.localName,
60
92
  });
61
- });
93
+ };
94
+ afterPaint(() => void check());
62
95
  };
@@ -8,7 +8,8 @@
8
8
  * import Toolbar's class; Toolbar adds its own class for a third tag.
9
9
  *
10
10
  * `closest` stops at a shadow root, which is what a slotted child wants: its
11
- * toolbar is its light-DOM parent or above.
11
+ * toolbar is its light-DOM parent or above. It also stops at a panel, so a row
12
+ * in a menu opened from a toolbar is in no toolbar.
12
13
  */
13
14
  export declare const toolbarAround: (element: Element) => Element | null;
14
15
  /**