@qti-components/processing 1.5.0 → 1.6.1-next.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 (86) hide show
  1. package/dist/components/index.d.ts +4 -0
  2. package/dist/components/index.js +4 -0
  3. package/dist/components/index.js.map +1 -1
  4. package/dist/components/qti-container-size/qti-container-size.d.ts +2 -0
  5. package/dist/components/qti-container-size/qti-container-size.js +3 -0
  6. package/dist/components/qti-container-size/qti-container-size.js.map +1 -1
  7. package/dist/components/qti-contains/qti-contains.d.ts +24 -3
  8. package/dist/components/qti-contains/qti-contains.js +82 -30
  9. package/dist/components/qti-contains/qti-contains.js.map +1 -1
  10. package/dist/components/qti-default/qti-default.js +0 -1
  11. package/dist/components/qti-default/qti-default.js.map +1 -1
  12. package/dist/components/qti-divide/qti-divide.d.ts +3 -0
  13. package/dist/components/qti-divide/qti-divide.js +4 -0
  14. package/dist/components/qti-divide/qti-divide.js.map +1 -1
  15. package/dist/components/qti-equal/qti-equal.d.ts +12 -1
  16. package/dist/components/qti-equal/qti-equal.js +51 -7
  17. package/dist/components/qti-equal/qti-equal.js.map +1 -1
  18. package/dist/components/qti-equal-rounded/qti-equal-rounded.js +9 -1
  19. package/dist/components/qti-equal-rounded/qti-equal-rounded.js.map +1 -1
  20. package/dist/components/qti-exit-response/qti-exit-response.d.ts +17 -0
  21. package/dist/components/qti-exit-response/qti-exit-response.js +16 -0
  22. package/dist/components/qti-exit-response/qti-exit-response.js.map +1 -0
  23. package/dist/components/qti-exit-test/qti-exit-test.d.ts +16 -0
  24. package/dist/components/qti-exit-test/qti-exit-test.js +15 -0
  25. package/dist/components/qti-exit-test/qti-exit-test.js.map +1 -0
  26. package/dist/components/qti-field-value/qti-field-value.d.ts +7 -1
  27. package/dist/components/qti-field-value/qti-field-value.js +15 -12
  28. package/dist/components/qti-field-value/qti-field-value.js.map +1 -1
  29. package/dist/components/qti-gte/qti-gte.js +1 -1
  30. package/dist/components/qti-gte/qti-gte.js.map +1 -1
  31. package/dist/components/qti-integer-divide/qti-integer-divide.d.ts +2 -0
  32. package/dist/components/qti-integer-divide/qti-integer-divide.js +3 -0
  33. package/dist/components/qti-integer-divide/qti-integer-divide.js.map +1 -1
  34. package/dist/components/qti-integer-modulus/qti-integer-modulus.d.ts +3 -1
  35. package/dist/components/qti-integer-modulus/qti-integer-modulus.js +9 -2
  36. package/dist/components/qti-integer-modulus/qti-integer-modulus.js.map +1 -1
  37. package/dist/components/qti-integer-to-float/qti-integer-to-float.d.ts +2 -0
  38. package/dist/components/qti-integer-to-float/qti-integer-to-float.js +3 -0
  39. package/dist/components/qti-integer-to-float/qti-integer-to-float.js.map +1 -1
  40. package/dist/components/qti-is-null/qti-is-null.js +4 -1
  41. package/dist/components/qti-is-null/qti-is-null.js.map +1 -1
  42. package/dist/components/qti-lookup-outcome-value/qti-lookup-outcome-value.d.ts +2 -1
  43. package/dist/components/qti-lookup-outcome-value/qti-lookup-outcome-value.js +29 -5
  44. package/dist/components/qti-lookup-outcome-value/qti-lookup-outcome-value.js.map +1 -1
  45. package/dist/components/qti-lte/qti-lte.js +1 -1
  46. package/dist/components/qti-lte/qti-lte.js.map +1 -1
  47. package/dist/components/qti-map-response-point/qti-map-response-point.js +6 -0
  48. package/dist/components/qti-map-response-point/qti-map-response-point.js.map +1 -1
  49. package/dist/components/qti-math-constant/qti-math-constant.d.ts +20 -0
  50. package/dist/components/qti-math-constant/qti-math-constant.js +40 -0
  51. package/dist/components/qti-math-constant/qti-math-constant.js.map +1 -0
  52. package/dist/components/qti-math-operator/qti-math-operator.d.ts +6 -0
  53. package/dist/components/qti-math-operator/qti-math-operator.js +43 -1
  54. package/dist/components/qti-math-operator/qti-math-operator.js.map +1 -1
  55. package/dist/components/qti-multiple/qti-multiple.js +0 -2
  56. package/dist/components/qti-multiple/qti-multiple.js.map +1 -1
  57. package/dist/components/qti-pattern-match/qti-pattern-match.d.ts +1 -0
  58. package/dist/components/qti-pattern-match/qti-pattern-match.js +28 -4
  59. package/dist/components/qti-pattern-match/qti-pattern-match.js.map +1 -1
  60. package/dist/components/qti-power/qti-power.d.ts +2 -0
  61. package/dist/components/qti-power/qti-power.js +3 -0
  62. package/dist/components/qti-power/qti-power.js.map +1 -1
  63. package/dist/components/qti-random/qti-random.js +0 -1
  64. package/dist/components/qti-random/qti-random.js.map +1 -1
  65. package/dist/components/qti-repeat/qti-repeat.d.ts +0 -8
  66. package/dist/components/qti-repeat/qti-repeat.js +15 -2
  67. package/dist/components/qti-repeat/qti-repeat.js.map +1 -1
  68. package/dist/components/qti-response-processing-fragment/qti-response-processing-fragment.d.ts +19 -0
  69. package/dist/components/qti-response-processing-fragment/qti-response-processing-fragment.js +19 -0
  70. package/dist/components/qti-response-processing-fragment/qti-response-processing-fragment.js.map +1 -0
  71. package/dist/components/qti-round/qti-round.d.ts +2 -0
  72. package/dist/components/qti-round/qti-round.js +3 -0
  73. package/dist/components/qti-round/qti-round.js.map +1 -1
  74. package/dist/components/qti-round-to/qti-round-to.d.ts +2 -0
  75. package/dist/components/qti-round-to/qti-round-to.js +3 -0
  76. package/dist/components/qti-round-to/qti-round-to.js.map +1 -1
  77. package/dist/components/qti-stats-operator/qti-stats-operator.d.ts +3 -1
  78. package/dist/components/qti-stats-operator/qti-stats-operator.js +29 -7
  79. package/dist/components/qti-stats-operator/qti-stats-operator.js.map +1 -1
  80. package/dist/components/qti-truncate/qti-truncate.d.ts +2 -0
  81. package/dist/components/qti-truncate/qti-truncate.js +3 -0
  82. package/dist/components/qti-truncate/qti-truncate.js.map +1 -1
  83. package/dist/elements.d.ts +17 -1
  84. package/dist/elements.js +9 -1
  85. package/dist/elements.js.map +1 -1
  86. package/package.json +4 -4
@@ -22,11 +22,14 @@ export * from './qti-integer-to-float/qti-integer-to-float';
22
22
  export * from './qti-lcm/qti-lcm';
23
23
  export * from './qti-null/qti-null';
24
24
  export * from './qti-is-null/qti-is-null';
25
+ export * from './qti-exit-response/qti-exit-response';
26
+ export * from './qti-exit-test/qti-exit-test';
25
27
  export * from './qti-field-value/qti-field-value';
26
28
  export * from './qti-lt/qti-lt';
27
29
  export * from './qti-lte/qti-lte';
28
30
  export * from './qti-map-response/qti-map-response';
29
31
  export * from './qti-map-response-point/qti-map-response-point';
32
+ export * from './qti-math-constant/qti-math-constant';
30
33
  export * from './qti-math-operator/qti-math-operator';
31
34
  export * from './qti-match/qti-match';
32
35
  export * from './qti-max/qti-max';
@@ -54,3 +57,4 @@ export * from './qti-random-integer/qti-random-integer';
54
57
  export * from './qti-random/qti-random';
55
58
  export * from './qti-delete/qti-delete';
56
59
  export * from './qti-index/qti-index';
60
+ export * from './qti-response-processing-fragment/qti-response-processing-fragment';
@@ -22,11 +22,14 @@ export * from './qti-integer-to-float/qti-integer-to-float';
22
22
  export * from './qti-lcm/qti-lcm';
23
23
  export * from './qti-null/qti-null';
24
24
  export * from './qti-is-null/qti-is-null';
25
+ export * from './qti-exit-response/qti-exit-response';
26
+ export * from './qti-exit-test/qti-exit-test';
25
27
  export * from './qti-field-value/qti-field-value';
26
28
  export * from './qti-lt/qti-lt';
27
29
  export * from './qti-lte/qti-lte';
28
30
  export * from './qti-map-response/qti-map-response';
29
31
  export * from './qti-map-response-point/qti-map-response-point';
32
+ export * from './qti-math-constant/qti-math-constant';
30
33
  export * from './qti-math-operator/qti-math-operator';
31
34
  export * from './qti-match/qti-match';
