phoenix_live_view 0.16.3 → 0.17.2
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/CHANGELOG.md +128 -7
- package/assets/js/phoenix_live_view/constants.js +2 -2
- package/assets/js/phoenix_live_view/dom.js +49 -13
- package/assets/js/phoenix_live_view/dom_patch.js +31 -11
- package/assets/js/phoenix_live_view/js.js +169 -0
- package/assets/js/phoenix_live_view/live_socket.js +143 -50
- package/assets/js/phoenix_live_view/view.js +113 -87
- package/assets/js/phoenix_live_view/view_hook.js +2 -2
- package/package.json +5 -3
- package/priv/static/phoenix_live_view.cjs.js +3737 -0
- package/priv/static/phoenix_live_view.cjs.js.map +7 -0
- package/priv/static/phoenix_live_view.esm.js +495 -167
- package/priv/static/phoenix_live_view.esm.js.map +3 -3
- package/priv/static/phoenix_live_view.js +495 -167
- package/priv/static/phoenix_live_view.min.js +6 -6
|
@@ -90,8 +90,8 @@ import {
|
|
|
90
90
|
PHX_ROOT_ID,
|
|
91
91
|
PHX_THROTTLE,
|
|
92
92
|
PHX_TRACK_UPLOADS,
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
PHX_SESSION,
|
|
94
|
+
RELOAD_JITTER,
|
|
95
95
|
} from "./constants"
|
|
96
96
|
|
|
97
97
|
import {
|
|
@@ -99,6 +99,7 @@ import {
|
|
|
99
99
|
closestPhxBinding,
|
|
100
100
|
closure,
|
|
101
101
|
debug,
|
|
102
|
+
isObject,
|
|
102
103
|
maybe
|
|
103
104
|
} from "./utils"
|
|
104
105
|
|
|
@@ -107,7 +108,7 @@ import DOM from "./dom"
|
|
|
107
108
|
import Hooks from "./hooks"
|
|
108
109
|
import LiveUploader from "./live_uploader"
|
|
109
110
|
import View from "./view"
|
|
110
|
-
import
|
|
111
|
+
import JS from "./js"
|
|
111
112
|
|
|
112
113
|
export default class LiveSocket {
|
|
113
114
|
constructor(url, phxSocket, opts = {}){
|
|
@@ -144,6 +145,7 @@ export default class LiveSocket {
|
|
|
144
145
|
this.sessionStorage = opts.sessionStorage || window.sessionStorage
|
|
145
146
|
this.boundTopLevelEvents = false
|
|
146
147
|
this.domCallbacks = Object.assign({onNodeAdded: closure(), onBeforeElUpdated: closure()}, opts.dom || {})
|
|
148
|
+
this.transitions = new TransitionSet()
|
|
147
149
|
window.addEventListener("pagehide", _e => {
|
|
148
150
|
this.unloaded = true
|
|
149
151
|
})
|
|
@@ -200,6 +202,10 @@ export default class LiveSocket {
|
|
|
200
202
|
|
|
201
203
|
disconnect(callback){ this.socket.disconnect(callback) }
|
|
202
204
|
|
|
205
|
+
execJS(el, encodedJS, eventType = null){
|
|
206
|
+
this.owner(el, view => JS.exec(eventType, encodedJS, view, el))
|
|
207
|
+
}
|
|
208
|
+
|
|
203
209
|
// private
|
|
204
210
|
|
|
205
211
|
triggerDOM(kind, args){ this.domCallbacks[kind](...args) }
|
|
@@ -222,6 +228,14 @@ export default class LiveSocket {
|
|
|
222
228
|
}
|
|
223
229
|
}
|
|
224
230
|
|
|
231
|
+
requestDOMUpdate(callback){
|
|
232
|
+
this.transitions.after(callback)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
transition(time, onDone = function(){}){
|
|
236
|
+
this.transitions.addTransition(time, onDone)
|
|
237
|
+
}
|
|
238
|
+
|
|
225
239
|
onChannel(channel, event, cb){
|
|
226
240
|
channel.on(event, data => {
|
|
227
241
|
let latency = this.getLatencySim()
|
|
@@ -324,10 +338,24 @@ export default class LiveSocket {
|
|
|
324
338
|
|
|
325
339
|
this.main = this.newRootView(newMainEl, flash)
|
|
326
340
|
this.main.setRedirect(href)
|
|
327
|
-
this.
|
|
341
|
+
this.transitionRemoves()
|
|
342
|
+
this.main.join((joinCount, onDone) => {
|
|
328
343
|
if(joinCount === 1 && this.commitPendingLink(linkRef)){
|
|
329
|
-
|
|
330
|
-
|
|
344
|
+
this.requestDOMUpdate(() => {
|
|
345
|
+
oldMainEl.replaceWith(newMainEl)
|
|
346
|
+
callback && callback()
|
|
347
|
+
onDone()
|
|
348
|
+
})
|
|
349
|
+
}
|
|
350
|
+
})
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
transitionRemoves(elements){
|
|
354
|
+
let removeAttr = this.binding("remove")
|
|
355
|
+
elements = elements || DOM.all(document, `[${removeAttr}]`)
|
|
356
|
+
elements.forEach(el => {
|
|
357
|
+
if(document.body.contains(el)){ // skip children already removed
|
|
358
|
+
this.execJS(el, el.getAttribute(removeAttr), "remove")
|
|
331
359
|
}
|
|
332
360
|
})
|
|
333
361
|
}
|
|
@@ -346,14 +374,7 @@ export default class LiveSocket {
|
|
|
346
374
|
}
|
|
347
375
|
|
|
348
376
|
withinOwners(childEl, callback){
|
|
349
|
-
this.owner(childEl, view =>
|
|
350
|
-
let phxTarget = childEl.getAttribute(this.binding("target"))
|
|
351
|
-
if(phxTarget === null){
|
|
352
|
-
callback(view, childEl)
|
|
353
|
-
} else {
|
|
354
|
-
view.withinTargets(phxTarget, callback)
|
|
355
|
-
}
|
|
356
|
-
})
|
|
377
|
+
this.owner(childEl, view => callback(view, childEl))
|
|
357
378
|
}
|
|
358
379
|
|
|
359
380
|
getViewByEl(el){
|
|
@@ -428,22 +449,25 @@ export default class LiveSocket {
|
|
|
428
449
|
this.bindNav()
|
|
429
450
|
this.bindClicks()
|
|
430
451
|
this.bindForms()
|
|
431
|
-
this.bind({keyup: "keyup", keydown: "keydown"}, (e, type, view,
|
|
432
|
-
let matchKey =
|
|
452
|
+
this.bind({keyup: "keyup", keydown: "keydown"}, (e, type, view, targetEl, phxEvent, eventTarget) => {
|
|
453
|
+
let matchKey = targetEl.getAttribute(this.binding(PHX_KEY))
|
|
433
454
|
let pressedKey = e.key && e.key.toLowerCase() // chrome clicked autocompletes send a keydown without key
|
|
434
455
|
if(matchKey && matchKey.toLowerCase() !== pressedKey){ return }
|
|
435
456
|
|
|
436
|
-
|
|
457
|
+
let data = {key: e.key, ...this.eventMeta(type, e, targetEl)}
|
|
458
|
+
JS.exec(type, phxEvent, view, targetEl, ["push", {data}])
|
|
437
459
|
})
|
|
438
|
-
this.bind({blur: "focusout", focus: "focusin"}, (e, type, view, targetEl,
|
|
439
|
-
if(!
|
|
440
|
-
|
|
460
|
+
this.bind({blur: "focusout", focus: "focusin"}, (e, type, view, targetEl, phxEvent, eventTarget) => {
|
|
461
|
+
if(!eventTarget){
|
|
462
|
+
let data = {key: e.key, ...this.eventMeta(type, e, targetEl)}
|
|
463
|
+
JS.exec(type, phxEvent, view, targetEl, ["push", {data}])
|
|
441
464
|
}
|
|
442
465
|
})
|
|
443
466
|
this.bind({blur: "blur", focus: "focus"}, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {
|
|
444
467
|
// blur and focus are triggered on document and window. Discard one to avoid dups
|
|
445
|
-
if(phxTarget
|
|
446
|
-
|
|
468
|
+
if(phxTarget === "window"){
|
|
469
|
+
let data = this.eventMeta(type, e, targetEl)
|
|
470
|
+
JS.exec(type, phxEvent, view, targetEl, ["push", {data}])
|
|
447
471
|
}
|
|
448
472
|
})
|
|
449
473
|
window.addEventListener("dragover", e => e.preventDefault())
|
|
@@ -503,16 +527,16 @@ export default class LiveSocket {
|
|
|
503
527
|
let targetPhxEvent = e.target.getAttribute && e.target.getAttribute(binding)
|
|
504
528
|
if(targetPhxEvent){
|
|
505
529
|
this.debounce(e.target, e, () => {
|
|
506
|
-
this.withinOwners(e.target,
|
|
507
|
-
callback(e, event, view, e.target,
|
|
530
|
+
this.withinOwners(e.target, view => {
|
|
531
|
+
callback(e, event, view, e.target, targetPhxEvent, null)
|
|
508
532
|
})
|
|
509
533
|
})
|
|
510
534
|
} else {
|
|
511
535
|
DOM.all(document, `[${windowBinding}]`, el => {
|
|
512
536
|
let phxEvent = el.getAttribute(windowBinding)
|
|
513
537
|
this.debounce(el, e, () => {
|
|
514
|
-
this.withinOwners(el,
|
|
515
|
-
callback(e, event, view, el,
|
|
538
|
+
this.withinOwners(el, view => {
|
|
539
|
+
callback(e, event, view, el, phxEvent, "window")
|
|
516
540
|
})
|
|
517
541
|
})
|
|
518
542
|
})
|
|
@@ -535,19 +559,32 @@ export default class LiveSocket {
|
|
|
535
559
|
target = e.target.matches(`[${click}]`) ? e.target : e.target.querySelector(`[${click}]`)
|
|
536
560
|
} else {
|
|
537
561
|
target = closestPhxBinding(e.target, click)
|
|
562
|
+
this.dispatchClickAway(e)
|
|
538
563
|
}
|
|
539
564
|
let phxEvent = target && target.getAttribute(click)
|
|
540
565
|
if(!phxEvent){ return }
|
|
541
566
|
if(target.getAttribute("href") === "#"){ e.preventDefault() }
|
|
542
567
|
|
|
543
568
|
this.debounce(target, e, () => {
|
|
544
|
-
this.withinOwners(target,
|
|
545
|
-
|
|
569
|
+
this.withinOwners(target, view => {
|
|
570
|
+
JS.exec("click", phxEvent, view, target, ["push", {data: this.eventMeta("click", e, target)}])
|
|
546
571
|
})
|
|
547
572
|
})
|
|
548
573
|
}, capture)
|
|
549
574
|
}
|
|
550
575
|
|
|
576
|
+
dispatchClickAway(e){
|
|
577
|
+
let binding = this.binding("click-away")
|
|
578
|
+
DOM.all(document, `[${binding}]`, el => {
|
|
579
|
+
if(!(el.isSameNode(e.target) || el.contains(e.target))){
|
|
580
|
+
this.withinOwners(e.target, view => {
|
|
581
|
+
let phxEvent = el.getAttribute(binding)
|
|
582
|
+
JS.exec("click", phxEvent, view, e.target, ["push", {data: this.eventMeta("click", e, e.target)}])
|
|
583
|
+
})
|
|
584
|
+
}
|
|
585
|
+
})
|
|
586
|
+
}
|
|
587
|
+
|
|
551
588
|
bindNav(){
|
|
552
589
|
if(!Browser.canPushState()){ return }
|
|
553
590
|
if(history.scrollRestoration){ history.scrollRestoration = "manual" }
|
|
@@ -563,18 +600,20 @@ export default class LiveSocket {
|
|
|
563
600
|
let {type, id, root, scroll} = event.state || {}
|
|
564
601
|
let href = window.location.href
|
|
565
602
|
|
|
566
|
-
|
|
567
|
-
this.main.
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
603
|
+
this.requestDOMUpdate(() => {
|
|
604
|
+
if(this.main.isConnected() && (type === "patch" && id === this.main.id)){
|
|
605
|
+
this.main.pushLinkPatch(href, null)
|
|
606
|
+
} else {
|
|
607
|
+
this.replaceMain(href, null, () => {
|
|
608
|
+
if(root){ this.replaceRootHistory() }
|
|
609
|
+
if(typeof(scroll) === "number"){
|
|
610
|
+
setTimeout(() => {
|
|
611
|
+
window.scrollTo(0, scroll)
|
|
612
|
+
}, 0) // the body needs to render before we scroll.
|
|
613
|
+
}
|
|
614
|
+
})
|
|
615
|
+
}
|
|
616
|
+
})
|
|
578
617
|
}, false)
|
|
579
618
|
window.addEventListener("click", e => {
|
|
580
619
|
let target = closestPhxBinding(e.target, PHX_LIVE_LINK)
|
|
@@ -586,16 +625,26 @@ export default class LiveSocket {
|
|
|
586
625
|
e.preventDefault()
|
|
587
626
|
if(this.pendingLink === href){ return }
|
|
588
627
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
628
|
+
this.requestDOMUpdate(() => {
|
|
629
|
+
if(type === "patch"){
|
|
630
|
+
this.pushHistoryPatch(href, linkState, target)
|
|
631
|
+
} else if(type === "redirect"){
|
|
632
|
+
this.historyRedirect(href, linkState)
|
|
633
|
+
} else {
|
|
634
|
+
throw new Error(`expected ${PHX_LIVE_LINK} to be "patch" or "redirect", got: ${type}`)
|
|
635
|
+
}
|
|
636
|
+
})
|
|
596
637
|
}, false)
|
|
597
638
|
}
|
|
598
639
|
|
|
640
|
+
dispatchEvent(event, payload = {}){
|
|
641
|
+
DOM.dispatchEvent(window, `phx:${event}`, payload)
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
dispatchEvents(events){
|
|
645
|
+
events.forEach(([event, payload]) => this.dispatchEvent(event, payload))
|
|
646
|
+
}
|
|
647
|
+
|
|
599
648
|
withPageLoading(info, callback){
|
|
600
649
|
DOM.dispatchEvent(window, "phx:page-loading-start", info)
|
|
601
650
|
let done = () => DOM.dispatchEvent(window, "phx:page-loading-stop", info)
|
|
@@ -650,7 +699,9 @@ export default class LiveSocket {
|
|
|
650
699
|
if(!phxEvent){ return }
|
|
651
700
|
e.preventDefault()
|
|
652
701
|
e.target.disabled = true
|
|
653
|
-
this.withinOwners(e.target,
|
|
702
|
+
this.withinOwners(e.target, view => {
|
|
703
|
+
JS.exec("submit", phxEvent, view, e.target, ["push", {}])
|
|
704
|
+
})
|
|
654
705
|
}, false)
|
|
655
706
|
|
|
656
707
|
for(let type of ["change", "input"]){
|
|
@@ -668,12 +719,12 @@ export default class LiveSocket {
|
|
|
668
719
|
DOM.putPrivate(input, "prev-iteration", {at: currentIterations, type: type})
|
|
669
720
|
|
|
670
721
|
this.debounce(input, e, () => {
|
|
671
|
-
this.withinOwners(input.form,
|
|
722
|
+
this.withinOwners(input.form, view => {
|
|
672
723
|
DOM.putPrivate(input, PHX_HAS_FOCUSED, true)
|
|
673
724
|
if(!DOM.isTextualInput(input)){
|
|
674
725
|
this.setActiveElement(input)
|
|
675
726
|
}
|
|
676
|
-
|
|
727
|
+
JS.exec("change", phxEvent, view, input, ["push", {_target: e.target.name}])
|
|
677
728
|
})
|
|
678
729
|
})
|
|
679
730
|
}, false)
|
|
@@ -700,3 +751,45 @@ export default class LiveSocket {
|
|
|
700
751
|
})
|
|
701
752
|
}
|
|
702
753
|
}
|
|
754
|
+
|
|
755
|
+
class TransitionSet {
|
|
756
|
+
constructor(){
|
|
757
|
+
this.transitions = new Set()
|
|
758
|
+
this.pendingOps = []
|
|
759
|
+
this.reset()
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
reset(){
|
|
763
|
+
this.transitions.forEach(timer => {
|
|
764
|
+
cancelTimeout(timer)
|
|
765
|
+
this.transitions.delete(timer)
|
|
766
|
+
})
|
|
767
|
+
this.flushPendingOps()
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
after(callback){
|
|
771
|
+
if(this.size() === 0){
|
|
772
|
+
callback()
|
|
773
|
+
} else {
|
|
774
|
+
this.pushPendingOp(callback)
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
addTransition(time, onDone){
|
|
779
|
+
let timer = setTimeout(() => {
|
|
780
|
+
this.transitions.delete(timer)
|
|
781
|
+
onDone()
|
|
782
|
+
if(this.size() === 0){ this.flushPendingOps() }
|
|
783
|
+
}, time)
|
|
784
|
+
this.transitions.add(timer)
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
pushPendingOp(op){ this.pendingOps.push(op) }
|
|
788
|
+
|
|
789
|
+
size(){ return this.transitions.size }
|
|
790
|
+
|
|
791
|
+
flushPendingOps(){
|
|
792
|
+
this.pendingOps.forEach(op => op())
|
|
793
|
+
this.pendingOps = []
|
|
794
|
+
}
|
|
795
|
+
}
|