@conform-ed/qti-react 0.0.13 → 0.0.15

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 (99) hide show
  1. package/dist/capability.d.ts +17 -0
  2. package/dist/content-model.d.ts +42 -0
  3. package/dist/graphic.d.ts +23 -0
  4. package/dist/index.d.ts +14 -0
  5. package/dist/index.js +155 -162
  6. package/dist/interactions/associate.d.ts +2 -0
  7. package/dist/interactions/choice.d.ts +2 -0
  8. package/dist/interactions/drawing.d.ts +2 -0
  9. package/dist/interactions/end-attempt.d.ts +2 -0
  10. package/dist/interactions/extended-text.d.ts +2 -0
  11. package/dist/interactions/gap-match.d.ts +2 -0
  12. package/dist/interactions/graphic.d.ts +13 -0
  13. package/dist/interactions/hottext.d.ts +2 -0
  14. package/dist/interactions/index.d.ts +18 -0
  15. package/dist/interactions/inline-choice.d.ts +2 -0
  16. package/dist/interactions/match.d.ts +2 -0
  17. package/dist/interactions/media.d.ts +2 -0
  18. package/dist/interactions/order.d.ts +2 -0
  19. package/dist/interactions/slider.d.ts +2 -0
  20. package/dist/interactions/text-entry.d.ts +2 -0
  21. package/dist/interactions/upload.d.ts +2 -0
  22. package/dist/normalized-item.d.ts +30 -0
  23. package/dist/pci/index.d.ts +6 -0
  24. package/dist/pci/interaction.d.ts +8 -0
  25. package/dist/pci/markup.d.ts +10 -0
  26. package/dist/pci/mount.d.ts +50 -0
  27. package/dist/pci/registry.d.ts +53 -0
  28. package/dist/pci/response.d.ts +11 -0
  29. package/dist/pci/skin.d.ts +12 -0
  30. package/dist/reference-skin/associate.d.ts +8 -0
  31. package/dist/reference-skin/choice.d.ts +8 -0
  32. package/dist/reference-skin/content.d.ts +6 -0
  33. package/dist/reference-skin/drawing.d.ts +9 -0
  34. package/dist/reference-skin/end-attempt.d.ts +7 -0
  35. package/dist/reference-skin/extended-text.d.ts +6 -0
  36. package/dist/reference-skin/gap-match.d.ts +8 -0
  37. package/dist/reference-skin/graphic-associate.d.ts +8 -0
  38. package/dist/reference-skin/graphic-base.d.ts +39 -0
  39. package/dist/reference-skin/graphic-gap-match.d.ts +8 -0
  40. package/dist/reference-skin/graphic-order.d.ts +8 -0
  41. package/dist/reference-skin/hotspot.d.ts +8 -0
  42. package/dist/reference-skin/hottext.d.ts +8 -0
  43. package/dist/reference-skin/index.d.ts +30 -0
  44. package/dist/reference-skin/inline-choice.d.ts +7 -0
  45. package/dist/reference-skin/match.d.ts +8 -0
  46. package/dist/reference-skin/media.d.ts +9 -0
  47. package/dist/reference-skin/order.d.ts +8 -0
  48. package/dist/reference-skin/position-object.d.ts +9 -0
  49. package/dist/reference-skin/select-point.d.ts +8 -0
  50. package/dist/reference-skin/slider.d.ts +8 -0
  51. package/dist/reference-skin/text-entry.d.ts +6 -0
  52. package/dist/reference-skin/upload.d.ts +8 -0
  53. package/dist/response-processing.d.ts +48 -0
  54. package/dist/rp/evaluate.d.ts +35 -0
  55. package/dist/rp/index.d.ts +4 -0
  56. package/dist/rp/interpreter.d.ts +15 -0
  57. package/dist/rp/template-processing.d.ts +49 -0
  58. package/dist/rp/templates.d.ts +8 -0
  59. package/dist/rp/types.d.ts +158 -0
  60. package/dist/rp/values.d.ts +27 -0
  61. package/dist/runtime.d.ts +164 -0
  62. package/dist/store.d.ts +61 -0
  63. package/dist/test/controller.d.ts +11 -0
  64. package/dist/test/index.d.ts +3 -0
  65. package/dist/test/session-store.d.ts +46 -0
  66. package/dist/test/types.d.ts +194 -0
  67. package/dist/types.d.ts +58 -0
  68. package/package.json +6 -6
  69. package/src/interactions/associate.ts +2 -2
  70. package/src/interactions/choice.ts +2 -2
  71. package/src/interactions/drawing.ts +2 -2
  72. package/src/interactions/end-attempt.ts +2 -2
  73. package/src/interactions/extended-text.ts +2 -2
  74. package/src/interactions/gap-match.ts +2 -2
  75. package/src/interactions/graphic.ts +7 -7
  76. package/src/interactions/hottext.ts +2 -2
  77. package/src/interactions/index.ts +0 -1
  78. package/src/interactions/inline-choice.ts +2 -2
  79. package/src/interactions/match.ts +2 -2
  80. package/src/interactions/media.ts +2 -2
  81. package/src/interactions/order.ts +2 -2
  82. package/src/interactions/slider.ts +2 -2
  83. package/src/interactions/text-entry.ts +2 -2
  84. package/src/interactions/upload.ts +2 -2
  85. package/src/normalized-item.ts +6 -4
  86. package/src/pci/interaction.ts +2 -2
  87. package/src/pci/mount.ts +12 -5
  88. package/src/pci/skin.ts +0 -1
  89. package/src/reference-skin/drawing.ts +25 -15
  90. package/src/reference-skin/index.ts +2 -3
  91. package/src/rp/evaluate.ts +22 -23
  92. package/src/rp/interpreter.ts +9 -6
  93. package/src/rp/template-processing.ts +8 -13
  94. package/src/rp/types.ts +12 -6
  95. package/src/rp/values.ts +19 -6
  96. package/src/runtime.ts +9 -7
  97. package/src/store.ts +14 -8
  98. package/src/test/controller.ts +10 -7
  99. package/src/test/session-store.ts +0 -1
