@novely/core 0.45.0 → 0.45.2

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
@@ -1,15 +1,91 @@
1
+ // src/novely.ts
2
+ import { dequal } from "dequal/lite";
3
+ import { throttle } from "es-toolkit/function";
4
+ import { merge as deepmerge } from "es-toolkit/object";
5
+ import { DEV as DEV3 } from "esm-env";
6
+
7
+ // ../../node_modules/.pnpm/klona@2.0.6/node_modules/klona/full/index.mjs
8
+ function set(obj, key, val) {
9
+ if (typeof val.value === "object") val.value = klona(val.value);
10
+ if (!val.enumerable || val.get || val.set || !val.configurable || !val.writable || key === "__proto__") {
11
+ Object.defineProperty(obj, key, val);
12
+ } else obj[key] = val.value;
13
+ }
14
+ function klona(x) {
15
+ if (typeof x !== "object") return x;
16
+ var i = 0, k, list, tmp, str2 = Object.prototype.toString.call(x);
17
+ if (str2 === "[object Object]") {
18
+ tmp = Object.create(x.__proto__ || null);
19
+ } else if (str2 === "[object Array]") {
20
+ tmp = Array(x.length);
21
+ } else if (str2 === "[object Set]") {
22
+ tmp = /* @__PURE__ */ new Set();
23
+ x.forEach(function(val) {
24
+ tmp.add(klona(val));
25
+ });
26
+ } else if (str2 === "[object Map]") {
27
+ tmp = /* @__PURE__ */ new Map();
28
+ x.forEach(function(val, key) {
29
+ tmp.set(klona(key), klona(val));
30
+ });
31
+ } else if (str2 === "[object Date]") {
32
+ tmp = /* @__PURE__ */ new Date(+x);
33
+ } else if (str2 === "[object RegExp]") {
34
+ tmp = new RegExp(x.source, x.flags);
35
+ } else if (str2 === "[object DataView]") {
36
+ tmp = new x.constructor(klona(x.buffer));
37
+ } else if (str2 === "[object ArrayBuffer]") {
38
+ tmp = x.slice(0);
39
+ } else if (str2.slice(-6) === "Array]") {
40
+ tmp = new x.constructor(x);
41
+ }
42
+ if (tmp) {
43
+ for (list = Object.getOwnPropertySymbols(x); i < list.length; i++) {
44
+ set(tmp, list[i], Object.getOwnPropertyDescriptor(x, list[i]));
45
+ }
46
+ for (i = 0, list = Object.getOwnPropertyNames(x); i < list.length; i++) {
47
+ if (Object.hasOwnProperty.call(tmp, k = list[i]) && tmp[k] === x[k]) continue;
48
+ set(tmp, k, Object.getOwnPropertyDescriptor(x, k));
49
+ }
50
+ }
51
+ return tmp || x;
52
+ }
53
+
54
+ // src/novely.ts
55
+ import pLimit from "p-limit";
56
+
57
+ // src/asset.ts
58
+ import { memoize as memoize2, once } from "es-toolkit/function";
59
+ import { DEV as DEV2 } from "esm-env";
60
+
61
+ // src/audio-codecs.ts
62
+ var cut = (str2) => str2.replace(/^no$/, "");
63
+ var audio = new Audio();
64
+ var canPlay = (type) => !!cut(audio.canPlayType(type));
65
+ var canPlayMultiple = (...types) => types.some((type) => canPlay(type));
66
+ var supportsMap = {
67
+ mp3: canPlayMultiple("audio/mpeg;", "audio/mp3;"),
68
+ mpeg: canPlay("audio/mpeg;"),
69
+ opus: canPlay('audio/ogg; codecs="opus"'),
70
+ ogg: canPlay('audio/ogg; codecs="vorbis"'),
71
+ oga: canPlay('audio/ogg; codecs="vorbis"'),
72
+ wav: canPlayMultiple('audio/wav; codecs="1"', "audio/wav;"),
73
+ aac: canPlay("audio/aac;"),
74
+ caf: canPlay("audio/x-caf;"),
75
+ m4a: canPlayMultiple("audio/x-m4a;", "audio/m4a;", "audio/aac;"),
76
+ m4b: canPlayMultiple("audio/x-m4b;", "audio/m4b;", "audio/aac;"),
77
+ mp4: canPlayMultiple("audio/x-mp4;", "audio/mp4;", "audio/aac;"),
78
+ weba: canPlay('audio/webm; codecs="vorbis"'),
79
+ webm: canPlay('audio/webm; codecs="vorbis"'),
80
+ dolby: canPlay('audio/mp4; codecs="ec-3"'),
81
+ flac: canPlayMultiple("audio/x-flac;", "audio/flac;")
82
+ };
83
+
1
84
  // src/constants.ts
2
85
  var SKIPPED_DURING_RESTORE = /* @__PURE__ */ new Set(["dialog", "choice", "input", "vibrate", "text"]);
3
86
  var BLOCK_EXIT_STATEMENTS = /* @__PURE__ */ new Set(["choice:exit", "condition:exit", "block:exit"]);
4
87
  var BLOCK_STATEMENTS = /* @__PURE__ */ new Set(["choice", "condition", "block"]);
