@citolab/qti-components 7.17.0 → 7.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/cdn/index.global.js +1 -1
  2. package/cdn/index.js +629 -278
  3. package/custom-elements.json +139 -1950
  4. package/dist/base.d.ts +11 -11
  5. package/dist/base.js +1 -1
  6. package/dist/{chunk-VEV4DGPH.js → chunk-3OCDS5FG.js} +2 -2
  7. package/dist/{chunk-C2HQFI2C.js → chunk-5D7GD3JS.js} +763 -481
  8. package/dist/chunk-5D7GD3JS.js.map +1 -0
  9. package/dist/{chunk-W4SQRNWO.js → chunk-5J5P5GJY.js} +4 -8
  10. package/dist/{chunk-W4SQRNWO.js.map → chunk-5J5P5GJY.js.map} +1 -1
  11. package/dist/{chunk-RI47B4ZT.js → chunk-DO3QY5GP.js} +2 -2
  12. package/dist/{chunk-DWIRLYDS.js → chunk-FOWDOLXY.js} +2 -2
  13. package/dist/{chunk-INKI27D5.js → chunk-GMNH42S6.js} +27 -36
  14. package/dist/chunk-GMNH42S6.js.map +1 -0
  15. package/dist/{chunk-2DOYPVF5.js → chunk-HRVIMT5Y.js} +4 -4
  16. package/dist/{chunk-O4XIWHTF.js → chunk-IDW2ETV2.js} +27 -12
  17. package/dist/chunk-IDW2ETV2.js.map +1 -0
  18. package/dist/{chunk-F44CI35W.js → chunk-L4TXKTUA.js} +2 -2
  19. package/dist/{chunk-JEUY3MYB.js → chunk-VUSGFL4B.js} +21 -17
  20. package/dist/chunk-VUSGFL4B.js.map +1 -0
  21. package/dist/{chunk-352OTVTY.js → chunk-YAPCC4BO.js} +37 -25
  22. package/dist/chunk-YAPCC4BO.js.map +1 -0
  23. package/dist/{computed-item.context-CiddHLPz.d.ts → computed-item.context-DfsW_96S.d.ts} +1 -1
  24. package/dist/{computed.context-CH09_LCR.d.ts → computed.context-DHbxad0N.d.ts} +2 -2
  25. package/dist/{config.context-DAdkDDf5.d.ts → config.context-DUz2-kxN.d.ts} +1 -1
  26. package/dist/elements.d.ts +5 -3
  27. package/dist/elements.js +4 -4
  28. package/dist/index.d.ts +10 -10
  29. package/dist/index.js +11 -11
  30. package/dist/{interaction-C5Up6-68.d.ts → interaction-BLQjAZqN.d.ts} +4 -4
  31. package/dist/interactions.d.ts +50 -119
  32. package/dist/interactions.js +4 -4
  33. package/dist/{item.context-BRKXBC3m.d.ts → item.context-CkUeDcaO.d.ts} +6 -1
  34. package/dist/item.css +1 -5
  35. package/dist/item.d.ts +3 -3
  36. package/dist/item.js +5 -5
  37. package/dist/loader.d.ts +3 -3
  38. package/dist/loader.js +2 -2
  39. package/dist/processing.d.ts +4 -4
  40. package/dist/processing.js +2 -2
  41. package/dist/qti-components-jsx.d.ts +6448 -2802
  42. package/dist/{qti-feedback-B4cMzOcq.d.ts → qti-feedback-BNXgxua1.d.ts} +2 -2
  43. package/dist/{qti-rule-base-dL4opfvi.d.ts → qti-rule-base-BZSb0WTW.d.ts} +4 -4
  44. package/dist/{qti-transform-test-Bz9A3hmD.d.ts → qti-transform-test-C2fL4PtU.d.ts} +1 -1
  45. package/dist/{test.context-Bpw1HNAZ.d.ts → test.context-Oc_8ovbV.d.ts} +3 -3
  46. package/dist/test.d.ts +28 -28
  47. package/dist/test.js +7 -7
  48. package/dist/transformers.d.ts +1 -1
  49. package/dist/transformers.js +1 -1
  50. package/dist/{variables-CusMRnyJ.d.ts → variables-CMYcDbyW.d.ts} +2 -1
  51. package/package.json +9 -9
  52. package/dist/chunk-352OTVTY.js.map +0 -1
  53. package/dist/chunk-C2HQFI2C.js.map +0 -1
  54. package/dist/chunk-INKI27D5.js.map +0 -1
  55. package/dist/chunk-JEUY3MYB.js.map +0 -1
  56. package/dist/chunk-O4XIWHTF.js.map +0 -1
  57. package/dist/vscode.css-custom-data.json +0 -11
  58. package/dist/vscode.html-custom-data.json +0 -960
  59. /package/dist/{chunk-VEV4DGPH.js.map → chunk-3OCDS5FG.js.map} +0 -0
  60. /package/dist/{chunk-RI47B4ZT.js.map → chunk-DO3QY5GP.js.map} +0 -0
  61. /package/dist/{chunk-DWIRLYDS.js.map → chunk-FOWDOLXY.js.map} +0 -0
  62. /package/dist/{chunk-2DOYPVF5.js.map → chunk-HRVIMT5Y.js.map} +0 -0
  63. /package/dist/{chunk-F44CI35W.js.map → chunk-L4TXKTUA.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  o as o2
3
- } from "./chunk-DWIRLYDS.js";
3
+ } from "./chunk-FOWDOLXY.js";
4
4
  import {
5
5
  M,
6
6
  e as e2,
@@ -11,7 +11,7 @@ import {
11
11
  s,
12
12
  t as t2,
13
13
  v
14
- } from "./chunk-F44CI35W.js";
14
+ } from "./chunk-L4TXKTUA.js";
15
15
  import {
16
16
  liveQuery,
17
17
  watch
@@ -34,7 +34,7 @@ import {
34
34
  t,
35
35
  w,
36
36
  x
37
- } from "./chunk-JEUY3MYB.js";
37
+ } from "./chunk-VUSGFL4B.js";
38
38
  import {
39
39
  __decorateClass
40
40
  } from "./chunk-EUXUH3YW.js";
@@ -1355,26 +1355,132 @@ QtiChoiceInteraction = __decorateClass([
1355
1355
  ], QtiChoiceInteraction);
1356
1356
 
1357
1357
  // ../qti-interactions/src/components/qti-custom-interaction/qti-custom-interaction.ts
