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.
@@ -55,10 +55,10 @@ var LiveView = (() => {
55
55
  var PHX_ACTIVE_ENTRY_REFS = "data-phx-active-refs";
56
56
  var PHX_LIVE_FILE_UPDATED = "phx:live-file:updated";
57
57
  var PHX_SKIP = "data-phx-skip";
58
- var PHX_REMOVE = "data-phx-remove";
58
+ var PHX_PRUNE = "data-phx-prune";
59
59
  var PHX_PAGE_LOADING = "page-loading";
60
60
  var PHX_CONNECTED_CLASS = "phx-connected";
61
- var PHX_DISCONNECTED_CLASS = "phx-disconnected";
61
+ var PHX_DISCONNECTED_CLASS = "phx-loading";
62
62
  var PHX_NO_FEEDBACK_CLASS = "phx-no-feedback";
63
63
  var PHX_ERROR_CLASS = "phx-error";
64
64
  var PHX_PARENT_ID = "data-phx-parent-id";
@@ -312,7 +312,9 @@ var LiveView = (() => {
312
312
  return node.id && DOM.private(node, "destroyed") ? true : false;
313
313
  },
314
314
  markPhxChildDestroyed(el) {
315
- el.setAttribute(PHX_SESSION, "");
315
+ if (this.isPhxChild(el)) {
316
+ el.setAttribute(PHX_SESSION, "");
317
+ }
316
318
  this.putPrivate(el, "destroyed", true);
317
319
  },
318
320
  findPhxChildrenInFragment(html, parentId) {
@@ -366,9 +368,17 @@ var LiveView = (() => {
366
368
  }
367
369
  el[PHX_PRIVATE][key] = value;
368
370
  },
371
+ updatePrivate(el, key, defaultVal, updateFunc) {
372
+ let existing = this.private(el, key);
373
+ if (existing === void 0) {
374
+ this.putPrivate(el, key, updateFunc(defaultVal));
375
+ } else {
376
+ this.putPrivate(el, key, updateFunc(existing));
377
+ }
378
+ },
369
379
  copyPrivates(target, source) {
370
380
  if (source[PHX_PRIVATE]) {
371
- target[PHX_PRIVATE] = clone(source[PHX_PRIVATE]);
381
+ target[PHX_PRIVATE] = source[PHX_PRIVATE];
372
382
  }
373
383
  },
374
384
  putTitle(str) {
@@ -550,14 +560,6 @@ var LiveView = (() => {
550
560
  el.checked = el.getAttribute("checked") !== null;
551
561
  }
552
562
  },
553
- syncPropsToAttrs(el) {
554
- if (el instanceof HTMLSelectElement) {
555
- let selectedItem = el.options.item(el.selectedIndex);
556
- if (selectedItem && selectedItem.getAttribute("selected") === null) {
557
- selectedItem.setAttribute("selected", "");
558
- }
559
- }
560
- },
561
563
  isTextualInput(el) {
562
564
  return FOCUSABLE_INPUTS.indexOf(el.type) >= 0;
563
565
  },
@@ -603,7 +605,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
603
605
  }
604
606
  },
605
607
  replaceRootContainer(container, tagName, attrs) {
606
- let retainedAttrs = new Set(["id", PHX_SESSION, PHX_STATIC, PHX_MAIN]);
608
+ let retainedAttrs = new Set(["id", PHX_SESSION, PHX_STATIC, PHX_MAIN, PHX_ROOT_ID]);
607
609
  if (container.tagName.toLowerCase() === tagName.toLowerCase()) {
608
610
  Array.from(container.attributes).filter((attr) => !retainedAttrs.has(attr.name.toLowerCase())).forEach((attr) => container.removeAttribute(attr.name));
609
611
  Object.keys(attrs).filter((name) => !retainedAttrs.has(name.toLowerCase())).forEach((attr) => container.setAttribute(attr, attrs[attr]));
@@ -616,6 +618,39 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
616
618
  container.replaceWith(newContainer);
617
619
  return newContainer;
618
620
  }
621
+ },
622
+ getSticky(el, name, defaultVal) {
623
+ let op = (DOM.private(el, "sticky") || []).find(([existingName]) => name === existingName);
624
+ if (op) {
625
+ let [_name, _op, stashedResult] = op;
626
+ return stashedResult;
627
+ } else {
628
+ return typeof defaultVal === "function" ? defaultVal() : defaultVal;
629
+ }
630
+ },
631
+ deleteSticky(el, name) {
632
+ this.updatePrivate(el, "sticky", [], (ops) => {
633
+ return ops.filter(([existingName, _]) => existingName !== name);
634
+ });
635
+ },
636
+ putSticky(el, name, op) {
637
+ let stashedResult = op(el);
638
+ this.updatePrivate(el, "sticky", [], (ops) => {
639
+ let existingIndex = ops.findIndex(([existingName]) => name === existingName);
640
+ if (existingIndex >= 0) {
641
+ ops[existingIndex] = [name, op, stashedResult];
642
+ } else {
643
+ ops.push([name, op, stashedResult]);
644
+ }
645
+ return ops;
646
+ });
647
+ },
648
+ applyStickyOperations(el) {
649
+ let ops = DOM.private(el, "sticky");
650
+ if (!ops) {
651
+ return;
652
+ }
653
+ ops.forEach(([name, op, _stashed]) => this.putSticky(el, name, op));
619
654
  }
620
655
  };
621
656
  var dom_default = DOM;
@@ -1413,7 +1448,8 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1413
1448
  afteradded: [],
1414
1449
  afterupdated: [],
1415
1450
  afterdiscarded: [],
1416
- afterphxChildAdded: []
1451
+ afterphxChildAdded: [],
1452
+ aftertransitionsDiscarded: []
1417
1453
  };
1418
1454
  }
1419
1455
  before(kind, callback) {
@@ -1430,7 +1466,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1430
1466
  }
1431
1467
  markPrunableContentForRemoval() {
1432
1468
  dom_default.all(this.container, "[phx-update=append] > *, [phx-update=prepend] > *", (el) => {
1433
- el.setAttribute(PHX_REMOVE, "");
1469
+ el.setAttribute(PHX_PRUNE, "");
1434
1470
  });
1435
1471
  }
