@conform-ed/qti-react 0.0.18 → 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/headless.d.ts +20 -7
- package/dist/headless.js +1932 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +207 -0
- package/dist/item-score.d.ts +17 -0
- package/dist/rp/schema.d.ts +116 -0
- package/dist/test/schema.d.ts +192 -0
- package/package.json +4 -4
- package/src/headless.ts +87 -7
- package/src/index.ts +41 -0
- package/src/item-score.ts +40 -0
- package/src/rp/schema.ts +143 -0
- package/src/test/schema.ts +272 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare const qtiReactPackageName = "@conform-ed/qti-react";
|
|
2
2
|
export { v0ContentModel, v0InteractionKinds, isAllowedFlowElement, isInteractionKind, sanitizeAttributes, type ContentModel, type V0InteractionKind, } from "./content-model";
|
|
3
3
|
export { foldString, mapResponse, matchCorrect, mapResponsePoint, scoreResponse } from "./response-processing";
|
|
4
|
+
export { effectiveItemScore, type EffectiveItemScore } from "./item-score";
|
|
4
5
|
export { assessmentItemViewFromNormalized, assessmentTestViewFromNormalized, stimulusContentFromNormalized, } from "./normalized-item";
|
|
5
6
|
export { referenceInteractionKinds, reportItemCapability, type ItemCapabilityOptions } from "./item-capability";
|
|
6
7
|
export { formatPoint, parseCoords, parsePoint, pointInShape, type Point, type QtiShape } from "./graphic";
|
|
@@ -15,3 +16,5 @@ export { associateInteraction, choiceInteraction, drawingInteraction, endAttempt
|
|
|
15
16
|
export { AssociateReferenceSkin, ChoiceReferenceSkin, DrawingReferenceSkin, EndAttemptReferenceSkin, ExtendedTextReferenceSkin, GapMatchReferenceSkin, GraphicAssociateReferenceSkin, GraphicGapMatchReferenceSkin, GraphicOrderReferenceSkin, GraphicStage, HotspotReferenceSkin, HottextReferenceSkin, InlineChoiceReferenceSkin, MatchReferenceSkin, MediaReferenceSkin, OrderReferenceSkin, PositionObjectReferenceSkin, SelectPointReferenceSkin, SliderReferenceSkin, TextEntryReferenceSkin, UploadReferenceSkin, referenceSkin, textOf, } from "./reference-skin";
|
|
16
17
|
export type { AreaMapEntryView, AreaMappingView, Cardinality, CorrectResponseView, MapEntryView, MappingView, ResponseDeclarationView, ResponseValue, ScoreResult, } from "./types";
|
|
17
18
|
export { resolveCatalogSupports, resolvePnpActivation, type CatalogCardEntryView, type CatalogCardView, type CatalogContentView, type CatalogFileHrefView, type CatalogResolution, type CatalogView, type PnpActivation, type PnpAdditionalTestingTimeView, type PnpFeatureSetView, type PnpLanguageModeView, type PnpReplaceAccessModeView, type PnpView, type ResolvedCatalogSupport, } from "./pnp";
|
|
19
|
+
export { cardinalitySchema, interpolationTableSchema, matchTableSchema, outcomeDeclarationSchema, rpExpressionSchema, rpScalarSchema, type OutcomeDeclarationSchema, type RpExpressionSchema, type RpScalarSchema, } from "./rp/schema";
|
|
20
|
+
export { assessmentItemRefViewSchema, assessmentTestViewSchema, branchRuleSchema, itemSessionControlSchema, makeAssessmentTestSchema, orderingSchema, outcomeConditionBranchSchema, outcomeProcessingSchema, outcomeRuleSchema, selectionSchema, testFeedbackSchema, timeLimitsSchema, type AssessmentItemRefViewSchema, type AssessmentSectionNode, type AssessmentTestNode, type AssessmentTestSchemas, type AssessmentTestViewSchema, type BranchRuleSchema, type ItemSessionControlSchema, type OrderingSchema, type SelectionSchema, type TestPartNode, type TimeLimitsSchema, } from "./test/schema";
|
package/dist/index.js
CHANGED
|
@@ -387,6 +387,23 @@ function scoreResponse(declaration, response, normalize) {
|
|
|
387
387
|
correct
|
|
388
388
|
};
|
|
389
389
|
}
|
|
390
|
+
// src/item-score.ts
|
|
391
|
+
function numericOutcome(value) {
|
|
392
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
393
|
+
}
|
|
394
|
+
function effectiveItemScore(scores, outcomes) {
|
|
395
|
+
const scoreOutcome = numericOutcome(outcomes["SCORE"]);
|
|
396
|
+
const maxOutcome = numericOutcome(outcomes["MAXSCORE"]);
|
|
397
|
+
const summedMax = scores.reduce((total, score) => total + score.maxScore, 0);
|
|
398
|
+
if (scoreOutcome !== null) {
|
|
399
|
+
return { raw: scoreOutcome, max: maxOutcome ?? summedMax, fromOutcomes: true };
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
raw: scores.reduce((total, score) => total + score.score, 0),
|
|
403
|
+
max: maxOutcome ?? summedMax,
|
|
404
|
+
fromOutcomes: false
|
|
405
|
+
};
|
|
406
|
+
}
|
|
390
407
|
// src/normalized-item.ts
|
|
391
408
|
function isRecord(value) {
|
|
392
409
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -6783,6 +6800,177 @@ var referenceSkin = {
|
|
|
6783
6800
|
textEntryInteraction: TextEntryReferenceSkin,
|
|
6784
6801
|
uploadInteraction: UploadReferenceSkin
|
|
6785
6802
|
};
|
|
6803
|
+
// src/rp/schema.ts
|
|
6804
|
+
import { z as z18 } from "zod";
|
|
6805
|
+
var rpScalarSchema = z18.union([z18.string(), z18.number(), z18.boolean()]);
|
|
6806
|
+
var numberOrRef = z18.union([z18.number(), z18.string()]);
|
|
6807
|
+
var categoryFilter = z18.union([z18.string(), z18.array(z18.string())]);
|
|
6808
|
+
var cardinalitySchema = z18.enum(["single", "multiple", "ordered", "record"]);
|
|
6809
|
+
var rpExpressionSchema = z18.lazy(() => z18.object({
|
|
6810
|
+
kind: z18.string(),
|
|
6811
|
+
identifier: z18.string().optional(),
|
|
6812
|
+
baseType: z18.string().optional(),
|
|
6813
|
+
value: rpScalarSchema.optional(),
|
|
6814
|
+
expressions: z18.array(rpExpressionSchema).optional(),
|
|
6815
|
+
min: numberOrRef.optional(),
|
|
6816
|
+
max: numberOrRef.optional(),
|
|
6817
|
+
step: numberOrRef.optional(),
|
|
6818
|
+
toleranceMode: z18.enum(["exact", "absolute", "relative"]).optional(),
|
|
6819
|
+
tolerance: z18.array(numberOrRef).optional(),
|
|
6820
|
+
includeLowerBound: z18.boolean().optional(),
|
|
6821
|
+
includeUpperBound: z18.boolean().optional(),
|
|
6822
|
+
n: numberOrRef.optional(),
|
|
6823
|
+
name: z18.string().optional(),
|
|
6824
|
+
roundingMode: z18.enum(["decimalPlaces", "significantFigures"]).optional(),
|
|
6825
|
+
figures: numberOrRef.optional(),
|
|
6826
|
+
numberRepeats: numberOrRef.optional(),
|
|
6827
|
+
pattern: z18.string().optional(),
|
|
6828
|
+
caseSensitive: z18.boolean().optional(),
|
|
6829
|
+
substring: z18.boolean().optional(),
|
|
6830
|
+
shape: z18.string().optional(),
|
|
6831
|
+
coords: z18.string().optional(),
|
|
6832
|
+
variableIdentifier: z18.string().optional(),
|
|
6833
|
+
outcomeIdentifier: z18.string().optional(),
|
|
6834
|
+
weightIdentifier: z18.string().optional(),
|
|
6835
|
+
sectionIdentifier: z18.string().optional(),
|
|
6836
|
+
includeCategory: categoryFilter.optional(),
|
|
6837
|
+
excludeCategory: categoryFilter.optional(),
|
|
6838
|
+
class: z18.string().optional(),
|
|
6839
|
+
definition: z18.string().optional(),
|
|
6840
|
+
fieldIdentifier: z18.string().optional()
|
|
6841
|
+
}));
|
|
6842
|
+
var matchTableSchema = z18.object({
|
|
6843
|
+
defaultValue: rpScalarSchema.optional(),
|
|
6844
|
+
matchTableEntries: z18.array(z18.object({ sourceValue: z18.number(), targetValue: rpScalarSchema }))
|
|
6845
|
+
});
|
|
6846
|
+
var interpolationTableSchema = z18.object({
|
|
6847
|
+
defaultValue: rpScalarSchema.optional(),
|
|
6848
|
+
interpolationTableEntries: z18.array(z18.object({
|
|
6849
|
+
sourceValue: z18.number(),
|
|
6850
|
+
targetValue: rpScalarSchema,
|
|
6851
|
+
includeBoundary: z18.boolean().optional()
|
|
6852
|
+
}))
|
|
6853
|
+
});
|
|
6854
|
+
var outcomeDeclarationSchema = z18.object({
|
|
6855
|
+
identifier: z18.string(),
|
|
6856
|
+
cardinality: cardinalitySchema,
|
|
6857
|
+
baseType: z18.string().optional(),
|
|
6858
|
+
defaultValue: z18.object({ values: z18.array(z18.object({ value: rpScalarSchema })) }).optional(),
|
|
6859
|
+
matchTable: matchTableSchema.optional(),
|
|
6860
|
+
interpolationTable: interpolationTableSchema.optional(),
|
|
6861
|
+
normalMaximum: z18.number().optional(),
|
|
6862
|
+
normalMinimum: z18.number().optional()
|
|
6863
|
+
});
|
|
6864
|
+
// src/test/schema.ts
|
|
6865
|
+
import { z as z19 } from "zod";
|
|
6866
|
+
var timeLimitsSchema = z19.object({
|
|
6867
|
+
minTime: z19.number().nonnegative().optional(),
|
|
6868
|
+
maxTime: z19.number().nonnegative().optional(),
|
|
6869
|
+
allowLateSubmission: z19.boolean().optional()
|
|
6870
|
+
});
|
|
6871
|
+
var itemSessionControlSchema = z19.object({
|
|
6872
|
+
maxAttempts: z19.number().int().nonnegative().optional(),
|
|
6873
|
+
showFeedback: z19.boolean().optional(),
|
|
6874
|
+
allowReview: z19.boolean().optional(),
|
|
6875
|
+
showSolution: z19.boolean().optional(),
|
|
6876
|
+
allowComment: z19.boolean().optional(),
|
|
6877
|
+
allowSkipping: z19.boolean().optional(),
|
|
6878
|
+
validateResponses: z19.boolean().optional()
|
|
6879
|
+
});
|
|
6880
|
+
var selectionSchema = z19.object({
|
|
6881
|
+
select: z19.number().int().nonnegative(),
|
|
6882
|
+
withReplacement: z19.boolean().optional()
|
|
6883
|
+
});
|
|
6884
|
+
var orderingSchema = z19.object({ shuffle: z19.boolean().optional() });
|
|
6885
|
+
var branchRuleSchema = z19.object({
|
|
6886
|
+
target: z19.string().trim().min(1),
|
|
6887
|
+
expression: rpExpressionSchema
|
|
6888
|
+
});
|
|
6889
|
+
var weightSchema = z19.object({ identifier: z19.string().trim().min(1), value: z19.number() });
|
|
6890
|
+
var templateDefaultSchema = z19.object({
|
|
6891
|
+
templateIdentifier: z19.string().trim().min(1),
|
|
6892
|
+
expression: rpExpressionSchema
|
|
6893
|
+
});
|
|
6894
|
+
var outcomeConditionBranchSchema = z19.lazy(() => z19.object({
|
|
6895
|
+
expression: rpExpressionSchema,
|
|
6896
|
+
rules: z19.array(outcomeRuleSchema)
|
|
6897
|
+
}));
|
|
6898
|
+
var outcomeRuleSchema = z19.lazy(() => z19.object({
|
|
6899
|
+
kind: z19.string(),
|
|
6900
|
+
identifier: z19.string().optional(),
|
|
6901
|
+
expression: rpExpressionSchema.optional(),
|
|
6902
|
+
rules: z19.array(outcomeRuleSchema).optional(),
|
|
6903
|
+
outcomeIf: outcomeConditionBranchSchema.optional(),
|
|
6904
|
+
outcomeElseIfs: z19.array(outcomeConditionBranchSchema).optional(),
|
|
6905
|
+
outcomeElse: z19.object({ rules: z19.array(outcomeRuleSchema) }).optional()
|
|
6906
|
+
}));
|
|
6907
|
+
var outcomeProcessingSchema = z19.object({ rules: z19.array(outcomeRuleSchema) });
|
|
6908
|
+
var testFeedbackSchema = z19.object({
|
|
6909
|
+
access: z19.enum(["atEnd", "during"]).optional(),
|
|
6910
|
+
outcomeIdentifier: z19.string().trim().min(1),
|
|
6911
|
+
identifier: z19.string().trim().min(1),
|
|
6912
|
+
showHide: z19.enum(["show", "hide"]).optional(),
|
|
6913
|
+
content: z19.array(z19.unknown()).optional()
|
|
6914
|
+
});
|
|
6915
|
+
var assessmentItemRefViewSchema = z19.object({
|
|
6916
|
+
kind: z19.literal("assessmentItemRef"),
|
|
6917
|
+
identifier: z19.string().trim().min(1),
|
|
6918
|
+
href: z19.string().optional(),
|
|
6919
|
+
categories: z19.array(z19.string()).optional(),
|
|
6920
|
+
fixed: z19.boolean().optional(),
|
|
6921
|
+
required: z19.boolean().optional(),
|
|
6922
|
+
preConditions: z19.array(rpExpressionSchema).optional(),
|
|
6923
|
+
branchRules: z19.array(branchRuleSchema).optional(),
|
|
6924
|
+
itemSessionControl: itemSessionControlSchema.optional(),
|
|
6925
|
+
timeLimits: timeLimitsSchema.optional(),
|
|
6926
|
+
weights: z19.array(weightSchema).optional(),
|
|
6927
|
+
templateDefaults: z19.array(templateDefaultSchema).optional()
|
|
6928
|
+
});
|
|
6929
|
+
var assessmentSectionFlags = {
|
|
6930
|
+
title: z19.string().trim().min(1).optional(),
|
|
6931
|
+
visible: z19.boolean().optional(),
|
|
6932
|
+
fixed: z19.boolean().optional(),
|
|
6933
|
+
required: z19.boolean().optional(),
|
|
6934
|
+
keepTogether: z19.boolean().optional(),
|
|
6935
|
+
selection: selectionSchema.optional(),
|
|
6936
|
+
ordering: orderingSchema.optional(),
|
|
6937
|
+
preConditions: z19.array(rpExpressionSchema).optional(),
|
|
6938
|
+
branchRules: z19.array(branchRuleSchema).optional(),
|
|
6939
|
+
itemSessionControl: itemSessionControlSchema.optional(),
|
|
6940
|
+
timeLimits: timeLimitsSchema.optional()
|
|
6941
|
+
};
|
|
6942
|
+
var testPartLevel = {
|
|
6943
|
+
preConditions: z19.array(rpExpressionSchema).optional(),
|
|
6944
|
+
branchRules: z19.array(branchRuleSchema).optional(),
|
|
6945
|
+
itemSessionControl: itemSessionControlSchema.optional(),
|
|
6946
|
+
timeLimits: timeLimitsSchema.optional()
|
|
6947
|
+
};
|
|
6948
|
+
function makeAssessmentTestSchema(itemRefSchema) {
|
|
6949
|
+
const assessmentSectionSchema = z19.lazy(() => z19.object({
|
|
6950
|
+
kind: z19.literal("assessmentSection"),
|
|
6951
|
+
identifier: z19.string().trim().min(1),
|
|
6952
|
+
...assessmentSectionFlags,
|
|
6953
|
+
children: z19.array(z19.union([assessmentSectionSchema, itemRefSchema]))
|
|
6954
|
+
}));
|
|
6955
|
+
const testPartSchema = z19.object({
|
|
6956
|
+
identifier: z19.string().trim().min(1),
|
|
6957
|
+
navigationMode: z19.enum(["linear", "nonlinear"]),
|
|
6958
|
+
submissionMode: z19.enum(["individual", "simultaneous"]),
|
|
6959
|
+
...testPartLevel,
|
|
6960
|
+
assessmentSections: z19.array(assessmentSectionSchema)
|
|
6961
|
+
});
|
|
6962
|
+
const assessmentTestSchema = z19.object({
|
|
6963
|
+
identifier: z19.string().trim().min(1),
|
|
6964
|
+
title: z19.string().trim().min(1).optional(),
|
|
6965
|
+
outcomeDeclarations: z19.array(outcomeDeclarationSchema).optional(),
|
|
6966
|
+
timeLimits: timeLimitsSchema.optional(),
|
|
6967
|
+
testParts: z19.array(testPartSchema),
|
|
6968
|
+
outcomeProcessing: outcomeProcessingSchema.optional(),
|
|
6969
|
+
testFeedbacks: z19.array(testFeedbackSchema).optional()
|
|
6970
|
+
});
|
|
6971
|
+
return { assessmentSectionSchema, testPartSchema, assessmentTestSchema };
|
|
6972
|
+
}
|
|
6973
|
+
var assessmentTestViewSchema = makeAssessmentTestSchema(assessmentItemRefViewSchema).assessmentTestSchema;
|
|
6786
6974
|
|
|
6787
6975
|
// src/index.ts
|
|
6788
6976
|
var qtiReactPackageName = "@conform-ed/qti-react";
|
|
@@ -6791,14 +6979,19 @@ export {
|
|
|
6791
6979
|
v0InteractionKinds,
|
|
6792
6980
|
v0ContentModel,
|
|
6793
6981
|
uploadInteraction,
|
|
6982
|
+
timeLimitsSchema,
|
|
6794
6983
|
textOf,
|
|
6795
6984
|
textEntryInteraction,
|
|
6985
|
+
testFeedbackSchema,
|
|
6796
6986
|
stimulusContentFromNormalized,
|
|
6797
6987
|
sliderInteraction,
|
|
6798
6988
|
serializePciMarkup,
|
|
6989
|
+
selectionSchema,
|
|
6799
6990
|
selectPointInteraction,
|
|
6800
6991
|
scoreResponse,
|
|
6801
6992
|
sanitizeAttributes,
|
|
6993
|
+
rpScalarSchema,
|
|
6994
|
+
rpExpressionSchema,
|
|
6802
6995
|
resolveTemplate,
|
|
6803
6996
|
resolvePnpActivation,
|
|
6804
6997
|
resolveCatalogSupports,
|
|
@@ -6813,16 +7006,25 @@ export {
|
|
|
6813
7006
|
pciResponseToValue,
|
|
6814
7007
|
parsePoint,
|
|
6815
7008
|
parseCoords,
|
|
7009
|
+
outcomeRuleSchema,
|
|
7010
|
+
outcomeProcessingSchema,
|
|
7011
|
+
outcomeDeclarationSchema,
|
|
7012
|
+
outcomeConditionBranchSchema,
|
|
7013
|
+
orderingSchema,
|
|
6816
7014
|
orderInteraction,
|
|
6817
7015
|
mulberry32,
|
|
6818
7016
|
mountPci,
|
|
6819
7017
|
mediaInteraction,
|
|
7018
|
+
matchTableSchema,
|
|
6820
7019
|
matchInteraction,
|
|
6821
7020
|
matchCorrect,
|
|
6822
7021
|
mapResponsePoint,
|
|
6823
7022
|
mapResponse,
|
|
7023
|
+
makeAssessmentTestSchema,
|
|
7024
|
+
itemSessionControlSchema,
|
|
6824
7025
|
isInteractionKind,
|
|
6825
7026
|
isAllowedFlowElement,
|
|
7027
|
+
interpolationTableSchema,
|
|
6826
7028
|
inlineChoiceInteraction,
|
|
6827
7029
|
hottextInteraction,
|
|
6828
7030
|
hotspotInteraction,
|
|
@@ -6836,6 +7038,7 @@ export {
|
|
|
6836
7038
|
executeTemplateProcessing,
|
|
6837
7039
|
executeResponseProcessing,
|
|
6838
7040
|
endAttemptInteraction,
|
|
7041
|
+
effectiveItemScore,
|
|
6839
7042
|
drawingInteraction,
|
|
6840
7043
|
defineInteraction,
|
|
6841
7044
|
createTestSessionStore,
|
|
@@ -6849,9 +7052,13 @@ export {
|
|
|
6849
7052
|
collectResponseViolations,
|
|
6850
7053
|
collectInteractionConstraints,
|
|
6851
7054
|
choiceInteraction,
|
|
7055
|
+
cardinalitySchema,
|
|
7056
|
+
branchRuleSchema,
|
|
6852
7057
|
associateInteraction,
|
|
7058
|
+
assessmentTestViewSchema,
|
|
6853
7059
|
assessmentTestViewFromNormalized,
|
|
6854
7060
|
assessmentItemViewFromNormalized,
|
|
7061
|
+
assessmentItemRefViewSchema,
|
|
6855
7062
|
applyCorrectResponseOverrides,
|
|
6856
7063
|
UploadReferenceSkin,
|
|
6857
7064
|
TextEntryReferenceSkin,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ScoreResult } from "./types";
|
|
2
|
+
export interface EffectiveItemScore {
|
|
3
|
+
readonly raw: number;
|
|
4
|
+
readonly max: number;
|
|
5
|
+
/** True when SCORE came from the RP outcomes of record rather than per-variable scoring. */
|
|
6
|
+
readonly fromOutcomes: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The item score of record (QTI): a numeric SCORE outcome from response processing is
|
|
10
|
+
* authoritative — PCI/RP-scored items (e.g. math-entry) have no per-variable
|
|
11
|
+
* correctResponse basis, so their standard scores read 0. Summed per-variable standard
|
|
12
|
+
* scoring is the fallback for items without RP. MAXSCORE follows the same precedence.
|
|
13
|
+
*
|
|
14
|
+
* Pure and framework-light: client and server (authoritative finalize) share it so the
|
|
15
|
+
* grade of record is derived identically on both sides.
|
|
16
|
+
*/
|
|
17
|
+
export declare function effectiveItemScore(scores: readonly ScoreResult[], outcomes: Readonly<Record<string, unknown>>): EffectiveItemScore;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod mirrors of the Response-Processing view types (./types.ts). conform-ed owns the
|
|
3
|
+
* QTI view *types*; this is the validation surface that sits beside them — reusable for
|
|
4
|
+
* import/parse validation and kept honest against the types it mirrors. React-free.
|
|
5
|
+
*
|
|
6
|
+
* The expression union is deliberately permissive (`kind: z.string()`), exactly like
|
|
7
|
+
* `RpExpressionView`: kinds the interpreter does not implement still validate structurally
|
|
8
|
+
* and surface as `unsupported-rp` issues at evaluation time rather than being rejected here.
|
|
9
|
+
*
|
|
10
|
+
* The recursive schema is annotated against an explicit interface (`RpExpressionNode`); under
|
|
11
|
+
* `exactOptionalPropertyTypes` zod emits `T | undefined` optionals, so `z.infer` is a
|
|
12
|
+
* superset of the hand-written views (every view value parses — verified in the schema
|
|
13
|
+
* tests against real view samples). Callers that need the exact view shape map explicitly.
|
|
14
|
+
*/
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
import type { RpScalar } from "./types";
|
|
17
|
+
/** string | number | boolean — the QTI scalar value space (NULL is modelled by absence). */
|
|
18
|
+
export declare const rpScalarSchema: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
19
|
+
/**
|
|
20
|
+
* The inferred shape of `rpExpressionSchema` — a faithful, recursive mirror of
|
|
21
|
+
* `RpExpressionView` with zod's `T | undefined` optionals. Declared explicitly (and the
|
|
22
|
+
* schema is annotated with it) because zod's getter-based recursion degrades the recursive
|
|
23
|
+
* `expressions` field to `Record<string, unknown>` under `z.infer`; the `z.lazy` + interface
|
|
24
|
+
* form keeps the type precise (the expression-builder authoring relies on it).
|
|
25
|
+
*/
|
|
26
|
+
export interface RpExpressionNode {
|
|
27
|
+
readonly kind: string;
|
|
28
|
+
readonly identifier?: string | undefined;
|
|
29
|
+
readonly baseType?: string | undefined;
|
|
30
|
+
readonly value?: RpScalar | undefined;
|
|
31
|
+
readonly expressions?: readonly RpExpressionNode[] | undefined;
|
|
32
|
+
readonly min?: number | string | undefined;
|
|
33
|
+
readonly max?: number | string | undefined;
|
|
34
|
+
readonly step?: number | string | undefined;
|
|
35
|
+
readonly toleranceMode?: "exact" | "absolute" | "relative" | undefined;
|
|
36
|
+
readonly tolerance?: readonly (number | string)[] | undefined;
|
|
37
|
+
readonly includeLowerBound?: boolean | undefined;
|
|
38
|
+
readonly includeUpperBound?: boolean | undefined;
|
|
39
|
+
readonly n?: number | string | undefined;
|
|
40
|
+
readonly name?: string | undefined;
|
|
41
|
+
readonly roundingMode?: "decimalPlaces" | "significantFigures" | undefined;
|
|
42
|
+
readonly figures?: number | string | undefined;
|
|
43
|
+
readonly numberRepeats?: number | string | undefined;
|
|
44
|
+
readonly pattern?: string | undefined;
|
|
45
|
+
readonly caseSensitive?: boolean | undefined;
|
|
46
|
+
readonly substring?: boolean | undefined;
|
|
47
|
+
readonly shape?: string | undefined;
|
|
48
|
+
readonly coords?: string | undefined;
|
|
49
|
+
readonly variableIdentifier?: string | undefined;
|
|
50
|
+
readonly outcomeIdentifier?: string | undefined;
|
|
51
|
+
readonly weightIdentifier?: string | undefined;
|
|
52
|
+
readonly sectionIdentifier?: string | undefined;
|
|
53
|
+
readonly includeCategory?: string | readonly string[] | undefined;
|
|
54
|
+
readonly excludeCategory?: string | readonly string[] | undefined;
|
|
55
|
+
readonly class?: string | undefined;
|
|
56
|
+
readonly definition?: string | undefined;
|
|
57
|
+
readonly fieldIdentifier?: string | undefined;
|
|
58
|
+
}
|
|
59
|
+
export declare const cardinalitySchema: z.ZodEnum<{
|
|
60
|
+
multiple: "multiple";
|
|
61
|
+
ordered: "ordered";
|
|
62
|
+
record: "record";
|
|
63
|
+
single: "single";
|
|
64
|
+
}>;
|
|
65
|
+
/** The recursive expression schema (`z.lazy` recursion; annotated for a precise `z.infer`). */
|
|
66
|
+
export declare const rpExpressionSchema: z.ZodType<RpExpressionNode>;
|
|
67
|
+
export declare const matchTableSchema: z.ZodObject<{
|
|
68
|
+
defaultValue: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
69
|
+
matchTableEntries: z.ZodArray<z.ZodObject<{
|
|
70
|
+
sourceValue: z.ZodNumber;
|
|
71
|
+
targetValue: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
72
|
+
}, z.core.$strip>>;
|
|
73
|
+
}, z.core.$strip>;
|
|
74
|
+
export declare const interpolationTableSchema: z.ZodObject<{
|
|
75
|
+
defaultValue: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
76
|
+
interpolationTableEntries: z.ZodArray<z.ZodObject<{
|
|
77
|
+
sourceValue: z.ZodNumber;
|
|
78
|
+
targetValue: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
79
|
+
includeBoundary: z.ZodOptional<z.ZodBoolean>;
|
|
80
|
+
}, z.core.$strip>>;
|
|
81
|
+
}, z.core.$strip>;
|
|
82
|
+
export declare const outcomeDeclarationSchema: z.ZodObject<{
|
|
83
|
+
identifier: z.ZodString;
|
|
84
|
+
cardinality: z.ZodEnum<{
|
|
85
|
+
multiple: "multiple";
|
|
86
|
+
ordered: "ordered";
|
|
87
|
+
record: "record";
|
|
88
|
+
single: "single";
|
|
89
|
+
}>;
|
|
90
|
+
baseType: z.ZodOptional<z.ZodString>;
|
|
91
|
+
defaultValue: z.ZodOptional<z.ZodObject<{
|
|
92
|
+
values: z.ZodArray<z.ZodObject<{
|
|
93
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
94
|
+
}, z.core.$strip>>;
|
|
95
|
+
}, z.core.$strip>>;
|
|
96
|
+
matchTable: z.ZodOptional<z.ZodObject<{
|
|
97
|
+
defaultValue: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
98
|
+
matchTableEntries: z.ZodArray<z.ZodObject<{
|
|
99
|
+
sourceValue: z.ZodNumber;
|
|
100
|
+
targetValue: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
101
|
+
}, z.core.$strip>>;
|
|
102
|
+
}, z.core.$strip>>;
|
|
103
|
+
interpolationTable: z.ZodOptional<z.ZodObject<{
|
|
104
|
+
defaultValue: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
105
|
+
interpolationTableEntries: z.ZodArray<z.ZodObject<{
|
|
106
|
+
sourceValue: z.ZodNumber;
|
|
107
|
+
targetValue: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
108
|
+
includeBoundary: z.ZodOptional<z.ZodBoolean>;
|
|
109
|
+
}, z.core.$strip>>;
|
|
110
|
+
}, z.core.$strip>>;
|
|
111
|
+
normalMaximum: z.ZodOptional<z.ZodNumber>;
|
|
112
|
+
normalMinimum: z.ZodOptional<z.ZodNumber>;
|
|
113
|
+
}, z.core.$strip>;
|
|
114
|
+
export type RpScalarSchema = z.infer<typeof rpScalarSchema>;
|
|
115
|
+
export type RpExpressionSchema = z.infer<typeof rpExpressionSchema>;
|
|
116
|
+
export type OutcomeDeclarationSchema = z.infer<typeof outcomeDeclarationSchema>;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod mirrors of the `assessmentTest` structural views (./types.ts) — the shared QTI
|
|
3
|
+
* atoms (`timeLimits`, `itemSessionControl`, `selection`, `ordering`, `branchRule`,
|
|
4
|
+
* test-level outcome processing/feedback) and the recursive structure
|
|
5
|
+
* (`assessmentSection` / `testPart` / `assessmentTest`).
|
|
6
|
+
*
|
|
7
|
+
* The structure is exposed as a **factory parameterized by the itemRef schema** —
|
|
8
|
+
* `makeAssessmentTestSchema(itemRefSchema)` — because the only thing that varies between
|
|
9
|
+
* delivery (an `href`) and authoring (an `itemVersionId`) is the item reference's identity.
|
|
10
|
+
* The ready `assessmentTestViewSchema` binds the delivery `href` itemRef; emergent binds
|
|
11
|
+
* its authoring itemRef. React-free; see ../rp/schema.ts for the RP-level mirrors.
|
|
12
|
+
*/
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import { type OutcomeDeclarationSchema, type RpExpressionSchema } from "../rp/schema";
|
|
15
|
+
/** `timeLimits` (seconds); the controller enforces these under its injected clock. */
|
|
16
|
+
export declare const timeLimitsSchema: z.ZodObject<{
|
|
17
|
+
minTime: z.ZodOptional<z.ZodNumber>;
|
|
18
|
+
maxTime: z.ZodOptional<z.ZodNumber>;
|
|
19
|
+
allowLateSubmission: z.ZodOptional<z.ZodBoolean>;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
/** `itemSessionControl`: per-level overrides cascading testPart → section → itemRef. */
|
|
22
|
+
export declare const itemSessionControlSchema: z.ZodObject<{
|
|
23
|
+
maxAttempts: z.ZodOptional<z.ZodNumber>;
|
|
24
|
+
showFeedback: z.ZodOptional<z.ZodBoolean>;
|
|
25
|
+
allowReview: z.ZodOptional<z.ZodBoolean>;
|
|
26
|
+
showSolution: z.ZodOptional<z.ZodBoolean>;
|
|
27
|
+
allowComment: z.ZodOptional<z.ZodBoolean>;
|
|
28
|
+
allowSkipping: z.ZodOptional<z.ZodBoolean>;
|
|
29
|
+
validateResponses: z.ZodOptional<z.ZodBoolean>;
|
|
30
|
+
}, z.core.$strip>;
|
|
31
|
+
/** `selection`: how many children to draw, optionally with replacement (§4.2.6). */
|
|
32
|
+
export declare const selectionSchema: z.ZodObject<{
|
|
33
|
+
select: z.ZodNumber;
|
|
34
|
+
withReplacement: z.ZodOptional<z.ZodBoolean>;
|
|
35
|
+
}, z.core.$strip>;
|
|
36
|
+
/** `ordering`: whether the surviving children are shuffled (§4.2.7). */
|
|
37
|
+
export declare const orderingSchema: z.ZodObject<{
|
|
38
|
+
shuffle: z.ZodOptional<z.ZodBoolean>;
|
|
39
|
+
}, z.core.$strip>;
|
|
40
|
+
export type TimeLimitsSchema = z.infer<typeof timeLimitsSchema>;
|
|
41
|
+
export type ItemSessionControlSchema = z.infer<typeof itemSessionControlSchema>;
|
|
42
|
+
export type SelectionSchema = z.infer<typeof selectionSchema>;
|
|
43
|
+
export type OrderingSchema = z.infer<typeof orderingSchema>;
|
|
44
|
+
/** `branchRule`: a target identifier (or EXIT_*) gated by a boolean expression. */
|
|
45
|
+
export declare const branchRuleSchema: z.ZodObject<{
|
|
46
|
+
target: z.ZodString;
|
|
47
|
+
expression: z.ZodType<import("../rp/schema").RpExpressionNode, unknown, z.core.$ZodTypeInternals<import("../rp/schema").RpExpressionNode, unknown>>;
|
|
48
|
+
}, z.core.$strip>;
|
|
49
|
+
export type BranchRuleSchema = z.infer<typeof branchRuleSchema>;
|
|
50
|
+
/** A gate plus its nested rules (`outcomeIf` / `outcomeElseIf`); see `OutcomeRuleNode`. */
|
|
51
|
+
export interface OutcomeConditionBranchNode {
|
|
52
|
+
readonly expression: RpExpressionSchema;
|
|
53
|
+
readonly rules: readonly OutcomeRuleNode[];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The recursive outcome-rule shape (a faithful mirror of `OutcomeRuleView` with zod's
|
|
57
|
+
* `T | undefined` optionals). Declared explicitly so `z.infer` stays precise through the
|
|
58
|
+
* recursion (the getter form degrades nested rules to `Record<string, unknown>`).
|
|
59
|
+
*/
|
|
60
|
+
export interface OutcomeRuleNode {
|
|
61
|
+
readonly kind: string;
|
|
62
|
+
readonly identifier?: string | undefined;
|
|
63
|
+
readonly expression?: RpExpressionSchema | undefined;
|
|
64
|
+
readonly rules?: readonly OutcomeRuleNode[] | undefined;
|
|
65
|
+
readonly outcomeIf?: OutcomeConditionBranchNode | undefined;
|
|
66
|
+
readonly outcomeElseIfs?: readonly OutcomeConditionBranchNode[] | undefined;
|
|
67
|
+
readonly outcomeElse?: {
|
|
68
|
+
readonly rules: readonly OutcomeRuleNode[];
|
|
69
|
+
} | undefined;
|
|
70
|
+
}
|
|
71
|
+
/** One `outcomeIf` / `outcomeElseIf` branch: a gate plus its nested rules. */
|
|
72
|
+
export declare const outcomeConditionBranchSchema: z.ZodType<OutcomeConditionBranchNode>;
|
|
73
|
+
/** A recursive outcome rule (mirrors `OutcomeRuleView`); `kind` stays permissive. */
|
|
74
|
+
export declare const outcomeRuleSchema: z.ZodType<OutcomeRuleNode>;
|
|
75
|
+
export declare const outcomeProcessingSchema: z.ZodObject<{
|
|
76
|
+
rules: z.ZodArray<z.ZodType<OutcomeRuleNode, unknown, z.core.$ZodTypeInternals<OutcomeRuleNode, unknown>>>;
|
|
77
|
+
}, z.core.$strip>;
|
|
78
|
+
export type OutcomeProcessingSchema = z.infer<typeof outcomeProcessingSchema>;
|
|
79
|
+
/**
|
|
80
|
+
* `testFeedback` — the scalar attributes are mirrored; the `content` body is accepted
|
|
81
|
+
* opaquely (BodyNode is the item runtime's content model, not authored at the test level).
|
|
82
|
+
*/
|
|
83
|
+
export declare const testFeedbackSchema: z.ZodObject<{
|
|
84
|
+
access: z.ZodOptional<z.ZodEnum<{
|
|
85
|
+
atEnd: "atEnd";
|
|
86
|
+
during: "during";
|
|
87
|
+
}>>;
|
|
88
|
+
outcomeIdentifier: z.ZodString;
|
|
89
|
+
identifier: z.ZodString;
|
|
90
|
+
showHide: z.ZodOptional<z.ZodEnum<{
|
|
91
|
+
hide: "hide";
|
|
92
|
+
show: "show";
|
|
93
|
+
}>>;
|
|
94
|
+
content: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
95
|
+
}, z.core.$strip>;
|
|
96
|
+
export type TestFeedbackSchema = z.infer<typeof testFeedbackSchema>;
|
|
97
|
+
/** The delivery `assessmentItemRef` (`href` identity) — the ready view itemRef. */
|
|
98
|
+
export declare const assessmentItemRefViewSchema: z.ZodObject<{
|
|
99
|
+
kind: z.ZodLiteral<"assessmentItemRef">;
|
|
100
|
+
identifier: z.ZodString;
|
|
101
|
+
href: z.ZodOptional<z.ZodString>;
|
|
102
|
+
categories: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
103
|
+
fixed: z.ZodOptional<z.ZodBoolean>;
|
|
104
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
105
|
+
preConditions: z.ZodOptional<z.ZodArray<z.ZodType<import("../rp/schema").RpExpressionNode, unknown, z.core.$ZodTypeInternals<import("../rp/schema").RpExpressionNode, unknown>>>>;
|
|
106
|
+
branchRules: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
107
|
+
target: z.ZodString;
|
|
108
|
+
expression: z.ZodType<import("../rp/schema").RpExpressionNode, unknown, z.core.$ZodTypeInternals<import("../rp/schema").RpExpressionNode, unknown>>;
|
|
109
|
+
}, z.core.$strip>>>;
|
|
110
|
+
itemSessionControl: z.ZodOptional<z.ZodObject<{
|
|
111
|
+
maxAttempts: z.ZodOptional<z.ZodNumber>;
|
|
112
|
+
showFeedback: z.ZodOptional<z.ZodBoolean>;
|
|
113
|
+
allowReview: z.ZodOptional<z.ZodBoolean>;
|
|
114
|
+
showSolution: z.ZodOptional<z.ZodBoolean>;
|
|
115
|
+
allowComment: z.ZodOptional<z.ZodBoolean>;
|
|
116
|
+
allowSkipping: z.ZodOptional<z.ZodBoolean>;
|
|
117
|
+
validateResponses: z.ZodOptional<z.ZodBoolean>;
|
|
118
|
+
}, z.core.$strip>>;
|
|
119
|
+
timeLimits: z.ZodOptional<z.ZodObject<{
|
|
120
|
+
minTime: z.ZodOptional<z.ZodNumber>;
|
|
121
|
+
maxTime: z.ZodOptional<z.ZodNumber>;
|
|
122
|
+
allowLateSubmission: z.ZodOptional<z.ZodBoolean>;
|
|
123
|
+
}, z.core.$strip>>;
|
|
124
|
+
weights: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
125
|
+
identifier: z.ZodString;
|
|
126
|
+
value: z.ZodNumber;
|
|
127
|
+
}, z.core.$strip>>>;
|
|
128
|
+
templateDefaults: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
129
|
+
templateIdentifier: z.ZodString;
|
|
130
|
+
expression: z.ZodType<import("../rp/schema").RpExpressionNode, unknown, z.core.$ZodTypeInternals<import("../rp/schema").RpExpressionNode, unknown>>;
|
|
131
|
+
}, z.core.$strip>>>;
|
|
132
|
+
}, z.core.$strip>;
|
|
133
|
+
/**
|
|
134
|
+
* The recursive section shape, parameterized by the injected itemRef's inferred type.
|
|
135
|
+
* Optionals carry `| undefined` and arrays are `readonly` so this matches zod's object
|
|
136
|
+
* output exactly — letting the `z.lazy` section schema below be annotated `z.ZodType<…>`
|
|
137
|
+
* (a getter cannot self-infer once a generic itemRef joins the `children` union).
|
|
138
|
+
*/
|
|
139
|
+
export interface AssessmentSectionNode<TItemRef> {
|
|
140
|
+
readonly kind: "assessmentSection";
|
|
141
|
+
readonly identifier: string;
|
|
142
|
+
readonly title?: string | undefined;
|
|
143
|
+
readonly visible?: boolean | undefined;
|
|
144
|
+
readonly fixed?: boolean | undefined;
|
|
145
|
+
readonly required?: boolean | undefined;
|
|
146
|
+
readonly keepTogether?: boolean | undefined;
|
|
147
|
+
readonly selection?: SelectionSchema | undefined;
|
|
148
|
+
readonly ordering?: OrderingSchema | undefined;
|
|
149
|
+
readonly preConditions?: readonly RpExpressionSchema[] | undefined;
|
|
150
|
+
readonly branchRules?: readonly BranchRuleSchema[] | undefined;
|
|
151
|
+
readonly itemSessionControl?: ItemSessionControlSchema | undefined;
|
|
152
|
+
readonly timeLimits?: TimeLimitsSchema | undefined;
|
|
153
|
+
readonly children: ReadonlyArray<AssessmentSectionNode<TItemRef> | TItemRef>;
|
|
154
|
+
}
|
|
155
|
+
/** A test part: navigation/submission modes plus the cascade levels and its sections. */
|
|
156
|
+
export interface TestPartNode<TItemRef> {
|
|
157
|
+
readonly identifier: string;
|
|
158
|
+
readonly navigationMode: "linear" | "nonlinear";
|
|
159
|
+
readonly submissionMode: "individual" | "simultaneous";
|
|
160
|
+
readonly preConditions?: readonly RpExpressionSchema[] | undefined;
|
|
161
|
+
readonly branchRules?: readonly BranchRuleSchema[] | undefined;
|
|
162
|
+
readonly itemSessionControl?: ItemSessionControlSchema | undefined;
|
|
163
|
+
readonly timeLimits?: TimeLimitsSchema | undefined;
|
|
164
|
+
readonly assessmentSections: readonly AssessmentSectionNode<TItemRef>[];
|
|
165
|
+
}
|
|
166
|
+
/** The whole `assessmentTest`: declarations/processing/feedback plus the ordered parts. */
|
|
167
|
+
export interface AssessmentTestNode<TItemRef> {
|
|
168
|
+
readonly identifier: string;
|
|
169
|
+
readonly title?: string | undefined;
|
|
170
|
+
readonly outcomeDeclarations?: readonly OutcomeDeclarationSchema[] | undefined;
|
|
171
|
+
readonly timeLimits?: TimeLimitsSchema | undefined;
|
|
172
|
+
readonly testParts: readonly TestPartNode<TItemRef>[];
|
|
173
|
+
readonly outcomeProcessing?: OutcomeProcessingSchema | undefined;
|
|
174
|
+
readonly testFeedbacks?: readonly TestFeedbackSchema[] | undefined;
|
|
175
|
+
}
|
|
176
|
+
/** The schema bundle the factory returns (each level parameterized by the itemRef type). */
|
|
177
|
+
export interface AssessmentTestSchemas<TItemRef> {
|
|
178
|
+
readonly assessmentSectionSchema: z.ZodType<AssessmentSectionNode<TItemRef>>;
|
|
179
|
+
readonly testPartSchema: z.ZodType<TestPartNode<TItemRef>>;
|
|
180
|
+
readonly assessmentTestSchema: z.ZodType<AssessmentTestNode<TItemRef>>;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Build the recursive `assessmentTest` schema around a caller-supplied itemRef schema.
|
|
184
|
+
* Sections nest sections-or-itemRefs through `children` (recursion via `z.lazy`); `z.infer`
|
|
185
|
+
* follows the recursion and carries the injected itemRef's inferred type. The explicit
|
|
186
|
+
* return type keeps the package's `isolatedDeclarations` build able to emit the bundle.
|
|
187
|
+
*/
|
|
188
|
+
export declare function makeAssessmentTestSchema<ItemRef extends z.ZodType>(itemRefSchema: ItemRef): AssessmentTestSchemas<z.infer<ItemRef>>;
|
|
189
|
+
/** The ready delivery-view schema: the recursive structure over the `href` itemRef. */
|
|
190
|
+
export declare const assessmentTestViewSchema: z.ZodType<AssessmentTestNode<AssessmentItemRefViewSchema>>;
|
|
191
|
+
export type AssessmentItemRefViewSchema = z.infer<typeof assessmentItemRefViewSchema>;
|
|
192
|
+
export type AssessmentTestViewSchema = z.infer<typeof assessmentTestViewSchema>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conform-ed/qti-react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"files": [
|
|
5
5
|
"src",
|
|
6
6
|
"dist"
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"xspattern": "^3.1.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@conform-ed/contracts": "0.0.
|
|
34
|
-
"@conform-ed/qti-xml": "0.0.
|
|
33
|
+
"@conform-ed/contracts": "0.0.20",
|
|
34
|
+
"@conform-ed/qti-xml": "0.0.20",
|
|
35
35
|
"@types/react": "^19.2.17",
|
|
36
36
|
"@types/react-dom": "^19",
|
|
37
|
-
"happy-dom": "^20.10.
|
|
37
|
+
"happy-dom": "^20.10.3",
|
|
38
38
|
"react": "^19.2.7",
|
|
39
39
|
"react-dom": "^19"
|
|
40
40
|
},
|