@novely/core 0.29.1 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -47,6 +47,7 @@ var MAIN_CONTEXT_KEY = "$MAIN";
47
47
 
48
48
  // src/shared.ts
49
49
  var STACK_MAP = /* @__PURE__ */ new Map();
50
+ var PRELOADED_ASSETS = /* @__PURE__ */ new Set();
50
51
 
51
52
  // src/utils.ts
52
53
  import { DEV } from "esm-env";
@@ -464,13 +465,23 @@ var getResourseType = async (request, url) => {
464
465
  }
465
466
  return "other";
466
467
  };
468
+ var capitalize = (str2) => {
469
+ return str2[0].toUpperCase() + str2.slice(1);
470
+ };
471
+ var getIntlLanguageDisplayName = (lang) => {
472
+ try {
473
+ const intl = new Intl.DisplayNames([lang], {
474
+ type: "language"
475
+ });
476
+ return intl.of(lang) || lang;
477
+ } catch {
478
+ return lang;
479
+ }
480
+ };
467
481
 
468
482
  // src/novely.ts
469
483
  import { dequal } from "dequal/lite";
470
484
 
471
- // src/global.ts
472
- var PRELOADED_ASSETS = /* @__PURE__ */ new Set();
473
-
474
485
  // src/store.ts
475
486
  var store = (current, subscribers = /* @__PURE__ */ new Set()) => {
476
487
  const subscribe = (cb) => {
@@ -495,7 +506,7 @@ var store = (current, subscribers = /* @__PURE__ */ new Set()) => {
495
506
  return { subscribe, update, set, get };
496
507
  };
497
508
 
498
- // ../deepmerge/dist/index.js
509
+ // ../../node_modules/.pnpm/@novely+deepmerge@0.0.0/node_modules/@novely/deepmerge/dist/index.js
499
510
  var { isArray } = Array;
500
511
  var { hasOwnProperty, propertyIsEnumerable, getOwnPropertySymbols } = Object;
501
512
  var propertyIsOnObject = (object, property) => {
@@ -618,10 +629,10 @@ var flattenAllowedContent = (c, state) => {
618
629
  }
619
630
  return c;
620
631
  };
621
- var replace = (str2, obj, pluralization, actions, pr) => {
622
- return str2.replaceAll(RGX, (x, key, y) => {
632
+ var replace = (input, data, pluralization, actions, pr) => {
633
+ return input.replaceAll(RGX, (x, key, y) => {
623
634
  x = 0;
624
- y = obj;
635
+ y = data;
625
636
  const [pathstr, plural, action] = split(key.trim(), ["@", "%"]);
626
637
  if (!pathstr) {
627
638
  return "";
@@ -632,7 +643,7 @@ var replace = (str2, obj, pluralization, actions, pr) => {
632
643
  if (plural && pluralization && y && pr) {
633
644
  y = pluralization[plural][pr.select(y)];
634
645
  }
635
- const actionHandler = actions && action && actions[action];
646
+ const actionHandler = actions && action ? actions[action] : void 0;
636
647
  if (actionHandler)
637
648
  y = actionHandler(y);
638
649
  return y == null ? "" : y;
@@ -660,11 +671,29 @@ var localStorageStorage = (options) => {
660
671
  };
661
672
  };
662
673
 
674
+ // src/browser.ts
675
+ var setupBrowserVisibilityChangeListeners = ({ onChange }) => {
676
+ if (typeof document === "undefined")
677
+ return noop;
678
+ const onVisibilityChange = () => {
679
+ if (document.visibilityState === "hidden") {
680
+ onChange();
681
+ }
682
+ };
683
+ addEventListener("visibilitychange", onVisibilityChange);
684
+ addEventListener("beforeunload", onChange);
685
+ return () => {
686
+ removeEventListener("visibilitychange", onVisibilityChange);
687
+ removeEventListener("beforeunload", onChange);
688
+ };
689
+ };
690
+
663
691
  // src/novely.ts
664
692
  import pLimit from "p-limit";
665
693
  import { DEV as DEV2 } from "esm-env";
666
694
  var novely = ({
667
695
  characters,
696
+ defaultEmotions = {},
668
697
  storage = localStorageStorage({ key: "novely-game-storage" }),
669
698
  storageDelay = Promise.resolve(),
670
699
  renderer: createRenderer,
@@ -681,7 +710,8 @@ var novely = ({
681
710
  preloadAssets = "lazy",
682
711
  parallelAssetsDownloadLimit = 15,
683
712
  fetch: request = fetch,
684
- saveOnUnload = true
713
+ saveOnUnload = true,
714
+ startKey = "start"
685
715
  }) => {
686
716
  const languages = Object.keys(translation);
687
717
  const limitScript = pLimit(1);
@@ -777,7 +807,7 @@ var novely = ({
777
807
  const getDefaultSave = (state = {}) => {
778
808
  return [
779
809
  [
780
- ["jump", "start"],
810
+ ["jump", startKey],
781
811
  [null, 0]
782
812
  ],
783
813
  state,
@@ -845,13 +875,9 @@ var novely = ({
845
875
  };
846
876
  storageDelay.then(getStoredData);
847
877
  const initial = getDefaultSave(klona(defaultState));
848
- const onVisibilityChange = () => {
849
- if (document.visibilityState === "hidden") {
850
- throttledEmergencyOnStorageDataChange();
851
- }
852
- };
853
- addEventListener("visibilitychange", onVisibilityChange);
854
- addEventListener("beforeunload", throttledEmergencyOnStorageDataChange);
878
+ const unsubscribeFromBrowserVisibilityChange = setupBrowserVisibilityChangeListeners({
879
+ onChange: throttledEmergencyOnStorageDataChange
880
+ });
855
881
  const save = (type) => {
856
882
  if (!coreData.get().dataLoaded)
857
883
  return;
@@ -905,6 +931,12 @@ var novely = ({
905
931
  };
906
932
  let interacted = 0;
907
933
  const restore = async (save2) => {
934
+ if (isEmpty(story)) {
935
+ if (DEV2) {
936
+ throw new Error("Story is empty. You should call an `enine.script` function [https://novely.pages.dev/guide/story.html]");
937
+ }
938
+ return;
939
+ }
908
940
  if (!coreData.get().dataLoaded)
909
941
  return;
910
942
  let latest = save2 || storageData.get().saves.at(-1);
@@ -1014,20 +1046,26 @@ var novely = ({
1014
1046
  };
1015
1047
  const back = async () => {
1016
1048
  const stack = useStack(MAIN_CONTEXT_KEY);
1049
+ const valueBeforeBack = stack.value;
1017
1050
  stack.back();
1051
+ if (dequal(valueBeforeBack, stack.value) && !stack.previous) {
1052
+ return;
1053
+ }
1018
1054
  await restore(stack.value);
1019
1055
  };
1020
1056
  const t = (key, lang) => {
1021
1057
  return translation[lang].internal[key];
1022
1058
  };
1023
1059
  const preview = async (save2, name) => {
1060
+ if (isEmpty(story))
1061
+ return;
1024
1062
  const [path, data2] = save2;
1025
1063
  const { queue } = getActionsFromPath(story, path, true);
1026
1064
  const ctx = renderer.getContext(name);
1027
1065
  ctx.meta.restoring = true;
1028
1066
  ctx.meta.preview = true;
1029
1067
  const processor = createQueueProcessor(queue, {
1030
- skip: /* @__PURE__ */ new Set()
1068
+ skip: EMPTY_SET
1031
1069
  });
1032
1070
  useStack(ctx).push(klona(save2));
1033
1071
  await processor.run(([action2, ...props]) => {
@@ -1063,6 +1101,13 @@ var novely = ({
1063
1101
  };
1064
1102
  return state;
1065
1103
  };
1104
+ const getLanguageDisplayName = (lang) => {
1105
+ const language = translation[lang];
1106
+ if (DEV2 && !language) {
1107
+ throw new Error(`Attempt to use unsupported language "${language}". Supported languages: ${languages.join(", ")}.`);
1108
+ }
1109
+ return capitalize(language.nameOverride || getIntlLanguageDisplayName(lang));
1110
+ };
1066
1111
  const renderer = createRenderer({
1067
1112
  mainContextKey: MAIN_CONTEXT_KEY,
1068
1113
  characters,
@@ -1078,7 +1123,8 @@ var novely = ({
1078
1123
  getStateFunction,
1079
1124
  languages,
1080
1125
  storageData,
1081
- coreData
1126
+ coreData,
1127
+ getLanguageDisplayName
1082
1128
  });
1083
1129
  const useStack = createUseStackFunction(renderer);
1084
1130
  useStack(MAIN_CONTEXT_KEY).push(initial);
@@ -1165,6 +1211,12 @@ var novely = ({
1165
1211
  push();
1166
1212
  },
1167
1213
  showCharacter({ ctx, push }, [character, emotion, className, style]) {
1214
+ emotion ??= defaultEmotions[character];
1215
+ if (DEV2 && !emotion) {
1216
+ throw new Error(`Attemp to show character "${character}" without emotion provided.`);
1217
+ }
1218
+ if (!emotion)
1219
+ return;
1168
1220
  if (DEV2 && !characters[character].emotions[emotion]) {
1169
1221
  throw new Error(`Attempt to show character "${character}" with unknown emotion "${emotion}"`);
1170
1222
  }
@@ -1302,11 +1354,7 @@ var novely = ({
1302
1354
  end({ ctx }) {
1303
1355
  if (ctx.meta.preview)
1304
1356
  return;
1305
- ctx.vibrate(0);
1306
- ctx.clear(EMPTY_SET, EMPTY_SET, { music: EMPTY_SET, sounds: EMPTY_SET }, noop);
1307
- renderer.ui.showScreen("mainmenu");
1308
- interactivity(false);
1309
- times.clear();
1357
+ exit(true);
1310
1358
  },
1311
1359
  input({ ctx, data: data2, forward }, [question, onInput, setup]) {
1312
1360
  ctx.input(
@@ -1430,6 +1478,11 @@ var novely = ({
1430
1478
  ctx,
1431
1479
  data: stack.value[1]
1432
1480
  });
1481
+ } else if (Object.values(story).some((branch) => branch === referred)) {
1482
+ match("end", [], {
1483
+ ctx,
1484
+ data: stack.value[1]
1485
+ });
1433
1486
  } else {
1434
1487
  match("exit", [], {
1435
1488
  ctx,
@@ -1441,14 +1494,10 @@ var novely = ({
1441
1494
  interacted = value ? interacted + 1 : 0;
1442
1495
  };
1443
1496
  const templateReplace = (content, values) => {
1444
- const {
1445
- data: data2,
1446
- meta: [lang]
1447
- } = storageData.get();
1497
+ const { data: data2, meta: [lang] } = storageData.get();
1448
1498
  const obj = values || data2;
1449
- const cnt = isFunction(content) ? content(obj) : typeof content === "string" ? content : content[lang];
1450
1499
  const str2 = flattenAllowedContent(
1451
- isFunction(cnt) ? cnt(obj) : cnt,
1500
+ !isFunction(content) && !isString(content) ? content[lang] : content,
1452
1501
  obj
1453
1502
  );
1454
1503
  const t2 = translation[lang];
@@ -1548,8 +1597,7 @@ var novely = ({
1548
1597
  destroy() {
1549
1598
  dataLoaded.cancel();
1550
1599
  UIInstance.unmount();
1551
- removeEventListener("visibilitychange", onVisibilityChange);
1552
- removeEventListener("beforeunload", throttledEmergencyOnStorageDataChange);
1600
+ unsubscribeFromBrowserVisibilityChange();
1553
1601
  }
1554
1602
  };
1555
1603
  };