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.
@@ -53,7 +53,8 @@ var LiveView = (() => {
53
53
  "phx-keydown-loading",
54
54
  "phx-keyup-loading",
55
55
  "phx-blur-loading",
56
- "phx-focus-loading"
56
+ "phx-focus-loading",
57
+ "phx-hook-loading"
57
58
  ];
58
59
  var PHX_COMPONENT = "data-phx-component";
59
60
  var PHX_LIVE_LINK = "data-phx-link";
@@ -85,6 +86,7 @@ var LiveView = (() => {
85
86
  var PHX_VIEWPORT_BOTTOM = "viewport-bottom";
86
87
  var PHX_TRIGGER_ACTION = "trigger-action";
87
88
  var PHX_FEEDBACK_FOR = "feedback-for";
89
+ var PHX_FEEDBACK_GROUP = "feedback-group";
88
90
  var PHX_HAS_FOCUSED = "phx-has-focused";
89
91
  var FOCUSABLE_INPUTS = ["text", "textarea", "number", "email", "password", "search", "tel", "url", "date", "time", "datetime-local", "color", "range"];
90
92
  var CHECKABLE_INPUTS = ["checkbox", "radio"];
@@ -340,7 +342,9 @@ var LiveView = (() => {
340
342
  return inputEl.hasAttribute("data-phx-auto-upload");
341
343
  },
342
344
  findUploadInputs(node) {
343
- return this.all(node, `input[type="file"][${PHX_UPLOAD_REF}]`);
345
+ const formId = node.id;
346
+ const inputsOutsideForm = this.all(document, `input[type="file"][${PHX_UPLOAD_REF}][form="${formId}"]`);
347
+ return this.all(node, `input[type="file"][${PHX_UPLOAD_REF}]`).concat(inputsOutsideForm);
344
348
  },
345
349
  findComponentNodeList(node, cid) {
346
350
  return this.filterWithinSameLiveView(this.all(node, `[${PHX_COMPONENT}="${cid}"]`), node);
@@ -534,7 +538,9 @@ var LiveView = (() => {
534
538
  });
535
539
  }
536
540
  if (this.once(el, "bind-debounce")) {
537
- el.addEventListener("blur", () => this.triggerCycle(el, DEBOUNCE_TRIGGER));
541
+ el.addEventListener("blur", () => {
542
+ callback();
543
+ });
538
544
  }
539
545
  }
540
546
  },
@@ -567,16 +573,39 @@ var LiveView = (() => {
567
573
  el.setAttribute("data-phx-hook", "Phoenix.InfiniteScroll");
568
574
  }
569
575
  },
570
- maybeHideFeedback(container, inputs, phxFeedbackFor) {
576
+ maybeHideFeedback(container, forms, phxFeedbackFor, phxFeedbackGroup) {
571
577
  let feedbacks = [];
572
- inputs.forEach((input) => {
573
- if (!(this.private(input, PHX_HAS_FOCUSED) || this.private(input, PHX_HAS_SUBMITTED))) {
574
- feedbacks.push(input.name);
575
- if (input.name.endsWith("[]")) {
576
- feedbacks.push(input.name.slice(0, -2));
578
+ let inputNamesFocused = {};
579
+ let feedbackGroups = {};
580
+ forms.forEach((form) => {
581
+ Array.from(form.elements).forEach((input) => {
582
+ const group = input.getAttribute(phxFeedbackGroup);
583
+ if (group && !(group in feedbackGroups)) {
584
+ feedbackGroups[group] = true;
577
585
  }
578
- }
586
+ if (!(input.name in inputNamesFocused)) {
587
+ inputNamesFocused[input.name] = false;
588
+ }
589
+ if (this.private(input, PHX_HAS_FOCUSED) || this.private(input, PHX_HAS_SUBMITTED)) {
590
+ inputNamesFocused[input.name] = true;
591
+ if (group) {
592
+ feedbackGroups[group] = false;
593
+ }
594
+ }
595
+ });
579
596
  });
597
+ for (const [name, focused] of Object.entries(inputNamesFocused)) {
598
+ if (!focused) {
599
+ feedbacks.push(name);
600
+ if (name.endsWith("[]")) {
601
+ feedbacks.push(name.slice(0, -2));
602
+ }
603
+ }
604
+ }
605
+ for (const [group, noFeedback] of Object.entries(feedbackGroups)) {
606
+ if (noFeedback)
607
+ feedbacks.push(group);
608
+ }
580
609
  if (feedbacks.length > 0) {
581
610
  let selector = feedbacks.map((f) => `[${phxFeedbackFor}="${f}"]`).join(", ");
582
611
  DOM.all(container, selector, (el) => el.classList.add(PHX_NO_FEEDBACK_CLASS));
@@ -607,11 +636,19 @@ var LiveView = (() => {
607
636
  isPhxSticky(node) {
608
637
  return node.getAttribute && node.getAttribute(PHX_STICKY) !== null;
609
638
  },
639
+ isChildOfAny(el, parents) {
640
+ return !!parents.find((parent) => parent.contains(el));
641
+ },
610
642
  firstPhxChild(el) {
611
643
  return this.isPhxChild(el) ? el : this.all(el, `[${PHX_PARENT_ID}]`)[0];
612
644
  },
613
645
  dispatchEvent(target, name, opts = {}) {
614
- let bubbles = opts.bubbles === void 0 ? true : !!opts.bubbles;
646
+ let defaultBubble = true;
647
+ let isUploadTarget = target.nodeName === "INPUT" && target.type === "file";
648
+ if (isUploadTarget && name === "click") {
649
+ defaultBubble = false;
650
+ }
651
+ let bubbles = opts.bubbles === void 0 ? defaultBubble : !!opts.bubbles;
615
652
  let eventOpts = { bubbles, cancelable: true, detail: opts.detail || {} };
616
653
  let event = name === "click" ? new MouseEvent("click", eventOpts) : new CustomEvent(name, eventOpts);
617
654
  target.dispatchEvent(event);
@@ -626,20 +663,27 @@ var LiveView = (() => {
626
663
  }
627
664
  },
628
665
  mergeAttrs(target, source, opts = {}) {
629
- let exclude = opts.exclude || [];
666
+ let exclude = new Set(opts.exclude || []);
630
667
  let isIgnored = opts.isIgnored;
631
668
  let sourceAttrs = source.attributes;
632
669
  for (let i = sourceAttrs.length - 1; i >= 0; i--) {
633
670
  let name = sourceAttrs[i].name;
634
- if (exclude.indexOf(name) < 0) {
635
- target.setAttribute(name, source.getAttribute(name));
671
+ if (!exclude.has(name)) {
672
+ const sourceValue = source.getAttribute(name);
673
+ if (target.getAttribute(name) !== sourceValue && (!isIgnored || isIgnored && name.startsWith("data-"))) {
674
+ target.setAttribute(name, sourceValue);
675
+ }
676
+ } else {
677
+ if (name === "value" && target.value === source.value) {
678
+ target.setAttribute("value", source.getAttribute(name));
679
+ }
636
680
  }
637
681
  }
638
682
  let targetAttrs = target.attributes;
639
683
  for (let i = targetAttrs.length - 1; i >= 0; i--) {
640
684
  let name = targetAttrs[i].name;
641
685
  if (isIgnored) {
642
- if (name.startsWith("data-") && !source.hasAttribute(name)) {
686
+ if (name.startsWith("data-") && !source.hasAttribute(name) && ![PHX_REF, PHX_REF_SRC].includes(name)) {
643
687
  target.removeAttribute(name);
644
688
  }
645
689
  } else {
@@ -663,6 +707,9 @@ var LiveView = (() => {
663
707
  return el.setSelectionRange && (el.type === "text" || el.type === "textarea");
664
708
  },
665
709
  restoreFocus(focused, selectionStart, selectionEnd) {
710
+ if (focused instanceof HTMLSelectElement) {
711
+ focused.focus();
712
+ }
666
713
  if (!DOM.isTextualInput(focused)) {
667
714
  return;
668
715
  }
@@ -786,15 +833,22 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
786
833
  var UploadEntry = class {
787
834
  static isActive(fileEl, file) {
788
835
  let isNew = file._phxRef === void 0;
836
+ let isPreflightInProgress = UploadEntry.isPreflightInProgress(file);
789
837
  let activeRefs = fileEl.getAttribute(PHX_ACTIVE_ENTRY_REFS).split(",");
790
838
  let isActive = activeRefs.indexOf(LiveUploader.genFileRef(file)) >= 0;
791
- return file.size > 0 && (isNew || isActive);
839
+ return file.size > 0 && (isNew || isActive || !isPreflightInProgress);
792
840
  }
793
841
  static isPreflighted(fileEl, file) {
794
842
  let preflightedRefs = fileEl.getAttribute(PHX_PREFLIGHTED_REFS).split(",");
795
843
  let isPreflighted = preflightedRefs.indexOf(LiveUploader.genFileRef(file)) >= 0;
796
844
  return isPreflighted && this.isActive(fileEl, file);
797
845
  }
846
+ static isPreflightInProgress(file) {
847
+ return file._preflightInProgress === true;
848
+ }
849
+ static markPreflightInProgress(file) {
850
+ file._preflightInProgress = true;
851
+ }
798
852
  constructor(fileEl, file, view) {
799
853
  this.ref = LiveUploader.genFileRef(file);
800
854
  this.fileEl = fileEl;
@@ -961,12 +1015,16 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
961
1015
  return Array.from(fileInputs).filter((input) => this.filesAwaitingPreflight(input).length > 0);
962
1016
  }
963
1017
  static filesAwaitingPreflight(input) {
964
- return this.activeFiles(input).filter((f) => !UploadEntry.isPreflighted(input, f));
1018
+ return this.activeFiles(input).filter((f) => !UploadEntry.isPreflighted(input, f) && !UploadEntry.isPreflightInProgress(f));
1019
+ }
1020
+ static markPreflightInProgress(entries) {
1021
+ entries.forEach((entry) => UploadEntry.markPreflightInProgress(entry.file));
965
1022
  }
966
1023
  constructor(inputEl, view, onComplete) {
967
1024
  this.view = view;
968
1025
  this.onComplete = onComplete;
969
1026
  this._entries = Array.from(LiveUploader.filesAwaitingPreflight(inputEl) || []).map((file) => new UploadEntry(inputEl, file, view));
1027
+ LiveUploader.markPreflightInProgress(this._entries);
970
1028
  this.numEntriesInProgress = this._entries.length;
971
1029
  }
972
1030
  entries() {
@@ -1107,23 +1165,50 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1107
1165
  }
1108
1166
  }
1109
1167
  };
1110
- var scrollTop = () => document.documentElement.scrollTop || document.body.scrollTop;
1111
- var winHeight = () => window.innerHeight || document.documentElement.clientHeight;
1112
- var isAtViewportTop = (el) => {
1168
+ var findScrollContainer = (el) => {
1169
+ if (["scroll", "auto"].indexOf(getComputedStyle(el).overflowY) >= 0)
1170
+ return el;
1171
+ if (document.documentElement === el)
1172
+ return null;
1173
+ return findScrollContainer(el.parentElement);
1174
+ };
1175
+ var scrollTop = (scrollContainer) => {
1176
+ if (scrollContainer) {
1177
+ return scrollContainer.scrollTop;
1178
+ } else {
1179
+ return document.documentElement.scrollTop || document.body.scrollTop;
1180
+ }
1181
+ };
1182
+ var bottom = (scrollContainer) => {
1183
+ if (scrollContainer) {
1184
+ return scrollContainer.getBoundingClientRect().bottom;
1185
+ } else {
1186
+ return window.innerHeight || document.documentElement.clientHeight;
1187
+ }
1188
+ };
1189
+ var top = (scrollContainer) => {
1190
+ if (scrollContainer) {
1191
+ return scrollContainer.getBoundingClientRect().top;
1192
+ } else {
1193
+ return 0;
1194
+ }
1195
+ };
1196
+ var isAtViewportTop = (el, scrollContainer) => {
1113
1197
  let rect = el.getBoundingClientRect();
1114
- return rect.top >= 0 && rect.left >= 0 && rect.top <= winHeight();
1198
+ return rect.top >= top(scrollContainer) && rect.left >= 0 && rect.top <= bottom(scrollContainer);
1115
1199
  };
1116
- var isAtViewportBottom = (el) => {
1200
+ var isAtViewportBottom = (el, scrollContainer) => {
1117
1201
  let rect = el.getBoundingClientRect();
1118
- return rect.right >= 0 && rect.left >= 0 && rect.bottom <= winHeight();
1202
+ return rect.right >= top(scrollContainer) && rect.left >= 0 && rect.bottom <= bottom(scrollContainer);
1119
1203
  };
1120
- var isWithinViewport = (el) => {
1204
+ var isWithinViewport = (el, scrollContainer) => {
1121
1205
  let rect = el.getBoundingClientRect();
1122
- return rect.top >= 0 && rect.left >= 0 && rect.top <= winHeight();
1206
+ return rect.top >= top(scrollContainer) && rect.left >= 0 && rect.top <= bottom(scrollContainer);
1123
1207
  };
1124
1208
  Hooks.InfiniteScroll = {
1125
1209
  mounted() {
1126
- let scrollBefore = scrollTop();
1210
+ this.scrollContainer = findScrollContainer(this.el);
1211
+ let scrollBefore = scrollTop(this.scrollContainer);
1127
1212
  let topOverran = false;
1128
1213
  let throttleInterval = 500;
1129
1214
  let pendingOp = null;
@@ -1137,22 +1222,26 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1137
1222
  pendingOp = () => firstChild.scrollIntoView({ block: "start" });
1138
1223
  this.liveSocket.execJSHookPush(this.el, topEvent, { id: firstChild.id }, () => {
1139
1224
  pendingOp = null;
1140
- if (!isWithinViewport(firstChild)) {
1141
- firstChild.scrollIntoView({ block: "start" });
1142
- }
1225
+ window.requestAnimationFrame(() => {
1226
+ if (!isWithinViewport(firstChild, this.scrollContainer)) {
1227
+ firstChild.scrollIntoView({ block: "start" });
1228
+ }
1229
+ });
1143
1230
  });
1144
1231
  });
1145
1232
  let onLastChildAtBottom = this.throttle(throttleInterval, (bottomEvent, lastChild) => {
1146
1233
  pendingOp = () => lastChild.scrollIntoView({ block: "end" });
1147
1234
  this.liveSocket.execJSHookPush(this.el, bottomEvent, { id: lastChild.id }, () => {
1148
1235
  pendingOp = null;
1149
- if (!isWithinViewport(lastChild)) {
1150
- lastChild.scrollIntoView({ block: "end" });
1151
- }
1236
+ window.requestAnimationFrame(() => {
1237
+ if (!isWithinViewport(lastChild, this.scrollContainer)) {
1238
+ lastChild.scrollIntoView({ block: "end" });
1239
+ }
1240
+ });
1152
1241
  });
1153
1242
  });
1154
- this.onScroll = (e) => {
1155
- let scrollNow = scrollTop();
1243
+ this.onScroll = (_e) => {
1244
+ let scrollNow = scrollTop(this.scrollContainer);
1156
1245
  if (pendingOp) {
1157
1246
  scrollBefore = scrollNow;
1158
1247
  return pendingOp();
@@ -1170,17 +1259,25 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1170
1259
  } else if (isScrollingDown && topOverran && rect.top <= 0) {
1171
1260
  topOverran = false;
1172
1261
  }
1173
- if (topEvent && isScrollingUp && isAtViewportTop(firstChild)) {
1262
+ if (topEvent && isScrollingUp && isAtViewportTop(firstChild, this.scrollContainer)) {
1174
1263
  onFirstChildAtTop(topEvent, firstChild);
1175
- } else if (bottomEvent && isScrollingDown && isAtViewportBottom(lastChild)) {
1264
+ } else if (bottomEvent && isScrollingDown && isAtViewportBottom(lastChild, this.scrollContainer)) {
1176
1265
  onLastChildAtBottom(bottomEvent, lastChild);
1177
1266
  }
1178
1267
  scrollBefore = scrollNow;
1179
1268
  };
1180
- window.addEventListener("scroll", this.onScroll);
1269
+ if (this.scrollContainer) {
1270
+ this.scrollContainer.addEventListener("scroll", this.onScroll);
1271
+ } else {
1272
+ window.addEventListener("scroll", this.onScroll);
1273
+ }
1181
1274
  },
1182
1275
  destroyed() {
1183
- window.removeEventListener("scroll", this.onScroll);
1276
+ if (this.scrollContainer) {
1277
+ this.scrollContainer.removeEventListener("scroll", this.onScroll);
1278
+ } else {
1279
+ window.removeEventListener("scroll", this.onScroll);
1280
+ }
1184
1281
  },
1185
1282
  throttle(interval, callback) {
1186
1283
  let lastCallAt = 0;
@@ -1627,6 +1724,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1627
1724
  removeNode(curFromNodeChild, fromEl, true);
1628
1725
  }
1629
1726
  curFromNodeChild = matchingFromEl;
1727
+ curFromNodeKey = getNodeKey(curFromNodeChild);
1630
1728
  }
1631
1729
  } else {
1632
1730
  isCompatible = false;
@@ -1759,6 +1857,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1759
1857
  this.html = html;
1760
1858
  this.streams = streams;
1761
1859
  this.streamInserts = {};
1860
+ this.streamComponentRestore = {};
1762
1861
  this.targetCID = targetCID;
1763
1862
  this.cidPatch = isCid(this.targetCID);
1764
1863
  this.pendingRemoves = [];
@@ -1788,7 +1887,6 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1788
1887
  }
1789
1888
  markPrunableContentForRemoval() {
1790
1889
  let phxUpdate = this.liveSocket.binding(PHX_UPDATE);
1791
- dom_default.all(this.container, `[${phxUpdate}=${PHX_STREAM}]`, (el) => el.innerHTML = "");
1792
1890
  dom_default.all(this.container, `[${phxUpdate}=append] > *, [${phxUpdate}=prepend] > *`, (el) => {
1793
1891
  el.setAttribute(PHX_PRUNE, "");
1794
1892
  });
@@ -1803,12 +1901,13 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1803
1901
  let { selectionStart, selectionEnd } = focused && dom_default.hasSelectionRange(focused) ? focused : {};
1804
1902
  let phxUpdate = liveSocket.binding(PHX_UPDATE);
1805
1903
  let phxFeedbackFor = liveSocket.binding(PHX_FEEDBACK_FOR);
1904
+ let phxFeedbackGroup = liveSocket.binding(PHX_FEEDBACK_GROUP);
1806
1905
  let disableWith = liveSocket.binding(PHX_DISABLE_WITH);
1807
1906
  let phxViewportTop = liveSocket.binding(PHX_VIEWPORT_TOP);
1808
1907
  let phxViewportBottom = liveSocket.binding(PHX_VIEWPORT_BOTTOM);
1809
1908
  let phxTriggerExternal = liveSocket.binding(PHX_TRIGGER_ACTION);
1810
1909
  let added = [];
1811
- let trackedInputs = [];
1910
+ let trackedForms = new Set();
1812
1911
  let updates = [];
1813
1912
  let appendPrependUpdates = [];
1814
1913
  let externalFormTriggered = null;
@@ -1816,16 +1915,12 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1816
1915
  this.trackBefore("updated", container, container);
1817
1916
  liveSocket.time("morphdom", () => {
1818
1917
  this.streams.forEach(([ref, inserts, deleteIds, reset]) => {
1819
- Object.entries(inserts).forEach(([key, [streamAt, limit]]) => {
1820
- this.streamInserts[key] = { ref, streamAt, limit, resetKept: false };
1918
+ inserts.forEach(([key, streamAt, limit]) => {
1919
+ this.streamInserts[key] = { ref, streamAt, limit, reset };
1821
1920
  });
1822
1921
  if (reset !== void 0) {
1823
1922
  dom_default.all(container, `[${PHX_STREAM_REF}="${ref}"]`, (child) => {
1824
- if (inserts[child.id]) {
1825
- this.streamInserts[child.id].resetKept = true;
1826
- } else {
1827
- this.removeStreamChildElement(child);
1828
- }
1923
+ this.removeStreamChildElement(child);
1829
1924
  });
1830
1925
  }
1831
1926
  deleteIds.forEach((id) => {
@@ -1835,6 +1930,17 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1835
1930
  }
1836
1931
  });
1837
1932
  });
1933
+ if (isJoinPatch) {
1934
+ dom_default.all(this.container, `[${phxUpdate}=${PHX_STREAM}]`, (el) => {
1935
+ this.liveSocket.owner(el, (view2) => {
1936
+ if (view2 === this.view) {
1937
+ Array.from(el.children).forEach((child) => {
1938
+ this.removeStreamChildElement(child);
1939
+ });
1940
+ }
1941
+ });
1942
+ });
1943
+ }
1838
1944
  morphdom_esm_default(targetContainer, html, {
1839
1945
  childrenOnly: targetContainer.getAttribute(PHX_COMPONENT) === null,
1840
1946
  getNodeKey: (node) => {
@@ -1850,11 +1956,17 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1850
1956
  return from.getAttribute(phxUpdate) === PHX_STREAM;
1851
1957
  },
1852
1958
  addChild: (parent, child) => {
1853
- let { ref, streamAt, limit } = this.getStreamInsert(child);
1959
+ let { ref, streamAt } = this.getStreamInsert(child);
1854
1960
  if (ref === void 0) {
1855
1961
  return parent.appendChild(child);
1856
1962
  }
1857
- dom_default.putSticky(child, PHX_STREAM_REF, (el) => el.setAttribute(PHX_STREAM_REF, ref));
1963
+ this.setStreamRef(child, ref);
1964
+ child.querySelectorAll(`[${PHX_MAGIC_ID}][${PHX_SKIP}]`).forEach((el) => {
1965
+ const component = this.streamComponentRestore[el.getAttribute(PHX_MAGIC_ID)];
1966
+ if (component) {
1967
+ el.replaceWith(component);
1968
+ }
1969
+ });
1858
1970
  if (streamAt === 0) {
1859
1971
  parent.insertAdjacentElement("afterbegin", child);
1860
1972
  } else if (streamAt === -1) {
@@ -1863,18 +1975,6 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1863
1975
  let sibling = Array.from(parent.children)[streamAt];
1864
1976
  parent.insertBefore(child, sibling);
1865
1977
  }
1866
- let children = limit !== null && Array.from(parent.children);
1867
- let childrenToRemove = [];
1868
- if (limit && limit < 0 && children.length > limit * -1) {
1869
- childrenToRemove = children.slice(0, children.length + limit);
1870
- } else if (limit && limit >= 0 && children.length > limit) {
1871
- childrenToRemove = children.slice(limit);
1872
- }
1873
- childrenToRemove.forEach((removeChild) => {
1874
- if (!this.streamInserts[removeChild.id]) {
1875
- this.removeStreamChildElement(removeChild);
1876
- }
1877
- });
1878
1978
  },
1879
1979
  onBeforeNodeAdded: (el) => {
1880
1980
  dom_default.maybeAddPrivateHooks(el, phxViewportTop, phxViewportBottom);
@@ -1883,7 +1983,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1883
1983
  },
1884
1984
  onNodeAdded: (el) => {
1885
1985
  if (el.getAttribute) {
1886
- this.maybeReOrderStream(el);
1986
+ this.maybeReOrderStream(el, true);
1887
1987
  }
1888
1988
  if (el instanceof HTMLImageElement && el.srcset) {
1889
1989
  el.srcset = el.srcset;
@@ -1894,32 +1994,13 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1894
1994
  externalFormTriggered = el;
1895
1995
  }
1896
1996
  if (el.getAttribute && el.getAttribute("name") && dom_default.isFormInput(el)) {
1897
- trackedInputs.push(el);
1997
+ trackedForms.add(el.form);
1898
1998
  }
1899
1999
  if (dom_default.isPhxChild(el) && view.ownsElement(el) || dom_default.isPhxSticky(el) && view.ownsElement(el.parentNode)) {
1900
2000
  this.trackAfter("phxChildAdded", el);
1901
2001
  }
1902
2002
  added.push(el);
1903
2003
  },
1904
- onBeforeElChildrenUpdated: (fromEl, toEl) => {
1905
- if (fromEl.getAttribute(phxUpdate) === PHX_STREAM) {
1906
- let toIds = Array.from(toEl.children).map((child) => child.id);
1907
- Array.from(fromEl.children).filter((child) => {
1908
- let { resetKept } = this.getStreamInsert(child);
1909
- return resetKept;
1910
- }).sort((a, b) => {
1911
- let aIdx = toIds.indexOf(a.id);
1912
- let bIdx = toIds.indexOf(b.id);
1913
- if (aIdx === bIdx) {
1914
- return 0;
1915
- } else if (aIdx < bIdx) {
1916
- return -1;
1917
- } else {
1918
- return 1;
1919
- }
1920
- }).forEach((child) => fromEl.appendChild(child));
1921
- }
1922
- },
1923
2004
  onNodeDiscarded: (el) => this.onNodeDiscarded(el),
1924
2005
  onBeforeNodeDiscarded: (el) => {
1925
2006
  if (el.getAttribute && el.getAttribute(PHX_PRUNE) !== null) {
@@ -1941,12 +2022,13 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1941
2022
  externalFormTriggered = el;
1942
2023
  }
1943
2024
  updates.push(el);
1944
- this.maybeReOrderStream(el);
2025
+ this.maybeReOrderStream(el, false);
1945
2026
  },
1946
2027
  onBeforeElUpdated: (fromEl, toEl) => {
1947
2028
  dom_default.maybeAddPrivateHooks(toEl, phxViewportTop, phxViewportBottom);
1948
2029
  dom_default.cleanChildNodes(toEl, phxUpdate);
1949
2030
  if (this.skipCIDSibling(toEl)) {
2031
+ this.maybeReOrderStream(fromEl);
1950
2032
  return false;
1951
2033
  }
1952
2034
  if (dom_default.isPhxSticky(fromEl)) {
@@ -1982,22 +2064,26 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1982
2064
  }
1983
2065
  dom_default.copyPrivates(toEl, fromEl);
1984
2066
  let isFocusedFormEl = focused && fromEl.isSameNode(focused) && dom_default.isFormInput(fromEl);
1985
- if (isFocusedFormEl && fromEl.type !== "hidden") {
2067
+ let focusedSelectChanged = isFocusedFormEl && this.isChangedSelect(fromEl, toEl);
2068
+ if (isFocusedFormEl && fromEl.type !== "hidden" && !focusedSelectChanged) {
1986
2069
  this.trackBefore("updated", fromEl, toEl);
1987
2070
  dom_default.mergeFocusedInput(fromEl, toEl);
1988
2071
  dom_default.syncAttrsToProps(fromEl);
1989
2072
  updates.push(fromEl);
1990
2073
  dom_default.applyStickyOperations(fromEl);
1991
- trackedInputs.push(fromEl);
2074
+ trackedForms.add(fromEl.form);
1992
2075
  return false;
1993
2076
  } else {
2077
+ if (focusedSelectChanged) {
2078
+ fromEl.blur();
2079
+ }
1994
2080
  if (dom_default.isPhxUpdate(toEl, phxUpdate, ["append", "prepend"])) {
1995
2081
  appendPrependUpdates.push(new DOMPostMorphRestorer(fromEl, toEl, toEl.getAttribute(phxUpdate)));
1996
2082
  }
1997
2083
  dom_default.syncAttrsToProps(toEl);
1998
2084
  dom_default.applyStickyOperations(toEl);
1999
2085
  if (toEl.getAttribute("name") && dom_default.isFormInput(toEl)) {
2000
- trackedInputs.push(toEl);
2086
+ trackedForms.add(toEl.form);
2001
2087
  }
2002
2088
  this.trackBefore("updated", fromEl, toEl);
2003
2089
  return true;
@@ -2013,7 +2099,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2013
2099
  appendPrependUpdates.forEach((update) => update.perform());
2014
2100
  });
2015
2101
  }
2016
- dom_default.maybeHideFeedback(targetContainer, trackedInputs, phxFeedbackFor);
2102
+ dom_default.maybeHideFeedback(targetContainer, trackedForms, phxFeedbackFor, phxFeedbackGroup);
2017
2103
  liveSocket.silenceEvents(() => dom_default.restoreFocus(focused, selectionStart, selectionEnd));
2018
2104
  dom_default.dispatchEvent(document, "phx:update");
2019
2105
  added.forEach((el) => this.trackAfter("added", el));
@@ -2041,6 +2127,11 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2041
2127
  }
2042
2128
  removeStreamChildElement(child) {
2043
2129
  if (!this.maybePendingRemove(child)) {
2130
+ if (this.streamInserts[child.id]) {
2131
+ child.querySelectorAll(`[${PHX_MAGIC_ID}]`).forEach((el) => {
2132
+ this.streamComponentRestore[el.getAttribute(PHX_MAGIC_ID)] = el;
2133
+ });
2134
+ }
2044
2135
  child.remove();
2045
2136
  this.onNodeDiscarded(child);
2046
2137
  }
@@ -2049,12 +2140,18 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2049
2140
  let insert = el.id ? this.streamInserts[el.id] : {};
2050
2141
  return insert || {};
2051
2142
  }
2052
- maybeReOrderStream(el) {
2053
- let { ref, streamAt, limit } = this.getStreamInsert(el);
2143
+ setStreamRef(el, ref) {
2144
+ dom_default.putSticky(el, PHX_STREAM_REF, (el2) => el2.setAttribute(PHX_STREAM_REF, ref));
2145
+ }
2146
+ maybeReOrderStream(el, isNew) {
2147
+ let { ref, streamAt, reset } = this.getStreamInsert(el);
2054
2148
  if (streamAt === void 0) {
2055
2149
  return;
2056
2150
  }
2057
- dom_default.putSticky(el, PHX_STREAM_REF, (el2) => el2.setAttribute(PHX_STREAM_REF, ref));
2151
+ this.setStreamRef(el, ref);
2152
+ if (!reset && !isNew) {
2153
+ return;
2154
+ }
2058
2155
  if (streamAt === 0) {
2059
2156
  el.parentElement.insertBefore(el, el.parentElement.firstElementChild);
2060
2157
  } else if (streamAt > 0) {
@@ -2071,6 +2168,16 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2071
2168
  }
2072
2169
  }
2073
2170
  }
2171
+ this.maybeLimitStream(el);
2172
+ }
2173
+ maybeLimitStream(el) {
2174
+ let { limit } = this.getStreamInsert(el);
2175
+ let children = limit !== null && Array.from(el.parentElement.children);
2176
+ if (limit && limit < 0 && children.length > limit * -1) {
2177
+ children.slice(0, children.length + limit).forEach((child) => this.removeStreamChildElement(child));
2178
+ } else if (limit && limit >= 0 && children.length > limit) {
2179
+ children.slice(limit).forEach((child) => this.removeStreamChildElement(child));
2180
+ }
2074
2181
  }
2075
2182
  transitionPendingRemoves() {
2076
2183
  let { pendingRemoves, liveSocket } = this;
@@ -2088,6 +2195,20 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2088
2195
  });
2089
2196
  }
2090
2197
  }
2198
+ isChangedSelect(fromEl, toEl) {
2199
+ if (!(fromEl instanceof HTMLSelectElement) || fromEl.multiple) {
2200
+ return false;
2201
+ }
2202
+ if (fromEl.options.length !== toEl.options.length) {
2203
+ return true;
2204
+ }
2205
+ let fromSelected = fromEl.selectedOptions[0];
2206
+ let toSelected = toEl.selectedOptions[0];
2207
+ if (fromSelected && fromSelected.hasAttribute("selected")) {
2208
+ toSelected.setAttribute("selected", fromSelected.getAttribute("selected"));
2209
+ }
2210
+ return !fromEl.isEqualNode(toEl);
2211
+ }
2091
2212
  isCIDPatch() {
2092
2213
  return this.cidPatch;
2093
2214
  }
@@ -2129,66 +2250,42 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2129
2250
  "track",
2130
2251
  "wbr"
2131
2252
  ]);
2132
- var endingTagNameChars = new Set([">", "/", " ", "\n", " ", "\r"]);
2133
2253
  var quoteChars = new Set(["'", '"']);
2134
2254
  var modifyRoot = (html, attrs, clearInnerHTML) => {
2135
2255
  let i = 0;
2136
2256
  let insideComment = false;
2137
2257
  let beforeTag, afterTag, tag, tagNameEndsAt, id, newHTML;
2138
- while (i < html.length) {
2139
- let char = html.charAt(i);
2140
- if (insideComment) {
2141
- if (char === "-" && html.slice(i, i + 3) === "-->") {
2142
- insideComment = false;
2143
- i += 3;
2144
- } else {
2145
- i++;
2146
- }
2147
- } else if (char === "<" && html.slice(i, i + 4) === "<!--") {
2148
- insideComment = true;
2149
- i += 4;
2150
- } else if (char === "<") {
2151
- beforeTag = html.slice(0, i);
2152
- let iAtOpen = i;
2258
+ let lookahead = html.match(/^(\s*(?:<!--.*?-->\s*)*)<([^\s\/>]+)/);
2259
+ if (lookahead === null) {
2260
+ throw new Error(`malformed html ${html}`);
2261
+ }
2262
+ i = lookahead[0].length;
2263
+ beforeTag = lookahead[1];
2264
+ tag = lookahead[2];
2265
+ tagNameEndsAt = i;
2266
+ for (i; i < html.length; i++) {
2267
+ if (html.charAt(i) === ">") {
2268
+ break;
2269
+ }
2270
+ if (html.charAt(i) === "=") {
2271
+ let isId = html.slice(i - 3, i) === " id";
2153
2272
  i++;
2154
- for (i; i < html.length; i++) {
2155
- if (endingTagNameChars.has(html.charAt(i))) {
2156
- break;
2273
+ let char = html.charAt(i);
2274
+ if (quoteChars.has(char)) {
2275
+ let attrStartsAt = i;
2276
+ i++;
2277
+ for (i; i < html.length; i++) {
2278
+ if (html.charAt(i) === char) {
2279
+ break;
2280
+ }
2157
2281
  }
2158
- }
2159
- tagNameEndsAt = i;
2160
- tag = html.slice(iAtOpen + 1, tagNameEndsAt);
2161
- for (i; i < html.length; i++) {
2162
- if (html.charAt(i) === ">") {
2282
+ if (isId) {
2283
+ id = html.slice(attrStartsAt + 1, i);
2163
2284
  break;
2164
2285
  }
2165
- if (html.charAt(i) === "=") {
2166
- let isId = html.slice(i - 3, i) === " id";
2167
- i++;
2168
- let char2 = html.charAt(i);
2169
- if (quoteChars.has(char2)) {
2170
- let attrStartsAt = i;
2171
- i++;
2172
- for (i; i < html.length; i++) {
2173
- if (html.charAt(i) === char2) {
2174
- break;
2175
- }
2176
- }
2177
- if (isId) {
2178
- id = html.slice(attrStartsAt + 1, i);
2179
- break;
2180
- }
2181
- }
2182
- }
2183
2286
  }
2184
- break;
2185
- } else {
2186
- i++;
2187
2287
  }
2188
2288
  }
2189
- if (!tag) {
2190
- throw new Error(`malformed html ${html}`);
2191
- }
2192
2289
  let closeAt = html.length - 1;
2193
2290
  insideComment = false;
2194
2291
  while (closeAt >= beforeTag.length + tag.length) {
@@ -2263,6 +2360,11 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2263
2360
  getComponent(diff, cid) {
2264
2361
  return diff[COMPONENTS][cid];
2265
2362
  }
2363
+ resetRender(cid) {
2364
+ if (this.rendered[COMPONENTS][cid]) {
2365
+ this.rendered[COMPONENTS][cid].reset = true;
2366
+ }
2367
+ }
2266
2368
  mergeDiff(diff) {
2267
2369
  let newc = diff[COMPONENTS];
2268
2370
  let cache = {};
@@ -2390,8 +2492,8 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2390
2492
  if (isRoot) {
2391
2493
  let skip = false;
2392
2494
  let attrs;
2393
- if (changeTracking || Object.keys(rootAttrs).length > 0) {
2394
- skip = !rendered.newRender;
2495
+ if (changeTracking || rendered.magicId) {
2496
+ skip = changeTracking && !rendered.newRender;
2395
2497
  attrs = __spreadValues({ [PHX_MAGIC_ID]: rendered.magicId }, rootAttrs);
2396
2498
  } else {
2397
2499
  attrs = rootAttrs;
@@ -2441,8 +2543,9 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2441
2543
  let skip = onlyCids && !onlyCids.has(cid);
2442
2544
  component.newRender = !skip;
2443
2545
  component.magicId = `${this.parentViewId()}-c-${cid}`;
2444
- let changeTracking = true;
2546
+ let changeTracking = !component.reset;
2445
2547
  let [html, streams] = this.recursiveToString(component, components, onlyCids, changeTracking, attrs);
2548
+ delete component.reset;
2446
2549
  return [html, streams];
2447
2550
  }
2448
2551
  };
@@ -2526,6 +2629,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2526
2629
 
2527
2630
  // js/phoenix_live_view/js.js
2528
2631
  var focusStack = null;
2632
+ var default_transition_time = 200;
2529
2633
  var JS = {
2530
2634
  exec(eventType, phxEvent, view, sourceEl, defaults) {
2531
2635
  let [defaultKind, defaultArgs] = defaults || [null, { callback: defaults && defaults.callback }];
@@ -2547,7 +2651,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2547
2651
  const rect = el.getBoundingClientRect();
2548
2652
  return rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
2549
2653
  },
2550
- exec_exec(eventType, phxEvent, view, sourceEl, el, [attr, to]) {
2654
+ exec_exec(eventType, phxEvent, view, sourceEl, el, { attr, to }) {
2551
2655
  let nodes = to ? dom_default.all(document, to) : [sourceEl];
2552
2656
  nodes.forEach((node) => {
2553
2657
  let encodedJS = node.getAttribute(attr);
@@ -2563,14 +2667,14 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2563
2667
  dom_default.dispatchEvent(el, event, { detail, bubbles });
2564
2668
  },
2565
2669
  exec_push(eventType, phxEvent, view, sourceEl, el, args) {
2566
- if (!view.isConnected()) {
2567
- return;
2568
- }
2569
2670
  let { event, data, target, page_loading, loading, value, dispatcher, callback } = args;
2570
2671
  let pushOpts = { loading, value, target, page_loading: !!page_loading };
2571
2672
  let targetSrc = eventType === "change" && dispatcher ? dispatcher : sourceEl;
2572
2673
  let phxTarget = target || targetSrc.getAttribute(view.binding("target")) || targetSrc;
2573
2674
  view.withinTargets(phxTarget, (targetView, targetCtx) => {
2675
+ if (!targetView.isConnected()) {
2676
+ return;
2677
+ }
2574
2678
  if (eventType === "change") {
2575
2679
  let { newCid, _target } = args;
2576
2680
  _target = _target || (dom_default.isFormInput(sourceEl) ? sourceEl.name : void 0);
@@ -2615,6 +2719,24 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2615
2719
  exec_remove_class(eventType, phxEvent, view, sourceEl, el, { names, transition, time }) {
2616
2720
  this.addOrRemoveClasses(el, [], names, transition, time, view);
2617
2721
  },
2722
+ exec_toggle_class(eventType, phxEvent, view, sourceEl, el, { to, names, transition, time }) {
2723
+ this.toggleClasses(el, names, transition, view);
2724
+ },
2725
+ exec_toggle_attr(eventType, phxEvent, view, sourceEl, el, { attr: [attr, val1, val2] }) {
2726
+ if (el.hasAttribute(attr)) {
2727
+ if (val2 !== void 0) {
2728
+ if (el.getAttribute(attr) === val1) {
2729
+ this.setOrRemoveAttrs(el, [[attr, val2]], []);
2730
+ } else {
2731
+ this.setOrRemoveAttrs(el, [[attr, val1]], []);
2732
+ }
2733
+ } else {
2734
+ this.setOrRemoveAttrs(el, [], [attr]);
2735
+ }
2736
+ } else {
2737
+ this.setOrRemoveAttrs(el, [[attr, val1]], []);
2738
+ }
2739
+ },
2618
2740
  exec_transition(eventType, phxEvent, view, sourceEl, el, { time, transition }) {
2619
2741
  this.addOrRemoveClasses(el, [], [], transition, time, view);
2620
2742
  },
@@ -2644,6 +2766,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2644
2766
  }
2645
2767
  },
2646
2768
  toggle(eventType, view, el, display, ins, outs, time) {
2769
+ time = time || default_transition_time;
2647
2770
  let [inClasses, inStartClasses, inEndClasses] = ins || [[], [], []];
2648
2771
  let [outClasses, outStartClasses, outEndClasses] = outs || [[], [], []];
2649
2772
  if (inClasses.length > 0 || outClasses.length > 0) {
@@ -2697,7 +2820,16 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2697
2820
  }
2698
2821
  }
2699
2822
  },
2823
+ toggleClasses(el, classes, transition, time, view) {
2824
+ window.requestAnimationFrame(() => {
2825
+ let [prevAdds, prevRemoves] = dom_default.getSticky(el, "classes", [[], []]);
2826
+ let newAdds = classes.filter((name) => prevAdds.indexOf(name) < 0 && !el.classList.contains(name));
2827
+ let newRemoves = classes.filter((name) => prevRemoves.indexOf(name) < 0 && el.classList.contains(name));
2828
+ this.addOrRemoveClasses(el, newAdds, newRemoves, transition, time, view);
2829
+ });
2830
+ },
2700
2831
  addOrRemoveClasses(el, adds, removes, transition, time, view) {
2832
+ time = time || default_transition_time;
2701
2833
  let [transitionRun, transitionStart, transitionEnd] = transition || [[], [], []];
2702
2834
  if (transitionRun.length > 0) {
2703
2835
  let onStart = () => {
@@ -2751,24 +2883,37 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2751
2883
 
2752
2884
  // js/phoenix_live_view/view.js
2753
2885
  var serializeForm = (form, metadata, onlyNames = []) => {
2754
- let _a = metadata, { submitter } = _a, meta = __objRest(_a, ["submitter"]);
2755
- let formData = new FormData(form);
2756
- if (submitter && submitter.hasAttribute("name") && submitter.form && submitter.form === form) {
2757
- formData.append(submitter.name, submitter.value);
2758
- }
2759
- let toRemove = [];
2886
+ const _a = metadata, { submitter } = _a, meta = __objRest(_a, ["submitter"]);
2887
+ let injectedElement;
2888
+ if (submitter && submitter.name) {
2889
+ const input = document.createElement("input");
2890
+ input.type = "hidden";
2891
+ const formId = submitter.getAttribute("form");
2892
+ if (formId) {
2893
+ input.setAttribute("form", form);
2894
+ }
2895
+ input.name = submitter.name;
2896
+ input.value = submitter.value;
2897
+ submitter.parentElement.insertBefore(input, submitter);
2898
+ injectedElement = input;
2899
+ }
2900
+ const formData = new FormData(form);
2901
+ const toRemove = [];
2760
2902
  formData.forEach((val, key, _index) => {
2761
2903
  if (val instanceof File) {
2762
2904
  toRemove.push(key);
2763
2905
  }
2764
2906
  });
2765
2907
  toRemove.forEach((key) => formData.delete(key));
2766
- let params = new URLSearchParams();
2908
+ const params = new URLSearchParams();
2767
2909
  for (let [key, val] of formData.entries()) {
2768
2910
  if (onlyNames.length === 0 || onlyNames.indexOf(key) >= 0) {
2769
2911
  params.append(key, val);
2770
2912
  }
2771
2913
  }
2914
+ if (submitter && injectedElement) {
2915
+ submitter.parentElement.removeChild(injectedElement);
2916
+ }
2772
2917
  for (let metaKey in meta) {
2773
2918
  params.append(metaKey, meta[metaKey]);
2774
2919
  }
@@ -2972,6 +3117,9 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2972
3117
  if (phxStatic) {
2973
3118
  toEl.setAttribute(PHX_STATIC, phxStatic);
2974
3119
  }
3120
+ if (fromEl) {
3121
+ fromEl.setAttribute(PHX_ROOT_ID, this.root.id);
3122
+ }
2975
3123
  return this.joinChild(toEl);
2976
3124
  });
2977
3125
  if (newChildren.length === 0) {
@@ -3051,6 +3199,9 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3051
3199
  let updatedHookIds = new Set();
3052
3200
  patch.after("added", (el) => {
3053
3201
  this.liveSocket.triggerDOM("onNodeAdded", [el]);
3202
+ let phxViewportTop = this.binding(PHX_VIEWPORT_TOP);
3203
+ let phxViewportBottom = this.binding(PHX_VIEWPORT_BOTTOM);
3204
+ dom_default.maybeAddPrivateHooks(el, phxViewportTop, phxViewportBottom);
3054
3205
  this.maybeAddNewHook(el);
3055
3206
  if (el.getAttribute) {
3056
3207
  this.maybeMounted(el);
@@ -3427,10 +3578,11 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3427
3578
  }
3428
3579
  dom_default.all(document, `[${PHX_REF_SRC}="${this.id}"][${PHX_REF}="${ref}"]`, (el) => {
3429
3580
  let disabledVal = el.getAttribute(PHX_DISABLED);
3581
+ let readOnlyVal = el.getAttribute(PHX_READONLY);
3430
3582
  el.removeAttribute(PHX_REF);
3431
3583
  el.removeAttribute(PHX_REF_SRC);
3432
- if (el.getAttribute(PHX_READONLY) !== null) {
3433
- el.readOnly = false;
3584
+ if (readOnlyVal !== null) {
3585
+ el.readOnly = readOnlyVal === "true" ? true : false;
3434
3586
  el.removeAttribute(PHX_READONLY);
3435
3587
  }
3436
3588
  if (disabledVal !== null) {
@@ -3472,6 +3624,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3472
3624
  if (disableText !== "") {
3473
3625
  el.innerText = disableText;
3474
3626
  }
3627
+ el.setAttribute(PHX_DISABLED, el.getAttribute(PHX_DISABLED) || el.disabled);
3475
3628
  el.setAttribute("disabled", "");
3476
3629
  }
3477
3630
  });
@@ -3573,6 +3726,9 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3573
3726
  let refGenerator = () => this.putRef([inputEl, inputEl.form], "change", opts);
3574
3727
  let formData;
3575
3728
  let meta = this.extractMeta(inputEl.form);
3729
+ if (inputEl instanceof HTMLButtonElement) {
3730
+ meta.submitter = inputEl;
3731
+ }
3576
3732
  if (inputEl.getAttribute(this.binding("change"))) {
3577
3733
  formData = serializeForm(inputEl.form, __spreadValues({ _target: opts._target }, meta), [inputEl.name]);
3578
3734
  } else {
@@ -3597,6 +3753,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3597
3753
  this.uploadFiles(inputEl.form, targetCtx, ref, cid, (_uploads) => {
3598
3754
  callback && callback(resp);
3599
3755
  this.triggerAwaitingSubmit(inputEl.form);
3756
+ this.undoRefs(ref);
3600
3757
  });
3601
3758
  }
3602
3759
  } else {
@@ -3795,7 +3952,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3795
3952
  let template = document.createElement("template");
3796
3953
  template.innerHTML = html;
3797
3954
  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) => {
3798
- const phxChangeValue = form.getAttribute(phxChange).replaceAll(/([\[\]"])/g, "\\$1");
3955
+ const phxChangeValue = CSS.escape(form.getAttribute(phxChange));
3799
3956
  let newForm = template.content.querySelector(`form[id="${form.id}"][${phxChange}="${phxChangeValue}"]`);
3800
3957
  if (newForm) {
3801
3958
  return [form, newForm, this.targetComponentID(newForm)];
@@ -3805,18 +3962,19 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3805
3962
  }).filter(([form, newForm, newCid]) => newForm);
3806
3963
  }
3807
3964
  maybePushComponentsDestroyed(destroyedCIDs) {
3808
- let willDestroyCIDs = destroyedCIDs.filter((cid) => {
3965
+ let willDestroyCIDs = destroyedCIDs.concat(this.pruningCIDs).filter((cid) => {
3809
3966
  return dom_default.findComponentNodeList(this.el, cid).length === 0;
3810
3967
  });
3968
+ this.pruningCIDs = willDestroyCIDs.concat([]);
3811
3969
  if (willDestroyCIDs.length > 0) {
3812
- this.pruningCIDs.push(...willDestroyCIDs);
3970
+ willDestroyCIDs.forEach((cid) => this.rendered.resetRender(cid));
3813
3971
  this.pushWithReply(null, "cids_will_destroy", { cids: willDestroyCIDs }, () => {
3814
- this.pruningCIDs = this.pruningCIDs.filter((cid) => willDestroyCIDs.indexOf(cid) !== -1);
3815
3972
  let completelyDestroyCIDs = willDestroyCIDs.filter((cid) => {
3816
3973
  return dom_default.findComponentNodeList(this.el, cid).length === 0;
3817
3974
  });
3818
3975
  if (completelyDestroyCIDs.length > 0) {
3819
3976
  this.pushWithReply(null, "cids_destroyed", { cids: completelyDestroyCIDs }, (resp) => {
3977
+ this.pruningCIDs = this.pruningCIDs.filter((cid) => resp.cids.indexOf(cid) === -1);
3820
3978
  this.rendered.pruneCIDs(resp.cids);
3821
3979
  });
3822
3980
  }
@@ -4132,22 +4290,26 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
4132
4290
  this.main.destroy();
4133
4291
  this.main = this.newRootView(newMainEl, flash, liveReferer);
4134
4292
  this.main.setRedirect(href);
4135
- this.transitionRemoves();
4293
+ this.transitionRemoves(null, true);
4136
4294
  this.main.join((joinCount, onDone) => {
4137
4295
  if (joinCount === 1 && this.commitPendingLink(linkRef)) {
4138
4296
  this.requestDOMUpdate(() => {
4139
4297
  dom_default.findPhxSticky(document).forEach((el) => newMainEl.appendChild(el));
4140
4298
  this.outgoingMainEl.replaceWith(newMainEl);
4141
4299
  this.outgoingMainEl = null;
4142
- callback && requestAnimationFrame(() => callback(linkRef));
4300
+ callback && callback(linkRef);
4143
4301
  onDone();
4144
4302
  });
4145
4303
  }
4146
4304
  });
4147
4305
  }
4148
- transitionRemoves(elements) {
4306
+ transitionRemoves(elements, skipSticky) {
4149
4307
  let removeAttr = this.binding("remove");
4150
4308
  elements = elements || dom_default.all(document, `[${removeAttr}]`);
4309
+ if (skipSticky) {
4310
+ const stickies = dom_default.findPhxSticky(document) || [];
4311
+ elements = elements.filter((el) => !dom_default.isChildOfAny(el, stickies));
4312
+ }
4151
4313
  elements.forEach((el) => {
4152
4314
  this.execJS(el, el.getAttribute(removeAttr), "remove");
4153
4315
  });
@@ -4363,6 +4525,8 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
4363
4525
  if (capture) {
4364
4526
  target = e.target.matches(`[${click}]`) ? e.target : e.target.querySelector(`[${click}]`);
4365
4527
  } else {
4528
+ if (e.detail === 0)
4529
+ this.clickStartedAtTarget = e.target;
4366
4530
  let clickStartedAtTarget = this.clickStartedAtTarget || e.target;
4367
4531
  target = closestPhxBinding(clickStartedAtTarget, click);
4368
4532
  this.dispatchClickAway(e, clickStartedAtTarget);
@@ -4392,7 +4556,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
4392
4556
  let phxClickAway = this.binding("click-away");
4393
4557
  dom_default.all(document, `[${phxClickAway}]`, (el) => {
4394
4558
  if (!(el.isSameNode(clickStartedAt) || el.contains(clickStartedAt))) {
4395
- this.withinOwners(e.target, (view) => {
4559
+ this.withinOwners(el, (view) => {
4396
4560
  let phxEvent = el.getAttribute(phxClickAway);
4397
4561
  if (js_default.isVisible(el) && js_default.isInViewport(el)) {
4398
4562
  js_default.exec("click", phxEvent, view, el, ["push", { data: this.eventMeta("click", e, e.target) }]);
@@ -4484,7 +4648,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
4484
4648
  return callback ? callback(done) : done;
4485
4649
  }
4486
4650
  pushHistoryPatch(href, linkState, targetEl) {
4487
- if (!this.isConnected()) {
4651
+ if (!this.isConnected() || !this.main.isMain()) {
4488
4652
  return browser_default.redirect(href);
4489
4653
  }
4490
4654
  this.withPageLoading({ to: href, kind: "patch" }, (done) => {
@@ -4503,7 +4667,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
4503
4667
  this.registerNewLocation(window.location);
4504
4668
  }
4505
4669
  historyRedirect(href, linkState, flash) {
4506
- if (!this.isConnected()) {
4670
+ if (!this.isConnected() || !this.main.isMain()) {
4507
4671
  return browser_default.redirect(href, flash);
4508
4672
  }
4509
4673
  if (/^\/$|^\/[^\/]+.*$/.test(href)) {
@@ -4604,9 +4768,11 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
4604
4768
  let form = e.target;
4605
4769
  dom_default.resetForm(form, this.binding(PHX_FEEDBACK_FOR));
4606
4770
  let input = Array.from(form.elements).find((el) => el.type === "reset");
4607
- window.requestAnimationFrame(() => {
4608
- input.dispatchEvent(new Event("input", { bubbles: true, cancelable: false }));
4609
- });
4771
+ if (input) {
4772
+ window.requestAnimationFrame(() => {
4773
+ input.dispatchEvent(new Event("input", { bubbles: true, cancelable: false }));
4774
+ });
4775
+ }
4610
4776
  });
4611
4777
  }
4612
4778
  debounce(el, event, eventType, callback) {