@pie-players/pie-players-shared 1.0.0-beta.0 → 1.0.0-beta.10

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.
Files changed (122) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +18 -17
  3. package/dist/THIRD-PARTY-LICENSES.txt +51 -0
  4. package/dist/components/vendor/nds/README.md +43 -5
  5. package/dist/components/vendor/nds/nds-icon-button.d.ts +2 -2
  6. package/dist/components/vendor/nds/nds-icon-button.js +2 -2
  7. package/dist/i18n/catalogs.d.ts +25 -13
  8. package/dist/i18n/catalogs.js +30 -22
  9. package/dist/i18n/content-language.d.ts +77 -0
  10. package/dist/i18n/content-language.js +129 -0
  11. package/dist/i18n/index.d.ts +7 -2
  12. package/dist/i18n/index.js +5 -2
  13. package/dist/i18n/language-tags.d.ts +3 -55
  14. package/dist/i18n/language-tags.js +3 -90
  15. package/dist/i18n/locale-load-error.d.ts +35 -0
  16. package/dist/i18n/locale-load-error.js +30 -0
  17. package/dist/i18n/messages/en-US.d.ts +151 -7
  18. package/dist/i18n/messages/en-US.js +168 -7
  19. package/dist/i18n/provider.d.ts +122 -18
  20. package/dist/i18n/provider.js +315 -133
  21. package/dist/i18n/pseudo.d.ts +34 -0
  22. package/dist/i18n/pseudo.js +106 -0
  23. package/dist/i18n/types.d.ts +19 -3
  24. package/dist/index.d.ts +16 -20
  25. package/dist/index.js +19 -19
  26. package/dist/instrumentation/providers/CompositeInstrumentationProvider.js +2 -1
  27. package/dist/item-session-control/index.d.ts +16 -0
  28. package/dist/item-session-control/index.js +5 -0
  29. package/dist/{formative → item-session-control}/mastery.d.ts +5 -5
  30. package/dist/{formative → item-session-control}/mastery.js +3 -3
  31. package/dist/{formative → item-session-control}/outcome.d.ts +3 -3
  32. package/dist/{formative → item-session-control}/outcome.js +2 -2
  33. package/dist/{formative → item-session-control}/policy.d.ts +11 -11
  34. package/dist/{formative → item-session-control}/policy.js +12 -12
  35. package/dist/item-session-control/session.d.ts +19 -0
  36. package/dist/{formative → item-session-control}/session.js +12 -12
  37. package/dist/{formative → item-session-control}/state.d.ts +26 -26
  38. package/dist/{formative → item-session-control}/state.js +13 -13
  39. package/dist/{formative → item-session-control}/types.d.ts +38 -40
  40. package/dist/{formative → item-session-control}/types.js +2 -2
  41. package/dist/loaders/ElementLoader.d.ts +1 -1
  42. package/dist/loaders/element-loader.d.ts +4 -1
  43. package/dist/loaders/element-loader.js +74 -21
  44. package/dist/loaders/element-package-spec.d.ts +16 -0
  45. package/dist/loaders/element-package-spec.js +30 -0
  46. package/dist/loaders/esm-adapter.d.ts +12 -1
  47. package/dist/loaders/esm-adapter.js +113 -19
  48. package/dist/loaders/iife-adapter.d.ts +7 -0
  49. package/dist/loaders/iife-adapter.js +1 -1
  50. package/dist/loaders/index.d.ts +2 -1
  51. package/dist/loaders/index.js +2 -1
  52. package/dist/loaders/preloaded-registration.d.ts +18 -6
  53. package/dist/loaders/preloaded-registration.js +44 -9
  54. package/dist/object/index.d.ts +4 -1
  55. package/dist/object/index.js +8 -1
  56. package/dist/pie/config.d.ts +8 -0
  57. package/dist/pie/config.js +32 -1
  58. package/dist/pie/index.d.ts +2 -0
  59. package/dist/pie/index.js +1 -0
  60. package/dist/pie/math-rendering.d.ts +13 -1
  61. package/dist/pie/math-rendering.js +54 -1
  62. package/dist/pie/updates.d.ts +4 -1
  63. package/dist/pie/updates.js +21 -5
  64. package/dist/pie/versioned-tag.d.ts +10 -0
  65. package/dist/pie/versioned-tag.js +27 -0
  66. package/dist/promise/index.d.ts +10 -0
  67. package/dist/promise/index.js +16 -0
  68. package/dist/security/overwide-regions.js +65 -0
  69. package/dist/security/private-hosts.d.ts +27 -0
  70. package/dist/security/private-hosts.js +85 -0
  71. package/dist/security/validate-style-url.js +2 -2
  72. package/dist/security/wrap-overwide-images.d.ts +6 -2
  73. package/dist/security/wrap-overwide-images.js +5 -2
  74. package/dist/security/wrap-overwide-tables.d.ts +6 -2
  75. package/dist/security/wrap-overwide-tables.js +5 -2
  76. package/dist/security/wrap-overwide.d.ts +13 -0
  77. package/dist/security/wrap-overwide.js +68 -3
  78. package/dist/timed-media/index.d.ts +1 -1
  79. package/dist/timed-media/index.js +1 -1
  80. package/dist/timed-media/policy.d.ts +2 -2
  81. package/dist/timed-media/policy.js +2 -2
  82. package/dist/timed-media/session.d.ts +1 -1
  83. package/dist/timed-media/session.js +1 -1
  84. package/dist/timed-media/types.d.ts +11 -11
  85. package/dist/tools/term-lookup.d.ts +15 -0
  86. package/dist/tools/term-lookup.js +16 -0
  87. package/dist/types/index.d.ts +127 -47
  88. package/dist/types/interchange.d.ts +116 -0
  89. package/dist/types/interchange.js +1 -0
  90. package/dist/ui/content-styles-entry.d.ts +1 -0
  91. package/dist/ui/content-styles-entry.js +2 -0
  92. package/dist/ui/content-styles.d.ts +24 -3
  93. package/dist/ui/content-styles.js +114 -3
  94. package/dist/ui/focus-trap.d.ts +12 -0
  95. package/dist/ui/focus-trap.js +41 -1
  96. package/dist/ui/fontawesome.d.ts +51 -0
  97. package/dist/ui/fontawesome.js +122 -0
  98. package/dist/ui/ime.d.ts +6 -0
  99. package/dist/ui/ime.js +10 -0
  100. package/dist/ui/overlay-containment.d.ts +13 -0
  101. package/dist/ui/overlay-containment.js +23 -0
  102. package/dist/ui/overlay-placement.d.ts +28 -0
  103. package/dist/ui/overlay-placement.js +17 -2
  104. package/package.json +72 -36
  105. package/dist/formative/index.d.ts +0 -16
  106. package/dist/formative/index.js +0 -5
  107. package/dist/formative/session.d.ts +0 -19
  108. package/dist/i18n/messages/nl-NL.d.ts +0 -529
  109. package/dist/i18n/messages/nl-NL.js +0 -529
  110. package/dist/instrumentation/new-relic-agent.d.ts +0 -20
  111. package/dist/loaders/module-shim.d.ts +0 -12
  112. package/dist/pie/authoring-tag.d.ts +0 -6
  113. package/dist/pie/configure-initialization.d.ts +0 -21
  114. package/dist/pie/correct-response-env.d.ts +0 -3
  115. package/dist/pie/element-announcements.d.ts +0 -39
  116. package/dist/pie/elements-rendered.d.ts +0 -23
  117. package/dist/pie/initialize-element.d.ts +0 -26
  118. package/dist/pie/markup-math.d.ts +0 -20
  119. package/dist/pie/resource-monitor.d.ts +0 -217
  120. package/dist/security/sanitize-forbidden-lists.d.ts +0 -8
  121. package/dist/ui/pointer-rotate.d.ts +0 -29
  122. package/dist/ui/safe-storage.d.ts +0 -2
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PIE Framework contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -2,13 +2,12 @@
2
2
 
