@pie-players/pie-default-tool-loaders 0.3.66 → 0.3.68

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.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The packaged capabilities that carry an authored alternate and render it as a
3
+ * region.
4
+ *
5
+ * A projection of the composition set below, kept in its own module for one
6
+ * reason: it must be importable without reaching the packaged composition table,
7
+ * whose lazy loaders name concrete `pie-tool-*` packages. A renderer that only
8
+ * ever shows text alternates — print — would otherwise carry a calculator and a
9
+ * TTS bundle it can never activate.
10
+ *
11
+ * Which slot an alternate fills is still the renderer's decision, not this list's:
12
+ * every entry declares its own `surfaces`, and a host resolves the slots it
13
+ * actually opens. Print opens the in-flow one and not the docked-media one,
14
+ * because a signed alternate on paper is a blank rectangle.
15
+ *
16
+ * `tests/content-alternates.test.ts` pins this against the composition set in both
17
+ * directions, so a capability added there cannot quietly fail to reach print.
18
+ */
19
+ import type { ToolRegistration } from "@pie-players/pie-assessment-toolkit/tools/internal";
20
+ export declare const CONTENT_ALTERNATE_REGISTRATIONS: readonly ToolRegistration[];
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The packaged capabilities that carry an authored alternate and render it as a
3
+ * region.
4
+ *
5
+ * A projection of the composition set below, kept in its own module for one
6
+ * reason: it must be importable without reaching the packaged composition table,
7
+ * whose lazy loaders name concrete `pie-tool-*` packages. A renderer that only
8
+ * ever shows text alternates — print — would otherwise carry a calculator and a
9
+ * TTS bundle it can never activate.
10
+ *
11
+ * Which slot an alternate fills is still the renderer's decision, not this list's:
12
+ * every entry declares its own `surfaces`, and a host resolves the slots it
13
+ * actually opens. Print opens the in-flow one and not the docked-media one,
14
+ * because a signed alternate on paper is a blank rectangle.
15
+ *
16
+ * `tests/content-alternates.test.ts` pins this against the composition set in both
17
+ * directions, so a capability added there cannot quietly fail to reach print.
18
+ */
19
+ import { audioTranscriptRegistration } from "./registrations/audio-transcript.js";
20
+ export const CONTENT_ALTERNATE_REGISTRATIONS = [
21
+ audioTranscriptRegistration,
22
+ ];
package/dist/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  export type { PackagedToolRegistryOptions, RegisterDefaultToolModuleLoadersOptions, RegisterPackagedToolsOptions, ToolModuleLoader, ToolRegistryLike, } from "./packaged-capability-composition.js";
2
2
  export { createPackagedToolRegistry, createUniversalPersonalNeedsProfile, DEFAULT_TOOL_MODULE_LOADERS, ITEM_TOOL_MODULE_LOADERS, PACKAGED_TOOL_ORDER, PACKAGED_TOOL_PLACEMENT, PACKAGED_TOOL_REGISTRATIONS, PACKAGED_TOOL_TAG_MAP, registerDefaultToolModuleLoaders, registerPackagedTools, registerSectionToolModuleLoaders, SECTION_PLAYER_PREFERRED_TOOL_PLACEMENT, SECTION_TOOL_MODULE_LOADERS, UNIVERSAL_SUPPORTS_PRESET, } from "./packaged-capability-composition.js";
3
+ export { CONTENT_ALTERNATE_REGISTRATIONS } from "./content-alternates.js";
3
4
  export { annotationToolbarRegistration, lineReaderToolRegistration, themeToolRegistration, } from "./registrations/accessibility-tools.js";
4
5
  export { AUDIO_TRANSCRIPT_FEATURE_ID, AUDIO_TRANSCRIPT_REGION_CLASS, AUDIO_TRANSCRIPT_REGION_LABEL, audioTranscriptRegistration, resolveAudioTranscript, type ResolvedAudioTranscript, } from "./registrations/audio-transcript.js";
