phoenix_live_view 0.20.2 → 0.20.4

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.
@@ -11,7 +11,8 @@ var PHX_EVENT_CLASSES = [
11
11
  "phx-keydown-loading",
12
12
  "phx-keyup-loading",
13
13
  "phx-blur-loading",
14
- "phx-focus-loading"
14
+ "phx-focus-loading",
15
+ "phx-hook-loading"
15
16
  ];
16
17
  var PHX_COMPONENT = "data-phx-component";
17
18
  var PHX_LIVE_LINK = "data-phx-link";
@@ -43,6 +44,7 @@ var PHX_VIEWPORT_TOP = "viewport-top";
43
44
  var PHX_VIEWPORT_BOTTOM = "viewport-bottom";
44
45
  var PHX_TRIGGER_ACTION = "trigger-action";
45
46
  var PHX_FEEDBACK_FOR = "feedback-for";
47
+ var PHX_FEEDBACK_GROUP = "feedback-group";
46
48
  var PHX_HAS_FOCUSED = "phx-has-focused";
47
49
  var FOCUSABLE_INPUTS = ["text", "textarea", "number", "email", "password", "search", "tel", "url", "date", "time", "datetime-local", "color", "range"];
48
50
  var CHECKABLE_INPUTS = ["checkbox", "radio"];
@@ -298,7 +300,9 @@ var DOM = {
298
300
  return inputEl.hasAttribute("data-phx-auto-upload");
299
301
  },
300
302
  findUploadInputs(node) {
301
- return this.all(node, `input[type="file"][${PHX_UPLOAD_REF}]`);
303
+ const formId = node.id;
304
+ const inputsOutsideForm = this.all(document, `input[type="file"][${PHX_UPLOAD_REF}][form="${formId}"]`);
305
+ return this.all(node, `input[type="file"][${PHX_UPLOAD_REF}]`).concat(inputsOutsideForm);
302
306
  },
303
307
  findComponentNodeList(node, cid) {
304
308
  return this.filterWithinSameLiveView(this.all(node, `[${PHX_COMPONENT}="${cid}"]`), node);
@@ -492,7 +496,9 @@ var DOM = {
492
496
  });
493
497
  }
494
498
  if (this.once(el, "bind-debounce")) {
495
- el.addEventListener("blur", () => this.triggerCycle(el, DEBOUNCE_TRIGGER));
499
+ el.addEventListener("blur", () => {
500
+ callback();
501
+ });
496
502
  }
497
503
  }
498
504
  },
@@ -525,16 +531,39 @@ var DOM = {
525
531
  el.setAttribute("data-phx-hook", "Phoenix.InfiniteScroll");
526
532
  }
527
533
  },
