phoenix_live_view 1.0.16 → 1.0.18

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.
@@ -70,9 +70,25 @@ export default class DOMPatch {
70
70
  }
71
71
 
72
72
  perform(isJoinPatch){
73
- let {view, liveSocket, html, container, targetContainer} = this
73
+ let {view, liveSocket, html, container} = this
74
+ let targetContainer = this.targetContainer
74
75
  if(this.isCIDPatch() && !targetContainer){ return }
75
76
 
77
+ if(this.isCIDPatch()){
78
+ // https://github.com/phoenixframework/phoenix_live_view/pull/3942
79
+ // we need to ensure that no parent is locked
80
+ const closestLock = targetContainer.closest(`[${PHX_REF_LOCK}]`)
81
+ if(closestLock){
82
+ const clonedTree = DOM.private(closestLock, PHX_REF_LOCK)
83
+ if(clonedTree){
84
+ // if a parent is locked with a cloned tree, we need to patch the cloned tree instead
85
+ targetContainer = clonedTree.querySelector(
86
+ `[data-phx-component="${this.targetCID}"]`,
87
+ )
88
+ }
89
+ }
90
+ }
91
+
76
92
  let focused = liveSocket.getActiveElement()
77
93
  let {selectionStart, selectionEnd} = focused && DOM.hasSelectionRange(focused) ? focused : {}
78
94
  let phxUpdate = liveSocket.binding(PHX_UPDATE)
@@ -461,7 +461,15 @@ export default class LiveSocket {
461
461
  }
462
462
 
463
463
  owner(childEl, callback){
464
- let view = maybe(childEl.closest(PHX_VIEW_SELECTOR), el => this.getViewByEl(el)) || this.main
464
+ let view
465
+ const closestViewEl = childEl.closest(PHX_VIEW_SELECTOR)
466
+ if(closestViewEl){
467
+ // it can happen that we find a view that is already destroyed;
468
+ // in that case we DO NOT want to fallback to the main element
469
+ view = this.getViewByEl(closestViewEl)
470
+ } else {
471
+ view = this.main
472
+ }
465
473
  return view && callback ? callback(view) : view
466
474
  }
467
475
 
@@ -57,6 +57,8 @@ import Rendered from "./rendered"
57
57
  import ViewHook from "./view_hook"
58
58
  import JS from "./js"
59
59
 
60
+ import morphdom from "morphdom"
61
+
60
62
  export let prependFormDataKey = (key, prefix) => {
61
63
  let isArray = key.endsWith("[]")
62
64
  // Remove the "[]" if it's an array
@@ -663,9 +665,13 @@ export default class View {
663
665
  })
664
666
  }
665
667
 