5
6
  export { calculatorToolRegistration } from "./registrations/calculator.js";
6
- export { answerEliminatorToolRegistration, highlighterToolRegistration, } from "./registrations/interaction-tools.js";
7
+ export { createDictionaryToolRegistration, createPictureDictionaryToolRegistration, dictionaryToolRegistration, type DictionaryVariantOptions, pictureDictionaryToolRegistration, spanishDictionaryToolRegistration, spanishPictureDictionaryToolRegistration, } from "./registrations/dictionary-tools.js";
8
+ export { answerEliminatorToolRegistration } from "./registrations/interaction-tools.js";
7
9
  export { protractorToolRegistration, rulerToolRegistration, } from "./registrations/measurement-tools.js";
8
10
  export { graphToolRegistration, periodicTableToolRegistration, } from "./registrations/subject-specific-tools.js";
9
11
  export { ttsToolRegistration } from "./registrations/tts.js";
package/dist/index.js CHANGED
@@ -1,10 +1,14 @@
1
1
  export { createPackagedToolRegistry, createUniversalPersonalNeedsProfile, DEFAULT_TOOL_MODULE_LOADERS, ITEM_TOOL_MODULE_LOADERS, PACKAGED_TOOL_ORDER, PACKAGED_TOOL_PLACEMENT, PACKAGED_TOOL_REGISTRATIONS, PACKAGED_TOOL_TAG_MAP, registerDefaultToolModuleLoaders, registerPackagedTools, registerSectionToolModuleLoaders, SECTION_PLAYER_PREFERRED_TOOL_PLACEMENT, SECTION_TOOL_MODULE_LOADERS, UNIVERSAL_SUPPORTS_PRESET, } from "./packaged-capability-composition.js";
2
+ // The authored-alternate subset, for a renderer that shows alternates and no
3
+ // toolbar — importable without the packaged composition's tool loaders.
4
+ export { CONTENT_ALTERNATE_REGISTRATIONS } from "./content-alternates.js";
2
5
  // Individual registrations, for a host composing a subset rather than the whole
3
6
  // packaged set.
4
7
  export { annotationToolbarRegistration, lineReaderToolRegistration, themeToolRegistration, } from "./registrations/accessibility-tools.js";
5
8
  export { AUDIO_TRANSCRIPT_FEATURE_ID, AUDIO_TRANSCRIPT_REGION_CLASS, AUDIO_TRANSCRIPT_REGION_LABEL, audioTranscriptRegistration, resolveAudioTranscript, } from "./registrations/audio-transcript.js";
6
9
  export { calculatorToolRegistration } from "./registrations/calculator.js";
7
- export { answerEliminatorToolRegistration, highlighterToolRegistration, } from "./registrations/interaction-tools.js";
10
+ export { createDictionaryToolRegistration, createPictureDictionaryToolRegistration, dictionaryToolRegistration, pictureDictionaryToolRegistration, spanishDictionaryToolRegistration, spanishPictureDictionaryToolRegistration, } from "./registrations/dictionary-tools.js";
11
+ export { answerEliminatorToolRegistration } from "./registrations/interaction-tools.js";
8
12
  export { protractorToolRegistration, rulerToolRegistration, } from "./registrations/measurement-tools.js";
9
13
  export { graphToolRegistration, periodicTableToolRegistration, } from "./registrations/subject-specific-tools.js";
10
14
  export { ttsToolRegistration } from "./registrations/tts.js";
