@pie-players/pie-default-tool-loaders 0.3.75 → 1.0.0-beta.0

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 CHANGED
@@ -1,16 +1,15 @@
1
1
  # PIE Default Tool Loaders
2
2
 
3
- Composition layer for the packaged PIE assessment tools: which capabilities exist
4
- in a deployment, and how a program tiers them.
3
+ The packaged PIE assessment tools: which capabilities exist in a deployment, how
4
+ they load, and which of them a program grants to everyone. This README is for
5
+ host integrators choosing a tool set and a calculator, and for contributors
6
+ adding a packaged capability.
5
7
 
6
- It owns the concrete `pie-tool-*` dependencies and the calculator engines
7
- (`pie-calculator-cortex`, `-desmos` and `-geogebra`) so toolkit core can stay
8
- dependency-light and cycle-safe, and it is the layer above core — core knows
9
- `featureId`, placement levels, activation kinds and precedence rules, and knows
10
- no capability ids.
11
-
12
- It owns five things, all answers to "which capabilities does this deployment have
13
- and how does the program tier them":
8
+ The package owns the concrete `pie-tool-*` dependencies and the calculator
9
+ engines (`pie-calculator-cortex`, `-desmos` and `-geogebra`), so toolkit core
10
+ stays dependency-light and cycle-safe. Core knows the concepts — support ids,
11
+ placement levels, activation kinds and precedence rules — and no concrete
12
+ capability id. A support id is the tool id a profile grants.
14
13
 
15
14
  | Export | What it decides |
16
15
  | --- | --- |
@@ -19,48 +18,54 @@ and how does the program tier them":
19
18
  | `SECTION_PLAYER_PREFERRED_TOOL_PLACEMENT` | Where they appear and in what order |
20
19
  | `createUniversalPersonalNeedsProfile`, `createEmptyPersonalNeedsProfile` | Which of them the program grants to everyone, or to no one |
21
20
  | `DEFAULT_TOOL_MODULE_LOADERS` | When each one's bundle loads |
21
+ | `CONTENT_ALTERNATE_REGISTRATIONS` | Which of them carry an authored alternate that renders as a region; the print player mounts these |
22
22
 
