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.
@@ -40,6 +40,7 @@ var PHX_DROP_TARGET = "drop-target";
40
40
  var PHX_ACTIVE_ENTRY_REFS = "data-phx-active-refs";
41
41
  var PHX_LIVE_FILE_UPDATED = "phx:live-file:updated";
42
42
  var PHX_SKIP = "data-phx-skip";
43
+ var PHX_MAGIC_ID = "data-phx-id";
43
44
  var PHX_PRUNE = "data-phx-prune";
44
45
  var PHX_PAGE_LOADING = "page-loading";
45
46
  var PHX_CONNECTED_CLASS = "phx-connected";
@@ -94,6 +95,7 @@ var DEFAULTS = {
94
95
  };
95
96
  var DYNAMICS = "d";
96
97
  var STATIC = "s";
98
+ var ROOT = "r";
97
99
  var COMPONENTS = "c";
98
100
  var EVENTS = "e";
99
101
  var REPLY = "r";
@@ -116,6 +118,7 @@ var EntryUploader = class {
116
118
  if (this.errored) {
117
119
  return;
118
120
  }
121
+ this.uploadChannel.leave();
119
122
  this.errored = true;
120
123
  clearTimeout(this.chunkTimer);
121
124
  this.entry.error(reason);
@@ -535,12 +538,17 @@ var DOM = {
535
538
  el.setAttribute("data-phx-hook", "Phoenix.InfiniteScroll");
536
539
  }
537
540
  },
538
- maybeHideFeedback(container, input, phxFeedbackFor) {
539
- if (!(this.private(input, PHX_HAS_FOCUSED) || this.private(input, PHX_HAS_SUBMITTED))) {
540
- let feedbacks = [input.name];
541
- if (input.name.endsWith("[]")) {
542
- feedbacks.push(input.name.slice(0, -2));
541
+ maybeHideFeedback(container, inputs, phxFeedbackFor) {
542
+ 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));
548
+ }
543
549
  }
550
+ });
551
+ if (feedbacks.length > 0) {
544
552
  let selector = feedbacks.map((f) => `[${phxFeedbackFor}="${f}"]`).join(", ");
545
553
  DOM.all(container, selector, (el) => el.classList.add(PHX_NO_FEEDBACK_CLASS));
546
554
  }
@@ -827,7 +835,8 @@ var UploadEntry = class {
827
835
  relative_path: this.file.webkitRelativePath,
828
836
  size: this.file.size,
829
837
  type: this.file.type,
830
- ref: this.ref
838
+ ref: this.ref,
839
+ meta: typeof this.file.meta === "function" ? this.file.meta() : void 0
831
840
  };
832
841
  }
833
842
  uploader(uploaders) {
@@ -884,6 +893,9 @@ var LiveUploader = class {
884
893
  entry.relative_path = file.webkitRelativePath;
885
894
  entry.type = file.type;
886
895
  entry.size = file.size;
896
+ if (typeof file.meta === "function") {
897
+ entry.meta = file.meta();
898
+ }
887
899
  fileData[uploadRef].push(entry);
888
900
  });
889
901
  return fileData;
@@ -973,7 +985,7 @@ var ARIA = {
973
985
  return classes.find((name) => instance instanceof name);
974
986
  },
975
987
  isFocusable(el, interactiveOnly) {
976
- 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");
988
+ 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");
977
989
  },
978
990
  attemptFocus(el, interactiveOnly) {
979
991
  if (this.isFocusable(el, interactiveOnly)) {
@@ -1549,7 +1561,7 @@ function morphdomFactory(morphAttrs2) {
1549
1561
  }
1550
1562
  }
1551
1563
  function morphChildren(fromEl, toEl) {
1552
- var skipFrom = skipFromChildren(fromEl);
1564
+ var skipFrom = skipFromChildren(fromEl, toEl);
1553
1565
  var curToNodeChild = toEl.firstChild;
1554
1566
  var curFromNodeChild = fromEl.firstChild;
1555
1567
  var curToNodeKey;
@@ -1752,7 +1764,7 @@ var DOMPatch = class {
1752
1764
  el.setAttribute(PHX_PRUNE, "");
1753
1765
  });
1754
1766
  }
