@novely/core 0.29.1 → 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;
@@ -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) => {
@@ -699,7 +710,9 @@ var Novely = (() => {
699
710
  }
700
711
  };
701
712
  var propertyIsUnsafe = (target, key) => {
702
- 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));
703
716
  };
704
717
  var getEnumerableOwnPropertySymbols = (target) => {
705
718
  if (!getOwnPropertySymbols)
@@ -811,10 +824,10 @@ var Novely = (() => {
811
824
  }
812
825
  return c;
813
826
  };
814
- var replace = (str2, obj, pluralization, actions, pr) => {
815
- return str2.replaceAll(RGX, (x, key, y) => {
827
+ var replace = (input, data, pluralization, actions, pr) => {
828
+ return input.replaceAll(RGX, (x, key, y) => {
816
829
  x = 0;
817
- y = obj;
830
+ y = data;
818
831
  const [pathstr, plural, action] = split(key.trim(), ["@", "%"]);
819
832
  if (!pathstr) {
820
833
  return "";
@@ -825,7 +838,7 @@ var Novely = (() => {
825
838
  if (plural && pluralization && y && pr) {
826
839
  y = pluralization[plural][pr.select(y)];
827
840
  }
828
- const actionHandler = actions && action && actions[action];
841
+ const actionHandler = actions && action ? actions[action] : void 0;
829
842
  if (actionHandler)
830
843
  y = actionHandler(y);
831
844
  return y == null ? "" : y;
@@ -853,6 +866,23 @@ var Novely = (() => {
853
866
  };
854
867
  };
855
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
+
856
886
  // src/novely.ts
857
887
  var import_p_limit = __toESM(require_p_limit(), 1);
858
888
  var novely = ({
@@ -873,7 +903,8 @@ var Novely = (() => {
873
903
  preloadAssets = "lazy",
874
904
  parallelAssetsDownloadLimit = 15,
875
905
  fetch: request = fetch,
876
- saveOnUnload = true
906
+ saveOnUnload = true,
907
+ startKey = "start"
877
908
  }) => {
878
909
  const languages = Object.keys(translation);
879
910
  const limitScript = (0, import_p_limit.default)(1);
@@ -969,7 +1000,7 @@ var Novely = (() => {
969
1000
  const getDefaultSave = (state = {}) => {
970
1001
  return [
971
1002
  [
972
- ["jump", "start"],
1003
+ ["jump", startKey],
973
1004
  [null, 0]
974
1005
  ],
975
1006
  state,
@@ -1037,13 +1068,9 @@ var Novely = (() => {
1037
1068
  };
1038
1069
  storageDelay.then(getStoredData);
1039
1070
  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);
1071
+ const unsubscribeFromBrowserVisibilityChange = setupBrowserVisibilityChangeListeners({
1072
+ onChange: throttledEmergencyOnStorageDataChange
1073
+ });
1047
1074
  const save = (type) => {
1048
1075
  if (!coreData.get().dataLoaded)
1049
1076
  return;
@@ -1206,7 +1233,11 @@ var Novely = (() => {
1206
1233
  };
1207
1234
  const back = async () => {
1208
1235
  const stack = useStack(MAIN_CONTEXT_KEY);
1236
+ const valueBeforeBack = stack.value;
1209
1237
  stack.back();
1238
+ if (dequal(valueBeforeBack, stack.value) && !stack.previous) {
1239
+ return;
1240
+ }
1210
1241
  await restore(stack.value);
1211
1242
  };
1212
1243
  const t = (key, lang) => {
@@ -1219,7 +1250,7 @@ var Novely = (() => {
1219
1250
  ctx.meta.restoring = true;
1220
1251
  ctx.meta.preview = true;
1221
1252
  const processor = createQueueProcessor(queue, {
1222
- skip: /* @__PURE__ */ new Set()
1253
+ skip: EMPTY_SET
1223
1254
  });
1224
1255
  useStack(ctx).push(klona(save2));
1225
1256
  await processor.run(([action2, ...props]) => {
@@ -1255,6 +1286,13 @@ var Novely = (() => {
1255
1286
  };
1256
1287
  return state;
1257
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
+ };
1258
1296
  const renderer = createRenderer({
1259
1297
  mainContextKey: MAIN_CONTEXT_KEY,
1260
1298
  characters,
@@ -1270,7 +1308,8 @@ var Novely = (() => {
1270
1308
  getStateFunction,
1271
1309
  languages,
1272
1310
  storageData,
1273
- coreData
1311
+ coreData,
1312
+ getLanguageDisplayName
1274
1313
  });
1275
1314
  const useStack = createUseStackFunction(renderer);
1276
1315
  useStack(MAIN_CONTEXT_KEY).push(initial);
@@ -1622,6 +1661,11 @@ var Novely = (() => {
1622
1661
  ctx,
1623
1662
  data: stack.value[1]
1624
1663
  });
1664
+ } else if (Object.values(story).some((branch) => branch === referred)) {
1665
+ match("end", [], {
1666
+ ctx,
1667
+ data: stack.value[1]
1668
+ });
1625
1669
  } else {
1626
1670
  match("exit", [], {
1627
1671
  ctx,
@@ -1633,14 +1677,10 @@ var Novely = (() => {
1633
1677
  interacted = value ? interacted + 1 : 0;
1634
1678
  };
1635
1679
  const templateReplace = (content, values) => {
1636
- const {
1637
- data: data2,
1638
- meta: [lang]
1639
- } = storageData.get();
1680
+ const { data: data2, meta: [lang] } = storageData.get();
1640
1681
  const obj = values || data2;
1641
- const cnt = isFunction(content) ? content(obj) : typeof content === "string" ? content : content[lang];
1642
1682
  const str2 = flattenAllowedContent(
1643
- isFunction(cnt) ? cnt(obj) : cnt,
1683
+ !isFunction(content) && !isString(content) ? content[lang] : content,
1644
1684
  obj
1645
1685
  );
1646
1686
  const t2 = translation[lang];
@@ -1740,8 +1780,7 @@ var Novely = (() => {
1740
1780
  destroy() {
1741
1781
  dataLoaded.cancel();
1742
1782
  UIInstance.unmount();
1743
- removeEventListener("visibilitychange", onVisibilityChange);
1744
- removeEventListener("beforeunload", throttledEmergencyOnStorageDataChange);
1783
+ unsubscribeFromBrowserVisibilityChange();
1745
1784
  }
1746
1785
  };
1747
1786
  };