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.
@@ -25,10 +25,10 @@ var PHX_DROP_TARGET = "drop-target";
25
25
  var PHX_ACTIVE_ENTRY_REFS = "data-phx-active-refs";
26
26
  var PHX_LIVE_FILE_UPDATED = "phx:live-file:updated";
27
27
  var PHX_SKIP = "data-phx-skip";
28
- var PHX_REMOVE = "data-phx-remove";
28
+ var PHX_PRUNE = "data-phx-prune";
29
29
  var PHX_PAGE_LOADING = "page-loading";
30
30
  var PHX_CONNECTED_CLASS = "phx-connected";
31
- var PHX_DISCONNECTED_CLASS = "phx-disconnected";
31
+ var PHX_DISCONNECTED_CLASS = "phx-loading";
32
32
  var PHX_NO_FEEDBACK_CLASS = "phx-no-feedback";
33
33
  var PHX_ERROR_CLASS = "phx-error";
34
34
  var PHX_PARENT_ID = "data-phx-parent-id";
@@ -282,7 +282,9 @@ var DOM = {
282
282
  return node.id && DOM.private(node, "destroyed") ? true : false;
283
283
  },
284
284
  markPhxChildDestroyed(el) {
285
- el.setAttribute(PHX_SESSION, "");
285
+ if (this.isPhxChild(el)) {
286
+ el.setAttribute(PHX_SESSION, "");
287
+ }
286
288
  this.putPrivate(el, "destroyed", true);
287
289
  },
288
290
  findPhxChildrenInFragment(html, parentId) {
@@ -336,9 +338,17 @@ var DOM = {
336
338
  }
337
339
  el[PHX_PRIVATE][key] = value;
338
340
  },
341
+ updatePrivate(el, key, defaultVal, updateFunc) {
342
+ let existing = this.private(el, key);
343
+ if (existing === void 0) {
344
+ this.putPrivate(el, key, updateFunc(defaultVal));
345
+ } else {
346
+ this.putPrivate(el, key, updateFunc(existing));
347
+ }
348
+ },
339
349
  copyPrivates(target, source) {
340
350
  if (source[PHX_PRIVATE]) {
341
- target[PHX_PRIVATE] = clone(source[PHX_PRIVATE]);
351
+ target[PHX_PRIVATE] = source[PHX_PRIVATE];
342
352
  }
343
353
  },
344
354
  putTitle(str) {
@@ -520,14 +530,6 @@ var DOM = {
520
530
  el.checked = el.getAttribute("checked") !== null;
521
531
  }
522
532
  },
523
- syncPropsToAttrs(el) {
524
- if (el instanceof HTMLSelectElement) {
525
- let selectedItem = el.options.item(el.selectedIndex);
526
- if (selectedItem && selectedItem.getAttribute("selected") === null) {
527
- selectedItem.setAttribute("selected", "");
528
- }
529
- }
530
- },
531
533
  isTextualInput(el) {
532
534
  return FOCUSABLE_INPUTS.indexOf(el.type) >= 0;
533
535
  },
@@ -573,7 +575,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
573
575
  }
574
576
  },
575
577
  replaceRootContainer(container, tagName, attrs) {
576
- let retainedAttrs = new Set(["id", PHX_SESSION, PHX_STATIC, PHX_MAIN]);
578
+ let retainedAttrs = new Set(["id", PHX_SESSION, PHX_STATIC, PHX_MAIN, PHX_ROOT_ID]);
577
579
  if (container.tagName.toLowerCase() === tagName.toLowerCase()) {
578
580
  Array.from(container.attributes).filter((attr) => !retainedAttrs.has(attr.name.toLowerCase())).forEach((attr) => container.removeAttribute(attr.name));
579
581
  Object.keys(attrs).filter((name) => !retainedAttrs.has(name.toLowerCase())).forEach((attr) => container.setAttribute(attr, attrs[attr]));
@@ -586,6 +588,39 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
586
588
  container.replaceWith(newContainer);
587
589
  return newContainer;
588
590
  }
591
+ },
592
+ getSticky(el, name, defaultVal) {
593
+ let op = (DOM.private(el, "sticky") || []).find(([existingName]) => name === existingName);
594
+ if (op) {
595
+ let [_name, _op, stashedResult] = op;
596
+ return stashedResult;
597
+ } else {
598
+ return typeof defaultVal === "function" ? defaultVal() : defaultVal;
599
+ }
600
+ },
601
+ deleteSticky(el, name) {
602
+ this.updatePrivate(el, "sticky", [], (ops) => {
603
+ return ops.filter(([existingName, _]) => existingName !== name);
604
+ });
605
+ },
606
+ putSticky(el, name, op) {
607
+ let stashedResult = op(el);
608
+ this.updatePrivate(el, "sticky", [], (ops) => {
609
+ let existingIndex = ops.findIndex(([existingName]) => name === existingName);
610
+ if (existingIndex >= 0) {
611
+ ops[existingIndex] = [name, op, stashedResult];
612
+ } else {
613
+ ops.push([name, op, stashedResult]);
614
+ }
615
+ return ops;
616
+ });
617
+ },
618
+ applyStickyOperations(el) {
619
+ let ops = DOM.private(el, "sticky");
620
+ if (!ops) {
621
+ return;
622
+ }
623
+ ops.forEach(([name, op, _stashed]) => this.putSticky(el, name, op));
589
624
  }
590
625
  };
591
626
  var dom_default = DOM;
@@ -1383,7 +1418,8 @@ var DOMPatch = class {
1383
1418
  afteradded: [],
1384
1419
  afterupdated: [],
1385
1420
  afterdiscarded: [],
1386
- afterphxChildAdded: []
1421
+ afterphxChildAdded: [],
1422
+ aftertransitionsDiscarded: []
1387
1423
  };
1388
1424
  }
