@momoyu-ink/kit 0.9.1 → 0.10.1

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/lib.cjs CHANGED
@@ -31,9 +31,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/lib.ts
32
32
  var lib_exports = {};
33
33
  __export(lib_exports, {
34
+ Button: () => Button,
35
+ Checkbox: () => Checkbox,
34
36
  DEBUG_EMPTY_PAGE: () => DEBUG_EMPTY_PAGE,
35
37
  OverlayParamsContext: () => OverlayParamsContext,
36
38
  PageParamsContext: () => PageParamsContext,
39
+ ScrollView: () => ScrollView,
40
+ Select: () => Select,
41
+ Slider: () => Slider,
37
42
  StageContextProvider: () => StageContextProvider,
38
43
  a: () => animated,
39
44
  addEventListener: () => addEventListener,
@@ -59,6 +64,7 @@ __export(lib_exports, {
59
64
  initGlobalVariableDefaults: () => initGlobalVariableDefaults,
60
65
  initUiData: () => initUiData,
61
66
  loadVariablesDocument: () => loadVariablesDocument,
67
+ mergeEvent: () => mergeEvent,
62
68
  moyu: () => moyu_exports,
63
69
  nextLine: () => nextLine,
64
70
  registerAppStateAdapter: () => registerAppStateAdapter,
@@ -85,6 +91,7 @@ __export(lib_exports, {
85
91
  useNavigationParams: () => useNavigationParams,
86
92
  useNavigationState: () => useNavigationState,
87
93
  useScenario: () => useScenario,
94
+ useScrollView: () => useScrollView,
88
95
  useSkipBlocker: () => useSkipBlocker,
89
96
  useSkipCallback: () => useSkipCallback,
90
97
  useSoundEffect: () => useSoundEffect,
@@ -795,6 +802,8 @@ var import_shared = require("@react-spring/shared");
795
802
  // src/spring/primitives.ts
796
803
  var primitives = [
797
804
  "container",
805
+ "vbox",
806
+ "hbox",
798
807
  "sprite",
799
808
  "text",
800
809
  "clip",
@@ -952,6 +961,19 @@ function getStageSize() {
952
961
  });
953
962
  return stageSize;
954
963
  }
964
+ function mergeEvent(handlers, defaultHandler) {
965
+ return (event) => {
966
+ if (handlers) {
967
+ const handlerList = Array.isArray(handlers) ? handlers : [handlers];
968
+ for (const handler of handlerList) {
969
+ handler(event);
970
+ }
971
+ }
972
+ if (!event.defaultPrevented) {
973
+ defaultHandler?.(event);
974
+ }
975
+ };
976
+ }
955
977
 
956
978
  // src/gameState.ts
957
979
  var import_react2 = require("react");
@@ -1029,7 +1051,11 @@ function createGameStateContext(defaults) {
1029
1051
  // src/components/navigation.tsx
1030
1052
  var import_react3 = require("react");
1031
1053
  var import_valtio2 = require("valtio");
1054
+
1055
+ // src/jsx-runtime.ts
1032
1056
  var import_jsx_runtime = require("react/jsx-runtime");
1057
+
1058
+ // src/components/navigation.tsx
1033
1059
  var PageParamsContext = (0, import_react3.createContext)(void 0);
1034
1060
  var OverlayParamsContext = (0, import_react3.createContext)(void 0);
1035
1061
  var DEBUG_EMPTY_PAGE = "debug-empty";
@@ -1185,6 +1211,440 @@ function useNavigationParams() {
1185
1211
  return overlayParams ?? pageParams ?? {};
1186
1212
  }
1187
1213
 
1214
+ // src/components/scroll-view.tsx
1215
+ function ScrollView({ width, height, controller, children, clipProps, contentProps }) {
1216
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1217
+ "clip",
1218
+ {
1219
+ ...clipProps,
1220
+ width,
1221
+ height,
1222
+ onWheel: controller.handleWheel,
1223
+ onTouchStart: controller.handleTouchStart,
1224
+ onTouchMove: controller.handleTouchMove,
1225
+ onTouchEnd: controller.handleTouchEnd,
1226
+ onTouchCancel: controller.handleTouchEnd,
1227
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1228
+ animated.vbox,
1229
+ {
1230
+ ...contentProps,
1231
+ y: controller.scrollOffset.to((value) => -value),
1232
+ onLayout: controller.handleContentLayout,
1233
+ children
1234
+ }
1235
+ )
1236
+ }
1237
+ );
1238
+ }
1239
+
1240
+ // src/components/button.tsx
1241
+ var import_react4 = require("react");
1242
+
1243
+ // src/components/control.ts
1244
+ function resolveControlStateValue(value, state) {
1245
+ if (!Array.isArray(value)) {
1246
+ return value;
1247
+ }
1248
+ const index = state === "idle" ? 0 : state === "hover" ? 1 : state === "press" ? 2 : 3;
1249
+ return value[index] ?? value[0];
1250
+ }
1251
+
1252
+ // src/components/button.tsx
1253
+ function Button({
1254
+ sprite,
1255
+ disabled = false,
1256
+ lockOn,
1257
+ onPress,
1258
+ text,
1259
+ textStyle,
1260
+ textOffsetX,
1261
+ textOffsetY,
1262
+ textAlign = "center",
1263
+ children,
1264
+ anchor,
1265
+ pivot = anchor,
1266
+ interactive,
1267
+ onMouseEnter,
1268
+ onMouseLeave,
1269
+ onMouseDown,
1270
+ onMouseUp,
1271
+ onTouchStart,
1272
+ onTouchEnd,
1273
+ onTouchCancel,
1274
+ ...containerProps
1275
+ }) {
1276
+ const [hovered, setHovered] = (0, import_react4.useState)(false);
1277
+ const [pressed, setPressed] = (0, import_react4.useState)(false);
1278
+ (0, import_react4.useEffect)(() => {
1279
+ if (!pressed) {
1280
+ return;
1281
+ }
1282
+ const release = () => setPressed(false);
1283
+ const removeMouseUp = addEventListener("mouseup", release);
1284
+ const removeTouchEnd = addEventListener("touchend", release);
1285
+ const removeTouchCancel = addEventListener("touchcancel", release);
1286
+ return () => {
1287
+ removeMouseUp();
1288
+ removeTouchEnd();
1289
+ removeTouchCancel();
1290
+ };
1291
+ }, [pressed]);
1292
+ (0, import_react4.useEffect)(() => {
1293
+ if (disabled) {
1294
+ setHovered(false);
1295
+ setPressed(false);
1296
+ }
1297
+ }, [disabled]);
1298
+ const state = disabled ? "disabled" : lockOn ?? (pressed ? "press" : hovered ? "hover" : "idle");
1299
+ const { src, ...spriteProps } = sprite;
1300
+ const resolvedTextStyle = textStyle ? resolveControlStateValue(textStyle, state) : void 0;
1301
+ const textAnchor = textAlign === "left" ? [0, 0.5] : textAlign === "right" ? [1, 0.5] : [0.5, 0.5];
1302
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1303
+ "container",
1304
+ {
1305
+ ...containerProps,
1306
+ anchor,
1307
+ pivot,
1308
+ interactive: disabled ? false : interactive,
1309
+ onMouseEnter: mergeEvent(onMouseEnter, (event) => {
1310
+ if (event.targetId === event.currentTargetId) {
1311
+ event.stopPropagation();
1312
+ return;
1313
+ }
1314
+ setHovered(true);
1315
+ }),
1316
+ onMouseLeave: mergeEvent(onMouseLeave, () => {
1317
+ setHovered(false);
1318
+ setPressed(false);
1319
+ }),
1320
+ onMouseDown: mergeEvent(onMouseDown, () => setPressed(true)),
1321
+ onMouseUp: mergeEvent(onMouseUp, (event) => {
1322
+ if (pressed) {
1323
+ onPress?.(event);
1324
+ }
1325
+ setPressed(false);
1326
+ setHovered(true);
1327
+ }),
1328
+ onTouchStart: mergeEvent(onTouchStart, () => setPressed(true)),
1329
+ onTouchEnd: mergeEvent(onTouchEnd, (event) => {
1330
+ if (pressed) {
1331
+ onPress?.(event);
1332
+ }
1333
+ setPressed(false);
1334
+ setHovered(true);
1335
+ }),
1336
+ onTouchCancel: mergeEvent(onTouchCancel, () => {
1337
+ setHovered(false);
1338
+ setPressed(false);
1339
+ }),
1340
+ onClick: (event) => event.stopPropagation(),
1341
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("sprite", { ...spriteProps, src: resolveControlStateValue(src, state), cursor: "pointer", children: [
1342
+ text !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1343
+ "text",
1344
+ {
1345
+ ...resolvedTextStyle,
1346
+ text,
1347
+ x: textOffsetX,
1348
+ y: textOffsetY,
1349
+ interactive: false,
1350
+ anchor: textAnchor,
1351
+ pivot: textAnchor
1352
+ }
1353
+ ) : void 0,
1354
+ children
1355
+ ] })
1356
+ }
1357
+ );
1358
+ }
1359
+
1360
+ // src/components/checkbox.tsx
1361
+ var import_react5 = require("react");
1362
+ function Checkbox({
1363
+ checked,
1364
+ defaultChecked = false,
1365
+ onCheckedChange,
1366
+ uncheckedSprite,
1367
+ checkedSprite,
1368
+ onPress,
1369
+ ...buttonProps
1370
+ }) {
1371
+ const [internalChecked, setInternalChecked] = (0, import_react5.useState)(defaultChecked);
1372
+ const currentChecked = checked ?? internalChecked;
1373
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1374
+ Button,
1375
+ {
1376
+ ...buttonProps,
1377
+ sprite: currentChecked ? checkedSprite : uncheckedSprite,
1378
+ onPress: (event) => {
1379
+ onPress?.(event);
1380
+ if (event.defaultPrevented) {
1381
+ return;
1382
+ }
1383
+ const nextChecked = !currentChecked;
1384
+ if (checked === void 0) {
1385
+ setInternalChecked(nextChecked);
1386
+ }
1387
+ onCheckedChange?.(nextChecked);
1388
+ }
1389
+ }
1390
+ );
1391
+ }
1392
+
1393
+ // src/components/select.tsx
1394
+ var import_react6 = require("react");
1395
+ function Select({
1396
+ value,
1397
+ defaultValue,
1398
+ onValueChange,
1399
+ onPress,
1400
+ disabled = false,
1401
+ options,
1402
+ trigger,
1403
+ list,
1404
+ option,
1405
+ textStyle,
1406
+ textOffsetX,
1407
+ textOffsetY,
1408
+ textAlign,
1409
+ anchor,
1410
+ pivot = anchor,
1411
+ ...containerProps
1412
+ }) {
1413
+ const [internalValue, setInternalValue] = (0, import_react6.useState)(defaultValue);
1414
+ const [open, setOpen] = (0, import_react6.useState)(false);
1415
+ const currentValue = value ?? internalValue;
1416
+ const currentOption = options.find((item) => item.value === currentValue);
1417
+ const { paddingX = 0, paddingY = 0, gap = 0, offsetX = 0, offsetY = 0, ...listSprite } = list;
1418
+ const listHeight = paddingY * 2 + options.length * option.targetHeight + Math.max(0, options.length - 1) * gap;
1419
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1420
+ "container",
1421
+ {
1422
+ ...containerProps,
1423
+ anchor,
1424
+ pivot,
1425
+ interactive: disabled ? false : containerProps.interactive,
1426
+ children: [
1427
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1428
+ Button,
1429
+ {
1430
+ sprite: trigger,
1431
+ disabled,
1432
+ lockOn: open ? "press" : void 0,
1433
+ text: currentOption?.text ?? "",
1434
+ textStyle,
1435
+ textOffsetX,
1436
+ textOffsetY,
1437
+ textAlign,
1438
+ onPress: (event) => {
1439
+ onPress?.(event);
1440
+ if (!event.defaultPrevented) {
1441
+ setOpen((active) => !active);
1442
+ }
1443
+ }
1444
+ }
1445
+ ),
1446
+ open && !disabled ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("sprite", { ...listSprite, targetHeight: listHeight, x: offsetX, y: (trigger.targetHeight ?? 0) + offsetY, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("vbox", { x: paddingX, y: paddingY, gap, children: options.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1447
+ Button,
1448
+ {
1449
+ sprite: option,
1450
+ lockOn: item.value === currentValue ? "press" : void 0,
1451
+ text: item.text,
1452
+ textStyle,
1453
+ textOffsetX,
1454
+ textOffsetY,
1455
+ textAlign,
1456
+ onPress: (event) => {
1457
+ if (event.defaultPrevented) {
1458
+ return;
1459
+ }
1460
+ if (value === void 0) {
1461
+ setInternalValue(item.value);
1462
+ }
1463
+ onValueChange?.(item.value, item);
1464
+ setOpen(false);
1465
+ }
1466
+ },
1467
+ item.value
1468
+ )) }) }) : void 0
1469
+ ]
1470
+ }
1471
+ );
1472
+ }
1473
+
1474
+ // src/components/slider.tsx
1475
+ var import_react7 = require("react");
1476
+ function clampValue(value) {
1477
+ if (Number.isNaN(value)) {
1478
+ return 0;
1479
+ }
1480
+ return Math.max(0, Math.min(1, value));
1481
+ }
1482
+ function Slider({
1483
+ value,
1484
+ defaultValue = 0,
1485
+ onValueChange,
1486
+ onValueCommit,
1487
+ onPress,
1488
+ disabled = false,
1489
+ track,
1490
+ thumb,
1491
+ anchor,
1492
+ pivot = anchor,
1493
+ interactive,
1494
+ onMouseEnter,
1495
+ onMouseLeave,
1496
+ onMouseDown,
1497
+ onMouseUp,
1498
+ onMouseMove,
1499
+ onTouchStart,
1500
+ onTouchMove,
1501
+ onTouchEnd,
1502
+ onTouchCancel,
1503
+ ...containerProps
1504
+ }) {
1505
+ const [internalValue, setInternalValue] = (0, import_react7.useState)(() => clampValue(defaultValue));
1506
+ const [dragging, setDragging] = (0, import_react7.useState)(false);
1507
+ const currentValue = clampValue(value ?? internalValue);
1508
+ const currentValueRef = (0, import_react7.useRef)(currentValue);
1509
+ currentValueRef.current = currentValue;
1510
+ const dragStateRef = (0, import_react7.useRef)(null);
1511
+ const distance = Math.max(0, track.targetWidth - thumb.targetWidth);
1512
+ const setSliderValue = (0, import_react7.useCallback)((nextValue) => {
1513
+ const clampedValue = clampValue(nextValue);
1514
+ currentValueRef.current = clampedValue;
1515
+ if (value === void 0) {
1516
+ setInternalValue(clampedValue);
1517
+ }
1518
+ onValueChange?.(clampedValue);
1519
+ }, [onValueChange, value]);
1520
+ const endDragging = (0, import_react7.useCallback)((commit) => {
1521
+ if (dragStateRef.current === null) {
1522
+ return;
1523
+ }
1524
+ dragStateRef.current = null;
1525
+ setDragging(false);
1526
+ if (commit) {
1527
+ onValueCommit?.(currentValueRef.current);
1528
+ }
1529
+ }, [onValueCommit]);
1530
+ (0, import_react7.useEffect)(() => {
1531
+ const handleMouseUp = () => endDragging(true);
1532
+ const handleTouchEnd = (event) => {
1533
+ if (dragStateRef.current?.touchIdentifier === event.identifier) {
1534
+ endDragging(true);
1535
+ }
1536
+ };
1537
+ const handleTouchCancel2 = (event) => {
1538
+ if (dragStateRef.current?.touchIdentifier === event.identifier) {
1539
+ endDragging(false);
1540
+ }
1541
+ };
1542
+ const removeMouseUp = addEventListener("mouseup", handleMouseUp);
1543
+ const removeTouchEnd = addEventListener("touchend", handleTouchEnd);
1544
+ const removeTouchCancel = addEventListener("touchcancel", handleTouchCancel2);
1545
+ return () => {
1546
+ removeMouseUp();
1547
+ removeTouchEnd();
1548
+ removeTouchCancel();
1549
+ };
1550
+ }, [endDragging]);
1551
+ (0, import_react7.useEffect)(() => {
1552
+ if (disabled) {
1553
+ endDragging(false);
1554
+ }
1555
+ }, [disabled, endDragging]);
1556
+ const startDragging = (event) => {
1557
+ event.stopPropagation();
1558
+ if (dragStateRef.current !== null) {
1559
+ return;
1560
+ }
1561
+ const thumbPosition = currentValueRef.current * distance;
1562
+ dragStateRef.current = {
1563
+ startPosition: event.offsetX,
1564
+ startValue: currentValueRef.current,
1565
+ touchIdentifier: "identifier" in event ? event.identifier : void 0,
1566
+ startedOnThumb: event.offsetX >= thumbPosition && event.offsetX <= thumbPosition + thumb.targetWidth,
1567
+ moved: false
1568
+ };
1569
+ setDragging(true);
1570
+ };
1571
+ const moveDragging = (event) => {
1572
+ const dragState = dragStateRef.current;
1573
+ if (dragState === null || "identifier" in event && dragState.touchIdentifier !== event.identifier || distance === 0) {
1574
+ return;
1575
+ }
1576
+ event.stopPropagation();
1577
+ const delta = event.offsetX - dragState.startPosition;
1578
+ if (delta !== 0) {
1579
+ dragState.moved = true;
1580
+ }
1581
+ setSliderValue(dragState.startValue + delta / distance);
1582
+ };
1583
+ const handlePress = (event) => {
1584
+ const dragState = dragStateRef.current;
1585
+ if (dragState === null || "identifier" in event && dragState.touchIdentifier !== event.identifier) {
1586
+ return;
1587
+ }
1588
+ onPress?.(event);
1589
+ if (event.defaultPrevented) {
1590
+ endDragging(false);
1591
+ return;
1592
+ }
1593
+ if (!dragState.moved && !dragState.startedOnThumb && distance > 0) {
1594
+ setSliderValue((event.offsetX - thumb.targetWidth / 2) / distance);
1595
+ }
1596
+ endDragging(true);
1597
+ };
1598
+ const handleTouchCancel = (event) => {
1599
+ if (dragStateRef.current?.touchIdentifier === event.identifier) {
1600
+ endDragging(false);
1601
+ }
1602
+ };
1603
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1604
+ Button,
1605
+ {
1606
+ ...containerProps,
1607
+ anchor,
1608
+ pivot,
1609
+ interactive,
1610
+ disabled,
1611
+ lockOn: dragging ? "press" : void 0,
1612
+ sprite: {
1613
+ ...track,
1614
+ pivot: [0, 0.5],
1615
+ y: (track.targetHeight ?? 0) / 2
1616
+ },
1617
+ onPress: handlePress,
1618
+ onMouseEnter,
1619
+ onMouseLeave,
1620
+ onMouseDown: mergeEvent(onMouseDown, startDragging),
1621
+ onMouseUp,
1622
+ onMouseMove: mergeEvent(onMouseMove, moveDragging),
1623
+ onTouchStart: mergeEvent(onTouchStart, startDragging),
1624
+ onTouchMove: mergeEvent(onTouchMove, moveDragging),
1625
+ onTouchEnd,
1626
+ onTouchCancel: mergeEvent(onTouchCancel, handleTouchCancel),
1627
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1628
+ Button,
1629
+ {
1630
+ sprite: {
1631
+ ...thumb,
1632
+ anchor: [0, 0.5],
1633
+ pivot: [0, 0.5],
1634
+ x: 0,
1635
+ interactive: false
1636
+ },
1637
+ lockOn: dragging ? "press" : void 0,
1638
+ x: currentValue * distance,
1639
+ anchor: [0, 0.5],
1640
+ pivot: [0, 0.5],
1641
+ interactive: false
1642
+ }
1643
+ )
1644
+ }
1645
+ );
1646
+ }
1647
+
1188
1648
  // src/debug/session.ts
