@jsweb/ui 1.2.6 → 1.2.7
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/README.md +18 -14
- package/package.json +8 -24
- package/.github/workflows/npm-publish.yml +0 -21
- package/.prettierignore +0 -3
- package/.prettierrc +0 -7
- package/HISTORY.md +0 -36
- package/PROJECT.md +0 -90
- package/dist/LICENSE +0 -21
- package/dist/README.md +0 -104
- package/dist/package.json +0 -34
- package/index.html +0 -162
- package/publish.js +0 -34
- package/src/evaluator.ts +0 -29
- package/src/index.ts +0 -10
- package/src/parser.ts +0 -426
- package/src/reactivity.ts +0 -177
- package/tsconfig.json +0 -23
- package/vite.config.ts +0 -16
- /package/{dist/index.d.ts → index.d.ts} +0 -0
- /package/{dist/index.es.js → index.es.js} +0 -0
- /package/{dist/index.es.js.map → index.es.js.map} +0 -0
- /package/{dist/index.umd.js → index.umd.js} +0 -0
- /package/{dist/index.umd.js.map → index.umd.js.map} +0 -0
- /package/{dist/src → src}/evaluator.d.ts +0 -0
- /package/{dist/src → src}/index.d.ts +0 -0
- /package/{dist/src → src}/parser.d.ts +0 -0
- /package/{dist/src → src}/reactivity.d.ts +0 -0
- /package/{dist/vite.config.d.ts → vite.config.d.ts} +0 -0
package/src/index.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { reactive, watch } from './reactivity'
|
|
2
|
-
import { createScope } from './parser'
|
|
3
|
-
|
|
4
|
-
export { reactive, watch, createScope }
|
|
5
|
-
|
|
6
|
-
if (typeof window !== 'undefined') {
|
|
7
|
-
const w = window as any
|
|
8
|
-
w.jsweb = w.jsweb || {}
|
|
9
|
-
w.jsweb.ui = { createScope, reactive, watch }
|
|
10
|
-
}
|
package/src/parser.ts
DELETED
|
@@ -1,426 +0,0 @@
|
|
|
1
|
-
import { effect, reactive } from './reactivity'
|
|
2
|
-
import { evaluate, evaluateEvent } from './evaluator'
|
|
3
|
-
|
|
4
|
-
export type Context = Record<string, any>
|
|
5
|
-
|
|
6
|
-
interface BoundNode extends Node {
|
|
7
|
-
_effects?: Array<() => void>
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function cleanupTree(node: Node) {
|
|
11
|
-
const bNode = node as BoundNode
|
|
12
|
-
if (bNode._effects) {
|
|
13
|
-
bNode._effects.forEach((stop) => stop())
|
|
14
|
-
bNode._effects = []
|
|
15
|
-
}
|
|
16
|
-
const children = Array.from(node.childNodes)
|
|
17
|
-
for (const child of children) cleanupTree(child)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function createContext(scope: any, context: Context = {}): Context {
|
|
21
|
-
const reactiveScope = scope._isReactive ? scope : reactive(scope)
|
|
22
|
-
|
|
23
|
-
return new Proxy(reactiveScope, {
|
|
24
|
-
get(target, prop) {
|
|
25
|
-
if (prop === '_isContext') return true
|
|
26
|
-
if (prop in target) return Reflect.get(target, prop, target)
|
|
27
|
-
if (prop in context) {
|
|
28
|
-
return Reflect.get(context, prop, context)
|
|
29
|
-
}
|
|
30
|
-
return Reflect.get(target, prop, target)
|
|
31
|
-
},
|
|
32
|
-
set(target, prop, value) {
|
|
33
|
-
if (prop in target) return Reflect.set(target, prop, value, target)
|
|
34
|
-
if (prop in context) {
|
|
35
|
-
return Reflect.set(context, prop, value, context)
|
|
36
|
-
}
|
|
37
|
-
return Reflect.set(target, prop, value, target)
|
|
38
|
-
},
|
|
39
|
-
has(target, prop) {
|
|
40
|
-
if (prop in target) return true
|
|
41
|
-
if (prop in context) return true
|
|
42
|
-
return false
|
|
43
|
-
},
|
|
44
|
-
})
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function parseNode(node: Node, context: Context) {
|
|
48
|
-
if (node.nodeType !== Node.ELEMENT_NODE) return
|
|
49
|
-
|
|
50
|
-
const el = node as HTMLElement
|
|
51
|
-
const scope = processScope(el, context)
|
|
52
|
-
if (!scope) return
|
|
53
|
-
|
|
54
|
-
const forAttrs = ['ui:for', ':for']
|
|
55
|
-
const forDirective = getDirectiveValue(el, forAttrs)
|
|
56
|
-
if (forDirective) {
|
|
57
|
-
removeDirectiveAttributes(el, forAttrs)
|
|
58
|
-
processFor(el, forDirective, scope)
|
|
59
|
-
return
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const ifAttrs = ['ui:if', ':if']
|
|
63
|
-
const ifDirective = getDirectiveValue(el, ifAttrs)
|
|
64
|
-
if (ifDirective) {
|
|
65
|
-
removeDirectiveAttributes(el, ifAttrs)
|
|
66
|
-
processIf(el, ifDirective, scope)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
processAttributes(el, scope)
|
|
70
|
-
|
|
71
|
-
const children = Array.from(el.childNodes)
|
|
72
|
-
for (const child of children) parseNode(child, scope)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function createScope(
|
|
76
|
-
selectorOrElement: string | HTMLElement,
|
|
77
|
-
context: Context = {},
|
|
78
|
-
) {
|
|
79
|
-
const el =
|
|
80
|
-
typeof selectorOrElement === 'string'
|
|
81
|
-
? document.querySelector(selectorOrElement)
|
|
82
|
-
: selectorOrElement
|
|
83
|
-
|
|
84
|
-
if (el) {
|
|
85
|
-
if (!context.$emit) {
|
|
86
|
-
context.$emit = (eventName: string, detail?: any) => {
|
|
87
|
-
el.dispatchEvent(
|
|
88
|
-
new CustomEvent(eventName, { detail, bubbles: true, composed: true }),
|
|
89
|
-
)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
parseNode(el, context)
|
|
94
|
-
} else {
|
|
95
|
-
console.warn('[jsweb/ui] Element not found:', selectorOrElement)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function bindEffect(node: Node, fn: () => void) {
|
|
100
|
-
const e = effect(fn)
|
|
101
|
-
const bNode = node as BoundNode
|
|
102
|
-
bNode._effects ??= []
|
|
103
|
-
bNode._effects.push(e.stop)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function getDirectiveValue(el: HTMLElement, names: string[]) {
|
|
107
|
-
for (const name of names) {
|
|
108
|
-
const value = el.getAttribute(name)
|
|
109
|
-
if (value !== null) return value
|
|
110
|
-
}
|
|
111
|
-
return null
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function removeDirectiveAttributes(el: HTMLElement, names: string[]) {
|
|
115
|
-
for (const name of names) {
|
|
116
|
-
el.removeAttribute(name)
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function processScope(el: HTMLElement, context: Context) {
|
|
121
|
-
const attrs = ['ui:scope', ':scope']
|
|
122
|
-
const directive = getDirectiveValue(el, attrs)
|
|
123
|
-
if (!directive) return context
|
|
124
|
-
|
|
125
|
-
const scope = evaluate(directive, context)
|
|
126
|
-
if (!scope) return undefined
|
|
127
|
-
|
|
128
|
-
removeDirectiveAttributes(el, attrs)
|
|
129
|
-
|
|
130
|
-
if (!scope.$emit) {
|
|
131
|
-
scope.$emit = (event: string, detail?: any) => {
|
|
132
|
-
el.dispatchEvent(
|
|
133
|
-
new CustomEvent(event, { detail, bubbles: true, composed: true }),
|
|
134
|
-
)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return createContext(scope, context)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function processFor(el: HTMLElement, expr: string, context: Context) {
|
|
142
|
-
const parent = el.parentNode
|
|
143
|
-
if (!parent) return
|
|
144
|
-
|
|
145
|
-
const match = /^\s*(.+)\s+(?:in|of)\s+(.+)\s*$/.exec(expr)
|
|
146
|
-
if (!match) {
|
|
147
|
-
return console.warn(`[jsweb/ui] Invalid ui:for expression: ${expr}`)
|
|
148
|
-
}
|
|
149
|
-
const [, itemName, listName] = match
|
|
150
|
-
|
|
151
|
-
const keyAttr = ['ui:key', ':key']
|
|
152
|
-
const keyDirective = getDirectiveValue(el, keyAttr)
|
|
153
|
-
removeDirectiveAttributes(el, keyAttr)
|
|
154
|
-
|
|
155
|
-
const uuid = crypto.randomUUID()
|
|
156
|
-
const comment = document.createComment(` ui:for ${uuid} `)
|
|
157
|
-
el.replaceWith(comment)
|
|
158
|
-
|
|
159
|
-
interface RenderedNode {
|
|
160
|
-
key: any
|
|
161
|
-
el: HTMLElement
|
|
162
|
-
scope: any
|
|
163
|
-
}
|
|
164
|
-
let renderedNodes: RenderedNode[] = []
|
|
165
|
-
|
|
166
|
-
bindEffect(comment, () => {
|
|
167
|
-
const list = evaluate(listName, context)
|
|
168
|
-
|
|
169
|
-
if (!Array.isArray(list)) {
|
|
170
|
-
renderedNodes.forEach((node) => {
|
|
171
|
-
node.el.remove()
|
|
172
|
-
cleanupTree(node.el)
|
|
173
|
-
})
|
|
174
|
-
renderedNodes = []
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const newNodes: RenderedNode[] = []
|
|
179
|
-
const oldNodesByKey = new Map<any, RenderedNode>()
|
|
180
|
-
renderedNodes.forEach((node) => oldNodesByKey.set(node.key, node))
|
|
181
|
-
|
|
182
|
-
list.forEach((item, index) => {
|
|
183
|
-
const scope = { [itemName]: item, $index: index }
|
|
184
|
-
let key: any = index
|
|
185
|
-
|
|
186
|
-
if (keyDirective) {
|
|
187
|
-
const tempContext = createContext(scope, context)
|
|
188
|
-
key = evaluate(keyDirective, tempContext)
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
let node = oldNodesByKey.get(key)
|
|
192
|
-
if (node) {
|
|
193
|
-
// Reuse node
|
|
194
|
-
node.scope[itemName] = item
|
|
195
|
-
node.scope.$index = index
|
|
196
|
-
oldNodesByKey.delete(key)
|
|
197
|
-
} else {
|
|
198
|
-
// Create new node
|
|
199
|
-
const clone = el.cloneNode(true) as HTMLElement
|
|
200
|
-
const reactiveScope = reactive(scope)
|
|
201
|
-
const localContext = createContext(reactiveScope, context)
|
|
202
|
-
parseNode(clone, localContext)
|
|
203
|
-
node = { key, el: clone, scope: reactiveScope }
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
newNodes.push(node)
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
// Remove un-reused nodes
|
|
210
|
-
oldNodesByKey.forEach((node) => {
|
|
211
|
-
node.el.remove()
|
|
212
|
-
cleanupTree(node.el)
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
// Reorder and insert new DOM nodes
|
|
216
|
-
let currentAnchor = comment.nextSibling
|
|
217
|
-
newNodes.forEach((node) => {
|
|
218
|
-
if (currentAnchor === node.el) {
|
|
219
|
-
currentAnchor = currentAnchor.nextSibling
|
|
220
|
-
} else {
|
|
221
|
-
comment.parentNode?.insertBefore(node.el, currentAnchor)
|
|
222
|
-
}
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
renderedNodes = newNodes
|
|
226
|
-
})
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function processIf(el: HTMLElement, expr: string, context: Context) {
|
|
230
|
-
const parent = el.parentNode
|
|
231
|
-
if (!parent) return
|
|
232
|
-
|
|
233
|
-
const uuid = crypto.randomUUID()
|
|
234
|
-
const comment = document.createComment(` ui:if ${uuid} `)
|
|
235
|
-
el.before(comment)
|
|
236
|
-
|
|
237
|
-
bindEffect(comment, () => {
|
|
238
|
-
const val = evaluate(expr, context)
|
|
239
|
-
if (val) {
|
|
240
|
-
if (!el.parentNode) {
|
|
241
|
-
comment.parentNode?.insertBefore(el, comment.nextSibling)
|
|
242
|
-
}
|
|
243
|
-
} else if (el.parentNode) {
|
|
244
|
-
el.remove()
|
|
245
|
-
}
|
|
246
|
-
})
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function processAttributes(el: HTMLElement, context: Context) {
|
|
250
|
-
const attrs = Array.from(el.attributes)
|
|
251
|
-
|
|
252
|
-
for (const attr of attrs) {
|
|
253
|
-
const { name, value } = attr
|
|
254
|
-
const isText = ['ui:text', ':text'].includes(name)
|
|
255
|
-
const isTwoWayBind = ['ui:bind', ':bind'].includes(name)
|
|
256
|
-
const isClassBind = ['ui:class', ':class'].includes(name)
|
|
257
|
-
const isStyleBind = ['ui:style', ':style'].includes(name)
|
|
258
|
-
const isAttrBind = name.startsWith('ui:') || name.startsWith(':')
|
|
259
|
-
const isEvent = name.startsWith('ui@') || name.startsWith('@')
|
|
260
|
-
|
|
261
|
-
if (isText) {
|
|
262
|
-
processTextBinding(el, value, context)
|
|
263
|
-
el.removeAttribute(name)
|
|
264
|
-
} else if (isTwoWayBind) {
|
|
265
|
-
processTwoWayBinding(el, value, context)
|
|
266
|
-
el.removeAttribute(name)
|
|
267
|
-
} else if (isClassBind) {
|
|
268
|
-
processClassBinding(el, value, context)
|
|
269
|
-
el.removeAttribute(name)
|
|
270
|
-
} else if (isStyleBind) {
|
|
271
|
-
processStyleBinding(el, value, context)
|
|
272
|
-
el.removeAttribute(name)
|
|
273
|
-
} else if (isAttrBind) {
|
|
274
|
-
const bound = name.split(':').pop()!
|
|
275
|
-
processAttrBinding(el, bound, value, context)
|
|
276
|
-
el.removeAttribute(name)
|
|
277
|
-
} else if (isEvent) {
|
|
278
|
-
processEventBinding(el, name, value, context)
|
|
279
|
-
el.removeAttribute(name)
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function processTextBinding(el: HTMLElement, expr: string, context: Context) {
|
|
285
|
-
bindEffect(el, () => {
|
|
286
|
-
const val = evaluate(expr, context)
|
|
287
|
-
el.textContent = val !== undefined && val !== null ? String(val) : ''
|
|
288
|
-
})
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function processTwoWayBinding(el: HTMLElement, expr: string, context: Context) {
|
|
292
|
-
const isCheckbox = el instanceof HTMLInputElement && el.type === 'checkbox'
|
|
293
|
-
const isRadio = el instanceof HTMLInputElement && el.type === 'radio'
|
|
294
|
-
|
|
295
|
-
// 1. Reactive state to DOM
|
|
296
|
-
bindEffect(el, () => {
|
|
297
|
-
const val = evaluate(expr, context)
|
|
298
|
-
if (isCheckbox) {
|
|
299
|
-
el.checked = !!val
|
|
300
|
-
} else if (isRadio) {
|
|
301
|
-
el.checked = el.value === String(val)
|
|
302
|
-
} else {
|
|
303
|
-
const target = el as
|
|
304
|
-
| HTMLInputElement
|
|
305
|
-
| HTMLSelectElement
|
|
306
|
-
| HTMLTextAreaElement
|
|
307
|
-
target.value = val == null ? '' : String(val)
|
|
308
|
-
}
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
// 2. DOM to Reactive state
|
|
312
|
-
const isChange = isCheckbox || isRadio || el instanceof HTMLSelectElement
|
|
313
|
-
const eventName = isChange ? 'change' : 'input'
|
|
314
|
-
el.addEventListener(eventName, ($event) => {
|
|
315
|
-
const target = isCheckbox ? 'checked' : 'value'
|
|
316
|
-
const value = `$event.target.${target}`
|
|
317
|
-
evaluateEvent($event, `${expr} = ${value}`, context)
|
|
318
|
-
})
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function processAttrBinding(
|
|
322
|
-
el: HTMLElement,
|
|
323
|
-
attr: string,
|
|
324
|
-
expr: string,
|
|
325
|
-
context: Context,
|
|
326
|
-
) {
|
|
327
|
-
bindEffect(el, () => {
|
|
328
|
-
const val = evaluate(expr, context)
|
|
329
|
-
if (val === null || val === undefined || val === false) {
|
|
330
|
-
el.removeAttribute(attr)
|
|
331
|
-
} else if (val === true) {
|
|
332
|
-
el.setAttribute(attr, '')
|
|
333
|
-
} else {
|
|
334
|
-
el.setAttribute(attr, String(val))
|
|
335
|
-
}
|
|
336
|
-
})
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function processClassBinding(el: HTMLElement, expr: string, context: Context) {
|
|
340
|
-
let oldClasses = new Set<string>()
|
|
341
|
-
|
|
342
|
-
bindEffect(el, () => {
|
|
343
|
-
const val = evaluate(expr, context)
|
|
344
|
-
const newClasses = new Set<string>()
|
|
345
|
-
const addClass = (c: string) => c && newClasses.add(c)
|
|
346
|
-
const addClasses = (c: string) => c.split(/\s+/).forEach(addClass)
|
|
347
|
-
|
|
348
|
-
if (typeof val === 'string') addClasses(val)
|
|
349
|
-
else if (Array.isArray(val)) {
|
|
350
|
-
val.flat().forEach((c: any) => {
|
|
351
|
-
if (typeof c === 'string') addClasses(c)
|
|
352
|
-
})
|
|
353
|
-
} else if (typeof val === 'object' && val !== null) {
|
|
354
|
-
Object.entries(val).forEach(([c, condition]: [string, any]) => {
|
|
355
|
-
if (condition) addClasses(c)
|
|
356
|
-
})
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
oldClasses.forEach((c) => {
|
|
360
|
-
if (!newClasses.has(c)) el.classList.remove(c)
|
|
361
|
-
})
|
|
362
|
-
newClasses.forEach((c) => {
|
|
363
|
-
if (!oldClasses.has(c)) el.classList.add(c)
|
|
364
|
-
})
|
|
365
|
-
|
|
366
|
-
oldClasses = newClasses
|
|
367
|
-
})
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
function processStyleBinding(el: HTMLElement, expr: string, context: Context) {
|
|
371
|
-
let oldStyles: Record<string, any> = {}
|
|
372
|
-
|
|
373
|
-
bindEffect(el, () => {
|
|
374
|
-
const val = evaluate(expr, context)
|
|
375
|
-
const newStyles = typeof val === 'object' && val !== null ? val : {}
|
|
376
|
-
|
|
377
|
-
for (const key in oldStyles) {
|
|
378
|
-
if (!(key in newStyles)) {
|
|
379
|
-
;(el.style as any)[key] = ''
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
for (const key in newStyles) {
|
|
384
|
-
if (oldStyles[key] !== newStyles[key]) {
|
|
385
|
-
;(el.style as any)[key] = newStyles[key]
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
oldStyles = { ...newStyles }
|
|
390
|
-
})
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
function processEventBinding(
|
|
394
|
-
el: HTMLElement,
|
|
395
|
-
evt: string,
|
|
396
|
-
expr: string,
|
|
397
|
-
context: Context,
|
|
398
|
-
) {
|
|
399
|
-
const refs = evt.split('@').pop()!
|
|
400
|
-
const [name, ...modifiers] = refs.split('.')
|
|
401
|
-
|
|
402
|
-
const isOutside = modifiers.includes('outside')
|
|
403
|
-
const target = isOutside ? document : el
|
|
404
|
-
|
|
405
|
-
const handler: EventListener = ($event: Event) => {
|
|
406
|
-
if (!el.isConnected) return
|
|
407
|
-
|
|
408
|
-
const isTargetNode = $event.target instanceof Node
|
|
409
|
-
|
|
410
|
-
if (isOutside && isTargetNode && el.contains($event.target)) return
|
|
411
|
-
if (modifiers.includes('self') && $event.target !== el) return
|
|
412
|
-
|
|
413
|
-
if (modifiers.includes('prevent')) $event.preventDefault()
|
|
414
|
-
if (modifiers.includes('stop')) $event.stopPropagation()
|
|
415
|
-
|
|
416
|
-
evaluateEvent($event, expr, context)
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
target.addEventListener(name, handler)
|
|
420
|
-
|
|
421
|
-
if (isOutside) {
|
|
422
|
-
const bNode = el as BoundNode
|
|
423
|
-
bNode._effects ??= []
|
|
424
|
-
bNode._effects.push(() => target.removeEventListener(name, handler))
|
|
425
|
-
}
|
|
426
|
-
}
|
package/src/reactivity.ts
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
let activeEffect: symbol | null = null
|
|
2
|
-
const targetMap = new WeakMap<
|
|
3
|
-
object,
|
|
4
|
-
Map<string | symbol, Set<ReactiveEffect>>
|
|
5
|
-
>()
|
|
6
|
-
const proxyMap = new WeakMap<object, any>()
|
|
7
|
-
const effectMap = new WeakMap<symbol, ReactiveEffect>()
|
|
8
|
-
|
|
9
|
-
export class ReactiveEffect {
|
|
10
|
-
active = true
|
|
11
|
-
deps: Set<Set<ReactiveEffect>> = new Set()
|
|
12
|
-
|
|
13
|
-
constructor(public fn: () => void) {}
|
|
14
|
-
|
|
15
|
-
run() {
|
|
16
|
-
if (!this.active) return this.fn()
|
|
17
|
-
|
|
18
|
-
this.cleanup()
|
|
19
|
-
|
|
20
|
-
activeEffect = Symbol()
|
|
21
|
-
effectMap.set(activeEffect, this)
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
return this.fn()
|
|
25
|
-
} finally {
|
|
26
|
-
effectMap.delete(activeEffect)
|
|
27
|
-
activeEffect = null
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
stop() {
|
|
32
|
-
if (this.active) {
|
|
33
|
-
this.cleanup()
|
|
34
|
-
this.active = false
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
cleanup() {
|
|
39
|
-
this.deps.forEach((dep) => dep.delete(this))
|
|
40
|
-
this.deps.clear()
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
effect() {
|
|
44
|
-
return {
|
|
45
|
-
run: () => this.run(),
|
|
46
|
-
stop: () => this.stop(),
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function effect(fn: () => void) {
|
|
52
|
-
const ref = new ReactiveEffect(fn)
|
|
53
|
-
ref.run()
|
|
54
|
-
return ref.effect()
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function track(target: object, key: string | symbol) {
|
|
58
|
-
if (activeEffect) {
|
|
59
|
-
let depsMap = targetMap.get(target)
|
|
60
|
-
if (!depsMap) {
|
|
61
|
-
depsMap = new Map()
|
|
62
|
-
targetMap.set(target, depsMap)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
let dep = depsMap.get(key)
|
|
66
|
-
if (!dep) {
|
|
67
|
-
dep = new Set()
|
|
68
|
-
depsMap.set(key, dep)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const active = effectMap.get(activeEffect)
|
|
72
|
-
if (active) {
|
|
73
|
-
dep.add(active)
|
|
74
|
-
active.deps.add(dep)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function trigger(target: object, key: string | symbol) {
|
|
80
|
-
const depsMap = targetMap.get(target)
|
|
81
|
-
if (!depsMap) return
|
|
82
|
-
|
|
83
|
-
const dep = depsMap.get(key)
|
|
84
|
-
if (dep) {
|
|
85
|
-
const effects = new Set(dep)
|
|
86
|
-
effects.forEach((effect) => effect.run())
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function reactive<T extends object>(target: T): T {
|
|
91
|
-
const notObject = typeof target !== 'object' || target === null
|
|
92
|
-
if (notObject) return target
|
|
93
|
-
|
|
94
|
-
const isReactive = Object.hasOwn(target, '_isReactive')
|
|
95
|
-
if (isReactive) return target
|
|
96
|
-
|
|
97
|
-
const existingProxy = proxyMap.get(target)
|
|
98
|
-
if (existingProxy) return existingProxy
|
|
99
|
-
|
|
100
|
-
const proxy = new Proxy(target, {
|
|
101
|
-
get(obj, key, receiver) {
|
|
102
|
-
if (key === '_isReactive') return true
|
|
103
|
-
track(obj, key)
|
|
104
|
-
|
|
105
|
-
// getter
|
|
106
|
-
const desc = Object.getOwnPropertyDescriptor(obj, key)
|
|
107
|
-
if (desc?.get) return desc.get.call(obj)
|
|
108
|
-
|
|
109
|
-
const res = Reflect.get(obj, key, receiver)
|
|
110
|
-
// deep reactivity
|
|
111
|
-
return typeof res === 'object' && res !== null ? reactive(res) : res
|
|
112
|
-
},
|
|
113
|
-
set(obj, key, value, receiver) {
|
|
114
|
-
const isArray = Array.isArray(obj)
|
|
115
|
-
const oldValue = Reflect.get(obj, key, receiver)
|
|
116
|
-
const hadKey =
|
|
117
|
-
isArray && String(Number(key)) === key
|
|
118
|
-
? Number(key) < obj.length
|
|
119
|
-
: Object.hasOwn(obj, key)
|
|
120
|
-
|
|
121
|
-
const result = Reflect.set(obj, key, value, receiver)
|
|
122
|
-
|
|
123
|
-
if (!hadKey) {
|
|
124
|
-
trigger(obj, key)
|
|
125
|
-
if (isArray && key !== 'length') {
|
|
126
|
-
trigger(obj, 'length')
|
|
127
|
-
}
|
|
128
|
-
} else if (oldValue !== value) {
|
|
129
|
-
trigger(obj, key)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return result
|
|
133
|
-
},
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
proxyMap.set(target, proxy)
|
|
137
|
-
return proxy
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function traverse(value: any, seen = new Set()) {
|
|
141
|
-
if (typeof value !== 'object' || value === null || seen.has(value)) {
|
|
142
|
-
return value
|
|
143
|
-
}
|
|
144
|
-
seen.add(value)
|
|
145
|
-
for (const key in value) {
|
|
146
|
-
traverse(value[key], seen)
|
|
147
|
-
}
|
|
148
|
-
return value
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export function watch(
|
|
152
|
-
source: any | (() => any),
|
|
153
|
-
cb: (newValue: any, oldValue: any) => void,
|
|
154
|
-
options?: { immediate?: boolean },
|
|
155
|
-
) {
|
|
156
|
-
let oldValue: any
|
|
157
|
-
let isFirstRun = true
|
|
158
|
-
|
|
159
|
-
const getter = source instanceof Function ? source : () => traverse(source)
|
|
160
|
-
|
|
161
|
-
const runner = effect(() => {
|
|
162
|
-
const newValue = getter()
|
|
163
|
-
|
|
164
|
-
if (isFirstRun) {
|
|
165
|
-
isFirstRun = false
|
|
166
|
-
oldValue = newValue
|
|
167
|
-
if (options?.immediate) {
|
|
168
|
-
cb(newValue, undefined)
|
|
169
|
-
}
|
|
170
|
-
} else {
|
|
171
|
-
cb(newValue, oldValue)
|
|
172
|
-
oldValue = newValue
|
|
173
|
-
}
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
return runner.stop
|
|
177
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ESNext",
|
|
4
|
-
"useDefineForClassFields": true,
|
|
5
|
-
"module": "ESNext",
|
|
6
|
-
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
7
|
-
"skipLibCheck": true,
|
|
8
|
-
|
|
9
|
-
/* Bundler mode */
|
|
10
|
-
"moduleResolution": "bundler",
|
|
11
|
-
"allowImportingTsExtensions": true,
|
|
12
|
-
"resolveJsonModule": true,
|
|
13
|
-
"isolatedModules": true,
|
|
14
|
-
"noEmit": true,
|
|
15
|
-
|
|
16
|
-
/* Linting */
|
|
17
|
-
"strict": true,
|
|
18
|
-
"noUnusedLocals": true,
|
|
19
|
-
"noUnusedParameters": true,
|
|
20
|
-
"noFallthroughCasesInSwitch": true
|
|
21
|
-
},
|
|
22
|
-
"include": ["src", "vite.config.ts"]
|
|
23
|
-
}
|
package/vite.config.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'vite'
|
|
2
|
-
import dts from 'vite-plugin-dts'
|
|
3
|
-
|
|
4
|
-
export default defineConfig({
|
|
5
|
-
build: {
|
|
6
|
-
lib: {
|
|
7
|
-
name: '@jsweb/ui',
|
|
8
|
-
formats: ['es', 'umd'],
|
|
9
|
-
entry: './src/index.ts',
|
|
10
|
-
fileName: (format: string) => `index.${format}.js`,
|
|
11
|
-
},
|
|
12
|
-
sourcemap: true,
|
|
13
|
-
minify: 'terser',
|
|
14
|
-
},
|
|
15
|
-
plugins: [dts({ insertTypesEntry: true })],
|
|
16
|
-
})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|