1389
1425
  before(kind, callback) {
@@ -1400,7 +1436,7 @@ var DOMPatch = class {
1400
1436
  }
1401
1437
  markPrunableContentForRemoval() {
1402
1438
  dom_default.all(this.container, "[phx-update=append] > *, [phx-update=prepend] > *", (el) => {
1403
- el.setAttribute(PHX_REMOVE, "");
1439
+ el.setAttribute(PHX_PRUNE, "");
1404
1440
  });
1405
1441
  }
1406
1442
  perform() {
@@ -1415,9 +1451,11 @@ var DOMPatch = class {
1415
1451
  let phxFeedbackFor = liveSocket.binding(PHX_FEEDBACK_FOR);
1416
1452
  let disableWith = liveSocket.binding(PHX_DISABLE_WITH);
1417
1453
  let phxTriggerExternal = liveSocket.binding(PHX_TRIGGER_ACTION);
1454
+ let phxRemove = liveSocket.binding("remove");
1418
1455
  let added = [];
1419
1456
  let updates = [];
1420
1457
  let appendPrependUpdates = [];
1458
+ let pendingRemoves = [];
1421
1459
  let externalFormTriggered = null;
1422
1460
  let diffHTML = liveSocket.time("premorph container prep", () => {
1423
1461
  return this.buildDiffHTML(container, html, phxUpdate, targetContainer);
@@ -1435,6 +1473,11 @@ var DOMPatch = class {
1435
1473
  return el;
1436
1474
  },
1437
1475
  onNodeAdded: (el) => {
1476
+ if (el instanceof HTMLImageElement && el.srcset) {
1477
+ el.srcset = el.srcset;
1478
+ } else if (el instanceof HTMLVideoElement && el.autoplay) {
1479
+ el.play();
1480
+ }
1438
1481
  if (dom_default.isNowTriggerFormExternal(el, phxTriggerExternal)) {
1439
1482
  externalFormTriggered = el;
1440
1483
  }
@@ -1451,12 +1494,16 @@ var DOMPatch = class {
1451
1494
  this.trackAfter("discarded", el);
1452
1495
  },
1453
1496
  onBeforeNodeDiscarded: (el) => {
1454
- if (el.getAttribute && el.getAttribute(PHX_REMOVE) !== null) {
1497
+ if (el.getAttribute && el.getAttribute(PHX_PRUNE) !== null) {
1455
1498
  return true;
1456
1499
  }
1457
1500
  if (el.parentNode !== null && dom_default.isPhxUpdate(el.parentNode, phxUpdate, ["append", "prepend"]) && el.id) {
1458
1501
  return false;
1459
1502
  }
1503
+ if (el.getAttribute && el.getAttribute(phxRemove)) {
1504
+ pendingRemoves.push(el);
1505
+ return false;
1506
+ }
1460
1507
  if (this.skipCIDSibling(el)) {
1461
1508
  return false;
1462
1509
  }
@@ -1477,6 +1524,7 @@ var DOMPatch = class {
1477
1524
  this.trackBefore("updated", fromEl, toEl);
1478
1525
  dom_default.mergeAttrs(fromEl, toEl, { isIgnored: true });
1479
1526
  updates.push(fromEl);
1527
+ dom_default.applyStickyOperations(fromEl);
1480
1528
  return false;
1481
1529
  }
1482
1530
  if (fromEl.type === "number" && (fromEl.validity && fromEl.validity.badInput)) {
@@ -1487,6 +1535,7 @@ var DOMPatch = class {
1487
1535
  this.trackBefore("updated", fromEl, toEl);
1488
1536
  updates.push(fromEl);
1489
1537
  }
1538
+ dom_default.applyStickyOperations(fromEl);
1490
1539
  return false;
1491
1540
  }
1492
1541
  if (dom_default.isPhxChild(toEl)) {
@@ -1496,23 +1545,25 @@ var DOMPatch = class {
1496
1545
  fromEl.setAttribute(PHX_SESSION, prevSession);
1497
1546
  }
1498
1547
  fromEl.setAttribute(PHX_ROOT_ID, this.rootID);
1548
+ dom_default.applyStickyOperations(fromEl);
1499
1549
  return false;
1500
1550
  }
1501
1551
  dom_default.copyPrivates(toEl, fromEl);
1502
1552
  dom_default.discardError(targetContainer, toEl, phxFeedbackFor);
1503
- dom_default.syncPropsToAttrs(toEl);
1504
1553
  let isFocusedFormEl = focused && fromEl.isSameNode(focused) && dom_default.isFormInput(fromEl);
1505
- if (isFocusedFormEl && !this.forceFocusedSelectUpdate(fromEl, toEl)) {
1554
+ if (isFocusedFormEl) {
1506
1555
  this.trackBefore("updated", fromEl, toEl);
1507
1556
  dom_default.mergeFocusedInput(fromEl, toEl);
1508
1557
  dom_default.syncAttrsToProps(fromEl);
1509
1558
  updates.push(fromEl);
1559
+ dom_default.applyStickyOperations(fromEl);
1510
1560
  return false;
1511
1561
  } else {
1512
1562
  if (dom_default.isPhxUpdate(toEl, phxUpdate, ["append", "prepend"])) {
1513
1563
  appendPrependUpdates.push(new DOMPostMorphRestorer(fromEl, toEl, toEl.getAttribute(phxUpdate)));
1514
1564
  }
1515
1565
  dom_default.syncAttrsToProps(toEl);
1566
+ dom_default.applyStickyOperations(toEl);
1516
1567
  this.trackBefore("updated", fromEl, toEl);
1517
1568
  return true;
1518
1569
  }
@@ -1531,16 +1582,19 @@ var DOMPatch = class {
1531
1582
  dom_default.dispatchEvent(document, "phx:update");
1532
1583
  added.forEach((el) => this.trackAfter("added", el));
1533
1584
  updates.forEach((el) => this.trackAfter("updated", el));
1585
+ if (pendingRemoves.length > 0) {
1586
+ liveSocket.transitionRemoves(pendingRemoves);
1587
+ liveSocket.requestDOMUpdate(() => {
1588
+ pendingRemoves.forEach((el) => el.remove());
1589
+ this.trackAfter("transitionsDiscarded", pendingRemoves);
1590
+ });
1591
+ }
1534
1592
  if (externalFormTriggered) {
1535
1593
  liveSocket.disconnect();
1536
1594
  externalFormTriggered.submit();
1537
1595
  }
1538
1596
  return true;
1539
1597
  }
1540
- forceFocusedSelectUpdate(fromEl, toEl) {
1541
- let isSelect = ["select", "select-one", "select-multiple"].find((t) => t === fromEl.type);
1542
- return fromEl.multiple === true || isSelect && fromEl.innerHTML != toEl.innerHTML;
1543
- }
1544
1598
  isCIDPatch() {
1545
1599
  return this.cidPatch;
1546
1600
  }
@@ -1842,13 +1896,13 @@ var ViewHook = class {
1842
1896
  }
1843
1897
  handleEvent(event, callback) {
1844
1898
  let callbackRef = (customEvent, bypass) => bypass ? event : callback(customEvent.detail);
1845
- window.addEventListener(`phx:hook:${event}`, callbackRef);
1899
+ window.addEventListener(`phx:${event}`, callbackRef);
1846
1900
  this.__listeners.add(callbackRef);
1847
1901
  return callbackRef;
1848
1902
  }
1849
1903
  removeHandleEvent(callbackRef) {
1850
1904
  let event = callbackRef(null, true);
1851
- window.removeEventListener(`phx:hook:${event}`, callbackRef);
1905
+ window.removeEventListener(`phx:${event}`, callbackRef);
1852
1906
  this.__listeners.delete(callbackRef);
1853
1907
  }
1854
1908
  upload(name, files) {
@@ -1862,6 +1916,156 @@ var ViewHook = class {
1862
1916
  }
1863
1917
  };
1864
1918
 
1919
+ // js/phoenix_live_view/js.js
1920
+ var JS = {
1921
+ exec(eventType, phxEvent, view, el, defaults) {
1922
+ let [defaultKind, defaultArgs] = defaults || [null, {}];
1923
+ let commands = phxEvent.charAt(0) === "[" ? JSON.parse(phxEvent) : [[defaultKind, defaultArgs]];
1924
+ commands.forEach(([kind, args]) => {
1925
+ if (kind === defaultKind && defaultArgs.data) {
1926
+ args.data = Object.assign(args.data || {}, defaultArgs.data);
1927
+ }
1928
+ this[`exec_${kind}`](eventType, phxEvent, view, el, args);
1929
+ });
1930
+ },
1931
+ exec_dispatch(eventType, phxEvent, view, sourceEl, { to, event, detail }) {
1932
+ if (to) {
1933
+ dom_default.all(document, to, (el) => dom_default.dispatchEvent(el, event, detail));
1934
+ } else {
1935
+ dom_default.dispatchEvent(sourceEl, event, detail);
1936
+ }
1937
+ },
1938
+ exec_push(eventType, phxEvent, view, sourceEl, args) {
1939
+ let { event, data, target, page_loading, loading, value } = args;
1940
+ let pushOpts = { page_loading: !!page_loading, loading, value };
1941
+ let targetSrc = eventType === "change" ? sourceEl.form : sourceEl;
1942
+ let phxTarget = target || targetSrc.getAttribute(view.binding("target")) || targetSrc;
1943
+ view.withinTargets(phxTarget, (targetView, targetCtx) => {
1944
+ if (eventType === "change") {
1945
+ let { newCid, _target, callback } = args;
1946
+ if (_target) {
1947
+ pushOpts._target = _target;
1948
+ }
1949
+ targetView.pushInput(sourceEl, targetCtx, newCid, event || phxEvent, pushOpts, callback);
1950
+ } else if (eventType === "submit") {
1951
+ targetView.submitForm(sourceEl, targetCtx, event || phxEvent, pushOpts);
1952
+ } else {
1953
+ targetView.pushEvent(eventType, sourceEl, targetCtx, event || phxEvent, data, pushOpts);
1954
+ }
1955
+ });
1956
+ },
1957
+ exec_add_class(eventType, phxEvent, view, sourceEl, { to, names, transition, time }) {
1958
+ if (to) {
1959
+ dom_default.all(document, to, (el) => this.addOrRemoveClasses(el, names, [], transition, time, view));
1960
+ } else {
1961
+ this.addOrRemoveClasses(sourceEl, names, [], transition, view);
1962
+ }
1963
+ },
1964
+ exec_remove_class(eventType, phxEvent, view, sourceEl, { to, names, transition, time }) {
1965
+ if (to) {
1966
+ dom_default.all(document, to, (el) => this.addOrRemoveClasses(el, [], names, transition, time, view));
1967
+ } else {
1968
+ this.addOrRemoveClasses(sourceEl, [], names, transition, time, view);
1969
+ }
1970
+ },
1971
+ exec_transition(eventType, phxEvent, view, sourceEl, { time, to, names }) {
1972
+ let els = to ? dom_default.all(document, to) : [sourceEl];
1973
+ els.forEach((el) => {
1974
+ this.addOrRemoveClasses(el, names, []);
1975
+ view.transition(time, () => this.addOrRemoveClasses(el, [], names));
1976
+ });
1977
+ },
1978
+ exec_toggle(eventType, phxEvent, view, sourceEl, { to, display, ins, outs, time }) {
1979
+ if (to) {
1980
+ dom_default.all(document, to, (el) => this.toggle(eventType, view, el, display, ins || [], outs || [], time));
1981
+ } else {
1982
+ this.toggle(eventType, view, sourceEl, display, ins || [], outs || [], time);
1983
+ }
1984
+ },
1985
+ exec_show(eventType, phxEvent, view, sourceEl, { to, display, transition, time }) {
1986
+ if (to) {
1987
+ dom_default.all(document, to, (el) => this.show(eventType, view, el, display, transition, time));
1988
+ } else {
1989
+ this.show(eventType, view, sourceEl, transition, time);
1990
+ }
1991
+ },
1992
+ exec_hide(eventType, phxEvent, view, sourceEl, { to, display, transition, time }) {
1993
+ if (to) {
1994
+ dom_default.all(document, to, (el) => this.hide(eventType, view, el, display, transition, time));
1995
+ } else {
1996
+ this.hide(eventType, view, sourceEl, display, transition, time);
1997
+ }
1998
+ },
1999
+ show(eventType, view, el, display, transition, time) {
2000
+ let isVisible = this.isVisible(el);
2001
+ if (transition.length > 0 && !isVisible) {
2002
+ this.toggle(eventType, view, el, display, transition, [], time);
2003
+ } else if (!isVisible) {
2004
+ this.toggle(eventType, view, el, display, [], [], null);
2005
+ }
2006
+ },
2007
+ hide(eventType, view, el, display, transition, time) {
2008
+ let isVisible = this.isVisible(el);
2009
+ if (transition.length > 0 && isVisible) {
2010
+ this.toggle(eventType, view, el, display, [], transition, time);
2011
+ } else if (isVisible) {
2012
+ this.toggle(eventType, view, el, display, [], [], time);
2013
+ }
2014
+ },
2015
+ toggle(eventType, view, el, display, in_classes, out_classes, time) {
2016
+ if (in_classes.length > 0 || out_classes.length > 0) {
2017
+ if (this.isVisible(el)) {
2018
+ this.addOrRemoveClasses(el, out_classes, in_classes);
2019
+ view.transition(time, () => {
2020
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = "none");
2021
+ this.addOrRemoveClasses(el, [], out_classes);
2022
+ });
2023
+ } else {
2024
+ if (eventType === "remove") {
2025
+ return;
2026
+ }
2027
+ this.addOrRemoveClasses(el, in_classes, out_classes);
2028
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = display || "block");
2029
+ view.transition(time, () => {
2030
+ this.addOrRemoveClasses(el, [], in_classes);
2031
+ });
2032
+ }
2033
+ } else {
2034
+ let newDisplay = this.isVisible(el) ? "none" : display || "block";
2035
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = newDisplay);
2036
+ }
2037
+ },
2038
+ addOrRemoveClasses(el, adds, removes, transition, time, view) {
2039
+ if (transition && transition.length > 0) {
2040
+ this.addOrRemoveClasses(el, transition, []);
2041
+ return view.transition(time, () => this.addOrRemoveClasses(el, adds, removes.concat(transition)));
2042
+ }
2043
+ window.requestAnimationFrame(() => {
2044
+ let [prevAdds, prevRemoves] = dom_default.getSticky(el, "classes", [[], []]);
2045
+ let keepAdds = adds.filter((name) => prevAdds.indexOf(name) < 0 && !el.classList.contains(name));
2046
+ let keepRemoves = removes.filter((name) => prevRemoves.indexOf(name) < 0 && el.classList.contains(name));
2047
+ let newAdds = prevAdds.filter((name) => removes.indexOf(name) < 0).concat(keepAdds);
2048
+ let newRemoves = prevRemoves.filter((name) => adds.indexOf(name) < 0).concat(keepRemoves);
2049
+ dom_default.putSticky(el, "classes", (currentEl) => {
2050
+ currentEl.classList.remove(...newRemoves);
2051
+ currentEl.classList.add(...newAdds);
2052
+ return [newAdds, newRemoves];
2053
+ });
2054
+ });
2055
+ },
2056
+ hasAllClasses(el, classes) {
2057
+ return classes.every((name) => el.classList.contains(name));
2058
+ },
2059
+ isVisible(el) {
2060
+ let style = window.getComputedStyle(el);
2061
+ return !(style.opacity === 0 || style.display === "none");
2062
+ },
2063
+ isToggledOut(el, out_classes) {
2064
+ return !this.isVisible(el) || this.hasAllClasses(el, out_classes);
2065
+ }
2066
+ };
2067
+ var js_default = JS;
2068
+
1865
2069
  // js/phoenix_live_view/view.js
1866
2070
  var serializeForm = (form, meta = {}) => {
1867
2071
  let formData = new FormData(form);
@@ -1899,7 +2103,8 @@ var View = class {
1899
2103
  this.joinCount = this.parent ? this.parent.joinCount - 1 : 0;
1900
2104
  this.joinPending = true;
1901
2105
  this.destroyed = false;
1902
- this.joinCallback = function() {
2106
+ this.joinCallback = function(onDone) {
2107
+ onDone && onDone();
1903
2108
  };
1904
2109
  this.stopCallback = function() {
1905
2110
  };
@@ -1974,9 +2179,6 @@ var View = class {
1974
2179
  this.el.classList.remove(PHX_CONNECTED_CLASS, PHX_DISCONNECTED_CLASS, PHX_ERROR_CLASS);
1975
2180
  this.el.classList.add(...classes);
1976
2181
  }
1977
- isLoading() {
1978
- return this.el.classList.contains(PHX_DISCONNECTED_CLASS);
1979
- }
1980
2182
  showLoader(timeout) {
1981
2183
  clearTimeout(this.loaderTimer);
1982
2184
  if (timeout) {
@@ -2000,16 +2202,20 @@ var View = class {
2000
2202
  log(kind, msgCallback) {
2001
2203
  this.liveSocket.log(this, kind, msgCallback);
2002
2204
  }
2205
+ transition(time, onDone = function() {
2206
+ }) {
2207
+ this.liveSocket.transition(time, onDone);
2208
+ }
2003
2209
  withinTargets(phxTarget, callback) {
2004
- if (phxTarget instanceof HTMLElement) {
2210
+ if (phxTarget instanceof HTMLElement || phxTarget instanceof SVGElement) {
2005
2211
  return this.liveSocket.owner(phxTarget, (view) => callback(view, phxTarget));
2006
2212
  }
2007
- if (/^(0|[1-9]\d*)$/.test(phxTarget)) {
2213
+ if (typeof phxTarget === "number" || /^(0|[1-9]\d*)$/.test(phxTarget)) {
2008
2214
  let targets = dom_default.findComponentNodeList(this.el, phxTarget);
2009
2215
  if (targets.length === 0) {
2010
2216
  logError(`no component found matching phx-target of ${phxTarget}`);
2011
2217
  } else {
2012
- callback(this, targets[0]);
2218
+ callback(this, parseInt(phxTarget));
2013
2219
  }
2014
2220
  } else {
2015
2221
  let targets = Array.from(document.querySelectorAll(phxTarget));
@@ -2088,11 +2294,6 @@ var View = class {
2088
2294
  this.el = dom_default.byId(this.id);
2089
2295
  this.el.setAttribute(PHX_ROOT_ID, this.root.id);
2090
2296
  }
2091
- dispatchEvents(events) {
2092
- events.forEach(([event, payload]) => {
2093
- window.dispatchEvent(new CustomEvent(`phx:hook:${event}`, { detail: payload }));
2094
- });
2095
- }
2096
2297
  applyJoinPatch(live_patch, html, events) {
2097
2298
  this.attachTrueDocEl();
2098
2299
  let patch = new DOMPatch(this, this.el, this.id, html, null);
@@ -2106,7 +2307,7 @@ var View = class {
2106
2307
  }
2107
2308
  });
2108
2309
  this.joinPending = false;
2109
- this.dispatchEvents(events);
2310
+ this.liveSocket.dispatchEvents(events);
2110
2311
  this.applyPendingUpdates();
2111
2312
  if (live_patch) {
2112
2313
  let { kind, to } = live_patch;
@@ -2128,7 +2329,7 @@ var View = class {
2128
2329
  }
2129
2330
  }
2130
2331
  performPatch(patch, pruneCids) {
2131
- let destroyedCIDs = [];
2332
+ let removedEls = [];
2132
2333
  let phxChildrenAdded = false;
2133
2334
  let updatedHookIds = new Set();
2134
2335
  patch.after("added", (el) => {
@@ -2151,18 +2352,31 @@ var View = class {
2151
2352
  }
2152
2353
  });
2153
2354
  patch.after("discarded", (el) => {
2154
- let cid = this.componentID(el);
2155
- if (isCid(cid) && destroyedCIDs.indexOf(cid) === -1) {
2156
- destroyedCIDs.push(cid);
2355
+ if (el.nodeType === Node.ELEMENT_NODE) {
2356
+ removedEls.push(el);
2157
2357
  }
2158
- let hook = this.getHook(el);
2159
- hook && this.destroyHook(hook);
2160
2358
  });
2359
+ patch.after("transitionsDiscarded", (els) => this.afterElementsRemoved(els, pruneCids));
2161
2360
  patch.perform();
2361
+ this.afterElementsRemoved(removedEls, pruneCids);
2362
+ return phxChildrenAdded;
2363
+ }
2364
+ afterElementsRemoved(elements, pruneCids) {
2365
+ let destroyedCIDs = [];
2366
+ elements.forEach((parent) => {
2367
+ let components = dom_default.all(parent, `[${PHX_COMPONENT}]`);
2368
+ components.concat(parent).forEach((el) => {
2369
+ let cid = this.componentID(el);
2370
+ if (isCid(cid) && destroyedCIDs.indexOf(cid) === -1) {
2371
+ destroyedCIDs.push(cid);
2372
+ }
2373
+ let hook = this.getHook(el);
2374
+ hook && this.destroyHook(hook);
2375
+ });
2376
+ });
2162
2377
  if (pruneCids) {
2163
2378
  this.maybePushComponentsDestroyed(destroyedCIDs);
2164
2379
  }
2165
- return phxChildrenAdded;
2166
2380
  }
2167
2381
  joinNewChildren() {
2168
2382
  dom_default.findPhxChildren(this.el, this.id).forEach((el) => this.joinChild(el));
@@ -2210,13 +2424,14 @@ var View = class {
2210
2424
  }
2211
2425
  }
2212
2426
  onAllChildJoinsComplete() {
2213
- this.joinCallback();
2214
- this.pendingJoinOps.forEach(([view, op]) => {
2215
- if (!view.isDestroyed()) {
2216
- op();
2217
- }
2427
+ this.joinCallback(() => {
2428
+ this.pendingJoinOps.forEach(([view, op]) => {
2429
+ if (!view.isDestroyed()) {
2430
+ op();
2431
+ }
2432
+ });
2433
+ this.pendingJoinOps = [];
2218
2434
  });
2219
- this.pendingJoinOps = [];
2220
2435
  }
2221
2436
  update(diff, events) {
2222
2437
  if (this.isJoinPending() || this.liveSocket.hasPendingLink()) {
@@ -2240,7 +2455,7 @@ var View = class {
2240
2455
  phxChildrenAdded = this.performPatch(patch, true);
2241
2456
  });
2242
2457
  }
2243
- this.dispatchEvents(events);
2458
+ this.liveSocket.dispatchEvents(events);
2244
2459
  if (phxChildrenAdded) {
2245
2460
  this.joinNewChildren();
2246
2461
  }
@@ -2298,13 +2513,15 @@ var View = class {
2298
2513
  if (this.isJoinPending()) {
2299
2514
  this.root.pendingJoinOps.push([this, () => cb(resp)]);
2300
2515
  } else {
2301
- cb(resp);
2516
+ this.liveSocket.requestDOMUpdate(() => cb(resp));
2302
2517
  }
2303
2518
  });
2304
2519
  }
2305
2520
  bindChannel() {
2306
2521
  this.liveSocket.onChannel(this.channel, "diff", (rawDiff) => {
2307
- this.applyDiff("update", rawDiff, ({ diff, events }) => this.update(diff, events));
2522
+ this.liveSocket.requestDOMUpdate(() => {
2523
+ this.applyDiff("update", rawDiff, ({ diff, events }) => this.update(diff, events));
2524
+ });
2308
2525
  });
2309
2526
  this.onChannel("redirect", ({ to, flash }) => this.onRedirect({ to, flash }));
2310
2527
  this.onChannel("live_patch", (redir) => this.onLivePatch(redir));
@@ -2340,9 +2557,17 @@ var View = class {
2340
2557
  if (!this.parent) {
2341
2558
  this.stopCallback = this.liveSocket.withPageLoading({ to: this.href, kind: "initial" });
2342
2559
  }
2343
- this.joinCallback = () => callback && callback(this.joinCount);
2560
+ this.joinCallback = (onDone) => {
2561
+ onDone = onDone || function() {
2562
+ };
2563
+ callback ? callback(this.joinCount, onDone) : onDone();
2564
+ };
2344
2565
  this.liveSocket.wrapPush(this, { timeout: false }, () => {
2345
- return this.channel.join().receive("ok", (data) => !this.isDestroyed() && this.onJoin(data)).receive("error", (resp) => !this.isDestroyed() && this.onJoinError(resp)).receive("timeout", () => !this.isDestroyed() && this.onJoinError({ reason: "timeout" }));
2566
+ return this.channel.join().receive("ok", (data) => {
2567
+ if (!this.isDestroyed()) {
2568
+ this.liveSocket.requestDOMUpdate(() => this.onJoin(data));
2569
+ }
2570
+ }).receive("error", (resp) => !this.isDestroyed() && this.onJoinError(resp)).receive("timeout", () => !this.isDestroyed() && this.onJoinError({ reason: "timeout" }));
2346
2571
  });
2347
2572
  }
2348
2573
  onJoinError(resp) {
@@ -2398,10 +2623,10 @@ var View = class {
2398
2623
  if (!this.isConnected()) {
2399
2624
  return;
2400
2625
  }
2401
- let [ref, [el]] = refGenerator ? refGenerator() : [null, []];
2626
+ let [ref, [el], opts] = refGenerator ? refGenerator() : [null, [], {}];
2402
2627
  let onLoadingDone = function() {
2403
2628
  };
2404
- if (el && el.getAttribute(this.binding(PHX_PAGE_LOADING)) !== null) {
2629
+ if (opts.page_loading || el && el.getAttribute(this.binding(PHX_PAGE_LOADING)) !== null) {
2405
2630
  onLoadingDone = this.liveSocket.withPageLoading({ kind: "element", target: el });
2406
2631
  }
2407
2632
  if (typeof payload.cid !== "number") {
@@ -2409,26 +2634,28 @@ var View = class {
2409
2634
  }
2410
2635
  return this.liveSocket.wrapPush(this, { timeout: true }, () => {
2411
2636
  return this.channel.push(event, payload, PUSH_TIMEOUT).receive("ok", (resp) => {
2412
- let hookReply = null;
2413
- if (ref !== null) {
2414
- this.undoRefs(ref);
2415
- }
2416
- if (resp.diff) {
2417
- hookReply = this.applyDiff("update", resp.diff, ({ diff, events }) => {
2418
- this.update(diff, events);
2419
- });
2420
- }
2421
- if (resp.redirect) {
2422
- this.onRedirect(resp.redirect);
2423
- }
2424
- if (resp.live_patch) {
2425
- this.onLivePatch(resp.live_patch);
2426
- }
2427
- if (resp.live_redirect) {
2428
- this.onLiveRedirect(resp.live_redirect);
2429
- }
2430
- onLoadingDone();
2431
- onReply(resp, hookReply);
2637
+ this.liveSocket.requestDOMUpdate(() => {
2638
+ let hookReply = null;
2639
+ if (ref !== null) {
2640
+ this.undoRefs(ref);
2641
+ }
2642
+ if (resp.diff) {
2643
+ hookReply = this.applyDiff("update", resp.diff, ({ diff, events }) => {
2644
+ this.update(diff, events);
2645
+ });
2646
+ }
2647
+ if (resp.redirect) {
2648
+ this.onRedirect(resp.redirect);
2649
+ }
2650
+ if (resp.live_patch) {
2651
+ this.onLivePatch(resp.live_patch);
2652
+ }
2653
+ if (resp.live_redirect) {
2654
+ this.onLiveRedirect(resp.live_redirect);
2655
+ }
2656
+ onLoadingDone();
2657
+ onReply(resp, hookReply);
2658
+ });
2432
2659
  });
2433
2660
  });
2434
2661
  }
@@ -2461,9 +2688,12 @@ var View = class {
2461
2688
  }
2462
2689
  });
2463
2690
  }
2464
- putRef(elements, event) {
2691
+ putRef(elements, event, opts = {}) {
2465
2692
  let newRef = this.ref++;
2466
2693
  let disableWith = this.binding(PHX_DISABLE_WITH);
2694
+ if (opts.loading) {
2695
+ elements = elements.concat(dom_default.all(document, opts.loading));
2696
+ }
2467
2697
  elements.forEach((el) => {
2468
2698
  el.classList.add(`phx-${event}-loading`);
2469
2699
  el.setAttribute(PHX_REF, newRef);
@@ -2475,21 +2705,25 @@ var View = class {
2475
2705
  el.innerText = disableText;
2476
2706
  }
2477
2707
  });
2478
- return [newRef, elements];
2708
+ return [newRef, elements, opts];
2479
2709
  }
2480
2710
  componentID(el) {
2481
2711
  let cid = el.getAttribute && el.getAttribute(PHX_COMPONENT);
2482
2712
  return cid ? parseInt(cid) : null;
2483
2713
  }
2484
2714
  targetComponentID(target, targetCtx) {
2485
- if (target.getAttribute(this.binding("target"))) {
2715
+ if (isCid(targetCtx)) {
2716
+ return targetCtx;
2717
+ } else if (target.getAttribute(this.binding("target"))) {
2486
2718
  return this.closestComponentID(targetCtx);
2487
2719
  } else {
2488
2720
  return null;
2489
2721
  }
2490
2722
  }
2491
2723
  closestComponentID(targetCtx) {
2492
- if (targetCtx) {
2724
+ if (isCid(targetCtx)) {
2725
+ return targetCtx;
2726
+ } else if (targetCtx) {
2493
2727
  return maybe(targetCtx.closest(`[${PHX_COMPONENT}]`), (el) => this.ownsElement(el) && this.componentID(el));
2494
2728
  } else {
2495
2729
  return null;
@@ -2500,8 +2734,8 @@ var View = class {
2500
2734
  this.log("hook", () => ["unable to push hook event. LiveView not connected", event, payload]);
2501
2735
  return false;
2502
2736
  }
2503
- let [ref, els] = this.putRef([], "hook");
2504
- this.pushWithReply(() => [ref, els], "event", {
2737
+ let [ref, els, opts] = this.putRef([], "hook");
2738
+ this.pushWithReply(() => [ref, els, opts], "event", {
2505
2739
  type: "hook",
2506
2740
  event,
2507
2741
  value: payload,
@@ -2509,38 +2743,44 @@ var View = class {
2509
2743
  }, (resp, reply) => onReply(reply, ref));
2510
2744
  return ref;
2511
2745
  }
2512
- extractMeta(el, meta) {
2746
+ extractMeta(el, meta, value) {
2513
2747
  let prefix = this.binding("value-");
2514
2748
  for (let i = 0; i < el.attributes.length; i++) {
2749
+ if (!meta) {
2750
+ meta = {};
2751
+ }
2515
2752
  let name = el.attributes[i].name;
2516
2753
  if (name.startsWith(prefix)) {
2517
2754
  meta[name.replace(prefix, "")] = el.getAttribute(name);
2518
2755
  }
2519
2756
  }
2520
2757
  if (el.value !== void 0) {
2758
+ if (!meta) {
2759
+ meta = {};
2760
+ }
2521
2761
  meta.value = el.value;
2522
2762
  if (el.tagName === "INPUT" && CHECKABLE_INPUTS.indexOf(el.type) >= 0 && !el.checked) {
2523
2763
  delete meta.value;
2524
2764
  }
2525
2765
  }
2766
+ if (value) {
2767
+ if (!meta) {
2768
+ meta = {};
2769
+ }
2770
+ for (let key in value) {
2771
+ meta[key] = value[key];
2772
+ }
2773
+ }
2526
2774
  return meta;
2527
2775
  }
2528
- pushEvent(type, el, targetCtx, phxEvent, meta) {
2529
- this.pushWithReply(() => this.putRef([el], type), "event", {
2776
+ pushEvent(type, el, targetCtx, phxEvent, meta, opts = {}) {
2777
+ this.pushWithReply(() => this.putRef([el], type, opts), "event", {
2530
2778
  type,
2531
2779
  event: phxEvent,
2532
- value: this.extractMeta(el, meta),
2780
+ value: this.extractMeta(el, meta, opts.value),
2533
2781
  cid: this.targetComponentID(el, targetCtx)
2534
2782
  });
2535
2783
  }
2536
- pushKey(keyElement, targetCtx, kind, phxEvent, meta) {
2537
- this.pushWithReply(() => this.putRef([keyElement], kind), "event", {
2538
- type: kind,
2539
- event: phxEvent,
2540
- value: this.extractMeta(keyElement, meta),
2541
- cid: this.targetComponentID(keyElement, targetCtx)
2542
- });
2543
- }
2544
2784
  pushFileProgress(fileEl, entryRef, progress, onReply = function() {
2545
2785
  }) {
2546
2786
  this.liveSocket.withinOwners(fileEl.form, (view, targetCtx) => {
@@ -2553,12 +2793,12 @@ var View = class {
2553
2793
  }, onReply);
2554
2794
  });
2555
2795
  }
2556
- pushInput(inputEl, targetCtx, forceCid, phxEvent, eventTarget, callback) {
2796
+ pushInput(inputEl, targetCtx, forceCid, phxEvent, opts, callback) {
2557
2797
  let uploads;
2558
2798
  let cid = isCid(forceCid) ? forceCid : this.targetComponentID(inputEl.form, targetCtx);
2559
- let refGenerator = () => this.putRef([inputEl, inputEl.form], "change");
2560
- let formData = serializeForm(inputEl.form, { _target: eventTarget.name });
2561
- if (inputEl.files && inputEl.files.length > 0) {
2799
+ let refGenerator = () => this.putRef([inputEl, inputEl.form], "change", opts);
2800
+ let formData = serializeForm(inputEl.form, { _target: opts._target });
2801
+ if (dom_default.isUploadInput(inputEl) && inputEl.files && inputEl.files.length > 0) {
2562
2802
  LiveUploader.trackFiles(inputEl, Array.from(inputEl.files));
2563
2803
  }
2564
2804
  uploads = LiveUploader.serializeUploads(inputEl);
@@ -2587,19 +2827,19 @@ var View = class {
2587
2827
  triggerAwaitingSubmit(formEl) {
2588
2828
  let awaitingSubmit = this.getScheduledSubmit(formEl);
2589
2829
  if (awaitingSubmit) {
2590
- let [_el, _ref, callback] = awaitingSubmit;
2830
+ let [_el, _ref, _opts, callback] = awaitingSubmit;
2591
2831
  this.cancelSubmit(formEl);
2592
2832
  callback();
2593
2833
  }
2594
2834
  }
2595
2835
  getScheduledSubmit(formEl) {
2596
- return this.formSubmits.find(([el, _callback]) => el.isSameNode(formEl));
2836
+ return this.formSubmits.find(([el, _ref, _opts, _callback]) => el.isSameNode(formEl));
2597
2837
  }
2598
- scheduleSubmit(formEl, ref, callback) {
2838
+ scheduleSubmit(formEl, ref, opts, callback) {
2599
2839
  if (this.getScheduledSubmit(formEl)) {
2600
2840
  return true;
2601
2841
  }
2602
- this.formSubmits.push([formEl, ref, callback]);
2842
+ this.formSubmits.push([formEl, ref, opts, callback]);
2603
2843
  }
2604
2844
  cancelSubmit(formEl) {
2605
2845
  this.formSubmits = this.formSubmits.filter(([el, ref, _callback]) => {
@@ -2611,7 +2851,7 @@ var View = class {
2611
2851
  }
2612
2852
  });
2613
2853
  }
2614
- pushFormSubmit(formEl, targetCtx, phxEvent, onReply) {
2854
+ pushFormSubmit(formEl, targetCtx, phxEvent, opts, onReply) {
2615
2855
  let filterIgnored = (el) => {
2616
2856
  let userIgnored = closestPhxBinding(el, `${this.binding(PHX_UPDATE)}=ignore`, el.form);
2617
2857
  return !(userIgnored || closestPhxBinding(el, "data-phx-update=ignore", el.form));
@@ -2639,15 +2879,16 @@ var View = class {
2639
2879
  }
2640
2880
  });
2641
2881
  formEl.setAttribute(this.binding(PHX_PAGE_LOADING), "");
2642
- return this.putRef([formEl].concat(disables).concat(buttons).concat(inputs), "submit");
2882
+ return this.putRef([formEl].concat(disables).concat(buttons).concat(inputs), "submit", opts);
2643
2883
  };
2644
2884
  let cid = this.targetComponentID(formEl, targetCtx);
2645
2885
  if (LiveUploader.hasUploadsInProgress(formEl)) {
2646
2886
  let [ref, _els] = refGenerator();
2647
- return this.scheduleSubmit(formEl, ref, () => this.pushFormSubmit(formEl, targetCtx, phxEvent, onReply));
2887
+ let push = () => this.pushFormSubmit(formEl, targetCtx, phxEvent, opts, onReply);
2888
+ return this.scheduleSubmit(formEl, ref, opts, push);
2648
2889
  } else if (LiveUploader.inputsAwaitingPreflight(formEl).length > 0) {
2649
2890
  let [ref, els] = refGenerator();
2650
- let proxyRefGen = () => [ref, els];
2891
+ let proxyRefGen = () => [ref, els, opts];
2651
2892
  this.uploadFiles(formEl, targetCtx, ref, cid, (_uploads) => {
2652
2893
  let formData = serializeForm(formEl, {});
2653
2894
  this.pushWithReply(proxyRefGen, "event", {
@@ -2719,22 +2960,24 @@ var View = class {
2719
2960
  this.liveSocket.withinOwners(form, (view, targetCtx) => {
2720
2961
  let input = form.elements[0];
2721
2962
  let phxEvent = form.getAttribute(this.binding(PHX_AUTO_RECOVER)) || form.getAttribute(this.binding("change"));
2722
- view.pushInput(input, targetCtx, newCid, phxEvent, input, callback);
2963
+ js_default.exec("change", phxEvent, view, input, ["push", { _target: input.name, newCid, callback }]);
2723
2964
  });
2724
2965
  }
2725
2966
  pushLinkPatch(href, targetEl, callback) {
2726
2967
  let linkRef = this.liveSocket.setPendingLink(href);
2727
2968
  let refGen = targetEl ? () => this.putRef([targetEl], "click") : null;
2728
2969
  this.pushWithReply(refGen, "live_patch", { url: href }, (resp) => {
2729
- if (resp.link_redirect) {
2730
- this.liveSocket.replaceMain(href, null, callback, linkRef);
2731
- } else {
2732
- if (this.liveSocket.commitPendingLink(linkRef)) {
2733
- this.href = href;
2970
+ this.liveSocket.requestDOMUpdate(() => {
2971
+ if (resp.link_redirect) {
2972
+ this.liveSocket.replaceMain(href, null, callback, linkRef);
2973
+ } else {
2974
+ if (this.liveSocket.commitPendingLink(linkRef)) {
2975
+ this.href = href;
2976
+ }
2977
+ this.applyPendingUpdates();
2978
+ callback && callback(linkRef);
2734
2979
  }
2735
- this.applyPendingUpdates();
2736
- callback && callback(linkRef);
2737
- }
2980
+ });
2738
2981
  }).receive("timeout", () => this.liveSocket.redirect(window.location.href));
2739
2982
  }
2740
2983
  formsForRecovery(html) {
@@ -2775,10 +3018,10 @@ var View = class {
2775
3018
  ownsElement(el) {
2776
3019
  return el.getAttribute(PHX_PARENT_ID) === this.id || maybe(el.closest(PHX_VIEW_SELECTOR), (node) => node.id) === this.id;
2777
3020
  }
2778
- submitForm(form, targetCtx, phxEvent) {
3021
+ submitForm(form, targetCtx, phxEvent, opts = {}) {
2779
3022
  dom_default.putPrivate(form, PHX_HAS_SUBMITTED, true);
2780
3023
  this.liveSocket.blurActiveElement(this);
2781
- this.pushFormSubmit(form, targetCtx, phxEvent, () => {
3024
+ this.pushFormSubmit(form, targetCtx, phxEvent, opts, () => {
2782
3025
  this.liveSocket.restorePreviouslyActiveFocus();
2783
3026
  });
2784
3027
  }
@@ -2823,6 +3066,7 @@ var LiveSocket = class {
2823
3066
  this.sessionStorage = opts.sessionStorage || window.sessionStorage;
2824
3067
  this.boundTopLevelEvents = false;
2825
3068
  this.domCallbacks = Object.assign({ onNodeAdded: closure(), onBeforeElUpdated: closure() }, opts.dom || {});
3069
+ this.transitions = new TransitionSet();
2826
3070
  window.addEventListener("pagehide", (_e) => {
2827
3071
  this.unloaded = true;
2828
3072
  });
@@ -2881,6 +3125,9 @@ var LiveSocket = class {
2881
3125
  disconnect(callback) {
2882
3126
  this.socket.disconnect(callback);
2883
3127
  }
3128
+ execJS(el, encodedJS, eventType = null) {
3129
+ this.owner(el, (view) => js_default.exec(eventType, encodedJS, view, el));
3130
+ }
2884
3131
  triggerDOM(kind, args) {
2885
3132
  this.domCallbacks[kind](...args);
2886
3133
  }
@@ -2902,6 +3149,13 @@ var LiveSocket = class {
2902
3149
  debug(view, kind, msg, obj);
2903
3150
  }
2904
3151
  }
3152
+ requestDOMUpdate(callback) {
3153
+ this.transitions.after(callback);
3154
+ }
3155
+ transition(time, onDone = function() {
3156
+ }) {
3157
+ this.transitions.addTransition(time, onDone);
3158
+ }
2905
3159
  onChannel(channel, event, cb) {
2906
3160
  channel.on(event, (data) => {
2907
3161
  let latency = this.getLatencySim();
@@ -3007,10 +3261,23 @@ var LiveSocket = class {
3007
3261
  this.main.destroy();
3008
3262
  this.main = this.newRootView(newMainEl, flash);
3009
3263
  this.main.setRedirect(href);
3010
- this.main.join((joinCount) => {
3264
+ this.transitionRemoves();
3265
+ this.main.join((joinCount, onDone) => {
3011
3266
  if (joinCount === 1 && this.commitPendingLink(linkRef)) {
3012
- oldMainEl.replaceWith(newMainEl);
3013
- callback && callback();
3267
+ this.requestDOMUpdate(() => {
3268
+ oldMainEl.replaceWith(newMainEl);
3269
+ callback && callback();
3270
+ onDone();
3271
+ });
3272
+ }
3273
+ });
3274
+ }
3275
+ transitionRemoves(elements) {
3276
+ let removeAttr = this.binding("remove");
3277
+ elements = elements || dom_default.all(document, `[${removeAttr}]`);
3278
+ elements.forEach((el) => {
3279
+ if (document.body.contains(el)) {
3280
+ this.execJS(el, el.getAttribute(removeAttr), "remove");
3014
3281
  }
3015
3282
  });
3016
3283
  }
@@ -3029,14 +3296,7 @@ var LiveSocket = class {
3029
3296
  }
3030
3297
  }
3031
3298
  withinOwners(childEl, callback) {
3032
- this.owner(childEl, (view) => {
3033
- let phxTarget = childEl.getAttribute(this.binding("target"));
3034
- if (phxTarget === null) {
3035
- callback(view, childEl);
3036
- } else {
3037
- view.withinTargets(phxTarget, callback);
3038
- }
3039
- });
3299
+ this.owner(childEl, (view) => callback(view, childEl));
3040
3300
  }
3041
3301
  getViewByEl(el) {
3042
3302
  let rootId = el.getAttribute(PHX_ROOT_ID);
@@ -3112,22 +3372,25 @@ var LiveSocket = class {
3112
3372
  this.bindNav();
3113
3373
  this.bindClicks();
3114
3374
  this.bindForms();
3115
- this.bind({ keyup: "keyup", keydown: "keydown" }, (e, type, view, target, targetCtx, phxEvent, _phxTarget) => {
3116
- let matchKey = target.getAttribute(this.binding(PHX_KEY));
3375
+ this.bind({ keyup: "keyup", keydown: "keydown" }, (e, type, view, targetEl, phxEvent, eventTarget) => {
3376
+ let matchKey = targetEl.getAttribute(this.binding(PHX_KEY));
3117
3377
  let pressedKey = e.key && e.key.toLowerCase();
3118
3378
  if (matchKey && matchKey.toLowerCase() !== pressedKey) {
3119
3379
  return;
3120
3380
  }
3121
- view.pushKey(target, targetCtx, type, phxEvent, { key: e.key, ...this.eventMeta(type, e, target) });
3381
+ let data = { key: e.key, ...this.eventMeta(type, e, targetEl) };
3382
+ js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
3122
3383
  });
3123
- this.bind({ blur: "focusout", focus: "focusin" }, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {
3124
- if (!phxTarget) {
3125
- view.pushEvent(type, targetEl, targetCtx, phxEvent, this.eventMeta(type, e, targetEl));
3384
+ this.bind({ blur: "focusout", focus: "focusin" }, (e, type, view, targetEl, phxEvent, eventTarget) => {
3385
+ if (!eventTarget) {
3386
+ let data = { key: e.key, ...this.eventMeta(type, e, targetEl) };
3387
+ js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
3126
3388
  }
3127
3389
  });
3128
3390
  this.bind({ blur: "blur", focus: "focus" }, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {
3129
- if (phxTarget && !phxTarget !== "window") {
3130
- view.pushEvent(type, targetEl, targetCtx, phxEvent, this.eventMeta(type, e, targetEl));
3391
+ if (phxTarget === "window") {
3392
+ let data = this.eventMeta(type, e, targetEl);
3393
+ js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
3131
3394
  }
3132
3395
  });
3133
3396
  window.addEventListener("dragover", (e) => e.preventDefault());
@@ -3187,16 +3450,16 @@ var LiveSocket = class {
3187
3450
  let targetPhxEvent = e.target.getAttribute && e.target.getAttribute(binding);
3188
3451
  if (targetPhxEvent) {
3189
3452
  this.debounce(e.target, e, () => {
3190
- this.withinOwners(e.target, (view, targetCtx) => {
3191
- callback(e, event, view, e.target, targetCtx, targetPhxEvent, null);
3453
+ this.withinOwners(e.target, (view) => {
3454
+ callback(e, event, view, e.target, targetPhxEvent, null);
3192
3455
  });
3193
3456
  });
3194
3457
  } else {
3195
3458
  dom_default.all(document, `[${windowBinding}]`, (el) => {
3196
3459
  let phxEvent = el.getAttribute(windowBinding);
3197
3460
  this.debounce(el, e, () => {
3198
- this.withinOwners(el, (view, targetCtx) => {
3199
- callback(e, event, view, el, targetCtx, phxEvent, "window");
3461
+ this.withinOwners(el, (view) => {
3462
+ callback(e, event, view, el, phxEvent, "window");
3200
3463
  });
3201
3464
  });
3202
3465
  });
@@ -3219,6 +3482,7 @@ var LiveSocket = class {
3219
3482
  target = e.target.matches(`[${click}]`) ? e.target : e.target.querySelector(`[${click}]`);
3220
3483
  } else {
3221
3484
  target = closestPhxBinding(e.target, click);
3485
+ this.dispatchClickAway(e);
3222
3486
  }
3223
3487
  let phxEvent = target && target.getAttribute(click);
3224
3488
  if (!phxEvent) {
@@ -3228,12 +3492,23 @@ var LiveSocket = class {
3228
3492
  e.preventDefault();
3229
3493
  }
3230
3494
  this.debounce(target, e, () => {
3231
- this.withinOwners(target, (view, targetCtx) => {
3232
- view.pushEvent("click", target, targetCtx, phxEvent, this.eventMeta("click", e, target));
3495
+ this.withinOwners(target, (view) => {
3496
+ js_default.exec("click", phxEvent, view, target, ["push", { data: this.eventMeta("click", e, target) }]);
3233
3497
  });
3234
3498
  });
3235
3499
  }, capture);
3236
3500
  }
3501
+ dispatchClickAway(e) {
3502
+ let binding = this.binding("click-away");
3503
+ dom_default.all(document, `[${binding}]`, (el) => {
3504
+ if (!(el.isSameNode(e.target) || el.contains(e.target))) {
3505
+ this.withinOwners(e.target, (view) => {
3506
+ let phxEvent = el.getAttribute(binding);
3507
+ js_default.exec("click", phxEvent, view, e.target, ["push", { data: this.eventMeta("click", e, e.target) }]);
3508
+ });
3509
+ }
3510
+ });
3511
+ }
3237
3512
  bindNav() {
3238
3513
  if (!browser_default.canPushState()) {
3239
3514
  return;
@@ -3254,20 +3529,22 @@ var LiveSocket = class {
3254
3529
  }
3255
3530
  let { type, id, root, scroll } = event.state || {};
3256
3531
  let href = window.location.href;
3257
- if (this.main.isConnected() && (type === "patch" && id === this.main.id)) {
3258
- this.main.pushLinkPatch(href, null);
3259
- } else {
3260
- this.replaceMain(href, null, () => {
3261
- if (root) {
3262
- this.replaceRootHistory();
3263
- }
3264
- if (typeof scroll === "number") {
3265
- setTimeout(() => {
3266
- window.scrollTo(0, scroll);
3267
- }, 0);
3268
- }
3269
- });
3270
- }
3532
+ this.requestDOMUpdate(() => {
3533
+ if (this.main.isConnected() && (type === "patch" && id === this.main.id)) {
3534
+ this.main.pushLinkPatch(href, null);
3535
+ } else {
3536
+ this.replaceMain(href, null, () => {
3537
+ if (root) {
3538
+ this.replaceRootHistory();
3539
+ }
3540
+ if (typeof scroll === "number") {
3541
+ setTimeout(() => {
3542
+ window.scrollTo(0, scroll);
3543
+ }, 0);
3544
+ }
3545
+ });
3546
+ }
3547
+ });
3271
3548
  }, false);
3272
3549
  window.addEventListener("click", (e) => {
3273
3550
  let target = closestPhxBinding(e.target, PHX_LIVE_LINK);
@@ -3282,15 +3559,23 @@ var LiveSocket = class {
3282
3559
  if (this.pendingLink === href) {
3283
3560
  return;
3284
3561
  }
3285
- if (type === "patch") {
3286
- this.pushHistoryPatch(href, linkState, target);
3287
- } else if (type === "redirect") {
3288
- this.historyRedirect(href, linkState);
3289
- } else {
3290
- throw new Error(`expected ${PHX_LIVE_LINK} to be "patch" or "redirect", got: ${type}`);
3291
- }
3562
+ this.requestDOMUpdate(() => {
3563
+ if (type === "patch") {
3564
+ this.pushHistoryPatch(href, linkState, target);
3565
+ } else if (type === "redirect") {
3566
+ this.historyRedirect(href, linkState);
3567
+ } else {
3568
+ throw new Error(`expected ${PHX_LIVE_LINK} to be "patch" or "redirect", got: ${type}`);
3569
+ }
3570
+ });
3292
3571
  }, false);
3293
3572
  }
3573
+ dispatchEvent(event, payload = {}) {
3574
+ dom_default.dispatchEvent(window, `phx:${event}`, payload);
3575
+ }
3576
+ dispatchEvents(events) {
3577
+ events.forEach(([event, payload]) => this.dispatchEvent(event, payload));
3578
+ }
3294
3579
  withPageLoading(info, callback) {
3295
3580
  dom_default.dispatchEvent(window, "phx:page-loading-start", info);
3296
3581
  let done = () => dom_default.dispatchEvent(window, "phx:page-loading-stop", info);
@@ -3342,7 +3627,9 @@ var LiveSocket = class {
3342
3627
  }
3343
3628
  e.preventDefault();
3344
3629
  e.target.disabled = true;
3345
- this.withinOwners(e.target, (view, targetCtx) => view.submitForm(e.target, targetCtx, phxEvent));
3630
+ this.withinOwners(e.target, (view) => {
3631
+ js_default.exec("submit", phxEvent, view, e.target, ["push", {}]);
3632
+ });
3346
3633
  }, false);
3347
3634
  for (let type of ["change", "input"]) {
3348
3635
  this.on(type, (e) => {
@@ -3362,12 +3649,12 @@ var LiveSocket = class {
3362
3649
  }
3363
3650
  dom_default.putPrivate(input, "prev-iteration", { at: currentIterations, type });
3364
3651
  this.debounce(input, e, () => {
3365
- this.withinOwners(input.form, (view, targetCtx) => {
3652
+ this.withinOwners(input.form, (view) => {
3366
3653
  dom_default.putPrivate(input, PHX_HAS_FOCUSED, true);
3367
3654
  if (!dom_default.isTextualInput(input)) {
3368
3655
  this.setActiveElement(input);
3369
3656
  }
3370
- view.pushInput(input, targetCtx, null, phxEvent, e.target);
3657
+ js_default.exec("change", phxEvent, view, input, ["push", { _target: e.target.name }]);
3371
3658
  });
3372
3659
  });
3373
3660
  }, false);
@@ -3393,6 +3680,47 @@ var LiveSocket = class {
3393
3680
  });
3394
3681
  }
3395
3682
  };
3683
+ var TransitionSet = class {
3684
+ constructor() {
3685
+ this.transitions = new Set();
3686
+ this.pendingOps = [];
3687
+ this.reset();
3688
+ }
3689
+ reset() {
3690
+ this.transitions.forEach((timer) => {
3691
+ cancelTimeout(timer);
3692
+ this.transitions.delete(timer);
3693
+ });
3694
+ this.flushPendingOps();
3695
+ }
3696
+ after(callback) {
3697
+ if (this.size() === 0) {
3698
+ callback();
3699
+ } else {
3700
+ this.pushPendingOp(callback);
3701
+ }
3702
+ }
3703
+ addTransition(time, onDone) {
3704
+ let timer = setTimeout(() => {
3705
+ this.transitions.delete(timer);
3706
+ onDone();
3707
+ if (this.size() === 0) {
3708
+ this.flushPendingOps();
3709
+ }
3710
+ }, time);
3711
+ this.transitions.add(timer);
3712
+ }
3713
+ pushPendingOp(op) {
3714
+ this.pendingOps.push(op);
3715
+ }
3716
+ size() {
3717
+ return this.transitions.size;
3718
+ }
3719
+ flushPendingOps() {
3720
+ this.pendingOps.forEach((op) => op());
3721
+ this.pendingOps = [];
3722
+ }
3723
+ };
3396
3724
  export {
3397
3725
  LiveSocket
3398
3726
  };