@pie-players/pie-players-shared 0.3.65 → 0.3.67
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/dist/types/index.d.ts +6 -0
- package/dist/ui/content-styles.d.ts +11 -3
- package/dist/ui/content-styles.js +60 -13
- package/dist/ui/focus-trap.js +11 -2
- package/package.json +1 -1
package/dist/types/index.d.ts
CHANGED
|
@@ -422,6 +422,12 @@ export type CatalogCardPayload = SignLanguageCardPayload | SpokenAudioCardPayloa
|
|
|
422
422
|
export interface CatalogCard {
|
|
423
423
|
catalog: string;
|
|
424
424
|
language?: string;
|
|
425
|
+
/**
|
|
426
|
+
* Optional presentation policy carried by catalog-backed UI such as an audio
|
|
427
|
+
* transcript. Unknown values remain authored metadata for the capability that
|
|
428
|
+
* understands them; the generic catalog resolver does not reinterpret them.
|
|
429
|
+
*/
|
|
430
|
+
visibility?: string;
|
|
425
431
|
/**
|
|
426
432
|
* The string form of the card's content, for catalog types a string can
|
|
427
433
|
* express: SSML for `spoken`, plain text for `simplified-language`, and so
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*
|
|
18
18
|
* The CSS text is passed in rather than imported here: this package builds with
|
|
19
19
|
* plain `tsc`, so it cannot inline a stylesheet. Bundler-built player packages
|
|
20
|
-
* import it with Vite's `?
|
|
20
|
+
* import it with Vite's `?raw` and hand the text over.
|
|
21
21
|
*/
|
|
22
22
|
export type ContentStylesResult = "installed" | "already-installed" | "opted-out" | "no-document";
|
|
23
23
|
/**
|
|
@@ -26,8 +26,16 @@ export type ContentStylesResult = "installed" | "already-installed" | "opted-out
|
|
|
26
26
|
*/
|
|
27
27
|
export declare function contentStylesOptedOut(): boolean;
|
|
28
28
|
/**
|
|
29
|
-
* True when `components.css` is applied to the document, by any route.
|
|
30
|
-
*
|
|
29
|
+
* True when `components.css` is applied to the document, by any route.
|
|
30
|
+
*
|
|
31
|
+
* Two probes, because neither alone covers both deliveries. The computed
|
|
32
|
+
* sentinel on `<html>` is authoritative for a stylesheet applying to the whole
|
|
33
|
+
* document. A host that confines its copy to its player subtree — `@scope
|
|
34
|
+
* (.item-content) { … }`, the documented remedy for these rules reaching host
|
|
35
|
+
* chrome — puts the stylesheet's `:root` rule somewhere it can never match,
|
|
36
|
+
* since `<html>` is not a descendant of the scoping root; the property then
|
|
37
|
+
* reads empty while the stylesheet is present and working. So an empty read
|
|
38
|
+
* falls back to scanning the document's sheets for the sentinel.
|
|
31
39
|
*
|
|
32
40
|
* Only meaningful once the document's stylesheets have been applied — a host
|
|
33
41
|
* that loads CSS via an async `<link>` reads as missing until it lands.
|
|
@@ -17,16 +17,50 @@
|
|
|
17
17
|
*
|
|
18
18
|
* The CSS text is passed in rather than imported here: this package builds with
|
|
19
19
|
* plain `tsc`, so it cannot inline a stylesheet. Bundler-built player packages
|
|
20
|
-
* import it with Vite's `?
|
|
20
|
+
* import it with Vite's `?raw` and hand the text over.
|
|
21
21
|
*/
|
|
22
22
|
/** Marks a `<style>` element this module owns, and keeps installs idempotent. */
|
|
23
23
|
const MARKER_ATTRIBUTE = "data-pie-content-styles";
|
|
24
24
|
/**
|
|
25
25
|
* Declared by `components.css` itself, so it is observable no matter how the
|
|
26
|
-
* stylesheet arrived — our injection or a host import.
|
|
27
|
-
* that opted out
|
|
26
|
+
* stylesheet arrived — our injection or a host import. Diagnostics only: it
|
|
27
|
+
* tells a host that opted out and then shipped nothing, and it spots a host copy
|
|
28
|
+
* sitting alongside ours.
|
|
28
29
|
*/
|
|
29
30
|
const SENTINEL_PROPERTY = "--pie-content-styles";
|
|
31
|
+
// Svelte's dev-mode custom-element reset expands `all: unset` into individual
|
|
32
|
+
// declarations, including custom properties it has observed elsewhere in the
|
|
33
|
+
// bundle. That can produce `--pie-content-styles: unset` on a scoped selector;
|
|
34
|
+
// it is a reset, not evidence that a host loaded components.css.
|
|
35
|
+
const CSS_WIDE_RESET_VALUES = new Set([
|
|
36
|
+
"inherit",
|
|
37
|
+
"initial",
|
|
38
|
+
"revert",
|
|
39
|
+
"revert-layer",
|
|
40
|
+
"unset",
|
|
41
|
+
]);
|
|
42
|
+
const declaresContentStylesSentinel = (rule) => {
|
|
43
|
+
const value = rule.style
|
|
44
|
+
?.getPropertyValue(SENTINEL_PROPERTY)
|
|
45
|
+
.trim()
|
|
46
|
+
.toLowerCase();
|
|
47
|
+
if (value)
|
|
48
|
+
return !CSS_WIDE_RESET_VALUES.has(value);
|
|
49
|
+
// Grouping rules hold no declarations of their own, so the sentinel sits one
|
|
50
|
+
// or more levels down. A host that confines its copy — `@scope
|
|
51
|
+
// (.item-content) { … }`, `@layer pie-content { … }` — presents exactly one
|
|
52
|
+
// top-level rule with an empty `.style`, and a top-level-only scan reads that
|
|
53
|
+
// as "no copy here". That made both detection paths blind to the one host
|
|
54
|
+
// configuration this module most needs to recognise.
|
|
55
|
+
const nested = rule.cssRules;
|
|
56
|
+
if (!nested)
|
|
57
|
+
return false;
|
|
58
|
+
for (const child of Array.from(nested)) {
|
|
59
|
+
if (declaresContentStylesSentinel(child))
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
};
|
|
30
64
|
/** `<html data-pie-content-styles="host">` opts a host out of installation. */
|
|
31
65
|
const OPT_OUT_ATTRIBUTE = "data-pie-content-styles";
|
|
32
66
|
const OPT_OUT_VALUE = "host";
|
|
@@ -41,8 +75,16 @@ export function contentStylesOptedOut() {
|
|
|
41
75
|
return (document.documentElement.getAttribute(OPT_OUT_ATTRIBUTE) === OPT_OUT_VALUE);
|
|
42
76
|
}
|
|
43
77
|
/**
|
|
44
|
-
* True when `components.css` is applied to the document, by any route.
|
|
45
|
-
*
|
|
78
|
+
* True when `components.css` is applied to the document, by any route.
|
|
79
|
+
*
|
|
80
|
+
* Two probes, because neither alone covers both deliveries. The computed
|
|
81
|
+
* sentinel on `<html>` is authoritative for a stylesheet applying to the whole
|
|
82
|
+
* document. A host that confines its copy to its player subtree — `@scope
|
|
83
|
+
* (.item-content) { … }`, the documented remedy for these rules reaching host
|
|
84
|
+
* chrome — puts the stylesheet's `:root` rule somewhere it can never match,
|
|
85
|
+
* since `<html>` is not a descendant of the scoping root; the property then
|
|
86
|
+
* reads empty while the stylesheet is present and working. So an empty read
|
|
87
|
+
* falls back to scanning the document's sheets for the sentinel.
|
|
46
88
|
*
|
|
47
89
|
* Only meaningful once the document's stylesheets have been applied — a host
|
|
48
90
|
* that loads CSS via an async `<link>` reads as missing until it lands.
|
|
@@ -53,7 +95,9 @@ export function contentStylesPresent() {
|
|
|
53
95
|
const value = getComputedStyle(document.documentElement)
|
|
54
96
|
.getPropertyValue(SENTINEL_PROPERTY)
|
|
55
97
|
.trim();
|
|
56
|
-
|
|
98
|
+
if (value !== "")
|
|
99
|
+
return true;
|
|
100
|
+
return countContentStyleSheets({ excludeInstalled: false }) > 0;
|
|
57
101
|
}
|
|
58
102
|
/**
|
|
59
103
|
* Installs `cssText` as a document-level stylesheet, once per document.
|
|
@@ -91,21 +135,22 @@ export function installContentStyles(cssText, source) {
|
|
|
91
135
|
return "installed";
|
|
92
136
|
}
|
|
93
137
|
/**
|
|
94
|
-
* Counts content stylesheets in the document
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
138
|
+
* Counts content stylesheets in the document, detected by the sentinel property
|
|
139
|
+
* rather than by URL, so a copy arriving as a `<link>`, a bundler-injected
|
|
140
|
+
* `<style>`, or anything else all count the same. `excludeInstalled` narrows the
|
|
141
|
+
* count to copies the host loaded itself.
|
|
98
142
|
*
|
|
99
143
|
* Cross-origin sheets throw on `cssRules` access and are skipped; a host copy
|
|
100
144
|
* served from another origin therefore reads as absent. That only costs a
|
|
101
145
|
* diagnostic, never correctness.
|
|
102
146
|
*/
|
|
103
|
-
const
|
|
147
|
+
const countContentStyleSheets = ({ excludeInstalled, }) => {
|
|
104
148
|
// Walks the owning elements rather than document.styleSheets: the marker
|
|
105
149
|
// attribute lives on the element, and CSSStyleSheet.ownerNode is not
|
|
106
150
|
// universally implemented (happy-dom omits it), which would make our own
|
|
107
151
|
// installed copy look like a host copy.
|
|
108
|
-
const
|
|
152
|
+
const exclusion = excludeInstalled ? `:not([${MARKER_ATTRIBUTE}])` : "";
|
|
153
|
+
const nodes = document.querySelectorAll(`style${exclusion}, link[rel~="stylesheet"]${exclusion}`);
|
|
109
154
|
let count = 0;
|
|
110
155
|
for (const node of Array.from(nodes)) {
|
|
111
156
|
let rules;
|
|
@@ -118,7 +163,7 @@ const countHostContentStyleSheets = () => {
|
|
|
118
163
|
if (!rules)
|
|
119
164
|
continue;
|
|
120
165
|
for (const rule of Array.from(rules)) {
|
|
121
|
-
if (rule
|
|
166
|
+
if (declaresContentStylesSentinel(rule)) {
|
|
122
167
|
count += 1;
|
|
123
168
|
break;
|
|
124
169
|
}
|
|
@@ -126,6 +171,8 @@ const countHostContentStyleSheets = () => {
|
|
|
126
171
|
}
|
|
127
172
|
return count;
|
|
128
173
|
};
|
|
174
|
+
/** Copies the host loaded itself, i.e. not the one this module installed. */
|
|
175
|
+
const countHostContentStyleSheets = () => countContentStyleSheets({ excludeInstalled: true });
|
|
129
176
|
let auditWarningIssued = false;
|
|
130
177
|
const pendingChecks = [];
|
|
131
178
|
/**
|
package/dist/ui/focus-trap.js
CHANGED
|
@@ -2,6 +2,13 @@ import { FOCUSABLE_SELECTOR, isProgrammaticFocusTarget, } from "./first-focusabl
|
|
|
2
2
|
function getFocusableElements(container) {
|
|
3
3
|
return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el) => isProgrammaticFocusTarget(el));
|
|
4
4
|
}
|
|
5
|
+
function getDeepActiveElement(root) {
|
|
6
|
+
let active = root.activeElement;
|
|
7
|
+
while (active?.shadowRoot?.activeElement) {
|
|
8
|
+
active = active.shadowRoot.activeElement;
|
|
9
|
+
}
|
|
10
|
+
return active;
|
|
11
|
+
}
|
|
5
12
|
function focusInitialTarget(container, initialFocus) {
|
|
6
13
|
try {
|
|
7
14
|
if (initialFocus && container.contains(initialFocus)) {
|
|
@@ -27,7 +34,7 @@ function focusInitialTarget(container, initialFocus) {
|
|
|
27
34
|
*/
|
|
28
35
|
export function createFocusTrap(container, options = {}) {
|
|
29
36
|
const prev = typeof document !== "undefined"
|
|
30
|
-
? document
|
|
37
|
+
? getDeepActiveElement(document)
|
|
31
38
|
: null;
|
|
32
39
|
const wrap = options.wrap ?? true;
|
|
33
40
|
const onKeydown = (event) => {
|
|
@@ -45,7 +52,9 @@ export function createFocusTrap(container, options = {}) {
|
|
|
45
52
|
container.focus?.();
|
|
46
53
|
return;
|
|
47
54
|
}
|
|
48
|
-
const current =
|
|
55
|
+
const current = getDeepActiveElement(container.getRootNode() instanceof ShadowRoot
|
|
56
|
+
? container.getRootNode()
|
|
57
|
+
: document);
|
|
49
58
|
const currentIndex = focusable.indexOf(current || focusable[0]);
|
|
50
59
|
if (event.shiftKey) {
|
|
51
60
|
if (currentIndex <= 0) {
|