1755
- perform() {
1767
+ perform(isJoinPatch) {
1756
1768
  let { view, liveSocket, container, html } = this;
1757
1769
  let targetContainer = this.isCIDPatch() ? this.targetCIDContainer(html) : container;
1758
1770
  if (this.isCIDPatch() && !targetContainer) {
@@ -1771,19 +1783,18 @@ var DOMPatch = class {
1771
1783
  let updates = [];
1772
1784
  let appendPrependUpdates = [];
1773
1785
  let externalFormTriggered = null;
1774
- let diffHTML = liveSocket.time("premorph container prep", () => {
1775
- return this.buildDiffHTML(container, html, phxUpdate, targetContainer);
1776
- });
1777
1786
  this.trackBefore("added", container);
1778
1787
  this.trackBefore("updated", container, container);
1779
1788
  liveSocket.time("morphdom", () => {
1780
1789
  this.streams.forEach(([ref, inserts, deleteIds, reset]) => {
1781
1790
  Object.entries(inserts).forEach(([key, [streamAt, limit]]) => {
1782
- this.streamInserts[key] = { ref, streamAt, limit };
1791
+ this.streamInserts[key] = { ref, streamAt, limit, resetKept: false };
1783
1792
  });
1784
1793
  if (reset !== void 0) {
1785
1794
  dom_default.all(container, `[${PHX_STREAM_REF}="${ref}"]`, (child) => {
1786
- if (!inserts[child.id]) {
1795
+ if (inserts[child.id]) {
1796
+ this.streamInserts[child.id].resetKept = true;
1797
+ } else {
1787
1798
  this.removeStreamChildElement(child);
1788
1799
  }
1789
1800
  });
@@ -1795,10 +1806,16 @@ var DOMPatch = class {
1795
1806
  }
1796
1807
  });
1797
1808
  });
1798
- morphdom_esm_default(targetContainer, diffHTML, {
1809
+ morphdom_esm_default(targetContainer, html, {
1799
1810
  childrenOnly: targetContainer.getAttribute(PHX_COMPONENT) === null,
1800
1811
  getNodeKey: (node) => {
1801
- return dom_default.isPhxDestroyed(node) ? null : node.id;
1812
+ if (dom_default.isPhxDestroyed(node)) {
1813
+ return null;
1814
+ }
1815
+ if (isJoinPatch) {
1816
+ return node.id;
1817
+ }
1818
+ return node.id || node.getAttribute && node.getAttribute(PHX_MAGIC_ID);
1802
1819
  },
1803
1820
  skipFromChildren: (from) => {
1804
1821
  return from.getAttribute(phxUpdate) === PHX_STREAM;
@@ -1855,6 +1872,25 @@ var DOMPatch = class {
1855
1872
  }
1856
1873
  added.push(el);
1857
1874
  },
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
+ },
1858
1894
  onNodeDiscarded: (el) => this.onNodeDiscarded(el),
1859
1895
  onBeforeNodeDiscarded: (el) => {
1860
1896
  if (el.getAttribute && el.getAttribute(PHX_PRUNE) !== null) {
@@ -1948,9 +1984,7 @@ var DOMPatch = class {
1948
1984
  appendPrependUpdates.forEach((update) => update.perform());
1949
1985
  });
1950
1986
  }
1951
- trackedInputs.forEach((input) => {
1952
- dom_default.maybeHideFeedback(targetContainer, input, phxFeedbackFor);
1953
- });
1987
+ dom_default.maybeHideFeedback(targetContainer, trackedInputs, phxFeedbackFor);
1954
1988
  liveSocket.silenceEvents(() => dom_default.restoreFocus(focused, selectionStart, selectionEnd));
1955
1989
  dom_default.dispatchEvent(document, "phx:update");
1956
1990
  added.forEach((el) => this.trackAfter("added", el));
@@ -2029,7 +2063,7 @@ var DOMPatch = class {
2029
2063
  return this.cidPatch;
2030
2064
  }
2031
2065
  skipCIDSibling(el) {
2032
- return el.nodeType === Node.ELEMENT_NODE && el.getAttribute(PHX_SKIP) !== null;
2066
+ return el.nodeType === Node.ELEMENT_NODE && el.hasAttribute(PHX_SKIP);
2033
2067
  }
2034
2068
  targetCIDContainer(html) {
2035
2069
  if (!this.isCIDPatch()) {
@@ -2042,35 +2076,125 @@ var DOMPatch = class {
2042
2076
  return first && first.parentNode;
2043
2077
  }
2044
2078
  }
2045
- buildDiffHTML(container, html, phxUpdate, targetContainer) {
2046
- let isCIDPatch = this.isCIDPatch();
2047
- let isCIDWithSingleRoot = isCIDPatch && targetContainer.getAttribute(PHX_COMPONENT) === this.targetCID.toString();
2048
- if (!isCIDPatch || isCIDWithSingleRoot) {
2049
- return html;
2050
- } else {
2051
- let diffContainer = null;
2052
- let template = document.createElement("template");
2053
- diffContainer = dom_default.cloneNode(targetContainer);
2054
- let [firstComponent, ...rest] = dom_default.findComponentNodeList(diffContainer, this.targetCID);
2055
- template.innerHTML = html;
2056
- rest.forEach((el) => el.remove());
2057
- Array.from(diffContainer.childNodes).forEach((child) => {
2058
- if (child.id && child.nodeType === Node.ELEMENT_NODE && child.getAttribute(PHX_COMPONENT) !== this.targetCID.toString()) {
2059
- child.setAttribute(PHX_SKIP, "");
2060
- child.innerHTML = "";
2061
- }
2062
- });
2063
- Array.from(template.content.childNodes).forEach((el) => diffContainer.insertBefore(el, firstComponent));
2064
- firstComponent.remove();
2065
- return diffContainer.outerHTML;
2066
- }
2067
- }
2068
2079
  indexOf(parent, child) {
2069
2080
  return Array.from(parent.children).indexOf(child);
2070
2081
  }
2071
2082
  };
2072
2083
 
2073
2084
  // js/phoenix_live_view/rendered.js
2085
+ var VOID_TAGS = new Set([
2086
+ "area",
2087
+ "base",
2088
+ "br",
2089
+ "col",
2090
+ "command",
2091
+ "embed",
2092
+ "hr",
2093
+ "img",
2094
+ "input",
2095
+ "keygen",
2096
+ "link",
2097
+ "meta",
2098
+ "param",
2099
+ "source",
2100
+ "track",
2101
+ "wbr"
2102
+ ]);
2103
+ var endingTagNameChars = new Set([">", "/", " ", "\n", " ", "\r"]);
2104
+ var quoteChars = new Set(["'", '"']);
2105
+ var modifyRoot = (html, attrs, clearInnerHTML) => {
2106
+ let i = 0;
2107
+ let insideComment = false;
2108
+ 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;
2124
+ i++;
2125
+ for (i; i < html.length; i++) {
2126
+ if (endingTagNameChars.has(html.charAt(i))) {
2127
+ break;
2128
+ }
2129
+ }
2130
+ tagNameEndsAt = i;
2131
+ tag = html.slice(iAtOpen + 1, tagNameEndsAt);
2132
+ for (i; i < html.length; i++) {
2133
+ if (html.charAt(i) === ">") {
2134
+ break;
2135
+ }
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
+ }
2155
+ break;
2156
+ } else {
2157
+ i++;
2158
+ }
2159
+ }
2160
+ if (!tag) {
2161
+ throw new Error(`malformed html ${html}`);
2162
+ }
2163
+ let closeAt = html.length - 1;
2164
+ insideComment = false;
2165
+ while (closeAt >= beforeTag.length + tag.length) {
2166
+ let char = html.charAt(closeAt);
2167
+ if (insideComment) {
2168
+ if (char === "-" && html.slice(closeAt - 3, closeAt) === "<!-") {
2169
+ insideComment = false;
2170
+ closeAt -= 4;
2171
+ } else {
2172
+ closeAt -= 1;
2173
+ }
2174
+ } else if (char === ">" && html.slice(closeAt - 2, closeAt) === "--") {
2175
+ insideComment = true;
2176
+ closeAt -= 3;
2177
+ } else if (char === ">") {
2178
+ break;
2179
+ } else {
2180
+ closeAt -= 1;
2181
+ }
2182
+ }
2183
+ afterTag = html.slice(closeAt + 1, html.length);
2184
+ let attrsStr = Object.keys(attrs).map((attr) => attrs[attr] === true ? attr : `${attr}="${attrs[attr]}"`).join(" ");
2185
+ if (clearInnerHTML) {
2186
+ let idAttrStr = id ? ` id="${id}"` : "";
2187
+ if (VOID_TAGS.has(tag)) {
2188
+ newHTML = `<${tag}${idAttrStr}${attrsStr === "" ? "" : " "}${attrsStr}/>`;
2189
+ } else {
2190
+ newHTML = `<${tag}${idAttrStr}${attrsStr === "" ? "" : " "}${attrsStr}></${tag}>`;
2191
+ }
2192
+ } else {
2193
+ let rest = html.slice(tagNameEndsAt, closeAt + 1);
2194
+ newHTML = `<${tag}${attrsStr === "" ? "" : " "}${attrsStr}${rest}`;
2195
+ }
2196
+ return [newHTML, beforeTag, afterTag];
2197
+ };
2074
2198
  var Rendered = class {
2075
2199
  static extract(diff) {
2076
2200
  let { [REPLY]: reply, [EVENTS]: events, [TITLE]: title } = diff;
@@ -2082,19 +2206,20 @@ var Rendered = class {
2082
2206
  constructor(viewId, rendered) {
2083
2207
  this.viewId = viewId;
2084
2208
  this.rendered = {};
2209
+ this.magicId = 0;
2085
2210
  this.mergeDiff(rendered);
2086
2211
  }
2087
2212
  parentViewId() {
2088
2213
  return this.viewId;
2089
2214
  }
2090
2215
  toString(onlyCids) {
2091
- let [str, streams] = this.recursiveToString(this.rendered, this.rendered[COMPONENTS], onlyCids);
2216
+ let [str, streams] = this.recursiveToString(this.rendered, this.rendered[COMPONENTS], onlyCids, true, {});
2092
2217
  return [str, streams];
2093
2218
  }
2094
- recursiveToString(rendered, components = rendered[COMPONENTS], onlyCids) {
2219
+ recursiveToString(rendered, components = rendered[COMPONENTS], onlyCids, changeTracking, rootAttrs) {
2095
2220
  onlyCids = onlyCids ? new Set(onlyCids) : null;
2096
2221
  let output = { buffer: "", components, onlyCids, streams: new Set() };
2097
- this.toOutputBuffer(rendered, null, output);
2222
+ this.toOutputBuffer(rendered, null, output, changeTracking, rootAttrs);
2098
2223
  return [output.buffer, output.streams];
2099
2224
  }
2100
2225
  componentCIDs(diff) {
@@ -2139,10 +2264,10 @@ var Rendered = class {
2139
2264
  tdiff = oldc[-scid];
2140
2265
  }
2141
2266
  stat = tdiff[STATIC];
2142
- ndiff = this.cloneMerge(tdiff, cdiff);
2267
+ ndiff = this.cloneMerge(tdiff, cdiff, true);
2143
2268
  ndiff[STATIC] = stat;
2144
2269
  } else {
2145
- ndiff = cdiff[STATIC] !== void 0 ? cdiff : this.cloneMerge(oldc[cid] || {}, cdiff);
2270
+ ndiff = cdiff[STATIC] !== void 0 || oldc[cid] === void 0 ? cdiff : this.cloneMerge(oldc[cid], cdiff, false);
2146
2271
  }
2147
2272
  cache[cid] = ndiff;
2148
2273
  return ndiff;
@@ -2167,21 +2292,31 @@ var Rendered = class {
2167
2292
  target[key] = val;
2168
2293
  }
2169
2294
  }
2295
+ if (target[ROOT]) {
2296
+ target.newRender = true;
2297
+ }
2170
2298
  }
2171
- cloneMerge(target, source) {
2299
+ cloneMerge(target, source, pruneMagicId) {
2172
2300
  let merged = { ...target, ...source };
2173
2301
  for (let key in merged) {
2174
2302
  let val = source[key];
2175
2303
  let targetVal = target[key];
2176
2304
  if (isObject(val) && val[STATIC] === void 0 && isObject(targetVal)) {
2177
- merged[key] = this.cloneMerge(targetVal, val);
2305
+ merged[key] = this.cloneMerge(targetVal, val, pruneMagicId);
2178
2306
  }
2179
2307
  }
2308
+ if (pruneMagicId) {
2309
+ delete merged.magicId;
2310
+ delete merged.newRender;
2311
+ } else if (target[ROOT]) {
2312
+ merged.newRender = true;
2313
+ }
2180
2314
  return merged;
2181
2315
  }
2182
2316
  componentToString(cid) {
2183
- let [str, streams] = this.recursiveCIDToString(this.rendered[COMPONENTS], cid, null, false);
2184
- return [str, streams];
2317
+ let [str, streams] = this.recursiveCIDToString(this.rendered[COMPONENTS], cid, null);
2318
+ let [strippedHTML, _before, _after] = modifyRoot(str, {});
2319
+ return [strippedHTML, streams];
2185
2320
  }
2186
2321
  pruneCIDs(cids) {
2187
2322
  cids.forEach((cid) => delete this.rendered[COMPONENTS][cid]);
@@ -2199,17 +2334,46 @@ var Rendered = class {
2199
2334
  return part;
2200
2335
  }
2201
2336
  }
2202
- toOutputBuffer(rendered, templates, output) {
2337
+ nextMagicID() {
2338
+ this.magicId++;
2339
+ return `${this.parentViewId()}-${this.magicId}`;
2340
+ }
2341
+ toOutputBuffer(rendered, templates, output, changeTracking, rootAttrs = {}) {
2203
2342
  if (rendered[DYNAMICS]) {
2204
2343
  return this.comprehensionToBuffer(rendered, templates, output);
2205
2344
  }
2206
2345
  let { [STATIC]: statics } = rendered;
2207
2346
  statics = this.templateStatic(statics, templates);
2347
+ let isRoot = rendered[ROOT];
2348
+ let prevBuffer = output.buffer;
2349
+ if (isRoot) {
2350
+ output.buffer = "";
2351
+ }
2352
+ if (changeTracking && isRoot && !rendered.magicId) {
2353
+ rendered.newRender = true;
2354
+ rendered.magicId = this.nextMagicID();
2355
+ }
2208
2356
  output.buffer += statics[0];
2209
2357
  for (let i = 1; i < statics.length; i++) {
2210
- this.dynamicToBuffer(rendered[i - 1], templates, output);
2358
+ this.dynamicToBuffer(rendered[i - 1], templates, output, changeTracking);
2211
2359
  output.buffer += statics[i];
2212
2360
  }
2361
+ if (isRoot) {
2362
+ let skip = false;
2363
+ let attrs;
2364
+ if (changeTracking || Object.keys(rootAttrs).length > 0) {
2365
+ skip = !rendered.newRender;
2366
+ attrs = { [PHX_MAGIC_ID]: rendered.magicId, ...rootAttrs };
2367
+ } else {
2368
+ attrs = rootAttrs;
2369
+ }
2370
+ if (skip) {
2371
+ attrs[PHX_SKIP] = true;
2372
+ }
2373
+ let [newRoot, commentBefore, commentAfter] = modifyRoot(output.buffer, attrs, skip);
2374
+ rendered.newRender = false;
2375
+ output.buffer = prevBuffer + commentBefore + newRoot + commentAfter;
2376
+ }
2213
2377
  }
2214
2378
  comprehensionToBuffer(rendered, templates, output) {
2215
2379
  let { [DYNAMICS]: dynamics, [STATIC]: statics, [STREAM]: stream } = rendered;
@@ -2220,7 +2384,8 @@ var Rendered = class {
2220
2384
  let dynamic = dynamics[d];
2221
2385
  output.buffer += statics[0];
2222
2386
  for (let i = 1; i < statics.length; i++) {
2223
- this.dynamicToBuffer(dynamic[i - 1], compTemplates, output);
2387
+ let changeTracking = false;
2388
+ this.dynamicToBuffer(dynamic[i - 1], compTemplates, output, changeTracking);
2224
2389
  output.buffer += statics[i];
2225
2390
  }
2226
2391
  }
@@ -2230,74 +2395,26 @@ var Rendered = class {
2230
2395
  output.streams.add(stream);
2231
2396
  }
2232
2397
  }
2233
- dynamicToBuffer(rendered, templates, output) {
2398
+ dynamicToBuffer(rendered, templates, output, changeTracking) {
2234
2399
  if (typeof rendered === "number") {
2235
2400
  let [str, streams] = this.recursiveCIDToString(output.components, rendered, output.onlyCids);
2236
2401
  output.buffer += str;
2237
2402
  output.streams = new Set([...output.streams, ...streams]);
2238
2403
  } else if (isObject(rendered)) {
2239
- this.toOutputBuffer(rendered, templates, output);
2404
+ this.toOutputBuffer(rendered, templates, output, changeTracking, {});
2240
2405
  } else {
2241
2406
  output.buffer += rendered;
2242
2407
  }
2243
2408
  }
2244
- recursiveCIDToString(components, cid, onlyCids, allowRootComments = true) {
2409
+ recursiveCIDToString(components, cid, onlyCids) {
2245
2410
  let component = components[cid] || logError(`no component for CID ${cid}`, components);
2246
- let template = document.createElement("template");
2247
- let [html, streams] = this.recursiveToString(component, components, onlyCids);
2248
- template.innerHTML = html;
2249
- let container = template.content;
2411
+ let attrs = { [PHX_COMPONENT]: cid };
2250
2412
  let skip = onlyCids && !onlyCids.has(cid);
2251
- let [hasChildNodes, hasChildComponents] = Array.from(container.childNodes).reduce(([hasNodes, hasComponents], child, i) => {
2252
- if (child.nodeType === Node.ELEMENT_NODE) {
2253
- if (child.getAttribute(PHX_COMPONENT)) {
2254
- return [hasNodes, true];
2255
- }
2256
- child.setAttribute(PHX_COMPONENT, cid);
2257
- if (!child.id) {
2258
- child.id = `${this.parentViewId()}-${cid}-${i}`;
2259
- }
2260
- if (skip) {
2261
- child.setAttribute(PHX_SKIP, "");
2262
- child.innerHTML = "";
2263
- }
2264
- return [true, hasComponents];
2265
- } else if (child.nodeType === Node.COMMENT_NODE) {
2266
- if (!allowRootComments) {
2267
- child.remove();
2268
- }
2269
- return [hasNodes, hasComponents];
2270
- } else {
2271
- if (child.nodeValue.trim() !== "") {
2272
- logError(`only HTML element tags are allowed at the root of components.
2273
-
2274
- got: "${child.nodeValue.trim()}"
2275
-
2276
- within:
2277
- `, template.innerHTML.trim());
2278
- child.replaceWith(this.createSpan(child.nodeValue, cid));
2279
- return [true, hasComponents];
2280
- } else {
2281
- child.remove();
2282
- return [hasNodes, hasComponents];
2283
- }
2284
- }
2285
- }, [false, false]);
2286
- if (!hasChildNodes && !hasChildComponents) {
2287
- logError("expected at least one HTML element tag inside a component, but the component is empty:\n", template.innerHTML.trim());
2288
- return [this.createSpan("", cid).outerHTML, streams];
2289
- } else if (!hasChildNodes && hasChildComponents) {
2290
- 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());
2291
- return [template.innerHTML, streams];
2292
- } else {
2293
- return [template.innerHTML, streams];
2294
- }
2295
- }
2296
- createSpan(text, cid) {
2297
- let span = document.createElement("span");
2298
- span.innerText = text;
2299
- span.setAttribute(PHX_COMPONENT, cid);
2300
- return span;
2413
+ component.newRender = !skip;
2414
+ component.magicId = `${this.parentViewId()}-c-${cid}`;
2415
+ let changeTracking = true;
2416
+ let [html, streams] = this.recursiveToString(component, components, onlyCids, changeTracking, attrs);
2417
+ return [html, streams];
2301
2418
  }
2302
2419
  };
2303
2420
 
@@ -2366,10 +2483,12 @@ var ViewHook = class {
2366
2483
  this.__listeners.delete(callbackRef);
2367
2484
  }
2368
2485
  upload(name, files) {
2369
- return this.__view.dispatchUploads(name, files);
2486
+ return this.__view.dispatchUploads(null, name, files);
2370
2487
  }
2371
2488
  uploadTo(phxTarget, name, files) {
2372
- return this.__view.withinTargets(phxTarget, (view) => view.dispatchUploads(name, files));
2489
+ return this.__view.withinTargets(phxTarget, (view, targetCtx) => {
2490
+ view.dispatchUploads(targetCtx, name, files);
2491
+ });
2373
2492
  }
2374
2493
  __cleanup__() {
2375
2494
  this.__listeners.forEach((callbackRef) => this.removeHandleEvent(callbackRef));
@@ -2395,6 +2514,10 @@ var JS = {
2395
2514
  isVisible(el) {
2396
2515
  return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length > 0);
2397
2516
  },
2517
+ isInViewport(el) {
2518
+ const rect = el.getBoundingClientRect();
2519
+ return rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
2520
+ },
2398
2521
  exec_exec(eventType, phxEvent, view, sourceEl, el, [attr, to]) {
2399
2522
  let nodes = to ? dom_default.all(document, to) : [sourceEl];
2400
2523
  nodes.forEach((node) => {
@@ -2653,9 +2776,10 @@ var View = class {
2653
2776
  this.children = this.parent ? null : {};
2654
2777
  this.root.children[this.id] = {};
2655
2778
  this.channel = this.liveSocket.channel(`lv:${this.id}`, () => {
2779
+ let url = this.href && this.expandURL(this.href);
2656
2780
  return {
2657
- redirect: this.redirect ? this.href : void 0,
2658
- url: this.redirect ? void 0 : this.href || void 0,
2781
+ redirect: this.redirect ? url : void 0,
2782
+ url: this.redirect ? void 0 : url || void 0,
2659
2783
  params: this.connectParams(liveReferer),
2660
2784
  session: this.getSession(),
2661
2785
  static: this.getStatic(),
@@ -2853,7 +2977,7 @@ var View = class {
2853
2977
  this.attachTrueDocEl();
2854
2978
  let patch = new DOMPatch(this, this.el, this.id, html, streams, null);
2855
2979
  patch.markPrunableContentForRemoval();
2856
- this.performPatch(patch, false);
2980
+ this.performPatch(patch, false, true);
2857
2981
  this.joinNewChildren();
2858
2982
  this.execNewMounted();
2859
2983
  this.joinPending = false;
@@ -2892,7 +3016,7 @@ var View = class {
2892
3016
  newHook.__mounted();
2893
3017
  }
2894
3018
  }
2895
- performPatch(patch, pruneCids) {
3019
+ performPatch(patch, pruneCids, isJoinPatch = false) {
2896
3020
  let removedEls = [];
2897
3021
  let phxChildrenAdded = false;
2898
3022
  let updatedHookIds = new Set();
@@ -2927,7 +3051,7 @@ var View = class {
2927
3051
  }
2928
3052
  });
2929
3053
  patch.after("transitionsDiscarded", (els) => this.afterElementsRemoved(els, pruneCids));
2930
- patch.perform();
3054
+ patch.perform(isJoinPatch);
2931
3055
  this.afterElementsRemoved(removedEls, pruneCids);
2932
3056
  return phxChildrenAdded;
2933
3057
  }
@@ -3332,7 +3456,7 @@ var View = class {
3332
3456
  if (isCid(targetCtx)) {
3333
3457
  return targetCtx;
3334
3458
  }
3335
- let cidOrSelector = target.getAttribute(this.binding("target"));
3459
+ let cidOrSelector = opts.target || target.getAttribute(this.binding("target"));
3336
3460
  if (isCid(cidOrSelector)) {
3337
3461
  return parseInt(cidOrSelector);
3338
3462
  } else if (targetCtx && (cidOrSelector !== null || opts.target)) {
@@ -3416,7 +3540,7 @@ var View = class {
3416
3540
  }
3417
3541
  pushInput(inputEl, targetCtx, forceCid, phxEvent, opts, callback) {
3418
3542
  let uploads;
3419
- let cid = isCid(forceCid) ? forceCid : this.targetComponentID(inputEl.form, targetCtx);
3543
+ let cid = isCid(forceCid) ? forceCid : this.targetComponentID(inputEl.form, targetCtx, opts);
3420
3544
  let refGenerator = () => this.putRef([inputEl, inputEl.form], "change", opts);
3421
3545
  let formData;
3422
3546
  let meta = this.extractMeta(inputEl.form);
@@ -3512,7 +3636,7 @@ var View = class {
3512
3636
  let cid = this.targetComponentID(formEl, targetCtx);
3513
3637
  if (LiveUploader.hasUploadsInProgress(formEl)) {
3514
3638
  let [ref, _els] = refGenerator();
3515
- let push = () => this.pushFormSubmit(formEl, submitter, targetCtx, phxEvent, opts, onReply);
3639
+ let push = () => this.pushFormSubmit(formEl, targetCtx, phxEvent, submitter, opts, onReply);
3516
3640
  return this.scheduleSubmit(formEl, ref, opts, push);
3517
3641
  } else if (LiveUploader.inputsAwaitingPreflight(formEl).length > 0) {
3518
3642
  let [ref, els] = refGenerator();
@@ -3576,8 +3700,9 @@ var View = class {
3576
3700
  });
3577
3701
  });
3578
3702
  }
3579
- dispatchUploads(name, filesOrBlobs) {
3580
- let inputs = dom_default.findUploadInputs(this.el).filter((el) => el.name === name);
3703
+ dispatchUploads(targetCtx, name, filesOrBlobs) {
3704
+ let targetElement = this.targetCtxElement(targetCtx) || this.el;
3705
+ let inputs = dom_default.findUploadInputs(targetElement).filter((el) => el.name === name);
3581
3706
  if (inputs.length === 0) {
3582
3707
  logError(`no live file inputs found matching the name "${name}"`);
3583
3708
  } else if (inputs.length > 1) {
@@ -3586,6 +3711,16 @@ var View = class {
3586
3711
  dom_default.dispatchEvent(inputs[0], PHX_TRACK_UPLOADS, { detail: { files: filesOrBlobs } });
3587
3712
  }
3588
3713
  }
3714
+ targetCtxElement(targetCtx) {
3715
+ if (isCid(targetCtx)) {
3716
+ let [target] = dom_default.findComponentNodeList(this.el, targetCtx);
3717
+ return target;
3718
+ } else if (targetCtx) {
3719
+ return targetCtx;
3720
+ } else {
3721
+ return null;
3722
+ }
3723
+ }
3589
3724
  pushFormRecovery(form, newCid, callback) {
3590
3725
  this.liveSocket.withinOwners(form, (view, targetCtx) => {
3591
3726
  let phxChange = this.binding("change");
@@ -4092,7 +4227,7 @@ var LiveSocket = class {
4092
4227
  if (!dead) {
4093
4228
  this.bindForms();
4094
4229
  }
4095
- this.bind({ keyup: "keyup", keydown: "keydown" }, (e, type, view, targetEl, phxEvent, eventTarget) => {
4230
+ this.bind({ keyup: "keyup", keydown: "keydown" }, (e, type, view, targetEl, phxEvent, phxTarget) => {
4096
4231
  let matchKey = targetEl.getAttribute(this.binding(PHX_KEY));
4097
4232
  let pressedKey = e.key && e.key.toLowerCase();
4098
4233
  if (matchKey && matchKey.toLowerCase() !== pressedKey) {
@@ -4101,13 +4236,13 @@ var LiveSocket = class {
4101
4236
  let data = { key: e.key, ...this.eventMeta(type, e, targetEl) };
4102
4237
  js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
4103
4238
  });
4104
- this.bind({ blur: "focusout", focus: "focusin" }, (e, type, view, targetEl, phxEvent, eventTarget) => {
4105
- if (!eventTarget) {
4239
+ this.bind({ blur: "focusout", focus: "focusin" }, (e, type, view, targetEl, phxEvent, phxTarget) => {
4240
+ if (!phxTarget) {
4106
4241
  let data = { key: e.key, ...this.eventMeta(type, e, targetEl) };
4107
4242
  js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
4108
4243
  }
4109
4244
  });
4110
- this.bind({ blur: "blur", focus: "focus" }, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {
4245
+ this.bind({ blur: "blur", focus: "focus" }, (e, type, view, targetEl, phxEvent, phxTarget) => {
4111
4246
  if (phxTarget === "window") {
4112
4247
  let data = this.eventMeta(type, e, targetEl);
4113
4248
  js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
@@ -4188,7 +4323,7 @@ var LiveSocket = class {
4188
4323
  }
4189
4324
  }
4190
4325
  bindClicks() {
4191
- window.addEventListener("click", (e) => this.clickStartedAtTarget = e.target);
4326
+ window.addEventListener("mousedown", (e) => this.clickStartedAtTarget = e.target);
4192
4327
  this.bindClick("click", "click", false);
4193
4328
  this.bindClick("mousedown", "capture-click", true);
4194
4329
  }
@@ -4230,7 +4365,7 @@ var LiveSocket = class {
4230
4365
  if (!(el.isSameNode(clickStartedAt) || el.contains(clickStartedAt))) {
4231
4366
  this.withinOwners(e.target, (view) => {
4232
4367
  let phxEvent = el.getAttribute(phxClickAway);
4233
- if (js_default.isVisible(el)) {
4368
+ if (js_default.isVisible(el) && js_default.isInViewport(el)) {
4234
4369
  js_default.exec("click", phxEvent, view, el, ["push", { data: this.eventMeta("click", e, e.target) }]);
4235
4370
  }
4236
4371
  });
@@ -4279,7 +4414,7 @@ var LiveSocket = class {
4279
4414
  if (!type || !this.isConnected() || !this.main || dom_default.wantsNewTab(e)) {
4280
4415
  return;
4281
4416
  }
4282
- let href = target.href;
4417
+ let href = target.href instanceof SVGAnimatedString ? target.href.baseVal : target.href;
4283
4418
  let linkState = target.getAttribute(PHX_LINK_STATE);
4284
4419
  e.preventDefault();
4285
4420
  e.stopImmediatePropagation();