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.
@@ -192,6 +192,16 @@ function detectDuplicateIds() {
192
192
  }
193
193
  }
194
194
  }
195
+ function detectInvalidStreamInserts(inserts) {
196
+ const errors = /* @__PURE__ */ new Set();
197
+ Object.keys(inserts).forEach((id) => {
198
+ const streamEl = document.getElementById(id);
199
+ if (streamEl && streamEl.parentElement && streamEl.parentElement.getAttribute("phx-update") !== "stream") {
200
+ 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.`);
201
+ }
202
+ });
203
+ errors.forEach((error) => console.error(error));
204
+ }
195
205
  var debug = (view, kind, msg, obj) => {
196
206
  if (view.liveSocket.isDebugEnabled()) {
197
207
  console.log(`${view.id} ${kind}: ${msg} - `, obj);
@@ -418,7 +428,7 @@ var DOM = {
418
428
  cids.forEach((cid) => {
419
429
  this.filterWithinSameLiveView(this.all(node, `[${PHX_COMPONENT}="${cid}"]`), node).forEach((parent) => {
420
430
  parentCids.add(cid);
421
- this.all(parent, `[${PHX_COMPONENT}]`).map((el) => parseInt(el.getAttribute(PHX_COMPONENT))).forEach((childCID) => childrenCids.add(childCID));
431
+ this.filterWithinSameLiveView(this.all(parent, `[${PHX_COMPONENT}]`), parent).map((el) => parseInt(el.getAttribute(PHX_COMPONENT))).forEach((childCID) => childrenCids.add(childCID));
422
432
  });
423
433
  });
424
434
  childrenCids.forEach((childCid) => parentCids.delete(childCid));
@@ -795,6 +805,9 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
795
805
  return;
796
806
  }
797
807
  ops.forEach(([name, op, _stashed]) => this.putSticky(el, name, op));
808
+ },
809
+ isLocked(el) {
810
+ return el.hasAttribute && el.hasAttribute(PHX_REF_LOCK);
798
811
  }
799
812
  };
800
813
  var dom_default = DOM;
@@ -1137,8 +1150,22 @@ var Hooks = {
1137
1150
  mounted() {
1138
1151
  this.focusStart = this.el.firstElementChild;
1139
1152
  this.focusEnd = this.el.lastElementChild;
1140
- this.focusStart.addEventListener("focus", () => aria_default.focusLast(this.el));
1141
- this.focusEnd.addEventListener("focus", () => aria_default.focusFirst(this.el));
1153
+ this.focusStart.addEventListener("focus", (e) => {
1154
+ if (!e.relatedTarget || !this.el.contains(e.relatedTarget)) {
1155
+ const nextFocus = e.target.nextElementSibling;
1156
+ aria_default.attemptFocus(nextFocus) || aria_default.focusFirst(nextFocus);
1157
+ } else {
1158
+ aria_default.focusLast(this.el);
1159
+ }
1160
+ });
1161
+ this.focusEnd.addEventListener("focus", (e) => {
1162
+ if (!e.relatedTarget || !this.el.contains(e.relatedTarget)) {
1163
+ const nextFocus = e.target.previousElementSibling;
1164
+ aria_default.attemptFocus(nextFocus) || aria_default.focusLast(nextFocus);
1165
+ } else {
1166
+ aria_default.focusFirst(this.el);
1167
+ }
1168
+ });
1142
1169
  this.el.addEventListener("phx:show-end", () => this.el.focus());
1143
1170
  if (window.getComputedStyle(this.el).display !== "none") {
1144
1171
  aria_default.focusFirst(this.el);
@@ -1287,6 +1314,16 @@ var hooks_default = Hooks;
1287
1314
 
1288
1315
  // js/phoenix_live_view/element_ref.js
1289
1316
  var ElementRef = class {
1317
+ static onUnlock(el, callback) {
1318
+ if (!dom_default.isLocked(el) && !el.closest(`[${PHX_REF_LOCK}]`)) {
1319
+ return callback();
1320
+ }
1321
+ const closestLock = el.closest(`[${PHX_REF_LOCK}]`);
1322
+ const ref = closestLock.closest(`[${PHX_REF_LOCK}]`).getAttribute(PHX_REF_LOCK);
1323
+ closestLock.addEventListener(`phx:undo-lock:${ref}`, () => {
1324
+ callback();
1325
+ }, { once: true });
1326
+ }
1290
1327
  constructor(el) {
1291
1328
  this.el = el;
1292
1329
  this.loadingRef = el.hasAttribute(PHX_REF_LOADING) ? parseInt(el.getAttribute(PHX_REF_LOADING), 10) : null;
@@ -1938,37 +1975,7 @@ var morphdom_esm_default = morphdom;
1938
1975
 
1939
1976
  // js/phoenix_live_view/dom_patch.js
1940
1977
  var DOMPatch = class {
1941
- static patchWithClonedTree(container, clonedTree, liveSocket) {
1942
- let focused = liveSocket.getActiveElement();
1943
- let { selectionStart, selectionEnd } = focused && dom_default.hasSelectionRange(focused) ? focused : {};
1944
- let phxUpdate = liveSocket.binding(PHX_UPDATE);
1945
- let externalFormTriggered = null;
1946
- morphdom_esm_default(container, clonedTree, {
1947
- childrenOnly: false,
1948
- onBeforeElUpdated: (fromEl, toEl) => {
1949
- dom_default.syncPendingAttrs(fromEl, toEl);
1950
- if (!container.isSameNode(fromEl) && fromEl.hasAttribute(PHX_REF_LOCK)) {
1951
- return false;
1952
- }
1953
- if (dom_default.isIgnored(fromEl, phxUpdate)) {
1954
- return false;
1955
- }
1956
- if (focused && focused.isSameNode(fromEl) && dom_default.isFormInput(fromEl)) {
1957
- dom_default.mergeFocusedInput(fromEl, toEl);
1958
- return false;
1959
- }
1960
- if (dom_default.isNowTriggerFormExternal(toEl, liveSocket.binding(PHX_TRIGGER_ACTION))) {
1961
- externalFormTriggered = toEl;
1962
- }
1963
- }
1964
- });
1965
- if (externalFormTriggered) {
1966
- liveSocket.unload();
1967
- Object.getPrototypeOf(externalFormTriggered).submit.call(externalFormTriggered);
1968
- }
1969
- liveSocket.silenceEvents(() => dom_default.restoreFocus(focused, selectionStart, selectionEnd));
1970
- }
1971
- constructor(view, container, id, html, streams, targetCID) {
1978
+ constructor(view, container, id, html, streams, targetCID, opts = {}) {
1972
1979
  this.view = view;
1973
1980
  this.liveSocket = view.liveSocket;
1974
1981
  this.container = container;
@@ -1993,6 +2000,8 @@ var DOMPatch = class {
1993
2000
  afterphxChildAdded: [],
1994
2001
  aftertransitionsDiscarded: []
1995
2002
  };
2003
+ this.withChildren = opts.withChildren || opts.undoRef || false;
2004
+ this.undoRef = opts.undoRef;
1996
2005
  }
1997
2006
  before(kind, callback) {
1998
2007
  this.callbacks[`before${kind}`].push(callback);
@@ -2027,7 +2036,7 @@ var DOMPatch = class {
2027
2036
  let updates = [];
2028
2037
  let appendPrependUpdates = [];
2029
2038
  let externalFormTriggered = null;
2030
- function morph(targetContainer2, source, withChildren = false) {
2039
+ function morph(targetContainer2, source, withChildren = this.withChildren) {
2031
2040
  let morphCallbacks = {
2032
2041
  // normally, we are running with childrenOnly, as the patch HTML for a LV
2033
2042
  // does not include the LV attrs (data-phx-session, etc.)
@@ -2153,7 +2162,7 @@ var DOMPatch = class {
2153
2162
  }
2154
2163
  let isFocusedFormEl = focused && fromEl.isSameNode(focused) && dom_default.isFormInput(fromEl);
2155
2164
  let focusedSelectChanged = isFocusedFormEl && this.isChangedSelect(fromEl, toEl);
2156
- if (fromEl.hasAttribute(PHX_REF_SRC)) {
2165
+ if (fromEl.hasAttribute(PHX_REF_SRC) && fromEl.getAttribute(PHX_REF_LOCK) != this.undoRef) {
2157
2166
  if (dom_default.isUploadInput(fromEl)) {
2158
2167
  dom_default.mergeAttrs(fromEl, toEl, { isIgnored: true });
2159
2168
  this.trackBefore("updated", fromEl, toEl);
@@ -2237,6 +2246,7 @@ var DOMPatch = class {
2237
2246
  });
2238
2247
  if (liveSocket.isDebugEnabled()) {
2239
2248
  detectDuplicateIds();
2249
+ detectInvalidStreamInserts(this.streamInserts);
2240
2250
  Array.from(document.querySelectorAll("input[name=id]")).forEach((node) => {
2241
2251
  if (node.form) {
2242
2252
  console.error('Detected an input with name="id" inside a form! This will cause problems when patching the DOM.\n', node);
@@ -3571,7 +3581,11 @@ var View = class _View {
3571
3581
  this.formsForRecovery = this.getFormsForRecovery();
3572
3582
  }
3573
3583
  if (this.isMain() && window.history.state === null) {
3574
- this.liveSocket.replaceRootHistory();
3584
+ browser_default.pushState("replace", {
3585
+ type: "patch",
3586
+ id: this.id,
3587
+ position: this.liveSocket.currentHistoryPosition
3588
+ });
3575
3589
  }
3576
3590
  if (liveview_version !== this.liveSocket.version()) {
3577
3591
  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.`);
@@ -3893,6 +3907,9 @@ var View = class _View {
3893
3907
  }
3894
3908
  addHook(el) {
3895
3909
  let hookElId = ViewHook.elementID(el);
3910
+ if (el.getAttribute && !this.ownsElement(el)) {
3911
+ return;
3912
+ }
3896
3913
  if (hookElId && !this.viewHooks[hookElId]) {
3897
3914
  let hook = dom_default.getCustomElHook(el) || logError(`no hook found for custom element: ${el.id}`);
3898
3915
  this.viewHooks[hookElId] = hook;
@@ -3902,9 +3919,6 @@ var View = class _View {
3902
3919
  return;
3903
3920
  } else {
3904
3921
  let hookName = el.getAttribute(`data-phx-${PHX_HOOK}`) || el.getAttribute(this.binding(PHX_HOOK));
3905
- if (hookName && !this.ownsElement(el)) {
3906
- return;
3907
- }
3908
3922
  let callbacks = this.liveSocket.getHookCallbacks(hookName);
3909
3923
  if (callbacks) {
3910
3924
  if (!el.id) {
@@ -3919,9 +3933,10 @@ var View = class _View {
3919
3933
  }
3920
3934
  }
3921
3935
  destroyHook(hook) {
3936
+ const hookId = ViewHook.elementID(hook.el);
3922
3937
  hook.__destroyed();
3923
3938
  hook.__cleanup__();
3924
- delete this.viewHooks[ViewHook.elementID(hook.el)];
3939
+ delete this.viewHooks[hookId];
3925
3940
  }
3926
3941
  applyPendingUpdates() {
3927
3942
  this.pendingDiffs.forEach(({ diff, events }) => this.update(diff, events));
@@ -4171,12 +4186,11 @@ var View = class _View {
4171
4186
  undoElRef(el, ref, phxEvent) {
4172
4187
  let elRef = new ElementRef(el);
4173
4188
  elRef.maybeUndo(ref, phxEvent, (clonedTree) => {
4174
- let hook = this.triggerBeforeUpdateHook(el, clonedTree);
4175
- DOMPatch.patchWithClonedTree(el, clonedTree, this.liveSocket);
4189
+ let patch = new DOMPatch(this, el, this.id, clonedTree, [], null, { undoRef: ref });
4190
+ const phxChildrenAdded = this.performPatch(patch, true);
4176
4191
  dom_default.all(el, `[${PHX_REF_SRC}="${this.refSrc()}"]`, (child) => this.undoElRef(child, ref, phxEvent));
4177
- this.execNewMounted(el);
4178
- if (hook) {
4179
- hook.__updated();
4192
+ if (phxChildrenAdded) {
4193
+ this.joinNewChildren();
4180
4194
  }
4181
4195
  });
4182
4196
  }
@@ -4392,15 +4406,17 @@ var View = class _View {
4392
4406
  };
4393
4407
  this.pushWithReply(refGenerator, "event", event).then(({ resp }) => {
4394
4408
  if (dom_default.isUploadInput(inputEl) && dom_default.isAutoUpload(inputEl)) {
4395
- if (LiveUploader.filesAwaitingPreflight(inputEl).length > 0) {
4396
- let [ref, _els] = refGenerator();
4397
- this.undoRefs(ref, phxEvent, [inputEl.form]);
4398
- this.uploadFiles(inputEl.form, phxEvent, targetCtx, ref, cid, (_uploads) => {
4399
- callback && callback(resp);
4400
- this.triggerAwaitingSubmit(inputEl.form, phxEvent);
4401
- this.undoRefs(ref, phxEvent);
4402
- });
4403
- }
4409
+ ElementRef.onUnlock(inputEl, () => {
4410
+ if (LiveUploader.filesAwaitingPreflight(inputEl).length > 0) {
4411
+ let [ref, _els] = refGenerator();
4412
+ this.undoRefs(ref, phxEvent, [inputEl.form]);
4413
+ this.uploadFiles(inputEl.form, phxEvent, targetCtx, ref, cid, (_uploads) => {
4414
+ callback && callback(resp);
4415
+ this.triggerAwaitingSubmit(inputEl.form, phxEvent);
4416
+ this.undoRefs(ref, phxEvent);
4417
+ });
4418
+ }
4419
+ });
4404
4420
  } else {
4405
4421
  callback && callback(resp);
4406
4422
  }
@@ -4751,7 +4767,7 @@ var LiveSocket = class {
4751
4767
  }
4752
4768
  // public
4753
4769
  version() {
4754
- return "1.0.2";
4770
+ return "1.0.3";
4755
4771
  }
4756
4772
  isProfileEnabled() {
4757
4773
  return this.sessionStorage.getItem(PHX_LV_PROFILE) === "true";
@@ -4948,7 +4964,9 @@ var LiveSocket = class {
4948
4964
  dom_default.all(document, `${PHX_VIEW_SELECTOR}:not([${PHX_PARENT_ID}])`, (rootEl) => {
4949
4965
  if (!this.getRootById(rootEl.id)) {
4950
4966
  let view = this.newRootView(rootEl);
4951
- view.setHref(this.getHref());
4967
+ if (!dom_default.isPhxSticky(rootEl)) {
4968
+ view.setHref(this.getHref());
4969
+ }
4952
4970
  view.join();
4953
4971
  if (rootEl.hasAttribute(PHX_MAIN)) {
4954
4972
  this.main = view;
@@ -5262,7 +5280,7 @@ var LiveSocket = class {
5262
5280
  if (!this.registerNewLocation(window.location)) {
5263
5281
  return;
5264
5282
  }
5265
- let { type, backType, id, root, scroll, position } = event.state || {};
5283
+ let { type, backType, id, scroll, position } = event.state || {};
5266
5284
  let href = window.location.href;
5267
5285
  let isForward = position > this.currentHistoryPosition;
5268
5286
  type = isForward ? type : backType || type;
@@ -5270,17 +5288,13 @@ var LiveSocket = class {
5270
5288
  this.sessionStorage.setItem(PHX_LV_HISTORY_POSITION, this.currentHistoryPosition.toString());
5271
5289
  dom_default.dispatchEvent(window, "phx:navigate", { detail: { href, patch: type === "patch", pop: true, direction: isForward ? "forward" : "backward" } });
5272
5290
  this.requestDOMUpdate(() => {
5291
+ const callback = () => {
5292
+ this.maybeScroll(scroll);
5293
+ };
5273
5294
  if (this.main.isConnected() && (type === "patch" && id === this.main.id)) {
5274
- this.main.pushLinkPatch(event, href, null, () => {
5275
- this.maybeScroll(scroll);
5276
- });
5295
+ this.main.pushLinkPatch(event, href, null, callback);
5277
5296
  } else {
5278
- this.replaceMain(href, null, () => {
5279
- if (root) {
5280
- this.replaceRootHistory();
5281
- }
5282
- this.maybeScroll(scroll);
5283
- });
5297
+ this.replaceMain(href, null, callback);
5284
5298
  }
5285
5299
  });
5286
5300
  }, false);
@@ -5387,15 +5401,6 @@ var LiveSocket = class {
5387
5401
  });
5388
5402
  });
5389
5403
  }
5390
- replaceRootHistory() {
5391
- browser_default.pushState("replace", {
5392
- root: true,
5393
- type: "patch",
5394
- id: this.main.id,
5395
- position: this.currentHistoryPosition
5396
- // Preserve current position
5397
- });
5398
- }
5399
5404
  registerNewLocation(newLocation) {
5400
5405
  let { pathname, search } = this.currentLocation;
5401
5406
  if (pathname + search === newLocation.pathname + newLocation.search) {