package/dist/index.js CHANGED
@@ -743,11 +743,7 @@ function fromResponse(declaration, response) {
743
743
  if (raw.length === 0) {
744
744
  return null;
745
745
  }
746
- return {
747
- cardinality: declaration.cardinality,
748
- baseType: declaration.baseType,
749
- values: raw.map((value) => coerceScalar(value, declaration.baseType))
750
- };
746
+ return rpValue(declaration.cardinality, raw.map((value) => coerceScalar(value, declaration.baseType)), declaration.baseType);
751
747
  }
752
748
  function singleNumber(value) {
753
749
  if (value === null || value.values.length !== 1) {
@@ -763,6 +759,13 @@ function singleBoolean(value) {
763
759
  const member = value.values[0];
764
760
  return typeof member === "boolean" ? member : null;
765
761
  }
762
+ function rpValue(cardinality, values, baseType) {
763
+ return {
764
+ cardinality,
765
+ values,
766
+ ...baseType !== undefined ? { baseType } : {}
767
+ };
768
+ }
766
769
  function booleanValue(value) {
767
770
  return { cardinality: "single", baseType: "boolean", values: [value] };
768
771
  }
@@ -826,7 +829,7 @@ function fromFlatValue(value, cardinality, baseType) {
826
829
  if (values.length === 0) {
827
830
  return null;
828
831
  }
829
- return { cardinality, baseType, values: values.map((member) => coerceScalar(member, baseType)) };
832
+ return rpValue(cardinality, values.map((member) => coerceScalar(member, baseType)), baseType);
830
833
  }
831
834
  function toOutcomeValue(value) {
832
835
  if (value === null || value.values.length === 0) {
@@ -979,7 +982,7 @@ function evaluateExpression(expression, env) {
979
982
  case "baseValue": {
980
983
  const baseType = expression.baseType;
981
984
  const value = expression.value;
982
- return value === undefined ? null : { cardinality: "single", baseType, values: [coerceScalar(value, baseType)] };
985
+ return value === undefined ? null : rpValue("single", [coerceScalar(value, baseType)], baseType);
983
986
  }
984
987
  case "variable":
985
988
  return env.lookupVariable(expression.identifier ?? "");
@@ -988,11 +991,7 @@ function evaluateExpression(expression, env) {
988
991
  if (!declaration?.correctResponse) {
989
992
  return null;
990
993
  }
991
- return {
992
- cardinality: declaration.cardinality,
993
- baseType: declaration.baseType,
994
- values: declaration.correctResponse.values.map((entry) => coerceScalar(entry.value, declaration.baseType))
995
- };
994
+ return rpValue(declaration.cardinality, declaration.correctResponse.values.map((entry) => coerceScalar(entry.value, declaration.baseType)), declaration.baseType);
996
995
  }
997
996
  case "mapResponse": {
998
997
  const identifier = expression.identifier ?? "";
@@ -1030,7 +1029,7 @@ function evaluateExpression(expression, env) {
1030
1029
  const operand = expression.expressions?.[0];
1031
1030
  const value = operand === undefined ? null : evaluate(operand);
1032
1031
  const field = value?.fields?.find((entry) => entry.name === expression.fieldIdentifier);
1033
- return field === undefined ? null : { cardinality: "single", ...field.baseType ? { baseType: field.baseType } : {}, values: [field.value] };
1032
+ return field === undefined ? null : rpValue("single", [field.value], field.baseType);
1034
1033
  }
1035
1034
  case "and":
1036
1035
  case "or": {
@@ -1119,7 +1118,7 @@ function evaluateExpression(expression, env) {
1119
1118
  if (member === undefined) {
1120
1119
  return null;
1121
1120
  }
1122
- return { cardinality: "single", baseType: container.baseType, values: [member] };
1121
+ return rpValue("single", [member], container.baseType);
1123
1122
  }
1124
1123
  case "mathConstant": {
1125
1124
  const constant = expression.name === undefined ? undefined : mathConstants[expression.name];
@@ -1270,7 +1269,7 @@ function evaluateExpression(expression, env) {
1270
1269
  }
1271
1270
  const baseType = container.baseType ?? value.baseType;
1272
1271
  const remaining = container.values.filter((member) => !scalarsEqual(member, scalar, baseType, env.normalization));
1273
- return remaining.length === 0 ? null : { cardinality: container.cardinality, baseType: container.baseType, values: remaining };
1272
+ return remaining.length === 0 ? null : rpValue(container.cardinality, remaining, container.baseType);
1274
1273
  }
1275
1274
  case "repeat": {
1276
1275
  if (typeof expression.numberRepeats !== "number") {
@@ -1291,7 +1290,7 @@ function evaluateExpression(expression, env) {
1291
1290
  members.push(...value.values);
1292
1291
  }
1293
1292
  }
1294
- return members.length === 0 ? null : { cardinality: "ordered", baseType, values: members };
1293
+ return members.length === 0 ? null : rpValue("ordered", members, baseType);
1295
1294
  }
1296
1295
  case "stringMatch":
1297
1296
  case "substring": {
@@ -1359,7 +1358,7 @@ function evaluateExpression(expression, env) {
1359
1358
  if (members.length === 0) {
1360
1359
  return null;
1361
1360
  }
1362
- return { cardinality: expression.kind, baseType, values: members };
1361
+ return rpValue(expression.kind, members, baseType);
1363
1362
  }
1364
1363
  case "randomInteger": {
1365
1364
  if (!env.random) {
@@ -1411,7 +1410,7 @@ function evaluateExpression(expression, env) {
1411
1410
  return null;
1412
1411
  }
1413
1412
  const pick = container.values[Math.floor(env.random() * container.values.length)];
1414
- return { cardinality: "single", baseType: container.baseType, values: [pick] };
1413
+ return rpValue("single", [pick], container.baseType);
1415
1414
  }
1416
1415
  default:
1417
1416
  throw new RpUnsupportedError(expression.kind);
@@ -1608,11 +1607,7 @@ function defaultOutcomes(declarations) {
1608
1607
  const outcomes = new Map;
1609
1608
  for (const declaration of declarations) {
1610
1609
  if (declaration.defaultValue) {
1611
- outcomes.set(declaration.identifier, {
1612
- cardinality: declaration.cardinality,
1613
- baseType: declaration.baseType,
1614
- values: declaration.defaultValue.values.map((entry) => coerceScalar(entry.value, declaration.baseType))
1615
- });
1610
+ outcomes.set(declaration.identifier, rpValue(declaration.cardinality, declaration.defaultValue.values.map((entry) => coerceScalar(entry.value, declaration.baseType)), declaration.baseType));
1616
1611
  continue;
1617
1612
  }
1618
1613
  outcomes.set(declaration.identifier, isNumericBaseType(declaration.baseType) ? floatValue(0) : null);
@@ -1780,11 +1775,7 @@ function executeTemplateProcessing(view, context) {
1780
1775
  function initialValues() {
1781
1776
  const values = new Map;
1782
1777
  for (const declaration of context.templateDeclarations) {
1783
- values.set(declaration.identifier, declaration.defaultValue ? {
1784
- cardinality: declaration.cardinality,
1785
- baseType: declaration.baseType,
1786
- values: declaration.defaultValue.values.map((entry) => coerceScalar(entry.value, declaration.baseType))
1787
- } : null);
1778
+ values.set(declaration.identifier, declaration.defaultValue ? rpValue(declaration.cardinality, declaration.defaultValue.values.map((entry) => coerceScalar(entry.value, declaration.baseType)), declaration.baseType) : null);
1788
1779
  }
1789
1780
  return values;
1790
1781
  }
@@ -1815,11 +1806,7 @@ function executeTemplateProcessing(view, context) {
1815
1806
  if (rule.identifier !== undefined && rule.expression !== undefined) {
1816
1807
  const declaration = declarationsById.get(rule.identifier);
1817
1808
  const value = evaluateExpression(rule.expression, env);
1818
- templateValues.set(rule.identifier, value === null || !declaration ? value : {
1819
- cardinality: declaration.cardinality,
1820
- baseType: declaration.baseType ?? value.baseType,
1821
- values: value.values
1822
- });
1809
+ templateValues.set(rule.identifier, value === null || !declaration ? value : rpValue(declaration.cardinality, value.values, declaration.baseType ?? value.baseType));
1823
1810
  }
1824
1811
  continue;
1825
1812
  }
@@ -2192,11 +2179,7 @@ function createTestController(view, options) {
2192
2179
  const outcomes = new Map;
2193
2180
  for (const declaration of view.outcomeDeclarations ?? []) {
2194
2181
  if (declaration.defaultValue) {
2195
- outcomes.set(declaration.identifier, {
2196
- cardinality: declaration.cardinality,
2197
- baseType: declaration.baseType,
2198
- values: declaration.defaultValue.values.map((entry) => coerceScalar(entry.value, declaration.baseType))
2199
- });
2182
+ outcomes.set(declaration.identifier, rpValue(declaration.cardinality, declaration.defaultValue.values.map((entry) => coerceScalar(entry.value, declaration.baseType)), declaration.baseType));
2200
2183
  continue;
2201
2184
  }
2202
2185
  outcomes.set(declaration.identifier, isNumericBaseType(declaration.baseType) ? floatValue(0) : null);
@@ -2244,7 +2227,7 @@ function createTestController(view, options) {
2244
2227
  baseType ??= lifted.baseType;
2245
2228
  members.push(...lifted.values);
2246
2229
  }
2247
- return members.length === 0 ? null : { cardinality: "multiple", baseType, values: members };
2230
+ return members.length === 0 ? null : rpValue("multiple", members, baseType);
2248
2231
  },
2249
2232
  testAggregate: (expression) => {
2250
2233
  const subset = subsetItems(expression);
@@ -3021,11 +3004,12 @@ function createQtiRuntime(config) {
3021
3004
  }
3022
3005
  const parsed = descriptor.schema.safeParse(node);
3023
3006
  if (!parsed.success) {
3007
+ const detail = parsed.error.issues[0]?.message;
3024
3008
  report({
3025
3009
  type: "invalid-interaction",
3026
3010
  name: node.kind,
3027
3011
  responseIdentifier: node.responseIdentifier,
3028
- detail: parsed.error.issues[0]?.message
3012
+ ...detail !== undefined ? { detail } : {}
3029
3013
  });
3030
3014
  }
3031
3015
  return;
@@ -3370,10 +3354,13 @@ async function resolveModule(node, registry) {
3370
3354
  async function mountPci(options) {
3371
3355
  const { container, node, registry } = options;
3372
3356
  const module = await resolveModule(node, registry);
3357
+ const mountRoot = container.ownerDocument.createElement("div");
3358
+ mountRoot.setAttribute("data-qti-pci-mount", "");
3359
+ container.appendChild(mountRoot);
3373
3360
  const markupHost = container.ownerDocument.createElement("div");
3374
3361
  markupHost.className = "qti-interaction-markup";
3375
3362
  markupHost.innerHTML = serializePciMarkup(node.interactionMarkup?.content);
3376
- container.appendChild(markupHost);
3363
+ mountRoot.appendChild(markupHost);
3377
3364
  let resolveReady;
3378
3365
  const ready = new Promise((resolve) => {
3379
3366
  resolveReady = resolve;
@@ -3384,9 +3371,9 @@ async function mountPci(options) {
3384
3371
  ...options.declaration ? { boundTo: { [node.responseIdentifier]: valueToPciResponse(options.boundValue ?? null, options.declaration) } } : {},
3385
3372
  status: "interacting",
3386
3373
  onready: (instance2) => resolveReady(instance2),
3387
- ondone: (instance2, response, state) => options.ondone?.(pciResponseToValue(response), state)
3374
+ ondone: (_instance, response, state) => options.ondone?.(pciResponseToValue(response), state)
3388
3375
  };
3389
- const returned = module.getInstance(container, configuration, options.state);
3376
+ const returned = module.getInstance(mountRoot, configuration, options.state);
3390
3377
  if (returned) {
3391
3378
  resolveReady(returned);
3392
3379
  }
@@ -3397,7 +3384,7 @@ async function mountPci(options) {
3397
3384
  getState: () => instance.getState?.(),
3398
3385
  unmount: () => {
3399
3386
  instance.oncompleted?.();
3400
- container.replaceChildren();
3387
+ mountRoot.remove();
3401
3388
  }
3402
3389
  };
3403
3390
  }
@@ -3919,7 +3906,12 @@ function ChoiceReferenceSkin(props) {
3919
3906
  }
3920
3907
 
3921
3908
  // src/reference-skin/drawing.ts
3922
- import { createElement as createElement5, useEffect as useEffect2, useRef as useRef2 } from "react";
3909
+ import {
3910
+ createElement as createElement5,
3911
+ useCallback,
3912
+ useEffect as useEffect2,
3913
+ useRef as useRef2
3914
+ } from "react";
3923
3915
  var strokeStyle = "#c2410c";
3924
3916
  var strokeWidth = 3;
3925
3917
  function DrawingReferenceSkin(props) {
@@ -3930,7 +3922,8 @@ function DrawingReferenceSkin(props) {
3930
3922
  const drawingRef = useRef2(false);
3931
3923
  const propsRef = useRef2(props);
3932
3924
  propsRef.current = props;
3933
- const paintBackground = (canvas) => {
3925
+ const stageData = node.object.data;
3926
+ const paintBackground = useCallback((canvas) => {
3934
3927
  const context = canvas.getContext("2d");
3935
3928
  if (!context) {
3936
3929
  return;
@@ -3940,13 +3933,13 @@ function DrawingReferenceSkin(props) {
3940
3933
  image.onload = () => {
3941
3934
  context.drawImage(image, 0, 0, canvas.width, canvas.height);
3942
3935
  };
3943
- image.src = propsRef.current.resolveAsset(node.object.data);
3944
- };
3936
+ image.src = propsRef.current.resolveAsset(stageData);
3937
+ }, [stageData]);
3945
3938
  useEffect2(() => {
3946
3939
  if (canvasRef.current) {
3947
3940
  paintBackground(canvasRef.current);
3948
3941
  }
3949
- }, [node.object.data]);
3942
+ }, [paintBackground]);
3950
3943
  const pointerPosition = (event) => {
3951
3944
  const canvas = event.currentTarget;
3952
3945
  const rect = canvas.getBoundingClientRect();
@@ -4359,124 +4352,27 @@ function HotspotReferenceSkin(props) {
4359
4352
  });
4360
4353
  }
4361
4354
 
4362
- // src/reference-skin/position-object.ts
4363
- import { createElement as createElement14 } from "react";
4364
- function PositionObjectReferenceSkin(props) {
4365
- const node = props.node;
4366
- const maxChoices = node.maxChoices ?? 1;
4367
- const points = props.value === null ? [] : typeof props.value === "string" ? [props.value] : Array.isArray(props.value) ? [...props.value] : [];
4368
- if (!node.stageObject || !node.object) {
4369
- return null;
4370
- }
4371
- const movable = node.object;
4372
- function stageClick(point) {
4373
- if (props.disabled) {
4374
- return;
4375
- }
4376
- const formatted = formatPoint(point);
4377
- if (maxChoices === 1) {
4378
- props.setValue(formatted);
4379
- return;
4380
- }
4381
- if (points.length < maxChoices) {
4382
- props.setValue([...points, formatted]);
4383
- }
4384
- }
4385
- return createElement14(GraphicStage, {
4386
- object: node.stageObject,
4387
- resolveAsset: props.resolveAsset,
4388
- interaction: "positionObjectStage",
4389
- status: props.status,
4390
- onStageClick: stageClick,
4391
- prompt: node.prompt ? createElement14("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null,
4392
- overlay: points.map((value, index) => {
4393
- const point = parsePoint(value);
4394
- if (!point) {
4395
- return null;
4396
- }
4397
- return createElement14("image", {
4398
- key: `${value}-${index}`,
4399
- href: props.resolveAsset(movable.data),
4400
- x: point.x - (movable.width ?? 0) / 2,
4401
- y: point.y - (movable.height ?? 0) / 2,
4402
- width: movable.width,
4403
- height: movable.height,
4404
- "data-qti-point": value,
4405
- style: { pointerEvents: "none" }
4406
- });
4407
- })
4408
- });
4409
- }
4410
-
4411
- // src/reference-skin/select-point.ts
4412
- import { Fragment as Fragment5, createElement as createElement15 } from "react";
4413
- function SelectPointReferenceSkin(props) {
4414
- const node = props.node;
4415
- const maxChoices = node.maxChoices ?? 1;
4416
- const points = props.value === null ? [] : typeof props.value === "string" ? [props.value] : Array.isArray(props.value) ? [...props.value] : [];
4417
- if (!node.object) {
4418
- return null;
4419
- }
4420
- function stageClick(point) {
4421
- if (props.disabled) {
4422
- return;
4423
- }
4424
- const formatted = formatPoint(point);
4425
- if (maxChoices === 1) {
4426
- props.setValue(formatted);
4427
- return;
4428
- }
4429
- if (points.length < maxChoices) {
4430
- props.setValue([...points, formatted]);
4431
- }
4432
- }
4433
- return createElement15(GraphicStage, {
4434
- object: node.object,
4435
- resolveAsset: props.resolveAsset,
4436
- interaction: "selectPointInteraction",
4437
- status: props.status,
4438
- onStageClick: stageClick,
4439
- prompt: node.prompt ? createElement15("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null,
4440
- overlay: points.map((value, index) => {
4441
- const [x, y] = value.split(/\s+/u).map(Number);
4442
- return createElement15(Fragment5, { key: `${value}-${index}` }, createElement15("circle", {
4443
- cx: x,
4444
- cy: y,
4445
- r: 5,
4446
- fill: "currentColor",
4447
- "data-qti-point": value,
4448
- style: { pointerEvents: "none" }
4449
- }));
4450
- }),
4451
- after: createElement15("button", {
4452
- type: "button",
4453
- disabled: props.disabled || points.length === 0,
4454
- onClick: () => props.setValue(null)
4455
- }, "Clear points")
4456
- });
4457
- }
4458
-
4459
4355
  // src/reference-skin/hottext.ts
4460
- import { createElement as createElement16 } from "react";
4356
+ import { createElement as createElement14 } from "react";
4461
4357
  function HottextReferenceSkin(props) {
4462
4358
  const node = props.node;
4463
- return createElement16("div", { "data-qti-interaction": "hottextInteraction", "data-status": props.status }, node.prompt ? createElement16("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null, props.renderContent(node.content, {
4359
+ return createElement14("div", { "data-qti-interaction": "hottextInteraction", "data-status": props.status }, node.prompt ? createElement14("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null, props.renderContent(node.content, {
4464
4360
  hottext: (child, key) => {
4465
4361
  const view = child;
4466
4362
  const identifier = view.identifier ?? "";
4467
4363
  const optionProps = props.getOptionProps(identifier);
4468
- return createElement16("button", { key, type: "button", disabled: props.disabled, "data-qti-hottext": identifier, ...optionProps }, props.renderContent(view.content) ?? identifier);
4364
+ return createElement14("button", { key, type: "button", disabled: props.disabled, "data-qti-hottext": identifier, ...optionProps }, props.renderContent(view.content) ?? identifier);
4469
4365
  }
4470
4366
  }));
4471
4367
  }
4472
4368
 
4473
4369
  // src/reference-skin/inline-choice.ts
4474
- import { createElement as createElement17 } from "react";
4370
+ import { createElement as createElement15 } from "react";
4475
4371
  function InlineChoiceReferenceSkin(props) {
4476
4372
  const node = props.node;
4477
4373
  const choices = node.inlineChoices ?? [];
4478
4374
  const value = typeof props.value === "string" ? props.value : "";
4479
- return createElement17("select", {
4375
+ return createElement15("select", {
4480
4376
  value,
4481
4377
  disabled: props.disabled,
4482
4378
  "aria-disabled": props.disabled,
@@ -4485,11 +4381,11 @@ function InlineChoiceReferenceSkin(props) {
4485
4381
  onChange: (event) => {
4486
4382
  props.setValue(event.target.value === "" ? null : event.target.value);
4487
4383
  }
4488
- }, createElement17("option", { key: "", value: "" }, ""), choices.map((choice) => createElement17("option", { key: choice.identifier, value: choice.identifier }, textOf(choice.content))));
4384
+ }, createElement15("option", { key: "", value: "" }, ""), choices.map((choice) => createElement15("option", { key: choice.identifier, value: choice.identifier }, textOf(choice.content))));
4489
4385
  }
4490
4386
 
4491
4387
  // src/reference-skin/match.ts
4492
- import { createElement as createElement18 } from "react";
4388
+ import { createElement as createElement16 } from "react";
4493
4389
  function MatchReferenceSkin(props) {
4494
4390
  const node = props.node;
4495
4391
  const rows = node.simpleMatchSets?.[0]?.simpleAssociableChoices ?? [];
@@ -4498,9 +4394,9 @@ function MatchReferenceSkin(props) {
4498
4394
  function togglePair(pair) {
4499
4395
  props.setValue(pairs.includes(pair) ? pairs.filter((entry) => entry !== pair) : [...pairs, pair]);
4500
4396
  }
4501
- return createElement18("div", { "data-qti-interaction": "matchInteraction", "data-status": props.status }, node.prompt ? createElement18("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null, createElement18("table", null, createElement18("thead", null, createElement18("tr", null, createElement18("td", null), columns.map((column) => createElement18("th", { key: column.identifier, scope: "col" }, textOf(column.content) || column.identifier)))), createElement18("tbody", null, rows.map((row) => createElement18("tr", { key: row.identifier }, createElement18("th", { scope: "row" }, textOf(row.content) || row.identifier), columns.map((column) => {
4397
+ return createElement16("div", { "data-qti-interaction": "matchInteraction", "data-status": props.status }, node.prompt ? createElement16("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null, createElement16("table", null, createElement16("thead", null, createElement16("tr", null, createElement16("td", null), columns.map((column) => createElement16("th", { key: column.identifier, scope: "col" }, textOf(column.content) || column.identifier)))), createElement16("tbody", null, rows.map((row) => createElement16("tr", { key: row.identifier }, createElement16("th", { scope: "row" }, textOf(row.content) || row.identifier), columns.map((column) => {
4502
4398
  const pair = `${row.identifier} ${column.identifier}`;
4503
- return createElement18("td", { key: column.identifier }, createElement18("input", {
4399
+ return createElement16("td", { key: column.identifier }, createElement16("input", {
4504
4400
  type: "checkbox",
4505
4401
  checked: pairs.includes(pair),
4506
4402
  disabled: props.disabled,
@@ -4512,7 +4408,7 @@ function MatchReferenceSkin(props) {
4512
4408
  }
4513
4409
 
4514
4410
  // src/reference-skin/media.ts
4515
- import { createElement as createElement19 } from "react";
4411
+ import { createElement as createElement17 } from "react";
4516
4412
  function findMediaElement(nodes) {
4517
4413
  for (const node of nodes ?? []) {
4518
4414
  if (node.kind === "xml") {
@@ -4534,10 +4430,10 @@ function MediaReferenceSkin(props) {
4534
4430
  const plays = typeof props.value === "string" ? Number(props.value) || 0 : 0;
4535
4431
  const playsExhausted = node.maxPlays !== undefined && node.maxPlays > 0 && plays >= node.maxPlays;
4536
4432
  if (!media) {
4537
- return createElement19("div", { "data-qti-interaction": "mediaInteraction", "data-status": props.status }, "No media element.");
4433
+ return createElement17("div", { "data-qti-interaction": "mediaInteraction", "data-status": props.status }, "No media element.");
4538
4434
  }
4539
4435
  const src = typeof media.attributes?.["src"] === "string" ? props.resolveAsset(media.attributes["src"]) : undefined;
4540
- return createElement19("div", { "data-qti-interaction": "mediaInteraction", "data-status": props.status, "data-qti-plays": plays }, node.prompt ? createElement19("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null, createElement19(media.name, {
4436
+ return createElement17("div", { "data-qti-interaction": "mediaInteraction", "data-status": props.status, "data-qti-plays": plays }, node.prompt ? createElement17("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null, createElement17(media.name, {
4541
4437
  src,
4542
4438
  controls: true,
4543
4439
  loop: node.loop ?? false,
@@ -4548,11 +4444,11 @@ function MediaReferenceSkin(props) {
4548
4444
  }
4549
4445
  props.setValue(String(plays + 1));
4550
4446
  }
4551
- }), playsExhausted ? createElement19("p", { role: "status" }, "No plays remaining.") : null);
4447
+ }), playsExhausted ? createElement17("p", { role: "status" }, "No plays remaining.") : null);
4552
4448
  }
4553
4449
 
4554
4450
  // src/reference-skin/order.ts
4555
- import { createElement as createElement20 } from "react";
4451
+ import { createElement as createElement18 } from "react";
4556
4452
  function OrderReferenceSkin(props) {
4557
4453
  const node = props.node;
4558
4454
  const choices = node.simpleChoices ?? [];
@@ -4570,12 +4466,12 @@ function OrderReferenceSkin(props) {
4570
4466
  next[target] = moved;
4571
4467
  props.setValue(next);
4572
4468
  }
4573
- return createElement20("div", { "data-qti-interaction": "orderInteraction", "data-status": props.status }, node.prompt ? createElement20("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null, createElement20("ol", null, order.map((identifier, index) => createElement20("li", { key: identifier }, props.renderContent(choicesById.get(identifier)?.content) ?? identifier, createElement20("button", {
4469
+ return createElement18("div", { "data-qti-interaction": "orderInteraction", "data-status": props.status }, node.prompt ? createElement18("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null, createElement18("ol", null, order.map((identifier, index) => createElement18("li", { key: identifier }, props.renderContent(choicesById.get(identifier)?.content) ?? identifier, createElement18("button", {
4574
4470
  type: "button",
4575
4471
  "aria-label": `Move ${identifier} up`,
4576
4472
  disabled: props.disabled || index === 0,
4577
4473
  onClick: () => move(index, -1)
4578
- }, "↑"), createElement20("button", {
4474
+ }, "↑"), createElement18("button", {
4579
4475
  type: "button",
4580
4476
  "aria-label": `Move ${identifier} down`,
4581
4477
  disabled: props.disabled || index === order.length - 1,
@@ -4583,6 +4479,103 @@ function OrderReferenceSkin(props) {
4583
4479
  }, "↓")))));
4584
4480
  }
4585
4481
 
4482
+ // src/reference-skin/position-object.ts
4483
+ import { createElement as createElement19 } from "react";
4484
+ function PositionObjectReferenceSkin(props) {
4485
+ const node = props.node;
4486
+ const maxChoices = node.maxChoices ?? 1;
4487
+ const points = props.value === null ? [] : typeof props.value === "string" ? [props.value] : Array.isArray(props.value) ? [...props.value] : [];
4488
+ if (!node.stageObject || !node.object) {
4489
+ return null;
4490
+ }
4491
+ const movable = node.object;
4492
+ function stageClick(point) {
4493
+ if (props.disabled) {
4494
+ return;
4495
+ }
4496
+ const formatted = formatPoint(point);
4497
+ if (maxChoices === 1) {
4498
+ props.setValue(formatted);
4499
+ return;
4500
+ }
4501
+ if (points.length < maxChoices) {
4502
+ props.setValue([...points, formatted]);
4503
+ }
4504
+ }
4505
+ return createElement19(GraphicStage, {
4506
+ object: node.stageObject,
4507
+ resolveAsset: props.resolveAsset,
4508
+ interaction: "positionObjectStage",
4509
+ status: props.status,
4510
+ onStageClick: stageClick,
4511
+ prompt: node.prompt ? createElement19("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null,
4512
+ overlay: points.map((value, index) => {
4513
+ const point = parsePoint(value);
4514
+ if (!point) {
4515
+ return null;
4516
+ }
4517
+ return createElement19("image", {
4518
+ key: `${value}-${index}`,
4519
+ href: props.resolveAsset(movable.data),
4520
+ x: point.x - (movable.width ?? 0) / 2,
4521
+ y: point.y - (movable.height ?? 0) / 2,
4522
+ width: movable.width,
4523
+ height: movable.height,
4524
+ "data-qti-point": value,
4525
+ style: { pointerEvents: "none" }
4526
+ });
4527
+ })
4528
+ });
4529
+ }
4530
+
4531
+ // src/reference-skin/select-point.ts
4532
+ import { Fragment as Fragment5, createElement as createElement20 } from "react";
4533
+ function SelectPointReferenceSkin(props) {
4534
+ const node = props.node;
4535
+ const maxChoices = node.maxChoices ?? 1;
4536
+ const points = props.value === null ? [] : typeof props.value === "string" ? [props.value] : Array.isArray(props.value) ? [...props.value] : [];
4537
+ if (!node.object) {
4538
+ return null;
4539
+ }
4540
+ function stageClick(point) {
4541
+ if (props.disabled) {
4542
+ return;
4543
+ }
4544
+ const formatted = formatPoint(point);
4545
+ if (maxChoices === 1) {
4546
+ props.setValue(formatted);
4547
+ return;
4548
+ }
4549
+ if (points.length < maxChoices) {
4550
+ props.setValue([...points, formatted]);
4551
+ }
4552
+ }
4553
+ return createElement20(GraphicStage, {
4554
+ object: node.object,
4555
+ resolveAsset: props.resolveAsset,
4556
+ interaction: "selectPointInteraction",
4557
+ status: props.status,
4558
+ onStageClick: stageClick,
4559
+ prompt: node.prompt ? createElement20("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null,
4560
+ overlay: points.map((value, index) => {
4561
+ const [x, y] = value.split(/\s+/u).map(Number);
4562
+ return createElement20(Fragment5, { key: `${value}-${index}` }, createElement20("circle", {
4563
+ cx: x,
4564
+ cy: y,
4565
+ r: 5,
4566
+ fill: "currentColor",
4567
+ "data-qti-point": value,
4568
+ style: { pointerEvents: "none" }
4569
+ }));
4570
+ }),
4571
+ after: createElement20("button", {
4572
+ type: "button",
4573
+ disabled: props.disabled || points.length === 0,
4574
+ onClick: () => props.setValue(null)
4575
+ }, "Clear points")
4576
+ });
4577
+ }
4578
+
4586
4579
  // src/reference-skin/slider.ts
4587
4580
  import { createElement as createElement21 } from "react";
4588
4581
  function SliderReferenceSkin(props) {
@@ -0,0 +1,2 @@
1
+ import { type InteractionDescriptor } from "../runtime";
2
+ export declare const associateInteraction: InteractionDescriptor<"associateInteraction">;
@@ -0,0 +1,2 @@
1
+ import { type InteractionDescriptor } from "../runtime";
2
+ export declare const choiceInteraction: InteractionDescriptor<"choiceInteraction">;
@@ -0,0 +1,2 @@
1
+ import { type InteractionDescriptor } from "../runtime";
2
+ export declare const drawingInteraction: InteractionDescriptor<"drawingInteraction">;
@@ -0,0 +1,2 @@
1
+ import { type InteractionDescriptor } from "../runtime";
2
+ export declare const endAttemptInteraction: InteractionDescriptor<"endAttemptInteraction">;
@@ -0,0 +1,2 @@
1
+ import { type InteractionDescriptor } from "../runtime";
2
+ export declare const extendedTextInteraction: InteractionDescriptor<"extendedTextInteraction">;
@@ -0,0 +1,2 @@
1
+ import { type InteractionDescriptor } from "../runtime";
2
+ export declare const gapMatchInteraction: InteractionDescriptor<"gapMatchInteraction">;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The graphic interaction family: descriptors share the object (stage image) and
3
+ * hotspot (shape/coords) schemas. Coordinates are numbers in image space, normalized
4
+ * upstream from the QTI coords attribute.
5
+ */
6
+ import { type InteractionDescriptor } from "../runtime";
7
+ export declare const hotspotInteraction: InteractionDescriptor<"hotspotInteraction">;
8
+ export declare const graphicOrderInteraction: InteractionDescriptor<"graphicOrderInteraction">;
9
+ export declare const graphicAssociateInteraction: InteractionDescriptor<"graphicAssociateInteraction">;
10
+ export declare const graphicGapMatchInteraction: InteractionDescriptor<"graphicGapMatchInteraction">;
11
+ export declare const selectPointInteraction: InteractionDescriptor<"selectPointInteraction">;
12
+ /** The common single-interaction stage; multi-interaction stages fail validation. */
13
+ export declare const positionObjectStage: InteractionDescriptor<"positionObjectStage">;
@@ -0,0 +1,2 @@
1
+ import { type InteractionDescriptor } from "../runtime";
2
+ export declare const hottextInteraction: InteractionDescriptor<"hottextInteraction">;
@@ -0,0 +1,18 @@
1
+ import type { InteractionDescriptor } from "../runtime";
2
+ export { associateInteraction } from "./associate";
3
+ export { choiceInteraction } from "./choice";
4
+ export { drawingInteraction } from "./drawing";
5
+ export { endAttemptInteraction } from "./end-attempt";
6
+ export { extendedTextInteraction } from "./extended-text";
7
+ export { gapMatchInteraction } from "./gap-match";
8
+ export { graphicAssociateInteraction, graphicGapMatchInteraction, graphicOrderInteraction, hotspotInteraction, positionObjectStage, selectPointInteraction, } from "./graphic";
9
+ export { hottextInteraction } from "./hottext";
10
+ export { inlineChoiceInteraction } from "./inline-choice";
11
+ export { matchInteraction } from "./match";
12
+ export { mediaInteraction } from "./media";
13
+ export { orderInteraction } from "./order";
14
+ export { sliderInteraction } from "./slider";
15
+ export { textEntryInteraction } from "./text-entry";
16
+ export { uploadInteraction } from "./upload";
17
+ /** The interaction set conform-ed ships; consumers assemble these plus their extensions. */
18
+ export declare const qtiCoreInteractions: readonly InteractionDescriptor[];
@@ -0,0 +1,2 @@
1
+ import { type InteractionDescriptor } from "../runtime";
2
+ export declare const inlineChoiceInteraction: InteractionDescriptor<"inlineChoiceInteraction">;
@@ -0,0 +1,2 @@
1
+ import { type InteractionDescriptor } from "../runtime";
2
+ export declare const matchInteraction: InteractionDescriptor<"matchInteraction">;
@@ -0,0 +1,2 @@
1
+ import { type InteractionDescriptor } from "../runtime";
2
+ export declare const mediaInteraction: InteractionDescriptor<"mediaInteraction">;
@@ -0,0 +1,2 @@
1
+ import { type InteractionDescriptor } from "../runtime";
2
+ export declare const orderInteraction: InteractionDescriptor<"orderInteraction">;
@@ -0,0 +1,2 @@
1
+ import { type InteractionDescriptor } from "../runtime";
2
+ export declare const sliderInteraction: InteractionDescriptor<"sliderInteraction">;
@@ -0,0 +1,2 @@
1
+ import { type InteractionDescriptor } from "../runtime";
2
+ export declare const textEntryInteraction: InteractionDescriptor<"textEntryInteraction">;
@@ -0,0 +1,2 @@
1
+ import { type InteractionDescriptor } from "../runtime";
2
+ export declare const uploadInteraction: InteractionDescriptor<"uploadInteraction">;