@pie-players/pie-default-tool-loaders 0.3.73 → 0.3.75
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 +56 -58
- package/dist/calculator-providers/CortexToolProvider.d.ts +12 -0
- package/dist/calculator-providers/CortexToolProvider.js +31 -0
- package/dist/calculator-providers/DesmosToolProvider.d.ts +13 -0
- package/dist/calculator-providers/DesmosToolProvider.js +31 -0
- package/dist/calculator-providers/GeoGebraToolProvider.d.ts +15 -0
- package/dist/calculator-providers/GeoGebraToolProvider.js +31 -0
- package/dist/calculator-providers/LazyCalculatorToolProvider.d.ts +34 -0
- package/dist/calculator-providers/LazyCalculatorToolProvider.js +95 -0
- package/dist/content-alternates.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/packaged-capability-composition.d.ts +17 -49
- package/dist/packaged-capability-composition.js +61 -262
- package/dist/registrations/accessibility-tools.d.ts +1 -5
- package/dist/registrations/accessibility-tools.js +6 -41
- package/dist/registrations/audio-transcript.d.ts +1 -1
- package/dist/registrations/audio-transcript.js +0 -1
- package/dist/registrations/calculator.d.ts +2 -6
- package/dist/registrations/calculator.js +18 -73
- package/dist/registrations/dictionary-tools.d.ts +6 -5
- package/dist/registrations/dictionary-tools.js +23 -27
- package/dist/registrations/interaction-tools.d.ts +1 -7
- package/dist/registrations/interaction-tools.js +24 -28
- package/dist/registrations/measurement-tools.d.ts +1 -5
- package/dist/registrations/measurement-tools.js +1 -19
- package/dist/registrations/overlay-element-cache.d.ts +23 -0
- package/dist/registrations/overlay-element-cache.js +48 -0
- package/dist/registrations/overlay-toolbar-render.d.ts +3 -10
- package/dist/registrations/overlay-toolbar-render.js +5 -8
- package/dist/registrations/selection-actions.d.ts +1 -1
- package/dist/registrations/selection-actions.js +1 -1
- package/dist/registrations/subject-specific-tools.d.ts +3 -8
- package/dist/registrations/subject-specific-tools.js +10 -26
- package/dist/registrations/tts.d.ts +1 -6
- package/dist/registrations/tts.js +41 -90
- package/package.json +24 -21
- package/dist/packaged-tool-registry.d.ts +0 -2
- package/dist/packaged-tool-registry.js +0 -1
- package/dist/tool-placement.d.ts +0 -1
- package/dist/tool-placement.js +0 -1
- package/dist/tool-tag-map.d.ts +0 -1
- package/dist/tool-tag-map.js +0 -1
- package/dist/universal-supports.d.ts +0 -1
- package/dist/universal-supports.js +0 -1
|
@@ -3,13 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Registers the calculator tool with support for multiple calculator types
|
|
5
5
|
* (basic, scientific, graphing) through a host-selected provider.
|
|
6
|
-
*
|
|
7
|
-
* Maps to QTI 3.0 standard access features:
|
|
8
|
-
* - calculator (cognitive support)
|
|
9
|
-
* - graphingCalculator (assessment tool)
|
|
10
6
|
*/
|
|
11
|
-
import type { ToolRegistration } from "@pie-players/pie-assessment-toolkit/tools/
|
|
12
|
-
import type { ToolProviderConfig } from "@pie-players/pie-assessment-toolkit/tools/
|
|
7
|
+
import type { ToolRegistration } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
8
|
+
import type { ToolProviderConfig } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
13
9
|
export type CalculatorProviderId = "calculator-desmos" | "calculator-geogebra" | "calculator-cortex";
|
|
14
10
|
export declare const DEFAULT_CALCULATOR_PROVIDER_ID: CalculatorProviderId;
|
|
15
11
|
export declare function resolveCalculatorProviderId(config: ToolProviderConfig | undefined): CalculatorProviderId;
|
|
@@ -3,17 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Registers the calculator tool with support for multiple calculator types
|
|
5
5
|
* (basic, scientific, graphing) through a host-selected provider.
|
|
6
|
-
*
|
|
7
|
-
* Maps to QTI 3.0 standard access features:
|
|
8
|
-
* - calculator (cognitive support)
|
|
9
|
-
* - graphingCalculator (assessment tool)
|
|
10
6
|
*/
|
|
11
|
-
import { hasMathContent } from "@pie-players/pie-assessment-toolkit/tools/
|
|
12
|
-
import { createScopedToolId } from "@pie-players/pie-assessment-toolkit/tools/
|
|
13
|
-
import {
|
|
14
|
-
import { CortexToolProvider } from "
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
7
|
+
import { hasMathContent } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
8
|
+
import { createScopedToolId } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
9
|
+
import { createToolElement } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
10
|
+
import { CortexToolProvider } from "../calculator-providers/CortexToolProvider.js";
|
|
11
|
+
import { DesmosToolProvider } from "../calculator-providers/DesmosToolProvider.js";
|
|
12
|
+
import { GeoGebraToolProvider } from "../calculator-providers/GeoGebraToolProvider.js";
|
|
13
|
+
import { resolveOverlayElement } from "./overlay-element-cache.js";
|
|
17
14
|
export const DEFAULT_CALCULATOR_PROVIDER_ID = "calculator-desmos";
|
|
18
15
|
export function resolveCalculatorProviderId(config) {
|
|
19
16
|
const configured = config?.provider?.id;
|
|
@@ -50,38 +47,6 @@ function getCalculatorInstanceConfig(config) {
|
|
|
50
47
|
: undefined,
|
|
51
48
|
};
|
|
52
49
|
}
|
|
53
|
-
// The toolbar parent re-derives `renderedTools` whenever item state changes
|
|
54
|
-
// (e.g. the learner answers a question and `effectiveItem`/`renderContext`
|
|
55
|
-
// recompute). Calculator initialization is expensive (Desmos boot, container
|
|
56
|
-
// mount), so we cache the overlay element by coordinator + scoped tool id.
|
|
57
|
-
// Reusing the same element keeps `mountContent` a no-op and avoids tearing
|
|
58
|
-
// down and re-initializing the calculator on every re-render.
|
|
59
|
-
const overlayElementCache = new WeakMap();
|
|
60
|
-
function getCachedOverlay(coordinator, fullToolId) {
|
|
61
|
-
if (!coordinator)
|
|
62
|
-
return null;
|
|
63
|
-
const scoped = overlayElementCache.get(coordinator);
|
|
64
|
-
const element = scoped?.get(fullToolId);
|
|
65
|
-
if (!element)
|
|
66
|
-
return null;
|
|
67
|
-
// Svelte custom elements destroy their component when disconnected.
|
|
68
|
-
// A detached cached element is a dead instance — drop it and recreate.
|
|
69
|
-
if (!element.isConnected) {
|
|
70
|
-
scoped?.delete(fullToolId);
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
return element;
|
|
74
|
-
}
|
|
75
|
-
function setCachedOverlay(coordinator, fullToolId, element) {
|
|
76
|
-
if (!coordinator)
|
|
77
|
-
return;
|
|
78
|
-
let scoped = overlayElementCache.get(coordinator);
|
|
79
|
-
if (!scoped) {
|
|
80
|
-
scoped = new Map();
|
|
81
|
-
overlayElementCache.set(coordinator, scoped);
|
|
82
|
-
}
|
|
83
|
-
scoped.set(fullToolId, element);
|
|
84
|
-
}
|
|
85
50
|
function normalizeCalculatorType(value) {
|
|
86
51
|
return value === "basic" || value === "scientific" || value === "graphing"
|
|
87
52
|
? value
|
|
@@ -117,11 +82,9 @@ const CALCULATOR_NAME_KEYS = {
|
|
|
117
82
|
graphing: "tools.calculator.nameGraphing",
|
|
118
83
|
untyped: "tools.calculator.name",
|
|
119
84
|
};
|
|
120
|
-
function applyCalculatorParamsToElement(element, calculatorType, availableTypes,
|
|
85
|
+
function applyCalculatorParamsToElement(element, calculatorType, availableTypes, calculatorConfig) {
|
|
121
86
|
const calculatorElement = element;
|
|
122
|
-
calculatorElement.providerId = providerId;
|
|
123
87
|
calculatorElement.calculatorConfig = calculatorConfig;
|
|
124
|
-
element.setAttribute("provider-id", providerId);
|
|
125
88
|
if (calculatorType) {
|
|
126
89
|
calculatorElement.calculatorType = calculatorType;
|
|
127
90
|
element.setAttribute("calculator-type", calculatorType);
|
|
@@ -153,7 +116,6 @@ export const calculatorToolRegistration = {
|
|
|
153
116
|
descriptionKey: "tools.calculator.description",
|
|
154
117
|
icon: "calculator",
|
|
155
118
|
provider: {
|
|
156
|
-
getProviderId: resolveCalculatorProviderId,
|
|
157
119
|
createProvider: createCalculatorToolProvider,
|
|
158
120
|
getInitConfig: (config) => config?.provider?.init ?? {},
|
|
159
121
|
getAuthFetcher: (config) => {
|
|
@@ -166,16 +128,6 @@ export const calculatorToolRegistration = {
|
|
|
166
128
|
},
|
|
167
129
|
// Calculator is item-level in this player architecture.
|
|
168
130
|
supportedLevels: ["item"],
|
|
169
|
-
// PNP support IDs that enable this tool
|
|
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.
|
|
175
|
-
pnpSupportIds: [
|
|
176
|
-
"calculator", // QTI 3.0 standard (cognitive.calculator)
|
|
177
|
-
"graphingCalculator", // QTI 3.0 standard (assessment.graphingCalculator)
|
|
178
|
-
],
|
|
179
131
|
/**
|
|
180
132
|
* Pass 2: Determine if calculator is relevant in this context
|
|
181
133
|
*
|
|
@@ -188,20 +140,16 @@ export const calculatorToolRegistration = {
|
|
|
188
140
|
},
|
|
189
141
|
renderToolbar(context, toolbarContext) {
|
|
190
142
|
const { calculatorType, availableTypes, displayName } = getCalculatorRenderParams(toolbarContext);
|
|
191
|
-
const calculatorToolConfig = toolbarContext.toolkitCoordinator?.
|
|
192
|
-
|
|
143
|
+
const calculatorToolConfig = toolbarContext.toolkitCoordinator?.getToolConfig(this.toolId) ||
|
|
144
|
+
undefined;
|
|
193
145
|
const calculatorConfig = getCalculatorInstanceConfig(calculatorToolConfig);
|
|
194
146
|
const fullToolId = createScopedToolId(this.toolId, toolbarContext.scope.level, toolbarContext.scope.scopeId);
|
|
195
147
|
const componentOverrides = toolbarContext.componentOverrides;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (!cachedOverlay) {
|
|
200
|
-
setCachedOverlay(toolbarContext.toolCoordinator, fullToolId, overlay);
|
|
201
|
-
}
|
|
148
|
+
// Reused across renders: besides keeping state, this spares a Desmos boot
|
|
149
|
+
// and container mount on every re-render.
|
|
150
|
+
const overlay = resolveOverlayElement(toolbarContext, fullToolId, () => createToolElement(this.toolId, context, toolbarContext, componentOverrides));
|
|
202
151
|
overlay.setAttribute("tool-id", fullToolId);
|
|
203
|
-
overlay
|
|
204
|
-
applyCalculatorParamsToElement(overlay, calculatorType, availableTypes, providerId, calculatorConfig);
|
|
152
|
+
applyCalculatorParamsToElement(overlay, calculatorType, availableTypes, calculatorConfig);
|
|
205
153
|
const button = {
|
|
206
154
|
toolId: this.toolId,
|
|
207
155
|
label: displayName,
|
|
@@ -218,7 +166,7 @@ export const calculatorToolRegistration = {
|
|
|
218
166
|
ariaLabel: displayName,
|
|
219
167
|
tooltip: displayName,
|
|
220
168
|
onClick: () => toolbarContext.toggleTool(this.toolId),
|
|
221
|
-
active: toolbarContext.isToolVisible(
|
|
169
|
+
active: toolbarContext.isToolVisible(this.toolId),
|
|
222
170
|
};
|
|
223
171
|
let lastVisibleState = button.active;
|
|
224
172
|
if (overlay.visible !== button.active) {
|
|
@@ -297,7 +245,7 @@ export const calculatorToolRegistration = {
|
|
|
297
245
|
],
|
|
298
246
|
button,
|
|
299
247
|
sync: () => {
|
|
300
|
-
const active = toolbarContext.isToolVisible(
|
|
248
|
+
const active = toolbarContext.isToolVisible(this.toolId);
|
|
301
249
|
button.active = active;
|
|
302
250
|
button.label = displayName;
|
|
303
251
|
// Static across the toggle. The previous `Close ${name.toLowerCase()}`
|
|
@@ -310,16 +258,13 @@ export const calculatorToolRegistration = {
|
|
|
310
258
|
overlay.visible = active;
|
|
311
259
|
lastVisibleState = active;
|
|
312
260
|
}
|
|
313
|
-
|
|
314
|
-
overlay.toolkitCoordinator = toolbarContext.toolkitCoordinator;
|
|
315
|
-
}
|
|
316
|
-
applyCalculatorParamsToElement(overlay, calculatorType, availableTypes, providerId, calculatorConfig);
|
|
261
|
+
applyCalculatorParamsToElement(overlay, calculatorType, availableTypes, calculatorConfig);
|
|
317
262
|
},
|
|
318
263
|
subscribeActive: (callback) => {
|
|
319
264
|
if (!toolbarContext.subscribeVisibility)
|
|
320
265
|
return () => { };
|
|
321
266
|
return toolbarContext.subscribeVisibility(() => {
|
|
322
|
-
callback(toolbarContext.isToolVisible(
|
|
267
|
+
callback(toolbarContext.isToolVisible(this.toolId));
|
|
323
268
|
});
|
|
324
269
|
},
|
|
325
270
|
};
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* there without caret browsing, an OS toggle absent on mobile — so a selection-only
|
|
16
16
|
* dictionary is unreachable for them. The panel's own field is the keyboard route.
|
|
17
17
|
*/
|
|
18
|
-
import type { ToolRegistration } from "@pie-players/pie-assessment-toolkit/tools/
|
|
18
|
+
import type { ToolRegistration } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
19
19
|
/**
|
|
20
20
|
* What distinguishes one dictionary capability from another.
|
|
21
21
|
*
|
|
@@ -32,14 +32,15 @@ import type { ToolRegistration } from "@pie-players/pie-assessment-toolkit/tools
|
|
|
32
32
|
* so a host catalog that lacks one is a plain label rather than a broken button.
|
|
33
33
|
*/
|
|
34
34
|
export interface DictionaryVariantOptions {
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Capability id, unique per language, and the PNP support id that grants it.
|
|
37
|
+
* Defaults to the base tool's id.
|
|
38
|
+
*/
|
|
36
39
|
toolId?: string;
|
|
37
40
|
name?: string;
|
|
38
41
|
description?: string;
|
|
39
42
|
/** Catalog prefix for this variant's strings. Defaults to `tools.<toolId>`. */
|
|
40
43
|
messageKeyPrefix?: string;
|
|
41
|
-
/** PNP ids that grant this variant. A language variant needs its own. */
|
|
42
|
-
pnpSupportIds?: string[];
|
|
43
44
|
/**
|
|
44
45
|
* The corpus language this capability looks words up in, as a BCP-47 tag.
|
|
45
46
|
*
|
|
@@ -53,7 +54,7 @@ export interface DictionaryVariantOptions {
|
|
|
53
54
|
* Dictionary tool registration
|
|
54
55
|
*
|
|
55
56
|
* Word lookup from a host-supplied service, in one language. Call this again with another
|
|
56
|
-
* `toolId
|
|
57
|
+
* `toolId` and `lookupLanguage` to offer a second language beside it.
|
|
57
58
|
*/
|
|
58
59
|
export declare function createDictionaryToolRegistration(options?: DictionaryVariantOptions): ToolRegistration;
|
|
59
60
|
/** The content-following dictionary every deployment gets. */
|
|
@@ -15,7 +15,14 @@
|
|
|
15
15
|
* there without caret browsing, an OS toggle absent on mobile — so a selection-only
|
|
16
16
|
* dictionary is unreachable for them. The panel's own field is the keyboard route.
|
|
17
17
|
*/
|
|
18
|
-
import { createScopedVisibilityBinding, createToolElement, hasReadableText, syncButtonAndOverlayVisibility, } from "@pie-players/pie-assessment-toolkit/tools/
|
|
18
|
+
import { createScopedVisibilityBinding, createToolElement, hasReadableText, syncButtonAndOverlayVisibility, } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
19
|
+
import { resolveOverlayElement } from "./overlay-element-cache.js";
|
|
20
|
+
/**
|
|
21
|
+
* The lookup fields each panel last took from render params. The panel outlives
|
|
22
|
+
* renders, so a field the host stops supplying is cleared here; a field it never
|
|
23
|
+
* supplied, such as one a component factory set, is left alone.
|
|
24
|
+
*/
|
|
25
|
+
const appliedLookupFields = new WeakMap();
|
|
19
26
|
/**
|
|
20
27
|
* Apply the host's per-tool render params to the panel.
|
|
21
28
|
*
|
|
@@ -33,14 +40,23 @@ function applyLookupParams(element, toolId, toolbarContext,
|
|
|
33
40
|
/** A language variant's own corpus language; see {@link DictionaryVariantOptions}. */
|
|
34
41
|
lookupLanguage) {
|
|
35
42
|
const params = toolbarContext.getToolRenderParams?.(toolId) ?? {};
|
|
43
|
+
const supplied = {};
|
|
36
44
|
if (typeof params.endpoint === "string")
|
|
37
|
-
|
|
45
|
+
supplied.endpoint = params.endpoint;
|
|
38
46
|
if (typeof params.lookup === "function")
|
|
39
|
-
|
|
47
|
+
supplied.lookup = params.lookup;
|
|
40
48
|
if (typeof params.headers === "function")
|
|
41
|
-
|
|
49
|
+
supplied.headers = params.headers;
|
|
42
50
|
if (typeof params.credentials === "string")
|
|
43
|
-
|
|
51
|
+
supplied.credentials = params.credentials;
|
|
52
|
+
// A dropped `lookup` left in place would keep outranking the endpoint that
|
|
53
|
+
// replaced it.
|
|
54
|
+
for (const field of appliedLookupFields.get(element) ?? []) {
|
|
55
|
+
if (!(field in supplied))
|
|
56
|
+
element[field] = undefined;
|
|
57
|
+
}
|
|
58
|
+
Object.assign(element, supplied);
|
|
59
|
+
appliedLookupFields.set(element, new Set(Object.keys(supplied)));
|
|
44
60
|
// The selection door: a gateway acting on the learner's selection requests this
|
|
45
61
|
// tool with the words in `term`, and the toolbar layers that over the host's own
|
|
46
62
|
// params. The toolbar stamps every request with a neutral `toolRequestId` — core
|
|
@@ -81,7 +97,7 @@ function renderDictionaryPanel(args) {
|
|
|
81
97
|
};
|
|
82
98
|
const componentOverrides = toolbarContext.componentOverrides ??
|
|
83
99
|
{};
|
|
84
|
-
const overlay = createToolElement(registration.toolId, context, toolbarContext, componentOverrides);
|
|
100
|
+
const overlay = resolveOverlayElement(toolbarContext, visibility.fullToolId, () => createToolElement(registration.toolId, context, toolbarContext, componentOverrides));
|
|
85
101
|
overlay.setAttribute("tool-id", visibility.fullToolId);
|
|
86
102
|
applyLookupParams(overlay, registration.toolId, toolbarContext, args.lookupLanguage);
|
|
87
103
|
return {
|
|
@@ -121,7 +137,7 @@ function renderDictionaryPanel(args) {
|
|
|
121
137
|
* Dictionary tool registration
|
|
122
138
|
*
|
|
123
139
|
* Word lookup from a host-supplied service, in one language. Call this again with another
|
|
124
|
-
* `toolId
|
|
140
|
+
* `toolId` and `lookupLanguage` to offer a second language beside it.
|
|
125
141
|
*/
|
|
126
142
|
export function createDictionaryToolRegistration(options = {}) {
|
|
127
143
|
const toolId = options.toolId ?? "dictionary";
|
|
@@ -135,15 +151,6 @@ export function createDictionaryToolRegistration(options = {}) {
|
|
|
135
151
|
icon: "book-open",
|
|
136
152
|
// Text can appear at any of these; the panel itself floats at section scope.
|
|
137
153
|
supportedLevels: ["section", "item", "passage", "rubric"],
|
|
138
|
-
// PNP support IDs
|
|
139
|
-
// Maps to AfA PNP 3.0 / QTI 3.0 dictionary support, plus the common variants a
|
|
140
|
-
// host profile is likely to carry.
|
|
141
|
-
pnpSupportIds: options.pnpSupportIds ?? [
|
|
142
|
-
"dictionary", // Canonical id
|
|
143
|
-
"englishDictionary", // Common variant
|
|
144
|
-
"glossary", // Common variant
|
|
145
|
-
"definitions", // Common variant
|
|
146
|
-
],
|
|
147
154
|
/** Pass 2: a dictionary is relevant wherever there is text to look words up from. */
|
|
148
155
|
isVisibleInContext(context) {
|
|
149
156
|
return hasReadableText(context);
|
|
@@ -184,15 +191,6 @@ export function createPictureDictionaryToolRegistration(options = {}) {
|
|
|
184
191
|
descriptionKey: `${prefix}.description`,
|
|
185
192
|
icon: "photo",
|
|
186
193
|
supportedLevels: ["section", "item", "passage", "rubric"],
|
|
187
|
-
// PNP support IDs
|
|
188
|
-
// AfA PNP 3.0 names an illustrated equivalent of a glossary; the variants cover
|
|
189
|
-
// what host profiles call it in practice.
|
|
190
|
-
pnpSupportIds: options.pnpSupportIds ?? [
|
|
191
|
-
"pictureDictionary", // Canonical id
|
|
192
|
-
"illustratedGlossary", // Common variant
|
|
193
|
-
"symbolDictionary", // Common variant
|
|
194
|
-
"pictureSupport", // Common variant
|
|
195
|
-
],
|
|
196
194
|
isVisibleInContext(context) {
|
|
197
195
|
return hasReadableText(context);
|
|
198
196
|
},
|
|
@@ -223,13 +221,11 @@ export const spanishDictionaryToolRegistration = createDictionaryToolRegistratio
|
|
|
223
221
|
toolId: "dictionarySpanish",
|
|
224
222
|
name: "Spanish Dictionary",
|
|
225
223
|
description: "Look up word definitions in Spanish",
|
|
226
|
-
pnpSupportIds: ["spanishDictionary"],
|
|
227
224
|
lookupLanguage: "es",
|
|
228
225
|
});
|
|
229
226
|
export const spanishPictureDictionaryToolRegistration = createPictureDictionaryToolRegistration({
|
|
230
227
|
toolId: "pictureDictionarySpanish",
|
|
231
228
|
name: "Spanish Picture Dictionary",
|
|
232
229
|
description: "Look up pictures for words in Spanish",
|
|
233
|
-
pnpSupportIds: ["spanishPictureDictionary"],
|
|
234
230
|
lookupLanguage: "es",
|
|
235
231
|
});
|
|
@@ -3,14 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Registers tools for interacting with question content:
|
|
5
5
|
* - Answer Eliminator (strike through answer choices)
|
|
6
|
-
* - Highlighter (highlight text passages)
|
|
7
|
-
*
|
|
8
|
-
* Maps to QTI 3.0 standard access features:
|
|
9
|
-
* - answerMasking (assessment tool)
|
|
10
|
-
* - strikethrough (visual transformation)
|
|
11
|
-
* - highlighting (cognitive/reading support)
|
|
12
6
|
*/
|
|
13
|
-
import type { ToolRegistration } from "@pie-players/pie-assessment-toolkit/tools/
|
|
7
|
+
import type { ToolRegistration } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
14
8
|
/**
|
|
15
9
|
* Answer Eliminator tool registration
|
|
16
10
|
*
|
|
@@ -3,16 +3,28 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Registers tools for interacting with question content:
|
|
5
5
|
* - Answer Eliminator (strike through answer choices)
|
|
6
|
-
* - Highlighter (highlight text passages)
|
|
7
|
-
*
|
|
8
|
-
* Maps to QTI 3.0 standard access features:
|
|
9
|
-
* - answerMasking (assessment tool)
|
|
10
|
-
* - strikethrough (visual transformation)
|
|
11
|
-
* - highlighting (cognitive/reading support)
|
|
12
6
|
*/
|
|
13
|
-
import { hasChoiceInteraction
|
|
14
|
-
import { createToolElement, } from "@pie-players/pie-assessment-toolkit/tools/
|
|
15
|
-
import { createScopedVisibilityBinding, syncButtonAndOverlayVisibility, } from "@pie-players/pie-assessment-toolkit/tools/
|
|
7
|
+
import { hasChoiceInteraction } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
8
|
+
import { createToolElement, } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
9
|
+
import { createScopedVisibilityBinding, syncButtonAndOverlayVisibility, } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
10
|
+
import { resolveOverlayElement } from "./overlay-element-cache.js";
|
|
11
|
+
/** `tools.providers.answerEliminator.strategy`, defaulting to strikethrough. */
|
|
12
|
+
const normalizeEliminationStrategy = (value) => value === "mask" ? "mask" : "strikethrough";
|
|
13
|
+
/** The state-store key of each PIE element in the item, by model id. */
|
|
14
|
+
function resolveElementStateKeys(context, toolbarContext) {
|
|
15
|
+
const keys = {};
|
|
16
|
+
if (!("item" in context) || !toolbarContext.getGlobalElementId)
|
|
17
|
+
return keys;
|
|
18
|
+
for (const model of context.item?.config?.models ?? []) {
|
|
19
|
+
const elementId = model?.id;
|
|
20
|
+
if (typeof elementId !== "string" || !elementId)
|
|
21
|
+
continue;
|
|
22
|
+
const key = toolbarContext.getGlobalElementId(elementId);
|
|
23
|
+
if (key)
|
|
24
|
+
keys[elementId] = key;
|
|
25
|
+
}
|
|
26
|
+
return keys;
|
|
27
|
+
}
|
|
16
28
|
/**
|
|
17
29
|
* Answer Eliminator tool registration
|
|
18
30
|
*
|
|
@@ -28,13 +40,6 @@ export const answerEliminatorToolRegistration = {
|
|
|
28
40
|
icon: "strikethrough",
|
|
29
41
|
// Answer eliminator appears at item level only
|
|
30
42
|
supportedLevels: ["item"],
|
|
31
|
-
// PNP support IDs
|
|
32
|
-
// Maps to QTI 3.0 standard feature: answerMasking
|
|
33
|
-
pnpSupportIds: [
|
|
34
|
-
"answerMasking", // QTI 3.0 standard (assessment.answerMasking)
|
|
35
|
-
"answerEliminator", // QTI 3.0 standard (assessment.answerEliminator)
|
|
36
|
-
"strikethrough", // QTI 3.0 standard (assessment.strikethrough)
|
|
37
|
-
],
|
|
38
43
|
/**
|
|
39
44
|
* Pass 2: Answer eliminator is relevant only for choice-based questions
|
|
40
45
|
*/
|
|
@@ -55,10 +60,10 @@ export const answerEliminatorToolRegistration = {
|
|
|
55
60
|
renderToolbar(context, toolbarContext) {
|
|
56
61
|
const visibility = createScopedVisibilityBinding(this.toolId, toolbarContext);
|
|
57
62
|
const componentOverrides = toolbarContext.componentOverrides ?? {};
|
|
58
|
-
const overlay = createToolElement(this.toolId, context, toolbarContext, componentOverrides);
|
|
63
|
+
const overlay = resolveOverlayElement(toolbarContext, visibility.fullToolId, () => createToolElement(this.toolId, context, toolbarContext, componentOverrides));
|
|
59
64
|
overlay.setAttribute("tool-id", visibility.fullToolId);
|
|
60
|
-
overlay.setAttribute("strategy", "strikethrough");
|
|
61
65
|
overlay.setAttribute("button-alignment", "inline");
|
|
66
|
+
overlay.setAttribute("strategy", normalizeEliminationStrategy(toolbarContext.toolkitCoordinator?.getToolConfig(this.toolId)?.strategy));
|
|
62
67
|
const button = {
|
|
63
68
|
toolId: this.toolId,
|
|
64
69
|
label: this.name,
|
|
@@ -79,17 +84,8 @@ export const answerEliminatorToolRegistration = {
|
|
|
79
84
|
overlay,
|
|
80
85
|
isActive: visibility.isActive,
|
|
81
86
|
});
|
|
82
|
-
if (toolbarContext.toolCoordinator) {
|
|
83
|
-
overlay.coordinator = toolbarContext.toolCoordinator;
|
|
84
|
-
}
|
|
85
87
|
overlay.scopeElement = toolbarContext.getScopeElement?.() || null;
|
|
86
|
-
|
|
87
|
-
overlay.elementToolStateStore = toolbarContext.elementToolStateStore;
|
|
88
|
-
}
|
|
89
|
-
const globalElementId = toolbarContext.getGlobalElementId?.();
|
|
90
|
-
if (globalElementId) {
|
|
91
|
-
overlay.globalElementId = globalElementId;
|
|
92
|
-
}
|
|
88
|
+
overlay.elementStateKeys = resolveElementStateKeys(context, toolbarContext);
|
|
93
89
|
},
|
|
94
90
|
subscribeActive: visibility.subscribeActive,
|
|
95
91
|
};
|
|
@@ -2,12 +2,8 @@
|
|
|
2
2
|
* Measurement Tools Registrations
|
|
3
3
|
*
|
|
4
4
|
* Registers ruler and protractor tools for on-screen measurements.
|
|
5
|
-
*
|
|
6
|
-
* Maps to QTI 3.0 standard access features:
|
|
7
|
-
* - ruler (assessment tool)
|
|
8
|
-
* - protractor (assessment tool)
|
|
9
5
|
*/
|
|
10
|
-
import type { ToolRegistration } from "@pie-players/pie-assessment-toolkit/tools/
|
|
6
|
+
import type { ToolRegistration } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
11
7
|
/**
|
|
12
8
|
* Ruler tool registration
|
|
13
9
|
*
|
|
@@ -2,14 +2,8 @@
|
|
|
2
2
|
* Measurement Tools Registrations
|
|
3
3
|
*
|
|
4
4
|
* Registers ruler and protractor tools for on-screen measurements.
|
|
5
|
-
*
|
|
6
|
-
* Maps to QTI 3.0 standard access features:
|
|
7
|
-
* - ruler (assessment tool)
|
|
8
|
-
* - protractor (assessment tool)
|
|
9
5
|
*/
|
|
10
|
-
import { hasMathContent } from "@pie-players/pie-assessment-toolkit/tools/
|
|
11
|
-
import { createToolElement, } from "@pie-players/pie-assessment-toolkit/tools/internal";
|
|
12
|
-
import { applyOverlaySurface, createScopedVisibilityBinding, syncButtonAndOverlayVisibility, } from "@pie-players/pie-assessment-toolkit/tools/internal";
|
|
6
|
+
import { hasMathContent } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
13
7
|
import { renderOverlayToolbar } from "./overlay-toolbar-render.js";
|
|
14
8
|
/**
|
|
15
9
|
* Ruler tool registration
|
|
@@ -26,11 +20,6 @@ export const rulerToolRegistration = {
|
|
|
26
20
|
icon: "ruler",
|
|
27
21
|
// Ruler typically appears at section/item/element level
|
|
28
22
|
supportedLevels: ["section", "item", "element"],
|
|
29
|
-
// PNP support IDs
|
|
30
|
-
// Maps to QTI 3.0 standard feature: ruler
|
|
31
|
-
pnpSupportIds: [
|
|
32
|
-
"ruler", // QTI 3.0 standard (assessment.ruler)
|
|
33
|
-
],
|
|
34
23
|
/**
|
|
35
24
|
* Pass 2: Ruler is relevant when math content is present
|
|
36
25
|
*/
|
|
@@ -40,7 +29,6 @@ export const rulerToolRegistration = {
|
|
|
40
29
|
renderToolbar(context, toolbarContext) {
|
|
41
30
|
return renderOverlayToolbar(this, context, toolbarContext, {
|
|
42
31
|
surface: "frameless",
|
|
43
|
-
handsOverCoordinator: true,
|
|
44
32
|
});
|
|
45
33
|
},
|
|
46
34
|
};
|
|
@@ -59,11 +47,6 @@ export const protractorToolRegistration = {
|
|
|
59
47
|
icon: "protractor",
|
|
60
48
|
// Protractor typically appears at section/item/element level
|
|
61
49
|
supportedLevels: ["section", "item", "element"],
|
|
62
|
-
// PNP support IDs
|
|
63
|
-
// Maps to QTI 3.0 standard feature: protractor
|
|
64
|
-
pnpSupportIds: [
|
|
65
|
-
"protractor", // QTI 3.0 standard (assessment.protractor)
|
|
66
|
-
],
|
|
67
50
|
/**
|
|
68
51
|
* Pass 2: Protractor is relevant when math content is present
|
|
69
52
|
*/
|
|
@@ -73,7 +56,6 @@ export const protractorToolRegistration = {
|
|
|
73
56
|
renderToolbar(context, toolbarContext) {
|
|
74
57
|
return renderOverlayToolbar(this, context, toolbarContext, {
|
|
75
58
|
surface: "frameless",
|
|
76
|
-
handsOverCoordinator: true,
|
|
77
59
|
});
|
|
78
60
|
},
|
|
79
61
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One overlay element per coordinator and scoped tool id, reused across renders.
|
|
3
|
+
*
|
|
4
|
+
* The toolbar re-derives its rendered tools whenever an input moves — a policy
|
|
5
|
+
* emit, a tool-config update, the interface locale — and mounts whichever
|
|
6
|
+
* element each render returns, swapping out one it has not seen. A registration
|
|
7
|
+
* that creates its overlay on every render therefore remounts it, and the
|
|
8
|
+
* learner loses the ruler's position, the graph's points or the eliminated
|
|
9
|
+
* choices while the tool stays open. Reusing the element keeps the mount a
|
|
10
|
+
* no-op, so per-render attributes are the registration's to re-apply.
|
|
11
|
+
*
|
|
12
|
+
* A Svelte custom element tears its component down once it leaves the document,
|
|
13
|
+
* so a cached element found disconnected is dropped and recreated.
|
|
14
|
+
*/
|
|
15
|
+
import type { ToolbarContext } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
16
|
+
/** The property the toolbar calls as it unmounts a tool element. */
|
|
17
|
+
export declare const TOOL_ELEMENT_UNMOUNT_CALLBACK_PROP = "__pieToolElementUnmount";
|
|
18
|
+
/**
|
|
19
|
+
* The element cached for `fullToolId` under this toolbar's coordinator, or the
|
|
20
|
+
* one `create` returns, which is then cached. Without a coordinator there is no
|
|
21
|
+
* session to key by, and every render creates.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveOverlayElement<T extends HTMLElement>(toolbarContext: ToolbarContext, fullToolId: string, create: () => T): T;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One overlay element per coordinator and scoped tool id, reused across renders.
|
|
3
|
+
*
|
|
4
|
+
* The toolbar re-derives its rendered tools whenever an input moves — a policy
|
|
5
|
+
* emit, a tool-config update, the interface locale — and mounts whichever
|
|
6
|
+
* element each render returns, swapping out one it has not seen. A registration
|
|
7
|
+
* that creates its overlay on every render therefore remounts it, and the
|
|
8
|
+
* learner loses the ruler's position, the graph's points or the eliminated
|
|
9
|
+
* choices while the tool stays open. Reusing the element keeps the mount a
|
|
10
|
+
* no-op, so per-render attributes are the registration's to re-apply.
|
|
11
|
+
*
|
|
12
|
+
* A Svelte custom element tears its component down once it leaves the document,
|
|
13
|
+
* so a cached element found disconnected is dropped and recreated.
|
|
14
|
+
*/
|
|
15
|
+
/** The property the toolbar calls as it unmounts a tool element. */
|
|
16
|
+
export const TOOL_ELEMENT_UNMOUNT_CALLBACK_PROP = "__pieToolElementUnmount";
|
|
17
|
+
const overlayElements = new WeakMap();
|
|
18
|
+
/**
|
|
19
|
+
* The element cached for `fullToolId` under this toolbar's coordinator, or the
|
|
20
|
+
* one `create` returns, which is then cached. Without a coordinator there is no
|
|
21
|
+
* session to key by, and every render creates.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveOverlayElement(toolbarContext, fullToolId, create) {
|
|
24
|
+
const coordinator = toolbarContext.toolCoordinator;
|
|
25
|
+
if (!coordinator)
|
|
26
|
+
return create();
|
|
27
|
+
let scoped = overlayElements.get(coordinator);
|
|
28
|
+
const cached = scoped?.get(fullToolId);
|
|
29
|
+
if (cached?.isConnected)
|
|
30
|
+
return cached;
|
|
31
|
+
const element = create();
|
|
32
|
+
if (!scoped) {
|
|
33
|
+
scoped = new Map();
|
|
34
|
+
overlayElements.set(coordinator, scoped);
|
|
35
|
+
}
|
|
36
|
+
scoped.set(fullToolId, element);
|
|
37
|
+
// The toolbar calls this as it unmounts the element, before detaching it, so
|
|
38
|
+
// the check waits a microtask: an element another toolbar has adopted in the
|
|
39
|
+
// meantime is still connected and stays cached.
|
|
40
|
+
element[TOOL_ELEMENT_UNMOUNT_CALLBACK_PROP] = () => {
|
|
41
|
+
queueMicrotask(() => {
|
|
42
|
+
if (!element.isConnected && scoped.get(fullToolId) === element) {
|
|
43
|
+
scoped.delete(fullToolId);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
return element;
|
|
48
|
+
}
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
* overlay behind one toolbar button.
|
|
4
4
|
*
|
|
5
5
|
* Seven registrations had this inline. What actually varies between them is the
|
|
6
|
-
*
|
|
7
|
-
* or takes a draggable window, that window's size
|
|
8
|
-
* the coordinator re-handed on each sync. Everything else — the visibility
|
|
6
|
+
* two options below and nothing else: whether the overlay paints its own chrome
|
|
7
|
+
* or takes a draggable window, and that window's size. Everything else — the visibility
|
|
9
8
|
* binding, the button, the component-override lookup, the `tool-id` attribute, the
|
|
10
9
|
* sync — was identical, which is why one rule landing in three spellings was
|
|
11
10
|
* possible: the shell title was `resolveToolRegistrationName` in two files,
|
|
@@ -16,7 +15,7 @@
|
|
|
16
15
|
* different prefix — the dictionary variants, which compose several capabilities
|
|
17
16
|
* off one element — keeps its own renderer.
|
|
18
17
|
*/
|
|
19
|
-
import { type ToolContext, type ToolRegistration, type ToolToolbarRenderResult, type ToolbarContext } from "@pie-players/pie-assessment-toolkit/tools/
|
|
18
|
+
import { type ToolContext, type ToolRegistration, type ToolToolbarRenderResult, type ToolbarContext } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
20
19
|
/** A window's geometry. Absent for overlays that paint their own chrome. */
|
|
21
20
|
export interface OverlayToolShell {
|
|
22
21
|
resizable: boolean;
|
|
@@ -33,11 +32,5 @@ export interface RenderOverlayToolbarOptions {
|
|
|
33
32
|
surface?: "frameless";
|
|
34
33
|
/** Present when the capability opens in a draggable, closeable window. */
|
|
35
34
|
shell?: OverlayToolShell;
|
|
36
|
-
/**
|
|
37
|
-
* Re-hand the toolkit coordinator on every sync. Overlays that take part in
|
|
38
|
-
* stacking or visibility restore need it; the two that only render their own
|
|
39
|
-
* model do not, and giving them a property they ignore is not free to assume.
|
|
40
|
-
*/
|
|
41
|
-
handsOverCoordinator?: boolean;
|
|
42
35
|
}
|
|
43
36
|
export declare function renderOverlayToolbar(registration: ToolRegistration, context: ToolContext, toolbarContext: ToolbarContext, options?: RenderOverlayToolbarOptions): ToolToolbarRenderResult;
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
* overlay behind one toolbar button.
|
|
4
4
|
*
|
|
5
5
|
* Seven registrations had this inline. What actually varies between them is the
|
|
6
|
-
*
|
|
7
|
-
* or takes a draggable window, that window's size
|
|
8
|
-
* the coordinator re-handed on each sync. Everything else — the visibility
|
|
6
|
+
* two options below and nothing else: whether the overlay paints its own chrome
|
|
7
|
+
* or takes a draggable window, and that window's size. Everything else — the visibility
|
|
9
8
|
* binding, the button, the component-override lookup, the `tool-id` attribute, the
|
|
10
9
|
* sync — was identical, which is why one rule landing in three spellings was
|
|
11
10
|
* possible: the shell title was `resolveToolRegistrationName` in two files,
|
|
@@ -16,7 +15,8 @@
|
|
|
16
15
|
* different prefix — the dictionary variants, which compose several capabilities
|
|
17
16
|
* off one element — keeps its own renderer.
|
|
18
17
|
*/
|
|
19
|
-
import { applyOverlaySurface, createScopedVisibilityBinding, createToolElement, resolveToolRegistrationName, syncButtonAndOverlayVisibility, } from "@pie-players/pie-assessment-toolkit/tools/
|
|
18
|
+
import { applyOverlaySurface, createScopedVisibilityBinding, createToolElement, resolveToolRegistrationName, syncButtonAndOverlayVisibility, } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
19
|
+
import { resolveOverlayElement } from "./overlay-element-cache.js";
|
|
20
20
|
export function renderOverlayToolbar(registration, context, toolbarContext, options = {}) {
|
|
21
21
|
const { toolId } = registration;
|
|
22
22
|
const visibility = createScopedVisibilityBinding(toolId, toolbarContext);
|
|
@@ -34,7 +34,7 @@ export function renderOverlayToolbar(registration, context, toolbarContext, opti
|
|
|
34
34
|
};
|
|
35
35
|
const componentOverrides = toolbarContext.componentOverrides ??
|
|
36
36
|
{};
|
|
37
|
-
const overlay = createToolElement(toolId, context, toolbarContext, componentOverrides);
|
|
37
|
+
const overlay = resolveOverlayElement(toolbarContext, visibility.fullToolId, () => createToolElement(toolId, context, toolbarContext, componentOverrides));
|
|
38
38
|
overlay.setAttribute("tool-id", visibility.fullToolId);
|
|
39
39
|
if (options.surface === "frameless") {
|
|
40
40
|
applyOverlaySurface(overlay, "frameless");
|
|
@@ -73,9 +73,6 @@ export function renderOverlayToolbar(registration, context, toolbarContext, opti
|
|
|
73
73
|
overlay,
|
|
74
74
|
isActive: visibility.isActive,
|
|
75
75
|
});
|
|
76
|
-
if (options.handsOverCoordinator && toolbarContext.toolkitCoordinator) {
|
|
77
|
-
overlay.toolkitCoordinator = toolbarContext.toolkitCoordinator;
|
|
78
|
-
}
|
|
79
76
|
},
|
|
80
77
|
subscribeActive: visibility.subscribeActive,
|
|
81
78
|
};
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* cannot originate one at all. Every tool named here also opens from the toolbar and
|
|
13
13
|
* carries its own term field.
|
|
14
14
|
*/
|
|
15
|
-
import type { ToolSelectionAction, ToolSurfaceServices } from "@pie-players/pie-assessment-toolkit/tools/
|
|
15
|
+
import type { ToolSelectionAction, ToolSurfaceServices } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
16
16
|
/**
|
|
17
17
|
* Actions for the gateway to render, given the services it was handed.
|
|
18
18
|
*
|