phoenix_live_view 0.19.4 → 0.20.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/assets/js/phoenix_live_view/dom.js +13 -1
- package/assets/js/phoenix_live_view/dom_patch.js +6 -2
- package/assets/js/phoenix_live_view/js.js +12 -5
- package/assets/js/phoenix_live_view/live_socket.js +13 -6
- package/assets/js/phoenix_live_view/live_uploader.js +1 -0
- package/assets/js/phoenix_live_view/rendered.js +9 -3
- package/assets/js/phoenix_live_view/upload_entry.js +2 -1
- package/assets/js/phoenix_live_view/view.js +15 -6
- package/assets/package.json +1 -1
- package/package.json +1 -1
- package/priv/static/phoenix_live_view.cjs.js +65 -21
- package/priv/static/phoenix_live_view.cjs.js.map +2 -2
- package/priv/static/phoenix_live_view.esm.js +65 -21
- package/priv/static/phoenix_live_view.esm.js.map +2 -2
- package/priv/static/phoenix_live_view.js +65 -21
- package/priv/static/phoenix_live_view.min.js +6 -6
|
@@ -48,6 +48,8 @@ let DOM = {
|
|
|
48
48
|
|
|
49
49
|
isUploadInput(el){ return el.type === "file" && el.getAttribute(PHX_UPLOAD_REF) !== null },
|
|
50
50
|
|
|
51
|
+
isAutoUpload(inputEl){ return inputEl.hasAttribute("data-phx-auto-upload") },
|
|
52
|
+
|
|
51
53
|
findUploadInputs(node){ return this.all(node, `input[type="file"][${PHX_UPLOAD_REF}]`) },
|
|
52
54
|
|
|
53
55
|
findComponentNodeList(node, cid){
|
|
@@ -66,7 +68,16 @@ let DOM = {
|
|
|
66
68
|
},
|
|
67
69
|
|
|
68
70
|
isUnloadableFormSubmit(e){
|
|
69
|
-
|
|
71
|
+
// Ignore form submissions intended to close a native <dialog> element
|
|
72
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog#usage_notes
|
|
73
|
+
let isDialogSubmit = (e.target && e.target.getAttribute("method") === "dialog") ||
|
|
74
|
+
(e.submitter && e.submitter.getAttribute("formmethod") === "dialog")
|
|
75
|
+
|
|
76
|
+
if(isDialogSubmit){
|
|
77
|
+
return false
|
|
78
|
+
} else {
|
|
79
|
+
return !e.defaultPrevented && !this.wantsNewTab(e)
|
|
80
|
+
}
|
|
70
81
|
},
|
|
71
82
|
|
|
72
83
|
isNewPageClick(e, currentLocation){
|
|
@@ -75,6 +86,7 @@ let DOM = {
|
|
|
75
86
|
|
|
76
87
|
if(e.defaultPrevented || href === null || this.wantsNewTab(e)){ return false }
|
|
77
88
|
if(href.startsWith("mailto:") || href.startsWith("tel:")){ return false }
|
|
89
|
+
if(e.target.isContentEditable){ return false }
|
|
78
90
|
|
|
79
91
|
try {
|
|
80
92
|
url = new URL(href)
|
|
@@ -110,7 +110,9 @@ export default class DOMPatch {
|
|
|
110
110
|
})
|
|
111
111
|
if(reset !== undefined){
|
|
112
112
|
DOM.all(container, `[${PHX_STREAM_REF}="${ref}"]`, child => {
|
|
113
|
-
|
|
113
|
+
if(!inserts[child.id]){
|
|
114
|
+
this.removeStreamChildElement(child)
|
|
115
|
+
}
|
|
114
116
|
})
|
|
115
117
|
}
|
|
116
118
|
deleteIds.forEach(id => {
|
|
@@ -284,7 +286,9 @@ export default class DOMPatch {
|
|
|
284
286
|
|
|
285
287
|
if(externalFormTriggered){
|
|
286
288
|
liveSocket.unload()
|
|
287
|
-
|
|
289
|
+
// use prototype's submit in case there's a form control with name or id of "submit"
|
|
290
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit
|
|
291
|
+
Object.getPrototypeOf(externalFormTriggered).submit.call(externalFormTriggered)
|
|
288
292
|
}
|
|
289
293
|
return true
|
|
290
294
|
}
|
|
@@ -194,12 +194,19 @@ let JS = {
|
|
|
194
194
|
},
|
|
195
195
|
|
|
196
196
|
addOrRemoveClasses(el, adds, removes, transition, time, view){
|
|
197
|
-
let [
|
|
198
|
-
if(
|
|
199
|
-
let onStart = () =>
|
|
200
|
-
|
|
197
|
+
let [transitionRun, transitionStart, transitionEnd] = transition || [[], [], []]
|
|
198
|
+
if(transitionRun.length > 0){
|
|
199
|
+
let onStart = () => {
|
|
200
|
+
this.addOrRemoveClasses(el, transitionStart, [].concat(transitionRun).concat(transitionEnd))
|
|
201
|
+
window.requestAnimationFrame(() => {
|
|
202
|
+
this.addOrRemoveClasses(el, transitionRun, [])
|
|
203
|
+
window.requestAnimationFrame(() => this.addOrRemoveClasses(el, transitionEnd, transitionStart))
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
let onDone = () => this.addOrRemoveClasses(el, adds.concat(transitionEnd), removes.concat(transitionRun).concat(transitionStart))
|
|
201
207
|
return view.transition(time, onStart, onDone)
|
|
202
208
|
}
|
|
209
|
+
|
|
203
210
|
window.requestAnimationFrame(() => {
|
|
204
211
|
let [prevAdds, prevRemoves] = DOM.getSticky(el, "classes", [[], []])
|
|
205
212
|
let keepAdds = adds.filter(name => prevAdds.indexOf(name) < 0 && !el.classList.contains(name))
|
|
@@ -244,4 +251,4 @@ let JS = {
|
|
|
244
251
|
}
|
|
245
252
|
}
|
|
246
253
|
|
|
247
|
-
export default JS
|
|
254
|
+
export default JS
|
|
@@ -407,7 +407,7 @@ export default class LiveSocket {
|
|
|
407
407
|
DOM.findPhxSticky(document).forEach(el => newMainEl.appendChild(el))
|
|
408
408
|
this.outgoingMainEl.replaceWith(newMainEl)
|
|
409
409
|
this.outgoingMainEl = null
|
|
410
|
-
callback && requestAnimationFrame(callback)
|
|
410
|
+
callback && requestAnimationFrame(() => callback(linkRef))
|
|
411
411
|
onDone()
|
|
412
412
|
})
|
|
413
413
|
}
|
|
@@ -684,6 +684,7 @@ export default class LiveSocket {
|
|
|
684
684
|
let {type, id, root, scroll} = event.state || {}
|
|
685
685
|
let href = window.location.href
|
|
686
686
|
|
|
687
|
+
DOM.dispatchEvent(window, "phx:navigate", {detail: {href, patch: type === "patch", pop: true}})
|
|
687
688
|
this.requestDOMUpdate(() => {
|
|
688
689
|
if(this.main.isConnected() && (type === "patch" && id === this.main.id)){
|
|
689
690
|
this.main.pushLinkPatch(href, null, () => {
|
|
@@ -761,6 +762,7 @@ export default class LiveSocket {
|
|
|
761
762
|
if(!this.commitPendingLink(linkRef)){ return }
|
|
762
763
|
|
|
763
764
|
Browser.pushState(linkState, {type: "patch", id: this.main.id}, href)
|
|
765
|
+
DOM.dispatchEvent(window, "phx:navigate", {detail: {patch: true, href, pop: false}})
|
|
764
766
|
this.registerNewLocation(window.location)
|
|
765
767
|
}
|
|
766
768
|
|
|
@@ -773,9 +775,12 @@ export default class LiveSocket {
|
|
|
773
775
|
}
|
|
774
776
|
let scroll = window.scrollY
|
|
775
777
|
this.withPageLoading({to: href, kind: "redirect"}, done => {
|
|
776
|
-
this.replaceMain(href, flash, () => {
|
|
777
|
-
|
|
778
|
-
|
|
778
|
+
this.replaceMain(href, flash, (linkRef) => {
|
|
779
|
+
if(linkRef === this.linkRef){
|
|
780
|
+
Browser.pushState(linkState, {type: "redirect", id: this.main.id, scroll: scroll}, href)
|
|
781
|
+
DOM.dispatchEvent(window, "phx:navigate", {detail: {href, patch: false, pop: false}})
|
|
782
|
+
this.registerNewLocation(window.location)
|
|
783
|
+
}
|
|
779
784
|
done()
|
|
780
785
|
})
|
|
781
786
|
})
|
|
@@ -844,8 +849,10 @@ export default class LiveSocket {
|
|
|
844
849
|
let currentIterations = iterations
|
|
845
850
|
iterations++
|
|
846
851
|
let {at: at, type: lastType} = DOM.private(input, "prev-iteration") || {}
|
|
847
|
-
//
|
|
848
|
-
|
|
852
|
+
// Browsers should always fire at least one "input" event before every "change"
|
|
853
|
+
// Ignore "change" events, unless there was no prior "input" event.
|
|
854
|
+
// This could happen if user code triggers a "change" event, or if the browser is non-conforming.
|
|
855
|
+
if(at === currentIterations - 1 && type === "change" && lastType === "input"){ return }
|
|
849
856
|
|
|
850
857
|
DOM.putPrivate(input, "prev-iteration", {at: currentIterations, type: type})
|
|
851
858
|
|
|
@@ -120,6 +120,7 @@ export default class LiveUploader {
|
|
|
120
120
|
})
|
|
121
121
|
|
|
122
122
|
let groupedEntries = this._entries.reduce((acc, entry) => {
|
|
123
|
+
if(!entry.meta){ return acc }
|
|
123
124
|
let {name, callback} = entry.uploader(liveSocket.uploaders)
|
|
124
125
|
acc[name] = acc[name] || {callback: callback, entries: []}
|
|
125
126
|
acc[name].entries.push(entry)
|
|
@@ -136,7 +136,7 @@ export default class Rendered {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
componentToString(cid){
|
|
139
|
-
let [str, streams] = this.recursiveCIDToString(this.rendered[COMPONENTS], cid)
|
|
139
|
+
let [str, streams] = this.recursiveCIDToString(this.rendered[COMPONENTS], cid, null, false)
|
|
140
140
|
return [str, streams]
|
|
141
141
|
}
|
|
142
142
|
|
|
@@ -186,6 +186,7 @@ export default class Rendered {
|
|
|
186
186
|
|
|
187
187
|
if(stream !== undefined && (rendered[DYNAMICS].length > 0 || deleteIds.length > 0 || reset)){
|
|
188
188
|
delete rendered[STREAM]
|
|
189
|
+
rendered[DYNAMICS] = []
|
|
189
190
|
output.streams.add(stream)
|
|
190
191
|
}
|
|
191
192
|
}
|
|
@@ -202,7 +203,7 @@ export default class Rendered {
|
|
|
202
203
|
}
|
|
203
204
|
}
|
|
204
205
|
|
|
205
|
-
recursiveCIDToString(components, cid, onlyCids){
|
|
206
|
+
recursiveCIDToString(components, cid, onlyCids, allowRootComments = true){
|
|
206
207
|
let component = components[cid] || logError(`no component for CID ${cid}`, components)
|
|
207
208
|
let template = document.createElement("template")
|
|
208
209
|
let [html, streams] = this.recursiveToString(component, components, onlyCids)
|
|
@@ -223,6 +224,11 @@ export default class Rendered {
|
|
|
223
224
|
child.innerHTML = ""
|
|
224
225
|
}
|
|
225
226
|
return [true, hasComponents]
|
|
227
|
+
} else if(child.nodeType === Node.COMMENT_NODE){
|
|
228
|
+
// we have to strip root comments when rendering a component directly
|
|
229
|
+
// for patching because the morphdom target must be exactly the root entrypoint
|
|
230
|
+
if(!allowRootComments){ child.remove() }
|
|
231
|
+
return [hasNodes, hasComponents]
|
|
226
232
|
} else {
|
|
227
233
|
if(child.nodeValue.trim() !== ""){
|
|
228
234
|
logError("only HTML element tags are allowed at the root of components.\n\n" +
|
|
@@ -256,4 +262,4 @@ export default class Rendered {
|
|
|
256
262
|
span.setAttribute(PHX_COMPONENT, cid)
|
|
257
263
|
return span
|
|
258
264
|
}
|
|
259
|
-
}
|
|
265
|
+
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "./utils"
|
|
11
11
|
|
|
12
12
|
import LiveUploader from "./live_uploader"
|
|
13
|
+
import DOM from "./dom"
|
|
13
14
|
|
|
14
15
|
export default class UploadEntry {
|
|
15
16
|
static isActive(fileEl, file){
|
|
@@ -71,7 +72,7 @@ export default class UploadEntry {
|
|
|
71
72
|
error(reason = "failed"){
|
|
72
73
|
this.fileEl.removeEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated)
|
|
73
74
|
this.view.pushFileProgress(this.fileEl, this.ref, {error: reason})
|
|
74
|
-
LiveUploader.clearFiles(this.fileEl)
|
|
75
|
+
if(!DOM.isAutoUpload(this.fileEl)){ LiveUploader.clearFiles(this.fileEl) }
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
//private
|
|
@@ -656,10 +656,12 @@ export default class View {
|
|
|
656
656
|
onJoinError(resp){
|
|
657
657
|
if(resp.reason === "reload"){
|
|
658
658
|
this.log("error", () => [`failed mount with ${resp.status}. Falling back to page request`, resp])
|
|
659
|
-
|
|
659
|
+
if(this.isMain()){ this.onRedirect({to: this.href}) }
|
|
660
|
+
return
|
|
660
661
|
} else if(resp.reason === "unauthorized" || resp.reason === "stale"){
|
|
661
662
|
this.log("error", () => ["unauthorized live_redirect. Falling back to page request", resp])
|
|
662
|
-
|
|
663
|
+
if(this.isMain()){ this.onRedirect({to: this.href}) }
|
|
664
|
+
return
|
|
663
665
|
}
|
|
664
666
|
if(resp.redirect || resp.live_redirect){
|
|
665
667
|
this.joinPending = false
|
|
@@ -911,7 +913,7 @@ export default class View {
|
|
|
911
913
|
}
|
|
912
914
|
this.pushWithReply(refGenerator, "event", event, resp => {
|
|
913
915
|
DOM.showError(inputEl, this.liveSocket.binding(PHX_FEEDBACK_FOR))
|
|
914
|
-
if(DOM.isUploadInput(inputEl) &&
|
|
916
|
+
if(DOM.isUploadInput(inputEl) && DOM.isAutoUpload(inputEl)){
|
|
915
917
|
if(LiveUploader.filesAwaitingPreflight(inputEl).length > 0){
|
|
916
918
|
let [ref, _els] = refGenerator()
|
|
917
919
|
this.uploadFiles(inputEl.form, targetCtx, ref, cid, (_uploads) => {
|
|
@@ -1073,7 +1075,10 @@ export default class View {
|
|
|
1073
1075
|
let inputs = Array.from(form.elements).filter(el => DOM.isFormInput(el) && el.name && !el.hasAttribute(phxChange))
|
|
1074
1076
|
if(inputs.length === 0){ return }
|
|
1075
1077
|
|
|
1076
|
-
|
|
1078
|
+
// we must clear tracked uploads before recovery as they no longer have valid refs
|
|
1079
|
+
inputs.forEach(input => input.hasAttribute(PHX_UPLOAD_REF) && LiveUploader.clearFiles(input))
|
|
1080
|
+
let input = inputs.find(el => el.type !== "hidden") || inputs[0]
|
|
1081
|
+
|
|
1077
1082
|
let phxEvent = form.getAttribute(this.binding(PHX_AUTO_RECOVER)) || form.getAttribute(this.binding("change"))
|
|
1078
1083
|
JS.exec("change", phxEvent, view, input, ["push", {_target: input.name, newCid: newCid, callback: callback}])
|
|
1079
1084
|
})
|
|
@@ -1083,8 +1088,9 @@ export default class View {
|
|
|
1083
1088
|
let linkRef = this.liveSocket.setPendingLink(href)
|
|
1084
1089
|
let refGen = targetEl ? () => this.putRef([targetEl], "click") : null
|
|
1085
1090
|
let fallback = () => this.liveSocket.redirect(window.location.href)
|
|
1091
|
+
let url = href.startsWith("/") ? `${location.protocol}//${location.host}${href}` : href
|
|
1086
1092
|
|
|
1087
|
-
let push = this.pushWithReply(refGen, "live_patch", {url
|
|
1093
|
+
let push = this.pushWithReply(refGen, "live_patch", {url}, resp => {
|
|
1088
1094
|
this.liveSocket.requestDOMUpdate(() => {
|
|
1089
1095
|
if(resp.link_redirect){
|
|
1090
1096
|
this.liveSocket.replaceMain(href, null, callback, linkRef)
|
|
@@ -1118,7 +1124,10 @@ export default class View {
|
|
|
1118
1124
|
.filter(form => form.elements.length > 0)
|
|
1119
1125
|
.filter(form => form.getAttribute(this.binding(PHX_AUTO_RECOVER)) !== "ignore")
|
|
1120
1126
|
.map(form => {
|
|
1121
|
-
|
|
1127
|
+
// attribute given via JS module needs to be escaped as it contains the symbols []",
|
|
1128
|
+
// which result in an invalid css selector otherwise.
|
|
1129
|
+
const phxChangeValue = form.getAttribute(phxChange).replaceAll(/([\[\]"])/g, '\\$1')
|
|
1130
|
+
let newForm = template.content.querySelector(`form[id="${form.id}"][${phxChange}="${phxChangeValue}"]`)
|
|
1122
1131
|
if(newForm){
|
|
1123
1132
|
return [form, newForm, this.targetComponentID(newForm)]
|
|
1124
1133
|
} else {
|
package/assets/package.json
CHANGED
package/package.json
CHANGED
|
@@ -304,6 +304,9 @@ var DOM = {
|
|
|
304
304
|
isUploadInput(el) {
|
|
305
305
|
return el.type === "file" && el.getAttribute(PHX_UPLOAD_REF) !== null;
|
|
306
306
|
},
|
|
307
|
+
isAutoUpload(inputEl) {
|
|
308
|
+
return inputEl.hasAttribute("data-phx-auto-upload");
|
|
309
|
+
},
|
|
307
310
|
findUploadInputs(node) {
|
|
308
311
|
return this.all(node, `input[type="file"][${PHX_UPLOAD_REF}]`);
|
|
309
312
|
},
|
|
@@ -320,7 +323,12 @@ var DOM = {
|
|
|
320
323
|
return wantsNewTab || isTargetBlank || isDownload;
|
|
321
324
|
},
|
|
322
325
|
isUnloadableFormSubmit(e) {
|
|
323
|
-
|
|
326
|
+
let isDialogSubmit = e.target && e.target.getAttribute("method") === "dialog" || e.submitter && e.submitter.getAttribute("formmethod") === "dialog";
|
|
327
|
+
if (isDialogSubmit) {
|
|
328
|
+
return false;
|
|
329
|
+
} else {
|
|
330
|
+
return !e.defaultPrevented && !this.wantsNewTab(e);
|
|
331
|
+
}
|
|
324
332
|
},
|
|
325
333
|
isNewPageClick(e, currentLocation) {
|
|
326
334
|
let href = e.target instanceof HTMLAnchorElement ? e.target.getAttribute("href") : null;
|
|
@@ -331,6 +339,9 @@ var DOM = {
|
|
|
331
339
|
if (href.startsWith("mailto:") || href.startsWith("tel:")) {
|
|
332
340
|
return false;
|
|
333
341
|
}
|
|
342
|
+
if (e.target.isContentEditable) {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
334
345
|
try {
|
|
335
346
|
url = new URL(href);
|
|
336
347
|
} catch (e2) {
|
|
@@ -793,7 +804,9 @@ var UploadEntry = class {
|
|
|
793
804
|
error(reason = "failed") {
|
|
794
805
|
this.fileEl.removeEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated);
|
|
795
806
|
this.view.pushFileProgress(this.fileEl, this.ref, { error: reason });
|
|
796
|
-
|
|
807
|
+
if (!dom_default.isAutoUpload(this.fileEl)) {
|
|
808
|
+
LiveUploader.clearFiles(this.fileEl);
|
|
809
|
+
}
|
|
797
810
|
}
|
|
798
811
|
onDone(callback) {
|
|
799
812
|
this._onDone = () => {
|
|
@@ -930,6 +943,9 @@ var LiveUploader = class {
|
|
|
930
943
|
return entry;
|
|
931
944
|
});
|
|
932
945
|
let groupedEntries = this._entries.reduce((acc, entry) => {
|
|
946
|
+
if (!entry.meta) {
|
|
947
|
+
return acc;
|
|
948
|
+
}
|
|
933
949
|
let { name, callback } = entry.uploader(liveSocket.uploaders);
|
|
934
950
|
acc[name] = acc[name] || { callback, entries: [] };
|
|
935
951
|
acc[name].entries.push(entry);
|
|
@@ -1767,7 +1783,9 @@ var DOMPatch = class {
|
|
|
1767
1783
|
});
|
|
1768
1784
|
if (reset !== void 0) {
|
|
1769
1785
|
dom_default.all(container, `[${PHX_STREAM_REF}="${ref}"]`, (child) => {
|
|
1770
|
-
|
|
1786
|
+
if (!inserts[child.id]) {
|
|
1787
|
+
this.removeStreamChildElement(child);
|
|
1788
|
+
}
|
|
1771
1789
|
});
|
|
1772
1790
|
}
|
|
1773
1791
|
deleteIds.forEach((id) => {
|
|
@@ -1940,7 +1958,7 @@ var DOMPatch = class {
|
|
|
1940
1958
|
this.transitionPendingRemoves();
|
|
1941
1959
|
if (externalFormTriggered) {
|
|
1942
1960
|
liveSocket.unload();
|
|
1943
|
-
externalFormTriggered.submit();
|
|
1961
|
+
Object.getPrototypeOf(externalFormTriggered).submit.call(externalFormTriggered);
|
|
1944
1962
|
}
|
|
1945
1963
|
return true;
|
|
1946
1964
|
}
|
|
@@ -2162,7 +2180,7 @@ var Rendered = class {
|
|
|
2162
2180
|
return merged;
|
|
2163
2181
|
}
|
|
2164
2182
|
componentToString(cid) {
|
|
2165
|
-
let [str, streams] = this.recursiveCIDToString(this.rendered[COMPONENTS], cid);
|
|
2183
|
+
let [str, streams] = this.recursiveCIDToString(this.rendered[COMPONENTS], cid, null, false);
|
|
2166
2184
|
return [str, streams];
|
|
2167
2185
|
}
|
|
2168
2186
|
pruneCIDs(cids) {
|
|
@@ -2208,6 +2226,7 @@ var Rendered = class {
|
|
|
2208
2226
|
}
|
|
2209
2227
|
if (stream !== void 0 && (rendered[DYNAMICS].length > 0 || deleteIds.length > 0 || reset)) {
|
|
2210
2228
|
delete rendered[STREAM];
|
|
2229
|
+
rendered[DYNAMICS] = [];
|
|
2211
2230
|
output.streams.add(stream);
|
|
2212
2231
|
}
|
|
2213
2232
|
}
|
|
@@ -2222,7 +2241,7 @@ var Rendered = class {
|
|
|
2222
2241
|
output.buffer += rendered;
|
|
2223
2242
|
}
|
|
2224
2243
|
}
|
|
2225
|
-
recursiveCIDToString(components, cid, onlyCids) {
|
|
2244
|
+
recursiveCIDToString(components, cid, onlyCids, allowRootComments = true) {
|
|
2226
2245
|
let component = components[cid] || logError(`no component for CID ${cid}`, components);
|
|
2227
2246
|
let template = document.createElement("template");
|
|
2228
2247
|
let [html, streams] = this.recursiveToString(component, components, onlyCids);
|
|
@@ -2243,6 +2262,11 @@ var Rendered = class {
|
|
|
2243
2262
|
child.innerHTML = "";
|
|
2244
2263
|
}
|
|
2245
2264
|
return [true, hasComponents];
|
|
2265
|
+
} else if (child.nodeType === Node.COMMENT_NODE) {
|
|
2266
|
+
if (!allowRootComments) {
|
|
2267
|
+
child.remove();
|
|
2268
|
+
}
|
|
2269
|
+
return [hasNodes, hasComponents];
|
|
2246
2270
|
} else {
|
|
2247
2271
|
if (child.nodeValue.trim() !== "") {
|
|
2248
2272
|
logError(`only HTML element tags are allowed at the root of components.
|
|
@@ -2522,10 +2546,16 @@ var JS = {
|
|
|
2522
2546
|
}
|
|
2523
2547
|
},
|
|
2524
2548
|
addOrRemoveClasses(el, adds, removes, transition, time, view) {
|
|
2525
|
-
let [
|
|
2526
|
-
if (
|
|
2527
|
-
let onStart = () =>
|
|
2528
|
-
|
|
2549
|
+
let [transitionRun, transitionStart, transitionEnd] = transition || [[], [], []];
|
|
2550
|
+
if (transitionRun.length > 0) {
|
|
2551
|
+
let onStart = () => {
|
|
2552
|
+
this.addOrRemoveClasses(el, transitionStart, [].concat(transitionRun).concat(transitionEnd));
|
|
2553
|
+
window.requestAnimationFrame(() => {
|
|
2554
|
+
this.addOrRemoveClasses(el, transitionRun, []);
|
|
2555
|
+
window.requestAnimationFrame(() => this.addOrRemoveClasses(el, transitionEnd, transitionStart));
|
|
2556
|
+
});
|
|
2557
|
+
};
|
|
2558
|
+
let onDone = () => this.addOrRemoveClasses(el, adds.concat(transitionEnd), removes.concat(transitionRun).concat(transitionStart));
|
|
2529
2559
|
return view.transition(time, onStart, onDone);
|
|
2530
2560
|
}
|
|
2531
2561
|
window.requestAnimationFrame(() => {
|
|
@@ -3126,10 +3156,16 @@ var View = class {
|
|
|
3126
3156
|
onJoinError(resp) {
|
|
3127
3157
|
if (resp.reason === "reload") {
|
|
3128
3158
|
this.log("error", () => [`failed mount with ${resp.status}. Falling back to page request`, resp]);
|
|
3129
|
-
|
|
3159
|
+
if (this.isMain()) {
|
|
3160
|
+
this.onRedirect({ to: this.href });
|
|
3161
|
+
}
|
|
3162
|
+
return;
|
|
3130
3163
|
} else if (resp.reason === "unauthorized" || resp.reason === "stale") {
|
|
3131
3164
|
this.log("error", () => ["unauthorized live_redirect. Falling back to page request", resp]);
|
|
3132
|
-
|
|
3165
|
+
if (this.isMain()) {
|
|
3166
|
+
this.onRedirect({ to: this.href });
|
|
3167
|
+
}
|
|
3168
|
+
return;
|
|
3133
3169
|
}
|
|
3134
3170
|
if (resp.redirect || resp.live_redirect) {
|
|
3135
3171
|
this.joinPending = false;
|
|
@@ -3402,7 +3438,7 @@ var View = class {
|
|
|
3402
3438
|
};
|
|
3403
3439
|
this.pushWithReply(refGenerator, "event", event, (resp) => {
|
|
3404
3440
|
dom_default.showError(inputEl, this.liveSocket.binding(PHX_FEEDBACK_FOR));
|
|
3405
|
-
if (dom_default.isUploadInput(inputEl) &&
|
|
3441
|
+
if (dom_default.isUploadInput(inputEl) && dom_default.isAutoUpload(inputEl)) {
|
|
3406
3442
|
if (LiveUploader.filesAwaitingPreflight(inputEl).length > 0) {
|
|
3407
3443
|
let [ref, _els] = refGenerator();
|
|
3408
3444
|
this.uploadFiles(inputEl.form, targetCtx, ref, cid, (_uploads) => {
|
|
@@ -3557,7 +3593,8 @@ var View = class {
|
|
|
3557
3593
|
if (inputs.length === 0) {
|
|
3558
3594
|
return;
|
|
3559
3595
|
}
|
|
3560
|
-
|
|
3596
|
+
inputs.forEach((input2) => input2.hasAttribute(PHX_UPLOAD_REF) && LiveUploader.clearFiles(input2));
|
|
3597
|
+
let input = inputs.find((el) => el.type !== "hidden") || inputs[0];
|
|
3561
3598
|
let phxEvent = form.getAttribute(this.binding(PHX_AUTO_RECOVER)) || form.getAttribute(this.binding("change"));
|
|
3562
3599
|
js_default.exec("change", phxEvent, view, input, ["push", { _target: input.name, newCid, callback }]);
|
|
3563
3600
|
});
|
|
@@ -3566,7 +3603,8 @@ var View = class {
|
|
|
3566
3603
|
let linkRef = this.liveSocket.setPendingLink(href);
|
|
3567
3604
|
let refGen = targetEl ? () => this.putRef([targetEl], "click") : null;
|
|
3568
3605
|
let fallback = () => this.liveSocket.redirect(window.location.href);
|
|
3569
|
-
let
|
|
3606
|
+
let url = href.startsWith("/") ? `${location.protocol}//${location.host}${href}` : href;
|
|
3607
|
+
let push = this.pushWithReply(refGen, "live_patch", { url }, (resp) => {
|
|
3570
3608
|
this.liveSocket.requestDOMUpdate(() => {
|
|
3571
3609
|
if (resp.link_redirect) {
|
|
3572
3610
|
this.liveSocket.replaceMain(href, null, callback, linkRef);
|
|
@@ -3593,7 +3631,8 @@ var View = class {
|
|
|
3593
3631
|
let template = document.createElement("template");
|
|
3594
3632
|
template.innerHTML = html;
|
|
3595
3633
|
return dom_default.all(this.el, `form[${phxChange}]`).filter((form) => form.id && this.ownsElement(form)).filter((form) => form.elements.length > 0).filter((form) => form.getAttribute(this.binding(PHX_AUTO_RECOVER)) !== "ignore").map((form) => {
|
|
3596
|
-
|
|
3634
|
+
const phxChangeValue = form.getAttribute(phxChange).replaceAll(/([\[\]"])/g, "\\$1");
|
|
3635
|
+
let newForm = template.content.querySelector(`form[id="${form.id}"][${phxChange}="${phxChangeValue}"]`);
|
|
3597
3636
|
if (newForm) {
|
|
3598
3637
|
return [form, newForm, this.targetComponentID(newForm)];
|
|
3599
3638
|
} else {
|
|
@@ -3936,7 +3975,7 @@ var LiveSocket = class {
|
|
|
3936
3975
|
dom_default.findPhxSticky(document).forEach((el) => newMainEl.appendChild(el));
|
|
3937
3976
|
this.outgoingMainEl.replaceWith(newMainEl);
|
|
3938
3977
|
this.outgoingMainEl = null;
|
|
3939
|
-
callback && requestAnimationFrame(callback);
|
|
3978
|
+
callback && requestAnimationFrame(() => callback(linkRef));
|
|
3940
3979
|
onDone();
|
|
3941
3980
|
});
|
|
3942
3981
|
}
|
|
@@ -4218,6 +4257,7 @@ var LiveSocket = class {
|
|
|
4218
4257
|
}
|
|
4219
4258
|
let { type, id, root, scroll } = event.state || {};
|
|
4220
4259
|
let href = window.location.href;
|
|
4260
|
+
dom_default.dispatchEvent(window, "phx:navigate", { detail: { href, patch: type === "patch", pop: true } });
|
|
4221
4261
|
this.requestDOMUpdate(() => {
|
|
4222
4262
|
if (this.main.isConnected() && (type === "patch" && id === this.main.id)) {
|
|
4223
4263
|
this.main.pushLinkPatch(href, null, () => {
|
|
@@ -4295,6 +4335,7 @@ var LiveSocket = class {
|
|
|
4295
4335
|
return;
|
|
4296
4336
|
}
|
|
4297
4337
|
browser_default.pushState(linkState, { type: "patch", id: this.main.id }, href);
|
|
4338
|
+
dom_default.dispatchEvent(window, "phx:navigate", { detail: { patch: true, href, pop: false } });
|
|
4298
4339
|
this.registerNewLocation(window.location);
|
|
4299
4340
|
}
|
|
4300
4341
|
historyRedirect(href, linkState, flash) {
|
|
@@ -4307,9 +4348,12 @@ var LiveSocket = class {
|
|
|
4307
4348
|
}
|
|
4308
4349
|
let scroll = window.scrollY;
|
|
4309
4350
|
this.withPageLoading({ to: href, kind: "redirect" }, (done) => {
|
|
4310
|
-
this.replaceMain(href, flash, () => {
|
|
4311
|
-
|
|
4312
|
-
|
|
4351
|
+
this.replaceMain(href, flash, (linkRef) => {
|
|
4352
|
+
if (linkRef === this.linkRef) {
|
|
4353
|
+
browser_default.pushState(linkState, { type: "redirect", id: this.main.id, scroll }, href);
|
|
4354
|
+
dom_default.dispatchEvent(window, "phx:navigate", { detail: { href, patch: false, pop: false } });
|
|
4355
|
+
this.registerNewLocation(window.location);
|
|
4356
|
+
}
|
|
4313
4357
|
done();
|
|
4314
4358
|
});
|
|
4315
4359
|
});
|
|
@@ -4377,7 +4421,7 @@ var LiveSocket = class {
|
|
|
4377
4421
|
let currentIterations = iterations;
|
|
4378
4422
|
iterations++;
|
|
4379
4423
|
let { at, type: lastType } = dom_default.private(input, "prev-iteration") || {};
|
|
4380
|
-
if (at === currentIterations - 1 && type
|
|
4424
|
+
if (at === currentIterations - 1 && type === "change" && lastType === "input") {
|
|
4381
4425
|
return;
|
|
4382
4426
|
}
|
|
4383
4427
|
dom_default.putPrivate(input, "prev-iteration", { at: currentIterations, type });
|