phoenix_live_view 1.0.0 → 1.0.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 +3 -1
- package/assets/js/phoenix_live_view/dom.js +1 -1
- package/assets/js/phoenix_live_view/dom_patch.js +12 -1
- package/assets/js/phoenix_live_view/js.js +9 -2
- package/assets/js/phoenix_live_view/live_socket.js +5 -1
- package/assets/js/phoenix_live_view/view.js +12 -4
- package/assets/package.json +1 -1
- package/package.json +1 -1
- package/priv/static/phoenix_live_view.cjs.js +33 -10
- package/priv/static/phoenix_live_view.cjs.js.map +2 -2
- package/priv/static/phoenix_live_view.esm.js +33 -10
- package/priv/static/phoenix_live_view.esm.js.map +2 -2
- package/priv/static/phoenix_live_view.js +34 -10
- package/priv/static/phoenix_live_view.min.js +5 -5
package/README.md
CHANGED
|
@@ -62,6 +62,8 @@ Also follow these announcements from the Phoenix team on LiveView for more examp
|
|
|
62
62
|
|
|
63
63
|
* [Build a real-time Twitter clone with LiveView](https://www.phoenixframework.org/blog/build-a-real-time-twitter-clone-in-15-minutes-with-live-view-and-phoenix-1-5)
|
|
64
64
|
|
|
65
|
+
* [Build a real-time Twitch clone with LiveView and Elixir WebRTC](https://www.youtube.com/watch?v=jziOb2Edfzk)
|
|
66
|
+
|
|
65
67
|
* [Initial announcement](https://dockyard.com/blog/2018/12/12/phoenix-liveview-interactive-real-time-apps-no-need-to-write-javascript)
|
|
66
68
|
|
|
67
69
|
## Component systems
|
|
@@ -74,7 +76,7 @@ community at different stages of development:
|
|
|
74
76
|
|
|
75
77
|
* [Doggo](https://github.com/woylie/doggo): Headless UI components for Phoenix
|
|
76
78
|
|
|
77
|
-
* [Petal Components](https://github.com/petalframework/petal_components): Phoenix + Live View HEEX Components
|
|
79
|
+
* [Petal Components](https://github.com/petalframework/petal_components): Phoenix + Live View HEEX Components
|
|
78
80
|
|
|
79
81
|
* [PrimerLive](https://github.com/ArthurClemens/primer_live): An implementation of GitHub's Primer Design System using Phoenix LiveView
|
|
80
82
|
|
|
@@ -467,7 +467,7 @@ let DOM = {
|
|
|
467
467
|
isTextualInput(el){ return FOCUSABLE_INPUTS.indexOf(el.type) >= 0 },
|
|
468
468
|
|
|
469
469
|
isNowTriggerFormExternal(el, phxTriggerExternal){
|
|
470
|
-
return el.getAttribute && el.getAttribute(phxTriggerExternal) !== null
|
|
470
|
+
return el.getAttribute && el.getAttribute(phxTriggerExternal) !== null && document.body.contains(el)
|
|
471
471
|
},
|
|
472
472
|
|
|
473
473
|
cleanChildNodes(container, phxUpdate){
|
|
@@ -30,6 +30,7 @@ export default class DOMPatch {
|
|
|
30
30
|
let focused = liveSocket.getActiveElement()
|
|
31
31
|
let {selectionStart, selectionEnd} = focused && DOM.hasSelectionRange(focused) ? focused : {}
|
|
32
32
|
let phxUpdate = liveSocket.binding(PHX_UPDATE)
|
|
33
|
+
let externalFormTriggered = null
|
|
33
34
|
|
|
34
35
|
morphdom(container, clonedTree, {
|
|
35
36
|
childrenOnly: false,
|
|
@@ -42,9 +43,19 @@ export default class DOMPatch {
|
|
|
42
43
|
DOM.mergeFocusedInput(fromEl, toEl)
|
|
43
44
|
return false
|
|
44
45
|
}
|
|
46
|
+
if(DOM.isNowTriggerFormExternal(toEl, liveSocket.binding(PHX_TRIGGER_ACTION))){
|
|
47
|
+
externalFormTriggered = toEl
|
|
48
|
+
}
|
|
45
49
|
}
|
|
46
50
|
})
|
|
47
51
|
|
|
52
|
+
if(externalFormTriggered){
|
|
53
|
+
liveSocket.unload()
|
|
54
|
+
// use prototype's submit in case there's a form control with name or id of "submit"
|
|
55
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit
|
|
56
|
+
Object.getPrototypeOf(externalFormTriggered).submit.call(externalFormTriggered)
|
|
57
|
+
}
|
|
58
|
+
|
|
48
59
|
liveSocket.silenceEvents(() => DOM.restoreFocus(focused, selectionStart, selectionEnd))
|
|
49
60
|
}
|
|
50
61
|
|
|
@@ -228,7 +239,7 @@ export default class DOMPatch {
|
|
|
228
239
|
return false
|
|
229
240
|
}
|
|
230
241
|
if(fromEl.type === "number" && (fromEl.validity && fromEl.validity.badInput)){ return false }
|
|
231
|
-
// If the element has
|
|
242
|
+
// If the element has PHX_REF_SRC, it is loading or locked and awaiting an ack.
|
|
232
243
|
// If it's locked, we clone the fromEl tree and instruct morphdom to use
|
|
233
244
|
// the cloned tree as the source of the morph for this branch from here on out.
|
|
234
245
|
// We keep a reference to the cloned tree in the element's private data, and
|
|
@@ -65,7 +65,7 @@ let JS = {
|
|
|
65
65
|
let pushOpts = {loading, value, target, page_loading: !!page_loading}
|
|
66
66
|
let targetSrc = eventType === "change" && dispatcher ? dispatcher : sourceEl
|
|
67
67
|
let phxTarget = target || targetSrc.getAttribute(view.binding("target")) || targetSrc
|
|
68
|
-
|
|
68
|
+
const handler = (targetView, targetCtx) => {
|
|
69
69
|
if(!targetView.isConnected()){ return }
|
|
70
70
|
if(eventType === "change"){
|
|
71
71
|
let {newCid, _target} = args
|
|
@@ -78,7 +78,14 @@ let JS = {
|
|
|
78
78
|
} else {
|
|
79
79
|
targetView.pushEvent(eventType, sourceEl, targetCtx, event || phxEvent, data, pushOpts, callback)
|
|
80
80
|
}
|
|
81
|
-
}
|
|
81
|
+
}
|
|
82
|
+
// in case of formRecovery, targetView and targetCtx are passed as argument
|
|
83
|
+
// as they are looked up in a template element, not the real DOM
|
|
84
|
+
if(args.targetView && args.targetCtx){
|
|
85
|
+
handler(args.targetView, args.targetCtx)
|
|
86
|
+
} else {
|
|
87
|
+
view.withinTargets(phxTarget, handler)
|
|
88
|
+
}
|
|
82
89
|
},
|
|
83
90
|
|
|
84
91
|
exec_navigate(e, eventType, phxEvent, view, sourceEl, el, {href, replace}){
|
|
@@ -362,7 +362,11 @@ export default class LiveSocket {
|
|
|
362
362
|
view.setHref(this.getHref())
|
|
363
363
|
view.joinDead()
|
|
364
364
|
if(!this.main){ this.main = view }
|
|
365
|
-
window.requestAnimationFrame(() =>
|
|
365
|
+
window.requestAnimationFrame(() => {
|
|
366
|
+
view.execNewMounted()
|
|
367
|
+
// restore scroll position when navigating from an external / non-live page
|
|
368
|
+
this.maybeScroll(history.state?.scroll)
|
|
369
|
+
})
|
|
366
370
|
}
|
|
367
371
|
}
|
|
368
372
|
|
|
@@ -54,6 +54,7 @@ import DOMPatch from "./dom_patch"
|
|
|
54
54
|
import LiveUploader from "./live_uploader"
|
|
55
55
|
import Rendered from "./rendered"
|
|
56
56
|
import ViewHook from "./view_hook"
|
|
57
|
+
import JS from "./js"
|
|
57
58
|
|
|
58
59
|
export let prependFormDataKey = (key, prefix) => {
|
|
59
60
|
let isArray = key.endsWith("[]")
|
|
@@ -1413,10 +1414,17 @@ export default class View {
|
|
|
1413
1414
|
this.withinTargets(phxTarget, (targetView, targetCtx) => {
|
|
1414
1415
|
const cid = this.targetComponentID(newForm, targetCtx)
|
|
1415
1416
|
pending++
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1417
|
+
let e = new CustomEvent("phx:form-recovery", {detail: {sourceElement: oldForm}})
|
|
1418
|
+
JS.exec(e, "change", phxEvent, this, input, ["push", {
|
|
1419
|
+
_target: input.name,
|
|
1420
|
+
targetView,
|
|
1421
|
+
targetCtx,
|
|
1422
|
+
newCid: cid,
|
|
1423
|
+
callback: () => {
|
|
1424
|
+
pending--
|
|
1425
|
+
if(pending === 0){ callback() }
|
|
1426
|
+
}
|
|
1427
|
+
}])
|
|
1420
1428
|
}, templateDom, templateDom)
|
|
1421
1429
|
}
|
|
1422
1430
|
|
package/assets/package.json
CHANGED
package/package.json
CHANGED
|
@@ -727,7 +727,7 @@ var DOM = {
|
|
|
727
727
|
return FOCUSABLE_INPUTS.indexOf(el.type) >= 0;
|
|
728
728
|
},
|
|
729
729
|
isNowTriggerFormExternal(el, phxTriggerExternal) {
|
|
730
|
-
return el.getAttribute && el.getAttribute(phxTriggerExternal) !== null;
|
|
730
|
+
return el.getAttribute && el.getAttribute(phxTriggerExternal) !== null && document.body.contains(el);
|
|
731
731
|
},
|
|
732
732
|
cleanChildNodes(container, phxUpdate) {
|
|
733
733
|
if (DOM.isPhxUpdate(container, phxUpdate, ["append", "prepend"])) {
|
|
@@ -1942,6 +1942,7 @@ var DOMPatch = class {
|
|
|
1942
1942
|
let focused = liveSocket.getActiveElement();
|
|
1943
1943
|
let { selectionStart, selectionEnd } = focused && dom_default.hasSelectionRange(focused) ? focused : {};
|
|
1944
1944
|
let phxUpdate = liveSocket.binding(PHX_UPDATE);
|
|
1945
|
+
let externalFormTriggered = null;
|
|
1945
1946
|
morphdom_esm_default(container, clonedTree, {
|
|
1946
1947
|
childrenOnly: false,
|
|
1947
1948
|
onBeforeElUpdated: (fromEl, toEl) => {
|
|
@@ -1956,8 +1957,15 @@ var DOMPatch = class {
|
|
|
1956
1957
|
dom_default.mergeFocusedInput(fromEl, toEl);
|
|
1957
1958
|
return false;
|
|
1958
1959
|
}
|
|
1960
|
+
if (dom_default.isNowTriggerFormExternal(toEl, liveSocket.binding(PHX_TRIGGER_ACTION))) {
|
|
1961
|
+
externalFormTriggered = toEl;
|
|
1962
|
+
}
|
|
1959
1963
|
}
|
|
1960
1964
|
});
|
|
1965
|
+
if (externalFormTriggered) {
|
|
1966
|
+
liveSocket.unload();
|
|
1967
|
+
Object.getPrototypeOf(externalFormTriggered).submit.call(externalFormTriggered);
|
|
1968
|
+
}
|
|
1961
1969
|
liveSocket.silenceEvents(() => dom_default.restoreFocus(focused, selectionStart, selectionEnd));
|
|
1962
1970
|
}
|
|
1963
1971
|
constructor(view, container, id, html, streams, targetCID) {
|
|
@@ -2754,7 +2762,7 @@ var JS = {
|
|
|
2754
2762
|
let pushOpts = { loading, value, target, page_loading: !!page_loading };
|
|
2755
2763
|
let targetSrc = eventType === "change" && dispatcher ? dispatcher : sourceEl;
|
|
2756
2764
|
let phxTarget = target || targetSrc.getAttribute(view.binding("target")) || targetSrc;
|
|
2757
|
-
|
|
2765
|
+
const handler = (targetView, targetCtx) => {
|
|
2758
2766
|
if (!targetView.isConnected()) {
|
|
2759
2767
|
return;
|
|
2760
2768
|
}
|
|
@@ -2771,7 +2779,12 @@ var JS = {
|
|
|
2771
2779
|
} else {
|
|
2772
2780
|
targetView.pushEvent(eventType, sourceEl, targetCtx, event || phxEvent, data, pushOpts, callback);
|
|
2773
2781
|
}
|
|
2774
|
-
}
|
|
2782
|
+
};
|
|
2783
|
+
if (args.targetView && args.targetCtx) {
|
|
2784
|
+
handler(args.targetView, args.targetCtx);
|
|
2785
|
+
} else {
|
|
2786
|
+
view.withinTargets(phxTarget, handler);
|
|
2787
|
+
}
|
|
2775
2788
|
},
|
|
2776
2789
|
exec_navigate(e, eventType, phxEvent, view, sourceEl, el, { href, replace }) {
|
|
2777
2790
|
view.liveSocket.historyRedirect(e, href, replace ? "replace" : "push", null, sourceEl);
|
|
@@ -4584,12 +4597,19 @@ var View = class _View {
|
|
|
4584
4597
|
this.withinTargets(phxTarget, (targetView, targetCtx) => {
|
|
4585
4598
|
const cid = this.targetComponentID(newForm, targetCtx);
|
|
4586
4599
|
pending++;
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4600
|
+
let e = new CustomEvent("phx:form-recovery", { detail: { sourceElement: oldForm } });
|
|
4601
|
+
js_default.exec(e, "change", phxEvent, this, input, ["push", {
|
|
4602
|
+
_target: input.name,
|
|
4603
|
+
targetView,
|
|
4604
|
+
targetCtx,
|
|
4605
|
+
newCid: cid,
|
|
4606
|
+
callback: () => {
|
|
4607
|
+
pending--;
|
|
4608
|
+
if (pending === 0) {
|
|
4609
|
+
callback();
|
|
4610
|
+
}
|
|
4591
4611
|
}
|
|
4592
|
-
});
|
|
4612
|
+
}]);
|
|
4593
4613
|
}, templateDom, templateDom);
|
|
4594
4614
|
}
|
|
4595
4615
|
pushLinkPatch(e, href, targetEl, callback) {
|
|
@@ -4731,7 +4751,7 @@ var LiveSocket = class {
|
|
|
4731
4751
|
}
|
|
4732
4752
|
// public
|
|
4733
4753
|
version() {
|
|
4734
|
-
return "1.0.
|
|
4754
|
+
return "1.0.2";
|
|
4735
4755
|
}
|
|
4736
4756
|
isProfileEnabled() {
|
|
4737
4757
|
return this.sessionStorage.getItem(PHX_LV_PROFILE) === "true";
|
|
@@ -4917,7 +4937,10 @@ var LiveSocket = class {
|
|
|
4917
4937
|
if (!this.main) {
|
|
4918
4938
|
this.main = view;
|
|
4919
4939
|
}
|
|
4920
|
-
window.requestAnimationFrame(() =>
|
|
4940
|
+
window.requestAnimationFrame(() => {
|
|
4941
|
+
view.execNewMounted();
|
|
4942
|
+
this.maybeScroll(history.state?.scroll);
|
|
4943
|
+
});
|
|
4921
4944
|
}
|
|
4922
4945
|
}
|
|
4923
4946
|
joinRootViews() {
|