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.
@@ -24,7 +24,8 @@ var PHX_EVENT_CLASSES = [
24
24
  "phx-keydown-loading",
25
25
  "phx-keyup-loading",
26
26
  "phx-blur-loading",
27
- "phx-focus-loading"
27
+ "phx-focus-loading",
28
+ "phx-hook-loading"
28
29
  ];
29
30
  var PHX_COMPONENT = "data-phx-component";
30
31
  var PHX_LIVE_LINK = "data-phx-link";
@@ -56,6 +57,7 @@ var PHX_VIEWPORT_TOP = "viewport-top";
56
57
  var PHX_VIEWPORT_BOTTOM = "viewport-bottom";
57
58
  var PHX_TRIGGER_ACTION = "trigger-action";
58
59
  var PHX_FEEDBACK_FOR = "feedback-for";
60
+ var PHX_FEEDBACK_GROUP = "feedback-group";
59
61
  var PHX_HAS_FOCUSED = "phx-has-focused";
60
62
  var FOCUSABLE_INPUTS = ["text", "textarea", "number", "email", "password", "search", "tel", "url", "date", "time", "datetime-local", "color", "range"];
61
63
  var CHECKABLE_INPUTS = ["checkbox", "radio"];
@@ -311,7 +313,9 @@ var DOM = {
311
313
  return inputEl.hasAttribute("data-phx-auto-upload");
312
314
  },
313
315
  findUploadInputs(node) {
314
- return this.all(node, `input[type="file"][${PHX_UPLOAD_REF}]`);
316
+ const formId = node.id;
317
+ const inputsOutsideForm = this.all(document, `input[type="file"][${PHX_UPLOAD_REF}][form="${formId}"]`);
318
+ return this.all(node, `input[type="file"][${PHX_UPLOAD_REF}]`).concat(inputsOutsideForm);
315
319
  },
316
320
  findComponentNodeList(node, cid) {
317
321
  return this.filterWithinSameLiveView(this.all(node, `[${PHX_COMPONENT}="${cid}"]`), node);
@@ -505,7 +509,9 @@ var DOM = {
505
509
  });
506
510
  }
507
511
  if (this.once(el, "bind-debounce")) {
508
- el.addEventListener("blur", () => this.triggerCycle(el, DEBOUNCE_TRIGGER));
512
+ el.addEventListener("blur", () => {
513
+ callback();
514
+ });
509
515
  }
510
516
  }
511
517
  },
@@ -538,16 +544,39 @@ var DOM = {
538
544
  el.setAttribute("data-phx-hook", "Phoenix.InfiniteScroll");
539
545
  }
540
546
  },
