@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.d.ts CHANGED
@@ -1,11 +1,3 @@
1
- type Name<Keys extends string = string> = string | Record<Keys, string>;
2
- type Emotions<Keys extends string = string> = Record<Keys, string | string[]>;
3
- type Character<LanguageKeys extends string = string> = {
4
- name: Name<LanguageKeys>;
5
- color: string;
6
- emotions: Emotions;
7
- };
8
-
9
1
  interface LocalStorageStorageSettings {
10
2
  key: string;
11
3
  }
@@ -17,7 +9,7 @@ declare const localStorageStorage: (options: LocalStorageStorageSettings) => Sto
17
9
 
18
10
  type PluralType = Intl.LDMLPluralRule;
19
11
  type Pluralization = Partial<Record<PluralType, string>>;
20
- type AllowedContent = string | ((state: State) => string | string[]) | string[] | (string | ((state: State) => string | string[]))[];
12
+ type AllowedContent = string | ((state: State | Data) => string | string[]) | string[] | (string | ((state: State | Data) => string | string[]))[];
21
13
  type TranslationActions = Partial<Record<string, (str: string) => string>>;
22
14
 
23
15
  declare const RU: {
@@ -215,6 +207,7 @@ type RendererInit = {
215
207
  preview: (save: Save<State>, name: string) => Promise<void>;
216
208
  removeContext: (name: string) => void;
217
209
  getStateFunction: (context: string) => StateFunction<State>;
210
+ getLanguageDisplayName: (lang: Lang) => string;
218
211
  };
219
212
 
220
213
  declare const getLanguage: (languages: string[]) => string;
@@ -236,7 +229,7 @@ type Save<S extends State = State> = [
236
229
  type Lang = string;
237
230
  type TypewriterSpeed = 'Slow' | 'Medium' | 'Fast' | 'Auto';
238
231
  type SoundVolume = number;
239
- type StorageMeta<L extends string = string> = [
232
+ type StorageMeta<L extends Lang = string> = [
240
233
  lang: L,
241
234
  typewriter_speed: TypewriterSpeed,
242
235
  music_volume: SoundVolume,
@@ -244,7 +237,7 @@ type StorageMeta<L extends string = string> = [
244
237
  voice_volume: SoundVolume
245
238
  ];
246
239
  type Migration = (save: unknown) => unknown;
247
- type StorageData<L extends string = string, D extends Data = Data> = {
240
+ type StorageData<L extends Lang = string, D extends Data = Data> = {
248
241
  saves: Save[];
249
242
  data: D;
250
243
  meta: StorageMeta<L>;
@@ -275,6 +268,10 @@ type TranslationDescription = {
275
268
  * IETF BCP 47 language tag
276
269
  */
277
270
  tag?: string;
271
+ /**
272
+ * Custom name
273
+ */
274
+ nameOverride?: string;
278
275
  plural?: Record<string, Pluralization>;
279
276
  actions?: TranslationActions;
280
277
  };
@@ -298,6 +295,36 @@ interface NovelyInit<$Language extends Lang, Characters extends Record<string, C
298
295
  * ```
299
296
  */
300
297
  characters: Characters;
298
+ /**
299
+ * Define default emotions for characters
300
+ * @example
301
+ * ```ts
302
+ * const engine = novely({
303
+ * characters: {
304
+ * Yuki: {
305
+ * name: 'Yuki',
306
+ * color: '#f595f6',
307
+ * emotions: {
308
+ * normal: './normal.png'
309
+ * }
310
+ * }
311
+ * },
312
+ * defaultEmotions: {
313
+ * Yuki: 'normal'
314
+ * }
315
+ * });
316
+ *
317
+ * engine.script({
318
+ * start: [
319
+ * // Without emotion!
320
+ * engine.action.showCharacter('Yuki')
321
+ * ]
322
+ * })
323
+ * ```
324
+ */
325
+ defaultEmotions?: {
326
+ [Character in keyof NoInfer<Characters>]?: (keyof NoInfer<Characters>[Character]['emotions'] & string);
327
+ };
301
328
  /**
302
329
  * An object that provides access to the game's storage system.
303
330
  * @default localStorage // at key `novely-game-storage`
@@ -409,12 +436,40 @@ interface NovelyInit<$Language extends Lang, Characters extends Record<string, C
409
436
  * @default true
410
437
  */
411
438
  saveOnUnload?: boolean | 'prod';
439
+ /**
440
+ * The key that signifies the start of the game. It is not recommended to override this parameter.
441
+ *
442
+ * @default 'start'
443
+ * @example
444
+ * ```ts
445
+ * const engine = novely({
446
+ * ...,
447
+ * startKey: 'PART_1'
448
+ * })
449
+ *
450
+ * engine.script({
451
+ * // now game will start from here
452
+ * PART_1: [
453
+ *
454
+ * ]
455
+ * })
456
+ * ```
457
+ */
458
+ startKey?: 'start' | (string & Record<never, never>);
412
459
  }
413
460
  type StateFunction<S extends State> = {
414
461
  (value: DeepPartial<S> | ((prev: S) => S)): void;
415
462
  (): S;
416
463
  };
417
464
 
465
+ type Name<$Lang extends Lang> = string | Record<$Lang, string>;
466
+ type Emotions<Emotion extends string = string> = Record<Emotion, string | string[]>;
467
+ type Character<$Lang extends Lang = string> = {
468
+ name: Name<$Lang>;
469
+ color: string;
470
+ emotions: Emotions;
471
+ };
472
+
418
473
  type ValidAction = ['choice', number] | ['clear', Set<keyof DefaultActionProxy>?, Set<string>?, {
419
474
  music: Set<string>;
420
475
  sounds: Set<string>;
@@ -515,10 +570,11 @@ type ChoiceCheckFunctionProps<L extends string, S extends State> = {
515
570
  type ChoiceCheckFunction<L extends string, S extends State> = {
516
571
  (props: ChoiceCheckFunctionProps<L, S>): boolean;
517
572
  };
573
+ type ConditionCheckFunction<S extends State, R extends string | true | false> = (state: S) => R;
518
574
  type FunctionAction<L extends string, S extends State> = (props: FunctionActionProps<L, S>) => Thenable<void>;
519
575
  type ActionInputSetup = (input: HTMLInputElement, cleanup: (cb: () => void) => void) => void;
520
576
  type BackgroundImage = Partial<Record<'portrait' | 'landscape' | 'all', string>> & Record<string, string>;
521
- type ActionProxy<Characters extends Record<string, Character>, Languages extends string, S extends State> = {
577
+ type ActionProxy<Characters extends Record<string, Character>, Languages extends Lang, S extends State> = {
522
578
  choice: {
523
579
  (...choices: [name: TextContent<Languages, S>, actions: ValidAction[], active?: ChoiceCheckFunction<Languages, S>][]): ValidAction;
524
580
  (question: TextContent<Languages, S>, ...choices: [name: TextContent<Languages, S>, actions: ValidAction[], active?: ChoiceCheckFunction<Languages, S>][]): ValidAction;
@@ -527,7 +583,7 @@ type ActionProxy<Characters extends Record<string, Character>, Languages extends
527
583
  music: Set<string>;
528
584
  sounds: Set<string>;
529
585
  }) => ValidAction;
530
- condition: <T extends string | true | false>(condition: (state: S) => T, variants: Record<T extends true ? 'true' : T extends false ? 'false' : T, ValidAction[]>) => ValidAction;
586
+ condition: <T extends string | true | false>(condition: ConditionCheckFunction<S, T>, variants: Record<T extends true ? 'true' : T extends false ? 'false' : T, ValidAction[]>) => ValidAction;
531
587
  exit: () => ValidAction;
532
588
  dialog: {
533
589
  <C extends keyof Characters>(character: C, content: TextContent<Languages, S>, emotion?: keyof Characters[C]['emotions']): ValidAction;
@@ -553,7 +609,7 @@ type ActionProxy<Characters extends Record<string, Character>, Languages extends
553
609
  stopVoice: () => ValidAction;
554
610
  jump: (scene: string) => ValidAction;
555
611
  showCharacter: {
556
- <C extends keyof Characters>(character: C, emotion: keyof Characters[C]['emotions'], className?: string, style?: string): ValidAction;
612
+ <C extends keyof Characters>(character: C, emotion?: keyof Characters[C]['emotions'], className?: string, style?: string): ValidAction;
557
613
  };
558
614
  hideCharacter: (character: keyof Characters, className?: string, style?: string, duration?: number) => ValidAction;
559
615
  animateCharacter: (character: keyof Characters, timeout: number, ...classes: string[]) => ValidAction;
@@ -567,10 +623,12 @@ type ActionProxy<Characters extends Record<string, Character>, Languages extends
567
623
  preload: (source: string) => ValidAction;
568
624
  block: (scene: string) => ValidAction;
569
625
  };
570
- type DefaultActionProxy = ActionProxy<Record<string, Character>, string, State>;
626
+ type DefaultActionProxy = ActionProxy<Record<string, Character>, Lang, State>;
571
627
  type GetActionParameters<T extends Capitalize<keyof DefaultActionProxy>> = Parameters<DefaultActionProxy[Uncapitalize<T>]>;
572
628
 
573
- declare const novely: <Languages extends string, Characters extends Record<string, Character<Languages>>, StateScheme extends State, DataScheme extends Data>({ characters, storage, storageDelay, renderer: createRenderer, initialScreen, translation, state: defaultState, data: defaultData, autosaves, migrations, throttleTimeout, getLanguage, overrideLanguage, askBeforeExit, preloadAssets, parallelAssetsDownloadLimit, fetch: request, saveOnUnload }: NovelyInit<Languages, Characters, StateScheme, DataScheme>) => {
629
+ type ConditionParams<T> = T extends ActionProxy<Record<string, Character>, Lang, infer $State> ? Parameters<ConditionCheckFunction<$State, string | boolean>>[0] : never;
630
+
631
+ declare const novely: <$Language extends string, Characters extends Record<string, Character<$Language>>, StateScheme extends State, DataScheme extends Data>({ characters, defaultEmotions, storage, storageDelay, renderer: createRenderer, initialScreen, translation, state: defaultState, data: defaultData, autosaves, migrations, throttleTimeout, getLanguage, overrideLanguage, askBeforeExit, preloadAssets, parallelAssetsDownloadLimit, fetch: request, saveOnUnload, startKey }: NovelyInit<$Language, Characters, StateScheme, DataScheme>) => {
574
632
  /**
575
633
  * Function to set game script
576
634
  *
@@ -596,7 +654,7 @@ declare const novely: <Languages extends string, Characters extends Record<strin
596
654
  * })
597
655
  * ```
598
656
  */
599
- action: ActionProxy<Characters, Languages, StateScheme>;
657
+ action: ActionProxy<Characters, $Language, StateScheme>;
600
658
  /**
601
659
  * @deprecated Will be removed BUT replaced with state passed into actions as a parameter
602
660
  */
@@ -621,7 +679,7 @@ declare const novely: <Languages extends string, Characters extends Record<strin
621
679
  /**
622
680
  * @deprecated Renamed into `templateReplace`
623
681
  */
624
- unwrap(content: TextContent<Languages, DataScheme>): string;
682
+ unwrap(content: TextContent<$Language, DataScheme>): string;
625
683
  /**
626
684
  * Replaces content inside {{braces}} with using global data
627
685
  * @example
@@ -634,7 +692,7 @@ declare const novely: <Languages extends string, Characters extends Record<strin
634
692
  * })
635
693
  * ```
636
694
  */
637
- templateReplace(content: TextContent<Languages, DataScheme>): string;
695
+ templateReplace(content: TextContent<$Language, DataScheme>): string;
638
696
  /**
639
697
  * Cancel data loading, hide UI, ignore page change events
640
698
  * Data updates still will work in case Novely already was loaded
@@ -642,4 +700,4 @@ declare const novely: <Languages extends string, Characters extends Record<strin
642
700
  destroy(): void;
643
701
  };
644
702
 
645
- export { type ActionInputOnInputMeta, type ActionInputSetup, type ActionProxy, type AllowedContent, type AudioHandle, type BackgroundImage, type BaseTranslationStrings, type Character, type CharacterHandle, type Context, type CoreData, type CustomHandler, type CustomHandlerFunctionGetFn, type CustomHandlerFunctionParameters, type CustomHandlerGetResult, type CustomHandlerGetResultDataFunction, type Data, type DeepPartial, type DefaultActionProxy, EN, type Emotions, type FunctionableValue, type GetActionParameters, JP, KK, type Lang, type NovelyInit, type NovelyScreen, type Path, type PluralType, type Pluralization, RU, type Renderer, type RendererInit, type Save, type Stack, type StackHolder, type State, type StateFunction, type Storage, type StorageData, type StorageMeta, type Stored, type Story, type TextContent, type Thenable, type TranslationActions, type TypewriterSpeed, type ValidAction, localStorageStorage, novely };
703
+ export { type ActionInputOnInputMeta, type ActionInputSetup, type ActionProxy, type AllowedContent, type AudioHandle, type BackgroundImage, type BaseTranslationStrings, type Character, type CharacterHandle, type ConditionParams, type Context, type CoreData, type CustomHandler, type CustomHandlerFunctionGetFn, type CustomHandlerFunctionParameters, type CustomHandlerGetResult, type CustomHandlerGetResultDataFunction, type Data, type DeepPartial, type DefaultActionProxy, EN, type Emotions, type FunctionableValue, type GetActionParameters, JP, KK, type Lang, type NovelyInit, type NovelyScreen, type Path, type PluralType, type Pluralization, RU, type Renderer, type RendererInit, type Save, type Stack, type StackHolder, type State, type StateFunction, type Storage, type StorageData, type StorageMeta, type Stored, type Story, type TextContent, type Thenable, type TranslationActions, type TypewriterSpeed, type ValidAction, localStorageStorage, novely };
@@ -209,6 +209,7 @@ var Novely = (() => {
209
209
 
210
210
  // src/shared.ts
211
211
  var STACK_MAP = /* @__PURE__ */ new Map();
212
+ var PRELOADED_ASSETS = /* @__PURE__ */ new Set();
212
213
 
213
214
  // ../../node_modules/.pnpm/esm-env@1.0.0/node_modules/esm-env/prod-ssr.js
214
215
  var DEV = false;
@@ -628,6 +629,19 @@ var Novely = (() => {
628
629
  }
629
630
  return "other";
630
631
  };
632
+ var capitalize = (str2) => {
633
+ return str2[0].toUpperCase() + str2.slice(1);
634
+ };
635
+ var getIntlLanguageDisplayName = (lang) => {
636
+ try {
637
+ const intl = new Intl.DisplayNames([lang], {
638
+ type: "language"
639
+ });
640
+ return intl.of(lang) || lang;
641
+ } catch {
642
+ return lang;
643
+ }
644
+ };
631
645
 
632
646
  // ../../node_modules/.pnpm/dequal@2.0.3/node_modules/dequal/lite/index.mjs
633
647
  var has = Object.prototype.hasOwnProperty;
@@ -661,9 +675,6 @@ var Novely = (() => {
661
675
  return foo !== foo && bar !== bar;
662
676
  }
663
677
 
664
- // src/global.ts
665
- var PRELOADED_ASSETS = /* @__PURE__ */ new Set();
666
-
667
678
  // src/store.ts
668
679
  var store = (current, subscribers = /* @__PURE__ */ new Set()) => {
669
680
  const subscribe = (cb) => {
@@ -688,7 +699,7 @@ var Novely = (() => {
688
699
  return { subscribe, update, set, get };
689
700
  };
690
701
 
691
- // ../deepmerge/dist/index.js
702
+ // ../../node_modules/.pnpm/@novely+deepmerge@0.0.0/node_modules/@novely/deepmerge/dist/index.js
692
703
  var { isArray } = Array;
693
704
  var { hasOwnProperty, propertyIsEnumerable, getOwnPropertySymbols } = Object;
694
705
  var propertyIsOnObject = (object, property) => {
@@ -811,10 +822,10 @@ var Novely = (() => {
811
822
  }
812
823
  return c;
813
824
  };
814
- var replace = (str2, obj, pluralization, actions, pr) => {
815
- return str2.replaceAll(RGX, (x, key, y) => {
825
+ var replace = (input, data, pluralization, actions, pr) => {
826
+ return input.replaceAll(RGX, (x, key, y) => {
816
827
  x = 0;
817
- y = obj;
828
+ y = data;
818
829
  const [pathstr, plural, action] = split(key.trim(), ["@", "%"]);
819
830
  if (!pathstr) {
820
831
  return "";
@@ -825,7 +836,7 @@ var Novely = (() => {
825
836
  if (plural && pluralization && y && pr) {
826
837
  y = pluralization[plural][pr.select(y)];
827
838
  }
828
- const actionHandler = actions && action && actions[action];
839
+ const actionHandler = actions && action ? actions[action] : void 0;
829
840
  if (actionHandler)
830
841
  y = actionHandler(y);
831
842
  return y == null ? "" : y;
@@ -853,10 +864,28 @@ var Novely = (() => {
853
864
  };
854
865
  };
855
866
 
867
+ // src/browser.ts
868
+ var setupBrowserVisibilityChangeListeners = ({ onChange }) => {
869
+ if (typeof document === "undefined")
870
+ return noop;
871
+ const onVisibilityChange = () => {
872
+ if (document.visibilityState === "hidden") {
873
+ onChange();
874
+ }
875
+ };
876
+ addEventListener("visibilitychange", onVisibilityChange);
877
+ addEventListener("beforeunload", onChange);
878
+ return () => {
879
+ removeEventListener("visibilitychange", onVisibilityChange);
880
+ removeEventListener("beforeunload", onChange);
881
+ };
882
+ };
883
+
856
884
  // src/novely.ts
857
885
  var import_p_limit = __toESM(require_p_limit(), 1);
858
886
  var novely = ({
859
887
  characters,
888
+ defaultEmotions = {},
860
889
  storage = localStorageStorage({ key: "novely-game-storage" }),
861
890
  storageDelay = Promise.resolve(),
862
891
  renderer: createRenderer,
@@ -873,7 +902,8 @@ var Novely = (() => {
873
902
  preloadAssets = "lazy",
874
903
  parallelAssetsDownloadLimit = 15,
875
904
  fetch: request = fetch,
876
- saveOnUnload = true
905
+ saveOnUnload = true,
906
+ startKey = "start"
877
907
  }) => {
878
908
  const languages = Object.keys(translation);
879
909
  const limitScript = (0, import_p_limit.default)(1);
@@ -969,7 +999,7 @@ var Novely = (() => {
969
999
  const getDefaultSave = (state = {}) => {
970
1000
  return [
971
1001
  [
972
- ["jump", "start"],
1002
+ ["jump", startKey],
973
1003
  [null, 0]
974
1004
  ],
975
1005
  state,
@@ -1037,13 +1067,9 @@ var Novely = (() => {
1037
1067
  };
1038
1068
  storageDelay.then(getStoredData);
1039
1069
  const initial = getDefaultSave(klona(defaultState));
1040
- const onVisibilityChange = () => {
1041
- if (document.visibilityState === "hidden") {
1042
- throttledEmergencyOnStorageDataChange();
1043
- }
1044
- };
1045
- addEventListener("visibilitychange", onVisibilityChange);
1046
- addEventListener("beforeunload", throttledEmergencyOnStorageDataChange);
1070
+ const unsubscribeFromBrowserVisibilityChange = setupBrowserVisibilityChangeListeners({
1071
+ onChange: throttledEmergencyOnStorageDataChange
1072
+ });
1047
1073
  const save = (type) => {
1048
1074
  if (!coreData.get().dataLoaded)
1049
1075
  return;
@@ -1097,6 +1123,12 @@ var Novely = (() => {
1097
1123
  };
1098
1124
  let interacted = 0;
1099
1125
  const restore = async (save2) => {
1126
+ if (isEmpty(story)) {
1127
+ if (DEV) {
1128
+ throw new Error("Story is empty. You should call an `enine.script` function [https://novely.pages.dev/guide/story.html]");
1129
+ }
1130
+ return;
1131
+ }
1100
1132
  if (!coreData.get().dataLoaded)
1101
1133
  return;
1102
1134
  let latest = save2 || storageData.get().saves.at(-1);
@@ -1206,20 +1238,26 @@ var Novely = (() => {
1206
1238
  };
1207
1239
  const back = async () => {
1208
1240
  const stack = useStack(MAIN_CONTEXT_KEY);
1241
+ const valueBeforeBack = stack.value;
1209
1242
  stack.back();
1243
+ if (dequal(valueBeforeBack, stack.value) && !stack.previous) {
1244
+ return;
1245
+ }
1210
1246
  await restore(stack.value);
1211
1247
  };
1212
1248
  const t = (key, lang) => {
1213
1249
  return translation[lang].internal[key];
1214
1250
  };
1215
1251
  const preview = async (save2, name) => {
1252
+ if (isEmpty(story))
1253
+ return;
1216
1254
  const [path, data2] = save2;
1217
1255
  const { queue } = getActionsFromPath(story, path, true);
1218
1256
  const ctx = renderer.getContext(name);
1219
1257
  ctx.meta.restoring = true;
1220
1258
  ctx.meta.preview = true;
1221
1259
  const processor = createQueueProcessor(queue, {
1222
- skip: /* @__PURE__ */ new Set()
1260
+ skip: EMPTY_SET
1223
1261
  });
1224
1262
  useStack(ctx).push(klona(save2));
1225
1263
  await processor.run(([action2, ...props]) => {
@@ -1255,6 +1293,13 @@ var Novely = (() => {
1255
1293
  };
1256
1294
  return state;
1257
1295
  };
1296
+ const getLanguageDisplayName = (lang) => {
1297
+ const language = translation[lang];
1298
+ if (DEV && !language) {
1299
+ throw new Error(`Attempt to use unsupported language "${language}". Supported languages: ${languages.join(", ")}.`);
1300
+ }
1301
+ return capitalize(language.nameOverride || getIntlLanguageDisplayName(lang));
1302
+ };
1258
1303
  const renderer = createRenderer({
1259
1304
  mainContextKey: MAIN_CONTEXT_KEY,
1260
1305
  characters,
@@ -1270,7 +1315,8 @@ var Novely = (() => {
1270
1315
  getStateFunction,
1271
1316
  languages,
1272
1317
  storageData,
1273
- coreData
1318
+ coreData,
1319
+ getLanguageDisplayName
1274
1320
  });
1275
1321
  const useStack = createUseStackFunction(renderer);
1276
1322
  useStack(MAIN_CONTEXT_KEY).push(initial);
@@ -1357,6 +1403,12 @@ var Novely = (() => {
1357
1403
  push();
1358
1404
  },
1359
1405
  showCharacter({ ctx, push }, [character, emotion, className, style]) {
1406
+ emotion ??= defaultEmotions[character];
1407
+ if (DEV && !emotion) {
1408
+ throw new Error(`Attemp to show character "${character}" without emotion provided.`);
1409
+ }
1410
+ if (!emotion)
1411
+ return;
1360
1412
  if (DEV && !characters[character].emotions[emotion]) {
1361
1413
  throw new Error(`Attempt to show character "${character}" with unknown emotion "${emotion}"`);
1362
1414
  }
@@ -1494,11 +1546,7 @@ var Novely = (() => {
1494
1546
  end({ ctx }) {
1495
1547
  if (ctx.meta.preview)
1496
1548
  return;
1497
- ctx.vibrate(0);
1498
- ctx.clear(EMPTY_SET, EMPTY_SET, { music: EMPTY_SET, sounds: EMPTY_SET }, noop);
1499
- renderer.ui.showScreen("mainmenu");
1500
- interactivity(false);
1501
- times.clear();
1549
+ exit(true);
1502
1550
  },
1503
1551
  input({ ctx, data: data2, forward }, [question, onInput, setup]) {
1504
1552
  ctx.input(
@@ -1622,6 +1670,11 @@ var Novely = (() => {
1622
1670
  ctx,
1623
1671
  data: stack.value[1]
1624
1672
  });
1673
+ } else if (Object.values(story).some((branch) => branch === referred)) {
1674
+ match("end", [], {
1675
+ ctx,
1676
+ data: stack.value[1]
1677
+ });
1625
1678
  } else {
1626
1679
  match("exit", [], {
1627
1680
  ctx,
@@ -1633,14 +1686,10 @@ var Novely = (() => {
1633
1686
  interacted = value ? interacted + 1 : 0;
1634
1687
  };
1635
1688
  const templateReplace = (content, values) => {
1636
- const {
1637
- data: data2,
1638
- meta: [lang]
1639
- } = storageData.get();
1689
+ const { data: data2, meta: [lang] } = storageData.get();
1640
1690
  const obj = values || data2;
1641
- const cnt = isFunction(content) ? content(obj) : typeof content === "string" ? content : content[lang];
1642
1691
  const str2 = flattenAllowedContent(
1643
- isFunction(cnt) ? cnt(obj) : cnt,
1692
+ !isFunction(content) && !isString(content) ? content[lang] : content,
1644
1693
  obj
1645
1694
  );
1646
1695
  const t2 = translation[lang];
@@ -1740,8 +1789,7 @@ var Novely = (() => {
1740
1789
  destroy() {
1741
1790
  dataLoaded.cancel();
1742
1791
  UIInstance.unmount();
1743
- removeEventListener("visibilitychange", onVisibilityChange);
1744
- removeEventListener("beforeunload", throttledEmergencyOnStorageDataChange);
1792
+ unsubscribeFromBrowserVisibilityChange();
1745
1793
  }
1746
1794
  };
1747
1795
  };