forked from kenkunz/svelte-fsm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
83 lines (75 loc) · 2.71 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
export default function (state, states = {}) {
/*
* Core Finite State Machine functionality
* - adheres to Svelte store contract (https://svelte.dev/docs#Store_contract)
* - invoked events are dispatched to handler of current state
* - transitions to returned state (or value if static property)
* - calls _exit() and _enter() methods if they are defined on exited/entered state
*/
const subscribers = new Set();
let proxy;
function subscribe(callback) {
if (!(callback instanceof Function)) {
throw new TypeError('callback is not a function');
}
subscribers.add(callback);
callback(state);
return () => subscribers.delete(callback);
}
function transition(newState, event, args) {
const metadata = { from: state, to: newState, event, args };
dispatch('_exit', metadata);
state = newState;
subscribers.forEach((callback) => callback(state));
dispatch('_enter', metadata);
}
function dispatch(event, ...args) {
const action = states[state]?.[event] ?? states['*']?.[event];
return action instanceof Function ? action.apply(proxy, args) : action;
}
function invoke(event, ...args) {
const newState = dispatch(event, ...args)?.valueOf();
if (['string', 'symbol'].includes(typeof newState) && newState !== state) {
transition(newState, event, args);
}
return state;
}
/*
* Debounce functionality
* - `debounce` is lazily bound to dynamic event invoker methods (see Proxy section below)
* - `event.debounce(wait, ...args)` calls event with args after wait (unless called again first)
* - cancels all prior invocations made for the same event
* - cancels entirely when called with `wait` of `null`
*/
const timeout = {};
async function debounce(event, wait = 100, ...args) {
clearTimeout(timeout[event]);
if (wait === null) {
return state;
} else {
await new Promise((resolve) => timeout[event] = setTimeout(resolve, wait));
delete timeout[event];
return invoke(event, ...args);
}
}
/*
* Proxy-based event invocation API:
* - return a proxy object with single native subscribe method
* - all other properties act as dynamic event invocation methods
* - event invokers also respond to .debounce(wait, ...args) (see above)
*/
proxy = new Proxy({ subscribe }, {
get(target, property) {
if (!Reflect.has(target, property)) {
target[property] = invoke.bind(null, property);
target[property].debounce = debounce.bind(null, property);
}
return Reflect.get(target, property);
}
});
/*
* `_enter` initial state and return the proxy object
*/
dispatch('_enter', { from: null, to: state, event: null, args: [] });
return proxy;
}