@pie-players/pie-default-tool-loaders 0.3.64 → 0.3.65

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,24 +1,97 @@
1
1
  # PIE Default Tool Loaders
2
2
 
3
- Default lazy module loader mappings for built-in PIE assessment tools.
3
+ Composition layer for the packaged PIE assessment tools: which capabilities exist
4
+ in a deployment, and how a program tiers them.
4
5
 
5
- This package intentionally owns concrete `pie-tool-*` dependencies so toolkit core can remain dependency-light and cycle-safe.
6
+ It owns the concrete `pie-tool-*` dependencies so toolkit core can stay
7
+ dependency-light and cycle-safe, and it is the layer above core — core knows
8
+ `featureId`, placement levels, activation kinds and precedence rules, and knows
9
+ no capability ids.
10
+
11
+ It owns four things, all answers to "which capabilities does this deployment have
12
+ and how does the program tier them":
13
+
14
+ | Export | What it decides |
15
+ | --- | --- |
16
+ | `PACKAGED_TOOL_REGISTRATIONS`, `createPackagedToolRegistry`, `registerPackagedTools` | Which capabilities exist, and their toolbar and surface contracts |
17
+ | `PACKAGED_TOOL_TAG_MAP` | Which custom element each one renders as |
18
+ | `PACKAGED_TOOL_PLACEMENT`, `SECTION_PLAYER_PREFERRED_TOOL_PLACEMENT`, `PACKAGED_TOOL_ORDER` | Where they appear and in what order |
19
+ | `UNIVERSAL_SUPPORTS_PRESET`, `createUniversalPersonalNeedsProfile` | Which of their support ids the program grants to everyone |
20
+ | `DEFAULT_TOOL_MODULE_LOADERS`, `registerSectionToolModuleLoaders` | When each one's bundle loads |
21
+
22
+ The individual registrations are exported too, so a host can compose its own set
23
+ rather than take the packaged one whole.
6
24
 
7
25
  ## Usage
8
26
 
9
27
  ```ts
10
- import { createDefaultToolRegistry } from "@pie-players/pie-assessment-toolkit";
11
28
  import {
29
+ createPackagedToolRegistry,
12
30
  DEFAULT_TOOL_MODULE_LOADERS,
13
- registerSectionToolModuleLoaders,
14
31
  } from "@pie-players/pie-default-tool-loaders";
15
32
 
16
- // Full default loaders (item + section tools)
17
- const registry = createDefaultToolRegistry({
33
+ // The packaged capability set, lazily loaded.
34
+ const registry = createPackagedToolRegistry({
18
35
  toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS,
19
36
  });
37
+ ```
38
+
39
+ `createDefaultToolRegistry()` in the toolkit is the other end of that choice: it
40
+ builds an empty registry, and a host composing its own set registers into it.
20
41
 
21
- // Section-only loaders (for section toolbar bootstrap points)
42
+ ```ts
43
+ import { createDefaultToolRegistry } from "@pie-players/pie-assessment-toolkit";
44
+ import {
45
+ calculatorToolRegistration,
46
+ registerSectionToolModuleLoaders,
47
+ ttsToolRegistration,
48
+ } from "@pie-players/pie-default-tool-loaders";
49
+
50
+ const registry = createDefaultToolRegistry({
51
+ registrations: [calculatorToolRegistration, ttsToolRegistration],
52
+ });
53
+ // Section-only loaders, for section toolbar bootstrap points.
22
54
  registerSectionToolModuleLoaders(registry);
23
55
  ```
24
56
 
