phoenix_live_view 1.1.24 → 1.1.26

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.
@@ -29,6 +29,7 @@ import {
29
29
  PHX_RELOAD_STATUS,
30
30
  PHX_RUNTIME_HOOK,
31
31
  PHX_DROP_TARGET_ACTIVE_CLASS,
32
+ PHX_TELEPORTED_SRC,
32
33
  } from "./constants";
33
34
 
34
35
  import {
@@ -872,15 +873,28 @@ export default class LiveSocket {
872
873
 
873
874
  dispatchClickAway(e, clickStartedAt) {
874
875
  const phxClickAway = this.binding("click-away");
876
+ const portal = clickStartedAt.closest(`[${PHX_TELEPORTED_SRC}]`);
877
+ const portalStartedAt =
878
+ portal && DOM.byId(portal.getAttribute(PHX_TELEPORTED_SRC));
875
879
  DOM.all(document, `[${phxClickAway}]`, (el) => {
880
+ let startedAt = clickStartedAt;
881
+ if (portal && !portal.contains(el)) {
882
+ // If we have a portal and the click-away element is not inside it,
883
+ // then treat the portal source as the starting point instead.
884
+ startedAt = portalStartedAt;
885
+ }
876
886
  if (
877
887
  !(
878
- el.isSameNode(clickStartedAt) ||
879
- el.contains(clickStartedAt) ||
888
+ el.isSameNode(startedAt) ||
889
+ el.contains(startedAt) ||
880
890
  // When clicking a link with custom method,
881
891
  // phoenix_html triggers a click on a submit button
882
892
  // of a hidden form appended to the body. For such cases
883
893
  // where the clicked target is hidden, we skip click-away.
894
+ //
895
+ // Also, when we have a portal, we don't want to check the visibility
896
+ // of the portal source, as it's a <template> that is always not visible.
897
+ // Instead, check the visibility of the original click target.
884
898
  !JS.isVisible(clickStartedAt)
885
899
  )
886
900
  ) {
@@ -966,6 +966,15 @@ export default class View {
966
966
  }
967
967
 
968
968
  if (hookElId && !this.viewHooks[hookElId]) {
969
+ if (ViewHook.deadHook(el)) {
970
+ // If the hook is on an element outside of the LiveView,
971
+ // it is initially mounted by the dead view (view.isDead).
972
+ // As soon as the main LiveView is connected, it is considered
973
+ // to be owned by it though, but since the live view has a new
974
+ // viewHooks object, we don't find it. We mark hooks on "dead"
975
+ // elements as such and just ignore them here.
976
+ return;
977
+ }
969
978
  // hook created, but not attached (createHook for web component)
970
979
  const hook =
971
980
  DOM.getCustomElHook(el) ||
@@ -4,6 +4,7 @@ import LiveSocket from "./live_socket";
4
4
  import View from "./view";
5
5
 
6
6
  const HOOK_ID = "hookId";
7
+ const DEAD_HOOK = "deadHook";
7
8
  let viewHookID = 1;
8
9
 
9
10
  export type OnReply = (reply: any, ref: number) => any;
@@ -256,6 +257,9 @@ export class ViewHook<E extends HTMLElement = HTMLElement>
256
257
  static elementID(el: HTMLElement) {
257
258
  return DOM.private(el, HOOK_ID);
258
259
  }
260
+ static deadHook(el: HTMLElement) {
261
+ return DOM.private(el, DEAD_HOOK) === true;
262
+ }
259
263
 
260
264
  constructor(view: View | null, el: E, callbacks?: Hook) {
261
265
  this.el = el;
@@ -263,6 +267,9 @@ export class ViewHook<E extends HTMLElement = HTMLElement>
263
267
  this.__listeners = new Set();
264
268
  this.__isDisconnected = false;
265
269
  DOM.putPrivate(this.el, HOOK_ID, ViewHook.makeID());
270
+ if (view && view.isDead) {
271
+ DOM.putPrivate(this.el, DEAD_HOOK, true);
272
+ }
266
273
 
267
274
  if (callbacks) {
268
275
  // This instance is for an object-literal hook. Copy methods/properties.
@@ -208,6 +208,7 @@ export declare class ViewHook<E extends HTMLElement = HTMLElement> implements Ho
208
208
  get liveSocket(): LiveSocket;
209
209
  static makeID(): number;
210
210
  static elementID(el: HTMLElement): any;
211
+ static deadHook(el: HTMLElement): boolean;
211
212
  constructor(view: View | null, el: E, callbacks?: Hook);
212
213
  mounted(): void;
213
214
  beforeUpdate(): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phoenix_live_view",
3
- "version": "1.1.24",
3
+ "version": "1.1.26",
4
4
  "description": "The Phoenix LiveView JavaScript client.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -3868,6 +3868,7 @@ var js_commands_default = (liveSocket, eventType) => {
3868
3868
 
3869
3869
  // js/phoenix_live_view/view_hook.ts
3870
3870
  var HOOK_ID = "hookId";
3871
+ var DEAD_HOOK = "deadHook";
3871
3872
  var viewHookID = 1;
3872
3873
  var ViewHook = class _ViewHook {
3873
3874
  get liveSocket() {
@@ -3879,12 +3880,18 @@ var ViewHook = class _ViewHook {
3879
3880
  static elementID(el) {
3880
3881
  return dom_default.private(el, HOOK_ID);
3881
3882
  }
3883
+ static deadHook(el) {
3884
+ return dom_default.private(el, DEAD_HOOK) === true;
3885
+ }
3882
3886
  constructor(view, el, callbacks) {
3883
3887
  this.el = el;
3884
3888
  this.__attachView(view);
3885
3889
  this.__listeners = /* @__PURE__ */ new Set();
3886
3890
  this.__isDisconnected = false;
3887
3891
  dom_default.putPrivate(this.el, HOOK_ID, _ViewHook.makeID());
3892
+ if (view && view.isDead) {
3893
+ dom_default.putPrivate(this.el, DEAD_HOOK, true);
3894
+ }
3888
3895
  if (callbacks) {
3889
3896
  const protectedProps = /* @__PURE__ */ new Set([
3890
3897
  "el",
@@ -4790,6 +4797,9 @@ var View = class _View {
4790
4797
  return;
4791
4798
  }
4792
4799
  if (hookElId && !this.viewHooks[hookElId]) {
4800
+ if (ViewHook.deadHook(el)) {
4801
+ return;
4802
+ }
4793
4803
  const hook = dom_default.getCustomElHook(el) || logError(`no hook found for custom element: ${el.id}`);
4794
4804
  this.viewHooks[hookElId] = hook;
4795
4805
  hook.__attachView(this);
@@ -5924,7 +5934,7 @@ var LiveSocket = class {
5924
5934
  }
5925
5935
  // public
5926
5936
  version() {
5927
- return "1.1.24";
5937
+ return "1.1.26";
5928
5938
  }
5929
5939
  isProfileEnabled() {
5930
5940
  return this.sessionStorage.getItem(PHX_LV_PROFILE) === "true";
@@ -6530,11 +6540,21 @@ var LiveSocket = class {
6530
6540
  }
6531
6541
  dispatchClickAway(e, clickStartedAt) {
6532
6542
  const phxClickAway = this.binding("click-away");
6543
+ const portal = clickStartedAt.closest(`[${PHX_TELEPORTED_SRC}]`);
6544
+ const portalStartedAt = portal && dom_default.byId(portal.getAttribute(PHX_TELEPORTED_SRC));
6533
6545
  dom_default.all(document, `[${phxClickAway}]`, (el) => {
6534
- if (!(el.isSameNode(clickStartedAt) || el.contains(clickStartedAt) || // When clicking a link with custom method,
6546
+ let startedAt = clickStartedAt;
6547
+ if (portal && !portal.contains(el)) {
6548
+ startedAt = portalStartedAt;
6549
+ }
6550
+ if (!(el.isSameNode(startedAt) || el.contains(startedAt) || // When clicking a link with custom method,
6535
6551
  // phoenix_html triggers a click on a submit button
6536
6552
  // of a hidden form appended to the body. For such cases
6537
6553
  // where the clicked target is hidden, we skip click-away.
6554
+ //
6555
+ // Also, when we have a portal, we don't want to check the visibility
6556
+ // of the portal source, as it's a <template> that is always not visible.
6557
+ // Instead, check the visibility of the original click target.
6538
6558
  !js_default.isVisible(clickStartedAt))) {
6539
6559
  this.withinOwners(el, (view) => {
6540
6560
  const phxEvent = el.getAttribute(phxClickAway);