32
35
  export * from './qti-max/qti-max';
@@ -59,4 +62,5 @@ export * from './qti-index/qti-index';
59
62
  // export * from './qti-area-mapping/qti-area-mapping';
60
63
  // export * from './qti-expression';
61
64
  // export * from './qti-condition-expression';
65
+ export * from './qti-response-processing-fragment/qti-response-processing-fragment';
62
66
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,+BAA+B,CAAC;AAE9C,cAAc,yCAAyC,CAAC;AACxD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uCAAuC,CAAC;AACtD,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qCAAqC,CAAC;AACpD,cAAc,mCAAmC,CAAC;AAElD,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yCAAyC,CAAC;AACxD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mCAAmC,CAAC;AAClD,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qCAAqC,CAAC;AACpD,cAAc,iDAAiD,CAAC;AAEhE,cAAc,uCAAuC,CAAC;AACtD,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,uCAAuC,CAAC;AACtD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,qDAAqD,CAAC;AACpE,cAAc,iDAAiD,CAAC;AAChE,cAAc,yCAAyC,CAAC;AACxD,cAAc,gBAAgB,CAAC;AAC/B,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qCAAqC,CAAC;AACpD,cAAc,mBAAmB,CAAC;AAClC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yCAAyC,CAAC;AACxD,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,kCAAkC;AAElC,6CAA6C;AAC7C,uDAAuD;AAEvD,oCAAoC;AACpC,8CAA8C","sourcesContent":["export * from './qti-and/qti-and';\nexport * from './qti-any-n/qti-any-n';\nexport * from './qti-basevalue/qti-basevalue';\n\nexport * from './qti-container-size/qti-container-size';\nexport * from './qti-contains/qti-contains';\nexport * from './qti-correct/qti-correct';\nexport * from './qti-default/qti-default';\nexport * from './qti-equal-rounded/qti-equal-rounded';\nexport * from './qti-round/qti-round';\nexport * from './qti-round-to/qti-round-to';\nexport * from './qti-equal/qti-equal';\nexport * from './qti-divide/qti-divide';\nexport * from './qti-duration-gte/qti-duration-gte';\nexport * from './qti-duration-lt/qti-duration-lt';\n\nexport * from './qti-gcd/qti.gcd';\nexport * from './qti-gt/qti-gt';\nexport * from './qti-gte/qti-gte';\nexport * from './qti-inside/qti-inside';\nexport * from './qti-integer-divide/qti-integer-divide';\nexport * from './qti-integer-modulus/qti-integer-modulus';\nexport * from './qti-integer-to-float/qti-integer-to-float';\nexport * from './qti-lcm/qti-lcm';\nexport * from './qti-null/qti-null';\nexport * from './qti-is-null/qti-is-null';\nexport * from './qti-field-value/qti-field-value';\nexport * from './qti-lt/qti-lt';\nexport * from './qti-lte/qti-lte';\nexport * from './qti-map-response/qti-map-response';\nexport * from './qti-map-response-point/qti-map-response-point';\n\nexport * from './qti-math-operator/qti-math-operator';\nexport * from './qti-match/qti-match';\nexport * from './qti-max/qti-max';\nexport * from './qti-member/qti-member';\nexport * from './qti-min/qti-min';\nexport * from './qti-multiple/qti-multiple';\nexport * from './qti-not/qti-not';\nexport * from './qti-or/qti-or';\nexport * from './qti-ordered/qti-ordered';\nexport * from './qti-power/qti-power';\nexport * from './qti-pattern-match/qti-pattern-match';\nexport * from './qti-printed-variable/qti-printed-variable';\nexport * from './qti-product/qti-product';\nexport * from './qti-repeat/qti-repeat';\nexport * from './qti-set-correct-response/qti-set-correct-response';\nexport * from './qti-set-template-value/qti-set-template-value';\nexport * from './qti-stats-operator/qti-stats-operator';\nexport * from './qti-subtract';\nexport * from './qti-substring/qti-substring';\nexport * from './qti-string-match/qti-string-match';\nexport * from './qti-sum/qti-sum';\nexport * from './qti-truncate/qti-truncate';\nexport * from './qti-variable/qti-variable';\nexport * from './qti-random-integer/qti-random-integer';\nexport * from './qti-random/qti-random';\nexport * from './qti-delete/qti-delete';\nexport * from './qti-index/qti-index';\n// FIXME are internal and abstract\n\n// export * from './qti-mapping/qti-mapping';\n// export * from './qti-area-mapping/qti-area-mapping';\n\n// export * from './qti-expression';\n// export * from './qti-condition-expression';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,+BAA+B,CAAC;AAE9C,cAAc,yCAAyC,CAAC;AACxD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uCAAuC,CAAC;AACtD,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qCAAqC,CAAC;AACpD,cAAc,mCAAmC,CAAC;AAElD,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yCAAyC,CAAC;AACxD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uCAAuC,CAAC;AACtD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mCAAmC,CAAC;AAClD,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qCAAqC,CAAC;AACpD,cAAc,iDAAiD,CAAC;AAEhE,cAAc,uCAAuC,CAAC;AACtD,cAAc,uCAAuC,CAAC;AACtD,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,uCAAuC,CAAC;AACtD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,qDAAqD,CAAC;AACpE,cAAc,iDAAiD,CAAC;AAChE,cAAc,yCAAyC,CAAC;AACxD,cAAc,gBAAgB,CAAC;AAC/B,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qCAAqC,CAAC;AACpD,cAAc,mBAAmB,CAAC;AAClC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yCAAyC,CAAC;AACxD,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,kCAAkC;AAElC,6CAA6C;AAC7C,uDAAuD;AAEvD,oCAAoC;AACpC,8CAA8C;AAC9C,cAAc,qEAAqE,CAAC","sourcesContent":["export * from './qti-and/qti-and';\nexport * from './qti-any-n/qti-any-n';\nexport * from './qti-basevalue/qti-basevalue';\n\nexport * from './qti-container-size/qti-container-size';\nexport * from './qti-contains/qti-contains';\nexport * from './qti-correct/qti-correct';\nexport * from './qti-default/qti-default';\nexport * from './qti-equal-rounded/qti-equal-rounded';\nexport * from './qti-round/qti-round';\nexport * from './qti-round-to/qti-round-to';\nexport * from './qti-equal/qti-equal';\nexport * from './qti-divide/qti-divide';\nexport * from './qti-duration-gte/qti-duration-gte';\nexport * from './qti-duration-lt/qti-duration-lt';\n\nexport * from './qti-gcd/qti.gcd';\nexport * from './qti-gt/qti-gt';\nexport * from './qti-gte/qti-gte';\nexport * from './qti-inside/qti-inside';\nexport * from './qti-integer-divide/qti-integer-divide';\nexport * from './qti-integer-modulus/qti-integer-modulus';\nexport * from './qti-integer-to-float/qti-integer-to-float';\nexport * from './qti-lcm/qti-lcm';\nexport * from './qti-null/qti-null';\nexport * from './qti-is-null/qti-is-null';\nexport * from './qti-exit-response/qti-exit-response';\nexport * from './qti-exit-test/qti-exit-test';\nexport * from './qti-field-value/qti-field-value';\nexport * from './qti-lt/qti-lt';\nexport * from './qti-lte/qti-lte';\nexport * from './qti-map-response/qti-map-response';\nexport * from './qti-map-response-point/qti-map-response-point';\n\nexport * from './qti-math-constant/qti-math-constant';\nexport * from './qti-math-operator/qti-math-operator';\nexport * from './qti-match/qti-match';\nexport * from './qti-max/qti-max';\nexport * from './qti-member/qti-member';\nexport * from './qti-min/qti-min';\nexport * from './qti-multiple/qti-multiple';\nexport * from './qti-not/qti-not';\nexport * from './qti-or/qti-or';\nexport * from './qti-ordered/qti-ordered';\nexport * from './qti-power/qti-power';\nexport * from './qti-pattern-match/qti-pattern-match';\nexport * from './qti-printed-variable/qti-printed-variable';\nexport * from './qti-product/qti-product';\nexport * from './qti-repeat/qti-repeat';\nexport * from './qti-set-correct-response/qti-set-correct-response';\nexport * from './qti-set-template-value/qti-set-template-value';\nexport * from './qti-stats-operator/qti-stats-operator';\nexport * from './qti-subtract';\nexport * from './qti-substring/qti-substring';\nexport * from './qti-string-match/qti-string-match';\nexport * from './qti-sum/qti-sum';\nexport * from './qti-truncate/qti-truncate';\nexport * from './qti-variable/qti-variable';\nexport * from './qti-random-integer/qti-random-integer';\nexport * from './qti-random/qti-random';\nexport * from './qti-delete/qti-delete';\nexport * from './qti-index/qti-index';\n// FIXME are internal and abstract\n\n// export * from './qti-mapping/qti-mapping';\n// export * from './qti-area-mapping/qti-area-mapping';\n\n// export * from './qti-expression';\n// export * from './qti-condition-expression';\nexport * from './qti-response-processing-fragment/qti-response-processing-fragment';\n"]}
@@ -1,4 +1,5 @@
1
1
  import { QtiExpression } from '@qti-components/base';
