@pie-players/pie-players-shared 0.3.73 → 0.3.75
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/README.md +30 -2
- package/dist/components/vendor/nds/README.md +7 -5
- package/dist/config/profile.d.ts +0 -2
- package/dist/config/profile.js +2 -8
- package/dist/i18n/index.d.ts +2 -3
- package/dist/i18n/index.js +2 -3
- package/dist/i18n/messages/en-US.d.ts +11 -24
- package/dist/i18n/messages/en-US.js +13 -26
- package/dist/i18n/messages/nl-NL.d.ts +7 -24
- package/dist/i18n/messages/nl-NL.js +9 -26
- package/dist/i18n/provider.d.ts +4 -5
- package/dist/i18n/provider.js +4 -5
- package/dist/i18n/types.d.ts +4 -5
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/instrumentation/index.d.ts +5 -4
- package/dist/instrumentation/index.js +5 -4
- package/dist/instrumentation/new-relic-agent.d.ts +20 -0
- package/dist/instrumentation/new-relic-agent.js +24 -0
- package/dist/instrumentation/providers/BaseInstrumentationProvider.d.ts +5 -4
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.d.ts +7 -7
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.js +21 -32
- package/dist/instrumentation/types.d.ts +7 -3
- package/dist/loader-config.d.ts +16 -6
- package/dist/loaders/controller-loading.d.ts +14 -0
- package/dist/loaders/controller-loading.js +12 -0
- package/dist/loaders/defaults.d.ts +10 -0
- package/dist/loaders/defaults.js +10 -0
- package/dist/loaders/element-loader.d.ts +20 -8
- package/dist/loaders/element-loader.js +102 -36
- package/dist/loaders/element-package-policy.d.ts +2 -0
- package/dist/loaders/element-package-policy.js +2 -1
- package/dist/loaders/esm-adapter.d.ts +29 -0
- package/dist/loaders/esm-adapter.js +635 -102
- package/dist/loaders/iife-adapter.d.ts +0 -5
- package/dist/loaders/iife-adapter.js +3 -9
- package/dist/loaders/index.d.ts +12 -3
- package/dist/loaders/index.js +9 -3
- package/dist/loaders/mathjax-events.d.ts +22 -0
- package/dist/loaders/mathjax-events.js +65 -0
- package/dist/loaders/module-shim.d.ts +12 -0
- package/dist/loaders/module-shim.js +12 -0
- package/dist/loaders/preloaded-alignment.d.ts +24 -0
- package/dist/loaders/preloaded-alignment.js +57 -0
- package/dist/loaders/preloaded-authored-tags.d.ts +26 -0
- package/dist/loaders/preloaded-authored-tags.js +69 -0
- package/dist/loaders/preloaded-registration.d.ts +72 -0
- package/dist/loaders/preloaded-registration.js +141 -0
- package/dist/media/index.d.ts +69 -0
- package/dist/media/index.js +194 -0
- package/dist/pie/authoring.d.ts +1 -1
- package/dist/pie/authoring.js +25 -18
- package/dist/pie/element-announcements.d.ts +39 -0
- package/dist/pie/element-announcements.js +67 -0
- package/dist/pie/element-observer.d.ts +3 -1
- package/dist/pie/element-observer.js +1 -0
- package/dist/pie/elements-rendered.d.ts +25 -0
- package/dist/pie/elements-rendered.js +96 -0
- package/dist/pie/index.d.ts +8 -10
- package/dist/pie/index.js +9 -10
- package/dist/pie/initialization.js +13 -22
- package/dist/pie/initialize-element.d.ts +3 -0
- package/dist/pie/initialize-element.js +3 -4
- package/dist/pie/instrumentation-event-bridge.js +23 -1
- package/dist/pie/instrumentation-event-map.js +11 -25
- package/dist/pie/instrumentation-provider-resolution.d.ts +2 -1
- package/dist/pie/instrumentation-provider-resolution.js +0 -1
- package/dist/pie/item-controller.js +4 -0
- package/dist/pie/item-session-contract.d.ts +8 -0
- package/dist/pie/item-session-contract.js +30 -9
- package/dist/pie/logger.d.ts +6 -0
- package/dist/pie/logger.js +10 -0
- package/dist/pie/markup-math.d.ts +20 -0
- package/dist/pie/markup-math.js +118 -0
- package/dist/pie/math-assets.d.ts +48 -0
- package/dist/pie/math-assets.js +81 -0
- package/dist/pie/math-rendering.d.ts +10 -21
- package/dist/pie/math-rendering.js +50 -35
- package/dist/pie/registry.d.ts +13 -1
- package/dist/pie/registry.js +39 -1
- package/dist/pie/resource-monitor.d.ts +6 -33
- package/dist/pie/resource-monitor.js +14 -213
- package/dist/pie/scoring.d.ts +12 -8
- package/dist/pie/scoring.js +21 -24
- package/dist/pie/session-commit.d.ts +16 -0
- package/dist/pie/session-commit.js +34 -6
- package/dist/pie/session-snapshot.d.ts +80 -0
- package/dist/pie/session-snapshot.js +166 -0
- package/dist/pie/stages.d.ts +10 -47
- package/dist/pie/stages.js +10 -47
- package/dist/pie/tag-names.d.ts +1 -0
- package/dist/pie/tag-names.js +1 -0
- package/dist/pie/types.d.ts +4 -5
- package/dist/pie/types.js +1 -0
- package/dist/pie/updates.d.ts +2 -2
- package/dist/pie/updates.js +33 -15
- package/dist/pie/versioned-tag.d.ts +4 -0
- package/dist/pie/versioned-tag.js +4 -0
- package/dist/security/index.d.ts +2 -3
- package/dist/security/index.js +2 -3
- package/dist/security/sanitize-item-markup.d.ts +4 -2
- package/dist/security/sanitize-item-markup.js +48 -6
- package/dist/security/sanitize-svg-icon.d.ts +0 -2
- package/dist/security/sanitize-svg-icon.js +0 -5
- package/dist/security/wrap-overwide-images.js +19 -0
- package/dist/security/wrap-overwide.d.ts +13 -0
- package/dist/security/wrap-overwide.js +63 -3
- package/dist/timed-media/policy.js +5 -16
- package/dist/types/index.d.ts +67 -423
- package/dist/types/index.js +0 -68
- package/dist/ui/attribute-coercion.d.ts +12 -0
- package/dist/ui/attribute-coercion.js +33 -0
- package/dist/ui/content-styles.d.ts +18 -16
- package/dist/ui/content-styles.js +67 -36
- package/dist/ui/overlay-containment.d.ts +31 -0
- package/dist/ui/overlay-containment.js +63 -0
- package/dist/ui/overlay-placement.d.ts +110 -0
- package/dist/ui/overlay-placement.js +222 -0
- package/dist/ui/pointer-gesture.d.ts +32 -0
- package/dist/ui/pointer-gesture.js +56 -0
- package/dist/ui/pointer-rotate.d.ts +29 -0
- package/dist/ui/pointer-rotate.js +39 -0
- package/package.json +23 -18
- package/dist/pie/component-context.d.ts +0 -21
- package/dist/pie/component-context.js +0 -29
- package/dist/pie/player-initializer.d.ts +0 -54
- package/dist/pie/player-initializer.js +0 -122
- package/dist/pie/stage-tracker.d.ts +0 -50
- package/dist/pie/stage-tracker.js +0 -105
- package/dist/security/wrap-model-rich-content.d.ts +0 -1
- package/dist/security/wrap-model-rich-content.js +0 -40
- package/dist/server/npm-auth-env.d.ts +0 -13
- package/dist/server/npm-auth-env.js +0 -64
- package/dist/server/npm-registry.d.ts +0 -7
- package/dist/server/npm-registry.js +0 -59
- package/dist/ui/debug-panel-persistence.d.ts +0 -17
- package/dist/ui/debug-panel-persistence.js +0 -37
|
@@ -17,3 +17,36 @@ export function coerceBooleanLike(value, defaultValue = false) {
|
|
|
17
17
|
}
|
|
18
18
|
return Boolean(normalizedValue);
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Reads a boolean attribute value: present is true and absent is false, as in
|
|
22
|
+
* HTML, except that the words `coerceBooleanLike` reads as false stay false.
|
|
23
|
+
*/
|
|
24
|
+
export function coerceBooleanAttribute(value) {
|
|
25
|
+
return value !== null && coerceBooleanLike(value, true);
|
|
26
|
+
}
|
|
27
|
+
const isBooleanProp = (element, attribute) => {
|
|
28
|
+
const definitions = element.$$p_d;
|
|
29
|
+
if (!definitions)
|
|
30
|
+
return false;
|
|
31
|
+
return Object.entries(definitions).some(([prop, definition]) => definition.type === "Boolean" &&
|
|
32
|
+
(definition.attribute ?? prop.toLowerCase()) === attribute);
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* `customElement.extend` hook for a Svelte element with `type: "Boolean"`
|
|
36
|
+
* props. Svelte reads those by presence, so `show-bottom-border="false"` sets
|
|
37
|
+
* the prop to `true`; this passes Svelte a removed attribute whenever
|
|
38
|
+
* `coerceBooleanAttribute` reads the value as false.
|
|
39
|
+
*/
|
|
40
|
+
export function coerceBooleanAttributes(ElementClass) {
|
|
41
|
+
const baseCallback = ElementClass.prototype.attributeChangedCallback;
|
|
42
|
+
return class extends ElementClass {
|
|
43
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
44
|
+
const value = newValue !== null &&
|
|
45
|
+
isBooleanProp(this, name) &&
|
|
46
|
+
!coerceBooleanAttribute(newValue)
|
|
47
|
+
? null
|
|
48
|
+
: newValue;
|
|
49
|
+
baseCallback?.call(this, name, oldValue, value);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* plain `tsc`, so it cannot inline a stylesheet. Bundler-built player packages
|
|
20
20
|
* import it with Vite's `?raw` and hand the text over.
|
|
21
21
|
*/
|
|
22
|
-
export type ContentStylesResult = "installed" | "already-installed" | "opted-out" | "no-document";
|
|
22
|
+
export type ContentStylesResult = "installed" | "already-installed" | "host-supplied" | "opted-out" | "no-document";
|
|
23
23
|
/**
|
|
24
24
|
* True when the host declared `<html data-pie-content-styles="host">`, i.e. it
|
|
25
25
|
* takes ownership of loading the stylesheet.
|
|
@@ -42,7 +42,16 @@ export declare function contentStylesOptedOut(): boolean;
|
|
|
42
42
|
*/
|
|
43
43
|
export declare function contentStylesPresent(): boolean;
|
|
44
44
|
/**
|
|
45
|
-
* Installs `cssText` as a document-level stylesheet, once per document
|
|
45
|
+
* Installs `cssText` as a document-level stylesheet, once per document, unless
|
|
46
|
+
* the host already supplies one.
|
|
47
|
+
*
|
|
48
|
+
* A host copy is recognised by the sentinel wherever it sits, including one
|
|
49
|
+
* confined to the host's player subtree with `@scope`. Such a host scoped its
|
|
50
|
+
* copy because the stylesheet's bare element selectors would otherwise reach
|
|
51
|
+
* its own UI, so a global copy from the player would reintroduce exactly that.
|
|
52
|
+
* A host copy can also land after this runs — a lazily injected `<style>`, a
|
|
53
|
+
* `<link>` still loading — so the installed copy keeps watching and removes
|
|
54
|
+
* itself when one appears, as it does when the host sets the opt-out late.
|
|
46
55
|
*
|
|
47
56
|
* The stylesheet is **prepended** to `<head>` and deliberately left out of a
|
|
48
57
|
* cascade layer. Prepending reproduces the placement hosts were told to use by
|
|
@@ -60,22 +69,15 @@ export declare function contentStylesPresent(): boolean;
|
|
|
60
69
|
*/
|
|
61
70
|
export declare function installContentStyles(cssText: string, source: string): ContentStylesResult;
|
|
62
71
|
/**
|
|
63
|
-
* Reports, once per page,
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* - **Missing**: the host opted out and then loaded nothing, so authored content
|
|
68
|
-
* renders unstyled. This is the failure the old host-import contract produced
|
|
69
|
-
* silently.
|
|
70
|
-
* - **Duplicated**: the host still imports `components.css` itself *and* the
|
|
71
|
-
* player installed a copy. Rendering is correct while the two agree, but the
|
|
72
|
-
* host's copy loads later and therefore wins ties at equal specificity — so a
|
|
73
|
-
* host copy pinned to an older `@pie-players/pie-theme` silently overrides the
|
|
74
|
-
* player's newer rules. Harmless today, a confusing override tomorrow.
|
|
72
|
+
* Reports, once per page, a host that opted out and then loaded no content
|
|
73
|
+
* stylesheet, so authored content renders unstyled — the failure the old
|
|
74
|
+
* host-import contract produced silently. Not detectable synchronously — a host
|
|
75
|
+
* copy may still be in flight as an async `<link>` — so the check is deferred.
|
|
75
76
|
*/
|
|
76
77
|
export declare function auditContentStyles(source: string): void;
|
|
77
78
|
/**
|
|
78
|
-
* Test-only: clears the once-per-page warning latch
|
|
79
|
-
* deferred check
|
|
79
|
+
* Test-only: clears the once-per-page warning latch, cancels any pending
|
|
80
|
+
* deferred check and stops watching for a host copy, so state from one test
|
|
81
|
+
* cannot act during the next.
|
|
80
82
|
*/
|
|
81
83
|
export declare function resetContentStylesWarningForTesting(): void;
|
|
@@ -23,9 +23,8 @@
|
|
|
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
|
-
*
|
|
28
|
-
* sitting alongside ours.
|
|
26
|
+
* stylesheet arrived — our injection or a host import. A host copy carrying it
|
|
27
|
+
* makes the player stand down, and an opted-out host with no copy is warned.
|
|
29
28
|
*/
|
|
30
29
|
const SENTINEL_PROPERTY = "--pie-content-styles";
|
|
31
30
|
// Svelte's dev-mode custom-element reset expands `all: unset` into individual
|
|
@@ -100,7 +99,16 @@ export function contentStylesPresent() {
|
|
|
100
99
|
return countContentStyleSheets({ excludeInstalled: false }) > 0;
|
|
101
100
|
}
|
|
102
101
|
/**
|
|
103
|
-
* Installs `cssText` as a document-level stylesheet, once per document
|
|
102
|
+
* Installs `cssText` as a document-level stylesheet, once per document, unless
|
|
103
|
+
* the host already supplies one.
|
|
104
|
+
*
|
|
105
|
+
* A host copy is recognised by the sentinel wherever it sits, including one
|
|
106
|
+
* confined to the host's player subtree with `@scope`. Such a host scoped its
|
|
107
|
+
* copy because the stylesheet's bare element selectors would otherwise reach
|
|
108
|
+
* its own UI, so a global copy from the player would reintroduce exactly that.
|
|
109
|
+
* A host copy can also land after this runs — a lazily injected `<style>`, a
|
|
110
|
+
* `<link>` still loading — so the installed copy keeps watching and removes
|
|
111
|
+
* itself when one appears, as it does when the host sets the opt-out late.
|
|
104
112
|
*
|
|
105
113
|
* The stylesheet is **prepended** to `<head>` and deliberately left out of a
|
|
106
114
|
* cascade layer. Prepending reproduces the placement hosts were told to use by
|
|
@@ -126,14 +134,58 @@ export function installContentStyles(cssText, source) {
|
|
|
126
134
|
// the same stylesheet. Duplicating it would be harmless but pointless.
|
|
127
135
|
return "already-installed";
|
|
128
136
|
}
|
|
137
|
+
if (countHostContentStyleSheets() > 0)
|
|
138
|
+
return "host-supplied";
|
|
129
139
|
if (!cssText)
|
|
130
140
|
return "no-document";
|
|
131
141
|
const style = document.createElement("style");
|
|
132
142
|
style.setAttribute(MARKER_ATTRIBUTE, source);
|
|
133
143
|
style.textContent = cssText;
|
|
134
144
|
document.head.prepend(style);
|
|
145
|
+
watchForHostOwnership(style);
|
|
135
146
|
return "installed";
|
|
136
147
|
}
|
|
148
|
+
let stopWatchingHostOwnership = null;
|
|
149
|
+
const watchForHostOwnership = (installed) => {
|
|
150
|
+
stopWatchingHostOwnership?.();
|
|
151
|
+
if (typeof MutationObserver === "undefined")
|
|
152
|
+
return;
|
|
153
|
+
const stop = () => {
|
|
154
|
+
observer.disconnect();
|
|
155
|
+
document.removeEventListener("load", onLoad, true);
|
|
156
|
+
if (stopWatchingHostOwnership === stop)
|
|
157
|
+
stopWatchingHostOwnership = null;
|
|
158
|
+
};
|
|
159
|
+
const reconcile = () => {
|
|
160
|
+
if (!installed.isConnected) {
|
|
161
|
+
stop();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (contentStylesOptedOut() || countHostContentStyleSheets() > 0) {
|
|
165
|
+
installed.remove();
|
|
166
|
+
stop();
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
const observer = new MutationObserver((records) => {
|
|
170
|
+
const relevant = records.some((record) => record.type === "attributes" ||
|
|
171
|
+
Array.from(record.addedNodes).some((node) => node.nodeName === "STYLE" || node.nodeName === "LINK"));
|
|
172
|
+
if (relevant)
|
|
173
|
+
reconcile();
|
|
174
|
+
});
|
|
175
|
+
// `load` does not bubble, so a capturing listener is what sees a host
|
|
176
|
+
// `<link>` finish, including one already in flight when this runs.
|
|
177
|
+
const onLoad = (event) => {
|
|
178
|
+
if (event.target?.nodeName === "LINK")
|
|
179
|
+
reconcile();
|
|
180
|
+
};
|
|
181
|
+
observer.observe(document.head, { childList: true });
|
|
182
|
+
observer.observe(document.documentElement, {
|
|
183
|
+
attributes: true,
|
|
184
|
+
attributeFilter: [OPT_OUT_ATTRIBUTE],
|
|
185
|
+
});
|
|
186
|
+
document.addEventListener("load", onLoad, true);
|
|
187
|
+
stopWatchingHostOwnership = stop;
|
|
188
|
+
};
|
|
137
189
|
/**
|
|
138
190
|
* Counts content stylesheets in the document, detected by the sentinel property
|
|
139
191
|
* rather than by URL, so a copy arriving as a `<link>`, a bundler-injected
|
|
@@ -141,8 +193,8 @@ export function installContentStyles(cssText, source) {
|
|
|
141
193
|
* count to copies the host loaded itself.
|
|
142
194
|
*
|
|
143
195
|
* Cross-origin sheets throw on `cssRules` access and are skipped; a host copy
|
|
144
|
-
* served from another origin therefore reads as absent
|
|
145
|
-
*
|
|
196
|
+
* served from another origin therefore reads as absent, and the player installs
|
|
197
|
+
* its own copy alongside it. Such a host sets the opt-out attribute instead.
|
|
146
198
|
*/
|
|
147
199
|
const countContentStyleSheets = ({ excludeInstalled, }) => {
|
|
148
200
|
// Walks the owning elements rather than document.styleSheets: the marker
|
|
@@ -176,18 +228,10 @@ const countHostContentStyleSheets = () => countContentStyleSheets({ excludeInsta
|
|
|
176
228
|
let auditWarningIssued = false;
|
|
177
229
|
const pendingChecks = [];
|
|
178
230
|
/**
|
|
179
|
-
* Reports, once per page,
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
* - **Missing**: the host opted out and then loaded nothing, so authored content
|
|
184
|
-
* renders unstyled. This is the failure the old host-import contract produced
|
|
185
|
-
* silently.
|
|
186
|
-
* - **Duplicated**: the host still imports `components.css` itself *and* the
|
|
187
|
-
* player installed a copy. Rendering is correct while the two agree, but the
|
|
188
|
-
* host's copy loads later and therefore wins ties at equal specificity — so a
|
|
189
|
-
* host copy pinned to an older `@pie-players/pie-theme` silently overrides the
|
|
190
|
-
* player's newer rules. Harmless today, a confusing override tomorrow.
|
|
231
|
+
* Reports, once per page, a host that opted out and then loaded no content
|
|
232
|
+
* stylesheet, so authored content renders unstyled — the failure the old
|
|
233
|
+
* host-import contract produced silently. Not detectable synchronously — a host
|
|
234
|
+
* copy may still be in flight as an async `<link>` — so the check is deferred.
|
|
191
235
|
*/
|
|
192
236
|
export function auditContentStyles(source) {
|
|
193
237
|
if (!isBrowser() || auditWarningIssued)
|
|
@@ -195,9 +239,7 @@ export function auditContentStyles(source) {
|
|
|
195
239
|
const check = () => {
|
|
196
240
|
if (auditWarningIssued)
|
|
197
241
|
return;
|
|
198
|
-
if (contentStylesOptedOut()) {
|
|
199
|
-
if (contentStylesPresent())
|
|
200
|
-
return;
|
|
242
|
+
if (contentStylesOptedOut() && !contentStylesPresent()) {
|
|
201
243
|
auditWarningIssued = true;
|
|
202
244
|
console.warn(`[${source}] No PIE content stylesheet found. This document sets ` +
|
|
203
245
|
`${OPT_OUT_ATTRIBUTE}="${OPT_OUT_VALUE}", so ${source} did not install ` +
|
|
@@ -206,19 +248,6 @@ export function auditContentStyles(source) {
|
|
|
206
248
|
`families, answer-eliminator styles) will render unstyled. Either ` +
|
|
207
249
|
`import "@pie-players/pie-theme/components.css" in the host app, or ` +
|
|
208
250
|
`drop the ${OPT_OUT_ATTRIBUTE} attribute to let the player install it.`);
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
if (countHostContentStyleSheets() > 0) {
|
|
212
|
-
auditWarningIssued = true;
|
|
213
|
-
console.warn(`[${source}] The PIE content stylesheet is loaded twice: ${source} ` +
|
|
214
|
-
`installs it, and this host also imports ` +
|
|
215
|
-
`"@pie-players/pie-theme/components.css". Rendering is unaffected ` +
|
|
216
|
-
`while both copies match, but the host copy loads later and wins ` +
|
|
217
|
-
`ties at equal specificity, so a copy pinned to an older ` +
|
|
218
|
-
`@pie-players/pie-theme will silently override newer player styles. ` +
|
|
219
|
-
`Remove the host import, or set ` +
|
|
220
|
-
`${OPT_OUT_ATTRIBUTE}="${OPT_OUT_VALUE}" on <html> to own the ` +
|
|
221
|
-
`stylesheet deliberately.`);
|
|
222
251
|
}
|
|
223
252
|
};
|
|
224
253
|
// A host stylesheet can land well after the module graph evaluates, and a
|
|
@@ -240,10 +269,12 @@ export function auditContentStyles(source) {
|
|
|
240
269
|
}
|
|
241
270
|
}
|
|
242
271
|
/**
|
|
243
|
-
* Test-only: clears the once-per-page warning latch
|
|
244
|
-
* deferred check
|
|
272
|
+
* Test-only: clears the once-per-page warning latch, cancels any pending
|
|
273
|
+
* deferred check and stops watching for a host copy, so state from one test
|
|
274
|
+
* cannot act during the next.
|
|
245
275
|
*/
|
|
246
276
|
export function resetContentStylesWarningForTesting() {
|
|
277
|
+
stopWatchingHostOwnership?.();
|
|
247
278
|
auditWarningIssued = false;
|
|
248
279
|
for (const handle of pendingChecks)
|
|
249
280
|
clearTimeout(handle);
|
|
@@ -36,6 +36,37 @@ export declare function resolveContainingBlockRect(element: HTMLElement | undefi
|
|
|
36
36
|
* than clamping to an inverted range.
|
|
37
37
|
*/
|
|
38
38
|
export declare function clampOffsetWithinBlock(offset: Point, box: Size, block: Size, gutter?: number): Point;
|
|
39
|
+
/**
|
|
40
|
+
* Clamps a translate offset applied to a box centred in its containing block so
|
|
41
|
+
* that at least `visible` pixels of the box stay inside the block on each axis.
|
|
42
|
+
*
|
|
43
|
+
* For an overlay that has to overhang its block to be useful — a ruler lined up
|
|
44
|
+
* against content near a card's edge — without ever being dragged out of reach
|
|
45
|
+
* behind that edge. A box or block narrower than `visible` keeps all of the
|
|
46
|
+
* smaller extent.
|
|
47
|
+
*/
|
|
48
|
+
export declare function clampOffsetOverlappingBlock(offset: Point, box: Size, block: Size, visible: number): Point;
|
|
49
|
+
/** The axis-aligned extent of a `size` box turned by `degrees` about its centre. */
|
|
50
|
+
export declare function rotatedExtent(size: Size, degrees: number): Size;
|
|
51
|
+
/**
|
|
52
|
+
* How far a `size` box's centre moves when the box turns by `degrees` about
|
|
53
|
+
* `origin`, given in the box's own coordinates from its top-left corner.
|
|
54
|
+
*
|
|
55
|
+
* Zero for a box turned about its centre. An overlay turned about another
|
|
56
|
+
* point adds this to its translate offset to get the offset of its rotated
|
|
57
|
+
* extent's centre, which is what the centred clamps above bound.
|
|
58
|
+
*/
|
|
59
|
+
export declare function rotationCentreShift(size: Size, origin: Point, degrees: number): Point;
|
|
60
|
+
/**
|
|
61
|
+
* Where to centre a control strip docked past the bottom edge of a `box`
|
|
62
|
+
* turned by `degrees`, in the box's own coordinates from its top-left corner.
|
|
63
|
+
*
|
|
64
|
+
* The strip is drawn counter-rotated so it stays level on screen. Its reach
|
|
65
|
+
* along the box's downward normal is its projection onto that normal, which
|
|
66
|
+
* grows from its height at no turn to its width at a quarter turn, so a strip
|
|
67
|
+
* placed that far out clears the box at any rotation.
|
|
68
|
+
*/
|
|
69
|
+
export declare function uprightDockCentre(box: Size, strip: Size, degrees: number, gap: number): Point;
|
|
39
70
|
/**
|
|
40
71
|
* Clamps an absolute centre point, in containing-block coordinates, so the box
|
|
41
72
|
* it positions stays inside that block.
|
|
@@ -49,6 +49,69 @@ export function clampOffsetWithinBlock(offset, box, block, gutter = DEFAULT_CONT
|
|
|
49
49
|
y: clampAxis(offset.y, box.height, block.height),
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Clamps a translate offset applied to a box centred in its containing block so
|
|
54
|
+
* that at least `visible` pixels of the box stay inside the block on each axis.
|
|
55
|
+
*
|
|
56
|
+
* For an overlay that has to overhang its block to be useful — a ruler lined up
|
|
57
|
+
* against content near a card's edge — without ever being dragged out of reach
|
|
58
|
+
* behind that edge. A box or block narrower than `visible` keeps all of the
|
|
59
|
+
* smaller extent.
|
|
60
|
+
*/
|
|
61
|
+
export function clampOffsetOverlappingBlock(offset, box, block, visible) {
|
|
62
|
+
const clampAxis = (value, boxExtent, blockExtent) => {
|
|
63
|
+
const keep = Math.min(visible, boxExtent, blockExtent);
|
|
64
|
+
const travel = blockExtent / 2 + boxExtent / 2 - keep;
|
|
65
|
+
return Math.max(-travel, Math.min(travel, value));
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
x: clampAxis(offset.x, box.width, block.width),
|
|
69
|
+
y: clampAxis(offset.y, box.height, block.height),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** The axis-aligned extent of a `size` box turned by `degrees` about its centre. */
|
|
73
|
+
export function rotatedExtent(size, degrees) {
|
|
74
|
+
const radians = (degrees * Math.PI) / 180;
|
|
75
|
+
const cos = Math.abs(Math.cos(radians));
|
|
76
|
+
const sin = Math.abs(Math.sin(radians));
|
|
77
|
+
return {
|
|
78
|
+
width: size.width * cos + size.height * sin,
|
|
79
|
+
height: size.width * sin + size.height * cos,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* How far a `size` box's centre moves when the box turns by `degrees` about
|
|
84
|
+
* `origin`, given in the box's own coordinates from its top-left corner.
|
|
85
|
+
*
|
|
86
|
+
* Zero for a box turned about its centre. An overlay turned about another
|
|
87
|
+
* point adds this to its translate offset to get the offset of its rotated
|
|
88
|
+
* extent's centre, which is what the centred clamps above bound.
|
|
89
|
+
*/
|
|
90
|
+
export function rotationCentreShift(size, origin, degrees) {
|
|
91
|
+
const radians = (degrees * Math.PI) / 180;
|
|
92
|
+
const cos = Math.cos(radians);
|
|
93
|
+
const sin = Math.sin(radians);
|
|
94
|
+
const dx = size.width / 2 - origin.x;
|
|
95
|
+
const dy = size.height / 2 - origin.y;
|
|
96
|
+
return {
|
|
97
|
+
x: dx * cos - dy * sin - dx,
|
|
98
|
+
y: dx * sin + dy * cos - dy,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Where to centre a control strip docked past the bottom edge of a `box`
|
|
103
|
+
* turned by `degrees`, in the box's own coordinates from its top-left corner.
|
|
104
|
+
*
|
|
105
|
+
* The strip is drawn counter-rotated so it stays level on screen. Its reach
|
|
106
|
+
* along the box's downward normal is its projection onto that normal, which
|
|
107
|
+
* grows from its height at no turn to its width at a quarter turn, so a strip
|
|
108
|
+
* placed that far out clears the box at any rotation.
|
|
109
|
+
*/
|
|
110
|
+
export function uprightDockCentre(box, strip, degrees, gap) {
|
|
111
|
+
const radians = (degrees * Math.PI) / 180;
|
|
112
|
+
const reach = strip.width * Math.abs(Math.sin(radians)) + strip.height * Math.abs(Math.cos(radians));
|
|
113
|
+
return { x: box.width / 2, y: box.height + gap + reach / 2 };
|
|
114
|
+
}
|
|
52
115
|
/**
|
|
53
116
|
* Clamps an absolute centre point, in containing-block coordinates, so the box
|
|
54
117
|
* it positions stays inside that block.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Placement of a tool overlay the learner moves and turns: the ruler and the
|
|
3
|
+
* protractor. One controller holds where the tool sits and is the only writer of
|
|
4
|
+
* its `style.transform`, so a drag, an arrow key and a tap control always agree
|
|
5
|
+
* about where the tool is.
|
|
6
|
+
*/
|
|
7
|
+
import { type Point, type Size } from "./overlay-containment.js";
|
|
8
|
+
/** An offset from the tool's centred position, and its turn about its pivot. */
|
|
9
|
+
export interface OverlayPlacement {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
rotation: number;
|
|
13
|
+
}
|
|
14
|
+
/** Pixels an arrow key or move button shifts the tool. */
|
|
15
|
+
export declare const OVERLAY_PLACEMENT_MOVE_STEP = 10;
|
|
16
|
+
/** Degrees Shift+arrow turns the tool. */
|
|
17
|
+
export declare const OVERLAY_PLACEMENT_ROTATE_STEP = 5;
|
|
18
|
+
/** Degrees Page Up/Down turns the tool. */
|
|
19
|
+
export declare const OVERLAY_PLACEMENT_FINE_ROTATE_STEP = 1;
|
|
20
|
+
/** Each move, with its tap control's glyph and label and its announcement. */
|
|
21
|
+
export declare const OVERLAY_PLACEMENT_NUDGES: {
|
|
22
|
+
readonly left: {
|
|
23
|
+
readonly dx: -1;
|
|
24
|
+
readonly dy: 0;
|
|
25
|
+
readonly glyph: "←";
|
|
26
|
+
readonly labelKey: "toolkit.window.moveLeftA11y";
|
|
27
|
+
readonly announceKey: "toolkit.announce.movedLeft";
|
|
28
|
+
};
|
|
29
|
+
readonly up: {
|
|
30
|
+
readonly dx: 0;
|
|
31
|
+
readonly dy: -1;
|
|
32
|
+
readonly glyph: "↑";
|
|
33
|
+
readonly labelKey: "toolkit.window.moveUpA11y";
|
|
34
|
+
readonly announceKey: "toolkit.announce.movedUp";
|
|
35
|
+
};
|
|
36
|
+
readonly down: {
|
|
37
|
+
readonly dx: 0;
|
|
38
|
+
readonly dy: 1;
|
|
39
|
+
readonly glyph: "↓";
|
|
40
|
+
readonly labelKey: "toolkit.window.moveDownA11y";
|
|
41
|
+
readonly announceKey: "toolkit.announce.movedDown";
|
|
42
|
+
};
|
|
43
|
+
readonly right: {
|
|
44
|
+
readonly dx: 1;
|
|
45
|
+
readonly dy: 0;
|
|
46
|
+
readonly glyph: "→";
|
|
47
|
+
readonly labelKey: "toolkit.window.moveRightA11y";
|
|
48
|
+
readonly announceKey: "toolkit.announce.movedRight";
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
export type OverlayPlacementNudge = keyof typeof OVERLAY_PLACEMENT_NUDGES;
|
|
52
|
+
/** The interface-catalog keys a move or turn is announced with. */
|
|
53
|
+
export type OverlayPlacementAnnouncement = (typeof OVERLAY_PLACEMENT_NUDGES)[OverlayPlacementNudge]["announceKey"] | "toolkit.announce.rotatedTo";
|
|
54
|
+
/** The tap controls' rotations, the keyboard's two steps each way. */
|
|
55
|
+
export declare const OVERLAY_PLACEMENT_ROTATIONS: readonly [number, number, 1, 5];
|
|
56
|
+
export interface OverlayPlacementOptions {
|
|
57
|
+
/** The tool's positioned element, which receives the transform. */
|
|
58
|
+
getElement: () => HTMLElement | undefined;
|
|
59
|
+
/** The tap controls, docked level past the tool's bottom edge. */
|
|
60
|
+
getControls: () => HTMLElement | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* The point the tool turns about, from its top-left corner at no turn.
|
|
63
|
+
* Defaults to its centre, and must match the stylesheet's `transform-origin`.
|
|
64
|
+
*/
|
|
65
|
+
pivot?: (size: Size) => Point;
|
|
66
|
+
/**
|
|
67
|
+
* That pivot on screen when a pointer rotation starts, or `undefined` to
|
|
68
|
+
* decline it. Defaults to the centre of the tool's bounding box, which a turn
|
|
69
|
+
* about the centre leaves in place.
|
|
70
|
+
*/
|
|
71
|
+
getScreenPivot?: () => Point | undefined;
|
|
72
|
+
/** Raises the tool above its peers when a gesture starts or it is shown. */
|
|
73
|
+
bringToFront?: (element: HTMLElement) => void;
|
|
74
|
+
/** Speaks a move or turn; the tool resolves the key in its interface catalog. */
|
|
75
|
+
announce: (key: OverlayPlacementAnnouncement, params: Record<string, number>) => void;
|
|
76
|
+
}
|
|
77
|
+
export interface OverlayPlacementController {
|
|
78
|
+
readonly placement: OverlayPlacement;
|
|
79
|
+
/**
|
|
80
|
+
* Writes `next` to `style.transform`, first keeping part of the turned tool
|
|
81
|
+
* inside the box it is positioned against. The bound lets the tool overhang:
|
|
82
|
+
* an item card is barely wider than a ruler, and full containment would pin
|
|
83
|
+
* its zero mark to the card's edge. The card clips what overhangs, and 100px
|
|
84
|
+
* stays in view to grab it back.
|
|
85
|
+
*/
|
|
86
|
+
apply: (next: OverlayPlacement) => void;
|
|
87
|
+
/** Raises the tool and re-applies its placement, as a fresh reveal does. */
|
|
88
|
+
show: () => void;
|
|
89
|
+
/** Ends any gesture and returns the tool to its centred, upright position. */
|
|
90
|
+
reset: () => void;
|
|
91
|
+
/** Starts a drag from a press on the tool. */
|
|
92
|
+
startDrag: (event: PointerEvent) => void;
|
|
93
|
+
/** Starts a turn from a press on `handle`, which must not also start a drag. */
|
|
94
|
+
startRotate: (event: PointerEvent, handle: HTMLElement) => void;
|
|
95
|
+
/** Moves the tool one step, as an arrow key or a move button does. */
|
|
96
|
+
nudge: (direction: OverlayPlacementNudge) => void;
|
|
97
|
+
/** Turns the tool by `degrees`, clockwise when positive, to a whole degree. */
|
|
98
|
+
rotateBy: (degrees: number) => void;
|
|
99
|
+
/**
|
|
100
|
+
* Arrows move, Shift+arrows turn 5°, Page Up/Down turn 1°. Returns whether
|
|
101
|
+
* it handled the key, having prevented its default.
|
|
102
|
+
*/
|
|
103
|
+
handleKeyDown: (event: KeyboardEvent) => boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Re-applies the placement on window resize, which can leave the tool outside
|
|
106
|
+
* its shrunken containing block. Returns a cleanup that also ends any gesture.
|
|
107
|
+
*/
|
|
108
|
+
connect: () => () => void;
|
|
109
|
+
}
|
|
110
|
+
export declare function createOverlayPlacement(options: OverlayPlacementOptions): OverlayPlacementController;
|