phoenix_live_view 1.0.2 → 1.0.3

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.
@@ -221,6 +221,16 @@ var LiveView = (() => {
221
221
  }
222
222
  }
223
223
  }
224
+ function detectInvalidStreamInserts(inserts) {
225
+ const errors = /* @__PURE__ */ new Set();
226
+ Object.keys(inserts).forEach((id) => {
227
+ const streamEl = document.getElementById(id);
228
+ if (streamEl && streamEl.parentElement && streamEl.parentElement.getAttribute("phx-update") !== "stream") {
229
+ errors.add(`The stream container with id "${streamEl.parentElement.id}" is missing the phx-update="stream" attribute. Ensure it is set for streams to work properly.`);
230
+ }
231
+ });
232
+ errors.forEach((error) => console.error(error));
233
+ }
224
234
  var debug = (view, kind, msg, obj) => {
225
235
  if (view.liveSocket.isDebugEnabled()) {
226
236
  console.log(`${view.id} ${kind}: ${msg} - `, obj);
@@ -447,7 +457,7 @@ var LiveView = (() => {
447
457
  cids.forEach((cid) => {
448
458
  this.filterWithinSameLiveView(this.all(node, `[${PHX_COMPONENT}="${cid}"]`), node).forEach((parent) => {
449
459
  parentCids.add(cid);
450
- this.all(parent, `[${PHX_COMPONENT}]`).map((el) => parseInt(el.getAttribute(PHX_COMPONENT))).forEach((childCID) => childrenCids.add(childCID));
460
+ this.filterWithinSameLiveView(this.all(parent, `[${PHX_COMPONENT}]`), parent).map((el) => parseInt(el.getAttribute(PHX_COMPONENT))).forEach((childCID) => childrenCids.add(childCID));
451
461
  });
452
462
  });
453
463
  childrenCids.forEach((childCid) => parentCids.delete(childCid));
@@ -824,6 +834,9 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
824
834
  return;
825
835
  }
826
836
  ops.forEach(([name, op, _stashed]) => this.putSticky(el, name, op));
837
+ },
838
+ isLocked(el) {
839
+ return el.hasAttribute && el.hasAttribute(PHX_REF_LOCK);
827
840
  }
828
841
  };
829
842
  var dom_default = DOM;
@@ -1166,8 +1179,22 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1166
1179
  mounted() {
1167
1180
  this.focusStart = this.el.firstElementChild;
1168
1181
  this.focusEnd = this.el.lastElementChild;
1169
- this.focusStart.addEventListener("focus", () => aria_default.focusLast(this.el));
1170
- this.focusEnd.addEventListener("focus", () => aria_default.focusFirst(this.el));
1182
+ this.focusStart.addEventListener("focus", (e) => {
1183
+ if (!e.relatedTarget || !this.el.contains(e.relatedTarget)) {
1184
+ const nextFocus = e.target.nextElementSibling;
1185
+ aria_default.attemptFocus(nextFocus) || aria_default.focusFirst(nextFocus);
1186
+ } else {
1187
+ aria_default.focusLast(this.el);
1188
+ }
1189
+ });
1190
+ this.focusEnd.addEventListener("focus", (e) => {
1191
+ if (!e.relatedTarget || !this.el.contains(e.relatedTarget)) {
1192
+ const nextFocus = e.target.previousElementSibling;
1193
+ aria_default.attemptFocus(nextFocus) || aria_default.focusLast(nextFocus);
1194
+ } else {
1195
+ aria_default.focusFirst(this.el);
1196
+ }
1197
+ });
1171
1198
  this.el.addEventListener("phx:show-end", () => this.el.focus());
1172
1199
  if (window.getComputedStyle(this.el).display !== "none") {
1173
1200
  aria_default.focusFirst(this.el);
@@ -1316,6 +1343,16 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1316
1343
 
1317
1344
  // js/phoenix_live_view/element_ref.js
1318
1345
  var ElementRef = class {
1346
+ static onUnlock(el, callback) {
1347
+ if (!dom_default.isLocked(el) && !el.closest(`[${PHX_REF_LOCK}]`)) {
1348
+ return callback();
1349
+ }
1350
+ const closestLock = el.closest(`[${PHX_REF_LOCK}]`);
1351
+ const ref = closestLock.closest(`[${PHX_REF_LOCK}]`).getAttribute(PHX_REF_LOCK);
1352
+ closestLock.addEventListener(`phx:undo-lock:${ref}`, () => {
1353
+ callback();
1354
+ }, { once: true });
1355
+ }
1319
1356
  constructor(el) {
1320
1357
  this.el = el;
1321
1358
  this.loadingRef = el.hasAttribute(PHX_REF_LOADING) ? parseInt(el.getAttribute(PHX_REF_LOADING), 10) : null;
@@ -1967,37 +2004,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1967
2004
 
1968
2005
  // js/phoenix_live_view/dom_patch.js
1969
2006
  var DOMPatch = class {
1970
- static patchWithClonedTree(container, clonedTree, liveSocket) {
1971
- let focused = liveSocket.getActiveElement();
1972
- let { selectionStart, selectionEnd } = focused && dom_default.hasSelectionRange(focused) ? focused : {};
1973
- let phxUpdate = liveSocket.binding(PHX_UPDATE);
1974
- let externalFormTriggered = null;
1975
- morphdom_esm_default(container, clonedTree, {
1976
- childrenOnly: false,
1977
- onBeforeElUpdated: (fromEl, toEl) => {
1978
- dom_default.syncPendingAttrs(fromEl, toEl);
1979
- if (!container.isSameNode(fromEl) && fromEl.hasAttribute(PHX_REF_LOCK)) {
1980
- return false;
1981
- }
1982
- if (dom_default.isIgnored(fromEl, phxUpdate)) {
1983
- return false;
1984
- }
1985
- if (focused && focused.isSameNode(fromEl) && dom_default.isFormInput(fromEl)) {
1986
- dom_default.mergeFocusedInput(fromEl, toEl);
1987
- return false;
1988
- }
1989
- if (dom_default.isNowTriggerFormExternal(toEl, liveSocket.binding(PHX_TRIGGER_ACTION))) {
1990
- externalFormTriggered = toEl;
1991
- }
1992
- }
1993
- });
1994
- if (externalFormTriggered) {
1995
- liveSocket.unload();
1996
- Object.getPrototypeOf(externalFormTriggered).submit.call(externalFormTriggered);
1997
- }
1998
- liveSocket.silenceEvents(() => dom_default.restoreFocus(focused, selectionStart, selectionEnd));
1999
- }
2000
- constructor(view, container, id, html, streams, targetCID) {
2007
+ constructor(view, container, id, html, streams, targetCID, opts = {}) {
2001
2008
  this.view = view;
2002
2009
  this.liveSocket = view.liveSocket;
2003
2010
  this.container = container;
@@ -2022,6 +2029,8 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2022
2029
  afterphxChildAdded: [],
2023
2030
  aftertransitionsDiscarded: []
2024
2031
  };
2032
+ this.withChildren = opts.withChildren || opts.undoRef || false;
2033
+ this.undoRef = opts.undoRef;
2025
2034
  }
2026
2035
  before(kind, callback) {
2027
2036
  this.callbacks[`before${kind}`].push(callback);
@@ -2056,7 +2065,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2056
2065
  let updates = [];
2057
2066
  let appendPrependUpdates = [];
2058
2067
  let externalFormTriggered = null;
2059
- function morph(targetContainer2, source, withChildren = false) {
2068
+ function morph(targetContainer2, source, withChildren = this.withChildren) {
2060
2069
  let morphCallbacks = {
2061
2070
  // normally, we are running with childrenOnly, as the patch HTML for a LV
2062
2071
  // does not include the LV attrs (data-phx-session, etc.)
@@ -2182,7 +2191,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2182
2191
  }
2183
2192
  let isFocusedFormEl = focused && fromEl.isSameNode(focused) && dom_default.isFormInput(fromEl);
2184
2193
  let focusedSelectChanged = isFocusedFormEl && this.isChangedSelect(fromEl, toEl);
2185
- if (fromEl.hasAttribute(PHX_REF_SRC)) {
2194
+ if (fromEl.hasAttribute(PHX_REF_SRC) && fromEl.getAttribute(PHX_REF_LOCK) != this.undoRef) {
2186
2195
  if (dom_default.isUploadInput(fromEl)) {
2187
2196
  dom_default.mergeAttrs(fromEl, toEl, { isIgnored: true });
2188
2197
  this.trackBefore("updated", fromEl, toEl);
@@ -2266,6 +2275,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2266
2275
  });
2267
2276
  if (liveSocket.isDebugEnabled()) {
2268
2277
  detectDuplicateIds();
2278
+ detectInvalidStreamInserts(this.streamInserts);
2269
2279
  Array.from(document.querySelectorAll("input[name=id]")).forEach((node) => {
2270
2280
  if (node.form) {
2271
2281
  console.error('Detected an input with name="id" inside a form! This will cause problems when patching the DOM.\n', node);
@@ -3600,7 +3610,11 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3600
3610
  this.formsForRecovery = this.getFormsForRecovery();
3601
3611
  }
3602
3612
  if (this.isMain() && window.history.state === null) {
3603
- this.liveSocket.replaceRootHistory();
3613
+ browser_default.pushState("replace", {
3614
+ type: "patch",
3615
+ id: this.id,
3616
+ position: this.liveSocket.currentHistoryPosition
3617
+ });
3604
3618
  }
3605
3619
  if (liveview_version !== this.liveSocket.version()) {
3606
3620
  console.error(`LiveView asset version mismatch. JavaScript version ${this.liveSocket.version()} vs. server ${liveview_version}. To avoid issues, please ensure that your assets use the same version as the server.`);
@@ -3923,6 +3937,9 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3923
3937
  }
3924
3938
  addHook(el) {
3925
3939
  let hookElId = ViewHook.elementID(el);
3940
+ if (el.getAttribute && !this.ownsElement(el)) {
3941
+ return;
3942
+ }
3926
3943
  if (hookElId && !this.viewHooks[hookElId]) {
3927
3944
  let hook = dom_default.getCustomElHook(el) || logError(`no hook found for custom element: ${el.id}`);
3928
3945
  this.viewHooks[hookElId] = hook;
@@ -3932,9 +3949,6 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3932
3949
  return;
3933
3950
  } else {
3934
3951
  let hookName = el.getAttribute(`data-phx-${PHX_HOOK}`) || el.getAttribute(this.binding(PHX_HOOK));
3935
- if (hookName && !this.ownsElement(el)) {
3936
- return;
3937
- }
3938
3952
  let callbacks = this.liveSocket.getHookCallbacks(hookName);
3939
3953
  if (callbacks) {
3940
3954
  if (!el.id) {
@@ -3949,9 +3963,10 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3949
3963
  }
3950
3964
  }
3951
3965
  destroyHook(hook) {
3966
+ const hookId = ViewHook.elementID(hook.el);
3952
3967
  hook.__destroyed();
3953
3968
  hook.__cleanup__();
3954
- delete this.viewHooks[ViewHook.elementID(hook.el)];
3969
+ delete this.viewHooks[hookId];
3955
3970
  }
3956
3971
  applyPendingUpdates() {
3957
3972
  this.pendingDiffs.forEach(({ diff, events }) => this.update(diff, events));
@@ -4201,12 +4216,11 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
4201
4216
  undoElRef(el, ref, phxEvent) {
4202
4217
  let elRef = new ElementRef(el);
4203
4218
  elRef.maybeUndo(ref, phxEvent, (clonedTree) => {
4204
- let hook = this.triggerBeforeUpdateHook(el, clonedTree);
4205
- DOMPatch.patchWithClonedTree(el, clonedTree, this.liveSocket);
4219
+ let patch = new DOMPatch(this, el, this.id, clonedTree, [], null, { undoRef: ref });
4220
+ const phxChildrenAdded = this.performPatch(patch, true);
4206
4221
  dom_default.all(el, `[${PHX_REF_SRC}="${this.refSrc()}"]`, (child) => this.undoElRef(child, ref, phxEvent));
4207
- this.execNewMounted(el);
4208
- if (hook) {
4209
- hook.__updated();
4222
+ if (phxChildrenAdded) {
4223
+ this.joinNewChildren();
4210
4224
  }
4211
4225
  });
4212
4226
  }
@@ -4422,15 +4436,17 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
4422
4436
  };
4423
4437
  this.pushWithReply(refGenerator, "event", event).then(({ resp }) => {
4424
4438
  if (dom_default.isUploadInput(inputEl) && dom_default.isAutoUpload(inputEl)) {
4425
- if (LiveUploader.filesAwaitingPreflight(inputEl).length > 0) {
4426
- let [ref, _els] = refGenerator();
4427
- this.undoRefs(ref, phxEvent, [inputEl.form]);
4428
- this.uploadFiles(inputEl.form, phxEvent, targetCtx, ref, cid, (_uploads) => {
4429
- callback && callback(resp);
4430
- this.triggerAwaitingSubmit(inputEl.form, phxEvent);
4431
- this.undoRefs(ref, phxEvent);
4432
- });
4433
- }
4439
+ ElementRef.onUnlock(inputEl, () => {
4440
+ if (LiveUploader.filesAwaitingPreflight(inputEl).length > 0) {
4441
+ let [ref, _els] = refGenerator();
4442
+ this.undoRefs(ref, phxEvent, [inputEl.form]);
4443
+ this.uploadFiles(inputEl.form, phxEvent, targetCtx, ref, cid, (_uploads) => {
4444
+ callback && callback(resp);
4445
+ this.triggerAwaitingSubmit(inputEl.form, phxEvent);
4446
+ this.undoRefs(ref, phxEvent);
4447
+ });
4448
+ }
4449
+ });
4434
4450
  } else {
4435
4451
  callback && callback(resp);
4436
4452
  }
@@ -4780,7 +4796,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
4780
4796
  }
4781
4797
  // public
4782
4798
  version() {
4783
- return "1.0.2";
4799
+ return "1.0.3";
4784
4800
  }
4785
4801
  isProfileEnabled() {
4786
4802
  return this.sessionStorage.getItem(PHX_LV_PROFILE) === "true";
@@ -4978,7 +4994,9 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
4978
4994
  dom_default.all(document, `${PHX_VIEW_SELECTOR}:not([${PHX_PARENT_ID}])`, (rootEl) => {
4979
4995
  if (!this.getRootById(rootEl.id)) {
4980
4996
  let view = this.newRootView(rootEl);
4981
- view.setHref(this.getHref());
4997
+ if (!dom_default.isPhxSticky(rootEl)) {
4998
+ view.setHref(this.getHref());
4999
+ }
4982
5000
  view.join();
4983
5001
  if (rootEl.hasAttribute(PHX_MAIN)) {
4984
5002
  this.main = view;
@@ -5292,7 +5310,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
5292
5310
  if (!this.registerNewLocation(window.location)) {
5293
5311
  return;
5294
5312
  }
5295
- let { type, backType, id, root, scroll, position } = event.state || {};
5313
+ let { type, backType, id, scroll, position } = event.state || {};
5296
5314
  let href = window.location.href;
5297
5315
  let isForward = position > this.currentHistoryPosition;
5298
5316
  type = isForward ? type : backType || type;
@@ -5300,17 +5318,13 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
5300
5318
  this.sessionStorage.setItem(PHX_LV_HISTORY_POSITION, this.currentHistoryPosition.toString());
5301
5319
  dom_default.dispatchEvent(window, "phx:navigate", { detail: { href, patch: type === "patch", pop: true, direction: isForward ? "forward" : "backward" } });
5302
5320
  this.requestDOMUpdate(() => {
5321
+ const callback = () => {
5322
+ this.maybeScroll(scroll);
5323
+ };
5303
5324
  if (this.main.isConnected() && (type === "patch" && id === this.main.id)) {
5304
- this.main.pushLinkPatch(event, href, null, () => {
5305
- this.maybeScroll(scroll);
5306
- });
5325
+ this.main.pushLinkPatch(event, href, null, callback);
5307
5326
  } else {
5308
- this.replaceMain(href, null, () => {
5309
- if (root) {
5310
- this.replaceRootHistory();
5311
- }
5312
- this.maybeScroll(scroll);
5313
- });
5327
+ this.replaceMain(href, null, callback);
5314
5328
  }
5315
5329
  });
5316
5330
  }, false);
@@ -5417,15 +5431,6 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
5417
5431
  });
5418
5432
  });
5419
5433
  }
5420
- replaceRootHistory() {
5421
- browser_default.pushState("replace", {
5422
- root: true,
5423
- type: "patch",
5424
- id: this.main.id,
5425
- position: this.currentHistoryPosition
5426
- // Preserve current position
5427
- });
5428
- }
5429
5434
  registerNewLocation(newLocation) {
5430
5435
  let { pathname, search } = this.currentLocation;
5431
5436
  if (pathname + search === newLocation.pathname + newLocation.search) {