phoenix_live_view 0.20.0 → 0.20.2
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 +10 -9
- package/assets/js/phoenix_live_view/aria.js +1 -1
- package/assets/js/phoenix_live_view/constants.js +3 -1
- package/assets/js/phoenix_live_view/dom.js +9 -4
- package/assets/js/phoenix_live_view/dom_patch.js +35 -45
- package/assets/js/phoenix_live_view/entry_uploader.js +1 -0
- package/assets/js/phoenix_live_view/js.js +10 -0
- package/assets/js/phoenix_live_view/live_socket.js +9 -7
- package/assets/js/phoenix_live_view/live_uploader.js +1 -0
- package/assets/js/phoenix_live_view/rendered.js +216 -71
- package/assets/js/phoenix_live_view/upload_entry.js +2 -1
- package/assets/js/phoenix_live_view/view.js +23 -10
- package/assets/js/phoenix_live_view/view_hook.js +4 -2
- package/assets/package.json +2 -2
- package/package.json +1 -1
- package/priv/static/phoenix_live_view.cjs.js +266 -131
- package/priv/static/phoenix_live_view.cjs.js.map +2 -2
- package/priv/static/phoenix_live_view.esm.js +266 -131
- package/priv/static/phoenix_live_view.esm.js.map +2 -2
- package/priv/static/phoenix_live_view.js +266 -131
- package/priv/static/phoenix_live_view.min.js +5 -10
|
@@ -5,10 +5,12 @@ import {
|
|
|
5
5
|
EVENTS,
|
|
6
6
|
PHX_COMPONENT,
|
|
7
7
|
PHX_SKIP,
|
|
8
|
+
PHX_MAGIC_ID,
|
|
8
9
|
REPLY,
|
|
9
10
|
STATIC,
|
|
10
11
|
TITLE,
|
|
11
12
|
STREAM,
|
|
13
|
+
ROOT,
|
|
12
14
|
} from "./constants"
|
|
13
15
|
|
|
14
16
|
import {
|
|
@@ -17,6 +19,122 @@ import {
|
|
|
17
19
|
isCid,
|
|
18
20
|
} from "./utils"
|
|
19
21
|
|
|
22
|
+
const VOID_TAGS = new Set([
|
|
23
|
+
"area",
|
|
24
|
+
"base",
|
|
25
|
+
"br",
|
|
26
|
+
"col",
|
|
27
|
+
"command",
|
|
28
|
+
"embed",
|
|
29
|
+
"hr",
|
|
30
|
+
"img",
|
|
31
|
+
"input",
|
|
32
|
+
"keygen",
|
|
33
|
+
"link",
|
|
34
|
+
"meta",
|
|
35
|
+
"param",
|
|
36
|
+
"source",
|
|
37
|
+
"track",
|
|
38
|
+
"wbr"
|
|
39
|
+
])
|
|
40
|
+
const endingTagNameChars = new Set([">", "/", " ", "\n", "\t", "\r"])
|
|
41
|
+
const quoteChars = new Set(["'", '"'])
|
|
42
|
+
|
|
43
|
+
export let modifyRoot = (html, attrs, clearInnerHTML) => {
|
|
44
|
+
let i = 0
|
|
45
|
+
let insideComment = false
|
|
46
|
+
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 {
|
|
54
|
+
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
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
break
|
|
89
|
+
} else {
|
|
90
|
+
i++
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if(!tag){ throw new Error(`malformed html ${html}`) }
|
|
94
|
+
|
|
95
|
+
let closeAt = html.length - 1
|
|
96
|
+
insideComment = false
|
|
97
|
+
while(closeAt >= beforeTag.length + tag.length){
|
|
98
|
+
let char = html.charAt(closeAt)
|
|
99
|
+
if(insideComment){
|
|
100
|
+
if(char === "-" && html.slice(closeAt - 3, closeAt) === "<!-"){
|
|
101
|
+
insideComment = false
|
|
102
|
+
closeAt -= 4
|
|
103
|
+
} else {
|
|
104
|
+
closeAt -= 1
|
|
105
|
+
}
|
|
106
|
+
} else if(char === ">" && html.slice(closeAt - 2, closeAt) === "--"){
|
|
107
|
+
insideComment = true
|
|
108
|
+
closeAt -= 3
|
|
109
|
+
} else if(char === ">"){
|
|
110
|
+
break
|
|
111
|
+
} else {
|
|
112
|
+
closeAt -= 1
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
afterTag = html.slice(closeAt + 1, html.length)
|
|
116
|
+
|
|
117
|
+
let attrsStr =
|
|
118
|
+
Object.keys(attrs)
|
|
119
|
+
.map(attr => attrs[attr] === true ? attr : `${attr}="${attrs[attr]}"`)
|
|
120
|
+
.join(" ")
|
|
121
|
+
|
|
122
|
+
if(clearInnerHTML){
|
|
123
|
+
// Keep the id if any
|
|
124
|
+
let idAttrStr = id ? ` id="${id}"` : "";
|
|
125
|
+
if(VOID_TAGS.has(tag)){
|
|
126
|
+
newHTML = `<${tag}${idAttrStr}${attrsStr === "" ? "" : " "}${attrsStr}/>`
|
|
127
|
+
} else {
|
|
128
|
+
newHTML = `<${tag}${idAttrStr}${attrsStr === "" ? "" : " "}${attrsStr}></${tag}>`
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
let rest = html.slice(tagNameEndsAt, closeAt + 1)
|
|
132
|
+
newHTML = `<${tag}${attrsStr === "" ? "" : " "}${attrsStr}${rest}`
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return [newHTML, beforeTag, afterTag]
|
|
136
|
+
}
|
|
137
|
+
|
|
20
138
|
export default class Rendered {
|
|
21
139
|
static extract(diff){
|
|
22
140
|
let {[REPLY]: reply, [EVENTS]: events, [TITLE]: title} = diff
|
|
@@ -29,20 +147,21 @@ export default class Rendered {
|
|
|
29
147
|
constructor(viewId, rendered){
|
|
30
148
|
this.viewId = viewId
|
|
31
149
|
this.rendered = {}
|
|
150
|
+
this.magicId = 0
|
|
32
151
|
this.mergeDiff(rendered)
|
|
33
152
|
}
|
|
34
153
|
|
|
35
154
|
parentViewId(){ return this.viewId }
|
|
36
155
|
|
|
37
156
|
toString(onlyCids){
|
|
38
|
-
let [str, streams] = this.recursiveToString(this.rendered, this.rendered[COMPONENTS], onlyCids)
|
|
157
|
+
let [str, streams] = this.recursiveToString(this.rendered, this.rendered[COMPONENTS], onlyCids, true, {})
|
|
39
158
|
return [str, streams]
|
|
40
159
|
}
|
|
41
160
|
|
|
42
|
-
recursiveToString(rendered, components = rendered[COMPONENTS], onlyCids){
|
|
161
|
+
recursiveToString(rendered, components = rendered[COMPONENTS], onlyCids, changeTracking, rootAttrs){
|
|
43
162
|
onlyCids = onlyCids ? new Set(onlyCids) : null
|
|
44
163
|
let output = {buffer: "", components: components, onlyCids: onlyCids, streams: new Set()}
|
|
45
|
-
this.toOutputBuffer(rendered, null, output)
|
|
164
|
+
this.toOutputBuffer(rendered, null, output, changeTracking, rootAttrs)
|
|
46
165
|
return [output.buffer, output.streams]
|
|
47
166
|
}
|
|
48
167
|
|
|
@@ -90,10 +209,11 @@ export default class Rendered {
|
|
|
90
209
|
}
|
|
91
210
|
|
|
92
211
|
stat = tdiff[STATIC]
|
|
93
|
-
ndiff = this.cloneMerge(tdiff, cdiff)
|
|
212
|
+
ndiff = this.cloneMerge(tdiff, cdiff, true)
|
|
94
213
|
ndiff[STATIC] = stat
|
|
95
214
|
} else {
|
|
96
|
-
ndiff = cdiff[STATIC] !== undefined
|
|
215
|
+
ndiff = cdiff[STATIC] !== undefined || oldc[cid] === undefined ?
|
|
216
|
+
cdiff : this.cloneMerge(oldc[cid], cdiff, false)
|
|
97
217
|
}
|
|
98
218
|
|
|
99
219
|
cache[cid] = ndiff
|
|
@@ -121,23 +241,41 @@ export default class Rendered {
|
|
|
121
241
|
target[key] = val
|
|
122
242
|
}
|
|
123
243
|
}
|
|
244
|
+
if(target[ROOT]){
|
|
245
|
+
target.newRender = true
|
|
246
|
+
}
|
|
124
247
|
}
|
|
125
248
|
|
|
126
|
-
|
|
249
|
+
// Merges cid trees together, copying statics from source tree.
|
|
250
|
+
//
|
|
251
|
+
// The `pruneMagicId` is passed to control pruning the magicId of the
|
|
252
|
+
// target. We must always prune the magicId when we are sharing statics
|
|
253
|
+
// from another component. If not pruning, we replicate the logic from
|
|
254
|
+
// mutableMerge, where we set newRender to true if there is a root
|
|
255
|
+
// (effectively forcing the new version to be rendered instead of skipped)
|
|
256
|
+
//
|
|
257
|
+
cloneMerge(target, source, pruneMagicId){
|
|
127
258
|
let merged = {...target, ...source}
|
|
128
259
|
for(let key in merged){
|
|
129
260
|
let val = source[key]
|
|
130
261
|
let targetVal = target[key]
|
|
131
262
|
if(isObject(val) && val[STATIC] === undefined && isObject(targetVal)){
|
|
132
|
-
merged[key] = this.cloneMerge(targetVal, val)
|
|
263
|
+
merged[key] = this.cloneMerge(targetVal, val, pruneMagicId)
|
|
133
264
|
}
|
|
134
265
|
}
|
|
266
|
+
if(pruneMagicId){
|
|
267
|
+
delete merged.magicId
|
|
268
|
+
delete merged.newRender
|
|
269
|
+
} else if(target[ROOT]){
|
|
270
|
+
merged.newRender = true
|
|
271
|
+
}
|
|
135
272
|
return merged
|
|
136
273
|
}
|
|
137
274
|
|
|
138
275
|
componentToString(cid){
|
|
139
|
-
let [str, streams] = this.recursiveCIDToString(this.rendered[COMPONENTS], cid, null
|
|
140
|
-
|
|
276
|
+
let [str, streams] = this.recursiveCIDToString(this.rendered[COMPONENTS], cid, null)
|
|
277
|
+
let [strippedHTML, _before, _after] = modifyRoot(str, {})
|
|
278
|
+
return [strippedHTML, streams]
|
|
141
279
|
}
|
|
142
280
|
|
|
143
281
|
pruneCIDs(cids){
|
|
@@ -158,16 +296,53 @@ export default class Rendered {
|
|
|
158
296
|
}
|
|
159
297
|
}
|
|
160
298
|
|
|
161
|
-
|
|
299
|
+
nextMagicID(){
|
|
300
|
+
this.magicId++
|
|
301
|
+
return `${this.parentViewId()}-${this.magicId}`
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Converts rendered tree to output buffer.
|
|
305
|
+
//
|
|
306
|
+
// changeTracking controls if we can apply the PHX_SKIP optimization.
|
|
307
|
+
// It is disabled for comprehensions since we must re-render the entire collection
|
|
308
|
+
// and no invidial element is tracked inside the comprehension.
|
|
309
|
+
toOutputBuffer(rendered, templates, output, changeTracking, rootAttrs = {}){
|
|
162
310
|
if(rendered[DYNAMICS]){ return this.comprehensionToBuffer(rendered, templates, output) }
|
|
163
311
|
let {[STATIC]: statics} = rendered
|
|
164
312
|
statics = this.templateStatic(statics, templates)
|
|
313
|
+
let isRoot = rendered[ROOT]
|
|
314
|
+
let prevBuffer = output.buffer
|
|
315
|
+
if(isRoot){ output.buffer = "" }
|
|
316
|
+
|
|
317
|
+
if(changeTracking && isRoot && !rendered.magicId){
|
|
318
|
+
rendered.newRender = true
|
|
319
|
+
rendered.magicId = this.nextMagicID()
|
|
320
|
+
}
|
|
165
321
|
|
|
166
322
|
output.buffer += statics[0]
|
|
167
323
|
for(let i = 1; i < statics.length; i++){
|
|
168
|
-
this.dynamicToBuffer(rendered[i - 1], templates, output)
|
|
324
|
+
this.dynamicToBuffer(rendered[i - 1], templates, output, changeTracking)
|
|
169
325
|
output.buffer += statics[i]
|
|
170
326
|
}
|
|
327
|
+
|
|
328
|
+
// Applies the root tag "skip" optimization if supported, which clears
|
|
329
|
+
// the root tag attributes and innerHTML, and only maintains the magicId.
|
|
330
|
+
// We can only skip when changeTracking is supported (outside of a comprehension),
|
|
331
|
+
// and when the root element hasn't experienced an unrendered merge (newRender true).
|
|
332
|
+
if(isRoot){
|
|
333
|
+
let skip = false
|
|
334
|
+
let attrs
|
|
335
|
+
if(changeTracking || Object.keys(rootAttrs).length > 0){
|
|
336
|
+
skip = !rendered.newRender
|
|
337
|
+
attrs = {[PHX_MAGIC_ID]: rendered.magicId, ...rootAttrs}
|
|
338
|
+
} else {
|
|
339
|
+
attrs = rootAttrs
|
|
340
|
+
}
|
|
341
|
+
if(skip){ attrs[PHX_SKIP] = true}
|
|
342
|
+
let [newRoot, commentBefore, commentAfter] = modifyRoot(output.buffer, attrs, skip)
|
|
343
|
+
rendered.newRender = false
|
|
344
|
+
output.buffer = prevBuffer + commentBefore + newRoot + commentAfter
|
|
345
|
+
}
|
|
171
346
|
}
|
|
172
347
|
|
|
173
348
|
comprehensionToBuffer(rendered, templates, output){
|
|
@@ -179,7 +354,12 @@ export default class Rendered {
|
|
|
179
354
|
let dynamic = dynamics[d]
|
|
180
355
|
output.buffer += statics[0]
|
|
181
356
|
for(let i = 1; i < statics.length; i++){
|
|
182
|
-
|
|
357
|
+
// Inside a comprehension, we don't track how dynamics change
|
|
358
|
+
// over time (and features like streams would make that impossible
|
|
359
|
+
// unless we move the stream diffing away from morphdom),
|
|
360
|
+
// so we can't perform root change tracking.
|
|
361
|
+
let changeTracking = false
|
|
362
|
+
this.dynamicToBuffer(dynamic[i - 1], compTemplates, output, changeTracking)
|
|
183
363
|
output.buffer += statics[i]
|
|
184
364
|
}
|
|
185
365
|
}
|
|
@@ -191,75 +371,40 @@ export default class Rendered {
|
|
|
191
371
|
}
|
|
192
372
|
}
|
|
193
373
|
|
|
194
|
-
dynamicToBuffer(rendered, templates, output){
|
|
374
|
+
dynamicToBuffer(rendered, templates, output, changeTracking){
|
|
195
375
|
if(typeof (rendered) === "number"){
|
|
196
376
|
let [str, streams] = this.recursiveCIDToString(output.components, rendered, output.onlyCids)
|
|
197
377
|
output.buffer += str
|
|
198
378
|
output.streams = new Set([...output.streams, ...streams])
|
|
199
379
|
} else if(isObject(rendered)){
|
|
200
|
-
this.toOutputBuffer(rendered, templates, output)
|
|
380
|
+
this.toOutputBuffer(rendered, templates, output, changeTracking, {})
|
|
201
381
|
} else {
|
|
202
382
|
output.buffer += rendered
|
|
203
383
|
}
|
|
204
384
|
}
|
|
205
385
|
|
|
206
|
-
recursiveCIDToString(components, cid, onlyCids
|
|
386
|
+
recursiveCIDToString(components, cid, onlyCids){
|
|
207
387
|
let component = components[cid] || logError(`no component for CID ${cid}`, components)
|
|
208
|
-
let
|
|
209
|
-
let [html, streams] = this.recursiveToString(component, components, onlyCids)
|
|
210
|
-
template.innerHTML = html
|
|
211
|
-
let container = template.content
|
|
388
|
+
let attrs = {[PHX_COMPONENT]: cid}
|
|
212
389
|
let skip = onlyCids && !onlyCids.has(cid)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
} else {
|
|
233
|
-
if(child.nodeValue.trim() !== ""){
|
|
234
|
-
logError("only HTML element tags are allowed at the root of components.\n\n" +
|
|
235
|
-
`got: "${child.nodeValue.trim()}"\n\n` +
|
|
236
|
-
"within:\n", template.innerHTML.trim())
|
|
237
|
-
child.replaceWith(this.createSpan(child.nodeValue, cid))
|
|
238
|
-
return [true, hasComponents]
|
|
239
|
-
} else {
|
|
240
|
-
child.remove()
|
|
241
|
-
return [hasNodes, hasComponents]
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}, [false, false])
|
|
245
|
-
|
|
246
|
-
if(!hasChildNodes && !hasChildComponents){
|
|
247
|
-
logError("expected at least one HTML element tag inside a component, but the component is empty:\n",
|
|
248
|
-
template.innerHTML.trim())
|
|
249
|
-
return [this.createSpan("", cid).outerHTML, streams]
|
|
250
|
-
} else if(!hasChildNodes && hasChildComponents){
|
|
251
|
-
logError("expected at least one HTML element tag directly inside a component, but only subcomponents were found. A component must render at least one HTML tag directly inside itself.",
|
|
252
|
-
template.innerHTML.trim())
|
|
253
|
-
return [template.innerHTML, streams]
|
|
254
|
-
} else {
|
|
255
|
-
return [template.innerHTML, streams]
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
createSpan(text, cid){
|
|
260
|
-
let span = document.createElement("span")
|
|
261
|
-
span.innerText = text
|
|
262
|
-
span.setAttribute(PHX_COMPONENT, cid)
|
|
263
|
-
return span
|
|
390
|
+
// Two optimization paths apply here:
|
|
391
|
+
//
|
|
392
|
+
// 1. The onlyCids optimization works by the server diff telling us only specific
|
|
393
|
+
// cid's have changed. This allows us to skip rendering any component that hasn't changed,
|
|
394
|
+
// which ultimately sets PHX_SKIP root attribute and avoids rendering the innerHTML.
|
|
395
|
+
//
|
|
396
|
+
// 2. The root PHX_SKIP optimization generalizes to all HEEx function components, and
|
|
397
|
+
// works in the same PHX_SKIP attribute fashion as 1, but the newRender tracking is done
|
|
398
|
+
// at the general diff merge level. If we merge a diff with new dynamics, we necessariy have
|
|
399
|
+
// experienced a change which must be a newRender, and thus we can't skip the render.
|
|
400
|
+
//
|
|
401
|
+
// Both optimization flows apply here. newRender is set based on the onlyCids optimization, and
|
|
402
|
+
// we track a deterministic magicId based on the cid.
|
|
403
|
+
component.newRender = !skip
|
|
404
|
+
component.magicId = `${this.parentViewId()}-c-${cid}`
|
|
405
|
+
let changeTracking = true
|
|
406
|
+
let [html, streams] = this.recursiveToString(component, components, onlyCids, changeTracking, attrs)
|
|
407
|
+
|
|
408
|
+
return [html, streams]
|
|
264
409
|
}
|
|
265
|
-
}
|
|
410
|
+
}
|
|
@@ -96,7 +96,8 @@ export default class UploadEntry {
|
|
|
96
96
|
relative_path: this.file.webkitRelativePath,
|
|
97
97
|
size: this.file.size,
|
|
98
98
|
type: this.file.type,
|
|
99
|
-
ref: this.ref
|
|
99
|
+
ref: this.ref,
|
|
100
|
+
meta: typeof(this.file.meta) === "function" ? this.file.meta() : undefined
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
|
|
@@ -115,9 +115,10 @@ export default class View {
|
|
|
115
115
|
this.children = this.parent ? null : {}
|
|
116
116
|
this.root.children[this.id] = {}
|
|
117
117
|
this.channel = this.liveSocket.channel(`lv:${this.id}`, () => {
|
|
118
|
+
let url = this.href && this.expandURL(this.href)
|
|
118
119
|
return {
|
|
119
|
-
redirect: this.redirect ?
|
|
120
|
-
url: this.redirect ? undefined :
|
|
120
|
+
redirect: this.redirect ? url : undefined,
|
|
121
|
+
url: this.redirect ? undefined : url || undefined,
|
|
121
122
|
params: this.connectParams(liveReferer),
|
|
122
123
|
session: this.getSession(),
|
|
123
124
|
static: this.getStatic(),
|
|
@@ -340,7 +341,7 @@ export default class View {
|
|
|
340
341
|
this.attachTrueDocEl()
|
|
341
342
|
let patch = new DOMPatch(this, this.el, this.id, html, streams, null)
|
|
342
343
|
patch.markPrunableContentForRemoval()
|
|
343
|
-
this.performPatch(patch, false)
|
|
344
|
+
this.performPatch(patch, false, true)
|
|
344
345
|
this.joinNewChildren()
|
|
345
346
|
this.execNewMounted()
|
|
346
347
|
|
|
@@ -381,7 +382,7 @@ export default class View {
|
|
|
381
382
|
if(newHook){ newHook.__mounted() }
|
|
382
383
|
}
|
|
383
384
|
|
|
384
|
-
performPatch(patch, pruneCids){
|
|
385
|
+
performPatch(patch, pruneCids, isJoinPatch = false){
|
|
385
386
|
let removedEls = []
|
|
386
387
|
let phxChildrenAdded = false
|
|
387
388
|
let updatedHookIds = new Set()
|
|
@@ -414,7 +415,7 @@ export default class View {
|
|
|
414
415
|
})
|
|
415
416
|
|
|
416
417
|
patch.after("transitionsDiscarded", els => this.afterElementsRemoved(els, pruneCids))
|
|
417
|
-
patch.perform()
|
|
418
|
+
patch.perform(isJoinPatch)
|
|
418
419
|
this.afterElementsRemoved(removedEls, pruneCids)
|
|
419
420
|
|
|
420
421
|
return phxChildrenAdded
|
|
@@ -808,7 +809,7 @@ export default class View {
|
|
|
808
809
|
targetComponentID(target, targetCtx, opts = {}){
|
|
809
810
|
if(isCid(targetCtx)){ return targetCtx }
|
|
810
811
|
|
|
811
|
-
let cidOrSelector = target.getAttribute(this.binding("target"))
|
|
812
|
+
let cidOrSelector = opts.target || target.getAttribute(this.binding("target"))
|
|
812
813
|
if(isCid(cidOrSelector)){
|
|
813
814
|
return parseInt(cidOrSelector)
|
|
814
815
|
} else if(targetCtx && (cidOrSelector !== null || opts.target)){
|
|
@@ -890,7 +891,7 @@ export default class View {
|
|
|
890
891
|
|
|
891
892
|
pushInput(inputEl, targetCtx, forceCid, phxEvent, opts, callback){
|
|
892
893
|
let uploads
|
|
893
|
-
let cid = isCid(forceCid) ? forceCid : this.targetComponentID(inputEl.form, targetCtx)
|
|
894
|
+
let cid = isCid(forceCid) ? forceCid : this.targetComponentID(inputEl.form, targetCtx, opts)
|
|
894
895
|
let refGenerator = () => this.putRef([inputEl, inputEl.form], "change", opts)
|
|
895
896
|
let formData
|
|
896
897
|
let meta = this.extractMeta(inputEl.form)
|
|
@@ -994,7 +995,7 @@ export default class View {
|
|
|
994
995
|
let cid = this.targetComponentID(formEl, targetCtx)
|
|
995
996
|
if(LiveUploader.hasUploadsInProgress(formEl)){
|
|
996
997
|
let [ref, _els] = refGenerator()
|
|
997
|
-
let push = () => this.pushFormSubmit(formEl,
|
|
998
|
+
let push = () => this.pushFormSubmit(formEl, targetCtx, phxEvent, submitter, opts, onReply)
|
|
998
999
|
return this.scheduleSubmit(formEl, ref, opts, push)
|
|
999
1000
|
} else if(LiveUploader.inputsAwaitingPreflight(formEl).length > 0){
|
|
1000
1001
|
let [ref, els] = refGenerator()
|
|
@@ -1062,13 +1063,25 @@ export default class View {
|
|
|
1062
1063
|
})
|
|
1063
1064
|
}
|
|
1064
1065
|
|
|
1065
|
-
dispatchUploads(name, filesOrBlobs){
|
|
1066
|
-
let
|
|
1066
|
+
dispatchUploads(targetCtx, name, filesOrBlobs){
|
|
1067
|
+
let targetElement = this.targetCtxElement(targetCtx) || this.el;
|
|
1068
|
+
let inputs = DOM.findUploadInputs(targetElement).filter(el => el.name === name)
|
|
1067
1069
|
if(inputs.length === 0){ logError(`no live file inputs found matching the name "${name}"`) }
|
|
1068
1070
|
else if(inputs.length > 1){ logError(`duplicate live file inputs found matching the name "${name}"`) }
|
|
1069
1071
|
else { DOM.dispatchEvent(inputs[0], PHX_TRACK_UPLOADS, {detail: {files: filesOrBlobs}}) }
|
|
1070
1072
|
}
|
|
1071
1073
|
|
|
1074
|
+
targetCtxElement(targetCtx) {
|
|
1075
|
+
if(isCid(targetCtx)){
|
|
1076
|
+
let [target] = DOM.findComponentNodeList(this.el, targetCtx)
|
|
1077
|
+
return target
|
|
1078
|
+
} else if(targetCtx) {
|
|
1079
|
+
return targetCtx
|
|
1080
|
+
} else {
|
|
1081
|
+
return null
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1072
1085
|
pushFormRecovery(form, newCid, callback){
|
|
1073
1086
|
this.liveSocket.withinOwners(form, (view, targetCtx) => {
|
|
1074
1087
|
let phxChange = this.binding("change")
|
|
@@ -53,11 +53,13 @@ export default class ViewHook {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
upload(name, files){
|
|
56
|
-
return this.__view.dispatchUploads(name, files)
|
|
56
|
+
return this.__view.dispatchUploads(null, name, files)
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
uploadTo(phxTarget, name, files){
|
|
60
|
-
return this.__view.withinTargets(phxTarget, view =>
|
|
60
|
+
return this.__view.withinTargets(phxTarget, (view, targetCtx) => {
|
|
61
|
+
view.dispatchUploads(targetCtx, name, files)
|
|
62
|
+
})
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
__cleanup__(){
|
package/assets/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phoenix_live_view",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.2",
|
|
4
4
|
"description": "The Phoenix LiveView JavaScript client.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {},
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"test.watch": "jest --watch"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"morphdom": "2.7.
|
|
13
|
+
"morphdom": "2.7.1"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@babel/cli": "7.14.3",
|