666
- update(diff, events){
668
+ update(diff, events, isPending=false){
667
669
  if(this.isJoinPending() || (this.liveSocket.hasPendingLink() && this.root.isMain())){
668
- return this.pendingDiffs.push({diff, events})
670
+ // don't mutate if this is already a pending diff
671
+ if(!isPending){
672
+ this.pendingDiffs.push({diff, events})
673
+ }
674
+ return false
669
675
  }
670
676
 
671
677
  this.rendered.mergeDiff(diff)
@@ -691,6 +697,8 @@ export default class View {
691
697
 
692
698
  this.liveSocket.dispatchEvents(events)
693
699
  if(phxChildrenAdded){ this.joinNewChildren() }
700
+
701
+ return true
694
702
  }
695
703
 
696
704
  renderContainer(diff, kind){
@@ -756,15 +764,13 @@ export default class View {
756
764
  }
757
765
 
758
766
  applyPendingUpdates(){
759
- // prevent race conditions where we might still be pending a new
760
- // navigation after applying the current one;
761
- // if we call update and a pendingDiff is not applied, it would
762
- // be silently dropped otherwise, as update would push it back to
763
- // pendingDiffs, but we clear it immediately after
764
- if(this.liveSocket.hasPendingLink() && this.root.isMain()){ return }
765
- this.pendingDiffs.forEach(({diff, events}) => this.update(diff, events))
766
- this.pendingDiffs = []
767
- this.eachChild(child => child.applyPendingUpdates())
767
+ // To prevent race conditions where we might still be pending a new
768
+ // navigation or the join is still pending, `this.update` returns false
769
+ // if the diff was not applied.
770
+ this.pendingDiffs = this.pendingDiffs.filter(
771
+ ({diff, events}) => !this.update(diff, events, true),
772
+ )
773
+ this.eachChild((child) => child.applyPendingUpdates())
768
774
  }
769
775
 
770
776
  eachChild(callback){
@@ -1533,13 +1539,20 @@ export default class View {
1533
1539
  .filter(form => form.elements.length > 0)
1534
1540
  .filter(form => form.getAttribute(this.binding(PHX_AUTO_RECOVER)) !== "ignore")
1535
1541
  .map(form => {
1536
- // we perform a shallow clone and manually copy all elementsAdd commentMore actions
1542
+ // we perform a shallow clone and manually copy all elements
1537
1543
  const clonedForm = form.cloneNode(false)
1538
1544
  // we need to copy the private data as it contains
1539
1545
  // the information about touched fields
1540
1546
  DOM.copyPrivates(clonedForm, form)
1541
1547
  Array.from(form.elements).forEach((el) => {
1542
- const clonedEl = el.cloneNode(false)
1548
+ // we need to clone all child nodes as well,
1549
+ // because those could also be selects
1550
+ const clonedEl = el.cloneNode(true)
1551
+ // we call morphdom to copy any special state
1552
+ // like the selected option of a <select> element;
1553
+ // this should be plenty fast as we call it on a small subset of the DOM,
1554
+ // single inputs or a select with children
1555
+ morphdom(clonedEl, el)
1543
1556
  DOM.copyPrivates(clonedEl, el)
1544
1557
  clonedForm.appendChild(clonedEl)
1545
1558
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phoenix_live_view",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
4
4
  "description": "The Phoenix LiveView JavaScript client.",
5
5
  "license": "MIT",
6
6
  "module": "./priv/static/phoenix_live_view.esm.js",
@@ -24,7 +24,7 @@
24
24
  "assets/js/phoenix_live_view/*"
25
25
  ],
26
26
  "dependencies": {
27
- "morphdom": "2.7.5"
27
+ "morphdom": "2.7.7"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@babel/cli": "7.27.0",
@@ -1675,6 +1675,10 @@ var specialElHandlers = {
1675
1675
  if (nodeName === "OPTGROUP") {
1676
1676
  optgroup = curChild;
1677
1677
  curChild = optgroup.firstChild;
1678
+ if (!curChild) {
1679
+ curChild = optgroup.nextSibling;
1680
+ optgroup = null;
1681
+ }
1678
1682
  } else {
1679
1683
  if (nodeName === "OPTION") {
1680
1684
  if (curChild.hasAttribute("selected")) {
@@ -2049,10 +2053,22 @@ var DOMPatch = class {
2049
2053
  });
2050
2054
  }
2051
2055
  perform(isJoinPatch) {
2052
- let { view, liveSocket, html, container, targetContainer } = this;
2056
+ let { view, liveSocket, html, container } = this;
2057
+ let targetContainer = this.targetContainer;
2053
2058
  if (this.isCIDPatch() && !targetContainer) {
2054
2059
  return;
2055
2060
  }
2061
+ if (this.isCIDPatch()) {
2062
+ const closestLock = targetContainer.closest(`[${PHX_REF_LOCK}]`);
2063
+ if (closestLock) {
2064
+ const clonedTree = dom_default.private(closestLock, PHX_REF_LOCK);
2065
+ if (clonedTree) {
2066
+ targetContainer = clonedTree.querySelector(
2067
+ `[data-phx-component="${this.targetCID}"]`
2068
+ );
2069
+ }
2070
+ }
2071
+ }
2056
2072
  let focused = liveSocket.getActiveElement();
2057
2073
  let { selectionStart, selectionEnd } = focused && dom_default.hasSelectionRange(focused) ? focused : {};
2058
2074
  let phxUpdate = liveSocket.binding(PHX_UPDATE);
@@ -3925,9 +3941,12 @@ var View = class _View {
3925
3941
  this.pendingJoinOps = [];
3926
3942
  });
3927
3943
  }
3928
- update(diff, events) {
3944
+ update(diff, events, isPending = false) {
3929
3945
  if (this.isJoinPending() || this.liveSocket.hasPendingLink() && this.root.isMain()) {
3930
- return this.pendingDiffs.push({ diff, events });
3946
+ if (!isPending) {
3947
+ this.pendingDiffs.push({ diff, events });
3948
+ }
3949
+ return false;
3931
3950
  }
3932
3951
  this.rendered.mergeDiff(diff);
3933
3952
  let phxChildrenAdded = false;
@@ -3951,6 +3970,7 @@ var View = class _View {
3951
3970
  if (phxChildrenAdded) {
3952
3971
  this.joinNewChildren();
3953
3972
  }
3973
+ return true;
3954
3974
  }
3955
3975
  renderContainer(diff, kind) {
3956
3976
  return this.liveSocket.time(`toString diff (${kind})`, () => {
@@ -4005,11 +4025,9 @@ var View = class _View {
4005
4025
  delete this.viewHooks[hookId];
4006
4026
  }
4007
4027
  applyPendingUpdates() {
4008
- if (this.liveSocket.hasPendingLink() && this.root.isMain()) {
4009
- return;
4010
- }
4011
- this.pendingDiffs.forEach(({ diff, events }) => this.update(diff, events));
4012
- this.pendingDiffs = [];
4028
+ this.pendingDiffs = this.pendingDiffs.filter(
4029
+ ({ diff, events }) => !this.update(diff, events, true)
4030
+ );
4013
4031
  this.eachChild((child) => child.applyPendingUpdates());
4014
4032
  }
4015
4033
  eachChild(callback) {
@@ -4747,7 +4765,8 @@ var View = class _View {
4747
4765
  const clonedForm = form.cloneNode(false);
4748
4766
  dom_default.copyPrivates(clonedForm, form);
4749
4767
  Array.from(form.elements).forEach((el) => {
4750
- const clonedEl = el.cloneNode(false);
4768
+ const clonedEl = el.cloneNode(true);
4769
+ morphdom_esm_default(clonedEl, el);
4751
4770
  dom_default.copyPrivates(clonedEl, el);
4752
4771
  clonedForm.appendChild(clonedEl);
4753
4772
  });
@@ -4864,7 +4883,7 @@ var LiveSocket = class {
4864
4883
  }
4865
4884
  // public
4866
4885
  version() {
4867
- return "1.0.16";
4886
+ return "1.0.18";
4868
4887
  }
4869
4888
  isProfileEnabled() {
4870
4889
  return this.sessionStorage.getItem(PHX_LV_PROFILE) === "true";
@@ -5134,7 +5153,13 @@ var LiveSocket = class {
5134
5153
  return view;
5135
5154
  }
5136
5155
  owner(childEl, callback) {
5137
- let view = maybe(childEl.closest(PHX_VIEW_SELECTOR), (el) => this.getViewByEl(el)) || this.main;
5156
+ let view;
5157
+ const closestViewEl = childEl.closest(PHX_VIEW_SELECTOR);
5158
+ if (closestViewEl) {
5159
+ view = this.getViewByEl(closestViewEl);
5160
+ } else {
5161
+ view = this.main;
5162
+ }
5138
5163
  return view && callback ? callback(view) : view;
5139
5164
  }
5140
5165
  withinOwners(childEl, callback) {