@pie-players/pie-assessment-toolkit 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.
Files changed (39) hide show
  1. package/README.md +83 -8
  2. package/dist/components/ItemToolBar.custom-element.js +1 -1
  3. package/dist/components/PieAssessmentToolkit.custom-element.js +13 -13
  4. package/dist/components/SectionToolBar.custom-element.js +1 -1
  5. package/dist/components/chunks/ItemToolBar-12jy8e7c.js +49 -0
  6. package/dist/components/chunks/ItemToolBar-16t5ary7.js +46 -0
  7. package/dist/policy/core/compose-decision.js +14 -0
  8. package/dist/policy/core/decision-types.d.ts +1 -1
  9. package/dist/policy/sources/PnpPolicySource.d.ts +8 -0
  10. package/dist/policy/sources/PnpPolicySource.js +19 -10
  11. package/dist/services/HighlightCoordinator.d.ts +5 -1
  12. package/dist/services/HighlightCoordinator.js +5 -8
  13. package/dist/services/ToolRegistry.d.ts +48 -0
  14. package/dist/services/ToolRegistry.js +34 -0
  15. package/dist/services/ToolkitCoordinator.d.ts +23 -13
  16. package/dist/services/ToolkitCoordinator.js +373 -66
  17. package/dist/services/tool-context.js +8 -3
  18. package/dist/services/tool-providers/CortexToolProvider.d.ts +13 -0
  19. package/dist/services/tool-providers/CortexToolProvider.js +32 -0
  20. package/dist/services/tool-providers/DesmosToolProvider.d.ts +8 -102
  21. package/dist/services/tool-providers/DesmosToolProvider.js +14 -145
  22. package/dist/services/tool-providers/GeoGebraToolProvider.d.ts +16 -0
  23. package/dist/services/tool-providers/GeoGebraToolProvider.js +32 -0
  24. package/dist/services/tool-providers/LazyCalculatorToolProvider.d.ts +35 -0
  25. package/dist/services/tool-providers/LazyCalculatorToolProvider.js +95 -0
  26. package/dist/services/tool-providers/index.d.ts +4 -0
  27. package/dist/services/tool-providers/index.js +2 -0
  28. package/dist/services/tool-request.d.ts +1 -0
  29. package/dist/services/tool-request.js +4 -0
  30. package/dist/tools/client.d.ts +0 -1
  31. package/dist/tools/client.js +0 -2
  32. package/dist/tools/internal.d.ts +1 -1
  33. package/dist/tools/internal.js +1 -1
  34. package/dist/tools/types.d.ts +1 -66
  35. package/package.json +22 -12
  36. package/dist/components/chunks/ItemToolBar-38mhtjsq.js +0 -51
  37. package/dist/components/chunks/ItemToolBar-9ymm7pd1.js +0 -46
  38. package/dist/tools/library-loader.d.ts +0 -62
  39. package/dist/tools/library-loader.js +0 -261
@@ -900,20 +900,17 @@ export class HighlightCoordinator {
900
900
  this.applyAdaptiveTTSStyle();
901
901
  }
902
902
  /**
903
- * Cleanup - remove all highlights
903
+ * Cleanup this coordinator's highlights and observer.
904
+ *
905
+ * The stylesheet is process-global and shared by every live coordinator, so
906
+ * removing it here would break a replacement or sibling coordinator. Keep the
907
+ * single bounded style element installed for the lifetime of the document.
904
908
  */
905
909
  destroy() {
906
910
  if (!this.supported)
907
911
  return;
908
912
  this.clearTTS();
909
913
  this.clearAnnotations();
910
- // Remove style element
911
- if (typeof document !== "undefined") {
912
- const styleEl = document.getElementById("pie-highlight-styles");
913
- if (styleEl) {
914
- styleEl.remove();
915
- }
916
- }
917
914
  this.themeObserver?.disconnect();
918
915
  this.themeObserver = null;
919
916
  }
