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 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 PHX_REF_SRC, it is loading or locked and awaiting an ack.
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
- view.withinTargets(phxTarget, (targetView, targetCtx) => {
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(() => view.execNewMounted())
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
- targetView.pushInput(input, targetCtx, cid, phxEvent, {_target: input.name}, () => {
1417
- pending--
1418
- if(pending === 0){ callback() }
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
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phoenix_live_view",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
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": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "The Phoenix LiveView JavaScript client.",
5
5
  "license": "MIT",
6
6
  "module": "./priv/static/phoenix_live_view.esm.js",
@@ -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
- view.withinTargets(phxTarget, (targetView, targetCtx) => {
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
- targetView.pushInput(input, targetCtx, cid, phxEvent, { _target: input.name }, () => {
4588
- pending--;
4589
- if (pending === 0) {
4590
- callback();
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.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(() => view.execNewMounted());
4940
+ window.requestAnimationFrame(() => {
4941
+ view.execNewMounted();
4942
+ this.maybeScroll(history.state?.scroll);
4943
+ });
4921
4944
  }
4922
4945
  }
4923
4946
  joinRootViews() {