5
- var AUDIO_ACTIONS = /* @__PURE__ */ new Set([
6
- "playMusic",
7
- "stopMusic",
8
- "playSound",
9
- "stopSound",
10
- "voice",
11
- "stopVoice"
12
- ]);
88
+ var AUDIO_ACTIONS = /* @__PURE__ */ new Set(["playMusic", "stopMusic", "playSound", "stopSound", "voice", "stopVoice"]);
13
89
  var EMPTY_SET = /* @__PURE__ */ new Set();
14
90
  var DEFAULT_TYPEWRITER_SPEED = "Medium";
15
91
  var HOWLER_SUPPORTED_FILE_FORMATS = /* @__PURE__ */ new Set([
@@ -45,42 +121,6 @@ var SUPPORTED_IMAGE_FILE_FORMATS = /* @__PURE__ */ new Set([
45
121
  ]);
46
122
  var MAIN_CONTEXT_KEY = "$MAIN";
47
123
 
48
- // src/shared.ts
49
- var STACK_MAP = /* @__PURE__ */ new Map();
50
- var CUSTOM_ACTION_MAP = /* @__PURE__ */ new Map();
51
- var PRELOADED_ASSETS = /* @__PURE__ */ new Set();
52
- var ASSETS_TO_PRELOAD = /* @__PURE__ */ new Set();
53
-
54
- // src/utils.ts
55
- import { DEV as DEV2 } from "esm-env";
56
- import { memoize as memoize2 } from "es-toolkit/function";
57
-
58
- // src/asset.ts
59
- import { DEV } from "esm-env";
60
-
61
- // src/audio-codecs.ts
62
- var cut = (str2) => str2.replace(/^no$/, "");
63
- var audio = new Audio();
64
- var canPlay = (type) => !!cut(audio.canPlayType(type));
65
- var canPlayMultiple = (...types) => types.some((type) => canPlay(type));
66
- var supportsMap = {
67
- mp3: canPlayMultiple("audio/mpeg;", "audio/mp3;"),
68
- mpeg: canPlay("audio/mpeg;"),
69
- opus: canPlay('audio/ogg; codecs="opus"'),
70
- ogg: canPlay('audio/ogg; codecs="vorbis"'),
71
- oga: canPlay('audio/ogg; codecs="vorbis"'),
72
- wav: canPlayMultiple('audio/wav; codecs="1"', "audio/wav;"),
73
- aac: canPlay("audio/aac;"),
74
- caf: canPlay("audio/x-caf;"),
75
- m4a: canPlayMultiple("audio/x-m4a;", "audio/m4a;", "audio/aac;"),
76
- m4b: canPlayMultiple("audio/x-m4b;", "audio/m4b;", "audio/aac;"),
77
- mp4: canPlayMultiple("audio/x-mp4;", "audio/mp4;", "audio/aac;"),
78
- weba: canPlay('audio/webm; codecs="vorbis"'),
79
- webm: canPlay('audio/webm; codecs="vorbis"'),
80
- dolby: canPlay('audio/mp4; codecs="ec-3"'),
81
- flac: canPlayMultiple("audio/x-flac;", "audio/flac;")
82
- };
83
-
84
124
  // src/image-formats.ts
85
125
  var avif = "data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A=";
86
126
  var jxl = "data:image/jxl;base64,/woIAAAMABKIAgC4AF3lEgAAFSqjjBu8nOv58kOHxbSN6wxttW1hSaLIODZJJ3BIEkkaoCUzGM6qJAE=";
@@ -117,74 +157,15 @@ var loadImageFormatsSupport = async () => {
117
157
  };
118
158
  loadImageFormatsSupport();
119
159
 
120
- // src/asset.ts
121
- import { memoize, once } from "es-toolkit/function";
122
- var getType = memoize(
123
- (extensions) => {
124
- if (extensions.every((extension) => HOWLER_SUPPORTED_FILE_FORMATS.has(extension))) {
125
- return "audio";
126
- }
127
- if (extensions.every((extension) => SUPPORTED_IMAGE_FILE_FORMATS.has(extension))) {
128
- return "image";
129
- }
130
- throw extensions;
131
- },
132
- {
133
- getCacheKey: (extensions) => extensions.join("~")
134
- }
135
- );
136
- var SUPPORT_MAPS = {
137
- "image": supportsMap2,
138
- "audio": supportsMap
139
- };
140
- var assetPrivate = memoize(
141
- (variants) => {
142
- if (DEV && variants.length === 0) {
143
- throw new Error(`Attempt to use "asset" function without arguments`);
144
- }
145
- const map = {};
146
- const extensions = [];
147
- for (const v of variants) {
148
- const e = getUrlFileExtension(v);
149
- map[e] = v;
150
- extensions.push(e);
151
- }
152
- const type = getType(extensions);
153
- const getSource = once(() => {
154
- const support = SUPPORT_MAPS[type];
155
- for (const extension of extensions) {
156
- if (extension in support) {
157
- if (support[extension]) {
158
- return map[extension];
159
- }
160
- } else {
161
- return map[extension];
162
- }
163
- }
164
- if (DEV) {
165
- throw new Error(`No matching asset was found for ${variants.map((v) => `"${v}"`).join(", ")}`);
166
- }
167
- return "";
168
- });
169
- return {
170
- get source() {
171
- return getSource();
172
- },
173
- get type() {
174
- return type;
175
- }
176
- };
177
- },
178
- {
179
- getCacheKey: (variants) => variants.join("~")
180
- }
181
- );
182
- var asset = (...variants) => {
183
- return assetPrivate(variants);
184
- };
185
- var isAsset = (suspect) => {
186
- return suspect !== null && typeof suspect === "object" && "source" in suspect && "type" in suspect;
187
- };
160
+ // src/utils.ts
161
+ import { memoize } from "es-toolkit/function";
162
+ import { DEV } from "esm-env";
163
+
164
+ // src/shared.ts
165
+ var STACK_MAP = /* @__PURE__ */ new Map();
166
+ var CUSTOM_ACTION_MAP = /* @__PURE__ */ new Map();
167
+ var PRELOADED_ASSETS = /* @__PURE__ */ new Set();
168
+ var ASSETS_TO_PRELOAD = /* @__PURE__ */ new Set();
188
169
 
189
170
  // src/utils.ts
190
171
  var matchAction = ({ getContext, onBeforeActionCall, push, forward }, values) => {
@@ -195,18 +176,21 @@ var matchAction = ({ getContext, onBeforeActionCall, push, forward }, values) =>
195
176
  props,
196
177
  ctx: context
197
178
  });
198
- return values[action]({
199
- ctx: context,
200
- data,
201
- push() {
202
- if (context.meta.preview) return;
203
- push(context);
179
+ return values[action](
180
+ {
181
+ ctx: context,
182
+ data,
183
+ push() {
184
+ if (context.meta.preview) return;
185
+ push(context);
186
+ },
187
+ forward() {
188
+ if (context.meta.preview) return;
189
+ forward(context);
190
+ }
204
191
  },
205
- forward() {
206
- if (context.meta.preview) return;
207
- forward(context);
208
- }
209
- }, props);
192
+ props
193
+ );
210
194
  };
211
195
  };
