@novely/core 0.52.0-next → 0.52.0-next.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
@@ -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[];
@@ -685,13 +681,9 @@ type CustomHandlerFunctionParameters<L extends string, S extends State> = {
685
681
  */
686
682
  getDomNodes: CustomHandlerFunctionGetFn;
687
683
  /**
688
- * @deprecated use `getSave` instead
689
- */
690
- getPath: () => Path;
691
- /**
692
- * Function to get current Save. It can be mutated.
684
+ * Function to get current Save. It can be mutated. Only use it when you know what you do
693
685
  *
694
- * Only use it when you know what you do
686
+ * @deprecated
695
687
  */
696
688
  getSave: () => Save<S>;
697
689
  /**
@@ -704,22 +696,68 @@ type CustomHandlerFunctionParameters<L extends string, S extends State> = {
704
696
  contextKey: string;
705
697
  /**
706
698
  * Function to work with custom action's state
699
+ *
700
+ * @example
701
+ * ```ts
702
+ * type Data = { name: string };
703
+ *
704
+ * const handler: CustomHandler = async ({ data }) => {
705
+ * const _data = data<Data>();
706
+ *
707
+ * if (!_data.name) {
708
+ * _data.name = 'Mr. Crabs'
709
+ * }
710
+ *
711
+ * data<Data>().name // 'Mr. Crabs'
712
+ *
713
+ * data<Data>() == _data // true
714
+ *
715
+ * // passing object will replace object
716
+ * data<Data>({ name: 'Mr. Crabs' })
717
+ * data<Data>() == _data // false
718
+ * }
719
+ * ```
707
720
  */
708
721
  data: CustomHandlerGetResultDataFunction;
709
722
  /**
710
- * Function to set cleanup handler
723
+ * Function to access data stored at specific key.
724
+ * @example
725
+ * ```ts
726
+ * const handler: CustomHandler = async ({ dataAtKey }) => {
727
+ * // peek at data at action with key 'action-2'
728
+ * console.log(dataAtKey('action-2'))
729
+ * }
730
+ *
731
+ * handler.key = 'action-1'
732
+ * ```
733
+ * @deprecated
734
+ */
735
+ dataAtKey: <T extends Record<string, unknown>>(key: string) => T | null;
736
+ /**
737
+ * Function to set cleanup functions.
738
+ *
739
+ * @example
740
+ * ```ts
741
+ * const handler: CustomHandler = async ({ clear, paused }) => {
742
+ * const unsubscribe = paused.subscribe((paused) => {
743
+ *
744
+ * })
745
+ *
746
+ * clear(paused);
747
+ * }
748
+ * ```
711
749
  */
712
750
  clear: (fn: () => void) => void;
713
751
  /**
714
- * Remove's custom handler instance
752
+ * It will call all clear actions and remove HTML element from `getDomNodes` function
715
753
  */
716
754
  remove: () => void;
717
755
  /**
718
- * Context's state function
756
+ * State function
719
757
  */
720
758
  state: StateFunction<S>;
721
759
  /**
722
- * Game flags (aka game states)
760
+ * Game state
723
761
  */
724
762
  flags: {
725
763
  restoring: boolean;
@@ -732,10 +770,27 @@ type CustomHandlerFunctionParameters<L extends string, S extends State> = {
732
770
  lang: L;
733
771
  /**
734
772
  * Function to replace template content
773
+ *
774
+ * @example
775
+ * ```ts
776
+ * const handler: CustomHandler = async ({ state, templateReplace }) => {
777
+ * const text = templateReplace({ en: '' }, state())
778
+ * }
779
+ * ```
735
780
  */
736
781
  templateReplace: (content: TextContent<L, State>, values?: State) => string;
737
782
  /**
738
783
  * Is game currently paused
784
+ *
785
+ * @example
786
+ * ```ts
787
+ * const handler: CustomHandler = async ({ clear, paused }) => {
788
+ * const unsubscribe = paused.subscribe((paused) => {
789
+ * // Here you can pause/resume animations, sounds, etc, etc
790
+ * })
791
+ *
792
+ * clear(paused);
793
+ * }
739
794
  */
740
795
  paused: Derived<boolean>;
741
796
  };
@@ -766,16 +821,12 @@ type CustomHandlerInfo = CustomHandlerCalling & {
766
821
  * When true interacting with it will be saved in history
767
822
  */
768
823
  requireUserAction?: boolean;
769
- /**
770
- * When player is going back we clear every custom action. But we can ignore clearing that.
771
- */
772
- skipClearOnGoingBack?: boolean;
773
824
  /**
774
825
  * Id by which we will determine what action is which
775
826
  */
776
827
  id: string | symbol;
777
828
  /**
778
- * Key by which we will save the data in the `get` function provided to custom action.
829
+ * Key by which we will save the custom action's data. It includes cleanup function's provided by `clear` and data in `data` function
779
830
  *
780
831
  * It can be a name of action or more specific thing. In example for custom `showCharacter` it may be `show-character-${character}
781
832
  */
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
 
@@ -674,7 +675,7 @@ var createQueueProcessor = (queue, options) => {
674
675
  if (action === "function" || action === "custom") {
675
676
  if (action === "custom") {
676
677
  const fn = params[0];
677
- if ("callOnlyLatest" in fn && fn.callOnlyLatest) {
678
+ if (fn.callOnlyLatest) {
678
679
  const notLatest = next(i).some(([name, func]) => {
679
680
  if (name !== "custom") return;
680
681
  const isIdenticalId = Boolean(func.id && fn.id && func.id === fn.id);
@@ -683,7 +684,7 @@ var createQueueProcessor = (queue, options) => {
683
684
  return isIdenticalId || isIdenticalByReference || isIdenticalByCode;
684
685
  });
685
686
  if (notLatest) continue;
686
- } else if ("skipOnRestore" in fn && fn.skipOnRestore) {
687
+ } else if (fn.skipOnRestore) {
687
688
  if (fn.skipOnRestore(next(i))) {
688
689
  continue;
689
690
  }
@@ -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,17 +1017,38 @@ 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
+ };
1019
1029
  var handleCustomAction = (ctx, fn, {
1020
1030
  lang,
1021
1031
  state,
1022
1032
  setMountElement,
1023
- setClear,
1024
1033
  remove: renderersRemove,
1025
1034
  getStack: getStack2,
1026
1035
  templateReplace,
1027
1036
  paused
1028
1037
  }) => {
1029
1038
  const holder = getCustomActionHolder(ctx, fn);
1039
+ const cleanupHolder = getCustomActionCleanupHolder(ctx);
1040
+ const cleanupNode = () => {
1041
+ if (!cleanupHolder.some((item) => item.fn.id === fn.id && item.fn.key === fn.key)) {
1042
+ holder.node = null;
1043
+ setMountElement(null);
1044
+ }
1045
+ };
1046
+ const cleanupSource = {
1047
+ fn,
1048
+ list: /* @__PURE__ */ new Set(),
1049
+ dom: cleanupNode
1050
+ };
1051
+ cleanupHolder.push(cleanupSource);
1030
1052
  const flags = {
1031
1053
  ...ctx.meta
1032
1054
  };
@@ -1045,15 +1067,7 @@ var handleCustomAction = (ctx, fn, {
1045
1067
  };
1046
1068
  };
1047
1069
  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
- );
1070
+ cleanupSource.list.add(once2(func));
1057
1071
  };
1058
1072
  const data = (updatedData) => {
1059
1073
  if (updatedData) {
@@ -1062,13 +1076,12 @@ var handleCustomAction = (ctx, fn, {
1062
1076
  return holder.localData;
1063
1077
  };
1064
1078
  const remove = () => {
1065
- holder.cleanup();
1079
+ cleanupSource.list.forEach((fn2) => fn2());
1080
+ cleanupSource.list.clear();
1081
+ cleanupSource.dom();
1066
1082
  renderersRemove();
1067
1083
  };
1068
1084
  const stack = getStack2(ctx);
1069
- const getPath = () => {
1070
- return stack.value[0];
1071
- };
1072
1085
  const getSave = () => {
1073
1086
  return stack.value;
1074
1087
  };
@@ -1077,13 +1090,13 @@ var handleCustomAction = (ctx, fn, {
1077
1090
  lang,
1078
1091
  state,
1079
1092
  data,
1093
+ dataAtKey: (key) => CUSTOM_ACTION_MAP.get(ctx.id + key)?.localData || null,
1080
1094
  templateReplace,
1081
1095
  clear,
1082
1096
  remove,
1083
1097
  rendererContext: ctx,
1084
1098
  getDomNodes,
1085
1099
  getSave,
1086
- getPath,
1087
1100
  contextKey: ctx.id,
1088
1101
  paused: flags.preview ? immutable(false) : paused
1089
1102
  });
@@ -1610,13 +1623,7 @@ var novely = ({
1610
1623
  filter: false,
1611
1624
  referGuarded
1612
1625
  });
1613
- const {
1614
- run,
1615
- keep: { keep, characters: characters2, audio: audio2 }
1616
- } = createQueueProcessor(queue, {
1617
- skip,
1618
- skipPreserve
1619
- });
1626
+ const cleanupHolder = getCustomActionCleanupHolder(context);
1620
1627
  if (previous) {
1621
1628
  const { queue: prevQueue } = await getActionsFromPath({
1622
1629
  story,
@@ -1624,17 +1631,38 @@ var novely = ({
1624
1631
  filter: false,
1625
1632
  referGuarded
1626
1633
  });
1627
- for (let i = prevQueue.length - 1; i > queue.length - 1; i--) {
1628
- const element = prevQueue[i];
1629
- if (!isAction(element)) {
1630
- continue;
1631
- }
1632
- const [action2, fn] = element;
1634
+ const futures = [];
1635
+ const isFromDifferentBranches = previous[0][0][1] !== path[0][1];
1636
+ const end = isFromDifferentBranches ? 0 : queue.length - 1;
1637
+ for (let i = prevQueue.length - 1; i >= end; i--) {
1638
+ const [action2, fn] = prevQueue[i];
1633
1639
  if (action2 === "custom") {
1634
- getCustomActionHolder(context, fn).cleanup();
1640
+ futures.push(fn);
1641
+ }
1642
+ }
1643
+ futures.reverse();
1644
+ const dom = /* @__PURE__ */ new Set();
1645
+ for (const future of futures) {
1646
+ inner: for (let i = cleanupHolder.length - 1; i >= 0; i--) {
1647
+ const item = cleanupHolder[i];
1648
+ if (future === item.fn) {
1649
+ item.list.forEach((fn) => fn());
1650
+ item.list.clear();
1651
+ cleanupHolder.splice(i, 1);
1652
+ dom.add(item.dom);
1653
+ break inner;
1654
+ }
1635
1655
  }
1636
1656
  }
1657
+ dom.forEach((f) => f());
1637
1658
  }
1659
+ const {
1660
+ run,
1661
+ keep: { keep, characters: characters2, audio: audio2 }
1662
+ } = createQueueProcessor(queue, {
1663
+ skip,
1664
+ skipPreserve
1665
+ });
1638
1666
  if (context.meta.goingBack) {
1639
1667
  match("clear", [keep, characters2, audio2], {
1640
1668
  ctx: context,
@@ -1650,6 +1678,11 @@ var novely = ({
1650
1678
  return;
1651
1679
  }
1652
1680
  const [action2, ...props] = item;
1681
+ if (action2 === "custom") {
1682
+ if (cleanupHolder.some((holder) => holder.fn === props[0])) {
1683
+ return;
1684
+ }
1685
+ }
1653
1686
  return match(action2, props, {
1654
1687
  ctx: context,
1655
1688
  data: latest[1]
@@ -1783,8 +1816,16 @@ var novely = ({
1783
1816
  }
1784
1817
  return capitalize(language.nameOverride || getIntlLanguageDisplayName(lang));
1785
1818
  };
1786
- const clearCustomAction = (ctx, fn) => {
1787
- getCustomActionHolder(ctx, fn).cleanup();
1819
+ const clearCustomActionsAtContext = (ctx) => {
1820
+ const cleanupHolder = getCustomActionCleanupHolder(ctx);
1821
+ const dom = /* @__PURE__ */ new Set();
1822
+ for (const item of cleanupHolder) {
1823
+ item.list.forEach((fn) => fn());
1824
+ item.list.clear();
1825
+ dom.add(item.dom);
1826
+ }
1827
+ cleanupHolder.length = 0;
1828
+ dom.forEach((fn) => fn());
1788
1829
  };
1789
1830
  const getResourseTypeWrapper = (url) => {
1790
1831
  return getResourseType({
@@ -1839,7 +1880,7 @@ var novely = ({
1839
1880
  preview,
1840
1881
  removeContext,
1841
1882
  getStateFunction,
1842
- clearCustomAction,
1883
+ clearCustomActionsAtContext,
1843
1884
  languages,
1844
1885
  storageData,
1845
1886
  coreData,