1436
1472
  perform() {
@@ -1445,9 +1481,11 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1445
1481
  let phxFeedbackFor = liveSocket.binding(PHX_FEEDBACK_FOR);
1446
1482
  let disableWith = liveSocket.binding(PHX_DISABLE_WITH);
1447
1483
  let phxTriggerExternal = liveSocket.binding(PHX_TRIGGER_ACTION);
1484
+ let phxRemove = liveSocket.binding("remove");
1448
1485
  let added = [];
1449
1486
  let updates = [];
1450
1487
  let appendPrependUpdates = [];
1488
+ let pendingRemoves = [];
1451
1489
  let externalFormTriggered = null;
1452
1490
  let diffHTML = liveSocket.time("premorph container prep", () => {
1453
1491
  return this.buildDiffHTML(container, html, phxUpdate, targetContainer);
@@ -1465,6 +1503,11 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1465
1503
  return el;
1466
1504
  },
1467
1505
  onNodeAdded: (el) => {
1506
+ if (el instanceof HTMLImageElement && el.srcset) {
1507
+ el.srcset = el.srcset;
1508
+ } else if (el instanceof HTMLVideoElement && el.autoplay) {
1509
+ el.play();
1510
+ }
1468
1511
  if (dom_default.isNowTriggerFormExternal(el, phxTriggerExternal)) {
1469
1512
  externalFormTriggered = el;
1470
1513
  }
@@ -1481,12 +1524,16 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1481
1524
  this.trackAfter("discarded", el);
1482
1525
  },
1483
1526
  onBeforeNodeDiscarded: (el) => {
1484
- if (el.getAttribute && el.getAttribute(PHX_REMOVE) !== null) {
1527
+ if (el.getAttribute && el.getAttribute(PHX_PRUNE) !== null) {
1485
1528
  return true;
1486
1529
  }
1487
1530
  if (el.parentNode !== null && dom_default.isPhxUpdate(el.parentNode, phxUpdate, ["append", "prepend"]) && el.id) {
1488
1531
  return false;
1489
1532
  }
1533
+ if (el.getAttribute && el.getAttribute(phxRemove)) {
1534
+ pendingRemoves.push(el);
1535
+ return false;
1536
+ }
1490
1537
  if (this.skipCIDSibling(el)) {
1491
1538
  return false;
1492
1539
  }
@@ -1507,6 +1554,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1507
1554
  this.trackBefore("updated", fromEl, toEl);
1508
1555
  dom_default.mergeAttrs(fromEl, toEl, { isIgnored: true });
1509
1556
  updates.push(fromEl);
1557
+ dom_default.applyStickyOperations(fromEl);
1510
1558
  return false;
1511
1559
  }
1512
1560
  if (fromEl.type === "number" && (fromEl.validity && fromEl.validity.badInput)) {
@@ -1517,6 +1565,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1517
1565
  this.trackBefore("updated", fromEl, toEl);
1518
1566
  updates.push(fromEl);
1519
1567
  }
1568
+ dom_default.applyStickyOperations(fromEl);
1520
1569
  return false;
1521
1570
  }
1522
1571
  if (dom_default.isPhxChild(toEl)) {
@@ -1526,23 +1575,25 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1526
1575
  fromEl.setAttribute(PHX_SESSION, prevSession);
1527
1576
  }
1528
1577
  fromEl.setAttribute(PHX_ROOT_ID, this.rootID);
1578
+ dom_default.applyStickyOperations(fromEl);
1529
1579
  return false;
1530
1580
  }
1531
1581
  dom_default.copyPrivates(toEl, fromEl);
1532
1582
  dom_default.discardError(targetContainer, toEl, phxFeedbackFor);
1533
- dom_default.syncPropsToAttrs(toEl);
1534
1583
  let isFocusedFormEl = focused && fromEl.isSameNode(focused) && dom_default.isFormInput(fromEl);
1535
- if (isFocusedFormEl && !this.forceFocusedSelectUpdate(fromEl, toEl)) {
1584
+ if (isFocusedFormEl) {
1536
1585
  this.trackBefore("updated", fromEl, toEl);
1537
1586
  dom_default.mergeFocusedInput(fromEl, toEl);
1538
1587
  dom_default.syncAttrsToProps(fromEl);
1539
1588
  updates.push(fromEl);
1589
+ dom_default.applyStickyOperations(fromEl);
1540
1590
  return false;
1541
1591
  } else {
1542
1592
  if (dom_default.isPhxUpdate(toEl, phxUpdate, ["append", "prepend"])) {
1543
1593
  appendPrependUpdates.push(new DOMPostMorphRestorer(fromEl, toEl, toEl.getAttribute(phxUpdate)));
1544
1594
  }
1545
1595
  dom_default.syncAttrsToProps(toEl);
1596
+ dom_default.applyStickyOperations(toEl);
1546
1597
  this.trackBefore("updated", fromEl, toEl);
1547
1598
  return true;
1548
1599
  }
@@ -1561,16 +1612,19 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
1561
1612
  dom_default.dispatchEvent(document, "phx:update");
1562
1613
  added.forEach((el) => this.trackAfter("added", el));
1563
1614
  updates.forEach((el) => this.trackAfter("updated", el));
1615
+ if (pendingRemoves.length > 0) {
1616
+ liveSocket.transitionRemoves(pendingRemoves);
1617
+ liveSocket.requestDOMUpdate(() => {
1618
+ pendingRemoves.forEach((el) => el.remove());
1619
+ this.trackAfter("transitionsDiscarded", pendingRemoves);
1620
+ });
1621
+ }
1564
1622
  if (externalFormTriggered) {
1565
1623
  liveSocket.disconnect();
1566
1624
  externalFormTriggered.submit();
1567
1625
  }
1568
1626
  return true;
1569
1627
  }
1570
- forceFocusedSelectUpdate(fromEl, toEl) {
1571
- let isSelect = ["select", "select-one", "select-multiple"].find((t) => t === fromEl.type);
1572
- return fromEl.multiple === true || isSelect && fromEl.innerHTML != toEl.innerHTML;
1573
- }
1574
1628
  isCIDPatch() {
1575
1629
  return this.cidPatch;
1576
1630
  }
@@ -1872,13 +1926,13 @@ within:
1872
1926
  }
1873
1927
  handleEvent(event, callback) {
1874
1928
  let callbackRef = (customEvent, bypass) => bypass ? event : callback(customEvent.detail);
1875
- window.addEventListener(`phx:hook:${event}`, callbackRef);
1929
+ window.addEventListener(`phx:${event}`, callbackRef);
1876
1930
  this.__listeners.add(callbackRef);
1877
1931
  return callbackRef;
1878
1932
  }
1879
1933
  removeHandleEvent(callbackRef) {
1880
1934
  let event = callbackRef(null, true);
1881
- window.removeEventListener(`phx:hook:${event}`, callbackRef);
1935
+ window.removeEventListener(`phx:${event}`, callbackRef);
1882
1936
  this.__listeners.delete(callbackRef);
1883
1937
  }
1884
1938
  upload(name, files) {
@@ -1892,6 +1946,156 @@ within:
1892
1946
  }
1893
1947
  };
1894
1948
 