3
3
  Shared runtime utilities for the `@pie-players/*` player suite: the element
4
4
  loader, PIE config and session helpers, markup security, i18n, and the
5
- formative, timed-media and media contracts. For framework code and host
5
+ item session control, timed-media and media contracts. For framework code and host
6
6
  integrations that need the helpers the player packages use.
7
7
 
8
8
  ## Public Exports
9
9
 
10
10
  ```ts
11
- import { safeLocalStorageGet } from "@pie-players/pie-players-shared";
12
11
  import { makeUniqueTags } from "@pie-players/pie-players-shared/pie";
13
12
  import { normalizeItemPlayerStrategy } from "@pie-players/pie-players-shared";
14
13
  ```
@@ -17,12 +16,13 @@ The subpaths below are the package's `exports`; nothing else is importable.
17
16
 
18
17
  | Subpath | Contents |
19
18
  | --- | --- |
20
- | `@pie-players/pie-players-shared` | The root: the loader, PIE, security and object helpers, loader config, instrumentation providers, the item-player strategy helpers, the default bundle host URL (`BUILDER_BUNDLE_URL`), a subset of the shared types, and the UI helpers (attribute coercion, content styles, pointer gestures, overlay placement, focus trap, safe storage, scoped CSS) |
19
+ | `@pie-players/pie-players-shared` | The root: the loader, PIE and security helpers, loader config, instrumentation providers, the item-player strategy helpers, the default bundle host URL (`BUILDER_BUNDLE_URL`), a subset of the shared types, and the UI helpers the tools share (focus trap and focusable lookup, IME composition, overlay containment and placement, pointer drag and gestures, scoped CSS). Attribute coercion, content styles and the object helpers are on their own subpaths only |
21
20
  | `/loader-config` | `LoaderConfig` and its defaults: instrumentation (`trackPageActions`, `instrumentationProvider`), resource retry, and IIFE bundle retry |
22
21
  | `/security` | The item-markup sanitizer (`sanitizeItemMarkup`, `createDefaultItemMarkupSanitizer`, `buildAuthoringAllowList`), external-stylesheet URL validation, the SVG icon and `style` attribute sanitizers, and the wrappers that put overwide images and tables in a scrollable region |
22
+ | `/security/private-hosts` | `isPrivateHostname` and `isCloudMetadataHostname`, the host classification for a URL a server fetches on someone else's behalf. Node-safe: it loads none of the markup sanitizers |
23
23
  | `/object` | `mergeObjectsIgnoringNullUndefined`, `cloneDeep`, `isPlainRecord` |
