@rohal12/spindle 0.52.0 → 0.52.1
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/pkg/format.js +1 -1
- package/dist/pkg/headless.js +241 -104
- package/package.json +1 -1
- package/src/action-registry.ts +70 -18
- package/src/components/macros/Computed.tsx +13 -5
- package/src/components/macros/Set.tsx +5 -4
- package/src/components/macros/Unset.tsx +5 -4
- package/src/execute-mutation.ts +1 -1
- package/src/expression.ts +5 -4
- package/src/hooks/use-action.ts +10 -4
- package/src/index.tsx +2 -1
- package/src/markup/ast.ts +7 -2
- package/src/prng.ts +33 -0
- package/src/saves/save-manager.ts +7 -2
- package/src/settings.ts +12 -9
- package/src/store.ts +119 -32
- package/src/story-api.ts +7 -6
- package/src/triggers.ts +4 -7
- package/src/utils/counts.ts +45 -0
package/dist/pkg/headless.js
CHANGED
|
@@ -149,10 +149,10 @@ function equal(a, b, assumed) {
|
|
|
149
149
|
const bo = b;
|
|
150
150
|
const keys = Object.keys(ao);
|
|
151
151
|
if (keys.length !== Object.keys(bo).length) return false;
|
|
152
|
-
for (const key of keys) if (!hasOwn$
|
|
152
|
+
for (const key of keys) if (!hasOwn$3(bo, key) || !equal(ao[key], bo[key], assumed)) return false;
|
|
153
153
|
return true;
|
|
154
154
|
}
|
|
155
|
-
var hasOwn$
|
|
155
|
+
var hasOwn$3 = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
156
156
|
var CLASS_TAG = "__spindle_class__";
|
|
157
157
|
var DATA_TAG = "__spindle_data__";
|
|
158
158
|
/** A tagged value, as serialize() writes and deserialize() reads it. */
|
|
@@ -224,7 +224,7 @@ function serialize(value) {
|
|
|
224
224
|
const name = registeredClassName(val);
|
|
225
225
|
if (name !== void 0) return tagged(name, serKeys(val));
|
|
226
226
|
const data = serKeys(val);
|
|
227
|
-
return hasOwn$
|
|
227
|
+
return hasOwn$3(val, CLASS_TAG) ? tagged("__Object__", data) : data;
|
|
228
228
|
}
|
|
229
229
|
return ser(value);
|
|
230
230
|
}
|
|
@@ -310,7 +310,7 @@ function isDeserializable(value) {
|
|
|
310
310
|
case "__Map__": return Array.isArray(data.entries) && Array.from(data.entries).every((entry) => Array.isArray(entry) && entry.length === 2 && check(entry[0]) && check(entry[1]));
|
|
311
311
|
case "__Set__": return Array.isArray(data.entries) && Array.from(data.entries).every(check);
|
|
312
312
|
case "__Undefined__": return true;
|
|
313
|
-
case "__Number__": return typeof data.value === "string" && hasOwn$
|
|
313
|
+
case "__Number__": return typeof data.value === "string" && hasOwn$3(SPECIAL_NUMBERS, data.value);
|
|
314
314
|
case "__BigInt__": return typeof data.value === "string" && /^-?\d+$/.test(data.value);
|
|
315
315
|
default: return checkKeys(data);
|
|
316
316
|
}
|
|
@@ -2743,8 +2743,8 @@ function buildAST(tokens) {
|
|
|
2743
2743
|
current().push(top.node);
|
|
2744
2744
|
break;
|
|
2745
2745
|
}
|
|
2746
|
-
|
|
2747
|
-
|
|
2746
|
+
const expectedParent = Object.prototype.hasOwnProperty.call(BRANCH_PARENT, name) ? BRANCH_PARENT[name] : void 0;
|
|
2747
|
+
if (expectedParent) {
|
|
2748
2748
|
const topNode = stack.length > 0 ? stack[stack.length - 1].node : null;
|
|
2749
2749
|
if (!topNode || topNode.type !== "macro" || topNode.name !== expectedParent) throw new Error(`{${token.name}} without matching {${expectedParent}} (at character ${token.start})`);
|
|
2750
2750
|
const branch = {
|
|
@@ -2860,6 +2860,8 @@ var enabled = false;
|
|
|
2860
2860
|
var currentSeed = "";
|
|
2861
2861
|
var currentPull = 0;
|
|
2862
2862
|
var generator = null;
|
|
2863
|
+
/** Incremented whenever the generator is replaced (init, restore, reset). */
|
|
2864
|
+
var generation = 0;
|
|
2863
2865
|
/**
|
|
2864
2866
|
* Initialize the PRNG.
|
|
2865
2867
|
* @param seed Optional seed string. If omitted, a random seed is generated.
|
|
@@ -2876,12 +2878,18 @@ function initPRNG(seed, useEntropy = true) {
|
|
|
2876
2878
|
currentPull = 0;
|
|
2877
2879
|
generator = mulberry32(hashSeed(resolvedSeed));
|
|
2878
2880
|
enabled = true;
|
|
2881
|
+
generation++;
|
|
2879
2882
|
}
|
|
2880
2883
|
/**
|
|
2881
2884
|
* Restore PRNG state from a snapshot (used on save/load).
|
|
2882
2885
|
* Recreates the generator from the seed and fast-forwards to the saved pull count.
|
|
2883
2886
|
*/
|
|
2884
2887
|
function restorePRNG(seed, pull) {
|
|
2888
|
+
generation++;
|
|
2889
|
+
moveTo(seed, pull);
|
|
2890
|
+
}
|
|
2891
|
+
/** Recreate the generator at `pull` pulls from `seed`. */
|
|
2892
|
+
function moveTo(seed, pull) {
|
|
2885
2893
|
currentSeed = seed;
|
|
2886
2894
|
const steps = Math.imul(pull % 4294967296, MULBERRY_STEP);
|
|
2887
2895
|
generator = mulberry32(hashSeed(seed) + steps | 0);
|
|
@@ -2890,11 +2898,32 @@ function restorePRNG(seed, pull) {
|
|
|
2890
2898
|
}
|
|
2891
2899
|
/** Disable the PRNG (used on restart before StoryInit re-enables it). */
|
|
2892
2900
|
function resetPRNG() {
|
|
2901
|
+
generation++;
|
|
2893
2902
|
enabled = false;
|
|
2894
2903
|
currentSeed = "";
|
|
2895
2904
|
currentPull = 0;
|
|
2896
2905
|
generator = null;
|
|
2897
2906
|
}
|
|
2907
|
+
/**
|
|
2908
|
+
* Run `fn` without its draws advancing the sequence: the draws it makes are
|
|
2909
|
+
* taken back afterwards (also when it throws), so the next draws return the
|
|
2910
|
+
* same values again. Save and load event handlers run like this, so that
|
|
2911
|
+
* saving or loading never shifts the story's random sequence (a load replays
|
|
2912
|
+
* the saved passage from its entry state, without the save's handlers). A
|
|
2913
|
+
* generator `fn` replaced (a restart or load it started) is kept.
|
|
2914
|
+
*/
|
|
2915
|
+
function withoutDraws(fn) {
|
|
2916
|
+
const start = {
|
|
2917
|
+
generation,
|
|
2918
|
+
seed: currentSeed,
|
|
2919
|
+
pull: currentPull
|
|
2920
|
+
};
|
|
2921
|
+
try {
|
|
2922
|
+
return fn();
|
|
2923
|
+
} finally {
|
|
2924
|
+
if (enabled && generation === start.generation && currentPull !== start.pull) moveTo(start.seed, start.pull);
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2898
2927
|
/** Returns the current PRNG state for snapshotting, or null if disabled. */
|
|
2899
2928
|
function snapshotPRNG() {
|
|
2900
2929
|
if (!enabled) return null;
|
|
@@ -2958,6 +2987,30 @@ var EMPTY_NAMESPACE = Object.freeze(createNamespace());
|
|
|
2958
2987
|
/** `ns[key]` if `ns` holds it as an own property, else undefined. */
|
|
2959
2988
|
var ownValue = (ns, key) => Object.prototype.hasOwnProperty.call(ns, key) ? ns[key] : void 0;
|
|
2960
2989
|
//#endregion
|
|
2990
|
+
//#region src/utils/counts.ts
|
|
2991
|
+
var isCount = (value) => typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
2992
|
+
/** The count of `name` in `counts`: its own entry, or 0. */
|
|
2993
|
+
function countOf(counts, name) {
|
|
2994
|
+
if (!counts || !Object.prototype.hasOwnProperty.call(counts, name)) return 0;
|
|
2995
|
+
const value = counts[name];
|
|
2996
|
+
return isCount(value) ? value : 0;
|
|
2997
|
+
}
|
|
2998
|
+
/**
|
|
2999
|
+
* Counters with no prototype holding the own entries of `source` that are
|
|
3000
|
+
* counts, with `name` (if given) counted once more. Immer copies the
|
|
3001
|
+
* counters on every write anyway, so counting into a fresh copy costs no
|
|
3002
|
+
* more than counting in place.
|
|
3003
|
+
*/
|
|
3004
|
+
function createCounts(source, name) {
|
|
3005
|
+
const counts = Object.create(null);
|
|
3006
|
+
if (source) for (const key of Object.keys(source)) {
|
|
3007
|
+
const value = source[key];
|
|
3008
|
+
if (isCount(value)) counts[key] = value;
|
|
3009
|
+
}
|
|
3010
|
+
if (name !== void 0) counts[name] = countOf(source, name) + 1;
|
|
3011
|
+
return counts;
|
|
3012
|
+
}
|
|
3013
|
+
//#endregion
|
|
2961
3014
|
//#region src/expression.ts
|
|
2962
3015
|
var FN_CACHE_MAX = 500;
|
|
2963
3016
|
var fnCache = /* @__PURE__ */ new Map();
|
|
@@ -3021,11 +3074,11 @@ var cachedRenderCounts = null;
|
|
|
3021
3074
|
function buildExpressionFns() {
|
|
3022
3075
|
const { visitCounts, renderCounts } = useStoryStore.getState();
|
|
3023
3076
|
if (cachedFns && cachedVisitCounts === visitCounts && cachedRenderCounts === renderCounts) return cachedFns;
|
|
3024
|
-
const visited = (name) => visitCounts
|
|
3077
|
+
const visited = (name) => countOf(visitCounts, name ?? useStoryStore.getState().currentPassage);
|
|
3025
3078
|
const hasVisited = (name) => visited(name) > 0;
|
|
3026
3079
|
const hasVisitedAny = (...names) => names.some((n) => visited(n) > 0);
|
|
3027
3080
|
const hasVisitedAll = (...names) => names.every((n) => visited(n) > 0);
|
|
3028
|
-
const rendered = (name) => renderCounts
|
|
3081
|
+
const rendered = (name) => countOf(renderCounts, name ?? useStoryStore.getState().currentPassage);
|
|
3029
3082
|
const hasRendered = (name) => rendered(name) > 0;
|
|
3030
3083
|
const hasRenderedAny = (...names) => names.some((n) => rendered(n) > 0);
|
|
3031
3084
|
const hasRenderedAll = (...names) => names.every((n) => rendered(n) > 0);
|
|
@@ -5150,6 +5203,10 @@ function establishPlaythrough(ifid) {
|
|
|
5150
5203
|
* that captures the payload after them, so the capture can tell which
|
|
5151
5204
|
* variables the hooks changed (the store's `beginSave`).
|
|
5152
5205
|
*
|
|
5206
|
+
* Random numbers the hooks draw do not advance the story's PRNG (see
|
|
5207
|
+
* withoutDraws()): the game goes on with the sequence a load of the save
|
|
5208
|
+
* continues with, which replays the saved passage without the hooks.
|
|
5209
|
+
*
|
|
5153
5210
|
* The hook and capture run synchronously, but an error they throw (a failing
|
|
5154
5211
|
* hook, an unserializable payload) rejects the returned promise like a failed
|
|
5155
5212
|
* write rather than escaping to the caller.
|
|
@@ -5157,9 +5214,9 @@ function establishPlaythrough(ifid) {
|
|
|
5157
5214
|
function saveWithHooks(slot, custom, beginCapture, write) {
|
|
5158
5215
|
try {
|
|
5159
5216
|
const capture = beginCapture();
|
|
5160
|
-
emit("beforesave", slot, custom);
|
|
5217
|
+
withoutDraws(() => emit("beforesave", slot, custom));
|
|
5161
5218
|
return write(capture()).then((result) => {
|
|
5162
|
-
emit("aftersave", slot);
|
|
5219
|
+
withoutDraws(() => emit("aftersave", slot));
|
|
5163
5220
|
return result;
|
|
5164
5221
|
});
|
|
5165
5222
|
} catch (err) {
|
|
@@ -5727,7 +5784,7 @@ function initializeStory(session) {
|
|
|
5727
5784
|
}
|
|
5728
5785
|
//#endregion
|
|
5729
5786
|
//#region src/utils/object-path.ts
|
|
5730
|
-
var hasOwn$
|
|
5787
|
+
var hasOwn$2 = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
5731
5788
|
/**
|
|
5732
5789
|
* Traverse dot-path segments on an object and return the nested value.
|
|
5733
5790
|
* Members of Object.prototype (`constructor`, `toString`, `__proto__`, ...)
|
|
@@ -5739,7 +5796,7 @@ function getByPath(obj, segments) {
|
|
|
5739
5796
|
let current = obj;
|
|
5740
5797
|
for (const seg of segments) {
|
|
5741
5798
|
if (current == null || typeof current !== "object") return void 0;
|
|
5742
|
-
if (seg in Object.prototype && !hasOwn$
|
|
5799
|
+
if (seg in Object.prototype && !hasOwn$2(current, seg)) return void 0;
|
|
5743
5800
|
current = current[seg];
|
|
5744
5801
|
}
|
|
5745
5802
|
return current;
|
|
@@ -5797,10 +5854,10 @@ function deleteByPath(root, segments) {
|
|
|
5797
5854
|
checkSegments(segments);
|
|
5798
5855
|
let holder = root;
|
|
5799
5856
|
for (const seg of segments.slice(0, -1)) {
|
|
5800
|
-
if (holder == null || typeof holder !== "object" || !hasOwn$
|
|
5857
|
+
if (holder == null || typeof holder !== "object" || !hasOwn$2(holder, seg)) return;
|
|
5801
5858
|
holder = holder[seg];
|
|
5802
5859
|
}
|
|
5803
|
-
if (holder == null || typeof holder !== "object" || !hasOwn$
|
|
5860
|
+
if (holder == null || typeof holder !== "object" || !hasOwn$2(holder, last)) return;
|
|
5804
5861
|
const parent = walkToParent(root, segments, false);
|
|
5805
5862
|
delete parent[last];
|
|
5806
5863
|
}
|
|
@@ -5826,7 +5883,7 @@ function walkToParent(root, segments, createMissing) {
|
|
|
5826
5883
|
for (let i = 0; i < segments.length - 1; i++) {
|
|
5827
5884
|
const seg = segments[i];
|
|
5828
5885
|
checkHolder(current, seg, segments, i);
|
|
5829
|
-
let next = hasOwn$
|
|
5886
|
+
let next = hasOwn$2(current, seg) ? current[seg] : void 0;
|
|
5830
5887
|
if (next == null || typeof next !== "object") {
|
|
5831
5888
|
if (!createMissing) throw new TypeError(`spindle: Cannot set property "${segments[i + 1]}" on ${next === null ? "null" : typeof next} (at "${segments.slice(0, i + 1).join(".")}")`);
|
|
5832
5889
|
next = {};
|
|
@@ -5890,7 +5947,7 @@ function frozenCopy(value) {
|
|
|
5890
5947
|
function isMergeable(value) {
|
|
5891
5948
|
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Map) && !(value instanceof Set) && !(value instanceof Date) && !(value instanceof RegExp);
|
|
5892
5949
|
}
|
|
5893
|
-
var hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
5950
|
+
var hasOwn$1 = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
5894
5951
|
/** Collect the property paths where `work` differs from `base`. */
|
|
5895
5952
|
function diff(base, work, path, changes, ancestors) {
|
|
5896
5953
|
if (ancestors.has(work)) return;
|
|
@@ -5898,7 +5955,7 @@ function diff(base, work, path, changes, ancestors) {
|
|
|
5898
5955
|
for (const key of Object.keys(work)) {
|
|
5899
5956
|
const b = base[key];
|
|
5900
5957
|
const w = work[key];
|
|
5901
|
-
if (!hasOwn(base, key)) changes.push({
|
|
5958
|
+
if (!hasOwn$1(base, key)) changes.push({
|
|
5902
5959
|
path: [...path, key],
|
|
5903
5960
|
deleted: false,
|
|
5904
5961
|
value: w
|
|
@@ -5911,7 +5968,7 @@ function diff(base, work, path, changes, ancestors) {
|
|
|
5911
5968
|
value: w
|
|
5912
5969
|
});
|
|
5913
5970
|
}
|
|
5914
|
-
for (const key of Object.keys(base)) if (!hasOwn(work, key)) changes.push({
|
|
5971
|
+
for (const key of Object.keys(base)) if (!hasOwn$1(work, key)) changes.push({
|
|
5915
5972
|
path: [...path, key],
|
|
5916
5973
|
deleted: true
|
|
5917
5974
|
});
|
|
@@ -5922,7 +5979,7 @@ function isApplied(ns, change) {
|
|
|
5922
5979
|
const parent = getByPath(ns, change.path.slice(0, -1));
|
|
5923
5980
|
if (parent === null || typeof parent !== "object") return change.deleted;
|
|
5924
5981
|
const key = change.path[change.path.length - 1];
|
|
5925
|
-
if (!hasOwn(parent, key)) return change.deleted;
|
|
5982
|
+
if (!hasOwn$1(parent, key)) return change.deleted;
|
|
5926
5983
|
return !change.deleted && deepEqual(parent[key], change.value);
|
|
5927
5984
|
}
|
|
5928
5985
|
function applyChange(ns, change) {
|
|
@@ -5947,7 +6004,7 @@ function mirror(draft, ns, change, scopes = activeScopes) {
|
|
|
5947
6004
|
});
|
|
5948
6005
|
} catch {
|
|
5949
6006
|
const stored = draft[ns][root];
|
|
5950
|
-
if (hasOwn(draft[ns], root)) copy[root] = cloneValue(isDraft(stored) ? current(stored) : stored);
|
|
6007
|
+
if (hasOwn$1(draft[ns], root)) copy[root] = cloneValue(isDraft(stored) ? current(stored) : stored);
|
|
5951
6008
|
else delete copy[root];
|
|
5952
6009
|
}
|
|
5953
6010
|
}
|
|
@@ -5971,7 +6028,7 @@ function changedPath(ns, segments) {
|
|
|
5971
6028
|
if (!isMergeable(node)) break;
|
|
5972
6029
|
const key = String(segment);
|
|
5973
6030
|
path.push(key);
|
|
5974
|
-
node = hasOwn(node, key) ? node[key] : void 0;
|
|
6031
|
+
node = hasOwn$1(node, key) ? node[key] : void 0;
|
|
5975
6032
|
}
|
|
5976
6033
|
return path;
|
|
5977
6034
|
}
|
|
@@ -5979,7 +6036,7 @@ function changedPath(ns, segments) {
|
|
|
5979
6036
|
function changeTo(ns, path) {
|
|
5980
6037
|
const parent = getByPath(ns, path.slice(0, -1));
|
|
5981
6038
|
const key = path[path.length - 1];
|
|
5982
|
-
return isMergeable(parent) && hasOwn(parent, key) ? {
|
|
6039
|
+
return isMergeable(parent) && hasOwn$1(parent, key) ? {
|
|
5983
6040
|
path,
|
|
5984
6041
|
deleted: false,
|
|
5985
6042
|
value: parent[key]
|
|
@@ -5995,7 +6052,7 @@ function changeTo(ns, path) {
|
|
|
5995
6052
|
* replaces exactly what it replaced in program order.
|
|
5996
6053
|
*/
|
|
5997
6054
|
function writtenPaths(before, after, patches) {
|
|
5998
|
-
if (patches.some((p) => p.path.length === 1)) return [...new Set([...Object.keys(before), ...Object.keys(after)])].filter((root) => !hasOwn(before, root) || before[root] !== after[root]).map((root) => changeTo(after, [root]));
|
|
6055
|
+
if (patches.some((p) => p.path.length === 1)) return [...new Set([...Object.keys(before), ...Object.keys(after)])].filter((root) => !hasOwn$1(before, root) || before[root] !== after[root]).map((root) => changeTo(after, [root]));
|
|
5999
6056
|
const seen = /* @__PURE__ */ new Set();
|
|
6000
6057
|
const changes = [];
|
|
6001
6058
|
for (const patch of patches) {
|
|
@@ -6067,7 +6124,7 @@ function routeStoreUpdate(recipe) {
|
|
|
6067
6124
|
try {
|
|
6068
6125
|
applyChange(namespaces[ns], owned(change));
|
|
6069
6126
|
} catch {
|
|
6070
|
-
if (hasOwn(inner.work[ns], root)) namespaces[ns][root] = cloneValue(inner.work[ns][root]);
|
|
6127
|
+
if (hasOwn$1(inner.work[ns], root)) namespaces[ns][root] = cloneValue(inner.work[ns][root]);
|
|
6071
6128
|
else delete namespaces[ns][root];
|
|
6072
6129
|
}
|
|
6073
6130
|
mirror(namespaces, ns, change);
|
|
@@ -6153,8 +6210,8 @@ function resync(scope) {
|
|
|
6153
6210
|
for (const ns of NAMESPACES) {
|
|
6154
6211
|
const work = scope.work[ns];
|
|
6155
6212
|
const stored = state[ns];
|
|
6156
|
-
for (const key of Object.keys(work)) if (!hasOwn(stored, key)) delete work[key];
|
|
6157
|
-
for (const key of Object.keys(stored)) if (!hasOwn(work, key) || !deepEqual(work[key], stored[key])) work[key] = deepClone(stored[key]);
|
|
6213
|
+
for (const key of Object.keys(work)) if (!hasOwn$1(stored, key)) delete work[key];
|
|
6214
|
+
for (const key of Object.keys(stored)) if (!hasOwn$1(work, key) || !deepEqual(work[key], stored[key])) work[key] = deepClone(stored[key]);
|
|
6158
6215
|
}
|
|
6159
6216
|
scope.base = cloneNamespaces(state);
|
|
6160
6217
|
}
|
|
@@ -6194,11 +6251,10 @@ function executeMutation(code, mergedLocals, scopeUpdate) {
|
|
|
6194
6251
|
keepRunning: false
|
|
6195
6252
|
}]);
|
|
6196
6253
|
for (const key of Object.keys(localsClone)) if (!deepEqual(localsClone[key], mergedLocals[key])) scopeUpdate(key, localsClone[key]);
|
|
6197
|
-
for (const key of Object.keys(mergedLocals)) if (!hasOwn(localsClone, key)) scopeUpdate(key, void 0);
|
|
6254
|
+
for (const key of Object.keys(mergedLocals)) if (!hasOwn$1(localsClone, key)) scopeUpdate(key, void 0);
|
|
6198
6255
|
}
|
|
6199
6256
|
//#endregion
|
|
6200
6257
|
//#region src/triggers.ts
|
|
6201
|
-
var nextId = 0;
|
|
6202
6258
|
var triggers = [];
|
|
6203
6259
|
var checking = false;
|
|
6204
6260
|
var dialogQueue = [];
|
|
@@ -6214,12 +6270,10 @@ function evalCondition(condition) {
|
|
|
6214
6270
|
}
|
|
6215
6271
|
}
|
|
6216
6272
|
function registerTrigger(condition, callbackOrOptions, macroKey) {
|
|
6217
|
-
const id = nextId++;
|
|
6218
6273
|
const isCallback = typeof callbackOrOptions === "function";
|
|
6219
6274
|
const options = isCallback ? void 0 : callbackOrOptions;
|
|
6220
6275
|
const callback = isCallback ? callbackOrOptions : void 0;
|
|
6221
6276
|
const trigger = {
|
|
6222
|
-
id,
|
|
6223
6277
|
name: options?.name,
|
|
6224
6278
|
condition,
|
|
6225
6279
|
callback,
|
|
@@ -6231,7 +6285,7 @@ function registerTrigger(condition, callbackOrOptions, macroKey) {
|
|
|
6231
6285
|
triggers.push(trigger);
|
|
6232
6286
|
triggers.sort((a, b) => b.priority - a.priority);
|
|
6233
6287
|
return () => {
|
|
6234
|
-
triggers = triggers.filter((t) => t
|
|
6288
|
+
triggers = triggers.filter((t) => t !== trigger);
|
|
6235
6289
|
};
|
|
6236
6290
|
}
|
|
6237
6291
|
function addTrigger(condition, callbackOrOptions) {
|
|
@@ -6283,7 +6337,7 @@ function runCheckLoop() {
|
|
|
6283
6337
|
trigger.lastResult = result;
|
|
6284
6338
|
if (result && wasFalse) {
|
|
6285
6339
|
anyFired = true;
|
|
6286
|
-
if (trigger.options?.once) triggers = triggers.filter((t) => t
|
|
6340
|
+
if (trigger.options?.once) triggers = triggers.filter((t) => t !== trigger);
|
|
6287
6341
|
fireTrigger(trigger);
|
|
6288
6342
|
}
|
|
6289
6343
|
}
|
|
@@ -6349,7 +6403,6 @@ function reinitTriggerState() {
|
|
|
6349
6403
|
function resetTriggers() {
|
|
6350
6404
|
triggers = [];
|
|
6351
6405
|
dialogQueue = [];
|
|
6352
|
-
nextId = 0;
|
|
6353
6406
|
dialogHostCallbacks?.closeAll();
|
|
6354
6407
|
}
|
|
6355
6408
|
function subscribeTriggerDialogs(cb) {
|
|
@@ -6716,20 +6769,65 @@ function loadedEntryMoment(payload) {
|
|
|
6716
6769
|
return moment;
|
|
6717
6770
|
}
|
|
6718
6771
|
/**
|
|
6719
|
-
*
|
|
6720
|
-
*
|
|
6721
|
-
*
|
|
6722
|
-
*
|
|
6723
|
-
*
|
|
6724
|
-
*
|
|
6725
|
-
*
|
|
6772
|
+
* Write the changes between `before` and `after` (the live variables around
|
|
6773
|
+
* the `beforesave` hooks) into the payload's snapshot of the saved moment. A
|
|
6774
|
+
* load restores that snapshot, the state on entering the passage, and runs
|
|
6775
|
+
* the passage again, so data a hook adds to a save would otherwise be lost
|
|
6776
|
+
* on load (#227). Only the property paths the hooks changed are written: a
|
|
6777
|
+
* whole variable would bring along what the passage did to the rest of it,
|
|
6778
|
+
* which the passage then does again on load (#232). Only the payload's copy
|
|
6779
|
+
* changes: the live history keeps the recorded snapshot (#159).
|
|
6726
6780
|
*/
|
|
6727
6781
|
function keepHookWrites(payload, before, after) {
|
|
6728
6782
|
const moment = payload.history[payload.historyIndex];
|
|
6729
6783
|
if (!moment) return;
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6784
|
+
mergeHookWrites(moment.variables, before, after, /* @__PURE__ */ new Set());
|
|
6785
|
+
}
|
|
6786
|
+
var hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
6787
|
+
/**
|
|
6788
|
+
* Whether changes between `a` and `b` merge key by key: both are arrays,
|
|
6789
|
+
* or both are objects of one class (see isMergeable).
|
|
6790
|
+
*/
|
|
6791
|
+
function mergeable(a, b) {
|
|
6792
|
+
if (Array.isArray(a) || Array.isArray(b)) return Array.isArray(a) && Array.isArray(b);
|
|
6793
|
+
return isMergeable(a) && isMergeable(b) && Object.getPrototypeOf(a) === Object.getPrototypeOf(b);
|
|
6794
|
+
}
|
|
6795
|
+
/**
|
|
6796
|
+
* Apply the hooks' change of a value, from `before` to `after`, to the
|
|
6797
|
+
* snapshot's copy of it, `target[key]`. Store updates are immutable, so a
|
|
6798
|
+
* value no hook touched keeps its identity; one rebuilt with equal content
|
|
6799
|
+
* (mutation code commits whole values) is no change either. Where `target`
|
|
6800
|
+
* lacks the key or holds another kind of value (the passage created or
|
|
6801
|
+
* replaced it), the hooks' whole value is written.
|
|
6802
|
+
*/
|
|
6803
|
+
function mergeHookWrite(target, key, before, after, ancestors) {
|
|
6804
|
+
if (Object.is(before, after)) return;
|
|
6805
|
+
if (mergeable(before, after) && hasOwn(target, key) && mergeable(after, target[key]) && !ancestors.has(after)) mergeHookWrites(target[key], before, after, ancestors);
|
|
6806
|
+
else if (!deepEqual(before, after)) target[key] = deepClone(after);
|
|
6807
|
+
}
|
|
6808
|
+
/**
|
|
6809
|
+
* Apply the changes between `before` and `after`, two objects or two arrays
|
|
6810
|
+
* (see mergeable()), to the snapshot's `target`. Array elements merge by
|
|
6811
|
+
* index. Elements the hooks removed from an array's end are removed from
|
|
6812
|
+
* the snapshot's array at the same indices, and elements they added are
|
|
6813
|
+
* appended to it: where the passage resized the array, its indices do not
|
|
6814
|
+
* line up with the snapshot's, and the passage resizes it again on load.
|
|
6815
|
+
*/
|
|
6816
|
+
function mergeHookWrites(target, before, after, ancestors) {
|
|
6817
|
+
ancestors.add(after);
|
|
6818
|
+
if (Array.isArray(target)) {
|
|
6819
|
+
const b = before;
|
|
6820
|
+
const a = after;
|
|
6821
|
+
const shared = Math.min(b.length, a.length, target.length);
|
|
6822
|
+
for (let i = 0; i < shared; i++) mergeHookWrite(target, String(i), b[i], a[i], ancestors);
|
|
6823
|
+
if (a.length < b.length) target.length = Math.min(target.length, a.length);
|
|
6824
|
+
for (let i = b.length; i < a.length; i++) target.push(deepClone(a[i]));
|
|
6825
|
+
} else {
|
|
6826
|
+
for (const key of Object.keys(before)) if (!hasOwn(after, key)) delete target[key];
|
|
6827
|
+
for (const key of Object.keys(after)) if (hasOwn(before, key)) mergeHookWrite(target, key, before[key], after[key], ancestors);
|
|
6828
|
+
else target[key] = deepClone(after[key]);
|
|
6829
|
+
}
|
|
6830
|
+
ancestors.delete(after);
|
|
6733
6831
|
}
|
|
6734
6832
|
/** Restore or reset PRNG from a history moment's snapshot. */
|
|
6735
6833
|
function restorePRNGFromMoment(moment) {
|
|
@@ -6828,8 +6926,8 @@ var useStoryStore = create()(storyStore((set, get) => ({
|
|
|
6828
6926
|
temporary: createNamespace(),
|
|
6829
6927
|
history: [],
|
|
6830
6928
|
historyIndex: -1,
|
|
6831
|
-
visitCounts:
|
|
6832
|
-
renderCounts:
|
|
6929
|
+
visitCounts: createCounts(),
|
|
6930
|
+
renderCounts: createCounts(),
|
|
6833
6931
|
knownSaves: {},
|
|
6834
6932
|
playthroughId: "",
|
|
6835
6933
|
maxHistory: 40,
|
|
@@ -6879,8 +6977,8 @@ var useStoryStore = create()(storyStore((set, get) => ({
|
|
|
6879
6977
|
timestamp: Date.now()
|
|
6880
6978
|
}];
|
|
6881
6979
|
state.historyIndex = 0;
|
|
6882
|
-
state.visitCounts =
|
|
6883
|
-
state.renderCounts =
|
|
6980
|
+
state.visitCounts = createCounts(null, startPassage.name);
|
|
6981
|
+
state.renderCounts = createCounts(null, startPassage.name);
|
|
6884
6982
|
});
|
|
6885
6983
|
lastNavigationVars = get().variables;
|
|
6886
6984
|
replaceStateNow();
|
|
@@ -6927,8 +7025,8 @@ var useStoryStore = create()(storyStore((set, get) => ({
|
|
|
6927
7025
|
});
|
|
6928
7026
|
state.historyIndex = state.history.length - 1;
|
|
6929
7027
|
trimHistory(state);
|
|
6930
|
-
state.visitCounts
|
|
6931
|
-
state.renderCounts
|
|
7028
|
+
state.visitCounts = createCounts(state.visitCounts, passageName);
|
|
7029
|
+
state.renderCounts = createCounts(state.renderCounts, passageName);
|
|
6932
7030
|
});
|
|
6933
7031
|
enteredMoment = {
|
|
6934
7032
|
navigationId: get().navigationId,
|
|
@@ -7032,7 +7130,7 @@ var useStoryStore = create()(storyStore((set, get) => ({
|
|
|
7032
7130
|
},
|
|
7033
7131
|
trackRender: (passageName) => {
|
|
7034
7132
|
set((state) => {
|
|
7035
|
-
state.renderCounts
|
|
7133
|
+
state.renderCounts = createCounts(state.renderCounts, passageName);
|
|
7036
7134
|
});
|
|
7037
7135
|
},
|
|
7038
7136
|
restart: () => {
|
|
@@ -7063,8 +7161,8 @@ var useStoryStore = create()(storyStore((set, get) => ({
|
|
|
7063
7161
|
timestamp: Date.now()
|
|
7064
7162
|
}];
|
|
7065
7163
|
state.historyIndex = 0;
|
|
7066
|
-
state.visitCounts =
|
|
7067
|
-
state.renderCounts =
|
|
7164
|
+
state.visitCounts = createCounts(null, startPassage.name);
|
|
7165
|
+
state.renderCounts = createCounts(null, startPassage.name);
|
|
7068
7166
|
if (!keepDeferred) state.renderDeferred = false;
|
|
7069
7167
|
});
|
|
7070
7168
|
lastNavigationVars = get().variables;
|
|
@@ -7287,8 +7385,8 @@ var useStoryStore = create()(storyStore((set, get) => ({
|
|
|
7287
7385
|
}));
|
|
7288
7386
|
state.historyIndex = Math.max(0, Math.min(payload.historyIndex, state.history.length - 1));
|
|
7289
7387
|
trimHistory(state);
|
|
7290
|
-
state.visitCounts = payload.visitCounts
|
|
7291
|
-
state.renderCounts = payload.renderCounts
|
|
7388
|
+
state.visitCounts = createCounts(payload.visitCounts);
|
|
7389
|
+
state.renderCounts = createCounts(payload.renderCounts);
|
|
7292
7390
|
state.temporary = createNamespace();
|
|
7293
7391
|
state.transient = createNamespace(deepClone(get().transientDefaults));
|
|
7294
7392
|
});
|
|
@@ -7298,7 +7396,7 @@ var useStoryStore = create()(storyStore((set, get) => ({
|
|
|
7298
7396
|
if (prng) restorePRNG(prng.seed, prng.pull);
|
|
7299
7397
|
else resetPRNG();
|
|
7300
7398
|
persistSession(get);
|
|
7301
|
-
emit("afterload", slot);
|
|
7399
|
+
withoutDraws(() => emit("afterload", slot));
|
|
7302
7400
|
},
|
|
7303
7401
|
getHistoryVariables: (index) => {
|
|
7304
7402
|
return deepClone(reconstructVarsAt(index));
|
|
@@ -7632,7 +7730,7 @@ function parseStoryData() {
|
|
|
7632
7730
|
//#endregion
|
|
7633
7731
|
//#region src/settings.ts
|
|
7634
7732
|
var definitions = /* @__PURE__ */ new Map();
|
|
7635
|
-
var values =
|
|
7733
|
+
var values = createNamespace();
|
|
7636
7734
|
var storageLoaded = false;
|
|
7637
7735
|
var unsubscribeStoryData = null;
|
|
7638
7736
|
function storageKey() {
|
|
@@ -7657,10 +7755,7 @@ function loadFromStorage() {
|
|
|
7657
7755
|
const raw = localStorage.getItem(storageKey());
|
|
7658
7756
|
if (raw) {
|
|
7659
7757
|
const parsed = JSON.parse(raw);
|
|
7660
|
-
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) values =
|
|
7661
|
-
...values,
|
|
7662
|
-
...parsed
|
|
7663
|
-
};
|
|
7758
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) values = createNamespace(values, parsed);
|
|
7664
7759
|
}
|
|
7665
7760
|
} catch {}
|
|
7666
7761
|
}
|
|
@@ -7670,7 +7765,7 @@ var settings = {
|
|
|
7670
7765
|
type: "toggle",
|
|
7671
7766
|
config
|
|
7672
7767
|
});
|
|
7673
|
-
if (!(name
|
|
7768
|
+
if (!Object.prototype.hasOwnProperty.call(values, name)) values[name] = config.default;
|
|
7674
7769
|
loadFromStorage();
|
|
7675
7770
|
},
|
|
7676
7771
|
addList(name, config) {
|
|
@@ -7678,7 +7773,7 @@ var settings = {
|
|
|
7678
7773
|
type: "list",
|
|
7679
7774
|
config
|
|
7680
7775
|
});
|
|
7681
|
-
if (!(name
|
|
7776
|
+
if (!Object.prototype.hasOwnProperty.call(values, name)) values[name] = config.default;
|
|
7682
7777
|
loadFromStorage();
|
|
7683
7778
|
},
|
|
7684
7779
|
addRange(name, config) {
|
|
@@ -7686,22 +7781,22 @@ var settings = {
|
|
|
7686
7781
|
type: "range",
|
|
7687
7782
|
config
|
|
7688
7783
|
});
|
|
7689
|
-
if (!(name
|
|
7784
|
+
if (!Object.prototype.hasOwnProperty.call(values, name)) values[name] = config.default;
|
|
7690
7785
|
loadFromStorage();
|
|
7691
7786
|
},
|
|
7692
7787
|
get(name) {
|
|
7693
|
-
return values
|
|
7788
|
+
return ownValue(values, name);
|
|
7694
7789
|
},
|
|
7695
7790
|
getToggle(name) {
|
|
7696
|
-
const v = values
|
|
7791
|
+
const v = ownValue(values, name);
|
|
7697
7792
|
return typeof v === "boolean" ? v : false;
|
|
7698
7793
|
},
|
|
7699
7794
|
getList(name) {
|
|
7700
|
-
const v = values
|
|
7795
|
+
const v = ownValue(values, name);
|
|
7701
7796
|
return typeof v === "string" ? v : "";
|
|
7702
7797
|
},
|
|
7703
7798
|
getRange(name) {
|
|
7704
|
-
const v = values
|
|
7799
|
+
const v = ownValue(values, name);
|
|
7705
7800
|
return typeof v === "number" ? v : 0;
|
|
7706
7801
|
},
|
|
7707
7802
|
set(name, value) {
|
|
@@ -7720,36 +7815,66 @@ var settings = {
|
|
|
7720
7815
|
};
|
|
7721
7816
|
//#endregion
|
|
7722
7817
|
//#region src/action-registry.ts
|
|
7818
|
+
/**
|
|
7819
|
+
* The registrations holding each ID, oldest first; the newest is the one
|
|
7820
|
+
* listed. Generated IDs don't collide, but author IDs can, and one control's
|
|
7821
|
+
* cleanup must not remove another control holding the same ID (#233).
|
|
7822
|
+
*/
|
|
7723
7823
|
var actions = /* @__PURE__ */ new Map();
|
|
7724
7824
|
var idCounters = /* @__PURE__ */ new Map();
|
|
7825
|
+
/**
|
|
7826
|
+
* The counters reset on every navigation, so a passage's controls get the
|
|
7827
|
+
* same IDs each time it is shown. Controls that stay mounted across
|
|
7828
|
+
* navigations (e.g. in StoryInterface) keep theirs, so allocation skips every
|
|
7829
|
+
* ID still registered (#233).
|
|
7830
|
+
*/
|
|
7725
7831
|
function generateActionId(type, key, authorId) {
|
|
7726
7832
|
if (authorId) return authorId;
|
|
7727
7833
|
const base = `${type}:${key}`;
|
|
7728
|
-
|
|
7834
|
+
let count = idCounters.get(base) ?? 0;
|
|
7835
|
+
let id;
|
|
7836
|
+
do {
|
|
7837
|
+
count++;
|
|
7838
|
+
id = count === 1 ? base : `${base}:${count}`;
|
|
7839
|
+
} while (actions.has(id));
|
|
7729
7840
|
idCounters.set(base, count);
|
|
7730
|
-
return
|
|
7841
|
+
return id;
|
|
7731
7842
|
}
|
|
7732
7843
|
function registerAction(action) {
|
|
7733
|
-
|
|
7844
|
+
const id = action.id;
|
|
7845
|
+
const entry = { action };
|
|
7846
|
+
const entries = actions.get(id);
|
|
7847
|
+
if (entries) {
|
|
7848
|
+
console.warn(`spindle: two mounted controls share the action ID "${id}"; Story.getActions() lists only the newer one.`);
|
|
7849
|
+
entries.push(entry);
|
|
7850
|
+
} else actions.set(id, [entry]);
|
|
7734
7851
|
notify();
|
|
7735
|
-
return
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7852
|
+
return {
|
|
7853
|
+
update(next) {
|
|
7854
|
+
const current = actions.get(id);
|
|
7855
|
+
if (!current?.includes(entry)) return;
|
|
7856
|
+
entry.action = next;
|
|
7857
|
+
if (current[current.length - 1] === entry) notify();
|
|
7858
|
+
},
|
|
7859
|
+
unregister() {
|
|
7860
|
+
const current = actions.get(id);
|
|
7861
|
+
const index = current ? current.indexOf(entry) : -1;
|
|
7862
|
+
if (!current || index === -1) return;
|
|
7863
|
+
current.splice(index, 1);
|
|
7864
|
+
if (current.length === 0) actions.delete(id);
|
|
7865
|
+
if (index === current.length) notify();
|
|
7866
|
+
}
|
|
7746
7867
|
};
|
|
7747
7868
|
}
|
|
7748
7869
|
function getActions() {
|
|
7749
|
-
return Array.from(actions.values());
|
|
7870
|
+
return Array.from(actions.values(), listed);
|
|
7750
7871
|
}
|
|
7751
7872
|
function getAction(id) {
|
|
7752
|
-
|
|
7873
|
+
const entries = actions.get(id);
|
|
7874
|
+
return entries && listed(entries);
|
|
7875
|
+
}
|
|
7876
|
+
function listed(entries) {
|
|
7877
|
+
return entries[entries.length - 1].action;
|
|
7753
7878
|
}
|
|
7754
7879
|
function resetIdCounters() {
|
|
7755
7880
|
idCounters.clear();
|
|
@@ -7779,14 +7904,19 @@ function useAction(opts) {
|
|
|
7779
7904
|
if (opts.disabled !== void 0) action.disabled = opts.disabled;
|
|
7780
7905
|
return action;
|
|
7781
7906
|
};
|
|
7782
|
-
|
|
7907
|
+
const registration = useRef(null);
|
|
7908
|
+
useLayoutEffect(() => {
|
|
7909
|
+
const own = registerAction(buildAction());
|
|
7910
|
+
registration.current = own;
|
|
7911
|
+
return () => own.unregister();
|
|
7912
|
+
}, [id]);
|
|
7783
7913
|
const registered = useRef(false);
|
|
7784
7914
|
useLayoutEffect(() => {
|
|
7785
7915
|
if (!registered.current) {
|
|
7786
7916
|
registered.current = true;
|
|
7787
7917
|
return;
|
|
7788
7918
|
}
|
|
7789
|
-
|
|
7919
|
+
registration.current?.update(buildAction());
|
|
7790
7920
|
}, [
|
|
7791
7921
|
opts.type,
|
|
7792
7922
|
opts.label,
|
|
@@ -8087,33 +8217,33 @@ function createStoryAPI() {
|
|
|
8087
8217
|
},
|
|
8088
8218
|
visited(name) {
|
|
8089
8219
|
const state = useStoryStore.getState();
|
|
8090
|
-
return state.visitCounts
|
|
8220
|
+
return countOf(state.visitCounts, name ?? state.currentPassage);
|
|
8091
8221
|
},
|
|
8092
8222
|
hasVisited(name) {
|
|
8093
8223
|
return this.visited(name) > 0;
|
|
8094
8224
|
},
|
|
8095
8225
|
hasVisitedAny(...names) {
|
|
8096
8226
|
const { visitCounts } = useStoryStore.getState();
|
|
8097
|
-
return names.some((n) => (visitCounts
|
|
8227
|
+
return names.some((n) => countOf(visitCounts, n) > 0);
|
|
8098
8228
|
},
|
|
8099
8229
|
hasVisitedAll(...names) {
|
|
8100
8230
|
const { visitCounts } = useStoryStore.getState();
|
|
8101
|
-
return names.every((n) => (visitCounts
|
|
8231
|
+
return names.every((n) => countOf(visitCounts, n) > 0);
|
|
8102
8232
|
},
|
|
8103
8233
|
rendered(name) {
|
|
8104
8234
|
const state = useStoryStore.getState();
|
|
8105
|
-
return state.renderCounts
|
|
8235
|
+
return countOf(state.renderCounts, name ?? state.currentPassage);
|
|
8106
8236
|
},
|
|
8107
8237
|
hasRendered(name) {
|
|
8108
8238
|
return this.rendered(name) > 0;
|
|
8109
8239
|
},
|
|
8110
8240
|
hasRenderedAny(...names) {
|
|
8111
8241
|
const { renderCounts } = useStoryStore.getState();
|
|
8112
|
-
return names.some((n) => (renderCounts
|
|
8242
|
+
return names.some((n) => countOf(renderCounts, n) > 0);
|
|
8113
8243
|
},
|
|
8114
8244
|
hasRenderedAll(...names) {
|
|
8115
8245
|
const { renderCounts } = useStoryStore.getState();
|
|
8116
|
-
return names.every((n) => (renderCounts
|
|
8246
|
+
return names.every((n) => countOf(renderCounts, n) > 0);
|
|
8117
8247
|
},
|
|
8118
8248
|
currentPassage() {
|
|
8119
8249
|
const state = useStoryStore.getState();
|
|
@@ -8588,19 +8718,19 @@ defineMacro({
|
|
|
8588
8718
|
name: "set",
|
|
8589
8719
|
render({ rawArgs }, ctx) {
|
|
8590
8720
|
const ran = ctx.hooks.useRef(false);
|
|
8591
|
-
const
|
|
8721
|
+
const failure = ctx.hooks.useRef(null);
|
|
8592
8722
|
if (!ran.current) {
|
|
8593
8723
|
ran.current = true;
|
|
8594
8724
|
try {
|
|
8595
8725
|
ctx.mutate(rawArgs);
|
|
8596
8726
|
} catch (err) {
|
|
8597
|
-
|
|
8727
|
+
failure.current = { error: err };
|
|
8598
8728
|
console.error(`spindle: Error in {set ${rawArgs}}${ctx.sourceLocation()}:`, err);
|
|
8599
8729
|
}
|
|
8600
8730
|
}
|
|
8601
|
-
if (
|
|
8731
|
+
if (failure.current) return /* @__PURE__ */ jsx(MacroError, {
|
|
8602
8732
|
macro: "set",
|
|
8603
|
-
error:
|
|
8733
|
+
error: failure.current.error
|
|
8604
8734
|
});
|
|
8605
8735
|
return null;
|
|
8606
8736
|
}
|
|
@@ -8630,6 +8760,12 @@ defineMacro({
|
|
|
8630
8760
|
});
|
|
8631
8761
|
//#endregion
|
|
8632
8762
|
//#region src/components/macros/Computed.tsx
|
|
8763
|
+
/**
|
|
8764
|
+
* The previous output before the first successful evaluation. Distinct from
|
|
8765
|
+
* every value an expression can produce, so the first result is always
|
|
8766
|
+
* applied, even `undefined`; a first evaluation that throws leaves it unset.
|
|
8767
|
+
*/
|
|
8768
|
+
var UNSET = Symbol("unset");
|
|
8633
8769
|
function parseComputedArgs(rawArgs) {
|
|
8634
8770
|
const trimmed = rawArgs.trim();
|
|
8635
8771
|
let depth = 0;
|
|
@@ -8658,10 +8794,11 @@ function parseComputedArgs(rawArgs) {
|
|
|
8658
8794
|
/**
|
|
8659
8795
|
* Evaluate `expr` against the current values — the store's state and the
|
|
8660
8796
|
* enclosing locals scope's live values (`getLocals`) — and write the result
|
|
8661
|
-
* to the target if it
|
|
8662
|
-
*
|
|
8663
|
-
*
|
|
8664
|
-
*
|
|
8797
|
+
* to the target if it differs from the previous output (always, the first
|
|
8798
|
+
* time). Reading live values rather than the render's snapshot means the
|
|
8799
|
+
* first computation sees a local a preceding {set} just assigned (the scope's
|
|
8800
|
+
* context value only catches up on its re-render), and the first computation
|
|
8801
|
+
* and later recomputations read the same source.
|
|
8665
8802
|
*/
|
|
8666
8803
|
function computeAndApply(expr, name, isTemp, isLocal, getLocals, rawArgs, prevRef, localsUpdate) {
|
|
8667
8804
|
let newValue;
|
|
@@ -8705,7 +8842,7 @@ defineMacro({
|
|
|
8705
8842
|
const isTemp = target.startsWith("_");
|
|
8706
8843
|
const name = target.slice(1);
|
|
8707
8844
|
const localsUpdate = isLocal ? ctx.update : null;
|
|
8708
|
-
const prevOutput = ctx.hooks.useRef(
|
|
8845
|
+
const prevOutput = ctx.hooks.useRef(UNSET);
|
|
8709
8846
|
const ran = ctx.hooks.useRef(false);
|
|
8710
8847
|
if (!ran.current) {
|
|
8711
8848
|
ran.current = true;
|
|
@@ -10066,7 +10203,7 @@ defineMacro({
|
|
|
10066
10203
|
name: "unset",
|
|
10067
10204
|
render({ rawArgs }, ctx) {
|
|
10068
10205
|
const ran = ctx.hooks.useRef(false);
|
|
10069
|
-
const
|
|
10206
|
+
const failure = ctx.hooks.useRef(null);
|
|
10070
10207
|
if (!ran.current) {
|
|
10071
10208
|
ran.current = true;
|
|
10072
10209
|
const state = useStoryStore.getState();
|
|
@@ -10078,13 +10215,13 @@ defineMacro({
|
|
|
10078
10215
|
else if (name.startsWith("@")) ctx.update(name.slice(1), void 0);
|
|
10079
10216
|
else throw new Error(`{unset} expects a variable ($name, _name, %name, or @name), got "${name}"`);
|
|
10080
10217
|
} catch (err) {
|
|
10081
|
-
|
|
10218
|
+
failure.current = { error: err };
|
|
10082
10219
|
console.error(`spindle: Error in {unset ${rawArgs}}${ctx.sourceLocation()}:`, err);
|
|
10083
10220
|
}
|
|
10084
10221
|
}
|
|
10085
|
-
if (
|
|
10222
|
+
if (failure.current) return /* @__PURE__ */ jsx(MacroError, {
|
|
10086
10223
|
macro: "unset",
|
|
10087
|
-
error:
|
|
10224
|
+
error: failure.current.error
|
|
10088
10225
|
});
|
|
10089
10226
|
return null;
|
|
10090
10227
|
}
|