1949
+ // js/phoenix_live_view/js.js
1950
+ var JS = {
1951
+ exec(eventType, phxEvent, view, el, defaults) {
1952
+ let [defaultKind, defaultArgs] = defaults || [null, {}];
1953
+ let commands = phxEvent.charAt(0) === "[" ? JSON.parse(phxEvent) : [[defaultKind, defaultArgs]];
1954
+ commands.forEach(([kind, args]) => {
1955
+ if (kind === defaultKind && defaultArgs.data) {
1956
+ args.data = Object.assign(args.data || {}, defaultArgs.data);
1957
+ }
1958
+ this[`exec_${kind}`](eventType, phxEvent, view, el, args);
1959
+ });
1960
+ },
1961
+ exec_dispatch(eventType, phxEvent, view, sourceEl, { to, event, detail }) {
1962
+ if (to) {
1963
+ dom_default.all(document, to, (el) => dom_default.dispatchEvent(el, event, detail));
1964
+ } else {
1965
+ dom_default.dispatchEvent(sourceEl, event, detail);
1966
+ }
1967
+ },
1968
+ exec_push(eventType, phxEvent, view, sourceEl, args) {
1969
+ let { event, data, target, page_loading, loading, value } = args;
1970
+ let pushOpts = { page_loading: !!page_loading, loading, value };
1971
+ let targetSrc = eventType === "change" ? sourceEl.form : sourceEl;
1972
+ let phxTarget = target || targetSrc.getAttribute(view.binding("target")) || targetSrc;
1973
+ view.withinTargets(phxTarget, (targetView, targetCtx) => {
1974
+ if (eventType === "change") {
1975
+ let { newCid, _target, callback } = args;
1976
+ if (_target) {
1977
+ pushOpts._target = _target;
1978
+ }
1979
+ targetView.pushInput(sourceEl, targetCtx, newCid, event || phxEvent, pushOpts, callback);
1980
+ } else if (eventType === "submit") {
1981
+ targetView.submitForm(sourceEl, targetCtx, event || phxEvent, pushOpts);
1982
+ } else {
1983
+ targetView.pushEvent(eventType, sourceEl, targetCtx, event || phxEvent, data, pushOpts);
1984
+ }
1985
+ });
1986
+ },
1987
+ exec_add_class(eventType, phxEvent, view, sourceEl, { to, names, transition, time }) {
1988
+ if (to) {
1989
+ dom_default.all(document, to, (el) => this.addOrRemoveClasses(el, names, [], transition, time, view));
1990
+ } else {
1991
+ this.addOrRemoveClasses(sourceEl, names, [], transition, view);
1992
+ }
1993
+ },
1994
+ exec_remove_class(eventType, phxEvent, view, sourceEl, { to, names, transition, time }) {
1995
+ if (to) {
1996
+ dom_default.all(document, to, (el) => this.addOrRemoveClasses(el, [], names, transition, time, view));
1997
+ } else {
1998
+ this.addOrRemoveClasses(sourceEl, [], names, transition, time, view);
1999
+ }
2000
+ },
2001
+ exec_transition(eventType, phxEvent, view, sourceEl, { time, to, names }) {
2002
+ let els = to ? dom_default.all(document, to) : [sourceEl];
2003
+ els.forEach((el) => {
2004
+ this.addOrRemoveClasses(el, names, []);
2005
+ view.transition(time, () => this.addOrRemoveClasses(el, [], names));
2006
+ });
2007
+ },
2008
+ exec_toggle(eventType, phxEvent, view, sourceEl, { to, display, ins, outs, time }) {
2009
+ if (to) {
2010
+ dom_default.all(document, to, (el) => this.toggle(eventType, view, el, display, ins || [], outs || [], time));
2011
+ } else {
2012
+ this.toggle(eventType, view, sourceEl, display, ins || [], outs || [], time);
2013
+ }
2014
+ },
2015
+ exec_show(eventType, phxEvent, view, sourceEl, { to, display, transition, time }) {
2016
+ if (to) {
2017
+ dom_default.all(document, to, (el) => this.show(eventType, view, el, display, transition, time));
2018
+ } else {
2019
+ this.show(eventType, view, sourceEl, transition, time);
2020
+ }
2021
+ },
2022
+ exec_hide(eventType, phxEvent, view, sourceEl, { to, display, transition, time }) {
2023
+ if (to) {
2024
+ dom_default.all(document, to, (el) => this.hide(eventType, view, el, display, transition, time));
2025
+ } else {
2026
+ this.hide(eventType, view, sourceEl, display, transition, time);
2027
+ }
2028
+ },
2029
+ show(eventType, view, el, display, transition, time) {
2030
+ let isVisible = this.isVisible(el);
2031
+ if (transition.length > 0 && !isVisible) {
2032
+ this.toggle(eventType, view, el, display, transition, [], time);
2033
+ } else if (!isVisible) {
2034
+ this.toggle(eventType, view, el, display, [], [], null);
2035
+ }
2036
+ },
2037
+ hide(eventType, view, el, display, transition, time) {
2038
+ let isVisible = this.isVisible(el);
2039
+ if (transition.length > 0 && isVisible) {
2040
+ this.toggle(eventType, view, el, display, [], transition, time);
2041
+ } else if (isVisible) {
2042
+ this.toggle(eventType, view, el, display, [], [], time);
2043
+ }
2044
+ },
2045
+ toggle(eventType, view, el, display, in_classes, out_classes, time) {
2046
+ if (in_classes.length > 0 || out_classes.length > 0) {
2047
+ if (this.isVisible(el)) {
2048
+ this.addOrRemoveClasses(el, out_classes, in_classes);
2049
+ view.transition(time, () => {
2050
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = "none");
2051
+ this.addOrRemoveClasses(el, [], out_classes);
2052
+ });
2053
+ } else {
2054
+ if (eventType === "remove") {
2055
+ return;
2056
+ }
2057
+ this.addOrRemoveClasses(el, in_classes, out_classes);
2058
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = display || "block");
2059
+ view.transition(time, () => {
2060
+ this.addOrRemoveClasses(el, [], in_classes);
2061
+ });
2062
+ }
2063
+ } else {
2064
+ let newDisplay = this.isVisible(el) ? "none" : display || "block";
2065
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = newDisplay);
2066
+ }
2067
+ },
2068
+ addOrRemoveClasses(el, adds, removes, transition, time, view) {
2069
+ if (transition && transition.length > 0) {
2070
+ this.addOrRemoveClasses(el, transition, []);
2071
+ return view.transition(time, () => this.addOrRemoveClasses(el, adds, removes.concat(transition)));
2072
+ }
2073
+ window.requestAnimationFrame(() => {
2074
+ let [prevAdds, prevRemoves] = dom_default.getSticky(el, "classes", [[], []]);
2075
+ let keepAdds = adds.filter((name) => prevAdds.indexOf(name) < 0 && !el.classList.contains(name));
2076
+ let keepRemoves = removes.filter((name) => prevRemoves.indexOf(name) < 0 && el.classList.contains(name));
2077
+ let newAdds = prevAdds.filter((name) => removes.indexOf(name) < 0).concat(keepAdds);
2078
+ let newRemoves = prevRemoves.filter((name) => adds.indexOf(name) < 0).concat(keepRemoves);
2079
+ dom_default.putSticky(el, "classes", (currentEl) => {
2080
+ currentEl.classList.remove(...newRemoves);
2081
+ currentEl.classList.add(...newAdds);
2082
+ return [newAdds, newRemoves];
2083
+ });
2084
+ });
2085
+ },
2086
+ hasAllClasses(el, classes) {
2087
+ return classes.every((name) => el.classList.contains(name));
2088
+ },
2089
+ isVisible(el) {
2090
+ let style = window.getComputedStyle(el);
2091
+ return !(style.opacity === 0 || style.display === "none");
2092
+ },
2093
+ isToggledOut(el, out_classes) {
2094
+ return !this.isVisible(el) || this.hasAllClasses(el, out_classes);
2095
+ }
2096
+ };
2097
+ var js_default = JS;
2098
+
1895
2099
  // js/phoenix_live_view/view.js