212
196
  var isNumber = (val) => {
@@ -318,10 +302,10 @@ var isExitImpossible = (path) => {
318
302
  };
319
303
  var getOppositeAction = (action) => {
320
304
  const MAP = {
321
- "showCharacter": "hideCharacter",
322
- "playSound": "stopSound",
323
- "playMusic": "stopMusic",
324
- "voice": "stopVoice"
305
+ showCharacter: "hideCharacter",
306
+ playSound: "stopSound",
307
+ playMusic: "stopMusic",
308
+ voice: "stopVoice"
325
309
  };
326
310
  return MAP[action];
327
311
  };
@@ -488,7 +472,7 @@ var createQueueProcessor = (queue, options) => {
488
472
  }
489
473
  };
490
474
  };
491
- var getStack = memoize2(
475
+ var getStack = memoize(
492
476
  (_) => {
493
477
  return [];
494
478
  },
@@ -540,7 +524,7 @@ var getUrlFileExtension = (address) => {
540
524
  const { pathname } = new URL(address, location.href);
541
525
  return pathname.split(".").at(-1).split("!")[0].split(":")[0];
542
526
  } catch (error) {
543
- if (DEV2) {
527
+ if (DEV) {
544
528
  console.error(new Error(`Could not construct URL "${address}".`, { cause: error }));
545
529
  }
546
530
  return "";
@@ -553,13 +537,13 @@ var fetchContentType = async (url, request) => {
553
537
  });
554
538
  return response.headers.get("Content-Type") || "";
555
539
  } catch (error) {
556
- if (DEV2) {
540
+ if (DEV) {
557
541
  console.error(new Error(`Failed to fetch file at "${url}"`, { cause: error }));
558
542
  }
559
543
  return "";
560
544
  }
561
545
  };
562
- var getResourseType = memoize2(
546
+ var getResourseType = memoize(
563
547
  async ({ url, request }) => {
564
548
  if (!isCSSImage(url)) {
565
549
  return "other";
@@ -587,7 +571,7 @@ var getResourseType = memoize2(
587
571
  var capitalize = (str2) => {
588
572
  return str2[0].toUpperCase() + str2.slice(1);
589
573
  };
590
- var getIntlLanguageDisplayName = memoize2((lang) => {
574
+ var getIntlLanguageDisplayName = memoize((lang) => {
591
575
  try {
592
576
  const intl = new Intl.DisplayNames([lang], {
593
577
  type: "language"
@@ -732,7 +716,10 @@ var collectActionsBeforeBlockingAction = ({ path, refer, clone }) => {
732
716
  }
733
717
  collection.push(action);
734
718
  if (action[0] === "jump") {
735
- path = [["jump", action[1]], [null, 0]];
719
+ path = [
720
+ ["jump", action[1]],
721
+ [null, 0]
722
+ ];
736
723
  } else if (action[0] == "block") {
737
724
  path.push(["block", action[1]], [null, 0]);
738
725
  } else {
@@ -746,13 +733,13 @@ var unwrapAsset = (asset2) => {
746
733
  return isAsset(asset2) ? asset2.source : asset2;
747
734
  };
748
735
  var handleAudioAsset = (asset2) => {
749
- if (DEV2 && isAsset(asset2) && asset2.type !== "audio") {
736
+ if (DEV && isAsset(asset2) && asset2.type !== "audio") {
750
737
  throw new Error("Attempt to use non-audio asset in audio action", { cause: asset2 });
751
738
  }
752
739
  return unwrapAsset(asset2);
753
740
  };
754
741
  var handleImageAsset = (asset2) => {
755
- if (DEV2 && isAsset(asset2) && asset2.type !== "image") {
742
+ if (DEV && isAsset(asset2) && asset2.type !== "image") {
756
743
  throw new Error("Attempt to use non-image asset in action that requires image assets", { cause: asset2 });
757
744
  }
758
745
  return unwrapAsset(asset2);
@@ -777,120 +764,88 @@ var getVolumeFromStore = (store2) => {
777
764
  };
778
765
  };
779
766
 
780
- // src/novely.ts
781
- import { throttle } from "es-toolkit/function";
782
- import { merge as deepmerge } from "es-toolkit/object";
783
- import { dequal } from "dequal/lite";
784
-
785
- // src/store.ts
786
- var store = (current, subscribers = /* @__PURE__ */ new Set()) => {
787
- const subscribe = (cb) => {
788
- subscribers.add(cb), cb(current);
789
- return () => {
790
- subscribers.delete(cb);
791
- };
792
- };
793
- const push = (value) => {
794
- for (const cb of subscribers) cb(value);
795
- };
796
- const update = (fn) => {
797
- push(current = fn(current));
798
- };
799
- const set2 = (val) => {
800
- update(() => val);
801
- };
802
- const get = () => {
803
- return current;
804
- };
805
- return { subscribe, update, set: set2, get };
806
- };
807
-
808
- // ../../node_modules/.pnpm/klona@2.0.6/node_modules/klona/full/index.mjs
809
- function set(obj, key, val) {
810
- if (typeof val.value === "object") val.value = klona(val.value);
811
- if (!val.enumerable || val.get || val.set || !val.configurable || !val.writable || key === "__proto__") {
812
- Object.defineProperty(obj, key, val);
813
- } else obj[key] = val.value;
814
- }
815
- function klona(x) {
816
- if (typeof x !== "object") return x;
817
- var i = 0, k, list, tmp, str2 = Object.prototype.toString.call(x);
818
- if (str2 === "[object Object]") {
819
- tmp = Object.create(x.__proto__ || null);
820
- } else if (str2 === "[object Array]") {
821
- tmp = Array(x.length);
822
- } else if (str2 === "[object Set]") {
823
- tmp = /* @__PURE__ */ new Set();
824
- x.forEach(function(val) {
825
- tmp.add(klona(val));
826
- });
827
- } else if (str2 === "[object Map]") {
828
- tmp = /* @__PURE__ */ new Map();
829
- x.forEach(function(val, key) {
830
- tmp.set(klona(key), klona(val));
831
- });
832
- } else if (str2 === "[object Date]") {
833
- tmp = /* @__PURE__ */ new Date(+x);
834
- } else if (str2 === "[object RegExp]") {
835
- tmp = new RegExp(x.source, x.flags);
836
- } else if (str2 === "[object DataView]") {
837
- tmp = new x.constructor(klona(x.buffer));
838
- } else if (str2 === "[object ArrayBuffer]") {
839
- tmp = x.slice(0);
840
- } else if (str2.slice(-6) === "Array]") {
841
- tmp = new x.constructor(x);
842
- }
843
- if (tmp) {
844
- for (list = Object.getOwnPropertySymbols(x); i < list.length; i++) {
845
- set(tmp, list[i], Object.getOwnPropertyDescriptor(x, list[i]));
767
+ // src/asset.ts
768
+ var getType = memoize2(
769
+ (extensions) => {
770
+ if (extensions.every((extension) => HOWLER_SUPPORTED_FILE_FORMATS.has(extension))) {
771
+ return "audio";
846
772
  }
847
- for (i = 0, list = Object.getOwnPropertyNames(x); i < list.length; i++) {
848
- if (Object.hasOwnProperty.call(tmp, k = list[i]) && tmp[k] === x[k]) continue;
849
- set(tmp, k, Object.getOwnPropertyDescriptor(x, k));
773
+ if (extensions.every((extension) => SUPPORTED_IMAGE_FILE_FORMATS.has(extension))) {
774
+ return "image";
850
775
  }
776
+ throw extensions;
777
+ },
778
+ {
779
+ getCacheKey: (extensions) => extensions.join("~")
851
780
  }
852
- return tmp || x;
853
- }
854
-
855
- // src/translation.ts
856
- var RGX = /{{(.*?)}}/g;
857
- var split = (input, delimeters) => {
858
- const output = [];
859
- for (const delimeter of delimeters) {
860
- if (!input) break;
861
- const [start, end] = input.split(delimeter, 2);
862
- output.push(start);
863
- input = end;
864
- }
865
- output.push(input);
866
- return output;
781
+ );
782
+ var SUPPORT_MAPS = {
783
+ image: supportsMap2,
784
+ audio: supportsMap
867
785
  };
868
- var flattenAllowedContent = (c, state) => {
869
- if (Array.isArray(c)) {
870
- return c.map((item) => flattenAllowedContent(item, state)).join("<br>");
871
- }
872
- if (typeof c === "function") {
873
- return flattenAllowedContent(c(state), state);
786
+ var assetPrivate = memoize2(
787
+ (variants) => {
788
+ if (DEV2 && variants.length === 0) {
789
+ throw new Error(`Attempt to use "asset" function without arguments`);
790
+ }
791
+ const map = {};
792
+ const extensions = [];
793
+ for (const v of variants) {
794
+ const e = getUrlFileExtension(v);
795
+ map[e] = v;
796
+ extensions.push(e);
797
+ }
798
+ const type = getType(extensions);
799
+ const getSource = once(() => {
800
+ const support = SUPPORT_MAPS[type];
801
+ for (const extension of extensions) {
802
+ if (extension in support) {
803
+ if (support[extension]) {
804
+ return map[extension];
805
+ }
806
+ } else {
807
+ return map[extension];
808
+ }
809
+ }
810
+ if (DEV2) {
811
+ throw new Error(`No matching asset was found for ${variants.map((v) => `"${v}"`).join(", ")}`);
812
+ }
813
+ return "";
814
+ });
815
+ return {
816
+ get source() {
817
+ return getSource();
818
+ },
819
+ get type() {
820
+ return type;
821
+ }
822
+ };
823
+ },
824
+ {
825
+ getCacheKey: (variants) => variants.join("~")
874
826
  }
875
- return c;
827
+ );
828
+ var asset = (...variants) => {
829
+ return assetPrivate(variants);
876
830
  };
877
- var replace = (input, data, pluralization, actions, pr) => {
878
- return input.replaceAll(RGX, (x, key, y) => {
879
- x = 0;
880
- y = data;
881
- const [pathstr, plural, action] = split(key.trim(), ["@", "%"]);
882
- if (!pathstr) {
883
- return "";
884
- }
885
- const path = pathstr.split(".");
886
- while (y && x < path.length) y = y[path[x++]];
887
- if (plural && pluralization && y && pr) {
888
- y = pluralization[plural][pr.select(y)];
831
+ var isAsset = (suspect) => {
832
+ return suspect !== null && typeof suspect === "object" && "source" in suspect && "type" in suspect;
833
+ };
834
+
835
+ // src/browser.ts
836
+ var setupBrowserVisibilityChangeListeners = ({ onChange }) => {
837
+ if (typeof document === "undefined") return noop;
838
+ const onVisibilityChange = () => {
839
+ if (document.visibilityState === "hidden") {
840
+ onChange();
889
841
  }
890
- const actionHandler = actions && action ? actions[action] : void 0;
891
- if (actionHandler) y = actionHandler(y);
892
- return y == null ? "" : y;
893
- });
842
+ };
843
+ addEventListener("visibilitychange", onVisibilityChange);
844
+ addEventListener("beforeunload", onChange);
845
+ return () => {
846
+ removeEventListener("visibilitychange", onVisibilityChange);
847
+ removeEventListener("beforeunload", onChange);
848
+ };
894
849
  };
895
850
 
896
851
  // src/custom-action.ts
@@ -933,13 +888,15 @@ var handleCustomAction = (ctx, fn, { lang, state, setMountElement, setClear, rem
933
888
  };
934
889
  };
935
890
  const clear = (func) => {
936
- setClear(holder.cleanup = () => {
937
- func();
938
- holder.node = null;
939
- holder.cleanup = noop;
940
- setMountElement(null);
941
- setClear(noop);
942
- });
891
+ setClear(
892
+ holder.cleanup = () => {
893
+ func();
894
+ holder.node = null;
895
+ holder.cleanup = noop;
896
+ setMountElement(null);
897
+ setClear(noop);
898
+ }
899
+ );
943
900
  };
944
901
  const data = (updatedData) => {
945
902
  if (updatedData) {
@@ -969,59 +926,23 @@ var handleCustomAction = (ctx, fn, { lang, state, setMountElement, setClear, rem
969
926
  });
970
927
  };
971
928
 
972
- // src/storage.ts
973
- var localStorageStorage = (options) => {
974
- return {
975
- async get() {
976
- const fallback = { saves: [], data: {}, meta: [] };
977
- try {
978
- const value = localStorage.getItem(options.key);
979
- return value ? JSON.parse(value) : fallback;
980
- } catch {
981
- return fallback;
982
- }
983
- },
984
- async set(data) {
985
- try {
986
- localStorage.setItem(options.key, JSON.stringify(data));
987
- } catch {
988
- }
989
- }
990
- };
991
- };
992
-
993
- // src/browser.ts
994
- var setupBrowserVisibilityChangeListeners = ({ onChange }) => {
995
- if (typeof document === "undefined") return noop;
996
- const onVisibilityChange = () => {
997
- if (document.visibilityState === "hidden") {
998
- onChange();
999
- }
1000
- };
1001
- addEventListener("visibilitychange", onVisibilityChange);
1002
- addEventListener("beforeunload", onChange);
1003
- return () => {
1004
- removeEventListener("visibilitychange", onVisibilityChange);
1005
- removeEventListener("beforeunload", onChange);
1006
- };
1007
- };
1008
-
1009
- // src/novely.ts
1010
- import pLimit from "p-limit";
1011
- import { DEV as DEV3 } from "esm-env";
1012
-
1013
929
  // src/preloading.ts
1014
930
  var ACTION_NAME_TO_VOLUME_MAP = {
1015
- "playMusic": "music",
1016
- "playSound": "sound",
1017
- "voice": "voice"
931
+ playMusic: "music",
932
+ playSound: "sound",
933
+ voice: "voice"
1018
934
  };
1019
935
  var enqueueAssetForPreloading = (asset2) => {
1020
936
  if (!PRELOADED_ASSETS.has(asset2)) {
1021
937
  ASSETS_TO_PRELOAD.add(asset2);
1022
938
  }
1023
939
  };
1024
- var handleAssetsPreloading = async ({ request, limiter, preloadAudioBlocking, preloadImageBlocking }) => {
940
+ var handleAssetsPreloading = async ({
941
+ request,
942
+ limiter,
943
+ preloadAudioBlocking,
944
+ preloadImageBlocking
945
+ }) => {
1025
946
  const list = mapSet(ASSETS_TO_PRELOAD, (asset2) => {
1026
947
  return limiter(async () => {
1027
948
  const type = await getResourseType({
@@ -1096,9 +1017,9 @@ var huntAssets = ({ volume, lang, mode, characters, action, props, handle }) =>
1096
1017
  }
1097
1018
  return;
1098
1019
  }
1099
- if (action === "custom" && props[0].assets && props[0].assets.length > 0) {
1020
+ if (action === "custom" && props[0].assets) {
1100
1021
  for (const asset2 of props[0].assets) {
1101
- handle(asset2);
1022
+ isAsset(asset2) ? handle(asset2.source) : handle(asset2);
1102
1023
  }
1103
1024
  return;
1104
1025
  }
@@ -1106,12 +1027,97 @@ var huntAssets = ({ volume, lang, mode, characters, action, props, handle }) =>
1106
1027
  for (let i = 1; i < props.length; i++) {
1107
1028
  const data = props[i];
1108
1029
  if (Array.isArray(data)) {
1109
- handle(handleImageAsset(data[4]));
1030
+ handle(handleImageAsset(data[5]));
1110
1031
  }
1111
1032
  }
1112
1033
  }
1113
1034
  };
1114
1035
 
1036
+ // src/storage.ts
1037
+ var localStorageStorage = (options) => {
1038
+ return {
1039
+ async get() {
1040
+ const fallback = { saves: [], data: {}, meta: [] };
1041
+ try {
1042
+ const value = localStorage.getItem(options.key);
1043
+ return value ? JSON.parse(value) : fallback;
1044
+ } catch {
1045
+ return fallback;
1046
+ }
1047
+ },
1048
+ async set(data) {
1049
+ try {
1050
+ localStorage.setItem(options.key, JSON.stringify(data));
1051
+ } catch {
1052
+ }
1053
+ }
1054
+ };
1055
+ };
1056
+
1057
+ // src/store.ts
1058
+ var store = (current, subscribers = /* @__PURE__ */ new Set()) => {
1059
+ const subscribe = (cb) => {
1060
+ subscribers.add(cb), cb(current);
1061
+ return () => {
1062
+ subscribers.delete(cb);
1063
+ };
1064
+ };
1065
+ const push = (value) => {
1066
+ for (const cb of subscribers) cb(value);
1067
+ };
1068
+ const update = (fn) => {
1069
+ push(current = fn(current));
1070
+ };
1071
+ const set2 = (val) => {
1072
+ update(() => val);
1073
+ };
1074
+ const get = () => {
1075
+ return current;
1076
+ };
1077
+ return { subscribe, update, set: set2, get };
1078
+ };
1079
+
1080
+ // src/translation.ts
1081
+ var RGX = /{{(.*?)}}/g;
1082
+ var split = (input, delimeters) => {
1083
+ const output = [];
1084
+ for (const delimeter of delimeters) {
1085
+ if (!input) break;
1086
+ const [start, end] = input.split(delimeter, 2);
1087
+ output.push(start);
1088
+ input = end;
1089
+ }
1090
+ output.push(input);
1091
+ return output;
1092
+ };
1093
+ var flattenAllowedContent = (c, state) => {
1094
+ if (Array.isArray(c)) {
1095
+ return c.map((item) => flattenAllowedContent(item, state)).join("<br>");
1096
+ }
1097
+ if (typeof c === "function") {
1098
+ return flattenAllowedContent(c(state), state);
1099
+ }
1100
+ return c;
1101
+ };
1102
+ var replace = (input, data, pluralization, actions, pr) => {
1103
+ return input.replaceAll(RGX, (x, key, y) => {
1104
+ x = 0;
1105
+ y = data;
1106
+ const [pathstr, plural, action] = split(key.trim(), ["@", "%"]);
1107
+ if (!pathstr) {
1108
+ return "";
1109
+ }
1110
+ const path = pathstr.split(".");
1111
+ while (y && x < path.length) y = y[path[x++]];
1112
+ if (plural && pluralization && y && pr) {
1113
+ y = pluralization[plural][pr.select(y)];
1114
+ }
1115
+ const actionHandler = actions && action ? actions[action] : void 0;
1116
+ if (actionHandler) y = actionHandler(y);
1117
+ return y == null ? "" : y;
1118
+ });
1119
+ };
1120
+
1115
1121
  // src/novely.ts
1116
1122
  var novely = ({
1117
1123
  characters,
@@ -1238,7 +1244,9 @@ var novely = ({
1238
1244
  return language;
1239
1245
  }
1240
1246
  if (DEV3) {
1241
- throw new Error(`Attempt to use unsupported language "${language}". Supported languages: ${languages.join(", ")}.`);
1247
+ throw new Error(
1248
+ `Attempt to use unsupported language "${language}". Supported languages: ${languages.join(", ")}.`
1249
+ );
1242
1250
  }
1243
1251
  throw 0;
1244
1252
  };
@@ -1354,7 +1362,9 @@ var novely = ({
1354
1362
  const restore = async (save2) => {
1355
1363
  if (isEmpty(story)) {
1356
1364
  if (DEV3) {
1357
- throw new Error("Story is empty. You should call an `enine.script` function [https://novely.pages.dev/guide/story.html]");
1365
+ throw new Error(
1366
+ "Story is empty. You should call an `enine.script` function [https://novely.pages.dev/guide/story.html]"
1367
+ );
1358
1368
  }
1359
1369
  return;
1360
1370
  }
@@ -1374,7 +1384,10 @@ var novely = ({
1374
1384
  const [path] = stack.value = latest;
1375
1385
  renderer.ui.showScreen("game");
1376
1386
  const { queue, skip, skipPreserve } = getActionsFromPath(story, path, false);
1377
- const { run, keep: { keep, characters: characters2, audio: audio2 } } = createQueueProcessor(queue, {
1387
+ const {
1388
+ run,
1389
+ keep: { keep, characters: characters2, audio: audio2 }
1390
+ } = createQueueProcessor(queue, {
1378
1391
  skip,
1379
1392
  skipPreserve
1380
1393
  });
@@ -1424,7 +1437,9 @@ var novely = ({
1424
1437
  const isSaved = () => {
1425
1438
  const { saves } = storageData.get();
1426
1439
  const [currentPath, currentData] = stack.value;
1427
- return saves.some(([path, data2, [date, type2]]) => type2 === "manual" && times.has(date) && dequal(path, currentPath) && dequal(data2, currentData));
1440
+ return saves.some(
1441
+ ([path, data2, [date, type2]]) => type2 === "manual" && times.has(date) && dequal(path, currentPath) && dequal(data2, currentData)
1442
+ );
1428
1443
  };
1429
1444
  if (interacted > 1 && !force && askBeforeExit && !isSaved()) {
1430
1445
  renderer.ui.showExitPrompt();
@@ -1520,7 +1535,9 @@ var novely = ({
1520
1535
  const getLanguageDisplayName = (lang) => {
1521
1536
  const language = translation[lang];
1522
1537
  if (DEV3 && !language) {
1523
- throw new Error(`Attempt to use unsupported language "${language}". Supported languages: ${languages.join(", ")}.`);
1538
+ throw new Error(
1539
+ `Attempt to use unsupported language "${language}". Supported languages: ${languages.join(", ")}.`
1540
+ );
1524
1541
  }
1525
1542
  return capitalize(language.nameOverride || getIntlLanguageDisplayName(lang));
1526
1543
  };
@@ -1629,10 +1646,12 @@ var novely = ({
1629
1646
  showBackground({ ctx, push }, [background]) {
1630
1647
  if (isString(background) || isAsset(background)) {
1631
1648
  ctx.background({
1632
- "all": handleImageAsset(background)
1649
+ all: handleImageAsset(background)
1633
1650
  });
1634
1651
  } else {
1635
- ctx.background(Object.fromEntries(Object.entries(background).map(([media, asset2]) => [media, handleImageAsset(asset2)])));
1652
+ ctx.background(
1653
+ Object.fromEntries(Object.entries(background).map(([media, asset2]) => [media, handleImageAsset(asset2)]))
1654
+ );
1636
1655
  }
1637
1656
  push();
1638
1657
  },
@@ -1708,13 +1727,7 @@ var novely = ({
1708
1727
  return c || "";
1709
1728
  })();
1710
1729
  ctx.clearBlockingActions("dialog");
1711
- ctx.dialog(
1712
- templateReplace(content, data2),
1713
- templateReplace(name, data2),
1714
- character,
1715
- emotion,
1716
- forward
1717
- );
1730
+ ctx.dialog(templateReplace(content, data2), templateReplace(name, data2), character, emotion, forward);
1718
1731
  },
1719
1732
  function({ ctx, push }, [fn]) {
1720
1733
  const { restoring, goingBack, preview: preview2 } = ctx.meta;
@@ -1740,28 +1753,33 @@ var novely = ({
1740
1753
  const active$ = store(false);
1741
1754
  const visible$ = store(false);
1742
1755
  const update = () => {
1756
+ const lang = getLanguageFromStore(storageData);
1757
+ const state = getStateAtCtx(ctx);
1743
1758
  const activeValue = !active || active({
1744
- lang: getLanguageFromStore(storageData),
1745
- state: getStateAtCtx(ctx)
1759
+ lang,
1760
+ state
1746
1761
  });
1747
1762
  const visibleValue = !visible || visible({
1748
- lang: getLanguageFromStore(storageData),
1749
- state: getStateAtCtx(ctx)
1763
+ lang,
1764
+ state
1750
1765
  });
1751
1766
  active$.set(activeValue);
1752
1767
  visible$.set(visibleValue);
1753
1768
  };
1754
1769
  update();
1755
1770
  const onSelectGuarded = onSelect || noop;
1756
- const onSelectWrapped = async () => {
1757
- await onSelectGuarded();
1758
- update();
1771
+ const onSelectWrapped = () => {
1772
+ onSelectGuarded({
1773
+ recompute: update
1774
+ });
1759
1775
  };
1760
1776
  const imageValue = image ? handleImageAsset(image) : "";
1761
1777
  return [templateReplace(content, data2), active$, visible$, onSelectWrapped, imageValue];
1762
1778
  });
1763
1779
  if (DEV3 && transformedChoices.length === 0) {
1764
- throw new Error(`Running choice without variants to choose from, look at how to use Choice action properly [https://novely.pages.dev/guide/actions/choice#usage]`);
1780
+ throw new Error(
1781
+ `Running choice without variants to choose from, look at how to use Choice action properly [https://novely.pages.dev/guide/actions/choice#usage]`
1782
+ );
1765
1783
  }
1766
1784
  ctx.clearBlockingActions("choice");
1767
1785
  ctx.choices(templateReplace(question, data2), transformedChoices, (selected) => {
@@ -1816,7 +1834,7 @@ var novely = ({
1816
1834
  if (DEV3 && variants[val].length === 0) {
1817
1835
  throw new Error(`Attempt to go to empty variant "${val}"`);
1818
1836
  }
1819
- const stack = useStack(MAIN_CONTEXT_KEY);
1837
+ const stack = useStack(ctx);
1820
1838
  stack.value[0].push(["condition", val], [null, 0]);
1821
1839
  render(ctx);
1822
1840
  }
@@ -1827,12 +1845,7 @@ var novely = ({
1827
1845
  },
1828
1846
  input({ ctx, data: data2, forward }, [question, onInput, setup]) {
1829
1847
  ctx.clearBlockingActions("input");
1830
- ctx.input(
1831
- templateReplace(question, data2),
1832
- onInput,
1833
- setup || noop,
1834
- forward
1835
- );
1848
+ ctx.input(templateReplace(question, data2), onInput, setup || noop, forward);
1836
1849
  },
1837
1850
  custom({ ctx, push }, [fn]) {
1838
1851
  if (fn.requireUserAction) {
@@ -1872,7 +1885,9 @@ var novely = ({
1872
1885
  animateCharacter({ ctx, push }, [character, className]) {
1873
1886
  const classes = className.split(" ");
1874
1887
  if (DEV3 && classes.length === 0) {
1875
- throw new Error("Attempt to use AnimateCharacter without classes. Classes should be provided [https://novely.pages.dev/guide/actions/animateCharacter.html]");
1888
+ throw new Error(
1889
+ "Attempt to use AnimateCharacter without classes. Classes should be provided [https://novely.pages.dev/guide/actions/animateCharacter.html]"
1890
+ );
1876
1891
  }
1877
1892
  if (ctx.meta.preview) return;
1878
1893
  ctx.character(character).animate(classes);
@@ -1906,7 +1921,9 @@ var novely = ({
1906
1921
  },
1907
1922
  preload({ ctx, push }, [source]) {
1908
1923
  if (DEV3 && preloadAssets !== "lazy") {
1909
- console.error(`You do not need a preload action becase "preloadAssets" strategy was set to "${preloadAssets}"`);
1924
+ console.error(
1925
+ `You do not need a preload action becase "preloadAssets" strategy was set to "${preloadAssets}"`
1926
+ );
1910
1927
  }
1911
1928
  if (!ctx.meta.goingBack && !ctx.meta.restoring && !PRELOADED_ASSETS.has(source)) {
1912
1929
  PRELOADED_ASSETS.add(renderer.misc.preloadImage(source));
@@ -1952,21 +1969,15 @@ var novely = ({
1952
1969
  interacted = value ? interacted + 1 : 0;
1953
1970
  };
1954
1971
  const templateReplace = (content, values) => {
1955
- const { data: data2, meta: [lang] } = storageData.get();
1972
+ const {
1973
+ data: data2,
1974
+ meta: [lang]
1975
+ } = storageData.get();
1956
1976
  const obj = values || data2;
1957
- const str2 = flattenAllowedContent(
1958
- !isFunction(content) && !isString(content) ? content[lang] : content,
1959
- obj
1960
- );
1977
+ const str2 = flattenAllowedContent(!isFunction(content) && !isString(content) ? content[lang] : content, obj);
1961
1978
  const t2 = translation[lang];
1962
1979
  const pluralRules = (t2.plural || t2.actions) && new Intl.PluralRules(t2.tag || lang);
1963
- return replace(
1964
- str2,
1965
- obj,
1966
- t2.plural,
1967
- t2.actions,
1968
- pluralRules
1969
- );
1980
+ return replace(str2, obj, t2.plural, t2.actions, pluralRules);
1970
1981
  };
1971
1982
  const data = (value) => {
1972
1983
  const _data = storageData.get().data;
@@ -1990,7 +2001,9 @@ var novely = ({
1990
2001
  const setStorageData = (data2) => {
1991
2002
  if (destroyed) {
1992
2003
  if (DEV3) {
1993
- throw new Error(`function \`setStorageData\` was called after novely instance was destroyed. Data is not updater nor synced after destroy.`);
2004
+ throw new Error(
2005
+ `function \`setStorageData\` was called after novely instance was destroyed. Data is not updater nor synced after destroy.`
2006
+ );
1994
2007
  }
1995
2008
  return;
1996
2009
  }
@@ -2050,7 +2063,7 @@ var novely = ({
2050
2063
  * @example
2051
2064
  * ```ts
2052
2065
  * import type { ConditionParams, StateFunction } from '@novely/core';
2053
- *
2066
+ *
2054
2067
  * const conditionCheck = (state: StateFunction<ConditionParams<typeof engine.typeEssintials>>) => {
2055
2068
  * return state.age >= 18;
2056
2069
  * }