phoenix_live_view 0.20.0 → 0.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,6 +27,7 @@ var PHX_DROP_TARGET = "drop-target";
27
27
  var PHX_ACTIVE_ENTRY_REFS = "data-phx-active-refs";
28
28
  var PHX_LIVE_FILE_UPDATED = "phx:live-file:updated";
29
29
  var PHX_SKIP = "data-phx-skip";
30
+ var PHX_MAGIC_ID = "data-phx-id";
30
31
  var PHX_PRUNE = "data-phx-prune";
31
32
  var PHX_PAGE_LOADING = "page-loading";
32
33
  var PHX_CONNECTED_CLASS = "phx-connected";
@@ -81,6 +82,7 @@ var DEFAULTS = {
81
82
  };
82
83
  var DYNAMICS = "d";
83
84
  var STATIC = "s";
85
+ var ROOT = "r";
84
86
  var COMPONENTS = "c";
85
87
  var EVENTS = "e";
86
88
  var REPLY = "r";
@@ -103,6 +105,7 @@ var EntryUploader = class {
103
105
  if (this.errored) {
104
106
  return;
105
107
  }
108
+ this.uploadChannel.leave();
106
109
  this.errored = true;
107
110
  clearTimeout(this.chunkTimer);
108
111
  this.entry.error(reason);
@@ -522,12 +525,17 @@ var DOM = {
522
525
  el.setAttribute("data-phx-hook", "Phoenix.InfiniteScroll");
523
526
  }
524
527
  },