1896
2100
  var serializeForm = (form, meta = {}) => {
1897
2101
  let formData = new FormData(form);
@@ -1929,7 +2133,8 @@ within:
1929
2133
  this.joinCount = this.parent ? this.parent.joinCount - 1 : 0;
1930
2134
  this.joinPending = true;
1931
2135
  this.destroyed = false;
1932
- this.joinCallback = function() {
2136
+ this.joinCallback = function(onDone) {
2137
+ onDone && onDone();
1933
2138
  };
1934
2139
  this.stopCallback = function() {
1935
2140
  };
@@ -2004,9 +2209,6 @@ within:
2004
2209
  this.el.classList.remove(PHX_CONNECTED_CLASS, PHX_DISCONNECTED_CLASS, PHX_ERROR_CLASS);
2005
2210
  this.el.classList.add(...classes);
2006
2211
  }
2007
- isLoading() {
2008
- return this.el.classList.contains(PHX_DISCONNECTED_CLASS);
2009
- }
2010
2212
  showLoader(timeout) {
2011
2213
  clearTimeout(this.loaderTimer);
2012
2214
  if (timeout) {
@@ -2030,16 +2232,20 @@ within:
2030
2232
  log(kind, msgCallback) {
2031
2233
  this.liveSocket.log(this, kind, msgCallback);
2032
2234
  }
2235
+ transition(time, onDone = function() {
2236
+ }) {
2237
+ this.liveSocket.transition(time, onDone);
2238
+ }
2033
2239
  withinTargets(phxTarget, callback) {
2034
- if (phxTarget instanceof HTMLElement) {
2240
+ if (phxTarget instanceof HTMLElement || phxTarget instanceof SVGElement) {
2035
2241
  return this.liveSocket.owner(phxTarget, (view) => callback(view, phxTarget));
2036
2242
  }
2037
- if (/^(0|[1-9]\d*)$/.test(phxTarget)) {
2243
+ if (typeof phxTarget === "number" || /^(0|[1-9]\d*)$/.test(phxTarget)) {
2038
2244
  let targets = dom_default.findComponentNodeList(this.el, phxTarget);
2039
2245
  if (targets.length === 0) {
2040
2246
  logError(`no component found matching phx-target of ${phxTarget}`);
2041
2247
  } else {
2042
- callback(this, targets[0]);
2248
+ callback(this, parseInt(phxTarget));
2043
2249
  }
2044
2250
  } else {
2045
2251
  let targets = Array.from(document.querySelectorAll(phxTarget));
@@ -2118,11 +2324,6 @@ within:
2118
2324
  this.el = dom_default.byId(this.id);
2119
2325
  this.el.setAttribute(PHX_ROOT_ID, this.root.id);
2120
2326
  }
2121
- dispatchEvents(events) {
2122
- events.forEach(([event, payload]) => {
2123
- window.dispatchEvent(new CustomEvent(`phx:hook:${event}`, { detail: payload }));
2124
- });
2125
- }
2126
2327
  applyJoinPatch(live_patch, html, events) {
2127
2328
  this.attachTrueDocEl();
2128
2329
  let patch = new DOMPatch(this, this.el, this.id, html, null);
@@ -2136,7 +2337,7 @@ within:
2136
2337
  }
2137
2338
  });
2138
2339
  this.joinPending = false;
2139
- this.dispatchEvents(events);
2340
+ this.liveSocket.dispatchEvents(events);
2140
2341
  this.applyPendingUpdates();
2141
2342
  if (live_patch) {
2142
2343
  let { kind, to } = live_patch;
@@ -2158,7 +2359,7 @@ within:
2158
2359
  }
2159
2360
  }
2160
2361
  performPatch(patch, pruneCids) {
2161
- let destroyedCIDs = [];
2362
+ let removedEls = [];
2162
2363
  let phxChildrenAdded = false;
2163
2364
  let updatedHookIds = new Set();
2164
2365
  patch.after("added", (el) => {
@@ -2181,18 +2382,31 @@ within:
2181
2382
  }
2182
2383
  });
2183
2384
  patch.after("discarded", (el) => {
2184
- let cid = this.componentID(el);
2185
- if (isCid(cid) && destroyedCIDs.indexOf(cid) === -1) {
2186
- destroyedCIDs.push(cid);
2385
+ if (el.nodeType === Node.ELEMENT_NODE) {
2386
+ removedEls.push(el);
2187
2387
  }
2188
- let hook = this.getHook(el);
2189
- hook && this.destroyHook(hook);
2190
2388
  });
2389
+ patch.after("transitionsDiscarded", (els) => this.afterElementsRemoved(els, pruneCids));
2191
2390
  patch.perform();
2391
+ this.afterElementsRemoved(removedEls, pruneCids);
2392
+ return phxChildrenAdded;
2393
+ }
2394
+ afterElementsRemoved(elements, pruneCids) {
2395
+ let destroyedCIDs = [];
2396
+ elements.forEach((parent) => {
2397
+ let components = dom_default.all(parent, `[${PHX_COMPONENT}]`);
2398
+ components.concat(parent).forEach((el) => {
2399
+ let cid = this.componentID(el);
2400
+ if (isCid(cid) && destroyedCIDs.indexOf(cid) === -1) {
2401
+ destroyedCIDs.push(cid);
2402
+ }
2403
+ let hook = this.getHook(el);
2404
+ hook && this.destroyHook(hook);
2405
+ });
2406
+ });
2192
2407
  if (pruneCids) {
2193
2408
  this.maybePushComponentsDestroyed(destroyedCIDs);
2194
2409
  }
2195
- return phxChildrenAdded;
2196
2410
  }
2197
2411
  joinNewChildren() {
2198
2412
  dom_default.findPhxChildren(this.el, this.id).forEach((el) => this.joinChild(el));
@@ -2240,13 +2454,14 @@ within:
2240
2454
  }
2241
2455
  }
2242
2456
  onAllChildJoinsComplete() {
2243
- this.joinCallback();
2244
- this.pendingJoinOps.forEach(([view, op]) => {
2245
- if (!view.isDestroyed()) {
2246
- op();
2247
- }
2457
+ this.joinCallback(() => {
2458
+ this.pendingJoinOps.forEach(([view, op]) => {
2459
+ if (!view.isDestroyed()) {
2460
+ op();
2461
+ }
2462
+ });
2463
+ this.pendingJoinOps = [];
2248
2464
  });
2249
- this.pendingJoinOps = [];
2250
2465
  }
2251
2466
  update(diff, events) {
2252
2467
  if (this.isJoinPending() || this.liveSocket.hasPendingLink()) {
@@ -2270,7 +2485,7 @@ within:
2270
2485
  phxChildrenAdded = this.performPatch(patch, true);
2271
2486
  });
2272
2487
  }
2273
- this.dispatchEvents(events);
2488
+ this.liveSocket.dispatchEvents(events);
2274
2489
  if (phxChildrenAdded) {
2275
2490
  this.joinNewChildren();
2276
2491
  }
@@ -2328,13 +2543,15 @@ within:
2328
2543
  if (this.isJoinPending()) {
2329
2544
  this.root.pendingJoinOps.push([this, () => cb(resp)]);
2330
2545
  } else {
2331
- cb(resp);
2546
+ this.liveSocket.requestDOMUpdate(() => cb(resp));
2332
2547
  }
2333
2548
  });
2334
2549
  }
2335
2550
  bindChannel() {
2336
2551
  this.liveSocket.onChannel(this.channel, "diff", (rawDiff) => {
2337
- this.applyDiff("update", rawDiff, ({ diff, events }) => this.update(diff, events));
2552
+ this.liveSocket.requestDOMUpdate(() => {
2553
+ this.applyDiff("update", rawDiff, ({ diff, events }) => this.update(diff, events));
2554
+ });
2338
2555
  });
2339
2556
  this.onChannel("redirect", ({ to, flash }) => this.onRedirect({ to, flash }));
2340
2557
  this.onChannel("live_patch", (redir) => this.onLivePatch(redir));
@@ -2370,9 +2587,17 @@ within:
2370
2587
  if (!this.parent) {
2371
2588
  this.stopCallback = this.liveSocket.withPageLoading({ to: this.href, kind: "initial" });
2372
2589
  }
2373
- this.joinCallback = () => callback && callback(this.joinCount);
2590
+ this.joinCallback = (onDone) => {
2591
+ onDone = onDone || function() {
2592
+ };
2593
+ callback ? callback(this.joinCount, onDone) : onDone();
2594
+ };
2374
2595
  this.liveSocket.wrapPush(this, { timeout: false }, () => {
2375
- 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" }));
2596
+ return this.channel.join().receive("ok", (data) => {
2597
+ if (!this.isDestroyed()) {
2598
+ this.liveSocket.requestDOMUpdate(() => this.onJoin(data));
2599
+ }
2600
+ }).receive("error", (resp) => !this.isDestroyed() && this.onJoinError(resp)).receive("timeout", () => !this.isDestroyed() && this.onJoinError({ reason: "timeout" }));
2376
2601
  });
2377
2602
  }