528
- maybeHideFeedback(container, inputs, phxFeedbackFor) {
534
+ maybeHideFeedback(container, forms, phxFeedbackFor, phxFeedbackGroup) {
529
535
  let feedbacks = [];
530
- inputs.forEach((input) => {
531
- if (!(this.private(input, PHX_HAS_FOCUSED) || this.private(input, PHX_HAS_SUBMITTED))) {
532
- feedbacks.push(input.name);
533
- if (input.name.endsWith("[]")) {
534
- feedbacks.push(input.name.slice(0, -2));
536
+ let inputNamesFocused = {};
537
+ let feedbackGroups = {};
538
+ forms.forEach((form) => {
539
+ Array.from(form.elements).forEach((input) => {
540
+ const group = input.getAttribute(phxFeedbackGroup);
541
+ if (group && !(group in feedbackGroups)) {
542
+ feedbackGroups[group] = true;
535
543
  }
536
- }
544
+ if (!(input.name in inputNamesFocused)) {
545
+ inputNamesFocused[input.name] = false;
546
+ }
547
+ if (this.private(input, PHX_HAS_FOCUSED) || this.private(input, PHX_HAS_SUBMITTED)) {
548
+ inputNamesFocused[input.name] = true;
549
+ if (group) {
550
+ feedbackGroups[group] = false;
551
+ }
552
+ }
553
+ });
537
554
  });
555
+ for (const [name, focused] of Object.entries(inputNamesFocused)) {
556
+ if (!focused) {
557
+ feedbacks.push(name);
558
+ if (name.endsWith("[]")) {
559
+ feedbacks.push(name.slice(0, -2));
560
+ }
561
+ }
562
+ }
563
+ for (const [group, noFeedback] of Object.entries(feedbackGroups)) {
564
+ if (noFeedback)
565
+ feedbacks.push(group);
566
+ }
538
567
  if (feedbacks.length > 0) {
539
568
  let selector = feedbacks.map((f) => `[${phxFeedbackFor}="${f}"]`).join(", ");
540
569
  DOM.all(container, selector, (el) => el.classList.add(PHX_NO_FEEDBACK_CLASS));
@@ -565,11 +594,19 @@ var DOM = {
565
594
  isPhxSticky(node) {
566
595
  return node.getAttribute && node.getAttribute(PHX_STICKY) !== null;
567
596
  },
597
+ isChildOfAny(el, parents) {
598
+ return !!parents.find((parent) => parent.contains(el));
599
+ },
568
600
  firstPhxChild(el) {
569
601
  return this.isPhxChild(el) ? el : this.all(el, `[${PHX_PARENT_ID}]`)[0];
570
602
  },
571
603
  dispatchEvent(target, name, opts = {}) {
572
- let bubbles = opts.bubbles === void 0 ? true : !!opts.bubbles;
604
+ let defaultBubble = true;
605
+ let isUploadTarget = target.nodeName === "INPUT" && target.type === "file";
606
+ if (isUploadTarget && name === "click") {
607
+ defaultBubble = false;
608
+ }
609
+ let bubbles = opts.bubbles === void 0 ? defaultBubble : !!opts.bubbles;
573
610
  let eventOpts = { bubbles, cancelable: true, detail: opts.detail || {} };
574
611
  let event = name === "click" ? new MouseEvent("click", eventOpts) : new CustomEvent(name, eventOpts);
575
612
  target.dispatchEvent(event);
@@ -584,20 +621,27 @@ var DOM = {
584
621
  }
585
622
  },
586
623
  mergeAttrs(target, source, opts = {}) {
587
- let exclude = opts.exclude || [];
624
+ let exclude = new Set(opts.exclude || []);
588
625
  let isIgnored = opts.isIgnored;
589
626
  let sourceAttrs = source.attributes;
590
627
  for (let i = sourceAttrs.length - 1; i >= 0; i--) {
591
628
  let name = sourceAttrs[i].name;
592
- if (exclude.indexOf(name) < 0) {
593
- target.setAttribute(name, source.getAttribute(name));
629
+ if (!exclude.has(name)) {
630
+ const sourceValue = source.getAttribute(name);
631
+ if (target.getAttribute(name) !== sourceValue && (!isIgnored || isIgnored && name.startsWith("data-"))) {
632
+ target.setAttribute(name, sourceValue);
633
+ }
634
+ } else {
635
+ if (name === "value" && target.value === source.value) {
636
+ target.setAttribute("value", source.getAttribute(name));
637
+ }
594
638
  }
595
639
  }
596
640
  let targetAttrs = target.attributes;
597
641
  for (let i = targetAttrs.length - 1; i >= 0; i--) {
598
642
  let name = targetAttrs[i].name;
599
643
  if (isIgnored) {
600
- if (name.startsWith("data-") && !source.hasAttribute(name)) {
644
+ if (name.startsWith("data-") && !source.hasAttribute(name) && ![PHX_REF, PHX_REF_SRC].includes(name)) {
601
645
  target.removeAttribute(name);
602
646
  }
603
647
  } else {
@@ -621,6 +665,9 @@ var DOM = {
621
665
  return el.setSelectionRange && (el.type === "text" || el.type === "textarea");
622
666
  },
623
667
  restoreFocus(focused, selectionStart, selectionEnd) {
668
+ if (focused instanceof HTMLSelectElement) {
669
+ focused.focus();
670
+ }
624
671
  if (!DOM.isTextualInput(focused)) {
625
672
  return;
626
673
  }
@@ -744,15 +791,22 @@ var dom_default = DOM;
744
791
  var UploadEntry = class {
745
792
  static isActive(fileEl, file) {
746
793
  let isNew = file._phxRef === void 0;
794
+ let isPreflightInProgress = UploadEntry.isPreflightInProgress(file);
747
795
  let activeRefs = fileEl.getAttribute(PHX_ACTIVE_ENTRY_REFS).split(",");
748
796
  let isActive = activeRefs.indexOf(LiveUploader.genFileRef(file)) >= 0;
749
- return file.size > 0 && (isNew || isActive);
797
+ return file.size > 0 && (isNew || isActive || !isPreflightInProgress);
750
798
  }
751
799
  static isPreflighted(fileEl, file) {
752
800
  let preflightedRefs = fileEl.getAttribute(PHX_PREFLIGHTED_REFS).split(",");
753
801
  let isPreflighted = preflightedRefs.indexOf(LiveUploader.genFileRef(file)) >= 0;
754
802
  return isPreflighted && this.isActive(fileEl, file);
755
803
  }
804
+ static isPreflightInProgress(file) {
805
+ return file._preflightInProgress === true;
806
+ }
807
+ static markPreflightInProgress(file) {
808
+ file._preflightInProgress = true;
809
+ }
756
810
  constructor(fileEl, file, view) {
757
811
  this.ref = LiveUploader.genFileRef(file);
758
812
  this.fileEl = fileEl;
@@ -919,12 +973,16 @@ var LiveUploader = class {
919
973
  return Array.from(fileInputs).filter((input) => this.filesAwaitingPreflight(input).length > 0);
920
974
  }
921
975
  static filesAwaitingPreflight(input) {
922
- return this.activeFiles(input).filter((f) => !UploadEntry.isPreflighted(input, f));
976
+ return this.activeFiles(input).filter((f) => !UploadEntry.isPreflighted(input, f) && !UploadEntry.isPreflightInProgress(f));
977
+ }
978
+ static markPreflightInProgress(entries) {
979
+ entries.forEach((entry) => UploadEntry.markPreflightInProgress(entry.file));
923
980
  }
924
981
  constructor(inputEl, view, onComplete) {
925
982
  this.view = view;
926
983
  this.onComplete = onComplete;
927
984
  this._entries = Array.from(LiveUploader.filesAwaitingPreflight(inputEl) || []).map((file) => new UploadEntry(inputEl, file, view));
985
+ LiveUploader.markPreflightInProgress(this._entries);
928
986
  this.numEntriesInProgress = this._entries.length;
929
987
  }
930
988
  entries() {
@@ -1065,23 +1123,50 @@ var Hooks = {
1065
1123
  }
1066
1124
  }
1067
1125
  };
1068
- var scrollTop = () => document.documentElement.scrollTop || document.body.scrollTop;
1069
- var winHeight = () => window.innerHeight || document.documentElement.clientHeight;
1070
- var isAtViewportTop = (el) => {
1126
+ var findScrollContainer = (el) => {
1127
+ if (["scroll", "auto"].indexOf(getComputedStyle(el).overflowY) >= 0)
1128
+ return el;
1129
+ if (document.documentElement === el)
1130
+ return null;
1131
+ return findScrollContainer(el.parentElement);
1132
+ };
1133
+ var scrollTop = (scrollContainer) => {
1134
+ if (scrollContainer) {
1135
+ return scrollContainer.scrollTop;
1136
+ } else {
1137
+ return document.documentElement.scrollTop || document.body.scrollTop;
1138
+ }
1139
+ };
1140
+ var bottom = (scrollContainer) => {
1141
+ if (scrollContainer) {
1142
+ return scrollContainer.getBoundingClientRect().bottom;
1143
+ } else {
1144
+ return window.innerHeight || document.documentElement.clientHeight;
1145
+ }
1146
+ };
1147
+ var top = (scrollContainer) => {
1148
+ if (scrollContainer) {
1149
+ return scrollContainer.getBoundingClientRect().top;
1150
+ } else {
1151
+ return 0;
1152
+ }
1153
+ };
1154
+ var isAtViewportTop = (el, scrollContainer) => {
1071
1155
  let rect = el.getBoundingClientRect();
1072
- return rect.top >= 0 && rect.left >= 0 && rect.top <= winHeight();
1156
+ return rect.top >= top(scrollContainer) && rect.left >= 0 && rect.top <= bottom(scrollContainer);
1073
1157
  };
1074
- var isAtViewportBottom = (el) => {
1158
+ var isAtViewportBottom = (el, scrollContainer) => {
1075
1159
  let rect = el.getBoundingClientRect();
1076
- return rect.right >= 0 && rect.left >= 0 && rect.bottom <= winHeight();
1160
+ return rect.right >= top(scrollContainer) && rect.left >= 0 && rect.bottom <= bottom(scrollContainer);
1077
1161
  };
1078
- var isWithinViewport = (el) => {
1162
+ var isWithinViewport = (el, scrollContainer) => {
1079
1163
  let rect = el.getBoundingClientRect();
1080
- return rect.top >= 0 && rect.left >= 0 && rect.top <= winHeight();
1164
+ return rect.top >= top(scrollContainer) && rect.left >= 0 && rect.top <= bottom(scrollContainer);
1081
1165
  };
1082
1166
  Hooks.InfiniteScroll = {
1083
1167
  mounted() {
1084
- let scrollBefore = scrollTop();
1168
+ this.scrollContainer = findScrollContainer(this.el);
1169
+ let scrollBefore = scrollTop(this.scrollContainer);
1085
1170
  let topOverran = false;
1086
1171
  let throttleInterval = 500;
1087
1172
  let pendingOp = null;
@@ -1095,22 +1180,26 @@ Hooks.InfiniteScroll = {
1095
1180
  pendingOp = () => firstChild.scrollIntoView({ block: "start" });
1096
1181
  this.liveSocket.execJSHookPush(this.el, topEvent, { id: firstChild.id }, () => {
1097
1182
  pendingOp = null;
1098
- if (!isWithinViewport(firstChild)) {
1099
- firstChild.scrollIntoView({ block: "start" });
1100
- }
1183
+ window.requestAnimationFrame(() => {
1184
+ if (!isWithinViewport(firstChild, this.scrollContainer)) {
1185
+ firstChild.scrollIntoView({ block: "start" });
1186
+ }
1187
+ });
1101
1188
  });
1102
1189
  });
1103
1190
  let onLastChildAtBottom = this.throttle(throttleInterval, (bottomEvent, lastChild) => {
1104
1191
  pendingOp = () => lastChild.scrollIntoView({ block: "end" });
1105
1192
  this.liveSocket.execJSHookPush(this.el, bottomEvent, { id: lastChild.id }, () => {
1106
1193
  pendingOp = null;
1107
- if (!isWithinViewport(lastChild)) {
1108
- lastChild.scrollIntoView({ block: "end" });
1109
- }
1194
+ window.requestAnimationFrame(() => {
1195
+ if (!isWithinViewport(lastChild, this.scrollContainer)) {
1196
+ lastChild.scrollIntoView({ block: "end" });
1197
+ }
1198
+ });
1110
1199
  });
1111
1200
  });
1112
- this.onScroll = (e) => {
1113
- let scrollNow = scrollTop();
1201
+ this.onScroll = (_e) => {
1202
+ let scrollNow = scrollTop(this.scrollContainer);
1114
1203
  if (pendingOp) {
1115
1204
  scrollBefore = scrollNow;
1116
1205
  return pendingOp();
@@ -1128,17 +1217,25 @@ Hooks.InfiniteScroll = {
1128
1217
  } else if (isScrollingDown && topOverran && rect.top <= 0) {
1129
1218
  topOverran = false;
1130
1219
  }
1131
- if (topEvent && isScrollingUp && isAtViewportTop(firstChild)) {
1220
+ if (topEvent && isScrollingUp && isAtViewportTop(firstChild, this.scrollContainer)) {
1132
1221
  onFirstChildAtTop(topEvent, firstChild);
1133
- } else if (bottomEvent && isScrollingDown && isAtViewportBottom(lastChild)) {
1222
+ } else if (bottomEvent && isScrollingDown && isAtViewportBottom(lastChild, this.scrollContainer)) {
1134
1223
  onLastChildAtBottom(bottomEvent, lastChild);
1135
1224
  }
1136
1225
  scrollBefore = scrollNow;
1137
1226
  };
1138
- window.addEventListener("scroll", this.onScroll);
1227
+ if (this.scrollContainer) {
1228
+ this.scrollContainer.addEventListener("scroll", this.onScroll);
1229
+ } else {
1230
+ window.addEventListener("scroll", this.onScroll);
1231
+ }
1139
1232
  },
1140
1233
  destroyed() {
1141
- window.removeEventListener("scroll", this.onScroll);
1234
+ if (this.scrollContainer) {
1235
+ this.scrollContainer.removeEventListener("scroll", this.onScroll);
1236
+ } else {
1237
+ window.removeEventListener("scroll", this.onScroll);
1238
+ }
1142
1239
  },
1143
1240
  throttle(interval, callback) {
1144
1241
  let lastCallAt = 0;
@@ -1585,6 +1682,7 @@ function morphdomFactory(morphAttrs2) {
1585
1682
  removeNode(curFromNodeChild, fromEl, true);
1586
1683
  }
1587
1684
  curFromNodeChild = matchingFromEl;
1685
+ curFromNodeKey = getNodeKey(curFromNodeChild);
1588
1686
  }
1589
1687
  } else {
1590
1688
  isCompatible = false;
@@ -1717,6 +1815,7 @@ var DOMPatch = class {
1717
1815
  this.html = html;
1718
1816
  this.streams = streams;
1719
1817
  this.streamInserts = {};
1818
+ this.streamComponentRestore = {};
1720
1819
  this.targetCID = targetCID;
1721
1820
  this.cidPatch = isCid(this.targetCID);
1722
1821
  this.pendingRemoves = [];
@@ -1746,7 +1845,6 @@ var DOMPatch = class {
1746
1845
  }
1747
1846
  markPrunableContentForRemoval() {
1748
1847
  let phxUpdate = this.liveSocket.binding(PHX_UPDATE);
1749
- dom_default.all(this.container, `[${phxUpdate}=${PHX_STREAM}]`, (el) => el.innerHTML = "");
1750
1848
  dom_default.all(this.container, `[${phxUpdate}=append] > *, [${phxUpdate}=prepend] > *`, (el) => {
1751
1849
  el.setAttribute(PHX_PRUNE, "");
1752
1850
  });
@@ -1761,12 +1859,13 @@ var DOMPatch = class {
1761
1859
  let { selectionStart, selectionEnd } = focused && dom_default.hasSelectionRange(focused) ? focused : {};
1762
1860
  let phxUpdate = liveSocket.binding(PHX_UPDATE);
1763
1861
  let phxFeedbackFor = liveSocket.binding(PHX_FEEDBACK_FOR);
1862
+ let phxFeedbackGroup = liveSocket.binding(PHX_FEEDBACK_GROUP);
1764
1863
  let disableWith = liveSocket.binding(PHX_DISABLE_WITH);
1765
1864
  let phxViewportTop = liveSocket.binding(PHX_VIEWPORT_TOP);
1766
1865
  let phxViewportBottom = liveSocket.binding(PHX_VIEWPORT_BOTTOM);
1767
1866
  let phxTriggerExternal = liveSocket.binding(PHX_TRIGGER_ACTION);
1768
1867
  let added = [];
1769
- let trackedInputs = [];
1868
+ let trackedForms = new Set();
1770
1869
  let updates = [];
1771
1870
  let appendPrependUpdates = [];
1772
1871
  let externalFormTriggered = null;
@@ -1774,16 +1873,12 @@ var DOMPatch = class {
1774
1873
  this.trackBefore("updated", container, container);
1775
1874
  liveSocket.time("morphdom", () => {
1776
1875
  this.streams.forEach(([ref, inserts, deleteIds, reset]) => {
1777
- Object.entries(inserts).forEach(([key, [streamAt, limit]]) => {
1778
- this.streamInserts[key] = { ref, streamAt, limit, resetKept: false };
1876
+ inserts.forEach(([key, streamAt, limit]) => {
1877
+ this.streamInserts[key] = { ref, streamAt, limit, reset };
1779
1878
  });
1780
1879
  if (reset !== void 0) {
1781
1880
  dom_default.all(container, `[${PHX_STREAM_REF}="${ref}"]`, (child) => {
1782
- if (inserts[child.id]) {
1783
- this.streamInserts[child.id].resetKept = true;
1784
- } else {
1785
- this.removeStreamChildElement(child);
1786
- }
1881
+ this.removeStreamChildElement(child);
1787
1882
  });
1788
1883
  }
1789
1884
  deleteIds.forEach((id) => {
@@ -1793,6 +1888,17 @@ var DOMPatch = class {
1793
1888
  }
1794
1889
  });
1795
1890
  });
1891
+ if (isJoinPatch) {
1892
+ dom_default.all(this.container, `[${phxUpdate}=${PHX_STREAM}]`, (el) => {
1893
+ this.liveSocket.owner(el, (view2) => {
1894
+ if (view2 === this.view) {
1895
+ Array.from(el.children).forEach((child) => {
1896
+ this.removeStreamChildElement(child);
1897
+ });
1898
+ }
1899
+ });
1900
+ });
1901
+ }
1796
1902
  morphdom_esm_default(targetContainer, html, {
1797
1903
  childrenOnly: targetContainer.getAttribute(PHX_COMPONENT) === null,
1798
1904
  getNodeKey: (node) => {
@@ -1808,11 +1914,17 @@ var DOMPatch = class {
1808
1914
  return from.getAttribute(phxUpdate) === PHX_STREAM;
1809
1915
  },
1810
1916
  addChild: (parent, child) => {
1811
- let { ref, streamAt, limit } = this.getStreamInsert(child);
1917
+ let { ref, streamAt } = this.getStreamInsert(child);
1812
1918
  if (ref === void 0) {
1813
1919
  return parent.appendChild(child);
1814
1920
  }
1815
- dom_default.putSticky(child, PHX_STREAM_REF, (el) => el.setAttribute(PHX_STREAM_REF, ref));
1921
+ this.setStreamRef(child, ref);
1922
+ child.querySelectorAll(`[${PHX_MAGIC_ID}][${PHX_SKIP}]`).forEach((el) => {
1923
+ const component = this.streamComponentRestore[el.getAttribute(PHX_MAGIC_ID)];
1924
+ if (component) {
1925
+ el.replaceWith(component);
1926
+ }
1927
+ });
1816
1928
  if (streamAt === 0) {
1817
1929
  parent.insertAdjacentElement("afterbegin", child);
1818
1930
  } else if (streamAt === -1) {
@@ -1821,18 +1933,6 @@ var DOMPatch = class {
1821
1933
  let sibling = Array.from(parent.children)[streamAt];
1822
1934
  parent.insertBefore(child, sibling);
1823
1935
  }
1824
- let children = limit !== null && Array.from(parent.children);
1825
- let childrenToRemove = [];
1826
- if (limit && limit < 0 && children.length > limit * -1) {
1827
- childrenToRemove = children.slice(0, children.length + limit);
1828
- } else if (limit && limit >= 0 && children.length > limit) {
1829
- childrenToRemove = children.slice(limit);
1830
- }
1831
- childrenToRemove.forEach((removeChild) => {
1832
- if (!this.streamInserts[removeChild.id]) {
1833
- this.removeStreamChildElement(removeChild);
1834
- }
1835
- });
1836
1936
  },
1837
1937
  onBeforeNodeAdded: (el) => {
1838
1938
  dom_default.maybeAddPrivateHooks(el, phxViewportTop, phxViewportBottom);
@@ -1841,7 +1941,7 @@ var DOMPatch = class {
1841
1941
  },
1842
1942
  onNodeAdded: (el) => {
1843
1943
  if (el.getAttribute) {
1844
- this.maybeReOrderStream(el);
1944
+ this.maybeReOrderStream(el, true);
1845
1945
  }
1846
1946
  if (el instanceof HTMLImageElement && el.srcset) {
1847
1947
  el.srcset = el.srcset;
@@ -1852,32 +1952,13 @@ var DOMPatch = class {
1852
1952
  externalFormTriggered = el;
1853
1953
  }
1854
1954
  if (el.getAttribute && el.getAttribute("name") && dom_default.isFormInput(el)) {
1855
- trackedInputs.push(el);
1955
+ trackedForms.add(el.form);
1856
1956
  }
1857
1957
  if (dom_default.isPhxChild(el) && view.ownsElement(el) || dom_default.isPhxSticky(el) && view.ownsElement(el.parentNode)) {
1858
1958
  this.trackAfter("phxChildAdded", el);
1859
1959
  }
1860
1960
  added.push(el);
1861
1961
  },
1862
- onBeforeElChildrenUpdated: (fromEl, toEl) => {
1863
- if (fromEl.getAttribute(phxUpdate) === PHX_STREAM) {
1864
- let toIds = Array.from(toEl.children).map((child) => child.id);
1865
- Array.from(fromEl.children).filter((child) => {
1866
- let { resetKept } = this.getStreamInsert(child);
1867
- return resetKept;
1868
- }).sort((a, b) => {
1869
- let aIdx = toIds.indexOf(a.id);
1870
- let bIdx = toIds.indexOf(b.id);
1871
- if (aIdx === bIdx) {
1872
- return 0;
1873
- } else if (aIdx < bIdx) {
1874
- return -1;
1875
- } else {
1876
- return 1;
1877
- }
1878
- }).forEach((child) => fromEl.appendChild(child));
1879
- }
1880
- },
1881
1962
  onNodeDiscarded: (el) => this.onNodeDiscarded(el),
1882
1963
  onBeforeNodeDiscarded: (el) => {
1883
1964
  if (el.getAttribute && el.getAttribute(PHX_PRUNE) !== null) {
@@ -1899,12 +1980,13 @@ var DOMPatch = class {
1899
1980
  externalFormTriggered = el;
1900
1981
  }
1901
1982
  updates.push(el);
1902
- this.maybeReOrderStream(el);
1983
+ this.maybeReOrderStream(el, false);
1903
1984
  },
1904
1985
  onBeforeElUpdated: (fromEl, toEl) => {
1905
1986
  dom_default.maybeAddPrivateHooks(toEl, phxViewportTop, phxViewportBottom);
1906
1987
  dom_default.cleanChildNodes(toEl, phxUpdate);
1907
1988
  if (this.skipCIDSibling(toEl)) {
1989
+ this.maybeReOrderStream(fromEl);
1908
1990
  return false;
1909
1991
  }
1910
1992
  if (dom_default.isPhxSticky(fromEl)) {
@@ -1940,22 +2022,26 @@ var DOMPatch = class {
1940
2022
  }
1941
2023
  dom_default.copyPrivates(toEl, fromEl);
1942
2024
  let isFocusedFormEl = focused && fromEl.isSameNode(focused) && dom_default.isFormInput(fromEl);
1943
- if (isFocusedFormEl && fromEl.type !== "hidden") {
2025
+ let focusedSelectChanged = isFocusedFormEl && this.isChangedSelect(fromEl, toEl);
2026
+ if (isFocusedFormEl && fromEl.type !== "hidden" && !focusedSelectChanged) {
1944
2027
  this.trackBefore("updated", fromEl, toEl);
1945
2028
  dom_default.mergeFocusedInput(fromEl, toEl);
1946
2029
  dom_default.syncAttrsToProps(fromEl);
1947
2030
  updates.push(fromEl);
1948
2031
  dom_default.applyStickyOperations(fromEl);
1949
- trackedInputs.push(fromEl);
2032
+ trackedForms.add(fromEl.form);
1950
2033
  return false;
1951
2034
  } else {
2035
+ if (focusedSelectChanged) {
2036
+ fromEl.blur();
2037
+ }
1952
2038
  if (dom_default.isPhxUpdate(toEl, phxUpdate, ["append", "prepend"])) {
1953
2039
  appendPrependUpdates.push(new DOMPostMorphRestorer(fromEl, toEl, toEl.getAttribute(phxUpdate)));
1954
2040
  }
1955
2041
  dom_default.syncAttrsToProps(toEl);
1956
2042
  dom_default.applyStickyOperations(toEl);
1957
2043
  if (toEl.getAttribute("name") && dom_default.isFormInput(toEl)) {
1958
- trackedInputs.push(toEl);
2044
+ trackedForms.add(toEl.form);
1959
2045
  }
1960
2046
  this.trackBefore("updated", fromEl, toEl);
1961
2047
  return true;
@@ -1971,7 +2057,7 @@ var DOMPatch = class {
1971
2057
  appendPrependUpdates.forEach((update) => update.perform());
1972
2058
  });
1973
2059
  }
1974
- dom_default.maybeHideFeedback(targetContainer, trackedInputs, phxFeedbackFor);
2060
+ dom_default.maybeHideFeedback(targetContainer, trackedForms, phxFeedbackFor, phxFeedbackGroup);
1975
2061
  liveSocket.silenceEvents(() => dom_default.restoreFocus(focused, selectionStart, selectionEnd));
1976
2062
  dom_default.dispatchEvent(document, "phx:update");
1977
2063
  added.forEach((el) => this.trackAfter("added", el));
@@ -1999,6 +2085,11 @@ var DOMPatch = class {
1999
2085
  }
2000
2086
  removeStreamChildElement(child) {
2001
2087
  if (!this.maybePendingRemove(child)) {
2088
+ if (this.streamInserts[child.id]) {
2089
+ child.querySelectorAll(`[${PHX_MAGIC_ID}]`).forEach((el) => {
2090
+ this.streamComponentRestore[el.getAttribute(PHX_MAGIC_ID)] = el;
2091
+ });
2092
+ }
2002
2093
  child.remove();
2003
2094
  this.onNodeDiscarded(child);
2004
2095
  }
@@ -2007,12 +2098,18 @@ var DOMPatch = class {
2007
2098
  let insert = el.id ? this.streamInserts[el.id] : {};
2008
2099
  return insert || {};
2009
2100
  }
2010
- maybeReOrderStream(el) {
2011
- let { ref, streamAt, limit } = this.getStreamInsert(el);
2101
+ setStreamRef(el, ref) {
2102
+ dom_default.putSticky(el, PHX_STREAM_REF, (el2) => el2.setAttribute(PHX_STREAM_REF, ref));
2103
+ }
2104
+ maybeReOrderStream(el, isNew) {
2105
+ let { ref, streamAt, reset } = this.getStreamInsert(el);
2012
2106
  if (streamAt === void 0) {
2013
2107
  return;
2014
2108
  }
2015
- dom_default.putSticky(el, PHX_STREAM_REF, (el2) => el2.setAttribute(PHX_STREAM_REF, ref));
2109
+ this.setStreamRef(el, ref);
2110
+ if (!reset && !isNew) {
2111
+ return;
2112
+ }
2016
2113
  if (streamAt === 0) {
2017
2114
  el.parentElement.insertBefore(el, el.parentElement.firstElementChild);
2018
2115
  } else if (streamAt > 0) {
@@ -2029,6 +2126,16 @@ var DOMPatch = class {
2029
2126
  }
2030
2127
  }
2031
2128
  }
2129
+ this.maybeLimitStream(el);
2130
+ }
2131
+ maybeLimitStream(el) {
2132
+ let { limit } = this.getStreamInsert(el);
2133
+ let children = limit !== null && Array.from(el.parentElement.children);
2134
+ if (limit && limit < 0 && children.length > limit * -1) {
2135
+ children.slice(0, children.length + limit).forEach((child) => this.removeStreamChildElement(child));
2136
+ } else if (limit && limit >= 0 && children.length > limit) {
2137
+ children.slice(limit).forEach((child) => this.removeStreamChildElement(child));
2138
+ }
2032
2139
  }
2033
2140
  transitionPendingRemoves() {
2034
2141
  let { pendingRemoves, liveSocket } = this;
@@ -2046,6 +2153,20 @@ var DOMPatch = class {
2046
2153
  });
2047
2154
  }
2048
2155
  }
2156
+ isChangedSelect(fromEl, toEl) {
2157
+ if (!(fromEl instanceof HTMLSelectElement) || fromEl.multiple) {
2158
+ return false;
2159
+ }
2160
+ if (fromEl.options.length !== toEl.options.length) {
2161
+ return true;
2162
+ }
2163
+ let fromSelected = fromEl.selectedOptions[0];
2164
+ let toSelected = toEl.selectedOptions[0];
2165
+ if (fromSelected && fromSelected.hasAttribute("selected")) {
2166
+ toSelected.setAttribute("selected", fromSelected.getAttribute("selected"));
2167
+ }
2168
+ return !fromEl.isEqualNode(toEl);
2169
+ }
2049
2170
  isCIDPatch() {
2050
2171
  return this.cidPatch;
2051
2172
  }
@@ -2087,66 +2208,42 @@ var VOID_TAGS = new Set([
2087
2208
  "track",
2088
2209
  "wbr"
2089
2210
  ]);
2090
- var endingTagNameChars = new Set([">", "/", " ", "\n", " ", "\r"]);
2091
2211
  var quoteChars = new Set(["'", '"']);
2092
2212
  var modifyRoot = (html, attrs, clearInnerHTML) => {
2093
2213
  let i = 0;
2094
2214
  let insideComment = false;
2095
2215
  let beforeTag, afterTag, tag, tagNameEndsAt, id, newHTML;
2096
- while (i < html.length) {
2097
- let char = html.charAt(i);
2098
- if (insideComment) {
2099
- if (char === "-" && html.slice(i, i + 3) === "-->") {
2100
- insideComment = false;
2101
- i += 3;
2102
- } else {
2103
- i++;
2104
- }
2105
- } else if (char === "<" && html.slice(i, i + 4) === "<!--") {
2106
- insideComment = true;
2107
- i += 4;
2108
- } else if (char === "<") {
2109
- beforeTag = html.slice(0, i);
2110
- let iAtOpen = i;
2216
+ let lookahead = html.match(/^(\s*(?:<!--.*?-->\s*)*)<([^\s\/>]+)/);
2217
+ if (lookahead === null) {
2218
+ throw new Error(`malformed html ${html}`);
2219
+ }
2220
+ i = lookahead[0].length;
2221
+ beforeTag = lookahead[1];
2222
+ tag = lookahead[2];
2223
+ tagNameEndsAt = i;
2224
+ for (i; i < html.length; i++) {
2225
+ if (html.charAt(i) === ">") {
2226
+ break;
2227
+ }
2228
+ if (html.charAt(i) === "=") {
2229
+ let isId = html.slice(i - 3, i) === " id";
2111
2230
  i++;
2112
- for (i; i < html.length; i++) {
2113
- if (endingTagNameChars.has(html.charAt(i))) {
2114
- break;
2231
+ let char = html.charAt(i);
2232
+ if (quoteChars.has(char)) {
2233
+ let attrStartsAt = i;
2234
+ i++;
2235
+ for (i; i < html.length; i++) {
2236
+ if (html.charAt(i) === char) {
2237
+ break;
2238
+ }
2115
2239
  }
2116
- }
2117
- tagNameEndsAt = i;
2118
- tag = html.slice(iAtOpen + 1, tagNameEndsAt);
2119
- for (i; i < html.length; i++) {
2120
- if (html.charAt(i) === ">") {
2240
+ if (isId) {
2241
+ id = html.slice(attrStartsAt + 1, i);
2121
2242
  break;
2122
2243
  }
2123
- if (html.charAt(i) === "=") {
2124
- let isId = html.slice(i - 3, i) === " id";
2125
- i++;
2126
- let char2 = html.charAt(i);
2127
- if (quoteChars.has(char2)) {
2128
- let attrStartsAt = i;
2129
- i++;
2130
- for (i; i < html.length; i++) {
2131
- if (html.charAt(i) === char2) {
2132
- break;
2133
- }
2134
- }
2135
- if (isId) {
2136
- id = html.slice(attrStartsAt + 1, i);
2137
- break;
2138
- }
2139
- }
2140
- }
2141
2244
  }
2142
- break;
2143
- } else {
2144
- i++;
2145
2245
  }
2146
2246
  }
2147
- if (!tag) {
2148
- throw new Error(`malformed html ${html}`);
2149
- }
2150
2247
  let closeAt = html.length - 1;
2151
2248
  insideComment = false;
2152
2249
  while (closeAt >= beforeTag.length + tag.length) {
@@ -2221,6 +2318,11 @@ var Rendered = class {
2221
2318
  getComponent(diff, cid) {
2222
2319
  return diff[COMPONENTS][cid];
2223
2320
  }
2321
+ resetRender(cid) {
2322
+ if (this.rendered[COMPONENTS][cid]) {
2323
+ this.rendered[COMPONENTS][cid].reset = true;
2324
+ }
2325
+ }
2224
2326
  mergeDiff(diff) {
2225
2327
  let newc = diff[COMPONENTS];
2226
2328
  let cache = {};
@@ -2348,8 +2450,8 @@ var Rendered = class {
2348
2450
  if (isRoot) {
2349
2451
  let skip = false;
2350
2452
  let attrs;
2351
- if (changeTracking || Object.keys(rootAttrs).length > 0) {
2352
- skip = !rendered.newRender;
2453
+ if (changeTracking || rendered.magicId) {
2454
+ skip = changeTracking && !rendered.newRender;
2353
2455
  attrs = { [PHX_MAGIC_ID]: rendered.magicId, ...rootAttrs };
2354
2456
  } else {
2355
2457
  attrs = rootAttrs;
@@ -2399,8 +2501,9 @@ var Rendered = class {
2399
2501
  let skip = onlyCids && !onlyCids.has(cid);
2400
2502
  component.newRender = !skip;
2401
2503
  component.magicId = `${this.parentViewId()}-c-${cid}`;
2402
- let changeTracking = true;
2504
+ let changeTracking = !component.reset;
2403
2505
  let [html, streams] = this.recursiveToString(component, components, onlyCids, changeTracking, attrs);
2506
+ delete component.reset;
2404
2507
  return [html, streams];
2405
2508
  }
2406
2509
  };
@@ -2484,6 +2587,7 @@ var ViewHook = class {
2484
2587
 
2485
2588
  // js/phoenix_live_view/js.js
2486
2589
  var focusStack = null;
2590
+ var default_transition_time = 200;
2487
2591
  var JS = {
2488
2592
  exec(eventType, phxEvent, view, sourceEl, defaults) {
2489
2593
  let [defaultKind, defaultArgs] = defaults || [null, { callback: defaults && defaults.callback }];
@@ -2505,7 +2609,7 @@ var JS = {
2505
2609
  const rect = el.getBoundingClientRect();
2506
2610
  return rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
2507
2611
  },
2508
- exec_exec(eventType, phxEvent, view, sourceEl, el, [attr, to]) {
2612
+ exec_exec(eventType, phxEvent, view, sourceEl, el, { attr, to }) {
2509
2613
  let nodes = to ? dom_default.all(document, to) : [sourceEl];
2510
2614
  nodes.forEach((node) => {
2511
2615
  let encodedJS = node.getAttribute(attr);
@@ -2521,14 +2625,14 @@ var JS = {
2521
2625
  dom_default.dispatchEvent(el, event, { detail, bubbles });
2522
2626
  },
2523
2627
  exec_push(eventType, phxEvent, view, sourceEl, el, args) {
2524
- if (!view.isConnected()) {
2525
- return;
2526
- }
2527
2628
  let { event, data, target, page_loading, loading, value, dispatcher, callback } = args;
2528
2629
  let pushOpts = { loading, value, target, page_loading: !!page_loading };
2529
2630
  let targetSrc = eventType === "change" && dispatcher ? dispatcher : sourceEl;
2530
2631
  let phxTarget = target || targetSrc.getAttribute(view.binding("target")) || targetSrc;
2531
2632
  view.withinTargets(phxTarget, (targetView, targetCtx) => {
2633
+ if (!targetView.isConnected()) {
2634
+ return;
2635
+ }
2532
2636
  if (eventType === "change") {
2533
2637
  let { newCid, _target } = args;
2534
2638
  _target = _target || (dom_default.isFormInput(sourceEl) ? sourceEl.name : void 0);
@@ -2573,6 +2677,24 @@ var JS = {
2573
2677
  exec_remove_class(eventType, phxEvent, view, sourceEl, el, { names, transition, time }) {
2574
2678
  this.addOrRemoveClasses(el, [], names, transition, time, view);
2575
2679
  },
2680
+ exec_toggle_class(eventType, phxEvent, view, sourceEl, el, { to, names, transition, time }) {
2681
+ this.toggleClasses(el, names, transition, view);
2682
+ },
2683
+ exec_toggle_attr(eventType, phxEvent, view, sourceEl, el, { attr: [attr, val1, val2] }) {
2684
+ if (el.hasAttribute(attr)) {
2685
+ if (val2 !== void 0) {
2686
+ if (el.getAttribute(attr) === val1) {
2687
+ this.setOrRemoveAttrs(el, [[attr, val2]], []);
2688
+ } else {
2689
+ this.setOrRemoveAttrs(el, [[attr, val1]], []);
2690
+ }
2691
+ } else {
2692
+ this.setOrRemoveAttrs(el, [], [attr]);
2693
+ }
2694
+ } else {
2695
+ this.setOrRemoveAttrs(el, [[attr, val1]], []);
2696
+ }
2697
+ },
2576
2698
  exec_transition(eventType, phxEvent, view, sourceEl, el, { time, transition }) {
2577
2699
  this.addOrRemoveClasses(el, [], [], transition, time, view);
2578
2700
  },
@@ -2602,6 +2724,7 @@ var JS = {
2602
2724
  }
2603
2725
  },
2604
2726
  toggle(eventType, view, el, display, ins, outs, time) {
2727
+ time = time || default_transition_time;
2605
2728
  let [inClasses, inStartClasses, inEndClasses] = ins || [[], [], []];
2606
2729
  let [outClasses, outStartClasses, outEndClasses] = outs || [[], [], []];
2607
2730
  if (inClasses.length > 0 || outClasses.length > 0) {
@@ -2655,7 +2778,16 @@ var JS = {
2655
2778
  }
2656
2779
  }
2657
2780
  },
2781
+ toggleClasses(el, classes, transition, time, view) {
2782
+ window.requestAnimationFrame(() => {
2783
+ let [prevAdds, prevRemoves] = dom_default.getSticky(el, "classes", [[], []]);
2784
+ let newAdds = classes.filter((name) => prevAdds.indexOf(name) < 0 && !el.classList.contains(name));
2785
+ let newRemoves = classes.filter((name) => prevRemoves.indexOf(name) < 0 && el.classList.contains(name));
2786
+ this.addOrRemoveClasses(el, newAdds, newRemoves, transition, time, view);
2787
+ });
2788
+ },
2658
2789
  addOrRemoveClasses(el, adds, removes, transition, time, view) {
2790
+ time = time || default_transition_time;
2659
2791
  let [transitionRun, transitionStart, transitionEnd] = transition || [[], [], []];
2660
2792
  if (transitionRun.length > 0) {
2661
2793
  let onStart = () => {
@@ -2709,24 +2841,37 @@ var js_default = JS;
2709
2841
 
2710
2842
  // js/phoenix_live_view/view.js
2711
2843
  var serializeForm = (form, metadata, onlyNames = []) => {
2712
- let { submitter, ...meta } = metadata;
2713
- let formData = new FormData(form);
2714
- if (submitter && submitter.hasAttribute("name") && submitter.form && submitter.form === form) {
2715
- formData.append(submitter.name, submitter.value);
2716
- }
2717
- let toRemove = [];
2844
+ const { submitter, ...meta } = metadata;
2845
+ let injectedElement;
2846
+ if (submitter && submitter.name) {
2847
+ const input = document.createElement("input");
2848
+ input.type = "hidden";
2849
+ const formId = submitter.getAttribute("form");
2850
+ if (formId) {
2851
+ input.setAttribute("form", form);
2852
+ }
2853
+ input.name = submitter.name;
2854
+ input.value = submitter.value;
2855
+ submitter.parentElement.insertBefore(input, submitter);
2856
+ injectedElement = input;
2857
+ }
2858
+ const formData = new FormData(form);
2859
+ const toRemove = [];
2718
2860
  formData.forEach((val, key, _index) => {
2719
2861
  if (val instanceof File) {
2720
2862
  toRemove.push(key);
2721
2863
  }
2722
2864
  });
2723
2865
  toRemove.forEach((key) => formData.delete(key));
2724
- let params = new URLSearchParams();
2866
+ const params = new URLSearchParams();
2725
2867
  for (let [key, val] of formData.entries()) {
2726
2868
  if (onlyNames.length === 0 || onlyNames.indexOf(key) >= 0) {
2727
2869
  params.append(key, val);
2728
2870
  }
2729
2871
  }
2872
+ if (submitter && injectedElement) {
2873
+ submitter.parentElement.removeChild(injectedElement);
2874
+ }
2730
2875
  for (let metaKey in meta) {
2731
2876
  params.append(metaKey, meta[metaKey]);
2732
2877
  }
@@ -2930,6 +3075,9 @@ var View = class {
2930
3075
  if (phxStatic) {
2931
3076
  toEl.setAttribute(PHX_STATIC, phxStatic);
2932
3077
  }
3078
+ if (fromEl) {
3079
+ fromEl.setAttribute(PHX_ROOT_ID, this.root.id);
3080
+ }
2933
3081
  return this.joinChild(toEl);
2934
3082
  });
2935
3083
  if (newChildren.length === 0) {
@@ -3009,6 +3157,9 @@ var View = class {
3009
3157
  let updatedHookIds = new Set();
3010
3158
  patch.after("added", (el) => {
3011
3159
  this.liveSocket.triggerDOM("onNodeAdded", [el]);
3160
+ let phxViewportTop = this.binding(PHX_VIEWPORT_TOP);
3161
+ let phxViewportBottom = this.binding(PHX_VIEWPORT_BOTTOM);
3162
+ dom_default.maybeAddPrivateHooks(el, phxViewportTop, phxViewportBottom);
3012
3163
  this.maybeAddNewHook(el);
3013
3164
  if (el.getAttribute) {
3014
3165
  this.maybeMounted(el);
@@ -3385,10 +3536,11 @@ var View = class {
3385
3536
  }
3386
3537
  dom_default.all(document, `[${PHX_REF_SRC}="${this.id}"][${PHX_REF}="${ref}"]`, (el) => {
3387
3538
  let disabledVal = el.getAttribute(PHX_DISABLED);
3539
+ let readOnlyVal = el.getAttribute(PHX_READONLY);
3388
3540
  el.removeAttribute(PHX_REF);
3389
3541
  el.removeAttribute(PHX_REF_SRC);
3390
- if (el.getAttribute(PHX_READONLY) !== null) {
3391
- el.readOnly = false;
3542
+ if (readOnlyVal !== null) {
3543
+ el.readOnly = readOnlyVal === "true" ? true : false;
3392
3544
  el.removeAttribute(PHX_READONLY);
3393
3545
  }
3394
3546
  if (disabledVal !== null) {
@@ -3430,6 +3582,7 @@ var View = class {
3430
3582
  if (disableText !== "") {
3431
3583
  el.innerText = disableText;
3432
3584
  }
3585
+ el.setAttribute(PHX_DISABLED, el.getAttribute(PHX_DISABLED) || el.disabled);
3433
3586
  el.setAttribute("disabled", "");
3434
3587
  }
3435
3588
  });
@@ -3531,6 +3684,9 @@ var View = class {
3531
3684
  let refGenerator = () => this.putRef([inputEl, inputEl.form], "change", opts);
3532
3685
  let formData;
3533
3686
  let meta = this.extractMeta(inputEl.form);
3687
+ if (inputEl instanceof HTMLButtonElement) {
3688
+ meta.submitter = inputEl;
3689
+ }
3534
3690
  if (inputEl.getAttribute(this.binding("change"))) {
3535
3691
  formData = serializeForm(inputEl.form, { _target: opts._target, ...meta }, [inputEl.name]);
3536
3692
  } else {
@@ -3555,6 +3711,7 @@ var View = class {
3555
3711
  this.uploadFiles(inputEl.form, targetCtx, ref, cid, (_uploads) => {
3556
3712
  callback && callback(resp);
3557
3713
  this.triggerAwaitingSubmit(inputEl.form);
3714
+ this.undoRefs(ref);
3558
3715
  });
3559
3716
  }
3560
3717
  } else {
@@ -3753,7 +3910,7 @@ var View = class {
3753
3910
  let template = document.createElement("template");
3754
3911
  template.innerHTML = html;
3755
3912
  return dom_default.all(this.el, `form[${phxChange}]`).filter((form) => form.id && this.ownsElement(form)).filter((form) => form.elements.length > 0).filter((form) => form.getAttribute(this.binding(PHX_AUTO_RECOVER)) !== "ignore").map((form) => {
3756
- const phxChangeValue = form.getAttribute(phxChange).replaceAll(/([\[\]"])/g, "\\$1");
3913
+ const phxChangeValue = CSS.escape(form.getAttribute(phxChange));
3757
3914
  let newForm = template.content.querySelector(`form[id="${form.id}"][${phxChange}="${phxChangeValue}"]`);
3758
3915
  if (newForm) {
3759
3916
  return [form, newForm, this.targetComponentID(newForm)];
@@ -3763,18 +3920,19 @@ var View = class {
3763
3920
  }).filter(([form, newForm, newCid]) => newForm);
3764
3921
  }
3765
3922
  maybePushComponentsDestroyed(destroyedCIDs) {
3766
- let willDestroyCIDs = destroyedCIDs.filter((cid) => {
3923
+ let willDestroyCIDs = destroyedCIDs.concat(this.pruningCIDs).filter((cid) => {
3767
3924
  return dom_default.findComponentNodeList(this.el, cid).length === 0;
3768
3925
  });
3926
+ this.pruningCIDs = willDestroyCIDs.concat([]);
3769
3927
  if (willDestroyCIDs.length > 0) {
3770
- this.pruningCIDs.push(...willDestroyCIDs);
3928
+ willDestroyCIDs.forEach((cid) => this.rendered.resetRender(cid));
3771
3929
  this.pushWithReply(null, "cids_will_destroy", { cids: willDestroyCIDs }, () => {
3772
- this.pruningCIDs = this.pruningCIDs.filter((cid) => willDestroyCIDs.indexOf(cid) !== -1);
3773
3930
  let completelyDestroyCIDs = willDestroyCIDs.filter((cid) => {
3774
3931
  return dom_default.findComponentNodeList(this.el, cid).length === 0;
3775
3932
  });
3776
3933
  if (completelyDestroyCIDs.length > 0) {
3777
3934
  this.pushWithReply(null, "cids_destroyed", { cids: completelyDestroyCIDs }, (resp) => {
3935
+ this.pruningCIDs = this.pruningCIDs.filter((cid) => resp.cids.indexOf(cid) === -1);
3778
3936
  this.rendered.pruneCIDs(resp.cids);
3779
3937
  });
3780
3938
  }
@@ -4090,22 +4248,26 @@ var LiveSocket = class {
4090
4248
  this.main.destroy();
4091
4249
  this.main = this.newRootView(newMainEl, flash, liveReferer);
4092
4250
  this.main.setRedirect(href);
4093
- this.transitionRemoves();
4251
+ this.transitionRemoves(null, true);
4094
4252
  this.main.join((joinCount, onDone) => {
4095
4253
  if (joinCount === 1 && this.commitPendingLink(linkRef)) {
4096
4254
  this.requestDOMUpdate(() => {
4097
4255
  dom_default.findPhxSticky(document).forEach((el) => newMainEl.appendChild(el));
4098
4256
  this.outgoingMainEl.replaceWith(newMainEl);
4099
4257
  this.outgoingMainEl = null;
4100
- callback && requestAnimationFrame(() => callback(linkRef));
4258
+ callback && callback(linkRef);
4101
4259
  onDone();
4102
4260
  });
4103
4261
  }
4104
4262
  });
4105
4263
  }
4106
- transitionRemoves(elements) {
4264
+ transitionRemoves(elements, skipSticky) {
4107
4265
  let removeAttr = this.binding("remove");
4108
4266
  elements = elements || dom_default.all(document, `[${removeAttr}]`);
4267
+ if (skipSticky) {
4268
+ const stickies = dom_default.findPhxSticky(document) || [];
4269
+ elements = elements.filter((el) => !dom_default.isChildOfAny(el, stickies));
4270
+ }
4109
4271
  elements.forEach((el) => {
4110
4272
  this.execJS(el, el.getAttribute(removeAttr), "remove");
4111
4273
  });
@@ -4321,6 +4483,8 @@ var LiveSocket = class {
4321
4483
  if (capture) {
4322
4484
  target = e.target.matches(`[${click}]`) ? e.target : e.target.querySelector(`[${click}]`);
4323
4485
  } else {
4486
+ if (e.detail === 0)
4487
+ this.clickStartedAtTarget = e.target;
4324
4488
  let clickStartedAtTarget = this.clickStartedAtTarget || e.target;
4325
4489
  target = closestPhxBinding(clickStartedAtTarget, click);
4326
4490
  this.dispatchClickAway(e, clickStartedAtTarget);
@@ -4350,7 +4514,7 @@ var LiveSocket = class {
4350
4514
  let phxClickAway = this.binding("click-away");
4351
4515
  dom_default.all(document, `[${phxClickAway}]`, (el) => {
4352
4516
  if (!(el.isSameNode(clickStartedAt) || el.contains(clickStartedAt))) {
4353
- this.withinOwners(e.target, (view) => {
4517
+ this.withinOwners(el, (view) => {
4354
4518
  let phxEvent = el.getAttribute(phxClickAway);
4355
4519
  if (js_default.isVisible(el) && js_default.isInViewport(el)) {
4356
4520
  js_default.exec("click", phxEvent, view, el, ["push", { data: this.eventMeta("click", e, e.target) }]);
@@ -4442,7 +4606,7 @@ var LiveSocket = class {
4442
4606
  return callback ? callback(done) : done;
4443
4607
  }
4444
4608
  pushHistoryPatch(href, linkState, targetEl) {
4445
- if (!this.isConnected()) {
4609
+ if (!this.isConnected() || !this.main.isMain()) {
4446
4610
  return browser_default.redirect(href);
4447
4611
  }
4448
4612
  this.withPageLoading({ to: href, kind: "patch" }, (done) => {
@@ -4461,7 +4625,7 @@ var LiveSocket = class {
4461
4625
  this.registerNewLocation(window.location);
4462
4626
  }
4463
4627
  historyRedirect(href, linkState, flash) {
4464
- if (!this.isConnected()) {
4628
+ if (!this.isConnected() || !this.main.isMain()) {
4465
4629
  return browser_default.redirect(href, flash);
4466
4630
  }
4467
4631
  if (/^\/$|^\/[^\/]+.*$/.test(href)) {
@@ -4562,9 +4726,11 @@ var LiveSocket = class {
4562
4726
  let form = e.target;
4563
4727
  dom_default.resetForm(form, this.binding(PHX_FEEDBACK_FOR));
4564
4728
  let input = Array.from(form.elements).find((el) => el.type === "reset");
4565
- window.requestAnimationFrame(() => {
4566
- input.dispatchEvent(new Event("input", { bubbles: true, cancelable: false }));
4567
- });
4729
+ if (input) {
4730
+ window.requestAnimationFrame(() => {
4731
+ input.dispatchEvent(new Event("input", { bubbles: true, cancelable: false }));
4732
+ });
4733
+ }
4568
4734
  });
4569
4735
  }
4570
4736
  debounce(el, event, eventType, callback) {