@pie-players/pie-players-shared 0.3.60 → 0.3.62
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/config/profile.js +0 -1
- package/dist/i18n/index.js +0 -1
- package/dist/i18n/loader.js +0 -1
- package/dist/i18n/simple-i18n.js +0 -1
- package/dist/i18n/types.js +0 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -1
- package/dist/instrumentation/debug-panel-stream.js +0 -1
- package/dist/instrumentation/index.js +0 -1
- package/dist/instrumentation/provider-guards.js +0 -1
- package/dist/instrumentation/providers/BaseInstrumentationProvider.js +0 -1
- package/dist/instrumentation/providers/CompositeInstrumentationProvider.js +0 -1
- package/dist/instrumentation/providers/ConsoleInstrumentationProvider.js +0 -1
- package/dist/instrumentation/providers/DebugPanelInstrumentationProvider.js +0 -1
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.js +0 -1
- package/dist/instrumentation/providers/index.js +0 -1
- package/dist/instrumentation/types.js +0 -1
- package/dist/loader-config.js +0 -1
- package/dist/loaders/ElementLoader.js +0 -1
- package/dist/loaders/element-loader-types.js +0 -1
- package/dist/loaders/element-loader.js +0 -1
- package/dist/loaders/element-package-policy.js +0 -1
- package/dist/loaders/esm-adapter.js +0 -1
- package/dist/loaders/iife-adapter.js +0 -1
- package/dist/loaders/index.js +0 -1
- package/dist/object/index.js +0 -1
- package/dist/pie/asset-handler.js +0 -1
- package/dist/pie/authoring-tag.js +0 -1
- package/dist/pie/authoring.js +0 -1
- package/dist/pie/component-context.js +0 -1
- package/dist/pie/config.js +0 -1
- package/dist/pie/configure-initialization.js +0 -1
- package/dist/pie/correct-response-env.js +0 -1
- package/dist/pie/custom-element-define.js +0 -1
- package/dist/pie/index.js +0 -1
- package/dist/pie/initialization.js +0 -1
- package/dist/pie/instrumentation-event-bridge.js +0 -1
- package/dist/pie/instrumentation-event-map.js +0 -1
- package/dist/pie/instrumentation-provider-resolution.js +0 -1
- package/dist/pie/item-controller-storage.js +0 -1
- package/dist/pie/item-controller.js +0 -1
- package/dist/pie/item-session-contract.js +0 -1
- package/dist/pie/logger.js +0 -1
- package/dist/pie/math-rendering.js +0 -1
- package/dist/pie/overrides.js +0 -1
- package/dist/pie/player-initializer.js +0 -1
- package/dist/pie/registry.js +0 -1
- package/dist/pie/resource-monitor.js +0 -1
- package/dist/pie/scoring.js +0 -1
- package/dist/pie/stage-tracker.js +0 -1
- package/dist/pie/stages.js +0 -1
- package/dist/pie/tag-names.js +0 -1
- package/dist/pie/types.js +0 -1
- package/dist/pie/updates.js +0 -1
- package/dist/pie/utils.js +0 -1
- package/dist/pie/versioned-tag.js +0 -1
- package/dist/player-strategy.js +0 -1
- package/dist/security/index.js +0 -1
- package/dist/security/sanitize-item-markup.d.ts +11 -0
- package/dist/security/sanitize-item-markup.js +5 -3
- package/dist/security/sanitize-svg-icon.js +0 -1
- package/dist/security/validate-style-url.js +0 -1
- package/dist/security/wrap-model-rich-content.js +0 -1
- package/dist/security/wrap-overwide-images.js +0 -1
- package/dist/security/wrap-overwide-tables.js +0 -1
- package/dist/server/npm-registry.js +0 -1
- package/dist/types/index.js +0 -1
- package/dist/ui/content-styles.d.ts +73 -0
- package/dist/ui/content-styles.js +204 -0
- package/dist/ui/debug-panel-persistence.js +0 -1
- package/dist/ui/first-focusable.js +0 -1
- package/dist/ui/focus-trap.js +0 -1
- package/dist/ui/safe-storage.js +0 -1
- package/dist/ui/scope-css.d.ts +56 -0
- package/dist/ui/scope-css.js +310 -0
- package/dist/ui/zoom-compensation.js +0 -1
- package/package.json +9 -5
- package/dist/config/profile.js.map +0 -1
- package/dist/i18n/index.js.map +0 -1
- package/dist/i18n/loader.js.map +0 -1
- package/dist/i18n/simple-i18n.js.map +0 -1
- package/dist/i18n/types.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/instrumentation/debug-panel-stream.js.map +0 -1
- package/dist/instrumentation/index.js.map +0 -1
- package/dist/instrumentation/provider-guards.js.map +0 -1
- package/dist/instrumentation/providers/BaseInstrumentationProvider.js.map +0 -1
- package/dist/instrumentation/providers/CompositeInstrumentationProvider.js.map +0 -1
- package/dist/instrumentation/providers/ConsoleInstrumentationProvider.js.map +0 -1
- package/dist/instrumentation/providers/DebugPanelInstrumentationProvider.js.map +0 -1
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.js.map +0 -1
- package/dist/instrumentation/providers/index.js.map +0 -1
- package/dist/instrumentation/types.js.map +0 -1
- package/dist/loader-config.js.map +0 -1
- package/dist/loaders/ElementLoader.js.map +0 -1
- package/dist/loaders/element-loader-types.js.map +0 -1
- package/dist/loaders/element-loader.js.map +0 -1
- package/dist/loaders/element-package-policy.js.map +0 -1
- package/dist/loaders/esm-adapter.js.map +0 -1
- package/dist/loaders/iife-adapter.js.map +0 -1
- package/dist/loaders/index.js.map +0 -1
- package/dist/object/index.js.map +0 -1
- package/dist/pie/asset-handler.js.map +0 -1
- package/dist/pie/authoring-tag.js.map +0 -1
- package/dist/pie/authoring.js.map +0 -1
- package/dist/pie/component-context.js.map +0 -1
- package/dist/pie/config.js.map +0 -1
- package/dist/pie/configure-initialization.js.map +0 -1
- package/dist/pie/correct-response-env.js.map +0 -1
- package/dist/pie/custom-element-define.js.map +0 -1
- package/dist/pie/index.js.map +0 -1
- package/dist/pie/initialization.js.map +0 -1
- package/dist/pie/instrumentation-event-bridge.js.map +0 -1
- package/dist/pie/instrumentation-event-map.js.map +0 -1
- package/dist/pie/instrumentation-provider-resolution.js.map +0 -1
- package/dist/pie/item-controller-storage.js.map +0 -1
- package/dist/pie/item-controller.js.map +0 -1
- package/dist/pie/item-session-contract.js.map +0 -1
- package/dist/pie/logger.js.map +0 -1
- package/dist/pie/math-rendering.js.map +0 -1
- package/dist/pie/overrides.js.map +0 -1
- package/dist/pie/player-initializer.js.map +0 -1
- package/dist/pie/registry.js.map +0 -1
- package/dist/pie/resource-monitor.js.map +0 -1
- package/dist/pie/scoring.js.map +0 -1
- package/dist/pie/stage-tracker.js.map +0 -1
- package/dist/pie/stages.js.map +0 -1
- package/dist/pie/tag-names.js.map +0 -1
- package/dist/pie/types.js.map +0 -1
- package/dist/pie/updates.js.map +0 -1
- package/dist/pie/utils.js.map +0 -1
- package/dist/pie/versioned-tag.js.map +0 -1
- package/dist/player-strategy.js.map +0 -1
- package/dist/security/index.js.map +0 -1
- package/dist/security/sanitize-item-markup.js.map +0 -1
- package/dist/security/sanitize-svg-icon.js.map +0 -1
- package/dist/security/validate-style-url.js.map +0 -1
- package/dist/security/wrap-model-rich-content.js.map +0 -1
- package/dist/security/wrap-overwide-images.js.map +0 -1
- package/dist/security/wrap-overwide-tables.js.map +0 -1
- package/dist/server/npm-registry.js.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/ui/debug-panel-persistence.js.map +0 -1
- package/dist/ui/first-focusable.js.map +0 -1
- package/dist/ui/focus-trap.js.map +0 -1
- package/dist/ui/safe-storage.js.map +0 -1
- package/dist/ui/zoom-compensation.js.map +0 -1
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installs the shared PIE content stylesheet
|
|
3
|
+
* (`@pie-players/pie-theme/components.css`) into the host document.
|
|
4
|
+
*
|
|
5
|
+
* Authored assessment content depends on classes that belong to no single
|
|
6
|
+
* component: passage markup (`.numbered-paragraph`, `.p-number`,
|
|
7
|
+
* `div.passage-title`), the legacy `kds-*` families, the `@media print`
|
|
8
|
+
* `.noprint` rules, and the answer-eliminator classes. Players render into
|
|
9
|
+
* light DOM, so those rules have to exist as a document-level stylesheet —
|
|
10
|
+
* there is no shadow root to scope them to.
|
|
11
|
+
*
|
|
12
|
+
* Hosts used to be required to import that stylesheet themselves. Nothing
|
|
13
|
+
* enforced it and nothing failed loudly when they didn't: the item rendered,
|
|
14
|
+
* the passage was simply unstyled, and it surfaced as a visual bug reported by
|
|
15
|
+
* hand days later. Players install the stylesheet themselves instead, which
|
|
16
|
+
* keeps the host contract at "import the player".
|
|
17
|
+
*
|
|
18
|
+
* The CSS text is passed in rather than imported here: this package builds with
|
|
19
|
+
* plain `tsc`, so it cannot inline a stylesheet. Bundler-built player packages
|
|
20
|
+
* import it with Vite's `?inline` and hand the text over.
|
|
21
|
+
*/
|
|
22
|
+
/** Marks a `<style>` element this module owns, and keeps installs idempotent. */
|
|
23
|
+
const MARKER_ATTRIBUTE = "data-pie-content-styles";
|
|
24
|
+
/**
|
|
25
|
+
* Declared by `components.css` itself, so it is observable no matter how the
|
|
26
|
+
* stylesheet arrived — our injection or a host import. Used only to tell a host
|
|
27
|
+
* that opted out but then shipped nothing.
|
|
28
|
+
*/
|
|
29
|
+
const SENTINEL_PROPERTY = "--pie-content-styles";
|
|
30
|
+
/** `<html data-pie-content-styles="host">` opts a host out of installation. */
|
|
31
|
+
const OPT_OUT_ATTRIBUTE = "data-pie-content-styles";
|
|
32
|
+
const OPT_OUT_VALUE = "host";
|
|
33
|
+
const isBrowser = () => typeof document !== "undefined" && !!document.head;
|
|
34
|
+
/**
|
|
35
|
+
* True when the host declared `<html data-pie-content-styles="host">`, i.e. it
|
|
36
|
+
* takes ownership of loading the stylesheet.
|
|
37
|
+
*/
|
|
38
|
+
export function contentStylesOptedOut() {
|
|
39
|
+
if (!isBrowser())
|
|
40
|
+
return false;
|
|
41
|
+
return (document.documentElement.getAttribute(OPT_OUT_ATTRIBUTE) === OPT_OUT_VALUE);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* True when `components.css` is applied to the document, by any route. Reads the
|
|
45
|
+
* sentinel custom property the stylesheet declares on `:root`.
|
|
46
|
+
*
|
|
47
|
+
* Only meaningful once the document's stylesheets have been applied — a host
|
|
48
|
+
* that loads CSS via an async `<link>` reads as missing until it lands.
|
|
49
|
+
*/
|
|
50
|
+
export function contentStylesPresent() {
|
|
51
|
+
if (!isBrowser())
|
|
52
|
+
return false;
|
|
53
|
+
const value = getComputedStyle(document.documentElement)
|
|
54
|
+
.getPropertyValue(SENTINEL_PROPERTY)
|
|
55
|
+
.trim();
|
|
56
|
+
return value !== "";
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Installs `cssText` as a document-level stylesheet, once per document.
|
|
60
|
+
*
|
|
61
|
+
* The stylesheet is **prepended** to `<head>` and deliberately left out of a
|
|
62
|
+
* cascade layer. Prepending reproduces the placement hosts were told to use by
|
|
63
|
+
* hand — first in the entry, before app CSS — so an app rule and a content rule
|
|
64
|
+
* of equal specificity resolve in the host's favour, exactly as before.
|
|
65
|
+
*
|
|
66
|
+
* A cascade layer looks tempting here and is the wrong tool: unlayered author
|
|
67
|
+
* declarations beat *all* layered ones regardless of specificity, so a host
|
|
68
|
+
* reset as broad as `p { margin: 0 }` would silently outrank
|
|
69
|
+
* `.numbered-paragraph { margin-left: 36px }`. That trades a visible missing
|
|
70
|
+
* stylesheet for a subtler override bug, so ordinary specificity wins instead.
|
|
71
|
+
*
|
|
72
|
+
* @param cssText Contents of `@pie-players/pie-theme/components.css`.
|
|
73
|
+
* @param source Package installing the styles, for diagnostics.
|
|
74
|
+
*/
|
|
75
|
+
export function installContentStyles(cssText, source) {
|
|
76
|
+
if (!isBrowser())
|
|
77
|
+
return "no-document";
|
|
78
|
+
if (contentStylesOptedOut())
|
|
79
|
+
return "opted-out";
|
|
80
|
+
if (document.querySelector(`style[${MARKER_ATTRIBUTE}]`)) {
|
|
81
|
+
// Another player package, or a second copy of this one, already installed
|
|
82
|
+
// the same stylesheet. Duplicating it would be harmless but pointless.
|
|
83
|
+
return "already-installed";
|
|
84
|
+
}
|
|
85
|
+
if (!cssText)
|
|
86
|
+
return "no-document";
|
|
87
|
+
const style = document.createElement("style");
|
|
88
|
+
style.setAttribute(MARKER_ATTRIBUTE, source);
|
|
89
|
+
style.textContent = cssText;
|
|
90
|
+
document.head.prepend(style);
|
|
91
|
+
return "installed";
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Counts content stylesheets in the document that this module did not install —
|
|
95
|
+
* i.e. copies the host loaded itself. Detected by the sentinel property rather
|
|
96
|
+
* than by URL, so a copy arriving as a `<link>`, a bundler-injected `<style>`, or
|
|
97
|
+
* anything else all count the same.
|
|
98
|
+
*
|
|
99
|
+
* Cross-origin sheets throw on `cssRules` access and are skipped; a host copy
|
|
100
|
+
* served from another origin therefore reads as absent. That only costs a
|
|
101
|
+
* diagnostic, never correctness.
|
|
102
|
+
*/
|
|
103
|
+
const countHostContentStyleSheets = () => {
|
|
104
|
+
// Walks the owning elements rather than document.styleSheets: the marker
|
|
105
|
+
// attribute lives on the element, and CSSStyleSheet.ownerNode is not
|
|
106
|
+
// universally implemented (happy-dom omits it), which would make our own
|
|
107
|
+
// installed copy look like a host copy.
|
|
108
|
+
const nodes = document.querySelectorAll(`style:not([${MARKER_ATTRIBUTE}]), link[rel~="stylesheet"]:not([${MARKER_ATTRIBUTE}])`);
|
|
109
|
+
let count = 0;
|
|
110
|
+
for (const node of Array.from(nodes)) {
|
|
111
|
+
let rules;
|
|
112
|
+
try {
|
|
113
|
+
rules = node.sheet?.cssRules;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!rules)
|
|
119
|
+
continue;
|
|
120
|
+
for (const rule of Array.from(rules)) {
|
|
121
|
+
if (rule.style?.getPropertyValue(SENTINEL_PROPERTY)) {
|
|
122
|
+
count += 1;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return count;
|
|
128
|
+
};
|
|
129
|
+
let auditWarningIssued = false;
|
|
130
|
+
const pendingChecks = [];
|
|
131
|
+
/**
|
|
132
|
+
* Reports, once per page, the two ways content styling can go wrong. Neither is
|
|
133
|
+
* detectable synchronously — a host copy may still be in flight as an async
|
|
134
|
+
* `<link>` — so the check is deferred.
|
|
135
|
+
*
|
|
136
|
+
* - **Missing**: the host opted out and then loaded nothing, so authored content
|
|
137
|
+
* renders unstyled. This is the failure the old host-import contract produced
|
|
138
|
+
* silently.
|
|
139
|
+
* - **Duplicated**: the host still imports `components.css` itself *and* the
|
|
140
|
+
* player installed a copy. Rendering is correct while the two agree, but the
|
|
141
|
+
* host's copy loads later and therefore wins ties at equal specificity — so a
|
|
142
|
+
* host copy pinned to an older `@pie-players/pie-theme` silently overrides the
|
|
143
|
+
* player's newer rules. Harmless today, a confusing override tomorrow.
|
|
144
|
+
*/
|
|
145
|
+
export function auditContentStyles(source) {
|
|
146
|
+
if (!isBrowser() || auditWarningIssued)
|
|
147
|
+
return;
|
|
148
|
+
const check = () => {
|
|
149
|
+
if (auditWarningIssued)
|
|
150
|
+
return;
|
|
151
|
+
if (contentStylesOptedOut()) {
|
|
152
|
+
if (contentStylesPresent())
|
|
153
|
+
return;
|
|
154
|
+
auditWarningIssued = true;
|
|
155
|
+
console.warn(`[${source}] No PIE content stylesheet found. This document sets ` +
|
|
156
|
+
`${OPT_OUT_ATTRIBUTE}="${OPT_OUT_VALUE}", so ${source} did not install ` +
|
|
157
|
+
`one. Authored content that relies on shared classes ` +
|
|
158
|
+
`(.numbered-paragraph, .p-number, div.passage-title, the kds-* ` +
|
|
159
|
+
`families, answer-eliminator styles) will render unstyled. Either ` +
|
|
160
|
+
`import "@pie-players/pie-theme/components.css" in the host app, or ` +
|
|
161
|
+
`drop the ${OPT_OUT_ATTRIBUTE} attribute to let the player install it.`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (countHostContentStyleSheets() > 0) {
|
|
165
|
+
auditWarningIssued = true;
|
|
166
|
+
console.warn(`[${source}] The PIE content stylesheet is loaded twice: ${source} ` +
|
|
167
|
+
`installs it, and this host also imports ` +
|
|
168
|
+
`"@pie-players/pie-theme/components.css". Rendering is unaffected ` +
|
|
169
|
+
`while both copies match, but the host copy loads later and wins ` +
|
|
170
|
+
`ties at equal specificity, so a copy pinned to an older ` +
|
|
171
|
+
`@pie-players/pie-theme will silently override newer player styles. ` +
|
|
172
|
+
`Remove the host import, or set ` +
|
|
173
|
+
`${OPT_OUT_ATTRIBUTE}="${OPT_OUT_VALUE}" on <html> to own the ` +
|
|
174
|
+
`stylesheet deliberately.`);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
// A host stylesheet can land well after the module graph evaluates, and a
|
|
178
|
+
// bundler-injected <link> is not necessarily parsed by the time `load` fires.
|
|
179
|
+
// This is advisory output, so it is checked at a few widening points rather
|
|
180
|
+
// than raced: whichever one first sees a settled document wins, and the latch
|
|
181
|
+
// keeps the rest quiet. Missing the window costs a diagnostic, not
|
|
182
|
+
// correctness.
|
|
183
|
+
if (typeof requestAnimationFrame !== "function") {
|
|
184
|
+
check();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
requestAnimationFrame(() => requestAnimationFrame(check));
|
|
188
|
+
if (typeof window !== "undefined" && document.readyState !== "complete") {
|
|
189
|
+
window.addEventListener("load", () => check(), { once: true });
|
|
190
|
+
}
|
|
191
|
+
if (typeof setTimeout === "function") {
|
|
192
|
+
pendingChecks.push(setTimeout(check, 1000));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Test-only: clears the once-per-page warning latch and cancels any pending
|
|
197
|
+
* deferred check, so a timer scheduled by one test cannot warn during the next.
|
|
198
|
+
*/
|
|
199
|
+
export function resetContentStylesWarningForTesting() {
|
|
200
|
+
auditWarningIssued = false;
|
|
201
|
+
for (const handle of pendingChecks)
|
|
202
|
+
clearTimeout(handle);
|
|
203
|
+
pendingChecks.length = 0;
|
|
204
|
+
}
|
package/dist/ui/focus-trap.js
CHANGED
package/dist/ui/safe-storage.js
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope an external stylesheet's rules to a single player instance, so a
|
|
3
|
+
* partner-authored stylesheet loaded via `<pie-item-player
|
|
4
|
+
* external-style-urls="...">` or `itemConfig.resources.stylesheets[*].url`
|
|
5
|
+
* styles that player's subtree instead of the whole host document.
|
|
6
|
+
*
|
|
7
|
+
* This replaces a single-regex implementation that prefixed every
|
|
8
|
+
* selector-like fragment. That is correct for flat selector rules and wrong for
|
|
9
|
+
* everything else: `@media screen { ... }` became
|
|
10
|
+
* `.scope @media screen { ... }`, an invalid selector, so the browser dropped
|
|
11
|
+
* the whole block and every rule inside it; `@font-face` and `@keyframes` were
|
|
12
|
+
* corrupted the same way; and `:root` became `.scope :root`, which can never
|
|
13
|
+
* match, because `:root` is `<html>` and is not a descendant of the player.
|
|
14
|
+
* At-rules and `:root` custom properties therefore never applied at all.
|
|
15
|
+
*
|
|
16
|
+
* The QTI player's `scopeCssRules` (`pie-qti` -
|
|
17
|
+
* `packages/item-player/src/components/utils/stylesheetRender.ts`) is not a
|
|
18
|
+
* drop-in either. Its `:root` handling is the behaviour we want and is adopted
|
|
19
|
+
* here, but it excludes `@` from its selector pattern rather than understanding
|
|
20
|
+
* at-rules, so `@media` and `@supports` lose their condition and their inner
|
|
21
|
+
* rules are hoisted and applied unconditionally — a mobile-only rule then
|
|
22
|
+
* applies at every viewport width, which is a subtler failure than dropping it.
|
|
23
|
+
*
|
|
24
|
+
* So this walks the stylesheet brace-by-brace instead of pattern-matching it.
|
|
25
|
+
* A real CSS parser would be the textbook answer and is deliberately not used:
|
|
26
|
+
* `@pie-players/pie-item-player` ships with two workspace dependencies, this
|
|
27
|
+
* runs in the delivery path, and scoping needs to know about rule boundaries
|
|
28
|
+
* only — not about property grammar.
|
|
29
|
+
*
|
|
30
|
+
* Known limits, all preserving existing behaviour rather than adding policy:
|
|
31
|
+
*
|
|
32
|
+
* - `@import` is passed through untouched, exactly as the old regex left it. It
|
|
33
|
+
* pulls in an unscoped stylesheet, so it defeats scoping, but blocking it is
|
|
34
|
+
* a security policy decision and not this function's job. Callers gate
|
|
35
|
+
* stylesheet URLs with `validateExternalStyleUrl` before fetching.
|
|
36
|
+
* - Declaration blocks are emitted verbatim, so `url(...)` references are left
|
|
37
|
+
* as authored and resolve against the stylesheet's own URL as before.
|
|
38
|
+
* - A style rule's block is never rewritten, which is also what native CSS
|
|
39
|
+
* nesting needs: nested selectors are relative to a parent that has already
|
|
40
|
+
* been scoped.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Rewrite `cssText` so every selector it contains is confined to
|
|
44
|
+
* `scopeSelector`.
|
|
45
|
+
*
|
|
46
|
+
* Returns `cssText` unchanged when there is no scope selector to apply, and
|
|
47
|
+
* `""` for empty or non-string input.
|
|
48
|
+
*/
|
|
49
|
+
export declare function scopeStylesheetCss(cssText: unknown, scopeSelector: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Confine a single selector to `scopeSelector`.
|
|
52
|
+
*
|
|
53
|
+
* Exported for tests and for callers that scope a selector they built
|
|
54
|
+
* themselves; `scopeStylesheetCss` is the entry point for stylesheet text.
|
|
55
|
+
*/
|
|
56
|
+
export declare function scopeSelector(selector: string, scopeSelector: string): string;
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope an external stylesheet's rules to a single player instance, so a
|
|
3
|
+
* partner-authored stylesheet loaded via `<pie-item-player
|
|
4
|
+
* external-style-urls="...">` or `itemConfig.resources.stylesheets[*].url`
|
|
5
|
+
* styles that player's subtree instead of the whole host document.
|
|
6
|
+
*
|
|
7
|
+
* This replaces a single-regex implementation that prefixed every
|
|
8
|
+
* selector-like fragment. That is correct for flat selector rules and wrong for
|
|
9
|
+
* everything else: `@media screen { ... }` became
|
|
10
|
+
* `.scope @media screen { ... }`, an invalid selector, so the browser dropped
|
|
11
|
+
* the whole block and every rule inside it; `@font-face` and `@keyframes` were
|
|
12
|
+
* corrupted the same way; and `:root` became `.scope :root`, which can never
|
|
13
|
+
* match, because `:root` is `<html>` and is not a descendant of the player.
|
|
14
|
+
* At-rules and `:root` custom properties therefore never applied at all.
|
|
15
|
+
*
|
|
16
|
+
* The QTI player's `scopeCssRules` (`pie-qti` -
|
|
17
|
+
* `packages/item-player/src/components/utils/stylesheetRender.ts`) is not a
|
|
18
|
+
* drop-in either. Its `:root` handling is the behaviour we want and is adopted
|
|
19
|
+
* here, but it excludes `@` from its selector pattern rather than understanding
|
|
20
|
+
* at-rules, so `@media` and `@supports` lose their condition and their inner
|
|
21
|
+
* rules are hoisted and applied unconditionally — a mobile-only rule then
|
|
22
|
+
* applies at every viewport width, which is a subtler failure than dropping it.
|
|
23
|
+
*
|
|
24
|
+
* So this walks the stylesheet brace-by-brace instead of pattern-matching it.
|
|
25
|
+
* A real CSS parser would be the textbook answer and is deliberately not used:
|
|
26
|
+
* `@pie-players/pie-item-player` ships with two workspace dependencies, this
|
|
27
|
+
* runs in the delivery path, and scoping needs to know about rule boundaries
|
|
28
|
+
* only — not about property grammar.
|
|
29
|
+
*
|
|
30
|
+
* Known limits, all preserving existing behaviour rather than adding policy:
|
|
31
|
+
*
|
|
32
|
+
* - `@import` is passed through untouched, exactly as the old regex left it. It
|
|
33
|
+
* pulls in an unscoped stylesheet, so it defeats scoping, but blocking it is
|
|
34
|
+
* a security policy decision and not this function's job. Callers gate
|
|
35
|
+
* stylesheet URLs with `validateExternalStyleUrl` before fetching.
|
|
36
|
+
* - Declaration blocks are emitted verbatim, so `url(...)` references are left
|
|
37
|
+
* as authored and resolve against the stylesheet's own URL as before.
|
|
38
|
+
* - A style rule's block is never rewritten, which is also what native CSS
|
|
39
|
+
* nesting needs: nested selectors are relative to a parent that has already
|
|
40
|
+
* been scoped.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* At-rules whose block contains nested *rules*, so scoping has to recurse into
|
|
44
|
+
* the block while the prelude is preserved verbatim.
|
|
45
|
+
*
|
|
46
|
+
* Everything not listed here is passed through untouched. That is the safe
|
|
47
|
+
* default in both directions: at-rules whose block holds declarations rather
|
|
48
|
+
* than rules (`@font-face`, `@page`, `@property`, `@counter-style`) or
|
|
49
|
+
* non-selector keys (`@keyframes` percentages) must not be scoped, and an
|
|
50
|
+
* at-rule this list has never heard of is more safely left alone than guessed
|
|
51
|
+
* at.
|
|
52
|
+
*/
|
|
53
|
+
const NESTED_RULE_AT_RULES = new Set([
|
|
54
|
+
"media",
|
|
55
|
+
"supports",
|
|
56
|
+
"container",
|
|
57
|
+
"layer",
|
|
58
|
+
"scope",
|
|
59
|
+
]);
|
|
60
|
+
/** Matches a leading `:root`, `html` or `body` not followed by more name characters. */
|
|
61
|
+
const DOCUMENT_ROOT_PREFIX = /^(?::root|html|body)(?![\w-])/;
|
|
62
|
+
/** Matches an at-rule name, with any vendor prefix, at the start of a prelude. */
|
|
63
|
+
const AT_RULE_NAME = /^@(?:-[a-z]+-)?([\w-]+)/i;
|
|
64
|
+
/**
|
|
65
|
+
* Rewrite `cssText` so every selector it contains is confined to
|
|
66
|
+
* `scopeSelector`.
|
|
67
|
+
*
|
|
68
|
+
* Returns `cssText` unchanged when there is no scope selector to apply, and
|
|
69
|
+
* `""` for empty or non-string input.
|
|
70
|
+
*/
|
|
71
|
+
export function scopeStylesheetCss(cssText, scopeSelector) {
|
|
72
|
+
if (typeof cssText !== "string" || cssText.length === 0)
|
|
73
|
+
return "";
|
|
74
|
+
const scope = scopeSelector?.trim();
|
|
75
|
+
if (!scope)
|
|
76
|
+
return cssText;
|
|
77
|
+
return scopeRuleList(stripComments(cssText), scope);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Confine a single selector to `scopeSelector`.
|
|
81
|
+
*
|
|
82
|
+
* Exported for tests and for callers that scope a selector they built
|
|
83
|
+
* themselves; `scopeStylesheetCss` is the entry point for stylesheet text.
|
|
84
|
+
*/
|
|
85
|
+
export function scopeSelector(selector, scopeSelector) {
|
|
86
|
+
const sel = selector.trim();
|
|
87
|
+
const scope = scopeSelector.trim();
|
|
88
|
+
if (!sel || !scope)
|
|
89
|
+
return "";
|
|
90
|
+
// Idempotent: re-scoping already-scoped CSS must not nest the scope twice.
|
|
91
|
+
// Deliberately narrow — a bare `startsWith` would also skip an authored
|
|
92
|
+
// selector that merely shares the scope's leading characters.
|
|
93
|
+
if (sel === scope || sel.startsWith(`${scope} `))
|
|
94
|
+
return sel;
|
|
95
|
+
// `:root`/`html`/`body` are replaced rather than prefixed. Prefixing yields
|
|
96
|
+
// `.scope :root`, a selector that cannot match, which is how external
|
|
97
|
+
// `:root { --var: ... }` custom properties used to disappear. Any compound
|
|
98
|
+
// or combinator that followed is preserved: `html.dark .a` -> `.scope.dark .a`.
|
|
99
|
+
const rootPrefix = DOCUMENT_ROOT_PREFIX.exec(sel);
|
|
100
|
+
if (rootPrefix)
|
|
101
|
+
return `${scope}${sel.slice(rootPrefix[0].length)}`;
|
|
102
|
+
// Everything else becomes a descendant, including a leading pseudo-class or
|
|
103
|
+
// pseudo-element. `pie-qti`'s scoper attaches those to the scope instead,
|
|
104
|
+
// which is not the same selector: `:is(.a, .b) .c` authored at stylesheet
|
|
105
|
+
// level means "some element matching .a or .b", so attaching it demands that
|
|
106
|
+
// the *player root* carry the partner's class, and it never does. The same
|
|
107
|
+
// holds for `:hover` and `::selection` — the author meant an element in the
|
|
108
|
+
// content, not the player's own box. Root selectors are the one case where
|
|
109
|
+
// attaching is right, and they are handled above.
|
|
110
|
+
return `${scope} ${sel}`;
|
|
111
|
+
}
|
|
112
|
+
function scopeRuleList(css, scope) {
|
|
113
|
+
let out = "";
|
|
114
|
+
let index = 0;
|
|
115
|
+
while (index < css.length) {
|
|
116
|
+
const node = readNode(css, index);
|
|
117
|
+
if (node.end <= index)
|
|
118
|
+
break;
|
|
119
|
+
out += renderNode(node, scope);
|
|
120
|
+
index = node.end;
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
function renderNode(node, scope) {
|
|
125
|
+
if (node.kind === "statement") {
|
|
126
|
+
const text = node.text.trim();
|
|
127
|
+
// A stray `;` at rule-list level carries nothing worth re-emitting.
|
|
128
|
+
return text === ";" ? "" : `${text}\n`;
|
|
129
|
+
}
|
|
130
|
+
if (node.kind === "trailing") {
|
|
131
|
+
// Only whitespace is expected here; anything else is malformed CSS that
|
|
132
|
+
// is better passed through than dropped.
|
|
133
|
+
return node.text.trim() ? `${node.text.trim()}\n` : "";
|
|
134
|
+
}
|
|
135
|
+
const prelude = node.prelude.trim();
|
|
136
|
+
if (!prelude)
|
|
137
|
+
return "";
|
|
138
|
+
if (prelude.startsWith("@")) {
|
|
139
|
+
const name = AT_RULE_NAME.exec(prelude)?.[1]?.toLowerCase();
|
|
140
|
+
if (name && NESTED_RULE_AT_RULES.has(name)) {
|
|
141
|
+
const inner = scopeRuleList(node.block, scope);
|
|
142
|
+
if (!inner.trim())
|
|
143
|
+
return "";
|
|
144
|
+
return `${prelude} {\n${inner}}\n`;
|
|
145
|
+
}
|
|
146
|
+
return `${prelude} {${node.block}}\n`;
|
|
147
|
+
}
|
|
148
|
+
const selectors = splitTopLevel(prelude, ",")
|
|
149
|
+
.map((selector) => scopeSelector(selector, scope))
|
|
150
|
+
.filter(Boolean);
|
|
151
|
+
if (selectors.length === 0)
|
|
152
|
+
return "";
|
|
153
|
+
return `${selectors.join(", ")} {${node.block}}\n`;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Read one node starting at `start`, tracking strings and parentheses so a `{`
|
|
157
|
+
* inside `content: "{"` or a `,` inside `:is(a, b)` is not mistaken for
|
|
158
|
+
* structure.
|
|
159
|
+
*/
|
|
160
|
+
function readNode(css, start) {
|
|
161
|
+
let index = start;
|
|
162
|
+
let quote = null;
|
|
163
|
+
let parenDepth = 0;
|
|
164
|
+
while (index < css.length) {
|
|
165
|
+
const char = css[index];
|
|
166
|
+
if (quote) {
|
|
167
|
+
if (char === "\\")
|
|
168
|
+
index += 1;
|
|
169
|
+
else if (char === quote)
|
|
170
|
+
quote = null;
|
|
171
|
+
}
|
|
172
|
+
else if (char === '"' || char === "'") {
|
|
173
|
+
quote = char;
|
|
174
|
+
}
|
|
175
|
+
else if (char === "(") {
|
|
176
|
+
parenDepth += 1;
|
|
177
|
+
}
|
|
178
|
+
else if (char === ")") {
|
|
179
|
+
if (parenDepth > 0)
|
|
180
|
+
parenDepth -= 1;
|
|
181
|
+
}
|
|
182
|
+
else if (parenDepth === 0 && char === ";") {
|
|
183
|
+
return {
|
|
184
|
+
kind: "statement",
|
|
185
|
+
text: css.slice(start, index + 1),
|
|
186
|
+
end: index + 1,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
else if (parenDepth === 0 && char === "{") {
|
|
190
|
+
const blockEnd = findBlockEnd(css, index);
|
|
191
|
+
return {
|
|
192
|
+
kind: "rule",
|
|
193
|
+
prelude: css.slice(start, index),
|
|
194
|
+
block: css.slice(index + 1, blockEnd.contentEnd),
|
|
195
|
+
end: blockEnd.end,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
index += 1;
|
|
199
|
+
}
|
|
200
|
+
return { kind: "trailing", text: css.slice(start), end: css.length };
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Find the `}` matching the `{` at `openIndex`. Unbalanced input (a truncated
|
|
204
|
+
* stylesheet) is treated as running to the end rather than throwing.
|
|
205
|
+
*/
|
|
206
|
+
function findBlockEnd(css, openIndex) {
|
|
207
|
+
let depth = 0;
|
|
208
|
+
let quote = null;
|
|
209
|
+
for (let index = openIndex; index < css.length; index += 1) {
|
|
210
|
+
const char = css[index];
|
|
211
|
+
if (quote) {
|
|
212
|
+
if (char === "\\")
|
|
213
|
+
index += 1;
|
|
214
|
+
else if (char === quote)
|
|
215
|
+
quote = null;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (char === '"' || char === "'") {
|
|
219
|
+
quote = char;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (char === "{")
|
|
223
|
+
depth += 1;
|
|
224
|
+
else if (char === "}") {
|
|
225
|
+
depth -= 1;
|
|
226
|
+
if (depth === 0)
|
|
227
|
+
return { contentEnd: index, end: index + 1 };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return { contentEnd: css.length, end: css.length };
|
|
231
|
+
}
|
|
232
|
+
/** Split on `separator` at paren depth zero and outside strings. */
|
|
233
|
+
function splitTopLevel(value, separator) {
|
|
234
|
+
const parts = [];
|
|
235
|
+
let current = "";
|
|
236
|
+
let quote = null;
|
|
237
|
+
let parenDepth = 0;
|
|
238
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
239
|
+
const char = value[index];
|
|
240
|
+
if (quote) {
|
|
241
|
+
current += char;
|
|
242
|
+
if (char === "\\" && index + 1 < value.length) {
|
|
243
|
+
current += value[index + 1];
|
|
244
|
+
index += 1;
|
|
245
|
+
}
|
|
246
|
+
else if (char === quote) {
|
|
247
|
+
quote = null;
|
|
248
|
+
}
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (char === '"' || char === "'") {
|
|
252
|
+
quote = char;
|
|
253
|
+
current += char;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (char === "(")
|
|
257
|
+
parenDepth += 1;
|
|
258
|
+
else if (char === ")" && parenDepth > 0)
|
|
259
|
+
parenDepth -= 1;
|
|
260
|
+
if (char === separator && parenDepth === 0) {
|
|
261
|
+
parts.push(current);
|
|
262
|
+
current = "";
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
current += char;
|
|
266
|
+
}
|
|
267
|
+
parts.push(current);
|
|
268
|
+
return parts;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Remove comments before walking, so a comment sitting between two rules is not
|
|
272
|
+
* absorbed into the next rule's selector list. String-aware, so a literal
|
|
273
|
+
* `content: "/*"` survives.
|
|
274
|
+
*/
|
|
275
|
+
function stripComments(css) {
|
|
276
|
+
let out = "";
|
|
277
|
+
let quote = null;
|
|
278
|
+
let index = 0;
|
|
279
|
+
while (index < css.length) {
|
|
280
|
+
const char = css[index];
|
|
281
|
+
if (quote) {
|
|
282
|
+
out += char;
|
|
283
|
+
if (char === "\\" && index + 1 < css.length) {
|
|
284
|
+
out += css[index + 1];
|
|
285
|
+
index += 2;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (char === quote)
|
|
289
|
+
quote = null;
|
|
290
|
+
index += 1;
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (char === '"' || char === "'") {
|
|
294
|
+
quote = char;
|
|
295
|
+
out += char;
|
|
296
|
+
index += 1;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (char === "/" && css[index + 1] === "*") {
|
|
300
|
+
const close = css.indexOf("*/", index + 2);
|
|
301
|
+
index = close === -1 ? css.length : close + 2;
|
|
302
|
+
// Keep a space so `a/**/b` does not become the single token `ab`.
|
|
303
|
+
out += " ";
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
out += char;
|
|
307
|
+
index += 1;
|
|
308
|
+
}
|
|
309
|
+
return out;
|
|
310
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pie-players/pie-players-shared",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.62",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shared runtime + UI utilities for PIE players",
|
|
6
6
|
"license": "MIT",
|
|
@@ -64,6 +64,10 @@
|
|
|
64
64
|
"./ui/zoom-compensation": {
|
|
65
65
|
"types": "./dist/ui/zoom-compensation.d.ts",
|
|
66
66
|
"import": "./dist/ui/zoom-compensation.js"
|
|
67
|
+
},
|
|
68
|
+
"./ui/content-styles": {
|
|
69
|
+
"types": "./dist/ui/content-styles.d.ts",
|
|
70
|
+
"import": "./dist/ui/content-styles.js"
|
|
67
71
|
}
|
|
68
72
|
},
|
|
69
73
|
"files": [
|
|
@@ -72,7 +76,7 @@
|
|
|
72
76
|
"dependencies": {
|
|
73
77
|
"@pie-lib/math-rendering-module": "5.1.0",
|
|
74
78
|
"dompurify": "^3.4.0",
|
|
75
|
-
"semver": "^7.
|
|
79
|
+
"semver": "^7.8.5"
|
|
76
80
|
},
|
|
77
81
|
"scripts": {
|
|
78
82
|
"build": "rm -rf dist tsconfig.tsbuildinfo && tsc -p tsconfig.json && mkdir -p dist/components && cp -R src/components/vendor dist/components/vendor",
|
|
@@ -85,11 +89,11 @@
|
|
|
85
89
|
"scan-hardcoded": "bun run src/i18n/scripts/scan-hardcoded.ts"
|
|
86
90
|
},
|
|
87
91
|
"devDependencies": {
|
|
88
|
-
"@biomejs/biome": "^2.
|
|
89
|
-
"@happy-dom/global-registrator": "^20.
|
|
92
|
+
"@biomejs/biome": "^2.5.6",
|
|
93
|
+
"@happy-dom/global-registrator": "^20.11.1",
|
|
90
94
|
"@types/semver": "^7.7.1",
|
|
91
95
|
"glob": "^13.0.0",
|
|
92
|
-
"svelte": "^5.
|
|
96
|
+
"svelte": "^5.56.8",
|
|
93
97
|
"typescript": "^5.9.3"
|
|
94
98
|
},
|
|
95
99
|
"homepage": "https://github.com/pie-framework/pie-players/tree/master/packages/players-shared#readme",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/config/profile.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,8EAA8E;AAC9E,4EAA4E;AAC5E,MAAM,0BAA0B,GAAG,2BAA2B,CAAC;AAC/D,MAAM,0BAA0B,GAAG,GAAG,0BAA0B,WAAW,CAAC;AAS5E,SAAS,aAAa,CAAC,GAAW;IACjC,IAAI,CAAC;QACJ,sBAAsB;QACtB,MAAM,CAAC,GAAI,MAAM,CAAC,IAAY,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;QAC3C,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,CAAC,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IACV,OAAO,SAAS,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,MAAM,kBAAkB,GAC9B,CAAC,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,sBAAsB,CAAC;IAChE,aAAa,CAAC,2BAA2B,CAAC;IAC1C,0BAA0B,CAAC;AAE5B,MAAM,CAAC,MAAM,kBAAkB,GAC9B,CAAC,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,sBAAsB,CAAC;IAChE,aAAa,CAAC,2BAA2B,CAAC;IAC1C,0BAA0B,CAAC","sourcesContent":["/**\n * Minimal, frontend-safe defaults for PIE build service URLs.\n *\n * In PIEOneer, these come from `$env/dynamic/public`. In `pie-players` we avoid\n * SvelteKit env dependencies so this can be consumed by any bundler.\n */\n\n// Default public bundle host. Keep this aligned with the IIFE loader default.\n// (We prefer the proxy endpoint, which is stable/cached for browser usage.)\nconst DEFAULT_BUILDER_ORIGIN_URL = \"https://proxy.pie-api.com\";\nconst DEFAULT_BUILDER_BUNDLE_URL = `${DEFAULT_BUILDER_ORIGIN_URL}/bundles/`;\n\ndeclare global {\n\tinterface Window {\n\t\tPIE_BUILDER_ORIGIN_URL?: string;\n\t\tPIE_BUILDER_BUNDLE_URL?: string;\n\t}\n}\n\nfunction readPublicEnv(key: string): string | undefined {\n\ttry {\n\t\t// Vite/SvelteKit-like\n\t\tconst v = (import.meta as any)?.env?.[key];\n\t\tif (typeof v === \"string\" && v.length > 0) return v;\n\t} catch {}\n\treturn undefined;\n}\n\nexport const BUILDER_ORIGIN_URL =\n\t(typeof window !== \"undefined\" && window.PIE_BUILDER_ORIGIN_URL) ||\n\treadPublicEnv(\"PUBLIC_BUILDER_ORIGIN_URL\") ||\n\tDEFAULT_BUILDER_ORIGIN_URL;\n\nexport const BUILDER_BUNDLE_URL =\n\t(typeof window !== \"undefined\" && window.PIE_BUILDER_BUNDLE_URL) ||\n\treadPublicEnv(\"PUBLIC_BUILDER_BUNDLE_URL\") ||\n\tDEFAULT_BUILDER_BUNDLE_URL;\n"]}
|
package/dist/i18n/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/i18n/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACN,oBAAoB,EACpB,mBAAmB,EACnB,WAAW,EACX,gBAAgB,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["/**\n * I18n Module\n *\n * Internationalization system for PIE Players.\n *\n * @module @pie-players/pie-players-shared/i18n\n */\n\nexport {\n\tBUNDLED_TRANSLATIONS,\n\tgetAvailableLocales,\n\tisRTLLocale,\n\tloadTranslations,\n} from \"./loader.js\";\nexport { SimpleI18n } from \"./simple-i18n.js\";\nexport type {\n\tI18nConfig,\n\tI18nServiceApi,\n\tPluralTranslation,\n\tTranslationBundle,\n} from \"./types.js\";\n"]}
|