1189
1649
  var import_valtio3 = require("valtio");
1190
1650
  var debugState = (0, import_valtio3.proxy)({
@@ -2132,14 +2592,144 @@ function useFadeOut(outTime, pause = false, onFinished) {
2132
2592
  );
2133
2593
  const skip = () => {
2134
2594
  api.stop();
2135
- api.set({ opacity: 1 });
2595
+ api.set({ opacity: 0 });
2136
2596
  onFinished?.();
2137
2597
  };
2138
2598
  return [style, api, skip];
2139
2599
  }
2140
2600
 
2601
+ // src/hooks/useScrollView.ts
2602
+ var import_core = require("@react-spring/core");
2603
+ var import_react8 = require("react");
2604
+ function clamp(value, min, max) {
2605
+ return Math.max(min, Math.min(max, value));
2606
+ }
2607
+ function normalizeWheelDelta(event, viewportHeight) {
2608
+ switch (event.deltaMode) {
2609
+ case "line":
2610
+ return event.deltaY * 36;
2611
+ case "page":
2612
+ return event.deltaY * viewportHeight * 0.85;
2613
+ case "pixel":
2614
+ default:
2615
+ return event.deltaY;
2616
+ }
2617
+ }
2618
+ function useScrollView({
2619
+ viewportHeight,
2620
+ initialPosition = "start",
2621
+ wheelStep = 72
2622
+ }) {
2623
+ const [contentHeight, setContentHeight] = (0, import_react8.useState)(0);
2624
+ const [hasContentLayout, setHasContentLayout] = (0, import_react8.useState)(false);
2625
+ const scrollTargetRef = (0, import_react8.useRef)(0);
2626
+ const didSetInitialPositionRef = (0, import_react8.useRef)(false);
2627
+ const touchDraggingRef = (0, import_react8.useRef)(false);
2628
+ const touchStartYRef = (0, import_react8.useRef)(0);
2629
+ const touchStartOffsetRef = (0, import_react8.useRef)(0);
2630
+ const scrollOffset = (0, import_core.useSpringValue)(0, {
2631
+ config: {
2632
+ tension: 320,
2633
+ friction: 32
2634
+ }
2635
+ });
2636
+ const maxScroll = (0, import_react8.useMemo)(() => Math.max(0, contentHeight - viewportHeight), [contentHeight, viewportHeight]);
2637
+ const scrollTo = (0, import_react8.useCallback)(
2638
+ (nextOffset, immediate = false) => {
2639
+ const offset = clamp(nextOffset, 0, maxScroll);
2640
+ if (offset === scrollTargetRef.current && !immediate) {
2641
+ return;
2642
+ }
2643
+ scrollTargetRef.current = offset;
2644
+ scrollOffset.start(offset, { immediate });
2645
+ },
2646
+ [maxScroll, scrollOffset]
2647
+ );
2648
+ const scrollToRatio = (0, import_react8.useCallback)(
2649
+ (ratio, immediate = false) => {
2650
+ scrollTo(clamp(ratio, 0, 1) * maxScroll, immediate);
2651
+ },
2652
+ [maxScroll, scrollTo]
2653
+ );
2654
+ const handleContentLayout = (0, import_react8.useCallback)(
2655
+ (event) => {
2656
+ if (!didSetInitialPositionRef.current) {
2657
+ const initialOffset = initialPosition === "end" ? Math.max(0, event.height - viewportHeight) : 0;
2658
+ didSetInitialPositionRef.current = true;
2659
+ scrollTargetRef.current = initialOffset;
2660
+ scrollOffset.start(initialOffset, { immediate: true });
2661
+ }
2662
+ setContentHeight(event.height);
2663
+ setHasContentLayout(true);
2664
+ },
2665
+ [initialPosition, scrollOffset, viewportHeight]
2666
+ );
2667
+ (0, import_react8.useEffect)(() => {
2668
+ if (!hasContentLayout) {
2669
+ return;
2670
+ }
2671
+ scrollTo(scrollTargetRef.current, true);
2672
+ }, [hasContentLayout, maxScroll, scrollTo]);
2673
+ const handleWheel = (0, import_react8.useCallback)(
2674
+ (event) => {
2675
+ if (maxScroll <= 0) {
2676
+ return;
2677
+ }
2678
+ const delta = normalizeWheelDelta(event, viewportHeight);
2679
+ if (delta === 0) {
2680
+ return;
2681
+ }
2682
+ event.stopPropagation();
2683
+ const step = -Math.sign(delta) * Math.max(wheelStep, Math.min(Math.abs(delta), viewportHeight * 0.45));
2684
+ scrollTo(scrollTargetRef.current + step);
2685
+ },
2686
+ [maxScroll, scrollTo, viewportHeight, wheelStep]
2687
+ );
2688
+ const handleTouchStart = (0, import_react8.useCallback)(
2689
+ (event) => {
2690
+ if (maxScroll <= 0) {
2691
+ return;
2692
+ }
2693
+ event.stopPropagation();
2694
+ touchDraggingRef.current = true;
2695
+ touchStartYRef.current = event.clientY;
2696
+ touchStartOffsetRef.current = scrollTargetRef.current;
2697
+ },
2698
+ [maxScroll]
2699
+ );
2700
+ const handleTouchMove = (0, import_react8.useCallback)(
2701
+ (event) => {
2702
+ if (!touchDraggingRef.current) {
2703
+ return;
2704
+ }
2705
+ event.stopPropagation();
2706
+ scrollTo(touchStartOffsetRef.current + touchStartYRef.current - event.clientY, true);
2707
+ },
2708
+ [scrollTo]
2709
+ );
2710
+ const handleTouchEnd = (0, import_react8.useCallback)((event) => {
2711
+ if (!touchDraggingRef.current) {
2712
+ return;
2713
+ }
2714
+ event.stopPropagation();
2715
+ touchDraggingRef.current = false;
2716
+ }, []);
2717
+ return {
2718
+ scrollOffset,
2719
+ contentHeight,
2720
+ maxScroll,
2721
+ handleContentLayout,
2722
+ handleWheel,
2723
+ handleTouchStart,
2724
+ handleTouchMove,
2725
+ handleTouchEnd,
2726
+ scrollTo,
2727
+ scrollToRatio
2728
+ };
2729
+ }
2730
+
2141
2731
  // src/hooks/useScenario.ts
2142
- var import_react4 = require("react");
2732
+ var import_react9 = require("react");
2143
2733
  var currentScenarioSession = null;
2144
2734
  var scenarioSessionId = 0;
2145
2735
  var scenarioLifecycleQueue = Promise.resolve();
@@ -2281,7 +2871,7 @@ function setWaiting(time, skippable) {
2281
2871
  executePluginCommand("scenario", { subCommand: "setWaiting", time, skippable });
2282
2872
  }
2283
2873
  function useScenario(stories, startName, entryName, goNextOnLoad = false) {
2284
- (0, import_react4.useEffect)(() => {
2874
+ (0, import_react9.useEffect)(() => {
2285
2875
  const session = acquireScenarioSession({
2286
2876
  stories: normalizeStories(stories),
2287
2877
  startName,
@@ -2295,9 +2885,9 @@ function useScenario(stories, startName, entryName, goNextOnLoad = false) {
2295
2885
  }
2296
2886
 
2297
2887
  // src/hooks/useSoundEffect.ts
2298
- var import_react5 = require("react");
2888
+ var import_react10 = require("react");
2299
2889
  function useSoundEffect(src) {
2300
- (0, import_react5.useEffect)(() => {
2890
+ (0, import_react10.useEffect)(() => {
2301
2891
  return () => {
2302
2892
  try {
2303
2893
  if (src) {
@@ -2312,7 +2902,7 @@ function useSoundEffect(src) {
2312
2902
  }
2313
2903
  };
2314
2904
  }, [src]);
2315
- return (0, import_react5.useCallback)(() => {
2905
+ return (0, import_react10.useCallback)(() => {
2316
2906
  try {
2317
2907
  if (src) {
2318
2908
  executePluginCommand("audio", {
@@ -2328,7 +2918,7 @@ function useSoundEffect(src) {
2328
2918
  }
2329
2919
 
2330
2920
  // src/hooks/useStage.ts
2331
- var import_react6 = require("react");
2921
+ var import_react11 = require("react");
2332
2922
  var import_valtio5 = require("valtio");
2333
2923
  var SKIP_INTERVAL_MS = 80;
2334
2924
  var DEFAULT_FAST_FORWARD_STEP_LIMIT = 1e4;
@@ -2998,45 +3588,45 @@ function createStage() {
2998
3588
  bindEvents
2999
3589
  };
3000
3590
  }
3001
- var StageContext = (0, import_react6.createContext)(null);
3591
+ var StageContext = (0, import_react11.createContext)(null);
3002
3592
  function useStageContext() {
3003
- const ctx = (0, import_react6.useContext)(StageContext);
3593
+ const ctx = (0, import_react11.useContext)(StageContext);
3004
3594
  if (!ctx) throw new Error("useStageContext must be used within StageContextProvider");
3005
3595
  return ctx;
3006
3596
  }
3007
3597
  function StageContextProvider({ stage, children }) {
3008
- (0, import_react6.useEffect)(() => {
3598
+ (0, import_react11.useEffect)(() => {
3009
3599
  return stage.bindEvents();
3010
3600
  }, [stage]);
3011
- return (0, import_react6.createElement)(StageContext.Provider, { value: stage }, children);
3601
+ return (0, import_react11.createElement)(StageContext.Provider, { value: stage }, children);
3012
3602
  }
3013
3603
  function useSkipCallback(callback) {
3014
3604
  const stage = useStageContext();
3015
- (0, import_react6.useEffect)(() => {
3605
+ (0, import_react11.useEffect)(() => {
3016
3606
  return stage.addSkipCallback(callback);
3017
3607
  }, [stage, callback]);
3018
3608
  }
3019
3609
  function useInterruptCallback(callback) {
3020
3610
  const stage = useStageContext();
3021
- (0, import_react6.useEffect)(() => {
3611
+ (0, import_react11.useEffect)(() => {
3022
3612
  return stage.addInterruptCallback(callback);
3023
3613
  }, [stage, callback]);
3024
3614
  }
3025
3615
  function useSkipBlocker(callback) {
3026
3616
  const stage = useStageContext();
3027
- (0, import_react6.useEffect)(() => {
3617
+ (0, import_react11.useEffect)(() => {
3028
3618
  return stage.addSkipBlocker(callback);
3029
3619
  }, [stage, callback]);
3030
3620
  }
3031
3621
  function useAutoBlocker(callback) {
3032
3622
  const stage = useStageContext();
3033
- (0, import_react6.useEffect)(() => {
3623
+ (0, import_react11.useEffect)(() => {
3034
3624
  return stage.addAutoBlocker(callback);
3035
3625
  }, [stage, callback]);
3036
3626
  }
3037
3627
  function useAutoTicket() {
3038
3628
  const stage = useStageContext();
3039
- return (0, import_react6.useCallback)(
3629
+ return (0, import_react11.useCallback)(
3040
3630
  (options) => {
3041
3631
  return stage.issueAutoTicket(options);
3042
3632
  },
@@ -3045,7 +3635,7 @@ function useAutoTicket() {
3045
3635
  }
3046
3636
  function useBeforeHandleCommandCallback(callback) {
3047
3637
  const stage = useStageContext();
3048
- (0, import_react6.useEffect)(() => {
3638
+ (0, import_react11.useEffect)(() => {
3049
3639
  return stage.addBeforeHandleCommandCallback(callback);
3050
3640
  }, [stage, callback]);
3051
3641
  }