@citolab/qti-components 7.0.2 → 7.0.4

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/dist/index.js CHANGED
@@ -1386,6 +1386,7 @@ var qtiTransformTest = () => {
1386
1386
  return new Promise((resolve, _2) => {
1387
1387
  loadXML(uri).then((xml2) => {
1388
1388
  xmlFragment = xml2;
1389
+ api.path(uri.substring(0, uri.lastIndexOf("/")));
1389
1390
  return resolve(api);
1390
1391
  });
1391
1392
  });
@@ -1394,6 +1395,10 @@ var qtiTransformTest = () => {
1394
1395
  xmlFragment = parseXML(xmlString);
1395
1396
  return api;
1396
1397
  },
1398
+ path: (location) => {
1399
+ setLocation(xmlFragment, location);
1400
+ return api;
1401
+ },
1397
1402
  fn(fn) {
1398
1403
  fn(xmlFragment);
1399
1404
  return api;
@@ -1423,28 +1428,12 @@ var TestLoaderMixin = (superClass) => {
1423
1428
  constructor(...args) {
1424
1429
  super(...args);
1425
1430
  this.testURL = "";
1426
- this.addEventListener("qti-load-test-request", (e10) => {
1427
- const { testURL } = e10.detail;
1428
- if (!testURL) {
1429
- console.warn(
1430
- "No test found, there should be an attribute test-url with the path to the test on the test-container"
1431
- );
1432
- } else {
1433
- this.testURL = testURL;
1434
- }
1435
- e10.detail.promise = (async () => {
1436
- e10.preventDefault();
1437
- const api = await qtiTransformTest().load(`${this.testURL}`);
1438
- return api.htmlDoc();
1439
- })();
1440
- });
1441
1431
  this.addEventListener("qti-assessment-test-connected", () => {
1442
1432
  });
1443
1433
  this.addEventListener("qti-load-item-request", ({ detail }) => {
1444
- if (!this.testURL) return;
1445
1434
  detail.promise = (async () => {
1446
1435
  const api = await qtiTransformItem().load(
1447
- `${this.testURL.slice(0, this.testURL.lastIndexOf("/"))}/${detail.href}`,
1436
+ `${detail.href}`,
1448
1437
  detail.cancelPreviousRequest
1449
1438
  );
1450
1439
  return api.htmlDoc();
@@ -1682,18 +1671,6 @@ __decorateClass([
1682
1671
 
1683
1672
  // src/lib/qti-test/qti-test.ts
1684
1673
  var QtiTest = class extends TestLoaderMixin(TestNavigationMixin(TestViewMixin(TestBase))) {
1685
- /**
1686
- * Lifecycle callback invoked when the element is added to the DOM.
1687
- * Automatically appends the content of a `<template>` element (if present)
1688
- * to the shadow DOM.
1689
- */
1690
- connectedCallback() {
1691
- super.connectedCallback();
1692
- const template = this.querySelector(":scope > template");
1693
- if (template) {
1694
- this.shadowRoot?.appendChild(template.content);
1695
- }
1696
- }
1697
1674
  /**
1698
1675
  * Renders the component's template.
1699
1676
  * Provides a default `<slot>` for content projection.
@@ -2018,6 +1995,14 @@ var TestItemLink = class extends TestComponent {
2018
1995
  return x` <slot></slot> `;
2019
1996
  }
2020
1997
  };
1998
+ TestItemLink.styles = i2`
1999
+ :host {
2000
+ ${btn};
2001
+ }
2002
+ :host([disabled]) {
2003
+ ${dis};
2004
+ }
2005
+ `;
2021
2006
  __decorateClass([
2022
2007
  n5({ type: String, attribute: "item-id" })
2023
2008
  ], TestItemLink.prototype, "itemId", 2);
@@ -4560,20 +4545,6 @@ qti-response-declaration {
4560
4545
 
4561
4546
  & qti-gap {
4562
4547
 
4563
- &[enabled] {
4564
-
4565
- /* Light theme override */
4566
- .qti-selections-light & {
4567
- border-color: var(--qti-light-border-active);
4568
- }
4569
-
4570
- /* Dark theme override */
4571
- .qti-selections-dark & {
4572
- border-color: var(--qti-dark-border-active);
4573
- }
4574
- background-color: var(--qti-bg-active)
4575
- }
4576
-
4577
4548
  &[disabled] {
4578
4549
 
4579
4550
  &:not(:empty) {
@@ -4591,6 +4562,20 @@ qti-response-declaration {
4591
4562
  outline: 4px solid var(--qti-disabled-bg)
4592
4563
  }
4593
4564
 
4565
+ &[enabled] {
4566
+
4567
+ /* Light theme override */
4568
+ .qti-selections-light & {
4569
+ border-color: var(--qti-light-border-active);
4570
+ }
4571
+
4572
+ /* Dark theme override */
4573
+ .qti-selections-dark & {
4574
+ border-color: var(--qti-dark-border-active);
4575
+ }
4576
+ background-color: var(--qti-bg-active)
4577
+ }
4578
+
4594
4579
  &[active] {
4595
4580
 
4596
4581
  /* Light theme override */
@@ -4973,12 +4958,22 @@ qti-response-declaration {
4973
4958
  }
4974
4959
 
4975
4960
  qti-match-interaction:not(.qti-match-tabular) {
4961
+ &:state(--dragzone-enabled) qti-simple-match-set:first-of-type {
4962
+ background-color: var(--qti-bg-active);
4963
+ }
4964
+
4965
+ &:state(--dragzone-active) qti-simple-match-set:first-of-type {
4966
+ border-color: var(--qti-border-active);
4967
+ background-color: var(--qti-bg-active);
4968
+ }
4969
+
4976
4970
  /* The draggables */
4977
4971
  & qti-simple-match-set:first-of-type {
4978
4972
  display: flex;
4979
4973
  flex-wrap: wrap;
4980
4974
  align-items: flex-start; /* Prevents children from stretching */
4981
4975
  gap: var(--qti-gap-size);
4976
+ border: 2px solid transparent;
4982
4977
 
4983
4978
  & qti-simple-associable-choice {
4984
4979
 
@@ -5370,6 +5365,16 @@ qti-response-declaration {
5370
5365
  }
5371
5366
 
5372
5367
  qti-associate-interaction {
5368
+ /* General styles for active and enabled states */
5369
+ &:state(--dragzone-active) slot[name='qti-simple-associable-choice'] {
5370
+ border-color: var(--qti-border-active);
5371
+ background-color: var(--qti-bg-active);
5372
+ }
5373
+
5374
+ &:state(--dragzone-enabled) slot[name='qti-simple-associable-choice'] {
5375
+ background-color: var(--qti-bg-active);
5376
+ }
5377
+
5373
5378
  & qti-simple-associable-choice, /* drags when in lightdom */
5374
5379
  &::part(qti-simple-associable-choice) /* drags when in shadowdom */ {
5375
5380
 
@@ -5672,76 +5677,8 @@ qti-response-declaration {
5672
5677
  }
5673
5678
 
5674
5679
  qti-slider-interaction {
5675
- display: block;
5676
-
5677
5680
  --qti-tick-color: rgb(229 231 235 / 100%);
5678
5681
  --qti-tick-width: 1px;
5679
-
5680
- &::part(slider) {
5681
- margin-left: 2rem; /* mx-8 */
5682
- margin-right: 2rem;
5683
- padding-bottom: 1rem; /* pb-4 */
5684
- padding-top: 1.25rem; /* pt-5 */
5685
- }
5686
-
5687
- --show-bounds: true;
5688
-
5689
- &::part(bounds) {
5690
- display: flex;
5691
- width: 100%;
5692
- justify-content: space-between;
5693
- margin-bottom: 0.5rem; /* mb-2 */
5694
- }
5695
-
5696
- --show-ticks: true;
5697
-
5698
- &::part(ticks) {
5699
- margin-left: 0.125rem; /* mx-0.5 */
5700
- margin-right: 0.125rem;
5701
- margin-bottom: 0.25rem; /* mb-1 */
5702
- height: 0.5rem; /* h-2 */
5703
- background: linear-gradient(to right, var(--qti-tick-color) var(--qti-tick-width), transparent 1px) repeat-x 0
5704
- center / calc(calc(100% - var(--qti-tick-width)) / ((var(--max) - var(--min)) / var(--step))) 100%;
5705
- }
5706
-
5707
- &::part(rail) {
5708
- display: flex;
5709
- align-items: center;
5710
- box-sizing: border-box;
5711
- height: 0.375rem; /* h-1.5 */
5712
- width: 100%;
5713
- cursor: pointer;
5714
- border-radius: 9999px; /* rounded-full */
5715
- border: 1px solid #d1d5db; /* border-gray-300 */
5716
- background-color: #e5e7eb; /* bg-gray-200 */
5717
- }
5718
-
5719
- &::part(knob) {
5720
- background-color: var(--qti-primary);
5721
- position: relative;
5722
- height: 1rem; /* h-4 */
5723
- width: 1rem; /* w-4 */
5724
- transform-origin: center;
5725
- transform: translateX(-50%);
5726
- cursor: pointer;
5727
- border-radius: 9999px; /* rounded-full */
5728
- left: var(--value-percentage);
5729
- }
5730
-
5731
- --show-value: true;
5732
-
5733
- &::part(value) {
5734
- position: absolute;
5735
- bottom: 2rem; /* bottom-8 */
5736
- left: 0.5rem; /* left-2 */
5737
- transform: translateX(-50%);
5738
- cursor: pointer;
5739
- border-radius: 0.25rem; /* rounded */
5740
- background-color: #f3f4f6; /* bg-gray-100 */
5741
- padding: 0.25rem 0.5rem; /* px-2 py-1 */
5742
- text-align: center;
5743
- color: #6b7280; /* text-gray-500 */
5744
- }
5745
5682
  }
5746
5683
 
5747
5684
  qti-select-point-interaction {
@@ -5788,62 +5725,72 @@ qti-response-declaration {
5788
5725
  var TestContainer = class extends r4 {
5789
5726
  constructor() {
5790
5727
  super(...arguments);
5791
- this.testURL = "";
5728
+ this.testURL = null;
5729
+ this.testDoc = null;
5730
+ this.testXML = null;
5731
+ /** Template content if provided */
5732
+ this.templateContent = null;
5733
+ }
5734
+ async handleTestURLChange() {
5735
+ if (!this.testURL) return;
5736
+ try {
5737
+ const api = await qtiTransformTest().load(this.testURL);
5738
+ this.testDoc = api.htmlDoc();
5739
+ } catch (error) {
5740
+ console.error("Error loading or parsing XML:", error);
5741
+ }
5742
+ }
5743
+ handleTestXMLChange() {
5744
+ if (!this.testXML) return;
5745
+ try {
5746
+ this.testDoc = qtiTransformTest().parse(this.testXML).htmlDoc();
5747
+ } catch (error) {
5748
+ console.error("Error parsing XML:", error);
5749
+ }
5792
5750
  }
5793
- /**
5794
- * Lifecycle callback invoked when the element is added to the DOM.
5795
- * Handles template preloading and dispatches a `qti-load-test-request` event
5796
- * if no template is found.
5797
- */
5798
5751
  async connectedCallback() {
5799
5752
  super.connectedCallback();
5800
- const template = this.querySelector("template");
5801
- if (template) {
5802
- this.preContent = template.content;
5803
- } else {
5804
- this.preContent = x``;
5805
- const sheet = new CSSStyleSheet();
5806
- sheet.replaceSync(item_default);
5807
- this.shadowRoot.adoptedStyleSheets = [sheet];
5808
- await this.updateComplete;
5809
- const event = new CustomEvent("qti-load-test-request", {
5810
- bubbles: true,
5811
- composed: true,
5812
- detail: {
5813
- promise: null,
5814
- testURL: this.testURL
5815
- }
5816
- });
5817
- const preventDefault = this.dispatchEvent(event);
5818
- if (!preventDefault) {
5819
- console.warn("No qti-load-test-request listener found");
5820
- this.preContent = x`<span>qti-load-test-request was not catched</span>`;
5821
- } else {
5822
- this.content = (async () => {
5823
- return await event.detail.promise;
5824
- })();
5825
- }
5753
+ this.initializeTemplateContent();
5754
+ this.applyStyles();
5755
+ if (this.testURL) {
5756
+ this.handleTestURLChange();
5757
+ }
5758
+ if (this.testXML) {
5759
+ this.handleTestXMLChange();
5826
5760
  }
5827
5761
  }
5828
- /**
5829
- * Renders the component content.
5830
- * Preloaded template content is rendered first, followed by the default slot
5831
- * and dynamically loaded content.
5832
- */
5762
+ initializeTemplateContent() {
5763
+ const template = this.querySelector("template");
5764
+ this.templateContent = template ? template.content : x``;
5765
+ }
5766
+ applyStyles() {
5767
+ const sheet = new CSSStyleSheet();
5768
+ sheet.replaceSync(item_default);
5769
+ this.shadowRoot.adoptedStyleSheets = [sheet];
5770
+ }
5833
5771
  render() {
5834
5772
  return x`
5835
- ${this.preContent}
5773
+ ${this.templateContent}
5836
5774
  <slot></slot>
5837
- ${m4(this.content, x`<span>Loading...</span>`)}
5775
+ ${m4(this.testDoc, x`<span>Loading...</span>`)}
5838
5776
  `;
5839
5777
  }
5840
5778
  };
5841
- __decorateClass([
5842
- r6()
5843
- ], TestContainer.prototype, "content", 2);
5844
5779
  __decorateClass([
5845
5780
  n5({ type: String, attribute: "test-url" })
5846
5781
  ], TestContainer.prototype, "testURL", 2);
5782
+ __decorateClass([
5783
+ r6()
5784
+ ], TestContainer.prototype, "testDoc", 2);
5785
+ __decorateClass([
5786
+ r6()
5787
+ ], TestContainer.prototype, "testXML", 2);
5788
+ __decorateClass([
5789
+ watch("testURL", { waitUntilFirstUpdate: true })
5790
+ ], TestContainer.prototype, "handleTestURLChange", 1);
5791
+ __decorateClass([
5792
+ watch("testXML", { waitUntilFirstUpdate: true })
5793
+ ], TestContainer.prototype, "handleTestXMLChange", 1);
5847
5794
  TestContainer = __decorateClass([
5848
5795
  t4("test-container")
5849
5796
  ], TestContainer);
@@ -6992,49 +6939,89 @@ TestPagingButtonsStamp = __decorateClass([
6992
6939
  t4("test-paging-buttons-stamp")
6993
6940
  ], TestPagingButtonsStamp);
6994
6941
 
6995
- // src/lib/qti-item/qti-item.mixin.ts
6996
- function QtiItemMixin(Base) {
6997
- class QtiItemClass extends Base {
6998
- // the XMLDocument
6999
- createRenderRoot() {
7000
- return this;
6942
+ // src/lib/qti-item/qti-item.ts
6943
+ var QtiItem = class extends r4 {
6944
+ render() {
6945
+ return x`<slot></slot>`;
6946
+ }
6947
+ };
6948
+ QtiItem = __decorateClass([
6949
+ t4("qti-item")
6950
+ ], QtiItem);
6951
+
6952
+ // src/lib/qti-item/components/item-container.ts
6953
+ var ItemContainer = class extends r4 {
6954
+ constructor() {
6955
+ super(...arguments);
6956
+ this.itemURL = null;
6957
+ this.itemDoc = null;
6958
+ this.itemXML = null;
6959
+ /** Template content if provided */
6960
+ this.templateContent = null;
6961
+ }
6962
+ async handleItemURLChange() {
6963
+ if (!this.itemURL) return;
6964
+ try {
6965
+ const api = await qtiTransformItem().load(this.itemURL);
6966
+ this.itemDoc = api.htmlDoc();
6967
+ } catch (error) {
6968
+ console.error("Error loading or parsing XML:", error);
7001
6969
  }
7002
- get assessmentItem() {
7003
- return this.renderRoot?.querySelector("qti-assessment-item");
6970
+ }
6971
+ handleItemXMLChange() {
6972
+ if (!this.itemXML) return;
6973
+ try {
6974
+ this.itemDoc = qtiTransformItem().parse(this.itemXML).htmlDoc();
6975
+ } catch (error) {
6976
+ console.error("Error parsing XML:", error);
7004
6977
  }
7005
- async connectedCallback() {
7006
- super.connectedCallback();
7007
- await this.updateComplete;
7008
- this.dispatchEvent(
7009
- new CustomEvent("qti-item-connected", {
7010
- bubbles: true,
7011
- composed: true,
7012
- detail: { identifier: this.identifier, href: this.href }
7013
- })
7014
- );
6978
+ }
6979
+ async connectedCallback() {
6980
+ super.connectedCallback();
6981
+ this.initializeTemplateContent();
6982
+ this.applyStyles();
6983
+ if (this.itemURL) {
6984
+ this.handleItemURLChange();
7015
6985
  }
7016
- render() {
7017
- return x`${this.xmlDoc}`;
6986
+ if (this.itemXML) {
6987
+ this.handleItemXMLChange();
7018
6988
  }
7019
6989
  }
7020
- __decorateClass([
7021
- n5({ type: String, reflect: true })
7022
- ], QtiItemClass.prototype, "identifier", 2);
7023
- __decorateClass([
7024
- n5({ type: String })
7025
- ], QtiItemClass.prototype, "href", 2);
7026
- __decorateClass([
7027
- n5({ type: Object, attribute: false })
7028
- ], QtiItemClass.prototype, "xmlDoc", 2);
7029
- return QtiItemClass;
7030
- }
7031
-
7032
- // src/lib/qti-item/qti-item.ts
7033
- var QtiItem = class extends QtiItemMixin(r4) {
6990
+ initializeTemplateContent() {
6991
+ const template = this.querySelector("template");
6992
+ this.templateContent = template ? template.content : x``;
6993
+ }
6994
+ applyStyles() {
6995
+ const sheet = new CSSStyleSheet();
6996
+ sheet.replaceSync(item_default);
6997
+ this.shadowRoot.adoptedStyleSheets = [sheet];
6998
+ }
6999
+ render() {
7000
+ return x`
7001
+ ${this.templateContent}
7002
+ <slot></slot>
7003
+ ${m4(this.itemDoc, x`<span>Loading...</span>`)}
7004
+ `;
7005
+ }
7034
7006
  };
7035
- QtiItem = __decorateClass([
7036
- t4("qti-item")
7037
- ], QtiItem);
7007
+ __decorateClass([
7008
+ n5({ type: String, attribute: "item-url" })
7009
+ ], ItemContainer.prototype, "itemURL", 2);
7010
+ __decorateClass([
7011
+ r6()
7012
+ ], ItemContainer.prototype, "itemDoc", 2);
7013
+ __decorateClass([
7014
+ r6()
7015
+ ], ItemContainer.prototype, "itemXML", 2);
7016
+ __decorateClass([
7017
+ watch("itemURL", { waitUntilFirstUpdate: true })
7018
+ ], ItemContainer.prototype, "handleItemURLChange", 1);
7019
+ __decorateClass([
7020
+ watch("itemXML", { waitUntilFirstUpdate: true })
7021
+ ], ItemContainer.prototype, "handleItemXMLChange", 1);
7022
+ ItemContainer = __decorateClass([
7023
+ t4("item-container")
7024
+ ], ItemContainer);
7038
7025
 
7039
7026
  // src/lib/qti-components/qti-assessment-item/qti-assessment-item.context.ts
7040
7027
  var itemContextVariables = [
@@ -7082,7 +7069,10 @@ var QtiAssessmentItem = class extends r4 {
7082
7069
  e10.stopPropagation();
7083
7070
  const feedbackElement = e10.detail;
7084
7071
  this._feedbackElements.push(feedbackElement);
7085
- feedbackElement.checkShowFeedback(feedbackElement.outcomeIdentifier);
7072
+ const numAttempts = Number(this._context.variables.find((v3) => v3.identifier === "numAttempts")?.value) || 0;
7073
+ if (numAttempts > 0) {
7074
+ feedbackElement.checkShowFeedback(feedbackElement.outcomeIdentifier);
7075
+ }
7086
7076
  });
7087
7077
  this.addEventListener("qti-register-interaction", (e10) => {
7088
7078
  e10.stopPropagation();
@@ -7138,9 +7128,6 @@ var QtiAssessmentItem = class extends r4 {
7138
7128
  interactionElement.value = variable.value;
7139
7129
  }
7140
7130
  }
7141
- if (variable.type === "outcome") {
7142
- this._feedbackElements.forEach((fe) => fe.checkShowFeedback(variable.identifier));
7143
- }
7144
7131
  });
7145
7132
  }
7146
7133
  async connectedCallback() {
@@ -7832,7 +7819,6 @@ var QtiFeedback = class extends r4 {
7832
7819
  }
7833
7820
  checkShowFeedback(outcomeIdentifier) {
7834
7821
  const outcomeVariable = this._context.variables.find((v3) => v3.identifier === outcomeIdentifier) || null;
7835
- ;
7836
7822
  if (this.outcomeIdentifier !== outcomeIdentifier || !outcomeVariable) return;
7837
7823
  let isFound = false;
7838
7824
  if (Array.isArray(outcomeVariable.value)) {
@@ -7908,17 +7894,92 @@ QtiFeedbackInline = __decorateClass([
7908
7894
 
7909
7895
  // src/lib/qti-components/qti-feedback/qti-modal-feedback/qti-modal-feedback.ts
7910
7896
  var QtiModalFeedback = class extends QtiFeedback {
7911
- constructor() {
7912
- super(...arguments);
7913
- this.render = () => x` <slot part="feedback" class="${this.showStatus}"></slot> `;
7897
+ render() {
7898
+ return x`
7899
+ <dialog class="qti-dialog" part="feedback" ?open="${this.showStatus === "on"}">
7900
+ <slot></slot>
7901
+ <div style="margin-top: var(--qti-gap-size); text-align: center;">
7902
+ <button class="button close-button" @click="${this.closeFeedback}"></button>
7903
+ </div>
7904
+ </dialog>
7905
+ `;
7906
+ }
7907
+ openFeedback() {
7908
+ const dialog = this.shadowRoot?.querySelector("dialog");
7909
+ if (dialog && !dialog.open) {
7910
+ dialog.showModal();
7911
+ }
7912
+ }
7913
+ closeFeedback() {
7914
+ const dialog = this.shadowRoot?.querySelector("dialog");
7915
+ if (dialog && dialog.open) {
7916
+ dialog.close();
7917
+ this.showStatus = "off";
7918
+ }
7919
+ }
7920
+ firstUpdated() {
7921
+ if (this.showStatus === "on") {
7922
+ this.openFeedback();
7923
+ }
7924
+ }
7925
+ updated(changedProperties) {
7926
+ if (changedProperties.has("showStatus")) {
7927
+ if (this.showStatus === "on") {
7928
+ this.openFeedback();
7929
+ } else {
7930
+ this.closeFeedback();
7931
+ }
7932
+ }
7914
7933
  }
7915
7934
  };
7916
7935
  QtiModalFeedback.styles = i2`
7917
- .on {
7936
+ .qti-dialog {
7937
+ background: var(--qti-bg);
7938
+ border: var(--qti-border-thickness) var(--qti-border-style) var(--qti-border-color);
7939
+ border-radius: var(--qti-border-radius);
7940
+ padding: var(--qti-padding-vertical) var(--qti-padding-horizontal);
7941
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
7942
+ position: fixed;
7943
+ top: 50%;
7944
+ left: 50%;
7945
+ transform: translate(-50%, -50%);
7946
+ z-index: 1000;
7947
+ width: auto;
7948
+ max-width: 90%;
7949
+ }
7950
+
7951
+ .button {
7952
+ border-radius: var(--qti-border-radius);
7953
+ padding: var(--qti-padding-vertical) var(--qti-padding-horizontal);
7954
+ background-color: var(--qti-bg-active);
7955
+ border: var(--qti-border-active);
7956
+ cursor: pointer;
7957
+ position: relative;
7918
7958
  display: inline-block;
7919
7959
  }
7920
- .off {
7921
- display: none;
7960
+
7961
+ .button:hover {
7962
+ background-color: var(--qti-hover-bg);
7963
+ }
7964
+
7965
+ .button:disabled {
7966
+ background-color: var(--qti-disabled-bg);
7967
+ color: var(--qti-disabled-color);
7968
+ cursor: not-allowed;
7969
+ }
7970
+
7971
+ .button:focus {
7972
+ outline: var(--qti-focus-border-width) solid var(--qti-focus-color);
7973
+ }
7974
+
7975
+ /* Text content of the button */
7976
+ .button::after {
7977
+ content: var(--qti-close-text, 'Close');
7978
+ color: inherit;
7979
+ font-size: inherit;
7980
+ text-align: center;
7981
+ display: inline-block;
7982
+ line-height: 1.5;
7922
7983
  }
7923
7984
  `;
7924
7985
  QtiModalFeedback = __decorateClass([
@@ -8911,6 +8972,85 @@ QtiChoiceInteraction = __decorateClass([
8911
8972
  t4("qti-choice-interaction")
8912
8973
  ], QtiChoiceInteraction);
8913
8974
 
8975
+ // src/lib/qti-components/qti-interaction/qti-upload-interaction/qti-upload-interaction.ts
8976
+ var QtiUploadInteraction = class extends Interaction {
8977
+ constructor() {
8978
+ super(...arguments);
8979
+ this._file = null;
8980
+ this._base64 = null;
8981
+ }
8982
+ reset() {
8983
+ this._file = null;
8984
+ this._base64 = null;
8985
+ this.saveResponse(null);
8986
+ }
8987
+ validate() {
8988
+ return this._base64 !== null;
8989
+ }
8990
+ get value() {
8991
+ return this._base64;
8992
+ }
8993
+ set value(base64) {
8994
+ if (typeof base64 === "string") {
8995
+ this._base64 = base64;
8996
+ this.saveResponse(base64);
8997
+ } else if (base64 === null) {
8998
+ this.reset();
8999
+ } else {
9000
+ throw new Error("Value must be a Base64-encoded string or null");
9001
+ }
9002
+ }
9003
+ static get properties() {
9004
+ return {
9005
+ ...Interaction.properties
9006
+ };
9007
+ }
9008
+ render() {
9009
+ return x`
9010
+ <div>
9011
+ <slot name="prompt"></slot>
9012
+ <input type="file" @change="${this._onFileChange}" ?disabled="${this.disabled}" ?readonly="${this.readonly}" />
9013
+ </div>
9014
+ `;
9015
+ }
9016
+ async _onFileChange(event) {
9017
+ const input = event.target;
9018
+ if (input.files && input.files.length > 0) {
9019
+ this._file = input.files[0];
9020
+ this._base64 = await this._convertToBase64(this._file);
9021
+ this.saveResponse(this._base64);
9022
+ this.dispatchEvent(
9023
+ new CustomEvent("qti-interaction-response", {
9024
+ detail: { response: this._base64 }
9025
+ })
9026
+ );
9027
+ }
9028
+ }
9029
+ _convertToBase64(file) {
9030
+ return new Promise((resolve, reject) => {
9031
+ const reader = new FileReader();
9032
+ reader.onload = () => resolve(reader.result);
9033
+ reader.onerror = () => reject(reader.error);
9034
+ reader.readAsDataURL(file);
9035
+ });
9036
+ }
9037
+ };
9038
+ QtiUploadInteraction.styles = [
9039
+ i2`
9040
+ :host {
9041
+ display: block;
9042
+ margin: 1em 0;
9043
+ }
9044
+ input[type='file'] {
9045
+ display: block;
9046
+ margin-top: 0.5em;
9047
+ }
9048
+ `
9049
+ ];
9050
+ QtiUploadInteraction = __decorateClass([
9051
+ t4("qti-upload-interaction")
9052
+ ], QtiUploadInteraction);
9053
+
8914
9054
  // src/lib/qti-components/qti-outcome-processing/qti-outcome-processing.ts
8915
9055
  var QtiOutcomeProcessing = class extends r4 {
8916
9056
  render() {
@@ -10493,44 +10633,41 @@ var TouchDragAndDrop = class _TouchDragAndDrop {
10493
10633
  findClosestDropzone() {
10494
10634
  if (!this.dragSource || this.allDropzones.length === 0) return null;
10495
10635
  const dragRect = this.dragSource.getBoundingClientRect();
10496
- const dragCorners = this.getCorners(dragRect);
10497
- const dragCenter = this.getCenter(dragRect);
10498
10636
  let closestDropzone = null;
10499
- let minDistance = Infinity;
10637
+ let maxOverlapArea = 0;
10500
10638
  for (const dropzone of this.allDropzones) {
10501
10639
  const dropRect = dropzone.getBoundingClientRect();
10502
- const dropCorners = this.getCorners(dropRect);
10503
- const dropCenter = this.getCenter(dropRect);
10504
- const cornerDistance = this.calculateTotalCornerDistance(dragCorners, dropCorners) / this.getRectDiagonal(dropRect);
10505
- const centerDistance = this.calculateDistance(dragCenter, dropCenter);
10506
- const totalDistance = cornerDistance * 0.5 + centerDistance * 0.5;
10507
- if (totalDistance < minDistance) {
10508
- minDistance = totalDistance;
10640
+ const overlapArea = this.calculateOverlapArea(dragRect, dropRect);
10641
+ if (overlapArea > maxOverlapArea) {
10642
+ maxOverlapArea = overlapArea;
10509
10643
  closestDropzone = dropzone;
10510
10644
  }
10511
10645
  }
10646
+ if (maxOverlapArea === 0) {
10647
+ const dragCenter = this.getCenter(dragRect);
10648
+ let minDistance = Infinity;
10649
+ for (const dropzone of this.allDropzones) {
10650
+ const dropCenter = this.getCenter(dropzone.getBoundingClientRect());
10651
+ const distance = this.calculateDistance(dragCenter, dropCenter);
10652
+ if (distance < minDistance) {
10653
+ minDistance = distance;
10654
+ closestDropzone = dropzone;
10655
+ }
10656
+ }
10657
+ }
10512
10658
  return closestDropzone;
10513
10659
  }
10660
+ calculateOverlapArea(rect1, rect2) {
10661
+ const xOverlap = Math.max(0, Math.min(rect1.right, rect2.right) - Math.max(rect1.left, rect2.left));
10662
+ const yOverlap = Math.max(0, Math.min(rect1.bottom, rect2.bottom) - Math.max(rect1.top, rect2.top));
10663
+ return xOverlap * yOverlap;
10664
+ }
10514
10665
  getCenter(rect) {
10515
10666
  return {
10516
10667
  x: rect.left + rect.width / 2,
10517
10668
  y: rect.top + rect.height / 2
10518
10669
  };
10519
10670
  }
10520
- getRectDiagonal(rect) {
10521
- return Math.sqrt(rect.width ** 2 + rect.height ** 2);
10522
- }
10523
- getCorners(rect) {
10524
- return {
10525
- topLeft: { x: rect.left, y: rect.top },
10526
- topRight: { x: rect.right, y: rect.top },
10527
- bottomLeft: { x: rect.left, y: rect.bottom },
10528
- bottomRight: { x: rect.right, y: rect.bottom }
10529
- };
10530
- }
10531
- calculateTotalCornerDistance(cornersA, cornersB) {
10532
- return this.calculateDistance(cornersA.topLeft, cornersB.topLeft) + this.calculateDistance(cornersA.topRight, cornersB.topRight) + this.calculateDistance(cornersA.bottomLeft, cornersB.bottomLeft) + this.calculateDistance(cornersA.bottomRight, cornersB.bottomRight);
10533
- }
10534
10671
  calculateDistance(pointA, pointB) {
10535
10672
  const dx = pointA.x - pointB.x;
10536
10673
  const dy = pointA.y - pointB.y;
@@ -10702,12 +10839,9 @@ var DroppablesMixin = (superClass, droppablesSelector) => {
10702
10839
  ev.preventDefault();
10703
10840
  }
10704
10841
  dragoverHandler(ev) {
10705
- const responseIdentifierDraggable = ev.dataTransfer.getData("responseIdentifier");
10706
10842
  ev.preventDefault();
10707
- if (responseIdentifierDraggable === this.responseIdentifier) {
10708
- this.activateDroppable(ev.currentTarget);
10709
- ev.dataTransfer.dropEffect = "move";
10710
- }
10843
+ this.activateDroppable(ev.currentTarget);
10844
+ ev.dataTransfer.dropEffect = "move";
10711
10845
  return false;
10712
10846
  }
10713
10847
  activateDroppable(droppable) {
@@ -10731,16 +10865,10 @@ var DroppablesMixin = (superClass, droppablesSelector) => {
10731
10865
  }
10732
10866
  findDraggable(responseIdentifier, identifier) {
10733
10867
  if (!identifier) return null;
10734
- if (responseIdentifier === this.responseIdentifier) {
10735
- return this.querySelector(`[identifier=${identifier}]`) || this.shadowRoot.querySelector(`[identifier=${identifier}]`);
10736
- } else {
10737
- const assessmentItem = this.closest("qti-assessment-item");
10738
- const interaction = assessmentItem.querySelector(`[response-identifier=${responseIdentifier}]`);
10739
- return interaction.querySelector(`[identifier=${identifier}]`);
10740
- }
10868
+ return this.querySelector(`[identifier=${identifier}]`) || this.shadowRoot.querySelector(`[identifier=${identifier}]`);
10741
10869
  }
10742
10870
  isValidDrop(_droppable, _draggable, responseIdentifierDraggable) {
10743
- return this.responseIdentifier === responseIdentifierDraggable;
10871
+ return true;
10744
10872
  }
10745
10873
  async moveDraggableToDroppable(draggable, droppable) {
10746
10874
  console.log(`moveDraggableToDroppable, draggable: ${draggable.tagName}, droppable: ${droppable.tagName}`);
@@ -10856,6 +10984,8 @@ var DragDropInteractionMixin = (superClass, draggablesSelector, droppablesSelect
10856
10984
  constructor() {
10857
10985
  super(...arguments);
10858
10986
  this.draggables = /* @__PURE__ */ new Map();
10987
+ this.observer = null;
10988
+ this.resizeObserver = null;
10859
10989
  this.configuration = {
10860
10990
  copyStylesDragClone: true,
10861
10991
  dragCanBePlacedBack: true,
@@ -10895,11 +11025,11 @@ var DragDropInteractionMixin = (superClass, draggablesSelector, droppablesSelect
10895
11025
  };
10896
11026
  this.handleDragEnd = async (ev) => {
10897
11027
  ev.preventDefault();
11028
+ const draggable = ev.currentTarget;
10898
11029
  this._internals.states.delete("--dragzone-enabled");
10899
11030
  this._internals.states.delete("--dragzone-active");
10900
11031
  this.deactivateDragLocation();
10901
11032
  this.deactivateDroppables();
10902
- const draggable = ev.currentTarget;
10903
11033
  draggable.removeAttribute("dragging");
10904
11034
  const wasDropped = await this.wasDropped(ev);
10905
11035
  if (!wasDropped) {
@@ -10941,6 +11071,12 @@ var DragDropInteractionMixin = (superClass, draggablesSelector, droppablesSelect
10941
11071
  this.dragContainersModified(dragContainers, []);
10942
11072
  this.droppablesModified(droppables, []);
10943
11073
  this.draggablesModified(draggables, []);
11074
+ this.updateMinDimensionsForDropZones();
11075
+ this.observer = new MutationObserver(() => this.updateMinDimensionsForDropZones());
11076
+ this.observer.observe(this, { childList: true, subtree: true });
11077
+ this.resizeObserver = new ResizeObserver(() => this.updateMinDimensionsForDropZones());
11078
+ const gapTexts = this.querySelectorAll("qti-gap-text");
11079
+ gapTexts.forEach((gapText) => this.resizeObserver?.observe(gapText));
10944
11080
  }
10945
11081
  connectedCallback() {
10946
11082
  super.connectedCallback();
@@ -10972,6 +11108,26 @@ var DragDropInteractionMixin = (superClass, draggablesSelector, droppablesSelect
10972
11108
  draggable.addEventListener("dragstart", this.handleDragStart);
10973
11109
  draggable.addEventListener("dragend", this.handleDragEnd);
10974
11110
  }
11111
+ updateMinDimensionsForDropZones() {
11112
+ const gapTexts = this.querySelectorAll(draggablesSelector);
11113
+ const gaps = Array.from(this.querySelectorAll(droppablesSelector)).map((d3) => d3);
11114
+ let maxHeight = 0;
11115
+ let maxWidth = 0;
11116
+ gapTexts.forEach((gapText) => {
11117
+ const rect = gapText.getBoundingClientRect();
11118
+ maxHeight = Math.max(maxHeight, rect.height);
11119
+ maxWidth = Math.max(maxWidth, rect.width);
11120
+ });
11121
+ const dragContainer = this.querySelector(dragContainersSelector) || this.shadowRoot?.querySelector(dragContainersSelector);
11122
+ if (dragContainer) {
11123
+ dragContainer.style.minHeight = `${maxHeight}px`;
11124
+ dragContainer.style.minWidth = `${maxWidth}px`;
11125
+ }
11126
+ for (const gap of gaps) {
11127
+ gap.style.minHeight = `${maxHeight}px`;
11128
+ gap.style.minWidth = `${maxWidth}px`;
11129
+ }
11130
+ }
10975
11131
  activateDroppables(target) {
10976
11132
  const dragContainers = this.dragDropApi.dragContainers;
10977
11133
  dragContainers.forEach((d3) => {
@@ -11004,13 +11160,12 @@ var DragDropInteractionMixin = (superClass, draggablesSelector, droppablesSelect
11004
11160
  });
11005
11161
  this.dragDropApi.droppables.forEach((d3) => d3.removeAttribute("enabled"));
11006
11162
  }
11007
- async wasDropped(ev) {
11008
- return ev.dataTransfer.dropEffect !== "none";
11163
+ wasDropped(ev) {
11164
+ return ev.dataTransfer.dropEffect && ev.dataTransfer.dropEffect !== "none";
11009
11165
  }
11010
11166
  async restoreInitialDraggablePosition(draggable) {
11011
11167
  const { parent, index } = this.draggables.get(draggable);
11012
11168
  const moveDraggable = (draggable2, parent2, index2) => {
11013
- console.log("moveDraggable", draggable2, parent2, index2);
11014
11169
  const targetIndex = Math.min(index2, parent2.children.length);
11015
11170
  parent2.insertBefore(draggable2, parent2.children[targetIndex]);
11016
11171
  draggable2.style.transform = "translate(0, 0)";
@@ -11020,8 +11175,24 @@ var DragDropInteractionMixin = (superClass, draggablesSelector, droppablesSelect
11020
11175
  moveDraggable(draggable, parent, index);
11021
11176
  return;
11022
11177
  }
11178
+ const transition = document.startViewTransition(() => {
11179
+ draggable.style.transform = "";
11180
+ moveDraggable(draggable, parent, index);
11181
+ });
11182
+ }
11183
+ disconnectedCallback() {
11184
+ super.disconnectedCallback();
11185
+ if (this.observer) {
11186
+ this.observer.disconnect();
11187
+ this.observer = null;
11188
+ }
11189
+ if (this.resizeObserver) {
11190
+ this.resizeObserver.disconnect();
11191
+ this.resizeObserver = null;
11192
+ }
11023
11193
  }
11024
11194
  validate() {
11195
+ if (!this.shadowRoot) return false;
11025
11196
  const validAssociations = this.getValidAssociations();
11026
11197
  let isValid = true;
11027
11198
  let validityMessage = "";
@@ -11074,8 +11245,8 @@ var DragDropInteractionMixin = (superClass, draggablesSelector, droppablesSelect
11074
11245
  set value(value) {
11075
11246
  if (this.isMatchTabular()) return;
11076
11247
  this.resetDroppables();
11077
- value?.forEach((entry) => this.placeResponse(entry));
11078
11248
  if (Array.isArray(value)) {
11249
+ value?.forEach((entry) => this.placeResponse(entry));
11079
11250
  const formData = new FormData();
11080
11251
  value.forEach((response) => {
11081
11252
  formData.append(this.responseIdentifier, response);
@@ -11191,6 +11362,7 @@ var qti_associate_interaction_styles_default = i2`
11191
11362
  display: flex;
11192
11363
  flex-wrap: wrap;
11193
11364
  gap: 0.5rem;
11365
+ border: 2px solid transparent;
11194
11366
  }
11195
11367
 
11196
11368
  :host::part(associables-container) {
@@ -11494,57 +11666,12 @@ var QtiGapMatchInteraction = class extends DragDropInteractionMixin(
11494
11666
  "qti-gap",
11495
11667
  `slot[part='drags']`
11496
11668
  ) {
11497
- constructor() {
11498
- super(...arguments);
11499
- this.observer = null;
11500
- this.resizeObserver = null;
11501
- }
11502
11669
  render() {
11503
11670
  return x`<slot name="prompt"> </slot>
11504
11671
  <slot part="drags" name="drags"></slot>
11505
11672
  <slot part="drops"></slot>
11506
11673
  <div role="alert" id="validationMessage"></div>`;
11507
11674
  }
11508
- firstUpdated(_changedProperties) {
11509
- super.firstUpdated(_changedProperties);
11510
- this.updateMinDimensionsForDrowZones();
11511
- this.observer = new MutationObserver(() => this.updateMinDimensionsForDrowZones());
11512
- this.observer.observe(this, { childList: true, subtree: true });
11513
- this.resizeObserver = new ResizeObserver(() => this.updateMinDimensionsForDrowZones());
11514
- const gapTexts = this.querySelectorAll("qti-gap-text");
11515
- gapTexts.forEach((gapText) => this.resizeObserver?.observe(gapText));
11516
- }
11517
- disconnectedCallback() {
11518
- super.disconnectedCallback();
11519
- if (this.observer) {
11520
- this.observer.disconnect();
11521
- this.observer = null;
11522
- }
11523
- if (this.resizeObserver) {
11524
- this.resizeObserver.disconnect();
11525
- this.resizeObserver = null;
11526
- }
11527
- }
11528
- updateMinDimensionsForDrowZones() {
11529
- const gapTexts = this.querySelectorAll("qti-gap-text");
11530
- const gaps = this.querySelectorAll("qti-gap");
11531
- let maxHeight = 0;
11532
- let maxWidth = 0;
11533
- gapTexts.forEach((gapText) => {
11534
- const rect = gapText.getBoundingClientRect();
11535
- maxHeight = Math.max(maxHeight, rect.height);
11536
- maxWidth = Math.max(maxWidth, rect.width);
11537
- });
11538
- const dragSlot = this.shadowRoot?.querySelector('[part="drags"]');
11539
- if (dragSlot) {
11540
- dragSlot.style.minHeight = `${maxHeight}px`;
11541
- dragSlot.style.minWidth = `${maxWidth}px`;
11542
- }
11543
- for (const gap of gaps) {
11544
- gap.style.minHeight = `${maxHeight}px`;
11545
- gap.style.minWidth = `${maxWidth}px`;
11546
- }
11547
- }
11548
11675
  set correctResponse(value) {
11549
11676
  let matches = [];
11550
11677
  const response = Array.isArray(value) ? value : [value];
@@ -11914,47 +12041,12 @@ var QtiGraphicGapMatchInteraction = class extends DragDropInteractionMixin(
11914
12041
  "qti-associable-hotspot",
11915
12042
  `slot[part='drags']`
11916
12043
  ) {
11917
- constructor() {
11918
- super(...arguments);
11919
- this.observer = null;
11920
- this.resizeObserver = null;
11921
- }
11922
12044
  render() {
11923
12045
  return x` <slot name="prompt"></slot>
11924
12046
  <slot part="image"></slot>
11925
12047
  <slot part="drags" name="drags" class="hover-border"></slot>
11926
12048
  <div role="alert" id="validationMessage"></div>`;
11927
12049
  }
11928
- firstUpdated(_changedProperties) {
11929
- super.firstUpdated(_changedProperties);
11930
- this.updateMinDimensionsForDrowZones();
11931
- this.observer = new MutationObserver(() => this.updateMinDimensionsForDrowZones());
11932
- this.observer.observe(this, { childList: true, subtree: true });
11933
- this.resizeObserver = new ResizeObserver(() => this.updateMinDimensionsForDrowZones());
11934
- const draggableGaps = this.querySelectorAll("qti-gap-img, qti-gap-text");
11935
- draggableGaps.forEach((gapText) => this.resizeObserver?.observe(gapText));
11936
- }
11937
- updateMinDimensionsForDrowZones() {
11938
- const draggableGaps = this.querySelectorAll("qti-gap-img, qti-gap-text");
11939
- const gaps = this.querySelectorAll("qti-associable-hotspot");
11940
- let maxHeight = 0;
11941
- let maxWidth = 0;
11942
- draggableGaps.forEach((gapText) => {
11943
- const rect = gapText.getBoundingClientRect();
11944
- maxHeight = Math.max(maxHeight, rect.height);
11945
- maxWidth = Math.max(maxWidth, rect.width);
11946
- });
11947
- const slots = this.shadowRoot.querySelectorAll("slot");
11948
- const dragSlot = Array.from(slots).find((slot) => slot.name === "drags");
11949
- if (dragSlot) {
11950
- dragSlot.style.minHeight = `${maxHeight}px`;
11951
- dragSlot.style.minWidth = `${maxWidth}px`;
11952
- }
11953
- for (const gap of gaps) {
11954
- gap.style.minHeight = `${maxHeight}px`;
11955
- gap.style.minWidth = `${maxWidth}px`;
11956
- }
11957
- }
11958
12050
  positionHotspotOnRegister(e10) {
11959
12051
  const hotspot = e10.target;
11960
12052
  const coords = hotspot.getAttribute("coords");
@@ -12188,7 +12280,7 @@ QtiSimpleAssociableChoice = __decorateClass([
12188
12280
 
12189
12281
  // src/lib/qti-components/qti-interaction/qti-match-interaction/qti-match-interaction.styles.ts
12190
12282
  var qti_match_interaction_styles_default = i2`
12191
- slot {
12283
+ slot:not([hidden]) {
12192
12284
  /* slot where the */
12193
12285
  display: flex;
12194
12286
  flex-direction: column;
@@ -12219,16 +12311,17 @@ var qti_match_interaction_styles_default = i2`
12219
12311
  // src/lib/qti-components/qti-interaction/qti-match-interaction/qti-match-interaction.ts
12220
12312
  var QtiMatchInteraction = class extends DragDropInteractionMixin(
12221
12313
  Interaction,
12222
- "qti-simple-match-set:first-of-type qti-simple-associable-choice",
12223
- "qti-simple-match-set:last-of-type qti-simple-associable-choice",
12224
- "qti-simple-match-set"
12314
+ "qti-simple-match-set:first-of-type qti-simple-associable-choice, qti-simple-match-set:last-of-type > qti-simple-associable-choice > qti-simple-associable-choice",
12315
+ "qti-simple-match-set:last-of-type > qti-simple-associable-choice",
12316
+ "qti-simple-match-set:first-of-type"
12225
12317
  ) {
12226
12318
  constructor() {
12227
12319
  super(...arguments);
12228
12320
  this.lastCheckedRadio = null;
12321
+ this.class = "";
12229
12322
  this._response = [];
12230
- this.correctOptions = [];
12231
12323
  this.responseIdentifier = "";
12324
+ this.correctOptions = [];
12232
12325
  this.handleRadioClick = (e10) => {
12233
12326
  const radio = e10.target;
12234
12327
  if (this.lastCheckedRadio === radio) {
@@ -12299,22 +12392,22 @@ var QtiMatchInteraction = class extends DragDropInteractionMixin(
12299
12392
  }
12300
12393
  }
12301
12394
  render() {
12302
- if (!this.classList.contains("qti-match-tabular")) {
12303
- return x`<slot name="prompt"></slot> <slot></slot>
12304
- <div role="alert" id="validationMessage"></div>`;
12305
- }
12395
+ const isTabular = this.class.split(" ").includes("qti-match-tabular");
12306
12396
  return x`
12307
12397
  <slot name="prompt"></slot>
12308
- <table>
12309
- <tr>
12310
- <td></td>
12311
- ${this.cols.map((col) => x`<th part="r-header">${o9(col.innerHTML)}</th>`)}
12312
- </tr>
12398
+ <slot ?hidden=${isTabular}></slot>
12399
+
12400
+ ${isTabular ? x`
12401
+ <table>
12402
+ <tr>
12403
+ <td></td>
12404
+ ${this.cols.map((col) => x`<th part="r-header">${o9(col.innerHTML)}</th>`)}
12405
+ </tr>
12313
12406
 
12314
- ${this.rows.map(
12407
+ ${this.rows.map(
12315
12408
  (row) => x`<tr>
12316
- <td part="c-header">${o9(row.innerHTML)}</td>
12317
- ${this.cols.map((col) => {
12409
+ <td part="c-header">${o9(row.innerHTML)}</td>
12410
+ ${this.cols.map((col) => {
12318
12411
  const rowId = row.getAttribute("identifier");
12319
12412
  const colId = col.getAttribute("identifier");
12320
12413
  const value = `${rowId} ${colId}`;
@@ -12323,33 +12416,39 @@ var QtiMatchInteraction = class extends DragDropInteractionMixin(
12323
12416
  const part = `rb ${checked ? "rb-checked" : ""} ${this.correctOptions.includes(value) ? "rb-correct" : ""}`;
12324
12417
  const disable = this.correctOptions.length > 0 ? true : row.matchMax === 1 ? false : selectedInRowCount >= row.matchMax && !checked;
12325
12418
  return x`<td>
12326
- <input
12327
- type=${row.matchMax === 1 ? "radio" : `checkbox`}
12328
- part=${part}
12329
- name=${rowId}
12330
- value=${value}
12331
- .disabled=${disable}
12332
- @change=${(e10) => this.handleRadioChange(e10)}
12333
- @click=${(e10) => row.matchMax === 1 ? this.handleRadioClick(e10) : null}
12334
- />
12335
- </td>`;
12419
+ <input
12420
+ type=${row.matchMax === 1 ? "radio" : `checkbox`}
12421
+ part=${part}
12422
+ name=${rowId}
12423
+ value=${value}
12424
+ .disabled=${disable}
12425
+ @change=${(e10) => this.handleRadioChange(e10)}
12426
+ @click=${(e10) => row.matchMax === 1 ? this.handleRadioClick(e10) : null}
12427
+ />
12428
+ </td>`;
12336
12429
  })}
12337
- </tr>`
12430
+ </tr>`
12338
12431
  )}
12339
- </table>
12432
+ </table>
12433
+ ` : E}
12434
+
12435
+ <div role="alert" id="validationMessage"></div>
12340
12436
  `;
12341
12437
  }
12342
12438
  };
12343
12439
  QtiMatchInteraction.styles = qti_match_interaction_styles_default;
12344
12440
  __decorateClass([
12345
- r6()
12346
- ], QtiMatchInteraction.prototype, "_response", 2);
12441
+ n5({ type: String })
12442
+ ], QtiMatchInteraction.prototype, "class", 2);
12347
12443
  __decorateClass([
12348
12444
  r6()
12349
- ], QtiMatchInteraction.prototype, "correctOptions", 2);
12445
+ ], QtiMatchInteraction.prototype, "_response", 2);
12350
12446
  __decorateClass([
12351
12447
  n5({ type: String, attribute: "response-identifier" })
12352
12448
  ], QtiMatchInteraction.prototype, "responseIdentifier", 2);
12449
+ __decorateClass([
12450
+ r6()
12451
+ ], QtiMatchInteraction.prototype, "correctOptions", 2);
12353
12452
  QtiMatchInteraction = __decorateClass([
12354
12453
  t4("qti-match-interaction")
12355
12454
  ], QtiMatchInteraction);
@@ -12748,187 +12847,177 @@ QtiSelectPointInteraction = __decorateClass([
12748
12847
  t4("qti-select-point-interaction")
12749
12848
  ], QtiSelectPointInteraction);
12750
12849
 
12751
- // src/lib/qti-components/qti-interaction/qti-slider-interaction/qti-slider-interaction.ts
12752
- var QtiSliderInteraction = class extends Interaction {
12753
- // static shadowRootOptions: ShadowRootInit = { ...LitElement.shadowRootOptions, delegatesFocus: true, mode: 'open' };
12754
- constructor() {
12755
- super();
12756
- this._value = 0;
12757
- this.stepLabel = false;
12758
- this.reverse = false;
12759
- this._handleDisabledChange = () => {
12760
- };
12761
- this._handleReadonlyChange = () => {
12762
- };
12763
- this.csLive = getComputedStyle(this);
12850
+ // src/lib/qti-components/qti-interaction/qti-slider-interaction/qti-slider-interaction.styles.ts
12851
+ var qti_slider_interaction_styles_default = i2`
12852
+ :host {
12853
+ display: block;
12854
+ --show-bounds: true;
12855
+ --show-ticks: true;
12856
+ --show-value: true;
12764
12857
  }
12765
- set min(value) {
12766
- this._min = value;
12767
- this._value = value;
12768
- this.style.setProperty("--min", `${this._min}`);
12858
+
12859
+ [part='slider'] {
12860
+ margin-left: 2rem; /* mx-8 */
12861
+ margin-right: 2rem;
12862
+ padding-bottom: 1rem; /* pb-4 */
12863
+ padding-top: 1.25rem; /* pt-5 */
12769
12864
  }
12770
- get min() {
12771
- return this._min;
12865
+
12866
+ [part='bounds'] {
12867
+ display: flex;
12868
+ width: 100%;
12869
+ justify-content: space-between;
12870
+ margin-bottom: 0.5rem; /* mb-2 */
12772
12871
  }
12773
- set max(value) {
12774
- this._max = value;
12775
- this.style.setProperty("--max", `${this._max}`);
12872
+
12873
+ [part='ticks'] {
12874
+ margin-left: 0.125rem; /* mx-0.5 */
12875
+ margin-right: 0.125rem;
12876
+ margin-bottom: 0.25rem; /* mb-1 */
12877
+ height: 0.5rem; /* h-2 */
12878
+ background: linear-gradient(to right, var(--qti-border-color) var(--qti-border-thickness), transparent 1px) repeat-x
12879
+ 0 center / calc(calc(100% - var(--qti-border-thickness)) / ((var(--max) - var(--min)) / var(--step))) 100%;
12776
12880
  }
12777
- get max() {
12778
- return this._max;
12881
+
12882
+ [part='rail'] {
12883
+ display: flex;
12884
+ align-items: center;
12885
+ box-sizing: border-box;
12886
+ height: 0.375rem; /* h-1.5 */
12887
+ width: 100%;
12888
+ cursor: pointer;
12889
+ border-radius: 9999px; /* rounded-full */
12890
+ border: 1px solid #d1d5db; /* border-gray-300 */
12891
+ background-color: #e5e7eb; /* bg-gray-200 */
12779
12892
  }
12780
- set step(value) {
12781
- this._step = value;
12782
- this.style.setProperty("--step", `${this._step}`);
12893
+
12894
+ [part='knob'] {
12895
+ background-color: var(--qti-bg-active);
12896
+ border: 2px solid var(--qti-border-active);
12897
+ position: relative;
12898
+ height: 1rem; /* h-4 */
12899
+ width: 1rem; /* w-4 */
12900
+ transform-origin: center;
12901
+ transform: translateX(-50%);
12902
+ cursor: pointer;
12903
+ border-radius: 9999px; /* rounded-full */
12904
+ left: var(--value-percentage);
12783
12905
  }
12784
- get step() {
12785
- return this._step;
12906
+
12907
+ [part='value'] {
12908
+ position: absolute;
12909
+ bottom: 2rem; /* bottom-8 */
12910
+ left: 0.5rem; /* left-2 */
12911
+ transform: translateX(-50%);
12912
+ cursor: pointer;
12913
+ border-radius: 0.25rem; /* rounded */
12914
+ background-color: #f3f4f6; /* bg-gray-100 */
12915
+ padding: 0.25rem 0.5rem; /* px-2 py-1 */
12916
+ text-align: center;
12917
+ color: #6b7280; /* text-gray-500 */
12786
12918
  }
12787
- reset() {
12919
+ `;
12920
+
12921
+ // src/lib/qti-components/qti-interaction/qti-slider-interaction/qti-slider-interaction.ts
12922
+ var QtiSliderInteraction = class extends r4 {
12923
+ constructor() {
12924
+ super();
12925
+ this._value = 0;
12926
+ this.min = 0;
12927
+ this.max = 100;
12928
+ this.step = 1;
12929
+ this._internals = this.attachInternals();
12788
12930
  }
12789
- validate() {
12790
- return true;
12931
+ connectedCallback() {
12932
+ super.connectedCallback();
12933
+ this._updateValue(this.min);
12934
+ this.setAttribute("tabindex", "0");
12935
+ this.setAttribute("role", "slider");
12791
12936
  }
12792
12937
  get value() {
12793
12938
  return this._value.toString();
12794
12939
  }
12795
12940
  set value(val) {
12796
- const isNumber = !isNaN(parseInt(val.toString()));
12797
- if (isNumber) {
12798
- this._value = parseInt(val.toString());
12799
- } else {
12800
- throw new Error("Value must be a number");
12941
+ const newValue = parseInt(val, 10);
12942
+ if (!isNaN(newValue)) {
12943
+ this._updateValue(newValue);
12801
12944
  }
12802
12945
  }
12803
- set response(myResponse) {
12804
- if (Array.isArray(myResponse)) {
12805
- console.error("QtiSliderInteraction: response is an array, but should be a single value");
12806
- return;
12807
- }
12808
- const value = parseInt(myResponse);
12809
- if (Number.isNaN(value)) {
12810
- console.error("QtiSliderInteraction: response is not a number");
12811
- return;
12812
- }
12813
- this._value = value;
12814
- }
12815
- render() {
12816
- if (this._value < this.min) {
12817
- this._value = this.min;
12818
- }
12819
- if (this._value > this.max) {
12820
- this._value = this.max;
12821
- }
12946
+ _updateValue(newValue) {
12947
+ this._value = Math.min(this.max, Math.max(this.min, newValue));
12822
12948
  const valuePercentage = (this._value - this.min) / (this.max - this.min) * 100;
12823
12949
  this.style.setProperty("--value-percentage", `${valuePercentage}%`);
12824
- this.setAttribute("aria-valuenow", this.value.toString());
12825
- return x`<slot name="prompt"></slot>
12950
+ this._internals.setFormValue(this.value);
12951
+ this.requestUpdate();
12952
+ }
12953
+ render() {
12954
+ return x`
12955
+ <slot name="prompt"></slot>
12826
12956
  <div id="slider" part="slider">
12827
- ${this.csLive.getPropertyValue("--show-bounds") == "true" ? x`<div id="bounds" part="bounds">
12828
- <div>${this._min}</div>
12829
- <div>${this._max}</div>
12830
- </div>` : E}
12831
- ${this.csLive.getPropertyValue("--show-ticks") == "true" ? x`<div id="ticks" part="ticks"></div>` : E}
12832
- <div id="rail" part="rail" @mousedown=${this._onMouseDown} @touchstart=${this._onTouchMove}>
12833
- <div id="knob" part="knob">
12834
- ${this.csLive.getPropertyValue("--show-value") == "true" ? x`<div id="value" part="value">${this.value}</div>` : E}
12835
- </div>
12957
+ <div id="bounds" part="bounds">
12958
+ <div>${this.min}</div>
12959
+ <div>${this.max}</div>
12836
12960
  </div>
12837
- </div>`;
12838
- }
12839
- connectedCallback() {
12840
- super.connectedCallback();
12841
- this.step = 1;
12842
- this.setAttribute("tabindex", "0");
12843
- this.setAttribute("role", "slider");
12844
- }
12845
- _onTouchMove(event) {
12846
- const handleTouchMove = (event2) => {
12847
- const { x: x3 } = this.getPositionFromEvent(event2);
12848
- const diffX2 = x3 - this._rail.getBoundingClientRect().left - document.documentElement.scrollLeft;
12849
- this.calculateValue(diffX2);
12850
- event2.stopPropagation();
12851
- };
12852
- const handleTouchEnd = () => {
12853
- document.removeEventListener("touchmove", handleTouchMove);
12854
- document.removeEventListener("touchend", handleTouchEnd);
12855
- this.saveResponse(this.value.toString());
12856
- };
12857
- document.addEventListener("touchmove", handleTouchMove);
12858
- document.addEventListener("touchend", handleTouchEnd);
12859
- const { x: x2 } = this.getPositionFromEvent(event);
12860
- const diffX = x2 - this._rail.getBoundingClientRect().left - document.documentElement.scrollLeft;
12861
- this.calculateValue(diffX);
12862
- event.stopPropagation();
12961
+
12962
+ <div id="ticks" part="ticks"></div>
12963
+
12964
+ <div id="rail" part="rail" @mousedown=${this._onMouseDown} @touchstart=${this._onTouchStart}>
12965
+ <div id="knob" part="knob"><div id="value" part="value">${this.value}</div></div>
12966
+ </div>
12967
+ </div>
12968
+ `;
12863
12969
  }
12864
12970
  _onMouseDown(event) {
12865
- const handleMouseMove = (event2) => {
12866
- const diffX2 = event2.pageX - this._rail.getBoundingClientRect().left - document.documentElement.scrollLeft;
12867
- this.calculateValue(diffX2);
12868
- event2.preventDefault();
12869
- event2.stopPropagation();
12870
- };
12971
+ this._startDrag(event.pageX);
12972
+ const handleMouseMove = (e10) => this._onDrag(e10.pageX);
12871
12973
  const handleMouseUp = () => {
12872
12974
  document.removeEventListener("mousemove", handleMouseMove);
12873
12975
  document.removeEventListener("mouseup", handleMouseUp);
12874
- this.saveResponse(this.value.toString());
12976
+ this._onDragEnd();
12875
12977
  };
12876
12978
  document.addEventListener("mousemove", handleMouseMove);
12877
12979
  document.addEventListener("mouseup", handleMouseUp);
12878
- const diffX = event.pageX - this._rail.getBoundingClientRect().left - document.documentElement.scrollLeft;
12879
- this.calculateValue(diffX);
12880
- event.preventDefault();
12881
- event.stopPropagation();
12882
- }
12883
- /** calculateValue gets x position and compares this with the total width in pixels */
12884
- calculateValue(diffX) {
12885
- const valueNow = this.min + (this.max - this.min) * diffX / this._rail.getBoundingClientRect().width;
12886
- const roundedStepValue = this.min + Math.round((valueNow - this.min) / this._step) * this._step;
12887
- this._value = roundedStepValue;
12888
- }
12889
- getPositionFromEvent(e10) {
12890
- let _touchMove;
12891
- if (e10.type == "touchstart" || e10.type == "touchmove" || e10.type == "touchend" || e10.type == "touchcancel") {
12892
- const evt = typeof e10.originalEvent === "undefined" ? e10 : e10.originalEvent;
12893
- const touch = evt.touches[0] || evt.changedTouches[0];
12894
- _touchMove = {
12895
- x: touch.pageX,
12896
- y: touch.pageY
12897
- };
12898
- } else if (e10.type == "mousedown" || e10.type == "mouseup" || e10.type == "mousemove" || e10.type == "mouseover" || e10.type == "mouseout" || e10.type == "mouseenter" || e10.type == "mouseleave") {
12899
- _touchMove = {
12900
- x: e10.clientX,
12901
- y: e10.clientY
12902
- };
12903
- }
12904
- return _touchMove;
12980
+ }
12981
+ _onTouchStart(event) {
12982
+ this._startDrag(event.touches[0].pageX);
12983
+ const handleTouchMove = (e10) => this._onDrag(e10.touches[0].pageX);
12984
+ const handleTouchEnd = () => {
12985
+ document.removeEventListener("touchmove", handleTouchMove);
12986
+ document.removeEventListener("touchend", handleTouchEnd);
12987
+ this._onDragEnd();
12988
+ };
12989
+ document.addEventListener("touchmove", handleTouchMove, { passive: false });
12990
+ document.addEventListener("touchend", handleTouchEnd);
12991
+ }
12992
+ _startDrag(pageX) {
12993
+ this._onDrag(pageX);
12994
+ }
12995
+ _onDrag(pageX) {
12996
+ const railRect = this._rail.getBoundingClientRect();
12997
+ const diffX = pageX - railRect.left;
12998
+ const percentage = Math.min(1, Math.max(0, diffX / railRect.width));
12999
+ const steppedValue = this.min + Math.round(percentage * (this.max - this.min) / this.step) * this.step;
13000
+ this._updateValue(steppedValue);
13001
+ }
13002
+ _onDragEnd() {
13003
+ this.dispatchEvent(new Event("change", { bubbles: true }));
12905
13004
  }
12906
13005
  };
12907
- QtiSliderInteraction.styles = [i2``];
13006
+ QtiSliderInteraction.formAssociated = true;
13007
+ // Enables elementInternals for forms
13008
+ QtiSliderInteraction.styles = qti_slider_interaction_styles_default;
12908
13009
  __decorateClass([
12909
13010
  e6("#rail")
12910
13011
  ], QtiSliderInteraction.prototype, "_rail", 2);
12911
- __decorateClass([
12912
- n5({ type: Boolean, attribute: "step-label" })
12913
- ], QtiSliderInteraction.prototype, "stepLabel", 2);
12914
- __decorateClass([
12915
- n5({ type: Boolean })
12916
- ], QtiSliderInteraction.prototype, "reverse", 2);
12917
13012
  __decorateClass([
12918
13013
  n5({ type: Number, attribute: "lower-bound" })
12919
- ], QtiSliderInteraction.prototype, "min", 1);
13014
+ ], QtiSliderInteraction.prototype, "min", 2);
12920
13015
  __decorateClass([
12921
13016
  n5({ type: Number, attribute: "upper-bound" })
12922
- ], QtiSliderInteraction.prototype, "max", 1);
13017
+ ], QtiSliderInteraction.prototype, "max", 2);
12923
13018
  __decorateClass([
12924
13019
  n5({ type: Number, attribute: "step" })
12925
- ], QtiSliderInteraction.prototype, "step", 1);
12926
- __decorateClass([
12927
- watch("disabled", { waitUntilFirstUpdate: true })
12928
- ], QtiSliderInteraction.prototype, "_handleDisabledChange", 2);
12929
- __decorateClass([
12930
- watch("readonly", { waitUntilFirstUpdate: true })
12931
- ], QtiSliderInteraction.prototype, "_handleReadonlyChange", 2);
13020
+ ], QtiSliderInteraction.prototype, "step", 2);
12932
13021
  QtiSliderInteraction = __decorateClass([
12933
13022
  t4("qti-slider-interaction")
12934
13023
  ], QtiSliderInteraction);
@@ -13220,6 +13309,7 @@ console.log(
13220
13309
  export {
13221
13310
  ActiveElementMixin,
13222
13311
  Interaction,
13312
+ ItemContainer,
13223
13313
  QtiAnd,
13224
13314
  QtiAssessmentItem,
13225
13315
  QtiAssessmentItemRef,
@@ -13262,7 +13352,6 @@ export {
13262
13352
  QtiIsNull,
13263
13353
  QtiItem,
13264
13354
  QtiItemBody,
13265
- QtiItemMixin,
13266
13355
  QtiLookupOutcomeValue,
13267
13356
  QtiLt,
13268
13357
  QtiLte,
@@ -13309,6 +13398,7 @@ export {
13309
13398
  QtiTest,
13310
13399
  QtiTestPart,
13311
13400
  QtiTextEntryInteraction,
13401
+ QtiUploadInteraction,
13312
13402
  QtiVariable,
13313
13403
  TestContainer,
13314
13404
  TestItemLink,
@@ -13319,7 +13409,9 @@ export {
13319
13409
  itemContext,
13320
13410
  itemContextVariables,
13321
13411
  qtiAndMixin,
13322
- qtiSubtractMixin
13412
+ qtiSubtractMixin,
13413
+ testContext,
13414
+ testElement
13323
13415
  };
13324
13416
  /*! Bundled license information:
13325
13417