@@ -41,7 +41,7 @@ export type RegisterDefaultToolModuleLoadersOptions = {
41
41
  * that prevents an otherwise usable assessment from rendering.
42
42
  */
43
43
  export declare function assertPackagedCapabilityComposition(): void;
44
- export declare const PACKAGED_TOOL_REGISTRATIONS: readonly [ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration];
44
+ export declare const PACKAGED_TOOL_REGISTRATIONS: readonly [ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration, ToolRegistration];
45
45
  export declare const PACKAGED_TOOL_TAG_MAP: ToolTagMap;
46
46
  export declare const ITEM_TOOL_MODULE_LOADERS: Record<string, ToolModuleLoader>;
47
47
  export declare const SECTION_TOOL_MODULE_LOADERS: Record<string, ToolModuleLoader>;
@@ -49,17 +49,17 @@ export declare const DEFAULT_TOOL_MODULE_LOADERS: Record<string, ToolModuleLoade
49
49
  export declare const PACKAGED_TOOL_PLACEMENT: {
50
50
  readonly assessment: readonly ["theme"];
51
51
  readonly section: readonly ["theme"];
52
- readonly item: readonly ["textToSpeech", "highlighter", "annotationToolbar", "graph", "periodicTable"];
53
- readonly passage: readonly ["textToSpeech", "highlighter", "annotationToolbar", "lineReader"];
54
- readonly rubric: readonly ["textToSpeech", "highlighter", "annotationToolbar", "lineReader"];
55
- readonly element: readonly ["calculator", "answerEliminator", "textToSpeech", "ruler", "protractor", "highlighter", "annotationToolbar", "graph", "periodicTable"];
52
+ readonly item: readonly ["textToSpeech", "annotationToolbar", "graph", "periodicTable", "dictionary", "pictureDictionary", "dictionarySpanish", "pictureDictionarySpanish"];
53
+ readonly passage: readonly ["textToSpeech", "annotationToolbar", "lineReader"];
54
+ readonly rubric: readonly ["textToSpeech", "annotationToolbar", "lineReader"];
55
+ readonly element: readonly ["calculator", "answerEliminator", "textToSpeech", "ruler", "protractor", "annotationToolbar", "graph", "periodicTable", "dictionary", "pictureDictionary", "dictionarySpanish", "pictureDictionarySpanish"];
56
56
  };
57
57
  export declare const SECTION_PLAYER_PREFERRED_TOOL_PLACEMENT: {
58
58
  section: string[];
59
59
  item: string[];
60
60
  passage: string[];
61
61
  };
62
- export declare const PACKAGED_TOOL_ORDER: readonly ["theme", "calculator", "textToSpeech", "lineReader", "annotationToolbar", "highlighter", "answerEliminator", "ruler", "protractor", "graph", "periodicTable"];
62
+ export declare const PACKAGED_TOOL_ORDER: readonly ["theme", "calculator", "textToSpeech", "lineReader", "annotationToolbar", "answerEliminator", "ruler", "protractor", "graph", "periodicTable", "dictionary", "pictureDictionary", "dictionarySpanish", "pictureDictionarySpanish"];
63
63
  export declare const UNIVERSAL_SUPPORTS_PRESET: readonly string[];
64
64
  /** Register packaged PIE capabilities onto an existing registry. */
65
65
  export declare function registerPackagedTools(registry: ToolRegistry, options?: RegisterPackagedToolsOptions): void;
@@ -11,7 +11,8 @@ import { ToolRegistry, } from "@pie-players/pie-assessment-toolkit/tools/interna
11
11
  import { annotationToolbarRegistration, lineReaderToolRegistration, themeToolRegistration, } from "./registrations/accessibility-tools.js";
12
12
  import { audioTranscriptRegistration } from "./registrations/audio-transcript.js";
13
13
  import { calculatorToolRegistration } from "./registrations/calculator.js";
14
- import { answerEliminatorToolRegistration, highlighterToolRegistration, } from "./registrations/interaction-tools.js";
14
+ import { dictionaryToolRegistration, pictureDictionaryToolRegistration, spanishDictionaryToolRegistration, spanishPictureDictionaryToolRegistration, } from "./registrations/dictionary-tools.js";
15
+ import { answerEliminatorToolRegistration } from "./registrations/interaction-tools.js";
15
16
  import { protractorToolRegistration, rulerToolRegistration, } from "./registrations/measurement-tools.js";
16
17
  import { graphToolRegistration, periodicTableToolRegistration, } from "./registrations/subject-specific-tools.js";
17
18
  import { ttsToolRegistration } from "./registrations/tts.js";
@@ -33,6 +34,8 @@ const loadLineReaderModule = () => loadSideEffectModule(() => import("@pie-playe
33
34
  const loadThemeModule = () => loadSideEffectModule(() => import("@pie-players/pie-tool-theme"));
34
35
  const loadGraphModule = () => loadSideEffectModule(() => import("@pie-players/pie-tool-graph"));
35
36
  const loadPeriodicTableModule = () => loadSideEffectModule(() => import("@pie-players/pie-tool-periodic-table"));
37
+ const loadDictionaryModule = () => loadSideEffectModule(() => import("@pie-players/pie-tool-dictionary"));
38
+ const loadPictureDictionaryModule = () => loadSideEffectModule(() => import("@pie-players/pie-tool-picture-dictionary"));
36
39
  const PACKAGED_CAPABILITY_DEFINITIONS = [
37
40
  {
38
41
  registration: calculatorToolRegistration,
@@ -94,15 +97,6 @@ const PACKAGED_CAPABILITY_DEFINITIONS = [
94
97
  "choiceMasking",
95
98
  ],
96
99
  },
97
- {
98
- registration: highlighterToolRegistration,
99
- tagName: "pie-tool-annotation-toolbar",
100
- loadModule: loadAnnotationToolbarModule,
101
- loaderTargets: ["item"],
102
- placementOrder: { item: 20, passage: 20, rubric: 20, element: 60 },
103
- toolbarOrder: 60,
104
- universalSupportIds: ["highlighter", "textHighlight", "annotation"],
105
- },
106
100
  {
107
101
  registration: lineReaderToolRegistration,
108
102
  tagName: "pie-tool-line-reader",
@@ -144,7 +138,16 @@ const PACKAGED_CAPABILITY_DEFINITIONS = [
144
138
  placementOrder: { item: 30, passage: 30, rubric: 30, element: 70 },
145
139
  preferredPlacementOrder: { item: 40, passage: 20 },
146
140
  toolbarOrder: 50,
147
- universalSupportIds: ["highlighting", "annotations"],
141
+ // The last three came from the `highlighter` capability, which mounted this
142
+ // same element behind a second identically-labelled button. Kept here so a
143
+ // profile granted one of the older ids still gets highlighting.
144
+ universalSupportIds: [
145
+ "highlighting",
146
+ "annotations",
147
+ "highlighter",
148
+ "textHighlight",
149
+ "annotation",
150
+ ],
148
151
  },
149
152
  {
150
153
  registration: graphToolRegistration,
@@ -170,6 +173,51 @@ const PACKAGED_CAPABILITY_DEFINITIONS = [
170
173
  "elementReference",
171
174
  ],
172
175
  },
176
+ {
177
+ registration: dictionaryToolRegistration,
178
+ tagName: "pie-tool-dictionary",
179
+ loadModule: loadDictionaryModule,
180
+ loaderTargets: ["section"],
181
+ placementOrder: { item: 60, element: 100 },
182
+ preferredPlacementOrder: { section: 70 },
183
+ toolbarOrder: 120,
184
+ // Deliberately empty: a dictionary is a granted accommodation, and on a
185
+ // vocabulary item it is construct-relevant, so it is never universal.
186
+ universalSupportIds: [],
187
+ },
188
+ {
189
+ registration: pictureDictionaryToolRegistration,
190
+ tagName: "pie-tool-picture-dictionary",
191
+ loadModule: loadPictureDictionaryModule,
192
+ loaderTargets: ["section"],
193
+ placementOrder: { item: 70, element: 110 },
194
+ preferredPlacementOrder: { section: 80 },
195
+ toolbarOrder: 130,
196
+ universalSupportIds: [],
197
+ },
198
+ // The Spanish variants render the same elements under their own capability ids, so a
199
+ // programme can grant a Spanish gloss beside the content-following dictionary or
200
+ // instead of it. Ordered after both, since English-content delivery is the common case.
201
+ {
202
+ registration: spanishDictionaryToolRegistration,
203
+ tagName: "pie-tool-dictionary",
204
+ loadModule: loadDictionaryModule,
205
+ loaderTargets: ["section"],
206
+ placementOrder: { item: 80, element: 120 },
207
+ preferredPlacementOrder: { section: 90 },
208
+ toolbarOrder: 140,
209
+ universalSupportIds: [],
210
+ },
211
+ {
212
+ registration: spanishPictureDictionaryToolRegistration,
213
+ tagName: "pie-tool-picture-dictionary",
214
+ loadModule: loadPictureDictionaryModule,
215
+ loaderTargets: ["section"],
216
+ placementOrder: { item: 90, element: 130 },
217
+ preferredPlacementOrder: { section: 100 },
218
+ toolbarOrder: 150,
219
+ universalSupportIds: [],
220
+ },
173
221
  {
174
222
  registration: audioTranscriptRegistration,
175
223
  universalSupportIds: [],
@@ -13,6 +13,8 @@
13
13
  import { hasReadableText } from "@pie-players/pie-assessment-toolkit/tools/internal";
14
14
  import { createToolElement, resolveToolTag, } from "@pie-players/pie-assessment-toolkit/tools/internal";
15
15
  import { applyOverlaySurface, createScopedVisibilityBinding, syncButtonAndOverlayVisibility, } from "@pie-players/pie-assessment-toolkit/tools/internal";
16
+ import { buildSelectionActions } from "./selection-actions.js";
17
+ import { renderOverlayToolbar } from "./overlay-toolbar-render.js";
16
18
  /**
17
19
  * Line Reader tool registration
18
20
  *
@@ -23,6 +25,8 @@ export const lineReaderToolRegistration = {
23
25
  toolId: "lineReader",
24
26
  name: "Line Reader",
25
27
  description: "Reading guide overlay",
28
+ nameKey: "tools.lineReader.name",
29
+ descriptionKey: "tools.lineReader.description",
26
30
  icon: "bars-3",
27
31
  // Line reader appears where there's text to read
28
32
  supportedLevels: ["section", "passage", "rubric", "item"],
@@ -42,37 +46,10 @@ export const lineReaderToolRegistration = {
42
46
  return hasReadableText(context);
43
47
  },
44
48
  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
- };
49
+ return renderOverlayToolbar(this, context, toolbarContext, {
50
+ surface: "frameless",
51
+ handsOverCoordinator: true,
52
+ });
76
53
  },
77
54
  };
78
55
  /**
@@ -85,6 +62,8 @@ export const themeToolRegistration = {
85
62
  toolId: "theme",
86
63
  name: "Theme",
87
64
  description: "Accessible themes and contrast",
65
+ nameKey: "tools.theme.name",
66
+ descriptionKey: "tools.theme.description",
88
67
  icon: "swatch",
89
68
  // Color scheme is assessment-wide
90
69
  supportedLevels: ["assessment", "section"],
@@ -105,51 +84,16 @@ export const themeToolRegistration = {
105
84
  return true; // Always show if allowed by orchestrator
106
85
  },
107
86
  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
- }
87
+ return renderOverlayToolbar(this, context, toolbarContext, {
88
+ shell: {
89
+ resizable: false,
90
+ initialWidth: 520,
91
+ initialHeight: 380,
92
+ minWidth: 420,
93
+ minHeight: 300,
150
94
  },
151
- subscribeActive: visibility.subscribeActive,
152
- };
95
+ handsOverCoordinator: true,
96
+ });
153
97
  },
154
98
  };
155
99
  /**
@@ -162,6 +106,8 @@ export const annotationToolbarRegistration = {
162
106
  toolId: "annotationToolbar",
163
107
  name: "Highlighter",
164
108
  description: "Highlight and annotate text",
109
+ nameKey: "tools.annotationToolbar.name",
110
+ descriptionKey: "tools.annotationToolbar.description",
165
111
  icon: "highlighter",
166
112
  activation: "selection-gateway",
167
113
  singletonScope: "section",
@@ -217,40 +163,17 @@ export const annotationToolbarRegistration = {
217
163
  element.ttsService = current.services.ttsService;
218
164
  element.highlightCoordinator =
219
165
  current.services.toolkitCoordinator?.highlightCoordinator ?? null;
166
+ // The second door onto the dictionaries. The gateway renders these and
167
+ // knows nothing of what they open; the pairing is composition's, which is
168
+ // why the list is built in `selection-actions.ts` and not here.
169
+ element.selectionActions = buildSelectionActions(current.services);
220
170
  };
221
171
  applyServices(context);
222
172
  return { element, sync: applyServices };
223
173
  },
224
174
  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
- };
175
+ return renderOverlayToolbar(this, context, toolbarContext, {
176
+ handsOverCoordinator: true,
177
+ });
255
178
  },
256
179
  };
@@ -144,6 +144,8 @@ export const audioTranscriptRegistration = {
144
144
  toolId: AUDIO_TRANSCRIPT_FEATURE_ID,
145
145
  name: "Audio Transcript",
146
146
  description: "Text of an audio prompt, read before the content it transcribes",
147
+ nameKey: "tools.audioTranscript.name",
148
+ descriptionKey: "tools.audioTranscript.description",
147
149
  supportedLevels: ["item", "passage"],
148
150
  pnpSupportIds: [AUDIO_TRANSCRIPT_FEATURE_ID],
149
151
  // No button to press: a transcript is either authored-visible, or granted and
@@ -173,7 +175,11 @@ export const audioTranscriptRegistration = {
173
175
  applyTranscript(region, transcript);
174
176
  return {
175
177
  element: region,
176
- ariaLabel: AUDIO_TRANSCRIPT_REGION_LABEL,
178
+ // `AUDIO_TRANSCRIPT_REGION_LABEL` stays the English source and the
179
+ // exported constant tests address; the catalog supplies the rendered
180
+ // label when a host published a provider.
181
+ ariaLabel: context.services.i18n?.t("tools.audioTranscript.regionA11y") ??
182
+ AUDIO_TRANSCRIPT_REGION_LABEL,
177
183
  // Reads the context it is handed, never the one captured above: on a
178
184
  // re-resolve the host's context carries the current card, and a learner must
179
185
  // not keep reading the previous item's transcript.
@@ -61,13 +61,21 @@ function getCalculatorRenderParams(toolbarContext) {
61
61
  return {
62
62
  calculatorType,
63
63
  availableTypes,
64
- displayName: calculatorType === "scientific"
65
- ? "Scientific Calculator"
66
- : calculatorType === "basic"
67
- ? "Basic Calculator"
68
- : "Calculator",
64
+ displayName: toolbarContext.i18n.t(CALCULATOR_NAME_KEYS[calculatorType ?? "untyped"]),
69
65
  };
70
66
  }
67
+ /**
68
+ * The calculator's displayed name, per host-declared type.
69
+ *
70
+ * `untyped` keeps the pre-adoption behaviour of naming the scientific
71
+ * calculator, which is what the toolbar has always announced when the host
72
+ * declares no type.
73
+ */
74
+ const CALCULATOR_NAME_KEYS = {
75
+ basic: "tools.calculator.nameBasic",
76
+ scientific: "tools.calculator.nameScientific",
77
+ untyped: "tools.calculator.name",
78
+ };
71
79
  function applyCalculatorParamsToElement(element, calculatorType, availableTypes) {
72
80
  const calculatorElement = element;
73
81
  if (calculatorType) {
@@ -97,6 +105,8 @@ export const calculatorToolRegistration = {
97
105
  toolId: "calculator",
98
106
  name: "Calculator",
99
107
  description: "Multi-type calculator (basic, scientific, graphing)",
108
+ nameKey: "tools.calculator.name",
109
+ descriptionKey: "tools.calculator.description",
100
110
  icon: "calculator",
101
111
  provider: {
102
112
  getProviderId: (config) => typeof config?.provider?.id === "string" && config.provider.id.length > 0
@@ -154,18 +164,20 @@ export const calculatorToolRegistration = {
154
164
  overlay.setAttribute("tool-id", fullToolId);
155
165
  overlay.toolkitCoordinator = toolbarContext.toolkitCoordinator;
156
166
  applyCalculatorParamsToElement(overlay, calculatorType, availableTypes);
157
- const openLabel = calculatorType === null
158
- ? "Open scientific calculator"
159
- : `Open ${displayName.toLowerCase()}`;
160
- const closeLabel = calculatorType === null
161
- ? "Close scientific calculator"
162
- : `Close ${displayName.toLowerCase()}`;
163
167
  const button = {
164
168
  toolId: this.toolId,
165
169
  label: displayName,
166
170
  icon: typeof this.icon === "function" ? this.icon(context) : this.icon,
171
+ // Routes this button through <nds-icon-button> where the host enables NDS
172
+ // icons. Declared here because which capabilities render in the host's
173
+ // design system is a composition decision; the toolbar used to map it from
174
+ // the `calculator` toolId, which put a capability name in the generic core.
175
+ faIconName: "calculator",
167
176
  disabled: false,
168
- ariaLabel: openLabel,
177
+ // The name stays put across open and closed. The toolbar mirrors
178
+ // `active` onto the button as `aria-pressed`, so the state is already
179
+ // exposed; a name that also swapped to "Close …" would contradict it.
180
+ ariaLabel: displayName,
169
181
  tooltip: displayName,
170
182
  onClick: () => toolbarContext.toggleTool(this.toolId),
171
183
  active: toolbarContext.isToolVisible(fullToolId),
@@ -181,7 +193,9 @@ export const calculatorToolRegistration = {
181
193
  element: overlay,
182
194
  mount: "after-buttons",
183
195
  shell: {
184
- title: this.name,
196
+ // The variant name the button already carries — "Basic Calculator",
197
+ // not the registration's generic one.
198
+ title: displayName,
185
199
  draggable: true,
186
200
  resizable: true,
187
201
  closeable: true,
@@ -191,6 +205,13 @@ export const calculatorToolRegistration = {
191
205
  minHeight: 420,
192
206
  initialAlign: "bottom-right",
193
207
  initialMargin: 16,
208
+ // Header controls in the host's design system, and the layout that
209
+ // goes with them.
210
+ ndsHeaderControls: true,
211
+ // A learner reads the question while using the calculator, so Tab and
212
+ // Shift+Tab cross between the page and the shell instead of cycling
213
+ // inside it.
214
+ pageTabOrder: true,
194
215
  content: {
195
216
  overflowY: "auto",
196
217
  preserveMinHeight: true,
@@ -203,10 +224,12 @@ export const calculatorToolRegistration = {
203
224
  const active = toolbarContext.isToolVisible(fullToolId);
204
225
  button.active = active;
205
226
  button.label = displayName;
206
- button.ariaLabel = active ? closeLabel : openLabel;
207
- button.tooltip = active
208
- ? `Close ${displayName.toLowerCase()}`
209
- : displayName;
227
+ // Static across the toggle. The previous `Close ${name.toLowerCase()}`
228
+ // was a hardcoded English template built by lowercasing a localized
229
+ // noun — "Close rekenmachine" under nl-NL — and it is the state
230
+ // `aria-pressed` already carries.
231
+ button.ariaLabel = displayName;
232
+ button.tooltip = displayName;
210
233
  if (lastVisibleState !== active) {
211
234
  overlay.visible = active;
212
235
  lastVisibleState = active;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Dictionary Tool Registrations
3
+ *
4
+ * Word lookup and picture (symbol) lookup, each a floating panel opened from the
5
+ * toolbar.
6
+ *
7
+ * Neither declares a universal support id. A dictionary is a granted accommodation,
8
+ * not a universal affordance: on a vocabulary item it is construct-relevant, so
9
+ * handing it to every learner by default would change what the item measures. A
10
+ * programme grants it through the PNP like any other accommodation.
11
+ *
12
+ * Both panels also accept a `term` from whatever selection affordance a host offers,
13
+ * but neither depends on one. A sighted keyboard-only learner cannot originate a text
14
+ * selection in non-editable content — Chromium does not extend one with Shift+Arrow
15
+ * there without caret browsing, an OS toggle absent on mobile — so a selection-only
16
+ * dictionary is unreachable for them. The panel's own field is the keyboard route.
17
+ */
18
+ import type { ToolRegistration } from "@pie-players/pie-assessment-toolkit/tools/internal";
19
+ /**
20
+ * What distinguishes one dictionary capability from another.
21
+ *
22
+ * A dictionary's language belongs to the learner, not to the content: the learner who
23
+ * needs a Spanish gloss is reading an English passage. So a deployment that offers two
24
+ * languages at once composes two capabilities, each granted on its own through the PNP,
25
+ * rather than one capability that follows whatever the section was authored in. That is
26
+ * also how the delivery systems PIE is measured against ship it — English Dictionary and
27
+ * Spanish Dictionary as two entries a programme grants separately.
28
+ *
29
+ * Catalog keys are derived from `toolId` (`tools.<toolId>.name` and so on), which holds for
30
+ * every packaged variant. A host composing a variant against its own catalog passes
31
+ * `messageKeyPrefix`; a key that does not resolve falls back to `name` and `description`,
32
+ * so a host catalog that lacks one is a plain label rather than a broken button.
33
+ */
34
+ export interface DictionaryVariantOptions {
35
+ /** Capability id, unique per language. Defaults to the base tool's id. */
36
+ toolId?: string;
37
+ name?: string;
38
+ description?: string;
39
+ /** Catalog prefix for this variant's strings. Defaults to `tools.<toolId>`. */
40
+ messageKeyPrefix?: string;
41
+ /** PNP ids that grant this variant. A language variant needs its own. */
42
+ pnpSupportIds?: string[];
43
+ /**
44
+ * The corpus language this capability looks words up in, as a BCP-47 tag.
45
+ *
46
+ * Sent with every lookup, outranking the toolbar's content-alternate language and
47
+ * outranked by a language the host names in this tool's render params. Absent on the
48
+ * base capabilities, which follow the content.
49
+ */
50
+ lookupLanguage?: string;
51
+ }
52
+ /**
53
+ * Dictionary tool registration
54
+ *
55
+ * Word lookup from a host-supplied service, in one language. Call this again with another
56
+ * `toolId`, `pnpSupportIds` and `lookupLanguage` to offer a second language beside it.
57
+ */
58
+ export declare function createDictionaryToolRegistration(options?: DictionaryVariantOptions): ToolRegistration;
59
+ /** The content-following dictionary every deployment gets. */
60
+ export declare const dictionaryToolRegistration: ToolRegistration;
61
+ /**
62
+ * Picture Dictionary tool registration
63
+ *
64
+ * Symbol or picture lookup from a host-supplied service. Sized wider than the word
65
+ * dictionary because its results are a grid rather than a column of text.
66
+ */
67
+ export declare function createPictureDictionaryToolRegistration(options?: DictionaryVariantOptions): ToolRegistration;
68
+ /** The content-following picture dictionary every deployment gets. */
69
+ export declare const pictureDictionaryToolRegistration: ToolRegistration;
70
+ /**
71
+ * Spanish variants, packaged because a Spanish gloss for English content is a named
72
+ * accommodation rather than a hypothetical. Neither appears unless a PNP grants it.
73
+ */
74
+ export declare const spanishDictionaryToolRegistration: ToolRegistration;
75
+ export declare const spanishPictureDictionaryToolRegistration: ToolRegistration;