24
24
  | `/types` | The shared types: item, passage, section and session entities, PIE models and controllers, accessibility catalogs, and the [media vocabulary](#media-validation) |
25
- | `/formative` | [Formative delivery](#formative-delivery) |
25
+ | `/item-session-control` | [Item session control](#item-session-control) |
26
26
  | `/timed-media` | [Timed media](#timed-media) |
27
27
  | `/media` | [Media validation](#media-validation) |
28
28
  | `/pie` | PIE runtime utilities: config helpers, bundle initialization, `ItemController` and the item session contract, session commit and snapshots, authoring helpers, math renderer setup, instrumentation event maps ([PIE utilities](src/pie/README.md)) |
@@ -32,15 +32,16 @@ The subpaths below are the package's `exports`; nothing else is importable.
32
32
  | `/i18n/types` | The i18n contract, types only |
33
33
  | `/i18n/provider` | `SimpleI18n`, `getDefaultI18n` and `resolveInterfaceI18n`, with the English catalog only |
34
34
  | `/i18n/catalogs` | `BUNDLED_LOCALES` and lazy loaders for the bundled locales other than English |
35
- | `/i18n/language-tags` | BCP 47 language tag normalization and matching |
36
- | `/nds-icon-button` | `<nds-icon-button>`, an icon button vendored as a prebuilt bundle with Lit inlined; importing it registers the tag. The assessment toolkit and `@pie-players/pie-tool-tts-inline` render it |
35
+ | `/i18n/language-tags` | BCP 47 language tag normalization and matching, re-exported from `@pie-players/pie-language-tags` |
36
+ | `/nds-icon-button` | `<pie-nds-icon-button>`, an icon button vendored as a prebuilt bundle with Lit inlined; importing it registers the tag. The assessment toolkit, the section player and `@pie-players/pie-tool-tts-inline` render it |
37
37
  | `/ui/attribute-coercion` | Boolean attribute coercion for custom elements |
38
38
  | `/ui/content-styles` | `installContentStyles`, `auditContentStyles` and the opt-out checks behind the item player's content styles |
39
+ | `/ui/fontawesome` | The FontAwesome `<pie-nds-icon-button>` glyphs need: the one FA Free pin, Pro detection, the `<head>` and shadow-root installs, and the glyph weight. The assessment toolkit, the section player and `@pie-players/pie-tool-tts-inline` use it |
39
40
  | `/tools/term-lookup` | Term normalization and the lookup session the dictionary tools share; the host supplies the endpoint or a resolver |
40
41
 
41
- ## Formative Delivery
42
+ ## Item Session Control
42
43
 
43
- `@pie-players/pie-players-shared/formative` holds the policy resolution, outcome
44
+ `@pie-players/pie-players-shared/item-session-control` holds the policy resolution, outcome
44
45
  aggregation, Try-state reducer, item view, and mastery rollup that
45
46
  `pie-section-player` drives. It is pure (no DOM, no timers, no element
46
47
  registry), so it is testable without a browser and importable by an adapter that
@@ -48,18 +49,18 @@ has no player.
48
49
 
49
50
  ```ts
50
51
  import {
51
- aggregateFormativeOutcome,
52
- resolveFormativePolicy,
53
- resolveFormativeItemView,
54
- rollupFormativeMastery,
55
- } from "@pie-players/pie-players-shared/formative";
52
+ aggregateTryOutcome,
53
+ resolveItemSessionControl,
54
+ resolveItemTryView,
55
+ rollupMastery,
56
+ } from "@pie-players/pie-players-shared/item-session-control";
56
57
  ```
57
58
 
58
- The authored half, `FormativeDeliveryPolicy` on `AssessmentSection` and
59
- `FormativeItemPolicy` on `AssessmentItemRef`, is re-exported from the package
59
+ The authored half, `ItemSessionControlPolicy` on `AssessmentSection` and
60
+ `AssessmentItemRef`, is re-exported from the package
60
61
  root and from `/types`, beside the section types it annotates.
61
62
 
62
- The contract is [`docs/prds/formative-delivery-contract.md`](../../docs/prds/formative-delivery-contract.md),
63
+ The contract is [`docs/prds/item-session-control-contract.md`](../../docs/prds/item-session-control-contract.md),
63
64
  including the QTI 3 mapping and why PIE says **Try** rather than "attempt".
64
65
 
65
66
  ## Timed Media
@@ -92,7 +93,7 @@ re-exported from the package root and from `/types`, beside the section types it
92
93
  annotates.
93
94
 
94
95
  The contract is [`docs/prds/timed-media-section-contract.md`](../../docs/prds/timed-media-section-contract.md),
95
- including why cue gate conditions name the formative vocabulary and why an
96
+ including why cue gate conditions name the Try vocabulary and why an
96
97
  unenforceable policy degrades to advisory rather than failing closed.
97
98
 
98
99
  ## Media Validation
@@ -0,0 +1,51 @@
1
+ Third-party software included in this package's bundles, with the licence each is used under.
2
+
3
+ ========================================================================
4
+
5
+ es-module-shims 2.8.4
6
+ License: MIT
7
+
8
+ MIT License
9
+ -----------
10
+
11
+ Copyright (C) 2018-2021 Guy Bedford
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18
+
19
+ ========================================================================
20
+
21
+ lit (pre-bundled in the vendored NDS icon button)
22
+ License: BSD-3-Clause
23
+
24
+ BSD 3-Clause License
25
+
26
+ Copyright (c) 2017 Google LLC. All rights reserved.
27
+
28
+ Redistribution and use in source and binary forms, with or without
29
+ modification, are permitted provided that the following conditions are met:
30
+
31
+ 1. Redistributions of source code must retain the above copyright notice, this
32
+ list of conditions and the following disclaimer.
33
+
34
+ 2. Redistributions in binary form must reproduce the above copyright notice,
35
+ this list of conditions and the following disclaimer in the documentation
36
+ and/or other materials provided with the distribution.
37
+
38
+ 3. Neither the name of the copyright holder nor the names of its
39
+ contributors may be used to endorse or promote products derived from
40
+ this software without specific prior written permission.
41
+
42
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
43
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
44
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
45
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
46
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
47
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
48
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
49
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
51
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -1,7 +1,8 @@
1
1
  # Vendored: `nds-icon-button`
2
2
 
3
3
  Pre-built bundle of the Renaissance Next Design System (NDS)
4
- `<nds-icon-button>` custom element. This is the **single source of truth** for
4
+ `nds-icon-button` component, registered here as `<pie-nds-icon-button>`. This is
5
+ the **single source of truth** for
5
6
  the button across `@pie-players/*`; consumers import it via the package export
6
7
  `@pie-players/pie-players-shared/nds-icon-button` rather than vendoring their
7
8
  own copy.
@@ -12,6 +13,7 @@ Known consumers:
12
13
  header (chevrons, zoom, close).
13
14
  - `@pie-players/pie-tool-tts-inline` — the Text-to-Speech play/pause trigger
14
15
  and the "more" overflow control.
16
+ - `@pie-players/pie-section-player` — the items pane's scroll hint.
15
17
 
16
18
  It ships as a pre-built single-file bundle (Lit inlined) so consumers get the
17
19
  button without taking a runtime dependency on Lit or the rest of NDS: the Lit
@@ -27,6 +29,17 @@ lands, so it never leaks into a consumer's own dependency set.
27
29
  | Last sync commit | `1c728d3` |
28
30
  | Bundler | Vite 7 in lib mode, ES output, single-file (Lit inlined) |
29
31
 
32
+ ## Licence
33
+
34
+ The inlined Lit runtime is BSD-3-Clause, Copyright Google LLC. Its `@license`
35
+ comments sit in `nds-icon-button.js` and must survive every refresh, since the
36
+ licence requires the notice to travel with a redistributed copy.
37
+
38
+ The NDS component code is Renaissance's own. The bundle carries no NDS licence
39
+ header and the repository records no separate terms, so it ships inside
40
+ `@pie-players/pie-players-shared` under that package's MIT licence (`LICENSE`).
41
+ Renaissance owns that call; record any different terms here.
42
+
30
43
  ## Why a vendored bundle, not an npm dep
31
44
 
32
45
  NDS is not currently published to a registry the `@pie-players/*` chain can
@@ -35,10 +48,22 @@ would force a `lit` dependency plus a chunk of foundations CSS onto every
35
48
  downstream consumer on lockstep release. A pre-built single-file bundle
36
49
  sidesteps both: zero new package deps, the bundle inlines what it needs.
37
50
 
51
+ ## Registered tag
52
+
53
+ The bundle registers `pie-nds-icon-button`, under PIE's prefix. A host page that
54
+ also loads NDS registers upstream's own `nds-icon-button`, often unguarded and
55
+ at a different version; sharing that name would leave whichever copy defines
56
+ second either skipped (PIE's guarded define) or throwing (the host's). Two
57
+ sites are patched: the `Pt("…")` decorator argument at the class registration,
58
+ and the `nds-icon-button { display: inline-block; }` element selector at the top
59
+ of `render()`'s inline `<style>`. The BEM class names (`.nds-icon-button`,
60
+ `.nds-icon-button__icon`, modifiers) stay upstream's, and so do the export path
61
+ and file names.
62
+
38
63
  ## Idempotent registration (built-in define guard)
39
64
 
40
- A host page can load more than one `nds-icon-button` consumer (e.g. the toolkit
41
- and the inline TTS tool), and each would otherwise register `nds-icon-button` a
65
+ A host page can load more than one `pie-nds-icon-button` consumer (e.g. the
66
+ toolkit and the inline TTS tool), and each would otherwise register the tag a
42
67
  second time and throw `NotSupportedError`. To make registration idempotent
43
68
  regardless of load order, the Lit `@customElement` decorator helper (`Pt`) in
44
69
  this bundle is patched to guard the define:
@@ -55,6 +80,18 @@ Re-apply this guard after any refresh from upstream. Because the guard still
55
80
  contains the literal `customElements.define(` token, this file is allow-listed
56
81
  in `scripts/check-ce-define-safety.mjs` (which would otherwise flag it).
57
82
 
83
+ ## One registration per page
84
+
85
+ The tag is registered at most once per page, by whichever consumer reaches it
86
+ first, and that copy serves every consumer. Consumers load the bundle with a
87
+ dynamic `import()` the first time an NDS control is about to render, which is
88
+ only when the host sets `ndsIcons: true`; a page that never opts in neither
89
+ fetches the bundle nor takes the `pie-nds-icon-button` tag. A second copy of the
90
+ bundle at a different version loads without error and is skipped by the define
91
+ guard, so the first copy to register decides the markup and styles every
92
+ consumer sees. A host that loads its own NDS registers upstream's
93
+ `nds-icon-button`, a different tag, and is unaffected.
94
+
58
95
  ## How this is built and shipped
59
96
 
60
97
  `players-shared` builds with `tsc`, which does not emit static `.js`/`.d.ts`
@@ -69,6 +106,7 @@ Consuming bundlers treat this module per their own externals config:
69
106
  - `tool-tts-inline` (Vite lib build) externalizes `players-shared` and its
70
107
  subpaths, so its published output imports
71
108
  `@pie-players/pie-players-shared/nds-icon-button` from the host.
109
+ - `section-player` externalizes the same specifier for the scroll hint.
72
110
  - `assessment-toolkit` builds its CE artifacts with every dependency and peer
73
111
  dependency, plus each one's subpaths, as esbuild externals, so this module
74
112
  stays a bare external import in those artifacts — resolved by the
@@ -107,8 +145,8 @@ When a newer `nds-icon-button` is needed:
107
145
  ```
108
146
 
109
147
  Then: `bunx vite build --config vite.self-contained.config.js`.
110
- 4. Copy the output over this file, re-apply the define guard above, and update
111
- the `Last sync commit` row.
148
+ 4. Copy the output over this file, re-apply the define guard and the two tag
149
+ renames above, and update the `Last sync commit` row.
112
150
  5. Rebuild the consumers (`assessment-toolkit`, `tool-tts-inline`) and verify
113
151
  the calculator shell and the TTS trigger still mount and register.
114
152
 
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Type surface for the vendored `<nds-icon-button>` bundle.
2
+ * Type surface for the vendored NDS icon button bundle.
3
3
  *
4
4
  * Consumers import this module for its side effect (it registers the
5
- * `nds-icon-button` custom element on evaluation). The named export is
5
+ * `pie-nds-icon-button` custom element on evaluation). The named export is
6
6
  * provided only so tooling can reference the constructor; the tag is what
7
7
  * consumers actually use, via markup or `document.createElement`.
8
8
  */
@@ -602,7 +602,7 @@ let p = class extends C {
602
602
  render() {
603
603
  return yt`
604
604
  <style>
605
- nds-icon-button {
605
+ pie-nds-icon-button {
606
606
  display: inline-block;
607
607
  }
608
608
 
@@ -819,7 +819,7 @@ b([
819
819
  _()
820
820
  ], p.prototype, "shape", 1);
821
821
  p = b([
822
- Pt("nds-icon-button")
822
+ Pt("pie-nds-icon-button")
823
823
  ], p);
824
824
  export {
825
825
  p as NdsIconButton
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Lazy catalog loaders for every locale this repository ships.
2
+ * The bundled catalog loader: `en-US`, the one statically shipped catalog.
3
3
  *
4
4
  * Imported by players, never by tools. A tool resolves its provider from the
5
5
  * toolkit runtime context and needs only the interface plus the English
@@ -7,31 +7,43 @@
7
7
  * into a bundle that never calls `setLocale`, because every tool
8
8
  * `vite.config.ts` sets `external: []`.
9
9
  *
10
- * An explicit static map rather than a bundler macro: `tsc` emits these
11
- * `import()` calls verbatim, so `players-shared/dist` evaluates anywhere ESM
12
- * does — webpack, esbuild, Rollup, Node, or a browser loading the files
13
- * directly. `pie-qti` used `import.meta.glob` and shipped a `dist` that throws
14
- * under every one of those. Each bundler still sees the specifiers statically,
15
- * so each locale stays its own chunk. Adding a locale means adding one line here
16
- * alongside the file.
10
+ * `en-US` is the source of truth — the fallback for every missing key in every
11
+ * other locale — so the provider imports it statically and it is always
12
+ * resident. It is the only catalog this package ships: every other locale this
13
+ * repository ships lives in the optional generated tier
14
+ * (`@pie-players/pie-i18n-generated`), whose loaders a host composes after
15
+ * this one. The composed shape is that package's README's wiring example:
17
16
  *
18
- * `en-US` is absent: it is the fallback for every missing key in every other
19
- * locale, so the provider imports it statically and it is always resident.
17
+ * ```ts
18
+ * loadCatalog: (locale) =>
19
+ * loadBundledCatalog(locale).catch(() => loadGeneratedCatalog(locale));
20
+ * ```
21
+ *
22
+ * A request this function cannot serve rejects, and the provider treats that
23
+ * as "resolve through the fallback chain" rather than an error, so a host
24
+ * locale nothing ships still renders English.
20
25
  *
21
26
  * @module @pie-players/pie-players-shared/i18n/catalogs
22
27
  */
23
28
  import type { BundledLocaleCode, MessageCatalog } from "./types.js";
24
29
  /**
25
- * Every locale tag this repository ships a catalog for, English included.
30
+ * Every locale tag this repository bundles a catalog for. `en-US` alone: the
31
+ * statically resident source of truth.
26
32
  *
27
33
  * The provider resolves a request against this list through RFC 4647 lookup and
28
- * primary-subtag widening, so `nl`, `nl_NL` and `nl-BE` all reach `nl-NL`.
34
+ * primary-subtag widening, so `en`, `en_US` and `en-GB`-without-a-catalog all
35
+ * land on `en-US` unless a composed loader serves the region.
29
36
  */
30
37
  export declare const BUNDLED_LOCALES: readonly BundledLocaleCode[];
31
38
  /**
32
39
  * Load one shipped catalog.
33
40
  *
34
- * Throws for a locale with no shipped catalog; the provider treats that as
41
+ * `en-US` comes back without a load: it is statically resident, and a host
42
+ * composing this loader into a provider — the generated tier's README pattern,
43
+ * or a pseudo-localization of the English catalog — asks for it by name like
44
+ * any other locale.
45
+ *
46
+ * Rejects for a locale with no bundled catalog; the provider treats that as
35
47
  * "resolve through the fallback chain" rather than an error, so a host locale we
36
48
  * do not ship still renders.
37
49
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Lazy catalog loaders for every locale this repository ships.
2
+ * The bundled catalog loader: `en-US`, the one statically shipped catalog.
3
3
  *
4
4
  * Imported by players, never by tools. A tool resolves its provider from the
5
5
  * toolkit runtime context and needs only the interface plus the English
@@ -7,41 +7,49 @@
7
7
  * into a bundle that never calls `setLocale`, because every tool
8
8
  * `vite.config.ts` sets `external: []`.
9
9
  *
10
- * An explicit static map rather than a bundler macro: `tsc` emits these
11
- * `import()` calls verbatim, so `players-shared/dist` evaluates anywhere ESM
12
- * does — webpack, esbuild, Rollup, Node, or a browser loading the files
13
- * directly. `pie-qti` used `import.meta.glob` and shipped a `dist` that throws
14
- * under every one of those. Each bundler still sees the specifiers statically,
15
- * so each locale stays its own chunk. Adding a locale means adding one line here
16
- * alongside the file.
10
+ * `en-US` is the source of truth — the fallback for every missing key in every
11
+ * other locale — so the provider imports it statically and it is always
12
+ * resident. It is the only catalog this package ships: every other locale this
13
+ * repository ships lives in the optional generated tier
14
+ * (`@pie-players/pie-i18n-generated`), whose loaders a host composes after
15
+ * this one. The composed shape is that package's README's wiring example:
17
16
  *
18
- * `en-US` is absent: it is the fallback for every missing key in every other
19
- * locale, so the provider imports it statically and it is always resident.
17
+ * ```ts
18
+ * loadCatalog: (locale) =>
19
+ * loadBundledCatalog(locale).catch(() => loadGeneratedCatalog(locale));
20
+ * ```
21
+ *
22
+ * A request this function cannot serve rejects, and the provider treats that
23
+ * as "resolve through the fallback chain" rather than an error, so a host
24
+ * locale nothing ships still renders English.
20
25
  *
21
26
  * @module @pie-players/pie-players-shared/i18n/catalogs
22
27
  */
23
- const CATALOG_LOADERS = {
24
- "nl-NL": () => import("./messages/nl-NL.js"),
25
- };
28
+ import enUS from "./messages/en-US.js";
26
29
  /**
27
- * Every locale tag this repository ships a catalog for, English included.
30
+ * Every locale tag this repository bundles a catalog for. `en-US` alone: the
31
+ * statically resident source of truth.
28
32
  *
29
33
  * The provider resolves a request against this list through RFC 4647 lookup and
30
- * primary-subtag widening, so `nl`, `nl_NL` and `nl-BE` all reach `nl-NL`.
34
+ * primary-subtag widening, so `en`, `en_US` and `en-GB`-without-a-catalog all
35
+ * land on `en-US` unless a composed loader serves the region.
31
36
  */
32
- export const BUNDLED_LOCALES = ["en-US", "nl-NL"];
37
+ export const BUNDLED_LOCALES = ["en-US"];
33
38
  /**
34
39
  * Load one shipped catalog.
35
40
  *
36
- * Throws for a locale with no shipped catalog; the provider treats that as
41
+ * `en-US` comes back without a load: it is statically resident, and a host
42
+ * composing this loader into a provider — the generated tier's README pattern,
43
+ * or a pseudo-localization of the English catalog — asks for it by name like
44
+ * any other locale.
45
+ *
46
+ * Rejects for a locale with no bundled catalog; the provider treats that as
37
47
  * "resolve through the fallback chain" rather than an error, so a host locale we
38
48
  * do not ship still renders.
39
49
  */
40
50
  export async function loadBundledCatalog(locale) {
41
- const loader = CATALOG_LOADERS[locale];
42
- if (!loader) {
43
- throw new Error(`No bundled i18n catalog for locale: ${locale}`);
51
+ if (locale === "en-US") {
52
+ return enUS;
44
53
  }
45
- const module = await loader();
46
- return module.default;
54
+ throw new Error(`No bundled i18n catalog for locale: ${locale}`);
47
55
  }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Content language on the delivery channel.
3
+ *
4
+ * Content language names the language the item's content is written in — a
5
+ * fact about the content, on a separate channel from the interface locale a
6
+ * player publishes to the elements inside it. It reaches the player in
7
+ * `env.locale`, the same channel `env.mode` travels: a delivery fact the
8
+ * container knows and content cannot. Authoring hosts emit it; a host that
9
+ * emits nothing leaves the content unstamped, and nothing is invented — there
10
+ * is no default language for content that names none.
11
+ *
12
+ * The player reflects the resolved language onto its content subtree's root as
13
+ * `lang` and `dir`, so a screen reader takes the content's voice rules and RTL
14
+ * content lays out right to left, independent of the chrome's locale.
15
+ *
16
+ * Content that declares nothing falls back to a default the container names:
17
+ * a section's `runtime.contentLanguage`, or {@link UNDECLARED_CONTENT_LANGUAGE}
18
+ * once the host sets an interface locale, which stamps `lang=""` and
19
+ * `dir="auto"` so the content takes neither the interface's language nor its
20
+ * direction. With neither, undeclared content inherits its context, which keeps
21
+ * hosts that set no interface locale rendering as they always have.
22
+ */
23
+ import type { TextDirection } from "./types.js";
24
+ /**
25
+ * The content-language default for content that declares no language under a
26
+ * host-set interface locale: `lang=""` names the language unknown and
27
+ * `dir="auto"` takes direction from the content's first strong character.
28
+ */
29
+ export declare const UNDECLARED_CONTENT_LANGUAGE = "auto";
30
+ /**
31
+ * The default a player stamps on content whose `env` declares no language: the
32
+ * host's content language, else {@link UNDECLARED_CONTENT_LANGUAGE} when the
33
+ * host set an interface locale, else `undefined`, which leaves undeclared
34
+ * content inheriting its context. Inputs are read defensively, since both
35
+ * arrive from attributes.
36
+ */
37
+ export declare function defaultContentLanguage(options: {
38
+ contentLanguage?: unknown;
39
+ interfaceLocale?: unknown;
40
+ }): string | undefined;
41
+ /** `lang` and `dir` for declared content, or `null` when nothing is declared. */
42
+ export interface ContentLanguageAttributes {
43
+ lang: string;
44
+ dir: TextDirection;
45
+ }
46
+ /**
47
+ * `lang` and `dir` for the content an `env` describes, or `null` when it
48
+ * declares no usable language. Reads `env.locale` defensively — `env` is
49
+ * pass-through and load-bearing, so anything may arrive — and never invents a
50
+ * default: undeclared content stays unstamped and inherits whatever its
51
+ * context genuinely provides.
52
+ */
53
+ export declare function contentLanguageAttributes(env: unknown): ContentLanguageAttributes | null;
54
+ /**
55
+ * Stamps the content language onto `target`: `contentLanguageAttributes(env)`,
56
+ * else `fallback` — a tag, or {@link UNDECLARED_CONTENT_LANGUAGE} for
57
+ * `lang=""` and `dir="auto"` — else neither attribute, so a model set can drop
58
+ * the language a previous one declared. A fallback that is not a usable tag
59
+ * counts as absent. No-op without a target.
60
+ */
61
+ export declare function stampContentLanguage(target: Element | null | undefined, env: unknown, fallback?: string | null): void;
62
+ /**
63
+ * The direction of the first letter in `root`'s text, skipping `script`,
64
+ * `style` and `template`, or `null` when it has none. Letters only, which is
65
+ * where the bidi algorithm's strong characters overwhelmingly sit.
66
+ */
67
+ export declare function firstStrongDirection(root: Node): TextDirection | null;
68
+ /**
69
+ * The mismatch between undeclared content's script and the direction a
70
+ * fallback stamped on `root`, or `null` when there is none to report: the env
71
+ * declares a language, `root` carries no stamped direction or `dir="auto"`, or
72
+ * the content has no letters.
73
+ */
74
+ export declare function undeclaredDirectionMismatch(root: Element, env: unknown): {
75
+ stamped: TextDirection;
76
+ script: TextDirection;
77
+ } | null;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Content language on the delivery channel.
3
+ *
4
+ * Content language names the language the item's content is written in — a
5
+ * fact about the content, on a separate channel from the interface locale a
6
+ * player publishes to the elements inside it. It reaches the player in
7
+ * `env.locale`, the same channel `env.mode` travels: a delivery fact the
8
+ * container knows and content cannot. Authoring hosts emit it; a host that
9
+ * emits nothing leaves the content unstamped, and nothing is invented — there
10
+ * is no default language for content that names none.
11
+ *
12
+ * The player reflects the resolved language onto its content subtree's root as
13
+ * `lang` and `dir`, so a screen reader takes the content's voice rules and RTL
14
+ * content lays out right to left, independent of the chrome's locale.
15
+ *
16
+ * Content that declares nothing falls back to a default the container names:
17
+ * a section's `runtime.contentLanguage`, or {@link UNDECLARED_CONTENT_LANGUAGE}
18
+ * once the host sets an interface locale, which stamps `lang=""` and
19
+ * `dir="auto"` so the content takes neither the interface's language nor its
20
+ * direction. With neither, undeclared content inherits its context, which keeps
21
+ * hosts that set no interface locale rendering as they always have.
22
+ */
23
+ import { canonicalLanguageTag } from "./language-tags.js";
24
+ import { localeDirection } from "./provider.js";
25
+ /**
26
+ * The content-language default for content that declares no language under a
27
+ * host-set interface locale: `lang=""` names the language unknown and
28
+ * `dir="auto"` takes direction from the content's first strong character.
29
+ */
30
+ export const UNDECLARED_CONTENT_LANGUAGE = "auto";
31
+ /**
32
+ * The default a player stamps on content whose `env` declares no language: the
33
+ * host's content language, else {@link UNDECLARED_CONTENT_LANGUAGE} when the
34
+ * host set an interface locale, else `undefined`, which leaves undeclared
35
+ * content inheriting its context. Inputs are read defensively, since both
36
+ * arrive from attributes.
37
+ */
38
+ export function defaultContentLanguage(options) {
39
+ const named = typeof options.contentLanguage === "string"
40
+ ? options.contentLanguage.trim()
41
+ : "";
42
+ if (named)
43
+ return named;
44
+ const requested = typeof options.interfaceLocale === "string"
45
+ ? options.interfaceLocale.trim()
46
+ : "";
47
+ return requested ? UNDECLARED_CONTENT_LANGUAGE : undefined;
48
+ }
49
+ /**
50
+ * `lang` and `dir` for the content an `env` describes, or `null` when it
51
+ * declares no usable language. Reads `env.locale` defensively — `env` is
52
+ * pass-through and load-bearing, so anything may arrive — and never invents a
53
+ * default: undeclared content stays unstamped and inherits whatever its
54
+ * context genuinely provides.
55
+ */
56
+ export function contentLanguageAttributes(env) {
57
+ const locale = env?.locale;
58
+ if (typeof locale !== "string")
59
+ return null;
60
+ const lang = canonicalLanguageTag(locale);
61
+ if (!lang)
62
+ return null;
63
+ return { lang, dir: localeDirection(lang) };
64
+ }
65
+ /**
66
+ * Stamps the content language onto `target`: `contentLanguageAttributes(env)`,
67
+ * else `fallback` — a tag, or {@link UNDECLARED_CONTENT_LANGUAGE} for
68
+ * `lang=""` and `dir="auto"` — else neither attribute, so a model set can drop
69
+ * the language a previous one declared. A fallback that is not a usable tag
70
+ * counts as absent. No-op without a target.
71
+ */
72
+ export function stampContentLanguage(target, env, fallback) {
73
+ if (!target)
74
+ return;
75
+ const attributes = contentLanguageAttributes(env) ??
76
+ (fallback === UNDECLARED_CONTENT_LANGUAGE
77
+ ? { lang: "", dir: "auto" }
78
+ : contentLanguageAttributes({ locale: fallback }));
79
+ if (attributes) {
80
+ target.setAttribute("lang", attributes.lang);
81
+ target.setAttribute("dir", attributes.dir);
82
+ }
83
+ else {
84
+ target.removeAttribute("lang");
85
+ target.removeAttribute("dir");
86
+ }
87
+ }
88
+ /** Scripts written right to left, for {@link firstStrongDirection}. */
89
+ const RTL_LETTER = /[\p{Script=Arabic}\p{Script=Hebrew}\p{Script=Syriac}\p{Script=Thaana}\p{Script=Nko}\p{Script=Adlam}\p{Script=Hanifi_Rohingya}\p{Script=Mandaic}\p{Script=Samaritan}]/u;
90
+ const LETTER = /\p{L}/u;
91
+ /**
92
+ * The direction of the first letter in `root`'s text, skipping `script`,
93
+ * `style` and `template`, or `null` when it has none. Letters only, which is
94
+ * where the bidi algorithm's strong characters overwhelmingly sit.
95
+ */
96
+ export function firstStrongDirection(root) {
97
+ const doc = root.ownerDocument ?? root;
98
+ const walker = doc.createTreeWalker(root, 4 /* NodeFilter.SHOW_TEXT */, {
99
+ acceptNode: (node) => node.parentElement?.closest("script, style, template")
100
+ ? 2 /* NodeFilter.FILTER_REJECT */
101
+ : 1 /* NodeFilter.FILTER_ACCEPT */,
102
+ });
103
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
104
+ for (const char of node.nodeValue ?? "") {
105
+ if (RTL_LETTER.test(char))
106
+ return "rtl";
107
+ if (LETTER.test(char))
108
+ return "ltr";
109
+ }
110
+ }
111
+ return null;
112
+ }
113
+ /**
114
+ * The mismatch between undeclared content's script and the direction a
115
+ * fallback stamped on `root`, or `null` when there is none to report: the env
116
+ * declares a language, `root` carries no stamped direction or `dir="auto"`, or
117
+ * the content has no letters.
118
+ */
119
+ export function undeclaredDirectionMismatch(root, env) {
120
+ if (contentLanguageAttributes(env))
121
+ return null;
122
+ const stamped = root.getAttribute("dir");
123
+ if (stamped !== "ltr" && stamped !== "rtl")
124
+ return null;
125
+ const script = firstStrongDirection(root);
126
+ if (!script)
127
+ return null;
128
+ return script === stamped ? null : { stamped, script };
129
+ }