phoenix_live_view 0.20.2 → 0.20.3

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.
@@ -363,7 +363,7 @@ let DOM = {
363
363
  for(let i = targetAttrs.length - 1; i >= 0; i--){
364
364
  let name = targetAttrs[i].name
365
365
  if(isIgnored){
366
- if(name.startsWith("data-") && !source.hasAttribute(name)){ target.removeAttribute(name) }
366
+ if(name.startsWith("data-") && !source.hasAttribute(name) && ![PHX_REF, PHX_REF_SRC].includes(name)){ target.removeAttribute(name) }
367
367
  } else {
368
368
  if(!source.hasAttribute(name)){ target.removeAttribute(name) }
369
369
  }
@@ -371,8 +371,8 @@ let DOM = {
371
371
  },
372
372
 
373
373
  mergeFocusedInput(target, source){
374
- // skip selects because FF will reset highlighted index for any setAttribute
375
- if(!(target instanceof HTMLSelectElement)){ DOM.mergeAttrs(target, source, {exclude: ["value"]}) }
374
+ DOM.mergeAttrs(target, source, {exclude: ["value"]})
375
+
376
376
  if(source.readOnly){
377
377
  target.setAttribute("readonly", true)
378
378
  } else {
@@ -167,7 +167,7 @@ export default class DOMPatch {
167
167
  return el
168
168
  },
169
169
  onNodeAdded: (el) => {
170
- if(el.getAttribute){ this.maybeReOrderStream(el) }
170
+ if(el.getAttribute){ this.maybeReOrderStream(el, true) }
171
171
 
172
172
  // hack to fix Safari handling of img srcset and video tags
173
173
  if(el instanceof HTMLImageElement && el.srcset){
@@ -196,17 +196,9 @@ export default class DOMPatch {
196
196
  Array.from(fromEl.children).filter(child => {
197
197
  let {resetKept} = this.getStreamInsert(child)
198
198
  return resetKept
199
- }).sort((a, b) => {
200
- let aIdx = toIds.indexOf(a.id)
201
- let bIdx = toIds.indexOf(b.id)
202
- if(aIdx === bIdx){
203
- return 0
204
- } else if(aIdx < bIdx){
205
- return -1
206
- } else {
207
- return 1
208
- }
209
- }).forEach(child => fromEl.appendChild(child))
199
+ }).forEach((child) => {
200
+ this.streamInserts[child.id].streamAt = toIds.indexOf(child.id)
201
+ })
210
202
  }
211
203
  },
212
204
  onNodeDiscarded: (el) => this.onNodeDiscarded(el),
@@ -226,7 +218,7 @@ export default class DOMPatch {
226
218
  externalFormTriggered = el
227
219
  }
228
220
  updates.push(el)
229
- this.maybeReOrderStream(el)
221
+ this.maybeReOrderStream(el, false)
230
222
  },
231
223
  onBeforeElUpdated: (fromEl, toEl) => {
232
224
  DOM.maybeAddPrivateHooks(toEl, phxViewportTop, phxViewportBottom)
@@ -342,9 +334,9 @@ export default class DOMPatch {
342
334
  return insert || {}
343
335
  }
344
336
 
345
- maybeReOrderStream(el){
337
+ maybeReOrderStream(el, isNew){
346
338
  let {ref, streamAt, limit} = this.getStreamInsert(el)
347
- if(streamAt === undefined){ return }
339
+ if(streamAt === undefined || (streamAt === 0 && !isNew)){ return }
348
340
 
349
341
  // we need to the PHX_STREAM_REF here as well as addChild is invoked only for parents
350
342
  DOM.putSticky(el, PHX_STREAM_REF, el => el.setAttribute(PHX_STREAM_REF, ref))
@@ -9,8 +9,6 @@ let JS = {
9
9
  let commands = phxEvent.charAt(0) === "[" ?
10
10
  JSON.parse(phxEvent) : [[defaultKind, defaultArgs]]
11
11
 
12
-
13
-
14
12
  commands.forEach(([kind, args]) => {
15
13
  if(kind === defaultKind && defaultArgs.data){
16
14
  args.data = Object.assign(args.data || {}, defaultArgs.data)
@@ -56,13 +54,12 @@ let JS = {
56
54
  },
57
55
 
58
56
  exec_push(eventType, phxEvent, view, sourceEl, el, args){
59
- if(!view.isConnected()){ return }
60
-
61
57
  let {event, data, target, page_loading, loading, value, dispatcher, callback} = args
62
58
  let pushOpts = {loading, value, target, page_loading: !!page_loading}
63
59
  let targetSrc = eventType === "change" && dispatcher ? dispatcher : sourceEl
64
60
  let phxTarget = target || targetSrc.getAttribute(view.binding("target")) || targetSrc
65
61
  view.withinTargets(phxTarget, (targetView, targetCtx) => {
62
+ if(!targetView.isConnected()){ return }
66
63
  if(eventType === "change"){
67
64
  let {newCid, _target} = args
68
65
  _target = _target || (DOM.isFormInput(sourceEl) ? sourceEl.name : undefined)
@@ -112,6 +109,10 @@ let JS = {
112
109
  this.addOrRemoveClasses(el, [], names, transition, time, view)
113
110
  },
114
111
 
112
+ exec_toggle_class(eventType, phxEvent, view, sourceEl, el, {to, names, transition, time}){
113
+ this.toggleClasses(el, names, transition, view)
114
+ },
115
+
115
116
  exec_transition(eventType, phxEvent, view, sourceEl, el, {time, transition}){
116
117
  this.addOrRemoveClasses(el, [], [], transition, time, view)
117
118
  },
@@ -203,6 +204,15 @@ let JS = {
203
204
  }
204
205
  },
205
206
 
207
+ toggleClasses(el, classes, transition, time, view){
208
+ window.requestAnimationFrame(() => {
209
+ let [prevAdds, prevRemoves] = DOM.getSticky(el, "classes", [[], []])
210
+ let newAdds = classes.filter(name => prevAdds.indexOf(name) < 0 && !el.classList.contains(name))
211
+ let newRemoves = classes.filter(name => prevRemoves.indexOf(name) < 0 && el.classList.contains(name))
212
+ this.addOrRemoveClasses(el, newAdds, newRemoves, transition, time, view)
213
+ })
214
+ },
215
+
206
216
  addOrRemoveClasses(el, adds, removes, transition, time, view){
207
217
  let [transitionRun, transitionStart, transitionEnd] = transition || [[], [], []]
208
218
  if(transitionRun.length > 0){
@@ -37,60 +37,41 @@ const VOID_TAGS = new Set([
37
37
  "track",
38
38
  "wbr"
39
39
  ])
40
- const endingTagNameChars = new Set([">", "/", " ", "\n", "\t", "\r"])
41
40
  const quoteChars = new Set(["'", '"'])
42
41
 
43
42
  export let modifyRoot = (html, attrs, clearInnerHTML) => {
44
43
  let i = 0
45
44
  let insideComment = false
46
45
  let beforeTag, afterTag, tag, tagNameEndsAt, id, newHTML
47
- while(i < html.length){
48
- let char = html.charAt(i)
49
- if(insideComment){
50
- if(char === "-" && html.slice(i, i + 3) === "-->"){
51
- insideComment = false
52
- i += 3
53
- } else {
46
+
47
+ let lookahead = html.match(/^(\s*(?:<!--.*?-->\s*)*)<([^\s\/>]+)/)
48
+ if(lookahead === null) { throw new Error(`malformed html ${html}`) }
49
+
50
+ i = lookahead[0].length
51
+ beforeTag = lookahead[1]
52
+ tag = lookahead[2]
53
+ tagNameEndsAt = i
54
+
55
+ // Scan the opening tag for id, if there is any
56
+ for(i; i < html.length; i++){
57
+ if(html.charAt(i) === ">" ){ break }
58
+ if(html.charAt(i) === "="){
59
+ let isId = html.slice(i - 3, i) === " id"
60
+ i++;
61
+ let char = html.charAt(i)
62
+ if (quoteChars.has(char)) {
63
+ let attrStartsAt = i
54
64
  i++
55
- }
56
- } else if(char === "<" && html.slice(i, i + 4) === "<!--"){
57
- insideComment = true
58
- i += 4
59
- } else if(char === "<"){
60
- beforeTag = html.slice(0, i)
61
- let iAtOpen = i
62
- i++
63
- for(i; i < html.length; i++){
64
- if(endingTagNameChars.has(html.charAt(i))){ break }
65
- }
66
- tagNameEndsAt = i
67
- tag = html.slice(iAtOpen + 1, tagNameEndsAt)
68
- // Scan the opening tag for id, if there is any
69
- for(i; i < html.length; i++){
70
- if(html.charAt(i) === ">" ){ break }
71
- if(html.charAt(i) === "="){
72
- let isId = html.slice(i - 3, i) === " id"
73
- i++;
74
- let char = html.charAt(i)
75
- if (quoteChars.has(char)) {
76
- let attrStartsAt = i
77
- i++
78
- for(i; i < html.length; i++){
79
- if(html.charAt(i) === char){ break }
80
- }
81
- if (isId) {
82
- id = html.slice(attrStartsAt + 1, i)
83
- break
84
- }
85
- }
65
+ for(i; i < html.length; i++){
66
+ if(html.charAt(i) === char){ break }
67
+ }
68
+ if (isId) {
69
+ id = html.slice(attrStartsAt + 1, i)
70
+ break
86
71
  }
87
72
  }
88
- break
89
- } else {
90
- i++
91
73
  }
92
74
  }
93
- if(!tag){ throw new Error(`malformed html ${html}`) }
94
75
 
95
76
  let closeAt = html.length - 1
96
77
  insideComment = false
@@ -389,6 +389,9 @@ export default class View {
389
389
 
390
390
  patch.after("added", el => {
391
391
  this.liveSocket.triggerDOM("onNodeAdded", [el])
392
+ let phxViewportTop = this.binding(PHX_VIEWPORT_TOP)
393
+ let phxViewportBottom = this.binding(PHX_VIEWPORT_BOTTOM)
394
+ DOM.maybeAddPrivateHooks(el, phxViewportTop, phxViewportBottom)
392
395
  this.maybeAddNewHook(el)
393
396
  if(el.getAttribute){ this.maybeMounted(el) }
394
397
  })
@@ -920,6 +923,7 @@ export default class View {
920
923
  this.uploadFiles(inputEl.form, targetCtx, ref, cid, (_uploads) => {
921
924
  callback && callback(resp)
922
925
  this.triggerAwaitingSubmit(inputEl.form)
926
+ this.undoRefs(ref)
923
927
  })
924
928
  }
925
929
  } else {
@@ -1064,7 +1068,7 @@ export default class View {
1064
1068
  }
1065
1069
 
1066
1070
  dispatchUploads(targetCtx, name, filesOrBlobs){
1067
- let targetElement = this.targetCtxElement(targetCtx) || this.el;
1071
+ let targetElement = this.targetCtxElement(targetCtx) || this.el
1068
1072
  let inputs = DOM.findUploadInputs(targetElement).filter(el => el.name === name)
1069
1073
  if(inputs.length === 0){ logError(`no live file inputs found matching the name "${name}"`) }
1070
1074
  else if(inputs.length > 1){ logError(`duplicate live file inputs found matching the name "${name}"`) }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phoenix_live_view",
3
- "version": "0.20.2",
3
+ "version": "0.20.3",
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.2",
3
+ "version": "0.20.3",
4
4
  "description": "The Phoenix LiveView JavaScript client.",
5
5
  "license": "MIT",
6
6
  "module": "./priv/static/phoenix_live_view.esm.js",
@@ -610,7 +610,7 @@ var DOM = {
610
610
  for (let i = targetAttrs.length - 1; i >= 0; i--) {
611
611
  let name = targetAttrs[i].name;
612
612
  if (isIgnored) {
613
- if (name.startsWith("data-") && !source.hasAttribute(name)) {
613
+ if (name.startsWith("data-") && !source.hasAttribute(name) && ![PHX_REF, PHX_REF_SRC].includes(name)) {
614
614
  target.removeAttribute(name);
615
615
  }
616
616
  } else {
@@ -621,9 +621,7 @@ var DOM = {
621
621
  }
622
622
  },
623
623
  mergeFocusedInput(target, source) {
624
- if (!(target instanceof HTMLSelectElement)) {
625
- DOM.mergeAttrs(target, source, { exclude: ["value"] });
626
- }
624
+ DOM.mergeAttrs(target, source, { exclude: ["value"] });
627
625
  if (source.readOnly) {
628
626
  target.setAttribute("readonly", true);
629
627
  } else {
@@ -1854,7 +1852,7 @@ var DOMPatch = class {
1854
1852
  },
1855
1853
  onNodeAdded: (el) => {
1856
1854
  if (el.getAttribute) {
1857
- this.maybeReOrderStream(el);
1855
+ this.maybeReOrderStream(el, true);
1858
1856
  }
1859
1857
  if (el instanceof HTMLImageElement && el.srcset) {
1860
1858
  el.srcset = el.srcset;
@@ -1878,17 +1876,9 @@ var DOMPatch = class {
1878
1876
  Array.from(fromEl.children).filter((child) => {
1879
1877
  let { resetKept } = this.getStreamInsert(child);
1880
1878
  return resetKept;
1881
- }).sort((a, b) => {
1882
- let aIdx = toIds.indexOf(a.id);
1883
- let bIdx = toIds.indexOf(b.id);
1884
- if (aIdx === bIdx) {
1885
- return 0;
1886
- } else if (aIdx < bIdx) {
1887
- return -1;
1888
- } else {
1889
- return 1;
1890
- }
1891
- }).forEach((child) => fromEl.appendChild(child));
1879
+ }).forEach((child) => {
1880
+ this.streamInserts[child.id].streamAt = toIds.indexOf(child.id);
1881
+ });
1892
1882
  }
1893
1883
  },
1894
1884
  onNodeDiscarded: (el) => this.onNodeDiscarded(el),
@@ -1912,7 +1902,7 @@ var DOMPatch = class {
1912
1902
  externalFormTriggered = el;
1913
1903
  }
1914
1904
  updates.push(el);
1915
- this.maybeReOrderStream(el);
1905
+ this.maybeReOrderStream(el, false);
1916
1906
  },
1917
1907
  onBeforeElUpdated: (fromEl, toEl) => {
1918
1908
  dom_default.maybeAddPrivateHooks(toEl, phxViewportTop, phxViewportBottom);
@@ -2020,9 +2010,9 @@ var DOMPatch = class {
2020
2010
  let insert = el.id ? this.streamInserts[el.id] : {};
2021
2011
  return insert || {};
2022
2012
  }
2023
- maybeReOrderStream(el) {
2013
+ maybeReOrderStream(el, isNew) {
2024
2014
  let { ref, streamAt, limit } = this.getStreamInsert(el);
2025
- if (streamAt === void 0) {
2015
+ if (streamAt === void 0 || streamAt === 0 && !isNew) {
2026
2016
  return;
2027
2017
  }
2028
2018
  dom_default.putSticky(el, PHX_STREAM_REF, (el2) => el2.setAttribute(PHX_STREAM_REF, ref));
@@ -2100,66 +2090,42 @@ var VOID_TAGS = new Set([
2100
2090
  "track",
2101
2091
  "wbr"
2102
2092
  ]);
2103
- var endingTagNameChars = new Set([">", "/", " ", "\n", " ", "\r"]);
2104
2093
  var quoteChars = new Set(["'", '"']);
2105
2094
  var modifyRoot = (html, attrs, clearInnerHTML) => {
2106
2095
  let i = 0;
2107
2096
  let insideComment = false;
2108
2097
  let beforeTag, afterTag, tag, tagNameEndsAt, id, newHTML;
2109
- while (i < html.length) {
2110
- let char = html.charAt(i);
2111
- if (insideComment) {
2112
- if (char === "-" && html.slice(i, i + 3) === "-->") {
2113
- insideComment = false;
2114
- i += 3;
2115
- } else {
2116
- i++;
2117
- }
2118
- } else if (char === "<" && html.slice(i, i + 4) === "<!--") {
2119
- insideComment = true;
2120
- i += 4;
2121
- } else if (char === "<") {
2122
- beforeTag = html.slice(0, i);
2123
- let iAtOpen = i;
2098
+ let lookahead = html.match(/^(\s*(?:<!--.*?-->\s*)*)<([^\s\/>]+)/);
2099
+ if (lookahead === null) {
2100
+ throw new Error(`malformed html ${html}`);
2101
+ }
2102
+ i = lookahead[0].length;
2103
+ beforeTag = lookahead[1];
2104
+ tag = lookahead[2];
2105
+ tagNameEndsAt = i;
2106
+ for (i; i < html.length; i++) {
2107
+ if (html.charAt(i) === ">") {
2108
+ break;
2109
+ }
2110
+ if (html.charAt(i) === "=") {
2111
+ let isId = html.slice(i - 3, i) === " id";
2124
2112
  i++;
2125
- for (i; i < html.length; i++) {
2126
- if (endingTagNameChars.has(html.charAt(i))) {
2127
- break;
2113
+ let char = html.charAt(i);
2114
+ if (quoteChars.has(char)) {
2115
+ let attrStartsAt = i;
2116
+ i++;
2117
+ for (i; i < html.length; i++) {
2118
+ if (html.charAt(i) === char) {
2119
+ break;
2120
+ }
2128
2121
  }
2129
- }
2130
- tagNameEndsAt = i;
2131
- tag = html.slice(iAtOpen + 1, tagNameEndsAt);
2132
- for (i; i < html.length; i++) {
2133
- if (html.charAt(i) === ">") {
2122
+ if (isId) {
2123
+ id = html.slice(attrStartsAt + 1, i);
2134
2124
  break;
2135
2125
  }
2136
- if (html.charAt(i) === "=") {
2137
- let isId = html.slice(i - 3, i) === " id";
2138
- i++;
2139
- let char2 = html.charAt(i);
2140
- if (quoteChars.has(char2)) {
2141
- let attrStartsAt = i;
2142
- i++;
2143
- for (i; i < html.length; i++) {
2144
- if (html.charAt(i) === char2) {
2145
- break;
2146
- }
2147
- }
2148
- if (isId) {
2149
- id = html.slice(attrStartsAt + 1, i);
2150
- break;
2151
- }
2152
- }
2153
- }
2154
2126
  }
2155
- break;
2156
- } else {
2157
- i++;
2158
2127
  }
2159
2128
  }
2160
- if (!tag) {
2161
- throw new Error(`malformed html ${html}`);
2162
- }
2163
2129
  let closeAt = html.length - 1;
2164
2130
  insideComment = false;
2165
2131
  while (closeAt >= beforeTag.length + tag.length) {
@@ -2534,14 +2500,14 @@ var JS = {
2534
2500
  dom_default.dispatchEvent(el, event, { detail, bubbles });
2535
2501
  },
2536
2502
  exec_push(eventType, phxEvent, view, sourceEl, el, args) {
2537
- if (!view.isConnected()) {
2538
- return;
2539
- }
2540
2503
  let { event, data, target, page_loading, loading, value, dispatcher, callback } = args;
2541
2504
  let pushOpts = { loading, value, target, page_loading: !!page_loading };
2542
2505
  let targetSrc = eventType === "change" && dispatcher ? dispatcher : sourceEl;
2543
2506
  let phxTarget = target || targetSrc.getAttribute(view.binding("target")) || targetSrc;
2544
2507
  view.withinTargets(phxTarget, (targetView, targetCtx) => {
2508
+ if (!targetView.isConnected()) {
2509
+ return;
2510
+ }
2545
2511
  if (eventType === "change") {
2546
2512
  let { newCid, _target } = args;
2547
2513
  _target = _target || (dom_default.isFormInput(sourceEl) ? sourceEl.name : void 0);
@@ -2586,6 +2552,9 @@ var JS = {
2586
2552
  exec_remove_class(eventType, phxEvent, view, sourceEl, el, { names, transition, time }) {
2587
2553
  this.addOrRemoveClasses(el, [], names, transition, time, view);
2588
2554
  },
2555
+ exec_toggle_class(eventType, phxEvent, view, sourceEl, el, { to, names, transition, time }) {
2556
+ this.toggleClasses(el, names, transition, view);
2557
+ },
2589
2558
  exec_transition(eventType, phxEvent, view, sourceEl, el, { time, transition }) {
2590
2559
  this.addOrRemoveClasses(el, [], [], transition, time, view);
2591
2560
  },
@@ -2668,6 +2637,14 @@ var JS = {
2668
2637
  }
2669
2638
  }
2670
2639
  },
2640
+ toggleClasses(el, classes, transition, time, view) {
2641
+ window.requestAnimationFrame(() => {
2642
+ let [prevAdds, prevRemoves] = dom_default.getSticky(el, "classes", [[], []]);
2643
+ let newAdds = classes.filter((name) => prevAdds.indexOf(name) < 0 && !el.classList.contains(name));
2644
+ let newRemoves = classes.filter((name) => prevRemoves.indexOf(name) < 0 && el.classList.contains(name));
2645
+ this.addOrRemoveClasses(el, newAdds, newRemoves, transition, time, view);
2646
+ });
2647
+ },
2671
2648
  addOrRemoveClasses(el, adds, removes, transition, time, view) {
2672
2649
  let [transitionRun, transitionStart, transitionEnd] = transition || [[], [], []];
2673
2650
  if (transitionRun.length > 0) {
@@ -3022,6 +2999,9 @@ var View = class {
3022
2999
  let updatedHookIds = new Set();
3023
3000
  patch.after("added", (el) => {
3024
3001
  this.liveSocket.triggerDOM("onNodeAdded", [el]);
3002
+ let phxViewportTop = this.binding(PHX_VIEWPORT_TOP);
3003
+ let phxViewportBottom = this.binding(PHX_VIEWPORT_BOTTOM);
3004
+ dom_default.maybeAddPrivateHooks(el, phxViewportTop, phxViewportBottom);
3025
3005
  this.maybeAddNewHook(el);
3026
3006
  if (el.getAttribute) {
3027
3007
  this.maybeMounted(el);
@@ -3568,6 +3548,7 @@ var View = class {
3568
3548
  this.uploadFiles(inputEl.form, targetCtx, ref, cid, (_uploads) => {
3569
3549
  callback && callback(resp);
3570
3550
  this.triggerAwaitingSubmit(inputEl.form);
3551
+ this.undoRefs(ref);
3571
3552
  });
3572
3553
  }
3573
3554
  } else {