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.
@@ -221,23 +221,7 @@ let DOM = {
221
221
 
222
222
  default:
223
223
  let timeout = parseInt(value)
224
- let trigger = (blur) => {
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", () => this.triggerCycle(el, DEBOUNCE_TRIGGER, null, [true]))
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, params=[]){
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(...params)
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
- this.trackBefore("added", container)
103
- this.trackBefore("updated", container, container)
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
- return el
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 restore them later
332
- // in case they are skipped
333
- child.querySelectorAll(`[${PHX_MAGIC_ID}]`).forEach(el => {
334
- this.streamComponentRestore[el.getAttribute(PHX_MAGIC_ID)] = el
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
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phoenix_live_view",
3
- "version": "0.20.5",
3
+ "version": "0.20.7",
4
4
  "description": "The Phoenix LiveView JavaScript client.",
5
5
  "license": "MIT",
6
6
  "repository": {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phoenix_live_view",
3
- "version": "0.20.5",
3
+ "version": "0.20.7",
4
4
  "description": "The Phoenix LiveView JavaScript client.",
5
5
  "license": "MIT",
6
6
  "module": "./priv/static/phoenix_live_view.esm.js",
@@ -780,18 +780,7 @@ var DOM = {
780
780
  return;
781
781
  default:
782
782
  let timeout = parseInt(value);
783
- let trigger = (blur) => {
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", () => this.triggerCycle(el, DEBOUNCE_TRIGGER, null, [true]));
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, params = []) {
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(...params);
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
- this.trackBefore("added", container);
2158
- this.trackBefore("updated", container, container);
2159
- liveSocket.time("morphdom", () => {
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
- return el;
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.querySelectorAll(`[${PHX_MAGIC_ID}]`).forEach((el) => {
2374
- this.streamComponentRestore[el.getAttribute(PHX_MAGIC_ID)] = el;
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) {