@pie-players/pie-players-shared 1.0.0-beta.7 → 1.0.0-beta.8

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.
@@ -1,7 +1,9 @@
1
1
  /**
2
- * Language-variant interchange types (PIE-1393). Backends and authoring tools
3
- * exchange them; no player reads `AdaptationLink` or `LanguagePolicy`.
2
+ * Interchange types: backends and authoring tools exchange them, and no player
3
+ * reads them. Their tier is listed on the content-model page.
4
4
  *
5
+ * Language variants (PIE-1393): no player reads `AdaptationLink` or
6
+ * `LanguagePolicy`.
5
7
  * A language variant is its own item or passage version that declares
6
8
  * `language` and, when it is a translation, `isAdaptationOf`. Its family is
7
9
  * `isAdaptationOf.baseId ?? baseId`.
@@ -40,8 +42,13 @@ export interface LanguagePolicy {
40
42
  */
41
43
  staleness: "serve" | "require-current";
42
44
  }
43
- /** Why the served content differs from what was requested. */
44
- export type ContentSubstitution = "fallback" | "stale";
45
+ /**
46
+ * Why the served content differs from what was requested: `"fallback"`, not
47
+ * in a requested language; `"variety"`, in another variety of a requested
48
+ * language, such as `es-ES` for `es-MX`; `"stale"`, a translation of an older
49
+ * source release.
50
+ */
51
+ export type ContentSubstitution = "fallback" | "stale" | "variety";
45
52
  /** The content version a backend served, and the language it served it in. */