2378
2603
  onJoinError(resp) {
@@ -2428,10 +2653,10 @@ within:
2428
2653
  if (!this.isConnected()) {
2429
2654
  return;
2430
2655
  }
2431
- let [ref, [el]] = refGenerator ? refGenerator() : [null, []];
2656
+ let [ref, [el], opts] = refGenerator ? refGenerator() : [null, [], {}];
2432
2657
  let onLoadingDone = function() {
2433
2658
  };
2434
- if (el && el.getAttribute(this.binding(PHX_PAGE_LOADING)) !== null) {
2659
+ if (opts.page_loading || el && el.getAttribute(this.binding(PHX_PAGE_LOADING)) !== null) {
2435
2660
  onLoadingDone = this.liveSocket.withPageLoading({ kind: "element", target: el });
2436
2661
  }
2437
2662
  if (typeof payload.cid !== "number") {
@@ -2439,26 +2664,28 @@ within:
2439
2664
  }
2440
2665
  return this.liveSocket.wrapPush(this, { timeout: true }, () => {
2441
2666
  return this.channel.push(event, payload, PUSH_TIMEOUT).receive("ok", (resp) => {
2442
- let hookReply = null;
2443
- if (ref !== null) {
2444
- this.undoRefs(ref);
2445
- }
2446
- if (resp.diff) {
2447
- hookReply = this.applyDiff("update", resp.diff, ({ diff, events }) => {
2448
- this.update(diff, events);
2449
- });
2450
- }
2451
- if (resp.redirect) {
2452
- this.onRedirect(resp.redirect);
2453
- }
2454
- if (resp.live_patch) {
2455
- this.onLivePatch(resp.live_patch);
2456
- }
2457
- if (resp.live_redirect) {
2458
- this.onLiveRedirect(resp.live_redirect);
2459
- }
2460
- onLoadingDone();
2461
- onReply(resp, hookReply);
2667
+ this.liveSocket.requestDOMUpdate(() => {
2668
+ let hookReply = null;
2669
+ if (ref !== null) {
2670
+ this.undoRefs(ref);
2671
+ }
2672
+ if (resp.diff) {
2673
+ hookReply = this.applyDiff("update", resp.diff, ({ diff, events }) => {
2674
+ this.update(diff, events);
2675
+ });
2676
+ }
2677
+ if (resp.redirect) {
2678
+ this.onRedirect(resp.redirect);
2679
+ }
2680
+ if (resp.live_patch) {
2681
+ this.onLivePatch(resp.live_patch);
2682
+ }
2683
+ if (resp.live_redirect) {
2684
+ this.onLiveRedirect(resp.live_redirect);
2685
+ }
2686
+ onLoadingDone();
2687
+ onReply(resp, hookReply);
2688
+ });
2462
2689
  });
2463
2690
  });
2464
2691
  }
@@ -2491,9 +2718,12 @@ within:
2491
2718
  }
2492
2719
  });
2493
2720
  }
2494
- putRef(elements, event) {
2721
+ putRef(elements, event, opts = {}) {
2495
2722
  let newRef = this.ref++;
2496
2723
  let disableWith = this.binding(PHX_DISABLE_WITH);
2724
+ if (opts.loading) {
2725
+ elements = elements.concat(dom_default.all(document, opts.loading));
2726
+ }
2497
2727
  elements.forEach((el) => {
2498
2728
  el.classList.add(`phx-${event}-loading`);
2499
2729
  el.setAttribute(PHX_REF, newRef);
@@ -2505,21 +2735,25 @@ within:
2505
2735
  el.innerText = disableText;
2506
2736
  }
2507
2737
  });
2508
- return [newRef, elements];
2738
+ return [newRef, elements, opts];
2509
2739
  }
2510
2740
  componentID(el) {
2511
2741
  let cid = el.getAttribute && el.getAttribute(PHX_COMPONENT);
2512
2742
  return cid ? parseInt(cid) : null;
2513
2743
  }