525
- maybeHideFeedback(container, input, phxFeedbackFor) {
526
- if (!(this.private(input, PHX_HAS_FOCUSED) || this.private(input, PHX_HAS_SUBMITTED))) {
527
- let feedbacks = [input.name];
528
- if (input.name.endsWith("[]")) {
529
- feedbacks.push(input.name.slice(0, -2));
528
+ maybeHideFeedback(container, inputs, phxFeedbackFor) {
529
+ let feedbacks = [];
530
+ inputs.forEach((input) => {
531
+ if (!(this.private(input, PHX_HAS_FOCUSED) || this.private(input, PHX_HAS_SUBMITTED))) {
532
+ feedbacks.push(input.name);
533
+ if (input.name.endsWith("[]")) {
534
+ feedbacks.push(input.name.slice(0, -2));
535
+ }
530
536
  }
537
+ });
538
+ if (feedbacks.length > 0) {
531
539
  let selector = feedbacks.map((f) => `[${phxFeedbackFor}="${f}"]`).join(", ");
532
540
  DOM.all(container, selector, (el) => el.classList.add(PHX_NO_FEEDBACK_CLASS));
533
541
  }
@@ -814,7 +822,8 @@ var UploadEntry = class {
814
822
  relative_path: this.file.webkitRelativePath,
815
823
  size: this.file.size,
816
824
  type: this.file.type,
817
- ref: this.ref
825
+ ref: this.ref,
826
+ meta: typeof this.file.meta === "function" ? this.file.meta() : void 0
818
827
  };
819
828
  }
820
829
  uploader(uploaders) {
@@ -871,6 +880,9 @@ var LiveUploader = class {
871
880
  entry.relative_path = file.webkitRelativePath;
872
881
  entry.type = file.type;
873
882
  entry.size = file.size;
883
+ if (typeof file.meta === "function") {
884
+ entry.meta = file.meta();
885
+ }
874
886
  fileData[uploadRef].push(entry);
875
887
  });
876
888
  return fileData;
@@ -960,7 +972,7 @@ var ARIA = {
960
972
  return classes.find((name) => instance instanceof name);
961
973
  },
962
974
  isFocusable(el, interactiveOnly) {
963
- return el instanceof HTMLAnchorElement && el.rel !== "ignore" || el instanceof HTMLAreaElement && el.href !== void 0 || !el.disabled && this.anyOf(el, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, HTMLButtonElement]) || el instanceof HTMLIFrameElement || (el.tabIndex > 0 || !interactiveOnly && el.tabIndex === 0 && el.getAttribute("tabindex") !== null && el.getAttribute("aria-hidden") !== "true");
975
+ return el instanceof HTMLAnchorElement && el.rel !== "ignore" || el instanceof HTMLAreaElement && el.href !== void 0 || !el.disabled && this.anyOf(el, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, HTMLButtonElement]) || el instanceof HTMLIFrameElement || (el.tabIndex > 0 || !interactiveOnly && el.getAttribute("tabindex") !== null && el.getAttribute("aria-hidden") !== "true");
964
976
  },
965
977
  attemptFocus(el, interactiveOnly) {
966
978
  if (this.isFocusable(el, interactiveOnly)) {
@@ -1536,7 +1548,7 @@ function morphdomFactory(morphAttrs2) {
1536
1548
  }
1537
1549
  }
1538
1550
  function morphChildren(fromEl, toEl) {
1539
- var skipFrom = skipFromChildren(fromEl);
1551
+ var skipFrom = skipFromChildren(fromEl, toEl);
1540
1552
  var curToNodeChild = toEl.firstChild;
1541
1553
  var curFromNodeChild = fromEl.firstChild;
1542
1554
  var curToNodeKey;
@@ -1739,7 +1751,7 @@ var DOMPatch = class {
1739
1751
  el.setAttribute(PHX_PRUNE, "");
1740
1752
  });
1741
1753
  }
1742
- perform() {
1754
+ perform(isJoinPatch) {
1743
1755
  let { view, liveSocket, container, html } = this;
1744
1756
  let targetContainer = this.isCIDPatch() ? this.targetCIDContainer(html) : container;
1745
1757
  if (this.isCIDPatch() && !targetContainer) {
@@ -1758,19 +1770,18 @@ var DOMPatch = class {
1758
1770
  let updates = [];
1759
1771
  let appendPrependUpdates = [];
1760
1772
  let externalFormTriggered = null;
1761
- let diffHTML = liveSocket.time("premorph container prep", () => {
1762
- return this.buildDiffHTML(container, html, phxUpdate, targetContainer);
1763
- });
1764
1773
  this.trackBefore("added", container);
1765
1774
  this.trackBefore("updated", container, container);
1766
1775
  liveSocket.time("morphdom", () => {
1767
1776
  this.streams.forEach(([ref, inserts, deleteIds, reset]) => {
1768
1777
  Object.entries(inserts).forEach(([key, [streamAt, limit]]) => {
1769
- this.streamInserts[key] = { ref, streamAt, limit };
1778
+ this.streamInserts[key] = { ref, streamAt, limit, resetKept: false };
1770
1779
  });
1771
1780
  if (reset !== void 0) {
1772
1781
  dom_default.all(container, `[${PHX_STREAM_REF}="${ref}"]`, (child) => {
1773
- if (!inserts[child.id]) {
1782
+ if (inserts[child.id]) {
1783
+ this.streamInserts[child.id].resetKept = true;
1784
+ } else {
1774
1785
  this.removeStreamChildElement(child);
1775
1786
  }
1776
1787
  });
@@ -1782,10 +1793,16 @@ var DOMPatch = class {
1782
1793
  }
1783
1794
  });
1784
1795
  });
1785
- morphdom_esm_default(targetContainer, diffHTML, {
1796
+ morphdom_esm_default(targetContainer, html, {
1786
1797
  childrenOnly: targetContainer.getAttribute(PHX_COMPONENT) === null,
1787
1798
  getNodeKey: (node) => {
1788
- return dom_default.isPhxDestroyed(node) ? null : node.id;
1799
+ if (dom_default.isPhxDestroyed(node)) {
1800
+ return null;
1801
+ }
1802
+ if (isJoinPatch) {
1803
+ return node.id;
1804
+ }
1805
+ return node.id || node.getAttribute && node.getAttribute(PHX_MAGIC_ID);
1789
1806
  },
1790
1807
  skipFromChildren: (from) => {
1791
1808
  return from.getAttribute(phxUpdate) === PHX_STREAM;
@@ -1842,6 +1859,25 @@ var DOMPatch = class {
1842
1859
  }
1843
1860
  added.push(el);
1844
1861
  },
1862
+ onBeforeElChildrenUpdated: (fromEl, toEl) => {
1863
+ if (fromEl.getAttribute(phxUpdate) === PHX_STREAM) {
1864
+ let toIds = Array.from(toEl.children).map((child) => child.id);
1865
+ Array.from(fromEl.children).filter((child) => {
1866
+ let { resetKept } = this.getStreamInsert(child);
1867
+ return resetKept;
1868
+ }).sort((a, b) => {
1869
+ let aIdx = toIds.indexOf(a.id);
1870
+ let bIdx = toIds.indexOf(b.id);
1871
+ if (aIdx === bIdx) {
1872
+ return 0;
1873
+ } else if (aIdx < bIdx) {
1874
+ return -1;
1875
+ } else {
1876
+ return 1;
1877
+ }
1878
+ }).forEach((child) => fromEl.appendChild(child));
1879
+ }
1880
+ },
1845
1881
  onNodeDiscarded: (el) => this.onNodeDiscarded(el),
1846
1882
  onBeforeNodeDiscarded: (el) => {
1847
1883
  if (el.getAttribute && el.getAttribute(PHX_PRUNE) !== null) {
@@ -1935,9 +1971,7 @@ var DOMPatch = class {
1935
1971
  appendPrependUpdates.forEach((update) => update.perform());
1936
1972
  });
1937
1973
  }
1938
- trackedInputs.forEach((input) => {
1939
- dom_default.maybeHideFeedback(targetContainer, input, phxFeedbackFor);
1940
- });
1974
+ dom_default.maybeHideFeedback(targetContainer, trackedInputs, phxFeedbackFor);
1941
1975
  liveSocket.silenceEvents(() => dom_default.restoreFocus(focused, selectionStart, selectionEnd));
1942
1976
  dom_default.dispatchEvent(document, "phx:update");
1943
1977
  added.forEach((el) => this.trackAfter("added", el));
@@ -2016,7 +2050,7 @@ var DOMPatch = class {
2016
2050
  return this.cidPatch;
2017
2051
  }
2018
2052
  skipCIDSibling(el) {
2019
- return el.nodeType === Node.ELEMENT_NODE && el.getAttribute(PHX_SKIP) !== null;
2053
+ return el.nodeType === Node.ELEMENT_NODE && el.hasAttribute(PHX_SKIP);
2020
2054
  }
2021
2055
  targetCIDContainer(html) {
2022
2056
  if (!this.isCIDPatch()) {
@@ -2029,35 +2063,125 @@ var DOMPatch = class {
2029
2063
  return first && first.parentNode;
2030
2064
  }
2031
2065
  }
2032
- buildDiffHTML(container, html, phxUpdate, targetContainer) {
2033
- let isCIDPatch = this.isCIDPatch();
2034
- let isCIDWithSingleRoot = isCIDPatch && targetContainer.getAttribute(PHX_COMPONENT) === this.targetCID.toString();
2035
- if (!isCIDPatch || isCIDWithSingleRoot) {
2036
- return html;
2037
- } else {
2038
- let diffContainer = null;
2039
- let template = document.createElement("template");
2040
- diffContainer = dom_default.cloneNode(targetContainer);
2041
- let [firstComponent, ...rest] = dom_default.findComponentNodeList(diffContainer, this.targetCID);
2042
- template.innerHTML = html;
2043
- rest.forEach((el) => el.remove());
2044
- Array.from(diffContainer.childNodes).forEach((child) => {
2045
- if (child.id && child.nodeType === Node.ELEMENT_NODE && child.getAttribute(PHX_COMPONENT) !== this.targetCID.toString()) {
2046
- child.setAttribute(PHX_SKIP, "");
2047
- child.innerHTML = "";
2048
- }
2049
- });
2050
- Array.from(template.content.childNodes).forEach((el) => diffContainer.insertBefore(el, firstComponent));
2051
- firstComponent.remove();
2052
- return diffContainer.outerHTML;
2053
- }
2054
- }
2055
2066
  indexOf(parent, child) {
2056
2067
  return Array.from(parent.children).indexOf(child);
2057
2068
  }
2058
2069
  };
2059
2070
 
2060
2071
  // js/phoenix_live_view/rendered.js
2072
+ var VOID_TAGS = new Set([
2073
+ "area",
2074
+ "base",
2075
+ "br",
2076
+ "col",
2077
+ "command",
2078
+ "embed",
2079
+ "hr",
2080
+ "img",
2081
+ "input",
2082
+ "keygen",
2083
+ "link",
2084
+ "meta",
2085
+ "param",
2086
+ "source",
2087
+ "track",
2088
+ "wbr"
2089
+ ]);
2090
+ var endingTagNameChars = new Set([">", "/", " ", "\n", " ", "\r"]);
2091
+ var quoteChars = new Set(["'", '"']);
2092
+ var modifyRoot = (html, attrs, clearInnerHTML) => {
2093
+ let i = 0;
2094
+ let insideComment = false;
2095
+ let beforeTag, afterTag, tag, tagNameEndsAt, id, newHTML;
2096
+ while (i < html.length) {
2097
+ let char = html.charAt(i);
2098
+ if (insideComment) {
2099
+ if (char === "-" && html.slice(i, i + 3) === "-->") {
2100
+ insideComment = false;
2101
+ i += 3;
2102
+ } else {
2103
+ i++;
2104
+ }
2105
+ } else if (char === "<" && html.slice(i, i + 4) === "<!--") {
2106
+ insideComment = true;
2107
+ i += 4;
2108
+ } else if (char === "<") {
2109
+ beforeTag = html.slice(0, i);
2110
+ let iAtOpen = i;
2111
+ i++;
2112
+ for (i; i < html.length; i++) {
2113
+ if (endingTagNameChars.has(html.charAt(i))) {
2114
+ break;
2115
+ }
2116
+ }
2117
+ tagNameEndsAt = i;
2118
+ tag = html.slice(iAtOpen + 1, tagNameEndsAt);
2119
+ for (i; i < html.length; i++) {
2120
+ if (html.charAt(i) === ">") {
2121
+ break;
2122
+ }
2123
+ if (html.charAt(i) === "=") {
2124
+ let isId = html.slice(i - 3, i) === " id";
2125
+ i++;
2126
+ let char2 = html.charAt(i);
2127
+ if (quoteChars.has(char2)) {
2128
+ let attrStartsAt = i;
2129
+ i++;
2130
+ for (i; i < html.length; i++) {
2131
+ if (html.charAt(i) === char2) {
2132
+ break;
2133
+ }
2134
+ }
2135
+ if (isId) {
2136
+ id = html.slice(attrStartsAt + 1, i);
2137
+ break;
2138
+ }
2139
+ }
2140
+ }
2141
+ }
2142
+ break;
2143
+ } else {
2144
+ i++;
2145
+ }
2146
+ }
2147
+ if (!tag) {
2148
+ throw new Error(`malformed html ${html}`);
2149
+ }
2150
+ let closeAt = html.length - 1;
2151
+ insideComment = false;
2152
+ while (closeAt >= beforeTag.length + tag.length) {
2153
+ let char = html.charAt(closeAt);
2154
+ if (insideComment) {
2155
+ if (char === "-" && html.slice(closeAt - 3, closeAt) === "<!-") {
2156
+ insideComment = false;
2157
+ closeAt -= 4;
2158
+ } else {
2159
+ closeAt -= 1;
2160
+ }
2161
+ } else if (char === ">" && html.slice(closeAt - 2, closeAt) === "--") {
2162
+ insideComment = true;
2163
+ closeAt -= 3;
2164
+ } else if (char === ">") {
2165
+ break;
2166
+ } else {
2167
+ closeAt -= 1;
2168
+ }
2169
+ }
2170
+ afterTag = html.slice(closeAt + 1, html.length);
2171
+ let attrsStr = Object.keys(attrs).map((attr) => attrs[attr] === true ? attr : `${attr}="${attrs[attr]}"`).join(" ");
2172
+ if (clearInnerHTML) {
2173
+ let idAttrStr = id ? ` id="${id}"` : "";
2174
+ if (VOID_TAGS.has(tag)) {
2175
+ newHTML = `<${tag}${idAttrStr}${attrsStr === "" ? "" : " "}${attrsStr}/>`;
2176
+ } else {
2177
+ newHTML = `<${tag}${idAttrStr}${attrsStr === "" ? "" : " "}${attrsStr}></${tag}>`;
2178
+ }
2179
+ } else {
2180
+ let rest = html.slice(tagNameEndsAt, closeAt + 1);
2181
+ newHTML = `<${tag}${attrsStr === "" ? "" : " "}${attrsStr}${rest}`;
2182
+ }
2183
+ return [newHTML, beforeTag, afterTag];
2184
+ };
2061
2185
  var Rendered = class {
2062
2186
  static extract(diff) {
2063
2187
  let { [REPLY]: reply, [EVENTS]: events, [TITLE]: title } = diff;
@@ -2069,19 +2193,20 @@ var Rendered = class {
2069
2193
  constructor(viewId, rendered) {
2070
2194
  this.viewId = viewId;
2071
2195
  this.rendered = {};
2196
+ this.magicId = 0;
2072
2197
  this.mergeDiff(rendered);
2073
2198
  }
2074
2199
  parentViewId() {
2075
2200
  return this.viewId;
2076
2201
  }
2077
2202
  toString(onlyCids) {
2078
- let [str, streams] = this.recursiveToString(this.rendered, this.rendered[COMPONENTS], onlyCids);
2203
+ let [str, streams] = this.recursiveToString(this.rendered, this.rendered[COMPONENTS], onlyCids, true, {});
2079
2204
  return [str, streams];
2080
2205
  }
2081
- recursiveToString(rendered, components = rendered[COMPONENTS], onlyCids) {
2206
+ recursiveToString(rendered, components = rendered[COMPONENTS], onlyCids, changeTracking, rootAttrs) {
2082
2207
  onlyCids = onlyCids ? new Set(onlyCids) : null;
2083
2208
  let output = { buffer: "", components, onlyCids, streams: new Set() };
2084
- this.toOutputBuffer(rendered, null, output);
2209
+ this.toOutputBuffer(rendered, null, output, changeTracking, rootAttrs);
2085
2210
  return [output.buffer, output.streams];
2086
2211
  }
2087
2212
  componentCIDs(diff) {
@@ -2126,10 +2251,10 @@ var Rendered = class {
2126
2251
  tdiff = oldc[-scid];
2127
2252
  }
2128
2253
  stat = tdiff[STATIC];
2129
- ndiff = this.cloneMerge(tdiff, cdiff);
2254
+ ndiff = this.cloneMerge(tdiff, cdiff, true);
2130
2255
  ndiff[STATIC] = stat;
2131
2256
  } else {
2132
- ndiff = cdiff[STATIC] !== void 0 ? cdiff : this.cloneMerge(oldc[cid] || {}, cdiff);
2257
+ ndiff = cdiff[STATIC] !== void 0 || oldc[cid] === void 0 ? cdiff : this.cloneMerge(oldc[cid], cdiff, false);
2133
2258
  }
2134
2259
  cache[cid] = ndiff;
2135
2260
  return ndiff;
@@ -2154,21 +2279,31 @@ var Rendered = class {
2154
2279
  target[key] = val;
2155
2280
  }
2156
2281
  }
2282
+ if (target[ROOT]) {
2283
+ target.newRender = true;
2284
+ }
2157
2285
  }
2158
- cloneMerge(target, source) {
2286
+ cloneMerge(target, source, pruneMagicId) {
2159
2287
  let merged = { ...target, ...source };
2160
2288
  for (let key in merged) {
2161
2289
  let val = source[key];
2162
2290
  let targetVal = target[key];
2163
2291
  if (isObject(val) && val[STATIC] === void 0 && isObject(targetVal)) {
2164
- merged[key] = this.cloneMerge(targetVal, val);
2292
+ merged[key] = this.cloneMerge(targetVal, val, pruneMagicId);
2165
2293
  }
2166
2294
  }
2295
+ if (pruneMagicId) {
2296
+ delete merged.magicId;
2297
+ delete merged.newRender;
2298
+ } else if (target[ROOT]) {
2299
+ merged.newRender = true;
2300
+ }
2167
2301
  return merged;
2168
2302
  }
2169
2303
  componentToString(cid) {
2170
- let [str, streams] = this.recursiveCIDToString(this.rendered[COMPONENTS], cid, null, false);
2171
- return [str, streams];
2304
+ let [str, streams] = this.recursiveCIDToString(this.rendered[COMPONENTS], cid, null);
2305
+ let [strippedHTML, _before, _after] = modifyRoot(str, {});
2306
+ return [strippedHTML, streams];
2172
2307
  }
2173
2308
  pruneCIDs(cids) {
2174
2309
  cids.forEach((cid) => delete this.rendered[COMPONENTS][cid]);
@@ -2186,17 +2321,46 @@ var Rendered = class {
2186
2321
  return part;
2187
2322
  }
2188
2323
  }
2189
- toOutputBuffer(rendered, templates, output) {
2324
+ nextMagicID() {
2325
+ this.magicId++;
2326
+ return `${this.parentViewId()}-${this.magicId}`;
2327
+ }
2328
+ toOutputBuffer(rendered, templates, output, changeTracking, rootAttrs = {}) {
2190
2329
  if (rendered[DYNAMICS]) {
2191
2330
  return this.comprehensionToBuffer(rendered, templates, output);
2192
2331
  }
2193
2332
  let { [STATIC]: statics } = rendered;
2194
2333
  statics = this.templateStatic(statics, templates);
2334
+ let isRoot = rendered[ROOT];
2335
+ let prevBuffer = output.buffer;
2336
+ if (isRoot) {
2337
+ output.buffer = "";
2338
+ }
2339
+ if (changeTracking && isRoot && !rendered.magicId) {
2340
+ rendered.newRender = true;
2341
+ rendered.magicId = this.nextMagicID();
2342
+ }
2195
2343
  output.buffer += statics[0];
2196
2344
  for (let i = 1; i < statics.length; i++) {
2197
- this.dynamicToBuffer(rendered[i - 1], templates, output);
2345
+ this.dynamicToBuffer(rendered[i - 1], templates, output, changeTracking);
2198
2346
  output.buffer += statics[i];
2199
2347
  }
2348
+ if (isRoot) {
2349
+ let skip = false;
2350
+ let attrs;
2351
+ if (changeTracking || Object.keys(rootAttrs).length > 0) {
2352
+ skip = !rendered.newRender;
2353
+ attrs = { [PHX_MAGIC_ID]: rendered.magicId, ...rootAttrs };
2354
+ } else {
2355
+ attrs = rootAttrs;
2356
+ }
2357
+ if (skip) {
2358
+ attrs[PHX_SKIP] = true;
2359
+ }
2360
+ let [newRoot, commentBefore, commentAfter] = modifyRoot(output.buffer, attrs, skip);
2361
+ rendered.newRender = false;
2362
+ output.buffer = prevBuffer + commentBefore + newRoot + commentAfter;
2363
+ }
2200
2364
  }
2201
2365
  comprehensionToBuffer(rendered, templates, output) {
2202
2366
  let { [DYNAMICS]: dynamics, [STATIC]: statics, [STREAM]: stream } = rendered;
@@ -2207,7 +2371,8 @@ var Rendered = class {
2207
2371
  let dynamic = dynamics[d];
2208
2372
  output.buffer += statics[0];
2209
2373
  for (let i = 1; i < statics.length; i++) {
2210
- this.dynamicToBuffer(dynamic[i - 1], compTemplates, output);
2374
+ let changeTracking = false;
2375
+ this.dynamicToBuffer(dynamic[i - 1], compTemplates, output, changeTracking);
2211
2376
  output.buffer += statics[i];
2212
2377
  }
2213
2378
  }
@@ -2217,74 +2382,26 @@ var Rendered = class {
2217
2382
  output.streams.add(stream);
2218
2383
  }
2219
2384
  }
2220
- dynamicToBuffer(rendered, templates, output) {
2385
+ dynamicToBuffer(rendered, templates, output, changeTracking) {
2221
2386
  if (typeof rendered === "number") {
2222
2387
  let [str, streams] = this.recursiveCIDToString(output.components, rendered, output.onlyCids);
2223
2388
  output.buffer += str;
2224
2389
  output.streams = new Set([...output.streams, ...streams]);
2225
2390
  } else if (isObject(rendered)) {
2226
- this.toOutputBuffer(rendered, templates, output);
2391
+ this.toOutputBuffer(rendered, templates, output, changeTracking, {});
2227
2392
  } else {
2228
2393
  output.buffer += rendered;
2229
2394
  }
2230
2395
  }
2231
- recursiveCIDToString(components, cid, onlyCids, allowRootComments = true) {
2396
+ recursiveCIDToString(components, cid, onlyCids) {
2232
2397
  let component = components[cid] || logError(`no component for CID ${cid}`, components);
2233
- let template = document.createElement("template");
2234
- let [html, streams] = this.recursiveToString(component, components, onlyCids);
2235
- template.innerHTML = html;
2236
- let container = template.content;
2398
+ let attrs = { [PHX_COMPONENT]: cid };
2237
2399
  let skip = onlyCids && !onlyCids.has(cid);
2238
- let [hasChildNodes, hasChildComponents] = Array.from(container.childNodes).reduce(([hasNodes, hasComponents], child, i) => {
2239
- if (child.nodeType === Node.ELEMENT_NODE) {
2240
- if (child.getAttribute(PHX_COMPONENT)) {
2241
- return [hasNodes, true];
2242
- }
2243
- child.setAttribute(PHX_COMPONENT, cid);
2244
- if (!child.id) {
2245
- child.id = `${this.parentViewId()}-${cid}-${i}`;
2246
- }
2247
- if (skip) {
2248
- child.setAttribute(PHX_SKIP, "");
2249
- child.innerHTML = "";
2250
- }
2251
- return [true, hasComponents];
2252
- } else if (child.nodeType === Node.COMMENT_NODE) {
2253
- if (!allowRootComments) {
2254
- child.remove();
2255
- }
2256
- return [hasNodes, hasComponents];
2257
- } else {
2258
- if (child.nodeValue.trim() !== "") {
2259
- logError(`only HTML element tags are allowed at the root of components.
2260
-
2261
- got: "${child.nodeValue.trim()}"
2262
-
2263
- within:
2264
- `, template.innerHTML.trim());
2265
- child.replaceWith(this.createSpan(child.nodeValue, cid));
2266
- return [true, hasComponents];
2267
- } else {
2268
- child.remove();
2269
- return [hasNodes, hasComponents];
2270
- }
2271
- }
2272
- }, [false, false]);
2273
- if (!hasChildNodes && !hasChildComponents) {
2274
- logError("expected at least one HTML element tag inside a component, but the component is empty:\n", template.innerHTML.trim());
2275
- return [this.createSpan("", cid).outerHTML, streams];
2276
- } else if (!hasChildNodes && hasChildComponents) {
2277
- logError("expected at least one HTML element tag directly inside a component, but only subcomponents were found. A component must render at least one HTML tag directly inside itself.", template.innerHTML.trim());
2278
- return [template.innerHTML, streams];
2279
- } else {
2280
- return [template.innerHTML, streams];
2281
- }
2282
- }
2283
- createSpan(text, cid) {
2284
- let span = document.createElement("span");
2285
- span.innerText = text;
2286
- span.setAttribute(PHX_COMPONENT, cid);
2287
- return span;
2400
+ component.newRender = !skip;
2401
+ component.magicId = `${this.parentViewId()}-c-${cid}`;
2402
+ let changeTracking = true;
2403
+ let [html, streams] = this.recursiveToString(component, components, onlyCids, changeTracking, attrs);
2404
+ return [html, streams];
2288
2405
  }
2289
2406
  };
2290
2407
 
@@ -2353,10 +2470,12 @@ var ViewHook = class {
2353
2470
  this.__listeners.delete(callbackRef);
2354
2471
  }
2355
2472
  upload(name, files) {
2356
- return this.__view.dispatchUploads(name, files);
2473
+ return this.__view.dispatchUploads(null, name, files);
2357
2474
  }
2358
2475
  uploadTo(phxTarget, name, files) {
2359
- return this.__view.withinTargets(phxTarget, (view) => view.dispatchUploads(name, files));
2476
+ return this.__view.withinTargets(phxTarget, (view, targetCtx) => {
2477
+ view.dispatchUploads(targetCtx, name, files);
2478
+ });
2360
2479
  }
2361
2480
  __cleanup__() {
2362
2481
  this.__listeners.forEach((callbackRef) => this.removeHandleEvent(callbackRef));
@@ -2382,6 +2501,10 @@ var JS = {
2382
2501
  isVisible(el) {
2383
2502
  return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length > 0);
2384
2503
  },
2504
+ isInViewport(el) {
2505
+ const rect = el.getBoundingClientRect();
2506
+ return rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
2507
+ },
2385
2508
  exec_exec(eventType, phxEvent, view, sourceEl, el, [attr, to]) {
2386
2509
  let nodes = to ? dom_default.all(document, to) : [sourceEl];
2387
2510
  nodes.forEach((node) => {
@@ -2640,9 +2763,10 @@ var View = class {
2640
2763
  this.children = this.parent ? null : {};
2641
2764
  this.root.children[this.id] = {};
2642
2765
  this.channel = this.liveSocket.channel(`lv:${this.id}`, () => {
2766
+ let url = this.href && this.expandURL(this.href);
2643
2767
  return {
2644
- redirect: this.redirect ? this.href : void 0,
2645
- url: this.redirect ? void 0 : this.href || void 0,
2768
+ redirect: this.redirect ? url : void 0,
2769
+ url: this.redirect ? void 0 : url || void 0,
2646
2770
  params: this.connectParams(liveReferer),
2647
2771
  session: this.getSession(),
2648
2772
  static: this.getStatic(),
@@ -2840,7 +2964,7 @@ var View = class {
2840
2964
  this.attachTrueDocEl();
2841
2965
  let patch = new DOMPatch(this, this.el, this.id, html, streams, null);
2842
2966
  patch.markPrunableContentForRemoval();
2843
- this.performPatch(patch, false);
2967
+ this.performPatch(patch, false, true);
2844
2968
  this.joinNewChildren();
2845
2969
  this.execNewMounted();
2846
2970
  this.joinPending = false;
@@ -2879,7 +3003,7 @@ var View = class {
2879
3003
  newHook.__mounted();
2880
3004
  }
2881
3005
  }
2882
- performPatch(patch, pruneCids) {
3006
+ performPatch(patch, pruneCids, isJoinPatch = false) {
2883
3007
  let removedEls = [];
2884
3008
  let phxChildrenAdded = false;
2885
3009
  let updatedHookIds = new Set();
@@ -2914,7 +3038,7 @@ var View = class {
2914
3038
  }
2915
3039
  });
2916
3040
  patch.after("transitionsDiscarded", (els) => this.afterElementsRemoved(els, pruneCids));
2917
- patch.perform();
3041
+ patch.perform(isJoinPatch);
2918
3042
  this.afterElementsRemoved(removedEls, pruneCids);
2919
3043
  return phxChildrenAdded;
2920
3044
  }
@@ -3319,7 +3443,7 @@ var View = class {
3319
3443
  if (isCid(targetCtx)) {
3320
3444
  return targetCtx;
3321
3445
  }
3322
- let cidOrSelector = target.getAttribute(this.binding("target"));
3446
+ let cidOrSelector = opts.target || target.getAttribute(this.binding("target"));
3323
3447
  if (isCid(cidOrSelector)) {
3324
3448
  return parseInt(cidOrSelector);
3325
3449
  } else if (targetCtx && (cidOrSelector !== null || opts.target)) {
@@ -3403,7 +3527,7 @@ var View = class {
3403
3527
  }
3404
3528
  pushInput(inputEl, targetCtx, forceCid, phxEvent, opts, callback) {
3405
3529
  let uploads;
3406
- let cid = isCid(forceCid) ? forceCid : this.targetComponentID(inputEl.form, targetCtx);
3530
+ let cid = isCid(forceCid) ? forceCid : this.targetComponentID(inputEl.form, targetCtx, opts);
3407
3531
  let refGenerator = () => this.putRef([inputEl, inputEl.form], "change", opts);
3408
3532
  let formData;
3409
3533
  let meta = this.extractMeta(inputEl.form);
@@ -3499,7 +3623,7 @@ var View = class {
3499
3623
  let cid = this.targetComponentID(formEl, targetCtx);
3500
3624
  if (LiveUploader.hasUploadsInProgress(formEl)) {
3501
3625
  let [ref, _els] = refGenerator();
3502
- let push = () => this.pushFormSubmit(formEl, submitter, targetCtx, phxEvent, opts, onReply);
3626
+ let push = () => this.pushFormSubmit(formEl, targetCtx, phxEvent, submitter, opts, onReply);
3503
3627
  return this.scheduleSubmit(formEl, ref, opts, push);
3504
3628
  } else if (LiveUploader.inputsAwaitingPreflight(formEl).length > 0) {
3505
3629
  let [ref, els] = refGenerator();
@@ -3563,8 +3687,9 @@ var View = class {
3563
3687
  });
3564
3688
  });
3565
3689
  }
3566
- dispatchUploads(name, filesOrBlobs) {
3567
- let inputs = dom_default.findUploadInputs(this.el).filter((el) => el.name === name);
3690
+ dispatchUploads(targetCtx, name, filesOrBlobs) {
3691
+ let targetElement = this.targetCtxElement(targetCtx) || this.el;
3692
+ let inputs = dom_default.findUploadInputs(targetElement).filter((el) => el.name === name);
3568
3693
  if (inputs.length === 0) {
3569
3694
  logError(`no live file inputs found matching the name "${name}"`);
3570
3695
  } else if (inputs.length > 1) {
@@ -3573,6 +3698,16 @@ var View = class {
3573
3698
  dom_default.dispatchEvent(inputs[0], PHX_TRACK_UPLOADS, { detail: { files: filesOrBlobs } });
3574
3699
  }
3575
3700
  }
3701
+ targetCtxElement(targetCtx) {
3702
+ if (isCid(targetCtx)) {
3703
+ let [target] = dom_default.findComponentNodeList(this.el, targetCtx);
3704
+ return target;
3705
+ } else if (targetCtx) {
3706
+ return targetCtx;
3707
+ } else {
3708
+ return null;
3709
+ }
3710
+ }
3576
3711
  pushFormRecovery(form, newCid, callback) {
3577
3712
  this.liveSocket.withinOwners(form, (view, targetCtx) => {
3578
3713
  let phxChange = this.binding("change");
@@ -4079,7 +4214,7 @@ var LiveSocket = class {
4079
4214
  if (!dead) {
4080
4215
  this.bindForms();
4081
4216
  }
4082
- this.bind({ keyup: "keyup", keydown: "keydown" }, (e, type, view, targetEl, phxEvent, eventTarget) => {
4217
+ this.bind({ keyup: "keyup", keydown: "keydown" }, (e, type, view, targetEl, phxEvent, phxTarget) => {
4083
4218
  let matchKey = targetEl.getAttribute(this.binding(PHX_KEY));
4084
4219
  let pressedKey = e.key && e.key.toLowerCase();
4085
4220
  if (matchKey && matchKey.toLowerCase() !== pressedKey) {
@@ -4088,13 +4223,13 @@ var LiveSocket = class {
4088
4223
  let data = { key: e.key, ...this.eventMeta(type, e, targetEl) };
4089
4224
  js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
4090
4225
  });
4091
- this.bind({ blur: "focusout", focus: "focusin" }, (e, type, view, targetEl, phxEvent, eventTarget) => {
4092
- if (!eventTarget) {
4226
+ this.bind({ blur: "focusout", focus: "focusin" }, (e, type, view, targetEl, phxEvent, phxTarget) => {
4227
+ if (!phxTarget) {
4093
4228
  let data = { key: e.key, ...this.eventMeta(type, e, targetEl) };
4094
4229
  js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
4095
4230
  }
4096
4231
  });
4097
- this.bind({ blur: "blur", focus: "focus" }, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {
4232
+ this.bind({ blur: "blur", focus: "focus" }, (e, type, view, targetEl, phxEvent, phxTarget) => {
4098
4233
  if (phxTarget === "window") {
4099
4234
  let data = this.eventMeta(type, e, targetEl);
4100
4235
  js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
@@ -4175,7 +4310,7 @@ var LiveSocket = class {
4175
4310
  }
4176
4311
  }
4177
4312
  bindClicks() {
4178
- window.addEventListener("click", (e) => this.clickStartedAtTarget = e.target);
4313
+ window.addEventListener("mousedown", (e) => this.clickStartedAtTarget = e.target);
4179
4314
  this.bindClick("click", "click", false);
4180
4315
  this.bindClick("mousedown", "capture-click", true);
4181
4316
  }
@@ -4217,7 +4352,7 @@ var LiveSocket = class {
4217
4352
  if (!(el.isSameNode(clickStartedAt) || el.contains(clickStartedAt))) {
4218
4353
  this.withinOwners(e.target, (view) => {
4219
4354
  let phxEvent = el.getAttribute(phxClickAway);
4220
- if (js_default.isVisible(el)) {
4355
+ if (js_default.isVisible(el) && js_default.isInViewport(el)) {
4221
4356
  js_default.exec("click", phxEvent, view, el, ["push", { data: this.eventMeta("click", e, e.target) }]);
4222
4357
  }
4223
4358
  });
@@ -4266,7 +4401,7 @@ var LiveSocket = class {
4266
4401
  if (!type || !this.isConnected() || !this.main || dom_default.wantsNewTab(e)) {
4267
4402
  return;
4268
4403
  }
4269
- let href = target.href;
4404
+ let href = target.href instanceof SVGAnimatedString ? target.href.baseVal : target.href;
4270
4405
  let linkState = target.getAttribute(PHX_LINK_STATE);
4271
4406
  e.preventDefault();
4272
4407
  e.stopImmediatePropagation();