46
53
  export interface ServedContentIdentity {
47
54
  baseId: string;
@@ -52,3 +59,58 @@ export interface ServedContentIdentity {
52
59
  servedLanguage?: string;
53
60
  substitution?: ContentSubstitution;
54
61
  }
62
+ /**
63
+ * One curriculum standard, skill or competency the content is aligned to
64
+ * (PIE-1381). QTI 3 Curriculum Standards Metadata (CSM 1.1), one entry per
65
+ * `labelledGUID`. Content records direct links only; a framework provider
66
+ * resolves parents, crosswalks and successors on read.
67
+ */
68
+ export interface CurriculumStandard {
69
+ /**
70
+ * CSM `curriculumStandardsMetadata@providerId`: who issued `guid`, from the
71
+ * provider-id registry on the content-model page, or a host value. Absent
72
+ * means `"AB"` (Academic Benchmarks).
73
+ */
74
+ providerId?: string;
75
+ /**
76
+ * CSM `labelledGUID/GUID`, as the provider issued it. Stored with its case
77
+ * and compared case-insensitively.
78
+ */
79
+ guid: string;
80
+ /** CSM `labelledGUID/caseItemURI`, for a CASE framework item. */
81
+ caseItemURI?: string;
82
+ /** CSM `labelledGUID/label`: a display code. Never a key. */
83
+ label?: string;
84
+ /** LOM classification purpose. Absent means `"educational objective"`. */
85
+ purpose?: "educational objective" | "prerequisite" | (string & {});
86
+ /** The record's main standard for this provider. At most one per `providerId`. */
87
+ primary?: boolean;
88
+ /** How the link was made. */
89
+ provenance?: "authored" | "derived" | "imported";
90
+ /**
91
+ * For a derived link: the link it was generated from. An absent
92
+ * `providerId` means `"AB"`.
93
+ */
94
+ derivedFrom?: {
95
+ providerId?: string;
96
+ guid: string;
97
+ };
98
+ }
99
+ /**
100
+ * A cognitive-complexity classification of the content (PIE-1381). LOM
101
+ * classification purpose "skill level".
102
+ */
103
+ export interface SkillLevel {
104
+ /**
105
+ * LOM `taxonPath/source`: `"webb-dok"`, `"blooms-revised"`,
106
+ * `"blooms-original"`, or a host taxonomy.
107
+ */
108
+ source: string;
109
+ /**
110
+ * LOM `taxon/id`: `"1"` to `"4"` for `webb-dok`; `"remember"` to `"create"`
111
+ * for `blooms-revised`.
112
+ */
113
+ id: string;
114
+ /** PIE extension: the author's justification. */
115
+ rationale?: string;
116
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * FontAwesome for `<nds-icon-button>`, which renders its glyph as
3
+ * `<i class="fa-light fa-${name}">` and ships no font of its own.
4
+ *
5
+ * FontAwesome comes from the host page when it links any FA stylesheet.
6
+ * Otherwise `ensureFontAwesome` adds FA Free from the ESM CDN the item player's
7
+ * `loaderOptions` name, jsDelivr by default. Pro is never fetched from here:
8
+ * ui.renaissance.com serves its font files to Renaissance origins only, so a
9
+ * host that licenses it links it itself, as section-demos does through its dev
10
+ * proxy.
11
+ *
12
+ * `fa-light` is a weight only Pro carries. Free has no `.fa-light` rule and its
13
+ * Regular font lacks most toolbar glyphs, so without Pro `applyNdsGlyphVariant`
14
+ * renders every glyph in Solid, the one Free weight that has them all.
15
+ *
16
+ * Roboto comes from the vendored button: its connectedCallback links
17
+ * ui.renaissance.com's Roboto unless the page links a stylesheet whose URL
18
+ * contains `Roboto`.
19
+ */
20
+ export type NdsGlyphVariant = "fa-light" | "fa-regular" | "fa-solid";
21
+ /**
22
+ * Adds FA Free to `<head>` unless the page already links a FontAwesome
23
+ * stylesheet. Appending Free after a host's Pro would override Pro's
24
+ * `.fa-regular` / `.fa-solid` font-family rules page-wide.
25
+ *
26
+ * A stylesheet that fails to load stays in `<head>` marked
27
+ * `data-pie-load-failed`: no later call requests it again, and no shadow root
28
+ * copies it.
29
+ */
30
+ export declare function ensureFontAwesome(loaderOptions: {
31
+ esmCdnUrl?: unknown;
32
+ esmCdnProvider?: unknown;
33
+ } | null | undefined): void;
34
+ /**
35
+ * Copies the page's FontAwesome `<link>`s into the shadow root `node` renders
36
+ * in, since `<head>` stylesheets do not cross a shadow boundary. Does nothing
37
+ * when `node` is in the document tree, and runs once per shadow root.
38
+ *
39
+ * A stylesheet still loading is copied once it loads, so the copy comes from
40
+ * the cache; one that fails is not copied, since every copy would request it
41
+ * again. Chromium gives a link a `sheet` even when its load fails, so a failed
42
+ * one is known by its mark.
43
+ */
44
+ export declare function installFontAwesomeInShadow(node: Node): void;
45
+ /**
46
+ * Renders the `fa-light` glyphs inside `button` in `preferred` when the page
47
+ * links Pro, and in Solid otherwise. Re-applies whenever Lit renders or
48
+ * rewrites the icon's class; the class edit removes `fa-light`, so the
49
+ * callback it triggers is a no-op. Returns the observer's disconnect.
50
+ */
51
+ export declare function applyNdsGlyphVariant(button: HTMLElement, preferred: NdsGlyphVariant): () => void;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * FontAwesome for `<nds-icon-button>`, which renders its glyph as
3
+ * `<i class="fa-light fa-${name}">` and ships no font of its own.
4
+ *
5
+ * FontAwesome comes from the host page when it links any FA stylesheet.
6
+ * Otherwise `ensureFontAwesome` adds FA Free from the ESM CDN the item player's
7
+ * `loaderOptions` name, jsDelivr by default. Pro is never fetched from here:
8
+ * ui.renaissance.com serves its font files to Renaissance origins only, so a
9
+ * host that licenses it links it itself, as section-demos does through its dev
10
+ * proxy.
11
+ *
12
+ * `fa-light` is a weight only Pro carries. Free has no `.fa-light` rule and its
13
+ * Regular font lacks most toolbar glyphs, so without Pro `applyNdsGlyphVariant`
14
+ * renders every glyph in Solid, the one Free weight that has them all.
15
+ *
16
+ * Roboto comes from the vendored button: its connectedCallback links
17
+ * ui.renaissance.com's Roboto unless the page links a stylesheet whose URL
18
+ * contains `Roboto`.
19
+ */
20
+ import { resolveEsmPackageFileUrl } from "../loaders/esm-adapter.js";
21
+ /** The one FA Free build the players add; `<package>@<version>/<path>`. */
22
+ const FONTAWESOME_FREE_FILE = "@fortawesome/fontawesome-free@6.5.2/css/all.min.css";
23
+ // Matches any FA stylesheet the host page links: `fontawesome.min.css`,
24
+ // `font-awesome.css`, a `/_fa-pro/` proxy, `fontawesome-free@…`.
25
+ const FA_HREF_PATTERN = /font.?awesome|fa-?pro/i;
26
+ const FA_FREE_HREF_PATTERN = /fontawesome-free/i;
27
+ const documentStylesheetLinks = () => Array.from(document.querySelectorAll('link[rel="stylesheet"][href]'));
28
+ const documentFontAwesomeLinks = () => documentStylesheetLinks().filter((link) => FA_HREF_PATTERN.test(link.href));
29
+ /** Whether the page links a FontAwesome build other than Free. */
30
+ const pageLinksFontAwesomePro = () => documentFontAwesomeLinks().some((link) => !FA_FREE_HREF_PATTERN.test(link.href));
31
+ /**
32
+ * Adds FA Free to `<head>` unless the page already links a FontAwesome
33
+ * stylesheet. Appending Free after a host's Pro would override Pro's
34
+ * `.fa-regular` / `.fa-solid` font-family rules page-wide.
35
+ *
36
+ * A stylesheet that fails to load stays in `<head>` marked
37
+ * `data-pie-load-failed`: no later call requests it again, and no shadow root
38
+ * copies it.
39
+ */
40
+ export function ensureFontAwesome(loaderOptions) {
41
+ if (typeof document === "undefined")
42
+ return;
43
+ if (documentFontAwesomeLinks().length > 0)
44
+ return;
45
+ const href = resolveEsmPackageFileUrl(loaderOptions, FONTAWESOME_FREE_FILE);
46
+ const link = document.createElement("link");
47
+ link.rel = "stylesheet";
48
+ link.href = href;
49
+ link.addEventListener("error", () => {
50
+ link.dataset.pieLoadFailed = "";
51
+ }, { once: true });
52
+ document.head.appendChild(link);
53
+ }
54
+ const mirroredShadowRoots = new WeakSet();
55
+ /**
56
+ * Copies the page's FontAwesome `<link>`s into the shadow root `node` renders
57
+ * in, since `<head>` stylesheets do not cross a shadow boundary. Does nothing
58
+ * when `node` is in the document tree, and runs once per shadow root.
59
+ *
60
+ * A stylesheet still loading is copied once it loads, so the copy comes from
61
+ * the cache; one that fails is not copied, since every copy would request it
62
+ * again. Chromium gives a link a `sheet` even when its load fails, so a failed
63
+ * one is known by its mark.
64
+ */
65
+ export function installFontAwesomeInShadow(node) {
66
+ const shadow = node.getRootNode();
67
+ if (!(shadow instanceof ShadowRoot) || mirroredShadowRoots.has(shadow)) {
68
+ return;
69
+ }
70
+ mirroredShadowRoots.add(shadow);
71
+ const appendLink = (href) => {
72
+ const present = Array.from(shadow.querySelectorAll('link[rel="stylesheet"]')).some((link) => link.href === href);
73
+ if (!href || present)
74
+ return;
75
+ const link = document.createElement("link");
76
+ link.rel = "stylesheet";
77
+ link.href = href;
78
+ shadow.appendChild(link);
79
+ };
80
+ for (const link of documentFontAwesomeLinks()) {
81
+ if ("pieLoadFailed" in link.dataset)
82
+ continue;
83
+ if (link.sheet) {
84
+ appendLink(link.href);
85
+ continue;
86
+ }
87
+ const settle = (event) => {
88
+ link.removeEventListener("load", settle);
89
+ link.removeEventListener("error", settle);
90
+ if (event.type === "load")
91
+ appendLink(link.href);
92
+ };
93
+ link.addEventListener("load", settle);
94
+ link.addEventListener("error", settle);
95
+ }
96
+ }
97
+ /**
98
+ * Renders the `fa-light` glyphs inside `button` in `preferred` when the page
99
+ * links Pro, and in Solid otherwise. Re-applies whenever Lit renders or
100
+ * rewrites the icon's class; the class edit removes `fa-light`, so the
101
+ * callback it triggers is a no-op. Returns the observer's disconnect.
102
+ */
103
+ export function applyNdsGlyphVariant(button, preferred) {
104
+ const variant = pageLinksFontAwesomePro() ? preferred : "fa-solid";
105
+ if (variant === "fa-light")
106
+ return () => { };
107
+ const apply = () => {
108
+ for (const icon of button.querySelectorAll("i.fa-light")) {
109
+ icon.classList.remove("fa-light");
110
+ icon.classList.add(variant);
111
+ }
112
+ };
113
+ apply();
114
+ const observer = new MutationObserver(apply);
115
+ observer.observe(button, {
116
+ childList: true,
117
+ subtree: true,
118
+ attributes: true,
119
+ attributeFilter: ["class"],
120
+ });
121
+ return () => observer.disconnect();
122
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pie-players/pie-players-shared",
3
- "version": "1.0.0-beta.7",
3
+ "version": "1.0.0-beta.8",
4
4
  "type": "module",
5
5
  "description": "Shared runtime + UI utilities for PIE players",
6
6
  "license": "MIT",
@@ -29,6 +29,10 @@
29
29
  "types": "./dist/security/index.d.ts",
30
30
  "import": "./dist/security/index.js"
31
31
  },
32
+ "./security/private-hosts": {
33
+ "types": "./dist/security/private-hosts.d.ts",
34
+ "import": "./dist/security/private-hosts.js"
35
+ },
32
36
  "./object": {
33
37
  "types": "./dist/object/index.d.ts",
34
38
  "import": "./dist/object/index.js"
@@ -97,6 +101,10 @@
97
101
  "types": "./dist/ui/content-styles.d.ts",
98
102
  "import": "./dist/ui/content-styles.js"
99
103
  },
104
+ "./ui/fontawesome": {
105
+ "types": "./dist/ui/fontawesome.d.ts",
106
+ "import": "./dist/ui/fontawesome.js"
107
+ },
100
108
  "./tools/term-lookup": {
101
109
  "types": "./dist/tools/term-lookup.d.ts",
102
110
  "import": "./dist/tools/term-lookup.js"
@@ -109,6 +117,7 @@
109
117
  "@pie-element/shared-utils": "0.1.6",
110
118
  "@pie-lib/math-rendering-module": "6.0.0",
111
119
  "dompurify": "^3.4.17",
120
+ "ipaddr.js": "^2.5.0",
112
121
  "semver": "^7.8.5"
113
122
  },
114
123
  "scripts": {
@@ -122,7 +131,7 @@
122
131
  "scan-hardcoded": "bun run src/i18n/scripts/scan-hardcoded.ts"
123
132
  },
124
133
  "devDependencies": {
125
- "@biomejs/biome": "^2.6.2",
134
+ "@biomejs/biome": "^2.6.4",
126
135
  "@happy-dom/global-registrator": "^21.0.4",
127
136
  "@pie-element/shared-math-rendering-mathjax": "0.1.8",
128
137
  "@playwright/test": "^1.64.1",
@@ -130,7 +139,7 @@
130
139
  "es-module-shims": "^2.8.4",
131
140
  "esbuild": "^0.28.2",
132
141
  "glob": "^13.0.0",
133
- "svelte": "^5.57.2",
142
+ "svelte": "^5.57.3",
134
143
  "svelte-check": "^4.7.6",
135
144
  "typescript": "^5.9.3"
136
145
  },