@fusion.dev/helix-core 0.56.1 → 0.58.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 (25) hide show
  1. package/custom-elements.json +870 -65
  2. package/dist/controllers/cue-controller/cue-controller.d.ts +85 -0
  3. package/dist/controllers/cue-controller/cue-controller.js +45 -0
  4. package/dist/controllers/fullscreen-controller/fullscreen-controller.d.ts +32 -0
  5. package/dist/controllers/fullscreen-controller/fullscreen-controller.js +28 -0
  6. package/dist/controllers/idle-controller/idle-controller.d.ts +35 -0
  7. package/dist/controllers/idle-controller/idle-controller.js +24 -0
  8. package/dist/controllers/media-session-controller/media-session-controller.d.ts +54 -0
  9. package/dist/controllers/media-session-controller/media-session-controller.js +49 -0
  10. package/dist/controllers/remote-playback-controller/remote-playback-controller.d.ts +34 -0
  11. package/dist/controllers/remote-playback-controller/remote-playback-controller.js +38 -0
  12. package/dist/controllers/token-color-controller/token-color-controller.d.ts +3 -3
  13. package/dist/controllers/token-color-controller/token-color-controller.js +9 -13
  14. package/dist/index.d.ts +8 -1
  15. package/dist/index.js +8 -1
  16. package/dist/utils/css-color/css-color.d.ts +76 -0
  17. package/dist/utils/css-color/css-color.js +168 -0
  18. package/dist/utils/focusable-elements/focusable-elements.d.ts +9 -10
  19. package/dist/utils/media-copy/media-copy.d.ts +27 -0
  20. package/dist/utils/media-copy/media-copy.js +18 -0
  21. package/dist/utils/read-color/read-color.d.ts +36 -0
  22. package/dist/utils/read-color/read-color.js +36 -0
  23. package/package.json +4 -4
  24. package/dist/controllers/focus-trap-controller/focus-trap-controller.d.ts +0 -26
  25. package/dist/controllers/focus-trap-controller/focus-trap-controller.js +0 -55