@@ -116,6 +116,20 @@ export interface ResolvedToolContext {
116
116
  export interface ToolRenderElement {
117
117
  element: HTMLElement | null;
118
118
  mount: "before-buttons" | "after-buttons" | "controls-row";
119
+ /**
120
+ * Which box the element is appended to. `mount` places it among the toolbar's
121
+ * own regions; this chooses the box it is positioned against.
122
+ *
123
+ * `"toolbar"` (the default) leaves it in the toolbar, whose element is the
124
+ * containing block for anything absolutely positioned inside it.
125
+ * `"content-boundary"` appends it to the nearest element the host marked with
126
+ * `data-pie-tool-overlay-boundary` — the content a tool was placed on. An
127
+ * element that draws its own surface over that content and computes its own
128
+ * coordinates needs the content's box as its frame, and the toolbar's box is a
129
+ * header-sized one. When the host declares no boundary the element stays in
130
+ * the toolbar.
131
+ */
132
+ container?: "toolbar" | "content-boundary";
119
133
  layoutHints?: {
120
134
  controlsRow?: {
121
135
  reserveSpace?: boolean;
@@ -483,6 +497,26 @@ export interface ToolRegistration {
483
497
  * @returns true if tool should be visible, false to hide
484
498
  */
485
499
  isVisibleInContext?(context: ToolContext): boolean;
500
+ /**
501
+ * Whether this tool can act on this content at all — a capability question,
502
+ * not a relevance heuristic. Answering `false` withdraws the tool even where
503
+ * a PNP grant would otherwise keep it, which {@link
504
+ * ToolRegistration.isVisibleInContext} deliberately cannot do.
505
+ *
506
+ * The two gates answer different questions, and most tools declare only the
507
+ * first. A calculator is *applicable* to every item — a learner granted one
508
+ * keeps it on an item that does not look mathematical — while its relevance
509
+ * is a guess about usefulness. An answer eliminator on an item with no choice
510
+ * interaction has nothing to strike through, so no grant can make it work.
511
+ *
512
+ * Declare this only where the tool's own controls provably do nothing:
513
+ * withdrawing a granted accommodation on a false negative is the more
514
+ * expensive failure. Omitting it means "applicable".
515
+ *
516
+ * @param context - The item or element context the tool would act on
517
+ * @returns false to withdraw the tool from this context
518
+ */
519
+ isApplicableToContent?(context: ToolContext): boolean;
486
520
  /**
487
521
  * Toolbar render contract. Required for `toolbar-toggle` and
488
522
  * `selection-gateway`; a region capability renders through
@@ -625,6 +659,20 @@ export declare class ToolRegistry {
625
659
  * @returns Array of visible tool registrations
626
660
  */
627
661
  filterVisibleInContext(allowedToolIds: string[], context: ToolContext): ToolRegistration[];
662
+ /**
663
+ * Whether a tool can act on any of the contexts it would be placed against.
664
+ * Unlike the relevance pass this is a veto: a `false` here removes the tool
665
+ * from a toolbar even when a grant protects it, so a tool answers `false`
666
+ * only where its controls provably do nothing.
667
+ *
668
+ * A tool that declares no applicability gate is applicable. So is one
669
+ * evaluated against no contexts — content that has not resolved yet cannot
670
+ * establish that a tool is useless.
671
+ *
672
+ * @param toolId - Tool to ask
673
+ * @param contexts - Every context the tool could act on at this placement
674
+ */
675
+ isApplicableToAnyContext(toolId: string, contexts: readonly ToolContext[]): boolean;
628
676
  /**
629
677
  * Get tool metadata for building UIs
630
678
  * Useful for building PNP configuration interfaces
@@ -164,6 +164,10 @@ function assertToolRegistrationShape(registration) {
164
164
  typeof registration.isVisibleInContext !== "function") {
165
165
  throw new Error(`Invalid tool registration "${registration.toolId}": "isVisibleInContext" must be a function when present.`);
166
166
  }
167
+ if (registration.isApplicableToContent !== undefined &&
168
+ typeof registration.isApplicableToContent !== "function") {
169
+ throw new Error(`Invalid tool registration "${registration.toolId}": "isApplicableToContent" must be a function when present.`);
170
+ }
167
171
  if (registration.requiresAuthoredContent !== undefined) {
168
172
  if (typeof registration.requiresAuthoredContent !== "object" ||
169
173
  registration.requiresAuthoredContent === null ||
@@ -442,6 +446,36 @@ export class ToolRegistry {
442
446
  }
443
447
  return visible;
444
448
  }
449
+ /**
450
+ * Whether a tool can act on any of the contexts it would be placed against.
451
+ * Unlike the relevance pass this is a veto: a `false` here removes the tool
452
+ * from a toolbar even when a grant protects it, so a tool answers `false`
453
+ * only where its controls provably do nothing.
454
+ *
455
+ * A tool that declares no applicability gate is applicable. So is one
456
+ * evaluated against no contexts — content that has not resolved yet cannot
457
+ * establish that a tool is useless.
458
+ *
459
+ * @param toolId - Tool to ask
460
+ * @param contexts - Every context the tool could act on at this placement
461
+ */
462
+ isApplicableToAnyContext(toolId, contexts) {
463
+ const tool = this.get(toolId);
464
+ if (!tool?.isApplicableToContent)
465
+ return true;
466
+ if (contexts.length === 0)
467
+ return true;
468
+ return contexts.some((context) => {
469
+ try {
470
+ return tool.isApplicableToContent?.(context) ?? true;
471
+ }
472
+ catch (error) {
473
+ console.error(`Error evaluating applicability for tool '${toolId}':`, error);
474
+ // A gate that throws has not established that the tool is useless.
475
+ return true;
476
+ }
477
+ });
478
+ }
445
479
  /**
446
480
  * Get tool metadata for building UIs
447
481
  * Useful for building PNP configuration interfaces
@@ -323,7 +323,8 @@ export declare class ToolkitCoordinator {
323
323
  private readonly toolRequests;
324
324
  private readonly sectionControllers;
325
325
  private readonly sectionControllerKeys;
326
- private readonly sectionControllerInitPromises;
326
+ private readonly sectionControllerInitEntries;
327
+ private readonly sectionControllerDisposePromises;
327
328
  private readonly sectionPersistenceStrategies;
328
329
  private readonly sectionControllerLifecycleListeners;
329
330
  /**
@@ -356,14 +357,10 @@ export declare class ToolkitCoordinator {
356
357
  private readonly telemetryListeners;
357
358
  private readonly frameworkErrorBus;
358
359
  private readonly ownsFrameworkErrorBus;
360
+ private frameworkErrorHookUnsubscribe;
361
+ private disposePromise;
359
362
  /**
360
- * Unified Tool Policy Engine. Owned by the coordinator and lives
361
- * for the lifetime of the coordinator instance — there is no
362
- * explicit teardown path today; the engine and its listener set
363
- * are reclaimed by GC when the coordinator becomes unreachable.
364
- * Subscribers attached via {@link onPolicyChange} must therefore
365
- * detach via the unsubscribe function the engine returns; do not
366
- * rely on a `disposed` event being emitted on coordinator teardown.
363
+ * Unified Tool Policy Engine. Owned by the coordinator and disposed with it.
367
364
  *
368
365
  * Hosts read decisions via {@link decideToolPolicy} or subscribe
369
366
  * to changes via {@link onPolicyChange}.
@@ -409,6 +406,7 @@ export declare class ToolkitCoordinator {
409
406
  private reportedUnboundFeaturePolicy;
410
407
  private static resolveConfig;
411
408
  constructor(config: ToolkitCoordinatorConfig);
409
+ private assertNotDisposed;
412
410
  /**
413
411
  * Subscribe the canonical `onFrameworkError` hook adapter to the
414
412
  * framework-error bus.
@@ -572,6 +570,10 @@ export declare class ToolkitCoordinator {
572
570
  private resolveExistingSectionController;
573
571
  private createSectionControllerContext;
574
572
  private initializeNewSectionController;
573
+ private createSectionControllerRetirementError;
574
+ private retireUnpublishedSectionControllerIfNeeded;
575
+ private cleanupUnpublishedSectionController;
576
+ private retirePublishedSectionControllerIfNeeded;
575
577
  private finalizeSectionControllerReady;
576
578
  private handleSectionControllerInitError;
577
579
  disposeSectionController(args: {
@@ -580,8 +582,18 @@ export declare class ToolkitCoordinator {
580
582
  persistBeforeDispose?: boolean;
581
583
  clearPersistence?: boolean;
582
584
  }): Promise<void>;
585
+ private trackSectionControllerDisposal;
586
+ private disposeSectionControllerEntry;
583
587
  private runSectionControllerDisposePipeline;
584
588
  private finalizeSectionControllerDispose;
589
+ /**
590
+ * Release every resource whose lifetime is owned by this coordinator.
591
+ * Borrowed constructor inputs, including a host framework-error bus and tool
592
+ * registry, remain owned by their caller.
593
+ */
594
+ dispose(): Promise<void>;
595
+ private disposeOwnedResources;
596
+ private waitForAdmittedInitialization;
585
597
  /**
586
598
  * Initialize TTS service with provider
587
599
  */
@@ -681,11 +693,9 @@ export declare class ToolkitCoordinator {
681
693
  * that want the new visible tool set should call
682
694
  * {@link decideToolPolicy} with their level / scope.
683
695
  *
684
- * Note: the engine itself can also emit `reason: "disposed"`, but
685
- * the coordinator does not dispose its engine on teardown today,
686
- * so subscribers attached via this method will not observe that
687
- * reason. Detach via the returned unsubscribe function instead of
688
- * relying on a `disposed` event.
696
+ * The owned engine emits `reason: "disposed"` during coordinator teardown.
697
+ * Callers should still detach through the returned unsubscribe function when
698
+ * their own lifetime ends before the coordinator's.
689
699
  */
690
700
  onPolicyChange(listener: ToolPolicyChangeListener): () => void;
691
701
  /**