sprae 8.1.2 → 9.0.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/core.js +127 -0
- package/directive/aria.js +10 -0
- package/directive/class.js +17 -0
- package/directive/data.js +10 -0
- package/directive/default.js +148 -0
- package/directive/each.js +64 -0
- package/directive/fx.js +6 -0
- package/directive/html.js +11 -0
- package/directive/if.js +40 -0
- package/directive/ref.js +10 -0
- package/directive/scope.js +11 -0
- package/directive/style.js +16 -0
- package/directive/text.js +12 -0
- package/directive/value.js +31 -0
- package/dist/sprae.js +749 -0
- package/dist/sprae.min.js +1 -0
- package/package.json +17 -15
- package/readme.md +379 -208
- package/sprae.js +14 -977
- package/sprae.auto.js +0 -976
- package/sprae.auto.min.js +0 -1
- package/sprae.min.js +0 -1
- package/src/core.js +0 -82
- package/src/directives.js +0 -447
- package/src/domdiff.js +0 -71
- package/src/index.js +0 -6
- package/src/state.proxy.js +0 -150
- package/src/state.signals-proxy.js +0 -151
- package/src/state.signals.js +0 -82
- package/src/util.js +0 -13
package/src/state.proxy.js
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
// proxy-based store implementation: more flexible, less optimal and with memory leak now
|
|
2
|
-
// FIXME: make simpler prototype chain, likely manual
|
|
3
|
-
// FIXME: detect circular updates
|
|
4
|
-
import { queueMicrotask } from "./util.js"
|
|
5
|
-
|
|
6
|
-
// currentFx stack of listeners
|
|
7
|
-
let currentFx, batch = [], pendingUpdate
|
|
8
|
-
|
|
9
|
-
const targetFxs = new WeakMap
|
|
10
|
-
const targetProxy = new WeakMap
|
|
11
|
-
const proxyTarget = new WeakMap
|
|
12
|
-
const _parent = Symbol('parent')
|
|
13
|
-
|
|
14
|
-
// default root sandbox
|
|
15
|
-
export const sandbox = {
|
|
16
|
-
Array, Object, Number, String, Boolean, Date,
|
|
17
|
-
console, window, document, history, location
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const callArg = (fn) => fn(), nop = () => { }
|
|
21
|
-
export { callArg as batch };
|
|
22
|
-
|
|
23
|
-
let lastProp
|
|
24
|
-
|
|
25
|
-
const handler = {
|
|
26
|
-
has() {
|
|
27
|
-
// sandbox everything
|
|
28
|
-
return true
|
|
29
|
-
},
|
|
30
|
-
|
|
31
|
-
get(target, prop) {
|
|
32
|
-
if (typeof prop === 'symbol') return lastProp = null, target[prop]
|
|
33
|
-
if (!(prop in target)) return target[_parent]?.[prop]
|
|
34
|
-
|
|
35
|
-
// ignore .length effects called within .push
|
|
36
|
-
if (lastProp && prop === 'length') return lastProp = null, target[prop];
|
|
37
|
-
|
|
38
|
-
// .constructor, .slice etc
|
|
39
|
-
if (Object.prototype[prop]) return target[prop]
|
|
40
|
-
if (Array.isArray(target) && Array.prototype[prop] && prop !== 'length') return lastProp = prop, target[prop];
|
|
41
|
-
|
|
42
|
-
let value = target[prop]
|
|
43
|
-
|
|
44
|
-
if (currentFx) {
|
|
45
|
-
// get actual target from prototype chain
|
|
46
|
-
// while (!target.hasOwnProperty(prop)) target = Object.getPrototypeOf(target)
|
|
47
|
-
|
|
48
|
-
// register an fx for target-prop path
|
|
49
|
-
let propFxs = targetFxs.get(target)
|
|
50
|
-
if (!propFxs) targetFxs.set(target, propFxs = {})
|
|
51
|
-
if (!propFxs[prop]) propFxs[prop] = [currentFx]
|
|
52
|
-
else if (!propFxs[prop].includes(currentFx)) propFxs[prop].push(currentFx)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// if internal is trackable path - return proxy
|
|
56
|
-
if ((value && value.constructor === Object) || Array.isArray(value)) {
|
|
57
|
-
let proxy = targetProxy.get(value)
|
|
58
|
-
// FIXME: we can avoid saving it here, since it's created by new state
|
|
59
|
-
if (!proxy) targetProxy.set(value, proxy = new Proxy(value, handler))
|
|
60
|
-
return proxy
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return value
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
set(target, prop, value) {
|
|
67
|
-
// "fake" prototype chain, since regular one doesn't fit
|
|
68
|
-
if (!(prop in target) && (target[_parent] && prop in target[_parent])) return target[_parent][prop] = value
|
|
69
|
-
|
|
70
|
-
// avoid bumping unchanged values
|
|
71
|
-
if (!Array.isArray(target) && Object.is(target[prop], value)) return true
|
|
72
|
-
|
|
73
|
-
target[prop] = value
|
|
74
|
-
|
|
75
|
-
// whenever target prop is set, call all dependent fxs
|
|
76
|
-
let propFxs = targetFxs.get(target)?.[prop]
|
|
77
|
-
|
|
78
|
-
if (propFxs) for (let fx of propFxs) {
|
|
79
|
-
// put fx latest
|
|
80
|
-
if (fx.planned != null) batch[fx.planned] = null
|
|
81
|
-
fx.planned = batch.push(fx) - 1
|
|
82
|
-
}
|
|
83
|
-
planUpdate()
|
|
84
|
-
|
|
85
|
-
// FIXME: unsubscribe / delete effects by setting null/undefined
|
|
86
|
-
// if (value == null) targetFxs.delete(prev)
|
|
87
|
-
|
|
88
|
-
return true
|
|
89
|
-
},
|
|
90
|
-
|
|
91
|
-
deleteProperty(target, prop) {
|
|
92
|
-
target[prop] = undefined
|
|
93
|
-
delete target[prop]
|
|
94
|
-
return true
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export default function state(obj, parent) {
|
|
99
|
-
if (targetProxy.has(obj)) return targetProxy.get(obj)
|
|
100
|
-
if (proxyTarget.has(obj)) return obj // is proxy already
|
|
101
|
-
|
|
102
|
-
let proxy = new Proxy(obj, handler)
|
|
103
|
-
targetProxy.set(obj, proxy)
|
|
104
|
-
proxyTarget.set(proxy, obj)
|
|
105
|
-
|
|
106
|
-
// bind all getters & functions here to proxy
|
|
107
|
-
// FIXME: alternatively we can store getters somewhere
|
|
108
|
-
let descriptors = Object.getOwnPropertyDescriptors(obj)
|
|
109
|
-
for (let name in descriptors) {
|
|
110
|
-
let desc = descriptors[name]
|
|
111
|
-
if (desc.get) {
|
|
112
|
-
if (desc.get) desc.get = desc.get.bind(proxy), Object.defineProperty(obj, name, desc)
|
|
113
|
-
}
|
|
114
|
-
// else if (typeof desc.value === 'function') {
|
|
115
|
-
// desc.value = desc.value.bind(proxy), Object.defineProperty(obj, name, desc)
|
|
116
|
-
// }
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// inherit from parent state
|
|
120
|
-
obj[_parent] = parent ? state(parent) : sandbox
|
|
121
|
-
|
|
122
|
-
return proxy
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const fx = (fn) => {
|
|
126
|
-
const call = () => {
|
|
127
|
-
let prev = currentFx
|
|
128
|
-
currentFx = call
|
|
129
|
-
fn()
|
|
130
|
-
currentFx = prev
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// collect deps from the first call
|
|
134
|
-
call()
|
|
135
|
-
|
|
136
|
-
return call
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const planUpdate = () => {
|
|
140
|
-
// if (!pendingUpdate) {
|
|
141
|
-
// pendingUpdate = true
|
|
142
|
-
// queueMicrotask(() => {
|
|
143
|
-
let fx
|
|
144
|
-
while (batch.length) fx = batch.shift(), fx && (fx(), fx.planned = null)
|
|
145
|
-
// pendingUpdate = false
|
|
146
|
-
// })
|
|
147
|
-
// }
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export { fx as effect };
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
// signals-based proxy
|
|
2
|
-
// + we need proxy for sandbox & arrays anyways
|
|
3
|
-
// + it seems faster than defining a bunch of props on sealed state object
|
|
4
|
-
// + we need support signal inputs
|
|
5
|
-
// + signals provide nice tracking mechanism, unlike own arrays
|
|
6
|
-
// + signals detect cycles
|
|
7
|
-
// + it's just robust
|
|
8
|
-
// ? must it modify initial store
|
|
9
|
-
|
|
10
|
-
import { signal, computed, effect, batch, untracked } from '@preact/signals-core'
|
|
11
|
-
// import { signal, computed } from 'usignal/sync'
|
|
12
|
-
// import { signal, computed } from '@webreflection/signal'
|
|
13
|
-
|
|
14
|
-
export { effect, computed, batch, untracked }
|
|
15
|
-
|
|
16
|
-
export const _dispose = (Symbol.dispose ||= Symbol('dispose'));
|
|
17
|
-
export const _signals = Symbol('signals'), _change = Symbol('length');
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// default root sandbox
|
|
21
|
-
export const sandbox = {
|
|
22
|
-
Array, Object, Number, String, Boolean, Date,
|
|
23
|
-
console, window, document, history, navigator, location, screen, localStorage, sessionStorage,
|
|
24
|
-
alert, prompt, confirm, fetch, performance,
|
|
25
|
-
setTimeout, setInterval, requestAnimationFrame
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const isObject = v => v?.constructor === Object
|
|
29
|
-
const isPrimitive = (value) => value !== Object(value);
|
|
30
|
-
|
|
31
|
-
// track last accessed property to figure out if .length was directly accessed from expression or via .push/etc method
|
|
32
|
-
let lastProp
|
|
33
|
-
|
|
34
|
-
export default function createState(values, parent) {
|
|
35
|
-
if (!isObject(values) && !Array.isArray(values)) return values;
|
|
36
|
-
|
|
37
|
-
// ignore existing state as argument
|
|
38
|
-
if (values[_signals] && !parent) return values;
|
|
39
|
-
const initSignals = values[_signals]
|
|
40
|
-
|
|
41
|
-
// .length signal is stored outside, since it cannot be replaced
|
|
42
|
-
// _len stores total values length (for objects as well)
|
|
43
|
-
const isArr = Array.isArray(values), _len = signal(isArr ? values.length : Object.values(values).length),
|
|
44
|
-
// dict with signals storing values
|
|
45
|
-
signals = parent ? Object.create((parent = createState(parent))[_signals]) : Array.isArray(values) ? [] : {},
|
|
46
|
-
proto = signals.constructor.prototype;
|
|
47
|
-
|
|
48
|
-
if (parent) for (let key in parent) parent[key] // touch parent keys
|
|
49
|
-
|
|
50
|
-
// proxy conducts prop access to signals
|
|
51
|
-
const state = new Proxy(values, {
|
|
52
|
-
// sandbox everything
|
|
53
|
-
has() { return true },
|
|
54
|
-
|
|
55
|
-
get(values, key) {
|
|
56
|
-
// if .length is read within .push/etc - peek signal (don't subscribe)
|
|
57
|
-
if (isArr)
|
|
58
|
-
if (key === 'length') return (proto[lastProp]) ? _len.peek() : _len.value;
|
|
59
|
-
else lastProp = key;
|
|
60
|
-
if (proto[key]) return proto[key]
|
|
61
|
-
if (key === _signals) return signals
|
|
62
|
-
if (key === _change) return _len.value
|
|
63
|
-
const s = signals[key] || initSignal(key)
|
|
64
|
-
if (s) return s.value // existing property
|
|
65
|
-
return sandbox[key] // Array, window etc
|
|
66
|
-
},
|
|
67
|
-
|
|
68
|
-
set(values, key, v) {
|
|
69
|
-
if (isArr) {
|
|
70
|
-
// .length
|
|
71
|
-
if (key === 'length') {
|
|
72
|
-
batch(() => {
|
|
73
|
-
// force cleaning up tail
|
|
74
|
-
for (let i = v, l = signals.length; i < l; i++) delete state[i]
|
|
75
|
-
_len.value = signals.length = values.length = v;
|
|
76
|
-
})
|
|
77
|
-
return true
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
let newProp = false
|
|
82
|
-
const s = signals[key] || initSignal(key, v) || (newProp = true, signal())
|
|
83
|
-
const cur = s.peek()
|
|
84
|
-
|
|
85
|
-
// skip unchanged (although can be handled by last condition - we skip a few checks this way)
|
|
86
|
-
if (v === cur);
|
|
87
|
-
// stashed _set for values with getter/setter
|
|
88
|
-
else if (s._set) s._set(v)
|
|
89
|
-
// patch array
|
|
90
|
-
else if (Array.isArray(v) && Array.isArray(cur)) {
|
|
91
|
-
untracked(() => batch(() => {
|
|
92
|
-
let i = 0, l = v.length, vals = values[key];
|
|
93
|
-
for (; i < l; i++) cur[i] = vals[i] = v[i]
|
|
94
|
-
cur.length = l // forces deleting tail signals
|
|
95
|
-
}))
|
|
96
|
-
}
|
|
97
|
-
// .x = y
|
|
98
|
-
else {
|
|
99
|
-
// reflect change in values
|
|
100
|
-
s.value = createState(values[key] = v)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// force changing length, if eg. a=[]; a[1]=1 - need to come after setting the item
|
|
104
|
-
if (isArr) {
|
|
105
|
-
if (key >= _len.peek()) _len.value = signals.length = values.length = Number(key) + 1
|
|
106
|
-
}
|
|
107
|
-
// bump _change for object
|
|
108
|
-
else if (newProp) {
|
|
109
|
-
_len.value++
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return true
|
|
113
|
-
},
|
|
114
|
-
|
|
115
|
-
deleteProperty(values, key) {
|
|
116
|
-
const s = signals[key]
|
|
117
|
-
if (s) {
|
|
118
|
-
const { _del } = s
|
|
119
|
-
delete s._del
|
|
120
|
-
delete signals[key]
|
|
121
|
-
_del?.()
|
|
122
|
-
}
|
|
123
|
-
delete values[key]
|
|
124
|
-
if (!isArr) _len.value--
|
|
125
|
-
return true
|
|
126
|
-
}
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
// init signals placeholders (instead of ownKeys & getOwnPropertyDescriptor handlers)
|
|
130
|
-
// if values are existing proxy (in case of extending parent) - take its signals instead of creating new ones
|
|
131
|
-
for (let key in values) signals[key] = initSignals?.[key] ?? initSignal(key);
|
|
132
|
-
|
|
133
|
-
// initialize signal for provided key
|
|
134
|
-
function initSignal(key) {
|
|
135
|
-
// init existing value
|
|
136
|
-
if (values.hasOwnProperty(key)) {
|
|
137
|
-
// create signal from descriptor
|
|
138
|
-
const desc = Object.getOwnPropertyDescriptor(values, key)
|
|
139
|
-
// getter turns into computed
|
|
140
|
-
if (desc?.get) {
|
|
141
|
-
// stash setter
|
|
142
|
-
(signals[key] = computed(desc.get.bind(state)))._set = desc.set?.bind(state);
|
|
143
|
-
return signals[key]
|
|
144
|
-
}
|
|
145
|
-
// take over existing signal or create new signal
|
|
146
|
-
return signals[key] = desc.value?.peek ? desc.value : signal(createState(desc.value))
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return state
|
|
151
|
-
}
|
package/src/state.signals.js
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
// signals-based store implementation
|
|
2
|
-
import { signal, computed, effect, batch } from '@preact/signals-core'
|
|
3
|
-
// import { signal, computed } from 'usignal/sync'
|
|
4
|
-
// import { signal, computed } from '@webreflection/signal'
|
|
5
|
-
|
|
6
|
-
const isSignal = v => v?.peek
|
|
7
|
-
const isState = v => v?.[_st]
|
|
8
|
-
const isObject = v => v?.constructor === Object
|
|
9
|
-
|
|
10
|
-
const _st = Symbol('state')
|
|
11
|
-
|
|
12
|
-
export { effect, batch }
|
|
13
|
-
|
|
14
|
-
export default function createState(values, proto) {
|
|
15
|
-
if (isState(values) && !proto) return values;
|
|
16
|
-
|
|
17
|
-
// define signal accessors - creates signals for all object props
|
|
18
|
-
|
|
19
|
-
if (isObject(values)) {
|
|
20
|
-
const
|
|
21
|
-
state = Object.create(proto || Object.getPrototypeOf(values)),
|
|
22
|
-
signals = {},
|
|
23
|
-
descs = Object.getOwnPropertyDescriptors(values)
|
|
24
|
-
|
|
25
|
-
// define signal accessors for exported object
|
|
26
|
-
for (let key in descs) {
|
|
27
|
-
let desc = descs[key]
|
|
28
|
-
|
|
29
|
-
// getter turns into computed
|
|
30
|
-
if (desc.get) {
|
|
31
|
-
let s = signals[key] = computed(desc.get.bind(state))
|
|
32
|
-
Object.defineProperty(state, key, {
|
|
33
|
-
get() { return s.value },
|
|
34
|
-
set: desc.set?.bind(state),
|
|
35
|
-
configurable: false,
|
|
36
|
-
enumerable: true
|
|
37
|
-
})
|
|
38
|
-
}
|
|
39
|
-
// regular value creates signal accessor
|
|
40
|
-
else {
|
|
41
|
-
let value = desc.value
|
|
42
|
-
let s = signals[key] = isSignal(value) ? value :
|
|
43
|
-
signal(
|
|
44
|
-
// if initial value is an object - we turn it into sealed struct
|
|
45
|
-
isObject(value) ? Object.seal(createState(value)) :
|
|
46
|
-
Array.isArray(value) ? createState(value) :
|
|
47
|
-
value
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
// define property accessor on struct
|
|
51
|
-
Object.defineProperty(state, key, {
|
|
52
|
-
get() { return s.value },
|
|
53
|
-
set(v) {
|
|
54
|
-
if (isObject(v)) {
|
|
55
|
-
// new object can have another schema than the new one
|
|
56
|
-
// so if it throws due to new props access then we fall back to creating new struct
|
|
57
|
-
if (isObject(s.value)) try { Object.assign(s.value, v); return } catch (e) { }
|
|
58
|
-
s.value = Object.seal(createState(v));
|
|
59
|
-
}
|
|
60
|
-
else if (Array.isArray(v)) s.value = createState(v)
|
|
61
|
-
else s.value = v;
|
|
62
|
-
|
|
63
|
-
},
|
|
64
|
-
enumerable: true,
|
|
65
|
-
configurable: false
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
Object.defineProperty(state, _st, { configurable: false, enumerable: false, value: true })
|
|
71
|
-
|
|
72
|
-
return state
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// for arrays we turn internals into signal structs
|
|
76
|
-
// FIXME: make proxy to intercept length and other single values
|
|
77
|
-
if (Array.isArray(values)) {
|
|
78
|
-
for (let i = 0; i < values.length; i++) values[i] = createState(values[i])
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return values
|
|
82
|
-
}
|
package/src/util.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
// reset stacktrace or plan for fastest next call
|
|
3
|
-
// https://twitter.com/_developit/status/1634033380940455936
|
|
4
|
-
export const queueMicrotask = Promise.prototype.then.bind(Promise.resolve());
|
|
5
|
-
|
|
6
|
-
// pick first prop
|
|
7
|
-
export function getFirstProperty(obj) {
|
|
8
|
-
for (let key in obj) {
|
|
9
|
-
if (obj.hasOwnProperty(key)) {
|
|
10
|
-
return obj[key];
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
}
|