57
+ ## Accommodations are not in the packaged set
58
+
59
+ A capability declaring `requiresAuthoredContent` is absent from
60
+ `PACKAGED_TOOL_REGISTRATIONS` and its support ids from
61
+ `UNIVERSAL_SUPPORTS_PRESET`. Signing is the shipped example: install
62
+ `@pie-players/pie-tool-sign-language` and register it, which is the same two lines
63
+ a host writes for a capability of its own.
64
+
65
+ ```ts
66
+ import { signLanguageRegistration } from "@pie-players/pie-tool-sign-language";
67
+
68
+ registry.register(signLanguageRegistration);
69
+ ```
70
+
71
+ ## Universal supports preset
72
+
73
+ `createUniversalPersonalNeedsProfile()` builds a `PersonalNeedsProfile` granting
74
+ `UNIVERSAL_SUPPORTS_PRESET` — the support ids the packaged tool set treats as
75
+ universal features.
76
+
77
+ ```ts
78
+ import { createUniversalPersonalNeedsProfile } from "@pie-players/pie-default-tool-loaders";
79
+
80
+ const section = {
81
+ ...authoredSection,
82
+ personalNeedsProfile: createUniversalPersonalNeedsProfile(),
83
+ };
84
+ ```
85
+
86
+ The core ships no populated default — `createEmptyPersonalNeedsProfile()` in
87
+ `@pie-players/pie-assessment-toolkit` grants nothing. Which capabilities a
88
+ deployment grants by default is a property of the program, not of a capability:
89
+ TTS is a universal feature in one program and a documented accommodation in
90
+ another. So the preset is data to adopt, extend or replace alongside the district
91
+ and test-administration configuration, and it deliberately excludes any
92
+ capability that declares a content dependency.
93
+
94
+ A section carrying no profile is left alone. `pnpEnforcement` auto-detection
95
+ engages on real host policy material, so supplying no profile means placement
96
+ alone decides which tools appear.
97
+
package/dist/index.d.ts CHANGED
@@ -1,3 +1,14 @@
1
+ export { UNIVERSAL_SUPPORTS_PRESET, createUniversalPersonalNeedsProfile, } from "./universal-supports.js";
2
+ export type { PackagedToolRegistryOptions, RegisterPackagedToolsOptions, } from "./packaged-tool-registry.js";
3
+ export { createPackagedToolRegistry, PACKAGED_TOOL_ORDER, PACKAGED_TOOL_REGISTRATIONS, registerPackagedTools, } from "./packaged-tool-registry.js";
4
+ export { PACKAGED_TOOL_TAG_MAP } from "./tool-tag-map.js";
5
+ export { PACKAGED_TOOL_PLACEMENT, SECTION_PLAYER_PREFERRED_TOOL_PLACEMENT, } from "./tool-placement.js";
6
+ export { annotationToolbarRegistration, lineReaderToolRegistration, themeToolRegistration, } from "./registrations/accessibility-tools.js";
7
+ export { calculatorToolRegistration } from "./registrations/calculator.js";
8
+ export { answerEliminatorToolRegistration, highlighterToolRegistration, } from "./registrations/interaction-tools.js";
9
+ export { protractorToolRegistration, rulerToolRegistration, } from "./registrations/measurement-tools.js";
10
+ export { graphToolRegistration, periodicTableToolRegistration, } from "./registrations/subject-specific-tools.js";
11
+ export { ttsToolRegistration } from "./registrations/tts.js";
1
12
  export type ToolModuleLoader = () => Promise<unknown>;
