@novely/core 0.29.0 → 0.29.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.d.ts CHANGED
@@ -17,7 +17,7 @@ declare const localStorageStorage: (options: LocalStorageStorageSettings) => Sto
17
17
 
18
18
  type PluralType = Intl.LDMLPluralRule;
19
19
  type Pluralization = Partial<Record<PluralType, string>>;
20
- type AllowedContent = string | ((state: State) => string | string[]) | string[] | (string | ((state: State) => string | string[]))[];
20
+ type AllowedContent = string | ((state: State | Data) => string | string[]) | string[] | (string | ((state: State | Data) => string | string[]))[];
21
21
  type TranslationActions = Partial<Record<string, (str: string) => string>>;
22
22
 
23
23
  declare const RU: {
@@ -215,6 +215,7 @@ type RendererInit = {
215
215
  preview: (save: Save<State>, name: string) => Promise<void>;
216
216
  removeContext: (name: string) => void;
217
217
  getStateFunction: (context: string) => StateFunction<State>;
218
+ getLanguageDisplayName: (lang: Lang) => string;
218
219
  };
219
220
 
220
221
  declare const getLanguage: (languages: string[]) => string;
@@ -275,6 +276,10 @@ type TranslationDescription = {
275
276
  * IETF BCP 47 language tag
276
277
  */
277
278
  tag?: string;
279
+ /**
280
+ * Custom name
281
+ */
282
+ nameOverride?: string;
278
283
  plural?: Record<string, Pluralization>;
279
284
  actions?: TranslationActions;
280
285
  };
@@ -409,6 +414,26 @@ interface NovelyInit<$Language extends Lang, Characters extends Record<string, C
409
414
  * @default true
410
415
  */
411
416
  saveOnUnload?: boolean | 'prod';
417
+ /**
418
+ * The key that signifies the start of the game. It is not recommended to override this parameter.
419
+ *
420
+ * @default 'start'
421
+ * @example
422
+ * ```ts
423
+ * const engine = novely({
424
+ * ...,
425
+ * startKey: 'PART_1'
426
+ * })
427
+ *
428
+ * engine.script({
429
+ * // now game will start from here
430
+ * PART_1: [
431
+ *
432
+ * ]
433
+ * })
434
+ * ```
435
+ */
436
+ startKey?: 'start' | (string & Record<never, never>);
412
437
  }