23
- The individual registrations are exported too, so a host can compose its own set
24
- rather than take the packaged one whole.
25
-
26
- These exports are projections of one internal **Packaged Capability
27
- Composition**. Each packaged capability is authored once with its registration,
28
- element delivery, module loader, preferred placement and explicit
29
- universal-support policy. The package build rejects contradictory PIE-owned data
30
- — for example, a region capability with a toolbar tag, or a content-dependent
31
- capability marked universal — so release tests find a missing facet
32
- instead of a learner finding a dead affordance. The browser does not repeat that
33
- strict gate at import time: a PIE authoring defect must block publication, not an
34
- otherwise usable assessment.
35
-
36
- That invariant boundary does not make host input stricter: lazy module loading
37
- is opt-in, registration/tag/factory overrides take precedence, an empty
38
- `toolIds` array selects the full packaged set, and unknown selected ids are
39
- ignored while known ids register.
23
+ The individual registrations are exported too, so a host can compose its own
24
+ set ([Custom sets](#custom-sets)).
40
25
 
41
26
  ## Usage
42
27
 
43
28
  ```ts
44
- import {
45
- createPackagedToolRegistry,
46
- DEFAULT_TOOL_MODULE_LOADERS,
47
- } from "@pie-players/pie-default-tool-loaders";
29
+ import { createPackagedToolRegistry } from "@pie-players/pie-default-tool-loaders";
48
30
 
49
31
  // The packaged capability set, lazily loaded.
50
- const registry = createPackagedToolRegistry({
51
- toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
52
- });
32
+ const registry = createPackagedToolRegistry();
33
+
34
+ // The same registrations for a host that defines the tool elements itself.
35
+ const preloaded = createPackagedToolRegistry({ toolModuleLoaders: {} });
53
36
  ```
54
37
 
55
38
  One loader set serves every host shape. `DEFAULT_TOOL_MODULE_LOADERS` holds a
56
- loader per packaged capability, and a registry loads a module only when a tool
57
- first renders, so an item player, a section player and an assessment player
58
- pass the same set and load only the tools they place.
59
-
60
- The calculator is one element, `<pie-tool-calculator>`, for every provider.
61
- Desmos is the provider when none is configured; the toolkit picks another from
62
- `tools.providers.calculator` when the element mounts, and an open calculator
63
- remounts on the new provider when a tool-config update replaces it.
39
+ loader per packaged capability and is what `createPackagedToolRegistry`
40
+ installs unless `toolModuleLoaders` replaces it. A registry loads a module only
41
+ when a tool first renders, so an item player, a section player and an
42
+ assessment player share the set and load only the tools they place.
43
+
44
+ `createPackagedToolRegistry` is lenient with host input. Registration, tag-map
45
+ and component-factory overrides take precedence over the packaged data. An
46
+ empty `toolIds` array selects the full packaged set, and unknown ids in it are
47
+ ignored while the known ones register.
48
+
49
+ ## Calculator providers
50
+
51
+ The calculator is one element, `<pie-tool-calculator>`, for every provider. The
52
+ toolkit picks the provider from `tools.providers.calculator` when the element
53
+ mounts, and an open calculator remounts on the new provider when a tool-config
54
+ update replaces it.
55
+
56
+ Desmos is the provider when none is configured, and it needs an API key: pass
57
+ it through `provider.runtime.authFetcher`, a `provider.init.proxyEndpoint` that
58
+ returns it, or `provider.init.apiKey` in development
59
+ ([Calculator with host auth](../../docs/tools-and-accomodations/tool_provider_system.md#calculator-with-host-auth)).
60
+ Without one the calculator fails to load. For a keyless, offline-capable
61
+ calculator with no runtime CDN, use `provider.id: "calculator-cortex"`. Its
62
+ `settings` take the `CortexCalculatorSettings` type that
63
+ `@pie-players/pie-calculator-cortex` exports: `angleMode`,
64
+ `calculationPrecision`, `displayPrecision`, `historyLimit`,
65
+ `evaluationTimeLimitMs`, `allowedFunctions`, `allowClipboard`, `messages`,
66
+ `direction` and `graph`. Clipboard blocking follows `restrictedMode`;
67
+ `allowClipboard` alone blocks nothing. The [Cortex README](../calculator-cortex/README.md)
68
+ covers localization, message overrides and the keypad.
64
69
 
65
70
  ```ts
66
71
  const tools = {
@@ -76,10 +81,7 @@ const tools = {
76
81
  };
77
82
  ```
78
83
 
79
- For an offline-capable calculator with no API key or runtime CDN, use
80
- `provider.id: "calculator-cortex"`. Its `settings` accept angle mode, precision,
81
- history, evaluation limit, allowed functions, clipboard policy, and graph
82
- viewport options documented by `@pie-players/pie-calculator-cortex`.
84
+ ## Custom sets
83
85
 
84
86
  A host composing its own set starts from the toolkit's empty `ToolRegistry`,
85
87
  registers into it, and installs the tag map its registrations create elements
@@ -105,19 +107,21 @@ A later `setToolModuleLoaders` call adds or replaces entries. A replacement
105
107
  registered after a tool's module has loaded is ignored with a warning, since the
106
108
  elements it defines are already in place.
107
109
 
108
- ## Content-dependent capabilities require an explicit packaging decision
110
+ ## Content-dependent capabilities
109
111
 
110
- A content-dependent capability is never granted by
111
- `createUniversalPersonalNeedsProfile()`. It may ship in `PACKAGED_TOOL_REGISTRATIONS` only
112
- when it has a useful presentation path without a grant and declares
113
- `resolvesWithoutGrant`. Audio transcript is the shipped example: an authored
114
- `visibility: "always"` transcript reaches every player, while its accommodation
115
- path remains policy-gated and `transcript` stays out of the universal profile.
112
+ A content-dependent capability declares `requiresAuthoredContent`: it renders
113
+ only where the content carries what it needs, and
114
+ `createUniversalPersonalNeedsProfile()` never grants one. It ships in
115
+ `PACKAGED_TOOL_REGISTRATIONS` only when it has a useful presentation path
116
+ without a grant and declares `resolvesWithoutGrant`. Audio transcript is the
117
+ shipped example: an authored `visibility: "always"` transcript reaches every
118
+ player, while its accommodation path stays policy-gated and `transcript` stays
119
+ out of the universal profile.
116
120
 
117
- A content-dependent capability with no presentation path remains an explicit
118
- deployment choice. Signing is the shipped example: install
119
- `@pie-players/pie-tool-sign-language` and register it, which is the same two
120
- lines a host writes for a capability of its own.
121
+ A content-dependent capability with no such path is an explicit deployment
122
+ choice. Signing is the shipped example: install
123
+ `@pie-players/pie-tool-sign-language` and register it, the same two lines a
124
+ host writes for a capability of its own.
121
125
 
122
126
  ```ts
123
127
  import { signLanguageRegistration } from "@pie-players/pie-tool-sign-language";
@@ -129,25 +133,40 @@ registry.register(signLanguageRegistration);
129
133
 
130
134
  `createUniversalPersonalNeedsProfile()` builds a fresh `PersonalNeedsProfile`
131
135
  granting the tool ids of the capabilities the packaged set treats as universal
132
- features. A support id is the tool id it grants.
136
+ features. Tool policy reads the profile from the assessment entity, so set it
137
+ there: on the player's `assessment` property, or through
138
+ `coordinator.updateAssessment(...)` on a coordinator you pass. A
139
+ `section.personalNeedsProfile` is ignored with a console warning.
133
140
 
134
141
  ```ts
135
142
  import { createUniversalPersonalNeedsProfile } from "@pie-players/pie-default-tool-loaders";
136
143
 
137
- const section = {
138
- ...authoredSection,
144
+ sectionPlayer.assessment = {
145
+ ...authoredAssessment,
139
146
  personalNeedsProfile: createUniversalPersonalNeedsProfile(),
140
147
  };
141
148
  ```
142
149
 
143
150
  The toolkit ships no populated default, and `createEmptyPersonalNeedsProfile()`
144
- builds a profile granting nothing. Which capabilities a
145
- deployment grants by default is a property of the program, not of a capability:
146
- TTS is a universal feature in one program and a documented accommodation in
147
- another. So the universal profile is data to adopt, extend or replace alongside the district
148
- and test-administration configuration, and it deliberately excludes any
149
- capability that declares a content dependency.
150
-
151
- A section carrying no profile is left alone. `pnpEnforcement` auto-detection
152
- engages on real host policy material, so supplying no profile means placement
153
- alone decides which tools appear.
151
+ builds a profile granting nothing. Which capabilities a deployment grants by
152
+ default is a property of the program: TTS is a universal feature in one program
153
+ and a documented accommodation in another. The universal profile is data to
154
+ adopt, extend or replace alongside the district and test-administration
155
+ configuration, and it excludes every capability that declares a content
156
+ dependency.
157
+
158
+ An assessment carrying no profile is left alone. `pnpEnforcement`
159
+ auto-detection engages only on host policy material, so with no profile,
160
+ placement alone decides which tools appear.
161
+
162
+ ## Packaged capability composition
163
+
164
+ The exports are projections of one internal composition. Each packaged
165
+ capability is authored once with its registration, element delivery, module
166
+ loader, preferred placement and explicit universal-support flag.
167
+
168
+ The package build rejects contradictory PIE-owned data, such as a region
169
+ capability with a toolbar tag or a content-dependent capability marked
170
+ universal, so release tests catch a missing facet before it ships. The browser
171
+ skips that strict check at import time: a PIE authoring defect blocks
172
+ publication and never an otherwise usable assessment.
@@ -1,12 +1,10 @@
1
1
  /** Fully bundled open-source calculator adapter for the tool-provider registry. */
2
2
  import type { CalculatorToolProviderInitConfig, LazyCalculatorProviderDefinition } from "./LazyCalculatorToolProvider.js";
3
3
  import { LazyCalculatorToolProvider } from "./LazyCalculatorToolProvider.js";
4
- import type { ToolProviderCapabilities } from "@pie-players/pie-assessment-toolkit/tools/registration";
5
4
  export type CortexToolProviderConfig = CalculatorToolProviderInitConfig;
6
5
  export declare class CortexToolProvider extends LazyCalculatorToolProvider<CortexToolProviderConfig> {
7
6
  readonly providerName = "PIE Open-Source Calculator";
8
7
  readonly version = "1";
9
8
  readonly requiresAuth = false;
10
9
  protected getDefinition(): LazyCalculatorProviderDefinition<CortexToolProviderConfig>;
11
- getCapabilities(): ToolProviderCapabilities;
12
10
  }
@@ -15,17 +15,4 @@ export class CortexToolProvider extends LazyCalculatorToolProvider {
15
15
  initializationErrorMessage: "Failed to initialize the PIE open-source calculator provider. Confirm that this browser supports module workers and that the bundled worker assets are available.",
16
16
  };
17
17
  }
18
- getCapabilities() {
19
- return {
20
- supportsOffline: true,
21
- requiresAuth: false,
22
- maxInstances: null,
23
- features: {
24
- basic: true,
25
- scientific: true,
26
- graphing: true,
27
- fourFunction: true,
28
- },
29
- };
30
- }
31
18
  }
@@ -1,7 +1,6 @@
1
1
  /** Desmos calculator adapter for the generic tool-provider registry. */
2
2
  import type { CalculatorProviderInit } from "@pie-players/pie-calculator";
3
3
  import { LazyCalculatorToolProvider, type LazyCalculatorProviderDefinition } from "./LazyCalculatorToolProvider.js";
4
- import type { ToolProviderCapabilities } from "@pie-players/pie-assessment-toolkit/tools/registration";
5
4
  /** Provider initialization is the provider-neutral calculator contract. */
6
5
  export type DesmosToolProviderConfig = CalculatorProviderInit;
7
6
  export declare class DesmosToolProvider extends LazyCalculatorToolProvider<DesmosToolProviderConfig> {
@@ -9,5 +8,4 @@ export declare class DesmosToolProvider extends LazyCalculatorToolProvider<Desmo
9
8
  readonly version = "1.12";
10
9
  readonly requiresAuth = true;
11
10
  protected getDefinition(): LazyCalculatorProviderDefinition<DesmosToolProviderConfig>;
12
- getCapabilities(): ToolProviderCapabilities;
13
11
  }
@@ -15,17 +15,4 @@ export class DesmosToolProvider extends LazyCalculatorToolProvider {
15
15
  initializationErrorMessage: "Failed to initialize Desmos calculator provider. Check the application key, preloaded API, runtime endpoint, or network access.",
16
16
  };
17
17
  }
18
- getCapabilities() {
19
- return {
20
- supportsOffline: false,
21
- requiresAuth: true,
22
- maxInstances: null,
23
- features: {
24
- basic: true,
25
- scientific: true,
26
- graphing: true,
27
- fourFunction: true,
28
- },
29
- };
30
- }
31
18
  }
@@ -1,7 +1,6 @@
1
1
  /** GeoGebra calculator adapter for the generic tool-provider registry. */
2
2
  import type { CalculatorToolProviderInitConfig, LazyCalculatorProviderDefinition } from "./LazyCalculatorToolProvider.js";
3
3
  import { LazyCalculatorToolProvider } from "./LazyCalculatorToolProvider.js";
4
- import type { ToolProviderCapabilities } from "@pie-players/pie-assessment-toolkit/tools/registration";
5
4
  export interface GeoGebraToolProviderConfig extends CalculatorToolProviderInitConfig {
6
5
  scriptUrl?: string;
7
6
  appletTimeoutMs?: number;
@@ -11,5 +10,4 @@ export declare class GeoGebraToolProvider extends LazyCalculatorToolProvider<Geo
11
10
  readonly version = "6";
12
11
  readonly requiresAuth = false;
13
12
  protected getDefinition(): LazyCalculatorProviderDefinition<GeoGebraToolProviderConfig>;
14
- getCapabilities(): ToolProviderCapabilities;
15
13
  }
@@ -15,17 +15,4 @@ export class GeoGebraToolProvider extends LazyCalculatorToolProvider {
15
15
  initializationErrorMessage: "Failed to initialize GeoGebra calculator provider. Confirm that the deployment may load GeoGebra and that its script URL is reachable.",
16
16
  };
17
17
  }
18
- getCapabilities() {
19
- return {
20
- supportsOffline: false,
21
- requiresAuth: false,
22
- maxInstances: null,
23
- features: {
24
- basic: true,
25
- scientific: true,
26
- graphing: true,
27
- fourFunction: false,
28
- },
29
- };
30
- }
31
18
  }
@@ -1,5 +1,5 @@
1
1
  import type { CalculatorProvider, CalculatorProviderInit } from "@pie-players/pie-calculator";
2
- import type { ToolProviderApi, ToolProviderCapabilities } from "@pie-players/pie-assessment-toolkit/tools/registration";
2
+ import type { ToolProviderApi } from "@pie-players/pie-assessment-toolkit/tools/registration";
3
3
  export type CalculatorToolProviderInitConfig = Pick<CalculatorProviderInit, "onTelemetry">;
4
4
  type InitializableCalculatorProvider<TConfig> = CalculatorProvider & {
5
5
  initialize(config: TConfig): Promise<void>;
@@ -12,7 +12,7 @@ export interface LazyCalculatorProviderDefinition<TConfig> {
12
12
  }
13
13
  /**
14
14
  * Shared lazy-module and lifecycle implementation for calculator tool adapters.
15
- * Concrete adapters own only their metadata, capabilities and provider import.
15
+ * Concrete adapters own only their metadata and provider import.
16
16
  */
17
17
  export declare abstract class LazyCalculatorToolProvider<TConfig extends CalculatorToolProviderInitConfig> implements ToolProviderApi<CalculatorProvider> {
18
18
  abstract readonly providerName: string;
@@ -20,7 +20,6 @@ export declare abstract class LazyCalculatorToolProvider<TConfig extends Calcula
20
20
  abstract readonly version: string;
21
21
  abstract readonly requiresAuth: boolean;
22
22
  protected abstract getDefinition(): LazyCalculatorProviderDefinition<TConfig>;
23
- abstract getCapabilities(): ToolProviderCapabilities;
24
23
  private calculatorProvider;
25
24
  private initializationPromise;
26
25
  private lifecycleGeneration;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Shared lazy-module and lifecycle implementation for calculator tool adapters.
3
- * Concrete adapters own only their metadata, capabilities and provider import.
3
+ * Concrete adapters own only their metadata and provider import.
4
4
  */
5
5
  export class LazyCalculatorToolProvider {
6
6
  category = "calculator";
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export type { PackagedToolRegistryOptions } from "./packaged-capability-composit
2
2
  export { createEmptyPersonalNeedsProfile, createPackagedToolRegistry, createUniversalPersonalNeedsProfile, DEFAULT_TOOL_MODULE_LOADERS, PACKAGED_TOOL_REGISTRATIONS, PACKAGED_TOOL_TAG_MAP, SECTION_PLAYER_PREFERRED_TOOL_PLACEMENT, } from "./packaged-capability-composition.js";
3
3
  export { CONTENT_ALTERNATE_REGISTRATIONS } from "./content-alternates.js";
4
4
  export { annotationToolbarRegistration, lineReaderToolRegistration, themeToolRegistration, } from "./registrations/accessibility-tools.js";
5
- export { AUDIO_TRANSCRIPT_FEATURE_ID, AUDIO_TRANSCRIPT_REGION_CLASS, AUDIO_TRANSCRIPT_REGION_LABEL, audioTranscriptRegistration, resolveAudioTranscript, type ResolvedAudioTranscript, } from "./registrations/audio-transcript.js";
5
+ export { AUDIO_TRANSCRIPT_FEATURE_ID, AUDIO_TRANSCRIPT_REGION_CLASS, AUDIO_TRANSCRIPT_REGION_LABEL, audioTranscriptRegistration, CONTENT_LEAD_SURFACE, resolveAudioTranscript, type ResolvedAudioTranscript, } from "./registrations/audio-transcript.js";
6
6
  export { calculatorToolRegistration } from "./registrations/calculator.js";
7
7
  export { createDictionaryToolRegistration, createPictureDictionaryToolRegistration, dictionaryToolRegistration, type DictionaryVariantOptions, pictureDictionaryToolRegistration, spanishDictionaryToolRegistration, spanishPictureDictionaryToolRegistration, } from "./registrations/dictionary-tools.js";
8
8
  export { answerEliminatorToolRegistration } from "./registrations/interaction-tools.js";
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ export { CONTENT_ALTERNATE_REGISTRATIONS } from "./content-alternates.js";
5
5
  // Individual registrations, for a host composing a subset rather than the whole
6
6
  // packaged set.
7
7
  export { annotationToolbarRegistration, lineReaderToolRegistration, themeToolRegistration, } from "./registrations/accessibility-tools.js";
8
- export { AUDIO_TRANSCRIPT_FEATURE_ID, AUDIO_TRANSCRIPT_REGION_CLASS, AUDIO_TRANSCRIPT_REGION_LABEL, audioTranscriptRegistration, resolveAudioTranscript, } from "./registrations/audio-transcript.js";
8
+ export { AUDIO_TRANSCRIPT_FEATURE_ID, AUDIO_TRANSCRIPT_REGION_CLASS, AUDIO_TRANSCRIPT_REGION_LABEL, audioTranscriptRegistration, CONTENT_LEAD_SURFACE, resolveAudioTranscript, } from "./registrations/audio-transcript.js";
9
9
  export { calculatorToolRegistration } from "./registrations/calculator.js";
10
10
  export { createDictionaryToolRegistration, createPictureDictionaryToolRegistration, dictionaryToolRegistration, pictureDictionaryToolRegistration, spanishDictionaryToolRegistration, spanishPictureDictionaryToolRegistration, } from "./registrations/dictionary-tools.js";
11
11
  export { answerEliminatorToolRegistration } from "./registrations/interaction-tools.js";
@@ -16,7 +16,11 @@ export interface PackagedToolRegistryOptions {
16
16
  toolTagMap?: Partial<ToolTagMap>;
17
17
  /** Override the component factory per tool. */
18
18
  toolComponentFactories?: Partial<ToolComponentFactoryMap>;
19
- /** Lazy module loaders keyed by toolId. */
19
+ /**
20
+ * Module loaders keyed by toolId, each loading its tool's element on first
21
+ * render. Defaults to `DEFAULT_TOOL_MODULE_LOADERS`; a map replaces it, so a
22
+ * host that defines its tool elements itself passes `{}`.
23
+ */
20
24
  toolModuleLoaders?: Partial<Record<string, ToolModuleLoader>>;
21
25
  /** Restrict registration to specific packaged tool ids. */
22
26
  toolIds?: string[];
@@ -248,13 +248,9 @@ class PackagedCapabilityComposition {
248
248
  for (const registration of this.selectRegistrations(options.toolIds)) {
249
249
  registry.register(options.overrides?.[registration.toolId] ?? registration);
250
250
  }
251
- // Loader installation remains opt-in. Some hosts preload tool elements or
252
- // provide their own tags; silently adding package loads here would change
253
- // their bundle/runtime behavior.
254
- if (options.toolModuleLoaders &&
255
- Object.keys(options.toolModuleLoaders).length > 0) {
256
- registry.setToolModuleLoaders({ ...options.toolModuleLoaders });
257
- }
251
+ registry.setToolModuleLoaders({
252
+ ...(options.toolModuleLoaders ?? this.moduleLoaders),
253
+ });
258
254
  registry.setComponentOverrides({
259
255
  toolTagMap: {
260
256
  ...this.toolTagMap,
@@ -23,7 +23,6 @@ export const lineReaderToolRegistration = {
23
23
  nameKey: "tools.lineReader.name",
24
24
  descriptionKey: "tools.lineReader.description",
25
25
  icon: "bars-3",
26
- // Line reader appears where there's text to read
27
26
  supportedLevels: ["section", "passage", "rubric", "item"],
28
27
  /**
29
28
  * Pass 2: Line reader is relevant when readable text is present
@@ -56,7 +55,7 @@ export const themeToolRegistration = {
56
55
  * Pass 2: Color scheme is always relevant when allowed
57
56
  */
58
57
  isVisibleInContext(context) {
59
- return true; // Always show if allowed by orchestrator
58
+ return true;
60
59
  },
61
60
  renderToolbar(context, toolbarContext) {
62
61
  return renderOverlayToolbar(this, context, toolbarContext, {
@@ -86,15 +85,12 @@ export const annotationToolbarRegistration = {
86
85
  activation: "selection-gateway",
87
86
  singletonScope: "section",
88
87
  /**
89
- * Section-scoped singleton surface. The gateway used to be mounted by
90
- * `PieSectionPlayerBaseElement.svelte`, which named this tool id in three
91
- * places — the policy check, the module load and the element — so no host
92
- * could contribute a second section-scoped capability. Declaring the surface
93
- * moves that wiring into this registration, and the renderer discovers it
94
- * through `ToolRegistry.getToolsBySurface("section-overlay")`.
88
+ * Section-scoped singleton surface. The renderer discovers it through
89
+ * `ToolRegistry.getToolsBySurface("section-overlay")`, so the section player
90
+ * names no tool id and a host can contribute another section-scoped
91
+ * capability.
95
92
  */
96
93
  surfaces: ["section-overlay"],
97
- // Annotation appears where there's text content
98
94
  supportedLevels: ["passage", "rubric", "item", "element"],
99
95
  /**
100
96
  * Pass 2: Annotation is relevant when readable text is present
@@ -5,13 +5,13 @@
5
5
  * Two kinds of content wear the same shape, and the card says which:
6
6
  *
7
7
  * - `visibility: "always"` is authored presentation. The item family was
8
- * designed to be delivered with its transcript on screen — Star Early
9
- * Literacy sentence items, for instance — so no profile grants it and none
10
- * revokes it.
8
+ * designed to be delivered with its transcript on screen — sentence items in
9
+ * an early-literacy assessment, for instance — so no profile grants it and
10
+ * none revokes it.
11
11
  * - anything else, `"onGrant"` included, is the accommodation. Policy decides
12
- * against the `transcript` support id, and silence means no. For the three
13
- * Star families where a visible transcript turns the item into a reading
14
- * task, that gate is the whole point.
12
+ * against the `transcript` support id, and silence means no. For item
13
+ * families where a visible transcript turns the item into a reading task,
14
+ * that gate is the whole point.
15
15
  *
16
16
  * The toolkit renders the text, not the element. Before this, the transcript rode
17
17
  * on `model.audioTranscript` and `mc-populated-blank` revealed its own copy when
@@ -47,6 +47,7 @@ export declare const AUDIO_TRANSCRIPT_REGION_CLASS = "pie-tool-audio-transcript"
47
47
  export interface ResolvedAudioTranscript {
48
48
  catalogId: string;
49
49
  text: string;
50
+ /** BCP 47, as the region's `lang` takes it, whatever syntax the card used. */
50
51
  language?: string;
51
52
  /** True when the card declares itself authored presentation. */
52
53
  always: boolean;
@@ -5,13 +5,13 @@
5
5
  * Two kinds of content wear the same shape, and the card says which:
6
6
  *
7
7
  * - `visibility: "always"` is authored presentation. The item family was
8
- * designed to be delivered with its transcript on screen — Star Early
9
- * Literacy sentence items, for instance — so no profile grants it and none
10
- * revokes it.
8
+ * designed to be delivered with its transcript on screen — sentence items in
9
+ * an early-literacy assessment, for instance — so no profile grants it and
10
+ * none revokes it.
11
11
  * - anything else, `"onGrant"` included, is the accommodation. Policy decides
12
- * against the `transcript` support id, and silence means no. For the three
13
- * Star families where a visible transcript turns the item into a reading
14
- * task, that gate is the whole point.
12
+ * against the `transcript` support id, and silence means no. For item
13
+ * families where a visible transcript turns the item into a reading task,
14
+ * that gate is the whole point.
15
15
  *
16
16
  * The toolkit renders the text, not the element. Before this, the transcript rode
17
17
  * on `model.audioTranscript` and `mc-populated-blank` revealed its own copy when
@@ -35,6 +35,7 @@
35
35
  * absent from the universal preset — a content-dependent support never belongs in
36
36
  * a wholesale grant.
37
37
  */
38
+ import { toLanguageTag } from "@pie-players/pie-players-shared/i18n/language-tags";
38
39
  /** QTI 3.0 / AfA support id gating an audio transcript. */
39
40
  export const AUDIO_TRANSCRIPT_FEATURE_ID = "transcript";
40
41
  /** Catalog type carrying the transcript text. */
@@ -55,6 +56,10 @@ const isTranscriptCard = (card) => !!card && card.catalog === AUDIO_TRANSCRIPT_C
55
56
  const isAlwaysVisible = (card) => typeof card.visibility === "string" &&
56
57
  card.visibility.trim().toLowerCase() === ALWAYS_VISIBLE;
57
58
  const cardText = (card) => typeof card.content === "string" ? card.content.trim() : "";
59
+ // The Learnosity transform emits POSIX `es_ES`, which `lang` does not accept.
60
+ const cardLanguage = (card) => typeof card.language === "string"
61
+ ? toLanguageTag(card.language) || undefined
62
+ : undefined;
58
63
  /**
59
64
  * The transcript an entity carries, or `null`.
60
65
  *
@@ -83,7 +88,7 @@ export function resolveAudioTranscript(context) {
83
88
  const resolved = {
84
89
  catalogId,
85
90
  text,
86
- language: typeof card.language === "string" ? card.language : undefined,
91
+ language: cardLanguage(card),
87
92
  always: isAlwaysVisible(card),
88
93
  };
89
94
  // An `always` card answers whether or not policy granted anything, so it
@@ -54,7 +54,10 @@ function normalizeCalculatorType(value) {
54
54
  }
55
55
  function getCalculatorRenderParams(toolbarContext) {
56
56
  const params = toolbarContext.getToolRenderParams?.("calculator") ?? {};
57
- const calculatorType = normalizeCalculatorType(params.calculatorType);
57
+ // A host resolver or a request names the type; the policy parameters'
58
+ // `type` is the default beneath both.
59
+ const calculatorType = normalizeCalculatorType(params.calculatorType) ??
60
+ normalizeCalculatorType(toolbarContext.getToolParameters?.("calculator")?.type);
58
61
  const availableTypesRaw = params.availableTypes;
59
62
  const availableTypes = Array.isArray(availableTypesRaw)
60
63
  ? availableTypesRaw
@@ -135,7 +138,6 @@ export const calculatorToolRegistration = {
135
138
  * (MathML, LaTeX, arithmetic markers).
136
139
  */
137
140
  isVisibleInContext(context) {
138
- // Show only when math is present in item content.
139
141
  return hasMathContent(context);
140
142
  },
141
143
  renderToolbar(context, toolbarContext) {
@@ -156,8 +158,7 @@ export const calculatorToolRegistration = {
156
158
  icon: typeof this.icon === "function" ? this.icon(context) : this.icon,
157
159
  // Routes this button through <nds-icon-button> where the host enables NDS
158
160
  // icons. Declared here because which capabilities render in the host's
159
- // design system is a composition decision; the toolbar used to map it from
160
- // the `calculator` toolId, which put a capability name in the generic core.
161
+ // design system is a composition decision.
161
162
  faIconName: "calculator",
162
163
  disabled: false,
163
164
  // The name stays put across open and closed. The toolbar mirrors
@@ -248,10 +249,9 @@ export const calculatorToolRegistration = {
248
249
  const active = toolbarContext.isToolVisible(this.toolId);
249
250
  button.active = active;
250
251
  button.label = displayName;
251
- // Static across the toggle. The previous `Close ${name.toLowerCase()}`
252
- // was a hardcoded English template built by lowercasing a localized
253
- // noun — "Close rekenmachine" under nl-NL — and it is the state
254
- // `aria-pressed` already carries.
252
+ // Static across the toggle: `aria-pressed` carries the state, and a
253
+ // "Close …" template built from a localized noun mixes languages
254
+ // ("Close rekenmachine" under nl-NL).
255
255
  button.ariaLabel = displayName;
256
256
  button.tooltip = displayName;
257
257
  if (lastVisibleState !== active) {
@@ -8,7 +8,7 @@ import { hasChoiceInteraction } from "@pie-players/pie-assessment-toolkit/tools/
8
8
  import { createToolElement, } from "@pie-players/pie-assessment-toolkit/tools/registration";
9
9
  import { createScopedVisibilityBinding, syncButtonAndOverlayVisibility, } from "@pie-players/pie-assessment-toolkit/tools/registration";
10
10
  import { resolveOverlayElement } from "./overlay-element-cache.js";
11
- /** `tools.providers.answerEliminator.strategy`, defaulting to strikethrough. */
11
+ /** The policy parameters' `strategy`, defaulting to strikethrough. */
12
12
  const normalizeEliminationStrategy = (value) => value === "mask" ? "mask" : "strikethrough";
13
13
  /** The state-store key of each PIE element in the item, by model id. */
14
14
  function resolveElementStateKeys(context, toolbarContext) {
@@ -38,7 +38,6 @@ export const answerEliminatorToolRegistration = {
38
38
  nameKey: "tools.answerEliminator.name",
39
39
  descriptionKey: "tools.answerEliminator.description",
40
40
  icon: "strikethrough",
41
- // Answer eliminator appears at item level only
42
41
  supportedLevels: ["item"],
43
42
  /**
44
43
  * Pass 2: Answer eliminator is relevant only for choice-based questions
@@ -61,9 +60,8 @@ export const answerEliminatorToolRegistration = {
61
60
  const visibility = createScopedVisibilityBinding(this.toolId, toolbarContext);
62
61
  const componentOverrides = toolbarContext.componentOverrides ?? {};
63
62
  const overlay = resolveOverlayElement(toolbarContext, visibility.fullToolId, () => createToolElement(this.toolId, context, toolbarContext, componentOverrides));
64
- overlay.setAttribute("tool-id", visibility.fullToolId);
65
63
  overlay.setAttribute("button-alignment", "inline");
66
- overlay.setAttribute("strategy", normalizeEliminationStrategy(toolbarContext.toolkitCoordinator?.getToolConfig(this.toolId)?.strategy));
64
+ overlay.setAttribute("strategy", normalizeEliminationStrategy(toolbarContext.getToolParameters?.("answerEliminator")?.strategy));
67
65
  const button = {
68
66
  toolId: this.toolId,
69
67
  label: this.name,
@@ -84,7 +82,6 @@ export const answerEliminatorToolRegistration = {
84
82
  overlay,
85
83
  isActive: visibility.isActive,
86
84
  });
87
- overlay.scopeElement = toolbarContext.getScopeElement?.() || null;
88
85
  overlay.elementStateKeys = resolveElementStateKeys(context, toolbarContext);
89
86
  },
90
87
  subscribeActive: visibility.subscribeActive,
@@ -2,18 +2,15 @@
2
2
  * The `renderToolbar` shape shared by every packaged capability that puts one
3
3
  * overlay behind one toolbar button.
4
4
  *
5
- * Seven registrations had this inline. What actually varies between them is the
6
- * two options below and nothing else: whether the overlay paints its own chrome
7
- * or takes a draggable window, and that window's size. Everything else — the visibility
8
- * binding, the button, the component-override lookup, the `tool-id` attribute, the
9
- * sync — was identical, which is why one rule landing in three spellings was
10
- * possible: the shell title was `resolveToolRegistrationName` in two files,
11
- * `displayName` in a third and a raw `t(key)` in a fourth.
5
+ * What varies between capabilities is the two options below: whether the overlay
6
+ * paints its own chrome or takes a draggable window, and that window's size. The
7
+ * visibility binding, the button, the component-override lookup, the `tool-id`
8
+ * attribute and the sync are shared, so the shell title has one spelling.
12
9
  *
13
- * Catalog keys are derived from `toolId`, which held at all seven sites:
14
- * `tools.<toolId>.buttonA11y` and `tools.<toolId>.tooltip`. A capability needing a
15
- * different prefix — the dictionary variants, which compose several capabilities
16
- * off one element — keeps its own renderer.
10
+ * Catalog keys are derived from `toolId`: `tools.<toolId>.buttonA11y` and
11
+ * `tools.<toolId>.tooltip`. A capability needing a different prefix — the
12
+ * dictionary variants, which compose several capabilities off one element —
13
+ * keeps its own renderer.
17
14
  */
18
15
  import { type ToolContext, type ToolRegistration, type ToolToolbarRenderResult, type ToolbarContext } from "@pie-players/pie-assessment-toolkit/tools/registration";
19
16
  /** A window's geometry. Absent for overlays that paint their own chrome. */
@@ -2,18 +2,15 @@
2
2
  * The `renderToolbar` shape shared by every packaged capability that puts one
3
3
  * overlay behind one toolbar button.
4
4
  *
5
- * Seven registrations had this inline. What actually varies between them is the
6
- * two options below and nothing else: whether the overlay paints its own chrome
7
- * or takes a draggable window, and that window's size. Everything else — the visibility
8
- * binding, the button, the component-override lookup, the `tool-id` attribute, the
9
- * sync — was identical, which is why one rule landing in three spellings was
10
- * possible: the shell title was `resolveToolRegistrationName` in two files,
11
- * `displayName` in a third and a raw `t(key)` in a fourth.
5
+ * What varies between capabilities is the two options below: whether the overlay
6
+ * paints its own chrome or takes a draggable window, and that window's size. The
7
+ * visibility binding, the button, the component-override lookup, the `tool-id`
8
+ * attribute and the sync are shared, so the shell title has one spelling.
12
9
  *
13
- * Catalog keys are derived from `toolId`, which held at all seven sites:
14
- * `tools.<toolId>.buttonA11y` and `tools.<toolId>.tooltip`. A capability needing a
15
- * different prefix — the dictionary variants, which compose several capabilities
16
- * off one element — keeps its own renderer.
10
+ * Catalog keys are derived from `toolId`: `tools.<toolId>.buttonA11y` and
11
+ * `tools.<toolId>.tooltip`. A capability needing a different prefix — the
12
+ * dictionary variants, which compose several capabilities off one element —
13
+ * keeps its own renderer.
17
14
  */
18
15
  import { applyOverlaySurface, createScopedVisibilityBinding, createToolElement, resolveToolRegistrationName, syncButtonAndOverlayVisibility, } from "@pie-players/pie-assessment-toolkit/tools/registration";
19
16
  import { resolveOverlayElement } from "./overlay-element-cache.js";
@@ -4,13 +4,12 @@
4
4
  * Registers the TTS tool for reading content aloud.
5
5
  */
6
6
  import type { ToolRegistration } from "@pie-players/pie-assessment-toolkit/tools/registration";
7
- export declare const TOOL_ACTIVE_CHANGE_EVENT = "pie-tool-active-change";
8
7
  /**
9
8
  * Text-to-Speech tool registration
10
9
  *
11
10
  * Supports:
12
11
  * - Reading content aloud using browser TTS or external providers
13
12
  * - Context-aware visibility (shows when readable text is available)
14
- * - All levels except assessment and element
13
+ * - Item and passage levels
15
14
  */
16
15
  export declare const ttsToolRegistration: ToolRegistration;
@@ -3,22 +3,21 @@
3
3
  *
4
4
  * Registers the TTS tool for reading content aloud.
5
5
  */
6
- import { hasReadableText } from "@pie-players/pie-assessment-toolkit/tools/registration";
7
- import { createScopedToolId, createToolElement, } from "@pie-players/pie-assessment-toolkit/tools/registration";
8
- import { buildRuntimeTTSConfig, normalizeTTSLayoutMode, normalizeTTSSpeedControlOptions, resolveTTSHostToolbarLayout, resolveTTSLayoutMode, resolveTTSRuntimeSettings, } from "@pie-players/pie-assessment-toolkit/tools/registration";
6
+ import { hasSpokenContent } from "@pie-players/pie-assessment-toolkit/tools/registration";
7
+ import { createScopedToolId, createToolElement, TOOL_ACTIVE_CHANGE_EVENT, } from "@pie-players/pie-assessment-toolkit/tools/registration";
8
+ import { buildRuntimeTTSConfig, normalizeTTSSpeedControlOptions, resolveTTSHostToolbarLayout, resolveTTSRuntimeSettings, } from "@pie-players/pie-assessment-toolkit/tools/registration";
9
9
  import { TTSToolProvider } from "@pie-players/pie-assessment-toolkit/tools/registration";
10
10
  import { resolveOverlayElement } from "./overlay-element-cache.js";
11
11
  // This package owns the server adapter's import, so the toolkit never names it
12
12
  // and a bundler building the toolkit alone has nothing to resolve.
13
13
  const loadServerTTSProvider = async () => (await import("@pie-players/tts-client-server")).ServerTTSProvider;
14
- export const TOOL_ACTIVE_CHANGE_EVENT = "pie-tool-active-change";
15
14
  /**
16
15
  * Text-to-Speech tool registration
17
16
  *
18
17
  * Supports:
19
18
  * - Reading content aloud using browser TTS or external providers
20
19
  * - Context-aware visibility (shows when readable text is available)
21
- * - All levels except assessment and element
20
+ * - Item and passage levels
22
21
  */
23
22
  export const ttsToolRegistration = {
24
23
  toolId: "textToSpeech",
@@ -46,9 +45,6 @@ export const ttsToolRegistration = {
46
45
  const normalizedConfig = {
47
46
  ...config,
48
47
  };
49
- if ("layoutMode" in normalizedConfig) {
50
- normalizedConfig.layoutMode = normalizeTTSLayoutMode(normalizedConfig.layoutMode);
51
- }
52
48
  if ("speedOptions" in normalizedConfig) {
53
49
  normalizedConfig.speedOptions = normalizeTTSSpeedControlOptions(normalizedConfig.speedOptions);
54
50
  }
@@ -59,11 +55,10 @@ export const ttsToolRegistration = {
59
55
  /**
60
56
  * Pass 2: Determine if TTS is relevant in this context
61
57
  *
62
- * TTS is relevant when:
63
- * - Context contains readable text (at least 10 characters)
58
+ * TTS is relevant when the context holds readable text or a catalog idref.
64
59
  */
65
60
  isVisibleInContext(context) {
66
- return hasReadableText(context);
61
+ return hasSpokenContent(context);
67
62
  },
68
63
  renderToolbar(context, toolbarContext) {
69
64
  const resolveRuntimeSettings = () => resolveTTSRuntimeSettings(toolbarContext.toolkitCoordinator?.getToolConfig(this.toolId) ||
@@ -72,19 +67,7 @@ export const ttsToolRegistration = {
72
67
  const runtimeSettings = resolveRuntimeSettings();
73
68
  return normalizeTTSSpeedControlOptions(runtimeSettings.speedOptions);
74
69
  };
75
- const resolveLayoutMode = () => resolveTTSLayoutMode(resolveRuntimeSettings());
76
- const resolveHostLayout = () => resolveTTSHostToolbarLayout(resolveRuntimeSettings());
77
70
  const fullToolId = createScopedToolId(this.toolId, toolbarContext.scope.level, toolbarContext.scope.scopeId);
78
- // Only a language the toolbar or its host named: unnamed, the control resolves
79
- // markup `lang`, and the browser voice follows the browser's language.
80
- const applyContentLanguage = (element) => {
81
- if (toolbarContext.language) {
82
- element.setAttribute("language", toolbarContext.language);
83
- }
84
- else {
85
- element.removeAttribute("language");
86
- }
87
- };
88
71
  const resolveControlSize = () => {
89
72
  const raw = toolbarContext.ui?.size;
90
73
  return raw === "sm" || raw === "lg" ? raw : "md";
@@ -92,22 +75,21 @@ export const ttsToolRegistration = {
92
75
  const element = resolveOverlayElement(toolbarContext, fullToolId, () => createToolElement(this.toolId, context, toolbarContext, toolbarContext.componentOverrides));
93
76
  const applyAttributes = () => {
94
77
  element.setAttribute("catalog-id", toolbarContext.catalogId || toolbarContext.itemId);
95
- applyContentLanguage(element);
96
78
  element.setAttribute("size", resolveControlSize());
97
- element.setAttribute("layout-mode", resolveLayoutMode());
79
+ element.setAttribute("layout-mode", resolveRuntimeSettings().layoutMode);
98
80
  element.speedOptions = resolveElementSpeedOptions();
99
81
  element.showSingleSpeedOption =
100
82
  resolveRuntimeSettings().showSingleSpeedOption === true;
101
83
  };
102
84
  applyAttributes();
103
- const hostLayout = resolveHostLayout();
85
+ const hostLayout = resolveTTSHostToolbarLayout(resolveRuntimeSettings());
104
86
  return {
105
87
  toolId: this.toolId,
106
88
  button: null,
107
89
  elements: [
108
90
  {
109
91
  element,
110
- mount: hostLayout.mount,
92
+ mount: "before-buttons",
111
93
  layoutHints: {
112
94
  controlsRow: {
113
95
  reserveSpace: hostLayout.controlsRow.reserveSpace,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pie-players/pie-default-tool-loaders",
3
- "version": "0.3.75",
3
+ "version": "1.0.0-beta.0",
4
4
  "type": "module",
5
5
  "description": "Composition layer for the packaged PIE assessment tools: registrations, tag map, placement preset, universal and empty profiles, and lazy module loaders",
6
6
  "repository": {
@@ -33,25 +33,25 @@
33
33
  "test": "bun test"
34
34
  },
35
35
  "dependencies": {
36
- "@pie-players/pie-assessment-toolkit": "0.3.75",
37
- "@pie-players/pie-calculator": "0.3.75",
38
- "@pie-players/pie-calculator-cortex": "0.3.75",
39
- "@pie-players/pie-calculator-desmos": "0.3.75",
40
- "@pie-players/pie-calculator-geogebra": "0.3.75",
41
- "@pie-players/pie-players-shared": "0.3.75",
42
- "@pie-players/pie-tool-annotation-toolbar": "0.3.75",
43
- "@pie-players/pie-tool-answer-eliminator": "0.3.75",
44
- "@pie-players/pie-tool-calculator-shared": "0.3.75",
45
- "@pie-players/pie-tool-dictionary": "0.3.75",
46
- "@pie-players/pie-tool-graph": "0.3.75",
47
- "@pie-players/pie-tool-line-reader": "0.3.75",
48
- "@pie-players/pie-tool-periodic-table": "0.3.75",
49
- "@pie-players/pie-tool-picture-dictionary": "0.3.75",
50
- "@pie-players/pie-tool-protractor": "0.3.75",
51
- "@pie-players/pie-tool-ruler": "0.3.75",
52
- "@pie-players/pie-tool-theme": "0.3.75",
53
- "@pie-players/pie-tool-tts-inline": "0.3.75",
54
- "@pie-players/tts-client-server": "0.3.75"
36
+ "@pie-players/pie-assessment-toolkit": "1.0.0-beta.0",
37
+ "@pie-players/pie-calculator": "1.0.0-beta.0",
38
+ "@pie-players/pie-calculator-cortex": "1.0.0-beta.0",
39
+ "@pie-players/pie-calculator-desmos": "1.0.0-beta.0",
40
+ "@pie-players/pie-calculator-geogebra": "1.0.0-beta.0",
41
+ "@pie-players/pie-players-shared": "1.0.0-beta.0",
42
+ "@pie-players/pie-tool-annotation-toolbar": "1.0.0-beta.0",
43
+ "@pie-players/pie-tool-answer-eliminator": "1.0.0-beta.0",
44
+ "@pie-players/pie-tool-calculator-shared": "1.0.0-beta.0",
45
+ "@pie-players/pie-tool-dictionary": "1.0.0-beta.0",
46
+ "@pie-players/pie-tool-graph": "1.0.0-beta.0",
47
+ "@pie-players/pie-tool-line-reader": "1.0.0-beta.0",
48
+ "@pie-players/pie-tool-periodic-table": "1.0.0-beta.0",
49
+ "@pie-players/pie-tool-picture-dictionary": "1.0.0-beta.0",
50
+ "@pie-players/pie-tool-protractor": "1.0.0-beta.0",
51
+ "@pie-players/pie-tool-ruler": "1.0.0-beta.0",
52
+ "@pie-players/pie-tool-theme": "1.0.0-beta.0",
53
+ "@pie-players/pie-tool-tts-inline": "1.0.0-beta.0",
54
+ "@pie-players/tts-client-server": "1.0.0-beta.0"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@biomejs/biome": "^2.5.15",