@pie-players/pie-assessment-toolkit 0.3.45 → 0.3.46
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 +5 -1
- package/dist/components/ItemToolBar.custom-element.js +299 -35
- package/dist/components/PieAssessmentToolkit.custom-element.js +6 -4
- package/dist/components/SectionToolBar.custom-element.js +299 -35
- package/dist/services/ToolRegistry.d.ts +9 -0
- package/dist/services/ToolRegistry.js.map +1 -1
- package/dist/tools/registrations/calculator.js +5 -3
- package/dist/tools/registrations/calculator.js.map +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1410,7 +1410,11 @@ from tool configuration. Two sanitization layers apply:
|
|
|
1410
1410
|
(PIE-94 / WCAG 1.4.10 Reflow at 400% zoom). The wrapper is
|
|
1411
1411
|
keyboard-scrollable (`tabindex="0"`, `role="region"`) and carries
|
|
1412
1412
|
the image's `alt` text in its `aria-label`; matching CSS lives in
|
|
1413
|
-
`@pie-players/pie-theme` (`components.css`).
|
|
1413
|
+
`@pie-players/pie-theme` (`components.css`). Authored `<table>`
|
|
1414
|
+
elements outside a `pie-*` custom element get the same treatment via
|
|
1415
|
+
`<div class="pie-table-scroll">` so wide data grids reflow into a
|
|
1416
|
+
horizontally scrollable region; the wrapper's `aria-label` is derived
|
|
1417
|
+
from the table's `aria-label` / `aria-labelledby` / `<caption>`.
|
|
1414
1418
|
- **Tool icons and SSML** - tool-registered icon markup is parsed and
|
|
1415
1419
|
DOMPurified inside the toolbar at render time; SSML payloads are
|
|
1416
1420
|
restricted to an allow-listed subset of SSML tags/attributes before
|
|
@@ -5332,7 +5332,11 @@ function isExternalIconUrl(icon) {
|
|
|
5332
5332
|
|
|
5333
5333
|
// dist/components/.ItemToolBar.custom-element.unbundled.js
|
|
5334
5334
|
import { sanitizeSvgIcon } from "@pie-players/pie-players-shared/security";
|
|
5335
|
-
import {
|
|
5335
|
+
import {
|
|
5336
|
+
createFocusTrap,
|
|
5337
|
+
FOCUSABLE_SELECTOR,
|
|
5338
|
+
isProgrammaticFocusTarget
|
|
5339
|
+
} from "@pie-players/pie-players-shared";
|
|
5336
5340
|
|
|
5337
5341
|
// dist/services/tool-config-defaults.js
|
|
5338
5342
|
var DEFAULT_TOOL_PLACEMENT = {
|
|
@@ -6761,10 +6765,12 @@ var calculatorToolRegistration = {
|
|
|
6761
6765
|
draggable: true,
|
|
6762
6766
|
resizable: true,
|
|
6763
6767
|
closeable: true,
|
|
6764
|
-
initialWidth:
|
|
6765
|
-
initialHeight:
|
|
6766
|
-
minWidth:
|
|
6767
|
-
minHeight: 420
|
|
6768
|
+
initialWidth: 380,
|
|
6769
|
+
initialHeight: 420,
|
|
6770
|
+
minWidth: 380,
|
|
6771
|
+
minHeight: 420,
|
|
6772
|
+
initialAlign: "bottom-right",
|
|
6773
|
+
initialMargin: 16
|
|
6768
6774
|
}
|
|
6769
6775
|
}
|
|
6770
6776
|
],
|
|
@@ -9232,6 +9238,10 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
9232
9238
|
let controlsEl = null;
|
|
9233
9239
|
let closeButtonEl = null;
|
|
9234
9240
|
let resizeHandleEl = null;
|
|
9241
|
+
let startFocusGuardEl = null;
|
|
9242
|
+
let endFocusGuardEl = null;
|
|
9243
|
+
let focusGuardRedirecting = false;
|
|
9244
|
+
let calculatorBridgeAttached = false;
|
|
9235
9245
|
let mountedContentElement = null;
|
|
9236
9246
|
let dragPointerId = null;
|
|
9237
9247
|
let resizePointerId = null;
|
|
@@ -9241,6 +9251,10 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
9241
9251
|
let resizeStartHeight = 0;
|
|
9242
9252
|
let resizeStartX = 0;
|
|
9243
9253
|
let resizeStartY = 0;
|
|
9254
|
+
let resizeStartShellX = 0;
|
|
9255
|
+
let resizeStartShellY = 0;
|
|
9256
|
+
let resizeCorner = null;
|
|
9257
|
+
const resizeHandleEls = [];
|
|
9244
9258
|
let x2 = 0;
|
|
9245
9259
|
let y2 = 0;
|
|
9246
9260
|
let width = currentArgs.mounted.entry.shell?.initialWidth ?? 720;
|
|
@@ -9427,8 +9441,31 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
9427
9441
|
const centerShell = () => {
|
|
9428
9442
|
const viewportW = window.innerWidth;
|
|
9429
9443
|
const viewportH = window.innerHeight;
|
|
9430
|
-
|
|
9431
|
-
|
|
9444
|
+
const shellConfig = currentArgs.mounted.entry.shell;
|
|
9445
|
+
const align = shellConfig?.initialAlign ?? "center";
|
|
9446
|
+
const margin = shellConfig?.initialMargin ?? 16;
|
|
9447
|
+
const verticalMargin = margin + 60;
|
|
9448
|
+
switch (align) {
|
|
9449
|
+
case "top-left":
|
|
9450
|
+
x2 = margin;
|
|
9451
|
+
y2 = verticalMargin;
|
|
9452
|
+
break;
|
|
9453
|
+
case "top-right":
|
|
9454
|
+
x2 = Math.max(0, viewportW - width - margin);
|
|
9455
|
+
y2 = verticalMargin;
|
|
9456
|
+
break;
|
|
9457
|
+
case "bottom-left":
|
|
9458
|
+
x2 = margin;
|
|
9459
|
+
y2 = Math.max(0, viewportH - height - verticalMargin);
|
|
9460
|
+
break;
|
|
9461
|
+
case "bottom-right":
|
|
9462
|
+
x2 = Math.max(0, viewportW - width - margin);
|
|
9463
|
+
y2 = Math.max(0, viewportH - height - verticalMargin);
|
|
9464
|
+
break;
|
|
9465
|
+
default:
|
|
9466
|
+
x2 = Math.max(0, Math.round((viewportW - width) / 2));
|
|
9467
|
+
y2 = Math.max(0, Math.round((viewportH - height) / 2));
|
|
9468
|
+
}
|
|
9432
9469
|
applyPositionAndSize();
|
|
9433
9470
|
};
|
|
9434
9471
|
const restoreOpenerFocus = () => {
|
|
@@ -9464,14 +9501,113 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
9464
9501
|
if (!openerEl && active && !shellEl.contains(active)) {
|
|
9465
9502
|
openerEl = active;
|
|
9466
9503
|
}
|
|
9504
|
+
const isCalculatorShellTrap = currentArgs.mounted.toolId === "calculator";
|
|
9505
|
+
if (isCalculatorShellTrap && !calculatorBridgeAttached) {
|
|
9506
|
+
document.addEventListener("keydown", onCalculatorBridgeKeydown, true);
|
|
9507
|
+
calculatorBridgeAttached = true;
|
|
9508
|
+
}
|
|
9467
9509
|
focusTrapCleanup = createFocusTrap(shellEl, {
|
|
9468
9510
|
initialFocus: closeButtonEl,
|
|
9469
9511
|
onEscape: () => {
|
|
9470
9512
|
closeShell();
|
|
9471
|
-
}
|
|
9513
|
+
},
|
|
9514
|
+
wrap: !isCalculatorShellTrap,
|
|
9515
|
+
onTabExit: isCalculatorShellTrap ? (direction, event2) => {
|
|
9516
|
+
if (direction === "backward") {
|
|
9517
|
+
if (openerEl?.isConnected) {
|
|
9518
|
+
event2.preventDefault();
|
|
9519
|
+
try {
|
|
9520
|
+
openerEl.focus();
|
|
9521
|
+
} catch {}
|
|
9522
|
+
}
|
|
9523
|
+
return;
|
|
9524
|
+
}
|
|
9525
|
+
const next2 = findFirstQuestionFocusable();
|
|
9526
|
+
if (next2) {
|
|
9527
|
+
event2.preventDefault();
|
|
9528
|
+
try {
|
|
9529
|
+
next2.focus();
|
|
9530
|
+
} catch {}
|
|
9531
|
+
}
|
|
9532
|
+
} : undefined
|
|
9472
9533
|
});
|
|
9473
9534
|
};
|
|
9535
|
+
const findFirstQuestionFocusable = () => {
|
|
9536
|
+
const scope = get2(toolbarContext).getScopeElement?.() ?? null;
|
|
9537
|
+
if (!scope)
|
|
9538
|
+
return null;
|
|
9539
|
+
const isExcluded = (el) => {
|
|
9540
|
+
if (get2(toolbarRootElement) && get2(toolbarRootElement).contains(el))
|
|
9541
|
+
return true;
|
|
9542
|
+
if (shellEl && shellEl.contains(el))
|
|
9543
|
+
return true;
|
|
9544
|
+
return false;
|
|
9545
|
+
};
|
|
9546
|
+
const visit = (node2) => {
|
|
9547
|
+
if (node2 instanceof HTMLElement) {
|
|
9548
|
+
if (isExcluded(node2))
|
|
9549
|
+
return null;
|
|
9550
|
+
if (node2.matches?.(FOCUSABLE_SELECTOR) && isProgrammaticFocusTarget(node2)) {
|
|
9551
|
+
return node2;
|
|
9552
|
+
}
|
|
9553
|
+
}
|
|
9554
|
+
const shadow = node2.shadowRoot;
|
|
9555
|
+
if (shadow) {
|
|
9556
|
+
for (let i = 0;i < shadow.children.length; i++) {
|
|
9557
|
+
const found = visit(shadow.children[i]);
|
|
9558
|
+
if (found)
|
|
9559
|
+
return found;
|
|
9560
|
+
}
|
|
9561
|
+
}
|
|
9562
|
+
for (let i = 0;i < node2.children.length; i++) {
|
|
9563
|
+
const found = visit(node2.children[i]);
|
|
9564
|
+
if (found)
|
|
9565
|
+
return found;
|
|
9566
|
+
}
|
|
9567
|
+
return null;
|
|
9568
|
+
};
|
|
9569
|
+
return visit(scope);
|
|
9570
|
+
};
|
|
9571
|
+
const getShellFocusables = () => {
|
|
9572
|
+
if (!shellEl)
|
|
9573
|
+
return [];
|
|
9574
|
+
return Array.from(shellEl.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el) => el !== startFocusGuardEl && el !== endFocusGuardEl).filter(isProgrammaticFocusTarget);
|
|
9575
|
+
};
|
|
9576
|
+
const onCalculatorBridgeKeydown = (event2) => {
|
|
9577
|
+
if (event2.key !== "Tab")
|
|
9578
|
+
return;
|
|
9579
|
+
if (!shellEl || !currentArgs.active)
|
|
9580
|
+
return;
|
|
9581
|
+
const active = getDeepActiveElement();
|
|
9582
|
+
if (!active)
|
|
9583
|
+
return;
|
|
9584
|
+
if (shellEl.contains(active))
|
|
9585
|
+
return;
|
|
9586
|
+
const focusables = getShellFocusables();
|
|
9587
|
+
if (focusables.length === 0)
|
|
9588
|
+
return;
|
|
9589
|
+
if (event2.shiftKey) {
|
|
9590
|
+
const firstQuestion = findFirstQuestionFocusable();
|
|
9591
|
+
if (active !== firstQuestion)
|
|
9592
|
+
return;
|
|
9593
|
+
event2.preventDefault();
|
|
9594
|
+
try {
|
|
9595
|
+
focusables[focusables.length - 1].focus();
|
|
9596
|
+
} catch {}
|
|
9597
|
+
return;
|
|
9598
|
+
}
|
|
9599
|
+
if (active !== openerEl)
|
|
9600
|
+
return;
|
|
9601
|
+
event2.preventDefault();
|
|
9602
|
+
try {
|
|
9603
|
+
focusables[0].focus();
|
|
9604
|
+
} catch {}
|
|
9605
|
+
};
|
|
9474
9606
|
const removeFocusTrap = () => {
|
|
9607
|
+
if (calculatorBridgeAttached) {
|
|
9608
|
+
document.removeEventListener("keydown", onCalculatorBridgeKeydown, true);
|
|
9609
|
+
calculatorBridgeAttached = false;
|
|
9610
|
+
}
|
|
9475
9611
|
if (!focusTrapCleanup)
|
|
9476
9612
|
return;
|
|
9477
9613
|
const cleanup = focusTrapCleanup;
|
|
@@ -9489,25 +9625,28 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
9489
9625
|
if (!currentArgs.mounted.entry.shell?.draggable)
|
|
9490
9626
|
return;
|
|
9491
9627
|
const target = event2.target;
|
|
9492
|
-
if (target.closest("button"))
|
|
9493
|
-
return;
|
|
9494
|
-
if (!shellEl)
|
|
9628
|
+
if (target.closest("button") || !shellEl)
|
|
9495
9629
|
return;
|
|
9630
|
+
event2.preventDefault();
|
|
9496
9631
|
dragPointerId = event2.pointerId;
|
|
9497
9632
|
dragOffsetX = event2.clientX - x2;
|
|
9498
9633
|
dragOffsetY = event2.clientY - y2;
|
|
9499
9634
|
shellEl.setPointerCapture(event2.pointerId);
|
|
9500
9635
|
bringToFront();
|
|
9501
9636
|
};
|
|
9502
|
-
const
|
|
9637
|
+
const createResizePointerDownHandler = (corner) => (event2) => {
|
|
9503
9638
|
if (!shellEl || !currentArgs.mounted.entry.shell?.resizable)
|
|
9504
9639
|
return;
|
|
9640
|
+
event2.preventDefault();
|
|
9505
9641
|
event2.stopPropagation();
|
|
9506
9642
|
resizePointerId = event2.pointerId;
|
|
9643
|
+
resizeCorner = corner;
|
|
9507
9644
|
resizeStartWidth = width;
|
|
9508
9645
|
resizeStartHeight = height;
|
|
9509
9646
|
resizeStartX = event2.clientX;
|
|
9510
9647
|
resizeStartY = event2.clientY;
|
|
9648
|
+
resizeStartShellX = x2;
|
|
9649
|
+
resizeStartShellY = y2;
|
|
9511
9650
|
shellEl.setPointerCapture(event2.pointerId);
|
|
9512
9651
|
bringToFront();
|
|
9513
9652
|
};
|
|
@@ -9515,6 +9654,7 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
9515
9654
|
if (!shellEl)
|
|
9516
9655
|
return;
|
|
9517
9656
|
if (dragPointerId === event2.pointerId) {
|
|
9657
|
+
event2.preventDefault();
|
|
9518
9658
|
const maxX = Math.max(0, window.innerWidth - width);
|
|
9519
9659
|
const maxY = Math.max(0, window.innerHeight - height);
|
|
9520
9660
|
x2 = clamp(event2.clientX - dragOffsetX, 0, maxX);
|
|
@@ -9523,13 +9663,39 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
9523
9663
|
return;
|
|
9524
9664
|
}
|
|
9525
9665
|
if (resizePointerId === event2.pointerId) {
|
|
9666
|
+
event2.preventDefault();
|
|
9526
9667
|
const shellConfig = currentArgs.mounted.entry.shell;
|
|
9527
9668
|
const minWidth = shellConfig?.minWidth ?? 320;
|
|
9528
9669
|
const minHeight = shellConfig?.minHeight ?? 240;
|
|
9529
9670
|
const maxWidth = shellConfig?.maxWidth ?? window.innerWidth;
|
|
9530
9671
|
const maxHeight = shellConfig?.maxHeight ?? window.innerHeight;
|
|
9531
|
-
|
|
9532
|
-
|
|
9672
|
+
const dx = event2.clientX - resizeStartX;
|
|
9673
|
+
const dy = event2.clientY - resizeStartY;
|
|
9674
|
+
if (resizeCorner === "se") {
|
|
9675
|
+
width = clamp(resizeStartWidth + dx, minWidth, Math.max(minWidth, Math.min(maxWidth, window.innerWidth - x2)));
|
|
9676
|
+
height = clamp(resizeStartHeight + dy, minHeight, Math.max(minHeight, Math.min(maxHeight, window.innerHeight - y2)));
|
|
9677
|
+
} else if (resizeCorner === "sw") {
|
|
9678
|
+
const rightEdge = resizeStartShellX + resizeStartWidth;
|
|
9679
|
+
const newWidth = clamp(resizeStartWidth - dx, minWidth, Math.min(maxWidth, rightEdge));
|
|
9680
|
+
x2 = rightEdge - newWidth;
|
|
9681
|
+
width = newWidth;
|
|
9682
|
+
height = clamp(resizeStartHeight + dy, minHeight, Math.max(minHeight, Math.min(maxHeight, window.innerHeight - y2)));
|
|
9683
|
+
} else if (resizeCorner === "ne") {
|
|
9684
|
+
const bottomEdge = resizeStartShellY + resizeStartHeight;
|
|
9685
|
+
width = clamp(resizeStartWidth + dx, minWidth, Math.max(minWidth, Math.min(maxWidth, window.innerWidth - x2)));
|
|
9686
|
+
const newHeight = clamp(resizeStartHeight - dy, minHeight, Math.min(maxHeight, bottomEdge));
|
|
9687
|
+
y2 = bottomEdge - newHeight;
|
|
9688
|
+
height = newHeight;
|
|
9689
|
+
} else if (resizeCorner === "nw") {
|
|
9690
|
+
const rightEdge = resizeStartShellX + resizeStartWidth;
|
|
9691
|
+
const bottomEdge = resizeStartShellY + resizeStartHeight;
|
|
9692
|
+
const newWidth = clamp(resizeStartWidth - dx, minWidth, Math.min(maxWidth, rightEdge));
|
|
9693
|
+
x2 = rightEdge - newWidth;
|
|
9694
|
+
width = newWidth;
|
|
9695
|
+
const newHeight = clamp(resizeStartHeight - dy, minHeight, Math.min(maxHeight, bottomEdge));
|
|
9696
|
+
y2 = bottomEdge - newHeight;
|
|
9697
|
+
height = newHeight;
|
|
9698
|
+
}
|
|
9533
9699
|
applyShellStyle();
|
|
9534
9700
|
notifyHostedResize();
|
|
9535
9701
|
}
|
|
@@ -9562,14 +9728,20 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
9562
9728
|
shellEl.style.border = "1px solid var(--pie-border-light, #d1d5db)";
|
|
9563
9729
|
shellEl.style.borderRadius = "12px";
|
|
9564
9730
|
shellEl.style.boxShadow = "0 10px 40px color-mix(in srgb, var(--pie-black, #000) 25%, transparent)";
|
|
9565
|
-
shellEl.style.overflow = "
|
|
9731
|
+
shellEl.style.overflow = "visible";
|
|
9566
9732
|
shellEl.style.display = currentArgs.active ? "flex" : "none";
|
|
9567
9733
|
shellEl.style.flexDirection = "column";
|
|
9734
|
+
shellEl.style.userSelect = "none";
|
|
9735
|
+
shellEl.style.webkitUserSelect = "none";
|
|
9736
|
+
shellEl.style.touchAction = "none";
|
|
9737
|
+
shellEl.setAttribute("draggable", "false");
|
|
9738
|
+
shellEl.addEventListener("dragstart", (e) => e.preventDefault());
|
|
9568
9739
|
headerEl = document.createElement("div");
|
|
9569
9740
|
headerEl.className = "pie-tool-shell__header";
|
|
9570
9741
|
headerEl.style.display = "flex";
|
|
9571
9742
|
headerEl.style.alignItems = "center";
|
|
9572
9743
|
headerEl.style.justifyContent = "space-between";
|
|
9744
|
+
headerEl.style.gap = "6px";
|
|
9573
9745
|
headerEl.style.padding = isCalculatorShell ? "12px 12px 12px 28px" : "10px 12px";
|
|
9574
9746
|
if (isCalculatorShell) {
|
|
9575
9747
|
headerEl.style.minHeight = "48px";
|
|
@@ -9584,6 +9756,13 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
9584
9756
|
}
|
|
9585
9757
|
headerEl.style.cursor = currentArgs.mounted.entry.shell.draggable === false ? "default" : "move";
|
|
9586
9758
|
headerEl.style.flex = "0 0 auto";
|
|
9759
|
+
headerEl.style.borderRadius = "12px 12px 0 0";
|
|
9760
|
+
headerEl.style.overflow = "hidden";
|
|
9761
|
+
headerEl.style.userSelect = "none";
|
|
9762
|
+
headerEl.style.webkitUserSelect = "none";
|
|
9763
|
+
headerEl.style.touchAction = "none";
|
|
9764
|
+
headerEl.setAttribute("draggable", "false");
|
|
9765
|
+
headerEl.addEventListener("dragstart", (e) => e.preventDefault());
|
|
9587
9766
|
titleEl = document.createElement("span");
|
|
9588
9767
|
titleEl.className = "pie-tool-shell__title";
|
|
9589
9768
|
titleEl.textContent = currentArgs.mounted.entry.shell.title || currentArgs.mounted.toolId;
|
|
@@ -9730,20 +9909,100 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
9730
9909
|
contentEl.style.width = "100%";
|
|
9731
9910
|
contentEl.style.flex = "1 1 auto";
|
|
9732
9911
|
contentEl.style.minHeight = "0";
|
|
9912
|
+
contentEl.style.overflow = "hidden";
|
|
9913
|
+
contentEl.style.borderRadius = "0 0 12px 12px";
|
|
9914
|
+
if (isCalculatorShell) {
|
|
9915
|
+
const makeFocusGuard = (label) => {
|
|
9916
|
+
const el = document.createElement("div");
|
|
9917
|
+
el.tabIndex = 0;
|
|
9918
|
+
el.setAttribute("aria-hidden", "true");
|
|
9919
|
+
el.setAttribute("data-pie-tool-shell-focus-guard", label);
|
|
9920
|
+
el.style.position = "absolute";
|
|
9921
|
+
el.style.width = "1px";
|
|
9922
|
+
el.style.height = "1px";
|
|
9923
|
+
el.style.padding = "0";
|
|
9924
|
+
el.style.margin = "-1px";
|
|
9925
|
+
el.style.overflow = "hidden";
|
|
9926
|
+
el.style.clipPath = "inset(50%)";
|
|
9927
|
+
el.style.whiteSpace = "nowrap";
|
|
9928
|
+
el.style.border = "0";
|
|
9929
|
+
return el;
|
|
9930
|
+
};
|
|
9931
|
+
startFocusGuardEl = makeFocusGuard("start");
|
|
9932
|
+
endFocusGuardEl = makeFocusGuard("end");
|
|
9933
|
+
startFocusGuardEl.addEventListener("focus", (event2) => {
|
|
9934
|
+
if (focusGuardRedirecting)
|
|
9935
|
+
return;
|
|
9936
|
+
focusGuardRedirecting = true;
|
|
9937
|
+
try {
|
|
9938
|
+
event2.stopPropagation();
|
|
9939
|
+
if (openerEl?.isConnected) {
|
|
9940
|
+
try {
|
|
9941
|
+
openerEl.focus();
|
|
9942
|
+
} catch {}
|
|
9943
|
+
}
|
|
9944
|
+
} finally {
|
|
9945
|
+
queueMicrotask(() => {
|
|
9946
|
+
focusGuardRedirecting = false;
|
|
9947
|
+
});
|
|
9948
|
+
}
|
|
9949
|
+
});
|
|
9950
|
+
endFocusGuardEl.addEventListener("focus", (event2) => {
|
|
9951
|
+
if (focusGuardRedirecting)
|
|
9952
|
+
return;
|
|
9953
|
+
focusGuardRedirecting = true;
|
|
9954
|
+
try {
|
|
9955
|
+
event2.stopPropagation();
|
|
9956
|
+
const next2 = findFirstQuestionFocusable();
|
|
9957
|
+
if (next2) {
|
|
9958
|
+
try {
|
|
9959
|
+
next2.focus();
|
|
9960
|
+
} catch {}
|
|
9961
|
+
}
|
|
9962
|
+
} finally {
|
|
9963
|
+
queueMicrotask(() => {
|
|
9964
|
+
focusGuardRedirecting = false;
|
|
9965
|
+
});
|
|
9966
|
+
}
|
|
9967
|
+
});
|
|
9968
|
+
shellEl.appendChild(startFocusGuardEl);
|
|
9969
|
+
}
|
|
9733
9970
|
shellEl.appendChild(headerEl);
|
|
9734
9971
|
shellEl.appendChild(contentEl);
|
|
9972
|
+
if (isCalculatorShell && endFocusGuardEl) {
|
|
9973
|
+
shellEl.appendChild(endFocusGuardEl);
|
|
9974
|
+
}
|
|
9735
9975
|
if (currentArgs.mounted.entry.shell.resizable !== false) {
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
|
|
9743
|
-
|
|
9744
|
-
|
|
9745
|
-
|
|
9746
|
-
|
|
9976
|
+
const addResizeHandle = (corner, top, right, bottom, left, cursor, gradientAngle) => {
|
|
9977
|
+
const el = document.createElement("div");
|
|
9978
|
+
el.className = "pie-tool-shell__resize";
|
|
9979
|
+
el.style.position = "absolute";
|
|
9980
|
+
el.style.width = "24px";
|
|
9981
|
+
el.style.height = "24px";
|
|
9982
|
+
el.style.cursor = cursor;
|
|
9983
|
+
el.style.zIndex = "10";
|
|
9984
|
+
el.style.touchAction = "none";
|
|
9985
|
+
el.setAttribute("draggable", "false");
|
|
9986
|
+
if (top !== null)
|
|
9987
|
+
el.style.top = top;
|
|
9988
|
+
if (right !== null)
|
|
9989
|
+
el.style.right = right;
|
|
9990
|
+
if (bottom !== null)
|
|
9991
|
+
el.style.bottom = bottom;
|
|
9992
|
+
if (left !== null)
|
|
9993
|
+
el.style.left = left;
|
|
9994
|
+
if (gradientAngle !== null) {
|
|
9995
|
+
el.style.background = `linear-gradient(${gradientAngle}, transparent 0%, transparent 40%, rgba(0,0,0,0.35) 40%, rgba(0,0,0,0.35) 60%, transparent 60%, transparent 100%)`;
|
|
9996
|
+
}
|
|
9997
|
+
const handler = createResizePointerDownHandler(corner);
|
|
9998
|
+
el.addEventListener("pointerdown", handler);
|
|
9999
|
+
shellEl.appendChild(el);
|
|
10000
|
+
resizeHandleEls.push({ el, handler });
|
|
10001
|
+
};
|
|
10002
|
+
addResizeHandle("se", null, "0", "0", null, "nwse-resize", null);
|
|
10003
|
+
addResizeHandle("nw", "0", null, null, "0", "nwse-resize", "135deg");
|
|
10004
|
+
addResizeHandle("ne", "0", "0", null, null, "nesw-resize", null);
|
|
10005
|
+
addResizeHandle("sw", null, null, "0", "0", "nesw-resize", null);
|
|
9747
10006
|
}
|
|
9748
10007
|
headerEl.addEventListener("pointerdown", onHeaderPointerDown);
|
|
9749
10008
|
shellEl.addEventListener("pointermove", onShellPointerMove);
|
|
@@ -9784,10 +10043,15 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
9784
10043
|
if (focusTrapCleanup) {
|
|
9785
10044
|
removeFocusTrap();
|
|
9786
10045
|
}
|
|
9787
|
-
|
|
9788
|
-
|
|
9789
|
-
|
|
10046
|
+
if (calculatorBridgeAttached) {
|
|
10047
|
+
document.removeEventListener("keydown", onCalculatorBridgeKeydown, true);
|
|
10048
|
+
calculatorBridgeAttached = false;
|
|
10049
|
+
}
|
|
10050
|
+
for (const { el, handler } of resizeHandleEls) {
|
|
10051
|
+
el.removeEventListener("pointerdown", handler);
|
|
9790
10052
|
}
|
|
10053
|
+
openerEl = null;
|
|
10054
|
+
resizeHandleEls.length = 0;
|
|
9791
10055
|
if (headerEl) {
|
|
9792
10056
|
headerEl.removeEventListener("pointerdown", onHeaderPointerDown);
|
|
9793
10057
|
headerEl.onkeydown = null;
|
|
@@ -9994,9 +10258,9 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
9994
10258
|
append($$anchor7, span_4);
|
|
9995
10259
|
};
|
|
9996
10260
|
var alternate_1 = ($$anchor7) => {
|
|
9997
|
-
var
|
|
9998
|
-
template_effect(() => set_class(
|
|
9999
|
-
append($$anchor7,
|
|
10261
|
+
var i_1 = root_5();
|
|
10262
|
+
template_effect(() => set_class(i_1, 1, `icon icon-${get2(item2).icon}`, "svelte-rnx3ie"));
|
|
10263
|
+
append($$anchor7, i_1);
|
|
10000
10264
|
};
|
|
10001
10265
|
if_block(node_8, ($$render) => {
|
|
10002
10266
|
if (get2(fallbackIcon))
|
|
@@ -10086,9 +10350,9 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
10086
10350
|
append($$anchor7, span_7);
|
|
10087
10351
|
};
|
|
10088
10352
|
var alternate_3 = ($$anchor7) => {
|
|
10089
|
-
var
|
|
10090
|
-
template_effect(() => set_class(
|
|
10091
|
-
append($$anchor7,
|
|
10353
|
+
var i_2 = root_5();
|
|
10354
|
+
template_effect(() => set_class(i_2, 1, `icon icon-${get2(item2).icon}`, "svelte-rnx3ie"));
|
|
10355
|
+
append($$anchor7, i_2);
|
|
10092
10356
|
};
|
|
10093
10357
|
if_block(node_11, ($$render) => {
|
|
10094
10358
|
if (get2(fallbackIcon))
|
|
@@ -18622,10 +18622,12 @@ var calculatorToolRegistration = {
|
|
|
18622
18622
|
draggable: true,
|
|
18623
18623
|
resizable: true,
|
|
18624
18624
|
closeable: true,
|
|
18625
|
-
initialWidth:
|
|
18626
|
-
initialHeight:
|
|
18627
|
-
minWidth:
|
|
18628
|
-
minHeight: 420
|
|
18625
|
+
initialWidth: 380,
|
|
18626
|
+
initialHeight: 420,
|
|
18627
|
+
minWidth: 380,
|
|
18628
|
+
minHeight: 420,
|
|
18629
|
+
initialAlign: "bottom-right",
|
|
18630
|
+
initialMargin: 16
|
|
18629
18631
|
}
|
|
18630
18632
|
}
|
|
18631
18633
|
],
|
|
@@ -4316,7 +4316,11 @@ function create_custom_element(Component, props_definition, slots, exports, shad
|
|
|
4316
4316
|
import { ContextConsumer } from "@pie-players/pie-context";
|
|
4317
4317
|
import { createContext as createContext2 } from "@pie-players/pie-context";
|
|
4318
4318
|
import { sanitizeSvgIcon } from "@pie-players/pie-players-shared/security";
|
|
4319
|
-
import {
|
|
4319
|
+
import {
|
|
4320
|
+
createFocusTrap,
|
|
4321
|
+
FOCUSABLE_SELECTOR,
|
|
4322
|
+
isProgrammaticFocusTarget
|
|
4323
|
+
} from "@pie-players/pie-players-shared";
|
|
4320
4324
|
import { validateCustomElementTag } from "@pie-players/pie-players-shared/pie/tag-names";
|
|
4321
4325
|
var PUBLIC_VERSION2 = "5";
|
|
4322
4326
|
if (typeof window !== "undefined") {
|
|
@@ -10949,10 +10953,12 @@ var calculatorToolRegistration = {
|
|
|
10949
10953
|
draggable: true,
|
|
10950
10954
|
resizable: true,
|
|
10951
10955
|
closeable: true,
|
|
10952
|
-
initialWidth:
|
|
10953
|
-
initialHeight:
|
|
10954
|
-
minWidth:
|
|
10955
|
-
minHeight: 420
|
|
10956
|
+
initialWidth: 380,
|
|
10957
|
+
initialHeight: 420,
|
|
10958
|
+
minWidth: 380,
|
|
10959
|
+
minHeight: 420,
|
|
10960
|
+
initialAlign: "bottom-right",
|
|
10961
|
+
initialMargin: 16
|
|
10956
10962
|
}
|
|
10957
10963
|
}
|
|
10958
10964
|
],
|
|
@@ -13396,6 +13402,10 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
13396
13402
|
let controlsEl = null;
|
|
13397
13403
|
let closeButtonEl = null;
|
|
13398
13404
|
let resizeHandleEl = null;
|
|
13405
|
+
let startFocusGuardEl = null;
|
|
13406
|
+
let endFocusGuardEl = null;
|
|
13407
|
+
let focusGuardRedirecting = false;
|
|
13408
|
+
let calculatorBridgeAttached = false;
|
|
13399
13409
|
let mountedContentElement = null;
|
|
13400
13410
|
let dragPointerId = null;
|
|
13401
13411
|
let resizePointerId = null;
|
|
@@ -13405,6 +13415,10 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
13405
13415
|
let resizeStartHeight = 0;
|
|
13406
13416
|
let resizeStartX = 0;
|
|
13407
13417
|
let resizeStartY = 0;
|
|
13418
|
+
let resizeStartShellX = 0;
|
|
13419
|
+
let resizeStartShellY = 0;
|
|
13420
|
+
let resizeCorner = null;
|
|
13421
|
+
const resizeHandleEls = [];
|
|
13408
13422
|
let x2 = 0;
|
|
13409
13423
|
let y2 = 0;
|
|
13410
13424
|
let width = currentArgs.mounted.entry.shell?.initialWidth ?? 720;
|
|
@@ -13591,8 +13605,31 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
13591
13605
|
const centerShell = () => {
|
|
13592
13606
|
const viewportW = window.innerWidth;
|
|
13593
13607
|
const viewportH = window.innerHeight;
|
|
13594
|
-
|
|
13595
|
-
|
|
13608
|
+
const shellConfig = currentArgs.mounted.entry.shell;
|
|
13609
|
+
const align = shellConfig?.initialAlign ?? "center";
|
|
13610
|
+
const margin = shellConfig?.initialMargin ?? 16;
|
|
13611
|
+
const verticalMargin = margin + 60;
|
|
13612
|
+
switch (align) {
|
|
13613
|
+
case "top-left":
|
|
13614
|
+
x2 = margin;
|
|
13615
|
+
y2 = verticalMargin;
|
|
13616
|
+
break;
|
|
13617
|
+
case "top-right":
|
|
13618
|
+
x2 = Math.max(0, viewportW - width - margin);
|
|
13619
|
+
y2 = verticalMargin;
|
|
13620
|
+
break;
|
|
13621
|
+
case "bottom-left":
|
|
13622
|
+
x2 = margin;
|
|
13623
|
+
y2 = Math.max(0, viewportH - height - verticalMargin);
|
|
13624
|
+
break;
|
|
13625
|
+
case "bottom-right":
|
|
13626
|
+
x2 = Math.max(0, viewportW - width - margin);
|
|
13627
|
+
y2 = Math.max(0, viewportH - height - verticalMargin);
|
|
13628
|
+
break;
|
|
13629
|
+
default:
|
|
13630
|
+
x2 = Math.max(0, Math.round((viewportW - width) / 2));
|
|
13631
|
+
y2 = Math.max(0, Math.round((viewportH - height) / 2));
|
|
13632
|
+
}
|
|
13596
13633
|
applyPositionAndSize();
|
|
13597
13634
|
};
|
|
13598
13635
|
const restoreOpenerFocus = () => {
|
|
@@ -13628,14 +13665,113 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
13628
13665
|
if (!openerEl && active && !shellEl.contains(active)) {
|
|
13629
13666
|
openerEl = active;
|
|
13630
13667
|
}
|
|
13668
|
+
const isCalculatorShellTrap = currentArgs.mounted.toolId === "calculator";
|
|
13669
|
+
if (isCalculatorShellTrap && !calculatorBridgeAttached) {
|
|
13670
|
+
document.addEventListener("keydown", onCalculatorBridgeKeydown, true);
|
|
13671
|
+
calculatorBridgeAttached = true;
|
|
13672
|
+
}
|
|
13631
13673
|
focusTrapCleanup = createFocusTrap(shellEl, {
|
|
13632
13674
|
initialFocus: closeButtonEl,
|
|
13633
13675
|
onEscape: () => {
|
|
13634
13676
|
closeShell();
|
|
13635
|
-
}
|
|
13677
|
+
},
|
|
13678
|
+
wrap: !isCalculatorShellTrap,
|
|
13679
|
+
onTabExit: isCalculatorShellTrap ? (direction, event2) => {
|
|
13680
|
+
if (direction === "backward") {
|
|
13681
|
+
if (openerEl?.isConnected) {
|
|
13682
|
+
event2.preventDefault();
|
|
13683
|
+
try {
|
|
13684
|
+
openerEl.focus();
|
|
13685
|
+
} catch {}
|
|
13686
|
+
}
|
|
13687
|
+
return;
|
|
13688
|
+
}
|
|
13689
|
+
const next22 = findFirstQuestionFocusable();
|
|
13690
|
+
if (next22) {
|
|
13691
|
+
event2.preventDefault();
|
|
13692
|
+
try {
|
|
13693
|
+
next22.focus();
|
|
13694
|
+
} catch {}
|
|
13695
|
+
}
|
|
13696
|
+
} : undefined
|
|
13636
13697
|
});
|
|
13637
13698
|
};
|
|
13699
|
+
const findFirstQuestionFocusable = () => {
|
|
13700
|
+
const scope = get22(toolbarContext).getScopeElement?.() ?? null;
|
|
13701
|
+
if (!scope)
|
|
13702
|
+
return null;
|
|
13703
|
+
const isExcluded = (el) => {
|
|
13704
|
+
if (get22(toolbarRootElement) && get22(toolbarRootElement).contains(el))
|
|
13705
|
+
return true;
|
|
13706
|
+
if (shellEl && shellEl.contains(el))
|
|
13707
|
+
return true;
|
|
13708
|
+
return false;
|
|
13709
|
+
};
|
|
13710
|
+
const visit = (node2) => {
|
|
13711
|
+
if (node2 instanceof HTMLElement) {
|
|
13712
|
+
if (isExcluded(node2))
|
|
13713
|
+
return null;
|
|
13714
|
+
if (node2.matches?.(FOCUSABLE_SELECTOR) && isProgrammaticFocusTarget(node2)) {
|
|
13715
|
+
return node2;
|
|
13716
|
+
}
|
|
13717
|
+
}
|
|
13718
|
+
const shadow = node2.shadowRoot;
|
|
13719
|
+
if (shadow) {
|
|
13720
|
+
for (let i = 0;i < shadow.children.length; i++) {
|
|
13721
|
+
const found = visit(shadow.children[i]);
|
|
13722
|
+
if (found)
|
|
13723
|
+
return found;
|
|
13724
|
+
}
|
|
13725
|
+
}
|
|
13726
|
+
for (let i = 0;i < node2.children.length; i++) {
|
|
13727
|
+
const found = visit(node2.children[i]);
|
|
13728
|
+
if (found)
|
|
13729
|
+
return found;
|
|
13730
|
+
}
|
|
13731
|
+
return null;
|
|
13732
|
+
};
|
|
13733
|
+
return visit(scope);
|
|
13734
|
+
};
|
|
13735
|
+
const getShellFocusables = () => {
|
|
13736
|
+
if (!shellEl)
|
|
13737
|
+
return [];
|
|
13738
|
+
return Array.from(shellEl.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el) => el !== startFocusGuardEl && el !== endFocusGuardEl).filter(isProgrammaticFocusTarget);
|
|
13739
|
+
};
|
|
13740
|
+
const onCalculatorBridgeKeydown = (event2) => {
|
|
13741
|
+
if (event2.key !== "Tab")
|
|
13742
|
+
return;
|
|
13743
|
+
if (!shellEl || !currentArgs.active)
|
|
13744
|
+
return;
|
|
13745
|
+
const active = getDeepActiveElement();
|
|
13746
|
+
if (!active)
|
|
13747
|
+
return;
|
|
13748
|
+
if (shellEl.contains(active))
|
|
13749
|
+
return;
|
|
13750
|
+
const focusables = getShellFocusables();
|
|
13751
|
+
if (focusables.length === 0)
|
|
13752
|
+
return;
|
|
13753
|
+
if (event2.shiftKey) {
|
|
13754
|
+
const firstQuestion = findFirstQuestionFocusable();
|
|
13755
|
+
if (active !== firstQuestion)
|
|
13756
|
+
return;
|
|
13757
|
+
event2.preventDefault();
|
|
13758
|
+
try {
|
|
13759
|
+
focusables[focusables.length - 1].focus();
|
|
13760
|
+
} catch {}
|
|
13761
|
+
return;
|
|
13762
|
+
}
|
|
13763
|
+
if (active !== openerEl)
|
|
13764
|
+
return;
|
|
13765
|
+
event2.preventDefault();
|
|
13766
|
+
try {
|
|
13767
|
+
focusables[0].focus();
|
|
13768
|
+
} catch {}
|
|
13769
|
+
};
|
|
13638
13770
|
const removeFocusTrap = () => {
|
|
13771
|
+
if (calculatorBridgeAttached) {
|
|
13772
|
+
document.removeEventListener("keydown", onCalculatorBridgeKeydown, true);
|
|
13773
|
+
calculatorBridgeAttached = false;
|
|
13774
|
+
}
|
|
13639
13775
|
if (!focusTrapCleanup)
|
|
13640
13776
|
return;
|
|
13641
13777
|
const cleanup = focusTrapCleanup;
|
|
@@ -13653,25 +13789,28 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
13653
13789
|
if (!currentArgs.mounted.entry.shell?.draggable)
|
|
13654
13790
|
return;
|
|
13655
13791
|
const target = event2.target;
|
|
13656
|
-
if (target.closest("button"))
|
|
13657
|
-
return;
|
|
13658
|
-
if (!shellEl)
|
|
13792
|
+
if (target.closest("button") || !shellEl)
|
|
13659
13793
|
return;
|
|
13794
|
+
event2.preventDefault();
|
|
13660
13795
|
dragPointerId = event2.pointerId;
|
|
13661
13796
|
dragOffsetX = event2.clientX - x2;
|
|
13662
13797
|
dragOffsetY = event2.clientY - y2;
|
|
13663
13798
|
shellEl.setPointerCapture(event2.pointerId);
|
|
13664
13799
|
bringToFront();
|
|
13665
13800
|
};
|
|
13666
|
-
const
|
|
13801
|
+
const createResizePointerDownHandler = (corner) => (event2) => {
|
|
13667
13802
|
if (!shellEl || !currentArgs.mounted.entry.shell?.resizable)
|
|
13668
13803
|
return;
|
|
13804
|
+
event2.preventDefault();
|
|
13669
13805
|
event2.stopPropagation();
|
|
13670
13806
|
resizePointerId = event2.pointerId;
|
|
13807
|
+
resizeCorner = corner;
|
|
13671
13808
|
resizeStartWidth = width;
|
|
13672
13809
|
resizeStartHeight = height;
|
|
13673
13810
|
resizeStartX = event2.clientX;
|
|
13674
13811
|
resizeStartY = event2.clientY;
|
|
13812
|
+
resizeStartShellX = x2;
|
|
13813
|
+
resizeStartShellY = y2;
|
|
13675
13814
|
shellEl.setPointerCapture(event2.pointerId);
|
|
13676
13815
|
bringToFront();
|
|
13677
13816
|
};
|
|
@@ -13679,6 +13818,7 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
13679
13818
|
if (!shellEl)
|
|
13680
13819
|
return;
|
|
13681
13820
|
if (dragPointerId === event2.pointerId) {
|
|
13821
|
+
event2.preventDefault();
|
|
13682
13822
|
const maxX = Math.max(0, window.innerWidth - width);
|
|
13683
13823
|
const maxY = Math.max(0, window.innerHeight - height);
|
|
13684
13824
|
x2 = clamp(event2.clientX - dragOffsetX, 0, maxX);
|
|
@@ -13687,13 +13827,39 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
13687
13827
|
return;
|
|
13688
13828
|
}
|
|
13689
13829
|
if (resizePointerId === event2.pointerId) {
|
|
13830
|
+
event2.preventDefault();
|
|
13690
13831
|
const shellConfig = currentArgs.mounted.entry.shell;
|
|
13691
13832
|
const minWidth = shellConfig?.minWidth ?? 320;
|
|
13692
13833
|
const minHeight = shellConfig?.minHeight ?? 240;
|
|
13693
13834
|
const maxWidth = shellConfig?.maxWidth ?? window.innerWidth;
|
|
13694
13835
|
const maxHeight = shellConfig?.maxHeight ?? window.innerHeight;
|
|
13695
|
-
|
|
13696
|
-
|
|
13836
|
+
const dx = event2.clientX - resizeStartX;
|
|
13837
|
+
const dy = event2.clientY - resizeStartY;
|
|
13838
|
+
if (resizeCorner === "se") {
|
|
13839
|
+
width = clamp(resizeStartWidth + dx, minWidth, Math.max(minWidth, Math.min(maxWidth, window.innerWidth - x2)));
|
|
13840
|
+
height = clamp(resizeStartHeight + dy, minHeight, Math.max(minHeight, Math.min(maxHeight, window.innerHeight - y2)));
|
|
13841
|
+
} else if (resizeCorner === "sw") {
|
|
13842
|
+
const rightEdge = resizeStartShellX + resizeStartWidth;
|
|
13843
|
+
const newWidth = clamp(resizeStartWidth - dx, minWidth, Math.min(maxWidth, rightEdge));
|
|
13844
|
+
x2 = rightEdge - newWidth;
|
|
13845
|
+
width = newWidth;
|
|
13846
|
+
height = clamp(resizeStartHeight + dy, minHeight, Math.max(minHeight, Math.min(maxHeight, window.innerHeight - y2)));
|
|
13847
|
+
} else if (resizeCorner === "ne") {
|
|
13848
|
+
const bottomEdge = resizeStartShellY + resizeStartHeight;
|
|
13849
|
+
width = clamp(resizeStartWidth + dx, minWidth, Math.max(minWidth, Math.min(maxWidth, window.innerWidth - x2)));
|
|
13850
|
+
const newHeight = clamp(resizeStartHeight - dy, minHeight, Math.min(maxHeight, bottomEdge));
|
|
13851
|
+
y2 = bottomEdge - newHeight;
|
|
13852
|
+
height = newHeight;
|
|
13853
|
+
} else if (resizeCorner === "nw") {
|
|
13854
|
+
const rightEdge = resizeStartShellX + resizeStartWidth;
|
|
13855
|
+
const bottomEdge = resizeStartShellY + resizeStartHeight;
|
|
13856
|
+
const newWidth = clamp(resizeStartWidth - dx, minWidth, Math.min(maxWidth, rightEdge));
|
|
13857
|
+
x2 = rightEdge - newWidth;
|
|
13858
|
+
width = newWidth;
|
|
13859
|
+
const newHeight = clamp(resizeStartHeight - dy, minHeight, Math.min(maxHeight, bottomEdge));
|
|
13860
|
+
y2 = bottomEdge - newHeight;
|
|
13861
|
+
height = newHeight;
|
|
13862
|
+
}
|
|
13697
13863
|
applyShellStyle();
|
|
13698
13864
|
notifyHostedResize();
|
|
13699
13865
|
}
|
|
@@ -13726,14 +13892,20 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
13726
13892
|
shellEl.style.border = "1px solid var(--pie-border-light, #d1d5db)";
|
|
13727
13893
|
shellEl.style.borderRadius = "12px";
|
|
13728
13894
|
shellEl.style.boxShadow = "0 10px 40px color-mix(in srgb, var(--pie-black, #000) 25%, transparent)";
|
|
13729
|
-
shellEl.style.overflow = "
|
|
13895
|
+
shellEl.style.overflow = "visible";
|
|
13730
13896
|
shellEl.style.display = currentArgs.active ? "flex" : "none";
|
|
13731
13897
|
shellEl.style.flexDirection = "column";
|
|
13898
|
+
shellEl.style.userSelect = "none";
|
|
13899
|
+
shellEl.style.webkitUserSelect = "none";
|
|
13900
|
+
shellEl.style.touchAction = "none";
|
|
13901
|
+
shellEl.setAttribute("draggable", "false");
|
|
13902
|
+
shellEl.addEventListener("dragstart", (e) => e.preventDefault());
|
|
13732
13903
|
headerEl = document.createElement("div");
|
|
13733
13904
|
headerEl.className = "pie-tool-shell__header";
|
|
13734
13905
|
headerEl.style.display = "flex";
|
|
13735
13906
|
headerEl.style.alignItems = "center";
|
|
13736
13907
|
headerEl.style.justifyContent = "space-between";
|
|
13908
|
+
headerEl.style.gap = "6px";
|
|
13737
13909
|
headerEl.style.padding = isCalculatorShell ? "12px 12px 12px 28px" : "10px 12px";
|
|
13738
13910
|
if (isCalculatorShell) {
|
|
13739
13911
|
headerEl.style.minHeight = "48px";
|
|
@@ -13748,6 +13920,13 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
13748
13920
|
}
|
|
13749
13921
|
headerEl.style.cursor = currentArgs.mounted.entry.shell.draggable === false ? "default" : "move";
|
|
13750
13922
|
headerEl.style.flex = "0 0 auto";
|
|
13923
|
+
headerEl.style.borderRadius = "12px 12px 0 0";
|
|
13924
|
+
headerEl.style.overflow = "hidden";
|
|
13925
|
+
headerEl.style.userSelect = "none";
|
|
13926
|
+
headerEl.style.webkitUserSelect = "none";
|
|
13927
|
+
headerEl.style.touchAction = "none";
|
|
13928
|
+
headerEl.setAttribute("draggable", "false");
|
|
13929
|
+
headerEl.addEventListener("dragstart", (e) => e.preventDefault());
|
|
13751
13930
|
titleEl = document.createElement("span");
|
|
13752
13931
|
titleEl.className = "pie-tool-shell__title";
|
|
13753
13932
|
titleEl.textContent = currentArgs.mounted.entry.shell.title || currentArgs.mounted.toolId;
|
|
@@ -13894,20 +14073,100 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
13894
14073
|
contentEl.style.width = "100%";
|
|
13895
14074
|
contentEl.style.flex = "1 1 auto";
|
|
13896
14075
|
contentEl.style.minHeight = "0";
|
|
14076
|
+
contentEl.style.overflow = "hidden";
|
|
14077
|
+
contentEl.style.borderRadius = "0 0 12px 12px";
|
|
14078
|
+
if (isCalculatorShell) {
|
|
14079
|
+
const makeFocusGuard = (label) => {
|
|
14080
|
+
const el = document.createElement("div");
|
|
14081
|
+
el.tabIndex = 0;
|
|
14082
|
+
el.setAttribute("aria-hidden", "true");
|
|
14083
|
+
el.setAttribute("data-pie-tool-shell-focus-guard", label);
|
|
14084
|
+
el.style.position = "absolute";
|
|
14085
|
+
el.style.width = "1px";
|
|
14086
|
+
el.style.height = "1px";
|
|
14087
|
+
el.style.padding = "0";
|
|
14088
|
+
el.style.margin = "-1px";
|
|
14089
|
+
el.style.overflow = "hidden";
|
|
14090
|
+
el.style.clipPath = "inset(50%)";
|
|
14091
|
+
el.style.whiteSpace = "nowrap";
|
|
14092
|
+
el.style.border = "0";
|
|
14093
|
+
return el;
|
|
14094
|
+
};
|
|
14095
|
+
startFocusGuardEl = makeFocusGuard("start");
|
|
14096
|
+
endFocusGuardEl = makeFocusGuard("end");
|
|
14097
|
+
startFocusGuardEl.addEventListener("focus", (event2) => {
|
|
14098
|
+
if (focusGuardRedirecting)
|
|
14099
|
+
return;
|
|
14100
|
+
focusGuardRedirecting = true;
|
|
14101
|
+
try {
|
|
14102
|
+
event2.stopPropagation();
|
|
14103
|
+
if (openerEl?.isConnected) {
|
|
14104
|
+
try {
|
|
14105
|
+
openerEl.focus();
|
|
14106
|
+
} catch {}
|
|
14107
|
+
}
|
|
14108
|
+
} finally {
|
|
14109
|
+
queueMicrotask(() => {
|
|
14110
|
+
focusGuardRedirecting = false;
|
|
14111
|
+
});
|
|
14112
|
+
}
|
|
14113
|
+
});
|
|
14114
|
+
endFocusGuardEl.addEventListener("focus", (event2) => {
|
|
14115
|
+
if (focusGuardRedirecting)
|
|
14116
|
+
return;
|
|
14117
|
+
focusGuardRedirecting = true;
|
|
14118
|
+
try {
|
|
14119
|
+
event2.stopPropagation();
|
|
14120
|
+
const next22 = findFirstQuestionFocusable();
|
|
14121
|
+
if (next22) {
|
|
14122
|
+
try {
|
|
14123
|
+
next22.focus();
|
|
14124
|
+
} catch {}
|
|
14125
|
+
}
|
|
14126
|
+
} finally {
|
|
14127
|
+
queueMicrotask(() => {
|
|
14128
|
+
focusGuardRedirecting = false;
|
|
14129
|
+
});
|
|
14130
|
+
}
|
|
14131
|
+
});
|
|
14132
|
+
shellEl.appendChild(startFocusGuardEl);
|
|
14133
|
+
}
|
|
13897
14134
|
shellEl.appendChild(headerEl);
|
|
13898
14135
|
shellEl.appendChild(contentEl);
|
|
14136
|
+
if (isCalculatorShell && endFocusGuardEl) {
|
|
14137
|
+
shellEl.appendChild(endFocusGuardEl);
|
|
14138
|
+
}
|
|
13899
14139
|
if (currentArgs.mounted.entry.shell.resizable !== false) {
|
|
13900
|
-
|
|
13901
|
-
|
|
13902
|
-
|
|
13903
|
-
|
|
13904
|
-
|
|
13905
|
-
|
|
13906
|
-
|
|
13907
|
-
|
|
13908
|
-
|
|
13909
|
-
|
|
13910
|
-
|
|
14140
|
+
const addResizeHandle = (corner, top, right, bottom, left, cursor, gradientAngle) => {
|
|
14141
|
+
const el = document.createElement("div");
|
|
14142
|
+
el.className = "pie-tool-shell__resize";
|
|
14143
|
+
el.style.position = "absolute";
|
|
14144
|
+
el.style.width = "24px";
|
|
14145
|
+
el.style.height = "24px";
|
|
14146
|
+
el.style.cursor = cursor;
|
|
14147
|
+
el.style.zIndex = "10";
|
|
14148
|
+
el.style.touchAction = "none";
|
|
14149
|
+
el.setAttribute("draggable", "false");
|
|
14150
|
+
if (top !== null)
|
|
14151
|
+
el.style.top = top;
|
|
14152
|
+
if (right !== null)
|
|
14153
|
+
el.style.right = right;
|
|
14154
|
+
if (bottom !== null)
|
|
14155
|
+
el.style.bottom = bottom;
|
|
14156
|
+
if (left !== null)
|
|
14157
|
+
el.style.left = left;
|
|
14158
|
+
if (gradientAngle !== null) {
|
|
14159
|
+
el.style.background = `linear-gradient(${gradientAngle}, transparent 0%, transparent 40%, rgba(0,0,0,0.35) 40%, rgba(0,0,0,0.35) 60%, transparent 60%, transparent 100%)`;
|
|
14160
|
+
}
|
|
14161
|
+
const handler = createResizePointerDownHandler(corner);
|
|
14162
|
+
el.addEventListener("pointerdown", handler);
|
|
14163
|
+
shellEl.appendChild(el);
|
|
14164
|
+
resizeHandleEls.push({ el, handler });
|
|
14165
|
+
};
|
|
14166
|
+
addResizeHandle("se", null, "0", "0", null, "nwse-resize", null);
|
|
14167
|
+
addResizeHandle("nw", "0", null, null, "0", "nwse-resize", "135deg");
|
|
14168
|
+
addResizeHandle("ne", "0", "0", null, null, "nesw-resize", null);
|
|
14169
|
+
addResizeHandle("sw", null, null, "0", "0", "nesw-resize", null);
|
|
13911
14170
|
}
|
|
13912
14171
|
headerEl.addEventListener("pointerdown", onHeaderPointerDown);
|
|
13913
14172
|
shellEl.addEventListener("pointermove", onShellPointerMove);
|
|
@@ -13948,10 +14207,15 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
13948
14207
|
if (focusTrapCleanup) {
|
|
13949
14208
|
removeFocusTrap();
|
|
13950
14209
|
}
|
|
13951
|
-
|
|
13952
|
-
|
|
13953
|
-
|
|
14210
|
+
if (calculatorBridgeAttached) {
|
|
14211
|
+
document.removeEventListener("keydown", onCalculatorBridgeKeydown, true);
|
|
14212
|
+
calculatorBridgeAttached = false;
|
|
14213
|
+
}
|
|
14214
|
+
for (const { el, handler } of resizeHandleEls) {
|
|
14215
|
+
el.removeEventListener("pointerdown", handler);
|
|
13954
14216
|
}
|
|
14217
|
+
openerEl = null;
|
|
14218
|
+
resizeHandleEls.length = 0;
|
|
13955
14219
|
if (headerEl) {
|
|
13956
14220
|
headerEl.removeEventListener("pointerdown", onHeaderPointerDown);
|
|
13957
14221
|
headerEl.onkeydown = null;
|
|
@@ -14158,9 +14422,9 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
14158
14422
|
append2($$anchor7, span_4);
|
|
14159
14423
|
};
|
|
14160
14424
|
var alternate_1 = ($$anchor7) => {
|
|
14161
|
-
var
|
|
14162
|
-
template_effect2(() => set_class2(
|
|
14163
|
-
append2($$anchor7,
|
|
14425
|
+
var i_1 = root_5();
|
|
14426
|
+
template_effect2(() => set_class2(i_1, 1, `icon icon-${get22(item2).icon}`, "svelte-rnx3ie"));
|
|
14427
|
+
append2($$anchor7, i_1);
|
|
14164
14428
|
};
|
|
14165
14429
|
if_block2(node_8, ($$render) => {
|
|
14166
14430
|
if (get22(fallbackIcon))
|
|
@@ -14250,9 +14514,9 @@ function ItemToolBar($$anchor, $$props) {
|
|
|
14250
14514
|
append2($$anchor7, span_7);
|
|
14251
14515
|
};
|
|
14252
14516
|
var alternate_3 = ($$anchor7) => {
|
|
14253
|
-
var
|
|
14254
|
-
template_effect2(() => set_class2(
|
|
14255
|
-
append2($$anchor7,
|
|
14517
|
+
var i_2 = root_5();
|
|
14518
|
+
template_effect2(() => set_class2(i_2, 1, `icon icon-${get22(item2).icon}`, "svelte-rnx3ie"));
|
|
14519
|
+
append2($$anchor7, i_2);
|
|
14256
14520
|
};
|
|
14257
14521
|
if_block2(node_11, ($$render) => {
|
|
14258
14522
|
if (get22(fallbackIcon))
|
|
@@ -87,6 +87,11 @@ export interface ToolWindowShellAction {
|
|
|
87
87
|
iconSvg?: string;
|
|
88
88
|
onClick: () => void;
|
|
89
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Alignment corner for the initial shell position.
|
|
92
|
+
* The shell is offset by `initialMargin` (default 16 px) from the chosen corner.
|
|
93
|
+
*/
|
|
94
|
+
export type ToolWindowShellAlign = 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
90
95
|
export interface ToolWindowShellConfig {
|
|
91
96
|
title?: string;
|
|
92
97
|
draggable?: boolean;
|
|
@@ -98,6 +103,10 @@ export interface ToolWindowShellConfig {
|
|
|
98
103
|
minHeight?: number;
|
|
99
104
|
maxWidth?: number;
|
|
100
105
|
maxHeight?: number;
|
|
106
|
+
/** Initial placement of the shell. Defaults to `'center'`. */
|
|
107
|
+
initialAlign?: ToolWindowShellAlign;
|
|
108
|
+
/** Distance (px) from the viewport edge when using a corner align. Defaults to 16. */
|
|
109
|
+
initialMargin?: number;
|
|
101
110
|
actions?: ToolWindowShellAction[];
|
|
102
111
|
}
|
|
103
112
|
export interface HostedToolContext {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToolRegistry.js","sourceRoot":"","sources":["../../src/services/ToolRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Tool Registry\n *\n * Central registry for all assessment tools. Manages tool metadata, visibility logic,\n * and button/instance creation. Supports dynamic registration and override by integrators.\n */\n\nimport type { ToolContext, ToolLevel } from \"./tool-context.js\";\nimport type { ToolComponentOverrides } from \"../tools/tool-tag-map.js\";\nimport type {\n\tElementToolStateStoreApi,\n\tToolCoordinatorApi,\n\tToolkitCoordinatorApi,\n\tTtsServiceApi,\n} from \"./interfaces.js\";\nimport type { ToolProviderApi } from \"./tool-providers/ToolProviderApi.js\";\nimport type { ToolProviderConfig as ToolRuntimeConfig } from \"./tools-config-normalizer.js\";\nimport type { ToolConfigDiagnostic } from \"./tool-config-validation.js\";\nimport { normalizeToolAlias } from \"./tools-config-normalizer.js\";\n\nexport type ToolModuleLoader = () => Promise<unknown>;\n\nexport interface ToolToolbarButtonDefinition {\n\ttoolId: string;\n\tlabel: string;\n\ticon: string;\n\tariaLabel: string;\n\ttooltip?: string;\n\tonClick: () => void;\n\tclassName?: string;\n\tdisabled?: boolean;\n\tactive?: boolean;\n}\n\nexport interface ToolbarContext {\n\tscope: {\n\t\tlevel: ToolLevel;\n\t\tscopeId: string;\n\t\tassessmentId?: string;\n\t\tsectionId?: string;\n\t\titemId?: string;\n\t\tcanonicalItemId?: string;\n\t\tcontentKind?: string;\n\t};\n\titemId: string;\n\tcatalogId: string;\n\tlanguage: string;\n\tui?: {\n\t\tsize?: string;\n\t};\n\tgetScopeElement?: () => HTMLElement | null;\n\tgetGlobalElementId?: () => string | null;\n\ttoolCoordinator: ToolCoordinatorApi | null;\n\ttoolkitCoordinator: ToolkitCoordinatorApi | null;\n\tttsService: TtsServiceApi | null;\n\telementToolStateStore: ElementToolStateStoreApi | null;\n\ttoggleTool: (toolId: string) => void;\n\tisToolVisible: (toolId: string) => boolean;\n\tsubscribeVisibility: ((listener: () => void) => () => void) | null;\n\tcomponentOverrides?: ToolComponentOverrides;\n\tgetResolvedToolContext?: (toolId: string) => ResolvedToolContext | null;\n\tgetToolRenderParams?: (toolId: string) => Record<string, unknown> | null;\n}\n\nexport interface ToolContextResolverContext {\n\ttoolId: string;\n\tcontext: ToolContext;\n\ttoolbarContext: ToolbarContext;\n}\n\nexport interface ToolContextResolverResult {\n\tvisible?: boolean;\n\tparams?: Record<string, unknown>;\n\treason?: string;\n}\n\nexport type ToolContextResolver = (\n\tcontext: ToolContextResolverContext,\n) => ToolContextResolverResult | null | undefined;\n\nexport type ToolContextResolverMap = Record<\n\tstring,\n\tToolContextResolver | null | undefined\n>;\n\nexport interface ResolvedToolContext {\n\ttoolId: string;\n\tvisible: boolean;\n\tparams: Record<string, unknown>;\n\treason?: string;\n}\n\nexport interface ToolRenderElement {\n\telement: HTMLElement | null;\n\tmount: \"before-buttons\" | \"after-buttons\" | \"controls-row\";\n\tlayoutHints?: {\n\t\tcontrolsRow?: {\n\t\t\treserveSpace?: boolean;\n\t\t\tshowWhenToolActive?: boolean;\n\t\t};\n\t};\n\tshell?: ToolWindowShellConfig;\n}\n\nexport interface ToolWindowShellAction {\n\tid: string;\n\tlabel: string;\n\tariaLabel?: string;\n\ticonSvg?: string;\n\tonClick: () => void;\n}\n\nexport interface ToolWindowShellConfig {\n\ttitle?: string;\n\tdraggable?: boolean;\n\tresizable?: boolean;\n\tcloseable?: boolean;\n\tinitialWidth?: number;\n\tinitialHeight?: number;\n\tminWidth?: number;\n\tminHeight?: number;\n\tmaxWidth?: number;\n\tmaxHeight?: number;\n\tactions?: ToolWindowShellAction[];\n}\n\nexport interface HostedToolContext {\n\ttoolId: string;\n\ttoolbarContext: ToolbarContext;\n\tshellConfig: ToolWindowShellConfig;\n}\n\nexport interface HostedToolSize {\n\twidth: number;\n\theight: number;\n}\n\nexport interface ToolProviderDescriptor {\n\tgetProviderId?: (config: ToolRuntimeConfig | undefined) => string;\n\tcreateProvider: (config: ToolRuntimeConfig | undefined) => ToolProviderApi;\n\tgetInitConfig?: (\n\t\tconfig: ToolRuntimeConfig | undefined,\n\t) => Record<string, unknown>;\n\tsanitizeConfig?: (config: ToolRuntimeConfig) => ToolRuntimeConfig;\n\tvalidateConfig?: (config: ToolRuntimeConfig) => ToolConfigDiagnostic[];\n\tgetAuthFetcher?: (\n\t\tconfig: ToolRuntimeConfig | undefined,\n\t) => (() => Promise<Record<string, unknown>>) | undefined;\n\tlazy?: boolean;\n}\n\nexport interface ToolToolbarRenderResult {\n\ttoolId: string;\n\telements?: ToolRenderElement[];\n\tbutton?: ToolToolbarButtonDefinition | null;\n\tsync?: () => void;\n\tsubscribeActive?: (callback: (active: boolean) => void) => () => void;\n}\n\nexport type ToolActivation = \"toolbar-toggle\" | \"selection-gateway\";\nexport type ToolSingletonScope = \"section\";\n\n/**\n * Tool registration interface\n */\nexport interface ToolRegistration {\n\t/** Unique tool identifier (e.g., 'calculator', 'textToSpeech') */\n\ttoolId: string;\n\n\t/** Human-readable name */\n\tname: string;\n\n\t/** Description of what the tool does */\n\tdescription: string;\n\n\t/** Icon identifier or SVG string */\n\ticon: string | ((context: ToolContext) => string);\n\n\t/** Which levels this tool supports */\n\tsupportedLevels: ToolLevel[];\n\n\t/**\n\t * Activation model for this tool.\n\t * - toolbar-toggle: rendered as a toolbar button (default)\n\t * - selection-gateway: rendered as a singleton selection-driven gateway\n\t */\n\tactivation?: ToolActivation;\n\n\t/**\n\t * Optional singleton scope for activation models that mount exactly one instance.\n\t */\n\tsingletonScope?: ToolSingletonScope;\n\n\t/**\n\t * PNP support IDs that enable this tool (optional)\n\t * Used by the tool policy engine to determine if a PNP support enables this tool.\n\t * Example: ['calculator', 'basic-calculator', 'scientific-calculator']\n\t */\n\tpnpSupportIds?: string[];\n\t/**\n\t * Optional provider registration metadata.\n\t * When present, ToolkitCoordinator can register provider(s) generically\n\t * without hardcoded tool-specific branches.\n\t */\n\tprovider?: ToolProviderDescriptor;\n\t/**\n\t * Optional shell-host lifecycle hooks for hosted (floating) tools.\n\t */\n\tonHostedMount?: (\n\t\telement: HTMLElement,\n\t\tcontext: HostedToolContext,\n\t) => void | Promise<void>;\n\tonHostedResize?: (\n\t\tsize: HostedToolSize,\n\t\telement: HTMLElement,\n\t\tcontext: HostedToolContext,\n\t) => void | Promise<void>;\n\tonHostedUnmount?: (\n\t\telement: HTMLElement,\n\t\tcontext: HostedToolContext,\n\t) => void | Promise<void>;\n\n\t/**\n\t * Pass 2: Tool decides if it's relevant in this context\n\t * Called ONLY if orchestrator has already allowed the tool (Pass 1)\n\t *\n\t * @param context - Rich context about where tool is being evaluated\n\t * @returns true if tool should be visible, false to hide\n\t */\n\tisVisibleInContext(context: ToolContext): boolean;\n\n\t/** Required toolbar-first render contract. */\n\trenderToolbar(\n\t\tcontext: ToolContext,\n\t\ttoolbarContext: ToolbarContext,\n\t): ToolToolbarRenderResult | null;\n}\n\nconst VALID_TOOL_LEVELS: ToolLevel[] = [\n\t\"assessment\",\n\t\"section\",\n\t\"item\",\n\t\"passage\",\n\t\"rubric\",\n\t\"element\",\n];\n\nfunction assertNonEmptyString(\n\tvalue: unknown,\n\tfieldName: string,\n): asserts value is string {\n\tif (typeof value !== \"string\" || value.trim().length === 0) {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration: \"${fieldName}\" must be a non-empty string.`,\n\t\t);\n\t}\n}\n\n// Defence-in-depth: reject obvious XSS payloads in tool-registered icon\n// markup at registration time. Runtime rendering still runs each icon\n// through DOMPurify (see `ToolIcon.svelte`), but surfacing the problem\n// early produces a clearer error for tool authors than \"the icon silently\n// disappeared after sanitization\".\nconst SCRIPTABLE_ICON_PATTERNS: Array<{ pattern: RegExp; reason: string }> = [\n\t{ pattern: /<script\\b/i, reason: \"contains a <script> tag\" },\n\t{\n\t\tpattern: /\\son[a-z]+\\s*=/i,\n\t\treason: \"contains an inline event handler (on*=) attribute\",\n\t},\n\t{ pattern: /javascript:/i, reason: \"contains a javascript: URL\" },\n\t{\n\t\tpattern: /<foreignObject\\b/i,\n\t\treason: \"contains a <foreignObject> element\",\n\t},\n];\n\nfunction assertIconStringIsSafe(\n\ttoolId: string,\n\ticon: string,\n\tfieldName: string,\n): void {\n\tconst trimmed = icon.trimStart();\n\tconst looksLikeSvg = trimmed.toLowerCase().startsWith(\"<svg\");\n\tconst looksLikeUrl = /^https?:/i.test(trimmed);\n\tconst looksLikeDataUrl = /^data:/i.test(trimmed);\n\tif (looksLikeDataUrl) {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${toolId}\": \"${fieldName}\" may not be a data: URL.`,\n\t\t);\n\t}\n\tif (!looksLikeSvg && !looksLikeUrl) return;\n\tfor (const { pattern, reason } of SCRIPTABLE_ICON_PATTERNS) {\n\t\tif (pattern.test(icon)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid tool registration \"${toolId}\": \"${fieldName}\" ${reason}. Inline SVG icons must not include scriptable content.`,\n\t\t\t);\n\t\t}\n\t}\n}\n\nfunction assertToolRegistrationShape(registration: ToolRegistration): void {\n\tassertNonEmptyString(registration.toolId, \"toolId\");\n\tassertNonEmptyString(registration.name, \"name\");\n\tassertNonEmptyString(registration.description, \"description\");\n\n\tif (\n\t\ttypeof registration.icon !== \"string\" &&\n\t\ttypeof registration.icon !== \"function\"\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${registration.toolId}\": \"icon\" must be a string or function.`,\n\t\t);\n\t}\n\tif (typeof registration.icon === \"string\") {\n\t\tassertIconStringIsSafe(registration.toolId, registration.icon, \"icon\");\n\t}\n\tif (\n\t\t!Array.isArray(registration.supportedLevels) ||\n\t\tregistration.supportedLevels.length === 0\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${registration.toolId}\": \"supportedLevels\" must be a non-empty array.`,\n\t\t);\n\t}\n\tconst invalidLevel = registration.supportedLevels.find(\n\t\t(level) => !VALID_TOOL_LEVELS.includes(level),\n\t);\n\tif (invalidLevel) {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${registration.toolId}\": unsupported level \"${invalidLevel}\".`,\n\t\t);\n\t}\n\tif (\n\t\tregistration.activation !== undefined &&\n\t\tregistration.activation !== \"toolbar-toggle\" &&\n\t\tregistration.activation !== \"selection-gateway\"\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${registration.toolId}\": unsupported activation \"${String(registration.activation)}\".`,\n\t\t);\n\t}\n\tif (\n\t\tregistration.singletonScope !== undefined &&\n\t\tregistration.singletonScope !== \"section\"\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${registration.toolId}\": unsupported singletonScope \"${String(registration.singletonScope)}\".`,\n\t\t);\n\t}\n\tif (\n\t\tregistration.activation === \"selection-gateway\" &&\n\t\tregistration.singletonScope !== \"section\"\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${registration.toolId}\": selection-gateway tools must declare singletonScope \"section\".`,\n\t\t);\n\t}\n\tif (\n\t\tregistration.pnpSupportIds !== undefined &&\n\t\t(!Array.isArray(registration.pnpSupportIds) ||\n\t\t\tregistration.pnpSupportIds.some(\n\t\t\t\t(pnpId) => typeof pnpId !== \"string\" || pnpId.trim().length === 0,\n\t\t\t))\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${registration.toolId}\": \"pnpSupportIds\" must be an array of non-empty strings.`,\n\t\t);\n\t}\n\tif (typeof registration.isVisibleInContext !== \"function\") {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${registration.toolId}\": \"isVisibleInContext\" must be a function.`,\n\t\t);\n\t}\n\tif (typeof registration.renderToolbar !== \"function\") {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${registration.toolId}\": \"renderToolbar\" must be a function.`,\n\t\t);\n\t}\n}\n\n/**\n * Tool Registry\n *\n * Manages tool registrations and provides query/lookup functionality\n */\nexport class ToolRegistry {\n\tprivate tools = new Map<string, ToolRegistration>();\n\tprivate pnpIndex = new Map<string, Set<string>>(); // pnpSupportId → Set<toolId>\n\tprivate componentOverrides: ToolComponentOverrides = {};\n\tprivate moduleLoaders = new Map<string, ToolModuleLoader>();\n\tprivate loadedToolModules = new Set<string>();\n\tprivate moduleLoadPromises = new Map<string, Promise<void>>();\n\n\t/**\n\t * Normalize a single tool alias to canonical toolId.\n\t */\n\tnormalizeToolId(toolId: string): string {\n\t\treturn normalizeToolAlias(toolId);\n\t}\n\n\t/**\n\t * Normalize a list of tool aliases to canonical toolIds.\n\t */\n\tnormalizeToolIds(toolIds: string[]): string[] {\n\t\treturn toolIds.map((toolId) => this.normalizeToolId(toolId));\n\t}\n\n\t/**\n\t * Register a tool\n\t *\n\t * @param registration - Tool registration\n\t * @throws Error if toolId is already registered\n\t */\n\tregister(registration: ToolRegistration): void {\n\t\tassertToolRegistrationShape(registration);\n\t\tif (this.tools.has(registration.toolId)) {\n\t\t\tthrow new Error(`Tool '${registration.toolId}' is already registered`);\n\t\t}\n\n\t\tthis.tools.set(registration.toolId, registration);\n\n\t\t// Index PNP support IDs\n\t\tif (registration.pnpSupportIds) {\n\t\t\tfor (const pnpId of registration.pnpSupportIds) {\n\t\t\t\tif (!this.pnpIndex.has(pnpId)) {\n\t\t\t\t\tthis.pnpIndex.set(pnpId, new Set());\n\t\t\t\t}\n\t\t\t\tthis.pnpIndex.get(pnpId)!.add(registration.toolId);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Override an existing tool registration\n\t *\n\t * @param registration - New tool registration (must have existing toolId)\n\t */\n\toverride(registration: ToolRegistration): void {\n\t\tassertToolRegistrationShape(registration);\n\t\tif (!this.tools.has(registration.toolId)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Cannot override non-existent tool '${registration.toolId}'`,\n\t\t\t);\n\t\t}\n\n\t\t// Remove old PNP index entries\n\t\tconst oldReg = this.tools.get(registration.toolId)!;\n\t\tif (oldReg.pnpSupportIds) {\n\t\t\tfor (const pnpId of oldReg.pnpSupportIds) {\n\t\t\t\tthis.pnpIndex.get(pnpId)?.delete(registration.toolId);\n\t\t\t}\n\t\t}\n\n\t\t// Add new registration\n\t\tthis.tools.set(registration.toolId, registration);\n\n\t\t// Re-index PNP support IDs\n\t\tif (registration.pnpSupportIds) {\n\t\t\tfor (const pnpId of registration.pnpSupportIds) {\n\t\t\t\tif (!this.pnpIndex.has(pnpId)) {\n\t\t\t\t\tthis.pnpIndex.set(pnpId, new Set());\n\t\t\t\t}\n\t\t\t\tthis.pnpIndex.get(pnpId)!.add(registration.toolId);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Unregister a tool\n\t *\n\t * @param toolId - Tool ID to remove\n\t */\n\tunregister(toolId: string): void {\n\t\tconst reg = this.tools.get(toolId);\n\t\tif (!reg) return;\n\n\t\t// Remove PNP index entries\n\t\tif (reg.pnpSupportIds) {\n\t\t\tfor (const pnpId of reg.pnpSupportIds) {\n\t\t\t\tthis.pnpIndex.get(pnpId)?.delete(toolId);\n\t\t\t}\n\t\t}\n\n\t\tthis.tools.delete(toolId);\n\t}\n\n\t/**\n\t * Get a tool registration by ID\n\t *\n\t * @param toolId - Tool ID\n\t * @returns Tool registration or undefined\n\t */\n\tget(toolId: string): ToolRegistration | undefined {\n\t\treturn this.tools.get(toolId);\n\t}\n\n\t/**\n\t * Check if a tool is registered\n\t *\n\t * @param toolId - Tool ID\n\t * @returns true if registered\n\t */\n\thas(toolId: string): boolean {\n\t\treturn this.tools.has(toolId);\n\t}\n\n\t/**\n\t * Get all registered tool IDs\n\t *\n\t * @returns Array of tool IDs\n\t */\n\tgetAllToolIds(): string[] {\n\t\treturn Array.from(this.tools.keys());\n\t}\n\n\t/**\n\t * Get all tool registrations\n\t *\n\t * @returns Array of tool registrations\n\t */\n\tgetAllTools(): ToolRegistration[] {\n\t\treturn Array.from(this.tools.values());\n\t}\n\n\t/**\n\t * Find tool IDs that support a given PNP support ID\n\t *\n\t * @param pnpSupportId - PNP support ID (e.g., 'calculator')\n\t * @returns Set of tool IDs that support this PNP ID\n\t */\n\tgetToolsByPNPSupport(pnpSupportId: string): Set<string> {\n\t\treturn this.pnpIndex.get(pnpSupportId) || new Set();\n\t}\n\n\t/**\n\t * Get tools that support a specific level\n\t *\n\t * @param level - Tool level (assessment, section, item, passage, element)\n\t * @returns Array of tool registrations that support this level\n\t */\n\tgetToolsByLevel(level: ToolLevel): ToolRegistration[] {\n\t\treturn this.getAllTools().filter((tool) =>\n\t\t\ttool.supportedLevels.includes(level),\n\t\t);\n\t}\n\n\t/**\n\t * Resolve tool activation, defaulting to toolbar-toggle.\n\t */\n\tgetToolActivation(toolId: string): ToolActivation {\n\t\treturn this.get(toolId)?.activation || \"toolbar-toggle\";\n\t}\n\n\t/**\n\t * Resolve singleton scope for a tool when present.\n\t */\n\tgetToolSingletonScope(toolId: string): ToolSingletonScope | null {\n\t\treturn this.get(toolId)?.singletonScope || null;\n\t}\n\n\t/**\n\t * Filter tool IDs by activation type.\n\t */\n\tfilterToolIdsByActivation(\n\t\ttoolIds: string[],\n\t\tactivation: ToolActivation,\n\t): string[] {\n\t\treturn toolIds.filter(\n\t\t\t(toolId) => this.getToolActivation(toolId) === activation,\n\t\t);\n\t}\n\n\t/**\n\t * Filter tools by visibility in a given context\n\t *\n\t * Pass 2 of the two-pass model: Given a list of allowed tool IDs (from Pass 1),\n\t * ask each tool if it's relevant in this context.\n\t *\n\t * @param allowedToolIds - Tool IDs that passed Pass 1 (orchestrator approval)\n\t * @param context - Context to evaluate\n\t * @returns Array of visible tool registrations\n\t */\n\tfilterVisibleInContext(\n\t\tallowedToolIds: string[],\n\t\tcontext: ToolContext,\n\t): ToolRegistration[] {\n\t\tconst visible: ToolRegistration[] = [];\n\n\t\tfor (const toolId of allowedToolIds) {\n\t\t\tconst tool = this.get(toolId);\n\t\t\tif (!tool) {\n\t\t\t\tconsole.warn(`Tool '${toolId}' is allowed but not registered`);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Check if tool supports this level\n\t\t\tif (!tool.supportedLevels.includes(context.level)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Pass 2: Ask tool if it's relevant\n\t\t\ttry {\n\t\t\t\tif (tool.isVisibleInContext(context)) {\n\t\t\t\t\tvisible.push(tool);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`Error evaluating visibility for tool '${toolId}':`,\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn visible;\n\t}\n\n\t/**\n\t * Get tool metadata for building UIs\n\t * Useful for building PNP configuration interfaces\n\t *\n\t * @returns Array of tool metadata (id, name, description, pnpSupportIds)\n\t */\n\tgetToolMetadata(): Array<{\n\t\ttoolId: string;\n\t\tname: string;\n\t\tdescription: string;\n\t\tpnpSupportIds: string[];\n\t\tsupportedLevels: ToolLevel[];\n\t\tactivation: ToolActivation;\n\t\tsingletonScope: ToolSingletonScope | null;\n\t}> {\n\t\treturn this.getAllTools().map((tool) => ({\n\t\t\ttoolId: tool.toolId,\n\t\t\tname: tool.name,\n\t\t\tdescription: tool.description,\n\t\t\tpnpSupportIds: tool.pnpSupportIds || [],\n\t\t\tsupportedLevels: tool.supportedLevels,\n\t\t\tactivation: tool.activation || \"toolbar-toggle\",\n\t\t\tsingletonScope: tool.singletonScope || null,\n\t\t}));\n\t}\n\n\t/**\n\t * Generate PNP support IDs from enabled tools\n\t * Useful for creating PNP profiles\n\t *\n\t * @param enabledToolIds - Tool IDs to enable\n\t * @returns Array of unique PNP support IDs\n\t */\n\tgeneratePNPSupportsFromTools(enabledToolIds: string[]): string[] {\n\t\tconst pnpSupports = new Set<string>();\n\n\t\tfor (const toolId of enabledToolIds) {\n\t\t\tconst tool = this.get(toolId);\n\t\t\tif (tool?.pnpSupportIds) {\n\t\t\t\tfor (const pnpId of tool.pnpSupportIds) {\n\t\t\t\t\tpnpSupports.add(pnpId);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn Array.from(pnpSupports);\n\t}\n\n\t/**\n\t * Clear all registrations (useful for testing)\n\t */\n\tclear(): void {\n\t\tthis.tools.clear();\n\t\tthis.pnpIndex.clear();\n\t}\n\n\t/**\n\t * Configure global component overrides used by tool instance creation.\n\t */\n\tsetComponentOverrides(overrides: ToolComponentOverrides): void {\n\t\tthis.componentOverrides = overrides;\n\t}\n\n\t/**\n\t * Register lazy module loaders by toolId.\n\t * Toolbars call ensureToolModuleLoaded(toolId) before instance creation.\n\t */\n\tsetToolModuleLoaders(\n\t\tloaders: Partial<Record<string, ToolModuleLoader>>,\n\t): void {\n\t\tfor (const [toolId, loader] of Object.entries(loaders)) {\n\t\t\tif (!loader) continue;\n\t\t\tassertNonEmptyString(toolId, \"tool module loader id\");\n\t\t\tif (typeof loader !== \"function\") {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Invalid tool module loader for \"${toolId}\": expected a function.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.moduleLoaders.set(toolId, loader);\n\t\t}\n\t}\n\n\t/**\n\t * Ensure tool module side-effects are loaded exactly once.\n\t * Safe to call repeatedly; concurrent callers share the same promise.\n\t */\n\tasync ensureToolModuleLoaded(toolId: string): Promise<void> {\n\t\tif (this.loadedToolModules.has(toolId)) return;\n\n\t\tconst existingPromise = this.moduleLoadPromises.get(toolId);\n\t\tif (existingPromise) {\n\t\t\tawait existingPromise;\n\t\t\treturn;\n\t\t}\n\n\t\tconst loader = this.moduleLoaders.get(toolId);\n\t\tif (!loader) return;\n\n\t\tconst loadPromise = (async () => {\n\t\t\tawait loader();\n\t\t\tthis.loadedToolModules.add(toolId);\n\t\t})();\n\n\t\tthis.moduleLoadPromises.set(toolId, loadPromise);\n\t\ttry {\n\t\t\tawait loadPromise;\n\t\t} finally {\n\t\t\tthis.moduleLoadPromises.delete(toolId);\n\t\t}\n\t}\n\n\t/**\n\t * Ensure a set of tool modules are loaded.\n\t */\n\tasync ensureToolModulesLoaded(toolIds: string[]): Promise<void> {\n\t\tawait Promise.all(\n\t\t\ttoolIds.map((toolId) => this.ensureToolModuleLoaded(toolId)),\n\t\t);\n\t}\n\n\t/**\n\t * Whether a tool module has already been loaded.\n\t */\n\tisToolModuleLoaded(toolId: string): boolean {\n\t\treturn this.loadedToolModules.has(toolId);\n\t}\n\n\t/**\n\t * Render a tool for toolbar use with component overrides attached.\n\t */\n\trenderForToolbar(\n\t\ttoolId: string,\n\t\tcontext: ToolContext,\n\t\ttoolbarContext: ToolbarContext,\n\t): ToolToolbarRenderResult | null {\n\t\tconst tool = this.get(toolId);\n\t\tif (!tool) {\n\t\t\tthrow new Error(`Tool '${toolId}' is not registered`);\n\t\t}\n\n\t\tconst mergedContext: ToolbarContext = {\n\t\t\t...toolbarContext,\n\t\t\tcomponentOverrides: {\n\t\t\t\t...(this.componentOverrides || {}),\n\t\t\t\t...(toolbarContext.componentOverrides || {}),\n\t\t\t},\n\t\t};\n\n\t\treturn tool.renderToolbar(context, mergedContext);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ToolRegistry.js","sourceRoot":"","sources":["../../src/services/ToolRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Tool Registry\n *\n * Central registry for all assessment tools. Manages tool metadata, visibility logic,\n * and button/instance creation. Supports dynamic registration and override by integrators.\n */\n\nimport type { ToolContext, ToolLevel } from \"./tool-context.js\";\nimport type { ToolComponentOverrides } from \"../tools/tool-tag-map.js\";\nimport type {\n\tElementToolStateStoreApi,\n\tToolCoordinatorApi,\n\tToolkitCoordinatorApi,\n\tTtsServiceApi,\n} from \"./interfaces.js\";\nimport type { ToolProviderApi } from \"./tool-providers/ToolProviderApi.js\";\nimport type { ToolProviderConfig as ToolRuntimeConfig } from \"./tools-config-normalizer.js\";\nimport type { ToolConfigDiagnostic } from \"./tool-config-validation.js\";\nimport { normalizeToolAlias } from \"./tools-config-normalizer.js\";\n\nexport type ToolModuleLoader = () => Promise<unknown>;\n\nexport interface ToolToolbarButtonDefinition {\n\ttoolId: string;\n\tlabel: string;\n\ticon: string;\n\tariaLabel: string;\n\ttooltip?: string;\n\tonClick: () => void;\n\tclassName?: string;\n\tdisabled?: boolean;\n\tactive?: boolean;\n}\n\nexport interface ToolbarContext {\n\tscope: {\n\t\tlevel: ToolLevel;\n\t\tscopeId: string;\n\t\tassessmentId?: string;\n\t\tsectionId?: string;\n\t\titemId?: string;\n\t\tcanonicalItemId?: string;\n\t\tcontentKind?: string;\n\t};\n\titemId: string;\n\tcatalogId: string;\n\tlanguage: string;\n\tui?: {\n\t\tsize?: string;\n\t};\n\tgetScopeElement?: () => HTMLElement | null;\n\tgetGlobalElementId?: () => string | null;\n\ttoolCoordinator: ToolCoordinatorApi | null;\n\ttoolkitCoordinator: ToolkitCoordinatorApi | null;\n\tttsService: TtsServiceApi | null;\n\telementToolStateStore: ElementToolStateStoreApi | null;\n\ttoggleTool: (toolId: string) => void;\n\tisToolVisible: (toolId: string) => boolean;\n\tsubscribeVisibility: ((listener: () => void) => () => void) | null;\n\tcomponentOverrides?: ToolComponentOverrides;\n\tgetResolvedToolContext?: (toolId: string) => ResolvedToolContext | null;\n\tgetToolRenderParams?: (toolId: string) => Record<string, unknown> | null;\n}\n\nexport interface ToolContextResolverContext {\n\ttoolId: string;\n\tcontext: ToolContext;\n\ttoolbarContext: ToolbarContext;\n}\n\nexport interface ToolContextResolverResult {\n\tvisible?: boolean;\n\tparams?: Record<string, unknown>;\n\treason?: string;\n}\n\nexport type ToolContextResolver = (\n\tcontext: ToolContextResolverContext,\n) => ToolContextResolverResult | null | undefined;\n\nexport type ToolContextResolverMap = Record<\n\tstring,\n\tToolContextResolver | null | undefined\n>;\n\nexport interface ResolvedToolContext {\n\ttoolId: string;\n\tvisible: boolean;\n\tparams: Record<string, unknown>;\n\treason?: string;\n}\n\nexport interface ToolRenderElement {\n\telement: HTMLElement | null;\n\tmount: \"before-buttons\" | \"after-buttons\" | \"controls-row\";\n\tlayoutHints?: {\n\t\tcontrolsRow?: {\n\t\t\treserveSpace?: boolean;\n\t\t\tshowWhenToolActive?: boolean;\n\t\t};\n\t};\n\tshell?: ToolWindowShellConfig;\n}\n\nexport interface ToolWindowShellAction {\n\tid: string;\n\tlabel: string;\n\tariaLabel?: string;\n\ticonSvg?: string;\n\tonClick: () => void;\n}\n\n/**\n * Alignment corner for the initial shell position.\n * The shell is offset by `initialMargin` (default 16 px) from the chosen corner.\n */\nexport type ToolWindowShellAlign =\n\t| 'center'\n\t| 'top-left'\n\t| 'top-right'\n\t| 'bottom-left'\n\t| 'bottom-right';\n\nexport interface ToolWindowShellConfig {\n\ttitle?: string;\n\tdraggable?: boolean;\n\tresizable?: boolean;\n\tcloseable?: boolean;\n\tinitialWidth?: number;\n\tinitialHeight?: number;\n\tminWidth?: number;\n\tminHeight?: number;\n\tmaxWidth?: number;\n\tmaxHeight?: number;\n\t/** Initial placement of the shell. Defaults to `'center'`. */\n\tinitialAlign?: ToolWindowShellAlign;\n\t/** Distance (px) from the viewport edge when using a corner align. Defaults to 16. */\n\tinitialMargin?: number;\n\tactions?: ToolWindowShellAction[];\n}\n\nexport interface HostedToolContext {\n\ttoolId: string;\n\ttoolbarContext: ToolbarContext;\n\tshellConfig: ToolWindowShellConfig;\n}\n\nexport interface HostedToolSize {\n\twidth: number;\n\theight: number;\n}\n\nexport interface ToolProviderDescriptor {\n\tgetProviderId?: (config: ToolRuntimeConfig | undefined) => string;\n\tcreateProvider: (config: ToolRuntimeConfig | undefined) => ToolProviderApi;\n\tgetInitConfig?: (\n\t\tconfig: ToolRuntimeConfig | undefined,\n\t) => Record<string, unknown>;\n\tsanitizeConfig?: (config: ToolRuntimeConfig) => ToolRuntimeConfig;\n\tvalidateConfig?: (config: ToolRuntimeConfig) => ToolConfigDiagnostic[];\n\tgetAuthFetcher?: (\n\t\tconfig: ToolRuntimeConfig | undefined,\n\t) => (() => Promise<Record<string, unknown>>) | undefined;\n\tlazy?: boolean;\n}\n\nexport interface ToolToolbarRenderResult {\n\ttoolId: string;\n\telements?: ToolRenderElement[];\n\tbutton?: ToolToolbarButtonDefinition | null;\n\tsync?: () => void;\n\tsubscribeActive?: (callback: (active: boolean) => void) => () => void;\n}\n\nexport type ToolActivation = \"toolbar-toggle\" | \"selection-gateway\";\nexport type ToolSingletonScope = \"section\";\n\n/**\n * Tool registration interface\n */\nexport interface ToolRegistration {\n\t/** Unique tool identifier (e.g., 'calculator', 'textToSpeech') */\n\ttoolId: string;\n\n\t/** Human-readable name */\n\tname: string;\n\n\t/** Description of what the tool does */\n\tdescription: string;\n\n\t/** Icon identifier or SVG string */\n\ticon: string | ((context: ToolContext) => string);\n\n\t/** Which levels this tool supports */\n\tsupportedLevels: ToolLevel[];\n\n\t/**\n\t * Activation model for this tool.\n\t * - toolbar-toggle: rendered as a toolbar button (default)\n\t * - selection-gateway: rendered as a singleton selection-driven gateway\n\t */\n\tactivation?: ToolActivation;\n\n\t/**\n\t * Optional singleton scope for activation models that mount exactly one instance.\n\t */\n\tsingletonScope?: ToolSingletonScope;\n\n\t/**\n\t * PNP support IDs that enable this tool (optional)\n\t * Used by the tool policy engine to determine if a PNP support enables this tool.\n\t * Example: ['calculator', 'basic-calculator', 'scientific-calculator']\n\t */\n\tpnpSupportIds?: string[];\n\t/**\n\t * Optional provider registration metadata.\n\t * When present, ToolkitCoordinator can register provider(s) generically\n\t * without hardcoded tool-specific branches.\n\t */\n\tprovider?: ToolProviderDescriptor;\n\t/**\n\t * Optional shell-host lifecycle hooks for hosted (floating) tools.\n\t */\n\tonHostedMount?: (\n\t\telement: HTMLElement,\n\t\tcontext: HostedToolContext,\n\t) => void | Promise<void>;\n\tonHostedResize?: (\n\t\tsize: HostedToolSize,\n\t\telement: HTMLElement,\n\t\tcontext: HostedToolContext,\n\t) => void | Promise<void>;\n\tonHostedUnmount?: (\n\t\telement: HTMLElement,\n\t\tcontext: HostedToolContext,\n\t) => void | Promise<void>;\n\n\t/**\n\t * Pass 2: Tool decides if it's relevant in this context\n\t * Called ONLY if orchestrator has already allowed the tool (Pass 1)\n\t *\n\t * @param context - Rich context about where tool is being evaluated\n\t * @returns true if tool should be visible, false to hide\n\t */\n\tisVisibleInContext(context: ToolContext): boolean;\n\n\t/** Required toolbar-first render contract. */\n\trenderToolbar(\n\t\tcontext: ToolContext,\n\t\ttoolbarContext: ToolbarContext,\n\t): ToolToolbarRenderResult | null;\n}\n\nconst VALID_TOOL_LEVELS: ToolLevel[] = [\n\t\"assessment\",\n\t\"section\",\n\t\"item\",\n\t\"passage\",\n\t\"rubric\",\n\t\"element\",\n];\n\nfunction assertNonEmptyString(\n\tvalue: unknown,\n\tfieldName: string,\n): asserts value is string {\n\tif (typeof value !== \"string\" || value.trim().length === 0) {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration: \"${fieldName}\" must be a non-empty string.`,\n\t\t);\n\t}\n}\n\n// Defence-in-depth: reject obvious XSS payloads in tool-registered icon\n// markup at registration time. Runtime rendering still runs each icon\n// through DOMPurify (see `ToolIcon.svelte`), but surfacing the problem\n// early produces a clearer error for tool authors than \"the icon silently\n// disappeared after sanitization\".\nconst SCRIPTABLE_ICON_PATTERNS: Array<{ pattern: RegExp; reason: string }> = [\n\t{ pattern: /<script\\b/i, reason: \"contains a <script> tag\" },\n\t{\n\t\tpattern: /\\son[a-z]+\\s*=/i,\n\t\treason: \"contains an inline event handler (on*=) attribute\",\n\t},\n\t{ pattern: /javascript:/i, reason: \"contains a javascript: URL\" },\n\t{\n\t\tpattern: /<foreignObject\\b/i,\n\t\treason: \"contains a <foreignObject> element\",\n\t},\n];\n\nfunction assertIconStringIsSafe(\n\ttoolId: string,\n\ticon: string,\n\tfieldName: string,\n): void {\n\tconst trimmed = icon.trimStart();\n\tconst looksLikeSvg = trimmed.toLowerCase().startsWith(\"<svg\");\n\tconst looksLikeUrl = /^https?:/i.test(trimmed);\n\tconst looksLikeDataUrl = /^data:/i.test(trimmed);\n\tif (looksLikeDataUrl) {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${toolId}\": \"${fieldName}\" may not be a data: URL.`,\n\t\t);\n\t}\n\tif (!looksLikeSvg && !looksLikeUrl) return;\n\tfor (const { pattern, reason } of SCRIPTABLE_ICON_PATTERNS) {\n\t\tif (pattern.test(icon)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid tool registration \"${toolId}\": \"${fieldName}\" ${reason}. Inline SVG icons must not include scriptable content.`,\n\t\t\t);\n\t\t}\n\t}\n}\n\nfunction assertToolRegistrationShape(registration: ToolRegistration): void {\n\tassertNonEmptyString(registration.toolId, \"toolId\");\n\tassertNonEmptyString(registration.name, \"name\");\n\tassertNonEmptyString(registration.description, \"description\");\n\n\tif (\n\t\ttypeof registration.icon !== \"string\" &&\n\t\ttypeof registration.icon !== \"function\"\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${registration.toolId}\": \"icon\" must be a string or function.`,\n\t\t);\n\t}\n\tif (typeof registration.icon === \"string\") {\n\t\tassertIconStringIsSafe(registration.toolId, registration.icon, \"icon\");\n\t}\n\tif (\n\t\t!Array.isArray(registration.supportedLevels) ||\n\t\tregistration.supportedLevels.length === 0\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${registration.toolId}\": \"supportedLevels\" must be a non-empty array.`,\n\t\t);\n\t}\n\tconst invalidLevel = registration.supportedLevels.find(\n\t\t(level) => !VALID_TOOL_LEVELS.includes(level),\n\t);\n\tif (invalidLevel) {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${registration.toolId}\": unsupported level \"${invalidLevel}\".`,\n\t\t);\n\t}\n\tif (\n\t\tregistration.activation !== undefined &&\n\t\tregistration.activation !== \"toolbar-toggle\" &&\n\t\tregistration.activation !== \"selection-gateway\"\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${registration.toolId}\": unsupported activation \"${String(registration.activation)}\".`,\n\t\t);\n\t}\n\tif (\n\t\tregistration.singletonScope !== undefined &&\n\t\tregistration.singletonScope !== \"section\"\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${registration.toolId}\": unsupported singletonScope \"${String(registration.singletonScope)}\".`,\n\t\t);\n\t}\n\tif (\n\t\tregistration.activation === \"selection-gateway\" &&\n\t\tregistration.singletonScope !== \"section\"\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${registration.toolId}\": selection-gateway tools must declare singletonScope \"section\".`,\n\t\t);\n\t}\n\tif (\n\t\tregistration.pnpSupportIds !== undefined &&\n\t\t(!Array.isArray(registration.pnpSupportIds) ||\n\t\t\tregistration.pnpSupportIds.some(\n\t\t\t\t(pnpId) => typeof pnpId !== \"string\" || pnpId.trim().length === 0,\n\t\t\t))\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${registration.toolId}\": \"pnpSupportIds\" must be an array of non-empty strings.`,\n\t\t);\n\t}\n\tif (typeof registration.isVisibleInContext !== \"function\") {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${registration.toolId}\": \"isVisibleInContext\" must be a function.`,\n\t\t);\n\t}\n\tif (typeof registration.renderToolbar !== \"function\") {\n\t\tthrow new Error(\n\t\t\t`Invalid tool registration \"${registration.toolId}\": \"renderToolbar\" must be a function.`,\n\t\t);\n\t}\n}\n\n/**\n * Tool Registry\n *\n * Manages tool registrations and provides query/lookup functionality\n */\nexport class ToolRegistry {\n\tprivate tools = new Map<string, ToolRegistration>();\n\tprivate pnpIndex = new Map<string, Set<string>>(); // pnpSupportId → Set<toolId>\n\tprivate componentOverrides: ToolComponentOverrides = {};\n\tprivate moduleLoaders = new Map<string, ToolModuleLoader>();\n\tprivate loadedToolModules = new Set<string>();\n\tprivate moduleLoadPromises = new Map<string, Promise<void>>();\n\n\t/**\n\t * Normalize a single tool alias to canonical toolId.\n\t */\n\tnormalizeToolId(toolId: string): string {\n\t\treturn normalizeToolAlias(toolId);\n\t}\n\n\t/**\n\t * Normalize a list of tool aliases to canonical toolIds.\n\t */\n\tnormalizeToolIds(toolIds: string[]): string[] {\n\t\treturn toolIds.map((toolId) => this.normalizeToolId(toolId));\n\t}\n\n\t/**\n\t * Register a tool\n\t *\n\t * @param registration - Tool registration\n\t * @throws Error if toolId is already registered\n\t */\n\tregister(registration: ToolRegistration): void {\n\t\tassertToolRegistrationShape(registration);\n\t\tif (this.tools.has(registration.toolId)) {\n\t\t\tthrow new Error(`Tool '${registration.toolId}' is already registered`);\n\t\t}\n\n\t\tthis.tools.set(registration.toolId, registration);\n\n\t\t// Index PNP support IDs\n\t\tif (registration.pnpSupportIds) {\n\t\t\tfor (const pnpId of registration.pnpSupportIds) {\n\t\t\t\tif (!this.pnpIndex.has(pnpId)) {\n\t\t\t\t\tthis.pnpIndex.set(pnpId, new Set());\n\t\t\t\t}\n\t\t\t\tthis.pnpIndex.get(pnpId)!.add(registration.toolId);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Override an existing tool registration\n\t *\n\t * @param registration - New tool registration (must have existing toolId)\n\t */\n\toverride(registration: ToolRegistration): void {\n\t\tassertToolRegistrationShape(registration);\n\t\tif (!this.tools.has(registration.toolId)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Cannot override non-existent tool '${registration.toolId}'`,\n\t\t\t);\n\t\t}\n\n\t\t// Remove old PNP index entries\n\t\tconst oldReg = this.tools.get(registration.toolId)!;\n\t\tif (oldReg.pnpSupportIds) {\n\t\t\tfor (const pnpId of oldReg.pnpSupportIds) {\n\t\t\t\tthis.pnpIndex.get(pnpId)?.delete(registration.toolId);\n\t\t\t}\n\t\t}\n\n\t\t// Add new registration\n\t\tthis.tools.set(registration.toolId, registration);\n\n\t\t// Re-index PNP support IDs\n\t\tif (registration.pnpSupportIds) {\n\t\t\tfor (const pnpId of registration.pnpSupportIds) {\n\t\t\t\tif (!this.pnpIndex.has(pnpId)) {\n\t\t\t\t\tthis.pnpIndex.set(pnpId, new Set());\n\t\t\t\t}\n\t\t\t\tthis.pnpIndex.get(pnpId)!.add(registration.toolId);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Unregister a tool\n\t *\n\t * @param toolId - Tool ID to remove\n\t */\n\tunregister(toolId: string): void {\n\t\tconst reg = this.tools.get(toolId);\n\t\tif (!reg) return;\n\n\t\t// Remove PNP index entries\n\t\tif (reg.pnpSupportIds) {\n\t\t\tfor (const pnpId of reg.pnpSupportIds) {\n\t\t\t\tthis.pnpIndex.get(pnpId)?.delete(toolId);\n\t\t\t}\n\t\t}\n\n\t\tthis.tools.delete(toolId);\n\t}\n\n\t/**\n\t * Get a tool registration by ID\n\t *\n\t * @param toolId - Tool ID\n\t * @returns Tool registration or undefined\n\t */\n\tget(toolId: string): ToolRegistration | undefined {\n\t\treturn this.tools.get(toolId);\n\t}\n\n\t/**\n\t * Check if a tool is registered\n\t *\n\t * @param toolId - Tool ID\n\t * @returns true if registered\n\t */\n\thas(toolId: string): boolean {\n\t\treturn this.tools.has(toolId);\n\t}\n\n\t/**\n\t * Get all registered tool IDs\n\t *\n\t * @returns Array of tool IDs\n\t */\n\tgetAllToolIds(): string[] {\n\t\treturn Array.from(this.tools.keys());\n\t}\n\n\t/**\n\t * Get all tool registrations\n\t *\n\t * @returns Array of tool registrations\n\t */\n\tgetAllTools(): ToolRegistration[] {\n\t\treturn Array.from(this.tools.values());\n\t}\n\n\t/**\n\t * Find tool IDs that support a given PNP support ID\n\t *\n\t * @param pnpSupportId - PNP support ID (e.g., 'calculator')\n\t * @returns Set of tool IDs that support this PNP ID\n\t */\n\tgetToolsByPNPSupport(pnpSupportId: string): Set<string> {\n\t\treturn this.pnpIndex.get(pnpSupportId) || new Set();\n\t}\n\n\t/**\n\t * Get tools that support a specific level\n\t *\n\t * @param level - Tool level (assessment, section, item, passage, element)\n\t * @returns Array of tool registrations that support this level\n\t */\n\tgetToolsByLevel(level: ToolLevel): ToolRegistration[] {\n\t\treturn this.getAllTools().filter((tool) =>\n\t\t\ttool.supportedLevels.includes(level),\n\t\t);\n\t}\n\n\t/**\n\t * Resolve tool activation, defaulting to toolbar-toggle.\n\t */\n\tgetToolActivation(toolId: string): ToolActivation {\n\t\treturn this.get(toolId)?.activation || \"toolbar-toggle\";\n\t}\n\n\t/**\n\t * Resolve singleton scope for a tool when present.\n\t */\n\tgetToolSingletonScope(toolId: string): ToolSingletonScope | null {\n\t\treturn this.get(toolId)?.singletonScope || null;\n\t}\n\n\t/**\n\t * Filter tool IDs by activation type.\n\t */\n\tfilterToolIdsByActivation(\n\t\ttoolIds: string[],\n\t\tactivation: ToolActivation,\n\t): string[] {\n\t\treturn toolIds.filter(\n\t\t\t(toolId) => this.getToolActivation(toolId) === activation,\n\t\t);\n\t}\n\n\t/**\n\t * Filter tools by visibility in a given context\n\t *\n\t * Pass 2 of the two-pass model: Given a list of allowed tool IDs (from Pass 1),\n\t * ask each tool if it's relevant in this context.\n\t *\n\t * @param allowedToolIds - Tool IDs that passed Pass 1 (orchestrator approval)\n\t * @param context - Context to evaluate\n\t * @returns Array of visible tool registrations\n\t */\n\tfilterVisibleInContext(\n\t\tallowedToolIds: string[],\n\t\tcontext: ToolContext,\n\t): ToolRegistration[] {\n\t\tconst visible: ToolRegistration[] = [];\n\n\t\tfor (const toolId of allowedToolIds) {\n\t\t\tconst tool = this.get(toolId);\n\t\t\tif (!tool) {\n\t\t\t\tconsole.warn(`Tool '${toolId}' is allowed but not registered`);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Check if tool supports this level\n\t\t\tif (!tool.supportedLevels.includes(context.level)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Pass 2: Ask tool if it's relevant\n\t\t\ttry {\n\t\t\t\tif (tool.isVisibleInContext(context)) {\n\t\t\t\t\tvisible.push(tool);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`Error evaluating visibility for tool '${toolId}':`,\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn visible;\n\t}\n\n\t/**\n\t * Get tool metadata for building UIs\n\t * Useful for building PNP configuration interfaces\n\t *\n\t * @returns Array of tool metadata (id, name, description, pnpSupportIds)\n\t */\n\tgetToolMetadata(): Array<{\n\t\ttoolId: string;\n\t\tname: string;\n\t\tdescription: string;\n\t\tpnpSupportIds: string[];\n\t\tsupportedLevels: ToolLevel[];\n\t\tactivation: ToolActivation;\n\t\tsingletonScope: ToolSingletonScope | null;\n\t}> {\n\t\treturn this.getAllTools().map((tool) => ({\n\t\t\ttoolId: tool.toolId,\n\t\t\tname: tool.name,\n\t\t\tdescription: tool.description,\n\t\t\tpnpSupportIds: tool.pnpSupportIds || [],\n\t\t\tsupportedLevels: tool.supportedLevels,\n\t\t\tactivation: tool.activation || \"toolbar-toggle\",\n\t\t\tsingletonScope: tool.singletonScope || null,\n\t\t}));\n\t}\n\n\t/**\n\t * Generate PNP support IDs from enabled tools\n\t * Useful for creating PNP profiles\n\t *\n\t * @param enabledToolIds - Tool IDs to enable\n\t * @returns Array of unique PNP support IDs\n\t */\n\tgeneratePNPSupportsFromTools(enabledToolIds: string[]): string[] {\n\t\tconst pnpSupports = new Set<string>();\n\n\t\tfor (const toolId of enabledToolIds) {\n\t\t\tconst tool = this.get(toolId);\n\t\t\tif (tool?.pnpSupportIds) {\n\t\t\t\tfor (const pnpId of tool.pnpSupportIds) {\n\t\t\t\t\tpnpSupports.add(pnpId);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn Array.from(pnpSupports);\n\t}\n\n\t/**\n\t * Clear all registrations (useful for testing)\n\t */\n\tclear(): void {\n\t\tthis.tools.clear();\n\t\tthis.pnpIndex.clear();\n\t}\n\n\t/**\n\t * Configure global component overrides used by tool instance creation.\n\t */\n\tsetComponentOverrides(overrides: ToolComponentOverrides): void {\n\t\tthis.componentOverrides = overrides;\n\t}\n\n\t/**\n\t * Register lazy module loaders by toolId.\n\t * Toolbars call ensureToolModuleLoaded(toolId) before instance creation.\n\t */\n\tsetToolModuleLoaders(\n\t\tloaders: Partial<Record<string, ToolModuleLoader>>,\n\t): void {\n\t\tfor (const [toolId, loader] of Object.entries(loaders)) {\n\t\t\tif (!loader) continue;\n\t\t\tassertNonEmptyString(toolId, \"tool module loader id\");\n\t\t\tif (typeof loader !== \"function\") {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Invalid tool module loader for \"${toolId}\": expected a function.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.moduleLoaders.set(toolId, loader);\n\t\t}\n\t}\n\n\t/**\n\t * Ensure tool module side-effects are loaded exactly once.\n\t * Safe to call repeatedly; concurrent callers share the same promise.\n\t */\n\tasync ensureToolModuleLoaded(toolId: string): Promise<void> {\n\t\tif (this.loadedToolModules.has(toolId)) return;\n\n\t\tconst existingPromise = this.moduleLoadPromises.get(toolId);\n\t\tif (existingPromise) {\n\t\t\tawait existingPromise;\n\t\t\treturn;\n\t\t}\n\n\t\tconst loader = this.moduleLoaders.get(toolId);\n\t\tif (!loader) return;\n\n\t\tconst loadPromise = (async () => {\n\t\t\tawait loader();\n\t\t\tthis.loadedToolModules.add(toolId);\n\t\t})();\n\n\t\tthis.moduleLoadPromises.set(toolId, loadPromise);\n\t\ttry {\n\t\t\tawait loadPromise;\n\t\t} finally {\n\t\t\tthis.moduleLoadPromises.delete(toolId);\n\t\t}\n\t}\n\n\t/**\n\t * Ensure a set of tool modules are loaded.\n\t */\n\tasync ensureToolModulesLoaded(toolIds: string[]): Promise<void> {\n\t\tawait Promise.all(\n\t\t\ttoolIds.map((toolId) => this.ensureToolModuleLoaded(toolId)),\n\t\t);\n\t}\n\n\t/**\n\t * Whether a tool module has already been loaded.\n\t */\n\tisToolModuleLoaded(toolId: string): boolean {\n\t\treturn this.loadedToolModules.has(toolId);\n\t}\n\n\t/**\n\t * Render a tool for toolbar use with component overrides attached.\n\t */\n\trenderForToolbar(\n\t\ttoolId: string,\n\t\tcontext: ToolContext,\n\t\ttoolbarContext: ToolbarContext,\n\t): ToolToolbarRenderResult | null {\n\t\tconst tool = this.get(toolId);\n\t\tif (!tool) {\n\t\t\tthrow new Error(`Tool '${toolId}' is not registered`);\n\t\t}\n\n\t\tconst mergedContext: ToolbarContext = {\n\t\t\t...toolbarContext,\n\t\t\tcomponentOverrides: {\n\t\t\t\t...(this.componentOverrides || {}),\n\t\t\t\t...(toolbarContext.componentOverrides || {}),\n\t\t\t},\n\t\t};\n\n\t\treturn tool.renderToolbar(context, mergedContext);\n\t}\n}\n"]}
|
|
@@ -185,10 +185,12 @@ export const calculatorToolRegistration = {
|
|
|
185
185
|
draggable: true,
|
|
186
186
|
resizable: true,
|
|
187
187
|
closeable: true,
|
|
188
|
-
initialWidth:
|
|
189
|
-
initialHeight:
|
|
190
|
-
minWidth:
|
|
188
|
+
initialWidth: 380,
|
|
189
|
+
initialHeight: 420,
|
|
190
|
+
minWidth: 380,
|
|
191
191
|
minHeight: 420,
|
|
192
|
+
initialAlign: 'bottom-right',
|
|
193
|
+
initialMargin: 16,
|
|
192
194
|
},
|
|
193
195
|
},
|
|
194
196
|
],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calculator.js","sourceRoot":"","sources":["../../../src/tools/registrations/calculator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Calculator Tool Registration\n *\n * Registers the calculator tool with support for multiple calculator types\n * (basic, scientific, graphing) via Desmos provider.\n *\n * Maps to QTI 3.0 standard access features:\n * - calculator (cognitive support)\n * - graphingCalculator (assessment tool)\n */\n\nimport type {\n\tToolRegistration,\n\tToolToolbarButtonDefinition,\n\tToolToolbarRenderResult,\n\tToolbarContext,\n} from \"../../services/ToolRegistry.js\";\nimport type { ToolCoordinatorApi } from \"../../services/interfaces.js\";\nimport type { ToolProviderConfig } from \"../../services/tools-config-normalizer.js\";\nimport type { ToolContext } from \"../../services/tool-context.js\";\nimport { hasMathContent } from \"../../services/tool-context.js\";\nimport { createScopedToolId } from \"../../services/tool-instance-id.js\";\nimport { DesmosToolProvider } from \"../../services/tool-providers/index.js\";\nimport { createToolElement } from \"../tool-tag-map.js\";\n\ntype CalculatorType = \"basic\" | \"scientific\";\n\n// The toolbar parent re-derives `renderedTools` whenever item state changes\n// (e.g. the learner answers a question and `effectiveItem`/`renderContext`\n// recompute). Calculator initialization is expensive (Desmos boot, container\n// mount), so we cache the overlay element by coordinator + scoped tool id.\n// Reusing the same element keeps `mountContent` a no-op and avoids tearing\n// down and re-initializing the calculator on every re-render.\nconst overlayElementCache = new WeakMap<\n\tToolCoordinatorApi,\n\tMap<string, HTMLElement>\n>();\n\nfunction getCachedOverlay(\n\tcoordinator: ToolCoordinatorApi | null,\n\tfullToolId: string,\n): HTMLElement | null {\n\tif (!coordinator) return null;\n\tconst scoped = overlayElementCache.get(coordinator);\n\tconst element = scoped?.get(fullToolId);\n\tif (!element) return null;\n\t// Svelte custom elements destroy their component when disconnected.\n\t// A detached cached element is a dead instance — drop it and recreate.\n\tif (!element.isConnected) {\n\t\tscoped?.delete(fullToolId);\n\t\treturn null;\n\t}\n\treturn element;\n}\n\nfunction setCachedOverlay(\n\tcoordinator: ToolCoordinatorApi | null,\n\tfullToolId: string,\n\telement: HTMLElement,\n): void {\n\tif (!coordinator) return;\n\tlet scoped = overlayElementCache.get(coordinator);\n\tif (!scoped) {\n\t\tscoped = new Map();\n\t\toverlayElementCache.set(coordinator, scoped);\n\t}\n\tscoped.set(fullToolId, element);\n}\n\nfunction normalizeCalculatorType(value: unknown): CalculatorType | null {\n\treturn value === \"basic\" || value === \"scientific\" ? value : null;\n}\n\nfunction getCalculatorRenderParams(toolbarContext: ToolbarContext): {\n\tcalculatorType: CalculatorType | null;\n\tavailableTypes: CalculatorType[] | null;\n\tdisplayName: string;\n} {\n\tconst params = toolbarContext.getToolRenderParams?.(\"calculator\") ?? {};\n\tconst calculatorType = normalizeCalculatorType(params.calculatorType);\n\tconst availableTypesRaw = params.availableTypes;\n\tconst availableTypes = Array.isArray(availableTypesRaw)\n\t\t? availableTypesRaw\n\t\t\t\t.map((value) => normalizeCalculatorType(value))\n\t\t\t\t.filter((value): value is CalculatorType => value !== null)\n\t\t: calculatorType\n\t\t\t? [calculatorType]\n\t\t\t: null;\n\n\treturn {\n\t\tcalculatorType,\n\t\tavailableTypes,\n\t\tdisplayName:\n\t\t\tcalculatorType === \"scientific\"\n\t\t\t\t? \"Scientific Calculator\"\n\t\t\t\t: calculatorType === \"basic\"\n\t\t\t\t\t? \"Basic Calculator\"\n\t\t\t\t\t: \"Calculator\",\n\t};\n}\n\nfunction applyCalculatorParamsToElement(\n\telement: HTMLElement,\n\tcalculatorType: CalculatorType | null,\n\tavailableTypes: CalculatorType[] | null,\n): void {\n\tconst calculatorElement = element as HTMLElement & {\n\t\tcalculatorType?: CalculatorType;\n\t\tavailableTypes?: CalculatorType[];\n\t};\n\n\tif (calculatorType) {\n\t\tcalculatorElement.calculatorType = calculatorType;\n\t\telement.setAttribute(\"calculator-type\", calculatorType);\n\t} else {\n\t\tdelete calculatorElement.calculatorType;\n\t\telement.removeAttribute(\"calculator-type\");\n\t}\n\n\tif (availableTypes && availableTypes.length > 0) {\n\t\tcalculatorElement.availableTypes = availableTypes;\n\t} else {\n\t\tdelete calculatorElement.availableTypes;\n\t}\n}\n\n/**\n * Calculator tool registration\n *\n * Supports:\n * - Basic, scientific, and graphing calculators via Desmos\n * - Context-aware visibility (shows only when math content is detected)\n * - Item level only\n */\nexport const calculatorToolRegistration: ToolRegistration = {\n\ttoolId: \"calculator\",\n\tname: \"Calculator\",\n\tdescription: \"Multi-type calculator (basic, scientific, graphing)\",\n\ticon: \"calculator\",\n\tprovider: {\n\t\tgetProviderId: (config: ToolProviderConfig | undefined) =>\n\t\t\ttypeof config?.provider?.id === \"string\" && config.provider.id.length > 0\n\t\t\t\t? config.provider.id\n\t\t\t\t: \"calculator-desmos\",\n\t\tcreateProvider: () => new DesmosToolProvider(),\n\t\tgetInitConfig: (config: ToolProviderConfig | undefined) =>\n\t\t\tconfig?.provider?.init ?? {},\n\t\tgetAuthFetcher: (config: ToolProviderConfig | undefined) => {\n\t\t\tconst runtimeAuthFetcher = config?.provider?.runtime?.authFetcher;\n\t\t\tif (typeof runtimeAuthFetcher === \"function\") return runtimeAuthFetcher;\n\t\t\treturn async () => {\n\t\t\t\tconst response = await fetch(\"/api/tools/desmos/auth\", {\n\t\t\t\t\tmethod: \"GET\",\n\t\t\t\t\tcredentials: \"same-origin\",\n\t\t\t\t});\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Failed to fetch Desmos auth config (${response.status})`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn (await response.json()) as Record<string, unknown>;\n\t\t\t};\n\t\t},\n\t\tlazy: true,\n\t},\n\n\t// Calculator is item-level in this player architecture.\n\tsupportedLevels: [\"item\"],\n\n\t// PNP support IDs that enable this tool\n\t// Maps to QTI 3.0 standard features: calculator, graphingCalculator\n\tpnpSupportIds: [\n\t\t\"calculator\", // QTI 3.0 standard (cognitive.calculator)\n\t\t\"graphingCalculator\", // QTI 3.0 standard (assessment.graphingCalculator)\n\t\t\"basicCalculator\", // Common variant\n\t\t\"scientificCalculator\", // Common variant\n\t],\n\n\t/**\n\t * Pass 2: Determine if calculator is relevant in this context\n\t *\n\t * Calculator is relevant when context contains mathematical content\n\t * (MathML, LaTeX, arithmetic markers).\n\t */\n\tisVisibleInContext(context: ToolContext): boolean {\n\t\t// Show only when math is present in item content.\n\t\treturn hasMathContent(context);\n\t},\n\n\trenderToolbar(\n\t\tcontext: ToolContext,\n\t\ttoolbarContext: ToolbarContext,\n\t): ToolToolbarRenderResult {\n\t\tconst { calculatorType, availableTypes, displayName } =\n\t\t\tgetCalculatorRenderParams(toolbarContext);\n\t\tconst fullToolId = createScopedToolId(\n\t\t\tthis.toolId,\n\t\t\ttoolbarContext.scope.level,\n\t\t\ttoolbarContext.scope.scopeId,\n\t\t);\n\t\tconst componentOverrides = toolbarContext.componentOverrides;\n\t\tconst cachedOverlay = getCachedOverlay(\n\t\t\ttoolbarContext.toolCoordinator,\n\t\t\tfullToolId,\n\t\t);\n\t\tconst overlay = (cachedOverlay ??\n\t\t\tcreateToolElement(\n\t\t\t\tthis.toolId,\n\t\t\t\tcontext,\n\t\t\t\ttoolbarContext,\n\t\t\t\tcomponentOverrides,\n\t\t\t)) as HTMLElement & {\n\t\t\tvisible?: boolean;\n\t\t\ttoolId?: string;\n\t\t\ttoolkitCoordinator?: unknown;\n\t\t};\n\t\tif (!cachedOverlay) {\n\t\t\tsetCachedOverlay(toolbarContext.toolCoordinator, fullToolId, overlay);\n\t\t}\n\t\toverlay.setAttribute(\"tool-id\", fullToolId);\n\t\toverlay.toolkitCoordinator = toolbarContext.toolkitCoordinator;\n\t\tapplyCalculatorParamsToElement(overlay, calculatorType, availableTypes);\n\t\tconst openLabel =\n\t\t\tcalculatorType === null\n\t\t\t\t? \"Open scientific calculator\"\n\t\t\t\t: `Open ${displayName.toLowerCase()}`;\n\t\tconst closeLabel =\n\t\t\tcalculatorType === null\n\t\t\t\t? \"Close scientific calculator\"\n\t\t\t\t: `Close ${displayName.toLowerCase()}`;\n\t\tconst button: ToolToolbarButtonDefinition = {\n\t\t\ttoolId: this.toolId,\n\t\t\tlabel: displayName,\n\t\t\ticon: typeof this.icon === \"function\" ? this.icon(context) : this.icon,\n\t\t\tdisabled: false,\n\t\t\tariaLabel: openLabel,\n\t\t\ttooltip: displayName,\n\t\t\tonClick: () => toolbarContext.toggleTool(this.toolId),\n\t\t\tactive: toolbarContext.isToolVisible(fullToolId),\n\t\t};\n\t\tlet lastVisibleState: boolean | undefined = button.active;\n\t\tif (overlay.visible !== button.active) {\n\t\t\toverlay.visible = button.active;\n\t\t}\n\n\t\treturn {\n\t\t\ttoolId: this.toolId,\n\t\t\telements: [\n\t\t\t\t{\n\t\t\t\t\telement: overlay,\n\t\t\t\t\tmount: \"after-buttons\",\n\t\t\t\t\tshell: {\n\t\t\t\t\t\ttitle: this.name,\n\t\t\t\t\t\tdraggable: true,\n\t\t\t\t\t\tresizable: true,\n\t\t\t\t\t\tcloseable: true,\n\t\t\t\t\t\tinitialWidth: 720,\n\t\t\t\t\t\tinitialHeight: 620,\n\t\t\t\t\t\tminWidth: 360,\n\t\t\t\t\t\tminHeight: 420,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t\tbutton,\n\t\t\tsync: () => {\n\t\t\t\tconst active = toolbarContext.isToolVisible(fullToolId);\n\t\t\t\tbutton.active = active;\n\t\t\t\tbutton.label = displayName;\n\t\t\t\tbutton.ariaLabel = active ? closeLabel : openLabel;\n\t\t\t\tbutton.tooltip = active\n\t\t\t\t\t? `Close ${displayName.toLowerCase()}`\n\t\t\t\t\t: displayName;\n\t\t\t\tif (lastVisibleState !== active) {\n\t\t\t\t\toverlay.visible = active;\n\t\t\t\t\tlastVisibleState = active;\n\t\t\t\t}\n\t\t\t\tif (overlay.toolkitCoordinator !== toolbarContext.toolkitCoordinator) {\n\t\t\t\t\toverlay.toolkitCoordinator = toolbarContext.toolkitCoordinator;\n\t\t\t\t}\n\t\t\t\tapplyCalculatorParamsToElement(overlay, calculatorType, availableTypes);\n\t\t\t},\n\t\t\tsubscribeActive: (callback: (active: boolean) => void) => {\n\t\t\t\tif (!toolbarContext.subscribeVisibility) return () => {};\n\t\t\t\treturn toolbarContext.subscribeVisibility(() => {\n\t\t\t\t\tcallback(toolbarContext.isToolVisible(fullToolId));\n\t\t\t\t});\n\t\t\t},\n\t\t};\n\t},\n};\n"]}
|
|
1
|
+
{"version":3,"file":"calculator.js","sourceRoot":"","sources":["../../../src/tools/registrations/calculator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Calculator Tool Registration\n *\n * Registers the calculator tool with support for multiple calculator types\n * (basic, scientific, graphing) via Desmos provider.\n *\n * Maps to QTI 3.0 standard access features:\n * - calculator (cognitive support)\n * - graphingCalculator (assessment tool)\n */\n\nimport type {\n\tToolRegistration,\n\tToolToolbarButtonDefinition,\n\tToolToolbarRenderResult,\n\tToolbarContext,\n} from \"../../services/ToolRegistry.js\";\nimport type { ToolCoordinatorApi } from \"../../services/interfaces.js\";\nimport type { ToolProviderConfig } from \"../../services/tools-config-normalizer.js\";\nimport type { ToolContext } from \"../../services/tool-context.js\";\nimport { hasMathContent } from \"../../services/tool-context.js\";\nimport { createScopedToolId } from \"../../services/tool-instance-id.js\";\nimport { DesmosToolProvider } from \"../../services/tool-providers/index.js\";\nimport { createToolElement } from \"../tool-tag-map.js\";\n\ntype CalculatorType = \"basic\" | \"scientific\";\n\n// The toolbar parent re-derives `renderedTools` whenever item state changes\n// (e.g. the learner answers a question and `effectiveItem`/`renderContext`\n// recompute). Calculator initialization is expensive (Desmos boot, container\n// mount), so we cache the overlay element by coordinator + scoped tool id.\n// Reusing the same element keeps `mountContent` a no-op and avoids tearing\n// down and re-initializing the calculator on every re-render.\nconst overlayElementCache = new WeakMap<\n\tToolCoordinatorApi,\n\tMap<string, HTMLElement>\n>();\n\nfunction getCachedOverlay(\n\tcoordinator: ToolCoordinatorApi | null,\n\tfullToolId: string,\n): HTMLElement | null {\n\tif (!coordinator) return null;\n\tconst scoped = overlayElementCache.get(coordinator);\n\tconst element = scoped?.get(fullToolId);\n\tif (!element) return null;\n\t// Svelte custom elements destroy their component when disconnected.\n\t// A detached cached element is a dead instance — drop it and recreate.\n\tif (!element.isConnected) {\n\t\tscoped?.delete(fullToolId);\n\t\treturn null;\n\t}\n\treturn element;\n}\n\nfunction setCachedOverlay(\n\tcoordinator: ToolCoordinatorApi | null,\n\tfullToolId: string,\n\telement: HTMLElement,\n): void {\n\tif (!coordinator) return;\n\tlet scoped = overlayElementCache.get(coordinator);\n\tif (!scoped) {\n\t\tscoped = new Map();\n\t\toverlayElementCache.set(coordinator, scoped);\n\t}\n\tscoped.set(fullToolId, element);\n}\n\nfunction normalizeCalculatorType(value: unknown): CalculatorType | null {\n\treturn value === \"basic\" || value === \"scientific\" ? value : null;\n}\n\nfunction getCalculatorRenderParams(toolbarContext: ToolbarContext): {\n\tcalculatorType: CalculatorType | null;\n\tavailableTypes: CalculatorType[] | null;\n\tdisplayName: string;\n} {\n\tconst params = toolbarContext.getToolRenderParams?.(\"calculator\") ?? {};\n\tconst calculatorType = normalizeCalculatorType(params.calculatorType);\n\tconst availableTypesRaw = params.availableTypes;\n\tconst availableTypes = Array.isArray(availableTypesRaw)\n\t\t? availableTypesRaw\n\t\t\t\t.map((value) => normalizeCalculatorType(value))\n\t\t\t\t.filter((value): value is CalculatorType => value !== null)\n\t\t: calculatorType\n\t\t\t? [calculatorType]\n\t\t\t: null;\n\n\treturn {\n\t\tcalculatorType,\n\t\tavailableTypes,\n\t\tdisplayName:\n\t\t\tcalculatorType === \"scientific\"\n\t\t\t\t? \"Scientific Calculator\"\n\t\t\t\t: calculatorType === \"basic\"\n\t\t\t\t\t? \"Basic Calculator\"\n\t\t\t\t\t: \"Calculator\",\n\t};\n}\n\nfunction applyCalculatorParamsToElement(\n\telement: HTMLElement,\n\tcalculatorType: CalculatorType | null,\n\tavailableTypes: CalculatorType[] | null,\n): void {\n\tconst calculatorElement = element as HTMLElement & {\n\t\tcalculatorType?: CalculatorType;\n\t\tavailableTypes?: CalculatorType[];\n\t};\n\n\tif (calculatorType) {\n\t\tcalculatorElement.calculatorType = calculatorType;\n\t\telement.setAttribute(\"calculator-type\", calculatorType);\n\t} else {\n\t\tdelete calculatorElement.calculatorType;\n\t\telement.removeAttribute(\"calculator-type\");\n\t}\n\n\tif (availableTypes && availableTypes.length > 0) {\n\t\tcalculatorElement.availableTypes = availableTypes;\n\t} else {\n\t\tdelete calculatorElement.availableTypes;\n\t}\n}\n\n/**\n * Calculator tool registration\n *\n * Supports:\n * - Basic, scientific, and graphing calculators via Desmos\n * - Context-aware visibility (shows only when math content is detected)\n * - Item level only\n */\nexport const calculatorToolRegistration: ToolRegistration = {\n\ttoolId: \"calculator\",\n\tname: \"Calculator\",\n\tdescription: \"Multi-type calculator (basic, scientific, graphing)\",\n\ticon: \"calculator\",\n\tprovider: {\n\t\tgetProviderId: (config: ToolProviderConfig | undefined) =>\n\t\t\ttypeof config?.provider?.id === \"string\" && config.provider.id.length > 0\n\t\t\t\t? config.provider.id\n\t\t\t\t: \"calculator-desmos\",\n\t\tcreateProvider: () => new DesmosToolProvider(),\n\t\tgetInitConfig: (config: ToolProviderConfig | undefined) =>\n\t\t\tconfig?.provider?.init ?? {},\n\t\tgetAuthFetcher: (config: ToolProviderConfig | undefined) => {\n\t\t\tconst runtimeAuthFetcher = config?.provider?.runtime?.authFetcher;\n\t\t\tif (typeof runtimeAuthFetcher === \"function\") return runtimeAuthFetcher;\n\t\t\treturn async () => {\n\t\t\t\tconst response = await fetch(\"/api/tools/desmos/auth\", {\n\t\t\t\t\tmethod: \"GET\",\n\t\t\t\t\tcredentials: \"same-origin\",\n\t\t\t\t});\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Failed to fetch Desmos auth config (${response.status})`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn (await response.json()) as Record<string, unknown>;\n\t\t\t};\n\t\t},\n\t\tlazy: true,\n\t},\n\n\t// Calculator is item-level in this player architecture.\n\tsupportedLevels: [\"item\"],\n\n\t// PNP support IDs that enable this tool\n\t// Maps to QTI 3.0 standard features: calculator, graphingCalculator\n\tpnpSupportIds: [\n\t\t\"calculator\", // QTI 3.0 standard (cognitive.calculator)\n\t\t\"graphingCalculator\", // QTI 3.0 standard (assessment.graphingCalculator)\n\t\t\"basicCalculator\", // Common variant\n\t\t\"scientificCalculator\", // Common variant\n\t],\n\n\t/**\n\t * Pass 2: Determine if calculator is relevant in this context\n\t *\n\t * Calculator is relevant when context contains mathematical content\n\t * (MathML, LaTeX, arithmetic markers).\n\t */\n\tisVisibleInContext(context: ToolContext): boolean {\n\t\t// Show only when math is present in item content.\n\t\treturn hasMathContent(context);\n\t},\n\n\trenderToolbar(\n\t\tcontext: ToolContext,\n\t\ttoolbarContext: ToolbarContext,\n\t): ToolToolbarRenderResult {\n\t\tconst { calculatorType, availableTypes, displayName } =\n\t\t\tgetCalculatorRenderParams(toolbarContext);\n\t\tconst fullToolId = createScopedToolId(\n\t\t\tthis.toolId,\n\t\t\ttoolbarContext.scope.level,\n\t\t\ttoolbarContext.scope.scopeId,\n\t\t);\n\t\tconst componentOverrides = toolbarContext.componentOverrides;\n\t\tconst cachedOverlay = getCachedOverlay(\n\t\t\ttoolbarContext.toolCoordinator,\n\t\t\tfullToolId,\n\t\t);\n\t\tconst overlay = (cachedOverlay ??\n\t\t\tcreateToolElement(\n\t\t\t\tthis.toolId,\n\t\t\t\tcontext,\n\t\t\t\ttoolbarContext,\n\t\t\t\tcomponentOverrides,\n\t\t\t)) as HTMLElement & {\n\t\t\tvisible?: boolean;\n\t\t\ttoolId?: string;\n\t\t\ttoolkitCoordinator?: unknown;\n\t\t};\n\t\tif (!cachedOverlay) {\n\t\t\tsetCachedOverlay(toolbarContext.toolCoordinator, fullToolId, overlay);\n\t\t}\n\t\toverlay.setAttribute(\"tool-id\", fullToolId);\n\t\toverlay.toolkitCoordinator = toolbarContext.toolkitCoordinator;\n\t\tapplyCalculatorParamsToElement(overlay, calculatorType, availableTypes);\n\t\tconst openLabel =\n\t\t\tcalculatorType === null\n\t\t\t\t? \"Open scientific calculator\"\n\t\t\t\t: `Open ${displayName.toLowerCase()}`;\n\t\tconst closeLabel =\n\t\t\tcalculatorType === null\n\t\t\t\t? \"Close scientific calculator\"\n\t\t\t\t: `Close ${displayName.toLowerCase()}`;\n\t\tconst button: ToolToolbarButtonDefinition = {\n\t\t\ttoolId: this.toolId,\n\t\t\tlabel: displayName,\n\t\t\ticon: typeof this.icon === \"function\" ? this.icon(context) : this.icon,\n\t\t\tdisabled: false,\n\t\t\tariaLabel: openLabel,\n\t\t\ttooltip: displayName,\n\t\t\tonClick: () => toolbarContext.toggleTool(this.toolId),\n\t\t\tactive: toolbarContext.isToolVisible(fullToolId),\n\t\t};\n\t\tlet lastVisibleState: boolean | undefined = button.active;\n\t\tif (overlay.visible !== button.active) {\n\t\t\toverlay.visible = button.active;\n\t\t}\n\n\t\treturn {\n\t\t\ttoolId: this.toolId,\n\t\t\telements: [\n\t\t\t\t{\n\t\t\t\t\telement: overlay,\n\t\t\t\t\tmount: \"after-buttons\",\n\t\t\t\t\tshell: {\n\t\t\t\t\t\ttitle: this.name,\n\t\t\t\t\t\tdraggable: true,\n\t\t\t\t\t\tresizable: true,\n\t\t\t\t\t\tcloseable: true,\n\t\t\t\t\t\tinitialWidth: 380,\n\t\t\t\t\t\tinitialHeight: 420,\n\t\t\t\t\t\tminWidth: 380,\n\t\t\t\t\t\tminHeight: 420,\n\t\t\t\t\t\tinitialAlign: 'bottom-right',\n\t\t\t\t\t\tinitialMargin: 16,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t\tbutton,\n\t\t\tsync: () => {\n\t\t\t\tconst active = toolbarContext.isToolVisible(fullToolId);\n\t\t\t\tbutton.active = active;\n\t\t\t\tbutton.label = displayName;\n\t\t\t\tbutton.ariaLabel = active ? closeLabel : openLabel;\n\t\t\t\tbutton.tooltip = active\n\t\t\t\t\t? `Close ${displayName.toLowerCase()}`\n\t\t\t\t\t: displayName;\n\t\t\t\tif (lastVisibleState !== active) {\n\t\t\t\t\toverlay.visible = active;\n\t\t\t\t\tlastVisibleState = active;\n\t\t\t\t}\n\t\t\t\tif (overlay.toolkitCoordinator !== toolbarContext.toolkitCoordinator) {\n\t\t\t\t\toverlay.toolkitCoordinator = toolbarContext.toolkitCoordinator;\n\t\t\t\t}\n\t\t\t\tapplyCalculatorParamsToElement(overlay, calculatorType, availableTypes);\n\t\t\t},\n\t\t\tsubscribeActive: (callback: (active: boolean) => void) => {\n\t\t\t\tif (!toolbarContext.subscribeVisibility) return () => {};\n\t\t\t\treturn toolbarContext.subscribeVisibility(() => {\n\t\t\t\t\tcallback(toolbarContext.isToolVisible(fullToolId));\n\t\t\t\t});\n\t\t\t},\n\t\t};\n\t},\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pie-players/pie-assessment-toolkit",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.46",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "PIE assessment toolkit: composable services + reference implementation for assessment players and tool coordination",
|
|
6
6
|
"license": "MIT",
|
|
@@ -75,15 +75,15 @@
|
|
|
75
75
|
"test": "bun test"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@pie-players/pie-calculator": "0.3.
|
|
79
|
-
"@pie-players/pie-context": "0.3.
|
|
80
|
-
"@pie-players/pie-players-shared": "0.3.
|
|
81
|
-
"@pie-players/pie-tts": "0.3.
|
|
78
|
+
"@pie-players/pie-calculator": "0.3.46",
|
|
79
|
+
"@pie-players/pie-context": "0.3.46",
|
|
80
|
+
"@pie-players/pie-players-shared": "0.3.46",
|
|
81
|
+
"@pie-players/pie-tts": "0.3.46",
|
|
82
82
|
"speech-rule-engine": "^5.0.0-rc.1"
|
|
83
83
|
},
|
|
84
84
|
"peerDependencies": {
|
|
85
|
-
"@pie-players/pie-calculator-desmos": "0.3.
|
|
86
|
-
"@pie-players/tts-client-server": "0.3.
|
|
85
|
+
"@pie-players/pie-calculator-desmos": "0.3.46",
|
|
86
|
+
"@pie-players/tts-client-server": "0.3.46"
|
|
87
87
|
},
|
|
88
88
|
"peerDependenciesMeta": {
|
|
89
89
|
"@pie-players/pie-calculator-desmos": {
|