phoenix_live_view 1.1.2 → 1.1.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.
- package/assets/js/phoenix_live_view/live_socket.js +3 -0
- package/assets/js/phoenix_live_view/view.js +45 -12
- package/assets/js/phoenix_live_view/view_hook.ts +2 -2
- package/assets/js/types/live_socket.d.ts +4 -1
- package/assets/js/types/view.d.ts +8 -2
- package/assets/js/types/view_hook.d.ts +2 -2
- package/package.json +3 -3
- package/priv/static/phoenix_live_view.cjs.js +15 -4
- package/priv/static/phoenix_live_view.cjs.js.map +2 -2
- package/priv/static/phoenix_live_view.esm.js +15 -4
- package/priv/static/phoenix_live_view.esm.js.map +2 -2
- package/priv/static/phoenix_live_view.js +15 -4
- package/priv/static/phoenix_live_view.min.js +6 -6
|
@@ -82,6 +82,9 @@ export default class LiveSocket {
|
|
|
82
82
|
this.uploaders = opts.uploaders || {};
|
|
83
83
|
this.loaderTimeout = opts.loaderTimeout || LOADER_TIMEOUT;
|
|
84
84
|
this.disconnectedTimeout = opts.disconnectedTimeout || DISCONNECTED_TIMEOUT;
|
|
85
|
+
/**
|
|
86
|
+
* @type {ReturnType<typeof setTimeout> | null}
|
|
87
|
+
*/
|
|
85
88
|
this.reloadWithJitterTimer = null;
|
|
86
89
|
this.maxReloads = opts.maxReloads || MAX_RELOADS;
|
|
87
90
|
this.reloadJitterMin = opts.reloadJitterMin || RELOAD_JITTER_MIN;
|
|
@@ -192,7 +192,13 @@ export default class View {
|
|
|
192
192
|
this.ref = 0;
|
|
193
193
|
this.lastAckRef = null;
|
|
194
194
|
this.childJoins = 0;
|
|
195
|
+
/**
|
|
196
|
+
* @type {ReturnType<typeof setTimeout> | null}
|
|
197
|
+
*/
|
|
195
198
|
this.loaderTimer = null;
|
|
199
|
+
/**
|
|
200
|
+
* @type {ReturnType<typeof setTimeout> | null}
|
|
201
|
+
*/
|
|
196
202
|
this.disconnectedTimer = null;
|
|
197
203
|
this.pendingDiffs = [];
|
|
198
204
|
this.pendingForms = new Set();
|
|
@@ -2041,6 +2047,19 @@ export default class View {
|
|
|
2041
2047
|
}
|
|
2042
2048
|
|
|
2043
2049
|
getFormsForRecovery() {
|
|
2050
|
+
// Form recovery is complex in LiveView:
|
|
2051
|
+
// We want to support nested LiveViews and also provide a good user experience.
|
|
2052
|
+
// Therefore, when the channel rejoins, we copy all forms that are eligible for
|
|
2053
|
+
// recovery to be able to access them later.
|
|
2054
|
+
// Why do we need to copy them? Because when the main LiveView joins, any forms
|
|
2055
|
+
// in nested LiveViews would be lost.
|
|
2056
|
+
//
|
|
2057
|
+
// We should rework this in the future to serialize the form payload here
|
|
2058
|
+
// instead of cloning the DOM nodes, but making this work correctly is tedious,
|
|
2059
|
+
// as sending the correct form payload relies on JS.push to extract values
|
|
2060
|
+
// from JS commands (phx-change={JS.push("event", value: ..., target: ...)}),
|
|
2061
|
+
// as well as view.pushInput, which expects DOM elements.
|
|
2062
|
+
|
|
2044
2063
|
if (this.joinCount === 0) {
|
|
2045
2064
|
return {};
|
|
2046
2065
|
}
|
|
@@ -2055,19 +2074,33 @@ export default class View {
|
|
|
2055
2074
|
form.getAttribute(this.binding(PHX_AUTO_RECOVER)) !== "ignore",
|
|
2056
2075
|
)
|
|
2057
2076
|
.map((form) => {
|
|
2058
|
-
//
|
|
2059
|
-
|
|
2060
|
-
//
|
|
2061
|
-
//
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2077
|
+
// We need to clone the whole form, as relying on form.elements can lead to
|
|
2078
|
+
// situations where we have
|
|
2079
|
+
//
|
|
2080
|
+
// <form><fieldset disabled><input name="foo" value="bar"></fieldset></form>
|
|
2081
|
+
//
|
|
2082
|
+
// and form.elements returns both the fieldset and the input separately.
|
|
2083
|
+
// Because the fieldset is disabled, the input should NOT be sent though.
|
|
2084
|
+
// We can only reliably serialize the form by cloning it fully.
|
|
2085
|
+
const clonedForm = form.cloneNode(true);
|
|
2086
|
+
// we call morphdom to copy any special state
|
|
2087
|
+
// like the selected option of a <select> element;
|
|
2088
|
+
// any also copy over privates (which contain information about touched fields)
|
|
2089
|
+
morphdom(clonedForm, form, {
|
|
2090
|
+
onBeforeElUpdated: (fromEl, toEl) => {
|
|
2091
|
+
DOM.copyPrivates(fromEl, toEl);
|
|
2092
|
+
return true;
|
|
2093
|
+
},
|
|
2094
|
+
});
|
|
2095
|
+
// next up, we also need to clone any elements with form="id" parameter
|
|
2096
|
+
const externalElements = document.querySelectorAll(
|
|
2097
|
+
`[form="${form.id}"]`,
|
|
2098
|
+
);
|
|
2099
|
+
Array.from(externalElements).forEach((el) => {
|
|
2100
|
+
if (form.contains(el)) {
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2066
2103
|
const clonedEl = el.cloneNode(true);
|
|
2067
|
-
// we call morphdom to copy any special state
|
|
2068
|
-
// like the selected option of a <select> element;
|
|
2069
|
-
// this should be plenty fast as we call it on a small subset of the DOM,
|
|
2070
|
-
// single inputs or a select with children
|
|
2071
2104
|
morphdom(clonedEl, el);
|
|
2072
2105
|
DOM.copyPrivates(clonedEl, el);
|
|
2073
2106
|
clonedForm.appendChild(clonedEl);
|
|
@@ -154,7 +154,7 @@ export interface HookInterface {
|
|
|
154
154
|
uploadTo(selectorOrTarget: PhxTarget, name: any, files: any): any;
|
|
155
155
|
|
|
156
156
|
// allow unknown methods, as people can define them in their hooks
|
|
157
|
-
[key:
|
|
157
|
+
[key: PropertyKey]: any;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
// based on https://github.com/DefinitelyTyped/DefinitelyTyped/blob/fac1aa75acdddbf4f1a95e98ee2297b54ce4b4c9/types/phoenix_live_view/hooks.d.ts#L26
|
|
@@ -204,7 +204,7 @@ export interface Hook<T = object> {
|
|
|
204
204
|
reconnected?: (this: T & HookInterface) => void;
|
|
205
205
|
|
|
206
206
|
// Allow custom methods with any signature and custom properties
|
|
207
|
-
[key:
|
|
207
|
+
[key: PropertyKey]: any;
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
/**
|
|
@@ -23,7 +23,10 @@ export default class LiveSocket {
|
|
|
23
23
|
uploaders: any;
|
|
24
24
|
loaderTimeout: any;
|
|
25
25
|
disconnectedTimeout: any;
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* @type {ReturnType<typeof setTimeout> | null}
|
|
28
|
+
*/
|
|
29
|
+
reloadWithJitterTimer: ReturnType<typeof setTimeout> | null;
|
|
27
30
|
maxReloads: any;
|
|
28
31
|
reloadJitterMin: any;
|
|
29
32
|
reloadJitterMax: any;
|
|
@@ -12,8 +12,14 @@ export default class View {
|
|
|
12
12
|
ref: number;
|
|
13
13
|
lastAckRef: any;
|
|
14
14
|
childJoins: number;
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* @type {ReturnType<typeof setTimeout> | null}
|
|
17
|
+
*/
|
|
18
|
+
loaderTimer: ReturnType<typeof setTimeout> | null;
|
|
19
|
+
/**
|
|
20
|
+
* @type {ReturnType<typeof setTimeout> | null}
|
|
21
|
+
*/
|
|
22
|
+
disconnectedTimer: ReturnType<typeof setTimeout> | null;
|
|
17
23
|
pendingDiffs: any[];
|
|
18
24
|
pendingForms: Set<any>;
|
|
19
25
|
redirect: boolean;
|
|
@@ -128,7 +128,7 @@ export interface HookInterface {
|
|
|
128
128
|
* @param files - The files to upload.
|
|
129
129
|
*/
|
|
130
130
|
uploadTo(selectorOrTarget: PhxTarget, name: any, files: any): any;
|
|
131
|
-
[key:
|
|
131
|
+
[key: PropertyKey]: any;
|
|
132
132
|
}
|
|
133
133
|
export interface Hook<T = object> {
|
|
134
134
|
/**
|
|
@@ -168,7 +168,7 @@ export interface Hook<T = object> {
|
|
|
168
168
|
* Called when the element's parent LiveView has reconnected to the server.
|
|
169
169
|
*/
|
|
170
170
|
reconnected?: (this: T & HookInterface) => void;
|
|
171
|
-
[key:
|
|
171
|
+
[key: PropertyKey]: any;
|
|
172
172
|
}
|
|
173
173
|
/**
|
|
174
174
|
* Base class for LiveView hooks. Users extend this class to define their hooks.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phoenix_live_view",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "The Phoenix LiveView JavaScript client.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"jsdelivr": "./priv/static/phoenix_live_view.min.js",
|
|
11
11
|
"exports": {
|
|
12
12
|
"import": {
|
|
13
|
-
"
|
|
14
|
-
"
|
|
13
|
+
"types": "./assets/js/types/index.d.ts",
|
|
14
|
+
"default": "./priv/static/phoenix_live_view.esm.js"
|
|
15
15
|
},
|
|
16
16
|
"require": "./priv/static/phoenix_live_view.cjs.js"
|
|
17
17
|
},
|
|
@@ -5616,9 +5616,20 @@ var View = class _View {
|
|
|
5616
5616
|
return dom_default.all(this.el, `form[${phxChange}]`).filter((form) => form.id).filter((form) => form.elements.length > 0).filter(
|
|
5617
5617
|
(form) => form.getAttribute(this.binding(PHX_AUTO_RECOVER)) !== "ignore"
|
|
5618
5618
|
).map((form) => {
|
|
5619
|
-
const clonedForm = form.cloneNode(
|
|
5620
|
-
|
|
5621
|
-
|
|
5619
|
+
const clonedForm = form.cloneNode(true);
|
|
5620
|
+
morphdom_esm_default(clonedForm, form, {
|
|
5621
|
+
onBeforeElUpdated: (fromEl, toEl) => {
|
|
5622
|
+
dom_default.copyPrivates(fromEl, toEl);
|
|
5623
|
+
return true;
|
|
5624
|
+
}
|
|
5625
|
+
});
|
|
5626
|
+
const externalElements = document.querySelectorAll(
|
|
5627
|
+
`[form="${form.id}"]`
|
|
5628
|
+
);
|
|
5629
|
+
Array.from(externalElements).forEach((el) => {
|
|
5630
|
+
if (form.contains(el)) {
|
|
5631
|
+
return;
|
|
5632
|
+
}
|
|
5622
5633
|
const clonedEl = el.cloneNode(true);
|
|
5623
5634
|
morphdom_esm_default(clonedEl, el);
|
|
5624
5635
|
dom_default.copyPrivates(clonedEl, el);
|
|
@@ -5759,7 +5770,7 @@ var LiveSocket = class {
|
|
|
5759
5770
|
}
|
|
5760
5771
|
// public
|
|
5761
5772
|
version() {
|
|
5762
|
-
return "1.1.
|
|
5773
|
+
return "1.1.3";
|
|
5763
5774
|
}
|
|
5764
5775
|
isProfileEnabled() {
|
|
5765
5776
|
return this.sessionStorage.getItem(PHX_LV_PROFILE) === "true";
|