@katabatic/runtime 1.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/README.md +3 -0
- package/package.json +19 -0
- package/src/animate.js +64 -0
- package/src/client.js +48 -0
- package/src/eachBlock.js +185 -0
- package/src/ifBlock.js +95 -0
- package/src/index.js +133 -0
- package/src/rootBlock.js +12 -0
package/README.md
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@katabatic/runtime",
|
|
3
|
+
"license": "MIT",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/katabatic-js/katabatic.git",
|
|
9
|
+
"directory": "packages/runtime"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./src/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@katabatic/signals": "^1.0.0"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/animate.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export class Animate {
|
|
2
|
+
constructor(fn, direction) {
|
|
3
|
+
this.fn = fn
|
|
4
|
+
this.direction = direction
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
#build(options) {
|
|
8
|
+
if (!this.animation) {
|
|
9
|
+
const animation = this.fn(options)
|
|
10
|
+
|
|
11
|
+
animation.finished
|
|
12
|
+
.catch(() => {})
|
|
13
|
+
.finally(() => {
|
|
14
|
+
if (this.animation === animation) {
|
|
15
|
+
this.animation = undefined
|
|
16
|
+
this.reversed = undefined
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
this.animation = animation
|
|
21
|
+
this.reversed = false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#direction(direction) {
|
|
26
|
+
if (this.animation) {
|
|
27
|
+
if (direction === 'in' && this.reversed) {
|
|
28
|
+
this.animation.reverse()
|
|
29
|
+
this.reversed = false
|
|
30
|
+
} else if (direction === 'out' && !this.reversed) {
|
|
31
|
+
this.animation.reverse()
|
|
32
|
+
this.reversed = true
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#cancel() {
|
|
38
|
+
if (this.animation) {
|
|
39
|
+
this.animation.cancel()
|
|
40
|
+
this.animation = undefined
|
|
41
|
+
this.reversed = undefined
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
run(direction) {
|
|
46
|
+
if (this.direction === 'both') {
|
|
47
|
+
this.#build({ direction: 'both' })
|
|
48
|
+
this.#direction(direction)
|
|
49
|
+
} else {
|
|
50
|
+
if (direction !== this.direction) {
|
|
51
|
+
this.#cancel()
|
|
52
|
+
} else {
|
|
53
|
+
this.#build({ direction })
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return this
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
dispose() {
|
|
60
|
+
this.animation?.cancel()
|
|
61
|
+
this.animation = undefined
|
|
62
|
+
this.reversed = undefined
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/client.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Effect } from '@katabatic/signals'
|
|
2
|
+
import { Animate } from './animate.js'
|
|
3
|
+
|
|
4
|
+
export class Client extends Set {
|
|
5
|
+
effect(fn) {
|
|
6
|
+
const effect = new Effect(fn, { orphaned: true, async: false }).run()
|
|
7
|
+
this.add(effect)
|
|
8
|
+
return effect
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
dispose() {
|
|
12
|
+
for (const entry of cleared(this)) {
|
|
13
|
+
entry.dispose?.()
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class AnimatedClient extends Client {
|
|
19
|
+
animate(direction, fn) {
|
|
20
|
+
const animate = new Animate(fn, direction)
|
|
21
|
+
this.add(animate)
|
|
22
|
+
return animate
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
runAnimate(direction, callback) {
|
|
26
|
+
const promises = []
|
|
27
|
+
for (const entry of this) {
|
|
28
|
+
if (entry instanceof Animate) {
|
|
29
|
+
promises.push(entry.run(direction).animation?.finished)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const finished = Promise.all(promises)
|
|
34
|
+
.catch(() => {})
|
|
35
|
+
.finally(() => {
|
|
36
|
+
if (this.finished === finished) {
|
|
37
|
+
callback?.()
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
this.finished = finished
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const cleared = (self) => {
|
|
45
|
+
const entries = [...self]
|
|
46
|
+
self.clear()
|
|
47
|
+
return entries
|
|
48
|
+
}
|
package/src/eachBlock.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { Signal, SignalEvent, Effect, track } from '@katabatic/signals'
|
|
2
|
+
import { Tracker } from '@katabatic/signals/tracker'
|
|
3
|
+
import { AnimatedClient } from './client.js'
|
|
4
|
+
|
|
5
|
+
export class EachBlock extends Map {
|
|
6
|
+
constructor(anchor, getIterable, getKey, fn) {
|
|
7
|
+
super()
|
|
8
|
+
this.getIterable = getIterable
|
|
9
|
+
this.getKey = getKey
|
|
10
|
+
this.fn = fn
|
|
11
|
+
this.#head = createHeadBlock(anchor)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
#head
|
|
15
|
+
#effect
|
|
16
|
+
|
|
17
|
+
#insertBlockAfter(block, tail) {
|
|
18
|
+
const anchor = tail.nextNode
|
|
19
|
+
this.fn(block, anchor, block.getValue)
|
|
20
|
+
block.anchor = anchor.previousSibling
|
|
21
|
+
|
|
22
|
+
tail.insertAfter(block)
|
|
23
|
+
this.set(block.key, block)
|
|
24
|
+
|
|
25
|
+
return block
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#moveBlockAfter(block, tail) {
|
|
29
|
+
if (block === tail) return block
|
|
30
|
+
|
|
31
|
+
const anchor = tail.nextNode
|
|
32
|
+
let node = block.previousBlock.nextNode
|
|
33
|
+
while (true) {
|
|
34
|
+
const nextNode = node.nextSibling
|
|
35
|
+
anchor.parentNode.insertBefore(node, anchor)
|
|
36
|
+
|
|
37
|
+
if (node === block.anchor) break
|
|
38
|
+
node = nextNode
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
tail.insertAfter(block)
|
|
42
|
+
return block
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#removeBlock(block) {
|
|
46
|
+
let node = block.previousBlock.nextNode
|
|
47
|
+
while (true) {
|
|
48
|
+
const nextNode = node.nextSibling
|
|
49
|
+
node.remove()
|
|
50
|
+
|
|
51
|
+
if (node === block.anchor) break
|
|
52
|
+
node = nextNode
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
block.dispose()
|
|
56
|
+
block.remove()
|
|
57
|
+
this.delete(block.key)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
*#getRemovedBlocks(iterable) {
|
|
61
|
+
const keys = new Set(iterable.map((v, i) => this.getKey(v, i)))
|
|
62
|
+
|
|
63
|
+
for (const block of this.values()) {
|
|
64
|
+
if (!keys.has(block.key)) yield block
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
init() {
|
|
69
|
+
this.#effect ??= new Effect(() => {
|
|
70
|
+
const iterable = this.getIterable()
|
|
71
|
+
|
|
72
|
+
for (const block of this.#getRemovedBlocks(iterable)) {
|
|
73
|
+
this.#removeBlock(block)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let tail = this.#head
|
|
77
|
+
let index = 0
|
|
78
|
+
for (const item of iterable) {
|
|
79
|
+
const key = this.getKey(item, index)
|
|
80
|
+
let block = this.get(key)
|
|
81
|
+
|
|
82
|
+
if (block) {
|
|
83
|
+
if (tail.nextBlock === block) {
|
|
84
|
+
tail = updateBlock(block, item)
|
|
85
|
+
} else {
|
|
86
|
+
tail = updateBlock(this.#moveBlockAfter(block, tail), item)
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
tail = this.#insertBlockAfter(new Block(key, item), tail)
|
|
90
|
+
if (this.#effect) tail.runAnimate('in')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
index++
|
|
94
|
+
}
|
|
95
|
+
}, { orphaned: true, async: false }).run()
|
|
96
|
+
|
|
97
|
+
return this
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
dispose() {
|
|
101
|
+
this.#effect?.dispose()
|
|
102
|
+
for (const entry of cleared(this)) {
|
|
103
|
+
entry.dispose()
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function updateBlock(block, value) {
|
|
109
|
+
block.setValue(value)
|
|
110
|
+
return block
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function eachBlock(anchor, getIterable, getKey, body) {
|
|
114
|
+
return new EachBlock(anchor, getIterable, getKey, body).init()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class Block extends AnimatedClient {
|
|
118
|
+
constructor(key, value) {
|
|
119
|
+
super()
|
|
120
|
+
this.key = key
|
|
121
|
+
this.value = value
|
|
122
|
+
this.signal = new Signal(undefined, this)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** @type {Node} */
|
|
126
|
+
anchor
|
|
127
|
+
/** @type {Node} */
|
|
128
|
+
parentAnchor
|
|
129
|
+
|
|
130
|
+
setValue(nextValue) {
|
|
131
|
+
const hasChange = this.value !== nextValue
|
|
132
|
+
this.value = nextValue
|
|
133
|
+
if (hasChange) {
|
|
134
|
+
this.signal.dispatchEvent(new SignalEvent('change'))
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
getValue = () => {
|
|
139
|
+
track?.(new Tracker(this.signal))
|
|
140
|
+
return this.value
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
insertAfter(block) {
|
|
144
|
+
if (block.previousBlock) {
|
|
145
|
+
block.previousBlock.nextBlock = block.nextBlock
|
|
146
|
+
}
|
|
147
|
+
if (block.nextBlock) {
|
|
148
|
+
block.nextBlock.previousBlock = block.previousBlock
|
|
149
|
+
}
|
|
150
|
+
if (this.nextBlock) {
|
|
151
|
+
this.nextBlock.previousBlock = block
|
|
152
|
+
}
|
|
153
|
+
block.previousBlock = this
|
|
154
|
+
block.nextBlock = this.nextBlock
|
|
155
|
+
this.nextBlock = block
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
remove() {
|
|
159
|
+
if (this.previousBlock) {
|
|
160
|
+
this.previousBlock.nextBlock = this.nextBlock
|
|
161
|
+
}
|
|
162
|
+
if (this.nextBlock) {
|
|
163
|
+
this.nextBlock.previousBlock = this.previousBlock
|
|
164
|
+
}
|
|
165
|
+
this.previousBlock = undefined
|
|
166
|
+
this.nextBlock = undefined
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
get nextNode() {
|
|
170
|
+
return this.anchor?.nextSibling ?? this.parentAnchor.firstChild
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function createHeadBlock(anchor) {
|
|
175
|
+
const block = new Block()
|
|
176
|
+
block.anchor = anchor.previousSibling
|
|
177
|
+
block.parentAnchor = block.anchor ? undefined : anchor.parentNode
|
|
178
|
+
return block
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const cleared = (self) => {
|
|
182
|
+
const entries = [...self.values()]
|
|
183
|
+
self.clear()
|
|
184
|
+
return entries
|
|
185
|
+
}
|
package/src/ifBlock.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Effect } from '@katabatic/signals'
|
|
2
|
+
import { AnimatedClient } from './client.js'
|
|
3
|
+
|
|
4
|
+
export class IfBlock {
|
|
5
|
+
constructor(anchor, getCondition, concequent, alternate) {
|
|
6
|
+
this.getCondition = getCondition
|
|
7
|
+
this.concequent = concequent
|
|
8
|
+
this.alternate = alternate
|
|
9
|
+
this.#headBlock = createHeadBlock(anchor)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
#headBlock
|
|
13
|
+
#condBlock
|
|
14
|
+
#altBlock
|
|
15
|
+
#effect
|
|
16
|
+
|
|
17
|
+
#insertBlock(block, fn) {
|
|
18
|
+
const alternate = fn === this.alternate
|
|
19
|
+
const previousBlock = alternate ? this.#condBlock ?? this.#headBlock : this.#headBlock
|
|
20
|
+
const anchor = previousBlock.nextNode
|
|
21
|
+
|
|
22
|
+
fn(block, anchor)
|
|
23
|
+
block.anchor = anchor.previousSibling
|
|
24
|
+
|
|
25
|
+
return block
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#removeBlock(block) {
|
|
29
|
+
const alternate = block === this.#altBlock
|
|
30
|
+
const previousBlock = alternate ? this.#condBlock ?? this.#headBlock : this.#headBlock
|
|
31
|
+
|
|
32
|
+
let node = previousBlock.nextNode
|
|
33
|
+
while (true) {
|
|
34
|
+
const nextNode = node.nextSibling
|
|
35
|
+
node.remove()
|
|
36
|
+
|
|
37
|
+
if (node === block.anchor) break
|
|
38
|
+
node = nextNode
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
block.dispose()
|
|
42
|
+
this.#condBlock = alternate ? this.#condBlock : undefined
|
|
43
|
+
this.#altBlock = alternate ? undefined : this.#altBlock
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
init() {
|
|
47
|
+
let previousCondition
|
|
48
|
+
|
|
49
|
+
this.#effect ??= new Effect(
|
|
50
|
+
() => {
|
|
51
|
+
const condition = this.getCondition()
|
|
52
|
+
|
|
53
|
+
if (condition === previousCondition) return
|
|
54
|
+
previousCondition = condition
|
|
55
|
+
|
|
56
|
+
if (condition) {
|
|
57
|
+
this.#altBlock?.runAnimate('out', () => this.#removeBlock(this.#altBlock))
|
|
58
|
+
this.#condBlock ??= this.#insertBlock(new Block(), this.concequent)
|
|
59
|
+
if (this.#effect) this.#condBlock.runAnimate('in')
|
|
60
|
+
} else {
|
|
61
|
+
this.#condBlock?.runAnimate('out', () => this.#removeBlock(this.#condBlock))
|
|
62
|
+
if (this.alternate) {
|
|
63
|
+
this.#altBlock ??= this.#insertBlock(new Block(), this.alternate)
|
|
64
|
+
if (this.#effect) this.#altBlock.runAnimate('in')
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
{ orphaned: true, async: false }
|
|
69
|
+
).run()
|
|
70
|
+
|
|
71
|
+
return this
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
dispose() {
|
|
75
|
+
this.#effect?.dispose()
|
|
76
|
+
this.#condBlock?.dispose()
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function ifBlock(anchor, getCondition, concequent, alternate) {
|
|
81
|
+
return new IfBlock(anchor, getCondition, concequent, alternate).init()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
class Block extends AnimatedClient {
|
|
85
|
+
get nextNode() {
|
|
86
|
+
return this.anchor?.nextSibling ?? this.parentAnchor.firstChild
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function createHeadBlock(anchor) {
|
|
91
|
+
const block = new Block()
|
|
92
|
+
block.anchor = anchor.previousSibling
|
|
93
|
+
block.parentAnchor = block.anchor ? undefined : anchor.parentNode
|
|
94
|
+
return block
|
|
95
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Signal, SignalEvent, Boundary, track } from '@katabatic/signals'
|
|
2
|
+
import { AttributeTracker, PropertyTracker } from '@katabatic/signals/tracker'
|
|
3
|
+
import { Client } from './client.js'
|
|
4
|
+
import { EachBlock } from './eachBlock.js'
|
|
5
|
+
import { IfBlock } from './ifBlock.js'
|
|
6
|
+
|
|
7
|
+
export { EachBlock, IfBlock }
|
|
8
|
+
|
|
9
|
+
export function $$(customElement) {
|
|
10
|
+
|
|
11
|
+
Client.prototype.ifBlock ??= function (anchor, getCondition, concequent, alternate) {
|
|
12
|
+
const block = new IfBlock(anchor, getCondition, concequent, alternate).init()
|
|
13
|
+
this.add(block)
|
|
14
|
+
return block
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
Client.prototype.eachBlock ??= function (anchor, getIterable, getKey, body) {
|
|
18
|
+
const block = new EachBlock(anchor, getIterable, getKey, body).init()
|
|
19
|
+
this.add(block)
|
|
20
|
+
return block
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const client = new Client()
|
|
24
|
+
let signal
|
|
25
|
+
let locked = false
|
|
26
|
+
|
|
27
|
+
client.boundary = function (fn) {
|
|
28
|
+
const boundary = new Boundary(fn, { orphaned: true }).init()
|
|
29
|
+
this.add(boundary)
|
|
30
|
+
return boundary
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
client.block = function (fn) {
|
|
34
|
+
const block = new Client()
|
|
35
|
+
fn(block)
|
|
36
|
+
this.add(block)
|
|
37
|
+
return block
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
client.lifecycle = function (event) {
|
|
41
|
+
const set = (state, result) => {
|
|
42
|
+
this.state = state
|
|
43
|
+
return result
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
switch (this.state) {
|
|
47
|
+
case 'connected':
|
|
48
|
+
if (event === 'disconnected') return set('disconnected', false)
|
|
49
|
+
if (event === 'microtask') return set('connected', false)
|
|
50
|
+
break
|
|
51
|
+
case 'disconnected':
|
|
52
|
+
if (event === 'connected') return set('connected', false)
|
|
53
|
+
if (event === 'microtask') return set('disconnected', true)
|
|
54
|
+
break
|
|
55
|
+
default:
|
|
56
|
+
if (event === 'connected') return set('connected', true)
|
|
57
|
+
break
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
client.instrument = function (property) {
|
|
62
|
+
signal ??= new Signal(undefined, customElement)
|
|
63
|
+
|
|
64
|
+
let value = customElement[property]
|
|
65
|
+
Object.defineProperty(customElement, property, {
|
|
66
|
+
get: () => {
|
|
67
|
+
track?.(new PropertyTracker(signal, property))
|
|
68
|
+
return value
|
|
69
|
+
},
|
|
70
|
+
set: (nextValue) => {
|
|
71
|
+
const hasChange = value !== nextValue
|
|
72
|
+
value = nextValue
|
|
73
|
+
|
|
74
|
+
if (hasChange) {
|
|
75
|
+
signal.dispatchEvent(new SignalEvent('set', { property }))
|
|
76
|
+
signal.dispatchEvent(new SignalEvent('change'))
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
client.trackAttribute = function (name) {
|
|
83
|
+
signal ??= new Signal(undefined, customElement)
|
|
84
|
+
track?.(new AttributeTracker(signal, name))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
client.attributeChanged = function (name, value, nextValue) {
|
|
88
|
+
if (value !== nextValue) {
|
|
89
|
+
signal ??= new Signal(undefined, customElement)
|
|
90
|
+
signal.dispatchEvent(new SignalEvent('attributeChanged', { name }))
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
client.getBindingProp = function (object, property, fn) {
|
|
95
|
+
if (
|
|
96
|
+
!!Object.getOwnPropertyDescriptor(object, property)?.get ||
|
|
97
|
+
!!Object.getOwnPropertyDescriptor(Object.getPrototypeOf(object), property)?.get
|
|
98
|
+
) {
|
|
99
|
+
locked = true
|
|
100
|
+
fn(object[property])
|
|
101
|
+
locked = false
|
|
102
|
+
return true
|
|
103
|
+
}
|
|
104
|
+
return false
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
client.setBindingProp = function (object, property, value) {
|
|
108
|
+
if (
|
|
109
|
+
!!Object.getOwnPropertyDescriptor(object, property)?.set ||
|
|
110
|
+
!!Object.getOwnPropertyDescriptor(Object.getPrototypeOf(object), property)?.set
|
|
111
|
+
) {
|
|
112
|
+
if (!locked) object[property] = value
|
|
113
|
+
return true
|
|
114
|
+
}
|
|
115
|
+
return false
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return client
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
$$.init = function (object, property, value) {
|
|
122
|
+
const isSetter = arguments.length === 2
|
|
123
|
+
|
|
124
|
+
if (object.hasOwnProperty(property)) {
|
|
125
|
+
value = object[property]
|
|
126
|
+
delete object[property]
|
|
127
|
+
|
|
128
|
+
if (isSetter) {
|
|
129
|
+
object[property] = value
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return value
|
|
133
|
+
}
|