@novely/core 0.52.0-next.1 → 0.52.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
@@ -66,10 +66,6 @@ type CustomActionHandle = {
66
66
  * Function that will give action root (element which you should add to the screen because custom actions rendered into that element)
67
67
  */
68
68
  setMountElement: (mountElement: null | HTMLDivElement) => void;
69
- /**
70
- * Function that will give you clean function provided by custom action.
71
- */
72
- setClear: (clear: () => void) => void;
73
69
  };
74
70
  type AudioHandle = {
75
71
  stop: () => void;
@@ -222,7 +218,7 @@ type RendererInit<$Language extends Lang, $Characters extends Record<string, Cha
222
218
  preview: (save: Save<State>, name: string) => Promise<RendererInitPreviewReturn>;
223
219
  removeContext: (name: string) => void;
224
220
  getStateFunction: (context: string) => StateFunction<State>;
225
- clearCustomAction: (ctx: Context, customAction: CustomHandler) => void;
221
+ clearCustomActionsAtContext: (ctx: Context) => void;
226
222
  getLanguageDisplayName: (lang: Lang) => string;
227
223
  getCharacterColor: (character: string) => string;
228
224
  getCharacterAssets: (character: string, emotion: string) => string[];
@@ -613,15 +609,6 @@ type StateFunction<S extends State> = {
613
609
  (value: DeepPartial<S> | ((prev: S) => S)): void;
614
610
  (): S;
615
611
  };
616
- /**
617
- * @deprecated `EngineTypes` should be used instead
618
- */
619
- type TypeEssentials<$Lang extends Lang, $State extends State, $Data extends Data, $Characters extends Record<string, Character<$Lang>>> = {
620
- readonly l: $Lang | null;
621
- readonly s: $State | null;
622
- readonly d: $Data | null;
623
- readonly c: $Characters | null;
624
- };
625
612
  type EngineTypes<$Lang extends Lang = Lang, $State extends State = State, $Data extends Data = Data, $Characters extends Record<string, Character<$Lang>> = Record<string, Character<$Lang>>> = {
626
613
  readonly l: $Lang;
627
614
  readonly s: $State;
@@ -685,9 +672,9 @@ type CustomHandlerFunctionParameters<L extends string, S extends State> = {
685
672
  */
686
673
  getDomNodes: CustomHandlerFunctionGetFn;
687
674
  /**
688
- * Function to get current Save. It can be mutated.
675
+ * Function to get current Save. It can be mutated. Only use it when you know what you do
689
676
  *
690
- * Only use it when you know what you do
677
+ * @deprecated
691
678
  */
692
679
  getSave: () => Save<S>;
693
680
  /**
@@ -700,27 +687,68 @@ type CustomHandlerFunctionParameters<L extends string, S extends State> = {
700
687
  contextKey: string;
701
688
  /**
702
689
  * Function to work with custom action's state
690
+ *
691
+ * @example
692
+ * ```ts
693
+ * type Data = { name: string };
694
+ *
695
+ * const handler: CustomHandler = async ({ data }) => {
696
+ * const _data = data<Data>();
697
+ *
698
+ * if (!_data.name) {
699
+ * _data.name = 'Mr. Crabs'
700
+ * }
701
+ *
702
+ * data<Data>().name // 'Mr. Crabs'
703
+ *
704
+ * data<Data>() == _data // true
705
+ *
706
+ * // passing object will replace object
707
+ * data<Data>({ name: 'Mr. Crabs' })
708
+ * data<Data>() == _data // false
709
+ * }
710
+ * ```
703
711
  */
704
712
  data: CustomHandlerGetResultDataFunction;
705
713
  /**
706
714
  * Function to access data stored at specific key.
715
+ * @example
716
+ * ```ts
717
+ * const handler: CustomHandler = async ({ dataAtKey }) => {
718
+ * // peek at data at action with key 'action-2'
719
+ * console.log(dataAtKey('action-2'))
720
+ * }
721
+ *
722
+ * handler.key = 'action-1'
723
+ * ```
707
724
  * @deprecated
708
725
  */
709
726
  dataAtKey: <T extends Record<string, unknown>>(key: string) => T | null;
710
727
  /**
711
- * Function to set cleanup handler
728
+ * Function to register cleanup callbacks (executed in reverse order of registration).
729
+ *
730
+ * @example
731
+ * ```ts
732
+ * const handler: CustomHandler = async ({ clear, paused }) => {
733
+ * const unsubscribe = paused.subscribe((paused) => {
734
+ *
735
+ * })
736
+ *
737
+ * clear(paused);
738
+ * }
739
+ * ```
712
740
  */
713
741
  clear: (fn: () => void) => void;
714
742
  /**
715
- * Remove's custom handler instance
743
+ * It will call all clear actions and remove HTML element from `getDomNodes` function
716
744
  */
717
745
  remove: () => void;
718
746
  /**
719
- * Context's state function
747
+ * State function
720
748
  */
721
749
  state: StateFunction<S>;
722
750
  /**
723
- * Game flags (aka game states)
751
+ * Game state
724
752
  */
725
753
  flags: {
726
754
  restoring: boolean;
@@ -733,10 +761,27 @@ type CustomHandlerFunctionParameters<L extends string, S extends State> = {
733
761
  lang: L;
734
762
  /**
735
763
  * Function to replace template content
764
+ *
765
+ * @example
766
+ * ```ts
767
+ * const handler: CustomHandler = async ({ state, templateReplace }) => {
768
+ * const text = templateReplace({ en: '' }, state())
769
+ * }
770
+ * ```
736
771
  */
737
772
  templateReplace: (content: TextContent<L, State>, values?: State) => string;
738
773
  /**
739
774
  * Is game currently paused
775
+ *
776
+ * @example
777
+ * ```ts
778
+ * const handler: CustomHandler = async ({ clear, paused }) => {
779
+ * const unsubscribe = paused.subscribe((paused) => {
780
+ * // Here you can pause/resume animations, sounds, etc, etc
781
+ * })
782
+ *
783
+ * clear(paused);
784
+ * }
740
785
  */
741
786
  paused: Derived<boolean>;
742
787
  };
@@ -767,17 +812,6 @@ type CustomHandlerInfo = CustomHandlerCalling & {
767
812
  * When true interacting with it will be saved in history
768
813
  */
769
814
  requireUserAction?: boolean;
770
- /**
771
- * When player is going back we clear every custom action. But we can ignore clearing that.
772
- */
773
- skipClearOnGoingBack?: boolean;
774
- /**
775
- * When goingBack the restoring method is used. In that case, current actions array changes
776
- *
777
- * [1, 2, 3] changes to [1, 2]
778
- * When you don't want 3'rd action to be cleared use this
779
- */
780
- skipClearOnRestore?: boolean;
781
815
  /**
782
816
  * Id by which we will determine what action is which
783
817
  */
@@ -1001,19 +1035,6 @@ declare const novely: <$Language extends string, $Characters extends Record<stri
1001
1035
  * ```
1002
1036
  */
1003
1037
  data: StateFunction<$Data>;
1004
- /**
1005
- * Used in combination with type utilities
1006
- * @deprecated Use `engine.types` instead
1007
- * @example
1008
- * ```ts
1009
- * import type { ConditionParams, StateFunction } from '@novely/core';
1010
- *
1011
- * const conditionCheck = (state: StateFunction<ConditionParams<typeof engine.typeEssintials>>) => {
1012
- * return state.age >= 18;
1013
- * }
1014
- * ```
1015
- */
1016
- typeEssentials: TypeEssentials<$Language, $State, $Data, $Characters>;
1017
1038
  /**
1018
1039
  * Used in combination with type utilities
1019
1040
  * @example
@@ -1157,4 +1178,4 @@ declare const pauseOnBlur: (engine: {
1157
1178
  unsubscribe: () => void;
1158
1179
  };
1159
1180
 
1160
- 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 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 StorageAdapter, type StorageData, type StorageMeta, type Stored, type Story, type TextContent, type Thenable, type TranslationActions, type TypeEssentials, type TypesFromEngine, type TypewriterSpeed, type ValidAction, asset, extendAction, novely, pauseOnBlur, storageAdapterLocal };
1181
+ 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 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 StorageAdapter, type StorageData, type StorageMeta, type Stored, type Story, type TextContent, type Thenable, type TranslationActions, type TypesFromEngine, type TypewriterSpeed, type ValidAction, asset, extendAction, novely, pauseOnBlur, storageAdapterLocal };
package/dist/index.js CHANGED
@@ -122,6 +122,7 @@ var MAIN_CONTEXT_KEY = "$MAIN";
122
122
  // src/shared.ts
123
123
  var STACK_MAP = /* @__PURE__ */ new Map();
124
124
  var CUSTOM_ACTION_MAP = /* @__PURE__ */ new Map();
125
+ var CUSTOM_ACTION_CLEANUP_MAP = /* @__PURE__ */ new Map();
125
126
  var PRELOADED_ASSETS = /* @__PURE__ */ new Set();
126
127
  var ASSETS_TO_PRELOAD = /* @__PURE__ */ new Set();
127
128
 
@@ -922,7 +923,7 @@ var capitalize = (str) => {
922
923
  return str[0].toUpperCase() + str.slice(1);
923
924
  };
924
925
 
925
- // src/utilities/functions.ts
926
+ // src/utilities/noop.ts
926
927
  var noop = () => {
927
928
  };
928
929
 
@@ -997,6 +998,7 @@ var immutable = (value) => {
997
998
  };
998
999
 
999
1000
  // src/custom-action.ts
1001
+ import { once as once2 } from "es-toolkit/function";
1000
1002
  var createCustomActionNode = (id) => {
1001
1003
  const div = document.createElement("div");
1002
1004
  div.setAttribute("data-id", id);
@@ -1008,7 +1010,6 @@ var getCustomActionHolder = (ctx, fn) => {
1008
1010
  return cached;
1009
1011
  }
1010
1012
  const holder = {
1011
- cleanup: noop,
1012
1013
  node: null,
1013
1014
  fn,
1014
1015
  localData: {}
@@ -1016,22 +1017,50 @@ var getCustomActionHolder = (ctx, fn) => {
1016
1017
  CUSTOM_ACTION_MAP.set(ctx.id + fn.key, holder);
1017
1018
  return holder;
1018
1019
  };
1020
+ var getCustomActionCleanupHolder = (ctx) => {
1021
+ const existing = CUSTOM_ACTION_CLEANUP_MAP.get(ctx.id);
1022
+ if (existing) {
1023
+ return existing;
1024
+ }
1025
+ const holder = [];
1026
+ CUSTOM_ACTION_CLEANUP_MAP.set(ctx.id, holder);
1027
+ return holder;
1028
+ };
1029
+ var cleanCleanupSource = ({ list }) => {
1030
+ while (list.length) {
1031
+ try {
1032
+ list.pop()();
1033
+ } catch (e) {
1034
+ console.error(e);
1035
+ }
1036
+ }
1037
+ };
1019
1038
  var handleCustomAction = (ctx, fn, {
1020
1039
  lang,
1021
1040
  state,
1022
1041
  setMountElement,
1023
- setClear,
1024
1042
  remove: renderersRemove,
1025
1043
  getStack: getStack2,
1026
1044
  templateReplace,
1027
1045
  paused
1028
1046
  }) => {
1029
1047
  const holder = getCustomActionHolder(ctx, fn);
1030
- const flags = {
1031
- ...ctx.meta
1048
+ const cleanupHolder = getCustomActionCleanupHolder(ctx);
1049
+ const cleanupNode = () => {
1050
+ if (!cleanupHolder.some((item) => item.fn.id === fn.id && item.fn.key === fn.key)) {
1051
+ holder.node = null;
1052
+ setMountElement(null);
1053
+ }
1054
+ };
1055
+ const cleanupSource = {
1056
+ fn,
1057
+ list: [],
1058
+ node: cleanupNode
1032
1059
  };
1060
+ cleanupHolder.push(cleanupSource);
1033
1061
  const getDomNodes = (insert = true) => {
1034
1062
  if (holder.node || !insert) {
1063
+ setMountElement(holder.node);
1035
1064
  return {
1036
1065
  element: holder.node,
1037
1066
  root: ctx.root
@@ -1045,15 +1074,7 @@ var handleCustomAction = (ctx, fn, {
1045
1074
  };
1046
1075
  };
1047
1076
  const clear = (func) => {
1048
- setClear(
1049
- holder.cleanup = () => {
1050
- func();
1051
- holder.node = null;
1052
- holder.cleanup = noop;
1053
- setMountElement(null);
1054
- setClear(noop);
1055
- }
1056
- );
1077
+ cleanupSource.list.push(once2(func));
1057
1078
  };
1058
1079
  const data = (updatedData) => {
1059
1080
  if (updatedData) {
@@ -1062,7 +1083,9 @@ var handleCustomAction = (ctx, fn, {
1062
1083
  return holder.localData;
1063
1084
  };
1064
1085
  const remove = () => {
1065
- holder.cleanup();
1086
+ cleanCleanupSource(cleanupSource);
1087
+ holder.node = null;
1088
+ setMountElement(null);
1066
1089
  renderersRemove();
1067
1090
  };
1068
1091
  const stack = getStack2(ctx);
@@ -1070,7 +1093,7 @@ var handleCustomAction = (ctx, fn, {
1070
1093
  return stack.value;
1071
1094
  };
1072
1095
  return fn({
1073
- flags,
1096
+ flags: ctx.meta,
1074
1097
  lang,
1075
1098
  state,
1076
1099
  data,
@@ -1082,7 +1105,7 @@ var handleCustomAction = (ctx, fn, {
1082
1105
  getDomNodes,
1083
1106
  getSave,
1084
1107
  contextKey: ctx.id,
1085
- paused: flags.preview ? immutable(false) : paused
1108
+ paused: ctx.meta.preview ? immutable(false) : paused
1086
1109
  });
1087
1110
  };
1088
1111
 
@@ -1607,13 +1630,7 @@ var novely = ({
1607
1630
  filter: false,
1608
1631
  referGuarded
1609
1632
  });
1610
- const {
1611
- run,
1612
- keep: { keep, characters: characters2, audio: audio2 }
1613
- } = createQueueProcessor(queue, {
1614
- skip,
1615
- skipPreserve
1616
- });
1633
+ const cleanupHolder = getCustomActionCleanupHolder(context);
1617
1634
  if (previous) {
1618
1635
  const { queue: prevQueue } = await getActionsFromPath({
1619
1636
  story,
@@ -1621,17 +1638,37 @@ var novely = ({
1621
1638
  filter: false,
1622
1639
  referGuarded
1623
1640
  });
1624
- for (let i = prevQueue.length - 1; i > queue.length - 1; i--) {
1625
- const element = prevQueue[i];
1626
- if (!isAction(element)) {
1627
- continue;
1641
+ const futures = [];
1642
+ const isFromDifferentBranches = previous[0][0][1] !== path[0][1];
1643
+ const end = isFromDifferentBranches ? 0 : queue.length - 1;
1644
+ for (let i = prevQueue.length - 1; i >= end; i--) {
1645
+ const [action2, fn] = prevQueue[i];
1646
+ if (action2 === "custom") {
1647
+ futures.push(fn);
1628
1648
  }
1629
- const [action2, fn] = element;
1630
- if (action2 === "custom" && !fn.skipClearOnRestore) {
1631
- clearCustomAction(context, fn);
1649
+ }
1650
+ futures.reverse();
1651
+ const nodeCleanup = /* @__PURE__ */ new Set();
1652
+ for (const future of futures) {
1653
+ inner: for (let i = cleanupHolder.length - 1; i >= 0; i--) {
1654
+ const item = cleanupHolder[i];
1655
+ if (future === item.fn) {
1656
+ cleanCleanupSource(item);
1657
+ nodeCleanup.add(item.node);
1658
+ cleanupHolder.splice(i, 1);
1659
+ break inner;
1660
+ }
1632
1661
  }
1633
1662
  }
1663
+ nodeCleanup.forEach((f) => f());
1634
1664
  }
1665
+ const {
1666
+ run,
1667
+ keep: { keep, characters: characters2, audio: audio2 }
1668
+ } = createQueueProcessor(queue, {
1669
+ skip,
1670
+ skipPreserve
1671
+ });
1635
1672
  if (context.meta.goingBack) {
1636
1673
  match("clear", [keep, characters2, audio2], {
1637
1674
  ctx: context,
@@ -1647,6 +1684,11 @@ var novely = ({
1647
1684
  return;
1648
1685
  }
1649
1686
  const [action2, ...props] = item;
1687
+ if (action2 === "custom") {
1688
+ if (cleanupHolder.some((holder) => holder.fn === props[0])) {
1689
+ return;
1690
+ }
1691
+ }
1650
1692
  return match(action2, props, {
1651
1693
  ctx: context,
1652
1694
  data: latest[1]
@@ -1681,6 +1723,7 @@ var novely = ({
1681
1723
  save("auto");
1682
1724
  }
1683
1725
  stack.clear();
1726
+ clearCustomActionsAtContext(ctx);
1684
1727
  ctx.clear(EMPTY_SET, EMPTY_SET, { music: EMPTY_SET, sounds: EMPTY_SET }, noop);
1685
1728
  renderer.ui.showScreen("mainmenu");
1686
1729
  ctx.audio.destroy();
@@ -1780,8 +1823,15 @@ var novely = ({
1780
1823
  }
1781
1824
  return capitalize(language.nameOverride || getIntlLanguageDisplayName(lang));
1782
1825
  };
1783
- const clearCustomAction = (ctx, fn) => {
1784
- getCustomActionHolder(ctx, fn).cleanup();
1826
+ const clearCustomActionsAtContext = (ctx) => {
1827
+ const cleanupHolder = getCustomActionCleanupHolder(ctx);
1828
+ const nodeCleanup = /* @__PURE__ */ new Set();
1829
+ for (const item of cleanupHolder) {
1830
+ cleanCleanupSource(item);
1831
+ nodeCleanup.add(item.node);
1832
+ }
1833
+ cleanupHolder.length = 0;
1834
+ nodeCleanup.forEach((fn) => fn());
1785
1835
  };
1786
1836
  const getResourseTypeWrapper = (url) => {
1787
1837
  return getResourseType({
@@ -1836,7 +1886,7 @@ var novely = ({
1836
1886
  preview,
1837
1887
  removeContext,
1838
1888
  getStateFunction,
1839
- clearCustomAction,
1889
+ clearCustomActionsAtContext,
1840
1890
  languages,
1841
1891
  storageData,
1842
1892
  coreData,
@@ -2343,19 +2393,6 @@ var novely = ({
2343
2393
  * ```
2344
2394
  */
2345
2395
  data,
2346
- /**
2347
- * Used in combination with type utilities
2348
- * @deprecated Use `engine.types` instead
2349
- * @example
2350
- * ```ts
2351
- * import type { ConditionParams, StateFunction } from '@novely/core';
2352
- *
2353
- * const conditionCheck = (state: StateFunction<ConditionParams<typeof engine.typeEssintials>>) => {
2354
- * return state.age >= 18;
2355
- * }
2356
- * ```
2357
- */
2358
- typeEssentials: {},
2359
2396
  /**
2360
2397
  * Used in combination with type utilities
2361
2398
  * @example