2
13
  export interface ToolRegistryLike {
3
14
  setToolModuleLoaders(loaders: Partial<Record<string, ToolModuleLoader>>): void;
package/dist/index.js CHANGED
@@ -1,3 +1,15 @@
1
+ export { UNIVERSAL_SUPPORTS_PRESET, createUniversalPersonalNeedsProfile, } from "./universal-supports.js";
2
+ export { createPackagedToolRegistry, PACKAGED_TOOL_ORDER, PACKAGED_TOOL_REGISTRATIONS, registerPackagedTools, } from "./packaged-tool-registry.js";
3
+ export { PACKAGED_TOOL_TAG_MAP } from "./tool-tag-map.js";
4
+ export { PACKAGED_TOOL_PLACEMENT, SECTION_PLAYER_PREFERRED_TOOL_PLACEMENT, } from "./tool-placement.js";
5
+ // Individual registrations, for a host composing a subset rather than the whole
6
+ // packaged set.
7
+ export { annotationToolbarRegistration, lineReaderToolRegistration, themeToolRegistration, } from "./registrations/accessibility-tools.js";
8
+ export { calculatorToolRegistration } from "./registrations/calculator.js";
9
+ export { answerEliminatorToolRegistration, highlighterToolRegistration, } from "./registrations/interaction-tools.js";
10
+ export { protractorToolRegistration, rulerToolRegistration, } from "./registrations/measurement-tools.js";
11
+ export { graphToolRegistration, periodicTableToolRegistration, } from "./registrations/subject-specific-tools.js";
12
+ export { ttsToolRegistration } from "./registrations/tts.js";
1
13
  const loadSideEffectModule = (load) => load().then(() => undefined);
2
14
  function loadCalculatorModule() {
3
15
  if (typeof globalThis !== "undefined" &&
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The packaged PIE capability set, and the registry factory that composes it.
3
+ *
4
+ * This is the composition layer: which capabilities exist in a deployment. It
5
+ * lived in `@pie-players/pie-assessment-toolkit`, where eleven concrete
6
+ * registrations inside the generic package meant the policy and registry core
7
+ * knew every capability by name and a host could not contribute one without a PR
8
+ * against that package.
9
+ *
10
+ * The toolkit still owns `ToolRegistry`, `createDefaultToolRegistry` (which
11
+ * builds an empty registry), the registration contract and the toolbar helpers.
12
+ * It knows `featureId`, placement levels, activation kinds and precedence rules,
13
+ * and knows no capability ids.
14
+ */
15
+ import { ToolRegistry, type ToolComponentFactory, type ToolComponentFactoryMap, type ToolModuleLoader, type ToolRegistration, type ToolTagMap } from "@pie-players/pie-assessment-toolkit/tools/internal";
16
+ export declare const PACKAGED_TOOL_REGISTRATIONS: readonly [ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration];
17
+ export interface PackagedToolRegistryOptions {
18
+ /**
19
+ * Override packaged registrations by toolId. Highest-precedence extension
20
+ * point.
21
+ */
22
+ overrides?: Partial<Record<string, ToolRegistration>>;
23
+ /** Override or extend the packaged element tag mapping. */
24
+ toolTagMap?: Partial<ToolTagMap>;
25
+ /** Override the component factory globally or per tool. */
26
+ toolComponentFactory?: ToolComponentFactory;
27
+ toolComponentFactories?: Partial<ToolComponentFactoryMap>;
28
+ /** Lazy module loaders keyed by toolId. */
29
+ toolModuleLoaders?: Partial<Record<string, ToolModuleLoader>>;
30
+ /** Restrict registration to specific packaged tool ids. */
31
+ toolIds?: string[];
32
+ }
33
+ export interface RegisterPackagedToolsOptions {
34
+ toolIds?: string[];
35
+ applyOverrides?: (registration: ToolRegistration) => ToolRegistration;
36
+ }
37
+ /**
38
+ * Register packaged PIE capabilities onto an existing registry.
39
+ */
40
+ export declare function registerPackagedTools(registry: ToolRegistry, options?: RegisterPackagedToolsOptions): void;
41
+ /**
42
+ * Create a registry holding the packaged PIE capabilities.
43
+ *
44
+ * The tag map is installed through `setComponentOverrides` rather than read from
45
+ * a core default, so a registry composed without it resolves no packaged tags —
46
+ * which is the point: core no longer carries the catalogue.
47
+ */
48
+ export declare function createPackagedToolRegistry(options?: PackagedToolRegistryOptions): ToolRegistry;
49
+ /**
50
+ * Render order when several capabilities are visible at once.
51
+ */
52
+ export declare const PACKAGED_TOOL_ORDER: readonly ["theme", "calculator", "textToSpeech", "lineReader", "annotationToolbar", "highlighter", "answerEliminator", "ruler", "protractor", "graph", "periodicTable"];
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The packaged PIE capability set, and the registry factory that composes it.
3
+ *
4
+ * This is the composition layer: which capabilities exist in a deployment. It
5
+ * lived in `@pie-players/pie-assessment-toolkit`, where eleven concrete
6
+ * registrations inside the generic package meant the policy and registry core
7
+ * knew every capability by name and a host could not contribute one without a PR
8
+ * against that package.
9
+ *
10
+ * The toolkit still owns `ToolRegistry`, `createDefaultToolRegistry` (which
11
+ * builds an empty registry), the registration contract and the toolbar helpers.
12
+ * It knows `featureId`, placement levels, activation kinds and precedence rules,
13
+ * and knows no capability ids.
14
+ */
15
+ import { ToolRegistry, } from "@pie-players/pie-assessment-toolkit/tools/internal";
16
+ import { annotationToolbarRegistration, lineReaderToolRegistration, themeToolRegistration, } from "./registrations/accessibility-tools.js";
17
+ import { calculatorToolRegistration } from "./registrations/calculator.js";
18
+ import { answerEliminatorToolRegistration, highlighterToolRegistration, } from "./registrations/interaction-tools.js";
19
+ import { protractorToolRegistration, rulerToolRegistration, } from "./registrations/measurement-tools.js";
20
+ import { graphToolRegistration, periodicTableToolRegistration, } from "./registrations/subject-specific-tools.js";
21
+ import { ttsToolRegistration } from "./registrations/tts.js";
22
+ import { PACKAGED_TOOL_TAG_MAP } from "./tool-tag-map.js";
23
+ export const PACKAGED_TOOL_REGISTRATIONS = [
24
+ calculatorToolRegistration,
25
+ ttsToolRegistration,
26
+ rulerToolRegistration,
27
+ protractorToolRegistration,
28
+ answerEliminatorToolRegistration,
29
+ highlighterToolRegistration,
30
+ lineReaderToolRegistration,
31
+ themeToolRegistration,
32
+ annotationToolbarRegistration,
33
+ graphToolRegistration,
34
+ periodicTableToolRegistration,
35
+ ];
36
+ /**
37
+ * Register packaged PIE capabilities onto an existing registry.
38
+ */
39
+ export function registerPackagedTools(registry, options = {}) {
40
+ const selectedToolIds = options.toolIds && options.toolIds.length > 0
41
+ ? new Set(options.toolIds)
42
+ : null;
43
+ const applyOverrides = options.applyOverrides ??
44
+ ((registration) => registration);
45
+ for (const registration of PACKAGED_TOOL_REGISTRATIONS) {
46
+ if (selectedToolIds && !selectedToolIds.has(registration.toolId))
47
+ continue;
48
+ registry.register(applyOverrides(registration));
49
+ }
50
+ }
51
+ /**
52
+ * Create a registry holding the packaged PIE capabilities.
53
+ *
54
+ * The tag map is installed through `setComponentOverrides` rather than read from
55
+ * a core default, so a registry composed without it resolves no packaged tags —
56
+ * which is the point: core no longer carries the catalogue.
57
+ */
58
+ export function createPackagedToolRegistry(options = {}) {
59
+ const registry = new ToolRegistry();
60
+ registerPackagedTools(registry, {
61
+ toolIds: options.toolIds,
62
+ applyOverrides: (registration) => options.overrides?.[registration.toolId] ?? registration,
63
+ });
64
+ if (options.toolModuleLoaders &&
65
+ Object.keys(options.toolModuleLoaders).length > 0) {
66
+ registry.setToolModuleLoaders(options.toolModuleLoaders);
67
+ }
68
+ registry.setComponentOverrides({
69
+ toolTagMap: { ...PACKAGED_TOOL_TAG_MAP, ...(options.toolTagMap ?? {}) },
70
+ toolComponentFactory: options.toolComponentFactory,
71
+ toolComponentFactories: options.toolComponentFactories,
72
+ });
73
+ return registry;
74
+ }
75
+ /**
76
+ * Render order when several capabilities are visible at once.
77
+ */
78
+ export const PACKAGED_TOOL_ORDER = [
79
+ // Global accessibility first
80
+ "theme",
81
+ // Common tools
82
+ "calculator",
83
+ "textToSpeech",
84
+ // Reading aids
85
+ "lineReader",
86
+ "annotationToolbar",
87
+ "highlighter",
88
+ // Interaction tools
89
+ "answerEliminator",
90
+ // Measurement tools
91
+ "ruler",
92
+ "protractor",
93
+ // Subject-specific
94
+ "graph",
95
+ "periodicTable",
96
+ ];
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Accessibility Tools Registrations
3
+ *
4
+ * Registers tools for visual accessibility and reading support:
5
+ * - Line Reader (reading guide)
6
+ * - Color Scheme (theme/contrast)
7
+ * - Annotation Toolbar (text highlighting)
8
+ *
9
+ * Maps to QTI 3.0 standard access features from:
10
+ * - visual category: highContrastDisplay, colorContrast
11
+ * - reading category: readingMask, readingGuide, highlighting
12
+ */
13
+ import type { ToolRegistration } from "@pie-players/pie-assessment-toolkit/tools/internal";
14
+ /**
15
+ * Line Reader tool registration
16
+ *
17
+ * Provides a reading guide overlay to help track reading position.
18
+ * Useful for text-heavy passages and questions.
19
+ */
20
+ export declare const lineReaderToolRegistration: ToolRegistration;
21
+ /**
22
+ * Theme tool registration
23
+ *
24
+ * Provides accessible theme and contrast controls.
25
+ * Global tool that affects entire assessment.
26
+ */
27
+ export declare const themeToolRegistration: ToolRegistration;
28
+ /**
29
+ * Annotation Toolbar registration
30
+ *
31
+ * Text highlighting with CSS Custom Highlight API.
32
+ * Zero DOM mutation, optimal performance.
33
+ */
34
+ export declare const annotationToolbarRegistration: ToolRegistration;
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Accessibility Tools Registrations
3
+ *
4
+ * Registers tools for visual accessibility and reading support:
5
+ * - Line Reader (reading guide)
6
+ * - Color Scheme (theme/contrast)
7
+ * - Annotation Toolbar (text highlighting)
8
+ *
9
+ * Maps to QTI 3.0 standard access features from:
10
+ * - visual category: highContrastDisplay, colorContrast
11
+ * - reading category: readingMask, readingGuide, highlighting
12
+ */
13
+ import { hasReadableText } from "@pie-players/pie-assessment-toolkit/tools/internal";
14
+ import { createToolElement, resolveToolTag, } from "@pie-players/pie-assessment-toolkit/tools/internal";
15
+ import { applyOverlaySurface, createScopedVisibilityBinding, syncButtonAndOverlayVisibility, } from "@pie-players/pie-assessment-toolkit/tools/internal";
16
+ /**
17
+ * Line Reader tool registration
18
+ *
19
+ * Provides a reading guide overlay to help track reading position.
20
+ * Useful for text-heavy passages and questions.
21
+ */
22
+ export const lineReaderToolRegistration = {
23
+ toolId: "lineReader",
24
+ name: "Line Reader",
25
+ description: "Reading guide overlay",
26
+ icon: "bars-3",
27
+ // Line reader appears where there's text to read
28
+ supportedLevels: ["section", "passage", "rubric", "item"],
29
+ // PNP support IDs
30
+ // Maps to QTI 3.0 standard features: readingMask, readingGuide, readingRuler
31
+ pnpSupportIds: [
32
+ "readingMask", // QTI 3.0 standard (reading.readingMask)
33
+ "readingGuide", // QTI 3.0 standard (reading.readingGuide)
34
+ "readingRuler", // QTI 3.0 standard (reading.readingRuler)
35
+ "lineReader", // Common variant
36
+ "trackingGuide", // Common variant
37
+ ],
38
+ /**
39
+ * Pass 2: Line reader is relevant when readable text is present
40
+ */
41
+ isVisibleInContext(context) {
42
+ return hasReadableText(context);
43
+ },
44
+ renderToolbar(context, toolbarContext) {
45
+ const visibility = createScopedVisibilityBinding(this.toolId, toolbarContext);
46
+ const button = {
47
+ toolId: this.toolId,
48
+ label: this.name,
49
+ icon: typeof this.icon === "function" ? this.icon(context) : this.icon,
50
+ disabled: false,
51
+ ariaLabel: "Line reader - Reading guide",
52
+ tooltip: "Line Reader",
53
+ onClick: () => toolbarContext.toggleTool(this.toolId),
54
+ active: visibility.isActive(),
55
+ };
56
+ const componentOverrides = toolbarContext.componentOverrides ?? {};
57
+ const overlay = createToolElement(this.toolId, context, toolbarContext, componentOverrides);
58
+ overlay.setAttribute("tool-id", visibility.fullToolId);
59
+ applyOverlaySurface(overlay, "frameless");
60
+ return {
61
+ toolId: this.toolId,
62
+ button,
63
+ elements: [{ element: overlay, mount: "after-buttons" }],
64
+ sync: () => {
65
+ syncButtonAndOverlayVisibility({
66
+ button,
67
+ overlay,
68
+ isActive: visibility.isActive,
69
+ });
70
+ if (toolbarContext.toolkitCoordinator) {
71
+ overlay.toolkitCoordinator = toolbarContext.toolkitCoordinator;
72
+ }
73
+ },
74
+ subscribeActive: visibility.subscribeActive,
75
+ };
76
+ },
77
+ };
78
+ /**
79
+ * Theme tool registration
80
+ *
81
+ * Provides accessible theme and contrast controls.
82
+ * Global tool that affects entire assessment.
83
+ */
84
+ export const themeToolRegistration = {
85
+ toolId: "theme",
86
+ name: "Theme",
87
+ description: "Accessible themes and contrast",
88
+ icon: "swatch",
89
+ // Color scheme is assessment-wide
90
+ supportedLevels: ["assessment", "section"],
91
+ // PNP support IDs
92
+ // Maps to QTI 3.0 standard features: highContrastDisplay, colorContrast, invertColors
93
+ pnpSupportIds: [
94
+ "highContrastDisplay", // QTI 3.0 standard (visual.highContrastDisplay)
95
+ "colorContrast", // QTI 3.0 standard (visual.colorContrast)
96
+ "invertColors", // QTI 3.0 standard (visual.invertColors)
97
+ "theme", // Canonical id
98
+ "highContrast", // Common variant
99
+ "customColors", // Common variant
100
+ ],
101
+ /**
102
+ * Pass 2: Color scheme is always relevant when allowed
103
+ */
104
+ isVisibleInContext(context) {
105
+ return true; // Always show if allowed by orchestrator
106
+ },
107
+ renderToolbar(context, toolbarContext) {
108
+ const visibility = createScopedVisibilityBinding(this.toolId, toolbarContext);
109
+ const button = {
110
+ toolId: this.toolId,
111
+ label: this.name,
112
+ icon: typeof this.icon === "function" ? this.icon(context) : this.icon,
113
+ disabled: false,
114
+ ariaLabel: "Theme - Change colors and contrast",
115
+ tooltip: "Theme",
116
+ onClick: () => toolbarContext.toggleTool(this.toolId),
117
+ active: visibility.isActive(),
118
+ };
119
+ const componentOverrides = toolbarContext.componentOverrides ?? {};
120
+ const overlay = createToolElement(this.toolId, context, toolbarContext, componentOverrides);
121
+ overlay.setAttribute("tool-id", visibility.fullToolId);
122
+ return {
123
+ toolId: this.toolId,
124
+ button,
125
+ elements: [
126
+ {
127
+ element: overlay,
128
+ mount: "after-buttons",
129
+ shell: {
130
+ title: this.name,
131
+ draggable: true,
132
+ resizable: false,
133
+ closeable: true,
134
+ initialWidth: 520,
135
+ initialHeight: 380,
136
+ minWidth: 420,
137
+ minHeight: 300,
138
+ },
139
+ },
140
+ ],
141
+ sync: () => {
142
+ syncButtonAndOverlayVisibility({
143
+ button,
144
+ overlay,
145
+ isActive: visibility.isActive,
146
+ });
147
+ if (toolbarContext.toolkitCoordinator) {
148
+ overlay.toolkitCoordinator = toolbarContext.toolkitCoordinator;
149
+ }
150
+ },
151
+ subscribeActive: visibility.subscribeActive,
152
+ };
153
+ },
154
+ };
155
+ /**
156
+ * Annotation Toolbar registration
157
+ *
158
+ * Text highlighting with CSS Custom Highlight API.
159
+ * Zero DOM mutation, optimal performance.
160
+ */
161
+ export const annotationToolbarRegistration = {
162
+ toolId: "annotationToolbar",
163
+ name: "Highlighter",
164
+ description: "Highlight and annotate text",
165
+ icon: "highlighter",
166
+ activation: "selection-gateway",
167
+ singletonScope: "section",
168
+ /**
169
+ * Section-scoped singleton surface. The gateway used to be mounted by
170
+ * `PieSectionPlayerBaseElement.svelte`, which named this tool id in three
171
+ * places — the policy check, the module load and the element — so no host
172
+ * could contribute a second section-scoped capability. Declaring the surface
173
+ * moves that wiring into this registration, and the renderer discovers it
174
+ * through `ToolRegistry.getToolsBySurface("section-overlay")`.
175
+ */
176
+ surfaces: ["section-overlay"],
177
+ // Annotation appears where there's text content
178
+ supportedLevels: ["passage", "rubric", "item", "element"],
179
+ // PNP support IDs
180
+ // Maps to QTI 3.0 standard features: highlighting, annotations
181
+ pnpSupportIds: [
182
+ "highlighting", // QTI 3.0 standard (cognitive.highlighting / reading.wordHighlighting)
183
+ "annotations", // QTI 3.0 standard (cognitive.annotations)
184
+ "highlighter", // Common variant
185
+ "textHighlight", // Common variant
186
+ "annotation", // Common variant
187
+ ],
188
+ /**
189
+ * Pass 2: Annotation is relevant when readable text is present
190
+ */
191
+ isVisibleInContext(context) {
192
+ return hasReadableText(context);
193
+ },
194
+ /**
195
+ * Mount the section-scoped gateway. The element positions itself (`position:
196
+ * fixed` inside its shadow root) and finds its scope through the toolkit's
197
+ * context, which bubbles from wherever the host mounted it, so this needs to
198
+ * know nothing about the host's layout.
199
+ */
200
+ renderSurface(context) {
201
+ const componentOverrides = context.componentOverrides ?? {};
202
+ const tagName = resolveToolTag(context.toolId, componentOverrides);
203
+ if (typeof customElements !== "undefined" && !customElements.get(tagName)) {
204
+ // The host loads the module before mounting, so reaching here means the
205
+ // optional package is absent. Declining is the right answer: mounting an
206
+ // undefined element would render an empty box the learner cannot use.
207
+ return null;
208
+ }
209
+ const element = document.createElement(tagName);
210
+ // Reads the context it is handed. These were reactive props before the
211
+ // gateway moved behind `renderSurface`, and a host calling
212
+ // `updateAssessment(...)` mid-session swaps the coordinator without
213
+ // remounting — closing over the render-time services would leave the gateway
214
+ // highlighting into the previous session's coordinator.
215
+ const applyServices = (current) => {
216
+ element.enabled = true;
217
+ element.ttsService = current.services.ttsService;
218
+ element.highlightCoordinator =
219
+ current.services.toolkitCoordinator?.highlightCoordinator ?? null;
220
+ };
221
+ applyServices(context);
222
+ return { element, sync: applyServices };
223
+ },
224
+ renderToolbar(context, toolbarContext) {
225
+ const visibility = createScopedVisibilityBinding(this.toolId, toolbarContext);
226
+ const button = {
227
+ toolId: this.toolId,
228
+ label: this.name,
229
+ icon: typeof this.icon === "function" ? this.icon(context) : this.icon,
230
+ disabled: false,
231
+ ariaLabel: "Annotation toolbar - Highlight text",
232
+ tooltip: "Highlight",
233
+ onClick: () => toolbarContext.toggleTool(this.toolId),
234
+ active: visibility.isActive(),
235
+ };
236
+ const componentOverrides = toolbarContext.componentOverrides ?? {};
237
+ const overlay = createToolElement(this.toolId, context, toolbarContext, componentOverrides);
238
+ overlay.setAttribute("tool-id", visibility.fullToolId);
239
+ return {
240
+ toolId: this.toolId,
241
+ button,
242
+ elements: [{ element: overlay, mount: "after-buttons" }],
243
+ sync: () => {
244
+ syncButtonAndOverlayVisibility({
245
+ button,
246
+ overlay,
247
+ isActive: visibility.isActive,
248
+ });
249
+ if (toolbarContext.toolkitCoordinator) {
250
+ overlay.toolkitCoordinator = toolbarContext.toolkitCoordinator;
251
+ }
252
+ },
253
+ subscribeActive: visibility.subscribeActive,
254
+ };
255
+ },
256
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Calculator Tool Registration
3
+ *
4
+ * Registers the calculator tool with support for multiple calculator types
5
+ * (basic, scientific, graphing) via Desmos provider.
6
+ *
7
+ * Maps to QTI 3.0 standard access features:
8
+ * - calculator (cognitive support)
9
+ * - graphingCalculator (assessment tool)
10
+ */
11
+ import type { ToolRegistration } from "@pie-players/pie-assessment-toolkit/tools/internal";
12
+ /**
13
+ * Calculator tool registration
14
+ *
15
+ * Supports:
16
+ * - Basic, scientific, and graphing calculators via Desmos
17
+ * - Context-aware visibility (shows only when math content is detected)
18
+ * - Item level only
19
+ */
20
+ export declare const calculatorToolRegistration: ToolRegistration;