@mathscan/math-exercise-engine 1.2.10 → 1.2.11
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/README.md +2 -0
- package/dist/{GeometryWidget-gtAP_-_S.mjs → GeometryWidget-2TCLbTEv.mjs} +2 -2
- package/dist/{GeometryWidget-gtAP_-_S.mjs.map → GeometryWidget-2TCLbTEv.mjs.map} +1 -1
- package/dist/{GeometryWidget-DgPfqyQv.js → GeometryWidget-BTPjXJz-.js} +2 -2
- package/dist/{GeometryWidget-DgPfqyQv.js.map → GeometryWidget-BTPjXJz-.js.map} +1 -1
- package/dist/{index-DBteHZkS.mjs → index-6z_8j1tM.mjs} +882 -264
- package/dist/index-6z_8j1tM.mjs.map +1 -0
- package/dist/{index-tEgJE6m2.js → index-C3qDr71I.js} +712 -94
- package/dist/index-C3qDr71I.js.map +1 -0
- package/dist/index.d.ts +891 -32
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +178 -171
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +431 -38
- package/package.json +1 -1
- package/dist/index-DBteHZkS.mjs.map +0 -1
- package/dist/index-tEgJE6m2.js.map +0 -1
|
@@ -241,6 +241,7 @@ const JSON_TYPES = {
|
|
|
241
241
|
GEOMETRY: "geometry"
|
|
242
242
|
};
|
|
243
243
|
const DRAG_OPTIONS_WIDGET_ID = "_drag_options";
|
|
244
|
+
const NUMERIC_KEYPAD_WIDGET_ID = "_numeric_keypad";
|
|
244
245
|
const RULER_WIDGET_ID = "_ruler";
|
|
245
246
|
const PROTRACTOR_WIDGET_ID = "_protractor";
|
|
246
247
|
const SET_SQUARE_WIDGET_ID = "_set_square";
|
|
@@ -273,7 +274,8 @@ const INPUT_TYPES = {
|
|
|
273
274
|
/** Numeric input (digits only) */
|
|
274
275
|
NUMERIC: "numeric"
|
|
275
276
|
};
|
|
276
|
-
const NUMBER_LINE_STYLES = ["arrow-right", "arrow-left", "arrow-both", "line"];
|
|
277
|
+
const NUMBER_LINE_STYLES = ["arrow-right", "arrow-left", "arrow-both", "line", "line-left"];
|
|
278
|
+
const DEFAULT_NUMBER_LINE_STYLE = "line-left";
|
|
277
279
|
const INTERNAL_PRECISION = 9;
|
|
278
280
|
const DISPLAY_PRECISION = 2;
|
|
279
281
|
const SIDE_PAD = 7;
|
|
@@ -2056,6 +2058,8 @@ const VISUAL_FRACTION_SHAPE = {
|
|
|
2056
2058
|
};
|
|
2057
2059
|
const VISUAL_FRACTION_DEFAULT_SIZE = 1.35;
|
|
2058
2060
|
const VISUAL_FRACTION_MAX_DIVISIONS = 100;
|
|
2061
|
+
const INLINE_INPUT_MAX_WIDTH = 20;
|
|
2062
|
+
const InlineInputWidthSchema = zod.z.number().int().positive().max(INLINE_INPUT_MAX_WIDTH, { message: `width cannot exceed ${INLINE_INPUT_MAX_WIDTH}` }).optional();
|
|
2059
2063
|
const FreeTextInlineSchema = zod.z.object({
|
|
2060
2064
|
type: zod.z.literal(AST_TYPES.FREE_TEXT),
|
|
2061
2065
|
value: zod.z.string()
|
|
@@ -2085,6 +2089,7 @@ const InlineInputWidgetSchema = zod.z.discriminatedUnion("inputType", [
|
|
|
2085
2089
|
name: zod.z.string(),
|
|
2086
2090
|
inputType: zod.z.literal(INPUT_TYPES.STRING),
|
|
2087
2091
|
length: zod.z.number().int().positive(),
|
|
2092
|
+
width: InlineInputWidthSchema,
|
|
2088
2093
|
placeholder: zod.z.string().max(32).optional(),
|
|
2089
2094
|
disabled: zod.z.boolean().optional(),
|
|
2090
2095
|
answer: zod.z.union([zod.z.string(), zod.z.number()]).optional()
|
|
@@ -2094,6 +2099,7 @@ const InlineInputWidgetSchema = zod.z.discriminatedUnion("inputType", [
|
|
|
2094
2099
|
name: zod.z.string(),
|
|
2095
2100
|
inputType: zod.z.literal(INPUT_TYPES.NUMERIC),
|
|
2096
2101
|
digits: zod.z.number().int().positive(),
|
|
2102
|
+
width: InlineInputWidthSchema,
|
|
2097
2103
|
placeholder: zod.z.string().max(32).optional(),
|
|
2098
2104
|
disabled: zod.z.boolean().optional(),
|
|
2099
2105
|
answer: zod.z.union([zod.z.string(), zod.z.number()]).optional()
|
|
@@ -2251,7 +2257,7 @@ function createBoldText(value, highlight) {
|
|
|
2251
2257
|
highlight
|
|
2252
2258
|
});
|
|
2253
2259
|
}
|
|
2254
|
-
function createStringInput(name, length, answer, placeholder, disabled) {
|
|
2260
|
+
function createStringInput(name, length, answer, placeholder, disabled, width) {
|
|
2255
2261
|
return parseOrThrow(
|
|
2256
2262
|
InlineInputWidgetSchema,
|
|
2257
2263
|
{
|
|
@@ -2259,6 +2265,7 @@ function createStringInput(name, length, answer, placeholder, disabled) {
|
|
|
2259
2265
|
name,
|
|
2260
2266
|
inputType: INPUT_TYPES.STRING,
|
|
2261
2267
|
length,
|
|
2268
|
+
width,
|
|
2262
2269
|
placeholder,
|
|
2263
2270
|
disabled,
|
|
2264
2271
|
// A disabled input is never graded, so its answer is dropped.
|
|
@@ -2267,7 +2274,7 @@ function createStringInput(name, length, answer, placeholder, disabled) {
|
|
|
2267
2274
|
"STRING_INPUT"
|
|
2268
2275
|
);
|
|
2269
2276
|
}
|
|
2270
|
-
function createNumericInput(name, digits, answer, placeholder, disabled) {
|
|
2277
|
+
function createNumericInput(name, digits, answer, placeholder, disabled, width) {
|
|
2271
2278
|
return parseOrThrow(
|
|
2272
2279
|
InlineInputWidgetSchema,
|
|
2273
2280
|
{
|
|
@@ -2275,6 +2282,7 @@ function createNumericInput(name, digits, answer, placeholder, disabled) {
|
|
|
2275
2282
|
name,
|
|
2276
2283
|
inputType: INPUT_TYPES.NUMERIC,
|
|
2277
2284
|
digits,
|
|
2285
|
+
width,
|
|
2278
2286
|
placeholder,
|
|
2279
2287
|
disabled,
|
|
2280
2288
|
// A disabled input is never graded, so its answer is dropped.
|
|
@@ -2491,7 +2499,7 @@ const NumberLineBlockSchema = zod.z.object({
|
|
|
2491
2499
|
errorMap: () => ({
|
|
2492
2500
|
message: `NUMBER_LINE: style must be one of ${NUMBER_LINE_STYLES.join(", ")}`
|
|
2493
2501
|
})
|
|
2494
|
-
}).
|
|
2502
|
+
}).default(DEFAULT_NUMBER_LINE_STYLE),
|
|
2495
2503
|
labelVisibility: zod.z.enum(NUMBER_LINE_LABELS, {
|
|
2496
2504
|
errorMap: () => ({
|
|
2497
2505
|
message: `NUMBER_LINE: labelVisibility must be one of ${NUMBER_LINE_LABELS.join(", ")}`
|
|
@@ -4324,12 +4332,19 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
4324
4332
|
`${ctx.stringInput ? "STRING_INPUT" : "NUMERIC_INPUT"} "${name}": disabled must be true or false, got ${JSON.stringify(params.disabled)}`
|
|
4325
4333
|
);
|
|
4326
4334
|
}
|
|
4335
|
+
const width = params.width;
|
|
4336
|
+
const maxChars = ctx.stringInput ? params.length : params.digits;
|
|
4337
|
+
if (width !== void 0 && width < maxChars) {
|
|
4338
|
+
throw new Error(
|
|
4339
|
+
`${ctx.stringInput ? "STRING_INPUT" : "NUMERIC_INPUT"} "${name}": width (${width}) cannot be smaller than ${ctx.stringInput ? "length" : "digits"} (${maxChars})`
|
|
4340
|
+
);
|
|
4341
|
+
}
|
|
4327
4342
|
if (ctx.stringInput) {
|
|
4328
4343
|
const length = params.length;
|
|
4329
|
-
return createStringInput(name, length, answer, placeholder, disabled);
|
|
4344
|
+
return createStringInput(name, length, answer, placeholder, disabled, width);
|
|
4330
4345
|
}
|
|
4331
4346
|
const digits = params.digits;
|
|
4332
|
-
return createNumericInput(name, digits, answer, placeholder, disabled);
|
|
4347
|
+
return createNumericInput(name, digits, answer, placeholder, disabled, width);
|
|
4333
4348
|
}
|
|
4334
4349
|
inlineDragOption(ctx) {
|
|
4335
4350
|
const valueToken = ctx.StringLiteral[0];
|
|
@@ -5069,6 +5084,8 @@ const StringInputJSONSchema = zod.z.object({
|
|
|
5069
5084
|
type: zod.z.literal(JSON_TYPES.INLINE_INPUT),
|
|
5070
5085
|
inputType: zod.z.literal(INPUT_TYPES.STRING),
|
|
5071
5086
|
length: zod.z.number().int().positive(),
|
|
5087
|
+
/** Box width in characters; defaults to `length`. */
|
|
5088
|
+
width: InlineInputWidthSchema,
|
|
5072
5089
|
placeholder: zod.z.string().max(32).optional(),
|
|
5073
5090
|
/** Gray, non-interactive, never graded (any answer for it is ignored). */
|
|
5074
5091
|
disabled: zod.z.boolean().optional()
|
|
@@ -5077,6 +5094,8 @@ const NumericInputJSONSchema = zod.z.object({
|
|
|
5077
5094
|
type: zod.z.literal(JSON_TYPES.INLINE_INPUT),
|
|
5078
5095
|
inputType: zod.z.literal(INPUT_TYPES.NUMERIC),
|
|
5079
5096
|
digits: zod.z.number().int().positive(),
|
|
5097
|
+
/** Box width in characters; defaults to `digits`. */
|
|
5098
|
+
width: InlineInputWidthSchema,
|
|
5080
5099
|
placeholder: zod.z.string().max(32).optional(),
|
|
5081
5100
|
/** Gray, non-interactive, never graded (any answer for it is ignored). */
|
|
5082
5101
|
disabled: zod.z.boolean().optional()
|
|
@@ -6067,7 +6086,7 @@ lodash.exports;
|
|
|
6067
6086
|
var reIsNative = RegExp2(
|
|
6068
6087
|
"^" + funcToString.call(hasOwnProperty).replace(reRegExpChar, "\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, "$1.*?") + "$"
|
|
6069
6088
|
);
|
|
6070
|
-
var Buffer = moduleExports ? context.Buffer : undefined$1,
|
|
6089
|
+
var Buffer = moduleExports ? context.Buffer : undefined$1, Symbol2 = context.Symbol, Uint8Array = context.Uint8Array, allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined$1, getPrototype = overArg(Object2.getPrototypeOf, Object2), objectCreate = Object2.create, propertyIsEnumerable = objectProto.propertyIsEnumerable, splice = arrayProto.splice, spreadableSymbol = Symbol2 ? Symbol2.isConcatSpreadable : undefined$1, symIterator = Symbol2 ? Symbol2.iterator : undefined$1, symToStringTag = Symbol2 ? Symbol2.toStringTag : undefined$1;
|
|
6071
6090
|
var defineProperty = function() {
|
|
6072
6091
|
try {
|
|
6073
6092
|
var func = getNative(Object2, "defineProperty");
|
|
@@ -6082,7 +6101,7 @@ lodash.exports;
|
|
|
6082
6101
|
var metaMap = WeakMap && new WeakMap();
|
|
6083
6102
|
var realNames = {};
|
|
6084
6103
|
var dataViewCtorString = toSource(DataView), mapCtorString = toSource(Map2), promiseCtorString = toSource(Promise2), setCtorString = toSource(Set2), weakMapCtorString = toSource(WeakMap);
|
|
6085
|
-
var symbolProto =
|
|
6104
|
+
var symbolProto = Symbol2 ? Symbol2.prototype : undefined$1, symbolValueOf = symbolProto ? symbolProto.valueOf : undefined$1, symbolToString = symbolProto ? symbolProto.toString : undefined$1;
|
|
6086
6105
|
function lodash2(value) {
|
|
6087
6106
|
if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {
|
|
6088
6107
|
if (value instanceof LodashWrapper) {
|
|
@@ -10981,6 +11000,7 @@ function toJSON(ast) {
|
|
|
10981
11000
|
type: JSON_TYPES.INLINE_INPUT,
|
|
10982
11001
|
inputType: INPUT_TYPES.STRING,
|
|
10983
11002
|
length: inline.length,
|
|
11003
|
+
...inline.width !== void 0 && { width: inline.width },
|
|
10984
11004
|
placeholder: inline.placeholder,
|
|
10985
11005
|
...inline.disabled && { disabled: true }
|
|
10986
11006
|
};
|
|
@@ -10989,6 +11009,7 @@ function toJSON(ast) {
|
|
|
10989
11009
|
type: JSON_TYPES.INLINE_INPUT,
|
|
10990
11010
|
inputType: INPUT_TYPES.NUMERIC,
|
|
10991
11011
|
digits: inline.digits,
|
|
11012
|
+
...inline.width !== void 0 && { width: inline.width },
|
|
10992
11013
|
placeholder: inline.placeholder,
|
|
10993
11014
|
...inline.disabled && { disabled: true }
|
|
10994
11015
|
};
|
|
@@ -11084,7 +11105,7 @@ function toJSON(ast) {
|
|
|
11084
11105
|
options: lodashExports.shuffle(tool.options)
|
|
11085
11106
|
};
|
|
11086
11107
|
} else if (tool.type === AST_TYPES.NUMERIC_KEYPAD) {
|
|
11087
|
-
widgets[
|
|
11108
|
+
widgets[NUMERIC_KEYPAD_WIDGET_ID] = {
|
|
11088
11109
|
type: JSON_TYPES.NUMERIC_KEYPAD,
|
|
11089
11110
|
includeOperators: tool.includeOperators,
|
|
11090
11111
|
includeDecimal: tool.includeDecimal,
|
|
@@ -11347,7 +11368,7 @@ function transformNumberLine(numberLine, idGenerator, inlineDragIds) {
|
|
|
11347
11368
|
max: numberLine.max,
|
|
11348
11369
|
step: numberLine.step,
|
|
11349
11370
|
...numberLine.minorTicks !== void 0 && { minorTicks: numberLine.minorTicks },
|
|
11350
|
-
|
|
11371
|
+
style: numberLine.style,
|
|
11351
11372
|
...numberLine.labelVisibility !== void 0 && { labelVisibility: numberLine.labelVisibility },
|
|
11352
11373
|
...counterItem !== void 0 && { counter: counterItem },
|
|
11353
11374
|
...highlights !== void 0 && { highlights },
|
|
@@ -16004,22 +16025,22 @@ function createDefaultDropAnimation(options) {
|
|
|
16004
16025
|
easing,
|
|
16005
16026
|
fill: "forwards"
|
|
16006
16027
|
});
|
|
16007
|
-
return new Promise((
|
|
16028
|
+
return new Promise((resolve2) => {
|
|
16008
16029
|
animation.onfinish = () => {
|
|
16009
16030
|
cleanup == null ? void 0 : cleanup();
|
|
16010
|
-
|
|
16031
|
+
resolve2();
|
|
16011
16032
|
};
|
|
16012
16033
|
});
|
|
16013
16034
|
};
|
|
16014
16035
|
}
|
|
16015
|
-
let key = 0;
|
|
16036
|
+
let key$1 = 0;
|
|
16016
16037
|
function useKey(id) {
|
|
16017
16038
|
return React.useMemo(() => {
|
|
16018
16039
|
if (id == null) {
|
|
16019
16040
|
return;
|
|
16020
16041
|
}
|
|
16021
|
-
key++;
|
|
16022
|
-
return key;
|
|
16042
|
+
key$1++;
|
|
16043
|
+
return key$1;
|
|
16023
16044
|
}, [id]);
|
|
16024
16045
|
}
|
|
16025
16046
|
const DragOverlay = /* @__PURE__ */ React.memo((_ref) => {
|
|
@@ -16101,6 +16122,7 @@ const DEFAULT_SOUNDS = {
|
|
|
16101
16122
|
"drag-start": dragStart,
|
|
16102
16123
|
"drag-drop": dragDrop,
|
|
16103
16124
|
"drag-cancel": dragDrop,
|
|
16125
|
+
"drag-hint": buttonClick,
|
|
16104
16126
|
"radio-select": buttonClick,
|
|
16105
16127
|
"pair-select": buttonClick,
|
|
16106
16128
|
"pair-link": buttonClick,
|
|
@@ -16204,14 +16226,24 @@ function useExerciseDnd() {
|
|
|
16204
16226
|
}
|
|
16205
16227
|
return ctx;
|
|
16206
16228
|
}
|
|
16229
|
+
const NO_EXERCISE = Symbol("no-exercise");
|
|
16207
16230
|
const ExerciseDndProvider = ({
|
|
16208
16231
|
children,
|
|
16209
16232
|
droppedSources,
|
|
16210
|
-
onDragEnd
|
|
16233
|
+
onDragEnd,
|
|
16234
|
+
exerciseKey
|
|
16211
16235
|
}) => {
|
|
16212
16236
|
const [activeItem, setActiveItem] = React.useState(null);
|
|
16237
|
+
const [draggedExerciseKey, setDraggedExerciseKey] = React.useState(NO_EXERCISE);
|
|
16238
|
+
const hasDraggedOption = draggedExerciseKey === exerciseKey;
|
|
16213
16239
|
const [dropHandlers] = React.useState(() => /* @__PURE__ */ new Map());
|
|
16214
16240
|
const playSound = useSound();
|
|
16241
|
+
const hintSoundExerciseRef = React.useRef(NO_EXERCISE);
|
|
16242
|
+
const playHintSound = React.useCallback(() => {
|
|
16243
|
+
if (hintSoundExerciseRef.current === exerciseKey) return;
|
|
16244
|
+
hintSoundExerciseRef.current = exerciseKey;
|
|
16245
|
+
playSound("drag-hint");
|
|
16246
|
+
}, [playSound, exerciseKey]);
|
|
16215
16247
|
const usedOptionIds = React.useMemo(() => {
|
|
16216
16248
|
return new Set(droppedSources.values());
|
|
16217
16249
|
}, [droppedSources]);
|
|
@@ -16235,6 +16267,7 @@ const ExerciseDndProvider = ({
|
|
|
16235
16267
|
const raw = active.data.current;
|
|
16236
16268
|
const item = raw?.type === "placed-chip" ? { type: "placed-chip", widgetId: raw.widgetId, label: raw.label, sourceOptionId: raw.sourceOptionId } : raw;
|
|
16237
16269
|
setActiveItem(item);
|
|
16270
|
+
if (item.type === "drag-option") setDraggedExerciseKey(exerciseKey);
|
|
16238
16271
|
playSound("drag-start");
|
|
16239
16272
|
};
|
|
16240
16273
|
const handleDragEnd = (event) => {
|
|
@@ -16260,6 +16293,9 @@ const ExerciseDndProvider = ({
|
|
|
16260
16293
|
value: {
|
|
16261
16294
|
activeItem,
|
|
16262
16295
|
isDragging: activeItem !== null,
|
|
16296
|
+
exerciseKey,
|
|
16297
|
+
hasDraggedOption,
|
|
16298
|
+
playHintSound,
|
|
16263
16299
|
usedOptionIds,
|
|
16264
16300
|
droppedSources,
|
|
16265
16301
|
registerDropHandler,
|
|
@@ -17040,7 +17076,38 @@ function isUndoDrop(event, dragId) {
|
|
|
17040
17076
|
const LockContext = React.createContext(false);
|
|
17041
17077
|
const ExerciseLockProvider = ({ locked, children }) => /* @__PURE__ */ jsxRuntime.jsx(LockContext.Provider, { value: locked, children });
|
|
17042
17078
|
const useExerciseLocked = () => React.useContext(LockContext);
|
|
17043
|
-
const
|
|
17079
|
+
const OVERLAY_STYLE = {
|
|
17080
|
+
position: "absolute",
|
|
17081
|
+
top: 0,
|
|
17082
|
+
left: 0,
|
|
17083
|
+
width: "100%",
|
|
17084
|
+
height: "100%",
|
|
17085
|
+
minWidth: 0,
|
|
17086
|
+
maxWidth: "none",
|
|
17087
|
+
margin: 0,
|
|
17088
|
+
boxSizing: "border-box"
|
|
17089
|
+
};
|
|
17090
|
+
const SlotSizer = ({
|
|
17091
|
+
style,
|
|
17092
|
+
...props
|
|
17093
|
+
}) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
17094
|
+
"input",
|
|
17095
|
+
{
|
|
17096
|
+
...props,
|
|
17097
|
+
type: "text",
|
|
17098
|
+
value: "",
|
|
17099
|
+
readOnly: true,
|
|
17100
|
+
tabIndex: -1,
|
|
17101
|
+
"aria-hidden": "true",
|
|
17102
|
+
style: { ...style, visibility: "hidden" }
|
|
17103
|
+
}
|
|
17104
|
+
);
|
|
17105
|
+
const PlacedInputChip = ({
|
|
17106
|
+
widgetId,
|
|
17107
|
+
value,
|
|
17108
|
+
className = "",
|
|
17109
|
+
onRemove
|
|
17110
|
+
}) => {
|
|
17044
17111
|
const { droppedSources } = useExerciseDnd();
|
|
17045
17112
|
const locked = useExerciseLocked();
|
|
17046
17113
|
const sourceOptionId = droppedSources.get(widgetId);
|
|
@@ -17064,13 +17131,41 @@ const PlacedInputChip = ({ widgetId, value, className = "", onRemove }) => {
|
|
|
17064
17131
|
if (!locked && e.detail !== 0) onRemove();
|
|
17065
17132
|
},
|
|
17066
17133
|
title: locked ? void 0 : "Drag to move, or click to remove",
|
|
17067
|
-
style: {
|
|
17134
|
+
style: {
|
|
17135
|
+
...OVERLAY_STYLE,
|
|
17136
|
+
touchAction: "none",
|
|
17137
|
+
cursor: locked ? "default" : "grab"
|
|
17138
|
+
},
|
|
17068
17139
|
...listeners,
|
|
17069
17140
|
...attributes,
|
|
17070
17141
|
children: value
|
|
17071
17142
|
}
|
|
17072
17143
|
);
|
|
17073
17144
|
};
|
|
17145
|
+
function getMaxInputLength(exercise, inputKey) {
|
|
17146
|
+
const widget = exercise.widgets[inputKey];
|
|
17147
|
+
if (widget?.type !== JSON_TYPES.INLINE_INPUT) return void 0;
|
|
17148
|
+
return widget.inputType === INPUT_TYPES.NUMERIC ? widget.digits : widget.length;
|
|
17149
|
+
}
|
|
17150
|
+
function getInputElementId(key2) {
|
|
17151
|
+
return `${key2}-input`;
|
|
17152
|
+
}
|
|
17153
|
+
function getTextInputs(container) {
|
|
17154
|
+
return Array.from(
|
|
17155
|
+
container.querySelectorAll(
|
|
17156
|
+
'input[type="text"], input[type="number"], textarea, select'
|
|
17157
|
+
)
|
|
17158
|
+
);
|
|
17159
|
+
}
|
|
17160
|
+
function canTakeInput(el) {
|
|
17161
|
+
return !el.readOnly && !el.disabled;
|
|
17162
|
+
}
|
|
17163
|
+
function findNextInput(container, currentKey) {
|
|
17164
|
+
const inputs = getTextInputs(container);
|
|
17165
|
+
const currentIndex = inputs.findIndex((el) => el.id === getInputElementId(currentKey));
|
|
17166
|
+
if (currentIndex === -1) return null;
|
|
17167
|
+
return inputs.slice(currentIndex + 1).find(canTakeInput) ?? null;
|
|
17168
|
+
}
|
|
17074
17169
|
const InlineInput = ({
|
|
17075
17170
|
widgetId,
|
|
17076
17171
|
value,
|
|
@@ -17079,6 +17174,7 @@ const InlineInput = ({
|
|
|
17079
17174
|
inputType,
|
|
17080
17175
|
length,
|
|
17081
17176
|
digits,
|
|
17177
|
+
width,
|
|
17082
17178
|
placeholder,
|
|
17083
17179
|
disabled = false,
|
|
17084
17180
|
onChange,
|
|
@@ -17092,8 +17188,8 @@ const InlineInput = ({
|
|
|
17092
17188
|
const handleDrop = React.useCallback((item) => {
|
|
17093
17189
|
return applyDropToInput(widgetId, item, onChange);
|
|
17094
17190
|
}, [onChange, widgetId]);
|
|
17095
|
-
const expectedLength = inputType ===
|
|
17096
|
-
const dynamicWidth = `${(expectedLength
|
|
17191
|
+
const expectedLength = inputType === INPUT_TYPES.NUMERIC ? digits : length;
|
|
17192
|
+
const dynamicWidth = `${(width ?? expectedLength ?? 5) * 0.8 + 1}em`;
|
|
17097
17193
|
const statusClass = validationStatus ? ` widget--${validationStatus}` : "";
|
|
17098
17194
|
const disabledClass = disabled ? " inline-input--disabled" : "";
|
|
17099
17195
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -17106,21 +17202,31 @@ const InlineInput = ({
|
|
|
17106
17202
|
className: "inline-widget inline-input-droppable",
|
|
17107
17203
|
children: isDropped && value !== "" ? (
|
|
17108
17204
|
// Show as chip only when value came from drag-drop
|
|
17109
|
-
/* @__PURE__ */ jsxRuntime.
|
|
17110
|
-
|
|
17111
|
-
|
|
17112
|
-
|
|
17113
|
-
|
|
17114
|
-
|
|
17115
|
-
|
|
17116
|
-
|
|
17117
|
-
|
|
17205
|
+
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
17206
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17207
|
+
SlotSizer,
|
|
17208
|
+
{
|
|
17209
|
+
className: `inline-input${disabledClass}`,
|
|
17210
|
+
style: { width: dynamicWidth },
|
|
17211
|
+
placeholder
|
|
17212
|
+
}
|
|
17213
|
+
),
|
|
17214
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17215
|
+
PlacedInputChip,
|
|
17216
|
+
{
|
|
17217
|
+
widgetId,
|
|
17218
|
+
value,
|
|
17219
|
+
className: statusClass.trim(),
|
|
17220
|
+
onRemove: () => handleChange("", false)
|
|
17221
|
+
}
|
|
17222
|
+
)
|
|
17223
|
+
] })
|
|
17118
17224
|
) : (
|
|
17119
17225
|
// Regular input for typing
|
|
17120
17226
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17121
17227
|
"input",
|
|
17122
17228
|
{
|
|
17123
|
-
id: widgetId
|
|
17229
|
+
id: getInputElementId(widgetId),
|
|
17124
17230
|
type: "text",
|
|
17125
17231
|
className: `inline-input${statusClass}${disabledClass}`,
|
|
17126
17232
|
value,
|
|
@@ -17140,15 +17246,20 @@ const InlineInput = ({
|
|
|
17140
17246
|
}
|
|
17141
17247
|
);
|
|
17142
17248
|
};
|
|
17143
|
-
const SingleDragOption = ({
|
|
17249
|
+
const SingleDragOption = (props) => {
|
|
17250
|
+
const { exerciseKey } = useExerciseDnd();
|
|
17251
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DragOptionChip, { ...props }, exerciseKey ?? "");
|
|
17252
|
+
};
|
|
17253
|
+
const DragOptionChip = ({
|
|
17144
17254
|
optionId,
|
|
17145
17255
|
value,
|
|
17146
17256
|
onOptionClick,
|
|
17147
17257
|
className = ""
|
|
17148
17258
|
}) => {
|
|
17149
|
-
const { usedOptionIds } = useExerciseDnd();
|
|
17259
|
+
const { usedOptionIds, hasDraggedOption, playHintSound } = useExerciseDnd();
|
|
17150
17260
|
const locked = useExerciseLocked();
|
|
17151
17261
|
const isUsed = usedOptionIds.has(optionId);
|
|
17262
|
+
const { jiggling: hinting, onAnimationEnd: onHintEnd } = useJiggle(hasDraggedOption);
|
|
17152
17263
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `drag-option-slot ${className}`.trim(), children: isUsed ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "drag-option-empty", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
17153
17264
|
Draggable,
|
|
17154
17265
|
{
|
|
@@ -17160,7 +17271,11 @@ const SingleDragOption = ({
|
|
|
17160
17271
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
17161
17272
|
"button",
|
|
17162
17273
|
{
|
|
17163
|
-
className: "drag-option",
|
|
17274
|
+
className: hinting && !locked ? "drag-option drag-option--hint" : "drag-option",
|
|
17275
|
+
onAnimationStart: () => {
|
|
17276
|
+
if (hinting) playHintSound();
|
|
17277
|
+
},
|
|
17278
|
+
onAnimationEnd: onHintEnd,
|
|
17164
17279
|
onClick: () => {
|
|
17165
17280
|
if (!locked) onOptionClick?.(value);
|
|
17166
17281
|
},
|
|
@@ -17289,23 +17404,23 @@ const NumericKeypad = ({
|
|
|
17289
17404
|
playSound("keypad-press");
|
|
17290
17405
|
onChange(value.slice(0, -1));
|
|
17291
17406
|
};
|
|
17292
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `numeric-keypad ${includeOperators ? "numeric-keypad-with-operators" : ""}`, children: [
|
|
17293
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("1"), children: "1" }),
|
|
17294
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("2"), children: "2" }),
|
|
17295
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("3"), children: "3" }),
|
|
17296
|
-
includeOperators && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", onClick: () => handleDigit("+"), children: "+" }),
|
|
17297
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("4"), children: "4" }),
|
|
17298
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("5"), children: "5" }),
|
|
17299
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("6"), children: "6" }),
|
|
17300
|
-
includeOperators && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", onClick: () => handleDigit("-"), children: "-" }),
|
|
17301
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("7"), children: "7" }),
|
|
17302
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("8"), children: "8" }),
|
|
17303
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("9"), children: "9" }),
|
|
17304
|
-
includeOperators && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", onClick: () => handleDigit("×"), children: "×" }),
|
|
17305
|
-
includeDecimal ? /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-decimal", onClick: () => handleDigit(","), children: "," }) : /* @__PURE__ */ jsxRuntime.jsx("div", {}),
|
|
17306
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("0"), children: "0" }),
|
|
17307
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-backspace", onClick: handleBackspace, children: "⌫" }),
|
|
17308
|
-
includeOperators && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", onClick: () => handleDigit("÷"), children: "÷" })
|
|
17407
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `numeric-keypad ${includeOperators ? "numeric-keypad-with-operators" : ""}`, "data-tutorial": "keypad", children: [
|
|
17408
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "1", onClick: () => handleDigit("1"), children: "1" }),
|
|
17409
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "2", onClick: () => handleDigit("2"), children: "2" }),
|
|
17410
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "3", onClick: () => handleDigit("3"), children: "3" }),
|
|
17411
|
+
includeOperators && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", "data-key": "+", onClick: () => handleDigit("+"), children: "+" }),
|
|
17412
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "4", onClick: () => handleDigit("4"), children: "4" }),
|
|
17413
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "5", onClick: () => handleDigit("5"), children: "5" }),
|
|
17414
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "6", onClick: () => handleDigit("6"), children: "6" }),
|
|
17415
|
+
includeOperators && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", "data-key": "-", onClick: () => handleDigit("-"), children: "-" }),
|
|
17416
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "7", onClick: () => handleDigit("7"), children: "7" }),
|
|
17417
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "8", onClick: () => handleDigit("8"), children: "8" }),
|
|
17418
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "9", onClick: () => handleDigit("9"), children: "9" }),
|
|
17419
|
+
includeOperators && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", "data-key": "×", onClick: () => handleDigit("×"), children: "×" }),
|
|
17420
|
+
includeDecimal ? /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-decimal", "data-key": ",", onClick: () => handleDigit(","), children: "," }) : /* @__PURE__ */ jsxRuntime.jsx("div", {}),
|
|
17421
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "0", onClick: () => handleDigit("0"), children: "0" }),
|
|
17422
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-backspace", "data-key": "backspace", onClick: handleBackspace, children: "⌫" }),
|
|
17423
|
+
includeOperators && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", "data-key": "÷", onClick: () => handleDigit("÷"), children: "÷" })
|
|
17309
17424
|
] });
|
|
17310
17425
|
};
|
|
17311
17426
|
const FractionInput = ({
|
|
@@ -17345,18 +17460,28 @@ const FractionInput = ({
|
|
|
17345
17460
|
onDrop: handleDrop(subKey),
|
|
17346
17461
|
disabled: locked,
|
|
17347
17462
|
className: "fraction-slot-droppable",
|
|
17348
|
-
children: isDroppedHere && currentValue !== "" ? /* @__PURE__ */ jsxRuntime.
|
|
17349
|
-
|
|
17350
|
-
|
|
17351
|
-
|
|
17352
|
-
|
|
17353
|
-
|
|
17354
|
-
|
|
17355
|
-
|
|
17356
|
-
|
|
17463
|
+
children: isDroppedHere && currentValue !== "" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
17464
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17465
|
+
SlotSizer,
|
|
17466
|
+
{
|
|
17467
|
+
className: "inline-input fraction-input",
|
|
17468
|
+
size: placeholder ? placeholder.length : void 0,
|
|
17469
|
+
placeholder
|
|
17470
|
+
}
|
|
17471
|
+
),
|
|
17472
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17473
|
+
PlacedInputChip,
|
|
17474
|
+
{
|
|
17475
|
+
widgetId: subKey,
|
|
17476
|
+
value: currentValue,
|
|
17477
|
+
className: `fraction-chip${slotClass}`,
|
|
17478
|
+
onRemove: () => onChange(subKey, "", false)
|
|
17479
|
+
}
|
|
17480
|
+
)
|
|
17481
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
17357
17482
|
"input",
|
|
17358
17483
|
{
|
|
17359
|
-
id:
|
|
17484
|
+
id: getInputElementId(subKey),
|
|
17360
17485
|
type: "text",
|
|
17361
17486
|
className: `inline-input fraction-input${slotClass}`,
|
|
17362
17487
|
value: currentValue,
|
|
@@ -18934,11 +19059,12 @@ const RadioOptions = ({
|
|
|
18934
19059
|
};
|
|
18935
19060
|
const Btn = ({
|
|
18936
19061
|
label,
|
|
19062
|
+
dataKey = label,
|
|
18937
19063
|
extraClass = "keypad-btn-digit",
|
|
18938
19064
|
onClick
|
|
18939
19065
|
}) => {
|
|
18940
19066
|
const locked = useExerciseLocked();
|
|
18941
|
-
return /* @__PURE__ */ jsxRuntime.jsx("button", { className: `keypad-btn ${extraClass}`, disabled: locked, onClick, children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }) });
|
|
19067
|
+
return /* @__PURE__ */ jsxRuntime.jsx("button", { className: `keypad-btn ${extraClass}`, "data-key": dataKey, disabled: locked, onClick, children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }) });
|
|
18942
19068
|
};
|
|
18943
19069
|
const DiamondKeypad = ({ value, onChange }) => {
|
|
18944
19070
|
const playSound = useSound();
|
|
@@ -18950,7 +19076,7 @@ const DiamondKeypad = ({ value, onChange }) => {
|
|
|
18950
19076
|
playSound("keypad-press");
|
|
18951
19077
|
onChange(value.slice(0, -1));
|
|
18952
19078
|
};
|
|
18953
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "diamond-keypad", children: [
|
|
19079
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "diamond-keypad", "data-tutorial": "keypad", children: [
|
|
18954
19080
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "diamond-keypad-row", children: [
|
|
18955
19081
|
/* @__PURE__ */ jsxRuntime.jsx(Btn, { label: "1", onClick: () => append("1") }),
|
|
18956
19082
|
/* @__PURE__ */ jsxRuntime.jsx(Btn, { label: "2", onClick: () => append("2") }),
|
|
@@ -18969,7 +19095,7 @@ const DiamondKeypad = ({ value, onChange }) => {
|
|
|
18969
19095
|
] }),
|
|
18970
19096
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "diamond-keypad-row", children: [
|
|
18971
19097
|
/* @__PURE__ */ jsxRuntime.jsx(Btn, { label: ".", extraClass: "keypad-btn-decimal", onClick: () => append(".") }),
|
|
18972
|
-
/* @__PURE__ */ jsxRuntime.jsx(Btn, { label: "⌫", extraClass: "keypad-btn-backspace", onClick: backspace })
|
|
19098
|
+
/* @__PURE__ */ jsxRuntime.jsx(Btn, { label: "⌫", dataKey: "backspace", extraClass: "keypad-btn-backspace", onClick: backspace })
|
|
18973
19099
|
] })
|
|
18974
19100
|
] });
|
|
18975
19101
|
};
|
|
@@ -19575,7 +19701,7 @@ function isGeometryWidget(widget) {
|
|
|
19575
19701
|
return widget.type === JSON_TYPES.GEOMETRY;
|
|
19576
19702
|
}
|
|
19577
19703
|
const GeometryWidget = React.lazy(async () => ({
|
|
19578
|
-
default: (await Promise.resolve().then(() => require("./GeometryWidget-
|
|
19704
|
+
default: (await Promise.resolve().then(() => require("./GeometryWidget-BTPjXJz-.js"))).GeometryWidget
|
|
19579
19705
|
}));
|
|
19580
19706
|
const WidgetRenderer = ({
|
|
19581
19707
|
widget,
|
|
@@ -19603,8 +19729,9 @@ const WidgetRenderer = ({
|
|
|
19603
19729
|
isDropped,
|
|
19604
19730
|
validationStatus,
|
|
19605
19731
|
inputType: widget.inputType,
|
|
19606
|
-
length: widget.inputType ===
|
|
19607
|
-
digits: widget.inputType ===
|
|
19732
|
+
length: widget.inputType === INPUT_TYPES.STRING ? widget.length : void 0,
|
|
19733
|
+
digits: widget.inputType === INPUT_TYPES.NUMERIC ? widget.digits : void 0,
|
|
19734
|
+
width: widget.width,
|
|
19608
19735
|
placeholder: widget.placeholder,
|
|
19609
19736
|
disabled: widget.disabled,
|
|
19610
19737
|
onChange,
|
|
@@ -19975,6 +20102,13 @@ const EPSILON = 10 ** -INTERNAL_PRECISION;
|
|
|
19975
20102
|
const cleanFloat = (n) => parseFloat(n.toFixed(INTERNAL_PRECISION));
|
|
19976
20103
|
const roundTick = (n) => parseFloat(n.toFixed(DISPLAY_PRECISION));
|
|
19977
20104
|
const almostEqual = (a, b) => Math.abs(a - b) <= EPSILON;
|
|
20105
|
+
const NUMBER_LINE_AXIS_ENDS = {
|
|
20106
|
+
"arrow-right": { left: "tick", right: "arrow" },
|
|
20107
|
+
"arrow-left": { left: "arrow", right: "tick" },
|
|
20108
|
+
"arrow-both": { left: "arrow", right: "arrow" },
|
|
20109
|
+
"line": { left: "line", right: "line" },
|
|
20110
|
+
"line-left": { left: "line", right: "arrow" }
|
|
20111
|
+
};
|
|
19978
20112
|
const toPercent = (value, min, max) => {
|
|
19979
20113
|
const raw = SIDE_PAD + (value - min) / (max - min) * (100 - 2 * SIDE_PAD);
|
|
19980
20114
|
return Math.max(SIDE_PAD, Math.min(100 - SIDE_PAD, raw));
|
|
@@ -21022,7 +21156,7 @@ const NumberLineRenderer = ({
|
|
|
21022
21156
|
max,
|
|
21023
21157
|
step,
|
|
21024
21158
|
minorTicks = 0,
|
|
21025
|
-
style: lineStyle =
|
|
21159
|
+
style: lineStyle = DEFAULT_NUMBER_LINE_STYLE,
|
|
21026
21160
|
labelVisibility,
|
|
21027
21161
|
highlights,
|
|
21028
21162
|
arrows,
|
|
@@ -21086,8 +21220,9 @@ const NumberLineRenderer = ({
|
|
|
21086
21220
|
() => getMinorTickValues(majorTicks, minorTicks),
|
|
21087
21221
|
[majorTicks, minorTicks]
|
|
21088
21222
|
);
|
|
21089
|
-
const
|
|
21090
|
-
const showArrowLeft =
|
|
21223
|
+
const axisEnds = NUMBER_LINE_AXIS_ENDS[lineStyle] ?? NUMBER_LINE_AXIS_ENDS[DEFAULT_NUMBER_LINE_STYLE];
|
|
21224
|
+
const showArrowLeft = axisEnds.left === "arrow";
|
|
21225
|
+
const showArrowRight = axisEnds.right === "arrow";
|
|
21091
21226
|
const hasDragOptions = Boolean(widgets[DRAG_OPTIONS_WIDGET_ID]);
|
|
21092
21227
|
const isAxisDragMode = hasDragOptions && item.dragMode === "axis";
|
|
21093
21228
|
const inputTickSet = React.useMemo(() => {
|
|
@@ -21135,7 +21270,7 @@ const NumberLineRenderer = ({
|
|
|
21135
21270
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
21136
21271
|
"div",
|
|
21137
21272
|
{
|
|
21138
|
-
className: `number-line${isAxisDragMode ? " number-line--drag-mode" : ""}${
|
|
21273
|
+
className: `number-line${isAxisDragMode ? " number-line--drag-mode" : ""}${axisEnds.left === "line" ? " number-line--extend-left" : ""}${axisEnds.right === "line" ? " number-line--extend-right" : ""}${nlValidationStatus ? ` number-line--${nlValidationStatus}` : ""}`,
|
|
21139
21274
|
style: {
|
|
21140
21275
|
[CSS_VAR_SIDE_PAD]: `${SIDE_PAD}%`,
|
|
21141
21276
|
...positionContentHeight && { "--nl-position-content-height": positionContentHeight }
|
|
@@ -21508,6 +21643,35 @@ function groupLineItems(items) {
|
|
|
21508
21643
|
}
|
|
21509
21644
|
return groups;
|
|
21510
21645
|
}
|
|
21646
|
+
function placeTableCells(rows, cols) {
|
|
21647
|
+
const placements = /* @__PURE__ */ new Map();
|
|
21648
|
+
const occupied = rows.map(() => []);
|
|
21649
|
+
const totalRows = rows.length;
|
|
21650
|
+
rows.forEach((row, rowIndex) => {
|
|
21651
|
+
const taken = occupied[rowIndex] ?? [];
|
|
21652
|
+
let col = 0;
|
|
21653
|
+
for (const cell of row.cells) {
|
|
21654
|
+
while (taken[col]) col++;
|
|
21655
|
+
const colspan = cell.colspan || 1;
|
|
21656
|
+
const rowspan = Math.min(cell.rowspan || 1, totalRows - rowIndex);
|
|
21657
|
+
for (let r = rowIndex; r < rowIndex + rowspan; r++) {
|
|
21658
|
+
const line = occupied[r];
|
|
21659
|
+
if (!line) continue;
|
|
21660
|
+
for (let c = col; c < col + colspan; c++) line[c] = true;
|
|
21661
|
+
}
|
|
21662
|
+
placements.set(cell.id, {
|
|
21663
|
+
column: col + 1,
|
|
21664
|
+
row: rowIndex + 1,
|
|
21665
|
+
colspan,
|
|
21666
|
+
rowspan,
|
|
21667
|
+
isLastColumn: col + colspan >= cols,
|
|
21668
|
+
isLastRow: rowIndex + rowspan >= totalRows
|
|
21669
|
+
});
|
|
21670
|
+
col += colspan;
|
|
21671
|
+
}
|
|
21672
|
+
});
|
|
21673
|
+
return placements;
|
|
21674
|
+
}
|
|
21511
21675
|
const toValidationStatus = (v) => v === true ? "correct" : v === false ? "incorrect" : null;
|
|
21512
21676
|
const isNumericText = (value) => /^[\d.,\s]+$/.test(value);
|
|
21513
21677
|
const numberClass = (value) => isNumericText(value) ? "text-item-number" : "";
|
|
@@ -21631,21 +21795,28 @@ const LayoutRenderer = ({
|
|
|
21631
21795
|
subItem.id
|
|
21632
21796
|
)) });
|
|
21633
21797
|
case JSON_TYPES.TABLE: {
|
|
21634
|
-
const
|
|
21798
|
+
const placements = placeTableCells(item.rows, item.cols);
|
|
21635
21799
|
const tableStyle = {
|
|
21636
|
-
"--exercise-table-border-color": item.borderColor ?? DEFAULT_TABLE_BORDER_COLOR
|
|
21800
|
+
"--exercise-table-border-color": item.borderColor ?? DEFAULT_TABLE_BORDER_COLOR,
|
|
21801
|
+
"--exercise-table-cols": item.cols
|
|
21637
21802
|
};
|
|
21638
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "exercise-table-container", style: tableStyle, children: /* @__PURE__ */ jsxRuntime.jsx("
|
|
21639
|
-
const
|
|
21640
|
-
const
|
|
21803
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "exercise-table-container", style: tableStyle, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "exercise-table", role: "table", children: item.rows.map((row) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "exercise-table-row", role: "row", children: row.cells.map((cell) => {
|
|
21804
|
+
const placement = placements.get(cell.id);
|
|
21805
|
+
const modifiers = [
|
|
21806
|
+
placement?.isLastRow ? " exercise-table-cell--last-row" : "",
|
|
21807
|
+
placement?.isLastColumn ? " exercise-table-cell--last-col" : ""
|
|
21808
|
+
].join("");
|
|
21641
21809
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
21642
|
-
"
|
|
21810
|
+
"div",
|
|
21643
21811
|
{
|
|
21644
|
-
className: `exercise-table-cell${
|
|
21645
|
-
|
|
21646
|
-
|
|
21647
|
-
|
|
21648
|
-
|
|
21812
|
+
className: `exercise-table-cell${modifiers}`,
|
|
21813
|
+
role: "cell",
|
|
21814
|
+
style: {
|
|
21815
|
+
backgroundColor: cell.backgroundColor ?? row.backgroundColor,
|
|
21816
|
+
gridColumn: placement && `${placement.column} / span ${placement.colspan}`,
|
|
21817
|
+
gridRow: placement && `${placement.row} / span ${placement.rowspan}`
|
|
21818
|
+
},
|
|
21819
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "exercise-table-cell-content", children: cell.items.map((subItem) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
21649
21820
|
LayoutRenderer,
|
|
21650
21821
|
{
|
|
21651
21822
|
item: subItem,
|
|
@@ -21658,11 +21829,11 @@ const LayoutRenderer = ({
|
|
|
21658
21829
|
resetKey
|
|
21659
21830
|
},
|
|
21660
21831
|
subItem.id
|
|
21661
|
-
))
|
|
21832
|
+
)) })
|
|
21662
21833
|
},
|
|
21663
21834
|
cell.id
|
|
21664
21835
|
);
|
|
21665
|
-
}) }, row.id)) }) })
|
|
21836
|
+
}) }, row.id)) }) });
|
|
21666
21837
|
}
|
|
21667
21838
|
case JSON_TYPES.NUMBER_LINE:
|
|
21668
21839
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -21832,6 +22003,394 @@ const LayoutRenderer = ({
|
|
|
21832
22003
|
return null;
|
|
21833
22004
|
}
|
|
21834
22005
|
};
|
|
22006
|
+
function LionHand({ className }) {
|
|
22007
|
+
const id = React.useId();
|
|
22008
|
+
const shadowId = `${id}-shadow`;
|
|
22009
|
+
const maskId = `${id}-mask`;
|
|
22010
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
22011
|
+
"svg",
|
|
22012
|
+
{
|
|
22013
|
+
"aria-hidden": "true",
|
|
22014
|
+
focusable: "false",
|
|
22015
|
+
width: "75",
|
|
22016
|
+
height: "78",
|
|
22017
|
+
viewBox: "0 0 75 78",
|
|
22018
|
+
fill: "none",
|
|
22019
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
22020
|
+
className,
|
|
22021
|
+
children: [
|
|
22022
|
+
/* @__PURE__ */ jsxRuntime.jsxs("g", { filter: `url(#${shadowId})`, children: [
|
|
22023
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
22024
|
+
"mask",
|
|
22025
|
+
{
|
|
22026
|
+
id: maskId,
|
|
22027
|
+
style: { maskType: "alpha" },
|
|
22028
|
+
maskUnits: "userSpaceOnUse",
|
|
22029
|
+
x: "9",
|
|
22030
|
+
y: "19",
|
|
22031
|
+
width: "49",
|
|
22032
|
+
height: "42",
|
|
22033
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M49.7484 33.9668C58.158 45.6281 37.9572 61.4399 28.1745 60.1051C17.1397 58.5995 5.03802 42.2474 10.4349 38.3224C15.8318 34.3974 18.9715 32.4031 23.1181 30.1278C24.1486 29.5624 25.1742 29.0263 26.1947 28.5358C33.7314 24.764 52.8138 16.0308 57.0969 21.97C59.9201 25.8848 54.8651 28.9522 48.1097 31.8806C48.658 32.518 49.2042 33.2123 49.7484 33.9668Z", fill: "#FCD592" })
|
|
22034
|
+
}
|
|
22035
|
+
),
|
|
22036
|
+
/* @__PURE__ */ jsxRuntime.jsxs("g", { mask: `url(#${maskId})`, children: [
|
|
22037
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M49.7484 33.9672C58.158 45.6285 38.3118 60.3151 32.1037 64.7332C22.6672 71.4486 2.68204 43.9969 12.2424 37.1926C15.1884 35.0959 18.9716 32.4035 23.1182 30.1282C24.7308 29.2433 26.3314 28.4303 27.9195 27.7514C36.611 23.6398 54.4269 16.1501 58.4471 21.7248C61.34 25.7362 55.5686 28.9294 48.2024 31.9893C48.7196 32.5968 49.2349 33.2552 49.7484 33.9672Z", fill: "#FCD592" }),
|
|
22038
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12.2426 37.1924C2.68223 43.9967 22.6674 71.4485 32.1039 64.733C31.3434 63.6785 26.4317 61.1668 25.3134 59.6161C24.1951 58.0654 22.794 50.5211 22.123 49.5907C21.4521 48.6603 16.3219 45.7409 15.5637 44.4032C14.8055 43.0655 13.8979 39.4877 14.7258 35.4253L12.2426 37.1924Z", fill: "#6B291E" }),
|
|
22039
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M50.4102 36.1737C50.6823 37.524 52.0035 38.4016 53.3614 38.1339C54.7192 37.8662 55.5994 36.5546 55.3274 35.2043L52.8688 35.689L50.4102 36.1737ZM38.8014 26.9523C37.4241 27.0934 36.425 28.3178 36.5697 29.6872C36.7144 31.0565 37.9482 32.0522 39.3255 31.9111L39.0634 29.4317L38.8014 26.9523ZM52.8688 35.689C55.3274 35.2043 55.327 35.2023 55.3266 35.2004C55.3265 35.1997 55.326 35.1977 55.3258 35.1963C55.3252 35.1935 55.3246 35.1906 55.324 35.1875C55.3227 35.1815 55.3213 35.1749 55.3198 35.1679C55.3168 35.1539 55.3133 35.1379 55.3092 35.1201C55.3011 35.0845 55.2907 35.0415 55.2779 34.9916C55.2522 34.8918 55.2163 34.764 55.1676 34.6127C55.0703 34.3106 54.9205 33.911 54.6961 33.4507C54.247 32.5296 53.4925 31.3535 52.2552 30.241C49.7078 27.9508 45.5021 26.2658 38.8014 26.9523L39.0634 29.4317L39.3255 31.9111C44.9159 31.3384 47.6128 32.782 48.8954 33.9352C49.5731 34.5445 49.9653 35.1687 50.1842 35.6177C50.2938 35.8424 50.3584 36.0201 50.3919 36.1242C50.4086 36.1759 50.4172 36.2084 50.4197 36.2181C50.4209 36.223 50.4206 36.222 50.419 36.2148C50.4181 36.2111 50.417 36.2059 50.4155 36.1991C50.4148 36.1957 50.414 36.1919 50.4131 36.1876C50.4127 36.1855 50.4122 36.1833 50.4117 36.181C50.4115 36.1798 50.4111 36.178 50.411 36.1774C50.4106 36.1755 50.4102 36.1737 52.8688 35.689Z", fill: "#A2634A" }),
|
|
22040
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M54.7526 47.3947C55.5602 48.5146 57.1269 48.7734 58.2518 47.9729C59.3767 47.1723 59.634 45.6155 58.8264 44.4956L56.7895 45.9452L54.7526 47.3947ZM38.1539 36.5617C36.7805 36.7357 35.8113 37.9837 35.9891 39.3493C36.1669 40.7148 37.4244 41.6808 38.7978 41.5068L38.4758 39.0342L38.1539 36.5617ZM56.7895 45.9452L58.8264 44.4956C57.4277 42.5562 55.3182 40.1317 51.9378 38.4204C48.5385 36.6995 44.0772 35.8112 38.1539 36.5617L38.4758 39.0342L38.7978 41.5068C43.8796 40.863 47.3003 41.6624 49.6691 42.8616C52.0569 44.0704 53.6015 45.7985 54.7526 47.3947L56.7895 45.9452Z", fill: "#A2634A" })
|
|
22041
|
+
] })
|
|
22042
|
+
] }),
|
|
22043
|
+
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("filter", { id: shadowId, x: "-1.99995", y: "0", width: "78.3658", height: "81.8518", filterUnits: "userSpaceOnUse", colorInterpolationFilters: "sRGB", children: [
|
|
22044
|
+
/* @__PURE__ */ jsxRuntime.jsx("feFlood", { floodOpacity: "0", result: "BackgroundImageFix" }),
|
|
22045
|
+
/* @__PURE__ */ jsxRuntime.jsx("feColorMatrix", { in: "SourceAlpha", type: "matrix", values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0", result: "hardAlpha" }),
|
|
22046
|
+
/* @__PURE__ */ jsxRuntime.jsx("feOffset", { dy: "2" }),
|
|
22047
|
+
/* @__PURE__ */ jsxRuntime.jsx("feGaussianBlur", { stdDeviation: "1" }),
|
|
22048
|
+
/* @__PURE__ */ jsxRuntime.jsx("feComposite", { in2: "hardAlpha", operator: "out" }),
|
|
22049
|
+
/* @__PURE__ */ jsxRuntime.jsx("feColorMatrix", { type: "matrix", values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" }),
|
|
22050
|
+
/* @__PURE__ */ jsxRuntime.jsx("feBlend", { mode: "normal", in2: "BackgroundImageFix", result: "effect1_dropShadow" }),
|
|
22051
|
+
/* @__PURE__ */ jsxRuntime.jsx("feBlend", { mode: "normal", in: "SourceGraphic", in2: "effect1_dropShadow", result: "shape" })
|
|
22052
|
+
] }) })
|
|
22053
|
+
]
|
|
22054
|
+
}
|
|
22055
|
+
);
|
|
22056
|
+
}
|
|
22057
|
+
const TUTORIAL_HAND_TIP = { x: 56 / 75, y: 22 / 78 };
|
|
22058
|
+
const CYCLE_MS = 1400;
|
|
22059
|
+
const PRESS_AT_MS = 650;
|
|
22060
|
+
const PRESS_HOLD_MS = 220;
|
|
22061
|
+
const HOLE_PAD = 10;
|
|
22062
|
+
const HOLE_RADIUS = 18;
|
|
22063
|
+
const HAND_W = 72;
|
|
22064
|
+
const HAND_H = 75;
|
|
22065
|
+
const BUBBLE_W = 240;
|
|
22066
|
+
const BUBBLE_GAP = 14;
|
|
22067
|
+
const EDGE = 8;
|
|
22068
|
+
const DEFAULT_NEXT_LABEL = "فهمت";
|
|
22069
|
+
function resolve(t) {
|
|
22070
|
+
return (typeof t === "function" ? t() : t) ?? null;
|
|
22071
|
+
}
|
|
22072
|
+
function toRect(t) {
|
|
22073
|
+
if (!t) return null;
|
|
22074
|
+
const r = t instanceof Element ? t.getBoundingClientRect() : t;
|
|
22075
|
+
if (r.width <= 0 || r.height <= 0) return null;
|
|
22076
|
+
return { x: Math.round(r.x), y: Math.round(r.y), width: Math.round(r.width), height: Math.round(r.height) };
|
|
22077
|
+
}
|
|
22078
|
+
const centerOf = (r) => ({ x: r.x + r.width / 2, y: r.y + r.height / 2 });
|
|
22079
|
+
const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
|
|
22080
|
+
const { steps } = tutorial;
|
|
22081
|
+
const [stepIndex, setStepIndex] = React.useState(
|
|
22082
|
+
() => Math.min(Math.max(0, Math.floor(options.startStep ?? 0)), steps.length - 1)
|
|
22083
|
+
);
|
|
22084
|
+
const step = steps[stepIndex];
|
|
22085
|
+
const optionsRef = React.useRef(options);
|
|
22086
|
+
optionsRef.current = options;
|
|
22087
|
+
const ctx = { root, json, submitTarget: resolve(options.submitTarget) };
|
|
22088
|
+
const ctxRef = React.useRef(ctx);
|
|
22089
|
+
ctxRef.current = ctx;
|
|
22090
|
+
const [layout, setLayout] = React.useState(null);
|
|
22091
|
+
React.useEffect(() => {
|
|
22092
|
+
let raf = 0;
|
|
22093
|
+
let last = "";
|
|
22094
|
+
const tick = () => {
|
|
22095
|
+
const c = ctxRef.current;
|
|
22096
|
+
const next2 = {
|
|
22097
|
+
spotlight: step.spotlight(c).map(toRect),
|
|
22098
|
+
taps: step.taps(c).map(toRect),
|
|
22099
|
+
anchor: toRect(resolve(optionsRef.current.bubbleAnchor)),
|
|
22100
|
+
root: toRect(c.root),
|
|
22101
|
+
vw: window.innerWidth,
|
|
22102
|
+
vh: window.innerHeight
|
|
22103
|
+
};
|
|
22104
|
+
const key2 = JSON.stringify(next2);
|
|
22105
|
+
if (key2 !== last) {
|
|
22106
|
+
last = key2;
|
|
22107
|
+
setLayout(next2);
|
|
22108
|
+
}
|
|
22109
|
+
raf = requestAnimationFrame(tick);
|
|
22110
|
+
};
|
|
22111
|
+
tick();
|
|
22112
|
+
return () => cancelAnimationFrame(raf);
|
|
22113
|
+
}, [step]);
|
|
22114
|
+
const [tapIndex, setTapIndex] = React.useState(0);
|
|
22115
|
+
const [tapCount, setTapCount] = React.useState(0);
|
|
22116
|
+
const [shownStep, setShownStep] = React.useState(stepIndex);
|
|
22117
|
+
if (shownStep !== stepIndex) {
|
|
22118
|
+
setShownStep(stepIndex);
|
|
22119
|
+
setTapIndex(0);
|
|
22120
|
+
setTapCount(0);
|
|
22121
|
+
}
|
|
22122
|
+
React.useEffect(() => {
|
|
22123
|
+
let release;
|
|
22124
|
+
const press = setTimeout(() => {
|
|
22125
|
+
setTapCount((n) => n + 1);
|
|
22126
|
+
const targets = step.taps(ctxRef.current);
|
|
22127
|
+
const el = targets[tapIndex % Math.max(1, targets.length)];
|
|
22128
|
+
if (el instanceof Element) {
|
|
22129
|
+
el.classList.add("met-tutorial-pressed");
|
|
22130
|
+
release = setTimeout(() => el.classList.remove("met-tutorial-pressed"), PRESS_HOLD_MS);
|
|
22131
|
+
}
|
|
22132
|
+
}, PRESS_AT_MS);
|
|
22133
|
+
const advance = setTimeout(() => setTapIndex((i) => i + 1), CYCLE_MS);
|
|
22134
|
+
return () => {
|
|
22135
|
+
clearTimeout(press);
|
|
22136
|
+
clearTimeout(advance);
|
|
22137
|
+
if (release) clearTimeout(release);
|
|
22138
|
+
root.querySelectorAll(".met-tutorial-pressed").forEach((el) => el.classList.remove("met-tutorial-pressed"));
|
|
22139
|
+
};
|
|
22140
|
+
}, [step, tapIndex, root]);
|
|
22141
|
+
const text = options.texts?.[step.id] ?? step.text;
|
|
22142
|
+
const textRef = React.useRef(text);
|
|
22143
|
+
textRef.current = text;
|
|
22144
|
+
React.useEffect(() => {
|
|
22145
|
+
optionsRef.current.onStep?.(stepIndex, step.id);
|
|
22146
|
+
optionsRef.current.onSpeak?.(step.id, textRef.current);
|
|
22147
|
+
}, [stepIndex, step]);
|
|
22148
|
+
const isLast = stepIndex === steps.length - 1;
|
|
22149
|
+
const next = () => {
|
|
22150
|
+
if (isLast) onClose(true);
|
|
22151
|
+
else setStepIndex((i) => i + 1);
|
|
22152
|
+
};
|
|
22153
|
+
const overlayRef = React.useRef(null);
|
|
22154
|
+
const nextRef = React.useRef(null);
|
|
22155
|
+
const onCloseRef = React.useRef(onClose);
|
|
22156
|
+
onCloseRef.current = onClose;
|
|
22157
|
+
React.useEffect(() => {
|
|
22158
|
+
const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
22159
|
+
previous?.blur();
|
|
22160
|
+
const onKeyDown = (e) => {
|
|
22161
|
+
const overlay = overlayRef.current;
|
|
22162
|
+
if (e.key === "Escape") {
|
|
22163
|
+
e.preventDefault();
|
|
22164
|
+
e.stopPropagation();
|
|
22165
|
+
onCloseRef.current(false);
|
|
22166
|
+
return;
|
|
22167
|
+
}
|
|
22168
|
+
if (e.key === "Tab" && overlay) {
|
|
22169
|
+
e.preventDefault();
|
|
22170
|
+
const buttons = Array.from(overlay.querySelectorAll("button"));
|
|
22171
|
+
if (buttons.length === 0) return;
|
|
22172
|
+
const at = buttons.indexOf(document.activeElement);
|
|
22173
|
+
const to = (at + (e.shiftKey ? -1 : 1) + buttons.length) % buttons.length;
|
|
22174
|
+
buttons[to]?.focus();
|
|
22175
|
+
return;
|
|
22176
|
+
}
|
|
22177
|
+
if (overlay && e.target instanceof Node && overlay.contains(e.target)) return;
|
|
22178
|
+
e.preventDefault();
|
|
22179
|
+
e.stopPropagation();
|
|
22180
|
+
};
|
|
22181
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
22182
|
+
return () => {
|
|
22183
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
22184
|
+
if (previous?.isConnected) previous.focus();
|
|
22185
|
+
};
|
|
22186
|
+
}, []);
|
|
22187
|
+
const bubbleShown = layout !== null;
|
|
22188
|
+
React.useEffect(() => {
|
|
22189
|
+
nextRef.current?.focus({ focusVisible: false });
|
|
22190
|
+
}, [stepIndex, bubbleShown]);
|
|
22191
|
+
const maskId = `met-tutorial-mask-${React.useId()}`;
|
|
22192
|
+
if (typeof document === "undefined") return null;
|
|
22193
|
+
const holes = (layout?.spotlight ?? []).filter((r) => r !== null);
|
|
22194
|
+
const tapTarget = layout?.taps.length ? layout.taps[tapIndex % layout.taps.length] ?? null : null;
|
|
22195
|
+
const Scene = step.Scene;
|
|
22196
|
+
return reactDom.createPortal(
|
|
22197
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { ref: overlayRef, className: "met-tutorial", role: "dialog", "aria-modal": "true", "aria-label": text, children: [
|
|
22198
|
+
/* @__PURE__ */ jsxRuntime.jsxs("svg", { className: "met-tutorial-dim", "aria-hidden": "true", children: [
|
|
22199
|
+
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("mask", { id: maskId, children: [
|
|
22200
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { width: "100%", height: "100%", fill: "white" }),
|
|
22201
|
+
holes.map((r, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
22202
|
+
"rect",
|
|
22203
|
+
{
|
|
22204
|
+
x: r.x - HOLE_PAD,
|
|
22205
|
+
y: r.y - HOLE_PAD,
|
|
22206
|
+
width: r.width + HOLE_PAD * 2,
|
|
22207
|
+
height: r.height + HOLE_PAD * 2,
|
|
22208
|
+
rx: HOLE_RADIUS,
|
|
22209
|
+
fill: "black"
|
|
22210
|
+
},
|
|
22211
|
+
i
|
|
22212
|
+
))
|
|
22213
|
+
] }) }),
|
|
22214
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { width: "100%", height: "100%", mask: `url(#${maskId})` })
|
|
22215
|
+
] }),
|
|
22216
|
+
holes.map((r, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
22217
|
+
"div",
|
|
22218
|
+
{
|
|
22219
|
+
className: "met-tutorial-glow",
|
|
22220
|
+
style: {
|
|
22221
|
+
left: r.x - HOLE_PAD,
|
|
22222
|
+
top: r.y - HOLE_PAD,
|
|
22223
|
+
width: r.width + HOLE_PAD * 2,
|
|
22224
|
+
height: r.height + HOLE_PAD * 2,
|
|
22225
|
+
borderRadius: HOLE_RADIUS
|
|
22226
|
+
}
|
|
22227
|
+
},
|
|
22228
|
+
i
|
|
22229
|
+
)),
|
|
22230
|
+
Scene && layout && /* @__PURE__ */ jsxRuntime.jsx(Scene, { ctx, spotlight: layout.spotlight, taps: layout.taps, tapCount }),
|
|
22231
|
+
tapTarget && /* @__PURE__ */ jsxRuntime.jsx(Hand, { target: tapTarget, tapKey: `${stepIndex}-${tapIndex}` }),
|
|
22232
|
+
layout && /* @__PURE__ */ jsxRuntime.jsx(
|
|
22233
|
+
Bubble,
|
|
22234
|
+
{
|
|
22235
|
+
layout,
|
|
22236
|
+
text,
|
|
22237
|
+
stepIndex,
|
|
22238
|
+
stepCount: steps.length,
|
|
22239
|
+
nextLabel: options.nextLabel ?? DEFAULT_NEXT_LABEL,
|
|
22240
|
+
onSpeak: options.onSpeak && (options.canSpeak?.(step.id) ?? true) ? () => optionsRef.current.onSpeak?.(step.id, textRef.current) : void 0,
|
|
22241
|
+
onNext: next,
|
|
22242
|
+
nextRef
|
|
22243
|
+
}
|
|
22244
|
+
)
|
|
22245
|
+
] }),
|
|
22246
|
+
document.body
|
|
22247
|
+
);
|
|
22248
|
+
};
|
|
22249
|
+
const Hand = ({ target, tapKey }) => {
|
|
22250
|
+
const c = centerOf(target);
|
|
22251
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
22252
|
+
"div",
|
|
22253
|
+
{
|
|
22254
|
+
className: "met-tutorial-hand",
|
|
22255
|
+
style: {
|
|
22256
|
+
width: HAND_W,
|
|
22257
|
+
height: HAND_H,
|
|
22258
|
+
transform: `translate(${c.x - TUTORIAL_HAND_TIP.x * HAND_W}px, ${c.y - TUTORIAL_HAND_TIP.y * HAND_H}px)`
|
|
22259
|
+
},
|
|
22260
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "met-tutorial-hand-press", children: [
|
|
22261
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
22262
|
+
"span",
|
|
22263
|
+
{
|
|
22264
|
+
className: "met-tutorial-ripple",
|
|
22265
|
+
style: { left: TUTORIAL_HAND_TIP.x * HAND_W, top: TUTORIAL_HAND_TIP.y * HAND_H }
|
|
22266
|
+
}
|
|
22267
|
+
),
|
|
22268
|
+
/* @__PURE__ */ jsxRuntime.jsx(LionHand, {})
|
|
22269
|
+
] }, tapKey)
|
|
22270
|
+
}
|
|
22271
|
+
);
|
|
22272
|
+
};
|
|
22273
|
+
const Bubble = ({ layout, text, stepIndex, stepCount, nextLabel, onSpeak, onNext, nextRef }) => {
|
|
22274
|
+
const { anchor, root, vw, vh } = layout;
|
|
22275
|
+
const clampLeft = (x) => Math.min(Math.max(EDGE, x), Math.max(EDGE, vw - BUBBLE_W - EDGE));
|
|
22276
|
+
const style = anchor ? { left: clampLeft(anchor.x), bottom: Math.max(EDGE, vh - anchor.y + BUBBLE_GAP), width: BUBBLE_W } : { left: clampLeft((root?.x ?? 0) + EDGE * 2), top: (root?.y ?? 0) + EDGE * 2, width: BUBBLE_W };
|
|
22277
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `met-tutorial-bubble${anchor ? " met-tutorial-bubble--anchored" : ""}`, style, children: [
|
|
22278
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "met-tutorial-text", dir: "auto", children: text }),
|
|
22279
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "met-tutorial-actions", children: [
|
|
22280
|
+
onSpeak && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "met-tutorial-speak", "aria-label": "🔊", onClick: onSpeak, children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", width: "24", height: "24", "aria-hidden": "true", children: [
|
|
22281
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 9v6h4l5 4V5L8 9H4z", fill: "currentColor" }),
|
|
22282
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 8.5a5 5 0 0 1 0 7M18.5 6a8.5 8.5 0 0 1 0 12", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })
|
|
22283
|
+
] }) }),
|
|
22284
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { ref: nextRef, type: "button", className: "met-tutorial-next", onClick: onNext, children: nextLabel })
|
|
22285
|
+
] }),
|
|
22286
|
+
stepCount > 1 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "met-tutorial-dots", "aria-hidden": "true", children: Array.from({ length: stepCount }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: i === stepIndex ? "is-current" : i < stepIndex ? "is-done" : "" }, i)) })
|
|
22287
|
+
] });
|
|
22288
|
+
};
|
|
22289
|
+
const DEMO_KEYS = ["2", "5", "8"];
|
|
22290
|
+
const DEFAULT_SLOTS = 3;
|
|
22291
|
+
const MAX_SLOTS = 6;
|
|
22292
|
+
const STAND_IN_CHECK_SIZE = 52;
|
|
22293
|
+
const STAND_IN_CHECK_GAP = 20;
|
|
22294
|
+
function keypad(ctx) {
|
|
22295
|
+
return ctx.root.querySelector('[data-tutorial="keypad"]');
|
|
22296
|
+
}
|
|
22297
|
+
function key(ctx, k) {
|
|
22298
|
+
return ctx.root.querySelector(`[data-tutorial="keypad"] [data-key="${k}"]`);
|
|
22299
|
+
}
|
|
22300
|
+
function answerBox(ctx) {
|
|
22301
|
+
const inputs = getTextInputs(ctx.root);
|
|
22302
|
+
return inputs.find(canTakeInput) ?? inputs[0] ?? null;
|
|
22303
|
+
}
|
|
22304
|
+
function answerSlots(ctx) {
|
|
22305
|
+
const id = answerBox(ctx)?.id.replace(/-input$/, "");
|
|
22306
|
+
const max = id ? getMaxInputLength(ctx.json, id) : void 0;
|
|
22307
|
+
return Math.min(MAX_SLOTS, Math.max(1, max ?? DEFAULT_SLOTS));
|
|
22308
|
+
}
|
|
22309
|
+
function checkTarget(ctx) {
|
|
22310
|
+
if (ctx.submitTarget) return ctx.submitTarget;
|
|
22311
|
+
const box = answerBox(ctx)?.getBoundingClientRect();
|
|
22312
|
+
if (!box) return null;
|
|
22313
|
+
return new DOMRect(
|
|
22314
|
+
box.right + STAND_IN_CHECK_GAP,
|
|
22315
|
+
box.top + box.height / 2 - STAND_IN_CHECK_SIZE / 2,
|
|
22316
|
+
STAND_IN_CHECK_SIZE,
|
|
22317
|
+
STAND_IN_CHECK_SIZE
|
|
22318
|
+
);
|
|
22319
|
+
}
|
|
22320
|
+
const demoKeys = (ctx) => DEMO_KEYS.map((k) => key(ctx, k));
|
|
22321
|
+
const StandInFill = ({ box, slots, filled }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
22322
|
+
"div",
|
|
22323
|
+
{
|
|
22324
|
+
className: "met-tutorial-fill",
|
|
22325
|
+
style: { left: box.x, top: box.y, width: box.width, height: box.height },
|
|
22326
|
+
children: Array.from({ length: slots }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: i < filled ? "met-tutorial-fill-dot" : "met-tutorial-fill-slot", children: i < filled ? "●" : "_" }, i))
|
|
22327
|
+
}
|
|
22328
|
+
);
|
|
22329
|
+
const center = (r) => ({ x: r.x + r.width / 2, y: r.y + r.height / 2 });
|
|
22330
|
+
const HopScene = ({ ctx, spotlight, taps, tapCount }) => {
|
|
22331
|
+
const box = spotlight[1];
|
|
22332
|
+
if (!box) return null;
|
|
22333
|
+
const slots = answerSlots(ctx);
|
|
22334
|
+
const filled = tapCount === 0 ? 0 : (tapCount - 1) % slots + 1;
|
|
22335
|
+
const from = tapCount > 0 ? taps[(tapCount - 1) % taps.length] : null;
|
|
22336
|
+
let arc = null;
|
|
22337
|
+
if (from) {
|
|
22338
|
+
const a = center(from);
|
|
22339
|
+
const b = center(box);
|
|
22340
|
+
const lift = Math.max(40, Math.hypot(b.x - a.x, b.y - a.y) * 0.35);
|
|
22341
|
+
arc = `M ${a.x} ${a.y} Q ${(a.x + b.x) / 2} ${Math.min(a.y, b.y) - lift} ${b.x} ${b.y}`;
|
|
22342
|
+
}
|
|
22343
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
22344
|
+
arc && /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "met-tutorial-arc", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: arc }) }),
|
|
22345
|
+
arc && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "met-tutorial-hop", style: { offsetPath: `path('${arc}')` } }, tapCount),
|
|
22346
|
+
/* @__PURE__ */ jsxRuntime.jsx(StandInFill, { box, slots, filled })
|
|
22347
|
+
] });
|
|
22348
|
+
};
|
|
22349
|
+
const CheckScene = ({ ctx, spotlight }) => {
|
|
22350
|
+
const box = spotlight[0];
|
|
22351
|
+
const check = spotlight[1];
|
|
22352
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
22353
|
+
box && /* @__PURE__ */ jsxRuntime.jsx(StandInFill, { box, slots: answerSlots(ctx), filled: answerSlots(ctx) }),
|
|
22354
|
+
check && !ctx.submitTarget && /* @__PURE__ */ jsxRuntime.jsx(
|
|
22355
|
+
"div",
|
|
22356
|
+
{
|
|
22357
|
+
className: "met-tutorial-stand-in-check",
|
|
22358
|
+
style: { left: check.x, top: check.y, width: check.width, height: check.height },
|
|
22359
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: "✓" })
|
|
22360
|
+
}
|
|
22361
|
+
)
|
|
22362
|
+
] });
|
|
22363
|
+
};
|
|
22364
|
+
const keypadTutorial = {
|
|
22365
|
+
id: "keypad",
|
|
22366
|
+
appliesTo: (json) => json.widgets[NUMERIC_KEYPAD_WIDGET_ID]?.type === JSON_TYPES.NUMERIC_KEYPAD,
|
|
22367
|
+
steps: [
|
|
22368
|
+
{
|
|
22369
|
+
id: "keypad.tap-number",
|
|
22370
|
+
text: "اضغط على رقم",
|
|
22371
|
+
spotlight: (ctx) => [keypad(ctx), answerBox(ctx)],
|
|
22372
|
+
taps: demoKeys
|
|
22373
|
+
},
|
|
22374
|
+
{
|
|
22375
|
+
id: "keypad.hop",
|
|
22376
|
+
text: "يظهر الرقم في الخانة",
|
|
22377
|
+
spotlight: (ctx) => [keypad(ctx), answerBox(ctx)],
|
|
22378
|
+
taps: demoKeys,
|
|
22379
|
+
Scene: HopScene
|
|
22380
|
+
},
|
|
22381
|
+
{
|
|
22382
|
+
id: "keypad.tap-check",
|
|
22383
|
+
text: "اضغط على زر التحقق",
|
|
22384
|
+
spotlight: (ctx) => [answerBox(ctx), checkTarget(ctx)],
|
|
22385
|
+
taps: (ctx) => [checkTarget(ctx)],
|
|
22386
|
+
Scene: CheckScene
|
|
22387
|
+
}
|
|
22388
|
+
]
|
|
22389
|
+
};
|
|
22390
|
+
const WIDGET_TUTORIALS = [keypadTutorial];
|
|
22391
|
+
function findWidgetTutorial(json) {
|
|
22392
|
+
return WIDGET_TUTORIALS.find((t) => t.appliesTo(json)) ?? null;
|
|
22393
|
+
}
|
|
21835
22394
|
const TOOL_ASPECT_RATIO = {
|
|
21836
22395
|
ruler: RULER_ASPECT_RATIO,
|
|
21837
22396
|
protractor: PROTRACTOR_ASPECT_RATIO,
|
|
@@ -21870,6 +22429,11 @@ function buildInitialValues(json) {
|
|
|
21870
22429
|
function toBaseId(widgetId) {
|
|
21871
22430
|
return parseFractionKey(widgetId)?.widgetId ?? widgetId;
|
|
21872
22431
|
}
|
|
22432
|
+
function hasJustFilledBox(json, key2, previous, value) {
|
|
22433
|
+
const maxLength = getMaxInputLength(json, key2);
|
|
22434
|
+
if (maxLength === void 0) return false;
|
|
22435
|
+
return String(previous ?? "").length < maxLength && String(value).length >= maxLength;
|
|
22436
|
+
}
|
|
21873
22437
|
function buildInitialValuesSignature(json) {
|
|
21874
22438
|
return JSON.stringify({
|
|
21875
22439
|
widgets: json.widgets,
|
|
@@ -21919,6 +22483,27 @@ function buildRevealedValues(json) {
|
|
|
21919
22483
|
}
|
|
21920
22484
|
return revealed;
|
|
21921
22485
|
}
|
|
22486
|
+
function buildRevealedDropSources(json, revealedValues) {
|
|
22487
|
+
const pool = [];
|
|
22488
|
+
const bank = json.widgets[DRAG_OPTIONS_WIDGET_ID];
|
|
22489
|
+
if (bank?.type === JSON_TYPES.DRAG_OPTIONS) {
|
|
22490
|
+
bank.options.forEach((value, idx) => pool.push({ optionId: `drag-option-${idx}`, value: String(value) }));
|
|
22491
|
+
}
|
|
22492
|
+
for (const [widgetId, widget] of Object.entries(json.widgets)) {
|
|
22493
|
+
if (widget.type === JSON_TYPES.INLINE_DRAG_OPTION) pool.push({ optionId: widgetId, value: widget.value });
|
|
22494
|
+
}
|
|
22495
|
+
const sources = /* @__PURE__ */ new Map();
|
|
22496
|
+
if (pool.length === 0) return sources;
|
|
22497
|
+
const used = /* @__PURE__ */ new Set();
|
|
22498
|
+
for (const [inputId, value] of Object.entries(revealedValues)) {
|
|
22499
|
+
if (typeof value !== "string" || value === "") continue;
|
|
22500
|
+
const match = pool.find((o) => !used.has(o.optionId) && normalize(o.value) === normalize(value));
|
|
22501
|
+
if (!match) continue;
|
|
22502
|
+
used.add(match.optionId);
|
|
22503
|
+
sources.set(inputId, match.optionId);
|
|
22504
|
+
}
|
|
22505
|
+
return sources;
|
|
22506
|
+
}
|
|
21922
22507
|
const ExerciseRenderer = React.forwardRef(({
|
|
21923
22508
|
json,
|
|
21924
22509
|
onSubmit,
|
|
@@ -21941,6 +22526,8 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
21941
22526
|
const rootRef = React.useRef(null);
|
|
21942
22527
|
const [resetKey, setResetKey] = React.useState(0);
|
|
21943
22528
|
const [revealed, setRevealed] = React.useState(false);
|
|
22529
|
+
const tutorial = React.useMemo(() => findWidgetTutorial(json), [json]);
|
|
22530
|
+
const [tutorialRun, setTutorialRun] = React.useState(null);
|
|
21944
22531
|
const themeStyle = React.useMemo(() => {
|
|
21945
22532
|
const style = {};
|
|
21946
22533
|
if (theme?.correct) style["--exercise-correct"] = theme.correct;
|
|
@@ -21961,6 +22548,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
21961
22548
|
setResetKey((k) => k + 1);
|
|
21962
22549
|
setLoadedValuesSignature(initialValuesSignature);
|
|
21963
22550
|
touchedRef.current = /* @__PURE__ */ new Set();
|
|
22551
|
+
setTutorialRun(null);
|
|
21964
22552
|
}, [json, initialValuesSignature, loadedValuesSignature]);
|
|
21965
22553
|
const locked = validationDetails !== null || externallyLocked;
|
|
21966
22554
|
const lastResultRef = React.useRef(null);
|
|
@@ -22044,7 +22632,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22044
22632
|
const next = { ...valuesRef.current, ...revealedValues };
|
|
22045
22633
|
valuesRef.current = next;
|
|
22046
22634
|
setValues(next);
|
|
22047
|
-
setDroppedSources(
|
|
22635
|
+
setDroppedSources(buildRevealedDropSources(json, revealedValues));
|
|
22048
22636
|
setValidationDetails(details);
|
|
22049
22637
|
setRevealed(true);
|
|
22050
22638
|
setResetKey((k) => k + 1);
|
|
@@ -22052,20 +22640,29 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22052
22640
|
focusFirstInput: () => {
|
|
22053
22641
|
const container = rootRef.current ?? document.querySelector(".math-exercise-renderer");
|
|
22054
22642
|
if (!container) return;
|
|
22055
|
-
const candidates =
|
|
22056
|
-
container.querySelectorAll('input[type="text"], input[type="number"], textarea, select')
|
|
22057
|
-
);
|
|
22058
|
-
const canTakeInput = (el) => !el.readOnly && !el.disabled;
|
|
22643
|
+
const candidates = getTextInputs(container);
|
|
22059
22644
|
const target = candidates.find(canTakeInput) ?? (locked ? void 0 : candidates[0]);
|
|
22060
22645
|
target?.focus();
|
|
22061
|
-
}
|
|
22062
|
-
|
|
22646
|
+
},
|
|
22647
|
+
hasTutorial: () => tutorial !== null,
|
|
22648
|
+
startTutorial: (options = {}) => {
|
|
22649
|
+
if (!tutorial || !rootRef.current) return false;
|
|
22650
|
+
setTutorialRun((prev) => ({ options, run: (prev?.run ?? 0) + 1 }));
|
|
22651
|
+
return true;
|
|
22652
|
+
},
|
|
22653
|
+
stopTutorial: () => setTutorialRun(null)
|
|
22654
|
+
}), [json, values, onSubmit, playSound, locked, initialValuesSignature, tutorial]);
|
|
22655
|
+
const focusNextInput = (currentKey) => {
|
|
22656
|
+
if (!rootRef.current) return;
|
|
22657
|
+
findNextInput(rootRef.current, currentKey)?.focus();
|
|
22658
|
+
};
|
|
22063
22659
|
const handleChange = (widgetId, value, fromDrop = false, sourceOptionId, silent = false) => {
|
|
22064
22660
|
if (locked) return;
|
|
22065
22661
|
if (!silent) {
|
|
22066
22662
|
setRevealed(false);
|
|
22067
22663
|
touchedRef.current.add(toBaseId(widgetId));
|
|
22068
22664
|
}
|
|
22665
|
+
const previous = valuesRef.current[widgetId];
|
|
22069
22666
|
const next = { ...valuesRef.current, [widgetId]: value };
|
|
22070
22667
|
valuesRef.current = next;
|
|
22071
22668
|
setValues(next);
|
|
@@ -22079,6 +22676,9 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22079
22676
|
return next2;
|
|
22080
22677
|
});
|
|
22081
22678
|
onChange?.(next);
|
|
22679
|
+
if (!fromDrop && !silent && hasJustFilledBox(json, widgetId, previous, value)) {
|
|
22680
|
+
focusNextInput(widgetId);
|
|
22681
|
+
}
|
|
22082
22682
|
};
|
|
22083
22683
|
const handleSubmit = () => {
|
|
22084
22684
|
runSubmit();
|
|
@@ -22193,7 +22793,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22193
22793
|
const rulerWidget = json.widgets[RULER_WIDGET_ID];
|
|
22194
22794
|
const protractorWidget = json.widgets[PROTRACTOR_WIDGET_ID];
|
|
22195
22795
|
const setSquareWidget = json.widgets[SET_SQUARE_WIDGET_ID];
|
|
22196
|
-
const numericKeypadWidget = json.widgets[
|
|
22796
|
+
const numericKeypadWidget = json.widgets[NUMERIC_KEYPAD_WIDGET_ID];
|
|
22197
22797
|
const { tools, toolsMinHeight } = React.useMemo(() => {
|
|
22198
22798
|
const list = [];
|
|
22199
22799
|
const PAD = 12;
|
|
@@ -22212,7 +22812,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22212
22812
|
if (setSquareWidget) add2("set-square-1", "set-square", Math.round(getSize(setSquareWidget) * 160));
|
|
22213
22813
|
return { tools: list, toolsMinHeight: maxH };
|
|
22214
22814
|
}, [rulerWidget, protractorWidget, setSquareWidget]);
|
|
22215
|
-
return /* @__PURE__ */ jsxRuntime.jsx(SoundPlayerProvider, { play: playSound, children: /* @__PURE__ */ jsxRuntime.jsx(ExerciseLockProvider, { locked, children: /* @__PURE__ */ jsxRuntime.jsx(ExerciseDndProvider, { droppedSources, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: rootRef, className: `math-exercise-renderer ${revealed ? "revealed" : ""} ${locked ? "locked" : ""} ${className}`, style: themeStyle, children: [
|
|
22815
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SoundPlayerProvider, { play: playSound, children: /* @__PURE__ */ jsxRuntime.jsx(ExerciseLockProvider, { locked, children: /* @__PURE__ */ jsxRuntime.jsx(ExerciseDndProvider, { droppedSources, exerciseKey: loadedValuesSignature, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: rootRef, className: `math-exercise-renderer ${revealed ? "revealed" : ""} ${locked ? "locked" : ""} ${className}`, style: themeStyle, children: [
|
|
22216
22816
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
22217
22817
|
"div",
|
|
22218
22818
|
{
|
|
@@ -22308,7 +22908,21 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22308
22908
|
}
|
|
22309
22909
|
}
|
|
22310
22910
|
) }),
|
|
22311
|
-
showSubmitButton && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "exercise-submit-btn", disabled: locked, onClick: handleSubmit, children: "Check Answer" })
|
|
22911
|
+
showSubmitButton && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "exercise-submit-btn", disabled: locked, onClick: handleSubmit, children: "Check Answer" }),
|
|
22912
|
+
tutorial && tutorialRun && rootRef.current && /* @__PURE__ */ jsxRuntime.jsx(
|
|
22913
|
+
TutorialOverlay,
|
|
22914
|
+
{
|
|
22915
|
+
tutorial,
|
|
22916
|
+
root: rootRef.current,
|
|
22917
|
+
json,
|
|
22918
|
+
options: tutorialRun.options,
|
|
22919
|
+
onClose: (finished) => {
|
|
22920
|
+
setTutorialRun(null);
|
|
22921
|
+
if (finished) tutorialRun.options.onDone?.();
|
|
22922
|
+
}
|
|
22923
|
+
},
|
|
22924
|
+
tutorialRun.run
|
|
22925
|
+
)
|
|
22312
22926
|
] }) }) }) });
|
|
22313
22927
|
});
|
|
22314
22928
|
ExerciseRenderer.displayName = "ExerciseRenderer";
|
|
@@ -22407,11 +23021,13 @@ exports.GridSymmetryJSONSchema = GridSymmetryJSONSchema;
|
|
|
22407
23021
|
exports.GroupItemSchema = GroupItemSchema;
|
|
22408
23022
|
exports.HighlightedTextInlineSchema = HighlightedTextInlineSchema;
|
|
22409
23023
|
exports.HighlightedTextSchema = HighlightedTextSchema;
|
|
23024
|
+
exports.INLINE_INPUT_MAX_WIDTH = INLINE_INPUT_MAX_WIDTH;
|
|
22410
23025
|
exports.ImageInlineSchema = ImageInlineSchema;
|
|
22411
23026
|
exports.ImageItemSchema = ImageItemSchema;
|
|
22412
23027
|
exports.InlineDragOptionSchema = InlineDragOptionSchema;
|
|
22413
23028
|
exports.InlineInputJSONSchema = InlineInputJSONSchema;
|
|
22414
23029
|
exports.InlineInputWidgetSchema = InlineInputWidgetSchema;
|
|
23030
|
+
exports.InlineInputWidthSchema = InlineInputWidthSchema;
|
|
22415
23031
|
exports.InlineNodeSchema = InlineNodeSchema;
|
|
22416
23032
|
exports.JSON_TYPES = JSON_TYPES;
|
|
22417
23033
|
exports.LayoutItemSchema = LayoutItemSchema;
|
|
@@ -22485,6 +23101,7 @@ exports.VopRowItemSchema = VopRowItemSchema;
|
|
|
22485
23101
|
exports.VopRowSchema = VopRowSchema;
|
|
22486
23102
|
exports.VopSeparatorItemSchema = VopSeparatorItemSchema;
|
|
22487
23103
|
exports.VopSeparatorSchema = VopSeparatorSchema;
|
|
23104
|
+
exports.WIDGET_TUTORIALS = WIDGET_TUTORIALS;
|
|
22488
23105
|
exports.WidgetJSONSchema = WidgetJSONSchema;
|
|
22489
23106
|
exports.WidgetRefSchema = WidgetRefSchema;
|
|
22490
23107
|
exports.WidgetRenderer = WidgetRenderer;
|
|
@@ -22544,6 +23161,7 @@ exports.createVopSeparator = createVopSeparator;
|
|
|
22544
23161
|
exports.evaluate = evaluate;
|
|
22545
23162
|
exports.extractVariables = extractVariables;
|
|
22546
23163
|
exports.findValidCombination = findValidCombination;
|
|
23164
|
+
exports.findWidgetTutorial = findWidgetTutorial;
|
|
22547
23165
|
exports.getAnswerableInputs = getAnswerableInputs;
|
|
22548
23166
|
exports.getFractionKey = getFractionKey;
|
|
22549
23167
|
exports.getMissingInputs = getMissingInputs;
|
|
@@ -22649,4 +23267,4 @@ exports.validateUniqueIds = validateUniqueIds;
|
|
|
22649
23267
|
exports.validateVisualFractionCapacity = validateVisualFractionCapacity;
|
|
22650
23268
|
exports.validateWidgetJSON = validateWidgetJSON;
|
|
22651
23269
|
exports.validateWidgetJSONSafe = validateWidgetJSONSafe;
|
|
22652
|
-
//# sourceMappingURL=index-
|
|
23270
|
+
//# sourceMappingURL=index-C3qDr71I.js.map
|