2
+ import type { BaseType } from '@qti-components/base';
2
3
  /**
3
4
  * @summary The qti-container-size operator returns the count of items in a container.
4
5
  * @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#containerSize
@@ -8,5 +9,6 @@ import { QtiExpression } from '@qti-components/base';
8
9
  * Special cases: Returns 0 if the sub-expression is NULL.
9
10
  */
10
11
  export declare class QtiContainerSize extends QtiExpression<number> {
12
+ get resultBaseType(): BaseType;
11
13
  getResult(): number;
12
14
  }
@@ -8,6 +8,9 @@ import { QtiExpression } from '@qti-components/base';
8
8
  * Special cases: Returns 0 if the sub-expression is NULL.
9
9
  */
10
10
  export class QtiContainerSize extends QtiExpression {
11
+ get resultBaseType() {
12
+ return 'integer';
13
+ }
11
14
  getResult() {
12
15
  if (this.children?.length !== 1) {
13
16
  console.error('qti-container-size must have exactly one child expression');
@@ -1 +1 @@
1
- {"version":3,"file":"qti-container-size.js","sourceRoot":"","sources":["../../../src/components/qti-container-size/qti-container-size.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAIrD;;;;;;;GAOG;AACH,MAAM,OAAO,gBAAiB,SAAQ,aAAqB;IACzC,SAAS;QACvB,IAAI,IAAI,CAAC,QAAQ,EAAE,MAAM,KAAK,CAAC,EAAE,CAAC;YAChC,OAAO,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAC;YAC3E,OAAO,CAAC,CAAC;QACX,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,EAAwB,CAAC;QAE5D,IAAI,CAAC,SAAS,IAAI,SAAS,EAAE,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1C,OAAO,CAAC,CAAC;QACX,CAAC;QAED,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;QAE9B,4CAA4C;QAC5C,IAAI,QAAQ,CAAC,KAAK,KAAK,IAAI,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAC5D,OAAO,CAAC,CAAC;QACX,CAAC;QAED,kDAAkD;QAClD,IAAI,QAAQ,CAAC,WAAW,KAAK,UAAU,IAAI,QAAQ,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;YAC9E,OAAO,CAAC,KAAK,CAAC,6DAA6D,CAAC,CAAC;YAC7E,OAAO,CAAC,CAAC;QACX,CAAC;QAED,gEAAgE;QAChE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACnC,OAAO,CAAC,KAAK,CAAC,2EAA2E,CAAC,CAAC;YAC3F,OAAO,CAAC,CAAC;QACX,CAAC;QAED,mCAAmC;QACnC,OAAO,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC;IAC/B,CAAC;CACF","sourcesContent":["import { QtiExpression } from '@qti-components/base';\n\nimport type { ResponseVariable } from '@qti-components/base';\n\n/**\n * @summary The qti-container-size operator returns the count of items in a container.\n * @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#containerSize\n *\n * Takes 1 sub-expression with multiple or ordered cardinality.\n * Returns a single integer representing the number of items.\n * Special cases: Returns 0 if the sub-expression is NULL.\n */\nexport class QtiContainerSize extends QtiExpression<number> {\n public override getResult(): number {\n if (this.children?.length !== 1) {\n console.error('qti-container-size must have exactly one child expression');\n return 0;\n }\n\n const variables = this.getVariables() as ResponseVariable[];\n\n if (!variables || variables?.length === 0) {\n return 0;\n }\n\n const variable = variables[0];\n\n // Handle null values - return 0 as per spec\n if (variable.value === null || variable.value === undefined) {\n return 0;\n }\n\n // Check cardinality - must be multiple or ordered\n if (variable.cardinality !== 'multiple' && variable.cardinality !== 'ordered') {\n console.error('qti-container-size requires multiple or ordered cardinality');\n return 0;\n }\n\n // The value should be an array for multiple/ordered cardinality\n if (!Array.isArray(variable.value)) {\n console.error('qti-container-size: expected array value for multiple/ordered cardinality');\n return 0;\n }\n\n // Return the size of the container\n return variable.value.length;\n }\n}\n"]}
1
+ {"version":3,"file":"qti-container-size.js","sourceRoot":"","sources":["../../../src/components/qti-container-size/qti-container-size.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAIrD;;;;;;;GAOG;AACH,MAAM,OAAO,gBAAiB,SAAQ,aAAqB;IACzD,IAAoB,cAAc;QAChC,OAAO,SAAS,CAAC;IACnB,CAAC;IAEe,SAAS;QACvB,IAAI,IAAI,CAAC,QAAQ,EAAE,MAAM,KAAK,CAAC,EAAE,CAAC;YAChC,OAAO,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAC;YAC3E,OAAO,CAAC,CAAC;QACX,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,EAAwB,CAAC;QAE5D,IAAI,CAAC,SAAS,IAAI,SAAS,EAAE,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1C,OAAO,CAAC,CAAC;QACX,CAAC;QAED,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;QAE9B,4CAA4C;QAC5C,IAAI,QAAQ,CAAC,KAAK,KAAK,IAAI,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAC5D,OAAO,CAAC,CAAC;QACX,CAAC;QAED,kDAAkD;QAClD,IAAI,QAAQ,CAAC,WAAW,KAAK,UAAU,IAAI,QAAQ,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;YAC9E,OAAO,CAAC,KAAK,CAAC,6DAA6D,CAAC,CAAC;YAC7E,OAAO,CAAC,CAAC;QACX,CAAC;QAED,gEAAgE;QAChE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACnC,OAAO,CAAC,KAAK,CAAC,2EAA2E,CAAC,CAAC;YAC3F,OAAO,CAAC,CAAC;QACX,CAAC;QAED,mCAAmC;QACnC,OAAO,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC;IAC/B,CAAC;CACF","sourcesContent":["import { QtiExpression } from '@qti-components/base';\n\nimport type { BaseType, ResponseVariable } from '@qti-components/base';\n\n/**\n * @summary The qti-container-size operator returns the count of items in a container.\n * @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#containerSize\n *\n * Takes 1 sub-expression with multiple or ordered cardinality.\n * Returns a single integer representing the number of items.\n * Special cases: Returns 0 if the sub-expression is NULL.\n */\nexport class QtiContainerSize extends QtiExpression<number> {\n public override get resultBaseType(): BaseType {\n return 'integer';\n }\n\n public override getResult(): number {\n if (this.children?.length !== 1) {\n console.error('qti-container-size must have exactly one child expression');\n return 0;\n }\n\n const variables = this.getVariables() as ResponseVariable[];\n\n if (!variables || variables?.length === 0) {\n return 0;\n }\n\n const variable = variables[0];\n\n // Handle null values - return 0 as per spec\n if (variable.value === null || variable.value === undefined) {\n return 0;\n }\n\n // Check cardinality - must be multiple or ordered\n if (variable.cardinality !== 'multiple' && variable.cardinality !== 'ordered') {\n console.error('qti-container-size requires multiple or ordered cardinality');\n return 0;\n }\n\n // The value should be an array for multiple/ordered cardinality\n if (!Array.isArray(variable.value)) {\n console.error('qti-container-size: expected array value for multiple/ordered cardinality');\n return 0;\n }\n\n // Return the size of the container\n return variable.value.length;\n }\n}\n"]}
@@ -1,4 +1,25 @@
1
- import { QtiConditionExpression } from '@qti-components/base';
2
- export declare class QtiContains extends QtiConditionExpression {
3
- getResult(): boolean;
1
+ import { QtiExpression } from '@qti-components/base';
2
+ /**
3
+ * @summary The qti-contains operator tests whether one container holds another.
4
+ * @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#contains
5
+ *
6
+ * Takes 2 sub-expressions of the same base-type and the same cardinality,
7
+ * either both multiple or both ordered.
8
+ *
9
+ * Cardinality decides what containment means. An unordered container holds
10
+ * another when every one of its values is present, multiplicity included and
11
+ * order disregarded: `[A,B,C]` contains `[C,A]`. An ordered container needs the
12
+ * second to appear as a strict sub-sequence: `[A,B,C]` contains `[B,C]` but not
13
+ * `[C,A]`.
14
+ *
15
+ * Special cases: Returns NULL if either sub-expression is NULL.
16
+ */
17
+ export declare class QtiContains extends QtiExpression<boolean | null> {
18
+ #private;
19
+ getResult(): boolean | null;
20
+ }
21
+ declare global {
22
+ interface HTMLElementTagNameMap {
23
+ 'qti-contains': QtiContains;
24
+ }
4
25
  }
@@ -1,35 +1,87 @@
1
- import { QtiConditionExpression } from '@qti-components/base';
2
- // PK: For the contains we assume the expressions to calculate are all directedPairs
3
- // I don't know it this in QTI is always the case however?
4
- export class QtiContains extends QtiConditionExpression {
1
+ import { QtiExpression, ScoringHelper } from '@qti-components/base';
2
+ /**
3
+ * @summary The qti-contains operator tests whether one container holds another.
4
+ * @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#contains
5
+ *
6
+ * Takes 2 sub-expressions of the same base-type and the same cardinality,
7
+ * either both multiple or both ordered.
8
+ *
9
+ * Cardinality decides what containment means. An unordered container holds
10
+ * another when every one of its values is present, multiplicity included and
11
+ * order disregarded: `[A,B,C]` contains `[C,A]`. An ordered container needs the
12
+ * second to appear as a strict sub-sequence: `[A,B,C]` contains `[B,C]` but not
13
+ * `[C,A]`.
14
+ *
15
+ * Special cases: Returns NULL if either sub-expression is NULL.
16
+ */
17
+ export class QtiContains extends QtiExpression {
5
18
  getResult() {
6
- // TODO: implement this for other types than directedPair
7
- const values = this.getVariables();
8
- if (this.children.length === 2) {
9
- const value1 = values[0];
10
- const value2 = values[1];
11
- if (value1.baseType === 'directedPair' &&
12
- value2.baseType === 'directedPair' &&
13
- value1.cardinality === 'multiple') {
14
- const projection1 = value1.value;
15
- const projection2 = value2.value;
16
- const enumerable = projection1.filter(x => projection2.includes(x));
17
- const result = enumerable.length > 0;
18
- return result;
19
- }
20
- else if (value1.baseType === 'directedPair' &&
21
- value2.baseType === 'directedPair' &&
22
- value1.cardinality === 'single') {
23
- const projection1 = value1.value;
24
- const projection2 = value2.value;
25
- return projection2.includes(projection1);
26
- }
27
- else {
28
- console.error('unsupported baseType or cardinality in qti contains, only baseType: directedPair and cardinality: multiple is supported');
29
- }
19
+ if (this.children.length !== 2) {
20
+ console.error('qti-contains takes two sub-expressions');
21
+ return null;
22
+ }
23
+ const [container, sub] = this.getVariables();
24
+ if (!container || !sub) {
25
+ return null;
26
+ }
27
+ // If either sub-expression is NULL then the result of the operator is NULL.
28
+ if (container.value === null || container.value === undefined || sub.value === null || sub.value === undefined) {
29
+ return null;
30
+ }
31
+ if (container.baseType !== sub.baseType) {
32
+ console.warn('qti-contains: both sub-expressions should have the same base-type');
30
33
  }
31
- else {
32
- console.error('unexpected number of children in qti contains');
34
+ const baseType = container.baseType ?? sub.baseType;
35
+ /*
36
+ * Not what the spec describes — it requires both operands to be containers
37
+ * — but items in the wild write `contains` with a single first operand and
38
+ * mean "is this value in that container", which is `qti-member`. Answering
39
+ * that rather than returning false keeps those items scoring; the warning
40
+ * is there so the authoring gets fixed.
41
+ */
42
+ if (container.cardinality === 'single' && sub.cardinality !== 'single') {
43
+ console.warn('qti-contains: first sub-expression is single; treating it as a membership test');
44
+ return this.#toArray(sub.value).some(candidate => this.#equal(candidate, container.value, baseType));
45
+ }
46
+ const haystack = this.#toArray(container.value);
47
+ const needle = this.#toArray(sub.value);
48
+ // An empty container is contained in anything.
49
+ if (needle.length === 0)
50
+ return true;
51
+ return container.cardinality === 'ordered' && sub.cardinality === 'ordered'
52
+ ? this.#containsSubSequence(haystack, needle, baseType)
53
+ : this.#containsSubMultiset(haystack, needle, baseType);
54
+ }
55
+ #toArray(value) {
56
+ return Array.isArray(value) ? [...value] : [value];
57
+ }
58
+ /**
59
+ * `ScoringHelper.compareSingleValues` knows how each base type compares — a
60
+ * `pair` ignoring the order within it, numbers compared numerically. It
61
+ * returns false for a base type it has no case for, so an unknown one falls
62
+ * back to comparing the values as written.
63
+ */
64
+ #equal(a, b, baseType) {
65
+ return ScoringHelper.compareSingleValues(a, b, baseType) || a === b;
66
+ }
67
+ /** Every needle value present, multiplicity respected, order disregarded. */
68
+ #containsSubMultiset(haystack, needle, baseType) {
69
+ const remaining = [...haystack];
70
+ return needle.every(value => {
71
+ const index = remaining.findIndex(candidate => this.#equal(candidate, value, baseType));
72
+ if (index === -1)
73
+ return false;
74
+ // Consumed, so a value needed twice has to appear twice.
75
+ remaining.splice(index, 1);
76
+ return true;
77
+ });
78
+ }
79
+ /** The needle appearing contiguously, in order, somewhere in the haystack. */
80
+ #containsSubSequence(haystack, needle, baseType) {
81
+ for (let start = 0; start + needle.length <= haystack.length; start++) {
82
+ if (needle.every((value, offset) => this.#equal(haystack[start + offset], value, baseType))) {
83
+ return true;
84
+ }
33
85
  }
34
86
  return false;
35
87
  }
@@ -1 +1 @@
1
- {"version":3,"file":"qti-contains.js","sourceRoot":"","sources":["../../../src/components/qti-contains/qti-contains.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAI9D,oFAAoF;AACpF,0DAA0D;AAC1D,MAAM,OAAO,WAAY,SAAQ,sBAAsB;IACrC,SAAS;QACvB,yDAAyD;QACzD,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,EAAwB,CAAC;QACzD,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/B,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YACzB,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YACzB,IACE,MAAM,CAAC,QAAQ,KAAK,cAAc;gBAClC,MAAM,CAAC,QAAQ,KAAK,cAAc;gBAClC,MAAM,CAAC,WAAW,KAAK,UAAU,EACjC,CAAC;gBACD,MAAM,WAAW,GAAG,MAAM,CAAC,KAAiB,CAAC;gBAC7C,MAAM,WAAW,GAAG,MAAM,CAAC,KAAiB,CAAC;gBAC7C,MAAM,UAAU,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;gBAEpE,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;gBACrC,OAAO,MAAM,CAAC;YAChB,CAAC;iBAAM,IACL,MAAM,CAAC,QAAQ,KAAK,cAAc;gBAClC,MAAM,CAAC,QAAQ,KAAK,cAAc;gBAClC,MAAM,CAAC,WAAW,KAAK,QAAQ,EAC/B,CAAC;gBACD,MAAM,WAAW,GAAG,MAAM,CAAC,KAAe,CAAC;gBAC3C,MAAM,WAAW,GAAG,MAAM,CAAC,KAAiB,CAAC;gBAC7C,OAAO,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;YAC3C,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,KAAK,CACX,yHAAyH,CAC1H,CAAC;YACJ,CAAC;QACH,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACjE,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;CACF","sourcesContent":["import { QtiConditionExpression } from '@qti-components/base';\n\nimport type { ResponseVariable } from '@qti-components/base';\n\n// PK: For the contains we assume the expressions to calculate are all directedPairs\n// I don't know it this in QTI is always the case however?\nexport class QtiContains extends QtiConditionExpression {\n public override getResult() {\n // TODO: implement this for other types than directedPair\n const values = this.getVariables() as ResponseVariable[];\n if (this.children.length === 2) {\n const value1 = values[0];\n const value2 = values[1];\n if (\n value1.baseType === 'directedPair' &&\n value2.baseType === 'directedPair' &&\n value1.cardinality === 'multiple'\n ) {\n const projection1 = value1.value as string[];\n const projection2 = value2.value as string[];\n const enumerable = projection1.filter(x => projection2.includes(x));\n\n const result = enumerable.length > 0;\n return result;\n } else if (\n value1.baseType === 'directedPair' &&\n value2.baseType === 'directedPair' &&\n value1.cardinality === 'single'\n ) {\n const projection1 = value1.value as string;\n const projection2 = value2.value as string[];\n return projection2.includes(projection1);\n } else {\n console.error(\n 'unsupported baseType or cardinality in qti contains, only baseType: directedPair and cardinality: multiple is supported'\n );\n }\n } else {\n console.error('unexpected number of children in qti contains');\n }\n return false;\n }\n}\n"]}
1
+ {"version":3,"file":"qti-contains.js","sourceRoot":"","sources":["../../../src/components/qti-contains/qti-contains.ts"],"names":[],"mappings":"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","sourcesContent":["import { QtiExpression, ScoringHelper } from '@qti-components/base';\n\nimport type { BaseType, ResponseVariable } from '@qti-components/base';\n\n/**\n * @summary The qti-contains operator tests whether one container holds another.\n * @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#contains\n *\n * Takes 2 sub-expressions of the same base-type and the same cardinality,\n * either both multiple or both ordered.\n *\n * Cardinality decides what containment means. An unordered container holds\n * another when every one of its values is present, multiplicity included and\n * order disregarded: `[A,B,C]` contains `[C,A]`. An ordered container needs the\n * second to appear as a strict sub-sequence: `[A,B,C]` contains `[B,C]` but not\n * `[C,A]`.\n *\n * Special cases: Returns NULL if either sub-expression is NULL.\n */\nexport class QtiContains extends QtiExpression<boolean | null> {\n public override getResult(): boolean | null {\n if (this.children.length !== 2) {\n console.error('qti-contains takes two sub-expressions');\n return null;\n }\n\n const [container, sub] = this.getVariables() as ResponseVariable[];\n if (!container || !sub) {\n return null;\n }\n\n // If either sub-expression is NULL then the result of the operator is NULL.\n if (container.value === null || container.value === undefined || sub.value === null || sub.value === undefined) {\n return null;\n }\n\n if (container.baseType !== sub.baseType) {\n console.warn('qti-contains: both sub-expressions should have the same base-type');\n }\n\n const baseType = container.baseType ?? sub.baseType;\n\n /*\n * Not what the spec describes — it requires both operands to be containers\n * — but items in the wild write `contains` with a single first operand and\n * mean \"is this value in that container\", which is `qti-member`. Answering\n * that rather than returning false keeps those items scoring; the warning\n * is there so the authoring gets fixed.\n */\n if (container.cardinality === 'single' && sub.cardinality !== 'single') {\n console.warn('qti-contains: first sub-expression is single; treating it as a membership test');\n return this.#toArray(sub.value).some(candidate => this.#equal(candidate, container.value as string, baseType));\n }\n\n const haystack = this.#toArray(container.value);\n const needle = this.#toArray(sub.value);\n\n // An empty container is contained in anything.\n if (needle.length === 0) return true;\n\n return container.cardinality === 'ordered' && sub.cardinality === 'ordered'\n ? this.#containsSubSequence(haystack, needle, baseType)\n : this.#containsSubMultiset(haystack, needle, baseType);\n }\n\n #toArray(value: Readonly<string | string[]>): string[] {\n return Array.isArray(value) ? [...value] : [value as string];\n }\n\n /**\n * `ScoringHelper.compareSingleValues` knows how each base type compares — a\n * `pair` ignoring the order within it, numbers compared numerically. It\n * returns false for a base type it has no case for, so an unknown one falls\n * back to comparing the values as written.\n */\n #equal(a: string, b: string, baseType: BaseType): boolean {\n return ScoringHelper.compareSingleValues(a, b, baseType) || a === b;\n }\n\n /** Every needle value present, multiplicity respected, order disregarded. */\n #containsSubMultiset(haystack: string[], needle: string[], baseType: BaseType): boolean {\n const remaining = [...haystack];\n return needle.every(value => {\n const index = remaining.findIndex(candidate => this.#equal(candidate, value, baseType));\n if (index === -1) return false;\n // Consumed, so a value needed twice has to appear twice.\n remaining.splice(index, 1);\n return true;\n });\n }\n\n /** The needle appearing contiguously, in order, somewhere in the haystack. */\n #containsSubSequence(haystack: string[], needle: string[], baseType: BaseType): boolean {\n for (let start = 0; start + needle.length <= haystack.length; start++) {\n if (needle.every((value, offset) => this.#equal(haystack[start + offset], value, baseType))) {\n return true;\n }\n }\n return false;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'qti-contains': QtiContains;\n }\n}\n"]}
@@ -45,7 +45,6 @@ export class QtiDefault extends QtiExpression {
45
45
  // If item identifier is specified, look in that specific item
46
46
  if (itemId) {
47
47
  const itemContext = this._testContext.items.find(item => item.identifier === itemId);
48
- console.debug(`itemContext: ${JSON.stringify(itemContext)}`);
49
48
  if (!this._testContext || !itemContext) {
50
49
  console.warn(`qti-default: item "${itemId}" not found in test context`);
51
50
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"qti-default.js","sourceRoot":"","sources":["../../../src/components/qti-default/qti-default.ts"],"names":[],"mappings":"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","sourcesContent":["import { property, state } from 'lit/decorators.js';\nimport { consume } from '@lit/context';\n\nimport { QtiExpression } from '@qti-components/base';\nimport { itemContext } from '@qti-components/base';\nimport { testContext } from '@qti-components/base';\n\nimport type { ItemContext } from '@qti-components/base';\nimport type { TestContext } from '@qti-components/base';\n\n/**\n * @summary The qti-default operator retrieves the defined default value for a variable.\n * @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#default\n *\n * Looks up the declaration for the identified item variable.\n * Returns the associated defaultValue.\n * Special cases: Returns NULL if no default value was declared.\n */\nexport class QtiDefault extends QtiExpression<any> {\n @property({ type: String }) identifier: string = '';\n\n @consume({ context: itemContext, subscribe: true })\n @state()\n protected override context?: ItemContext;\n\n @state()\n @consume({ context: testContext, subscribe: true })\n protected _testContext?: TestContext;\n\n public override getResult(): any {\n if (!this.identifier) {\n console.error('qti-default requires an identifier attribute');\n return null;\n }\n\n // Check for item identifier prefixing (format: \"itemId.variableId\")\n const parts = this.identifier.split('.');\n\n let itemId: string | null = null;\n let variableId: string;\n\n if (parts.length === 2) {\n itemId = parts[0];\n variableId = parts[1];\n } else if (parts.length === 1) {\n variableId = parts[0];\n } else {\n console.error('qti-default: invalid identifier format');\n return null;\n }\n\n // If item identifier is specified, look in that specific item\n if (itemId) {\n const itemContext = this._testContext.items.find(item => item.identifier === itemId);\n console.debug(`itemContext: ${JSON.stringify(itemContext)}`);\n if (!this._testContext || !itemContext) {\n console.warn(`qti-default: item \"${itemId}\" not found in test context`);\n return null;\n }\n const variable = this._testContext.items\n .find(item => item.identifier === itemId)\n ?.variables.find(v => v.identifier === variableId);\n if (!variable) {\n console.warn(`qti-default: variable \"${variableId}\" not found in item \"${itemId}\"`);\n return null;\n }\n return variable.defaultValue || null;\n }\n\n // Otherwise, look in the current item context\n if (!this.context?.variables) {\n console.warn('qti-default: no variables found in item context');\n return null;\n }\n const variable = this.context.variables.find(v => v.identifier === variableId);\n if (!variable) {\n console.warn(`qti-default: variable \"${variableId}\" not found in item context`);\n return null;\n }\n return variable.defaultValue || null;\n }\n}\n"]}
1
+ {"version":3,"file":"qti-default.js","sourceRoot":"","sources":["../../../src/components/qti-default/qti-default.ts"],"names":[],"mappings":"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","sourcesContent":["import { property, state } from 'lit/decorators.js';\nimport { consume } from '@lit/context';\n\nimport { QtiExpression } from '@qti-components/base';\nimport { itemContext } from '@qti-components/base';\nimport { testContext } from '@qti-components/base';\n\nimport type { ItemContext } from '@qti-components/base';\nimport type { TestContext } from '@qti-components/base';\n\n/**\n * @summary The qti-default operator retrieves the defined default value for a variable.\n * @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#default\n *\n * Looks up the declaration for the identified item variable.\n * Returns the associated defaultValue.\n * Special cases: Returns NULL if no default value was declared.\n */\nexport class QtiDefault extends QtiExpression<any> {\n @property({ type: String }) identifier: string = '';\n\n @consume({ context: itemContext, subscribe: true })\n @state()\n protected override context?: ItemContext;\n\n @state()\n @consume({ context: testContext, subscribe: true })\n protected _testContext?: TestContext;\n\n public override getResult(): any {\n if (!this.identifier) {\n console.error('qti-default requires an identifier attribute');\n return null;\n }\n\n // Check for item identifier prefixing (format: \"itemId.variableId\")\n const parts = this.identifier.split('.');\n\n let itemId: string | null = null;\n let variableId: string;\n\n if (parts.length === 2) {\n itemId = parts[0];\n variableId = parts[1];\n } else if (parts.length === 1) {\n variableId = parts[0];\n } else {\n console.error('qti-default: invalid identifier format');\n return null;\n }\n\n // If item identifier is specified, look in that specific item\n if (itemId) {\n const itemContext = this._testContext.items.find(item => item.identifier === itemId);\n if (!this._testContext || !itemContext) {\n console.warn(`qti-default: item \"${itemId}\" not found in test context`);\n return null;\n }\n const variable = this._testContext.items\n .find(item => item.identifier === itemId)\n ?.variables.find(v => v.identifier === variableId);\n if (!variable) {\n console.warn(`qti-default: variable \"${variableId}\" not found in item \"${itemId}\"`);\n return null;\n }\n return variable.defaultValue || null;\n }\n\n // Otherwise, look in the current item context\n if (!this.context?.variables) {\n console.warn('qti-default: no variables found in item context');\n return null;\n }\n const variable = this.context.variables.find(v => v.identifier === variableId);\n if (!variable) {\n console.warn(`qti-default: variable \"${variableId}\" not found in item context`);\n return null;\n }\n return variable.defaultValue || null;\n }\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import { QtiExpression } from '@qti-components/base';
2
+ import type { BaseType } from '@qti-components/base';
2
3
  /**
3
4
  * @summary The qti-divide operator performs numerical division.
4
5
  * @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#divide
@@ -9,5 +10,7 @@ import { QtiExpression } from '@qti-components/base';
9
10
  */
10
11
  export declare class QtiDivide extends QtiExpression<number | null> {
11
12
  #private;
13
+ /** Always a float, even dividing two integers exactly. */
14
+ get resultBaseType(): BaseType;
12
15
  getResult(): number | null;
13
16
  }
@@ -8,6 +8,10 @@ import { QtiExpression } from '@qti-components/base';
8
8
  * Special cases: Returns NULL if either sub-expression is NULL or if divisor is 0.
9
9
  */
10
10
  export class QtiDivide extends QtiExpression {
11
+ /** Always a float, even dividing two integers exactly. */
12
+ get resultBaseType() {
13
+ return 'float';
14
+ }
11
15
  getResult() {
12
16
  const values = this.#collectNumericValues(this.getVariables());
13
17
  if (values.length !== 2) {
@@ -1 +1 @@
1
- {"version":3,"file":"qti-divide.js","sourceRoot":"","sources":["../../../src/components/qti-divide/qti-divide.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAIrD;;;;;;;GAOG;AACH,MAAM,OAAO,SAAU,SAAQ,aAA4B;IACzC,SAAS;QACvB,MAAM,MAAM,GAAG,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,YAAY,EAAwB,CAAC,CAAC;QACrF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,OAAO,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;YAC9D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;YACpB,OAAO,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;YAC7C,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC/B,CAAC;IAED,qBAAqB,CAAC,SAA6B;QACjD,MAAM,MAAM,GAAa,EAAE,CAAC;QAE5B,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,SAAS;YACX,CAAC;YAED,MAAM,SAAS,GAAG,CAAC,GAAY,EAAE,EAAE;gBACjC,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;oBACtC,OAAO;gBACT,CAAC;gBACD,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;gBAC5C,IAAI,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC3B,OAAO,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;oBAC1D,OAAO;gBACT,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACxB,CAAC,CAAC;YAEF,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBAClC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;gBAChD,SAAS;YACX,CAAC;YAED,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF","sourcesContent":["import { QtiExpression } from '@qti-components/base';\n\nimport type { ResponseVariable } from '@qti-components/base';\n\n/**\n * @summary The qti-divide operator performs numerical division.\n * @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#divide\n *\n * Takes exactly 2 sub-expressions with numerical base-types and single cardinality.\n * Returns a single float representing (first / second).\n * Special cases: Returns NULL if either sub-expression is NULL or if divisor is 0.\n */\nexport class QtiDivide extends QtiExpression<number | null> {\n public override getResult(): number | null {\n const values = this.#collectNumericValues(this.getVariables() as ResponseVariable[]);\n if (values.length !== 2) {\n console.warn('qti-divide: expected exactly 2 numeric values');\n return null;\n }\n\n if (values[1] === 0) {\n console.warn('qti-divide: division by zero');\n return null;\n }\n\n return values[0] / values[1];\n }\n\n #collectNumericValues(variables: ResponseVariable[]): number[] {\n const values: number[] = [];\n\n for (const variable of variables) {\n if (!variable) {\n continue;\n }\n\n const pushValue = (raw: unknown) => {\n if (raw === null || raw === undefined) {\n return;\n }\n const numValue = parseFloat(raw.toString());\n if (Number.isNaN(numValue)) {\n console.warn('qti-divide: non-numeric value encountered');\n return;\n }\n values.push(numValue);\n };\n\n if (Array.isArray(variable.value)) {\n variable.value.forEach(item => pushValue(item));\n continue;\n }\n\n pushValue(variable.value);\n }\n\n return values;\n }\n}\n"]}
1
+ {"version":3,"file":"qti-divide.js","sourceRoot":"","sources":["../../../src/components/qti-divide/qti-divide.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAIrD;;;;;;;GAOG;AACH,MAAM,OAAO,SAAU,SAAQ,aAA4B;IACzD,0DAA0D;IAC1D,IAAoB,cAAc;QAChC,OAAO,OAAO,CAAC;IACjB,CAAC;IAEe,SAAS;QACvB,MAAM,MAAM,GAAG,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,YAAY,EAAwB,CAAC,CAAC;QACrF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,OAAO,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;YAC9D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;YACpB,OAAO,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;YAC7C,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC/B,CAAC;IAED,qBAAqB,CAAC,SAA6B;QACjD,MAAM,MAAM,GAAa,EAAE,CAAC;QAE5B,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,SAAS;YACX,CAAC;YAED,MAAM,SAAS,GAAG,CAAC,GAAY,EAAE,EAAE;gBACjC,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;oBACtC,OAAO;gBACT,CAAC;gBACD,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;gBAC5C,IAAI,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC3B,OAAO,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;oBAC1D,OAAO;gBACT,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACxB,CAAC,CAAC;YAEF,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBAClC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;gBAChD,SAAS;YACX,CAAC;YAED,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF","sourcesContent":["import { QtiExpression } from '@qti-components/base';\n\nimport type { BaseType, ResponseVariable } from '@qti-components/base';\n\n/**\n * @summary The qti-divide operator performs numerical division.\n * @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#divide\n *\n * Takes exactly 2 sub-expressions with numerical base-types and single cardinality.\n * Returns a single float representing (first / second).\n * Special cases: Returns NULL if either sub-expression is NULL or if divisor is 0.\n */\nexport class QtiDivide extends QtiExpression<number | null> {\n /** Always a float, even dividing two integers exactly. */\n public override get resultBaseType(): BaseType {\n return 'float';\n }\n\n public override getResult(): number | null {\n const values = this.#collectNumericValues(this.getVariables() as ResponseVariable[]);\n if (values.length !== 2) {\n console.warn('qti-divide: expected exactly 2 numeric values');\n return null;\n }\n\n if (values[1] === 0) {\n console.warn('qti-divide: division by zero');\n return null;\n }\n\n return values[0] / values[1];\n }\n\n #collectNumericValues(variables: ResponseVariable[]): number[] {\n const values: number[] = [];\n\n for (const variable of variables) {\n if (!variable) {\n continue;\n }\n\n const pushValue = (raw: unknown) => {\n if (raw === null || raw === undefined) {\n return;\n }\n const numValue = parseFloat(raw.toString());\n if (Number.isNaN(numValue)) {\n console.warn('qti-divide: non-numeric value encountered');\n return;\n }\n values.push(numValue);\n };\n\n if (Array.isArray(variable.value)) {\n variable.value.forEach(item => pushValue(item));\n continue;\n }\n\n pushValue(variable.value);\n }\n\n return values;\n }\n}\n"]}
@@ -4,11 +4,22 @@ import { QtiExpression } from '@qti-components/base';
4
4
  * @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#equal
5
5
  *
6
6
  * Takes 2 sub-expressions with numerical base-types and single cardinality.
7
- * Supports tolerance-mode (exact, absolute, relative) for floating-point comparisons.
7
+ * Supports tolerance-mode (exact, absolute, relative) for floating-point comparisons,
8
+ * with 'tolerance', 'include-lower-bound' and 'include-upper-bound'.
8
9
  * Returns true if numerically equal within specified tolerance, false otherwise.
9
10
  * Special cases: Returns NULL if either sub-expression is NULL.
10
11
  */
11
12
  export declare class QtiEqual extends QtiExpression<boolean> {
13
+ #private;
12
14
  toleranceMode: 'exact' | 'relative' | 'absolute';
15
+ /** One or two numbers: the lower and upper tolerance. One means both. */
16
+ tolerance: string;
17
+ /**
18
+ * Read off the attribute rather than declared as a Lit boolean property: QTI
19
+ * writes `include-lower-bound="false"`, and Lit's boolean converter takes the
20
+ * mere presence of an attribute as true. Both default to true, per the spec.
21
+ */
22
+ get includeLowerBound(): boolean;
23
+ get includeUpperBound(): boolean;
13
24
  getResult(): boolean;
14
25
  }
@@ -12,7 +12,8 @@ import { ScoringHelper } from '@qti-components/base';
12
12
  * @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#equal
13
13
  *
14
14
  * Takes 2 sub-expressions with numerical base-types and single cardinality.
15
- * Supports tolerance-mode (exact, absolute, relative) for floating-point comparisons.
15
+ * Supports tolerance-mode (exact, absolute, relative) for floating-point comparisons,
16
+ * with 'tolerance', 'include-lower-bound' and 'include-upper-bound'.
16
17
  * Returns true if numerically equal within specified tolerance, false otherwise.
17
18
  * Special cases: Returns NULL if either sub-expression is NULL.
18
19
  */
@@ -20,6 +21,19 @@ export class QtiEqual extends QtiExpression {
20
21
  constructor() {
21
22
  super(...arguments);
22
23
  this.toleranceMode = 'exact';
24
+ /** One or two numbers: the lower and upper tolerance. One means both. */
25
+ this.tolerance = '';
26
+ }
27
+ /**
28
+ * Read off the attribute rather than declared as a Lit boolean property: QTI
29
+ * writes `include-lower-bound="false"`, and Lit's boolean converter takes the
30
+ * mere presence of an attribute as true. Both default to true, per the spec.
31
+ */
32
+ get includeLowerBound() {
33
+ return this.getAttribute('include-lower-bound') !== 'false';
34
+ }
35
+ get includeUpperBound() {
36
+ return this.getAttribute('include-upper-bound') !== 'false';
23
37
  }
24
38
  getResult() {
25
39
  if (this.children.length === 2) {
@@ -29,10 +43,6 @@ export class QtiEqual extends QtiExpression {
29
43
  if (!value1 || !value2) {
30
44
  return null;
31
45
  }
32
- if (this.toleranceMode !== 'exact') {
33
- console.error('toleranceMode is not supported yet');
34
- return false;
35
- }
36
46
  if (value1.cardinality !== 'single' ||
37
47
  value2.cardinality !== 'single' ||
38
48
  Array.isArray(value1.value) ||
@@ -43,13 +53,47 @@ export class QtiEqual extends QtiExpression {
43
53
  if (value1.value === null || value2.value === null || value1.value === undefined || value2.value === undefined) {
44
54
  return null;
45
55
  }
46
- return ScoringHelper.compareSingleValues(value1.value, value2.value, value1.baseType);
56
+ if (this.toleranceMode === 'exact') {
57
+ return ScoringHelper.compareSingleValues(value1.value, value2.value, value1.baseType);
58
+ }
59
+ return this.#withinTolerance(parseFloat(value1.value), parseFloat(value2.value));
47
60
  }
48
61
  console.error('unexpected number of children in qti-equal');
49
62
  return null;
50
63
  }
64
+ /**
65
+ * The tolerant comparison: the *second* expression is the reference value and
66
+ * the tolerance is applied around it, so `equal(x, y)` asks whether x falls in
67
+ * y's tolerance band rather than the other way round.
68
+ *
69
+ * `absolute` takes the tolerances as offsets, `relative` as percentages of the
70
+ * reference. A single tolerance is used on both sides.
71
+ */
72
+ #withinTolerance(value, reference) {
73
+ if (Number.isNaN(value) || Number.isNaN(reference)) {
74
+ console.error('qti-equal with a tolerance needs numeric values');
75
+ return null;
76
+ }
77
+ const tolerances = this.tolerance
78
+ .split(/\s+/)
79
+ .filter(Boolean)
80
+ .map(t => parseFloat(t));
81
+ if (tolerances.length === 0 || tolerances.some(t => Number.isNaN(t))) {
82
+ console.error(`qti-equal with tolerance-mode="${this.toleranceMode}" needs a numeric tolerance`);
83
+ return null;
84
+ }
85
+ const [lowerTolerance, upperTolerance = lowerTolerance] = tolerances;
86
+ const [lower, upper] = this.toleranceMode === 'absolute'
87
+ ? [reference - lowerTolerance, reference + upperTolerance]
88
+ : [reference * (1 - lowerTolerance / 100), reference * (1 + upperTolerance / 100)];
89
+ return ((this.includeLowerBound ? value >= lower : value > lower) &&
90
+ (this.includeUpperBound ? value <= upper : value < upper));
91
+ }
51
92
  }
52
93
  __decorate([
53
- property({ type: String })
94
+ property({ type: String, attribute: 'tolerance-mode' })
54
95
  ], QtiEqual.prototype, "toleranceMode", void 0);
96
+ __decorate([
97
+ property({ type: String })
98
+ ], QtiEqual.prototype, "tolerance", void 0);
55
99
  //# sourceMappingURL=qti-equal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"qti-equal.js","sourceRoot":"","sources":["../../../src/components/qti-equal/qti-equal.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAIrD;;;;;;;;GAQG;AACH,MAAM,OAAO,QAAS,SAAQ,aAAsB;IAApD;;QAC8B,kBAAa,GAAsC,OAAO,CAAC;IAmCzF,CAAC;IAjCiB,SAAS;QACvB,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,EAAwB,CAAC;YACzD,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YACzB,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YAEzB,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;gBACvB,OAAO,IAAI,CAAC;YACd,CAAC;YAED,IAAI,IAAI,CAAC,aAAa,KAAK,OAAO,EAAE,CAAC;gBACnC,OAAO,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;gBACpD,OAAO,KAAK,CAAC;YACf,CAAC;YACD,IACE,MAAM,CAAC,WAAW,KAAK,QAAQ;gBAC/B,MAAM,CAAC,WAAW,KAAK,QAAQ;gBAC/B,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;gBAC3B,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAC3B,CAAC;gBACD,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;gBACrD,OAAO,KAAK,CAAC;YACf,CAAC;YAED,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC/G,OAAO,IAAI,CAAC;YACd,CAAC;YAED,OAAO,aAAa,CAAC,mBAAmB,CAAC,MAAM,CAAC,KAAe,EAAE,MAAM,CAAC,KAAe,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC5G,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAC5D,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAnC6B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;+CAA4D","sourcesContent":["import { property } from 'lit/decorators.js';\n\nimport { QtiExpression } from '@qti-components/base';\nimport { ScoringHelper } from '@qti-components/base';\n\nimport type { ResponseVariable } from '@qti-components/base';\n\n/**\n * @summary The qti-equal operator checks if two numerical expressions are equal.\n * @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#equal\n *\n * Takes 2 sub-expressions with numerical base-types and single cardinality.\n * Supports tolerance-mode (exact, absolute, relative) for floating-point comparisons.\n * Returns true if numerically equal within specified tolerance, false otherwise.\n * Special cases: Returns NULL if either sub-expression is NULL.\n */\nexport class QtiEqual extends QtiExpression<boolean> {\n @property({ type: String }) toleranceMode: 'exact' | 'relative' | 'absolute' = 'exact';\n\n public override getResult() {\n if (this.children.length === 2) {\n const values = this.getVariables() as ResponseVariable[];\n const value1 = values[0];\n const value2 = values[1];\n\n if (!value1 || !value2) {\n return null;\n }\n\n if (this.toleranceMode !== 'exact') {\n console.error('toleranceMode is not supported yet');\n return false;\n }\n if (\n value1.cardinality !== 'single' ||\n value2.cardinality !== 'single' ||\n Array.isArray(value1.value) ||\n Array.isArray(value2.value)\n ) {\n console.error('unexpected cardinality in qti equal');\n return false;\n }\n\n if (value1.value === null || value2.value === null || value1.value === undefined || value2.value === undefined) {\n return null;\n }\n\n return ScoringHelper.compareSingleValues(value1.value as string, value2.value as string, value1.baseType);\n }\n console.error('unexpected number of children in qti-equal');\n return null;\n }\n}\n"]}
1
+ {"version":3,"file":"qti-equal.js","sourceRoot":"","sources":["../../../src/components/qti-equal/qti-equal.ts"],"names":[],"mappings":"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","sourcesContent":["import { property } from 'lit/decorators.js';\n\nimport { QtiExpression } from '@qti-components/base';\nimport { ScoringHelper } from '@qti-components/base';\n\nimport type { ResponseVariable } from '@qti-components/base';\n\n/**\n * @summary The qti-equal operator checks if two numerical expressions are equal.\n * @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#equal\n *\n * Takes 2 sub-expressions with numerical base-types and single cardinality.\n * Supports tolerance-mode (exact, absolute, relative) for floating-point comparisons,\n * with 'tolerance', 'include-lower-bound' and 'include-upper-bound'.\n * Returns true if numerically equal within specified tolerance, false otherwise.\n * Special cases: Returns NULL if either sub-expression is NULL.\n */\nexport class QtiEqual extends QtiExpression<boolean> {\n @property({ type: String, attribute: 'tolerance-mode' }) toleranceMode: 'exact' | 'relative' | 'absolute' = 'exact';\n\n /** One or two numbers: the lower and upper tolerance. One means both. */\n @property({ type: String }) tolerance: string = '';\n\n /**\n * Read off the attribute rather than declared as a Lit boolean property: QTI\n * writes `include-lower-bound=\"false\"`, and Lit's boolean converter takes the\n * mere presence of an attribute as true. Both default to true, per the spec.\n */\n get includeLowerBound(): boolean {\n return this.getAttribute('include-lower-bound') !== 'false';\n }\n\n get includeUpperBound(): boolean {\n return this.getAttribute('include-upper-bound') !== 'false';\n }\n\n public override getResult() {\n if (this.children.length === 2) {\n const values = this.getVariables() as ResponseVariable[];\n const value1 = values[0];\n const value2 = values[1];\n\n if (!value1 || !value2) {\n return null;\n }\n\n if (\n value1.cardinality !== 'single' ||\n value2.cardinality !== 'single' ||\n Array.isArray(value1.value) ||\n Array.isArray(value2.value)\n ) {\n console.error('unexpected cardinality in qti equal');\n return false;\n }\n\n if (value1.value === null || value2.value === null || value1.value === undefined || value2.value === undefined) {\n return null;\n }\n\n if (this.toleranceMode === 'exact') {\n return ScoringHelper.compareSingleValues(value1.value as string, value2.value as string, value1.baseType);\n }\n\n return this.#withinTolerance(parseFloat(value1.value as string), parseFloat(value2.value as string));\n }\n console.error('unexpected number of children in qti-equal');\n return null;\n }\n\n /**\n * The tolerant comparison: the *second* expression is the reference value and\n * the tolerance is applied around it, so `equal(x, y)` asks whether x falls in\n * y's tolerance band rather than the other way round.\n *\n * `absolute` takes the tolerances as offsets, `relative` as percentages of the\n * reference. A single tolerance is used on both sides.\n */\n #withinTolerance(value: number, reference: number): boolean | null {\n if (Number.isNaN(value) || Number.isNaN(reference)) {\n console.error('qti-equal with a tolerance needs numeric values');\n return null;\n }\n\n const tolerances = this.tolerance\n .split(/\\s+/)\n .filter(Boolean)\n .map(t => parseFloat(t));\n if (tolerances.length === 0 || tolerances.some(t => Number.isNaN(t))) {\n console.error(`qti-equal with tolerance-mode=\"${this.toleranceMode}\" needs a numeric tolerance`);\n return null;\n }\n\n const [lowerTolerance, upperTolerance = lowerTolerance] = tolerances;\n\n const [lower, upper] =\n this.toleranceMode === 'absolute'\n ? [reference - lowerTolerance, reference + upperTolerance]\n : [reference * (1 - lowerTolerance / 100), reference * (1 + upperTolerance / 100)];\n\n return (\n (this.includeLowerBound ? value >= lower : value > lower) &&\n (this.includeUpperBound ? value <= upper : value < upper)\n );\n }\n}\n"]}
@@ -18,6 +18,14 @@ import { QtiExpression } from '@qti-components/base';
18
18
  export class QtiEqualRounded extends QtiExpression {
19
19
  constructor() {
20
20
  super(...arguments);
21
+ /*
22
+ * `attribute` spelled out, because Lit would otherwise derive `roundingmode`
23
+ * from the property name while QTI writes `rounding-mode`. Without it the
24
+ * attribute never reached the property and every comparison silently used the
25
+ * `significantFigures` default — an item asking for 3 decimal places got 3
26
+ * significant figures instead, so 54.598 and 54.608 both read as 54.6 and a
27
+ * wrong answer scored full marks.
28
+ */
21
29
  this.roundingMode = 'significantFigures';
22
30
  }
23
31
  get figures() {
@@ -87,6 +95,6 @@ export class QtiEqualRounded extends QtiExpression {
87
95
  }
88
96
  }
89
97
  __decorate([
90
- property({ type: String })
98
+ property({ type: String, attribute: 'rounding-mode' })
91
99
  ], QtiEqualRounded.prototype, "roundingMode", void 0);
92
100
  //# sourceMappingURL=qti-equal-rounded.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"qti-equal-rounded.js","sourceRoot":"","sources":["../../../src/components/qti-equal-rounded/qti-equal-rounded.ts"],"names":[],"mappings":"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","sourcesContent":["import { property } from 'lit/decorators.js';\n\nimport { QtiExpression } from '@qti-components/base';\n\nimport type { ResponseVariable } from '@qti-components/base';\n\n/**\n * @summary The qti-equal-rounded operator compares two values after rounding.\n * @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#equalRounded\n *\n * Takes 2 sub-expressions with numerical base-types and single cardinality.\n * Requires 'figures' and 'rounding-mode' (decimalPlaces or significantFigures) attributes.\n * Returns true if equal after specified rounding, false otherwise.\n * Special cases: Returns NULL if either sub-expression is NULL.\n */\nexport class QtiEqualRounded extends QtiExpression<boolean> {\n @property({ type: String }) roundingMode: 'decimalPlaces' | 'significantFigures' = 'significantFigures';\n\n get figures() {\n const attr = this.getAttribute('figures');\n if (!attr) {\n console.error('figures attribute is missing');\n return null;\n }\n const figures = parseInt(this.getAttribute('figures') || '0');\n if (isNaN(figures)) {\n console.error('figures attribute is not a number');\n return null;\n }\n if (figures < 0) {\n console.error('figures attribute is negative');\n return null;\n }\n if (figures < 1 && this.roundingMode === 'significantFigures') {\n console.error('figures cannot be smaller than 1 for RoundingMode significantFigures');\n return null;\n }\n return figures;\n }\n\n public override getResult() {\n if (this.children.length === 2) {\n const values = this.getVariables() as ResponseVariable[];\n const value1 = values[0];\n const value2 = values[1];\n if (this.roundingMode === null) {\n return null;\n }\n if (\n value1.cardinality !== 'single' ||\n value2.cardinality !== 'single' ||\n Array.isArray(value1.value) ||\n Array.isArray(value2.value)\n ) {\n console.error('unexpected cardinality in qti equal');\n return false;\n }\n switch (values[0].baseType) {\n case 'integer':\n case 'float': {\n const float1 = parseFloat(value1.value as string);\n const float2 = parseFloat(value2.value as string);\n\n if (!isNaN(float1) && !isNaN(float2)) {\n if (this.roundingMode === 'significantFigures') {\n return float1.toPrecision(this.figures) === float2.toPrecision(this.figures);\n } else {\n return (\n Math.round(float1 * Math.pow(10, this.figures)) / Math.pow(10, this.figures) ===\n Math.round(float2 * Math.pow(10, this.figures)) / Math.pow(10, this.figures)\n );\n }\n } else {\n console.error(`value cannot be casted to numeric value in equalRounded operator: ${float1}, ${float2}`);\n }\n break;\n }\n default: {\n console.error(`values other than float and int cannot be used in equalRounded operator.`);\n break;\n }\n }\n return false;\n }\n console.error('unexpected number of children in qti-equal-rounded');\n return null;\n }\n}\n"]}
1
+ {"version":3,"file":"qti-equal-rounded.js","sourceRoot":"","sources":["../../../src/components/qti-equal-rounded/qti-equal-rounded.ts"],"names":[],"mappings":"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","sourcesContent":["import { property } from 'lit/decorators.js';\n\nimport { QtiExpression } from '@qti-components/base';\n\nimport type { ResponseVariable } from '@qti-components/base';\n\n/**\n * @summary The qti-equal-rounded operator compares two values after rounding.\n * @documentation https://www.imsglobal.org/spec/qti/v3p0/info/index.html#equalRounded\n *\n * Takes 2 sub-expressions with numerical base-types and single cardinality.\n * Requires 'figures' and 'rounding-mode' (decimalPlaces or significantFigures) attributes.\n * Returns true if equal after specified rounding, false otherwise.\n * Special cases: Returns NULL if either sub-expression is NULL.\n */\nexport class QtiEqualRounded extends QtiExpression<boolean> {\n /*\n * `attribute` spelled out, because Lit would otherwise derive `roundingmode`\n * from the property name while QTI writes `rounding-mode`. Without it the\n * attribute never reached the property and every comparison silently used the\n * `significantFigures` default — an item asking for 3 decimal places got 3\n * significant figures instead, so 54.598 and 54.608 both read as 54.6 and a\n * wrong answer scored full marks.\n */\n @property({ type: String, attribute: 'rounding-mode' })\n roundingMode: 'decimalPlaces' | 'significantFigures' = 'significantFigures';\n\n get figures() {\n const attr = this.getAttribute('figures');\n if (!attr) {\n console.error('figures attribute is missing');\n return null;\n }\n const figures = parseInt(this.getAttribute('figures') || '0');\n if (isNaN(figures)) {\n console.error('figures attribute is not a number');\n return null;\n }\n if (figures < 0) {\n console.error('figures attribute is negative');\n return null;\n }\n if (figures < 1 && this.roundingMode === 'significantFigures') {\n console.error('figures cannot be smaller than 1 for RoundingMode significantFigures');\n return null;\n }\n return figures;\n }\n\n public override getResult() {\n if (this.children.length === 2) {\n const values = this.getVariables() as ResponseVariable[];\n const value1 = values[0];\n const value2 = values[1];\n if (this.roundingMode === null) {\n return null;\n }\n if (\n value1.cardinality !== 'single' ||\n value2.cardinality !== 'single' ||\n Array.isArray(value1.value) ||\n Array.isArray(value2.value)\n ) {\n console.error('unexpected cardinality in qti equal');\n return false;\n }\n switch (values[0].baseType) {\n case 'integer':\n case 'float': {\n const float1 = parseFloat(value1.value as string);\n const float2 = parseFloat(value2.value as string);\n\n if (!isNaN(float1) && !isNaN(float2)) {\n if (this.roundingMode === 'significantFigures') {\n return float1.toPrecision(this.figures) === float2.toPrecision(this.figures);\n } else {\n return (\n Math.round(float1 * Math.pow(10, this.figures)) / Math.pow(10, this.figures) ===\n Math.round(float2 * Math.pow(10, this.figures)) / Math.pow(10, this.figures)\n );\n }\n } else {\n console.error(`value cannot be casted to numeric value in equalRounded operator: ${float1}, ${float2}`);\n }\n break;\n }\n default: {\n console.error(`values other than float and int cannot be used in equalRounded operator.`);\n break;\n }\n }\n return false;\n }\n console.error('unexpected number of children in qti-equal-rounded');\n return null;\n }\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import { QtiRule } from '../qti-rule/qti-rule';
2
+ /**
3
+ * @summary The qti-exit-response rule ends the response processing of an attempt.
4
+ * @documentation https://www.imsglobal.org/spec/qti/v3p0/impl#h.exit-response
5
+ *
6
+ * No rule after it runs, at any depth. The outcomes already set are kept: a
7
+ * `qti-set-outcome-value` writes as it is processed, so everything before the
8
+ * exit has landed by the time this throws.
9
+ */
10
+ export declare class QtiExitResponse extends QtiRule {
11
+ process(): never;
12
+ }
13
+ declare global {
14
+ interface HTMLElementTagNameMap {
15
+ 'qti-exit-response': QtiExitResponse;
16
+ }
17
+ }