phoenix_live_view 1.2.0-rc.2 → 1.2.0
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/README.md +5 -5
- package/assets/js/phoenix_live_view/README.md +3 -0
- package/assets/js/phoenix_live_view/{aria.js → aria.ts} +18 -10
- package/assets/js/phoenix_live_view/{browser.js → browser.ts} +12 -8
- package/assets/js/phoenix_live_view/{dom.js → dom.ts} +107 -34
- package/assets/js/phoenix_live_view/{dom_patch.js → dom_patch.ts} +187 -124
- package/assets/js/phoenix_live_view/{dom_post_morph_restorer.js → dom_post_morph_restorer.ts} +17 -2
- package/assets/js/phoenix_live_view/{element_ref.js → element_ref.ts} +17 -11
- package/assets/js/phoenix_live_view/entry_uploader.js +4 -4
- package/assets/js/phoenix_live_view/{hooks.js → hooks.ts} +108 -91
- package/assets/js/phoenix_live_view/index.ts +14 -301
- package/assets/js/phoenix_live_view/js.js +2 -1
- package/assets/js/phoenix_live_view/js_commands.ts +12 -9
- package/assets/js/phoenix_live_view/{live_socket.js → live_socket.ts} +582 -114
- package/assets/js/phoenix_live_view/live_uploader.js +1 -1
- package/assets/js/phoenix_live_view/rendered.js +3 -0
- package/assets/js/phoenix_live_view/{utils.js → utils.ts} +35 -6
- package/assets/js/phoenix_live_view/{view.js → view.ts} +221 -110
- package/assets/js/phoenix_live_view/view_hook.ts +92 -32
- package/package.json +5 -2
- package/priv/static/phoenix_live_view.cjs.js +577 -314
- package/priv/static/phoenix_live_view.cjs.js.map +4 -4
- package/priv/static/phoenix_live_view.esm.js +577 -314
- package/priv/static/phoenix_live_view.esm.js.map +4 -4
- package/priv/static/phoenix_live_view.js +584 -314
- package/priv/static/phoenix_live_view.min.js +7 -7
- /package/assets/js/phoenix_live_view/{constants.js → constants.ts} +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Channel } from "phoenix";
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
BEFORE_UNLOAD_LOADER_TIMEOUT,
|
|
3
5
|
CHECKABLE_INPUTS,
|
|
@@ -55,7 +57,7 @@ import {
|
|
|
55
57
|
} from "./utils";
|
|
56
58
|
|
|
57
59
|
import Browser from "./browser";
|
|
58
|
-
import DOM from "./dom";
|
|
60
|
+
import DOM, { FormInputLike, QueryableNode } from "./dom";
|
|
59
61
|
import ElementRef from "./element_ref";
|
|
60
62
|
import DOMPatch from "./dom_patch";
|
|
61
63
|
import LiveUploader from "./live_uploader";
|
|
@@ -64,6 +66,7 @@ import { ViewHook } from "./view_hook";
|
|
|
64
66
|
import JS from "./js";
|
|
65
67
|
|
|
66
68
|
import morphdom from "morphdom";
|
|
69
|
+
import LiveSocket from "./live_socket";
|
|
67
70
|
|
|
68
71
|
export const prependFormDataKey = (key, prefix) => {
|
|
69
72
|
const isArray = key.endsWith("[]");
|
|
@@ -78,13 +81,52 @@ export const prependFormDataKey = (key, prefix) => {
|
|
|
78
81
|
return baseKey;
|
|
79
82
|
};
|
|
80
83
|
|
|
84
|
+
/** @internal */
|
|
81
85
|
export default class View {
|
|
82
86
|
static closestView(el) {
|
|
83
87
|
const liveViewEl = el.closest(PHX_VIEW_SELECTOR);
|
|
84
88
|
return liveViewEl ? DOM.private(liveViewEl, "view") : null;
|
|
85
89
|
}
|
|
86
90
|
|
|
87
|
-
|
|
91
|
+
liveSocket: LiveSocket;
|
|
92
|
+
id: string;
|
|
93
|
+
el: Element;
|
|
94
|
+
isDead: boolean;
|
|
95
|
+
root: View;
|
|
96
|
+
portalElementIds: Set<string>;
|
|
97
|
+
private channel: Channel;
|
|
98
|
+
private rendered: Rendered | null;
|
|
99
|
+
private flash: string | null;
|
|
100
|
+
private parent: View | null;
|
|
101
|
+
private ref: number;
|
|
102
|
+
private lastAckRef: number | null;
|
|
103
|
+
private childJoins: number;
|
|
104
|
+
private loaderTimer: ReturnType<typeof setTimeout> | null;
|
|
105
|
+
private disconnectedTimer: ReturnType<typeof setTimeout> | null;
|
|
106
|
+
private pendingDiffs: any[];
|
|
107
|
+
private redirect: boolean;
|
|
108
|
+
private href: string | null;
|
|
109
|
+
private joinCount: number;
|
|
110
|
+
private joinAttempts: number;
|
|
111
|
+
private joinPending: boolean;
|
|
112
|
+
private destroyed: boolean;
|
|
113
|
+
private joinCallback: (onDone?: () => void) => void;
|
|
114
|
+
private stopCallback: () => void;
|
|
115
|
+
private pendingJoinOps: any[];
|
|
116
|
+
private viewHooks: Record<string, ViewHook>;
|
|
117
|
+
private formSubmits: any[];
|
|
118
|
+
private children: Record<string, Record<string, View>> | null;
|
|
119
|
+
private pendingForms: Set<string>;
|
|
120
|
+
private formsForRecovery: Record<string, HTMLFormElement>;
|
|
121
|
+
|
|
122
|
+
constructor(
|
|
123
|
+
el: Element,
|
|
124
|
+
liveSocket: LiveSocket,
|
|
125
|
+
parentView: View | null,
|
|
126
|
+
flash: string | null = null,
|
|
127
|
+
liveReferer: string | null = null,
|
|
128
|
+
) {
|
|
129
|
+
this.rendered = null;
|
|
88
130
|
this.isDead = false;
|
|
89
131
|
this.liveSocket = liveSocket;
|
|
90
132
|
this.flash = flash;
|
|
@@ -146,7 +188,7 @@ export default class View {
|
|
|
146
188
|
this.viewHooks = {};
|
|
147
189
|
this.formSubmits = [];
|
|
148
190
|
this.children = this.parent ? null : {};
|
|
149
|
-
this.root.children[this.id] = {};
|
|
191
|
+
this.root.children![this.id] = {};
|
|
150
192
|
this.formsForRecovery = {};
|
|
151
193
|
this.channel = this.liveSocket.channel(`lv:${this.id}`, () => {
|
|
152
194
|
const url = this.href && this.expandURL(this.href);
|
|
@@ -156,7 +198,7 @@ export default class View {
|
|
|
156
198
|
params: this.connectParams(liveReferer),
|
|
157
199
|
session: this.getSession(),
|
|
158
200
|
static: this.getStatic(),
|
|
159
|
-
flash: this.flash,
|
|
201
|
+
flash: this.flash ?? undefined,
|
|
160
202
|
sticky: this.el.hasAttribute(PHX_STICKY),
|
|
161
203
|
};
|
|
162
204
|
});
|
|
@@ -179,7 +221,9 @@ export default class View {
|
|
|
179
221
|
connectParams(liveReferer) {
|
|
180
222
|
const params = this.liveSocket.params(this.el);
|
|
181
223
|
const manifest = DOM.all(document, `[${this.binding(PHX_TRACK_STATIC)}]`)
|
|
182
|
-
.map(
|
|
224
|
+
.map(
|
|
225
|
+
(node) => ("src" in node && node.src) || ("href" in node && node.href),
|
|
226
|
+
)
|
|
183
227
|
.filter((url) => typeof url === "string");
|
|
184
228
|
|
|
185
229
|
if (manifest.length > 0) {
|
|
@@ -187,21 +231,22 @@ export default class View {
|
|
|
187
231
|
}
|
|
188
232
|
params["_mounts"] = this.joinCount;
|
|
189
233
|
params["_mount_attempts"] = this.joinAttempts;
|
|
190
|
-
params["_live_referer"] = liveReferer;
|
|
234
|
+
params["_live_referer"] = liveReferer ?? undefined;
|
|
191
235
|
this.joinAttempts++;
|
|
192
236
|
|
|
193
237
|
return params;
|
|
194
238
|
}
|
|
195
239
|
|
|
196
240
|
isConnected() {
|
|
197
|
-
|
|
241
|
+
// TODO: canPush is private in phoenix
|
|
242
|
+
return (this.channel as any).canPush();
|
|
198
243
|
}
|
|
199
244
|
|
|
200
|
-
getSession() {
|
|
201
|
-
return this.el.getAttribute(PHX_SESSION)
|
|
245
|
+
getSession(): string {
|
|
246
|
+
return this.el.getAttribute(PHX_SESSION)!;
|
|
202
247
|
}
|
|
203
248
|
|
|
204
|
-
getStatic() {
|
|
249
|
+
getStatic(): string | null {
|
|
205
250
|
const val = this.el.getAttribute(PHX_STATIC);
|
|
206
251
|
return val === "" ? null : val;
|
|
207
252
|
}
|
|
@@ -211,11 +256,11 @@ export default class View {
|
|
|
211
256
|
this.destroyPortalElements();
|
|
212
257
|
this.destroyed = true;
|
|
213
258
|
DOM.deletePrivate(this.el, "view");
|
|
214
|
-
delete this.root.children[this.id];
|
|
259
|
+
delete this.root.children![this.id];
|
|
215
260
|
if (this.parent) {
|
|
216
|
-
delete this.root.children[this.parent.id][this.id];
|
|
261
|
+
delete this.root.children![this.parent.id][this.id];
|
|
217
262
|
}
|
|
218
|
-
clearTimeout(this.loaderTimer);
|
|
263
|
+
this.loaderTimer != null && clearTimeout(this.loaderTimer);
|
|
219
264
|
const onFinished = () => {
|
|
220
265
|
callback();
|
|
221
266
|
for (const id in this.viewHooks) {
|
|
@@ -244,8 +289,8 @@ export default class View {
|
|
|
244
289
|
this.el.classList.add(...classes);
|
|
245
290
|
}
|
|
246
291
|
|
|
247
|
-
showLoader(timeout) {
|
|
248
|
-
clearTimeout(this.loaderTimer);
|
|
292
|
+
showLoader(timeout?: number) {
|
|
293
|
+
this.loaderTimer != null && clearTimeout(this.loaderTimer);
|
|
249
294
|
if (timeout) {
|
|
250
295
|
this.loaderTimer = setTimeout(() => this.showLoader(), timeout);
|
|
251
296
|
} else {
|
|
@@ -258,13 +303,13 @@ export default class View {
|
|
|
258
303
|
|
|
259
304
|
execAll(binding) {
|
|
260
305
|
DOM.all(this.el, `[${binding}]`, (el) =>
|
|
261
|
-
this.liveSocket.execJS(el, el.getAttribute(binding)),
|
|
306
|
+
this.liveSocket.execJS(el, el.getAttribute(binding)!),
|
|
262
307
|
);
|
|
263
308
|
}
|
|
264
309
|
|
|
265
310
|
hideLoader() {
|
|
266
|
-
clearTimeout(this.loaderTimer);
|
|
267
|
-
clearTimeout(this.disconnectedTimer);
|
|
311
|
+
this.loaderTimer != null && clearTimeout(this.loaderTimer);
|
|
312
|
+
this.disconnectedTimer != null && clearTimeout(this.disconnectedTimer);
|
|
268
313
|
this.setContainerClasses(PHX_CONNECTED_CLASS);
|
|
269
314
|
this.execAll(this.binding("connected"));
|
|
270
315
|
}
|
|
@@ -289,7 +334,7 @@ export default class View {
|
|
|
289
334
|
// * a CID (Component ID), then we first search the component's element in the DOM
|
|
290
335
|
// * a selector, then we search the selector in the DOM and call the callback
|
|
291
336
|
// for each element found with the corresponding owner view
|
|
292
|
-
withinTargets(phxTarget, callback, dom = document) {
|
|
337
|
+
withinTargets(phxTarget, callback, dom: QueryableNode = document) {
|
|
293
338
|
// in the form recovery case we search in a template fragment instead of
|
|
294
339
|
// the real dom, therefore we optionally pass dom and viewEl
|
|
295
340
|
|
|
@@ -300,11 +345,14 @@ export default class View {
|
|
|
300
345
|
}
|
|
301
346
|
|
|
302
347
|
if (isCid(phxTarget)) {
|
|
303
|
-
const
|
|
304
|
-
if (
|
|
348
|
+
const target = DOM.findComponent(this.id, phxTarget, dom);
|
|
349
|
+
if (!target) {
|
|
305
350
|
logError(`no component found matching phx-target of ${phxTarget}`);
|
|
306
351
|
} else {
|
|
307
|
-
callback(
|
|
352
|
+
callback(
|
|
353
|
+
this,
|
|
354
|
+
typeof phxTarget === "number" ? phxTarget : parseInt(phxTarget),
|
|
355
|
+
);
|
|
308
356
|
}
|
|
309
357
|
} else {
|
|
310
358
|
const targets = Array.from(dom.querySelectorAll(phxTarget));
|
|
@@ -464,7 +512,11 @@ export default class View {
|
|
|
464
512
|
}
|
|
465
513
|
|
|
466
514
|
attachTrueDocEl() {
|
|
467
|
-
|
|
515
|
+
const el = DOM.byId(this.id);
|
|
516
|
+
if (!el) {
|
|
517
|
+
throw new Error("unable to find root element for view");
|
|
518
|
+
}
|
|
519
|
+
this.el = el;
|
|
468
520
|
this.el.setAttribute(PHX_ROOT_ID, this.root.id);
|
|
469
521
|
}
|
|
470
522
|
|
|
@@ -518,7 +570,7 @@ export default class View {
|
|
|
518
570
|
}
|
|
519
571
|
}
|
|
520
572
|
this.attachTrueDocEl();
|
|
521
|
-
const patch = new DOMPatch(this, this.el,
|
|
573
|
+
const patch = new DOMPatch(this, this.el, html, streams, null);
|
|
522
574
|
patch.markPrunableContentForRemoval();
|
|
523
575
|
this.performPatch(patch, false, true);
|
|
524
576
|
this.joinNewChildren();
|
|
@@ -570,13 +622,13 @@ export default class View {
|
|
|
570
622
|
}
|
|
571
623
|
|
|
572
624
|
performPatch(patch, pruneCids, isJoinPatch = false) {
|
|
573
|
-
const removedEls = [];
|
|
625
|
+
const removedEls: Array<Element> = [];
|
|
574
626
|
let phxChildrenAdded = false;
|
|
575
627
|
const updatedHookIds = new Set();
|
|
576
628
|
|
|
577
629
|
this.liveSocket.triggerDOM("onPatchStart", [patch.targetContainer]);
|
|
578
630
|
|
|
579
|
-
patch.
|
|
631
|
+
patch.afterAdded((el) => {
|
|
580
632
|
this.liveSocket.triggerDOM("onNodeAdded", [el]);
|
|
581
633
|
const phxViewportTop = this.binding(PHX_VIEWPORT_TOP);
|
|
582
634
|
const phxViewportBottom = this.binding(PHX_VIEWPORT_BOTTOM);
|
|
@@ -587,7 +639,7 @@ export default class View {
|
|
|
587
639
|
}
|
|
588
640
|
});
|
|
589
641
|
|
|
590
|
-
patch.
|
|
642
|
+
patch.afterPhxChildAdded((el) => {
|
|
591
643
|
if (DOM.isPhxSticky(el)) {
|
|
592
644
|
this.liveSocket.joinRootViews();
|
|
593
645
|
} else {
|
|
@@ -595,7 +647,7 @@ export default class View {
|
|
|
595
647
|
}
|
|
596
648
|
});
|
|
597
649
|
|
|
598
|
-
patch.
|
|
650
|
+
patch.beforeUpdated((fromEl, toEl) => {
|
|
599
651
|
const hook = this.triggerBeforeUpdateHook(fromEl, toEl);
|
|
600
652
|
if (hook) {
|
|
601
653
|
updatedHookIds.add(fromEl.id);
|
|
@@ -604,20 +656,20 @@ export default class View {
|
|
|
604
656
|
JS.onBeforeElUpdated(fromEl, toEl);
|
|
605
657
|
});
|
|
606
658
|
|
|
607
|
-
patch.
|
|
659
|
+
patch.afterUpdated((el) => {
|
|
608
660
|
if (updatedHookIds.has(el.id)) {
|
|
609
661
|
const hook = this.getHook(el);
|
|
610
662
|
hook && hook.__updated();
|
|
611
663
|
}
|
|
612
664
|
});
|
|
613
665
|
|
|
614
|
-
patch.
|
|
666
|
+
patch.afterDiscarded((el) => {
|
|
615
667
|
if (el.nodeType === Node.ELEMENT_NODE) {
|
|
616
668
|
removedEls.push(el);
|
|
617
669
|
}
|
|
618
670
|
});
|
|
619
671
|
|
|
620
|
-
patch.
|
|
672
|
+
patch.afterTransitionsDiscarded((els) =>
|
|
621
673
|
this.afterElementsRemoved(els, pruneCids),
|
|
622
674
|
);
|
|
623
675
|
patch.perform(isJoinPatch);
|
|
@@ -628,7 +680,7 @@ export default class View {
|
|
|
628
680
|
}
|
|
629
681
|
|
|
630
682
|
afterElementsRemoved(elements, pruneCids) {
|
|
631
|
-
const destroyedCIDs = [];
|
|
683
|
+
const destroyedCIDs: Array<number> = [];
|
|
632
684
|
elements.forEach((parent) => {
|
|
633
685
|
const components = DOM.all(
|
|
634
686
|
parent,
|
|
@@ -678,22 +730,29 @@ export default class View {
|
|
|
678
730
|
const template = document.createElement("template");
|
|
679
731
|
template.innerHTML = html;
|
|
680
732
|
|
|
733
|
+
if (!template.content.firstElementChild) {
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
|
|
681
737
|
// we special case <.portal> here and teleport it into our temporary DOM for recovery
|
|
682
738
|
// as we'd otherwise not find teleported forms
|
|
683
739
|
DOM.all(template.content, `[${PHX_PORTAL}]`).forEach((portalTemplate) => {
|
|
684
|
-
|
|
685
|
-
|
|
740
|
+
if (!(portalTemplate instanceof HTMLTemplateElement)) {
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
template.content.firstElementChild?.appendChild(
|
|
744
|
+
portalTemplate.content.firstElementChild!,
|
|
686
745
|
);
|
|
687
746
|
});
|
|
688
747
|
|
|
689
748
|
// because we work with a template element, we must manually copy the attributes
|
|
690
749
|
// otherwise the owner / target helpers don't work properly
|
|
691
|
-
const rootEl = template.content.firstElementChild
|
|
750
|
+
const rootEl = template.content.firstElementChild!;
|
|
692
751
|
rootEl.id = this.id;
|
|
693
752
|
rootEl.setAttribute(PHX_ROOT_ID, this.root.id);
|
|
694
753
|
rootEl.setAttribute(PHX_SESSION, this.getSession());
|
|
695
|
-
rootEl.setAttribute(PHX_STATIC, this.getStatic());
|
|
696
|
-
rootEl.setAttribute(PHX_PARENT_ID, this.parent
|
|
754
|
+
rootEl.setAttribute(PHX_STATIC, this.getStatic() ?? "");
|
|
755
|
+
this.parent && rootEl.setAttribute(PHX_PARENT_ID, this.parent.id);
|
|
697
756
|
|
|
698
757
|
// we go over all form elements in the new HTML for the LV
|
|
699
758
|
// and look for old forms in the `formsForRecovery` object;
|
|
@@ -701,7 +760,7 @@ export default class View {
|
|
|
701
760
|
const formsToRecover =
|
|
702
761
|
// we go over all forms in the new DOM; because this is only the HTML for the current
|
|
703
762
|
// view, we can be sure that all forms are owned by this view:
|
|
704
|
-
DOM.all(template.content, "form")
|
|
763
|
+
(DOM.all(template.content, "form") as HTMLFormElement[])
|
|
705
764
|
// only recover forms that have an id and are in the old DOM
|
|
706
765
|
.filter((newForm) => newForm.id && oldForms[newForm.id])
|
|
707
766
|
// abandon forms we already tried to recover to prevent looping a failed state
|
|
@@ -729,7 +788,7 @@ export default class View {
|
|
|
729
788
|
this.pushFormRecovery(
|
|
730
789
|
oldForm,
|
|
731
790
|
newForm,
|
|
732
|
-
template.content.firstElementChild
|
|
791
|
+
template.content.firstElementChild!,
|
|
733
792
|
() => {
|
|
734
793
|
this.pendingForms.delete(newForm.id);
|
|
735
794
|
// we only call the callback once all forms have been recovered
|
|
@@ -742,14 +801,16 @@ export default class View {
|
|
|
742
801
|
}
|
|
743
802
|
|
|
744
803
|
getChildById(id) {
|
|
745
|
-
return this.root.children[this.id][id];
|
|
804
|
+
return this.root.children![this.id][id];
|
|
746
805
|
}
|
|
747
806
|
|
|
748
807
|
getDescendentByEl(el) {
|
|
749
808
|
if (el.id === this.id) {
|
|
750
809
|
return this;
|
|
751
810
|
} else {
|
|
752
|
-
return
|
|
811
|
+
return (
|
|
812
|
+
this.children && this.children[el.getAttribute(PHX_PARENT_ID)]?.[el.id]
|
|
813
|
+
);
|
|
753
814
|
}
|
|
754
815
|
}
|
|
755
816
|
|
|
@@ -767,7 +828,7 @@ export default class View {
|
|
|
767
828
|
const child = this.getChildById(el.id);
|
|
768
829
|
if (!child) {
|
|
769
830
|
const view = new View(el, this.liveSocket, this);
|
|
770
|
-
this.root.children[this.id][view.id] = view;
|
|
831
|
+
this.root.children![this.id][view.id] = view;
|
|
771
832
|
view.join();
|
|
772
833
|
this.childJoins++;
|
|
773
834
|
return true;
|
|
@@ -818,22 +879,22 @@ export default class View {
|
|
|
818
879
|
return false;
|
|
819
880
|
}
|
|
820
881
|
|
|
821
|
-
this.rendered
|
|
882
|
+
this.rendered!.mergeDiff(diff);
|
|
822
883
|
let phxChildrenAdded = false;
|
|
823
884
|
|
|
824
885
|
// When the diff only contains component diffs, then walk components
|
|
825
886
|
// and patch only the parent component containers found in the diff.
|
|
826
887
|
// Otherwise, patch entire LV container.
|
|
827
|
-
if (this.rendered
|
|
888
|
+
if (this.rendered!.isComponentOnlyDiff(diff)) {
|
|
828
889
|
this.liveSocket.time("component patch complete", () => {
|
|
829
890
|
const parentCids = DOM.findExistingParentCIDs(
|
|
830
891
|
this.id,
|
|
831
|
-
this.rendered
|
|
892
|
+
this.rendered!.componentCIDs(diff),
|
|
832
893
|
);
|
|
833
894
|
parentCids.forEach((parentCID) => {
|
|
834
895
|
if (
|
|
835
896
|
this.componentPatch(
|
|
836
|
-
this.rendered
|
|
897
|
+
this.rendered!.getComponent(diff, parentCID),
|
|
837
898
|
parentCID,
|
|
838
899
|
)
|
|
839
900
|
) {
|
|
@@ -844,7 +905,7 @@ export default class View {
|
|
|
844
905
|
} else if (!isEmpty(diff)) {
|
|
845
906
|
this.liveSocket.time("full patch complete", () => {
|
|
846
907
|
const [html, streams] = this.renderContainer(diff, "update");
|
|
847
|
-
const patch = new DOMPatch(this, this.el,
|
|
908
|
+
const patch = new DOMPatch(this, this.el, html, streams, null);
|
|
848
909
|
phxChildrenAdded = this.performPatch(patch, true);
|
|
849
910
|
});
|
|
850
911
|
}
|
|
@@ -862,16 +923,16 @@ export default class View {
|
|
|
862
923
|
const tag = this.el.tagName;
|
|
863
924
|
// Don't skip any component in the diff nor any marked as pruned
|
|
864
925
|
// (as they may have been added back)
|
|
865
|
-
const cids = diff ? this.rendered
|
|
866
|
-
const { buffer: html, streams } = this.rendered
|
|
926
|
+
const cids = diff ? this.rendered!.componentCIDs(diff) : null;
|
|
927
|
+
const { buffer: html, streams } = this.rendered!.toString(cids);
|
|
867
928
|
return [`<${tag}>${html}</${tag}>`, streams];
|
|
868
929
|
});
|
|
869
930
|
}
|
|
870
931
|
|
|
871
932
|
componentPatch(diff, cid) {
|
|
872
933
|
if (isEmpty(diff)) return false;
|
|
873
|
-
const { buffer: html, streams } = this.rendered
|
|
874
|
-
const patch = new DOMPatch(this, this.el,
|
|
934
|
+
const { buffer: html, streams } = this.rendered!.componentToString(cid);
|
|
935
|
+
const patch = new DOMPatch(this, this.el, html, streams, cid);
|
|
875
936
|
const childrenAdded = this.performPatch(patch, true);
|
|
876
937
|
return childrenAdded;
|
|
877
938
|
}
|
|
@@ -984,7 +1045,7 @@ export default class View {
|
|
|
984
1045
|
}
|
|
985
1046
|
|
|
986
1047
|
eachChild(callback) {
|
|
987
|
-
const children = this.root.children[this.id] || {};
|
|
1048
|
+
const children = this.root.children![this.id] || {};
|
|
988
1049
|
for (const id in children) {
|
|
989
1050
|
callback(this.getChildById(id));
|
|
990
1051
|
}
|
|
@@ -1049,11 +1110,16 @@ export default class View {
|
|
|
1049
1110
|
: to;
|
|
1050
1111
|
}
|
|
1051
1112
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1113
|
+
onRedirect({
|
|
1114
|
+
to,
|
|
1115
|
+
flash,
|
|
1116
|
+
reloadToken,
|
|
1117
|
+
}: {
|
|
1118
|
+
to: string;
|
|
1119
|
+
flash?: string | null;
|
|
1120
|
+
reloadToken?: string;
|
|
1121
|
+
}) {
|
|
1122
|
+
this.liveSocket.redirect(to, flash ?? null, reloadToken ?? null);
|
|
1057
1123
|
}
|
|
1058
1124
|
|
|
1059
1125
|
isDestroyed() {
|
|
@@ -1064,12 +1130,7 @@ export default class View {
|
|
|
1064
1130
|
this.isDead = true;
|
|
1065
1131
|
}
|
|
1066
1132
|
|
|
1067
|
-
|
|
1068
|
-
this.joinPush = this.joinPush || this.channel.join();
|
|
1069
|
-
return this.joinPush;
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
join(callback) {
|
|
1133
|
+
join(callback?) {
|
|
1073
1134
|
this.showLoader(this.liveSocket.loaderTimeout);
|
|
1074
1135
|
this.bindChannel();
|
|
1075
1136
|
if (this.isMain()) {
|
|
@@ -1091,13 +1152,17 @@ export default class View {
|
|
|
1091
1152
|
}
|
|
1092
1153
|
|
|
1093
1154
|
onJoinError(resp) {
|
|
1155
|
+
if (resp.events) {
|
|
1156
|
+
this.liveSocket.dispatchEvents(resp.events);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1094
1159
|
if (resp.reason === "reload") {
|
|
1095
1160
|
this.log("error", () => [
|
|
1096
1161
|
`failed mount with ${resp.status}. Falling back to page reload`,
|
|
1097
1162
|
resp,
|
|
1098
1163
|
]);
|
|
1099
1164
|
this.onRedirect({
|
|
1100
|
-
to: this.liveSocket.main
|
|
1165
|
+
to: this.liveSocket.main!.href!,
|
|
1101
1166
|
reloadToken: resp.token,
|
|
1102
1167
|
});
|
|
1103
1168
|
return;
|
|
@@ -1106,7 +1171,7 @@ export default class View {
|
|
|
1106
1171
|
"unauthorized live_redirect. Falling back to page request",
|
|
1107
1172
|
resp,
|
|
1108
1173
|
]);
|
|
1109
|
-
this.onRedirect({ to: this.liveSocket.main
|
|
1174
|
+
this.onRedirect({ to: this.liveSocket.main!.href!, flash: this.flash });
|
|
1110
1175
|
return;
|
|
1111
1176
|
}
|
|
1112
1177
|
if (resp.redirect || resp.live_redirect) {
|
|
@@ -1230,7 +1295,11 @@ export default class View {
|
|
|
1230
1295
|
});
|
|
1231
1296
|
}
|
|
1232
1297
|
|
|
1233
|
-
pushWithReply(
|
|
1298
|
+
pushWithReply(
|
|
1299
|
+
refGenerator,
|
|
1300
|
+
event,
|
|
1301
|
+
payload,
|
|
1302
|
+
): Promise<{ resp: any; reply: any; ref: number | null }> {
|
|
1234
1303
|
if (!this.isConnected()) {
|
|
1235
1304
|
return Promise.reject(new Error("no connection"));
|
|
1236
1305
|
}
|
|
@@ -1303,7 +1372,7 @@ export default class View {
|
|
|
1303
1372
|
});
|
|
1304
1373
|
}
|
|
1305
1374
|
|
|
1306
|
-
undoRefs(ref, phxEvent, onlyEls) {
|
|
1375
|
+
undoRefs(ref, phxEvent, onlyEls?) {
|
|
1307
1376
|
if (!this.isConnected()) {
|
|
1308
1377
|
return;
|
|
1309
1378
|
} // exit if external form triggered
|
|
@@ -1332,7 +1401,7 @@ export default class View {
|
|
|
1332
1401
|
elRef.maybeUndo(ref, phxEvent, (clonedTree) => {
|
|
1333
1402
|
// we need to perform a full patch on unlocked elements
|
|
1334
1403
|
// to perform all the necessary logic (like calling updated for hooks, etc.)
|
|
1335
|
-
const patch = new DOMPatch(this, el,
|
|
1404
|
+
const patch = new DOMPatch(this, el, clonedTree, new Set(), null, {
|
|
1336
1405
|
undoRef: ref,
|
|
1337
1406
|
});
|
|
1338
1407
|
const phxChildrenAdded = this.performPatch(patch, true);
|
|
@@ -1349,7 +1418,12 @@ export default class View {
|
|
|
1349
1418
|
return this.el.id;
|
|
1350
1419
|
}
|
|
1351
1420
|
|
|
1352
|
-
putRef(
|
|
1421
|
+
putRef(
|
|
1422
|
+
elements: Array<{ el: Element; lock: boolean; loading: boolean }>,
|
|
1423
|
+
phxEvent: string | null,
|
|
1424
|
+
eventType: string,
|
|
1425
|
+
opts: { [key: string]: any } = {},
|
|
1426
|
+
) {
|
|
1353
1427
|
const newRef = this.ref++;
|
|
1354
1428
|
const disableWith = this.binding(PHX_DISABLE_WITH);
|
|
1355
1429
|
if (opts.loading) {
|
|
@@ -1365,10 +1439,10 @@ export default class View {
|
|
|
1365
1439
|
}
|
|
1366
1440
|
el.setAttribute(PHX_REF_SRC, this.refSrc());
|
|
1367
1441
|
if (loading) {
|
|
1368
|
-
el.setAttribute(PHX_REF_LOADING, newRef);
|
|
1442
|
+
el.setAttribute(PHX_REF_LOADING, newRef.toString());
|
|
1369
1443
|
}
|
|
1370
1444
|
if (lock) {
|
|
1371
|
-
el.setAttribute(PHX_REF_LOCK, newRef);
|
|
1445
|
+
el.setAttribute(PHX_REF_LOCK, newRef.toString());
|
|
1372
1446
|
}
|
|
1373
1447
|
|
|
1374
1448
|
if (
|
|
@@ -1396,7 +1470,7 @@ export default class View {
|
|
|
1396
1470
|
const disableText = el.getAttribute(disableWith);
|
|
1397
1471
|
if (disableText !== null) {
|
|
1398
1472
|
if (!el.getAttribute(PHX_DISABLE_WITH_RESTORE)) {
|
|
1399
|
-
el.setAttribute(PHX_DISABLE_WITH_RESTORE, el.textContent);
|
|
1473
|
+
el.setAttribute(PHX_DISABLE_WITH_RESTORE, el.textContent || "");
|
|
1400
1474
|
}
|
|
1401
1475
|
if (disableText !== "") {
|
|
1402
1476
|
el.textContent = disableText;
|
|
@@ -1404,7 +1478,8 @@ export default class View {
|
|
|
1404
1478
|
// PHX_DISABLED could have already been set in disableForm
|
|
1405
1479
|
el.setAttribute(
|
|
1406
1480
|
PHX_DISABLED,
|
|
1407
|
-
el.getAttribute(PHX_DISABLED) ||
|
|
1481
|
+
el.getAttribute(PHX_DISABLED) ||
|
|
1482
|
+
("disabled" in el ? String(el.disabled) : ""),
|
|
1408
1483
|
);
|
|
1409
1484
|
el.setAttribute("disabled", "");
|
|
1410
1485
|
}
|
|
@@ -1473,12 +1548,12 @@ export default class View {
|
|
|
1473
1548
|
return this.lastAckRef !== null && this.lastAckRef >= ref;
|
|
1474
1549
|
}
|
|
1475
1550
|
|
|
1476
|
-
componentID(el) {
|
|
1551
|
+
componentID(el): number | null {
|
|
1477
1552
|
const cid = el.getAttribute && el.getAttribute(PHX_COMPONENT);
|
|
1478
1553
|
return cid ? parseInt(cid) : null;
|
|
1479
1554
|
}
|
|
1480
1555
|
|
|
1481
|
-
targetComponentID(target, targetCtx, opts = {}) {
|
|
1556
|
+
targetComponentID(target, targetCtx, opts: { [key: string]: any } = {}) {
|
|
1482
1557
|
if (isCid(targetCtx)) {
|
|
1483
1558
|
return targetCtx;
|
|
1484
1559
|
}
|
|
@@ -1486,7 +1561,9 @@ export default class View {
|
|
|
1486
1561
|
const cidOrSelector =
|
|
1487
1562
|
opts.target || target.getAttribute(this.binding("target"));
|
|
1488
1563
|
if (isCid(cidOrSelector)) {
|
|
1489
|
-
return
|
|
1564
|
+
return typeof cidOrSelector === "number"
|
|
1565
|
+
? cidOrSelector
|
|
1566
|
+
: parseInt(cidOrSelector);
|
|
1490
1567
|
} else if (targetCtx && (cidOrSelector !== null || opts.target)) {
|
|
1491
1568
|
return this.closestComponentID(targetCtx);
|
|
1492
1569
|
} else {
|
|
@@ -1521,7 +1598,12 @@ export default class View {
|
|
|
1521
1598
|
}
|
|
1522
1599
|
}
|
|
1523
1600
|
|
|
1524
|
-
pushHookEvent(
|
|
1601
|
+
pushHookEvent(
|
|
1602
|
+
el,
|
|
1603
|
+
targetCtx,
|
|
1604
|
+
event,
|
|
1605
|
+
payload,
|
|
1606
|
+
): Promise<{ reply: any; ref: number }> {
|
|
1525
1607
|
if (!this.isConnected()) {
|
|
1526
1608
|
this.log("hook", () => [
|
|
1527
1609
|
"unable to push hook event. LiveView not connected",
|
|
@@ -1544,7 +1626,10 @@ export default class View {
|
|
|
1544
1626
|
event: event,
|
|
1545
1627
|
value: payload,
|
|
1546
1628
|
cid: this.closestComponentID(targetCtx),
|
|
1547
|
-
}).then(
|
|
1629
|
+
}).then(
|
|
1630
|
+
({ resp: _resp, reply, ref }) =>
|
|
1631
|
+
({ reply, ref }) as { reply: any; ref: number },
|
|
1632
|
+
);
|
|
1548
1633
|
}
|
|
1549
1634
|
|
|
1550
1635
|
extractMeta(el, meta, value) {
|
|
@@ -1583,7 +1668,11 @@ export default class View {
|
|
|
1583
1668
|
return meta;
|
|
1584
1669
|
}
|
|
1585
1670
|
|
|
1586
|
-
serializeForm(
|
|
1671
|
+
serializeForm(
|
|
1672
|
+
form: HTMLFormElement,
|
|
1673
|
+
opts: { [key: string]: any } = {},
|
|
1674
|
+
onlyNames: string[] = [],
|
|
1675
|
+
) {
|
|
1587
1676
|
const { submitter } = opts;
|
|
1588
1677
|
|
|
1589
1678
|
// We must inject the submitter in the order that it exists in the DOM
|
|
@@ -1605,7 +1694,7 @@ export default class View {
|
|
|
1605
1694
|
}
|
|
1606
1695
|
|
|
1607
1696
|
const formData = new FormData(form);
|
|
1608
|
-
const toRemove = [];
|
|
1697
|
+
const toRemove: string[] = [];
|
|
1609
1698
|
|
|
1610
1699
|
formData.forEach((val, key, _index) => {
|
|
1611
1700
|
if (val instanceof File) {
|
|
@@ -1620,6 +1709,10 @@ export default class View {
|
|
|
1620
1709
|
|
|
1621
1710
|
const { inputsUnused, onlyHiddenInputs } = Array.from(form.elements).reduce(
|
|
1622
1711
|
(acc, input) => {
|
|
1712
|
+
if (!DOM.isFormAssociated(input)) {
|
|
1713
|
+
return acc;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1623
1716
|
const { inputsUnused, onlyHiddenInputs } = acc;
|
|
1624
1717
|
const key = input.name;
|
|
1625
1718
|
if (!key) {
|
|
@@ -1684,9 +1777,17 @@ export default class View {
|
|
|
1684
1777
|
return params.toString();
|
|
1685
1778
|
}
|
|
1686
1779
|
|
|
1687
|
-
pushEvent(
|
|
1780
|
+
pushEvent(
|
|
1781
|
+
type,
|
|
1782
|
+
el,
|
|
1783
|
+
targetCtx,
|
|
1784
|
+
phxEvent,
|
|
1785
|
+
meta,
|
|
1786
|
+
opts: { [key: string]: any } = {},
|
|
1787
|
+
onReply?,
|
|
1788
|
+
) {
|
|
1688
1789
|
this.pushWithReply(
|
|
1689
|
-
(maybePayload) =>
|
|
1790
|
+
(maybePayload?) =>
|
|
1690
1791
|
this.putRef([{ el, loading: true, lock: true }], phxEvent, type, {
|
|
1691
1792
|
...opts,
|
|
1692
1793
|
payload: maybePayload?.payload,
|
|
@@ -1718,7 +1819,7 @@ export default class View {
|
|
|
1718
1819
|
});
|
|
1719
1820
|
}
|
|
1720
1821
|
|
|
1721
|
-
pushInput(inputEl, targetCtx, forceCid, phxEvent, opts, callback) {
|
|
1822
|
+
pushInput(inputEl, targetCtx, forceCid, phxEvent, opts, callback?) {
|
|
1722
1823
|
if (!inputEl.form) {
|
|
1723
1824
|
throw new Error("form events require the input to be inside a form");
|
|
1724
1825
|
}
|
|
@@ -1727,7 +1828,7 @@ export default class View {
|
|
|
1727
1828
|
const cid = isCid(forceCid)
|
|
1728
1829
|
? forceCid
|
|
1729
1830
|
: this.targetComponentID(inputEl.form, targetCtx, opts);
|
|
1730
|
-
const refGenerator = (maybePayload) => {
|
|
1831
|
+
const refGenerator = (maybePayload?) => {
|
|
1731
1832
|
return this.putRef(
|
|
1732
1833
|
[
|
|
1733
1834
|
{ el: inputEl, loading: true, lock: true },
|
|
@@ -1740,7 +1841,7 @@ export default class View {
|
|
|
1740
1841
|
};
|
|
1741
1842
|
let formData;
|
|
1742
1843
|
const meta = this.extractMeta(inputEl.form, {}, opts.value);
|
|
1743
|
-
const serializeOpts = {};
|
|
1844
|
+
const serializeOpts: { submitter?: HTMLButtonElement } = {};
|
|
1744
1845
|
if (inputEl instanceof HTMLButtonElement) {
|
|
1745
1846
|
serializeOpts.submitter = inputEl;
|
|
1746
1847
|
}
|
|
@@ -1841,7 +1942,7 @@ export default class View {
|
|
|
1841
1942
|
);
|
|
1842
1943
|
}
|
|
1843
1944
|
|
|
1844
|
-
disableForm(formEl, phxEvent, opts = {}) {
|
|
1945
|
+
disableForm(formEl: HTMLFormElement, phxEvent: string, opts = {}) {
|
|
1845
1946
|
const filterIgnored = (el) => {
|
|
1846
1947
|
const userIgnored = closestPhxBinding(
|
|
1847
1948
|
el,
|
|
@@ -1855,10 +1956,11 @@ export default class View {
|
|
|
1855
1956
|
const filterDisables = (el) => {
|
|
1856
1957
|
return el.hasAttribute(this.binding(PHX_DISABLE_WITH));
|
|
1857
1958
|
};
|
|
1858
|
-
const filterButton = (el)
|
|
1959
|
+
const filterButton = (el): el is HTMLButtonElement =>
|
|
1960
|
+
el.tagName == "BUTTON";
|
|
1859
1961
|
|
|
1860
|
-
const filterInput = (el) =>
|
|
1861
|
-
["INPUT", "TEXTAREA"
|
|
1962
|
+
const filterInput = (el): el is HTMLInputElement | HTMLTextAreaElement =>
|
|
1963
|
+
["INPUT", "TEXTAREA"].includes(el.tagName);
|
|
1862
1964
|
|
|
1863
1965
|
const formElements = Array.from(formEl.elements);
|
|
1864
1966
|
const disables = formElements.filter(filterDisables);
|
|
@@ -1866,14 +1968,14 @@ export default class View {
|
|
|
1866
1968
|
const inputs = formElements.filter(filterInput).filter(filterIgnored);
|
|
1867
1969
|
|
|
1868
1970
|
buttons.forEach((button) => {
|
|
1869
|
-
button.setAttribute(PHX_DISABLED, button.disabled);
|
|
1971
|
+
button.setAttribute(PHX_DISABLED, button.disabled.toString());
|
|
1870
1972
|
button.disabled = true;
|
|
1871
1973
|
});
|
|
1872
1974
|
inputs.forEach((input) => {
|
|
1873
|
-
input.setAttribute(PHX_READONLY, input.readOnly);
|
|
1975
|
+
input.setAttribute(PHX_READONLY, input.readOnly.toString());
|
|
1874
1976
|
input.readOnly = true;
|
|
1875
|
-
if (input.files) {
|
|
1876
|
-
input.setAttribute(PHX_DISABLED, input.disabled);
|
|
1977
|
+
if (input instanceof HTMLInputElement && input.files) {
|
|
1978
|
+
input.setAttribute(PHX_DISABLED, input.disabled.toString());
|
|
1877
1979
|
input.disabled = true;
|
|
1878
1980
|
}
|
|
1879
1981
|
});
|
|
@@ -1886,14 +1988,15 @@ export default class View {
|
|
|
1886
1988
|
|
|
1887
1989
|
// we reverse the order so form children are already locked by the time
|
|
1888
1990
|
// the form is locked
|
|
1889
|
-
const els = [
|
|
1890
|
-
|
|
1891
|
-
|
|
1991
|
+
const els = [
|
|
1992
|
+
{ el: formEl, loading: true, lock: false },
|
|
1993
|
+
...formEls,
|
|
1994
|
+
].reverse();
|
|
1892
1995
|
return this.putRef(els, phxEvent, "submit", opts);
|
|
1893
1996
|
}
|
|
1894
1997
|
|
|
1895
1998
|
pushFormSubmit(formEl, targetCtx, phxEvent, submitter, opts, onReply) {
|
|
1896
|
-
const refGenerator = (maybePayload) =>
|
|
1999
|
+
const refGenerator = (maybePayload?) =>
|
|
1897
2000
|
this.disableForm(formEl, phxEvent, {
|
|
1898
2001
|
...opts,
|
|
1899
2002
|
form: formEl,
|
|
@@ -2058,7 +2161,7 @@ export default class View {
|
|
|
2058
2161
|
|
|
2059
2162
|
targetCtxElement(targetCtx) {
|
|
2060
2163
|
if (isCid(targetCtx)) {
|
|
2061
|
-
const
|
|
2164
|
+
const target = DOM.findComponent(this.id, targetCtx);
|
|
2062
2165
|
return target;
|
|
2063
2166
|
} else if (targetCtx) {
|
|
2064
2167
|
return targetCtx;
|
|
@@ -2067,7 +2170,12 @@ export default class View {
|
|
|
2067
2170
|
}
|
|
2068
2171
|
}
|
|
2069
2172
|
|
|
2070
|
-
pushFormRecovery(
|
|
2173
|
+
pushFormRecovery(
|
|
2174
|
+
oldForm: HTMLFormElement,
|
|
2175
|
+
newForm: HTMLFormElement,
|
|
2176
|
+
templateDom: Element | DocumentFragment,
|
|
2177
|
+
callback: () => void,
|
|
2178
|
+
) {
|
|
2071
2179
|
// we are only recovering forms inside the current view, therefore it is safe to
|
|
2072
2180
|
// skip withinOwners here and always use this when referring to the view
|
|
2073
2181
|
const phxChange = this.binding("change");
|
|
@@ -2076,8 +2184,9 @@ export default class View {
|
|
|
2076
2184
|
newForm.getAttribute(this.binding(PHX_AUTO_RECOVER)) ||
|
|
2077
2185
|
newForm.getAttribute(this.binding("change"));
|
|
2078
2186
|
const inputs = Array.from(oldForm.elements).filter(
|
|
2079
|
-
(el) =>
|
|
2080
|
-
|
|
2187
|
+
(el) =>
|
|
2188
|
+
DOM.isFormAssociated(el) && el.name && !el.hasAttribute(phxChange),
|
|
2189
|
+
) as Array<FormInputLike>;
|
|
2081
2190
|
if (inputs.length === 0) {
|
|
2082
2191
|
callback();
|
|
2083
2192
|
return;
|
|
@@ -2137,7 +2246,8 @@ export default class View {
|
|
|
2137
2246
|
"click",
|
|
2138
2247
|
)
|
|
2139
2248
|
: null;
|
|
2140
|
-
const fallback = () =>
|
|
2249
|
+
const fallback = () =>
|
|
2250
|
+
this.liveSocket.redirect(window.location.href, null, null);
|
|
2141
2251
|
const url = href.startsWith("/")
|
|
2142
2252
|
? `${location.protocol}//${location.host}${href}`
|
|
2143
2253
|
: href;
|
|
@@ -2187,6 +2297,7 @@ export default class View {
|
|
|
2187
2297
|
document,
|
|
2188
2298
|
`#${CSS.escape(this.id)} form[${phxChange}], [${PHX_TELEPORTED_REF}="${CSS.escape(this.id)}"] form[${phxChange}]`,
|
|
2189
2299
|
)
|
|
2300
|
+
.filter((form) => form instanceof HTMLFormElement)
|
|
2190
2301
|
.filter((form) => form.id)
|
|
2191
2302
|
.filter((form) => form.elements.length > 0)
|
|
2192
2303
|
.filter(
|
|
@@ -2202,14 +2313,14 @@ export default class View {
|
|
|
2202
2313
|
// and form.elements returns both the fieldset and the input separately.
|
|
2203
2314
|
// Because the fieldset is disabled, the input should NOT be sent though.
|
|
2204
2315
|
// We can only reliably serialize the form by cloning it fully.
|
|
2205
|
-
const clonedForm = form.cloneNode(true);
|
|
2316
|
+
const clonedForm = form.cloneNode(true) as HTMLFormElement;
|
|
2206
2317
|
// we call morphdom to copy any special state
|
|
2207
2318
|
// like the selected option of a <select> element;
|
|
2208
2319
|
// any also copy over privates (which contain information about touched fields)
|
|
2209
2320
|
morphdom(clonedForm, form, {
|
|
2210
2321
|
onBeforeElUpdated: (fromEl, toEl) => {
|
|
2211
2322
|
DOM.copyPrivates(fromEl, toEl);
|
|
2212
|
-
if (fromEl.getAttribute("form") === form.id) {
|
|
2323
|
+
if (fromEl.getAttribute("form") === form.id && fromEl.parentNode) {
|
|
2213
2324
|
// In case the form contains an element with form="id" pointing
|
|
2214
2325
|
// to the form itself, firefox still associates the element with the
|
|
2215
2326
|
// original form element. This is not fixed by removing the parameter,
|
|
@@ -2227,7 +2338,7 @@ export default class View {
|
|
|
2227
2338
|
`[form="${CSS.escape(form.id)}"]`,
|
|
2228
2339
|
);
|
|
2229
2340
|
Array.from(externalElements).forEach((el) => {
|
|
2230
|
-
const clonedEl =
|
|
2341
|
+
const clonedEl = el.cloneNode(true) as Element;
|
|
2231
2342
|
morphdom(clonedEl, el);
|
|
2232
2343
|
DOM.copyPrivates(clonedEl, el);
|
|
2233
2344
|
// See https://github.com/phoenixframework/phoenix_live_view/issues/4021
|
|
@@ -2244,7 +2355,7 @@ export default class View {
|
|
|
2244
2355
|
|
|
2245
2356
|
maybePushComponentsDestroyed(destroyedCIDs) {
|
|
2246
2357
|
let willDestroyCIDs = destroyedCIDs.filter((cid) => {
|
|
2247
|
-
return DOM.
|
|
2358
|
+
return DOM.findComponent(this.id, cid) === null;
|
|
2248
2359
|
});
|
|
2249
2360
|
|
|
2250
2361
|
const onError = (error) => {
|
|
@@ -2256,7 +2367,7 @@ export default class View {
|
|
|
2256
2367
|
if (willDestroyCIDs.length > 0) {
|
|
2257
2368
|
// we must reset the render change tracking for cids that
|
|
2258
2369
|
// could be added back from the server so we don't skip them
|
|
2259
|
-
willDestroyCIDs.forEach((cid) => this.rendered
|
|
2370
|
+
willDestroyCIDs.forEach((cid) => this.rendered!.resetRender(cid));
|
|
2260
2371
|
|
|
2261
2372
|
this.pushWithReply(null, "cids_will_destroy", { cids: willDestroyCIDs })
|
|
2262
2373
|
.then(() => {
|
|
@@ -2266,7 +2377,7 @@ export default class View {
|
|
|
2266
2377
|
// See if any of the cids we wanted to destroy were added back,
|
|
2267
2378
|
// if they were added back, we don't actually destroy them.
|
|
2268
2379
|
let completelyDestroyCIDs = willDestroyCIDs.filter((cid) => {
|
|
2269
|
-
return DOM.
|
|
2380
|
+
return DOM.findComponent(this.id, cid) === null;
|
|
2270
2381
|
});
|
|
2271
2382
|
|
|
2272
2383
|
if (completelyDestroyCIDs.length > 0) {
|
|
@@ -2274,7 +2385,7 @@ export default class View {
|
|
|
2274
2385
|
cids: completelyDestroyCIDs,
|
|
2275
2386
|
})
|
|
2276
2387
|
.then(({ resp }) => {
|
|
2277
|
-
this.rendered
|
|
2388
|
+
this.rendered!.pruneCIDs(resp.cids);
|
|
2278
2389
|
})
|
|
2279
2390
|
.catch(onError);
|
|
2280
2391
|
}
|
|
@@ -2297,7 +2408,7 @@ export default class View {
|
|
|
2297
2408
|
DOM.putPrivate(form, PHX_HAS_SUBMITTED, true);
|
|
2298
2409
|
const inputs = Array.from(form.elements);
|
|
2299
2410
|
inputs.forEach((input) => DOM.putPrivate(input, PHX_HAS_SUBMITTED, true));
|
|
2300
|
-
this.liveSocket.blurActiveElement(
|
|
2411
|
+
this.liveSocket.blurActiveElement();
|
|
2301
2412
|
this.pushFormSubmit(form, targetCtx, phxEvent, submitter, opts, () => {
|
|
2302
2413
|
this.liveSocket.restorePreviouslyActiveFocus();
|
|
2303
2414
|
});
|