@plastic-js/plastic 1.0.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/LICENSE +21 -0
- package/README.md +442 -0
- package/package.json +78 -0
- package/src/computation-context.js +11 -0
- package/src/control-flow.js +367 -0
- package/src/index.js +87 -0
- package/src/jsx-runtime.js +1058 -0
- package/src/merge-props.js +245 -0
- package/src/reactivity.js +408 -0
- package/src/router.js +919 -0
- package/src/split-props.js +42 -0
- package/src/utils.js +51 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// Solid-style props proxy. Returns a Proxy that lazily resolves each key from
|
|
2
|
+
// the supplied sources, so getter-defined properties remain reactive: the
|
|
3
|
+
// outer consumer reads `proxy.foo` inside a binding effect, which invokes the
|
|
4
|
+
// getter, which in turn subscribes to any signals it reads.
|
|
5
|
+
//
|
|
6
|
+
// Semantics:
|
|
7
|
+
// - Sources are scanned in argument order. For most keys, the last source
|
|
8
|
+
// that has the key wins (matches Solid).
|
|
9
|
+
// - `class` / `className`: both aliases participate in one merged class
|
|
10
|
+
// stream. Reads of either key see the same concatenated result.
|
|
11
|
+
// - `style`: object values shallow-merge across sources; string values
|
|
12
|
+
// concatenate with `; `; mixing prefers the latest resolved value.
|
|
13
|
+
// - Thunk-valued `class`, `className`, and `style` remain lazy: reads of
|
|
14
|
+
// those keys return a merged accessor thunk that resolves all sources at
|
|
15
|
+
// call time, preserving runtime dependency tracking.
|
|
16
|
+
// - `ref` and `onXxx` event handlers: last source wins (matches Solid).
|
|
17
|
+
// - The proxy is read-only: writes throw.
|
|
18
|
+
|
|
19
|
+
import { isPlainObject } from './utils.js'
|
|
20
|
+
|
|
21
|
+
const CLASS_KEYS = ['class', 'className']
|
|
22
|
+
const STYLE_KEYS = ['style']
|
|
23
|
+
const MAX_MERGE_VALUE_RESOLVE_STEPS = 16
|
|
24
|
+
|
|
25
|
+
const normalizeClassValue = (value)=> {
|
|
26
|
+
if (value == null || value === false || value === ''){
|
|
27
|
+
return undefined
|
|
28
|
+
}
|
|
29
|
+
if (typeof value !== 'string'){
|
|
30
|
+
return undefined
|
|
31
|
+
}
|
|
32
|
+
return value
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const isClassKey = key=> key === 'class' || key === 'className'
|
|
36
|
+
|
|
37
|
+
const resolveThunkValue = (value)=> {
|
|
38
|
+
let resolved = value
|
|
39
|
+
let steps = 0
|
|
40
|
+
while (typeof resolved === 'function' && steps < MAX_MERGE_VALUE_RESOLVE_STEPS){
|
|
41
|
+
resolved = resolved()
|
|
42
|
+
steps += 1
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return resolved
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Concatenate only string-valued class sources. If any source carries a
|
|
49
|
+
// function or signal (a reactive accessor for the class string), it would not
|
|
50
|
+
// be safe to coerce via String(...) — that would emit the function's source
|
|
51
|
+
// code. Fall back to last-wins so the consumer's `resolveReactiveValue` can
|
|
52
|
+
// unwrap it normally.
|
|
53
|
+
const mergeClassValues = (values)=> {
|
|
54
|
+
const hasNonString = values.some(value=> value != null && typeof value !== 'string' && value !== false)
|
|
55
|
+
if (hasNonString){
|
|
56
|
+
return values[values.length - 1]
|
|
57
|
+
}
|
|
58
|
+
const parts = values.map(normalizeClassValue).filter(Boolean)
|
|
59
|
+
return parts.length ? parts.join(' ') : undefined
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const mergeStyleValues = (values)=> {
|
|
63
|
+
let result
|
|
64
|
+
for (const value of values){
|
|
65
|
+
if (value == null){
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
if (isPlainObject(result) && isPlainObject(value)){
|
|
69
|
+
result = { ...result, ...value }
|
|
70
|
+
} else if (typeof result === 'string' && typeof value === 'string'){
|
|
71
|
+
result = `${result}; ${value}`
|
|
72
|
+
} else {
|
|
73
|
+
result = value
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return result
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
// Sources can be either plain objects or zero-arg functions ("thunks") that
|
|
81
|
+
// the Babel plugin emits for dynamic spread sources like `{...api()}`. The
|
|
82
|
+
// thunk is invoked on every access so signal reads inside `api()` are tracked
|
|
83
|
+
// by whatever effect is currently consuming the proxy.
|
|
84
|
+
const resolveSource = source=> typeof source === 'function' ? source() : source
|
|
85
|
+
|
|
86
|
+
const collectPresentKeys = (source, keys)=> {
|
|
87
|
+
const matches = []
|
|
88
|
+
const seen = new Set()
|
|
89
|
+
for (const key of Reflect.ownKeys(source)){
|
|
90
|
+
if (typeof key !== 'string' || !keys.includes(key) || seen.has(key)){
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
seen.add(key)
|
|
94
|
+
matches.push(key)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const key of keys){
|
|
98
|
+
if (seen.has(key) || !(key in source)){
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
matches.push(key)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return matches
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Collect each source's value for a key family in source order, invoking any
|
|
108
|
+
// getter (which is where signal-tracking happens for reactive props).
|
|
109
|
+
const collectValues = (sources, keys)=> {
|
|
110
|
+
const values = []
|
|
111
|
+
for (const source of sources){
|
|
112
|
+
const resolved = resolveSource(source)
|
|
113
|
+
if (resolved == null){
|
|
114
|
+
continue
|
|
115
|
+
}
|
|
116
|
+
for (const key of collectPresentKeys(resolved, keys)){
|
|
117
|
+
values.push(resolved[key])
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return values
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const getKeyFamily = (key)=> {
|
|
124
|
+
if (isClassKey(key)){
|
|
125
|
+
return CLASS_KEYS
|
|
126
|
+
}
|
|
127
|
+
if (key === 'style'){
|
|
128
|
+
return STYLE_KEYS
|
|
129
|
+
}
|
|
130
|
+
return [key]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const resolveKey = (sources, key)=> {
|
|
134
|
+
const values = collectValues(sources, getKeyFamily(key))
|
|
135
|
+
if (values.length === 0){
|
|
136
|
+
return undefined
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (key === 'class' || key === 'className'){
|
|
140
|
+
if (values.some(value=> typeof value === 'function')){
|
|
141
|
+
// Keep merged class values lazy so runtime consumers can resolve the
|
|
142
|
+
// accessor inside their own tracking scope instead of subscribing here.
|
|
143
|
+
return ()=> mergeClassValues(values.map(resolveThunkValue))
|
|
144
|
+
}
|
|
145
|
+
return mergeClassValues(values)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (key === 'style'){
|
|
149
|
+
if (values.some(value=> typeof value === 'function')){
|
|
150
|
+
// Style follows the same rule as class: preserve thunk semantics so
|
|
151
|
+
// updates track at the eventual DOM-binding read site.
|
|
152
|
+
return ()=> mergeStyleValues(values.map(resolveThunkValue))
|
|
153
|
+
}
|
|
154
|
+
return mergeStyleValues(values)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return values[values.length - 1]
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const hasKey = (sources, key)=> {
|
|
161
|
+
const keys = getKeyFamily(key)
|
|
162
|
+
for (const source of sources){
|
|
163
|
+
const resolved = resolveSource(source)
|
|
164
|
+
if (resolved == null){
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
167
|
+
if (keys.some(candidate=> candidate in resolved)){
|
|
168
|
+
return true
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return false
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const getCanonicalClassKey = (sources)=> {
|
|
175
|
+
let canonicalKey
|
|
176
|
+
for (const source of sources){
|
|
177
|
+
const resolved = resolveSource(source)
|
|
178
|
+
if (resolved == null){
|
|
179
|
+
continue
|
|
180
|
+
}
|
|
181
|
+
for (const key of collectPresentKeys(resolved, CLASS_KEYS)){
|
|
182
|
+
canonicalKey = key
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return canonicalKey
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const collectKeys = (sources)=> {
|
|
189
|
+
const seen = new Set()
|
|
190
|
+
const keys = []
|
|
191
|
+
let hasClassAlias = false
|
|
192
|
+
for (const source of sources){
|
|
193
|
+
const resolved = resolveSource(source)
|
|
194
|
+
if (resolved == null){
|
|
195
|
+
continue
|
|
196
|
+
}
|
|
197
|
+
for (const key of Reflect.ownKeys(resolved)){
|
|
198
|
+
if (isClassKey(key)){
|
|
199
|
+
hasClassAlias = true
|
|
200
|
+
continue
|
|
201
|
+
}
|
|
202
|
+
if (!seen.has(key)){
|
|
203
|
+
seen.add(key)
|
|
204
|
+
keys.push(key)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (hasClassAlias){
|
|
209
|
+
// Expose one canonical class key during enumeration so reflection APIs
|
|
210
|
+
// (`Object.assign`, `Object.entries`) don't duplicate `class`/`className`.
|
|
211
|
+
keys.push(getCanonicalClassKey(sources) ?? 'class')
|
|
212
|
+
}
|
|
213
|
+
return keys
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const readOnlyTrap = ()=> {
|
|
217
|
+
throw new Error('mergeProps result is read-only')
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const IS_MERGED_PROPS = Symbol('mergeProps')
|
|
221
|
+
|
|
222
|
+
export const mergeProps = (...sources)=> {
|
|
223
|
+
return new Proxy({}, {
|
|
224
|
+
get: (_, key)=> {
|
|
225
|
+
if (key === IS_MERGED_PROPS) return true
|
|
226
|
+
return resolveKey(sources, key)
|
|
227
|
+
},
|
|
228
|
+
has: (_, key)=> hasKey(sources, key),
|
|
229
|
+
ownKeys: ()=> collectKeys(sources),
|
|
230
|
+
getOwnPropertyDescriptor: (_, key)=> {
|
|
231
|
+
if (!hasKey(sources, key)){
|
|
232
|
+
return undefined
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
enumerable: true,
|
|
236
|
+
configurable: true,
|
|
237
|
+
get: ()=> resolveKey(sources, key),
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
set: readOnlyTrap,
|
|
241
|
+
deleteProperty: readOnlyTrap,
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export const isMergedProps = (value)=> value != null && typeof value === 'object' && value[IS_MERGED_PROPS] === true
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* reactivity.js — Reactive system for the Plastic framework.
|
|
3
|
+
*
|
|
4
|
+
* Built on top of the `alien-signals` library, which provides fine-grained
|
|
5
|
+
* signal primitives (`signal`, `computed`, `effect`) suited for primitive values.
|
|
6
|
+
* This module extends that foundation with deep object reactivity.
|
|
7
|
+
*
|
|
8
|
+
* ## Core concepts
|
|
9
|
+
*
|
|
10
|
+
* ### Signals (`createSignal`)
|
|
11
|
+
* A thin public wrapper around `alien-signals`'s `signal()`. Calling
|
|
12
|
+
* `createSignal(x)` on an already-signal value is a no-op — the original
|
|
13
|
+
* signal is returned unchanged.
|
|
14
|
+
*
|
|
15
|
+
* ### Reactive trees (`tree` / `createTree`)
|
|
16
|
+
* `tree(obj)` wraps a plain object (or array) in an ES Proxy that makes every
|
|
17
|
+
* property access and mutation reactive. It is conceptually equivalent to
|
|
18
|
+
* Vue 3's `reactive()`.
|
|
19
|
+
*
|
|
20
|
+
* Key implementation details:
|
|
21
|
+
* - **Per-property signals**: Each accessed property is lazily backed by an
|
|
22
|
+
* `alien-signals` signal stored in a `signals` map (keyed by property name
|
|
23
|
+
* or symbol). Reads subscribe the current effect; writes trigger updates.
|
|
24
|
+
* - **Proxy cache**: A `WeakMap` (proxyCache) ensures that wrapping the same
|
|
25
|
+
* raw object multiple times always returns the same proxy, preventing
|
|
26
|
+
* duplicate subscriptions.
|
|
27
|
+
* - **`RAW` / `IS_TREE` sentinels**: Two well-known symbols allow consumers to
|
|
28
|
+
* unwrap to the original object (`toRaw`) and to test whether a value is
|
|
29
|
+
* already a reactive tree (`isTree`), avoiding double-wrapping.
|
|
30
|
+
* - **Iterate tracking**: A dedicated `ITERATE_KEY` signal is used to track
|
|
31
|
+
* structural changes (property addition/deletion, array length changes).
|
|
32
|
+
* Operations like `for…in`, `Object.keys`, and spread trigger this signal so
|
|
33
|
+
* effects that iterate over an object re-run when its shape changes.
|
|
34
|
+
* - **Non-trackable keys**: Built-in symbols (`Symbol.iterator`, etc.) and a
|
|
35
|
+
* small set of Vue-compatibility keys are excluded from tracking to avoid
|
|
36
|
+
* spurious subscriptions.
|
|
37
|
+
* - **Array instrumentations**: Mutating array methods (`push`, `pop`, `shift`,
|
|
38
|
+
* `unshift`, `splice`) temporarily pause dependency tracking while executing
|
|
39
|
+
* to prevent the read of `length` inside those methods from creating
|
|
40
|
+
* unintended subscriptions. Search methods (`includes`, `indexOf`,
|
|
41
|
+
* `lastIndexOf`) explicitly track all indices and also fall back to comparing
|
|
42
|
+
* raw (unwrapped) values, supporting reactive proxies as search arguments.
|
|
43
|
+
* - **Nested reactivity**: When `get` returns an object value it is recursively
|
|
44
|
+
* wrapped with `tree()`, providing deep reactivity on demand.
|
|
45
|
+
* - **Raw value storage**: `set` always unwraps values through `toRaw` before
|
|
46
|
+
* writing to the underlying target, keeping raw objects free of proxy
|
|
47
|
+
* references and preventing double-wrapping in the signal store.
|
|
48
|
+
*
|
|
49
|
+
* ### Tracking pause/resume
|
|
50
|
+
* `pauseTracking` / `resumeTracking` manipulate `alien-signals`'s active
|
|
51
|
+
* subscriber stack via `getActiveSub` / `setActiveSub`, temporarily
|
|
52
|
+
* suspending dependency collection for mutation-only code paths.
|
|
53
|
+
*
|
|
54
|
+
* ## Public API
|
|
55
|
+
* Re-exports from `alien-signals`: `computed`, `effect`, `isSignal`, `isComputed`
|
|
56
|
+
* Added by this module: `tree`, `createSignal`, `createTree`, `isTree`, `toRaw`
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
import {
|
|
60
|
+
computed, effect as originalEffect, endBatch, getActiveSub, isComputed as originalIsComputed, isSignal as originalIsSignal, setActiveSub, signal, startBatch,
|
|
61
|
+
} from 'alien-signals'
|
|
62
|
+
|
|
63
|
+
// alien-signals 3.x treats the effect callback's return value as a cleanup
|
|
64
|
+
// function. Most callers return non-function values (e.g. `log.push(x)` →
|
|
65
|
+
// number), which crash on re-run. Discard non-function returns.
|
|
66
|
+
const effect = (fn)=> originalEffect(()=> {
|
|
67
|
+
const result = fn()
|
|
68
|
+
return typeof result === 'function' ? result : undefined
|
|
69
|
+
})
|
|
70
|
+
import { isObject } from './utils.js'
|
|
71
|
+
|
|
72
|
+
const RAW = Symbol('raw')
|
|
73
|
+
const IS_TREE = Symbol('isTree')
|
|
74
|
+
const ITERATE_KEY = Symbol('iterate')
|
|
75
|
+
const proxyCache = new WeakMap()
|
|
76
|
+
const nonTrackableKeys = new Set([
|
|
77
|
+
'__proto__',
|
|
78
|
+
'__v_isRef',
|
|
79
|
+
'__isVue',
|
|
80
|
+
])
|
|
81
|
+
const builtInSymbols = new Set(Object.getOwnPropertyNames(Symbol)
|
|
82
|
+
.map(key=> Symbol[key])
|
|
83
|
+
.filter(symbol=> typeof symbol === 'symbol'))
|
|
84
|
+
|
|
85
|
+
const isSignal = value=> {
|
|
86
|
+
if (typeof value === 'function' && originalIsSignal(value)){
|
|
87
|
+
return true
|
|
88
|
+
}
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const isComputed = value=> {
|
|
93
|
+
if (typeof value === 'function' && originalIsComputed(value)){
|
|
94
|
+
return true
|
|
95
|
+
}
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const isTrackableKey = (key)=> {
|
|
100
|
+
if (typeof key === 'symbol'){
|
|
101
|
+
return !builtInSymbols.has(key)
|
|
102
|
+
}
|
|
103
|
+
if (typeof key !== 'string'){
|
|
104
|
+
return false
|
|
105
|
+
}
|
|
106
|
+
return !nonTrackableKeys.has(key)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const isIntegerKey = (key)=> {
|
|
110
|
+
if (typeof key === 'number'){
|
|
111
|
+
return Number.isInteger(key) && key >= 0
|
|
112
|
+
}
|
|
113
|
+
if (typeof key !== 'string' || key === 'NaN' || key[0] === '-'){
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
const parsed = Number(key)
|
|
117
|
+
return Number.isInteger(parsed) && parsed >= 0 && `${parsed}` === key
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const tree = (obj)=> {
|
|
121
|
+
if (!isObject(obj)){
|
|
122
|
+
return obj
|
|
123
|
+
}
|
|
124
|
+
if (obj[RAW]){
|
|
125
|
+
return obj
|
|
126
|
+
}
|
|
127
|
+
if (proxyCache.has(obj)){
|
|
128
|
+
return proxyCache.get(obj)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const signals = Object.create(null)
|
|
132
|
+
const hasSignal = key=> Object.hasOwn(signals, key)
|
|
133
|
+
const isArrayTarget = Array.isArray(obj)
|
|
134
|
+
let iterateVersion = 0
|
|
135
|
+
const trackKey = (target, key, receiver)=> {
|
|
136
|
+
if (!isTrackableKey(key)){
|
|
137
|
+
return Reflect.get(target, key, receiver)
|
|
138
|
+
}
|
|
139
|
+
const currentValue = Reflect.get(target, key, receiver)
|
|
140
|
+
if (!hasSignal(key)){
|
|
141
|
+
signals[key] = signal(currentValue)
|
|
142
|
+
}
|
|
143
|
+
return signals[key]()
|
|
144
|
+
}
|
|
145
|
+
const triggerKey = (key, value)=> {
|
|
146
|
+
if (!isTrackableKey(key)){
|
|
147
|
+
if (hasSignal(key)){
|
|
148
|
+
signals[key](value)
|
|
149
|
+
}
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
if (!hasSignal(key)){
|
|
153
|
+
signals[key] = signal(value)
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
signals[key](value)
|
|
157
|
+
}
|
|
158
|
+
const prevSubStack = []
|
|
159
|
+
const pauseTracking = ()=> {
|
|
160
|
+
prevSubStack.push(getActiveSub())
|
|
161
|
+
setActiveSub(undefined)
|
|
162
|
+
}
|
|
163
|
+
const resumeTracking = ()=> {
|
|
164
|
+
setActiveSub(prevSubStack.pop())
|
|
165
|
+
}
|
|
166
|
+
const trackIterate = ()=> {
|
|
167
|
+
if (!hasSignal(ITERATE_KEY)){
|
|
168
|
+
signals[ITERATE_KEY] = signal(iterateVersion)
|
|
169
|
+
}
|
|
170
|
+
signals[ITERATE_KEY]()
|
|
171
|
+
}
|
|
172
|
+
const triggerIterate = ()=> {
|
|
173
|
+
iterateVersion += 1
|
|
174
|
+
if (!hasSignal(ITERATE_KEY)){
|
|
175
|
+
signals[ITERATE_KEY] = signal(iterateVersion)
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
signals[ITERATE_KEY](iterateVersion)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let arrayInstrumentations = null
|
|
182
|
+
if(isArrayTarget){
|
|
183
|
+
arrayInstrumentations = {
|
|
184
|
+
includes(...args){
|
|
185
|
+
trackKey(obj, 'length')
|
|
186
|
+
for (let i = 0; i < obj.length; i++){
|
|
187
|
+
trackKey(obj, `${i}`)
|
|
188
|
+
}
|
|
189
|
+
const rawResult = Array.prototype.includes.apply(obj, args)
|
|
190
|
+
return rawResult || Array.prototype.includes.apply(obj, args.map(toRaw))
|
|
191
|
+
},
|
|
192
|
+
indexOf(...args){
|
|
193
|
+
trackKey(obj, 'length')
|
|
194
|
+
for (let i = 0; i < obj.length; i++){
|
|
195
|
+
trackKey(obj, `${i}`)
|
|
196
|
+
}
|
|
197
|
+
const rawResult = Array.prototype.indexOf.apply(obj, args)
|
|
198
|
+
if (rawResult !== -1){
|
|
199
|
+
return rawResult
|
|
200
|
+
}
|
|
201
|
+
return Array.prototype.indexOf.apply(obj, args.map(toRaw))
|
|
202
|
+
},
|
|
203
|
+
lastIndexOf(...args){
|
|
204
|
+
trackKey(obj, 'length')
|
|
205
|
+
for (let i = 0; i < obj.length; i++){
|
|
206
|
+
trackKey(obj, `${i}`)
|
|
207
|
+
}
|
|
208
|
+
const rawResult = Array.prototype.lastIndexOf.apply(obj, args)
|
|
209
|
+
if (rawResult !== -1){
|
|
210
|
+
return rawResult
|
|
211
|
+
}
|
|
212
|
+
return Array.prototype.lastIndexOf.apply(obj, args.map(toRaw))
|
|
213
|
+
},
|
|
214
|
+
push(...args){
|
|
215
|
+
pauseTracking()
|
|
216
|
+
try {
|
|
217
|
+
return Array.prototype.push.apply(proxy, args.map(toRaw))
|
|
218
|
+
} finally {
|
|
219
|
+
resumeTracking()
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
pop(...args){
|
|
223
|
+
pauseTracking()
|
|
224
|
+
try {
|
|
225
|
+
return Array.prototype.pop.apply(proxy, args)
|
|
226
|
+
} finally {
|
|
227
|
+
resumeTracking()
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
shift(...args){
|
|
231
|
+
pauseTracking()
|
|
232
|
+
try {
|
|
233
|
+
return Array.prototype.shift.apply(proxy, args)
|
|
234
|
+
} finally {
|
|
235
|
+
resumeTracking()
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
unshift(...args){
|
|
239
|
+
pauseTracking()
|
|
240
|
+
try {
|
|
241
|
+
return Array.prototype.unshift.apply(proxy, args.map(toRaw))
|
|
242
|
+
} finally {
|
|
243
|
+
resumeTracking()
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
splice(...args){
|
|
247
|
+
pauseTracking()
|
|
248
|
+
try {
|
|
249
|
+
const normalized = args.map((arg, index)=> index < 2 ? arg : toRaw(arg))
|
|
250
|
+
return Array.prototype.splice.apply(proxy, normalized)
|
|
251
|
+
} finally {
|
|
252
|
+
resumeTracking()
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const proxy = new Proxy(obj, {
|
|
259
|
+
get(target, key, receiver){
|
|
260
|
+
if (key === RAW){
|
|
261
|
+
return target
|
|
262
|
+
}
|
|
263
|
+
if (key === IS_TREE){
|
|
264
|
+
return true
|
|
265
|
+
}
|
|
266
|
+
if (isArrayTarget && typeof key === 'string'){
|
|
267
|
+
if (arrayInstrumentations && Object.hasOwn(arrayInstrumentations, key)){
|
|
268
|
+
return arrayInstrumentations[key]
|
|
269
|
+
}
|
|
270
|
+
if (typeof target[key] === 'function'){
|
|
271
|
+
return Reflect.get(target, key, receiver)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (!isTrackableKey(key)){
|
|
275
|
+
return Reflect.get(target, key, receiver)
|
|
276
|
+
}
|
|
277
|
+
const value = trackKey(target, key, receiver)
|
|
278
|
+
if (isObject(value)){
|
|
279
|
+
return tree(value)
|
|
280
|
+
}
|
|
281
|
+
return value
|
|
282
|
+
},
|
|
283
|
+
set(target, key, value, receiver){
|
|
284
|
+
const oldLength = isArrayTarget ? target.length : 0
|
|
285
|
+
const isLengthKey = isArrayTarget && key === 'length'
|
|
286
|
+
const isIndexKey = isArrayTarget && isIntegerKey(key)
|
|
287
|
+
const hadKey = Object.hasOwn(target, key)
|
|
288
|
+
const rawValue = toRaw(value)
|
|
289
|
+
const setOk = Reflect.set(target, key, rawValue, receiver)
|
|
290
|
+
if (!setOk){
|
|
291
|
+
return false
|
|
292
|
+
}
|
|
293
|
+
const nextValue = Reflect.get(target, key, receiver)
|
|
294
|
+
if (!isTrackableKey(key)){
|
|
295
|
+
triggerKey(key, nextValue)
|
|
296
|
+
if (!hadKey){
|
|
297
|
+
triggerIterate()
|
|
298
|
+
}
|
|
299
|
+
return true
|
|
300
|
+
}
|
|
301
|
+
triggerKey(key, nextValue)
|
|
302
|
+
if (isLengthKey){
|
|
303
|
+
const newLength = target.length
|
|
304
|
+
if (newLength < oldLength){
|
|
305
|
+
for (let i = newLength; i < oldLength; i++){
|
|
306
|
+
triggerKey(String(i), undefined)
|
|
307
|
+
}
|
|
308
|
+
triggerIterate()
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (isIndexKey && Number(key) >= oldLength){
|
|
312
|
+
triggerKey('length', target.length)
|
|
313
|
+
}
|
|
314
|
+
if (!hadKey){
|
|
315
|
+
triggerIterate()
|
|
316
|
+
}
|
|
317
|
+
return true
|
|
318
|
+
},
|
|
319
|
+
has(target, key){
|
|
320
|
+
if (key === IS_TREE){ return true }
|
|
321
|
+
if (key === RAW){ return key in target }
|
|
322
|
+
if (!isTrackableKey(key)){
|
|
323
|
+
return key in target
|
|
324
|
+
}
|
|
325
|
+
trackKey(target, key)
|
|
326
|
+
return key in target
|
|
327
|
+
},
|
|
328
|
+
ownKeys(target){
|
|
329
|
+
trackIterate()
|
|
330
|
+
return Reflect.ownKeys(target)
|
|
331
|
+
},
|
|
332
|
+
deleteProperty(target, key){
|
|
333
|
+
const hadKey = Object.hasOwn(target, key)
|
|
334
|
+
const deleted = Reflect.deleteProperty(target, key)
|
|
335
|
+
if (deleted && hadKey){
|
|
336
|
+
if (hasSignal(key)){
|
|
337
|
+
signals[key](undefined)
|
|
338
|
+
}
|
|
339
|
+
triggerIterate()
|
|
340
|
+
}
|
|
341
|
+
return deleted
|
|
342
|
+
},
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
proxyCache.set(obj, proxy)
|
|
346
|
+
return proxy
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Component bodies must not subscribe to the reactive context above them. If
|
|
350
|
+
// the materializer happens to run inside an outer effect (e.g. a router
|
|
351
|
+
// outlet), every signal read by the component would re-trigger that outer
|
|
352
|
+
// effect — re-mounting the whole subtree on every unrelated state change.
|
|
353
|
+
// Internal binding effects/computations create their own active subscribers,
|
|
354
|
+
// so suppressing the outer one here only affects bare signal reads in the
|
|
355
|
+
// component body.
|
|
356
|
+
const runUntracked = (fn)=> {
|
|
357
|
+
const prevSub = getActiveSub()
|
|
358
|
+
setActiveSub(undefined)
|
|
359
|
+
try {
|
|
360
|
+
return fn()
|
|
361
|
+
} finally {
|
|
362
|
+
setActiveSub(prevSub)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const createSignal = (value)=> {
|
|
367
|
+
if (isSignal(value)){
|
|
368
|
+
return value
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (isComputed(value)){
|
|
372
|
+
console.warn('[reactivity] createSignal: wrapping a computed in a signal is redundant, use the computed directly.')
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return signal(value)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const createTree = (value)=> {
|
|
379
|
+
if (!isObject(value)){
|
|
380
|
+
return value
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (isTree(value)){
|
|
384
|
+
return value
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return tree(value)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const isTree = value=> isObject(value) && value[IS_TREE] === true
|
|
391
|
+
|
|
392
|
+
const toRaw = (value)=> {
|
|
393
|
+
const raw = isObject(value) && value[RAW]
|
|
394
|
+
return raw ? toRaw(raw) : value
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const batch = (fn)=> {
|
|
398
|
+
startBatch()
|
|
399
|
+
try {
|
|
400
|
+
return fn()
|
|
401
|
+
} finally {
|
|
402
|
+
endBatch()
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export {
|
|
407
|
+
batch, effect, runUntracked, isComputed, isSignal, isTree, toRaw, createSignal, createTree, computed as createComputed,
|
|
408
|
+
}
|