2514
2744
  targetComponentID(target, targetCtx) {
2515
- if (target.getAttribute(this.binding("target"))) {
2745
+ if (isCid(targetCtx)) {
2746
+ return targetCtx;
2747
+ } else if (target.getAttribute(this.binding("target"))) {
2516
2748
  return this.closestComponentID(targetCtx);
2517
2749
  } else {
2518
2750
  return null;
2519
2751
  }
2520
2752
  }
2521
2753
  closestComponentID(targetCtx) {
2522
- if (targetCtx) {
2754
+ if (isCid(targetCtx)) {
2755
+ return targetCtx;
2756
+ } else if (targetCtx) {
2523
2757
  return maybe(targetCtx.closest(`[${PHX_COMPONENT}]`), (el) => this.ownsElement(el) && this.componentID(el));
2524
2758
  } else {
2525
2759
  return null;
@@ -2530,8 +2764,8 @@ within:
2530
2764
  this.log("hook", () => ["unable to push hook event. LiveView not connected", event, payload]);
2531
2765
  return false;
2532
2766
  }
2533
- let [ref, els] = this.putRef([], "hook");
2534
- this.pushWithReply(() => [ref, els], "event", {
2767
+ let [ref, els, opts] = this.putRef([], "hook");
2768
+ this.pushWithReply(() => [ref, els, opts], "event", {
2535
2769
  type: "hook",
2536
2770
  event,
2537
2771
  value: payload,
@@ -2539,38 +2773,44 @@ within:
2539
2773
  }, (resp, reply) => onReply(reply, ref));
2540
2774
  return ref;
2541
2775
  }
2542
- extractMeta(el, meta) {
2776
+ extractMeta(el, meta, value) {
2543
2777
  let prefix = this.binding("value-");
2544
2778
  for (let i = 0; i < el.attributes.length; i++) {
2779
+ if (!meta) {
2780
+ meta = {};
2781
+ }
2545
2782
  let name = el.attributes[i].name;
2546
2783
  if (name.startsWith(prefix)) {
2547
2784
  meta[name.replace(prefix, "")] = el.getAttribute(name);
2548
2785
  }
2549
2786
  }
2550
2787
  if (el.value !== void 0) {
2788
+ if (!meta) {
2789
+ meta = {};
2790
+ }
2551
2791
  meta.value = el.value;
2552
2792
  if (el.tagName === "INPUT" && CHECKABLE_INPUTS.indexOf(el.type) >= 0 && !el.checked) {
2553
2793
  delete meta.value;
2554
2794
  }
2555
2795
  }
2796
+ if (value) {
2797
+ if (!meta) {
2798
+ meta = {};
2799
+ }
2800
+ for (let key in value) {
2801
+ meta[key] = value[key];
2802
+ }
2803
+ }
2556
2804
  return meta;
2557
2805
  }
2558
- pushEvent(type, el, targetCtx, phxEvent, meta) {
2559
- this.pushWithReply(() => this.putRef([el], type), "event", {
2806
+ pushEvent(type, el, targetCtx, phxEvent, meta, opts = {}) {
2807
+ this.pushWithReply(() => this.putRef([el], type, opts), "event", {
2560
2808
  type,
2561
2809
  event: phxEvent,
2562
- value: this.extractMeta(el, meta),
2810
+ value: this.extractMeta(el, meta, opts.value),
2563
2811
  cid: this.targetComponentID(el, targetCtx)
2564
2812
  });
2565
2813
  }
2566
- pushKey(keyElement, targetCtx, kind, phxEvent, meta) {
2567
- this.pushWithReply(() => this.putRef([keyElement], kind), "event", {
2568
- type: kind,
2569
- event: phxEvent,
2570
- value: this.extractMeta(keyElement, meta),
2571
- cid: this.targetComponentID(keyElement, targetCtx)
2572
- });
2573
- }
2574
2814
  pushFileProgress(fileEl, entryRef, progress, onReply = function() {
2575
2815
  }) {
2576
2816
  this.liveSocket.withinOwners(fileEl.form, (view, targetCtx) => {
@@ -2583,12 +2823,12 @@ within:
2583
2823
  }, onReply);
2584
2824
  });
2585
2825
  }
2586
- pushInput(inputEl, targetCtx, forceCid, phxEvent, eventTarget, callback) {
2826
+ pushInput(inputEl, targetCtx, forceCid, phxEvent, opts, callback) {
2587
2827
  let uploads;
2588
2828
  let cid = isCid(forceCid) ? forceCid : this.targetComponentID(inputEl.form, targetCtx);
2589
- let refGenerator = () => this.putRef([inputEl, inputEl.form], "change");
2590
- let formData = serializeForm(inputEl.form, { _target: eventTarget.name });
2591
- if (inputEl.files && inputEl.files.length > 0) {
2829
+ let refGenerator = () => this.putRef([inputEl, inputEl.form], "change", opts);
2830
+ let formData = serializeForm(inputEl.form, { _target: opts._target });
2831
+ if (dom_default.isUploadInput(inputEl) && inputEl.files && inputEl.files.length > 0) {
2592
2832
  LiveUploader.trackFiles(inputEl, Array.from(inputEl.files));
2593
2833
  }
2594
2834
  uploads = LiveUploader.serializeUploads(inputEl);
@@ -2617,19 +2857,19 @@ within:
2617
2857
  triggerAwaitingSubmit(formEl) {
2618
2858
  let awaitingSubmit = this.getScheduledSubmit(formEl);
2619
2859
  if (awaitingSubmit) {
2620
- let [_el, _ref, callback] = awaitingSubmit;
2860
+ let [_el, _ref, _opts, callback] = awaitingSubmit;
2621
2861
  this.cancelSubmit(formEl);
2622
2862
  callback();
2623
2863
  }
2624
2864
  }
2625
2865
  getScheduledSubmit(formEl) {
2626
- return this.formSubmits.find(([el, _callback]) => el.isSameNode(formEl));
2866
+ return this.formSubmits.find(([el, _ref, _opts, _callback]) => el.isSameNode(formEl));
2627
2867
  }
2628
- scheduleSubmit(formEl, ref, callback) {
2868
+ scheduleSubmit(formEl, ref, opts, callback) {
2629
2869
  if (this.getScheduledSubmit(formEl)) {
2630
2870
  return true;
2631
2871
  }
2632
- this.formSubmits.push([formEl, ref, callback]);
2872
+ this.formSubmits.push([formEl, ref, opts, callback]);
2633
2873
  }
2634
2874
  cancelSubmit(formEl) {
2635
2875
  this.formSubmits = this.formSubmits.filter(([el, ref, _callback]) => {
@@ -2641,7 +2881,7 @@ within:
2641
2881
  }
2642
2882
  });
2643
2883
  }
2644
- pushFormSubmit(formEl, targetCtx, phxEvent, onReply) {
2884
+ pushFormSubmit(formEl, targetCtx, phxEvent, opts, onReply) {
2645
2885
  let filterIgnored = (el) => {
2646
2886
  let userIgnored = closestPhxBinding(el, `${this.binding(PHX_UPDATE)}=ignore`, el.form);
2647
2887
  return !(userIgnored || closestPhxBinding(el, "data-phx-update=ignore", el.form));
@@ -2669,15 +2909,16 @@ within:
2669
2909
  }
2670
2910
  });
2671
2911
  formEl.setAttribute(this.binding(PHX_PAGE_LOADING), "");
2672
- return this.putRef([formEl].concat(disables).concat(buttons).concat(inputs), "submit");
2912
+ return this.putRef([formEl].concat(disables).concat(buttons).concat(inputs), "submit", opts);
2673
2913
  };
2674
2914
  let cid = this.targetComponentID(formEl, targetCtx);
2675
2915
  if (LiveUploader.hasUploadsInProgress(formEl)) {
2676
2916
  let [ref, _els] = refGenerator();
2677
- return this.scheduleSubmit(formEl, ref, () => this.pushFormSubmit(formEl, targetCtx, phxEvent, onReply));
2917
+ let push = () => this.pushFormSubmit(formEl, targetCtx, phxEvent, opts, onReply);
2918
+ return this.scheduleSubmit(formEl, ref, opts, push);
2678
2919
  } else if (LiveUploader.inputsAwaitingPreflight(formEl).length > 0) {
2679
2920
  let [ref, els] = refGenerator();
2680
- let proxyRefGen = () => [ref, els];
2921
+ let proxyRefGen = () => [ref, els, opts];
2681
2922
  this.uploadFiles(formEl, targetCtx, ref, cid, (_uploads) => {
2682
2923
  let formData = serializeForm(formEl, {});
2683
2924
  this.pushWithReply(proxyRefGen, "event", {
@@ -2749,22 +2990,24 @@ within:
2749
2990
  this.liveSocket.withinOwners(form, (view, targetCtx) => {
2750
2991
  let input = form.elements[0];
2751
2992
  let phxEvent = form.getAttribute(this.binding(PHX_AUTO_RECOVER)) || form.getAttribute(this.binding("change"));
2752
- view.pushInput(input, targetCtx, newCid, phxEvent, input, callback);
2993
+ js_default.exec("change", phxEvent, view, input, ["push", { _target: input.name, newCid, callback }]);
2753
2994
  });
2754
2995
  }
2755
2996
  pushLinkPatch(href, targetEl, callback) {
2756
2997
  let linkRef = this.liveSocket.setPendingLink(href);
2757
2998
  let refGen = targetEl ? () => this.putRef([targetEl], "click") : null;
2758
2999
  this.pushWithReply(refGen, "live_patch", { url: href }, (resp) => {
2759
- if (resp.link_redirect) {
2760
- this.liveSocket.replaceMain(href, null, callback, linkRef);
2761
- } else {
2762
- if (this.liveSocket.commitPendingLink(linkRef)) {
2763
- this.href = href;
3000
+ this.liveSocket.requestDOMUpdate(() => {
3001
+ if (resp.link_redirect) {
3002
+ this.liveSocket.replaceMain(href, null, callback, linkRef);
3003
+ } else {
3004
+ if (this.liveSocket.commitPendingLink(linkRef)) {
3005
+ this.href = href;
3006
+ }
3007
+ this.applyPendingUpdates();
3008
+ callback && callback(linkRef);
2764
3009
  }
2765
- this.applyPendingUpdates();
2766
- callback && callback(linkRef);
2767
- }
3010
+ });
2768
3011
  }).receive("timeout", () => this.liveSocket.redirect(window.location.href));
2769
3012
  }
2770
3013
  formsForRecovery(html) {
@@ -2805,10 +3048,10 @@ within:
2805
3048
  ownsElement(el) {
2806
3049
  return el.getAttribute(PHX_PARENT_ID) === this.id || maybe(el.closest(PHX_VIEW_SELECTOR), (node) => node.id) === this.id;
2807
3050
  }
2808
- submitForm(form, targetCtx, phxEvent) {
3051
+ submitForm(form, targetCtx, phxEvent, opts = {}) {
2809
3052
  dom_default.putPrivate(form, PHX_HAS_SUBMITTED, true);
2810
3053
  this.liveSocket.blurActiveElement(this);
2811
- this.pushFormSubmit(form, targetCtx, phxEvent, () => {
3054
+ this.pushFormSubmit(form, targetCtx, phxEvent, opts, () => {
2812
3055
  this.liveSocket.restorePreviouslyActiveFocus();
2813
3056
  });
2814
3057
  }
@@ -2853,6 +3096,7 @@ within:
2853
3096
  this.sessionStorage = opts.sessionStorage || window.sessionStorage;
2854
3097
  this.boundTopLevelEvents = false;
2855
3098
  this.domCallbacks = Object.assign({ onNodeAdded: closure(), onBeforeElUpdated: closure() }, opts.dom || {});
3099
+ this.transitions = new TransitionSet();
2856
3100
  window.addEventListener("pagehide", (_e) => {
2857
3101
  this.unloaded = true;
2858
3102
  });
@@ -2911,6 +3155,9 @@ within:
2911
3155
  disconnect(callback) {
2912
3156
  this.socket.disconnect(callback);
2913
3157
  }
3158
+ execJS(el, encodedJS, eventType = null) {
3159
+ this.owner(el, (view) => js_default.exec(eventType, encodedJS, view, el));
3160
+ }
2914
3161
  triggerDOM(kind, args) {
2915
3162
  this.domCallbacks[kind](...args);
2916
3163
  }
@@ -2932,6 +3179,13 @@ within:
2932
3179
  debug(view, kind, msg, obj);
2933
3180
  }
2934
3181
  }
3182
+ requestDOMUpdate(callback) {
3183
+ this.transitions.after(callback);
3184
+ }
3185
+ transition(time, onDone = function() {
3186
+ }) {
3187
+ this.transitions.addTransition(time, onDone);
3188
+ }
2935
3189
  onChannel(channel, event, cb) {
2936
3190
  channel.on(event, (data) => {
2937
3191
  let latency = this.getLatencySim();
@@ -3037,10 +3291,23 @@ within:
3037
3291
  this.main.destroy();
3038
3292
  this.main = this.newRootView(newMainEl, flash);
3039
3293
  this.main.setRedirect(href);
3040
- this.main.join((joinCount) => {
3294
+ this.transitionRemoves();
3295
+ this.main.join((joinCount, onDone) => {
3041
3296
  if (joinCount === 1 && this.commitPendingLink(linkRef)) {
3042
- oldMainEl.replaceWith(newMainEl);
3043
- callback && callback();
3297
+ this.requestDOMUpdate(() => {
3298
+ oldMainEl.replaceWith(newMainEl);
3299
+ callback && callback();
3300
+ onDone();
3301
+ });
3302
+ }
3303
+ });
3304
+ }
3305
+ transitionRemoves(elements) {
3306
+ let removeAttr = this.binding("remove");
3307
+ elements = elements || dom_default.all(document, `[${removeAttr}]`);
3308
+ elements.forEach((el) => {
3309
+ if (document.body.contains(el)) {
3310
+ this.execJS(el, el.getAttribute(removeAttr), "remove");
3044
3311
  }
3045
3312
  });
3046
3313
  }
@@ -3059,14 +3326,7 @@ within:
3059
3326
  }
3060
3327
  }
3061
3328
  withinOwners(childEl, callback) {
3062
- this.owner(childEl, (view) => {
3063
- let phxTarget = childEl.getAttribute(this.binding("target"));
3064
- if (phxTarget === null) {
3065
- callback(view, childEl);
3066
- } else {
3067
- view.withinTargets(phxTarget, callback);
3068
- }
3069
- });
3329
+ this.owner(childEl, (view) => callback(view, childEl));
3070
3330
  }
3071
3331
  getViewByEl(el) {
3072
3332
  let rootId = el.getAttribute(PHX_ROOT_ID);
@@ -3142,22 +3402,25 @@ within:
3142
3402
  this.bindNav();
3143
3403
  this.bindClicks();
3144
3404
  this.bindForms();
3145
- this.bind({ keyup: "keyup", keydown: "keydown" }, (e, type, view, target, targetCtx, phxEvent, _phxTarget) => {
3146
- let matchKey = target.getAttribute(this.binding(PHX_KEY));
3405
+ this.bind({ keyup: "keyup", keydown: "keydown" }, (e, type, view, targetEl, phxEvent, eventTarget) => {
3406
+ let matchKey = targetEl.getAttribute(this.binding(PHX_KEY));
3147
3407
  let pressedKey = e.key && e.key.toLowerCase();
3148
3408
  if (matchKey && matchKey.toLowerCase() !== pressedKey) {
3149
3409
  return;
3150
3410
  }
3151
- view.pushKey(target, targetCtx, type, phxEvent, __spreadValues({ key: e.key }, this.eventMeta(type, e, target)));
3411
+ let data = __spreadValues({ key: e.key }, this.eventMeta(type, e, targetEl));
3412
+ js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
3152
3413
  });
3153
- this.bind({ blur: "focusout", focus: "focusin" }, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {
3154
- if (!phxTarget) {
3155
- view.pushEvent(type, targetEl, targetCtx, phxEvent, this.eventMeta(type, e, targetEl));
3414
+ this.bind({ blur: "focusout", focus: "focusin" }, (e, type, view, targetEl, phxEvent, eventTarget) => {
3415
+ if (!eventTarget) {
3416
+ let data = __spreadValues({ key: e.key }, this.eventMeta(type, e, targetEl));
3417
+ js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
3156
3418
  }
3157
3419
  });
3158
3420
  this.bind({ blur: "blur", focus: "focus" }, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {
3159
- if (phxTarget && !phxTarget !== "window") {
3160
- view.pushEvent(type, targetEl, targetCtx, phxEvent, this.eventMeta(type, e, targetEl));
3421
+ if (phxTarget === "window") {
3422
+ let data = this.eventMeta(type, e, targetEl);
3423
+ js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
3161
3424
  }
3162
3425
  });
3163
3426
  window.addEventListener("dragover", (e) => e.preventDefault());
@@ -3217,16 +3480,16 @@ within:
3217
3480
  let targetPhxEvent = e.target.getAttribute && e.target.getAttribute(binding);
3218
3481
  if (targetPhxEvent) {
3219
3482
  this.debounce(e.target, e, () => {
3220
- this.withinOwners(e.target, (view, targetCtx) => {
3221
- callback(e, event, view, e.target, targetCtx, targetPhxEvent, null);
3483
+ this.withinOwners(e.target, (view) => {
3484
+ callback(e, event, view, e.target, targetPhxEvent, null);
3222
3485
  });
3223
3486
  });
3224
3487
  } else {
3225
3488
  dom_default.all(document, `[${windowBinding}]`, (el) => {
3226
3489
  let phxEvent = el.getAttribute(windowBinding);
3227
3490
  this.debounce(el, e, () => {
3228
- this.withinOwners(el, (view, targetCtx) => {
3229
- callback(e, event, view, el, targetCtx, phxEvent, "window");
3491
+ this.withinOwners(el, (view) => {
3492
+ callback(e, event, view, el, phxEvent, "window");
3230
3493
  });
3231
3494
  });
3232
3495
  });
@@ -3249,6 +3512,7 @@ within:
3249
3512
  target = e.target.matches(`[${click}]`) ? e.target : e.target.querySelector(`[${click}]`);
3250
3513
  } else {
3251
3514
  target = closestPhxBinding(e.target, click);
3515
+ this.dispatchClickAway(e);
3252
3516
  }
3253
3517
  let phxEvent = target && target.getAttribute(click);
3254
3518
  if (!phxEvent) {
@@ -3258,12 +3522,23 @@ within:
3258
3522
  e.preventDefault();
3259
3523
  }
3260
3524
  this.debounce(target, e, () => {
3261
- this.withinOwners(target, (view, targetCtx) => {
3262
- view.pushEvent("click", target, targetCtx, phxEvent, this.eventMeta("click", e, target));
3525
+ this.withinOwners(target, (view) => {
3526
+ js_default.exec("click", phxEvent, view, target, ["push", { data: this.eventMeta("click", e, target) }]);
3263
3527
  });
3264
3528
  });
3265
3529
  }, capture);
3266
3530
  }
3531
+ dispatchClickAway(e) {
3532
+ let binding = this.binding("click-away");
3533
+ dom_default.all(document, `[${binding}]`, (el) => {
3534
+ if (!(el.isSameNode(e.target) || el.contains(e.target))) {
3535
+ this.withinOwners(e.target, (view) => {
3536
+ let phxEvent = el.getAttribute(binding);
3537
+ js_default.exec("click", phxEvent, view, e.target, ["push", { data: this.eventMeta("click", e, e.target) }]);
3538
+ });
3539
+ }
3540
+ });
3541
+ }
3267
3542
  bindNav() {
3268
3543
  if (!browser_default.canPushState()) {
3269
3544
  return;
@@ -3284,20 +3559,22 @@ within:
3284
3559
  }
3285
3560
  let { type, id, root, scroll } = event.state || {};
3286
3561
  let href = window.location.href;
3287
- if (this.main.isConnected() && (type === "patch" && id === this.main.id)) {
3288
- this.main.pushLinkPatch(href, null);
3289
- } else {
3290
- this.replaceMain(href, null, () => {
3291
- if (root) {
3292
- this.replaceRootHistory();
3293
- }
3294
- if (typeof scroll === "number") {
3295
- setTimeout(() => {
3296
- window.scrollTo(0, scroll);
3297
- }, 0);
3298
- }
3299
- });
3300
- }
3562
+ this.requestDOMUpdate(() => {
3563
+ if (this.main.isConnected() && (type === "patch" && id === this.main.id)) {
3564
+ this.main.pushLinkPatch(href, null);
3565
+ } else {
3566
+ this.replaceMain(href, null, () => {
3567
+ if (root) {
3568
+ this.replaceRootHistory();
3569
+ }
3570
+ if (typeof scroll === "number") {
3571
+ setTimeout(() => {
3572
+ window.scrollTo(0, scroll);
3573
+ }, 0);
3574
+ }
3575
+ });
3576
+ }
3577
+ });
3301
3578
  }, false);
3302
3579
  window.addEventListener("click", (e) => {
3303
3580
  let target = closestPhxBinding(e.target, PHX_LIVE_LINK);
@@ -3312,15 +3589,23 @@ within:
3312
3589
  if (this.pendingLink === href) {
3313
3590
  return;
3314
3591
  }
3315
- if (type === "patch") {
3316
- this.pushHistoryPatch(href, linkState, target);
3317
- } else if (type === "redirect") {
3318
- this.historyRedirect(href, linkState);
3319
- } else {
3320
- throw new Error(`expected ${PHX_LIVE_LINK} to be "patch" or "redirect", got: ${type}`);
3321
- }
3592
+ this.requestDOMUpdate(() => {
3593
+ if (type === "patch") {
3594
+ this.pushHistoryPatch(href, linkState, target);
3595
+ } else if (type === "redirect") {
3596
+ this.historyRedirect(href, linkState);
3597
+ } else {
3598
+ throw new Error(`expected ${PHX_LIVE_LINK} to be "patch" or "redirect", got: ${type}`);
3599
+ }
3600
+ });
3322
3601
  }, false);
3323
3602
  }
3603
+ dispatchEvent(event, payload = {}) {
3604
+ dom_default.dispatchEvent(window, `phx:${event}`, payload);
3605
+ }
3606
+ dispatchEvents(events) {
3607
+ events.forEach(([event, payload]) => this.dispatchEvent(event, payload));
3608
+ }
3324
3609
  withPageLoading(info, callback) {
3325
3610
  dom_default.dispatchEvent(window, "phx:page-loading-start", info);
3326
3611
  let done = () => dom_default.dispatchEvent(window, "phx:page-loading-stop", info);
@@ -3372,7 +3657,9 @@ within:
3372
3657
  }
3373
3658
  e.preventDefault();
3374
3659
  e.target.disabled = true;
3375
- this.withinOwners(e.target, (view, targetCtx) => view.submitForm(e.target, targetCtx, phxEvent));
3660
+ this.withinOwners(e.target, (view) => {
3661
+ js_default.exec("submit", phxEvent, view, e.target, ["push", {}]);
3662
+ });
3376
3663
  }, false);
3377
3664
  for (let type of ["change", "input"]) {
3378
3665
  this.on(type, (e) => {
@@ -3392,12 +3679,12 @@ within:
3392
3679
  }
3393
3680
  dom_default.putPrivate(input, "prev-iteration", { at: currentIterations, type });
3394
3681
  this.debounce(input, e, () => {
3395
- this.withinOwners(input.form, (view, targetCtx) => {
3682
+ this.withinOwners(input.form, (view) => {
3396
3683
  dom_default.putPrivate(input, PHX_HAS_FOCUSED, true);
3397
3684
  if (!dom_default.isTextualInput(input)) {
3398
3685
  this.setActiveElement(input);
3399
3686
  }
3400
- view.pushInput(input, targetCtx, null, phxEvent, e.target);
3687
+ js_default.exec("change", phxEvent, view, input, ["push", { _target: e.target.name }]);
3401
3688
  });
3402
3689
  });
3403
3690
  }, false);
@@ -3423,5 +3710,46 @@ within:
3423
3710
  });
3424
3711
  }
3425
3712
  };
3713
+ var TransitionSet = class {
3714
+ constructor() {
3715
+ this.transitions = new Set();
3716
+ this.pendingOps = [];
3717
+ this.reset();
3718
+ }
3719
+ reset() {
3720
+ this.transitions.forEach((timer) => {
3721
+ cancelTimeout(timer);
3722
+ this.transitions.delete(timer);
3723
+ });
3724
+ this.flushPendingOps();
3725
+ }
3726
+ after(callback) {
3727
+ if (this.size() === 0) {
3728
+ callback();
3729
+ } else {
3730
+ this.pushPendingOp(callback);
3731
+ }
3732
+ }
3733
+ addTransition(time, onDone) {
3734
+ let timer = setTimeout(() => {
3735
+ this.transitions.delete(timer);
3736
+ onDone();
3737
+ if (this.size() === 0) {
3738
+ this.flushPendingOps();
3739
+ }
3740
+ }, time);
3741
+ this.transitions.add(timer);
3742
+ }
3743
+ pushPendingOp(op) {
3744
+ this.pendingOps.push(op);
3745
+ }
3746
+ size() {
3747
+ return this.transitions.size;
3748
+ }
3749
+ flushPendingOps() {
3750
+ this.pendingOps.forEach((op) => op());
3751
+ this.pendingOps = [];
3752
+ }
3753
+ };
3426
3754
  return phoenix_live_view_exports;
3427
3755
  })();