@@ -0,0 +1,85 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ /** One caption track, named as the menu names it. */
3
+ export interface Caption {
4
+ label: string;
5
+ language: string;
6
+ }
7
+ /** A `<track>` as the consumer wrote it, reduced to what naming it needs. */
8
+ interface TrackCopy {
9
+ attrs: Record<string, string>;
10
+ tag: string;
11
+ }
12
+ /**
13
+ * The tracks a reader could choose between, in the order they were written.
14
+ *
15
+ * Filtered by kind, because `textTracks` also carries `chapters`, `metadata`
16
+ * and `descriptions`, and a menu offering "Chapters" as a subtitle language
17
+ * would be offering something that draws no words. Read off the media element
18
+ * rather than off the slot: a `<track>` added later is in one and not the
19
+ * other, and the element is the one that has resolved them.
20
+ */
21
+ export declare const captionTracks: (media: HTMLMediaElement | null) => TextTrack[];
22
+ /**
23
+ * Shows one track and disables every other, or shows none at `-1`.
24
+ *
25
+ * Every other, rather than only the one that was on. Two tracks in `hidden`
26
+ * both fire `cuechange`, so the rendered cue becomes whichever fired last and
27
+ * a reader who switched language gets the two interleaved. Measured as two
28
+ * subtitles alternating line by line.
29
+ *
30
+ * `hidden`, never `showing`. Hidden is the mode where a track still fires
31
+ * `cuechange` and still fills `activeCues`, and the browser draws nothing,
32
+ * which is exactly what is wanted when something else is doing the drawing.
33
+ */
34
+ export declare const showCaption: (tracks: readonly TextTrack[], index: number) => void;
35
+ /**
36
+ * Whether a track the platform started showing is now ours to draw.
37
+ *
38
+ * A `<track default>` arrives `showing`, which is the browser drawing it.
39
+ * Moved to `hidden` so the cues keep coming and we draw them instead. What
40
+ * comes back is whether captions are on, which is not always "off": a button
41
+ * that began there reported the opposite of what was on screen.
42
+ */
43
+ export declare const adoptTrack: (track: TextTrack) => boolean;
44
+ /**
45
+ * The caption tracks among the copies, before the render that draws them.
46
+ *
47
+ * By the platform's own reading of `kind`, which is what makes a `<track>` a
48
+ * caption at all: missing is `subtitles` and anything it does not know is
49
+ * `metadata`. Read from the video's tracks after the render instead, the count
50
+ * arrived a render late and asked for one more. Choosing one still goes
51
+ * through the video's tracks, which hold them in the same order.
52
+ */
53
+ export declare const captionsAmong: (media: readonly TrackCopy[]) => Caption[];
54
+ /**
55
+ * The cue on screen now, as nodes, or nothing between cues.
56
+ *
57
+ * Nodes rather than a string, because `VTTCue.text` is the cue's *source*:
58
+ * WebVTT carries markup, and a caption file in the wild is full of it. `<v
59
+ * Hildy>` names who is speaking, `<b>` and `<i>` and `<u>` mark emphasis, and
60
+ * `<c.loud>` classes a line. Rendered as text every one of those arrives on
61
+ * screen as visible angle brackets.
62
+ *
63
+ * `getCueAsHTML` is the platform parsing its own format, so nothing here has
64
+ * to know the grammar and no cue text is ever treated as HTML: what comes
65
+ * back is a fragment the browser built, not a string anybody interpolated.
66
+ */
67
+ export declare const activeCue: (track: TextTrack | undefined) => DocumentFragment | undefined;
68
+ /**
69
+ * Listens to one track's cues at a time, so the next one can take it off.
70
+ *
71
+ * Taken off whatever it was on before adding it here. `follow` runs from
72
+ * `loadedmetadata`, which fires again for every source a reader moves to, and
73
+ * a consumer adding a `<track>` later gets another. Added without removing, a
74
+ * playlist of six left six listeners on tracks that between them fire for
75
+ * every cue in the video.
76
+ */
77
+ export declare class CueController implements ReactiveController {
78
+ private readonly listener;
79
+ private track;
80
+ constructor(host: ReactiveControllerHost, listener: () => void);
81
+ follow(track: TextTrack): void;
82
+ hostDisconnected(): void;
83
+ private stop;
84
+ }
85
+ export {};
@@ -0,0 +1,45 @@
1
+ export const captionTracks = (media) => {
2
+ const tracks = media?.textTracks;
3
+ if (!tracks) {
4
+ return [];
5
+ }
6
+ return [...tracks].filter((one) => one.kind === 'subtitles' || one.kind === 'captions');
7
+ };
8
+ export const showCaption = (tracks, index) => {
9
+ tracks.forEach((track, at) => {
10
+ track.mode = at === index ? 'hidden' : 'disabled';
11
+ });
12
+ };
13
+ export const adoptTrack = (track) => {
14
+ if (track.mode === 'showing') {
15
+ track.mode = 'hidden';
16
+ }
17
+ return track.mode === 'hidden';
18
+ };
19
+ export const captionsAmong = (media) => media
20
+ .filter((one) => one.tag === 'track' &&
21
+ (one.attrs.kind === undefined ||
22
+ ['captions', 'subtitles'].includes(one.attrs.kind.toLowerCase())))
23
+ .map((one) => ({ label: one.attrs.label ?? '', language: one.attrs.srclang ?? '' }));
24
+ export const activeCue = (track) => {
25
+ const active = track?.activeCues?.[0];
26
+ return active instanceof VTTCue ? active.getCueAsHTML() : undefined;
27
+ };
28
+ export class CueController {
29
+ constructor(host, listener) {
30
+ this.listener = listener;
31
+ host.addController(this);
32
+ }
33
+ follow(track) {
34
+ this.stop();
35
+ this.track = track;
36
+ track.addEventListener('cuechange', this.listener);
37
+ }
38
+ hostDisconnected() {
39
+ this.stop();
40
+ }
41
+ stop() {
42
+ this.track?.removeEventListener('cuechange', this.listener);
43
+ this.track = undefined;
44
+ }
45
+ }
@@ -0,0 +1,32 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ /**
3
+ * Into fullscreen and back, and honest about where the element is.
4
+ *
5
+ * The state follows `fullscreenchange` rather than the request, so a refused
6
+ * request leaves a button that correctly reports "not fullscreen".
7
+ *
8
+ * The document, rather than the host, is what the event is read from: the
9
+ * platform fires it at the document whichever element went full, so a host that
10
+ * asked and was overruled by another one still hears about it.
11
+ */
12
+ export declare class FullscreenController implements ReactiveController {
13
+ private readonly host;
14
+ private readonly report;
15
+ constructor(host: ReactiveControllerHost & HTMLElement, report: (full: boolean) => void);
16
+ /**
17
+ * Into fullscreen and back, and quiet about being refused.
18
+ *
19
+ * `requestFullscreen` rejects rather than returning false, and the two
20
+ * refusals a consumer will actually meet are a Permissions Policy that does
21
+ * not grant `fullscreen` to the frame the player is in, and a call without
22
+ * user activation. Both are `TypeError: Permissions check failed`, which is
23
+ * the same message for two different causes.
24
+ *
25
+ * Caught rather than `void`ed, or a refusal becomes an unhandled rejection
26
+ * naming neither the element nor the reason.
27
+ */
28
+ toggle(): Promise<void>;
29
+ hostConnected(): void;
30
+ hostDisconnected(): void;
31
+ private readonly handleChange;
32
+ }
@@ -0,0 +1,28 @@
1
+ export class FullscreenController {
2
+ constructor(host, report) {
3
+ this.host = host;
4
+ this.report = report;
5
+ this.handleChange = () => {
6
+ this.report(document.fullscreenElement === this.host);
7
+ };
8
+ host.addController(this);
9
+ }
10
+ async toggle() {
11
+ try {
12
+ if (document.fullscreenElement === this.host) {
13
+ await document.exitFullscreen();
14
+ return;
15
+ }
16
+ await this.host.requestFullscreen();
17
+ }
18
+ catch (error) {
19
+ console.warn(`<${this.host.localName}> could not enter fullscreen. If it is inside an iframe, that frame needs allow="fullscreen".`, error);
20
+ }
21
+ }
22
+ hostConnected() {
23
+ document.addEventListener('fullscreenchange', this.handleChange);
24
+ }
25
+ hostDisconnected() {
26
+ document.removeEventListener('fullscreenchange', this.handleChange);
27
+ }
28
+ }
@@ -0,0 +1,35 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ /**
3
+ * How long the reader has been still, which is what an overlay hides itself on.
4
+ *
5
+ * A wait rather than leaving with the pointer, or a reader moving across a
6
+ * surface to reach a control watches the control vanish under them. The wait is
7
+ * only ever about *idling*: whether anything else keeps the overlay up, a
8
+ * paused player or an open menu, is the host's decision, and this only says
9
+ * when the reader stopped.
10
+ *
11
+ * The delay is asked for on every restart rather than taken once, because it
12
+ * is a property the consumer can change while the video plays.
13
+ */
14
+ export declare class IdleController implements ReactiveController {
15
+ private readonly delay;
16
+ private readonly report;
17
+ private timer;
18
+ constructor(host: ReactiveControllerHost, delay: () => number, report: (idle: boolean) => void);
19
+ /**
20
+ * Restarts the wait, on anything that counts as the reader being here.
21
+ *
22
+ * Pointer movement and focus both count, and both have to: a reader using the
23
+ * keyboard never moves a pointer, and a bar that only answered the mouse
24
+ * would leave them nothing to look at. Zero turns the wait off and the bar
25
+ * goes immediately; a large number keeps it up for as long as anyone is
26
+ * likely to look.
27
+ */
28
+ readonly stir: () => void;
29
+ /**
30
+ * Leaving is the one case that does not wait: the reader has gone somewhere
31
+ * else, and there is nothing to reach for.
32
+ */
33
+ readonly leave: () => void;
34
+ hostDisconnected(): void;
35
+ }
@@ -0,0 +1,24 @@
1
+ export class IdleController {
2
+ constructor(host, delay, report) {
3
+ this.delay = delay;
4
+ this.report = report;
5
+ this.stir = () => {
6
+ this.report(false);
7
+ clearTimeout(this.timer);
8
+ const delay = this.delay();
9
+ if (delay > 0) {
10
+ this.timer = setTimeout(() => {
11
+ this.report(true);
12
+ }, delay);
13
+ }
14
+ };
15
+ this.leave = () => {
16
+ clearTimeout(this.timer);
17
+ this.report(true);
18
+ };
19
+ host.addController(this);
20
+ }
21
+ hostDisconnected() {
22
+ clearTimeout(this.timer);
23
+ }
24
+ }
@@ -0,0 +1,54 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ /**
3
+ * How far a skip control moves, in seconds.
4
+ *
5
+ * Ten, which is the number every player that has one uses, and the number the
6
+ * platform names in `seekbackward` and `seekforward` when it hands over a
7
+ * default. A different length here would be correct only on this player.
8
+ */
9
+ export declare const SKIP_SECONDS = 10;
10
+ /** What a headset's buttons do, handed over by the player that owns them. */
11
+ export interface SessionHandlers {
12
+ /**
13
+ * Asks for the next item, or `null` where the consumer has named nothing to
14
+ * skip to: the platform draws the key from whether a handler is set.
15
+ */
16
+ next: (() => void) | null;
17
+ /** And the previous one, under the same rule. */
18
+ previous: (() => void) | null;
19
+ seek: (seconds: number) => void;
20
+ skipBy: (seconds: number) => void;
21
+ toggle: () => void;
22
+ }
23
+ /**
24
+ * Holds the page's media session for one player, and tells the platform what
25
+ * is playing.
26
+ *
27
+ * The handlers are what make a headset's play, pause and skip work at all.
28
+ * Without them a platform falls back to guessing which element was meant,
29
+ * which is right often enough to hide the fault and wrong exactly when there
30
+ * are two.
31
+ */
32
+ export declare class MediaSessionController implements ReactiveController {
33
+ private readonly host;
34
+ constructor(host: ReactiveControllerHost & HTMLElement);
35
+ /**
36
+ * Takes the session.
37
+ *
38
+ * On play rather than on render, which is the ownership rule: a page with two
39
+ * players gives the session to whichever one the reader started, so a
40
+ * headset's buttons land on what they are listening to rather than on
41
+ * whichever came first in the markup.
42
+ */
43
+ claim(metadata: {
44
+ artwork: string;
45
+ title: string;
46
+ }, handlers: SessionHandlers): void;
47
+ /** Tells the platform whether the owner is playing, and nothing if it is not the owner. */
48
+ report(playing: boolean): void;
49
+ /**
50
+ * The page's one media session goes back to nobody, but only if this
51
+ * element is the one holding it.
52
+ */
53
+ hostDisconnected(): void;
54
+ }
@@ -0,0 +1,49 @@
1
+ let owner;
2
+ export const SKIP_SECONDS = 10;
3
+ export class MediaSessionController {
4
+ constructor(host) {
5
+ this.host = host;
6
+ host.addController(this);
7
+ }
8
+ claim(metadata, handlers) {
9
+ if (typeof navigator === 'undefined' || !navigator.mediaSession) {
10
+ return;
11
+ }
12
+ owner = this.host;
13
+ const session = navigator.mediaSession;
14
+ session.metadata = new MediaMetadata({
15
+ artwork: metadata.artwork === '' ? [] : [{ src: metadata.artwork }],
16
+ title: metadata.title,
17
+ });
18
+ session.setActionHandler('play', handlers.toggle);
19
+ session.setActionHandler('pause', handlers.toggle);
20
+ session.setActionHandler('seekto', (details) => {
21
+ if (details.seekTime !== undefined && details.seekTime !== null) {
22
+ handlers.seek(details.seekTime);
23
+ }
24
+ });
25
+ session.setActionHandler('seekbackward', (details) => {
26
+ handlers.skipBy(-(details.seekOffset ?? SKIP_SECONDS));
27
+ });
28
+ session.setActionHandler('seekforward', (details) => {
29
+ handlers.skipBy(details.seekOffset ?? SKIP_SECONDS);
30
+ });
31
+ session.setActionHandler('nexttrack', handlers.next);
32
+ session.setActionHandler('previoustrack', handlers.previous);
33
+ }
34
+ report(playing) {
35
+ if (owner === this.host && typeof navigator !== 'undefined' && navigator.mediaSession) {
36
+ navigator.mediaSession.playbackState = playing ? 'playing' : 'paused';
37
+ }
38
+ }
39
+ hostDisconnected() {
40
+ if (owner !== this.host) {
41
+ return;
42
+ }
43
+ owner = undefined;
44
+ if (typeof navigator !== 'undefined' && navigator.mediaSession) {
45
+ navigator.mediaSession.metadata = null;
46
+ navigator.mediaSession.playbackState = 'none';
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,34 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ /** What the watch reports, as it learns it. */
3
+ export interface RemoteReport {
4
+ /** Whether anything on the network offers to play this. */
5
+ available: (available: boolean) => void;
6
+ /** Whether it is playing somewhere else right now. */
7
+ connected: (connected: boolean) => void;
8
+ }
9
+ /**
10
+ * Watches for something to play the media on, and stops on the way out.
11
+ *
12
+ * The Remote Playback API is the standard one, and it is what Safari
13
+ * implements for AirPlay and Chrome for Cast: one call covers both, where the
14
+ * older `webkitShowPlaybackTargetPicker` covers only the first. Firefox has
15
+ * neither, and `remote` simply being absent is what turns the control off
16
+ * there.
17
+ *
18
+ * `watchAvailability` returns an id that has to be canceled, or the callback
19
+ * outlives the element that owns it: a callback holding a removed element is a
20
+ * leak with a `@state` write on the end.
21
+ */
22
+ export declare class RemotePlaybackController implements ReactiveController {
23
+ private readonly report;
24
+ private media;
25
+ /** Cancels the availability watch, since it outlives a single render. */
26
+ private watch;
27
+ constructor(host: ReactiveControllerHost, report: RemoteReport);
28
+ /** Starts watching the media element, once there is one. */
29
+ attach(media: HTMLMediaElement): Promise<void>;
30
+ /** Opens the platform's own device picker. It has no list of its own. */
31
+ prompt(): Promise<void>;
32
+ hostDisconnected(): void;
33
+ private readonly handleState;
34
+ }
@@ -0,0 +1,38 @@
1
+ export class RemotePlaybackController {
2
+ constructor(host, report) {
3
+ this.report = report;
4
+ this.handleState = () => {
5
+ this.report.connected(this.media?.remote?.state === 'connected');
6
+ };
7
+ host.addController(this);
8
+ }
9
+ async attach(media) {
10
+ this.media = media;
11
+ media.remote?.addEventListener?.('connect', this.handleState);
12
+ media.remote?.addEventListener?.('disconnect', this.handleState);
13
+ const remote = media.remote;
14
+ if (!remote?.watchAvailability) {
15
+ return;
16
+ }
17
+ try {
18
+ this.watch = await remote.watchAvailability((available) => {
19
+ this.report.available(available);
20
+ });
21
+ }
22
+ catch {
23
+ }
24
+ }
25
+ async prompt() {
26
+ try {
27
+ await this.media?.remote?.prompt();
28
+ }
29
+ catch {
30
+ }
31
+ }
32
+ hostDisconnected() {
33
+ if (this.watch !== undefined) {
34
+ void this.media?.remote?.cancelWatchAvailability?.(this.watch);
35
+ this.watch = undefined;
36
+ }
37
+ }
38
+ }
@@ -7,7 +7,9 @@ export type TokenColor = [number, number, number];
7
7
  *
8
8
  * **Two hops, and both are load-bearing.** A custom property's own value is the
9
9
  * unevaluated function text, so `getComputedStyle` on a probe is the only way
10
- * to see what it resolves to. And what it resolves to is not `rgb()`: the
10
+ * to see what it resolves to. The second hop is `readColor`, which is shared
11
+ * with every other caller that has a resolved color and needs channels: what it
12
+ * resolves to is not `rgb()`, the
11
13
  * palette derives from an `oklch()` seed, and Chrome answers `lab(100 0 0)` for
12
14
  * white. Parsing that with a regular expression for numbers reads the three
13
15
  * channels as 100, 0 and 0, which divided by 255 is a dark red.
@@ -33,8 +35,6 @@ export type TokenColor = [number, number, number];
33
35
  export declare class TokenColorController implements ReactiveController {
34
36
  private readonly host;
35
37
  private readonly onChange?;
36
- /** Where a resolved color is converted, one pixel at a time. */
37
- private context;
38
38
  /** Watches the document for `data-color-mode`. */
39
39
  private modes;
40
40
  /** The hidden element a token is resolved against. */
@@ -1,8 +1,8 @@
1
+ import { readColor } from '../../utils/read-color/read-color.js';
1
2
  export class TokenColorController {
2
3
  constructor(host, onChange) {
3
4
  this.host = host;
4
5
  this.onChange = onChange;
5
- this.context = null;
6
6
  this.announce = () => {
7
7
  this.host.requestUpdate();
8
8
  this.onChange?.();
@@ -16,10 +16,6 @@ export class TokenColorController {
16
16
  this.probe = document.createElement('span');
17
17
  this.probe.style.display = 'none';
18
18
  document.body.appendChild(this.probe);
19
- const swatch = document.createElement('canvas');
20
- swatch.width = 1;
21
- swatch.height = 1;
22
- this.context = swatch.getContext('2d', { willReadFrequently: true });
23
19
  this.modes = new MutationObserver(() => this.announce());
24
20
  this.modes.observe(document.documentElement, {
25
21
  attributeFilter: ['data-color-mode'],
@@ -35,18 +31,18 @@ export class TokenColorController {
35
31
  this.scheme = undefined;
36
32
  this.probe?.remove();
37
33
  this.probe = undefined;
38
- this.context = null;
39
34
  }
40
35
  read(token, fallback) {
41
- const { context, probe } = this;
42
- if (!context || !probe) {
36
+ const { probe } = this;
37
+ if (!probe) {
43
38
  return [...fallback];
44
39
  }
45
40
  probe.style.color = `var(${token})`;
46
- context.fillStyle = '#000000';
47
- context.fillStyle = getComputedStyle(probe).color;
48
- context.fillRect(0, 0, 1, 1);
49
- const [r, g, b] = context.getImageData(0, 0, 1, 1).data;
50
- return [(r ?? 0) / 255, (g ?? 0) / 255, (b ?? 0) / 255];
41
+ const read = readColor(getComputedStyle(probe).color);
42
+ if (!read) {
43
+ return [...fallback];
44
+ }
45
+ const [r, g, b] = read;
46
+ return [r / 255, g / 255, b / 255];
51
47
  }
52
48
  }
package/dist/index.d.ts CHANGED
@@ -2,14 +2,18 @@ export { AriaForwardController, type AriaForwardOptions, } from './controllers/a
2
2
  export { ChartPointerController, type ChartPointerHost, } from './controllers/chart-pointer-controller/chart-pointer-controller.js';
3
3
  export { CommandController, type CommandOptions, HelixFallbackCommandEvent, supportsInvokers, } from './controllers/command-controller/command-controller.js';
4
4
  export { CopyController, type CopyOptions, } from './controllers/copy-controller/copy-controller.js';
5
+ export { activeCue, adoptTrack, type Caption, CueController, captionsAmong, captionTracks, showCaption, } from './controllers/cue-controller/cue-controller.js';
5
6
  export { DisabledCascadeController, type DisabledCascadeOptions, } from './controllers/disabled-cascade-controller/disabled-cascade-controller.js';
6
7
  export { DismissController, type DismissOptions, } from './controllers/dismiss-controller/dismiss-controller.js';
7
8
  export { DrilldownController, type DrilldownOptions, } from './controllers/drilldown-controller/drilldown-controller.js';
8
9
  export { FloatingController, type FloatingOptions, type Placement, type Side, } from './controllers/floating-controller/floating-controller.js';
9
- export { FocusTrapController } from './controllers/focus-trap-controller/focus-trap-controller.js';
10
+ export { FullscreenController } from './controllers/fullscreen-controller/fullscreen-controller.js';
11
+ export { IdleController } from './controllers/idle-controller/idle-controller.js';
10
12
  export { IndicatorController, type IndicatorOptions, } from './controllers/indicator-controller/indicator-controller.js';
11
13
  export { KeyboardNavigationController, type KeyboardNavigationOptions, type NavigableItem, } from './controllers/keyboard-navigation-controller/keyboard-navigation-controller.js';
14
+ export { MediaSessionController, type SessionHandlers, SKIP_SECONDS, } from './controllers/media-session-controller/media-session-controller.js';
12
15
  export { PopoverController, type PopoverOptions, } from './controllers/popover-controller/popover-controller.js';
16
+ export { RemotePlaybackController, type RemoteReport, } from './controllers/remote-playback-controller/remote-playback-controller.js';
13
17
  export { SourceController, type SourceHost, } from './controllers/source-controller/source-controller.js';
14
18
  export { type TokenColor, TokenColorController, } from './controllers/token-color-controller/token-color-controller.js';
15
19
  export { type Rendered, VirtualizeController, type VirtualizeOptions, type Window as VirtualizeWindow, } from './controllers/virtualize-controller/virtualize-controller.js';
@@ -33,6 +37,7 @@ export { anchorElement } from './utils/anchor-element/anchor-element.js';
33
37
  export { capturePointer } from './utils/capture-pointer/capture-pointer.js';
34
38
  export { closestInstance } from './utils/closest-instance/closest-instance.js';
35
39
  export { createSheet } from './utils/create-sheet/create-sheet.js';
40
+ export { type Color, type ColorFormat, formatColor, parseColor, splitColors, toCss, } from './utils/css-color/css-color.js';
36
41
  export { customElement, definedTags, duplicateRegistrations, } from './utils/custom-element/custom-element.js';
37
42
  export { type DrawIconOptions, drawIcon } from './utils/draw-icon/draw-icon.js';
38
43
  export { CHART_PLOT, drawTooltip } from './utils/draw-tooltip/draw-tooltip.js';
@@ -44,7 +49,9 @@ export { type DisableableItem, forgetDisabled, inheritDisabled, } from './utils/
44
49
  export { inlineStep } from './utils/inline-step/inline-step.js';
45
50
  export { type Curve, linePath, type Point } from './utils/line-path/line-path.js';
46
51
  export { markRegion, type RegionOptions } from './utils/mark-region/mark-region.js';
52
+ export { copiesOf, type MediaCopy, sameCopies, } from './utils/media-copy/media-copy.js';
47
53
  export { onUnlessFalse } from './utils/on-unless-false/on-unless-false.js';
54
+ export { type ColorBytes, readColor } from './utils/read-color/read-color.js';
48
55
  export { type Row, type RowCell, readRows, readSeries, type Series, type SeriesPoint, } from './utils/read-series/read-series.js';
49
56
  export { type Relative, relativeTime, } from './utils/relative-time/relative-time.js';
50
57
  export { type ChangeRequestOptions, requestChange, } from './utils/request-change/request-change.js';
package/dist/index.js CHANGED
@@ -2,14 +2,18 @@ export { AriaForwardController, } from './controllers/aria-forward-controller/ar
2
2
  export { ChartPointerController, } from './controllers/chart-pointer-controller/chart-pointer-controller.js';
3
3
  export { CommandController, HelixFallbackCommandEvent, supportsInvokers, } from './controllers/command-controller/command-controller.js';
4
4
  export { CopyController, } from './controllers/copy-controller/copy-controller.js';
5
+ export { activeCue, adoptTrack, CueController, captionsAmong, captionTracks, showCaption, } from './controllers/cue-controller/cue-controller.js';
5
6
  export { DisabledCascadeController, } from './controllers/disabled-cascade-controller/disabled-cascade-controller.js';
6
7
  export { DismissController, } from './controllers/dismiss-controller/dismiss-controller.js';
7
8
  export { DrilldownController, } from './controllers/drilldown-controller/drilldown-controller.js';
8
9
  export { FloatingController, } from './controllers/floating-controller/floating-controller.js';
9
- export { FocusTrapController } from './controllers/focus-trap-controller/focus-trap-controller.js';
10
+ export { FullscreenController } from './controllers/fullscreen-controller/fullscreen-controller.js';
11
+ export { IdleController } from './controllers/idle-controller/idle-controller.js';
10
12
  export { IndicatorController, } from './controllers/indicator-controller/indicator-controller.js';
11
13
  export { KeyboardNavigationController, } from './controllers/keyboard-navigation-controller/keyboard-navigation-controller.js';
14
+ export { MediaSessionController, SKIP_SECONDS, } from './controllers/media-session-controller/media-session-controller.js';
12
15
  export { PopoverController, } from './controllers/popover-controller/popover-controller.js';
16
+ export { RemotePlaybackController, } from './controllers/remote-playback-controller/remote-playback-controller.js';
13
17
  export { SourceController, } from './controllers/source-controller/source-controller.js';
14
18
  export { TokenColorController, } from './controllers/token-color-controller/token-color-controller.js';
15
19
  export { VirtualizeController, } from './controllers/virtualize-controller/virtualize-controller.js';
@@ -33,6 +37,7 @@ export { anchorElement } from './utils/anchor-element/anchor-element.js';
33
37
  export { capturePointer } from './utils/capture-pointer/capture-pointer.js';
34
38
  export { closestInstance } from './utils/closest-instance/closest-instance.js';
35
39
  export { createSheet } from './utils/create-sheet/create-sheet.js';
40
+ export { formatColor, parseColor, splitColors, toCss, } from './utils/css-color/css-color.js';
36
41
  export { customElement, definedTags, duplicateRegistrations, } from './utils/custom-element/custom-element.js';
37
42
  export { drawIcon } from './utils/draw-icon/draw-icon.js';
38
43
  export { CHART_PLOT, drawTooltip } from './utils/draw-tooltip/draw-tooltip.js';
@@ -44,7 +49,9 @@ export { forgetDisabled, inheritDisabled, } from './utils/inherit-disabled/inher
44
49
  export { inlineStep } from './utils/inline-step/inline-step.js';
45
50
  export { linePath } from './utils/line-path/line-path.js';
46
51
  export { markRegion } from './utils/mark-region/mark-region.js';
52
+ export { copiesOf, sameCopies, } from './utils/media-copy/media-copy.js';
47
53
  export { onUnlessFalse } from './utils/on-unless-false/on-unless-false.js';
54
+ export { readColor } from './utils/read-color/read-color.js';
48
55
  export { readRows, readSeries, } from './utils/read-series/read-series.js';
49
56
  export { relativeTime, } from './utils/relative-time/relative-time.js';
50
57
  export { requestChange, } from './utils/request-change/request-change.js';
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Reading and writing the color syntaxes a field is allowed to hold.
3
+ *
4
+ * **The working model is HSB**, hue and saturation and brightness, because that
5
+ * is what the panel is: a square of saturation against brightness with a hue
6
+ * ramp beside it. HSL would put the square's corners somewhere a reader does not
7
+ * expect, since full saturation in HSL is a mid-lightness band rather than a
8
+ * corner.
9
+ *
10
+ * **Every syntax here is read; three are written.** A field that refuses
11
+ * `rgb(255 0 0)` because it prefers hex is a field arguing with the person using
12
+ * it, so the space-separated forms, the comma forms, `rgba` and `hsla` are all
13
+ * taken. What is written back is whichever of hex, rgb or hsl the component is
14
+ * set to, so a value stays in the shape the consumer asked for however it was
15
+ * entered.
16
+ *
17
+ * **What is written is the comma form.** Both are valid, and the commas are what
18
+ * a reader recognizes, what most code around them holds, and what pastes into
19
+ * anything without a second thought. A syntax a value is handed *out* in is not
20
+ * the place to be early. `rgba` and `hsla` come with them, since a comma form
21
+ * with a fourth argument is spelled that way.
22
+ *
23
+ * **Core's rather than a field's, because the CSS color grammar is the
24
+ * platform's.** Nothing here needs a color picker to make sense, and two
25
+ * families read it already. It is arithmetic with no DOM in it, so it runs on a
26
+ * server, which is also why named colors are refused below: resolving
27
+ * `rebeccapurple` means asking a browser that is not there.
28
+ *
29
+ * **One decision in here is a default rather than a capability.** What
30
+ * `formatColor` writes back, the comma form and an alpha only where one was
31
+ * asked for, is a choice about what a value looks like when it is handed out.
32
+ * It is core's choice now instead of the picker's, and a caller wanting another
33
+ * shape formats it itself.
34
+ */
35
+ /** A color as the panel holds it. Alpha and the two percentages are 0 to 100. */
36
+ export interface Color {
37
+ alpha: number;
38
+ brightness: number;
39
+ hue: number;
40
+ saturation: number;
41
+ }
42
+ /** The syntaxes a value can be written in. */
43
+ export type ColorFormat = 'hex' | 'hsl' | 'rgb';
44
+ /**
45
+ * Splits a comma-separated list of colors, ignoring the commas inside them.
46
+ *
47
+ * A plain `split(',')` cannot hold the legacy syntaxes:
48
+ * `#ef4444, rgb(249 115 22), rgba(34,197,94,.5)` comes apart into six fragments,
49
+ * four of them not colors, and the row quietly renders three swatches instead of
50
+ * four. The modern space-separated forms survive it, which is what makes such a
51
+ * list look like it works.
52
+ *
53
+ * Depth rather than a regular expression, because the thing that matters is
54
+ * whether a comma is inside a function call and that is exactly what a counter
55
+ * answers. No color syntax nests, so a single counter is enough.
56
+ */
57
+ export declare const splitColors: (value: string) => string[];
58
+ /**
59
+ * Any of the syntaxes to the working model, or `null` where it is not a color.
60
+ *
61
+ * Named colors are not here. Resolving `rebeccapurple` means either shipping
62
+ * the table of a hundred and forty or asking the browser, and the browser is not
63
+ * present on the server: a field that accepted a name client-side and rejected
64
+ * it during a server render would be worse than one that never took it.
65
+ */
66
+ export declare const parseColor: (value: string) => Color | null;
67
+ /**
68
+ * The working model back out, in the syntax asked for.
69
+ *
70
+ * The alpha is written only where the component carries one. A consumer who did
71
+ * not ask for transparency should not have to strip `ff` off every hex or read
72
+ * past a fourth argument they never wanted.
73
+ */
74
+ export declare const formatColor: (color: Color, format: ColorFormat, withAlpha: boolean) => string;
75
+ /** The color as CSS, for the swatches and the tracks. Always with its alpha. */
76
+ export declare const toCss: (color: Color) => string;