1358
+ var registerCES = `
1359
+ const postToParentWindows = (type, data) => {
1360
+ window.top.postMessage(data ? { type, data } : { type }, '*');
1361
+ let w = window.parent;
1362
+ while (w) {
1363
+ if (w !== window.top) {
1364
+ w.postMessage({ type, data }, '*');
1365
+ }
1366
+ if (w !== w.parent) {
1367
+ w = w.parent;
1368
+ } else {
1369
+ w = null;
1370
+ }
1371
+ }
1372
+ };
1373
+
1374
+ window.CES = {
1375
+ media: null,
1376
+ response: null,
1377
+ load: () => {
1378
+ let resolveCount = 0;
1379
+
1380
+ const handleMessage = (event) => {
1381
+ if (event.data.type === "mediaData") {
1382
+ const media = event.data.data;
1383
+ CES.media = media;
1384
+ resolveCount++;
1385
+ } else if (event.data.type === "responseData") {
1386
+ const response = event.data.data;
1387
+ if (response && Array.isArray(response) && response.length > 0) {
1388
+ // state is stored in the first element of the array
1389
+ CES.response = response[0];
1390
+ // Wait a short moment to ensure CES.response is set
1391
+ setTimeout(() => {
1392
+ // Re-create the Controller instance if it exists
1393
+ if (typeof Controller === 'function') {
1394
+ console.log("Re-creating Controller instance");
1395
+ ctrl = new Controller();
1396
+ }
1397
+ }, 50);
1398
+ } else {
1399
+ CES.response = response;
1400
+ }
1401
+ resolveCount++;
1402
+ }
1403
+ if (resolveCount === 2) {
1404
+ //window.removeEventListener("message", handleMessage);
1405
+ }
1406
+ };
1407
+ window.addEventListener("message", handleMessage);
1408
+ postToParentWindows("getMedia");
1409
+ postToParentWindows("getResponse");
1410
+ },
1411
+ setResponse: (data) => {
1412
+ postToParentWindows("setResponse", data);
1413
+ },
1414
+ getResponse: () => {
1415
+ return CES.response;
1416
+ },
1417
+ getMedia: () => {
1418
+ return CES.media;
1419
+ },
1420
+ setStageHeight: () => {
1421
+ postToParentWindows("setStageHeight");
1422
+ },
1423
+ };
1424
+ CES.load();
1425
+ `;
1426
+ var ciBootstrap = `
1427
+ window.onload = function () {
1428
+ const handleMessage = (event) => {
1429
+ if (event.data.type === 'blobUrl') {
1430
+ const blobUrl = event.data.data;
1431
+ var n = document.createElement('iframe');
1432
+ n.frameBorder = '0';
1433
+ n.scrolling = 'no';
1434
+ n.src = blobUrl;
1435
+ n.style.width = '100%';
1436
+ n.style.height = '100%';
1437
+ document.body.appendChild(n);
1438
+ window.removeEventListener('message', handleMessage);
1439
+ }
1440
+ };
1441
+ window.addEventListener('message', handleMessage);
1442
+ // Request the blob URL from parent
1443
+ let w = window.parent;
1444
+ while (w) {
1445
+ w.postMessage({ type: 'getBlobUrl' }, '*');
1446
+ if (w !== w.parent) {
1447
+ w = w.parent;
1448
+ } else {
1449
+ w = null;
1450
+ }
1451
+ }
1452
+ };
1453
+ `;
1358
1454
  var QtiCustomInteraction = class extends Interaction {
1359
1455
  constructor() {
1360
1456
  super();
1361
- // This custom-interaction support the CES API which is use in FACET
1362
- //
1363
- // It works like this:
1364
- // 1. The CI manifest is fetched
1365
- // 2. An iframe is created and the first style and first script from the manifest are loaded
1366
- // 3. The first script is bootstrap.js which also creates an iframe and loads the first media from the manifest
1367
- // 4. Communication is done via the CES API but because the iframe is not allowed to access the global CES object we need to use window.postMessage
1368
- // To achieve this we change the package by replacing the bootstrap.js with our own and inject a proxy CES API that communicates via postMessage
1369
- // Because we also want to run this in storybook, we cannot use window.top because to send messages there, because in case of storybook that is not the top window.
1370
- // So we send messages to all parent windows
1371
1457
  this.rawResponse = "";
1458
+ this._manifestUrl = null;
1459
+ this._resourceBaseUrl = null;
1372
1460
  this._errorMessage = null;
1461
+ // Pre-created blob URL for the index.html with injected CES proxy
1462
+ this._contentBlobUrl = null;
1373
1463
  this.handlePostMessage = this.handlePostMessage.bind(this);
1374
1464
  }
1375
1465
  connectedCallback() {
1376
1466
  super.connectedCallback();
1377
- const uriToManifest = this.data.startsWith("http") || this.data.startsWith("blob") ? this.data : removeDoubleSlashes(this.baseItemUrl + "/" + this.data);
1467
+ let manifestPath = this.data;
1468
+ if (!manifestPath) {
1469
+ const objectEl = this.querySelector("object[data]");
1470
+ if (objectEl) {
1471
+ manifestPath = objectEl.getAttribute("data");
1472
+ const width = objectEl.getAttribute("width");
1473
+ const height = objectEl.getAttribute("height");
1474
+ if (width) this.setAttribute("width", width);
1475
+ if (height) this.setAttribute("height", height);
1476
+ }
1477
+ }
1478
+ if (!manifestPath) {
1479
+ this._errorMessage = "No manifest path found (neither data attribute nor object child)";
1480
+ return;
1481
+ }
1482
+ const uriToManifest = manifestPath.startsWith("http") || manifestPath.startsWith("blob") ? manifestPath : removeDoubleSlashes((this.baseItemUrl || "") + "/" + manifestPath);
1483
+ this._manifestUrl = new URL(uriToManifest, window.location.href).toString();
1378
1484
  fetch(uriToManifest).then((response) => {
1379
1485
  return response.json();
1380
1486
  }).then((data) => {
@@ -1384,11 +1490,18 @@ var QtiCustomInteraction = class extends Interaction {
1384
1490
  this._errorMessage = err;
1385
1491
  });
1386
1492
  }
1387
- // MH: Changed the default bootstrap.js to use the new CES API
1388
- // Because the old one uses the global CES object that is not allowed to be accessed when the CI
1389
- // is embedded in an iframe and coming from another domain
1390
- // Therefor we need to use the new CES API to communicates via the broadcast API
1391
- setupCES() {
1493
+ /**
1494
+ * Sets up the CES custom interaction by creating the iframe structure and
1495
+ * handling the CES API communication via postMessage.
1496
+ *
1497
+ * For interactions that use CES, this method:
1498
+ * 1. Fetches the original bootstrap.js and checks if it uses CES
1499
+ * 2. If CES is used, fetches index.html and injects the registerCES proxy
1500
+ * 3. Creates a blob URL for the modified HTML (stored in _contentBlobUrl)
1501
+ * 4. Replaces bootstrap.js with ciBootstrap that loads the blob URL
1502
+ * 5. Sets up postMessage listeners for CES API calls
1503
+ */
1504
+ async setupCES() {
1392
1505
  const iframe = this.shadowRoot.querySelector("#pciContainer");
1393
1506
  const iframeDoc = iframe.contentDocument;
1394
1507
  if (!this.manifest.script || this.manifest.script.length === 0) {
@@ -1400,16 +1513,106 @@ var QtiCustomInteraction = class extends Interaction {
1400
1513
  return;
1401
1514
  }
1402
1515
  const cssRef = this.manifest.style[0];
1403
- const cssUrl = cssRef.startsWith("http") || cssRef.startsWith("blob") ? cssRef : removeDoubleSlashes(`${this.baseRefUrl}/${cssRef}`);
1404
- const scriptRef = this.manifest.script[0];
1405
- const scriptUrl = scriptRef.startsWith("http") || scriptRef.startsWith("blob") ? scriptRef : removeDoubleSlashes(`${this.baseRefUrl}/${scriptRef}`);
1516
+ const baseCandidates = this.getBaseCandidates();
1517
+ console.debug("[qti-custom-interaction] manifest url", this._manifestUrl);
1518
+ console.debug("[qti-custom-interaction] base candidates", baseCandidates);
1519
+ console.debug("[qti-custom-interaction] refs", { cssRef, media: this.manifest.media });
1520
+ const cssResolved = await this.resolveResourceWithFallback(cssRef, baseCandidates);
1521
+ const cssUrl = cssResolved.url;
1522
+ if (cssResolved.baseUrl && !this._resourceBaseUrl) {
1523
+ this._resourceBaseUrl = cssResolved.baseUrl;
1524
+ }
1525
+ console.debug("[qti-custom-interaction] css resolved", cssResolved);
1526
+ const usesCES = true;
1527
+ console.debug("[qti-custom-interaction] using built-in ciBootstrap (skipping server bootstrap.js)");
1528
+ if (usesCES) {
1529
+ let indexUrl = "";
1530
+ if (this.manifest.media && this.manifest.media.length > 0) {
1531
+ const mediaRef = this.manifest.media[0];
1532
+ const indexResolved = await this.resolveResourceWithFallback(mediaRef, baseCandidates, {
1533
+ returnText: true
1534
+ });
1535
+ indexUrl = indexResolved.url;
1536
+ if (indexResolved.baseUrl && !this._resourceBaseUrl) {
1537
+ this._resourceBaseUrl = indexResolved.baseUrl;
1538
+ }
1539
+ console.debug("[qti-custom-interaction] index resolved (media)", {
1540
+ url: indexResolved.url,
1541
+ baseUrl: indexResolved.baseUrl,
1542
+ hasText: Boolean(indexResolved.text)
1543
+ });
1544
+ if (indexResolved.text) {
1545
+ let html = indexResolved.text;
1546
+ const cesScript = "<script>" + registerCES + "</script>";
1547
+ const headIndex = html.indexOf("<head>");
1548
+ if (headIndex !== -1) {
1549
+ html = html.slice(0, headIndex + 6) + cesScript + html.slice(headIndex + 6);
1550
+ } else {
1551
+ html = cesScript + html;
1552
+ }
1553
+ const blob = new Blob([html], { type: "text/html" });
1554
+ this._contentBlobUrl = URL.createObjectURL(blob);
1555
+ }
1556
+ } else {
1557
+ const manifestPath = this.data || "manifest.json";
1558
+ const basePath = manifestPath.includes("/") ? manifestPath.substring(0, manifestPath.lastIndexOf("/")) : "";
1559
+ const indexPath = basePath ? `${basePath}/index.html` : "index.html";
1560
+ const indexResolved = await this.resolveResourceWithFallback(indexPath, baseCandidates, {
1561
+ returnText: true
1562
+ });
1563
+ indexUrl = indexResolved.url;
1564
+ if (indexResolved.baseUrl && !this._resourceBaseUrl) {
1565
+ this._resourceBaseUrl = indexResolved.baseUrl;
1566
+ }
1567
+ console.debug("[qti-custom-interaction] index resolved (fallback)", {
1568
+ url: indexResolved.url,
1569
+ baseUrl: indexResolved.baseUrl,
1570
+ hasText: Boolean(indexResolved.text)
1571
+ });
1572
+ if (indexResolved.text) {
1573
+ let html = indexResolved.text;
1574
+ const cesScript = "<script>" + registerCES + "</script>";
1575
+ const headIndex = html.indexOf("<head>");
1576
+ if (headIndex !== -1) {
1577
+ html = html.slice(0, headIndex + 6) + cesScript + html.slice(headIndex + 6);
1578
+ } else {
1579
+ html = cesScript + html;
1580
+ }
1581
+ const blob = new Blob([html], { type: "text/html" });
1582
+ this._contentBlobUrl = URL.createObjectURL(blob);
1583
+ }
1584
+ }
1585
+ if (indexUrl && !this._contentBlobUrl) {
1586
+ try {
1587
+ const indexResponse = await fetch(indexUrl);
1588
+ if (indexResponse.ok) {
1589
+ let html = await indexResponse.text();
1590
+ const cesScript = "<script>" + registerCES + "</script>";
1591
+ const headIndex = html.indexOf("<head>");
1592
+ if (headIndex !== -1) {
1593
+ html = html.slice(0, headIndex + 6) + cesScript + html.slice(headIndex + 6);
1594
+ } else {
1595
+ html = cesScript + html;
1596
+ }
1597
+ const blob = new Blob([html], { type: "text/html" });
1598
+ this._contentBlobUrl = URL.createObjectURL(blob);
1599
+ } else {
1600
+ console.error(`Failed to fetch index.html: ${indexResponse.status}`);
1601
+ }
1602
+ } catch (e5) {
1603
+ console.error(`Error fetching index.html: ${e5}`);
1604
+ }
1605
+ }
1606
+ }
1607
+ const inlineScript = `<script>${ciBootstrap}</script>`;
1608
+ console.debug("[qti-custom-interaction] using ciBootstrap inline script");
1406
1609
  window.addEventListener("message", this.handlePostMessage);
1407
1610
  iframeDoc.open();
1408
1611
  iframeDoc.write(`
1409
1612
  <html>
1410
1613
  <head>
1411
- <link href='${cssUrl}' rel="stylesheet" />
1412
- <script src='${scriptUrl}'></script>
1614
+ ${cssUrl ? `<link href='${cssUrl}' rel="stylesheet" />` : ""}
1615
+ ${inlineScript}
1413
1616
  </head>
1414
1617
  <body></body>
1415
1618
  </html>
@@ -1471,6 +1674,9 @@ var QtiCustomInteraction = class extends Interaction {
1471
1674
  }
1472
1675
  handlePostMessage(event) {
1473
1676
  const { type, data } = event.data;
1677
+ if (type && type !== "setResponse") {
1678
+ console.debug("[qti-custom-interaction] postMessage", { type, data });
1679
+ }
1474
1680
  switch (type) {
1475
1681
  case "setResponse":
1476
1682
  if (data === null || !(Array.isArray(data) && data.length === 1 && data[0] === "")) {
@@ -1482,11 +1688,25 @@ var QtiCustomInteraction = class extends Interaction {
1482
1688
  this.postToWindowAndIframes("responseData", this.rawResponse);
1483
1689
  break;
1484
1690
  }
1691
+ case "getBlobUrl": {
1692
+ if (this._contentBlobUrl) {
1693
+ this.postToWindowAndIframes("blobUrl", this._contentBlobUrl);
1694
+ }
1695
+ break;
1696
+ }
1485
1697
  case "getMedia": {
1698
+ const baseCandidates = this.getBaseCandidates();
1486
1699
  const mediaData = this.manifest.media.map((media) => {
1487
- const url = media.startsWith("http") || media.startsWith("blob") ? media : removeDoubleSlashes(this.baseRefUrl + "/" + media);
1488
- return url;
1700
+ if (media.startsWith("http") || media.startsWith("blob")) {
1701
+ return media;
1702
+ }
1703
+ const baseUrl = this._resourceBaseUrl || baseCandidates[0];
1704
+ if (!baseUrl) {
1705
+ return media;
1706
+ }
1707
+ return new URL(media, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).toString();
1489
1708
  });
1709
+ console.debug("[qti-custom-interaction] mediaData", mediaData);
1490
1710
  this.postToWindowAndIframes("mediaData", mediaData);
1491
1711
  break;
1492
1712
  }
@@ -1543,6 +1763,51 @@ var QtiCustomInteraction = class extends Interaction {
1543
1763
  ${this._errorMessage}
1544
1764
  </div>`}`;
1545
1765
  }
1766
+ getBaseCandidates() {
1767
+ const candidates = [this._resourceBaseUrl, this.baseRefUrl, this.baseItemUrl, this.getManifestBaseUrl()];
1768
+ const resolved = candidates.filter(Boolean).map((base) => new URL(base, window.location.href).toString());
1769
+ const manifestParent = this.getManifestParentBaseUrl();
1770
+ if (manifestParent) {
1771
+ resolved.push(manifestParent);
1772
+ }
1773
+ return [...new Set(resolved)];
1774
+ }
1775
+ getManifestBaseUrl() {
1776
+ if (!this._manifestUrl) {
1777
+ return null;
1778
+ }
1779
+ return new URL(".", this._manifestUrl).toString();
1780
+ }
1781
+ getManifestParentBaseUrl() {
1782
+ if (!this._manifestUrl) {
1783
+ return null;
1784
+ }
1785
+ return new URL("..", this._manifestUrl).toString();
1786
+ }
1787
+ async resolveResourceWithFallback(ref, baseCandidates, options = {}) {
1788
+ if (!ref) {
1789
+ return { url: "", baseUrl: null };
1790
+ }
1791
+ if (ref.startsWith("http") || ref.startsWith("blob")) {
1792
+ return { url: ref, baseUrl: null };
1793
+ }
1794
+ for (const base of baseCandidates) {
1795
+ const baseUrl = new URL(base, window.location.href).toString();
1796
+ const url = new URL(ref, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).toString();
1797
+ try {
1798
+ const response = await fetch(url);
1799
+ if (response.ok) {
1800
+ if (options.returnText) {
1801
+ const text = await response.text();
1802
+ return { url, baseUrl, text };
1803
+ }
1804
+ return { url, baseUrl };
1805
+ }
1806
+ } catch (e5) {
1807
+ }
1808
+ }
1809
+ return { url: "", baseUrl: null };
1810
+ }
1546
1811
  };
1547
1812
  __decorateClass([
1548
1813
  n({ type: String, attribute: "data" })
@@ -3195,19 +3460,17 @@ var qti_portable_custom_interaction_styles_default = i`
3195
3460
  var QtiPortableCustomInteraction = class extends Interaction {
3196
3461
  constructor() {
3197
3462
  super(...arguments);
3198
- // Only used in iframe mode
3199
3463
  this._iframeLoaded = false;
3200
3464
  this._pendingMessages = [];
3201
- this._responseCheckInterval = null;
3465
+ this._iframeMessageOrigin = null;
3202
3466
  this.requirePathsJson = "";
3203
3467
  this.requireShimJson = "";
3204
3468
  this.requireJsUrl = "https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js";
3205
3469
  this.baseUrl = "";
3206
- this.useIframe = false;
3207
3470
  this.useDefaultShims = false;
3208
3471
  this.useDefaultPaths = false;
3209
3472
  this._errorMessage = null;
3210
- this.response = [];
3473
+ this.response = null;
3211
3474
  this._parsedRequirePaths = null;
3212
3475
  this._parsedRequireShim = null;
3213
3476
  /**
@@ -3218,6 +3481,17 @@ var QtiPortableCustomInteraction = class extends Interaction {
3218
3481
  if (!data || data.source !== "qti-pci-iframe") {
3219
3482
  return;
3220
3483
  }
3484
+ if (!this.iframe?.contentWindow || event.source !== this.iframe.contentWindow) {
3485
+ return;
3486
+ }
3487
+ if (data.responseIdentifier && data.responseIdentifier !== this.responseIdentifier) {
3488
+ return;
3489
+ }
3490
+ if (this._iframeMessageOrigin === null) {
3491
+ this._iframeMessageOrigin = event.origin;
3492
+ } else if (event.origin !== this._iframeMessageOrigin) {
3493
+ return;
3494
+ }
3221
3495
  switch (data.method) {
3222
3496
  case "iframeReady":
3223
3497
  this.initializeInteraction();
@@ -3235,10 +3509,19 @@ var QtiPortableCustomInteraction = class extends Interaction {
3235
3509
  }
3236
3510
  break;
3237
3511
  case "interactionChanged": {
3238
- const value = this.convertQtiVariableJSON(data.params.value);
3239
- this.response = value;
3512
+ const raw = data?.params?.value;
3513
+ const converted = raw && typeof raw === "object" ? this.convertQtiVariableJSON(raw) : null;
3514
+ const state = typeof data?.params?.state === "string" ? data.params.state : null;
3515
+ if (converted === null) {
3516
+ const emptyResponse = this.responseVariable?.cardinality === "single" ? "" : [];
3517
+ this.response = emptyResponse;
3518
+ this.validate();
3519
+ this.saveResponse(emptyResponse, state);
3520
+ break;
3521
+ }
3522
+ this.response = converted;
3240
3523
  this.validate();
3241
- this.saveResponse(this.response);
3524
+ this.saveResponse(converted, state);
3242
3525
  break;
3243
3526
  }
3244
3527
  case "error":
@@ -3247,46 +3530,6 @@ var QtiPortableCustomInteraction = class extends Interaction {
3247
3530
  break;
3248
3531
  }
3249
3532
  };
3250
- this._onInteractionChanged = (event) => {
3251
- event.stopPropagation();
3252
- const value = this.convertQtiVariableJSON(event.detail.value);
3253
- this.response = value;
3254
- this.saveResponse(value);
3255
- };
3256
- /**
3257
- * DIRECT MODE: Load config from URL
3258
- */
3259
- this.loadConfig = async (url, baseUrl) => {
3260
- url = this.removeDoubleSlashes(url);
3261
- try {
3262
- const requireConfig = await fetch(url);
3263
- if (requireConfig.ok) {
3264
- const config = await requireConfig.json();
3265
- const moduleCong = config;
3266
- for (const moduleId in moduleCong.paths) {
3267
- if (baseUrl) {
3268
- moduleCong.paths[moduleId] = this.getResolvablePath(moduleCong.paths[moduleId], baseUrl);
3269
- }
3270
- }
3271
- return moduleCong;
3272
- }
3273
- } catch (e5) {
3274
- }
3275
- return null;
3276
- };
3277
- /**
3278
- * DIRECT MODE: Helper method to get resolvable path string
3279
- */
3280
- this.getResolvablePathString = (path, basePath) => {
3281
- path = path.replace(/\.js$/, "");
3282
- return path?.toLocaleLowerCase().startsWith("http") || !basePath ? path : this.removeDoubleSlashes(`${basePath}/${path}`);
3283
- };
3284
- /**
3285
- * DIRECT MODE: Helper method to get resolvable path
3286
- */
3287
- this.getResolvablePath = (path, basePath) => {
3288
- return Array.isArray(path) ? path.map((p2) => this.getResolvablePathString(p2, basePath)) : this.getResolvablePathString(path, basePath);
3289
- };
3290
3533
  // Add this property to store the previous state
3291
3534
  this._previousState = null;
3292
3535
  }
@@ -3454,7 +3697,12 @@ var QtiPortableCustomInteraction = class extends Interaction {
3454
3697
  }
3455
3698
  }
3456
3699
  validate() {
3457
- return true;
3700
+ if (this.response === null || this.response === void 0) return false;
3701
+ if (Array.isArray(this.response)) {
3702
+ if (this.response.length === 0) return false;
3703
+ return this.response.some((v2) => v2 !== "" && v2 !== null && v2 !== void 0);
3704
+ }
3705
+ return this.response !== "";
3458
3706
  }
3459
3707
  set value(v2) {
3460
3708
  if (v2 === null) {
@@ -3464,16 +3712,8 @@ var QtiPortableCustomInteraction = class extends Interaction {
3464
3712
  }
3465
3713
  }
3466
3714
  get value() {
3467
- if (this.useIframe) {
3468
- return this._value?.toString() || null;
3469
- } else {
3470
- const pciValue = this.pci?.getResponse();
3471
- if (pciValue) {
3472
- const convertedValue = this.convertQtiVariableJSON(pciValue);
3473
- return Array.isArray(convertedValue) ? convertedValue.join(",") : convertedValue;
3474
- }
3475
- }
3476
- return Array.isArray(this._value) ? this._value.join(",") : this._value?.toString() || null;
3715
+ if (this._value === null || this._value === void 0) return null;
3716
+ return Array.isArray(this._value) ? this._value.join(",") : String(this._value);
3477
3717
  }
3478
3718
  set boundTo(newValue) {
3479
3719
  if (!newValue || !newValue[this.responseIdentifier]) {
@@ -3516,129 +3756,23 @@ var QtiPortableCustomInteraction = class extends Interaction {
3516
3756
  }
3517
3757
  return unescaped;
3518
3758
  }
3519
- /**
3520
- * DIRECT MODE: Register PCI instance
3521
- */
3522
- register(pci) {
3523
- this.pci = pci;
3524
- this.dom = this.querySelector("qti-interaction-markup");
3525
- if (!this.dom) {
3526
- this.dom = document.createElement("div");
3527
- this.appendChild(this.dom);
3528
- }
3529
- this.dom.classList.add("qti-customInteraction");
3530
- this.dom.style.width = "100%";
3531
- this.dom.style.minHeight = "50px";
3532
- this.dom.style.display = "block";
3533
- this.dom.addEventListener("qti-interaction-changed", this._onInteractionChanged);
3534
- if (this.querySelector("properties")) {
3535
- this.querySelector("properties").style.display = "none";
3536
- }
3537
- const config = {
3538
- properties: this.addHyphenatedKeys(this.unescapeDataAttributes({ ...this.dataset })),
3539
- contextVariables: {},
3540
- templateVariables: {},
3541
- onready: (pciInstance) => {
3542
- this.pci = pciInstance;
3543
- try {
3544
- const resizeObserver = new ResizeObserver((entries) => {
3545
- for (const entry of entries) {
3546
- if (entry.contentRect) {
3547
- if (entry.contentRect.height > 0) {
3548
- this.style.height = `${entry.contentRect.height + 20}px`;
3549
- }
3550
- }
3551
- }
3552
- });
3553
- resizeObserver.observe(this.dom);
3554
- this._resizeObserver = resizeObserver;
3555
- } catch (e5) {
3556
- console.warn("ResizeObserver not supported, falling back to static sizing");
3557
- }
3558
- },
3559
- ondone: (_pciInstance, response, _state, _status) => {
3560
- this.response = this.convertQtiVariableJSON(response);
3561
- this.saveResponse(this.response);
3562
- },
3563
- responseIdentifier: this.responseIdentifier,
3564
- boundTo: this.boundTo
3565
- };
3566
- if (pci.getInstance) {
3567
- pci.getInstance(this.dom, config, void 0);
3568
- } else {
3569
- const restoreTAOConfig = (element) => {
3570
- const config2 = {};
3571
- const parseDataAttributes = (element2) => {
3572
- const result = {};
3573
- Object.entries(element2.dataset).forEach(([key, value]) => {
3574
- if (!key.includes("__")) {
3575
- result[key] = value;
3576
- }
3577
- });
3578
- const nestedData = {};
3579
- Object.entries(element2.dataset).forEach(([key, value]) => {
3580
- const parts = key.split("__");
3581
- if (parts.length > 1) {
3582
- const [group, index, prop] = parts;
3583
- nestedData[group] = nestedData[group] || {};
3584
- nestedData[group][index] = nestedData[group][index] || {};
3585
- nestedData[group][index][prop] = value;
3586
- }
3587
- });
3588
- Object.entries(nestedData).forEach(([key, group]) => {
3589
- result[key] = Object.values(group);
3590
- });
3591
- return result;
3592
- };
3593
- const data = parseDataAttributes(element);
3594
- for (const key in data) {
3595
- if (Object.prototype.hasOwnProperty.call(data, key)) {
3596
- const value = data[key];
3597
- if (key === "config") {
3598
- config2[key] = JSON.parse(value);
3599
- } else {
3600
- config2[key] = value;
3601
- }
3602
- }
3603
- }
3604
- return config2;
3605
- };
3606
- const taoConfig = restoreTAOConfig(this);
3607
- pci.initialize(
3608
- this.customInteractionTypeIdentifier,
3609
- this.dom.firstElementChild || this.dom,
3610
- Object.keys(taoConfig).length ? taoConfig : null
3611
- );
3612
- }
3613
- }
3614
- /* ... rest of the code remains the same ... */
3615
3759
  disconnectedCallback() {
3616
3760
  super.disconnectedCallback();
3617
- if (this.useIframe) {
3618
- window.removeEventListener("message", this.handleIframeMessage);
3619
- } else {
3620
- this.stopResponseCheck();
3621
- if (this._resizeObserver) {
3622
- this._resizeObserver.disconnect();
3623
- this._resizeObserver = null;
3624
- }
3625
- requirejs.undef(this.customInteractionTypeIdentifier);
3626
- const context = requirejs.s.contexts;
3627
- delete context[this.customInteractionTypeIdentifier];
3628
- this.removeEventListener("qti-interaction-changed", this._onInteractionChanged);
3629
- }
3761
+ window.removeEventListener("message", this.handleIframeMessage);
3630
3762
  }
3631
3763
  /**
3632
3764
  * IFRAME MODE: Send message to iframe
3633
3765
  */
3634
3766
  sendMessageToIframe(method, params) {
3635
- if (!this._iframeLoaded) {
3767
+ const targetWindow = this.iframe?.contentWindow;
3768
+ if (!this._iframeLoaded || !targetWindow) {
3636
3769
  this._pendingMessages.push({ method, params });
3637
3770
  return;
3638
3771
  }
3639
- this.iframe.contentWindow.postMessage(
3772
+ targetWindow.postMessage(
3640
3773
  {
3641
3774
  source: "qti-portable-custom-interaction",
3775
+ responseIdentifier: this.responseIdentifier,
3642
3776
  method,
3643
3777
  params
3644
3778
  },
@@ -3685,14 +3819,19 @@ var QtiPortableCustomInteraction = class extends Interaction {
3685
3819
  * IFRAME MODE: Send initialization data to iframe
3686
3820
  */
3687
3821
  sendIframeInitData() {
3822
+ const properties = this.addHyphenatedKeys(this.unescapeDataAttributes({ ...this.dataset }));
3823
+ const storedStateRaw = this.context?.state?.[this.responseIdentifier];
3824
+ const storedState = typeof storedStateRaw === "string" && storedStateRaw.length > 0 ? storedStateRaw : null;
3688
3825
  const initData = {
3689
3826
  module: this.module,
3690
3827
  customInteractionTypeIdentifier: this.customInteractionTypeIdentifier,
3691
3828
  baseUrl: !this.baseUrl ? window.location.origin : this.baseUrl.startsWith("http") || this.baseUrl.startsWith("blob") || this.baseUrl.startsWith("base64") ? this.baseUrl : removeDoubleSlashes(`${window.location.origin}${this.baseUrl}`),
3692
3829
  responseIdentifier: this.responseIdentifier,
3830
+ properties,
3693
3831
  dataAttributes: { ...this.dataset },
3694
3832
  interactionModules: this.getInteractionModules(),
3695
- boundTo: this.boundTo
3833
+ boundTo: storedState ? null : this.boundTo,
3834
+ state: storedState
3696
3835
  };
3697
3836
  this.sendMessageToIframe("initialize", initData);
3698
3837
  }
@@ -3734,99 +3873,9 @@ var QtiPortableCustomInteraction = class extends Interaction {
3734
3873
  }
3735
3874
  connectedCallback() {
3736
3875
  super.connectedCallback();
3737
- if (this.useIframe) {
3738
- window.addEventListener("message", this.handleIframeMessage);
3739
- this.createIframe();
3740
- } else {
3741
- define("qtiCustomInteractionContext", () => {
3742
- return {
3743
- register: (ctxA) => {
3744
- this.register(ctxA);
3745
- },
3746
- notifyReady: () => {
3747
- }
3748
- };
3749
- });
3750
- const config = this.buildRequireConfig();
3751
- if (config) {
3752
- const requirePCI = requirejs.config(config);
3753
- requirejs.onError = function(err) {
3754
- console.error("RequireJS error:", err);
3755
- if (err.requireType === "timeout") {
3756
- console.error("Modules that timed out:", err.requireModules);
3757
- }
3758
- throw err;
3759
- };
3760
- requirePCI(["require"], (require2) => {
3761
- try {
3762
- require2([this.module], () => {
3763
- }, (err) => {
3764
- console.error("Error loading module:", err);
3765
- });
3766
- } catch (error) {
3767
- console.error("Error in require call:", error);
3768
- }
3769
- });
3770
- }
3771
- }
3772
- }
3773
- /**
3774
- * Stop checking for response changes
3775
- */
3776
- stopResponseCheck() {
3777
- if (this._responseCheckInterval !== null) {
3778
- window.clearInterval(this._responseCheckInterval);
3779
- this._responseCheckInterval = null;
3780
- }
3781
- }
3782
- /**
3783
- * DIRECT MODE: Build RequireJS configuration
3784
- */
3785
- buildRequireConfig() {
3786
- const config = {
3787
- context: this.customInteractionTypeIdentifier,
3788
- catchError: true,
3789
- paths: { ...this.getFinalRequirePaths(), ...window["requirePaths"] || {} },
3790
- shim: { ...this.getFinalRequireShim(), ...window["requireShim"] || {} }
3791
- };
3792
- if (!globalThis.require) {
3793
- this._errorMessage = `RequireJS not found. Please load it via CDN: https://cdnjs.com/libraries/require.js`;
3794
- return null;
3795
- }
3796
- const baseUrl = this.getAttribute("data-base-url");
3797
- const interactionModules = this.querySelector("qti-interaction-modules");
3798
- if (interactionModules) {
3799
- const modules = interactionModules.querySelectorAll("qti-interaction-module");
3800
- for (const module of modules) {
3801
- const moduleId = module.getAttribute("id");
3802
- const primaryPath = module.getAttribute("primary-path");
3803
- const fallbackPath = module.getAttribute("fallback-path");
3804
- if (moduleId && primaryPath) {
3805
- const paths = fallbackPath ? this.combineRequireResolvePaths(
3806
- this.getResolvablePath(primaryPath, baseUrl),
3807
- this.getResolvablePath(fallbackPath, baseUrl)
3808
- ) : this.getResolvablePath(primaryPath, baseUrl);
3809
- const existingPath = config.paths[moduleId] || [];
3810
- config.paths[moduleId] = this.combineRequireResolvePaths(existingPath, paths);
3811
- }
3812
- }
3813
- }
3814
- return config;
3815
- }
3816
- /**
3817
- * DIRECT MODE: Helper method to combine require paths
3818
- */
3819
- combineRequireResolvePaths(path1, path2) {
3820
- const path1Array = Array.isArray(path1) ? path1 : [path1];
3821
- const path2Array = Array.isArray(path2) ? path2 : [path2];
3822
- return path1Array.concat(path2Array).filter((value, index, self) => self.indexOf(value) === index);
3823
- }
3824
- /**
3825
- * DIRECT MODE: Helper method to remove double slashes
3826
- */
3827
- removeDoubleSlashes(str) {
3828
- const singleForwardSlashes = str.replace(/([^:]\/)\/+/g, "$1").replace(/\/\//g, "/").replace("http:/", "http://").replace("https:/", "https://");
3829
- return singleForwardSlashes;
3876
+ this.response = this.responseVariable?.value ?? null;
3877
+ window.addEventListener("message", this.handleIframeMessage);
3878
+ this.createIframe();
3830
3879
  }
3831
3880
  /**
3832
3881
  * IFRAME MODE: Generate iframe HTML content
@@ -3864,6 +3913,12 @@ var QtiPortableCustomInteraction = class extends Interaction {
3864
3913
  }
3865
3914
  #pci-container {
3866
3915
  width: 100%;
3916
+ }
3917
+ qti-interaction-markup {
3918
+ display: block;
3919
+ width: 100%;
3920
+ min-height: 50px;
3921
+ }
3867
3922
  </style>
3868
3923
  <script src="${this.requireJsUrl}"></script>
3869
3924
  <script>
@@ -3898,14 +3953,15 @@ var QtiPortableCustomInteraction = class extends Interaction {
3898
3953
  console.error('Script error usually indicates a network or CORS issue with:', err.requireModules);
3899
3954
  }
3900
3955
 
3901
- // Notify parent window about the error
3902
- window.parent.postMessage({
3903
- source: 'qti-pci-iframe',
3904
- method: 'error',
3905
- params: {
3906
- message: 'RequireJS ' + err.requireType + ' error for modules: ' + err.requireModules,
3907
- details: {
3908
- type: err.requireType,
3956
+ // Notify parent window about the error
3957
+ window.parent.postMessage({
3958
+ source: 'qti-pci-iframe',
3959
+ responseIdentifier: (window.PCIManager && window.PCIManager.responseIdentifier) || null,
3960
+ method: 'error',
3961
+ params: {
3962
+ message: 'RequireJS ' + err.requireType + ' error for modules: ' + err.requireModules,
3963
+ details: {
3964
+ type: err.requireType,
3909
3965
  modules: err.requireModules,
3910
3966
  error: err.toString()
3911
3967
  }
@@ -3918,13 +3974,96 @@ var QtiPortableCustomInteraction = class extends Interaction {
3918
3974
  window.PCIManager = {
3919
3975
  pciInstance: null,
3920
3976
  container: null,
3977
+ markupEl: null,
3978
+ propertiesEl: null,
3921
3979
  customInteractionTypeIdentifier: null,
3980
+ responseIdentifier: null,
3981
+ pendingBoundTo: null,
3982
+ pendingMarkup: null,
3983
+ pendingProperties: null,
3984
+ pendingState: null,
3985
+ interactionChangedViaEvent: false,
3986
+ eventBridgeAttached: false,
3987
+ lastResponseStr: null,
3988
+ hadResponse: false,
3922
3989
 
3923
3990
  initialize: function(config) {
3924
3991
  this.customInteractionTypeIdentifier = config.customInteractionTypeIdentifier;
3992
+ this.responseIdentifier = config.responseIdentifier;
3925
3993
  this.container = document.getElementById('pci-container');
3926
3994
  this.container.classList.add('qti-customInteraction');
3927
3995
 
3996
+ function qtiVariableHasValue(qtiVar) {
3997
+ if (!qtiVar) return false;
3998
+ if (qtiVar.base) {
3999
+ for (const k in qtiVar.base) {
4000
+ if (!Object.prototype.hasOwnProperty.call(qtiVar.base, k)) continue;
4001
+ const v = qtiVar.base[k];
4002
+ if (v !== null && v !== undefined && v !== '') return true;
4003
+ }
4004
+ }
4005
+ if (qtiVar.list) {
4006
+ for (const k in qtiVar.list) {
4007
+ if (!Object.prototype.hasOwnProperty.call(qtiVar.list, k)) continue;
4008
+ const v = qtiVar.list[k];
4009
+ if (Array.isArray(v) && v.some(x => x !== null && x !== undefined && x !== '')) return true;
4010
+ }
4011
+ }
4012
+ if (Array.isArray(qtiVar.record) && qtiVar.record.length > 0) return true;
4013
+ return false;
4014
+ }
4015
+
4016
+ const initialBoundTo = config.boundTo && config.boundTo[this.responseIdentifier];
4017
+ this.hadResponse = qtiVariableHasValue(initialBoundTo);
4018
+ this.lastResponseStr = this.hadResponse ? JSON.stringify(initialBoundTo) : null;
4019
+ // Ensure expected DOM structure exists (markup + properties)
4020
+ this.markupEl = this.container.querySelector('qti-interaction-markup');
4021
+ if (!this.markupEl) {
4022
+ this.markupEl = document.createElement('qti-interaction-markup');
4023
+ this.container.appendChild(this.markupEl);
4024
+ }
4025
+ this.markupEl.classList.add('qti-customInteraction');
4026
+ this.propertiesEl = this.container.querySelector('properties');
4027
+ if (!this.propertiesEl) {
4028
+ this.propertiesEl = document.createElement('properties');
4029
+ this.propertiesEl.style.display = 'none';
4030
+ this.container.appendChild(this.propertiesEl);
4031
+ } else {
4032
+ this.propertiesEl.style.display = 'none';
4033
+ }
4034
+
4035
+ // Apply any markup/properties that arrived before initialization
4036
+ if (this.pendingMarkup !== null) {
4037
+ this.setMarkup(this.pendingMarkup);
4038
+ this.pendingMarkup = null;
4039
+ }
4040
+ if (this.pendingProperties !== null) {
4041
+ this.setProperties(this.pendingProperties);
4042
+ this.pendingProperties = null;
4043
+ }
4044
+
4045
+ // Bridge qti-interaction-changed events (preferred over polling)
4046
+ if (!this.eventBridgeAttached) {
4047
+ this.eventBridgeAttached = true;
4048
+ const self = this;
4049
+ this.container.addEventListener(
4050
+ 'qti-interaction-changed',
4051
+ function(evt) {
4052
+ try {
4053
+ self.interactionChangedViaEvent = true;
4054
+ const value = evt && evt.detail ? evt.detail.value : undefined;
4055
+ if (value !== undefined) {
4056
+ const state = self.pciInstance && typeof self.pciInstance.getState === 'function' ? self.pciInstance.getState() : null;
4057
+ self.notifyInteractionChanged(value, state);
4058
+ }
4059
+ } catch (e) {
4060
+ // ignore bridge errors, polling fallback may still work
4061
+ }
4062
+ },
4063
+ true
4064
+ );
4065
+ }
4066
+
3928
4067
  function getResolvablePath(path, basePath) {
3929
4068
  if (Array.isArray(path)) {
3930
4069
  return path.map(p => getResolvablePathString(p, basePath));
@@ -3987,23 +4126,33 @@ var QtiPortableCustomInteraction = class extends Interaction {
3987
4126
  this.pciInstance = pciInstance;
3988
4127
  // Configure PCI instance
3989
4128
  const pciConfig = {
3990
- properties: this.addHyphenatedKeys(this.unescapeDataAttributes({ ...config.dataAttributes })),
4129
+ properties: config.properties || {},
3991
4130
  contextVariables: config.contextVariables || {},
3992
4131
  templateVariables: config.templateVariables || {},
3993
4132
  onready: pciInstance => {
3994
4133
  this.pciInstance = pciInstance;
4134
+ // Apply any pending updates that arrived before onready
4135
+ if (this.pendingBoundTo) {
4136
+ this.applyBoundTo(this.pendingBoundTo);
4137
+ this.pendingBoundTo = null;
4138
+ }
4139
+ if (this.pendingState && typeof this.pciInstance.setState === 'function') {
4140
+ this.pciInstance.setState(this.pendingState);
4141
+ this.pendingState = null;
4142
+ }
3995
4143
  this.notifyReady();
3996
4144
  },
3997
- ondone: (pciInstance, response, state, status) => {
3998
- this.notifyInteractionChanged(response);
3999
- },
4000
- responseIdentifier: config.responseIdentifier,
4001
- boundTo: config.boundTo,
4002
- };
4003
-
4004
- if (pciInstance.getInstance) {
4005
- pciInstance.getInstance(this.container, pciConfig, undefined);
4006
- } else {
4145
+ ondone: (pciInstance, response, state, status) => {
4146
+ this.notifyInteractionChanged(response, typeof state === 'string' ? state : null);
4147
+ },
4148
+ responseIdentifier: config.responseIdentifier,
4149
+ boundTo: config.boundTo,
4150
+ };
4151
+
4152
+ if (pciInstance.getInstance) {
4153
+ const dom = this.markupEl || this.container;
4154
+ pciInstance.getInstance(dom, pciConfig, config.state || undefined);
4155
+ } else {
4007
4156
  // TAO custom interaction initialization
4008
4157
  const restoreTAOConfig = (dataset) => {
4009
4158
  const config = {};
@@ -4011,7 +4160,7 @@ var QtiPortableCustomInteraction = class extends Interaction {
4011
4160
  const result = {};
4012
4161
 
4013
4162
  // Separate direct attributes from nested ones
4014
- Object.entries(dataset || []).forEach(([key, value]) => {
4163
+ Object.entries(dataset || {}).forEach(([key, value]) => {
4015
4164
  if (!key.includes('__')) {
4016
4165
  // Direct attributes (like version)
4017
4166
  result[key] = value;
@@ -4021,7 +4170,7 @@ var QtiPortableCustomInteraction = class extends Interaction {
4021
4170
  // Parse nested attributes
4022
4171
  const nestedData = {};
4023
4172
 
4024
- Object.entries(dataset || []).forEach(([key, value]) => {
4173
+ Object.entries(dataset || {}).forEach(([key, value]) => {
4025
4174
  const parts = key.split('__');
4026
4175
  if (parts.length > 1) {
4027
4176
  const [group, index, prop] = parts;
@@ -4054,17 +4203,13 @@ var QtiPortableCustomInteraction = class extends Interaction {
4054
4203
 
4055
4204
  this.pciInstance.initialize(
4056
4205
  this.customInteractionTypeIdentifier,
4057
- this.container.firstElementChild || this.container,
4206
+ (this.markupEl || this.container).firstElementChild || (this.markupEl || this.container),
4058
4207
  Object.keys(taoConfig).length ? taoConfig : null
4059
4208
  );
4060
4209
  }
4061
4210
  },
4062
4211
  notifyReady: () => {
4063
- // Notify parent that the PCI is ready
4064
- window.parent.postMessage({
4065
- source: 'qti-pci-iframe',
4066
- method: 'pciReady'
4067
- }, '*');
4212
+ PCIManager.notifyReady();
4068
4213
  }
4069
4214
  };
4070
4215
  });
@@ -4094,73 +4239,121 @@ var QtiPortableCustomInteraction = class extends Interaction {
4094
4239
  }
4095
4240
  },
4096
4241
 
4097
- notifyReady: function() {
4098
- window.parent.postMessage({
4099
- source: 'qti-pci-iframe',
4100
- method: 'iframeReady'
4101
- }, '*');
4102
- },
4242
+ notifyReady: function() {
4243
+ window.parent.postMessage({
4244
+ source: 'qti-pci-iframe',
4245
+ responseIdentifier: this.responseIdentifier,
4246
+ method: 'iframeReady'
4247
+ }, '*');
4248
+ },
4249
+
4250
+ notifyInteractionChanged: function(response, state) {
4251
+ window.parent.postMessage({
4252
+ source: 'qti-pci-iframe',
4253
+ responseIdentifier: this.responseIdentifier,
4254
+ method: 'interactionChanged',
4255
+ params: { value: response, state: state }
4256
+ }, '*');
4257
+ },
4258
+
4259
+ notifyError: function(message) {
4260
+ console.error('PCI Error:', message);
4261
+ window.parent.postMessage({
4262
+ source: 'qti-pci-iframe',
4263
+ responseIdentifier: this.responseIdentifier,
4264
+ method: 'error',
4265
+ params: { message: message }
4266
+ }, '*');
4267
+ },
4103
4268
 
4104
- notifyInteractionChanged: function(response) {
4105
- window.parent.postMessage({
4106
- source: 'qti-pci-iframe',
4107
- method: 'interactionChanged',
4108
- params: { value: response }
4109
- }, '*');
4269
+ setMarkup: function(markupHtml) {
4270
+ if (!this.container) {
4271
+ this.container = document.getElementById('pci-container');
4272
+ }
4273
+ if (!this.container) {
4274
+ this.pendingMarkup = markupHtml;
4275
+ return;
4276
+ }
4277
+ this.markupEl = this.container.querySelector('qti-interaction-markup');
4278
+ if (!this.markupEl) {
4279
+ this.markupEl = document.createElement('qti-interaction-markup');
4280
+ this.container.appendChild(this.markupEl);
4281
+ }
4282
+ this.markupEl.classList.add('qti-customInteraction');
4283
+ this.markupEl.innerHTML = markupHtml || '';
4110
4284
  },
4111
4285
 
4112
- notifyError: function(message) {
4113
- console.error('PCI Error:', message);
4114
- window.parent.postMessage({
4115
- source: 'qti-pci-iframe',
4116
- method: 'error',
4117
- params: { message: message }
4118
- }, '*');
4286
+ setProperties: function(propertiesHtml) {
4287
+ if (!this.container) {
4288
+ this.container = document.getElementById('pci-container');
4289
+ }
4290
+ if (!this.container) {
4291
+ this.pendingProperties = propertiesHtml;
4292
+ return;
4293
+ }
4294
+ this.propertiesEl = this.container.querySelector('properties');
4295
+ if (!this.propertiesEl) {
4296
+ this.propertiesEl = document.createElement('properties');
4297
+ this.container.appendChild(this.propertiesEl);
4298
+ }
4299
+ this.propertiesEl.style.display = 'none';
4300
+ this.propertiesEl.innerHTML = propertiesHtml || '';
4119
4301
  },
4120
4302
 
4121
- setMarkup: function(markupHtml) {
4122
- this.container = document.getElementById('pci-container');
4123
- this.container.innerHTML = markupHtml;
4303
+ applyBoundTo: function(boundTo) {
4304
+ if (!this.pciInstance || typeof this.pciInstance.setResponse !== 'function') return;
4305
+ const value = boundTo && (boundTo[this.responseIdentifier] || boundTo[Object.keys(boundTo)[0]]);
4306
+ if (value) this.pciInstance.setResponse(value);
4124
4307
  },
4308
+ };
4125
4309
 
4126
- addHyphenatedKeys: function(properties) {
4127
- const updatedProperties = { ...properties };
4128
- for (const key in properties) {
4129
- if (Object.prototype.hasOwnProperty.call(properties, key)) {
4130
- const hyphenatedKey = key.replace(/[A-Z]/g, char => \`-\${char.toLowerCase()}\`);
4131
- updatedProperties[hyphenatedKey] = properties[key];
4132
- }
4310
+ // Set up message listener for communication with parent
4311
+ let expectedParentOrigin = null;
4312
+ window.addEventListener('message', function(event) {
4313
+ const { data } = event;
4314
+
4315
+ // Ensure the message is from our parent
4316
+ if (event.source !== window.parent || !data || data.source !== 'qti-portable-custom-interaction') {
4317
+ return;
4318
+ }
4319
+ if (expectedParentOrigin === null) {
4320
+ expectedParentOrigin = event.origin;
4321
+ } else if (event.origin !== expectedParentOrigin) {
4322
+ return;
4323
+ }
4324
+
4325
+ function deepQuerySelector(root, selector) {
4326
+ if (!root) return null;
4327
+ try {
4328
+ const direct = root.querySelector ? root.querySelector(selector) : null;
4329
+ if (direct) return direct;
4330
+ } catch (e) {
4331
+ // ignore invalid selector for this root
4133
4332
  }
4134
- return updatedProperties;
4135
- },
4136
- unescapeDataAttributes: function(obj) {
4137
- const unescaped = {};
4138
- for (const [key, value] of Object.entries(obj)) {
4139
- if (typeof value === 'string') {
4140
- unescaped[key] = value
4141
- .replace(/&amp;/g, '&')
4142
- .replace(/&lt;/g, '<')
4143
- .replace(/&gt;/g, '>')
4144
- .replace(/&quot;/g, '"')
4145
- .replace(/&#x27;/g, "'")
4146
- .replace(/&#x2F;/g, '/')
4147
- .replace(/&#x60;/g, '\`')
4148
- .replace(/&#x3D;/g, '=');
4149
- } else {
4150
- unescaped[key] = value;
4333
+ if (!root.querySelectorAll) return null;
4334
+ const nodes = root.querySelectorAll('*');
4335
+ for (const node of nodes) {
4336
+ if (node && node.shadowRoot) {
4337
+ const found = deepQuerySelector(node.shadowRoot, selector);
4338
+ if (found) return found;
4151
4339
  }
4152
4340
  }
4153
- return unescaped;
4154
- },
4155
- };
4156
-
4157
- // Set up message listener for communication with parent
4158
- window.addEventListener('message', function(event) {
4159
- const { data } = event;
4341
+ return null;
4342
+ }
4160
4343
 
4161
- // Ensure the message is from our parent
4162
- if (!data || data.source !== 'qti-portable-custom-interaction') {
4163
- return;
4344
+ function deepFindElementByExactText(root, text) {
4345
+ if (!root || !text) return null;
4346
+ if (root.querySelectorAll) {
4347
+ const nodes = root.querySelectorAll('*');
4348
+ for (const node of nodes) {
4349
+ if ((node.textContent || '').trim() === text) return node;
4350
+ if (node.shadowRoot) {
4351
+ const found = deepFindElementByExactText(node.shadowRoot, text);
4352
+ if (found) return found;
4353
+ }
4354
+ }
4355
+ }
4356
+ return null;
4164
4357
  }
4165
4358
 
4166
4359
  switch(data.method) {
@@ -4173,22 +4366,140 @@ var QtiPortableCustomInteraction = class extends Interaction {
4173
4366
  break;
4174
4367
 
4175
4368
  case 'setBoundTo':
4176
- // Handle setting boundTo
4369
+ if (PCIManager.pciInstance) {
4370
+ PCIManager.applyBoundTo(data.params);
4371
+ } else {
4372
+ PCIManager.pendingBoundTo = data.params;
4373
+ }
4177
4374
  break;
4178
4375
 
4179
4376
  case 'setProperties':
4180
- // Handle setting properties
4377
+ PCIManager.setProperties(data.params);
4181
4378
  break;
4379
+
4380
+ case 'setState':
4381
+ if (PCIManager.pciInstance && typeof PCIManager.pciInstance.setState === 'function') {
4382
+ PCIManager.pciInstance.setState((data.params && data.params.state) || data.params);
4383
+ } else {
4384
+ PCIManager.pendingState = (data.params && data.params.state) || data.params;
4385
+ }
4386
+ break;
4387
+
4388
+ case 'getContent': {
4389
+ const messageId = data.params && data.params.messageId;
4390
+ const collectShadowHtml = root => {
4391
+ const parts = [];
4392
+ if (!root || !root.querySelectorAll) return parts;
4393
+ const nodes = root.querySelectorAll('*');
4394
+ for (const node of nodes) {
4395
+ if (node && node.shadowRoot) {
4396
+ parts.push(node.shadowRoot.innerHTML || '');
4397
+ parts.push(...collectShadowHtml(node.shadowRoot));
4398
+ }
4399
+ }
4400
+ return parts;
4401
+ };
4402
+ const shadowHtml = collectShadowHtml(document).join('\\n');
4403
+ window.parent.postMessage(
4404
+ {
4405
+ source: 'qti-pci-iframe',
4406
+ responseIdentifier: PCIManager.responseIdentifier,
4407
+ method: 'getContentResponse',
4408
+ messageId: messageId,
4409
+ content: (document.documentElement ? document.documentElement.outerHTML : '') + '\\n' + shadowHtml
4410
+ },
4411
+ '*'
4412
+ );
4413
+ break;
4414
+ }
4415
+
4416
+ case 'simulateClick': {
4417
+ const messageId = data.params && data.params.messageId;
4418
+ const x = data.params && data.params.x;
4419
+ const y = data.params && data.params.y;
4420
+ const el = typeof x === 'number' && typeof y === 'number' ? document.elementFromPoint(x, y) : null;
4421
+ if (el && typeof el.click === 'function') el.click();
4422
+ window.parent.postMessage(
4423
+ {
4424
+ source: 'qti-pci-iframe',
4425
+ responseIdentifier: PCIManager.responseIdentifier,
4426
+ method: 'clickResponse',
4427
+ messageId: messageId
4428
+ },
4429
+ '*'
4430
+ );
4431
+ break;
4432
+ }
4433
+
4434
+ case 'clickOnSelector': {
4435
+ const messageId = data.params && data.params.messageId;
4436
+ const selector = data.params && data.params.selector;
4437
+ const el = selector ? deepQuerySelector(document, selector) : null;
4438
+ const success = !!el;
4439
+ if (el && typeof el.click === 'function') el.click();
4440
+ window.parent.postMessage(
4441
+ {
4442
+ source: 'qti-pci-iframe',
4443
+ responseIdentifier: PCIManager.responseIdentifier,
4444
+ method: 'clickSelectorResponse',
4445
+ messageId: messageId,
4446
+ success: success
4447
+ },
4448
+ '*'
4449
+ );
4450
+ break;
4451
+ }
4452
+
4453
+ case 'clickOnElementByText': {
4454
+ const messageId = data.params && data.params.messageId;
4455
+ const text = data.params && data.params.text;
4456
+ const target = text ? deepFindElementByExactText(document, text) : null;
4457
+ const success = !!target;
4458
+ if (target && typeof target.click === 'function') target.click();
4459
+ window.parent.postMessage(
4460
+ {
4461
+ source: 'qti-pci-iframe',
4462
+ responseIdentifier: PCIManager.responseIdentifier,
4463
+ method: 'clickTextResponse',
4464
+ messageId: messageId,
4465
+ success: success
4466
+ },
4467
+ '*'
4468
+ );
4469
+ break;
4470
+ }
4471
+
4472
+ case 'setValueElement': {
4473
+ const messageId = data.params && data.params.messageId;
4474
+ const selector = data.params && data.params.selector;
4475
+ const value = data.params && data.params.value;
4476
+ const el = selector ? deepQuerySelector(document, selector) : null;
4477
+ let success = false;
4478
+ if (el && 'value' in el) {
4479
+ try {
4480
+ el.value = value;
4481
+ el.dispatchEvent(new Event('input', { bubbles: true }));
4482
+ el.dispatchEvent(new Event('change', { bubbles: true }));
4483
+ success = true;
4484
+ } catch (e) {
4485
+ success = false;
4486
+ }
4487
+ }
4488
+ window.parent.postMessage(
4489
+ {
4490
+ source: 'qti-pci-iframe',
4491
+ responseIdentifier: PCIManager.responseIdentifier,
4492
+ method: 'setValueResponse',
4493
+ messageId: messageId,
4494
+ success: success
4495
+ },
4496
+ '*'
4497
+ );
4498
+ break;
4499
+ }
4182
4500
  }
4183
4501
  });
4184
4502
 
4185
- // Notify parent that iframe has loaded
4186
- window.addEventListener('load', function() {
4187
- window.parent.postMessage({
4188
- source: 'qti-pci-iframe',
4189
- method: 'iframeLoaded'
4190
- }, '*');
4191
- });
4192
4503
  let resizeTimeout;
4193
4504
  let previousHeight = 0;
4194
4505
  const notifyResize = () => {
@@ -4197,14 +4508,15 @@ var QtiPortableCustomInteraction = class extends Interaction {
4197
4508
  if (newHeight !== previousHeight) {
4198
4509
  previousHeight = newHeight;
4199
4510
  clearTimeout(resizeTimeout);
4200
- resizeTimeout = setTimeout(() => {
4201
- window.parent.postMessage({
4202
- source: 'qti-pci-iframe',
4203
- method: 'resize',
4204
- height: newHeight,
4205
- width: container.scrollWidth
4206
- }, '*');
4207
- }, 100); // Adjust debounce time as needed
4511
+ resizeTimeout = setTimeout(() => {
4512
+ window.parent.postMessage({
4513
+ source: 'qti-pci-iframe',
4514
+ responseIdentifier: PCIManager.responseIdentifier,
4515
+ method: 'resize',
4516
+ height: newHeight,
4517
+ width: container.scrollWidth
4518
+ }, '*');
4519
+ }, 100); // Adjust debounce time as needed
4208
4520
  }
4209
4521
  };
4210
4522
 
@@ -4238,18 +4550,43 @@ var QtiPortableCustomInteraction = class extends Interaction {
4238
4550
  });
4239
4551
  let lastResponseStr = '';
4240
4552
  setInterval(() => {
4553
+ if (PCIManager.interactionChangedViaEvent) return;
4241
4554
  if (PCIManager.pciInstance && PCIManager.pciInstance.getResponse) {
4242
4555
  const response = PCIManager.pciInstance.getResponse();
4556
+ if (response === undefined) {
4557
+ // Don't emit an initial empty on load; only emit a clear if we previously had a value
4558
+ if (!PCIManager.hadResponse) return;
4559
+ PCIManager.hadResponse = false;
4560
+ PCIManager.lastResponseStr = null;
4561
+ const state = PCIManager.pciInstance && typeof PCIManager.pciInstance.getState === 'function' ? PCIManager.pciInstance.getState() : null;
4562
+ window.parent.postMessage(
4563
+ {
4564
+ source: 'qti-pci-iframe',
4565
+ responseIdentifier: PCIManager.responseIdentifier,
4566
+ method: 'interactionChanged',
4567
+ params: { value: null, state: state }
4568
+ },
4569
+ '*'
4570
+ );
4571
+ return;
4572
+ }
4573
+
4243
4574
  const responseStr = JSON.stringify(response);
4244
4575
 
4245
- if (responseStr !== lastResponseStr) {
4246
- lastResponseStr = responseStr;
4247
- window.parent.postMessage({
4248
- source: 'qti-pci-iframe',
4249
- method: 'interactionChanged',
4250
- params: { value: response }
4251
- }, '*');
4252
- }
4576
+ if (responseStr !== PCIManager.lastResponseStr) {
4577
+ PCIManager.lastResponseStr = responseStr;
4578
+ PCIManager.hadResponse = true;
4579
+ const state = PCIManager.pciInstance && typeof PCIManager.pciInstance.getState === 'function' ? PCIManager.pciInstance.getState() : null;
4580
+ window.parent.postMessage(
4581
+ {
4582
+ source: 'qti-pci-iframe',
4583
+ responseIdentifier: PCIManager.responseIdentifier,
4584
+ method: 'interactionChanged',
4585
+ params: { value: response, state: state }
4586
+ },
4587
+ '*'
4588
+ );
4589
+ }
4253
4590
  }
4254
4591
  }, 500); // Check every 500ms
4255
4592
  </script>
@@ -4313,73 +4650,33 @@ var QtiPortableCustomInteraction = class extends Interaction {
4313
4650
  correctResponseViewer.appendChild(clonedChild);
4314
4651
  });
4315
4652
  const correctResponseValue = responseVariable.correctResponse;
4316
- if (this.useIframe) {
4317
- const originalConnectedCallback = correctResponseViewer.connectedCallback;
4318
- correctResponseViewer.connectedCallback = function() {
4319
- originalConnectedCallback.call(this);
4320
- const checkIframeLoaded = () => {
4321
- if (this._iframeLoaded) {
4322
- setTimeout(() => {
4323
- const qtiVariableJSON = this.responseVariablesToQtiVariableJSON(
4324
- correctResponseValue,
4325
- responseVariable.cardinality,
4326
- responseVariable.baseType
4327
- );
4328
- this.sendMessageToIframe("setBoundTo", {
4329
- [originalResponseId]: qtiVariableJSON
4330
- });
4331
- this.sendMessageToIframe("setState", { state: "review" });
4332
- }, 1e3);
4333
- return true;
4334
- }
4335
- return false;
4336
- };
4337
- if (!checkIframeLoaded()) {
4338
- const intervalId = setInterval(() => {
4339
- if (checkIframeLoaded()) {
4340
- clearInterval(intervalId);
4341
- }
4342
- }, 100);
4343
- setTimeout(() => {
4344
- clearInterval(intervalId);
4345
- }, 1e4);
4346
- }
4347
- };
4348
- } else {
4349
- const originalRegister = correctResponseViewer.register;
4350
- correctResponseViewer.register = function(pci) {
4351
- originalRegister.call(this, pci);
4352
- const setCorrectResponse = () => {
4353
- if (this.pci) {
4354
- if (typeof this.pci.setResponse === "function") {
4355
- const pciResponse = this.responseVariablesToQtiVariableJSON(
4356
- correctResponseValue,
4357
- responseVariable.cardinality,
4358
- responseVariable.baseType
4359
- );
4360
- this.pci.setResponse(pciResponse);
4361
- if (typeof this.pci.setState === "function") {
4362
- this.pci.setState("review");
4363
- }
4364
- return true;
4365
- }
4366
- }
4367
- return false;
4368
- };
4653
+ const originalConnectedCallback = correctResponseViewer.connectedCallback;
4654
+ correctResponseViewer.connectedCallback = function() {
4655
+ originalConnectedCallback.call(this);
4656
+ const checkIframeLoaded = () => {
4657
+ if (!this._iframeLoaded) return false;
4369
4658
  setTimeout(() => {
4370
- if (!setCorrectResponse()) {
4371
- const intervalId = setInterval(() => {
4372
- if (setCorrectResponse()) {
4373
- clearInterval(intervalId);
4374
- }
4375
- }, 200);
4376
- setTimeout(() => {
4377
- clearInterval(intervalId);
4378
- }, 5e3);
4379
- }
4380
- }, 500);
4659
+ const qtiVariableJSON = this.responseVariablesToQtiVariableJSON(
4660
+ correctResponseValue,
4661
+ responseVariable.cardinality,
4662
+ responseVariable.baseType
4663
+ );
4664
+ this.sendMessageToIframe("setBoundTo", {
4665
+ [originalResponseId]: qtiVariableJSON
4666
+ });
4667
+ this.sendMessageToIframe("setState", { state: "review" });
4668
+ }, 1e3);
4669
+ return true;
4381
4670
  };
4382
- }
4671
+ if (!checkIframeLoaded()) {
4672
+ const intervalId = setInterval(() => {
4673
+ if (checkIframeLoaded()) clearInterval(intervalId);
4674
+ }, 100);
4675
+ setTimeout(() => {
4676
+ clearInterval(intervalId);
4677
+ }, 1e4);
4678
+ }
4679
+ };
4383
4680
  correctResponseViewer.style.pointerEvents = "none";
4384
4681
  correctResponseContainer.appendChild(correctResponseViewer);
4385
4682
  this.after(correctResponseContainer);
@@ -4393,10 +4690,7 @@ var QtiPortableCustomInteraction = class extends Interaction {
4393
4690
  pointerEvents: this.style.pointerEvents,
4394
4691
  position: this.style.position
4395
4692
  };
4396
- if (this.useIframe) {
4397
- this.sendMessageToIframe("setState", { state: "disabled" });
4398
- } else {
4399
- }
4693
+ this.sendMessageToIframe("setState", { state: "disabled" });
4400
4694
  const existingOverlay = this.querySelector(".pci-interaction-overlay");
4401
4695
  if (!existingOverlay) {
4402
4696
  const overlay = document.createElement("div");
@@ -4433,10 +4727,7 @@ var QtiPortableCustomInteraction = class extends Interaction {
4433
4727
  }
4434
4728
  this._previousState = null;
4435
4729
  }
4436
- if (this.useIframe) {
4437
- this.sendMessageToIframe("setState", { state: "interacting" });
4438
- } else {
4439
- }
4730
+ this.sendMessageToIframe("setState", { state: "interacting" });
4440
4731
  }
4441
4732
  render() {
4442
4733
  return x`
@@ -4448,21 +4739,15 @@ var QtiPortableCustomInteraction = class extends Interaction {
4448
4739
  `;
4449
4740
  }
4450
4741
  };
4451
- // Define a static style that applies to both direct and iframe modes
4742
+ // This implementation always renders inside an iframe.
4452
4743
  QtiPortableCustomInteraction.styles = [
4453
4744
  qti_portable_custom_interaction_styles_default,
4454
- // Add default width/height for direct mode
4455
4745
  i`
4456
4746
  :host {
4457
4747
  display: block;
4458
4748
  width: 100%;
4459
4749
  min-height: 50px;
4460
4750
  }
4461
- .qti-customInteraction {
4462
- display: block;
4463
- width: 100%;
4464
- min-height: 50px;
4465
- }
4466
4751
  `
4467
4752
  ];
4468
4753
  __decorateClass([
@@ -4483,9 +4768,6 @@ __decorateClass([
4483
4768
  __decorateClass([
4484
4769
  n({ type: String, attribute: "data-base-url" })
4485
4770
  ], QtiPortableCustomInteraction.prototype, "baseUrl", 2);
4486
- __decorateClass([
4487
- n({ type: Boolean, attribute: "data-use-iframe" })
4488
- ], QtiPortableCustomInteraction.prototype, "useIframe", 2);
4489
4771
  __decorateClass([
4490
4772
  n({ type: Boolean, attribute: "data-use-default-shims" })
4491
4773
  ], QtiPortableCustomInteraction.prototype, "useDefaultShims", 2);
@@ -5924,4 +6206,4 @@ lit-html/node/directives/ref.js:
5924
6206
  * SPDX-License-Identifier: BSD-3-Clause
5925
6207
  *)
5926
6208
  */
5927
- //# sourceMappingURL=chunk-C2HQFI2C.js.map
6209
+ //# sourceMappingURL=chunk-5D7GD3JS.js.map