@novely/core 0.50.0 → 0.51.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/index.d.ts CHANGED
@@ -4,6 +4,10 @@ type Stored<T> = {
4
4
  set: (val: T) => void;
5
5
  get: () => T;
6
6
  };
7
+ type Derived<T> = {
8
+ subscribe: (cb: (value: T) => void) => () => void;
9
+ get: () => T;
10
+ };
7
11
 
8
12
  declare const RU: {
9
13
  NewGame: string;
@@ -100,9 +104,9 @@ type Context = {
100
104
  text: (str: string, resolve: () => void) => void;
101
105
  vibrate: (pattern: VibratePattern) => void;
102
106
  audio: {
103
- voice: (source: string) => void;
107
+ voice: (source: string, paused: Derived<boolean>) => void;
104
108
  voiceStop: () => void;
105
- music: (source: string, method: 'music' | 'sound') => AudioHandle;
109
+ music: (source: string, paused: Derived<boolean>, method: 'music' | 'sound') => AudioHandle;
106
110
  /**
107
111
  * Stop all sounds
108
112
  */
@@ -224,6 +228,7 @@ type RendererInit<$Language extends Lang, $Characters extends Record<string, Cha
224
228
  getCharacterAssets: (character: string, emotion: string) => string[];
225
229
  getDialogOverview: () => Promise<DialogOverview>;
226
230
  getResourseType: (url: string) => Promise<'image' | 'audio' | 'other'>;
231
+ setLanguage: (language: string) => void;
227
232
  };
228
233
 
229
234
  type LocalStorageStorageSettings = {
@@ -292,6 +297,8 @@ type Assign<A extends object, B extends object> = Pick<A, Exclude<keyof A, keyof
292
297
  type NonEmptyRecord<T extends Record<PropertyKey, unknown>> = keyof T extends never ? never : T;
293
298
  type CoreData = {
294
299
  dataLoaded: boolean;
300
+ paused: boolean;
301
+ focused: boolean;
295
302
  };
296
303
  type StackHolder = Save[] & {
297
304
  previous: Save | undefined;
@@ -370,6 +377,7 @@ type StoryOptionsDynamic = {
370
377
  load: (scene: string) => Promise<Story>;
371
378
  };
372
379
  type StoryOptions = StoryOptionsStatic | StoryOptionsDynamic;
380
+ type OnLanguageChange<$Lang extends Lang> = (language: $Lang) => void;
373
381
  interface NovelyInit<$Language extends Lang, $Characters extends Record<string, Character<NoInfer<$Language>>>, $State extends State, $Data extends Data, $Actions extends Record<string, (...args: any[]) => ValidAction>> {
374
382
  /**
375
383
  * An object containing the characters in the game.
@@ -590,9 +598,13 @@ interface NovelyInit<$Language extends Lang, $Characters extends Record<string,
590
598
  */
591
599
  defaultTypewriterSpeed?: TypewriterSpeed;
592
600
  /**
593
- *
601
+ * Options to control story loading behaviour
594
602
  */
595
603
  storyOptions?: StoryOptions;
604
+ /**
605
+ * Will be called ONLY when language was changed by player
606
+ */
607
+ onLanguageChange?: OnLanguageChange<NoInfer<$Language>>;
596
608
  }
597
609
  type StateFunction<S extends State> = {
598
610
  (value: DeepPartial<S> | ((prev: S) => S)): void;
@@ -670,11 +682,15 @@ type CustomHandlerFunctionParameters<L extends string, S extends State> = {
670
682
  */
671
683
  getDomNodes: CustomHandlerFunctionGetFn;
672
684
  /**
673
- * Function to get current Path. Path can be mutated. Can be helpful when making complex custom actions.
685
+ * @deprecated use `getSave` instead
686
+ */
687
+ getPath: () => Path;
688
+ /**
689
+ * Function to get current Save. It can be mutated.
674
690
  *
675
691
  * Only use it when you know what you do
676
692
  */
677
- getPath: () => Path;
693
+ getSave: () => Save<S>;
678
694
  /**
679
695
  * Renderer Context
680
696
  */
@@ -715,6 +731,10 @@ type CustomHandlerFunctionParameters<L extends string, S extends State> = {
715
731
  * Function to replace template content
716
732
  */
717
733
  templateReplace: (content: TextContent<L, State>, values?: State) => string;
734
+ /**
735
+ * Is game currently paused
736
+ */
737
+ paused: Derived<boolean>;
718
738
  };
719
739
  type CustomHandlerFunction<L extends string, S extends State> = (parameters: CustomHandlerFunctionParameters<L, S>) => Thenable<void>;
720
740
  type CustomHandlerCalling = {
@@ -921,7 +941,7 @@ type TypesFromEngine<T> = T extends {
921
941
  types: EngineTypes<infer $Lang, infer $State, infer $Data, infer $Characters> | null;
922
942
  } ? EngineTypes<$Lang, $State, $Data, $Characters> : never;
923
943
 
924
- declare const novely: <$Language extends string, $Characters extends Record<string, Character<$Language>>, $State extends State, $Data extends Data, $Actions extends Record<string, (...args: any[]) => ValidAction>>({ characters, characterAssetSizes, defaultEmotions, storage, storageDelay, renderer: createRenderer, initialScreen, translation, state: defaultState, data: defaultData, autosaves, migrations, throttleTimeout, getLanguage, overrideLanguage, askBeforeExit, preloadAssets, parallelAssetsDownloadLimit, fetch: request, cloneFunction: clone, saveOnUnload, startKey, defaultTypewriterSpeed, storyOptions, }: NovelyInit<$Language, $Characters, $State, $Data, $Actions>) => {
944
+ declare const novely: <$Language extends string, $Characters extends Record<string, Character<$Language>>, $State extends State, $Data extends Data, $Actions extends Record<string, (...args: any[]) => ValidAction>>({ characters, characterAssetSizes, defaultEmotions, storage, storageDelay, renderer: createRenderer, initialScreen, translation, state: defaultState, data: defaultData, autosaves, migrations, throttleTimeout, getLanguage, overrideLanguage, askBeforeExit, preloadAssets, parallelAssetsDownloadLimit, fetch: request, cloneFunction: clone, saveOnUnload, startKey, defaultTypewriterSpeed, storyOptions, onLanguageChange, }: NovelyInit<$Language, $Characters, $State, $Data, $Actions>) => {
925
945
  /**
926
946
  * Function to set game script
927
947
  *
@@ -1045,6 +1065,33 @@ declare const novely: <$Language extends string, $Characters extends Record<stri
1045
1065
  * ```
1046
1066
  */
1047
1067
  setStorageData: (data: StorageData<$Language, $Data>) => void;
1068
+ /**
1069
+ * Function to control paused state. Custom Actions are provided with `paused` store they can subscribe to.
1070
+ * This function will notify Custom Actions. Pause state can be used when showing ads.
1071
+ * @example
1072
+ * ```ts
1073
+ * sdk.on('pause' () => engine.setPaused(true));
1074
+ * sdk.on('resume', () => engine.setPaused(false));
1075
+ * ```
1076
+ */
1077
+ setPaused: (paused: boolean) => void;
1078
+ /**
1079
+ * Function to control focused state. It will affect `paused` store passed to Custom Actions.
1080
+ * This function can be used to pause game when it's not focused.
1081
+ * @example
1082
+ * ```ts
1083
+ * import { pauseOnBlur } from '@novely/core';
1084
+ *
1085
+ * // Will subscribe to blur/focus events and call `setFocused`
1086
+ * pauseOnBlur(engine);
1087
+ *
1088
+ * // OR
1089
+ *
1090
+ * sdk.on('focus' () => engine.setFocused(true));
1091
+ * sdk.on('blur', () => engine.setFocused(false));
1092
+ * ```
1093
+ */
1094
+ setFocused: (focused: boolean) => void;
1048
1095
  };
1049
1096
 
1050
1097
  type Part = Record<string, (...args: any[]) => ValidAction>;
@@ -1093,4 +1140,10 @@ declare const asset: {
1093
1140
  audio(source: string): NovelyAsset;
1094
1141
  };
1095
1142
 
1096
- export { type ActionChoiceChoice, type ActionChoiceChoiceObject, type ActionInputOnInputMeta, type ActionInputSetup, type ActionInputSetupCleanup, type ActionProxy, type AllowedContent, type AudioHandle, type BackgroundImage, type BaseTranslationStrings, type Character, type CharacterAssetSizes, type CharacterHandle, type CharactersData, type ChoiceCheckFunction, type ChoiceCheckFunctionProps, type ChoiceOnSelectFunction, type ChoiceOnSelectFunctionProps, type ChoiceParams, type ConditionCheckFunction, type ConditionParams, type Context, type CoreData, type CustomActionHandle, type CustomHandler, type CustomHandlerFunction, type CustomHandlerFunctionGetFn, type CustomHandlerFunctionParameters, type CustomHandlerGetResult, type CustomHandlerGetResultDataFunction, type CustomHandlerInfo, type Data, type DeepPartial, type DefaultActionProxy, EN, type Emotions, type EngineTypes, type FunctionParams, type FunctionableValue, type GetActionParameters, type InputHandler, type Lang, type NovelyAsset, type NovelyInit, type NovelyScreen, type NovelyStorage, type Path, type PathItem, type PluralType, type Pluralization, RU, type Renderer, type RendererInit, type RendererInitPreviewReturn, type Save, type Stack, type StackHolder, type State, type StateFunction, type StorageData, type StorageMeta, type Stored, type Story, type TextContent, type Thenable, type TranslationActions, type TypeEssentials, type TypesFromEngine, type TypewriterSpeed, type ValidAction, asset, extendAction, localStorageStorage, novely };
1143
+ declare const pauseOnBlur: (engine: {
1144
+ setFocused: (focused: boolean) => void;
1145
+ }) => {
1146
+ unsubscribe: () => void;
1147
+ };
1148
+
1149
+ export { type ActionChoiceChoice, type ActionChoiceChoiceObject, type ActionInputOnInputMeta, type ActionInputSetup, type ActionInputSetupCleanup, type ActionProxy, type AllowedContent, type AudioHandle, type BackgroundImage, type BaseTranslationStrings, type Character, type CharacterAssetSizes, type CharacterHandle, type CharactersData, type ChoiceCheckFunction, type ChoiceCheckFunctionProps, type ChoiceOnSelectFunction, type ChoiceOnSelectFunctionProps, type ChoiceParams, type ConditionCheckFunction, type ConditionParams, type Context, type CoreData, type CustomActionHandle, type CustomHandler, type CustomHandlerFunction, type CustomHandlerFunctionGetFn, type CustomHandlerFunctionParameters, type CustomHandlerGetResult, type CustomHandlerGetResultDataFunction, type CustomHandlerInfo, type Data, type DeepPartial, type DefaultActionProxy, type Derived, EN, type Emotions, type EngineTypes, type FunctionParams, type FunctionableValue, type GetActionParameters, type InputHandler, type Lang, type NovelyAsset, type NovelyInit, type NovelyScreen, type NovelyStorage, type Path, type PathItem, type PluralType, type Pluralization, RU, type Renderer, type RendererInit, type RendererInitPreviewReturn, type Save, type Stack, type StackHolder, type State, type StateFunction, type StorageData, type StorageMeta, type Stored, type Story, type TextContent, type Thenable, type TranslationActions, type TypeEssentials, type TypesFromEngine, type TypewriterSpeed, type ValidAction, asset, extendAction, localStorageStorage, novely, pauseOnBlur };
package/dist/index.js CHANGED
@@ -951,6 +951,48 @@ var getCharactersData = (characters) => {
951
951
  return Object.fromEntries(mapped);
952
952
  };
953
953
 
954
+ // src/store.ts
955
+ var store = (current, subscribers = /* @__PURE__ */ new Set()) => {
956
+ const subscribe = (cb) => {
957
+ subscribers.add(cb), cb(current);
958
+ return () => {
959
+ subscribers.delete(cb);
960
+ };
961
+ };
962
+ const push = (value) => {
963
+ for (const cb of subscribers) cb(value);
964
+ };
965
+ const update = (fn) => {
966
+ push(current = fn(current));
967
+ };
968
+ const set2 = (val) => {
969
+ update(() => val);
970
+ };
971
+ const get = () => {
972
+ return current;
973
+ };
974
+ return { subscribe, update, set: set2, get };
975
+ };
976
+ var derive = (input, map) => {
977
+ return {
978
+ get: () => map(input.get()),
979
+ subscribe: (subscriber) => {
980
+ return input.subscribe((value) => {
981
+ return subscriber(map(value));
982
+ });
983
+ }
984
+ };
985
+ };
986
+ var immutable = (value) => {
987
+ return {
988
+ get: () => value,
989
+ subscribe: (subscriber) => {
990
+ subscriber(value);
991
+ return noop;
992
+ }
993
+ };
994
+ };
995
+
954
996
  // src/custom-action.ts
955
997
  var createCustomActionNode = (id) => {
956
998
  const div = document.createElement("div");
@@ -978,7 +1020,8 @@ var handleCustomAction = (ctx, fn, {
978
1020
  setClear,
979
1021
  remove: renderersRemove,
980
1022
  getStack: getStack2,
981
- templateReplace
1023
+ templateReplace,
1024
+ paused
982
1025
  }) => {
983
1026
  const holder = getCustomActionHolder(ctx, fn);
984
1027
  const flags = {
@@ -1023,6 +1066,9 @@ var handleCustomAction = (ctx, fn, {
1023
1066
  const getPath = () => {
1024
1067
  return stack.value[0];
1025
1068
  };
1069
+ const getSave = () => {
1070
+ return stack.value;
1071
+ };
1026
1072
  return fn({
1027
1073
  flags,
1028
1074
  lang,
@@ -1033,8 +1079,10 @@ var handleCustomAction = (ctx, fn, {
1033
1079
  remove,
1034
1080
  rendererContext: ctx,
1035
1081
  getDomNodes,
1082
+ getSave,
1036
1083
  getPath,
1037
- contextKey: ctx.id
1084
+ contextKey: ctx.id,
1085
+ paused: flags.preview ? immutable(false) : paused
1038
1086
  });
1039
1087
  };
1040
1088
 
@@ -1164,29 +1212,6 @@ var localStorageStorage = (options) => {
1164
1212
  };
1165
1213
  };
1166
1214
 
1167
- // src/store.ts
1168
- var store = (current, subscribers = /* @__PURE__ */ new Set()) => {
1169
- const subscribe = (cb) => {
1170
- subscribers.add(cb), cb(current);
1171
- return () => {
1172
- subscribers.delete(cb);
1173
- };
1174
- };
1175
- const push = (value) => {
1176
- for (const cb of subscribers) cb(value);
1177
- };
1178
- const update = (fn) => {
1179
- push(current = fn(current));
1180
- };
1181
- const set2 = (val) => {
1182
- update(() => val);
1183
- };
1184
- const get = () => {
1185
- return current;
1186
- };
1187
- return { subscribe, update, set: set2, get };
1188
- };
1189
-
1190
1215
  // src/translation.ts
1191
1216
  var RGX = /{{(.*?)}}/g;
1192
1217
  var split = (input, delimeters) => {
@@ -1330,6 +1355,11 @@ var getDialogOverview = async function() {
1330
1355
  return entries;
1331
1356
  };
1332
1357
 
1358
+ // src/utilities/document.ts
1359
+ var setDocumentLanguage = (language) => {
1360
+ document.documentElement.lang = language;
1361
+ };
1362
+
1333
1363
  // src/novely.ts
1334
1364
  var novely = ({
1335
1365
  characters,
@@ -1355,7 +1385,8 @@ var novely = ({
1355
1385
  saveOnUnload = true,
1356
1386
  startKey = "start",
1357
1387
  defaultTypewriterSpeed = DEFAULT_TYPEWRITER_SPEED,
1358
- storyOptions = { mode: "static" }
1388
+ storyOptions = { mode: "static" },
1389
+ onLanguageChange
1359
1390
  }) => {
1360
1391
  const languages = Object.keys(translation);
1361
1392
  const limitScript = pLimit(1);
@@ -1412,6 +1443,7 @@ var novely = ({
1412
1443
  const getLanguageWithoutParameters = () => {
1413
1444
  const language = getLanguage2(languages, getLanguage);
1414
1445
  if (languages.includes(language)) {
1446
+ setDocumentLanguage(language);
1415
1447
  return language;
1416
1448
  }
1417
1449
  if (DEV5) {
@@ -1428,8 +1460,11 @@ var novely = ({
1428
1460
  };
1429
1461
  const storageData = store(initialData);
1430
1462
  const coreData = store({
1431
- dataLoaded: false
1463
+ dataLoaded: false,
1464
+ paused: false,
1465
+ focused: document.visibilityState === "visible"
1432
1466
  });
1467
+ const paused = derive(coreData, (s) => s.paused || !s.focused);
1433
1468
  const onDataLoadedPromise = async ({ cancelled }) => {
1434
1469
  if (cancelled) {
1435
1470
  dataLoaded.promise.then(onDataLoadedPromise);
@@ -1775,6 +1810,18 @@ var novely = ({
1775
1810
  }
1776
1811
  return String(c);
1777
1812
  };
1813
+ const setLanguage = (lang) => {
1814
+ storageData.update((prev) => {
1815
+ if (languages.includes(lang)) {
1816
+ prev.meta[0] = lang;
1817
+ }
1818
+ if (lang === prev.meta[0]) {
1819
+ setDocumentLanguage(lang);
1820
+ onLanguageChange?.(lang);
1821
+ }
1822
+ return prev;
1823
+ });
1824
+ };
1778
1825
  const renderer = createRenderer({
1779
1826
  mainContextKey: MAIN_CONTEXT_KEY,
1780
1827
  characters: getCharactersData(characters),
@@ -1804,7 +1851,8 @@ var novely = ({
1804
1851
  getStack: () => useStack(MAIN_CONTEXT_KEY),
1805
1852
  templateReplace: (...args) => templateReplace(...args)
1806
1853
  }),
1807
- getResourseType: getResourseTypeWrapper
1854
+ getResourseType: getResourseTypeWrapper,
1855
+ setLanguage
1808
1856
  });
1809
1857
  const useStack = createUseStackFunction(renderer);
1810
1858
  useStack(MAIN_CONTEXT_KEY).push(initial);
@@ -1882,27 +1930,27 @@ var novely = ({
1882
1930
  push();
1883
1931
  },
1884
1932
  playMusic({ ctx, push }, [source]) {
1885
- ctx.audio.music(unwrapAudioAsset(source), "music").play(true);
1933
+ ctx.audio.music(unwrapAudioAsset(source), paused, "music").play(true);
1886
1934
  push();
1887
1935
  },
1888
1936
  pauseMusic({ ctx, push }, [source]) {
1889
- ctx.audio.music(unwrapAudioAsset(source), "music").pause();
1937
+ ctx.audio.music(unwrapAudioAsset(source), paused, "music").pause();
1890
1938
  push();
1891
1939
  },
1892
1940
  stopMusic({ ctx, push }, [source]) {
1893
- ctx.audio.music(unwrapAudioAsset(source), "music").stop();
1941
+ ctx.audio.music(unwrapAudioAsset(source), paused, "music").stop();
1894
1942
  push();
1895
1943
  },
1896
1944
  playSound({ ctx, push }, [source, loop]) {
1897
- ctx.audio.music(unwrapAudioAsset(source), "sound").play(loop || false);
1945
+ ctx.audio.music(unwrapAudioAsset(source), paused, "sound").play(loop || false);
1898
1946
  push();
1899
1947
  },
1900
1948
  pauseSound({ ctx, push }, [source]) {
1901
- ctx.audio.music(unwrapAudioAsset(source), "sound").pause();
1949
+ ctx.audio.music(unwrapAudioAsset(source), paused, "sound").pause();
1902
1950
  push();
1903
1951
  },
1904
1952
  stopSound({ ctx, push }, [source]) {
1905
- ctx.audio.music(unwrapAudioAsset(source), "sound").stop();
1953
+ ctx.audio.music(unwrapAudioAsset(source), paused, "sound").stop();
1906
1954
  push();
1907
1955
  },
1908
1956
  voice({ ctx, push }, [source]) {
@@ -1912,7 +1960,7 @@ var novely = ({
1912
1960
  push();
1913
1961
  return;
1914
1962
  }
1915
- ctx.audio.voice(unwrapAudioAsset(audioSource));
1963
+ ctx.audio.voice(unwrapAudioAsset(audioSource), paused);
1916
1964
  push();
1917
1965
  },
1918
1966
  stopVoice({ ctx, push }) {
@@ -2069,6 +2117,7 @@ var novely = ({
2069
2117
  state,
2070
2118
  lang,
2071
2119
  getStack: useStack,
2120
+ paused,
2072
2121
  templateReplace
2073
2122
  });
2074
2123
  const next2 = () => {
@@ -2347,6 +2396,7 @@ var novely = ({
2347
2396
  * Data updates still will work in case Novely already was loaded
2348
2397
  */
2349
2398
  destroy() {
2399
+ if (destroyed) return;
2350
2400
  dataLoaded.cancel();
2351
2401
  UIInstance.unmount();
2352
2402
  removeEventListener("beforeunload", throttledShortOnStorageDataChange);
@@ -2377,7 +2427,44 @@ var novely = ({
2377
2427
  * }
2378
2428
  * ```
2379
2429
  */
2380
- setStorageData
2430
+ setStorageData,
2431
+ /**
2432
+ * Function to control paused state. Custom Actions are provided with `paused` store they can subscribe to.
2433
+ * This function will notify Custom Actions. Pause state can be used when showing ads.
2434
+ * @example
2435
+ * ```ts
2436
+ * sdk.on('pause' () => engine.setPaused(true));
2437
+ * sdk.on('resume', () => engine.setPaused(false));
2438
+ * ```
2439
+ */
2440
+ setPaused: (paused2) => {
2441
+ coreData.update((prev) => {
2442
+ prev.paused = paused2;
2443
+ return prev;
2444
+ });
2445
+ },
2446
+ /**
2447
+ * Function to control focused state. It will affect `paused` store passed to Custom Actions.
2448
+ * This function can be used to pause game when it's not focused.
2449
+ * @example
2450
+ * ```ts
2451
+ * import { pauseOnBlur } from '@novely/core';
2452
+ *
2453
+ * // Will subscribe to blur/focus events and call `setFocused`
2454
+ * pauseOnBlur(engine);
2455
+ *
2456
+ * // OR
2457
+ *
2458
+ * sdk.on('focus' () => engine.setFocused(true));
2459
+ * sdk.on('blur', () => engine.setFocused(false));
2460
+ * ```
2461
+ */
2462
+ setFocused: (focused) => {
2463
+ coreData.update((prev) => {
2464
+ prev.focused = focused;
2465
+ return prev;
2466
+ });
2467
+ }
2381
2468
  };
2382
2469
  };
2383
2470
 
@@ -2466,12 +2553,53 @@ var EN = {
2466
2553
  Close: "Close",
2467
2554
  DialogOverview: "Dialog Overview"
2468
2555
  };
2556
+
2557
+ // src/browser-events.ts
2558
+ var BLUR_HANDLERS = /* @__PURE__ */ new Set();
2559
+ var FOCUS_HANDLERS = /* @__PURE__ */ new Set();
2560
+ var registerEventListeners = (listeners) => {
2561
+ BLUR_HANDLERS.add(listeners.blur);
2562
+ FOCUS_HANDLERS.add(listeners.focus);
2563
+ return () => {
2564
+ BLUR_HANDLERS.delete(listeners.blur);
2565
+ FOCUS_HANDLERS.delete(listeners.focus);
2566
+ };
2567
+ };
2568
+ addEventListener("focus", function(event) {
2569
+ for (const handler of FOCUS_HANDLERS) {
2570
+ try {
2571
+ handler.call(this.document, event);
2572
+ } catch {
2573
+ }
2574
+ }
2575
+ });
2576
+ addEventListener("blur", function(event) {
2577
+ for (const handler of BLUR_HANDLERS) {
2578
+ try {
2579
+ handler.call(this.document, event);
2580
+ } catch {
2581
+ }
2582
+ }
2583
+ });
2584
+ var pauseOnBlur = (engine) => {
2585
+ return {
2586
+ unsubscribe: registerEventListeners({
2587
+ focus: () => {
2588
+ engine.setFocused(true);
2589
+ },
2590
+ blur: () => {
2591
+ engine.setFocused(false);
2592
+ }
2593
+ })
2594
+ };
2595
+ };
2469
2596
  export {
2470
2597
  EN,
2471
2598
  RU,
2472
2599
  asset,
2473
2600
  extendAction,
2474
2601
  localStorageStorage,
2475
- novely
2602
+ novely,
2603
+ pauseOnBlur
2476
2604
  };
2477
2605
  //# sourceMappingURL=index.js.map