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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,7 +17,7 @@ and how does the program tier them":
17
17
  | `PACKAGED_TOOL_TAG_MAP` | Which custom element each one renders as |
18
18
  | `PACKAGED_TOOL_PLACEMENT`, `SECTION_PLAYER_PREFERRED_TOOL_PLACEMENT`, `PACKAGED_TOOL_ORDER` | Where they appear and in what order |
19
19
  | `UNIVERSAL_SUPPORTS_PRESET`, `createUniversalPersonalNeedsProfile` | Which of their support ids the program grants to everyone |
20
- | `DEFAULT_TOOL_MODULE_LOADERS`, `registerSectionToolModuleLoaders` | When each one's bundle loads |
20
+ | `DEFAULT_TOOL_MODULE_LOADERS`, `createDefaultToolModuleLoaders`, `registerSectionToolModuleLoaders` | When each one's bundle loads |
21
21
 
22
22
  The individual registrations are exported too, so a host can compose its own set
23
23
  rather than take the packaged one whole.
@@ -52,6 +52,43 @@ const registry = createPackagedToolRegistry({
52
52
  });
53
53
  ```
54
54
 
55
+ Desmos remains the calculator delivery when no provider is configured. To use
56
+ GeoGebra or the fully bundled open-source Cortex implementation, pass the same
57
+ calculator configuration to both the toolkit and the
58
+ packaged composition. The composition then owns the matching element tag and
59
+ module loader; hosts do not hand-maintain a second vendor map.
60
+
61
+ ```ts
62
+ import {
63
+ createDefaultToolModuleLoaders,
64
+ createPackagedToolRegistry,
65
+ } from "@pie-players/pie-default-tool-loaders";
66
+
67
+ const calculatorProviderConfig = {
68
+ provider: {
69
+ id: "calculator-geogebra",
70
+ init: { appletTimeoutMs: 20_000 },
71
+ },
72
+ settings: { showResetIcon: true },
73
+ };
74
+
75
+ const registry = createPackagedToolRegistry({
76
+ calculatorProviderConfig,
77
+ toolModuleLoaders: createDefaultToolModuleLoaders({
78
+ calculatorProviderConfig,
79
+ }),
80
+ });
81
+
82
+ const tools = {
83
+ providers: { calculator: calculatorProviderConfig },
84
+ };
85
+ ```
86
+
87
+ For an offline-capable calculator with no API key or runtime CDN, use
88
+ `provider.id: "calculator-cortex"`. Its `settings` accept angle mode, precision,
89
+ history, evaluation limit, allowed functions, clipboard policy, and graph
90
+ viewport options documented by `@pie-players/pie-calculator-cortex`.
91
+
55
92
  `createDefaultToolRegistry()` in the toolkit is the other end of that choice: it
56
93
  builds an empty registry, and a host composing its own set registers into it.
57
94
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export type { PackagedToolRegistryOptions, RegisterDefaultToolModuleLoadersOptions, RegisterPackagedToolsOptions, ToolModuleLoader, ToolRegistryLike, } from "./packaged-capability-composition.js";
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";
1
+ export type { PackagedCalculatorCompositionOptions, PackagedToolRegistryOptions, RegisterDefaultToolModuleLoadersOptions, RegisterPackagedToolsOptions, ToolModuleLoader, ToolRegistryLike, } from "./packaged-capability-composition.js";
2
+ export { createPackagedToolRegistry, createDefaultToolModuleLoaders, createSectionToolModuleLoaders, 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
3
  export { CONTENT_ALTERNATE_REGISTRATIONS } from "./content-alternates.js";
4
4
  export { annotationToolbarRegistration, lineReaderToolRegistration, themeToolRegistration, } from "./registrations/accessibility-tools.js";
5
5
  export { AUDIO_TRANSCRIPT_FEATURE_ID, AUDIO_TRANSCRIPT_REGION_CLASS, AUDIO_TRANSCRIPT_REGION_LABEL, audioTranscriptRegistration, resolveAudioTranscript, type ResolvedAudioTranscript, } from "./registrations/audio-transcript.js";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
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";
1
+ export { createPackagedToolRegistry, createDefaultToolModuleLoaders, createSectionToolModuleLoaders, 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
2
  // The authored-alternate subset, for a renderer that shows alternates and no
3
3
  // toolbar — importable without the packaged composition's tool loaders.
4
4
  export { CONTENT_ALTERNATE_REGISTRATIONS } from "./content-alternates.js";
@@ -7,13 +7,21 @@
7
7
  * Public constants are projections of this composition so adding a capability
8
8
  * cannot leave a second hand-maintained catalogue silently behind.
9
9
  */
10
- import { ToolRegistry, type ToolComponentFactory, type ToolComponentFactoryMap, type ToolRegistration, type ToolTagMap } from "@pie-players/pie-assessment-toolkit/tools/internal";
10
+ import { ToolRegistry, type ToolComponentFactory, type ToolComponentFactoryMap, type ToolRegistration, type ToolTagMap, type ToolProviderConfig } from "@pie-players/pie-assessment-toolkit/tools/internal";
11
11
  import type { PersonalNeedsProfile } from "@pie-players/pie-players-shared/types";
12
12
  export type ToolModuleLoader = () => Promise<unknown>;
13
13
  export interface ToolRegistryLike {
14
14
  setToolModuleLoaders(loaders: Partial<Record<string, ToolModuleLoader>>): void;
15
15
  }
16
- export interface PackagedToolRegistryOptions {
16
+ export interface PackagedCalculatorCompositionOptions {
17
+ /**
18
+ * The same calculator config supplied at `tools.providers.calculator`.
19
+ * Selects the matching packaged element and default loader. Omit it to retain
20
+ * the existing Desmos delivery.
21
+ */
22
+ calculatorProviderConfig?: ToolProviderConfig;
23
+ }
24
+ export interface PackagedToolRegistryOptions extends PackagedCalculatorCompositionOptions {
17
25
  /** Override packaged registrations by toolId. */
18
26
  overrides?: Partial<Record<string, ToolRegistration>>;
19
27
  /** Override or extend the packaged element tag mapping. */
@@ -30,9 +38,9 @@ export interface RegisterPackagedToolsOptions {
30
38
  toolIds?: string[];
31
39
  applyOverrides?: (registration: ToolRegistration) => ToolRegistration;
32
40
  }
33
- export type RegisterDefaultToolModuleLoadersOptions = {
41
+ export interface RegisterDefaultToolModuleLoadersOptions extends PackagedCalculatorCompositionOptions {
34
42
  loaders?: Partial<Record<string, ToolModuleLoader>>;
35
- };
43
+ }
36
44
  /**
37
45
  * Release/build gate for PIE-authored composition data.
38
46
  *
@@ -46,6 +54,10 @@ export declare const PACKAGED_TOOL_TAG_MAP: ToolTagMap;
46
54
  export declare const ITEM_TOOL_MODULE_LOADERS: Record<string, ToolModuleLoader>;
47
55
  export declare const SECTION_TOOL_MODULE_LOADERS: Record<string, ToolModuleLoader>;
48
56
  export declare const DEFAULT_TOOL_MODULE_LOADERS: Record<string, ToolModuleLoader>;
57
+ /** Build the packaged loader map for a selected calculator provider. */
58
+ export declare function createDefaultToolModuleLoaders(options?: PackagedCalculatorCompositionOptions): Record<string, ToolModuleLoader>;
59
+ /** Build the section-bootstrap loader subset for a selected calculator provider. */
60
+ export declare function createSectionToolModuleLoaders(options?: PackagedCalculatorCompositionOptions): Record<string, ToolModuleLoader>;
49
61
  export declare const PACKAGED_TOOL_PLACEMENT: {
50
62
  readonly assessment: readonly ["theme"];
51
63
  readonly section: readonly ["theme"];
@@ -10,20 +10,40 @@
10
10
  import { ToolRegistry, } from "@pie-players/pie-assessment-toolkit/tools/internal";
11
11
  import { annotationToolbarRegistration, lineReaderToolRegistration, themeToolRegistration, } from "./registrations/accessibility-tools.js";
12
12
  import { audioTranscriptRegistration } from "./registrations/audio-transcript.js";
13
- import { calculatorToolRegistration } from "./registrations/calculator.js";
13
+ import { calculatorToolRegistration, resolveCalculatorProviderId, } from "./registrations/calculator.js";
14
14
  import { dictionaryToolRegistration, pictureDictionaryToolRegistration, spanishDictionaryToolRegistration, spanishPictureDictionaryToolRegistration, } from "./registrations/dictionary-tools.js";
15
15
  import { answerEliminatorToolRegistration } from "./registrations/interaction-tools.js";
16
16
  import { protractorToolRegistration, rulerToolRegistration, } from "./registrations/measurement-tools.js";
17
17
  import { graphToolRegistration, periodicTableToolRegistration, } from "./registrations/subject-specific-tools.js";
18
18
  import { ttsToolRegistration } from "./registrations/tts.js";
19
19
  const loadSideEffectModule = (load) => load().then(() => undefined);
20
- function loadCalculatorModule() {
20
+ function loadCalculatorElement(tagName, load) {
21
21
  if (typeof globalThis !== "undefined" &&
22
22
  "customElements" in globalThis &&
23
- globalThis.customElements?.get("pie-tool-calculator")) {
23
+ globalThis.customElements?.get(tagName)) {
24
24
  return Promise.resolve();
25
25
  }
26
- return loadSideEffectModule(() => import("@pie-players/pie-tool-calculator-desmos"));
26
+ return loadSideEffectModule(load);
27
+ }
28
+ const loadDesmosCalculatorModule = () => loadCalculatorElement("pie-tool-calculator", () => import("@pie-players/pie-tool-calculator-shared/calculator-element"));
29
+ const loadGeoGebraCalculatorModule = () => loadCalculatorElement("pie-tool-calculator-geogebra", () => import("@pie-players/pie-tool-calculator-geogebra"));
30
+ const loadCortexCalculatorModule = () => loadCalculatorElement("pie-tool-calculator-cortex", () => import("@pie-players/pie-tool-calculator-cortex"));
31
+ const CALCULATOR_DELIVERY = {
32
+ "calculator-desmos": {
33
+ tagName: "pie-tool-calculator",
34
+ loadModule: loadDesmosCalculatorModule,
35
+ },
36
+ "calculator-geogebra": {
37
+ tagName: "pie-tool-calculator-geogebra",
38
+ loadModule: loadGeoGebraCalculatorModule,
39
+ },
40
+ "calculator-cortex": {
41
+ tagName: "pie-tool-calculator-cortex",
42
+ loadModule: loadCortexCalculatorModule,
43
+ },
44
+ };
45
+ function resolveCalculatorDelivery(config) {
46
+ return CALCULATOR_DELIVERY[resolveCalculatorProviderId(config)];
27
47
  }
28
48
  const loadTtsModule = () => loadSideEffectModule(() => import("@pie-players/pie-tool-tts-inline"));
29
49
  const loadRulerModule = () => loadSideEffectModule(() => import("@pie-players/pie-tool-ruler"));
@@ -39,18 +59,13 @@ const loadPictureDictionaryModule = () => loadSideEffectModule(() => import("@pi
39
59
  const PACKAGED_CAPABILITY_DEFINITIONS = [
40
60
  {
41
61
  registration: calculatorToolRegistration,
42
- tagName: "pie-tool-calculator",
43
- loadModule: loadCalculatorModule,
62
+ tagName: CALCULATOR_DELIVERY["calculator-desmos"].tagName,
63
+ loadModule: CALCULATOR_DELIVERY["calculator-desmos"].loadModule,
44
64
  loaderTargets: ["item", "section"],
45
65
  placementOrder: { element: 10 },
46
66
  preferredPlacementOrder: { item: 10 },
47
67
  toolbarOrder: 20,
48
- universalSupportIds: [
49
- "calculator",
50
- "graphingCalculator",
51
- "basicCalculator",
52
- "scientificCalculator",
53
- ],
68
+ universalSupportIds: ["calculator", "graphingCalculator"],
54
69
  },
55
70
  {
56
71
  registration: ttsToolRegistration,
@@ -60,7 +75,7 @@ const PACKAGED_CAPABILITY_DEFINITIONS = [
60
75
  placementOrder: { item: 10, passage: 10, rubric: 10, element: 30 },
61
76
  preferredPlacementOrder: { item: 20, passage: 10 },
62
77
  toolbarOrder: 30,
63
- universalSupportIds: ["textToSpeech", "readAloud", "tts", "speechOutput"],
78
+ universalSupportIds: ["textToSpeech", "readAloud"],
64
79
  },
65
80
  {
66
81
  registration: rulerToolRegistration,
@@ -70,7 +85,7 @@ const PACKAGED_CAPABILITY_DEFINITIONS = [
70
85
  placementOrder: { element: 40 },
71
86
  preferredPlacementOrder: { section: 50 },
72
87
  toolbarOrder: 80,
73
- universalSupportIds: ["ruler", "measurement"],
88
+ universalSupportIds: ["ruler"],
74
89
  },
75
90
  {
76
91
  registration: protractorToolRegistration,
@@ -80,7 +95,7 @@ const PACKAGED_CAPABILITY_DEFINITIONS = [
80
95
  placementOrder: { element: 50 },
81
96
  preferredPlacementOrder: { section: 60 },
82
97
  toolbarOrder: 90,
83
- universalSupportIds: ["protractor", "angleMeasurement"],
98
+ universalSupportIds: ["protractor"],
84
99
  },
85
100
  {
86
101
  registration: answerEliminatorToolRegistration,
@@ -90,12 +105,7 @@ const PACKAGED_CAPABILITY_DEFINITIONS = [
90
105
  placementOrder: { element: 20 },
91
106
  preferredPlacementOrder: { item: 30 },
92
107
  toolbarOrder: 70,
93
- universalSupportIds: [
94
- "answerMasking",
95
- "answerEliminator",
96
- "strikethrough",
97
- "choiceMasking",
98
- ],
108
+ universalSupportIds: ["answerMasking", "answerEliminator", "strikethrough"],
99
109
  },
100
110
  {
101
111
  registration: lineReaderToolRegistration,
@@ -105,13 +115,7 @@ const PACKAGED_CAPABILITY_DEFINITIONS = [
105
115
  placementOrder: { passage: 40, rubric: 40 },
106
116
  preferredPlacementOrder: { section: 40 },
107
117
  toolbarOrder: 40,
108
- universalSupportIds: [
109
- "readingMask",
110
- "readingGuide",
111
- "readingRuler",
112
- "lineReader",
113
- "trackingGuide",
114
- ],
118
+ universalSupportIds: ["readingMask", "readingGuide", "readingRuler"],
115
119
  },
116
120
  {
117
121
  registration: themeToolRegistration,
@@ -126,8 +130,6 @@ const PACKAGED_CAPABILITY_DEFINITIONS = [
126
130
  "colorContrast",
127
131
  "invertColors",
128
132
  "theme",
129
- "highContrast",
130
- "customColors",
131
133
  ],
132
134
  },
133
135
  {
@@ -141,13 +143,7 @@ const PACKAGED_CAPABILITY_DEFINITIONS = [
141
143
  // The last three came from the `highlighter` capability, which mounted this
142
144
  // same element behind a second identically-labelled button. Kept here so a
143
145
  // profile granted one of the older ids still gets highlighting.
144
- universalSupportIds: [
145
- "highlighting",
146
- "annotations",
147
- "highlighter",
148
- "textHighlight",
149
- "annotation",
150
- ],
146
+ universalSupportIds: ["highlighting", "annotations"],
151
147
  },
152
148
  {
153
149
  registration: graphToolRegistration,
@@ -157,7 +153,7 @@ const PACKAGED_CAPABILITY_DEFINITIONS = [
157
153
  placementOrder: { item: 40, element: 80 },
158
154
  preferredPlacementOrder: { section: 20 },
159
155
  toolbarOrder: 100,
160
- universalSupportIds: ["graph", "coordinatePlane", "graphingTool"],
156
+ universalSupportIds: ["graph"],
161
157
  },
162
158
  {
163
159
  registration: periodicTableToolRegistration,
@@ -167,11 +163,7 @@ const PACKAGED_CAPABILITY_DEFINITIONS = [
167
163
  placementOrder: { item: 50, element: 90 },
168
164
  preferredPlacementOrder: { section: 30 },
169
165
  toolbarOrder: 110,
170
- universalSupportIds: [
171
- "periodicTable",
172
- "chemistryReference",
173
- "elementReference",
174
- ],
166
+ universalSupportIds: ["periodicTable"],
175
167
  },
176
168
  {
177
169
  registration: dictionaryToolRegistration,
@@ -406,10 +398,27 @@ class PackagedCapabilityComposition {
406
398
  // their bundle/runtime behavior.
407
399
  if (options.toolModuleLoaders &&
408
400
  Object.keys(options.toolModuleLoaders).length > 0) {
409
- registry.setToolModuleLoaders(options.toolModuleLoaders);
401
+ const toolModuleLoaders = { ...options.toolModuleLoaders };
402
+ const calculatorDelivery = resolveCalculatorDelivery(options.calculatorProviderConfig);
403
+ // A host using the packaged default map gets a coherent provider-specific
404
+ // loader. A genuinely custom calculator loader remains the host's override.
405
+ if (options.calculatorProviderConfig &&
406
+ toolModuleLoaders.calculator === this.defaultModuleLoaders.calculator) {
407
+ toolModuleLoaders.calculator = calculatorDelivery.loadModule;
408
+ }
409
+ registry.setToolModuleLoaders(toolModuleLoaders);
410
410
  }
411
+ const calculatorTagMap = options.calculatorProviderConfig
412
+ ? {
413
+ calculator: resolveCalculatorDelivery(options.calculatorProviderConfig).tagName,
414
+ }
415
+ : {};
411
416
  registry.setComponentOverrides({
412
- toolTagMap: { ...this.toolTagMap, ...(options.toolTagMap ?? {}) },
417
+ toolTagMap: {
418
+ ...this.toolTagMap,
419
+ ...calculatorTagMap,
420
+ ...(options.toolTagMap ?? {}),
421
+ },
413
422
  toolComponentFactory: options.toolComponentFactory,
414
423
  toolComponentFactories: options.toolComponentFactories,
415
424
  });
@@ -450,6 +459,22 @@ export const SECTION_TOOL_MODULE_LOADERS = {
450
459
  export const DEFAULT_TOOL_MODULE_LOADERS = {
451
460
  ...packagedCapabilityComposition.defaultModuleLoaders,
452
461
  };
462
+ /** Build the packaged loader map for a selected calculator provider. */
463
+ export function createDefaultToolModuleLoaders(options = {}) {
464
+ return {
465
+ ...DEFAULT_TOOL_MODULE_LOADERS,
466
+ calculator: resolveCalculatorDelivery(options.calculatorProviderConfig)
467
+ .loadModule,
468
+ };
469
+ }
470
+ /** Build the section-bootstrap loader subset for a selected calculator provider. */
471
+ export function createSectionToolModuleLoaders(options = {}) {
472
+ return {
473
+ ...SECTION_TOOL_MODULE_LOADERS,
474
+ calculator: resolveCalculatorDelivery(options.calculatorProviderConfig)
475
+ .loadModule,
476
+ };
477
+ }
453
478
  export const PACKAGED_TOOL_PLACEMENT = {
454
479
  assessment: [...packagedCapabilityComposition.placement.assessment],
455
480
  section: [...packagedCapabilityComposition.placement.section],
@@ -482,14 +507,14 @@ export function createUniversalPersonalNeedsProfile() {
482
507
  /** Register packaged lazy module loaders, followed by host overrides. */
483
508
  export function registerDefaultToolModuleLoaders(registry, options = {}) {
484
509
  registry.setToolModuleLoaders({
485
- ...DEFAULT_TOOL_MODULE_LOADERS,
510
+ ...createDefaultToolModuleLoaders(options),
486
511
  ...(options.loaders ?? {}),
487
512
  });
488
513
  }
489
514
  /** Register only the section-bootstrap loader subset, then host overrides. */
490
515
  export function registerSectionToolModuleLoaders(registry, options = {}) {
491
516
  registry.setToolModuleLoaders({
492
- ...SECTION_TOOL_MODULE_LOADERS,
517
+ ...createSectionToolModuleLoaders(options),
493
518
  ...(options.loaders ?? {}),
494
519
  });
495
520
  }
@@ -36,8 +36,6 @@ export const lineReaderToolRegistration = {
36
36
  "readingMask", // QTI 3.0 standard (reading.readingMask)
37
37
  "readingGuide", // QTI 3.0 standard (reading.readingGuide)
38
38
  "readingRuler", // QTI 3.0 standard (reading.readingRuler)
39
- "lineReader", // Common variant
40
- "trackingGuide", // Common variant
41
39
  ],
42
40
  /**
43
41
  * Pass 2: Line reader is relevant when readable text is present
@@ -73,9 +71,7 @@ export const themeToolRegistration = {
73
71
  "highContrastDisplay", // QTI 3.0 standard (visual.highContrastDisplay)
74
72
  "colorContrast", // QTI 3.0 standard (visual.colorContrast)
75
73
  "invertColors", // QTI 3.0 standard (visual.invertColors)
76
- "theme", // Canonical id
77
- "highContrast", // Common variant
78
- "customColors", // Common variant
74
+ "theme", // Canonical id, and this registration's toolId
79
75
  ],
80
76
  /**
81
77
  * Pass 2: Color scheme is always relevant when allowed
@@ -127,9 +123,6 @@ export const annotationToolbarRegistration = {
127
123
  pnpSupportIds: [
128
124
  "highlighting", // QTI 3.0 standard (cognitive.highlighting / reading.wordHighlighting)
129
125
  "annotations", // QTI 3.0 standard (cognitive.annotations)
130
- "highlighter", // Common variant
131
- "textHighlight", // Common variant
132
- "annotation", // Common variant
133
126
  ],
134
127
  /**
135
128
  * Pass 2: Annotation is relevant when readable text is present
@@ -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) via Desmos provider.
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 via Desmos
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) via Desmos provider.
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" ? value : null;
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") ?? {};
@@ -74,10 +114,14 @@ function getCalculatorRenderParams(toolbarContext) {
74
114
  const CALCULATOR_NAME_KEYS = {
75
115
  basic: "tools.calculator.nameBasic",
76
116
  scientific: "tools.calculator.nameScientific",
117
+ graphing: "tools.calculator.nameGraphing",
77
118
  untyped: "tools.calculator.name",
78
119
  };
79
- function applyCalculatorParamsToElement(element, calculatorType, availableTypes) {
120
+ function applyCalculatorParamsToElement(element, calculatorType, availableTypes, providerId, calculatorConfig) {
80
121
  const calculatorElement = element;
122
+ calculatorElement.providerId = providerId;
123
+ calculatorElement.calculatorConfig = calculatorConfig;
124
+ element.setAttribute("provider-id", providerId);
81
125
  if (calculatorType) {
82
126
  calculatorElement.calculatorType = calculatorType;
83
127
  element.setAttribute("calculator-type", calculatorType);
@@ -97,7 +141,7 @@ function applyCalculatorParamsToElement(element, calculatorType, availableTypes)
97
141
  * Calculator tool registration
98
142
  *
99
143
  * Supports:
100
- * - Basic, scientific, and graphing calculators via Desmos
144
+ * - Basic, scientific, and graphing calculators through Desmos, GeoGebra, or Cortex
101
145
  * - Context-aware visibility (shows only when math content is detected)
102
146
  * - Item level only
103
147
  */
@@ -109,25 +153,14 @@ export const calculatorToolRegistration = {
109
153
  descriptionKey: "tools.calculator.description",
110
154
  icon: "calculator",
111
155
  provider: {
112
- getProviderId: (config) => typeof config?.provider?.id === "string" && config.provider.id.length > 0
113
- ? config.provider.id
114
- : "calculator-desmos",
115
- createProvider: () => new DesmosToolProvider(),
156
+ getProviderId: resolveCalculatorProviderId,
157
+ createProvider: createCalculatorToolProvider,
116
158
  getInitConfig: (config) => config?.provider?.init ?? {},
117
159
  getAuthFetcher: (config) => {
118
160
  const runtimeAuthFetcher = config?.provider?.runtime?.authFetcher;
119
- if (typeof runtimeAuthFetcher === "function")
120
- return runtimeAuthFetcher;
121
- return async () => {
122
- const response = await fetch("/api/tools/desmos/auth", {
123
- method: "GET",
124
- credentials: "same-origin",
125
- });
126
- if (!response.ok) {
127
- throw new Error(`Failed to fetch Desmos auth config (${response.status})`);
128
- }
129
- return (await response.json());
130
- };
161
+ return typeof runtimeAuthFetcher === "function"
162
+ ? runtimeAuthFetcher
163
+ : undefined;
131
164
  },
132
165
  lazy: true,
133
166
  },
@@ -135,11 +168,13 @@ export const calculatorToolRegistration = {
135
168
  supportedLevels: ["item"],
136
169
  // PNP support IDs that enable this tool
137
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.
138
175
  pnpSupportIds: [
139
176
  "calculator", // QTI 3.0 standard (cognitive.calculator)
140
177
  "graphingCalculator", // QTI 3.0 standard (assessment.graphingCalculator)
141
- "basicCalculator", // Common variant
142
- "scientificCalculator", // Common variant
143
178
  ],
144
179
  /**
145
180
  * Pass 2: Determine if calculator is relevant in this context
@@ -153,6 +188,9 @@ export const calculatorToolRegistration = {
153
188
  },
154
189
  renderToolbar(context, toolbarContext) {
155
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);
156
194
  const fullToolId = createScopedToolId(this.toolId, toolbarContext.scope.level, toolbarContext.scope.scopeId);
157
195
  const componentOverrides = toolbarContext.componentOverrides;
158
196
  const cachedOverlay = getCachedOverlay(toolbarContext.toolCoordinator, fullToolId);
@@ -163,7 +201,7 @@ export const calculatorToolRegistration = {
163
201
  }
164
202
  overlay.setAttribute("tool-id", fullToolId);
165
203
  overlay.toolkitCoordinator = toolbarContext.toolkitCoordinator;
166
- applyCalculatorParamsToElement(overlay, calculatorType, availableTypes);
204
+ applyCalculatorParamsToElement(overlay, calculatorType, availableTypes, providerId, calculatorConfig);
167
205
  const button = {
168
206
  toolId: this.toolId,
169
207
  label: displayName,
@@ -199,11 +237,49 @@ export const calculatorToolRegistration = {
199
237
  draggable: true,
200
238
  resizable: true,
201
239
  closeable: true,
202
- initialWidth: 380,
203
- initialHeight: 420,
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,
204
271
  minWidth: 380,
205
- minHeight: 420,
206
- initialAlign: "bottom-right",
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",
207
283
  initialMargin: 16,
208
284
  // Header controls in the host's design system, and the layout that
209
285
  // goes with them.
@@ -237,7 +313,7 @@ export const calculatorToolRegistration = {
237
313
  if (overlay.toolkitCoordinator !== toolbarContext.toolkitCoordinator) {
238
314
  overlay.toolkitCoordinator = toolbarContext.toolkitCoordinator;
239
315
  }
240
- applyCalculatorParamsToElement(overlay, calculatorType, availableTypes);
316
+ applyCalculatorParamsToElement(overlay, calculatorType, availableTypes, providerId, calculatorConfig);
241
317
  },
242
318
  subscribeActive: (callback) => {
243
319
  if (!toolbarContext.subscribeVisibility)
@@ -223,13 +223,13 @@ export const spanishDictionaryToolRegistration = createDictionaryToolRegistratio
223
223
  toolId: "dictionarySpanish",
224
224
  name: "Spanish Dictionary",
225
225
  description: "Look up word definitions in Spanish",
226
- pnpSupportIds: ["spanishDictionary", "spanishGlossary"],
226
+ pnpSupportIds: ["spanishDictionary"],
227
227
  lookupLanguage: "es",
228
228
  });
229
229
  export const spanishPictureDictionaryToolRegistration = createPictureDictionaryToolRegistration({
230
230
  toolId: "pictureDictionarySpanish",
231
231
  name: "Spanish Picture Dictionary",
232
232
  description: "Look up pictures for words in Spanish",
233
- pnpSupportIds: ["spanishPictureDictionary", "spanishIllustratedGlossary"],
233
+ pnpSupportIds: ["spanishPictureDictionary"],
234
234
  lookupLanguage: "es",
235
235
  });
@@ -33,8 +33,7 @@ export const answerEliminatorToolRegistration = {
33
33
  pnpSupportIds: [
34
34
  "answerMasking", // QTI 3.0 standard (assessment.answerMasking)
35
35
  "answerEliminator", // QTI 3.0 standard (assessment.answerEliminator)
36
- "strikethrough", // Common variant
37
- "choiceMasking", // Common variant
36
+ "strikethrough", // QTI 3.0 standard (assessment.strikethrough)
38
37
  ],
39
38
  /**
40
39
  * Pass 2: Answer eliminator is relevant only for choice-based questions
@@ -42,6 +41,17 @@ export const answerEliminatorToolRegistration = {
42
41
  isVisibleInContext(context) {
43
42
  return hasChoiceInteraction(context);
44
43
  },
44
+ /**
45
+ * Pass 3: the same question, asked as a capability. Elimination controls are
46
+ * rendered per choice, so an item with no choice interaction gives this tool
47
+ * nothing to act on — `placement-ordering`, `categorize` and
48
+ * `drag-in-the-blank` included, whose `choices` hold draggables the
49
+ * eliminator cannot reach. Answering the relevance gate alone left the button
50
+ * on those items for any learner whose profile grants answer masking.
51
+ */
52
+ isApplicableToContent(context) {
53
+ return hasChoiceInteraction(context);
54
+ },
45
55
  renderToolbar(context, toolbarContext) {
46
56
  const visibility = createScopedVisibilityBinding(this.toolId, toolbarContext);
47
57
  const componentOverrides = toolbarContext.componentOverrides ?? {};
@@ -30,7 +30,6 @@ export const rulerToolRegistration = {
30
30
  // Maps to QTI 3.0 standard feature: ruler
31
31
  pnpSupportIds: [
32
32
  "ruler", // QTI 3.0 standard (assessment.ruler)
33
- "measurement", // Common variant
34
33
  ],
35
34
  /**
36
35
  * Pass 2: Ruler is relevant when math content is present
@@ -64,7 +63,6 @@ export const protractorToolRegistration = {
64
63
  // Maps to QTI 3.0 standard feature: protractor
65
64
  pnpSupportIds: [
66
65
  "protractor", // QTI 3.0 standard (assessment.protractor)
67
- "angleMeasurement", // Common variant
68
66
  ],
69
67
  /**
70
68
  * Pass 2: Protractor is relevant when math content is present
@@ -46,6 +46,13 @@ export function renderOverlayToolbar(registration, context, toolbarContext, opti
46
46
  {
47
47
  element: overlay,
48
48
  mount: "after-buttons",
49
+ // A frameless overlay draws its own surface over the content and
50
+ // positions itself, so the content's box has to be its containing
51
+ // block. A shelled tool is `position: fixed` and clamps to the
52
+ // viewport, so it stays where the toolbar puts it.
53
+ ...(options.surface === "frameless"
54
+ ? { container: "content-boundary" }
55
+ : {}),
49
56
  ...(options.shell
50
57
  ? {
51
58
  shell: {
@@ -34,8 +34,6 @@ export const graphToolRegistration = {
34
34
  pnpSupportIds: [
35
35
  "graph", // QTI 3.0 standard (assessment.graph)
36
36
  "graphingCalculator", // QTI 3.0 standard (assessment.graphingCalculator)
37
- "coordinatePlane", // Common variant
38
- "graphingTool", // Common variant
39
37
  ],
40
38
  /**
41
39
  * Pass 2: Graph is relevant when math content is present
@@ -74,8 +72,6 @@ export const periodicTableToolRegistration = {
74
72
  // Maps to QTI 3.0 standard feature: periodicTable
75
73
  pnpSupportIds: [
76
74
  "periodicTable", // QTI 3.0 standard (assessment.periodicTable)
77
- "chemistryReference", // Common variant
78
- "elementReference", // Common variant
79
75
  ],
80
76
  /**
81
77
  * Pass 2: Periodic table is relevant when science content is present
@@ -86,8 +86,6 @@ export const ttsToolRegistration = {
86
86
  pnpSupportIds: [
87
87
  "textToSpeech", // QTI 3.0 standard (auditory.textToSpeech)
88
88
  "readAloud", // QTI 3.0 standard (auditory.readAloud)
89
- "tts", // Common abbreviation
90
- "speechOutput", // Common variant
91
89
  ],
92
90
  /**
93
91
  * Pass 2: Determine if TTS is relevant in this context
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pie-players/pie-default-tool-loaders",
3
- "version": "0.3.68",
3
+ "version": "0.3.70",
4
4
  "type": "module",
5
5
  "description": "Composition layer for the packaged PIE assessment tools: registrations, tag map, placement presets, universal supports and lazy module loaders",
6
6
  "repository": {
@@ -33,23 +33,26 @@
33
33
  "test": "bun test"
34
34
  },
35
35
  "dependencies": {
36
- "@pie-players/pie-assessment-toolkit": "0.3.68",
37
- "@pie-players/pie-players-shared": "0.3.68",
38
- "@pie-players/pie-tool-annotation-toolbar": "0.3.68",
39
- "@pie-players/pie-tool-answer-eliminator": "0.3.68",
40
- "@pie-players/pie-tool-calculator-desmos": "0.3.68",
41
- "@pie-players/pie-tool-dictionary": "0.3.68",
42
- "@pie-players/pie-tool-graph": "0.3.68",
43
- "@pie-players/pie-tool-line-reader": "0.3.68",
44
- "@pie-players/pie-tool-periodic-table": "0.3.68",
45
- "@pie-players/pie-tool-picture-dictionary": "0.3.68",
46
- "@pie-players/pie-tool-protractor": "0.3.68",
47
- "@pie-players/pie-tool-ruler": "0.3.68",
48
- "@pie-players/pie-tool-theme": "0.3.68",
49
- "@pie-players/pie-tool-tts-inline": "0.3.68"
36
+ "@pie-players/pie-assessment-toolkit": "0.3.70",
37
+ "@pie-players/pie-players-shared": "0.3.70",
38
+ "@pie-players/pie-tool-annotation-toolbar": "0.3.70",
39
+ "@pie-players/pie-tool-answer-eliminator": "0.3.70",
40
+ "@pie-players/pie-tool-calculator-desmos": "0.3.70",
41
+ "@pie-players/pie-tool-calculator-cortex": "0.3.70",
42
+ "@pie-players/pie-tool-calculator-geogebra": "0.3.70",
43
+ "@pie-players/pie-tool-calculator-shared": "0.3.70",
44
+ "@pie-players/pie-tool-dictionary": "0.3.70",
45
+ "@pie-players/pie-tool-graph": "0.3.70",
46
+ "@pie-players/pie-tool-line-reader": "0.3.70",
47
+ "@pie-players/pie-tool-periodic-table": "0.3.70",
48
+ "@pie-players/pie-tool-picture-dictionary": "0.3.70",
49
+ "@pie-players/pie-tool-protractor": "0.3.70",
50
+ "@pie-players/pie-tool-ruler": "0.3.70",
51
+ "@pie-players/pie-tool-theme": "0.3.70",
52
+ "@pie-players/pie-tool-tts-inline": "0.3.70"
50
53
  },
51
54
  "devDependencies": {
52
- "@biomejs/biome": "^2.5.8",
55
+ "@biomejs/biome": "^2.5.11",
53
56
  "typescript": "^5.9.3"
54
57
  },
55
58
  "homepage": "https://github.com/pie-framework/pie-players/tree/master/packages/default-tool-loaders#readme",