@pie-players/pie-default-tool-loaders 0.3.67 → 0.3.69
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 +38 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/packaged-capability-composition.d.ts +22 -10
- package/dist/packaged-capability-composition.js +123 -50
- package/dist/registrations/accessibility-tools.js +29 -113
- package/dist/registrations/audio-transcript.js +7 -1
- package/dist/registrations/calculator.d.ts +6 -2
- package/dist/registrations/calculator.js +144 -45
- package/dist/registrations/dictionary-tools.d.ts +75 -0
- package/dist/registrations/dictionary-tools.js +235 -0
- package/dist/registrations/interaction-tools.d.ts +0 -7
- package/dist/registrations/interaction-tools.js +16 -62
- package/dist/registrations/measurement-tools.js +13 -64
- package/dist/registrations/overlay-toolbar-render.d.ts +43 -0
- package/dist/registrations/overlay-toolbar-render.js +75 -0
- package/dist/registrations/selection-actions.d.ts +24 -0
- package/dist/registrations/selection-actions.js +73 -0
- package/dist/registrations/subject-specific-tools.js +21 -86
- package/dist/registrations/tts.js +2 -2
- package/package.json +19 -14
|
@@ -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"],
|
|
@@ -32,8 +36,6 @@ export const lineReaderToolRegistration = {
|
|
|
32
36
|
"readingMask", // QTI 3.0 standard (reading.readingMask)
|
|
33
37
|
"readingGuide", // QTI 3.0 standard (reading.readingGuide)
|
|
34
38
|
"readingRuler", // QTI 3.0 standard (reading.readingRuler)
|
|
35
|
-
"lineReader", // Common variant
|
|
36
|
-
"trackingGuide", // Common variant
|
|
37
39
|
],
|
|
38
40
|
/**
|
|
39
41
|
* Pass 2: Line reader is relevant when readable text is present
|
|
@@ -42,37 +44,10 @@ export const lineReaderToolRegistration = {
|
|
|
42
44
|
return hasReadableText(context);
|
|
43
45
|
},
|
|
44
46
|
renderToolbar(context, toolbarContext) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
};
|
|
47
|
+
return renderOverlayToolbar(this, context, toolbarContext, {
|
|
48
|
+
surface: "frameless",
|
|
49
|
+
handsOverCoordinator: true,
|
|
50
|
+
});
|
|
76
51
|
},
|
|
77
52
|
};
|
|
78
53
|
/**
|
|
@@ -85,6 +60,8 @@ export const themeToolRegistration = {
|
|
|
85
60
|
toolId: "theme",
|
|
86
61
|
name: "Theme",
|
|
87
62
|
description: "Accessible themes and contrast",
|
|
63
|
+
nameKey: "tools.theme.name",
|
|
64
|
+
descriptionKey: "tools.theme.description",
|
|
88
65
|
icon: "swatch",
|
|
89
66
|
// Color scheme is assessment-wide
|
|
90
67
|
supportedLevels: ["assessment", "section"],
|
|
@@ -94,9 +71,7 @@ export const themeToolRegistration = {
|
|
|
94
71
|
"highContrastDisplay", // QTI 3.0 standard (visual.highContrastDisplay)
|
|
95
72
|
"colorContrast", // QTI 3.0 standard (visual.colorContrast)
|
|
96
73
|
"invertColors", // QTI 3.0 standard (visual.invertColors)
|
|
97
|
-
"theme", // Canonical id
|
|
98
|
-
"highContrast", // Common variant
|
|
99
|
-
"customColors", // Common variant
|
|
74
|
+
"theme", // Canonical id, and this registration's toolId
|
|
100
75
|
],
|
|
101
76
|
/**
|
|
102
77
|
* Pass 2: Color scheme is always relevant when allowed
|
|
@@ -105,51 +80,16 @@ export const themeToolRegistration = {
|
|
|
105
80
|
return true; // Always show if allowed by orchestrator
|
|
106
81
|
},
|
|
107
82
|
renderToolbar(context, toolbarContext) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
}
|
|
83
|
+
return renderOverlayToolbar(this, context, toolbarContext, {
|
|
84
|
+
shell: {
|
|
85
|
+
resizable: false,
|
|
86
|
+
initialWidth: 520,
|
|
87
|
+
initialHeight: 380,
|
|
88
|
+
minWidth: 420,
|
|
89
|
+
minHeight: 300,
|
|
150
90
|
},
|
|
151
|
-
|
|
152
|
-
};
|
|
91
|
+
handsOverCoordinator: true,
|
|
92
|
+
});
|
|
153
93
|
},
|
|
154
94
|
};
|
|
155
95
|
/**
|
|
@@ -162,6 +102,8 @@ export const annotationToolbarRegistration = {
|
|
|
162
102
|
toolId: "annotationToolbar",
|
|
163
103
|
name: "Highlighter",
|
|
164
104
|
description: "Highlight and annotate text",
|
|
105
|
+
nameKey: "tools.annotationToolbar.name",
|
|
106
|
+
descriptionKey: "tools.annotationToolbar.description",
|
|
165
107
|
icon: "highlighter",
|
|
166
108
|
activation: "selection-gateway",
|
|
167
109
|
singletonScope: "section",
|
|
@@ -181,9 +123,6 @@ export const annotationToolbarRegistration = {
|
|
|
181
123
|
pnpSupportIds: [
|
|
182
124
|
"highlighting", // QTI 3.0 standard (cognitive.highlighting / reading.wordHighlighting)
|
|
183
125
|
"annotations", // QTI 3.0 standard (cognitive.annotations)
|
|
184
|
-
"highlighter", // Common variant
|
|
185
|
-
"textHighlight", // Common variant
|
|
186
|
-
"annotation", // Common variant
|
|
187
126
|
],
|
|
188
127
|
/**
|
|
189
128
|
* Pass 2: Annotation is relevant when readable text is present
|
|
@@ -217,40 +156,17 @@ export const annotationToolbarRegistration = {
|
|
|
217
156
|
element.ttsService = current.services.ttsService;
|
|
218
157
|
element.highlightCoordinator =
|
|
219
158
|
current.services.toolkitCoordinator?.highlightCoordinator ?? null;
|
|
159
|
+
// The second door onto the dictionaries. The gateway renders these and
|
|
160
|
+
// knows nothing of what they open; the pairing is composition's, which is
|
|
161
|
+
// why the list is built in `selection-actions.ts` and not here.
|
|
162
|
+
element.selectionActions = buildSelectionActions(current.services);
|
|
220
163
|
};
|
|
221
164
|
applyServices(context);
|
|
222
165
|
return { element, sync: applyServices };
|
|
223
166
|
},
|
|
224
167
|
renderToolbar(context, toolbarContext) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
};
|
|
168
|
+
return renderOverlayToolbar(this, context, toolbarContext, {
|
|
169
|
+
handsOverCoordinator: true,
|
|
170
|
+
});
|
|
255
171
|
},
|
|
256
172
|
};
|
|
@@ -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
|
-
|
|
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.
|
|
@@ -2,18 +2,22 @@
|
|
|
2
2
|
* Calculator Tool Registration
|
|
3
3
|
*
|
|
4
4
|
* Registers the calculator tool with support for multiple calculator types
|
|
5
|
-
* (basic, scientific, graphing)
|
|
5
|
+
* (basic, scientific, graphing) through a host-selected provider.
|
|
6
6
|
*
|
|
7
7
|
* Maps to QTI 3.0 standard access features:
|
|
8
8
|
* - calculator (cognitive support)
|
|
9
9
|
* - graphingCalculator (assessment tool)
|
|
10
10
|
*/
|
|
11
11
|
import type { ToolRegistration } from "@pie-players/pie-assessment-toolkit/tools/internal";
|
|
12
|
+
import type { ToolProviderConfig } from "@pie-players/pie-assessment-toolkit/tools/internal";
|
|
13
|
+
export type CalculatorProviderId = "calculator-desmos" | "calculator-geogebra" | "calculator-cortex";
|
|
14
|
+
export declare const DEFAULT_CALCULATOR_PROVIDER_ID: CalculatorProviderId;
|
|
15
|
+
export declare function resolveCalculatorProviderId(config: ToolProviderConfig | undefined): CalculatorProviderId;
|
|
12
16
|
/**
|
|
13
17
|
* Calculator tool registration
|
|
14
18
|
*
|
|
15
19
|
* Supports:
|
|
16
|
-
* - Basic, scientific, and graphing calculators
|
|
20
|
+
* - Basic, scientific, and graphing calculators through Desmos, GeoGebra, or Cortex
|
|
17
21
|
* - Context-aware visibility (shows only when math content is detected)
|
|
18
22
|
* - Item level only
|
|
19
23
|
*/
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Calculator Tool Registration
|
|
3
3
|
*
|
|
4
4
|
* Registers the calculator tool with support for multiple calculator types
|
|
5
|
-
* (basic, scientific, graphing)
|
|
5
|
+
* (basic, scientific, graphing) through a host-selected provider.
|
|
6
6
|
*
|
|
7
7
|
* Maps to QTI 3.0 standard access features:
|
|
8
8
|
* - calculator (cognitive support)
|
|
@@ -11,7 +11,45 @@
|
|
|
11
11
|
import { hasMathContent } from "@pie-players/pie-assessment-toolkit/tools/internal";
|
|
12
12
|
import { createScopedToolId } from "@pie-players/pie-assessment-toolkit/tools/internal";
|
|
13
13
|
import { DesmosToolProvider } from "@pie-players/pie-assessment-toolkit/tools/internal";
|
|
14
|
+
import { CortexToolProvider } from "@pie-players/pie-assessment-toolkit/tools/internal";
|
|
15
|
+
import { GeoGebraToolProvider } from "@pie-players/pie-assessment-toolkit/tools/internal";
|
|
14
16
|
import { createToolElement } from "@pie-players/pie-assessment-toolkit/tools/internal";
|
|
17
|
+
export const DEFAULT_CALCULATOR_PROVIDER_ID = "calculator-desmos";
|
|
18
|
+
export function resolveCalculatorProviderId(config) {
|
|
19
|
+
const configured = config?.provider?.id;
|
|
20
|
+
if (configured === undefined || configured === "") {
|
|
21
|
+
return DEFAULT_CALCULATOR_PROVIDER_ID;
|
|
22
|
+
}
|
|
23
|
+
if (configured === "calculator-desmos" ||
|
|
24
|
+
configured === "calculator-geogebra" ||
|
|
25
|
+
configured === "calculator-cortex") {
|
|
26
|
+
return configured;
|
|
27
|
+
}
|
|
28
|
+
throw new Error(`Unsupported calculator provider "${String(configured)}". Expected "calculator-desmos", "calculator-geogebra", or "calculator-cortex".`);
|
|
29
|
+
}
|
|
30
|
+
function createCalculatorToolProvider(config) {
|
|
31
|
+
switch (resolveCalculatorProviderId(config)) {
|
|
32
|
+
case "calculator-cortex":
|
|
33
|
+
return new CortexToolProvider();
|
|
34
|
+
case "calculator-geogebra":
|
|
35
|
+
return new GeoGebraToolProvider();
|
|
36
|
+
default:
|
|
37
|
+
return new DesmosToolProvider();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function getCalculatorInstanceConfig(config) {
|
|
41
|
+
const theme = config?.theme;
|
|
42
|
+
return {
|
|
43
|
+
settings: config?.settings && typeof config.settings === "object"
|
|
44
|
+
? { ...config.settings }
|
|
45
|
+
: {},
|
|
46
|
+
restrictedMode: config?.restrictedMode === true,
|
|
47
|
+
locale: typeof config?.locale === "string" ? config.locale : undefined,
|
|
48
|
+
theme: theme === "light" || theme === "dark" || theme === "auto"
|
|
49
|
+
? theme
|
|
50
|
+
: undefined,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
15
53
|
// The toolbar parent re-derives `renderedTools` whenever item state changes
|
|
16
54
|
// (e.g. the learner answers a question and `effectiveItem`/`renderContext`
|
|
17
55
|
// recompute). Calculator initialization is expensive (Desmos boot, container
|
|
@@ -45,7 +83,9 @@ function setCachedOverlay(coordinator, fullToolId, element) {
|
|
|
45
83
|
scoped.set(fullToolId, element);
|
|
46
84
|
}
|
|
47
85
|
function normalizeCalculatorType(value) {
|
|
48
|
-
return value === "basic" || value === "scientific"
|
|
86
|
+
return value === "basic" || value === "scientific" || value === "graphing"
|
|
87
|
+
? value
|
|
88
|
+
: null;
|
|
49
89
|
}
|
|
50
90
|
function getCalculatorRenderParams(toolbarContext) {
|
|
51
91
|
const params = toolbarContext.getToolRenderParams?.("calculator") ?? {};
|
|
@@ -61,15 +101,27 @@ function getCalculatorRenderParams(toolbarContext) {
|
|
|
61
101
|
return {
|
|
62
102
|
calculatorType,
|
|
63
103
|
availableTypes,
|
|
64
|
-
displayName: calculatorType
|
|
65
|
-
? "Scientific Calculator"
|
|
66
|
-
: calculatorType === "basic"
|
|
67
|
-
? "Basic Calculator"
|
|
68
|
-
: "Calculator",
|
|
104
|
+
displayName: toolbarContext.i18n.t(CALCULATOR_NAME_KEYS[calculatorType ?? "untyped"]),
|
|
69
105
|
};
|
|
70
106
|
}
|
|
71
|
-
|
|
107
|
+
/**
|
|
108
|
+
* The calculator's displayed name, per host-declared type.
|
|
109
|
+
*
|
|
110
|
+
* `untyped` keeps the pre-adoption behaviour of naming the scientific
|
|
111
|
+
* calculator, which is what the toolbar has always announced when the host
|
|
112
|
+
* declares no type.
|
|
113
|
+
*/
|
|
114
|
+
const CALCULATOR_NAME_KEYS = {
|
|
115
|
+
basic: "tools.calculator.nameBasic",
|
|
116
|
+
scientific: "tools.calculator.nameScientific",
|
|
117
|
+
graphing: "tools.calculator.nameGraphing",
|
|
118
|
+
untyped: "tools.calculator.name",
|
|
119
|
+
};
|
|
120
|
+
function applyCalculatorParamsToElement(element, calculatorType, availableTypes, providerId, calculatorConfig) {
|
|
72
121
|
const calculatorElement = element;
|
|
122
|
+
calculatorElement.providerId = providerId;
|
|
123
|
+
calculatorElement.calculatorConfig = calculatorConfig;
|
|
124
|
+
element.setAttribute("provider-id", providerId);
|
|
73
125
|
if (calculatorType) {
|
|
74
126
|
calculatorElement.calculatorType = calculatorType;
|
|
75
127
|
element.setAttribute("calculator-type", calculatorType);
|
|
@@ -89,7 +141,7 @@ function applyCalculatorParamsToElement(element, calculatorType, availableTypes)
|
|
|
89
141
|
* Calculator tool registration
|
|
90
142
|
*
|
|
91
143
|
* Supports:
|
|
92
|
-
* - Basic, scientific, and graphing calculators
|
|
144
|
+
* - Basic, scientific, and graphing calculators through Desmos, GeoGebra, or Cortex
|
|
93
145
|
* - Context-aware visibility (shows only when math content is detected)
|
|
94
146
|
* - Item level only
|
|
95
147
|
*/
|
|
@@ -97,27 +149,18 @@ export const calculatorToolRegistration = {
|
|
|
97
149
|
toolId: "calculator",
|
|
98
150
|
name: "Calculator",
|
|
99
151
|
description: "Multi-type calculator (basic, scientific, graphing)",
|
|
152
|
+
nameKey: "tools.calculator.name",
|
|
153
|
+
descriptionKey: "tools.calculator.description",
|
|
100
154
|
icon: "calculator",
|
|
101
155
|
provider: {
|
|
102
|
-
getProviderId:
|
|
103
|
-
|
|
104
|
-
: "calculator-desmos",
|
|
105
|
-
createProvider: () => new DesmosToolProvider(),
|
|
156
|
+
getProviderId: resolveCalculatorProviderId,
|
|
157
|
+
createProvider: createCalculatorToolProvider,
|
|
106
158
|
getInitConfig: (config) => config?.provider?.init ?? {},
|
|
107
159
|
getAuthFetcher: (config) => {
|
|
108
160
|
const runtimeAuthFetcher = config?.provider?.runtime?.authFetcher;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const response = await fetch("/api/tools/desmos/auth", {
|
|
113
|
-
method: "GET",
|
|
114
|
-
credentials: "same-origin",
|
|
115
|
-
});
|
|
116
|
-
if (!response.ok) {
|
|
117
|
-
throw new Error(`Failed to fetch Desmos auth config (${response.status})`);
|
|
118
|
-
}
|
|
119
|
-
return (await response.json());
|
|
120
|
-
};
|
|
161
|
+
return typeof runtimeAuthFetcher === "function"
|
|
162
|
+
? runtimeAuthFetcher
|
|
163
|
+
: undefined;
|
|
121
164
|
},
|
|
122
165
|
lazy: true,
|
|
123
166
|
},
|
|
@@ -125,11 +168,13 @@ export const calculatorToolRegistration = {
|
|
|
125
168
|
supportedLevels: ["item"],
|
|
126
169
|
// PNP support IDs that enable this tool
|
|
127
170
|
// Maps to QTI 3.0 standard features: calculator, graphingCalculator
|
|
171
|
+
// A type is not a feature id: `calculatorType` arrives through the host's
|
|
172
|
+
// render params, so `basicCalculator` / `scientificCalculator` granted the
|
|
173
|
+
// same untyped calculator these two do and only looked like they selected a
|
|
174
|
+
// variant.
|
|
128
175
|
pnpSupportIds: [
|
|
129
176
|
"calculator", // QTI 3.0 standard (cognitive.calculator)
|
|
130
177
|
"graphingCalculator", // QTI 3.0 standard (assessment.graphingCalculator)
|
|
131
|
-
"basicCalculator", // Common variant
|
|
132
|
-
"scientificCalculator", // Common variant
|
|
133
178
|
],
|
|
134
179
|
/**
|
|
135
180
|
* Pass 2: Determine if calculator is relevant in this context
|
|
@@ -143,6 +188,9 @@ export const calculatorToolRegistration = {
|
|
|
143
188
|
},
|
|
144
189
|
renderToolbar(context, toolbarContext) {
|
|
145
190
|
const { calculatorType, availableTypes, displayName } = getCalculatorRenderParams(toolbarContext);
|
|
191
|
+
const calculatorToolConfig = toolbarContext.toolkitCoordinator?.config.tools?.providers?.calculator;
|
|
192
|
+
const providerId = resolveCalculatorProviderId(calculatorToolConfig);
|
|
193
|
+
const calculatorConfig = getCalculatorInstanceConfig(calculatorToolConfig);
|
|
146
194
|
const fullToolId = createScopedToolId(this.toolId, toolbarContext.scope.level, toolbarContext.scope.scopeId);
|
|
147
195
|
const componentOverrides = toolbarContext.componentOverrides;
|
|
148
196
|
const cachedOverlay = getCachedOverlay(toolbarContext.toolCoordinator, fullToolId);
|
|
@@ -153,19 +201,21 @@ export const calculatorToolRegistration = {
|
|
|
153
201
|
}
|
|
154
202
|
overlay.setAttribute("tool-id", fullToolId);
|
|
155
203
|
overlay.toolkitCoordinator = toolbarContext.toolkitCoordinator;
|
|
156
|
-
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()}`;
|
|
204
|
+
applyCalculatorParamsToElement(overlay, calculatorType, availableTypes, providerId, calculatorConfig);
|
|
163
205
|
const button = {
|
|
164
206
|
toolId: this.toolId,
|
|
165
207
|
label: displayName,
|
|
166
208
|
icon: typeof this.icon === "function" ? this.icon(context) : this.icon,
|
|
209
|
+
// Routes this button through <nds-icon-button> where the host enables NDS
|
|
210
|
+
// icons. Declared here because which capabilities render in the host's
|
|
211
|
+
// design system is a composition decision; the toolbar used to map it from
|
|
212
|
+
// the `calculator` toolId, which put a capability name in the generic core.
|
|
213
|
+
faIconName: "calculator",
|
|
167
214
|
disabled: false,
|
|
168
|
-
|
|
215
|
+
// The name stays put across open and closed. The toolbar mirrors
|
|
216
|
+
// `active` onto the button as `aria-pressed`, so the state is already
|
|
217
|
+
// exposed; a name that also swapped to "Close …" would contradict it.
|
|
218
|
+
ariaLabel: displayName,
|
|
169
219
|
tooltip: displayName,
|
|
170
220
|
onClick: () => toolbarContext.toggleTool(this.toolId),
|
|
171
221
|
active: toolbarContext.isToolVisible(fullToolId),
|
|
@@ -181,16 +231,63 @@ export const calculatorToolRegistration = {
|
|
|
181
231
|
element: overlay,
|
|
182
232
|
mount: "after-buttons",
|
|
183
233
|
shell: {
|
|
184
|
-
|
|
234
|
+
// The variant name the button already carries — "Basic Calculator",
|
|
235
|
+
// not the registration's generic one.
|
|
236
|
+
title: displayName,
|
|
185
237
|
draggable: true,
|
|
186
238
|
resizable: true,
|
|
187
239
|
closeable: true,
|
|
188
|
-
|
|
189
|
-
|
|
240
|
+
/*
|
|
241
|
+
* Sized per type from what each layout measures, rather than one
|
|
242
|
+
* size for all three. A shell subtracts ~74px of header from the
|
|
243
|
+
* height it is given, and the numbers below are the content each
|
|
244
|
+
* type needs plus room for its history tape:
|
|
245
|
+
*
|
|
246
|
+
* - basic: strip, display, edit row and a four-row keypad measure
|
|
247
|
+
* 398px. 560 left ~90px of blank above the entry line, which is
|
|
248
|
+
* the gap that made the panel look mis-sized.
|
|
249
|
+
* - scientific: the same plus a keypad layer tab row.
|
|
250
|
+
* - graphing: the expression rail beside the plot, whose column
|
|
251
|
+
* wants 486px on its own — viewport controls, a 14rem board floor
|
|
252
|
+
* and the readout that *is* the graph for assistive technology,
|
|
253
|
+
* since the board itself is `aria-hidden`. At 620 that column was
|
|
254
|
+
* 12px short and the readout's last line was cut off.
|
|
255
|
+
*
|
|
256
|
+
* The minimums stay shared across types and providers. This
|
|
257
|
+
* registration serves Desmos, GeoGebra and Cortex alike, so a
|
|
258
|
+
* graphing-only floor would move the resize limit under two vendors
|
|
259
|
+
* whose layouts were never measured for it. Cortex below 42rem
|
|
260
|
+
* stacks the rail above the plot and needs 701px there, which no
|
|
261
|
+
* spacing tier closes — it scrolls its own content instead of
|
|
262
|
+
* clipping it, which is the contract every size below the opening
|
|
263
|
+
* one already relies on.
|
|
264
|
+
*/
|
|
265
|
+
initialWidth: calculatorType === "graphing" ? 720 : 380,
|
|
266
|
+
initialHeight: calculatorType === "graphing"
|
|
267
|
+
? 660
|
|
268
|
+
: calculatorType === "basic"
|
|
269
|
+
? 500
|
|
270
|
+
: 560,
|
|
190
271
|
minWidth: 380,
|
|
191
|
-
minHeight:
|
|
192
|
-
|
|
272
|
+
minHeight: 480,
|
|
273
|
+
/*
|
|
274
|
+
* `bottom-right` put a 560-620px-tall shell over the items
|
|
275
|
+
* column it shares the viewport with — tall enough, at
|
|
276
|
+
* ordinary viewport heights, to sit on top of a sibling
|
|
277
|
+
* item's own toolbar row and block its button from mouse
|
|
278
|
+
* and touch input. `bottom-left` keeps the same footprint
|
|
279
|
+
* over the passage column instead, which carries no
|
|
280
|
+
* per-item controls to collide with.
|
|
281
|
+
*/
|
|
282
|
+
initialAlign: "bottom-left",
|
|
193
283
|
initialMargin: 16,
|
|
284
|
+
// Header controls in the host's design system, and the layout that
|
|
285
|
+
// goes with them.
|
|
286
|
+
ndsHeaderControls: true,
|
|
287
|
+
// A learner reads the question while using the calculator, so Tab and
|
|
288
|
+
// Shift+Tab cross between the page and the shell instead of cycling
|
|
289
|
+
// inside it.
|
|
290
|
+
pageTabOrder: true,
|
|
194
291
|
content: {
|
|
195
292
|
overflowY: "auto",
|
|
196
293
|
preserveMinHeight: true,
|
|
@@ -203,10 +300,12 @@ export const calculatorToolRegistration = {
|
|
|
203
300
|
const active = toolbarContext.isToolVisible(fullToolId);
|
|
204
301
|
button.active = active;
|
|
205
302
|
button.label = displayName;
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
303
|
+
// Static across the toggle. The previous `Close ${name.toLowerCase()}`
|
|
304
|
+
// was a hardcoded English template built by lowercasing a localized
|
|
305
|
+
// noun — "Close rekenmachine" under nl-NL — and it is the state
|
|
306
|
+
// `aria-pressed` already carries.
|
|
307
|
+
button.ariaLabel = displayName;
|
|
308
|
+
button.tooltip = displayName;
|
|
210
309
|
if (lastVisibleState !== active) {
|
|
211
310
|
overlay.visible = active;
|
|
212
311
|
lastVisibleState = active;
|
|
@@ -214,7 +313,7 @@ export const calculatorToolRegistration = {
|
|
|
214
313
|
if (overlay.toolkitCoordinator !== toolbarContext.toolkitCoordinator) {
|
|
215
314
|
overlay.toolkitCoordinator = toolbarContext.toolkitCoordinator;
|
|
216
315
|
}
|
|
217
|
-
applyCalculatorParamsToElement(overlay, calculatorType, availableTypes);
|
|
316
|
+
applyCalculatorParamsToElement(overlay, calculatorType, availableTypes, providerId, calculatorConfig);
|
|
218
317
|
},
|
|
219
318
|
subscribeActive: (callback) => {
|
|
220
319
|
if (!toolbarContext.subscribeVisibility)
|
|
@@ -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;
|