@fusion.dev/helix-headless 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.
- package/custom-elements.json +0 -716
- package/dist/color-input/root/color-input-root.d.ts +1 -2
- package/dist/color-input/root/color-input-root.js +1 -2
- package/dist/color-picker/index.d.ts +0 -1
- package/dist/color-picker/index.js +0 -1
- package/dist/color-picker/root/color-picker-root.d.ts +1 -2
- package/dist/color-picker/root/color-picker-root.js +1 -2
- package/dist/video/root/keys.js +1 -1
- package/dist/video/root/video-root.js +1 -7
- package/package.json +5 -5
- package/dist/color-picker/color.d.ts +0 -73
- package/dist/color-picker/color.js +0 -168
- package/dist/video/root/captions.d.ts +0 -85
- package/dist/video/root/captions.js +0 -45
- package/dist/video/root/fullscreen.d.ts +0 -28
- package/dist/video/root/fullscreen.js +0 -28
- package/dist/video/root/idle.d.ts +0 -34
- package/dist/video/root/idle.js +0 -24
- package/dist/video/root/media-copy.d.ts +0 -25
- package/dist/video/root/media-copy.js +0 -18
- package/dist/video/root/media-session.d.ts +0 -54
- package/dist/video/root/media-session.js +0 -49
- package/dist/video/root/remote-playback.d.ts +0 -34
- package/dist/video/root/remote-playback.js +0 -38
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { AriaForwardController, HelixFormElement } from '@fusion.dev/helix-core';
|
|
1
|
+
import { AriaForwardController, type ColorFormat, HelixFormElement } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
import '../../color-picker/index.js';
|
|
4
|
-
import { type ColorFormat } from '../../color-picker/color.js';
|
|
5
4
|
declare const HelixHeadlessColorInput_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinPickerFieldInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinFieldInterface) & typeof HelixFormElement;
|
|
6
5
|
/**
|
|
7
6
|
* @summary A color, typed in any syntax or picked from a panel.
|
|
@@ -4,12 +4,11 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { AriaForwardController, customElement, HelixFormElement, MixinControllerOpen, MixinControllerValue, MixinField, MixinPickerField, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, customElement, HelixFormElement, MixinControllerOpen, MixinControllerValue, MixinField, MixinPickerField, parseColor, toCss, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { html } from 'lit/static-html.js';
|
|
11
11
|
import '../../color-picker/index.js';
|
|
12
|
-
import { parseColor, toCss } from '../../color-picker/color.js';
|
|
13
12
|
let HelixHeadlessColorInput = class HelixHeadlessColorInput extends MixinPickerField(MixinControllerOpen(MixinControllerValue(MixinField(HelixFormElement)))) {
|
|
14
13
|
constructor() {
|
|
15
14
|
super(...arguments);
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { HelixFormElement } from '@fusion.dev/helix-core';
|
|
1
|
+
import { type Color, type ColorFormat, HelixFormElement } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
import '../../button/index.js';
|
|
4
4
|
import '../../button-group/index.js';
|
|
5
5
|
import '../../slider/index.js';
|
|
6
|
-
import { type Color, type ColorFormat } from '../color.js';
|
|
7
6
|
declare const HelixHeadlessColorPicker_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & typeof HelixFormElement;
|
|
8
7
|
/**
|
|
9
8
|
* @summary Picks a color, from a square and two ramps.
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { CopyController, capturePointer, customElement, drawIcon, HelixFormElement, HelixSampleEndEvent, HelixSampleStartEvent, MixinControllerValue, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { CopyController, capturePointer, customElement, drawIcon, formatColor, HelixFormElement, HelixSampleEndEvent, HelixSampleStartEvent, MixinControllerValue, parseColor, splitColors, toCss, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { eyedropper } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
@@ -12,7 +12,6 @@ import { html } from 'lit/static-html.js';
|
|
|
12
12
|
import '../../button/index.js';
|
|
13
13
|
import '../../button-group/index.js';
|
|
14
14
|
import '../../slider/index.js';
|
|
15
|
-
import { formatColor, parseColor, splitColors, toCss, } from '../color.js';
|
|
16
15
|
const FORMATS = ['hex', 'rgb', 'hsl'];
|
|
17
16
|
const START = { alpha: 100, brightness: 100, hue: 0, saturation: 100 };
|
|
18
17
|
let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinControllerValue(HelixFormElement) {
|
package/dist/video/root/keys.js
CHANGED
|
@@ -4,20 +4,14 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { customElement, drawIcon, HelixElement, HelixNextEvent, HelixPreviousEvent, onUnlessFalse, writtenList, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { activeCue, adoptTrack, CueController, captionsAmong, captionTracks, copiesOf, customElement, drawIcon, FullscreenController, HelixElement, HelixNextEvent, HelixPreviousEvent, IdleController, MediaSessionController, onUnlessFalse, RemotePlaybackController, SKIP_SECONDS, sameCopies, showCaption, writtenList, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { airplay, arrowClockwise, arrowCounterClockwise, closedCaptioning, cornersIn, cornersOut, dotsThreeVertical, download, gauge, pause, pictureInPicture, play, skipBack, skipForward, speakerHigh, speakerX, } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
11
11
|
import { html } from 'lit/static-html.js';
|
|
12
12
|
import '../../menu/index.js';
|
|
13
|
-
import { activeCue, adoptTrack, CueController, captionsAmong, captionTracks, showCaption, } from './captions.js';
|
|
14
13
|
import { barControls, menuControls, ownFile } from './extra-controls.js';
|
|
15
|
-
import { FullscreenController } from './fullscreen.js';
|
|
16
|
-
import { IdleController } from './idle.js';
|
|
17
14
|
import { takeKey } from './keys.js';
|
|
18
|
-
import { copiesOf, sameCopies } from './media-copy.js';
|
|
19
|
-
import { MediaSessionController, SKIP_SECONDS } from './media-session.js';
|
|
20
|
-
import { RemotePlaybackController } from './remote-playback.js';
|
|
21
15
|
const clock = (seconds) => {
|
|
22
16
|
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
23
17
|
return '0:00';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusion.dev/helix-headless",
|
|
3
3
|
"description": "Headless component tier. Structure, accessibility, state, and form participation. No styling.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.58.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ross Sheppard <ross.sheppard@fusion.dev>",
|
|
7
7
|
"repository": {
|
|
@@ -46,14 +46,14 @@
|
|
|
46
46
|
},
|
|
47
47
|
"bugs": "https://github.com/project-fusion/fusion/issues",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@fusion.dev/helix-core": "0.
|
|
50
|
-
"@fusion.dev/helix-icons": "0.
|
|
49
|
+
"@fusion.dev/helix-core": "0.58.0",
|
|
50
|
+
"@fusion.dev/helix-icons": "0.58.0",
|
|
51
51
|
"uqr": "^0.1.3"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@custom-elements-manifest/analyzer": "^0.11.0",
|
|
55
|
-
"@fusion.dev/helix-cem": "0.
|
|
56
|
-
"@fusion.dev/helix-testing": "0.
|
|
55
|
+
"@fusion.dev/helix-cem": "0.58.0",
|
|
56
|
+
"@fusion.dev/helix-testing": "0.58.0",
|
|
57
57
|
"@fusion.dev/scripts": "0.0.0-dev",
|
|
58
58
|
"@happy-dom/global-registrator": "^20.0.0",
|
|
59
59
|
"@lit-labs/ssr": "^4.1.0",
|
|
@@ -1,73 +0,0 @@
|
|
|
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
|
-
* Its own module rather than sitting in the element, because it is arithmetic
|
|
24
|
-
* with no DOM in it: this is the half worth testing directly.
|
|
25
|
-
*/
|
|
26
|
-
/** A color as the panel holds it. Alpha and the two percentages are 0 to 100. */
|
|
27
|
-
export interface Color {
|
|
28
|
-
alpha: number;
|
|
29
|
-
brightness: number;
|
|
30
|
-
hue: number;
|
|
31
|
-
saturation: number;
|
|
32
|
-
}
|
|
33
|
-
/** The syntaxes a value can be written in. */
|
|
34
|
-
export type ColorFormat = 'hex' | 'hsl' | 'rgb';
|
|
35
|
-
/** HSB to bytes, which is the road every written syntax goes down. */
|
|
36
|
-
export declare const toRgb: (color: Color) => {
|
|
37
|
-
blue: number;
|
|
38
|
-
green: number;
|
|
39
|
-
red: number;
|
|
40
|
-
};
|
|
41
|
-
/**
|
|
42
|
-
* Splits a comma-separated list of colors, ignoring the commas inside them.
|
|
43
|
-
*
|
|
44
|
-
* A plain `split(',')` cannot hold the legacy syntaxes:
|
|
45
|
-
* `#ef4444, rgb(249 115 22), rgba(34,197,94,.5)` comes apart into six fragments,
|
|
46
|
-
* four of them not colors, and the row quietly renders three swatches instead of
|
|
47
|
-
* four. The modern space-separated forms survive it, which is what makes such a
|
|
48
|
-
* list look like it works.
|
|
49
|
-
*
|
|
50
|
-
* Depth rather than a regular expression, because the thing that matters is
|
|
51
|
-
* whether a comma is inside a function call and that is exactly what a counter
|
|
52
|
-
* answers. No color syntax nests, so a single counter is enough.
|
|
53
|
-
*/
|
|
54
|
-
export declare const splitColors: (value: string) => string[];
|
|
55
|
-
/**
|
|
56
|
-
* Any of the syntaxes to the working model, or `null` where it is not a color.
|
|
57
|
-
*
|
|
58
|
-
* Named colors are not here. Resolving `rebeccapurple` means either shipping
|
|
59
|
-
* the table of a hundred and forty or asking the browser, and the browser is not
|
|
60
|
-
* present on the server: a field that accepted a name client-side and rejected
|
|
61
|
-
* it during a server render would be worse than one that never took it.
|
|
62
|
-
*/
|
|
63
|
-
export declare const parseColor: (value: string) => Color | null;
|
|
64
|
-
/**
|
|
65
|
-
* The working model back out, in the syntax asked for.
|
|
66
|
-
*
|
|
67
|
-
* The alpha is written only where the component carries one. A consumer who did
|
|
68
|
-
* not ask for transparency should not have to strip `ff` off every hex or read
|
|
69
|
-
* past a fourth argument they never wanted.
|
|
70
|
-
*/
|
|
71
|
-
export declare const formatColor: (color: Color, format: ColorFormat, withAlpha: boolean) => string;
|
|
72
|
-
/** The color as CSS, for the swatches and the tracks. Always with its alpha. */
|
|
73
|
-
export declare const toCss: (color: Color) => string;
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
const clamp = (value, low, high) => Math.min(high, Math.max(low, value));
|
|
2
|
-
const toAlpha = (token) => {
|
|
3
|
-
if (token === undefined) {
|
|
4
|
-
return 100;
|
|
5
|
-
}
|
|
6
|
-
const number = Number.parseFloat(token);
|
|
7
|
-
if (Number.isNaN(number)) {
|
|
8
|
-
return 100;
|
|
9
|
-
}
|
|
10
|
-
return clamp(token.includes('%') ? number : number * 100, 0, 100);
|
|
11
|
-
};
|
|
12
|
-
const toByte = (token) => {
|
|
13
|
-
const number = Number.parseFloat(token);
|
|
14
|
-
return clamp(token.includes('%') ? (number / 100) * 255 : number, 0, 255);
|
|
15
|
-
};
|
|
16
|
-
const rgbToHsb = (red, green, blue, alpha) => {
|
|
17
|
-
const r = red / 255;
|
|
18
|
-
const g = green / 255;
|
|
19
|
-
const b = blue / 255;
|
|
20
|
-
const high = Math.max(r, g, b);
|
|
21
|
-
const low = Math.min(r, g, b);
|
|
22
|
-
const span = high - low;
|
|
23
|
-
let hue = 0;
|
|
24
|
-
if (span !== 0) {
|
|
25
|
-
if (high === r) {
|
|
26
|
-
hue = (g - b) / span + (g < b ? 6 : 0);
|
|
27
|
-
}
|
|
28
|
-
else if (high === g) {
|
|
29
|
-
hue = (b - r) / span + 2;
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
hue = (r - g) / span + 4;
|
|
33
|
-
}
|
|
34
|
-
hue *= 60;
|
|
35
|
-
}
|
|
36
|
-
return {
|
|
37
|
-
alpha,
|
|
38
|
-
brightness: Math.round(high * 100),
|
|
39
|
-
hue: Math.round(hue),
|
|
40
|
-
saturation: Math.round(high === 0 ? 0 : (span / high) * 100),
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
|
-
export const toRgb = (color) => {
|
|
44
|
-
const hue = (((color.hue % 360) + 360) % 360) / 60;
|
|
45
|
-
const saturation = color.saturation / 100;
|
|
46
|
-
const brightness = color.brightness / 100;
|
|
47
|
-
const chroma = brightness * saturation;
|
|
48
|
-
const middle = chroma * (1 - Math.abs((hue % 2) - 1));
|
|
49
|
-
const base = brightness - chroma;
|
|
50
|
-
const sextants = [
|
|
51
|
-
[chroma, middle, 0],
|
|
52
|
-
[middle, chroma, 0],
|
|
53
|
-
[0, chroma, middle],
|
|
54
|
-
[0, middle, chroma],
|
|
55
|
-
[middle, 0, chroma],
|
|
56
|
-
[chroma, 0, middle],
|
|
57
|
-
];
|
|
58
|
-
const [red, green, blue] = sextants[Math.floor(hue) % 6] ?? [0, 0, 0];
|
|
59
|
-
return {
|
|
60
|
-
blue: Math.round((blue + base) * 255),
|
|
61
|
-
green: Math.round((green + base) * 255),
|
|
62
|
-
red: Math.round((red + base) * 255),
|
|
63
|
-
};
|
|
64
|
-
};
|
|
65
|
-
const toHsl = (color) => {
|
|
66
|
-
const saturation = color.saturation / 100;
|
|
67
|
-
const brightness = color.brightness / 100;
|
|
68
|
-
const lightness = (brightness * (2 - saturation)) / 2;
|
|
69
|
-
return {
|
|
70
|
-
hue: ((color.hue % 360) + 360) % 360,
|
|
71
|
-
lightness: Math.round(lightness * 100),
|
|
72
|
-
saturation: Math.round((lightness === 0 || lightness === 1
|
|
73
|
-
? 0
|
|
74
|
-
: (brightness - lightness) / Math.min(lightness, 1 - lightness)) * 100),
|
|
75
|
-
};
|
|
76
|
-
};
|
|
77
|
-
export const splitColors = (value) => {
|
|
78
|
-
const found = [];
|
|
79
|
-
let depth = 0;
|
|
80
|
-
let current = '';
|
|
81
|
-
for (const character of value) {
|
|
82
|
-
if (character === '(') {
|
|
83
|
-
depth += 1;
|
|
84
|
-
}
|
|
85
|
-
else if (character === ')') {
|
|
86
|
-
depth = Math.max(0, depth - 1);
|
|
87
|
-
}
|
|
88
|
-
if (character === ',' && depth === 0) {
|
|
89
|
-
found.push(current.trim());
|
|
90
|
-
current = '';
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
current += character;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
found.push(current.trim());
|
|
97
|
-
return found.filter(Boolean);
|
|
98
|
-
};
|
|
99
|
-
export const parseColor = (value) => {
|
|
100
|
-
const text = value.trim().toLowerCase();
|
|
101
|
-
if (text === '') {
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
if (text.startsWith('#')) {
|
|
105
|
-
const digits = text.slice(1);
|
|
106
|
-
const short = digits.length === 3 || digits.length === 4;
|
|
107
|
-
const long = digits.length === 6 || digits.length === 8;
|
|
108
|
-
if ((!short && !long) || !/^[\da-f]+$/.test(digits)) {
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
const width = short ? 1 : 2;
|
|
112
|
-
const channel = (index) => {
|
|
113
|
-
const piece = digits.slice(index * width, index * width + width);
|
|
114
|
-
return Number.parseInt(short ? piece + piece : piece, 16);
|
|
115
|
-
};
|
|
116
|
-
const carriesAlpha = digits.length === 4 || digits.length === 8;
|
|
117
|
-
return rgbToHsb(channel(0), channel(1), channel(2), carriesAlpha ? Math.round((channel(3) / 255) * 100) : 100);
|
|
118
|
-
}
|
|
119
|
-
const call = /^(rgba?|hsla?)\(([^)]*)\)$/.exec(text);
|
|
120
|
-
if (!call?.[1] || call[2] === undefined) {
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
const [first = '', second = '', third = '', fourth] = call[2]
|
|
124
|
-
.replace(/[,/]/g, ' ')
|
|
125
|
-
.split(/\s+/)
|
|
126
|
-
.filter(Boolean);
|
|
127
|
-
if (first === '' || second === '' || third === '') {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
const alpha = toAlpha(fourth);
|
|
131
|
-
if (call[1].startsWith('rgb')) {
|
|
132
|
-
return rgbToHsb(toByte(first), toByte(second), toByte(third), alpha);
|
|
133
|
-
}
|
|
134
|
-
const hue = Number.parseFloat(first);
|
|
135
|
-
const saturation = clamp(Number.parseFloat(second), 0, 100) / 100;
|
|
136
|
-
const lightness = clamp(Number.parseFloat(third), 0, 100) / 100;
|
|
137
|
-
if (Number.isNaN(hue) || Number.isNaN(saturation) || Number.isNaN(lightness)) {
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
const brightness = lightness + saturation * Math.min(lightness, 1 - lightness);
|
|
141
|
-
return {
|
|
142
|
-
alpha,
|
|
143
|
-
brightness: Math.round(brightness * 100),
|
|
144
|
-
hue: Math.round(((hue % 360) + 360) % 360),
|
|
145
|
-
saturation: Math.round((brightness === 0 ? 0 : 2 * (1 - lightness / brightness)) * 100),
|
|
146
|
-
};
|
|
147
|
-
};
|
|
148
|
-
export const formatColor = (color, format, withAlpha) => {
|
|
149
|
-
const alpha = clamp(color.alpha, 0, 100) / 100;
|
|
150
|
-
const opacity = Number(alpha.toFixed(2));
|
|
151
|
-
if (format === 'hsl') {
|
|
152
|
-
const { hue, lightness, saturation } = toHsl(color);
|
|
153
|
-
const body = `${Math.round(hue)}, ${saturation}%, ${lightness}%`;
|
|
154
|
-
return withAlpha ? `hsla(${body}, ${opacity})` : `hsl(${body})`;
|
|
155
|
-
}
|
|
156
|
-
const { blue, green, red } = toRgb(color);
|
|
157
|
-
if (format === 'rgb') {
|
|
158
|
-
const body = `${red}, ${green}, ${blue}`;
|
|
159
|
-
return withAlpha ? `rgba(${body}, ${opacity})` : `rgb(${body})`;
|
|
160
|
-
}
|
|
161
|
-
const byte = (one) => one.toString(16).padStart(2, '0');
|
|
162
|
-
const hex = `#${byte(red)}${byte(green)}${byte(blue)}`;
|
|
163
|
-
return withAlpha ? hex + byte(Math.round(alpha * 255)) : hex;
|
|
164
|
-
};
|
|
165
|
-
export const toCss = (color) => {
|
|
166
|
-
const { blue, green, red } = toRgb(color);
|
|
167
|
-
return `rgb(${red} ${green} ${blue} / ${clamp(color.alpha, 0, 100)}%)`;
|
|
168
|
-
};
|
|
@@ -1,85 +0,0 @@
|
|
|
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 video rather
|
|
18
|
-
* than off the slot: a `<track>` added later is in one and not the other, and
|
|
19
|
-
* the video is the one that has resolved them.
|
|
20
|
-
*/
|
|
21
|
-
export declare const captionTracks: (video: HTMLVideoElement | 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 {};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
export const captionTracks = (video) => {
|
|
2
|
-
const tracks = video?.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
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
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
|
-
export declare class FullscreenController implements ReactiveController {
|
|
9
|
-
private readonly host;
|
|
10
|
-
private readonly report;
|
|
11
|
-
constructor(host: ReactiveControllerHost & HTMLElement, report: (full: boolean) => void);
|
|
12
|
-
/**
|
|
13
|
-
* Into fullscreen and back, and quiet about being refused.
|
|
14
|
-
*
|
|
15
|
-
* `requestFullscreen` rejects rather than returning false, and the two
|
|
16
|
-
* refusals a consumer will actually meet are a Permissions Policy that does
|
|
17
|
-
* not grant `fullscreen` to the frame the player is in, and a call without
|
|
18
|
-
* user activation. Both are `TypeError: Permissions check failed`, which is
|
|
19
|
-
* the same message for two different causes.
|
|
20
|
-
*
|
|
21
|
-
* Caught rather than `void`ed, or a refusal becomes an unhandled rejection
|
|
22
|
-
* naming neither the element nor the reason.
|
|
23
|
-
*/
|
|
24
|
-
toggle(): Promise<void>;
|
|
25
|
-
hostConnected(): void;
|
|
26
|
-
hostDisconnected(): void;
|
|
27
|
-
private readonly handleChange;
|
|
28
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
|
2
|
-
/**
|
|
3
|
-
* How long the reader has been still, which decides when the bar goes.
|
|
4
|
-
*
|
|
5
|
-
* A wait rather than leaving with the pointer, or a reader moving across the
|
|
6
|
-
* picture to reach a control watches the bar vanish under them. The wait is
|
|
7
|
-
* only ever about *idling*: whether a paused video keeps its bar is the
|
|
8
|
-
* element's decision, and this only says when the reader stopped.
|
|
9
|
-
*
|
|
10
|
-
* The delay is asked for on every restart rather than taken once, because it
|
|
11
|
-
* is a property the consumer can change while the video plays.
|
|
12
|
-
*/
|
|
13
|
-
export declare class IdleController implements ReactiveController {
|
|
14
|
-
private readonly delay;
|
|
15
|
-
private readonly report;
|
|
16
|
-
private timer;
|
|
17
|
-
constructor(host: ReactiveControllerHost, delay: () => number, report: (idle: boolean) => void);
|
|
18
|
-
/**
|
|
19
|
-
* Restarts the wait, on anything that counts as the reader being here.
|
|
20
|
-
*
|
|
21
|
-
* Pointer movement and focus both count, and both have to: a reader using the
|
|
22
|
-
* keyboard never moves a pointer, and a bar that only answered the mouse
|
|
23
|
-
* would leave them nothing to look at. Zero turns the wait off and the bar
|
|
24
|
-
* goes immediately; a large number keeps it up for as long as anyone is
|
|
25
|
-
* likely to look.
|
|
26
|
-
*/
|
|
27
|
-
readonly stir: () => void;
|
|
28
|
-
/**
|
|
29
|
-
* Leaving is the one case that does not wait: the reader has gone somewhere
|
|
30
|
-
* else, and there is nothing to reach for.
|
|
31
|
-
*/
|
|
32
|
-
readonly leave: () => void;
|
|
33
|
-
hostDisconnected(): void;
|
|
34
|
-
}
|
package/dist/video/root/idle.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* What the consumer slotted, as the plain data a `<video>` can be given.
|
|
3
|
-
*
|
|
4
|
-
* Separable because it is a read of light DOM and nothing else: no media
|
|
5
|
-
* element, no playback state, nothing drawn. A media element runs resource
|
|
6
|
-
* selection over its own child nodes and a slot is one node, so the element
|
|
7
|
-
* copies rather than slots, and the two questions the copying asks are answered
|
|
8
|
-
* here: which attributes come across, and whether a fresh read differs from the
|
|
9
|
-
* set already held. When to read, and what to do with the answer, stay with the
|
|
10
|
-
* element.
|
|
11
|
-
*/
|
|
12
|
-
/** A `<source>` or `<track>` as the consumer wrote it, reduced to what the copy carries. */
|
|
13
|
-
export interface MediaCopy {
|
|
14
|
-
attrs: Record<string, string>;
|
|
15
|
-
tag: 'source' | 'track';
|
|
16
|
-
}
|
|
17
|
-
/** The copies to make of what the default slot is given. */
|
|
18
|
-
export declare const copiesOf: (elements: readonly Element[]) => MediaCopy[];
|
|
19
|
-
/**
|
|
20
|
-
* Whether two reads describe the same media.
|
|
21
|
-
*
|
|
22
|
-
* The same set again is not a change. A video reloads for every new list, and
|
|
23
|
-
* the first `slotchange` reports the children the element has already read.
|
|
24
|
-
*/
|
|
25
|
-
export declare const sameCopies: (one: readonly MediaCopy[], two: readonly MediaCopy[]) => boolean;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
const CARRIED = {
|
|
2
|
-
source: ['media', 'sizes', 'src', 'srcset', 'type'],
|
|
3
|
-
track: ['default', 'kind', 'label', 'src', 'srclang'],
|
|
4
|
-
};
|
|
5
|
-
export const copiesOf = (elements) => elements
|
|
6
|
-
.filter((node) => node instanceof HTMLSourceElement || node instanceof HTMLTrackElement)
|
|
7
|
-
.map((node) => {
|
|
8
|
-
const tag = node instanceof HTMLTrackElement ? 'track' : 'source';
|
|
9
|
-
const attrs = {};
|
|
10
|
-
for (const name of CARRIED[tag]) {
|
|
11
|
-
const value = node.getAttribute(name);
|
|
12
|
-
if (value !== null) {
|
|
13
|
-
attrs[name] = value;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
return { attrs, tag };
|
|
17
|
-
});
|
|
18
|
-
export const sameCopies = (one, two) => JSON.stringify(one) === JSON.stringify(two);
|