phoenix_live_view 1.1.30 → 1.1.32
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.
- package/assets/js/phoenix_live_view/dom.js +3 -1
- package/assets/js/phoenix_live_view/dom_patch.js +1 -1
- package/assets/js/phoenix_live_view/entry_uploader.js +1 -1
- package/assets/js/phoenix_live_view/js_commands.ts +3 -0
- package/assets/js/phoenix_live_view/live_socket.js +15 -8
- package/assets/js/phoenix_live_view/utils.js +21 -0
- package/assets/js/phoenix_live_view/view.js +4 -0
- package/assets/js/phoenix_live_view/view_hook.ts +45 -17
- package/assets/js/types/live_socket.d.ts +1 -1
- package/assets/js/types/utils.d.ts +1 -0
- package/assets/js/types/view_hook.d.ts +43 -15
- package/package.json +2 -2
- package/priv/static/phoenix_live_view.cjs.js +34 -10
- package/priv/static/phoenix_live_view.cjs.js.map +2 -2
- package/priv/static/phoenix_live_view.esm.js +34 -10
- package/priv/static/phoenix_live_view.esm.js.map +2 -2
- package/priv/static/phoenix_live_view.js +34 -10
- package/priv/static/phoenix_live_view.min.js +6 -6
|
@@ -370,7 +370,9 @@ const DOM = {
|
|
|
370
370
|
// we also clear the throttle timeout to prevent the callback
|
|
371
371
|
// from being called again after the timeout fires
|
|
372
372
|
clearTimeout(this.private(el, THROTTLED));
|
|
373
|
-
|
|
373
|
+
if (asyncFilter()) {
|
|
374
|
+
this.triggerCycle(el, DEBOUNCE_TRIGGER);
|
|
375
|
+
}
|
|
374
376
|
});
|
|
375
377
|
}
|
|
376
378
|
}
|
|
@@ -711,7 +711,7 @@ export default class DOMPatch {
|
|
|
711
711
|
transitionPendingRemoves() {
|
|
712
712
|
const { pendingRemoves, liveSocket } = this;
|
|
713
713
|
if (pendingRemoves.length > 0) {
|
|
714
|
-
liveSocket.transitionRemoves(pendingRemoves, () => {
|
|
714
|
+
liveSocket.transitionRemoves(pendingRemoves, this.view, () => {
|
|
715
715
|
pendingRemoves.forEach((el) => {
|
|
716
716
|
const child = DOM.firstPhxChild(el);
|
|
717
717
|
if (child) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import JS from "./js";
|
|
2
2
|
import LiveSocket from "./live_socket";
|
|
3
|
+
import { ensureSameOrigin } from "./utils";
|
|
3
4
|
|
|
4
5
|
type Transition = string | string[];
|
|
5
6
|
|
|
@@ -346,6 +347,7 @@ export default (
|
|
|
346
347
|
});
|
|
347
348
|
},
|
|
348
349
|
navigate(href, opts = {}) {
|
|
350
|
+
ensureSameOrigin(href, "navigate");
|
|
349
351
|
const customEvent = new CustomEvent("phx:exec");
|
|
350
352
|
liveSocket.historyRedirect(
|
|
351
353
|
customEvent,
|
|
@@ -356,6 +358,7 @@ export default (
|
|
|
356
358
|
);
|
|
357
359
|
},
|
|
358
360
|
patch(href, opts = {}) {
|
|
361
|
+
ensureSameOrigin(href, "patch");
|
|
359
362
|
const customEvent = new CustomEvent("phx:exec");
|
|
360
363
|
liveSocket.pushHistoryPatch(
|
|
361
364
|
customEvent,
|
|
@@ -472,12 +472,15 @@ export default class LiveSocket {
|
|
|
472
472
|
).filter((el) => !DOM.isChildOfAny(el, stickies));
|
|
473
473
|
|
|
474
474
|
const newMainEl = DOM.cloneNode(this.outgoingMainEl, "");
|
|
475
|
-
this.main
|
|
476
|
-
this.
|
|
475
|
+
const oldMainView = this.main;
|
|
476
|
+
oldMainView.showLoader(this.loaderTimeout);
|
|
477
|
+
oldMainView.destroy();
|
|
477
478
|
|
|
478
479
|
this.main = this.newRootView(newMainEl, flash, liveReferer);
|
|
479
480
|
this.main.setRedirect(href);
|
|
480
|
-
this
|
|
481
|
+
// the old view is destroyed at this point; pass it explicitly so the
|
|
482
|
+
// phx-remove commands execute in the context of the outgoing view
|
|
483
|
+
this.transitionRemoves(removeEls, oldMainView);
|
|
481
484
|
this.main.join((joinCount, onDone) => {
|
|
482
485
|
if (joinCount === 1 && this.commitPendingLink(linkRef)) {
|
|
483
486
|
this.requestDOMUpdate(() => {
|
|
@@ -493,7 +496,7 @@ export default class LiveSocket {
|
|
|
493
496
|
});
|
|
494
497
|
}
|
|
495
498
|
|
|
496
|
-
transitionRemoves(elements, callback) {
|
|
499
|
+
transitionRemoves(elements, view, callback) {
|
|
497
500
|
const removeAttr = this.binding("remove");
|
|
498
501
|
const silenceEvents = (e) => {
|
|
499
502
|
e.preventDefault();
|
|
@@ -505,7 +508,8 @@ export default class LiveSocket {
|
|
|
505
508
|
for (const event of this.boundEventNames) {
|
|
506
509
|
el.addEventListener(event, silenceEvents, true);
|
|
507
510
|
}
|
|
508
|
-
|
|
511
|
+
const e = new CustomEvent("phx:exec", { detail: { sourceElement: el } });
|
|
512
|
+
JS.exec(e, "remove", el.getAttribute(removeAttr), view, el);
|
|
509
513
|
});
|
|
510
514
|
// remove the silenced listeners when transitions are done incase the element is re-used
|
|
511
515
|
// and call caller's callback as soon as we are done with transitions
|
|
@@ -533,9 +537,12 @@ export default class LiveSocket {
|
|
|
533
537
|
let view;
|
|
534
538
|
const viewEl = DOM.closestViewEl(childEl);
|
|
535
539
|
if (viewEl) {
|
|
536
|
-
//
|
|
537
|
-
//
|
|
538
|
-
|
|
540
|
+
// resolve the view by element identity instead of id; during live
|
|
541
|
+
// navigation the new view is registered under the same id while the
|
|
542
|
+
// old DOM is still attached, and events from the old DOM must not be
|
|
543
|
+
// routed to the new view. A destroyed view removes its element binding,
|
|
544
|
+
// in which case we DO NOT want to fallback to the main element
|
|
545
|
+
view = DOM.private(viewEl, "view");
|
|
539
546
|
} else {
|
|
540
547
|
if (!childEl.isConnected) {
|
|
541
548
|
// if the element is not part of the DOM any more
|
|
@@ -4,6 +4,27 @@ import EntryUploader from "./entry_uploader";
|
|
|
4
4
|
|
|
5
5
|
export const logError = (msg, obj) => console.error && console.error(msg, obj);
|
|
6
6
|
|
|
7
|
+
// Live navigation can only stay within the current origin, as it joins the
|
|
8
|
+
// target over the existing socket. A full URL to a different origin (or a
|
|
9
|
+
// non-http(s) scheme, which resolves to an opaque "null" origin) is a
|
|
10
|
+
// programming error, so we fail loudly instead of attempting a broken join.
|
|
11
|
+
export const ensureSameOrigin = (href, kind) => {
|
|
12
|
+
let url;
|
|
13
|
+
try {
|
|
14
|
+
url = new URL(href, window.location.href);
|
|
15
|
+
} catch {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`expected ${kind} destination to be a valid URL, got: ${href}`,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
if (url.origin !== window.location.origin) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`cannot ${kind} to "${href}" because its origin does not match the ` +
|
|
23
|
+
`current origin "${window.location.origin}". Use window.location directly for cross-origin navigation.`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
7
28
|
export const isCid = (cid) => {
|
|
8
29
|
const type = typeof cid;
|
|
9
30
|
return type === "number" || (type === "string" && /^(0|[1-9]\d*)$/.test(cid));
|
|
@@ -443,6 +443,7 @@ export default class View {
|
|
|
443
443
|
if (container) {
|
|
444
444
|
const [tag, attrs] = container;
|
|
445
445
|
this.el = DOM.replaceRootContainer(this.el, tag, attrs);
|
|
446
|
+
DOM.putPrivate(this.el, "view", this);
|
|
446
447
|
}
|
|
447
448
|
this.childJoins = 0;
|
|
448
449
|
this.joinPending = true;
|
|
@@ -548,6 +549,7 @@ export default class View {
|
|
|
548
549
|
|
|
549
550
|
attachTrueDocEl() {
|
|
550
551
|
this.el = DOM.byId(this.id);
|
|
552
|
+
DOM.putPrivate(this.el, "view", this);
|
|
551
553
|
this.el.setAttribute(PHX_ROOT_ID, this.root.id);
|
|
552
554
|
}
|
|
553
555
|
|
|
@@ -770,6 +772,7 @@ export default class View {
|
|
|
770
772
|
});
|
|
771
773
|
|
|
772
774
|
// because we work with a template element, we must manually copy the attributes
|
|
775
|
+
// and bind the template root to this view,
|
|
773
776
|
// otherwise the owner / target helpers don't work properly
|
|
774
777
|
const rootEl = template.content.firstElementChild;
|
|
775
778
|
rootEl.id = this.id;
|
|
@@ -777,6 +780,7 @@ export default class View {
|
|
|
777
780
|
rootEl.setAttribute(PHX_SESSION, this.getSession());
|
|
778
781
|
rootEl.setAttribute(PHX_STATIC, this.getStatic());
|
|
779
782
|
rootEl.setAttribute(PHX_PARENT_ID, this.parent ? this.parent.id : null);
|
|
783
|
+
DOM.putPrivate(rootEl, "view", this);
|
|
780
784
|
|
|
781
785
|
// we go over all form elements in the new HTML for the LV
|
|
782
786
|
// and look for old forms in the `formsForRecovery` object;
|
|
@@ -73,38 +73,45 @@ export interface HookInterface<E extends HTMLElement = HTMLElement> {
|
|
|
73
73
|
js(): HookJSCommands;
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
|
-
* Pushes an event to the server.
|
|
76
|
+
* Pushes an event to the server and invokes a callback with the server's reply.
|
|
77
77
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* @param [onReply] - A callback to handle the server's reply.
|
|
78
|
+
* **Note:** this version silently ignores push errors.
|
|
79
|
+
* Use the {@link pushEvent | promise-returning version} to handle errors.
|
|
81
80
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
81
|
+
* @param event - The event name.
|
|
82
|
+
* @param payload - The payload to send to the server. Defaults to an empty object.
|
|
83
|
+
* @param onReply - A callback to handle the server's reply.
|
|
84
84
|
*/
|
|
85
85
|
pushEvent(event: string, payload: any, onReply: OnReply): void;
|
|
86
|
+
/**
|
|
87
|
+
* Pushes an event to the server and returns a Promise that resolves with the server's reply.
|
|
88
|
+
*
|
|
89
|
+
* The promise will be rejected in case of errors
|
|
90
|
+
* such as a disconnected state, timeout, or the server rejecting the event.
|
|
91
|
+
*
|
|
92
|
+
* @param event - The event name.
|
|
93
|
+
* @param [payload] - The payload to send to the server. Defaults to an empty object.
|
|
94
|
+
* @returns A promise that fulfills or rejects with the server's reply.
|
|
95
|
+
*/
|
|
86
96
|
pushEvent(event: string, payload?: any): Promise<any>;
|
|
87
97
|
|
|
88
98
|
/**
|
|
89
|
-
*
|
|
99
|
+
* Pushes a targeted event to the server and invokes a callback with the server's reply.
|
|
90
100
|
*
|
|
91
101
|
* It sends the event to the LiveComponent or LiveView the `selectorOrTarget` is defined in,
|
|
92
102
|
* where its value can be either a query selector, an actual DOM element, or a CID (component id)
|
|
93
103
|
* returned by the `@myself` assign.
|
|
94
104
|
*
|
|
95
|
-
* If the
|
|
96
|
-
* even if
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
105
|
+
* If the selector matches multiple elements, the event is sent to all of them,
|
|
106
|
+
* even if they belong to the same LiveComponent or LiveView.
|
|
107
|
+
*
|
|
108
|
+
* **Note:** this version silently ignores push errors.
|
|
109
|
+
* Use the {@link pushEventTo | promise-returning version} to handle errors.
|
|
100
110
|
*
|
|
101
111
|
* @param selectorOrTarget - The selector, element, or CID to target.
|
|
102
112
|
* @param event - The event name.
|
|
103
|
-
* @param
|
|
104
|
-
* @param
|
|
105
|
-
*
|
|
106
|
-
* When onReply is not provided, the method returns a Promise.
|
|
107
|
-
* When onReply is provided, the method returns void.
|
|
113
|
+
* @param payload - The payload to send to the server. Defaults to an empty object.
|
|
114
|
+
* @param onReply - A callback to handle the server's reply.
|
|
108
115
|
*/
|
|
109
116
|
pushEventTo(
|
|
110
117
|
selectorOrTarget: PhxTarget,
|
|
@@ -112,6 +119,27 @@ export interface HookInterface<E extends HTMLElement = HTMLElement> {
|
|
|
112
119
|
payload: object,
|
|
113
120
|
onReply: OnReply,
|
|
114
121
|
): void;
|
|
122
|
+
/**
|
|
123
|
+
* Pushes a targeted event to the server and returns a Promise that resolves with the server's reply.
|
|
124
|
+
*
|
|
125
|
+
* It sends the event to the LiveComponent or LiveView the `selectorOrTarget` is defined in,
|
|
126
|
+
* where its value can be either a query selector, an actual DOM element, or a CID (component id)
|
|
127
|
+
* returned by the `@myself` assign.
|
|
128
|
+
*
|
|
129
|
+
* If the selector matches multiple elements, the event is sent to all of them,
|
|
130
|
+
* even if they belong to the same LiveComponent or LiveView.
|
|
131
|
+
* Because of this, it returns a single promise that matches the return value of
|
|
132
|
+
* [`Promise.allSettled()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled#return_value).
|
|
133
|
+
* Individual fulfilled values are of the format `{ reply, ref }`, where `reply` is the server's reply.
|
|
134
|
+
*
|
|
135
|
+
* The individual promises will be rejected in case of errors
|
|
136
|
+
* such as a disconnected state, timeout, or the server rejecting the event.
|
|
137
|
+
*
|
|
138
|
+
* @param selectorOrTarget - The selector, element, or CID to target.
|
|
139
|
+
* @param event - The event name.
|
|
140
|
+
* @param [payload] - The payload to send to the server. Defaults to an empty object.
|
|
141
|
+
* @returns A promise that resolves when the event has been handled by all targets.
|
|
142
|
+
*/
|
|
115
143
|
pushEventTo(
|
|
116
144
|
selectorOrTarget: PhxTarget,
|
|
117
145
|
event: string,
|
|
@@ -89,7 +89,7 @@ export default class LiveSocket {
|
|
|
89
89
|
joinRootViews(): boolean;
|
|
90
90
|
redirect(to: any, flash: any, reloadToken: any): void;
|
|
91
91
|
replaceMain(href: any, flash: any, callback?: any, linkRef?: number): void;
|
|
92
|
-
transitionRemoves(elements: any, callback: any): void;
|
|
92
|
+
transitionRemoves(elements: any, view: any, callback: any): void;
|
|
93
93
|
isPhxView(el: any): boolean;
|
|
94
94
|
newRootView(el: any, flash: any, liveReferer: any): View;
|
|
95
95
|
owner(childEl: any, callback: any): any;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export function detectDuplicateIds(): void;
|
|
2
2
|
export function detectInvalidStreamInserts(inserts: any): void;
|
|
3
3
|
export function logError(msg: any, obj: any): void;
|
|
4
|
+
export function ensureSameOrigin(href: any, kind: any): void;
|
|
4
5
|
export function isCid(cid: any): boolean;
|
|
5
6
|
export function debug(view: any, kind: any, msg: any, obj: any): void;
|
|
6
7
|
export function closure(val: any): any;
|
|
@@ -59,39 +59,67 @@ export interface HookInterface<E extends HTMLElement = HTMLElement> {
|
|
|
59
59
|
*/
|
|
60
60
|
js(): HookJSCommands;
|
|
61
61
|
/**
|
|
62
|
-
* Pushes an event to the server.
|
|
62
|
+
* Pushes an event to the server and invokes a callback with the server's reply.
|
|
63
63
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* @param [onReply] - A callback to handle the server's reply.
|
|
64
|
+
* **Note:** this version silently ignores push errors.
|
|
65
|
+
* Use the {@link pushEvent | promise-returning version} to handle errors.
|
|
67
66
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
67
|
+
* @param event - The event name.
|
|
68
|
+
* @param payload - The payload to send to the server. Defaults to an empty object.
|
|
69
|
+
* @param onReply - A callback to handle the server's reply.
|
|
70
70
|
*/
|
|
71
71
|
pushEvent(event: string, payload: any, onReply: OnReply): void;
|
|
72
|
+
/**
|
|
73
|
+
* Pushes an event to the server and returns a Promise that resolves with the server's reply.
|
|
74
|
+
*
|
|
75
|
+
* The promise will be rejected in case of errors
|
|
76
|
+
* such as a disconnected state, timeout, or the server rejecting the event.
|
|
77
|
+
*
|
|
78
|
+
* @param event - The event name.
|
|
79
|
+
* @param [payload] - The payload to send to the server. Defaults to an empty object.
|
|
80
|
+
* @returns A promise that fulfills or rejects with the server's reply.
|
|
81
|
+
*/
|
|
72
82
|
pushEvent(event: string, payload?: any): Promise<any>;
|
|
73
83
|
/**
|
|
74
|
-
*
|
|
84
|
+
* Pushes a targeted event to the server and invokes a callback with the server's reply.
|
|
85
|
+
*
|
|
86
|
+
* It sends the event to the LiveComponent or LiveView the `selectorOrTarget` is defined in,
|
|
87
|
+
* where its value can be either a query selector, an actual DOM element, or a CID (component id)
|
|
88
|
+
* returned by the `@myself` assign.
|
|
89
|
+
*
|
|
90
|
+
* If the selector matches multiple elements, the event is sent to all of them,
|
|
91
|
+
* even if they belong to the same LiveComponent or LiveView.
|
|
92
|
+
*
|
|
93
|
+
* **Note:** this version silently ignores push errors.
|
|
94
|
+
* Use the {@link pushEventTo | promise-returning version} to handle errors.
|
|
95
|
+
*
|
|
96
|
+
* @param selectorOrTarget - The selector, element, or CID to target.
|
|
97
|
+
* @param event - The event name.
|
|
98
|
+
* @param payload - The payload to send to the server. Defaults to an empty object.
|
|
99
|
+
* @param onReply - A callback to handle the server's reply.
|
|
100
|
+
*/
|
|
101
|
+
pushEventTo(selectorOrTarget: PhxTarget, event: string, payload: object, onReply: OnReply): void;
|
|
102
|
+
/**
|
|
103
|
+
* Pushes a targeted event to the server and returns a Promise that resolves with the server's reply.
|
|
75
104
|
*
|
|
76
105
|
* It sends the event to the LiveComponent or LiveView the `selectorOrTarget` is defined in,
|
|
77
106
|
* where its value can be either a query selector, an actual DOM element, or a CID (component id)
|
|
78
107
|
* returned by the `@myself` assign.
|
|
79
108
|
*
|
|
80
|
-
* If the
|
|
81
|
-
* even if
|
|
82
|
-
*
|
|
109
|
+
* If the selector matches multiple elements, the event is sent to all of them,
|
|
110
|
+
* even if they belong to the same LiveComponent or LiveView.
|
|
111
|
+
* Because of this, it returns a single promise that matches the return value of
|
|
83
112
|
* [`Promise.allSettled()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled#return_value).
|
|
84
113
|
* Individual fulfilled values are of the format `{ reply, ref }`, where `reply` is the server's reply.
|
|
85
114
|
*
|
|
115
|
+
* The individual promises will be rejected in case of errors
|
|
116
|
+
* such as a disconnected state, timeout, or the server rejecting the event.
|
|
117
|
+
*
|
|
86
118
|
* @param selectorOrTarget - The selector, element, or CID to target.
|
|
87
119
|
* @param event - The event name.
|
|
88
120
|
* @param [payload] - The payload to send to the server. Defaults to an empty object.
|
|
89
|
-
* @
|
|
90
|
-
*
|
|
91
|
-
* When onReply is not provided, the method returns a Promise.
|
|
92
|
-
* When onReply is provided, the method returns void.
|
|
121
|
+
* @returns A promise that resolves when the event has been handled by all targets.
|
|
93
122
|
*/
|
|
94
|
-
pushEventTo(selectorOrTarget: PhxTarget, event: string, payload: object, onReply: OnReply): void;
|
|
95
123
|
pushEventTo(selectorOrTarget: PhxTarget, event: string, payload?: object): Promise<PromiseSettledResult<{
|
|
96
124
|
reply: any;
|
|
97
125
|
ref: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phoenix_live_view",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.32",
|
|
4
4
|
"description": "The Phoenix LiveView JavaScript client.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@babel/preset-env": "7.27.2",
|
|
38
38
|
"@babel/preset-typescript": "^7.27.1",
|
|
39
39
|
"@eslint/js": "^9.29.0",
|
|
40
|
-
"@playwright/test": "^1.
|
|
40
|
+
"@playwright/test": "^1.60.0",
|
|
41
41
|
"@types/jest": "^30.0.0",
|
|
42
42
|
"@types/phoenix": "^1.6.6",
|
|
43
43
|
"css.escape": "^1.5.1",
|
|
@@ -172,7 +172,7 @@ var EntryUploader = class {
|
|
|
172
172
|
}
|
|
173
173
|
upload() {
|
|
174
174
|
this.uploadChannel.onError((reason) => this.error(reason));
|
|
175
|
-
this.uploadChannel.join().receive("ok", (_data) => this.readNextChunk()).receive("error", (reason) => this.error(reason));
|
|
175
|
+
this.uploadChannel.join().receive("ok", (_data) => this.readNextChunk()).receive("error", ({ reason }) => this.error(reason));
|
|
176
176
|
}
|
|
177
177
|
isDone() {
|
|
178
178
|
return this.offset >= this.entry.file.size;
|
|
@@ -215,6 +215,21 @@ var EntryUploader = class {
|
|
|
215
215
|
|
|
216
216
|
// js/phoenix_live_view/utils.js
|
|
217
217
|
var logError = (msg, obj) => console.error && console.error(msg, obj);
|
|
218
|
+
var ensureSameOrigin = (href, kind) => {
|
|
219
|
+
let url;
|
|
220
|
+
try {
|
|
221
|
+
url = new URL(href, window.location.href);
|
|
222
|
+
} catch {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`expected ${kind} destination to be a valid URL, got: ${href}`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
if (url.origin !== window.location.origin) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`cannot ${kind} to "${href}" because its origin does not match the current origin "${window.location.origin}". Use window.location directly for cross-origin navigation.`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
218
233
|
var isCid = (cid) => {
|
|
219
234
|
const type = typeof cid;
|
|
220
235
|
return type === "number" || type === "string" && /^(0|[1-9]\d*)$/.test(cid);
|
|
@@ -635,7 +650,9 @@ var DOM = {
|
|
|
635
650
|
if (this.once(el, "bind-debounce")) {
|
|
636
651
|
el.addEventListener("blur", () => {
|
|
637
652
|
clearTimeout(this.private(el, THROTTLED));
|
|
638
|
-
|
|
653
|
+
if (asyncFilter()) {
|
|
654
|
+
this.triggerCycle(el, DEBOUNCE_TRIGGER);
|
|
655
|
+
}
|
|
639
656
|
});
|
|
640
657
|
}
|
|
641
658
|
}
|
|
@@ -2741,7 +2758,7 @@ var DOMPatch = class {
|
|
|
2741
2758
|
transitionPendingRemoves() {
|
|
2742
2759
|
const { pendingRemoves, liveSocket } = this;
|
|
2743
2760
|
if (pendingRemoves.length > 0) {
|
|
2744
|
-
liveSocket.transitionRemoves(pendingRemoves, () => {
|
|
2761
|
+
liveSocket.transitionRemoves(pendingRemoves, this.view, () => {
|
|
2745
2762
|
pendingRemoves.forEach((el) => {
|
|
2746
2763
|
const child = dom_default.firstPhxChild(el);
|
|
2747
2764
|
if (child) {
|
|
@@ -3882,6 +3899,7 @@ var js_commands_default = (liveSocket, eventType) => {
|
|
|
3882
3899
|
});
|
|
3883
3900
|
},
|
|
3884
3901
|
navigate(href, opts = {}) {
|
|
3902
|
+
ensureSameOrigin(href, "navigate");
|
|
3885
3903
|
const customEvent = new CustomEvent("phx:exec");
|
|
3886
3904
|
liveSocket.historyRedirect(
|
|
3887
3905
|
customEvent,
|
|
@@ -3892,6 +3910,7 @@ var js_commands_default = (liveSocket, eventType) => {
|
|
|
3892
3910
|
);
|
|
3893
3911
|
},
|
|
3894
3912
|
patch(href, opts = {}) {
|
|
3913
|
+
ensureSameOrigin(href, "patch");
|
|
3895
3914
|
const customEvent = new CustomEvent("phx:exec");
|
|
3896
3915
|
liveSocket.pushHistoryPatch(
|
|
3897
3916
|
customEvent,
|
|
@@ -4431,6 +4450,7 @@ var View = class _View {
|
|
|
4431
4450
|
if (container) {
|
|
4432
4451
|
const [tag, attrs] = container;
|
|
4433
4452
|
this.el = dom_default.replaceRootContainer(this.el, tag, attrs);
|
|
4453
|
+
dom_default.putPrivate(this.el, "view", this);
|
|
4434
4454
|
}
|
|
4435
4455
|
this.childJoins = 0;
|
|
4436
4456
|
this.joinPending = true;
|
|
@@ -4513,6 +4533,7 @@ var View = class _View {
|
|
|
4513
4533
|
}
|
|
4514
4534
|
attachTrueDocEl() {
|
|
4515
4535
|
this.el = dom_default.byId(this.id);
|
|
4536
|
+
dom_default.putPrivate(this.el, "view", this);
|
|
4516
4537
|
this.el.setAttribute(PHX_ROOT_ID, this.root.id);
|
|
4517
4538
|
}
|
|
4518
4539
|
// this is invoked for dead and live views, so we must filter by
|
|
@@ -4696,6 +4717,7 @@ var View = class _View {
|
|
|
4696
4717
|
rootEl.setAttribute(PHX_SESSION, this.getSession());
|
|
4697
4718
|
rootEl.setAttribute(PHX_STATIC, this.getStatic());
|
|
4698
4719
|
rootEl.setAttribute(PHX_PARENT_ID, this.parent ? this.parent.id : null);
|
|
4720
|
+
dom_default.putPrivate(rootEl, "view", this);
|
|
4699
4721
|
const formsToRecover = (
|
|
4700
4722
|
// we go over all forms in the new DOM; because this is only the HTML for the current
|
|
4701
4723
|
// view, we can be sure that all forms are owned by this view:
|
|
@@ -5976,7 +5998,7 @@ var LiveSocket = class {
|
|
|
5976
5998
|
}
|
|
5977
5999
|
// public
|
|
5978
6000
|
version() {
|
|
5979
|
-
return "1.1.
|
|
6001
|
+
return "1.1.32";
|
|
5980
6002
|
}
|
|
5981
6003
|
isProfileEnabled() {
|
|
5982
6004
|
return this.sessionStorage.getItem(PHX_LV_PROFILE) === "true";
|
|
@@ -6255,11 +6277,12 @@ var LiveSocket = class {
|
|
|
6255
6277
|
`[${this.binding("remove")}]`
|
|
6256
6278
|
).filter((el) => !dom_default.isChildOfAny(el, stickies));
|
|
6257
6279
|
const newMainEl = dom_default.cloneNode(this.outgoingMainEl, "");
|
|
6258
|
-
this.main
|
|
6259
|
-
this.
|
|
6280
|
+
const oldMainView = this.main;
|
|
6281
|
+
oldMainView.showLoader(this.loaderTimeout);
|
|
6282
|
+
oldMainView.destroy();
|
|
6260
6283
|
this.main = this.newRootView(newMainEl, flash, liveReferer);
|
|
6261
6284
|
this.main.setRedirect(href);
|
|
6262
|
-
this.transitionRemoves(removeEls);
|
|
6285
|
+
this.transitionRemoves(removeEls, oldMainView);
|
|
6263
6286
|
this.main.join((joinCount, onDone) => {
|
|
6264
6287
|
if (joinCount === 1 && this.commitPendingLink(linkRef)) {
|
|
6265
6288
|
this.requestDOMUpdate(() => {
|
|
@@ -6273,7 +6296,7 @@ var LiveSocket = class {
|
|
|
6273
6296
|
}
|
|
6274
6297
|
});
|
|
6275
6298
|
}
|
|
6276
|
-
transitionRemoves(elements, callback) {
|
|
6299
|
+
transitionRemoves(elements, view, callback) {
|
|
6277
6300
|
const removeAttr = this.binding("remove");
|
|
6278
6301
|
const silenceEvents = (e) => {
|
|
6279
6302
|
e.preventDefault();
|
|
@@ -6283,7 +6306,8 @@ var LiveSocket = class {
|
|
|
6283
6306
|
for (const event of this.boundEventNames) {
|
|
6284
6307
|
el.addEventListener(event, silenceEvents, true);
|
|
6285
6308
|
}
|
|
6286
|
-
|
|
6309
|
+
const e = new CustomEvent("phx:exec", { detail: { sourceElement: el } });
|
|
6310
|
+
js_default.exec(e, "remove", el.getAttribute(removeAttr), view, el);
|
|
6287
6311
|
});
|
|
6288
6312
|
this.requestDOMUpdate(() => {
|
|
6289
6313
|
elements.forEach((el) => {
|
|
@@ -6306,7 +6330,7 @@ var LiveSocket = class {
|
|
|
6306
6330
|
let view;
|
|
6307
6331
|
const viewEl = dom_default.closestViewEl(childEl);
|
|
6308
6332
|
if (viewEl) {
|
|
6309
|
-
view =
|
|
6333
|
+
view = dom_default.private(viewEl, "view");
|
|
6310
6334
|
} else {
|
|
6311
6335
|
if (!childEl.isConnected) {
|
|
6312
6336
|
return null;
|