phoenix_live_view 0.17.7 → 0.17.8
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/CHANGELOG.md +23 -7
- package/README.md +1 -1
- package/assets/js/phoenix_live_view/dom.js +5 -3
- package/assets/js/phoenix_live_view/js.js +13 -10
- package/assets/js/phoenix_live_view/live_socket.js +33 -22
- package/assets/js/phoenix_live_view/view.js +20 -8
- package/assets/js/phoenix_live_view/view_hook.js +1 -1
- package/package.json +1 -1
- package/priv/static/phoenix_live_view.cjs.js +69 -46
- package/priv/static/phoenix_live_view.cjs.js.map +2 -2
- package/priv/static/phoenix_live_view.esm.js +69 -46
- package/priv/static/phoenix_live_view.esm.js.map +2 -2
- package/priv/static/phoenix_live_view.js +69 -46
- package/priv/static/phoenix_live_view.min.js +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.17.8 (2022-04-06)
|
|
4
|
+
|
|
5
|
+
### Enhancements
|
|
6
|
+
- Add HEEx formatter
|
|
7
|
+
- Support `phx-change` on individual inputs
|
|
8
|
+
- Dispatch `MouseEvent` on client
|
|
9
|
+
- Add `:bubbles` option to `JS.dispatch` to control event bubbling
|
|
10
|
+
- Expose underlying `liveSocket` instance on hooks
|
|
11
|
+
- Enable client debug by default on localhost
|
|
12
|
+
|
|
13
|
+
### Bug fixes
|
|
14
|
+
- Fix hook and sticky LiveView issues caused by back-to-back live redirects from mount
|
|
15
|
+
- Fix hook destroyed callback failing to be invoked for children of phx-remove in some cases
|
|
16
|
+
- Do not failsafe reload the page on push timeout if disconnected
|
|
17
|
+
- Do not bubble navigation click events to regular phx-click's
|
|
18
|
+
|
|
3
19
|
## 0.17.7 (2022-02-07)
|
|
4
20
|
|
|
5
21
|
### Enhancements
|
|
@@ -200,7 +216,7 @@ Some functionality that was previously deprecated has been removed:
|
|
|
200
216
|
|
|
201
217
|
## 0.16.0 (2021-08-10)
|
|
202
218
|
|
|
203
|
-
|
|
219
|
+
### Security Considerations Upgrading from 0.15
|
|
204
220
|
|
|
205
221
|
LiveView v0.16 optimizes live redirects by supporting navigation purely
|
|
206
222
|
over the existing WebSocket connection. This is accomplished by the new
|
|
@@ -615,7 +631,7 @@ as LiveView introduces a macro with that name and it is special cased by the und
|
|
|
615
631
|
- No longer send event metadata by default. Metadata is now opt-in and user defined at the `LiveSocket` level.
|
|
616
632
|
To maintain backwards compatibility with pre-0.13 behaviour, you can provide the following metadata option:
|
|
617
633
|
|
|
618
|
-
```
|
|
634
|
+
```
|
|
619
635
|
let liveSocket = new LiveSocket("/live", Socket, {
|
|
620
636
|
params: {_csrf_token: csrfToken},
|
|
621
637
|
metadata: {
|
|
@@ -714,7 +730,7 @@ The new implementation will check there is a button at `#term .buttons a`, with
|
|
|
714
730
|
- `Phoenix.LiveViewTest.assert_remove/3` has been removed. If the LiveView crashes, it will cause the test to crash too
|
|
715
731
|
- Passing a path with DOM IDs to `render_*` test functions is deprecated. Furthermore, they now require a `phx-target="<%= @id %>"` on the given DOM ID:
|
|
716
732
|
|
|
717
|
-
```
|
|
733
|
+
```heex
|
|
718
734
|
<div id="component-id" phx-target="component-id">
|
|
719
735
|
...
|
|
720
736
|
</div>
|
|
@@ -931,14 +947,14 @@ The steps are:
|
|
|
931
947
|
|
|
932
948
|
4) You should define the CSRF meta tag inside <head> in your layout, before `app.js` is included:
|
|
933
949
|
|
|
934
|
-
```
|
|
950
|
+
```heex
|
|
935
951
|
<%= csrf_meta_tag() %>
|
|
936
952
|
<script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
|
|
937
953
|
```
|
|
938
954
|
|
|
939
955
|
5) Then in your app.js:
|
|
940
956
|
|
|
941
|
-
```
|
|
957
|
+
```
|
|
942
958
|
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");
|
|
943
959
|
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}});
|
|
944
960
|
```
|
|
@@ -1019,7 +1035,7 @@ Also note that **the session from now on will have string keys**. LiveView will
|
|
|
1019
1035
|
- All `phx-update` containers now require a unique ID
|
|
1020
1036
|
- `LiveSocket` JavaScript constructor now requires explicit dependency injection of Phoenix Socket constructor. For example:
|
|
1021
1037
|
|
|
1022
|
-
```
|
|
1038
|
+
```
|
|
1023
1039
|
import {Socket} from "phoenix"
|
|
1024
1040
|
import LiveSocket from "phoenix_live_view"
|
|
1025
1041
|
|
|
@@ -1035,7 +1051,7 @@ let liveSocket = new LiveSocket("/live", Socket, {...})
|
|
|
1035
1051
|
- Fix params failing to update on re-mounts after live_redirect
|
|
1036
1052
|
- Fix blur event metadata being sent with type of `"focus"`
|
|
1037
1053
|
|
|
1038
|
-
## 0.1.2
|
|
1054
|
+
## 0.1.2 (2019-08-28)
|
|
1039
1055
|
|
|
1040
1056
|
### Backwards incompatible changes
|
|
1041
1057
|
- `phx-value` has no effect, use `phx-value-*` instead
|
package/README.md
CHANGED
|
@@ -125,7 +125,7 @@ $ npm install --save --prefix assets mdn-polyfills url-search-params-polyfill fo
|
|
|
125
125
|
|
|
126
126
|
Note: The `shim-keyboard-event-key` polyfill is also required for [MS Edge 12-18](https://caniuse.com/#feat=keyboardevent-key).
|
|
127
127
|
|
|
128
|
-
```
|
|
128
|
+
```
|
|
129
129
|
// assets/js/app.js
|
|
130
130
|
import "mdn-polyfills/Object.assign"
|
|
131
131
|
import "mdn-polyfills/CustomEvent"
|
|
@@ -250,8 +250,10 @@ let DOM = {
|
|
|
250
250
|
return this.isPhxChild(el) ? el : this.all(el, `[${PHX_PARENT_ID}]`)[0]
|
|
251
251
|
},
|
|
252
252
|
|
|
253
|
-
dispatchEvent(target,
|
|
254
|
-
let
|
|
253
|
+
dispatchEvent(target, name, opts = {}){
|
|
254
|
+
let bubbles = opts.bubbles === undefined ? true : !!opts.bubbles
|
|
255
|
+
let eventOpts = {bubbles: bubbles, cancelable: true, detail: opts.detail || {}}
|
|
256
|
+
let event = name === "click" ? new MouseEvent("click", eventOpts) : new CustomEvent(name, eventOpts)
|
|
255
257
|
target.dispatchEvent(event)
|
|
256
258
|
},
|
|
257
259
|
|
|
@@ -287,7 +289,7 @@ let DOM = {
|
|
|
287
289
|
|
|
288
290
|
mergeFocusedInput(target, source){
|
|
289
291
|
// skip selects because FF will reset highlighted index for any setAttribute
|
|
290
|
-
if(!(target instanceof HTMLSelectElement)){ DOM.mergeAttrs(target, source, {
|
|
292
|
+
if(!(target instanceof HTMLSelectElement)){ DOM.mergeAttrs(target, source, {exclude: ["value"]}) }
|
|
291
293
|
if(source.readOnly){
|
|
292
294
|
target.setAttribute("readonly", true)
|
|
293
295
|
} else {
|
|
@@ -24,18 +24,23 @@ let JS = {
|
|
|
24
24
|
|
|
25
25
|
// commands
|
|
26
26
|
|
|
27
|
-
exec_dispatch(eventType, phxEvent, view, sourceEl, el, {to, event, detail}){
|
|
28
|
-
|
|
27
|
+
exec_dispatch(eventType, phxEvent, view, sourceEl, el, {to, event, detail, bubbles}){
|
|
28
|
+
detail = detail || {}
|
|
29
|
+
detail.dispatcher = sourceEl
|
|
30
|
+
DOM.dispatchEvent(el, event, {detail, bubbles})
|
|
29
31
|
},
|
|
30
32
|
|
|
31
33
|
exec_push(eventType, phxEvent, view, sourceEl, el, args){
|
|
32
|
-
|
|
34
|
+
if(!view.isConnected()){ return }
|
|
35
|
+
|
|
36
|
+
let {event, data, target, page_loading, loading, value, dispatcher} = args
|
|
33
37
|
let pushOpts = {loading, value, target, page_loading: !!page_loading}
|
|
34
|
-
let targetSrc = eventType === "change" ?
|
|
38
|
+
let targetSrc = eventType === "change" && dispatcher ? dispatcher : sourceEl
|
|
35
39
|
let phxTarget = target || targetSrc.getAttribute(view.binding("target")) || targetSrc
|
|
36
40
|
view.withinTargets(phxTarget, (targetView, targetCtx) => {
|
|
37
41
|
if(eventType === "change"){
|
|
38
42
|
let {newCid, _target, callback} = args
|
|
43
|
+
_target = _target || (sourceEl instanceof HTMLInputElement ? sourceEl.name : undefined)
|
|
39
44
|
if(_target){ pushOpts._target = _target }
|
|
40
45
|
targetView.pushInput(sourceEl, targetCtx, newCid, event || phxEvent, pushOpts, callback)
|
|
41
46
|
} else if(eventType === "submit"){
|
|
@@ -170,10 +175,10 @@ let JS = {
|
|
|
170
175
|
|
|
171
176
|
setOrRemoveAttrs(el, sets, removes){
|
|
172
177
|
let [prevSets, prevRemoves] = DOM.getSticky(el, "attrs", [[], []])
|
|
173
|
-
|
|
174
|
-
let
|
|
175
|
-
let newSets = prevSets.filter(([attr, _val]) =>
|
|
176
|
-
let newRemoves = prevRemoves.filter(attr => !
|
|
178
|
+
|
|
179
|
+
let alteredAttrs = sets.map(([attr, _val]) => attr).concat(removes);
|
|
180
|
+
let newSets = prevSets.filter(([attr, _val]) => !alteredAttrs.includes(attr)).concat(sets);
|
|
181
|
+
let newRemoves = prevRemoves.filter((attr) => !alteredAttrs.includes(attr)).concat(removes);
|
|
177
182
|
|
|
178
183
|
DOM.putSticky(el, "attrs", currentEl => {
|
|
179
184
|
newRemoves.forEach(attr => currentEl.removeAttribute(attr))
|
|
@@ -182,8 +187,6 @@ let JS = {
|
|
|
182
187
|
})
|
|
183
188
|
},
|
|
184
189
|
|
|
185
|
-
hasSet(sets, nameSearch){ return sets.find(([name, val]) => name === nameSearch) },
|
|
186
|
-
|
|
187
190
|
hasAllClasses(el, classes){ return classes.every(name => el.classList.contains(name)) },
|
|
188
191
|
|
|
189
192
|
isToggledOut(el, outClasses){
|
|
@@ -138,8 +138,9 @@ export default class LiveSocket {
|
|
|
138
138
|
this.prevActive = null
|
|
139
139
|
this.silenced = false
|
|
140
140
|
this.main = null
|
|
141
|
+
this.outgoingMainEl = null
|
|
142
|
+
this.clickStartedAtTarget = null
|
|
141
143
|
this.linkRef = 1
|
|
142
|
-
this.clickRef = 1
|
|
143
144
|
this.roots = {}
|
|
144
145
|
this.href = window.location.href
|
|
145
146
|
this.pendingLink = null
|
|
@@ -173,11 +174,13 @@ export default class LiveSocket {
|
|
|
173
174
|
|
|
174
175
|
isDebugEnabled(){ return this.sessionStorage.getItem(PHX_LV_DEBUG) === "true" }
|
|
175
176
|
|
|
177
|
+
isDebugDisabled(){ return this.sessionStorage.getItem(PHX_LV_DEBUG) === "false" }
|
|
178
|
+
|
|
176
179
|
enableDebug(){ this.sessionStorage.setItem(PHX_LV_DEBUG, "true") }
|
|
177
180
|
|
|
178
181
|
enableProfiling(){ this.sessionStorage.setItem(PHX_LV_PROFILE, "true") }
|
|
179
182
|
|
|
180
|
-
disableDebug(){ this.sessionStorage.
|
|
183
|
+
disableDebug(){ this.sessionStorage.setItem(PHX_LV_DEBUG, "false") }
|
|
181
184
|
|
|
182
185
|
disableProfiling(){ this.sessionStorage.removeItem(PHX_LV_PROFILE) }
|
|
183
186
|
|
|
@@ -197,6 +200,8 @@ export default class LiveSocket {
|
|
|
197
200
|
getSocket(){ return this.socket }
|
|
198
201
|
|
|
199
202
|
connect(){
|
|
203
|
+
// enable debug by default if on localhost and not explicitly disabled
|
|
204
|
+
if(window.location.hostname === "localhost" && !this.isDebugDisabled()){ this.enableDebug() }
|
|
200
205
|
let doConnect = () => {
|
|
201
206
|
if(this.joinRootViews()){
|
|
202
207
|
this.bindTopLevelEvents()
|
|
@@ -262,7 +267,7 @@ export default class LiveSocket {
|
|
|
262
267
|
let latency = this.getLatencySim()
|
|
263
268
|
let oldJoinCount = view.joinCount
|
|
264
269
|
if(!latency){
|
|
265
|
-
if(opts.timeout){
|
|
270
|
+
if(this.isConnected() && opts.timeout){
|
|
266
271
|
return push().receive("timeout", () => {
|
|
267
272
|
if(view.joinCount === oldJoinCount && !view.isDestroyed()){
|
|
268
273
|
this.reloadWithJitter(view, () => {
|
|
@@ -342,8 +347,8 @@ export default class LiveSocket {
|
|
|
342
347
|
}
|
|
343
348
|
|
|
344
349
|
replaceMain(href, flash, callback = null, linkRef = this.setPendingLink(href)){
|
|
345
|
-
|
|
346
|
-
let newMainEl = DOM.cloneNode(
|
|
350
|
+
this.outgoingMainEl = this.outgoingMainEl || this.main.el
|
|
351
|
+
let newMainEl = DOM.cloneNode(this.outgoingMainEl, "")
|
|
347
352
|
this.main.showLoader(this.loaderTimeout)
|
|
348
353
|
this.main.destroy()
|
|
349
354
|
|
|
@@ -354,7 +359,8 @@ export default class LiveSocket {
|
|
|
354
359
|
if(joinCount === 1 && this.commitPendingLink(linkRef)){
|
|
355
360
|
this.requestDOMUpdate(() => {
|
|
356
361
|
DOM.findPhxSticky(document).forEach(el => newMainEl.appendChild(el))
|
|
357
|
-
|
|
362
|
+
this.outgoingMainEl.replaceWith(newMainEl)
|
|
363
|
+
this.outgoingMainEl = null
|
|
358
364
|
callback && callback()
|
|
359
365
|
onDone()
|
|
360
366
|
})
|
|
@@ -457,7 +463,7 @@ export default class LiveSocket {
|
|
|
457
463
|
this.boundTopLevelEvents = true
|
|
458
464
|
// enter failsafe reload if server has gone away intentionally, such as "disconnect" broadcast
|
|
459
465
|
this.socket.onClose(event => {
|
|
460
|
-
if(event.code === 1000 && this.main){
|
|
466
|
+
if(event && event.code === 1000 && this.main){
|
|
461
467
|
this.reloadWithJitter(this.main)
|
|
462
468
|
}
|
|
463
469
|
})
|
|
@@ -569,6 +575,7 @@ export default class LiveSocket {
|
|
|
569
575
|
}
|
|
570
576
|
|
|
571
577
|
bindClicks(){
|
|
578
|
+
window.addEventListener("mousedown", e => this.clickStartedAtTarget = e.target)
|
|
572
579
|
this.bindClick("click", "click", false)
|
|
573
580
|
this.bindClick("mousedown", "capture-click", true)
|
|
574
581
|
}
|
|
@@ -576,15 +583,14 @@ export default class LiveSocket {
|
|
|
576
583
|
bindClick(eventName, bindingName, capture){
|
|
577
584
|
let click = this.binding(bindingName)
|
|
578
585
|
window.addEventListener(eventName, e => {
|
|
579
|
-
if(!this.isConnected()){ return }
|
|
580
|
-
this.clickRef++
|
|
581
|
-
let clickRefWas = this.clickRef
|
|
582
586
|
let target = null
|
|
583
587
|
if(capture){
|
|
584
588
|
target = e.target.matches(`[${click}]`) ? e.target : e.target.querySelector(`[${click}]`)
|
|
585
589
|
} else {
|
|
586
|
-
|
|
587
|
-
|
|
590
|
+
let clickStartedAtTarget = this.clickStartedAtTarget || e.target
|
|
591
|
+
target = closestPhxBinding(clickStartedAtTarget, click)
|
|
592
|
+
this.dispatchClickAway(e, clickStartedAtTarget)
|
|
593
|
+
this.clickStartedAtTarget = null
|
|
588
594
|
}
|
|
589
595
|
let phxEvent = target && target.getAttribute(click)
|
|
590
596
|
if(!phxEvent){ return }
|
|
@@ -598,15 +604,13 @@ export default class LiveSocket {
|
|
|
598
604
|
}, capture)
|
|
599
605
|
}
|
|
600
606
|
|
|
601
|
-
dispatchClickAway(e,
|
|
607
|
+
dispatchClickAway(e, clickStartedAt){
|
|
602
608
|
let phxClickAway = this.binding("click-away")
|
|
603
|
-
let phxClick = this.binding("click")
|
|
604
609
|
DOM.all(document, `[${phxClickAway}]`, el => {
|
|
605
|
-
if(!(el.isSameNode(
|
|
610
|
+
if(!(el.isSameNode(clickStartedAt) || el.contains(clickStartedAt))){
|
|
606
611
|
this.withinOwners(e.target, view => {
|
|
607
612
|
let phxEvent = el.getAttribute(phxClickAway)
|
|
608
613
|
if(JS.isVisible(el)){
|
|
609
|
-
let target = e.target.closest(`[${phxClick}]`) || e.target
|
|
610
614
|
JS.exec("click", phxEvent, view, el, ["push", {data: this.eventMeta("click", e, e.target)}])
|
|
611
615
|
}
|
|
612
616
|
})
|
|
@@ -649,9 +653,11 @@ export default class LiveSocket {
|
|
|
649
653
|
let type = target && target.getAttribute(PHX_LIVE_LINK)
|
|
650
654
|
let wantsNewTab = e.metaKey || e.ctrlKey || e.button === 1
|
|
651
655
|
if(!type || !this.isConnected() || !this.main || wantsNewTab){ return }
|
|
656
|
+
|
|
652
657
|
let href = target.href
|
|
653
658
|
let linkState = target.getAttribute(PHX_LINK_STATE)
|
|
654
659
|
e.preventDefault()
|
|
660
|
+
e.stopImmediatePropagation() // do not bubble click to regular phx-click bindings
|
|
655
661
|
if(this.pendingLink === href){ return }
|
|
656
662
|
|
|
657
663
|
this.requestDOMUpdate(() => {
|
|
@@ -667,7 +673,7 @@ export default class LiveSocket {
|
|
|
667
673
|
}
|
|
668
674
|
|
|
669
675
|
dispatchEvent(event, payload = {}){
|
|
670
|
-
DOM.dispatchEvent(window, `phx:${event}`, payload)
|
|
676
|
+
DOM.dispatchEvent(window, `phx:${event}`, {detail: payload})
|
|
671
677
|
}
|
|
672
678
|
|
|
673
679
|
dispatchEvents(events){
|
|
@@ -675,8 +681,8 @@ export default class LiveSocket {
|
|
|
675
681
|
}
|
|
676
682
|
|
|
677
683
|
withPageLoading(info, callback){
|
|
678
|
-
DOM.dispatchEvent(window, "phx:page-loading-start", info)
|
|
679
|
-
let done = () => DOM.dispatchEvent(window, "phx:page-loading-stop", info)
|
|
684
|
+
DOM.dispatchEvent(window, "phx:page-loading-start", {detail: info})
|
|
685
|
+
let done = () => DOM.dispatchEvent(window, "phx:page-loading-stop", {detail: info})
|
|
680
686
|
return callback ? callback(done) : done
|
|
681
687
|
}
|
|
682
688
|
|
|
@@ -735,10 +741,15 @@ export default class LiveSocket {
|
|
|
735
741
|
|
|
736
742
|
for(let type of ["change", "input"]){
|
|
737
743
|
this.on(type, e => {
|
|
744
|
+
let phxChange = this.binding("change")
|
|
738
745
|
let input = e.target
|
|
739
|
-
let
|
|
746
|
+
let inputEvent = input.getAttribute(phxChange)
|
|
747
|
+
let formEvent = input.form && input.form.getAttribute(phxChange)
|
|
748
|
+
let phxEvent = inputEvent || formEvent
|
|
740
749
|
if(!phxEvent){ return }
|
|
741
750
|
if(input.type === "number" && input.validity && input.validity.badInput){ return }
|
|
751
|
+
|
|
752
|
+
let dispatcher = inputEvent ? input : input.form
|
|
742
753
|
let currentIterations = iterations
|
|
743
754
|
iterations++
|
|
744
755
|
let {at: at, type: lastType} = DOM.private(input, "prev-iteration") || {}
|
|
@@ -748,12 +759,12 @@ export default class LiveSocket {
|
|
|
748
759
|
DOM.putPrivate(input, "prev-iteration", {at: currentIterations, type: type})
|
|
749
760
|
|
|
750
761
|
this.debounce(input, e, () => {
|
|
751
|
-
this.withinOwners(
|
|
762
|
+
this.withinOwners(dispatcher, view => {
|
|
752
763
|
DOM.putPrivate(input, PHX_HAS_FOCUSED, true)
|
|
753
764
|
if(!DOM.isTextualInput(input)){
|
|
754
765
|
this.setActiveElement(input)
|
|
755
766
|
}
|
|
756
|
-
JS.exec("change", phxEvent, view, input, ["push", {_target: e.target.name}])
|
|
767
|
+
JS.exec("change", phxEvent, view, input, ["push", {_target: e.target.name, dispatcher: dispatcher}])
|
|
757
768
|
})
|
|
758
769
|
})
|
|
759
770
|
}, false)
|
|
@@ -50,7 +50,7 @@ import Rendered from "./rendered"
|
|
|
50
50
|
import ViewHook from "./view_hook"
|
|
51
51
|
import JS from "./js"
|
|
52
52
|
|
|
53
|
-
let serializeForm = (form, meta =
|
|
53
|
+
let serializeForm = (form, meta, onlyNames = []) => {
|
|
54
54
|
let formData = new FormData(form)
|
|
55
55
|
let toRemove = []
|
|
56
56
|
|
|
@@ -62,7 +62,11 @@ let serializeForm = (form, meta = {}) => {
|
|
|
62
62
|
toRemove.forEach(key => formData.delete(key))
|
|
63
63
|
|
|
64
64
|
let params = new URLSearchParams()
|
|
65
|
-
for(let [key, val] of formData.entries()){
|
|
65
|
+
for(let [key, val] of formData.entries()){
|
|
66
|
+
if(onlyNames.length === 0 || onlyNames.indexOf(key) >= 0){
|
|
67
|
+
params.append(key, val)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
66
70
|
for(let metaKey in meta){ params.append(metaKey, meta[metaKey]) }
|
|
67
71
|
|
|
68
72
|
return params.toString()
|
|
@@ -377,10 +381,13 @@ export default class View {
|
|
|
377
381
|
let destroyedCIDs = []
|
|
378
382
|
elements.forEach(parent => {
|
|
379
383
|
let components = DOM.all(parent, `[${PHX_COMPONENT}]`)
|
|
380
|
-
|
|
384
|
+
let hooks = DOM.all(parent, `[${this.binding(PHX_HOOK)}]`)
|
|
385
|
+
components.concat(parent).forEach(el => {
|
|
381
386
|
let cid = this.componentID(el)
|
|
382
387
|
if(isCid(cid) && destroyedCIDs.indexOf(cid) === -1){ destroyedCIDs.push(cid) }
|
|
383
|
-
|
|
388
|
+
})
|
|
389
|
+
hooks.concat(parent).forEach(hookEl => {
|
|
390
|
+
let hook = this.getHook(hookEl)
|
|
384
391
|
hook && this.destroyHook(hook)
|
|
385
392
|
})
|
|
386
393
|
})
|
|
@@ -635,7 +642,7 @@ export default class View {
|
|
|
635
642
|
}
|
|
636
643
|
|
|
637
644
|
displayError(){
|
|
638
|
-
if(this.isMain()){ DOM.dispatchEvent(window, "phx:page-loading-start", {to: this.href, kind: "error"}) }
|
|
645
|
+
if(this.isMain()){ DOM.dispatchEvent(window, "phx:page-loading-start", {detail: {to: this.href, kind: "error"}}) }
|
|
639
646
|
this.showLoader()
|
|
640
647
|
this.setContainerClasses(PHX_DISCONNECTED_CLASS, PHX_ERROR_CLASS)
|
|
641
648
|
}
|
|
@@ -821,7 +828,12 @@ export default class View {
|
|
|
821
828
|
let uploads
|
|
822
829
|
let cid = isCid(forceCid) ? forceCid : this.targetComponentID(inputEl.form, targetCtx)
|
|
823
830
|
let refGenerator = () => this.putRef([inputEl, inputEl.form], "change", opts)
|
|
824
|
-
let formData
|
|
831
|
+
let formData
|
|
832
|
+
if(inputEl.getAttribute(this.binding("change"))){
|
|
833
|
+
formData = serializeForm(inputEl.form, {_target: opts._target}, [inputEl.name])
|
|
834
|
+
} else {
|
|
835
|
+
formData = serializeForm(inputEl.form, {_target: opts._target})
|
|
836
|
+
}
|
|
825
837
|
if(DOM.isUploadInput(inputEl) && inputEl.files && inputEl.files.length > 0){
|
|
826
838
|
LiveUploader.trackFiles(inputEl, Array.from(inputEl.files))
|
|
827
839
|
}
|
|
@@ -930,7 +942,7 @@ export default class View {
|
|
|
930
942
|
}, onReply)
|
|
931
943
|
})
|
|
932
944
|
} else {
|
|
933
|
-
let formData = serializeForm(formEl)
|
|
945
|
+
let formData = serializeForm(formEl, {})
|
|
934
946
|
this.pushWithReply(refGenerator, "event", {
|
|
935
947
|
type: "form",
|
|
936
948
|
event: phxEvent,
|
|
@@ -985,7 +997,7 @@ export default class View {
|
|
|
985
997
|
let inputs = DOM.findUploadInputs(this.el).filter(el => el.name === name)
|
|
986
998
|
if(inputs.length === 0){ logError(`no live file inputs found matching the name "${name}"`) }
|
|
987
999
|
else if(inputs.length > 1){ logError(`duplicate live file inputs found matching the name "${name}"`) }
|
|
988
|
-
else { DOM.dispatchEvent(inputs[0], PHX_TRACK_UPLOADS, {files: filesOrBlobs}) }
|
|
1000
|
+
else { DOM.dispatchEvent(inputs[0], PHX_TRACK_UPLOADS, {detail: {files: filesOrBlobs}}) }
|
|
989
1001
|
}
|
|
990
1002
|
|
|
991
1003
|
pushFormRecovery(form, newCid, callback){
|