541
- maybeHideFeedback(container, inputs, phxFeedbackFor) {
547
+ maybeHideFeedback(container, forms, phxFeedbackFor, phxFeedbackGroup) {
542
548
  let feedbacks = [];
543
- inputs.forEach((input) => {
544
- if (!(this.private(input, PHX_HAS_FOCUSED) || this.private(input, PHX_HAS_SUBMITTED))) {
545
- feedbacks.push(input.name);
546
- if (input.name.endsWith("[]")) {
547
- feedbacks.push(input.name.slice(0, -2));
549
+ let inputNamesFocused = {};
550
+ let feedbackGroups = {};
551
+ forms.forEach((form) => {
552
+ Array.from(form.elements).forEach((input) => {
553
+ const group = input.getAttribute(phxFeedbackGroup);
554
+ if (group && !(group in feedbackGroups)) {
555
+ feedbackGroups[group] = true;
548
556
  }
549
- }
557
+ if (!(input.name in inputNamesFocused)) {
558
+ inputNamesFocused[input.name] = false;
559
+ }
560
+ if (this.private(input, PHX_HAS_FOCUSED) || this.private(input, PHX_HAS_SUBMITTED)) {
561
+ inputNamesFocused[input.name] = true;
562
+ if (group) {
563
+ feedbackGroups[group] = false;
564
+ }
565
+ }
566
+ });
550
567
  });
568
+ for (const [name, focused] of Object.entries(inputNamesFocused)) {
569
+ if (!focused) {
570
+ feedbacks.push(name);
571
+ if (name.endsWith("[]")) {
572
+ feedbacks.push(name.slice(0, -2));
573
+ }
574
+ }
575
+ }
576
+ for (const [group, noFeedback] of Object.entries(feedbackGroups)) {
577
+ if (noFeedback)
578
+ feedbacks.push(group);
579
+ }
551
580
  if (feedbacks.length > 0) {
552
581
  let selector = feedbacks.map((f) => `[${phxFeedbackFor}="${f}"]`).join(", ");
553
582
  DOM.all(container, selector, (el) => el.classList.add(PHX_NO_FEEDBACK_CLASS));
@@ -578,11 +607,19 @@ var DOM = {
578
607
  isPhxSticky(node) {
579
608
  return node.getAttribute && node.getAttribute(PHX_STICKY) !== null;
580
609
  },
610
+ isChildOfAny(el, parents) {
611
+ return !!parents.find((parent) => parent.contains(el));
612
+ },
581
613
  firstPhxChild(el) {
582
614
  return this.isPhxChild(el) ? el : this.all(el, `[${PHX_PARENT_ID}]`)[0];
583
615
  },
584
616
  dispatchEvent(target, name, opts = {}) {
585
- let bubbles = opts.bubbles === void 0 ? true : !!opts.bubbles;
617
+ let defaultBubble = true;
618
+ let isUploadTarget = target.nodeName === "INPUT" && target.type === "file";
619
+ if (isUploadTarget && name === "click") {
620
+ defaultBubble = false;
621
+ }
622
+ let bubbles = opts.bubbles === void 0 ? defaultBubble : !!opts.bubbles;
586
623
  let eventOpts = { bubbles, cancelable: true, detail: opts.detail || {} };
587
624
  let event = name === "click" ? new MouseEvent("click", eventOpts) : new CustomEvent(name, eventOpts);
588
625
  target.dispatchEvent(event);
@@ -597,20 +634,27 @@ var DOM = {
597
634
  }
598
635
  },
599
636
  mergeAttrs(target, source, opts = {}) {
600
- let exclude = opts.exclude || [];
637
+ let exclude = new Set(opts.exclude || []);
601
638
  let isIgnored = opts.isIgnored;
602
639
  let sourceAttrs = source.attributes;
603
640
  for (let i = sourceAttrs.length - 1; i >= 0; i--) {
604
641
  let name = sourceAttrs[i].name;
605
- if (exclude.indexOf(name) < 0) {
606
- target.setAttribute(name, source.getAttribute(name));
642
+ if (!exclude.has(name)) {
643
+ const sourceValue = source.getAttribute(name);
644
+ if (target.getAttribute(name) !== sourceValue && (!isIgnored || isIgnored && name.startsWith("data-"))) {
645
+ target.setAttribute(name, sourceValue);
646
+ }
647
+ } else {
648
+ if (name === "value" && target.value === source.value) {
649
+ target.setAttribute("value", source.getAttribute(name));
650
+ }
607
651
  }
608
652
  }
609
653
  let targetAttrs = target.attributes;
610
654
  for (let i = targetAttrs.length - 1; i >= 0; i--) {
611
655
  let name = targetAttrs[i].name;
612
656
  if (isIgnored) {
613
- if (name.startsWith("data-") && !source.hasAttribute(name)) {
657
+ if (name.startsWith("data-") && !source.hasAttribute(name) && ![PHX_REF, PHX_REF_SRC].includes(name)) {
614
658
  target.removeAttribute(name);
615
659
  }
616
660
  } else {
@@ -634,6 +678,9 @@ var DOM = {
634
678
  return el.setSelectionRange && (el.type === "text" || el.type === "textarea");
635
679
  },
636
680
  restoreFocus(focused, selectionStart, selectionEnd) {
681
+ if (focused instanceof HTMLSelectElement) {
682
+ focused.focus();
683
+ }
637
684
  if (!DOM.isTextualInput(focused)) {
638
685
  return;
639
686
  }
@@ -757,15 +804,22 @@ var dom_default = DOM;
757
804
  var UploadEntry = class {
758
805
  static isActive(fileEl, file) {
759
806
  let isNew = file._phxRef === void 0;
807
+ let isPreflightInProgress = UploadEntry.isPreflightInProgress(file);
760
808
  let activeRefs = fileEl.getAttribute(PHX_ACTIVE_ENTRY_REFS).split(",");
761
809
  let isActive = activeRefs.indexOf(LiveUploader.genFileRef(file)) >= 0;
762
- return file.size > 0 && (isNew || isActive);
810
+ return file.size > 0 && (isNew || isActive || !isPreflightInProgress);
763
811
  }
764
812
  static isPreflighted(fileEl, file) {
765
813
  let preflightedRefs = fileEl.getAttribute(PHX_PREFLIGHTED_REFS).split(",");
766
814
  let isPreflighted = preflightedRefs.indexOf(LiveUploader.genFileRef(file)) >= 0;
767
815
  return isPreflighted && this.isActive(fileEl, file);
768
816
  }
817
+ static isPreflightInProgress(file) {
818
+ return file._preflightInProgress === true;
819
+ }
820
+ static markPreflightInProgress(file) {
821
+ file._preflightInProgress = true;
822
+ }
769
823
  constructor(fileEl, file, view) {
770
824
  this.ref = LiveUploader.genFileRef(file);
771
825
  this.fileEl = fileEl;
@@ -932,12 +986,16 @@ var LiveUploader = class {
932
986
  return Array.from(fileInputs).filter((input) => this.filesAwaitingPreflight(input).length > 0);
933
987
  }
934
988
  static filesAwaitingPreflight(input) {
935
- return this.activeFiles(input).filter((f) => !UploadEntry.isPreflighted(input, f));
989
+ return this.activeFiles(input).filter((f) => !UploadEntry.isPreflighted(input, f) && !UploadEntry.isPreflightInProgress(f));
990
+ }
991
+ static markPreflightInProgress(entries) {
992
+ entries.forEach((entry) => UploadEntry.markPreflightInProgress(entry.file));
936
993
  }
937
994
  constructor(inputEl, view, onComplete) {
938
995
  this.view = view;
939
996
  this.onComplete = onComplete;
940
997
  this._entries = Array.from(LiveUploader.filesAwaitingPreflight(inputEl) || []).map((file) => new UploadEntry(inputEl, file, view));
998
+ LiveUploader.markPreflightInProgress(this._entries);
941
999
  this.numEntriesInProgress = this._entries.length;
942
1000
  }
943
1001
  entries() {
@@ -1078,23 +1136,50 @@ var Hooks = {
1078
1136
  }
1079
1137
  }
1080
1138
  };
1081
- var scrollTop = () => document.documentElement.scrollTop || document.body.scrollTop;
1082
- var winHeight = () => window.innerHeight || document.documentElement.clientHeight;
1083
- var isAtViewportTop = (el) => {
1139
+ var findScrollContainer = (el) => {
1140
+ if (["scroll", "auto"].indexOf(getComputedStyle(el).overflowY) >= 0)
1141
+ return el;
1142
+ if (document.documentElement === el)
1143
+ return null;
1144
+ return findScrollContainer(el.parentElement);
1145
+ };
1146
+ var scrollTop = (scrollContainer) => {
1147
+ if (scrollContainer) {
1148
+ return scrollContainer.scrollTop;
1149
+ } else {
1150
+ return document.documentElement.scrollTop || document.body.scrollTop;
1151
+ }
1152
+ };
1153
+ var bottom = (scrollContainer) => {
1154
+ if (scrollContainer) {
1155
+ return scrollContainer.getBoundingClientRect().bottom;
1156
+ } else {
1157
+ return window.innerHeight || document.documentElement.clientHeight;
1158
+ }
1159
+ };
1160
+ var top = (scrollContainer) => {
1161
+ if (scrollContainer) {
1162
+ return scrollContainer.getBoundingClientRect().top;
1163
+ } else {
1164
+ return 0;
1165
+ }
1166
+ };
1167
+ var isAtViewportTop = (el, scrollContainer) => {
1084
1168
  let rect = el.getBoundingClientRect();
1085
- return rect.top >= 0 && rect.left >= 0 && rect.top <= winHeight();
1169
+ return rect.top >= top(scrollContainer) && rect.left >= 0 && rect.top <= bottom(scrollContainer);
1086
1170
  };
1087
- var isAtViewportBottom = (el) => {
1171
+ var isAtViewportBottom = (el, scrollContainer) => {
1088
1172
  let rect = el.getBoundingClientRect();
1089
- return rect.right >= 0 && rect.left >= 0 && rect.bottom <= winHeight();
1173
+ return rect.right >= top(scrollContainer) && rect.left >= 0 && rect.bottom <= bottom(scrollContainer);
1090
1174
  };
1091
- var isWithinViewport = (el) => {
1175
+ var isWithinViewport = (el, scrollContainer) => {
1092
1176
  let rect = el.getBoundingClientRect();
1093
- return rect.top >= 0 && rect.left >= 0 && rect.top <= winHeight();
1177
+ return rect.top >= top(scrollContainer) && rect.left >= 0 && rect.top <= bottom(scrollContainer);
1094
1178
  };
1095
1179
  Hooks.InfiniteScroll = {
1096
1180
  mounted() {
1097
- let scrollBefore = scrollTop();
1181
+ this.scrollContainer = findScrollContainer(this.el);
1182
+ let scrollBefore = scrollTop(this.scrollContainer);
1098
1183
  let topOverran = false;
1099
1184
  let throttleInterval = 500;
1100
1185
  let pendingOp = null;
@@ -1108,22 +1193,26 @@ Hooks.InfiniteScroll = {
1108
1193
  pendingOp = () => firstChild.scrollIntoView({ block: "start" });
1109
1194
  this.liveSocket.execJSHookPush(this.el, topEvent, { id: firstChild.id }, () => {
1110
1195
  pendingOp = null;
1111
- if (!isWithinViewport(firstChild)) {
1112
- firstChild.scrollIntoView({ block: "start" });
1113
- }
1196
+ window.requestAnimationFrame(() => {
1197
+ if (!isWithinViewport(firstChild, this.scrollContainer)) {
1198
+ firstChild.scrollIntoView({ block: "start" });
1199
+ }
1200
+ });
1114
1201
  });
1115
1202
  });
1116
1203
  let onLastChildAtBottom = this.throttle(throttleInterval, (bottomEvent, lastChild) => {
1117
1204
  pendingOp = () => lastChild.scrollIntoView({ block: "end" });
1118
1205
  this.liveSocket.execJSHookPush(this.el, bottomEvent, { id: lastChild.id }, () => {
1119
1206
  pendingOp = null;
1120
- if (!isWithinViewport(lastChild)) {
1121
- lastChild.scrollIntoView({ block: "end" });
1122
- }
1207
+ window.requestAnimationFrame(() => {
1208
+ if (!isWithinViewport(lastChild, this.scrollContainer)) {
1209
+ lastChild.scrollIntoView({ block: "end" });
1210
+ }
1211
+ });
1123
1212
  });
1124
1213
  });
1125
- this.onScroll = (e) => {
1126
- let scrollNow = scrollTop();
1214
+ this.onScroll = (_e) => {
1215
+ let scrollNow = scrollTop(this.scrollContainer);
1127
1216
  if (pendingOp) {
1128
1217
  scrollBefore = scrollNow;
1129
1218
  return pendingOp();
@@ -1141,17 +1230,25 @@ Hooks.InfiniteScroll = {
1141
1230
  } else if (isScrollingDown && topOverran && rect.top <= 0) {
1142
1231
  topOverran = false;
1143
1232
  }
1144
- if (topEvent && isScrollingUp && isAtViewportTop(firstChild)) {
1233
+ if (topEvent && isScrollingUp && isAtViewportTop(firstChild, this.scrollContainer)) {
1145
1234
  onFirstChildAtTop(topEvent, firstChild);
1146
- } else if (bottomEvent && isScrollingDown && isAtViewportBottom(lastChild)) {
1235
+ } else if (bottomEvent && isScrollingDown && isAtViewportBottom(lastChild, this.scrollContainer)) {
1147
1236
  onLastChildAtBottom(bottomEvent, lastChild);
1148
1237
  }
1149
1238
  scrollBefore = scrollNow;
1150
1239
  };
1151
- window.addEventListener("scroll", this.onScroll);
1240
+ if (this.scrollContainer) {
1241
+ this.scrollContainer.addEventListener("scroll", this.onScroll);
1242
+ } else {
1243
+ window.addEventListener("scroll", this.onScroll);
1244
+ }
1152
1245
  },
1153
1246
  destroyed() {
1154
- window.removeEventListener("scroll", this.onScroll);
1247
+ if (this.scrollContainer) {
1248
+ this.scrollContainer.removeEventListener("scroll", this.onScroll);
1249
+ } else {
1250
+ window.removeEventListener("scroll", this.onScroll);
1251
+ }
1155
1252
  },
1156
1253
  throttle(interval, callback) {
1157
1254
  let lastCallAt = 0;
@@ -1598,6 +1695,7 @@ function morphdomFactory(morphAttrs2) {
1598
1695
  removeNode(curFromNodeChild, fromEl, true);
1599
1696
  }
1600
1697
  curFromNodeChild = matchingFromEl;
1698
+ curFromNodeKey = getNodeKey(curFromNodeChild);
1601
1699
  }
1602
1700
  } else {
1603
1701
  isCompatible = false;
@@ -1730,6 +1828,7 @@ var DOMPatch = class {
1730
1828
  this.html = html;
1731
1829
  this.streams = streams;
1732
1830
  this.streamInserts = {};
1831
+ this.streamComponentRestore = {};
1733
1832
  this.targetCID = targetCID;
1734
1833
  this.cidPatch = isCid(this.targetCID);
1735
1834
  this.pendingRemoves = [];
@@ -1759,7 +1858,6 @@ var DOMPatch = class {
1759
1858
  }
1760
1859
  markPrunableContentForRemoval() {
1761
1860
  let phxUpdate = this.liveSocket.binding(PHX_UPDATE);
1762
- dom_default.all(this.container, `[${phxUpdate}=${PHX_STREAM}]`, (el) => el.innerHTML = "");
1763
1861
  dom_default.all(this.container, `[${phxUpdate}=append] > *, [${phxUpdate}=prepend] > *`, (el) => {
1764
1862
  el.setAttribute(PHX_PRUNE, "");
1765
1863
  });
@@ -1774,12 +1872,13 @@ var DOMPatch = class {
1774
1872
  let { selectionStart, selectionEnd } = focused && dom_default.hasSelectionRange(focused) ? focused : {};
1775
1873
  let phxUpdate = liveSocket.binding(PHX_UPDATE);
1776
1874
  let phxFeedbackFor = liveSocket.binding(PHX_FEEDBACK_FOR);
1875
+ let phxFeedbackGroup = liveSocket.binding(PHX_FEEDBACK_GROUP);
1777
1876
  let disableWith = liveSocket.binding(PHX_DISABLE_WITH);
1778
1877
  let phxViewportTop = liveSocket.binding(PHX_VIEWPORT_TOP);
1779
1878
  let phxViewportBottom = liveSocket.binding(PHX_VIEWPORT_BOTTOM);
1780
1879
  let phxTriggerExternal = liveSocket.binding(PHX_TRIGGER_ACTION);
1781
1880
  let added = [];
1782
- let trackedInputs = [];
1881
+ let trackedForms = new Set();
1783
1882
  let updates = [];
1784
1883
  let appendPrependUpdates = [];
1785
1884
  let externalFormTriggered = null;
@@ -1787,16 +1886,12 @@ var DOMPatch = class {
1787
1886
  this.trackBefore("updated", container, container);
1788
1887
  liveSocket.time("morphdom", () => {
1789
1888
  this.streams.forEach(([ref, inserts, deleteIds, reset]) => {
1790
- Object.entries(inserts).forEach(([key, [streamAt, limit]]) => {
1791
- this.streamInserts[key] = { ref, streamAt, limit, resetKept: false };
1889
+ inserts.forEach(([key, streamAt, limit]) => {
1890
+ this.streamInserts[key] = { ref, streamAt, limit, reset };
1792
1891
  });
1793
1892
  if (reset !== void 0) {
1794
1893
  dom_default.all(container, `[${PHX_STREAM_REF}="${ref}"]`, (child) => {
1795
- if (inserts[child.id]) {
1796
- this.streamInserts[child.id].resetKept = true;
1797
- } else {
1798
- this.removeStreamChildElement(child);
1799
- }
1894
+ this.removeStreamChildElement(child);
1800
1895
  });
1801
1896
  }
1802
1897
  deleteIds.forEach((id) => {
@@ -1806,6 +1901,17 @@ var DOMPatch = class {
1806
1901
  }
1807
1902
  });
1808
1903
  });
1904
+ if (isJoinPatch) {
1905
+ dom_default.all(this.container, `[${phxUpdate}=${PHX_STREAM}]`, (el) => {
1906
+ this.liveSocket.owner(el, (view2) => {
1907
+ if (view2 === this.view) {
1908
+ Array.from(el.children).forEach((child) => {
1909
+ this.removeStreamChildElement(child);
1910
+ });
1911
+ }
1912
+ });
1913
+ });
1914
+ }
1809
1915
  morphdom_esm_default(targetContainer, html, {
1810
1916
  childrenOnly: targetContainer.getAttribute(PHX_COMPONENT) === null,
1811
1917
  getNodeKey: (node) => {
@@ -1821,11 +1927,17 @@ var DOMPatch = class {
1821
1927
  return from.getAttribute(phxUpdate) === PHX_STREAM;
1822
1928
  },
1823
1929
  addChild: (parent, child) => {
1824
- let { ref, streamAt, limit } = this.getStreamInsert(child);
1930
+ let { ref, streamAt } = this.getStreamInsert(child);
1825
1931
  if (ref === void 0) {
1826
1932
  return parent.appendChild(child);
1827
1933
  }
1828
- dom_default.putSticky(child, PHX_STREAM_REF, (el) => el.setAttribute(PHX_STREAM_REF, ref));
1934
+ this.setStreamRef(child, ref);
1935
+ child.querySelectorAll(`[${PHX_MAGIC_ID}][${PHX_SKIP}]`).forEach((el) => {
1936
+ const component = this.streamComponentRestore[el.getAttribute(PHX_MAGIC_ID)];
1937
+ if (component) {
1938
+ el.replaceWith(component);
1939
+ }
1940
+ });
1829
1941
  if (streamAt === 0) {
1830
1942
  parent.insertAdjacentElement("afterbegin", child);
1831
1943
  } else if (streamAt === -1) {
@@ -1834,18 +1946,6 @@ var DOMPatch = class {
1834
1946
  let sibling = Array.from(parent.children)[streamAt];
1835
1947
  parent.insertBefore(child, sibling);
1836
1948
  }
1837
- let children = limit !== null && Array.from(parent.children);
1838
- let childrenToRemove = [];
1839
- if (limit && limit < 0 && children.length > limit * -1) {
1840
- childrenToRemove = children.slice(0, children.length + limit);
1841
- } else if (limit && limit >= 0 && children.length > limit) {
1842
- childrenToRemove = children.slice(limit);
1843
- }
1844
- childrenToRemove.forEach((removeChild) => {
1845
- if (!this.streamInserts[removeChild.id]) {
1846
- this.removeStreamChildElement(removeChild);
1847
- }
1848
- });
1849
1949
  },
1850
1950
  onBeforeNodeAdded: (el) => {
1851
1951
  dom_default.maybeAddPrivateHooks(el, phxViewportTop, phxViewportBottom);
@@ -1854,7 +1954,7 @@ var DOMPatch = class {
1854
1954
  },
1855
1955
  onNodeAdded: (el) => {
1856
1956
  if (el.getAttribute) {
1857
- this.maybeReOrderStream(el);
1957
+ this.maybeReOrderStream(el, true);
1858
1958
  }
1859
1959
  if (el instanceof HTMLImageElement && el.srcset) {
1860
1960
  el.srcset = el.srcset;
@@ -1865,32 +1965,13 @@ var DOMPatch = class {
1865
1965
  externalFormTriggered = el;
1866
1966
  }
1867
1967
  if (el.getAttribute && el.getAttribute("name") && dom_default.isFormInput(el)) {
1868
- trackedInputs.push(el);
1968
+ trackedForms.add(el.form);
1869
1969
  }
1870
1970
  if (dom_default.isPhxChild(el) && view.ownsElement(el) || dom_default.isPhxSticky(el) && view.ownsElement(el.parentNode)) {
1871
1971
  this.trackAfter("phxChildAdded", el);
1872
1972
  }
1873
1973
  added.push(el);
1874
1974
  },
1875
- onBeforeElChildrenUpdated: (fromEl, toEl) => {
1876
- if (fromEl.getAttribute(phxUpdate) === PHX_STREAM) {
1877
- let toIds = Array.from(toEl.children).map((child) => child.id);
1878
- Array.from(fromEl.children).filter((child) => {
1879
- let { resetKept } = this.getStreamInsert(child);
1880
- return resetKept;
1881
- }).sort((a, b) => {
1882
- let aIdx = toIds.indexOf(a.id);
1883
- let bIdx = toIds.indexOf(b.id);
1884
- if (aIdx === bIdx) {
1885
- return 0;
1886
- } else if (aIdx < bIdx) {
1887
- return -1;
1888
- } else {
1889
- return 1;
1890
- }
1891
- }).forEach((child) => fromEl.appendChild(child));
1892
- }
1893
- },
1894
1975
  onNodeDiscarded: (el) => this.onNodeDiscarded(el),
1895
1976
  onBeforeNodeDiscarded: (el) => {
1896
1977
  if (el.getAttribute && el.getAttribute(PHX_PRUNE) !== null) {
@@ -1912,12 +1993,13 @@ var DOMPatch = class {
1912
1993
  externalFormTriggered = el;
1913
1994
  }
1914
1995
  updates.push(el);
1915
- this.maybeReOrderStream(el);
1996
+ this.maybeReOrderStream(el, false);
1916
1997
  },
1917
1998
  onBeforeElUpdated: (fromEl, toEl) => {
1918
1999
  dom_default.maybeAddPrivateHooks(toEl, phxViewportTop, phxViewportBottom);
1919
2000
  dom_default.cleanChildNodes(toEl, phxUpdate);
1920
2001
  if (this.skipCIDSibling(toEl)) {
2002
+ this.maybeReOrderStream(fromEl);
1921
2003
  return false;
1922
2004
  }
1923
2005
  if (dom_default.isPhxSticky(fromEl)) {
@@ -1953,22 +2035,26 @@ var DOMPatch = class {
1953
2035
  }
1954
2036
  dom_default.copyPrivates(toEl, fromEl);
1955
2037
  let isFocusedFormEl = focused && fromEl.isSameNode(focused) && dom_default.isFormInput(fromEl);
1956
- if (isFocusedFormEl && fromEl.type !== "hidden") {
2038
+ let focusedSelectChanged = isFocusedFormEl && this.isChangedSelect(fromEl, toEl);
2039
+ if (isFocusedFormEl && fromEl.type !== "hidden" && !focusedSelectChanged) {
1957
2040
  this.trackBefore("updated", fromEl, toEl);
1958
2041
  dom_default.mergeFocusedInput(fromEl, toEl);
1959
2042
  dom_default.syncAttrsToProps(fromEl);
1960
2043
  updates.push(fromEl);
1961
2044
  dom_default.applyStickyOperations(fromEl);
1962
- trackedInputs.push(fromEl);
2045
+ trackedForms.add(fromEl.form);
1963
2046
  return false;
1964
2047
  } else {
2048
+ if (focusedSelectChanged) {
2049
+ fromEl.blur();
2050
+ }
1965
2051
  if (dom_default.isPhxUpdate(toEl, phxUpdate, ["append", "prepend"])) {
1966
2052
  appendPrependUpdates.push(new DOMPostMorphRestorer(fromEl, toEl, toEl.getAttribute(phxUpdate)));
1967
2053
  }
1968
2054
  dom_default.syncAttrsToProps(toEl);
1969
2055
  dom_default.applyStickyOperations(toEl);
1970
2056
  if (toEl.getAttribute("name") && dom_default.isFormInput(toEl)) {
1971
- trackedInputs.push(toEl);
2057
+ trackedForms.add(toEl.form);
1972
2058
  }
1973
2059
  this.trackBefore("updated", fromEl, toEl);
1974
2060
  return true;
@@ -1984,7 +2070,7 @@ var DOMPatch = class {
1984
2070
  appendPrependUpdates.forEach((update) => update.perform());
1985
2071
  });
1986
2072
  }
1987
- dom_default.maybeHideFeedback(targetContainer, trackedInputs, phxFeedbackFor);
2073
+ dom_default.maybeHideFeedback(targetContainer, trackedForms, phxFeedbackFor, phxFeedbackGroup);
1988
2074
  liveSocket.silenceEvents(() => dom_default.restoreFocus(focused, selectionStart, selectionEnd));
1989
2075
  dom_default.dispatchEvent(document, "phx:update");
1990
2076
  added.forEach((el) => this.trackAfter("added", el));
@@ -2012,6 +2098,11 @@ var DOMPatch = class {
2012
2098
  }
2013
2099
  removeStreamChildElement(child) {
2014
2100
  if (!this.maybePendingRemove(child)) {
2101
+ if (this.streamInserts[child.id]) {
2102
+ child.querySelectorAll(`[${PHX_MAGIC_ID}]`).forEach((el) => {
2103
+ this.streamComponentRestore[el.getAttribute(PHX_MAGIC_ID)] = el;
2104
+ });
2105
+ }
2015
2106
  child.remove();
2016
2107
  this.onNodeDiscarded(child);
2017
2108
  }
@@ -2020,12 +2111,18 @@ var DOMPatch = class {
2020
2111
  let insert = el.id ? this.streamInserts[el.id] : {};
2021
2112
  return insert || {};
2022
2113
  }
2023
- maybeReOrderStream(el) {
2024
- let { ref, streamAt, limit } = this.getStreamInsert(el);
2114
+ setStreamRef(el, ref) {
2115
+ dom_default.putSticky(el, PHX_STREAM_REF, (el2) => el2.setAttribute(PHX_STREAM_REF, ref));
2116
+ }
2117
+ maybeReOrderStream(el, isNew) {
2118
+ let { ref, streamAt, reset } = this.getStreamInsert(el);
2025
2119
  if (streamAt === void 0) {
2026
2120
  return;
2027
2121
  }
2028
- dom_default.putSticky(el, PHX_STREAM_REF, (el2) => el2.setAttribute(PHX_STREAM_REF, ref));
2122
+ this.setStreamRef(el, ref);
2123
+ if (!reset && !isNew) {
2124
+ return;
2125
+ }
2029
2126
  if (streamAt === 0) {
2030
2127
  el.parentElement.insertBefore(el, el.parentElement.firstElementChild);
2031
2128
  } else if (streamAt > 0) {
@@ -2042,6 +2139,16 @@ var DOMPatch = class {
2042
2139
  }
2043
2140
  }
2044
2141
  }
2142
+ this.maybeLimitStream(el);
2143
+ }
2144
+ maybeLimitStream(el) {
2145
+ let { limit } = this.getStreamInsert(el);
2146
+ let children = limit !== null && Array.from(el.parentElement.children);
2147
+ if (limit && limit < 0 && children.length > limit * -1) {
2148
+ children.slice(0, children.length + limit).forEach((child) => this.removeStreamChildElement(child));
2149
+ } else if (limit && limit >= 0 && children.length > limit) {
2150
+ children.slice(limit).forEach((child) => this.removeStreamChildElement(child));
2151
+ }
2045
2152
  }
2046
2153
  transitionPendingRemoves() {
2047
2154
  let { pendingRemoves, liveSocket } = this;
@@ -2059,6 +2166,20 @@ var DOMPatch = class {
2059
2166
  });
2060
2167
  }
2061
2168
  }
2169
+ isChangedSelect(fromEl, toEl) {
2170
+ if (!(fromEl instanceof HTMLSelectElement) || fromEl.multiple) {
2171
+ return false;
2172
+ }
2173
+ if (fromEl.options.length !== toEl.options.length) {
2174
+ return true;
2175
+ }
2176
+ let fromSelected = fromEl.selectedOptions[0];
2177
+ let toSelected = toEl.selectedOptions[0];
2178
+ if (fromSelected && fromSelected.hasAttribute("selected")) {
2179
+ toSelected.setAttribute("selected", fromSelected.getAttribute("selected"));
2180
+ }
2181
+ return !fromEl.isEqualNode(toEl);
2182
+ }
2062
2183
  isCIDPatch() {
2063
2184
  return this.cidPatch;
2064
2185
  }
@@ -2100,66 +2221,42 @@ var VOID_TAGS = new Set([
2100
2221
  "track",
2101
2222
  "wbr"
2102
2223
  ]);
2103
- var endingTagNameChars = new Set([">", "/", " ", "\n", " ", "\r"]);
2104
2224
  var quoteChars = new Set(["'", '"']);
2105
2225
  var modifyRoot = (html, attrs, clearInnerHTML) => {
2106
2226
  let i = 0;
2107
2227
  let insideComment = false;
2108
2228
  let beforeTag, afterTag, tag, tagNameEndsAt, id, newHTML;
2109
- while (i < html.length) {
2110
- let char = html.charAt(i);
2111
- if (insideComment) {
2112
- if (char === "-" && html.slice(i, i + 3) === "-->") {
2113
- insideComment = false;
2114
- i += 3;
2115
- } else {
2116
- i++;
2117
- }
2118
- } else if (char === "<" && html.slice(i, i + 4) === "<!--") {
2119
- insideComment = true;
2120
- i += 4;
2121
- } else if (char === "<") {
2122
- beforeTag = html.slice(0, i);
2123
- let iAtOpen = i;
2229
+ let lookahead = html.match(/^(\s*(?:<!--.*?-->\s*)*)<([^\s\/>]+)/);
2230
+ if (lookahead === null) {
2231
+ throw new Error(`malformed html ${html}`);
2232
+ }
2233
+ i = lookahead[0].length;
2234
+ beforeTag = lookahead[1];
2235
+ tag = lookahead[2];
2236
+ tagNameEndsAt = i;
2237
+ for (i; i < html.length; i++) {
2238
+ if (html.charAt(i) === ">") {
2239
+ break;
2240
+ }
2241
+ if (html.charAt(i) === "=") {
2242
+ let isId = html.slice(i - 3, i) === " id";
2124
2243
  i++;
2125
- for (i; i < html.length; i++) {
2126
- if (endingTagNameChars.has(html.charAt(i))) {
2127
- break;
2244
+ let char = html.charAt(i);
2245
+ if (quoteChars.has(char)) {
2246
+ let attrStartsAt = i;
2247
+ i++;
2248
+ for (i; i < html.length; i++) {
2249
+ if (html.charAt(i) === char) {
2250
+ break;
2251
+ }
2128
2252
  }
2129
- }
2130
- tagNameEndsAt = i;
2131
- tag = html.slice(iAtOpen + 1, tagNameEndsAt);
2132
- for (i; i < html.length; i++) {
2133
- if (html.charAt(i) === ">") {
2253
+ if (isId) {
2254
+ id = html.slice(attrStartsAt + 1, i);
2134
2255
  break;
2135
2256
  }
2136
- if (html.charAt(i) === "=") {
2137
- let isId = html.slice(i - 3, i) === " id";
2138
- i++;
2139
- let char2 = html.charAt(i);
2140
- if (quoteChars.has(char2)) {
2141
- let attrStartsAt = i;
2142
- i++;
2143
- for (i; i < html.length; i++) {
2144
- if (html.charAt(i) === char2) {
2145
- break;
2146
- }
2147
- }
2148
- if (isId) {
2149
- id = html.slice(attrStartsAt + 1, i);
2150
- break;
2151
- }
2152
- }
2153
- }
2154
2257
  }
2155
- break;
2156
- } else {
2157
- i++;
2158
2258
  }
2159
2259
  }
2160
- if (!tag) {
2161
- throw new Error(`malformed html ${html}`);
2162
- }
2163
2260
  let closeAt = html.length - 1;
2164
2261
  insideComment = false;
2165
2262
  while (closeAt >= beforeTag.length + tag.length) {
@@ -2234,6 +2331,11 @@ var Rendered = class {
2234
2331
  getComponent(diff, cid) {
2235
2332
  return diff[COMPONENTS][cid];
2236
2333
  }
2334
+ resetRender(cid) {
2335
+ if (this.rendered[COMPONENTS][cid]) {
2336
+ this.rendered[COMPONENTS][cid].reset = true;
2337
+ }
2338
+ }
2237
2339
  mergeDiff(diff) {
2238
2340
  let newc = diff[COMPONENTS];
2239
2341
  let cache = {};
@@ -2361,8 +2463,8 @@ var Rendered = class {
2361
2463
  if (isRoot) {
2362
2464
  let skip = false;
2363
2465
  let attrs;
2364
- if (changeTracking || Object.keys(rootAttrs).length > 0) {
2365
- skip = !rendered.newRender;
2466
+ if (changeTracking || rendered.magicId) {
2467
+ skip = changeTracking && !rendered.newRender;
2366
2468
  attrs = { [PHX_MAGIC_ID]: rendered.magicId, ...rootAttrs };
2367
2469
  } else {
2368
2470
  attrs = rootAttrs;
@@ -2412,8 +2514,9 @@ var Rendered = class {
2412
2514
  let skip = onlyCids && !onlyCids.has(cid);
2413
2515
  component.newRender = !skip;
2414
2516
  component.magicId = `${this.parentViewId()}-c-${cid}`;
2415
- let changeTracking = true;
2517
+ let changeTracking = !component.reset;
2416
2518
  let [html, streams] = this.recursiveToString(component, components, onlyCids, changeTracking, attrs);
2519
+ delete component.reset;
2417
2520
  return [html, streams];
2418
2521
  }
2419
2522
  };
@@ -2497,6 +2600,7 @@ var ViewHook = class {
2497
2600
 
2498
2601
  // js/phoenix_live_view/js.js
2499
2602
  var focusStack = null;
2603
+ var default_transition_time = 200;
2500
2604
  var JS = {
2501
2605
  exec(eventType, phxEvent, view, sourceEl, defaults) {
2502
2606
  let [defaultKind, defaultArgs] = defaults || [null, { callback: defaults && defaults.callback }];
@@ -2518,7 +2622,7 @@ var JS = {
2518
2622
  const rect = el.getBoundingClientRect();
2519
2623
  return rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
2520
2624
  },
2521
- exec_exec(eventType, phxEvent, view, sourceEl, el, [attr, to]) {
2625
+ exec_exec(eventType, phxEvent, view, sourceEl, el, { attr, to }) {
2522
2626
  let nodes = to ? dom_default.all(document, to) : [sourceEl];
2523
2627
  nodes.forEach((node) => {
2524
2628
  let encodedJS = node.getAttribute(attr);
@@ -2534,14 +2638,14 @@ var JS = {
2534
2638
  dom_default.dispatchEvent(el, event, { detail, bubbles });
2535
2639
  },
2536
2640
  exec_push(eventType, phxEvent, view, sourceEl, el, args) {
2537
- if (!view.isConnected()) {
2538
- return;
2539
- }
2540
2641
  let { event, data, target, page_loading, loading, value, dispatcher, callback } = args;
2541
2642
  let pushOpts = { loading, value, target, page_loading: !!page_loading };
2542
2643
  let targetSrc = eventType === "change" && dispatcher ? dispatcher : sourceEl;
2543
2644
  let phxTarget = target || targetSrc.getAttribute(view.binding("target")) || targetSrc;
2544
2645
  view.withinTargets(phxTarget, (targetView, targetCtx) => {
2646
+ if (!targetView.isConnected()) {
2647
+ return;
2648
+ }
2545
2649
  if (eventType === "change") {
2546
2650
  let { newCid, _target } = args;
2547
2651
  _target = _target || (dom_default.isFormInput(sourceEl) ? sourceEl.name : void 0);
@@ -2586,6 +2690,24 @@ var JS = {
2586
2690
  exec_remove_class(eventType, phxEvent, view, sourceEl, el, { names, transition, time }) {
2587
2691
  this.addOrRemoveClasses(el, [], names, transition, time, view);
2588
2692
  },
2693
+ exec_toggle_class(eventType, phxEvent, view, sourceEl, el, { to, names, transition, time }) {
2694
+ this.toggleClasses(el, names, transition, view);
2695
+ },
2696
+ exec_toggle_attr(eventType, phxEvent, view, sourceEl, el, { attr: [attr, val1, val2] }) {
2697
+ if (el.hasAttribute(attr)) {
2698
+ if (val2 !== void 0) {
2699
+ if (el.getAttribute(attr) === val1) {
2700
+ this.setOrRemoveAttrs(el, [[attr, val2]], []);
2701
+ } else {
2702
+ this.setOrRemoveAttrs(el, [[attr, val1]], []);
2703
+ }
2704
+ } else {
2705
+ this.setOrRemoveAttrs(el, [], [attr]);
2706
+ }
2707
+ } else {
2708
+ this.setOrRemoveAttrs(el, [[attr, val1]], []);
2709
+ }
2710
+ },
2589
2711
  exec_transition(eventType, phxEvent, view, sourceEl, el, { time, transition }) {
2590
2712
  this.addOrRemoveClasses(el, [], [], transition, time, view);
2591
2713
  },
@@ -2615,6 +2737,7 @@ var JS = {
2615
2737
  }
2616
2738
  },
2617
2739
  toggle(eventType, view, el, display, ins, outs, time) {
2740
+ time = time || default_transition_time;
2618
2741
  let [inClasses, inStartClasses, inEndClasses] = ins || [[], [], []];
2619
2742
  let [outClasses, outStartClasses, outEndClasses] = outs || [[], [], []];
2620
2743
  if (inClasses.length > 0 || outClasses.length > 0) {
@@ -2668,7 +2791,16 @@ var JS = {
2668
2791
  }
2669
2792
  }
2670
2793
  },
2794
+ toggleClasses(el, classes, transition, time, view) {
2795
+ window.requestAnimationFrame(() => {
2796
+ let [prevAdds, prevRemoves] = dom_default.getSticky(el, "classes", [[], []]);
2797
+ let newAdds = classes.filter((name) => prevAdds.indexOf(name) < 0 && !el.classList.contains(name));
2798
+ let newRemoves = classes.filter((name) => prevRemoves.indexOf(name) < 0 && el.classList.contains(name));
2799
+ this.addOrRemoveClasses(el, newAdds, newRemoves, transition, time, view);
2800
+ });
2801
+ },
2671
2802
  addOrRemoveClasses(el, adds, removes, transition, time, view) {
2803
+ time = time || default_transition_time;
2672
2804
  let [transitionRun, transitionStart, transitionEnd] = transition || [[], [], []];
2673
2805
  if (transitionRun.length > 0) {
2674
2806
  let onStart = () => {
@@ -2722,24 +2854,37 @@ var js_default = JS;
2722
2854
 
2723
2855
  // js/phoenix_live_view/view.js
2724
2856
  var serializeForm = (form, metadata, onlyNames = []) => {
2725
- let { submitter, ...meta } = metadata;
2726
- let formData = new FormData(form);
2727
- if (submitter && submitter.hasAttribute("name") && submitter.form && submitter.form === form) {
2728
- formData.append(submitter.name, submitter.value);
2729
- }
2730
- let toRemove = [];
2857
+ const { submitter, ...meta } = metadata;
2858
+ let injectedElement;
2859
+ if (submitter && submitter.name) {
2860
+ const input = document.createElement("input");
2861
+ input.type = "hidden";
2862
+ const formId = submitter.getAttribute("form");
2863
+ if (formId) {
2864
+ input.setAttribute("form", form);
2865
+ }
2866
+ input.name = submitter.name;
2867
+ input.value = submitter.value;
2868
+ submitter.parentElement.insertBefore(input, submitter);
2869
+ injectedElement = input;
2870
+ }
2871
+ const formData = new FormData(form);
2872
+ const toRemove = [];
2731
2873
  formData.forEach((val, key, _index) => {
2732
2874
  if (val instanceof File) {
2733
2875
  toRemove.push(key);
2734
2876
  }
2735
2877
  });
2736
2878
  toRemove.forEach((key) => formData.delete(key));
2737
- let params = new URLSearchParams();
2879
+ const params = new URLSearchParams();
2738
2880
  for (let [key, val] of formData.entries()) {
2739
2881
  if (onlyNames.length === 0 || onlyNames.indexOf(key) >= 0) {
2740
2882
  params.append(key, val);
2741
2883
  }
2742
2884
  }
2885
+ if (submitter && injectedElement) {
2886
+ submitter.parentElement.removeChild(injectedElement);
2887
+ }
2743
2888
  for (let metaKey in meta) {
2744
2889
  params.append(metaKey, meta[metaKey]);
2745
2890
  }
@@ -2943,6 +3088,9 @@ var View = class {
2943
3088
  if (phxStatic) {
2944
3089
  toEl.setAttribute(PHX_STATIC, phxStatic);
2945
3090
  }
3091
+ if (fromEl) {
3092
+ fromEl.setAttribute(PHX_ROOT_ID, this.root.id);
3093
+ }
2946
3094
  return this.joinChild(toEl);
2947
3095
  });
2948
3096
  if (newChildren.length === 0) {
@@ -3022,6 +3170,9 @@ var View = class {
3022
3170
  let updatedHookIds = new Set();
3023
3171
  patch.after("added", (el) => {
3024
3172
  this.liveSocket.triggerDOM("onNodeAdded", [el]);
3173
+ let phxViewportTop = this.binding(PHX_VIEWPORT_TOP);
3174
+ let phxViewportBottom = this.binding(PHX_VIEWPORT_BOTTOM);
3175
+ dom_default.maybeAddPrivateHooks(el, phxViewportTop, phxViewportBottom);
3025
3176
  this.maybeAddNewHook(el);
3026
3177
  if (el.getAttribute) {
3027
3178
  this.maybeMounted(el);
@@ -3398,10 +3549,11 @@ var View = class {
3398
3549
  }
3399
3550
  dom_default.all(document, `[${PHX_REF_SRC}="${this.id}"][${PHX_REF}="${ref}"]`, (el) => {
3400
3551
  let disabledVal = el.getAttribute(PHX_DISABLED);
3552
+ let readOnlyVal = el.getAttribute(PHX_READONLY);
3401
3553
  el.removeAttribute(PHX_REF);
3402
3554
  el.removeAttribute(PHX_REF_SRC);
3403
- if (el.getAttribute(PHX_READONLY) !== null) {
3404
- el.readOnly = false;
3555
+ if (readOnlyVal !== null) {
3556
+ el.readOnly = readOnlyVal === "true" ? true : false;
3405
3557
  el.removeAttribute(PHX_READONLY);
3406
3558
  }
3407
3559
  if (disabledVal !== null) {
@@ -3443,6 +3595,7 @@ var View = class {
3443
3595
  if (disableText !== "") {
3444
3596
  el.innerText = disableText;
3445
3597
  }
3598
+ el.setAttribute(PHX_DISABLED, el.getAttribute(PHX_DISABLED) || el.disabled);
3446
3599
  el.setAttribute("disabled", "");
3447
3600
  }
3448
3601
  });
@@ -3544,6 +3697,9 @@ var View = class {
3544
3697
  let refGenerator = () => this.putRef([inputEl, inputEl.form], "change", opts);
3545
3698
  let formData;
3546
3699
  let meta = this.extractMeta(inputEl.form);
3700
+ if (inputEl instanceof HTMLButtonElement) {
3701
+ meta.submitter = inputEl;
3702
+ }
3547
3703
  if (inputEl.getAttribute(this.binding("change"))) {
3548
3704
  formData = serializeForm(inputEl.form, { _target: opts._target, ...meta }, [inputEl.name]);
3549
3705
  } else {
@@ -3568,6 +3724,7 @@ var View = class {
3568
3724
  this.uploadFiles(inputEl.form, targetCtx, ref, cid, (_uploads) => {
3569
3725
  callback && callback(resp);
3570
3726
  this.triggerAwaitingSubmit(inputEl.form);
3727
+ this.undoRefs(ref);
3571
3728
  });
3572
3729
  }
3573
3730
  } else {
@@ -3766,7 +3923,7 @@ var View = class {
3766
3923
  let template = document.createElement("template");
3767
3924
  template.innerHTML = html;
3768
3925
  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) => {
3769
- const phxChangeValue = form.getAttribute(phxChange).replaceAll(/([\[\]"])/g, "\\$1");
3926
+ const phxChangeValue = CSS.escape(form.getAttribute(phxChange));
3770
3927
  let newForm = template.content.querySelector(`form[id="${form.id}"][${phxChange}="${phxChangeValue}"]`);
3771
3928
  if (newForm) {
3772
3929
  return [form, newForm, this.targetComponentID(newForm)];
@@ -3776,18 +3933,19 @@ var View = class {
3776
3933
  }).filter(([form, newForm, newCid]) => newForm);
3777
3934
  }
3778
3935
  maybePushComponentsDestroyed(destroyedCIDs) {
3779
- let willDestroyCIDs = destroyedCIDs.filter((cid) => {
3936
+ let willDestroyCIDs = destroyedCIDs.concat(this.pruningCIDs).filter((cid) => {
3780
3937
  return dom_default.findComponentNodeList(this.el, cid).length === 0;
3781
3938
  });
3939
+ this.pruningCIDs = willDestroyCIDs.concat([]);
3782
3940
  if (willDestroyCIDs.length > 0) {
3783
- this.pruningCIDs.push(...willDestroyCIDs);
3941
+ willDestroyCIDs.forEach((cid) => this.rendered.resetRender(cid));
3784
3942
  this.pushWithReply(null, "cids_will_destroy", { cids: willDestroyCIDs }, () => {
3785
- this.pruningCIDs = this.pruningCIDs.filter((cid) => willDestroyCIDs.indexOf(cid) !== -1);
3786
3943
  let completelyDestroyCIDs = willDestroyCIDs.filter((cid) => {
3787
3944
  return dom_default.findComponentNodeList(this.el, cid).length === 0;
3788
3945
  });
3789
3946
  if (completelyDestroyCIDs.length > 0) {
3790
3947
  this.pushWithReply(null, "cids_destroyed", { cids: completelyDestroyCIDs }, (resp) => {
3948
+ this.pruningCIDs = this.pruningCIDs.filter((cid) => resp.cids.indexOf(cid) === -1);
3791
3949
  this.rendered.pruneCIDs(resp.cids);
3792
3950
  });
3793
3951
  }
@@ -4103,22 +4261,26 @@ var LiveSocket = class {
4103
4261
  this.main.destroy();
4104
4262
  this.main = this.newRootView(newMainEl, flash, liveReferer);
4105
4263
  this.main.setRedirect(href);
4106
- this.transitionRemoves();
4264
+ this.transitionRemoves(null, true);
4107
4265
  this.main.join((joinCount, onDone) => {
4108
4266
  if (joinCount === 1 && this.commitPendingLink(linkRef)) {
4109
4267
  this.requestDOMUpdate(() => {
4110
4268
  dom_default.findPhxSticky(document).forEach((el) => newMainEl.appendChild(el));
4111
4269
  this.outgoingMainEl.replaceWith(newMainEl);
4112
4270
  this.outgoingMainEl = null;
4113
- callback && requestAnimationFrame(() => callback(linkRef));
4271
+ callback && callback(linkRef);
4114
4272
  onDone();
4115
4273
  });
4116
4274
  }
4117
4275
  });
4118
4276
  }
4119
- transitionRemoves(elements) {
4277
+ transitionRemoves(elements, skipSticky) {
4120
4278
  let removeAttr = this.binding("remove");
4121
4279
  elements = elements || dom_default.all(document, `[${removeAttr}]`);
4280
+ if (skipSticky) {
4281
+ const stickies = dom_default.findPhxSticky(document) || [];
4282
+ elements = elements.filter((el) => !dom_default.isChildOfAny(el, stickies));
4283
+ }
4122
4284
  elements.forEach((el) => {
4123
4285
  this.execJS(el, el.getAttribute(removeAttr), "remove");
4124
4286
  });
@@ -4334,6 +4496,8 @@ var LiveSocket = class {
4334
4496
  if (capture) {
4335
4497
  target = e.target.matches(`[${click}]`) ? e.target : e.target.querySelector(`[${click}]`);
4336
4498
  } else {
4499
+ if (e.detail === 0)
4500
+ this.clickStartedAtTarget = e.target;
4337
4501
  let clickStartedAtTarget = this.clickStartedAtTarget || e.target;
4338
4502
  target = closestPhxBinding(clickStartedAtTarget, click);
4339
4503
  this.dispatchClickAway(e, clickStartedAtTarget);
@@ -4363,7 +4527,7 @@ var LiveSocket = class {
4363
4527
  let phxClickAway = this.binding("click-away");
4364
4528
  dom_default.all(document, `[${phxClickAway}]`, (el) => {
4365
4529
  if (!(el.isSameNode(clickStartedAt) || el.contains(clickStartedAt))) {
4366
- this.withinOwners(e.target, (view) => {
4530
+ this.withinOwners(el, (view) => {
4367
4531
  let phxEvent = el.getAttribute(phxClickAway);
4368
4532
  if (js_default.isVisible(el) && js_default.isInViewport(el)) {
4369
4533
  js_default.exec("click", phxEvent, view, el, ["push", { data: this.eventMeta("click", e, e.target) }]);
@@ -4455,7 +4619,7 @@ var LiveSocket = class {
4455
4619
  return callback ? callback(done) : done;
4456
4620
  }
4457
4621
  pushHistoryPatch(href, linkState, targetEl) {
4458
- if (!this.isConnected()) {
4622
+ if (!this.isConnected() || !this.main.isMain()) {
4459
4623
  return browser_default.redirect(href);
4460
4624
  }
4461
4625
  this.withPageLoading({ to: href, kind: "patch" }, (done) => {
@@ -4474,7 +4638,7 @@ var LiveSocket = class {
4474
4638
  this.registerNewLocation(window.location);
4475
4639
  }
4476
4640
  historyRedirect(href, linkState, flash) {
4477
- if (!this.isConnected()) {
4641
+ if (!this.isConnected() || !this.main.isMain()) {
4478
4642
  return browser_default.redirect(href, flash);
4479
4643
  }
4480
4644
  if (/^\/$|^\/[^\/]+.*$/.test(href)) {
@@ -4575,9 +4739,11 @@ var LiveSocket = class {
4575
4739
  let form = e.target;
4576
4740
  dom_default.resetForm(form, this.binding(PHX_FEEDBACK_FOR));
4577
4741
  let input = Array.from(form.elements).find((el) => el.type === "reset");
4578
- window.requestAnimationFrame(() => {
4579
- input.dispatchEvent(new Event("input", { bubbles: true, cancelable: false }));
4580
- });
4742
+ if (input) {
4743
+ window.requestAnimationFrame(() => {
4744
+ input.dispatchEvent(new Event("input", { bubbles: true, cancelable: false }));
4745
+ });
4746
+ }
4581
4747
  });
4582
4748
  }
4583
4749
  debounce(el, event, eventType, callback) {