@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 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 { createFocusTrap } from "@pie-players/pie-players-shared";
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: 720,
6765
- initialHeight: 620,
6766
- minWidth: 360,
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
- x2 = Math.max(0, Math.round((viewportW - width) / 2));
9431
- y2 = Math.max(0, Math.round((viewportH - height) / 2));
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 onResizePointerDown = (event2) => {
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
- width = clamp(resizeStartWidth + (event2.clientX - resizeStartX), minWidth, Math.max(minWidth, Math.min(maxWidth, window.innerWidth - x2)));
9532
- height = clamp(resizeStartHeight + (event2.clientY - resizeStartY), minHeight, Math.max(minHeight, Math.min(maxHeight, window.innerHeight - y2)));
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 = "hidden";
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
- resizeHandleEl = document.createElement("div");
9737
- resizeHandleEl.className = "pie-tool-shell__resize";
9738
- resizeHandleEl.style.position = "absolute";
9739
- resizeHandleEl.style.right = "0";
9740
- resizeHandleEl.style.bottom = "0";
9741
- resizeHandleEl.style.width = "24px";
9742
- resizeHandleEl.style.height = "24px";
9743
- resizeHandleEl.style.cursor = "nwse-resize";
9744
- resizeHandleEl.style.background = "linear-gradient(135deg, transparent 0%, transparent 40%, rgba(0,0,0,0.35) 40%, rgba(0,0,0,0.35) 60%, transparent 60%, transparent 100%)";
9745
- shellEl.appendChild(resizeHandleEl);
9746
- resizeHandleEl.addEventListener("pointerdown", onResizePointerDown);
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
- openerEl = null;
9788
- if (resizeHandleEl) {
9789
- resizeHandleEl.removeEventListener("pointerdown", onResizePointerDown);
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 i = root_5();
9998
- template_effect(() => set_class(i, 1, `icon icon-${get2(item2).icon}`, "svelte-rnx3ie"));
9999
- append($$anchor7, i);
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 i_1 = root_5();
10090
- template_effect(() => set_class(i_1, 1, `icon icon-${get2(item2).icon}`, "svelte-rnx3ie"));
10091
- append($$anchor7, i_1);
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: 720,
18626
- initialHeight: 620,
18627
- minWidth: 360,
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 { createFocusTrap } from "@pie-players/pie-players-shared";
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: 720,
10953
- initialHeight: 620,
10954
- minWidth: 360,
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
- x2 = Math.max(0, Math.round((viewportW - width) / 2));
13595
- y2 = Math.max(0, Math.round((viewportH - height) / 2));
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 onResizePointerDown = (event2) => {
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
- width = clamp(resizeStartWidth + (event2.clientX - resizeStartX), minWidth, Math.max(minWidth, Math.min(maxWidth, window.innerWidth - x2)));
13696
- height = clamp(resizeStartHeight + (event2.clientY - resizeStartY), minHeight, Math.max(minHeight, Math.min(maxHeight, window.innerHeight - y2)));
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 = "hidden";
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
- resizeHandleEl = document.createElement("div");
13901
- resizeHandleEl.className = "pie-tool-shell__resize";
13902
- resizeHandleEl.style.position = "absolute";
13903
- resizeHandleEl.style.right = "0";
13904
- resizeHandleEl.style.bottom = "0";
13905
- resizeHandleEl.style.width = "24px";
13906
- resizeHandleEl.style.height = "24px";
13907
- resizeHandleEl.style.cursor = "nwse-resize";
13908
- resizeHandleEl.style.background = "linear-gradient(135deg, transparent 0%, transparent 40%, rgba(0,0,0,0.35) 40%, rgba(0,0,0,0.35) 60%, transparent 60%, transparent 100%)";
13909
- shellEl.appendChild(resizeHandleEl);
13910
- resizeHandleEl.addEventListener("pointerdown", onResizePointerDown);
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
- openerEl = null;
13952
- if (resizeHandleEl) {
13953
- resizeHandleEl.removeEventListener("pointerdown", onResizePointerDown);
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 i = root_5();
14162
- template_effect2(() => set_class2(i, 1, `icon icon-${get22(item2).icon}`, "svelte-rnx3ie"));
14163
- append2($$anchor7, i);
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 i_1 = root_5();
14254
- template_effect2(() => set_class2(i_1, 1, `icon icon-${get22(item2).icon}`, "svelte-rnx3ie"));
14255
- append2($$anchor7, i_1);
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: 720,
189
- initialHeight: 620,
190
- minWidth: 360,
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.45",
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.45",
79
- "@pie-players/pie-context": "0.3.45",
80
- "@pie-players/pie-players-shared": "0.3.45",
81
- "@pie-players/pie-tts": "0.3.45",
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.45",
86
- "@pie-players/tts-client-server": "0.3.45"
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": {