phoenix_live_view 0.20.5 → 0.20.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/assets/js/phoenix_live_view/dom.js +10 -24
- package/assets/js/phoenix_live_view/dom_patch.js +57 -52
- package/assets/js/phoenix_live_view/rendered.js +7 -0
- package/assets/package.json +1 -1
- package/package.json +1 -1
- package/priv/static/phoenix_live_view.cjs.js +64 -61
- package/priv/static/phoenix_live_view.cjs.js.map +2 -2
- package/priv/static/phoenix_live_view.esm.js +64 -61
- package/priv/static/phoenix_live_view.esm.js.map +2 -2
- package/priv/static/phoenix_live_view.js +64 -61
- package/priv/static/phoenix_live_view.min.js +5 -5
|
@@ -221,23 +221,7 @@ let DOM = {
|
|
|
221
221
|
|
|
222
222
|
default:
|
|
223
223
|
let timeout = parseInt(value)
|
|
224
|
-
let trigger = (
|
|
225
|
-
if(blur){
|
|
226
|
-
// if the input is blurred, we need to cancel the next throttle timeout
|
|
227
|
-
// therefore we store the timer id in the THROTTLED private attribute
|
|
228
|
-
if(throttle && this.private(el, THROTTLED)){
|
|
229
|
-
clearTimeout(this.private(el, THROTTLED))
|
|
230
|
-
this.deletePrivate(el, THROTTLED)
|
|
231
|
-
}
|
|
232
|
-
// on debounce we just trigger the callback
|
|
233
|
-
return callback()
|
|
234
|
-
}
|
|
235
|
-
// no blur, remove the throttle attribute if we are in throttle mode
|
|
236
|
-
if(throttle) this.deletePrivate(el, THROTTLED)
|
|
237
|
-
// always call the callback to ensure that the latest event is processed,
|
|
238
|
-
// even when throttle is active
|
|
239
|
-
callback()
|
|
240
|
-
}
|
|
224
|
+
let trigger = () => throttle ? this.deletePrivate(el, THROTTLED) : callback()
|
|
241
225
|
let currentCycle = this.incCycle(el, DEBOUNCE_TRIGGER, trigger)
|
|
242
226
|
if(isNaN(timeout)){ return logError(`invalid throttle/debounce value: ${value}`) }
|
|
243
227
|
if(throttle){
|
|
@@ -252,10 +236,6 @@ let DOM = {
|
|
|
252
236
|
return false
|
|
253
237
|
} else {
|
|
254
238
|
callback()
|
|
255
|
-
// store the throttle timer id in the THROTTLED private attribute,
|
|
256
|
-
// so that we can cancel it if the input is blurred
|
|
257
|
-
// otherwise, when new events happen after blur, but before the old
|
|
258
|
-
// timeout is triggered, we would actually trigger the callback multiple times
|
|
259
239
|
const t = setTimeout(() => {
|
|
260
240
|
if(asyncFilter()){ this.triggerCycle(el, DEBOUNCE_TRIGGER) }
|
|
261
241
|
}, timeout)
|
|
@@ -278,17 +258,23 @@ let DOM = {
|
|
|
278
258
|
})
|
|
279
259
|
}
|
|
280
260
|
if(this.once(el, "bind-debounce")){
|
|
281
|
-
el.addEventListener("blur", () =>
|
|
261
|
+
el.addEventListener("blur", () => {
|
|
262
|
+
// because we trigger the callback here,
|
|
263
|
+
// we also clear the throttle timeout to prevent the callback
|
|
264
|
+
// from being called again after the timeout fires
|
|
265
|
+
clearTimeout(this.private(el, THROTTLED))
|
|
266
|
+
this.triggerCycle(el, DEBOUNCE_TRIGGER)
|
|
267
|
+
})
|
|
282
268
|
}
|
|
283
269
|
}
|
|
284
270
|
},
|
|
285
271
|
|
|
286
|
-
triggerCycle(el, key, currentCycle
|
|
272
|
+
triggerCycle(el, key, currentCycle){
|
|
287
273
|
let [cycle, trigger] = this.private(el, key)
|
|
288
274
|
if(!currentCycle){ currentCycle = cycle }
|
|
289
275
|
if(currentCycle === cycle){
|
|
290
276
|
this.incCycle(el, key)
|
|
291
|
-
trigger(
|
|
277
|
+
trigger()
|
|
292
278
|
}
|
|
293
279
|
},
|
|
294
280
|
|
|
@@ -99,41 +99,8 @@ export default class DOMPatch {
|
|
|
99
99
|
|
|
100
100
|
let externalFormTriggered = null
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
liveSocket.time("morphdom", () => {
|
|
106
|
-
this.streams.forEach(([ref, inserts, deleteIds, reset]) => {
|
|
107
|
-
inserts.forEach(([key, streamAt, limit]) => {
|
|
108
|
-
this.streamInserts[key] = {ref, streamAt, limit, reset}
|
|
109
|
-
})
|
|
110
|
-
if(reset !== undefined){
|
|
111
|
-
DOM.all(container, `[${PHX_STREAM_REF}="${ref}"]`, child => {
|
|
112
|
-
this.removeStreamChildElement(child)
|
|
113
|
-
})
|
|
114
|
-
}
|
|
115
|
-
deleteIds.forEach(id => {
|
|
116
|
-
let child = container.querySelector(`[id="${id}"]`)
|
|
117
|
-
if(child){ this.removeStreamChildElement(child) }
|
|
118
|
-
})
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
// clear stream items from the dead render if they are not inserted again
|
|
122
|
-
if(isJoinPatch){
|
|
123
|
-
DOM.all(this.container, `[${phxUpdate}=${PHX_STREAM}]`, el => {
|
|
124
|
-
// make sure to only remove elements owned by the current view
|
|
125
|
-
// see https://github.com/phoenixframework/phoenix_live_view/issues/3047
|
|
126
|
-
this.liveSocket.owner(el, (view) => {
|
|
127
|
-
if(view === this.view){
|
|
128
|
-
Array.from(el.children).forEach(child => {
|
|
129
|
-
this.removeStreamChildElement(child)
|
|
130
|
-
})
|
|
131
|
-
}
|
|
132
|
-
})
|
|
133
|
-
})
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
morphdom(targetContainer, html, {
|
|
102
|
+
function morph(targetContainer, source){
|
|
103
|
+
morphdom(targetContainer, source, {
|
|
137
104
|
childrenOnly: targetContainer.getAttribute(PHX_COMPONENT) === null,
|
|
138
105
|
getNodeKey: (node) => {
|
|
139
106
|
if(DOM.isPhxDestroyed(node)){ return null }
|
|
@@ -151,14 +118,6 @@ export default class DOMPatch {
|
|
|
151
118
|
|
|
152
119
|
this.setStreamRef(child, ref)
|
|
153
120
|
|
|
154
|
-
// we may need to restore skipped components, see removeStreamChildElement
|
|
155
|
-
child.querySelectorAll(`[${PHX_MAGIC_ID}][${PHX_SKIP}]`).forEach(el => {
|
|
156
|
-
const component = this.streamComponentRestore[el.getAttribute(PHX_MAGIC_ID)]
|
|
157
|
-
if(component){
|
|
158
|
-
el.replaceWith(component)
|
|
159
|
-
}
|
|
160
|
-
})
|
|
161
|
-
|
|
162
121
|
// streaming
|
|
163
122
|
if(streamAt === 0){
|
|
164
123
|
parent.insertAdjacentElement("afterbegin", child)
|
|
@@ -171,12 +130,21 @@ export default class DOMPatch {
|
|
|
171
130
|
},
|
|
172
131
|
onBeforeNodeAdded: (el) => {
|
|
173
132
|
DOM.maybeAddPrivateHooks(el, phxViewportTop, phxViewportBottom)
|
|
174
|
-
if(DOM.isFeedbackContainer(el, phxFeedbackFor)) feedbackContainers.push(el)
|
|
175
133
|
this.trackBefore("added", el)
|
|
176
|
-
|
|
134
|
+
|
|
135
|
+
let morphedEl = el
|
|
136
|
+
// this is a stream item that was kept on reset, recursively morph it
|
|
137
|
+
if(!isJoinPatch && this.streamComponentRestore[el.id]){
|
|
138
|
+
morphedEl = this.streamComponentRestore[el.id]
|
|
139
|
+
delete this.streamComponentRestore[el.id]
|
|
140
|
+
morph.bind(this)(morphedEl, el)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return morphedEl
|
|
177
144
|
},
|
|
178
145
|
onNodeAdded: (el) => {
|
|
179
146
|
if(el.getAttribute){ this.maybeReOrderStream(el, true) }
|
|
147
|
+
if(DOM.isFeedbackContainer(el, phxFeedbackFor)) feedbackContainers.push(el)
|
|
180
148
|
|
|
181
149
|
// hack to fix Safari handling of img srcset and video tags
|
|
182
150
|
if(el instanceof HTMLImageElement && el.srcset){
|
|
@@ -282,6 +250,43 @@ export default class DOMPatch {
|
|
|
282
250
|
}
|
|
283
251
|
}
|
|
284
252
|
})
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
this.trackBefore("added", container)
|
|
256
|
+
this.trackBefore("updated", container, container)
|
|
257
|
+
|
|
258
|
+
liveSocket.time("morphdom", () => {
|
|
259
|
+
this.streams.forEach(([ref, inserts, deleteIds, reset]) => {
|
|
260
|
+
inserts.forEach(([key, streamAt, limit]) => {
|
|
261
|
+
this.streamInserts[key] = {ref, streamAt, limit, reset}
|
|
262
|
+
})
|
|
263
|
+
if(reset !== undefined){
|
|
264
|
+
DOM.all(container, `[${PHX_STREAM_REF}="${ref}"]`, child => {
|
|
265
|
+
this.removeStreamChildElement(child)
|
|
266
|
+
})
|
|
267
|
+
}
|
|
268
|
+
deleteIds.forEach(id => {
|
|
269
|
+
let child = container.querySelector(`[id="${id}"]`)
|
|
270
|
+
if(child){ this.removeStreamChildElement(child) }
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
// clear stream items from the dead render if they are not inserted again
|
|
275
|
+
if(isJoinPatch){
|
|
276
|
+
DOM.all(this.container, `[${phxUpdate}=${PHX_STREAM}]`, el => {
|
|
277
|
+
// make sure to only remove elements owned by the current view
|
|
278
|
+
// see https://github.com/phoenixframework/phoenix_live_view/issues/3047
|
|
279
|
+
this.liveSocket.owner(el, (view) => {
|
|
280
|
+
if(view === this.view){
|
|
281
|
+
Array.from(el.children).forEach(child => {
|
|
282
|
+
this.removeStreamChildElement(child)
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
})
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
morph.bind(this)(targetContainer, html)
|
|
285
290
|
})
|
|
286
291
|
|
|
287
292
|
if(liveSocket.isDebugEnabled()){ detectDuplicateIds() }
|
|
@@ -328,14 +333,14 @@ export default class DOMPatch {
|
|
|
328
333
|
removeStreamChildElement(child){
|
|
329
334
|
if(!this.maybePendingRemove(child)){
|
|
330
335
|
if(this.streamInserts[child.id]){
|
|
331
|
-
// we need to store children so we can
|
|
332
|
-
|
|
333
|
-
child.
|
|
334
|
-
|
|
335
|
-
|
|
336
|
+
// we need to store children so we can morph them later
|
|
337
|
+
this.streamComponentRestore[child.id] = child
|
|
338
|
+
child.remove()
|
|
339
|
+
} else {
|
|
340
|
+
child.remove()
|
|
341
|
+
// TODO: check if we really don't want to call discarded for re-added stream items
|
|
342
|
+
this.onNodeDiscarded(child)
|
|
336
343
|
}
|
|
337
|
-
child.remove()
|
|
338
|
-
this.onNodeDiscarded(child)
|
|
339
344
|
}
|
|
340
345
|
}
|
|
341
346
|
|
|
@@ -167,6 +167,13 @@ export default class Rendered {
|
|
|
167
167
|
let newc = diff[COMPONENTS]
|
|
168
168
|
let cache = {}
|
|
169
169
|
delete diff[COMPONENTS]
|
|
170
|
+
// we must consider all newly added components as reset for proper change tracking
|
|
171
|
+
if(newc){
|
|
172
|
+
let prevComponents = this.rendered[COMPONENTS] || {}
|
|
173
|
+
for(let cid in newc){
|
|
174
|
+
if(prevComponents[cid] === undefined){ newc[cid].reset = true }
|
|
175
|
+
}
|
|
176
|
+
}
|
|
170
177
|
this.rendered = this.mutableMerge(this.rendered, diff)
|
|
171
178
|
this.rendered[COMPONENTS] = this.rendered[COMPONENTS] || {}
|
|
172
179
|
|
package/assets/package.json
CHANGED
package/package.json
CHANGED
|
@@ -780,18 +780,7 @@ var DOM = {
|
|
|
780
780
|
return;
|
|
781
781
|
default:
|
|
782
782
|
let timeout = parseInt(value);
|
|
783
|
-
let trigger = (
|
|
784
|
-
if (blur) {
|
|
785
|
-
if (throttle && this.private(el, THROTTLED)) {
|
|
786
|
-
clearTimeout(this.private(el, THROTTLED));
|
|
787
|
-
this.deletePrivate(el, THROTTLED);
|
|
788
|
-
}
|
|
789
|
-
return callback();
|
|
790
|
-
}
|
|
791
|
-
if (throttle)
|
|
792
|
-
this.deletePrivate(el, THROTTLED);
|
|
793
|
-
callback();
|
|
794
|
-
};
|
|
783
|
+
let trigger = () => throttle ? this.deletePrivate(el, THROTTLED) : callback();
|
|
795
784
|
let currentCycle = this.incCycle(el, DEBOUNCE_TRIGGER, trigger);
|
|
796
785
|
if (isNaN(timeout)) {
|
|
797
786
|
return logError(`invalid throttle/debounce value: ${value}`);
|
|
@@ -832,18 +821,21 @@ var DOM = {
|
|
|
832
821
|
});
|
|
833
822
|
}
|
|
834
823
|
if (this.once(el, "bind-debounce")) {
|
|
835
|
-
el.addEventListener("blur", () =>
|
|
824
|
+
el.addEventListener("blur", () => {
|
|
825
|
+
clearTimeout(this.private(el, THROTTLED));
|
|
826
|
+
this.triggerCycle(el, DEBOUNCE_TRIGGER);
|
|
827
|
+
});
|
|
836
828
|
}
|
|
837
829
|
}
|
|
838
830
|
},
|
|
839
|
-
triggerCycle(el, key, currentCycle
|
|
831
|
+
triggerCycle(el, key, currentCycle) {
|
|
840
832
|
let [cycle, trigger] = this.private(el, key);
|
|
841
833
|
if (!currentCycle) {
|
|
842
834
|
currentCycle = cycle;
|
|
843
835
|
}
|
|
844
836
|
if (currentCycle === cycle) {
|
|
845
837
|
this.incCycle(el, key);
|
|
846
|
-
trigger(
|
|
838
|
+
trigger();
|
|
847
839
|
}
|
|
848
840
|
},
|
|
849
841
|
once(el, key) {
|
|
@@ -2154,38 +2146,9 @@ var DOMPatch = class {
|
|
|
2154
2146
|
let updates = [];
|
|
2155
2147
|
let appendPrependUpdates = [];
|
|
2156
2148
|
let externalFormTriggered = null;
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
this.streams.forEach(([ref, inserts, deleteIds, reset]) => {
|
|
2161
|
-
inserts.forEach(([key, streamAt, limit]) => {
|
|
2162
|
-
this.streamInserts[key] = { ref, streamAt, limit, reset };
|
|
2163
|
-
});
|
|
2164
|
-
if (reset !== void 0) {
|
|
2165
|
-
dom_default.all(container, `[${PHX_STREAM_REF}="${ref}"]`, (child) => {
|
|
2166
|
-
this.removeStreamChildElement(child);
|
|
2167
|
-
});
|
|
2168
|
-
}
|
|
2169
|
-
deleteIds.forEach((id) => {
|
|
2170
|
-
let child = container.querySelector(`[id="${id}"]`);
|
|
2171
|
-
if (child) {
|
|
2172
|
-
this.removeStreamChildElement(child);
|
|
2173
|
-
}
|
|
2174
|
-
});
|
|
2175
|
-
});
|
|
2176
|
-
if (isJoinPatch) {
|
|
2177
|
-
dom_default.all(this.container, `[${phxUpdate}=${PHX_STREAM}]`, (el) => {
|
|
2178
|
-
this.liveSocket.owner(el, (view2) => {
|
|
2179
|
-
if (view2 === this.view) {
|
|
2180
|
-
Array.from(el.children).forEach((child) => {
|
|
2181
|
-
this.removeStreamChildElement(child);
|
|
2182
|
-
});
|
|
2183
|
-
}
|
|
2184
|
-
});
|
|
2185
|
-
});
|
|
2186
|
-
}
|
|
2187
|
-
morphdom_esm_default(targetContainer, html, {
|
|
2188
|
-
childrenOnly: targetContainer.getAttribute(PHX_COMPONENT) === null,
|
|
2149
|
+
function morph(targetContainer2, source) {
|
|
2150
|
+
morphdom_esm_default(targetContainer2, source, {
|
|
2151
|
+
childrenOnly: targetContainer2.getAttribute(PHX_COMPONENT) === null,
|
|
2189
2152
|
getNodeKey: (node) => {
|
|
2190
2153
|
if (dom_default.isPhxDestroyed(node)) {
|
|
2191
2154
|
return null;
|
|
@@ -2204,12 +2167,6 @@ var DOMPatch = class {
|
|
|
2204
2167
|
return parent.appendChild(child);
|
|
2205
2168
|
}
|
|
2206
2169
|
this.setStreamRef(child, ref);
|
|
2207
|
-
child.querySelectorAll(`[${PHX_MAGIC_ID}][${PHX_SKIP}]`).forEach((el) => {
|
|
2208
|
-
const component = this.streamComponentRestore[el.getAttribute(PHX_MAGIC_ID)];
|
|
2209
|
-
if (component) {
|
|
2210
|
-
el.replaceWith(component);
|
|
2211
|
-
}
|
|
2212
|
-
});
|
|
2213
2170
|
if (streamAt === 0) {
|
|
2214
2171
|
parent.insertAdjacentElement("afterbegin", child);
|
|
2215
2172
|
} else if (streamAt === -1) {
|
|
@@ -2221,15 +2178,21 @@ var DOMPatch = class {
|
|
|
2221
2178
|
},
|
|
2222
2179
|
onBeforeNodeAdded: (el) => {
|
|
2223
2180
|
dom_default.maybeAddPrivateHooks(el, phxViewportTop, phxViewportBottom);
|
|
2224
|
-
if (dom_default.isFeedbackContainer(el, phxFeedbackFor))
|
|
2225
|
-
feedbackContainers.push(el);
|
|
2226
2181
|
this.trackBefore("added", el);
|
|
2227
|
-
|
|
2182
|
+
let morphedEl = el;
|
|
2183
|
+
if (!isJoinPatch && this.streamComponentRestore[el.id]) {
|
|
2184
|
+
morphedEl = this.streamComponentRestore[el.id];
|
|
2185
|
+
delete this.streamComponentRestore[el.id];
|
|
2186
|
+
morph.bind(this)(morphedEl, el);
|
|
2187
|
+
}
|
|
2188
|
+
return morphedEl;
|
|
2228
2189
|
},
|
|
2229
2190
|
onNodeAdded: (el) => {
|
|
2230
2191
|
if (el.getAttribute) {
|
|
2231
2192
|
this.maybeReOrderStream(el, true);
|
|
2232
2193
|
}
|
|
2194
|
+
if (dom_default.isFeedbackContainer(el, phxFeedbackFor))
|
|
2195
|
+
feedbackContainers.push(el);
|
|
2233
2196
|
if (el instanceof HTMLImageElement && el.srcset) {
|
|
2234
2197
|
el.srcset = el.srcset;
|
|
2235
2198
|
} else if (el instanceof HTMLVideoElement && el.autoplay) {
|
|
@@ -2332,6 +2295,38 @@ var DOMPatch = class {
|
|
|
2332
2295
|
}
|
|
2333
2296
|
}
|
|
2334
2297
|
});
|
|
2298
|
+
}
|
|
2299
|
+
this.trackBefore("added", container);
|
|
2300
|
+
this.trackBefore("updated", container, container);
|
|
2301
|
+
liveSocket.time("morphdom", () => {
|
|
2302
|
+
this.streams.forEach(([ref, inserts, deleteIds, reset]) => {
|
|
2303
|
+
inserts.forEach(([key, streamAt, limit]) => {
|
|
2304
|
+
this.streamInserts[key] = { ref, streamAt, limit, reset };
|
|
2305
|
+
});
|
|
2306
|
+
if (reset !== void 0) {
|
|
2307
|
+
dom_default.all(container, `[${PHX_STREAM_REF}="${ref}"]`, (child) => {
|
|
2308
|
+
this.removeStreamChildElement(child);
|
|
2309
|
+
});
|
|
2310
|
+
}
|
|
2311
|
+
deleteIds.forEach((id) => {
|
|
2312
|
+
let child = container.querySelector(`[id="${id}"]`);
|
|
2313
|
+
if (child) {
|
|
2314
|
+
this.removeStreamChildElement(child);
|
|
2315
|
+
}
|
|
2316
|
+
});
|
|
2317
|
+
});
|
|
2318
|
+
if (isJoinPatch) {
|
|
2319
|
+
dom_default.all(this.container, `[${phxUpdate}=${PHX_STREAM}]`, (el) => {
|
|
2320
|
+
this.liveSocket.owner(el, (view2) => {
|
|
2321
|
+
if (view2 === this.view) {
|
|
2322
|
+
Array.from(el.children).forEach((child) => {
|
|
2323
|
+
this.removeStreamChildElement(child);
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
2326
|
+
});
|
|
2327
|
+
});
|
|
2328
|
+
}
|
|
2329
|
+
morph.bind(this)(targetContainer, html);
|
|
2335
2330
|
});
|
|
2336
2331
|
if (liveSocket.isDebugEnabled()) {
|
|
2337
2332
|
detectDuplicateIds();
|
|
@@ -2370,12 +2365,12 @@ var DOMPatch = class {
|
|
|
2370
2365
|
removeStreamChildElement(child) {
|
|
2371
2366
|
if (!this.maybePendingRemove(child)) {
|
|
2372
2367
|
if (this.streamInserts[child.id]) {
|
|
2373
|
-
child.
|
|
2374
|
-
|
|
2375
|
-
|
|
2368
|
+
this.streamComponentRestore[child.id] = child;
|
|
2369
|
+
child.remove();
|
|
2370
|
+
} else {
|
|
2371
|
+
child.remove();
|
|
2372
|
+
this.onNodeDiscarded(child);
|
|
2376
2373
|
}
|
|
2377
|
-
child.remove();
|
|
2378
|
-
this.onNodeDiscarded(child);
|
|
2379
2374
|
}
|
|
2380
2375
|
}
|
|
2381
2376
|
getStreamInsert(el) {
|
|
@@ -2611,6 +2606,14 @@ var Rendered = class {
|
|
|
2611
2606
|
let newc = diff[COMPONENTS];
|
|
2612
2607
|
let cache = {};
|
|
2613
2608
|
delete diff[COMPONENTS];
|
|
2609
|
+
if (newc) {
|
|
2610
|
+
let prevComponents = this.rendered[COMPONENTS] || {};
|
|
2611
|
+
for (let cid in newc) {
|
|
2612
|
+
if (prevComponents[cid] === void 0) {
|
|
2613
|
+
newc[cid].reset = true;
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2614
2617
|
this.rendered = this.mutableMerge(this.rendered, diff);
|
|
2615
2618
|
this.rendered[COMPONENTS] = this.rendered[COMPONENTS] || {};
|
|
2616
2619
|
if (newc) {
|