413
438
  type StateFunction<S extends State> = {
414
439
  (value: DeepPartial<S> | ((prev: S) => S)): void;
@@ -515,10 +540,11 @@ type ChoiceCheckFunctionProps<L extends string, S extends State> = {
515
540
  type ChoiceCheckFunction<L extends string, S extends State> = {
516
541
  (props: ChoiceCheckFunctionProps<L, S>): boolean;
517
542
  };
543
+ type ConditionCheckFunction<S extends State, R extends string | true | false> = (state: S) => R;
518
544
  type FunctionAction<L extends string, S extends State> = (props: FunctionActionProps<L, S>) => Thenable<void>;
519
545
  type ActionInputSetup = (input: HTMLInputElement, cleanup: (cb: () => void) => void) => void;
520
546
  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> = {
547
+ type ActionProxy<Characters extends Record<string, Character>, Languages extends Lang, S extends State> = {
522
548
  choice: {
523
549
  (...choices: [name: TextContent<Languages, S>, actions: ValidAction[], active?: ChoiceCheckFunction<Languages, S>][]): ValidAction;
524
550
  (question: TextContent<Languages, S>, ...choices: [name: TextContent<Languages, S>, actions: ValidAction[], active?: ChoiceCheckFunction<Languages, S>][]): ValidAction;
@@ -527,7 +553,7 @@ type ActionProxy<Characters extends Record<string, Character>, Languages extends
527
553
  music: Set<string>;
528
554
  sounds: Set<string>;
529
555
  }) => 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;
556
+ condition: <T extends string | true | false>(condition: ConditionCheckFunction<S, T>, variants: Record<T extends true ? 'true' : T extends false ? 'false' : T, ValidAction[]>) => ValidAction;
531
557
  exit: () => ValidAction;
532
558
  dialog: {
533
559
  <C extends keyof Characters>(character: C, content: TextContent<Languages, S>, emotion?: keyof Characters[C]['emotions']): ValidAction;
@@ -567,10 +593,12 @@ type ActionProxy<Characters extends Record<string, Character>, Languages extends
567
593
  preload: (source: string) => ValidAction;
568
594
  block: (scene: string) => ValidAction;
569
595
  };
570
- type DefaultActionProxy = ActionProxy<Record<string, Character>, string, State>;
596
+ type DefaultActionProxy = ActionProxy<Record<string, Character>, Lang, State>;
571
597
  type GetActionParameters<T extends Capitalize<keyof DefaultActionProxy>> = Parameters<DefaultActionProxy[Uncapitalize<T>]>;
572
598
 
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>) => {
599
+ type ConditionParams<T> = T extends ActionProxy<Record<string, Character>, Lang, infer $State> ? Parameters<ConditionCheckFunction<$State, string | boolean>>[0] : never;
600
+
601
+ declare const novely: <$Language extends string, Characters extends Record<string, Character<$Language>>, 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, startKey }: NovelyInit<$Language, Characters, StateScheme, DataScheme>) => {
574
602
  /**
575
603
  * Function to set game script
576
604
  *
@@ -596,7 +624,7 @@ declare const novely: <Languages extends string, Characters extends Record<strin
596
624
  * })
597
625
  * ```
598
626
  */
599
- action: ActionProxy<Characters, Languages, StateScheme>;
627
+ action: ActionProxy<Characters, $Language, StateScheme>;
600
628
  /**
601
629
  * @deprecated Will be removed BUT replaced with state passed into actions as a parameter
602
630
  */
@@ -621,7 +649,7 @@ declare const novely: <Languages extends string, Characters extends Record<strin
621
649
  /**
622
650
  * @deprecated Renamed into `templateReplace`
623
651
  */
624
- unwrap(content: TextContent<Languages, DataScheme>): string;
652
+ unwrap(content: TextContent<$Language, DataScheme>): string;
625
653
  /**
626
654
  * Replaces content inside {{braces}} with using global data
627
655
  * @example
@@ -634,7 +662,7 @@ declare const novely: <Languages extends string, Characters extends Record<strin
634
662
  * })
635
663
  * ```
636
664
  */
637
- templateReplace(content: TextContent<Languages, DataScheme>): string;
665
+ templateReplace(content: TextContent<$Language, DataScheme>): string;
638
666
  /**
639
667
  * Cancel data loading, hide UI, ignore page change events
640
668
  * Data updates still will work in case Novely already was loaded
@@ -642,4 +670,4 @@ declare const novely: <Languages extends string, Characters extends Record<strin
642
670
  destroy(): void;
643
671
  };
644
672
 
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 };
673
+ 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;
@@ -486,7 +487,9 @@ var Novely = (() => {
486
487
  if (target !== params[0]) {
487
488
  return false;
488
489
  }
489
- return _action === closing || _action === action;
490
+ const musicGonnaBePaused = action === "playMusic" && _action === "pauseMusic";
491
+ const soundGonnaBePaused = action === "playSound" && _action === "pauseSound";
492
+ return musicGonnaBePaused || soundGonnaBePaused || _action === closing || _action === action;
490
493
  });
491
494
  if (skip)
492
495
  continue;
@@ -626,6 +629,19 @@ var Novely = (() => {
626
629
  }
627
630
  return "other";
628
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
+ };
629
645
 
630
646
  // ../../node_modules/.pnpm/dequal@2.0.3/node_modules/dequal/lite/index.mjs
631
647
  var has = Object.prototype.hasOwnProperty;
@@ -659,9 +675,6 @@ var Novely = (() => {
659
675
  return foo !== foo && bar !== bar;
660
676
  }
661
677
 
662
- // src/global.ts
663
- var PRELOADED_ASSETS = /* @__PURE__ */ new Set();
664
-
665
678
  // src/store.ts
666
679
  var store = (current, subscribers = /* @__PURE__ */ new Set()) => {
667
680
  const subscribe = (cb) => {
@@ -697,7 +710,9 @@ var Novely = (() => {
697
710
  }
698
711
  };
699
712
  var propertyIsUnsafe = (target, key) => {
700
- return propertyIsOnObject(target, key) && !(hasOwnProperty.call(target, key) && propertyIsEnumerable.call(target, key));
713
+ return propertyIsOnObject(target, key) && // Properties are safe to merge if they don't exist in the target yet,
714
+ !(hasOwnProperty.call(target, key) && // unsafe if they exist up the prototype chain,
715
+ propertyIsEnumerable.call(target, key));
701
716
  };
702
717
  var getEnumerableOwnPropertySymbols = (target) => {
703
718
  if (!getOwnPropertySymbols)
@@ -809,10 +824,10 @@ var Novely = (() => {
809
824
  }
810
825
  return c;
811
826
  };
812
- var replace = (str2, obj, pluralization, actions, pr) => {
813
- return str2.replaceAll(RGX, (x, key, y) => {
827
+ var replace = (input, data, pluralization, actions, pr) => {
828
+ return input.replaceAll(RGX, (x, key, y) => {
814
829
  x = 0;
815
- y = obj;
830
+ y = data;
816
831
  const [pathstr, plural, action] = split(key.trim(), ["@", "%"]);
817
832
  if (!pathstr) {
818
833
  return "";
@@ -823,7 +838,7 @@ var Novely = (() => {
823
838
  if (plural && pluralization && y && pr) {
824
839
  y = pluralization[plural][pr.select(y)];
825
840
  }
826
- const actionHandler = actions && action && actions[action];
841
+ const actionHandler = actions && action ? actions[action] : void 0;
827
842
  if (actionHandler)
828
843
  y = actionHandler(y);
829
844
  return y == null ? "" : y;
@@ -851,6 +866,23 @@ var Novely = (() => {
851
866
  };
852
867
  };
853
868
 
869
+ // src/browser.ts
870
+ var setupBrowserVisibilityChangeListeners = ({ onChange }) => {
871
+ if (typeof document === "undefined")
872
+ return noop;
873
+ const onVisibilityChange = () => {
874
+ if (document.visibilityState === "hidden") {
875
+ onChange();
876
+ }
877
+ };
878
+ addEventListener("visibilitychange", onVisibilityChange);
879
+ addEventListener("beforeunload", onChange);
880
+ return () => {
881
+ removeEventListener("visibilitychange", onVisibilityChange);
882
+ removeEventListener("beforeunload", onChange);
883
+ };
884
+ };
885
+
854
886
  // src/novely.ts
855
887
  var import_p_limit = __toESM(require_p_limit(), 1);
856
888
  var novely = ({
@@ -871,7 +903,8 @@ var Novely = (() => {
871
903
  preloadAssets = "lazy",
872
904
  parallelAssetsDownloadLimit = 15,
873
905
  fetch: request = fetch,
874
- saveOnUnload = true
906
+ saveOnUnload = true,
907
+ startKey = "start"
875
908
  }) => {
876
909
  const languages = Object.keys(translation);
877
910
  const limitScript = (0, import_p_limit.default)(1);
@@ -967,7 +1000,7 @@ var Novely = (() => {
967
1000
  const getDefaultSave = (state = {}) => {
968
1001
  return [
969
1002
  [
970
- ["jump", "start"],
1003
+ ["jump", startKey],
971
1004
  [null, 0]
972
1005
  ],
973
1006
  state,
@@ -1035,13 +1068,9 @@ var Novely = (() => {
1035
1068
  };
1036
1069
  storageDelay.then(getStoredData);
1037
1070
  const initial = getDefaultSave(klona(defaultState));
1038
- const onVisibilityChange = () => {
1039
- if (document.visibilityState === "hidden") {
1040
- throttledEmergencyOnStorageDataChange();
1041
- }
1042
- };
1043
- addEventListener("visibilitychange", onVisibilityChange);
1044
- addEventListener("beforeunload", throttledEmergencyOnStorageDataChange);
1071
+ const unsubscribeFromBrowserVisibilityChange = setupBrowserVisibilityChangeListeners({
1072
+ onChange: throttledEmergencyOnStorageDataChange
1073
+ });
1045
1074
  const save = (type) => {
1046
1075
  if (!coreData.get().dataLoaded)
1047
1076
  return;
@@ -1204,7 +1233,11 @@ var Novely = (() => {
1204
1233
  };
1205
1234
  const back = async () => {
1206
1235
  const stack = useStack(MAIN_CONTEXT_KEY);
1236
+ const valueBeforeBack = stack.value;
1207
1237
  stack.back();
1238
+ if (dequal(valueBeforeBack, stack.value) && !stack.previous) {
1239
+ return;
1240
+ }
1208
1241
  await restore(stack.value);
1209
1242
  };
1210
1243
  const t = (key, lang) => {
@@ -1217,7 +1250,7 @@ var Novely = (() => {
1217
1250
  ctx.meta.restoring = true;
1218
1251
  ctx.meta.preview = true;
1219
1252
  const processor = createQueueProcessor(queue, {
1220
- skip: /* @__PURE__ */ new Set()
1253
+ skip: EMPTY_SET
1221
1254
  });
1222
1255
  useStack(ctx).push(klona(save2));
1223
1256
  await processor.run(([action2, ...props]) => {
@@ -1253,6 +1286,13 @@ var Novely = (() => {
1253
1286
  };
1254
1287
  return state;
1255
1288
  };
1289
+ const getLanguageDisplayName = (lang) => {
1290
+ const language = translation[lang];
1291
+ if (DEV && !language) {
1292
+ throw new Error(`Attempt to use unsupported language "${language}". Supported languages: ${languages.join(", ")}.`);
1293
+ }
1294
+ return capitalize(language.nameOverride || getIntlLanguageDisplayName(lang));
1295
+ };
1256
1296
  const renderer = createRenderer({
1257
1297
  mainContextKey: MAIN_CONTEXT_KEY,
1258
1298
  characters,
@@ -1268,7 +1308,8 @@ var Novely = (() => {
1268
1308
  getStateFunction,
1269
1309
  languages,
1270
1310
  storageData,
1271
- coreData
1311
+ coreData,
1312
+ getLanguageDisplayName
1272
1313
  });
1273
1314
  const useStack = createUseStackFunction(renderer);
1274
1315
  useStack(MAIN_CONTEXT_KEY).push(initial);
@@ -1620,6 +1661,11 @@ var Novely = (() => {
1620
1661
  ctx,
1621
1662
  data: stack.value[1]
1622
1663
  });
1664
+ } else if (Object.values(story).some((branch) => branch === referred)) {
1665
+ match("end", [], {
1666
+ ctx,
1667
+ data: stack.value[1]
1668
+ });
1623
1669
  } else {
1624
1670
  match("exit", [], {
1625
1671
  ctx,
@@ -1631,14 +1677,10 @@ var Novely = (() => {
1631
1677
  interacted = value ? interacted + 1 : 0;
1632
1678
  };
1633
1679
  const templateReplace = (content, values) => {
1634
- const {
1635
- data: data2,
1636
- meta: [lang]
1637
- } = storageData.get();
1680
+ const { data: data2, meta: [lang] } = storageData.get();
1638
1681
  const obj = values || data2;
1639
- const cnt = isFunction(content) ? content(obj) : typeof content === "string" ? content : content[lang];
1640
1682
  const str2 = flattenAllowedContent(
1641
- isFunction(cnt) ? cnt(obj) : cnt,
1683
+ !isFunction(content) && !isString(content) ? content[lang] : content,
1642
1684
  obj
1643
1685
  );
1644
1686
  const t2 = translation[lang];
@@ -1738,8 +1780,7 @@ var Novely = (() => {
1738
1780
  destroy() {
1739
1781
  dataLoaded.cancel();
1740
1782
  UIInstance.unmount();
1741
- removeEventListener("visibilitychange", onVisibilityChange);
1742
- removeEventListener("beforeunload", throttledEmergencyOnStorageDataChange);
1783
+ unsubscribeFromBrowserVisibilityChange();
1743
1784
  }
1744
1785
  };
1745
1786
  };