pyview-web 0.3.0__py3-none-any.whl → 0.8.0a2__py3-none-any.whl

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.
Files changed (78) hide show
  1. pyview/__init__.py +16 -6
  2. pyview/assets/js/app.js +1 -0
  3. pyview/assets/js/uploaders.js +221 -0
  4. pyview/assets/package-lock.json +16 -14
  5. pyview/assets/package.json +2 -2
  6. pyview/async_stream_runner.py +2 -1
  7. pyview/auth/__init__.py +3 -1
  8. pyview/auth/provider.py +6 -6
  9. pyview/auth/required.py +7 -10
  10. pyview/binding/__init__.py +47 -0
  11. pyview/binding/binder.py +134 -0
  12. pyview/binding/context.py +33 -0
  13. pyview/binding/converters.py +191 -0
  14. pyview/binding/helpers.py +78 -0
  15. pyview/binding/injectables.py +119 -0
  16. pyview/binding/params.py +105 -0
  17. pyview/binding/result.py +32 -0
  18. pyview/changesets/__init__.py +2 -0
  19. pyview/changesets/changesets.py +8 -3
  20. pyview/cli/commands/create_view.py +4 -3
  21. pyview/cli/main.py +1 -1
  22. pyview/components/__init__.py +72 -0
  23. pyview/components/base.py +212 -0
  24. pyview/components/lifecycle.py +85 -0
  25. pyview/components/manager.py +366 -0
  26. pyview/components/renderer.py +14 -0
  27. pyview/components/slots.py +73 -0
  28. pyview/csrf.py +4 -2
  29. pyview/events/AutoEventDispatch.py +98 -0
  30. pyview/events/BaseEventHandler.py +51 -8
  31. pyview/events/__init__.py +2 -1
  32. pyview/instrumentation/__init__.py +3 -3
  33. pyview/instrumentation/interfaces.py +57 -33
  34. pyview/instrumentation/noop.py +21 -18
  35. pyview/js.py +20 -23
  36. pyview/live_routes.py +5 -3
  37. pyview/live_socket.py +167 -44
  38. pyview/live_view.py +24 -12
  39. pyview/meta.py +14 -2
  40. pyview/phx_message.py +7 -8
  41. pyview/playground/__init__.py +10 -0
  42. pyview/playground/builder.py +118 -0
  43. pyview/playground/favicon.py +39 -0
  44. pyview/pyview.py +54 -20
  45. pyview/session.py +2 -0
  46. pyview/static/assets/app.js +2088 -806
  47. pyview/static/assets/uploaders.js +221 -0
  48. pyview/stream.py +308 -0
  49. pyview/template/__init__.py +11 -1
  50. pyview/template/live_template.py +12 -8
  51. pyview/template/live_view_template.py +338 -0
  52. pyview/template/render_diff.py +33 -7
  53. pyview/template/root_template.py +21 -9
  54. pyview/template/serializer.py +2 -5
  55. pyview/template/template_view.py +170 -0
  56. pyview/template/utils.py +3 -2
  57. pyview/uploads.py +344 -55
  58. pyview/vendor/flet/pubsub/__init__.py +3 -1
  59. pyview/vendor/flet/pubsub/pub_sub.py +10 -18
  60. pyview/vendor/ibis/__init__.py +3 -7
  61. pyview/vendor/ibis/compiler.py +25 -32
  62. pyview/vendor/ibis/context.py +13 -15
  63. pyview/vendor/ibis/errors.py +0 -6
  64. pyview/vendor/ibis/filters.py +70 -76
  65. pyview/vendor/ibis/loaders.py +6 -7
  66. pyview/vendor/ibis/nodes.py +40 -42
  67. pyview/vendor/ibis/template.py +4 -5
  68. pyview/vendor/ibis/tree.py +62 -3
  69. pyview/vendor/ibis/utils.py +14 -15
  70. pyview/ws_handler.py +116 -86
  71. {pyview_web-0.3.0.dist-info → pyview_web-0.8.0a2.dist-info}/METADATA +39 -33
  72. pyview_web-0.8.0a2.dist-info/RECORD +80 -0
  73. pyview_web-0.8.0a2.dist-info/WHEEL +4 -0
  74. pyview_web-0.8.0a2.dist-info/entry_points.txt +3 -0
  75. pyview_web-0.3.0.dist-info/LICENSE +0 -21
  76. pyview_web-0.3.0.dist-info/RECORD +0 -58
  77. pyview_web-0.3.0.dist-info/WHEEL +0 -4
  78. pyview_web-0.3.0.dist-info/entry_points.txt +0 -3
@@ -1,7 +1,9 @@
1
1
  (() => {
2
2
  var __create = Object.create;
3
3
  var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
5
7
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
8
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
7
9
  var __getProtoOf = Object.getPrototypeOf;
@@ -19,6 +21,19 @@
19
21
  }
20
22
  return a;
21
23
  };
24
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
25
+ var __objRest = (source, exclude) => {
26
+ var target = {};
27
+ for (var prop in source)
28
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
29
+ target[prop] = source[prop];
30
+ if (source != null && __getOwnPropSymbols)
31
+ for (var prop of __getOwnPropSymbols(source)) {
32
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
33
+ target[prop] = source[prop];
34
+ }
35
+ return target;
36
+ };
22
37
  var __commonJS = (cb, mod) => function __require() {
23
38
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
24
39
  };
@@ -39,9 +54,9 @@
39
54
  mod
40
55
  ));
41
56
 
42
- // assets/node_modules/nprogress/nprogress.js
57
+ // node_modules/nprogress/nprogress.js
43
58
  var require_nprogress = __commonJS({
44
- "assets/node_modules/nprogress/nprogress.js"(exports, module) {
59
+ "node_modules/nprogress/nprogress.js"(exports, module) {
45
60
  (function(root, factory) {
46
61
  if (typeof define === "function" && define.amd) {
47
62
  define(factory);
@@ -316,7 +331,7 @@
316
331
  }
317
332
  });
318
333
 
319
- // assets/node_modules/phoenix_html/priv/static/phoenix_html.js
334
+ // node_modules/phoenix_html/priv/static/phoenix_html.js
320
335
  (function() {
321
336
  var PolyfillEvent = eventConstructor();
322
337
  function eventConstructor() {
@@ -383,7 +398,7 @@
383
398
  }, false);
384
399
  })();
385
400
 
386
- // assets/node_modules/phoenix/priv/static/phoenix.mjs
401
+ // node_modules/phoenix/priv/static/phoenix.mjs
387
402
  var closure = (value) => {
388
403
  if (typeof value === "function") {
389
404
  return value;
@@ -396,7 +411,7 @@
396
411
  };
397
412
  var globalSelf = typeof self !== "undefined" ? self : null;
398
413
  var phxWindow = typeof window !== "undefined" ? window : null;
399
- var global = globalSelf || phxWindow || global;
414
+ var global = globalSelf || phxWindow || globalThis;
400
415
  var DEFAULT_VSN = "2.0.0";
401
416
  var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 };
402
417
  var DEFAULT_TIMEOUT = 1e4;
@@ -422,6 +437,7 @@
422
437
  var XHR_STATES = {
423
438
  complete: 4
424
439
  };
440
+ var AUTH_TOKEN_PREFIX = "base64url.bearer.phx.";
425
441
  var Push = class {
426
442
  constructor(channel, event, payload, timeout) {
427
443
  this.channel = channel;
@@ -435,11 +451,18 @@
435
451
  this.recHooks = [];
436
452
  this.sent = false;
437
453
  }
454
+ /**
455
+ *
456
+ * @param {number} timeout
457
+ */
438
458
  resend(timeout) {
439
459
  this.timeout = timeout;
440
460
  this.reset();
441
461
  this.send();
442
462
  }
463
+ /**
464
+ *
465
+ */
443
466
  send() {
444
467
  if (this.hasReceived("timeout")) {
445
468
  return;
@@ -454,6 +477,11 @@
454
477
  join_ref: this.channel.joinRef()
455
478
  });
456
479
  }
480
+ /**
481
+ *
482
+ * @param {*} status
483
+ * @param {*} callback
484
+ */
457
485
  receive(status, callback) {
458
486
  if (this.hasReceived(status)) {
459
487
  callback(this.receivedResp.response);
@@ -461,6 +489,9 @@
461
489
  this.recHooks.push({ status, callback });
462
490
  return this;
463
491
  }
492
+ /**
493
+ * @private
494
+ */
464
495
  reset() {
465
496
  this.cancelRefEvent();
466
497
  this.ref = null;
@@ -468,19 +499,31 @@
468
499
  this.receivedResp = null;
469
500
  this.sent = false;
470
501
  }
502
+ /**
503
+ * @private
504
+ */
471
505
  matchReceive({ status, response, _ref }) {
472
506
  this.recHooks.filter((h) => h.status === status).forEach((h) => h.callback(response));
473
507
  }
508
+ /**
509
+ * @private
510
+ */
474
511
  cancelRefEvent() {
475
512
  if (!this.refEvent) {
476
513
  return;
477
514
  }
478
515
  this.channel.off(this.refEvent);
479
516
  }
517
+ /**
518
+ * @private
519
+ */
480
520
  cancelTimeout() {
481
521
  clearTimeout(this.timeoutTimer);
482
522
  this.timeoutTimer = null;
483
523
  }
524
+ /**
525
+ * @private
526
+ */
484
527
  startTimeout() {
485
528
  if (this.timeoutTimer) {
486
529
  this.cancelTimeout();
@@ -497,9 +540,15 @@
497
540
  this.trigger("timeout", {});
498
541
  }, this.timeout);
499
542
  }
543
+ /**
544
+ * @private
545
+ */
500
546
  hasReceived(status) {
501
547
  return this.receivedResp && this.receivedResp.status === status;
502
548
  }
549
+ /**
550
+ * @private
551
+ */
503
552
  trigger(status, response) {
504
553
  this.channel.trigger(this.refEvent, { status, response });
505
554
  }
@@ -515,6 +564,9 @@
515
564
  this.tries = 0;
516
565
  clearTimeout(this.timer);
517
566
  }
567
+ /**
568
+ * Cancels any previous scheduleTimeout and schedules callback
569
+ */
518
570
  scheduleTimeout() {
519
571
  clearTimeout(this.timer);
520
572
  this.timer = setTimeout(() => {
@@ -542,12 +594,14 @@
542
594
  }
543
595
  }, this.socket.rejoinAfterMs);
544
596
  this.stateChangeRefs.push(this.socket.onError(() => this.rejoinTimer.reset()));
545
- this.stateChangeRefs.push(this.socket.onOpen(() => {
546
- this.rejoinTimer.reset();
547
- if (this.isErrored()) {
548
- this.rejoin();
549
- }
550
- }));
597
+ this.stateChangeRefs.push(
598
+ this.socket.onOpen(() => {
599
+ this.rejoinTimer.reset();
600
+ if (this.isErrored()) {
601
+ this.rejoin();
602
+ }
603
+ })
604
+ );
551
605
  this.joinPush.receive("ok", () => {
552
606
  this.state = CHANNEL_STATES.joined;
553
607
  this.rejoinTimer.reset();
@@ -593,6 +647,11 @@
593
647
  this.trigger(this.replyEventName(ref), payload);
594
648
  });
595
649
  }
650
+ /**
651
+ * Join the channel
652
+ * @param {integer} timeout
653
+ * @returns {Push}
654
+ */
596
655
  join(timeout = this.timeout) {
597
656
  if (this.joinedOnce) {
598
657
  throw new Error("tried to join multiple times. 'join' can only be called a single time per channel instance");
@@ -603,25 +662,87 @@
603
662
  return this.joinPush;
604
663
  }
605
664
  }
665
+ /**
666
+ * Hook into channel close
667
+ * @param {Function} callback
668
+ */
606
669
  onClose(callback) {
607
670
  this.on(CHANNEL_EVENTS.close, callback);
608
671
  }
672
+ /**
673
+ * Hook into channel errors
674
+ * @param {Function} callback
675
+ */
609
676
  onError(callback) {
610
677
  return this.on(CHANNEL_EVENTS.error, (reason) => callback(reason));
611
678
  }
679
+ /**
680
+ * Subscribes on channel events
681
+ *
682
+ * Subscription returns a ref counter, which can be used later to
683
+ * unsubscribe the exact event listener
684
+ *
685
+ * @example
686
+ * const ref1 = channel.on("event", do_stuff)
687
+ * const ref2 = channel.on("event", do_other_stuff)
688
+ * channel.off("event", ref1)
689
+ * // Since unsubscription, do_stuff won't fire,
690
+ * // while do_other_stuff will keep firing on the "event"
691
+ *
692
+ * @param {string} event
693
+ * @param {Function} callback
694
+ * @returns {integer} ref
695
+ */
612
696
  on(event, callback) {
613
697
  let ref = this.bindingRef++;
614
698
  this.bindings.push({ event, ref, callback });
615
699
  return ref;
616
700
  }
701
+ /**
702
+ * Unsubscribes off of channel events
703
+ *
704
+ * Use the ref returned from a channel.on() to unsubscribe one
705
+ * handler, or pass nothing for the ref to unsubscribe all
706
+ * handlers for the given event.
707
+ *
708
+ * @example
709
+ * // Unsubscribe the do_stuff handler
710
+ * const ref1 = channel.on("event", do_stuff)
711
+ * channel.off("event", ref1)
712
+ *
713
+ * // Unsubscribe all handlers from event
714
+ * channel.off("event")
715
+ *
716
+ * @param {string} event
717
+ * @param {integer} ref
718
+ */
617
719
  off(event, ref) {
618
720
  this.bindings = this.bindings.filter((bind) => {
619
721
  return !(bind.event === event && (typeof ref === "undefined" || ref === bind.ref));
620
722
  });
621
723
  }
724
+ /**
725
+ * @private
726
+ */
622
727
  canPush() {
623
728
  return this.socket.isConnected() && this.isJoined();
624
729
  }
730
+ /**
731
+ * Sends a message `event` to phoenix with the payload `payload`.
732
+ * Phoenix receives this in the `handle_in(event, payload, socket)`
733
+ * function. if phoenix replies or it times out (default 10000ms),
734
+ * then optionally the reply can be received.
735
+ *
736
+ * @example
737
+ * channel.push("event")
738
+ * .receive("ok", payload => console.log("phoenix replied:", payload))
739
+ * .receive("error", err => console.log("phoenix errored", err))
740
+ * .receive("timeout", () => console.log("timed out pushing"))
741
+ * @param {string} event
742
+ * @param {Object} payload
743
+ * @param {number} [timeout]
744
+ * @returns {Push}
745
+ */
625
746
  push(event, payload, timeout = this.timeout) {
626
747
  payload = payload || {};
627
748
  if (!this.joinedOnce) {
@@ -638,6 +759,22 @@
638
759
  }
639
760
  return pushEvent;
640
761
  }
762
+ /** Leaves the channel
763
+ *
764
+ * Unsubscribes from server events, and
765
+ * instructs channel to terminate on server
766
+ *
767
+ * Triggers onClose() hooks
768
+ *
769
+ * To receive leave acknowledgements, use the `receive`
770
+ * hook to bind to the server ack, ie:
771
+ *
772
+ * @example
773
+ * channel.leave().receive("ok", () => alert("left!") )
774
+ *
775
+ * @param {integer} timeout
776
+ * @returns {Push}
777
+ */
641
778
  leave(timeout = this.timeout) {
642
779
  this.rejoinTimer.reset();
643
780
  this.joinPush.cancelTimeout();
@@ -655,9 +792,24 @@
655
792
  }
656
793
  return leavePush;
657
794
  }
795
+ /**
796
+ * Overridable message hook
797
+ *
798
+ * Receives all events for specialized message handling
799
+ * before dispatching to the channel callbacks.
800
+ *
801
+ * Must return the payload, modified or unmodified
802
+ * @param {string} event
803
+ * @param {Object} payload
804
+ * @param {integer} ref
805
+ * @returns {Object}
806
+ */
658
807
  onMessage(_event, payload, _ref) {
659
808
  return payload;
660
809
  }
810
+ /**
811
+ * @private
812
+ */
661
813
  isMember(topic, event, payload, joinRef) {
662
814
  if (this.topic !== topic) {
663
815
  return false;
@@ -670,9 +822,15 @@
670
822
  return true;
671
823
  }
672
824
  }
825
+ /**
826
+ * @private
827
+ */
673
828
  joinRef() {
674
829
  return this.joinPush.ref;
675
830
  }
831
+ /**
832
+ * @private
833
+ */
676
834
  rejoin(timeout = this.timeout) {
677
835
  if (this.isLeaving()) {
678
836
  return;
@@ -681,6 +839,9 @@
681
839
  this.state = CHANNEL_STATES.joining;
682
840
  this.joinPush.resend(timeout);
683
841
  }
842
+ /**
843
+ * @private
844
+ */
684
845
  trigger(event, payload, ref, joinRef) {
685
846
  let handledPayload = this.onMessage(event, payload, ref, joinRef);
686
847
  if (payload && !handledPayload) {
@@ -692,34 +853,77 @@
692
853
  bind.callback(handledPayload, ref, joinRef || this.joinRef());
693
854
  }
694
855
  }
856
+ /**
857
+ * @private
858
+ */
695
859
  replyEventName(ref) {
696
860
  return `chan_reply_${ref}`;
697
861
  }
862
+ /**
863
+ * @private
864
+ */
698
865
  isClosed() {
699
866
  return this.state === CHANNEL_STATES.closed;
700
867
  }
868
+ /**
869
+ * @private
870
+ */
701
871
  isErrored() {
702
872
  return this.state === CHANNEL_STATES.errored;
703
873
  }
874
+ /**
875
+ * @private
876
+ */
704
877
  isJoined() {
705
878
  return this.state === CHANNEL_STATES.joined;
706
879
  }
880
+ /**
881
+ * @private
882
+ */
707
883
  isJoining() {
708
884
  return this.state === CHANNEL_STATES.joining;
709
885
  }
886
+ /**
887
+ * @private
888
+ */
710
889
  isLeaving() {
711
890
  return this.state === CHANNEL_STATES.leaving;
712
891
  }
713
892
  };
714
893
  var Ajax = class {
715
- static request(method, endPoint, accept, body, timeout, ontimeout, callback) {
894
+ static request(method, endPoint, headers, body, timeout, ontimeout, callback) {
716
895
  if (global.XDomainRequest) {
717
896
  let req = new global.XDomainRequest();
718
897
  return this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback);
719
- } else {
898
+ } else if (global.XMLHttpRequest) {
720
899
  let req = new global.XMLHttpRequest();
721
- return this.xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback);
900
+ return this.xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback);
901
+ } else if (global.fetch && global.AbortController) {
902
+ return this.fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback);
903
+ } else {
904
+ throw new Error("No suitable XMLHttpRequest implementation found");
905
+ }
906
+ }
907
+ static fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback) {
908
+ let options = {
909
+ method,
910
+ headers,
911
+ body
912
+ };
913
+ let controller = null;
914
+ if (timeout) {
915
+ controller = new AbortController();
916
+ const _timeoutId = setTimeout(() => controller.abort(), timeout);
917
+ options.signal = controller.signal;
722
918
  }
919
+ global.fetch(endPoint, options).then((response) => response.text()).then((data) => this.parseJSON(data)).then((data) => callback && callback(data)).catch((err) => {
920
+ if (err.name === "AbortError" && ontimeout) {
921
+ ontimeout();
922
+ } else {
923
+ callback && callback(null);
924
+ }
925
+ });
926
+ return controller;
723
927
  }
724
928
  static xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback) {
725
929
  req.timeout = timeout;
@@ -736,10 +940,12 @@
736
940
  req.send(body);
737
941
  return req;
738
942
  }
739
- static xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback) {
943
+ static xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback) {
740
944
  req.open(method, endPoint, true);
741
945
  req.timeout = timeout;
742
- req.setRequestHeader("Content-Type", accept);
946
+ for (let [key, value] of Object.entries(headers)) {
947
+ req.setRequestHeader(key, value);
948
+ }
743
949
  req.onerror = () => callback && callback(null);
744
950
  req.onreadystatechange = () => {
745
951
  if (req.readyState === XHR_STATES.complete && callback) {
@@ -788,12 +994,28 @@
788
994
  return `${url}${prefix}${this.serialize(params)}`;
789
995
  }
790
996
  };
997
+ var arrayBufferToBase64 = (buffer) => {
998
+ let binary = "";
999
+ let bytes = new Uint8Array(buffer);
1000
+ let len = bytes.byteLength;
1001
+ for (let i = 0; i < len; i++) {
1002
+ binary += String.fromCharCode(bytes[i]);
1003
+ }
1004
+ return btoa(binary);
1005
+ };
791
1006
  var LongPoll = class {
792
- constructor(endPoint) {
1007
+ constructor(endPoint, protocols) {
1008
+ if (protocols && protocols.length === 2 && protocols[1].startsWith(AUTH_TOKEN_PREFIX)) {
1009
+ this.authToken = atob(protocols[1].slice(AUTH_TOKEN_PREFIX.length));
1010
+ }
793
1011
  this.endPoint = null;
794
1012
  this.token = null;
795
1013
  this.skipHeartbeat = true;
796
1014
  this.reqs = /* @__PURE__ */ new Set();
1015
+ this.awaitingBatchAck = false;
1016
+ this.currentBatch = null;
1017
+ this.currentBatchTimer = null;
1018
+ this.batchBuffer = [];
797
1019
  this.onopen = function() {
798
1020
  };
799
1021
  this.onerror = function() {
@@ -804,7 +1026,7 @@
804
1026
  };
805
1027
  this.pollEndpoint = this.normalizeEndpoint(endPoint);
806
1028
  this.readyState = SOCKET_STATES.connecting;
807
- this.poll();
1029
+ setTimeout(() => this.poll(), 0);
808
1030
  }
809
1031
  normalizeEndpoint(endPoint) {
810
1032
  return endPoint.replace("ws://", "http://").replace("wss://", "https://").replace(new RegExp("(.*)/" + TRANSPORTS.websocket), "$1/" + TRANSPORTS.longpoll);
@@ -824,9 +1046,18 @@
824
1046
  return this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting;
825
1047
  }
826
1048
  poll() {
827
- this.ajax("GET", null, () => this.ontimeout(), (resp) => {
1049
+ const headers = { "Accept": "application/json" };
1050
+ if (this.authToken) {
1051
+ headers["X-Phoenix-AuthToken"] = this.authToken;
1052
+ }
1053
+ this.ajax("GET", headers, null, () => this.ontimeout(), (resp) => {
828
1054
  if (resp) {
829
1055
  var { status, token, messages } = resp;
1056
+ if (status === 410 && this.token !== null) {
1057
+ this.onerror(410);
1058
+ this.closeAndRetry(3410, "session_gone", false);
1059
+ return;
1060
+ }
830
1061
  this.token = token;
831
1062
  } else {
832
1063
  status = 0;
@@ -860,11 +1091,35 @@
860
1091
  }
861
1092
  });
862
1093
  }
1094
+ // we collect all pushes within the current event loop by
1095
+ // setTimeout 0, which optimizes back-to-back procedural
1096
+ // pushes against an empty buffer
863
1097
  send(body) {
864
- this.ajax("POST", body, () => this.onerror("timeout"), (resp) => {
1098
+ if (typeof body !== "string") {
1099
+ body = arrayBufferToBase64(body);
1100
+ }
1101
+ if (this.currentBatch) {
1102
+ this.currentBatch.push(body);
1103
+ } else if (this.awaitingBatchAck) {
1104
+ this.batchBuffer.push(body);
1105
+ } else {
1106
+ this.currentBatch = [body];
1107
+ this.currentBatchTimer = setTimeout(() => {
1108
+ this.batchSend(this.currentBatch);
1109
+ this.currentBatch = null;
1110
+ }, 0);
1111
+ }
1112
+ }
1113
+ batchSend(messages) {
1114
+ this.awaitingBatchAck = true;
1115
+ this.ajax("POST", { "Content-Type": "application/x-ndjson" }, messages.join("\n"), () => this.onerror("timeout"), (resp) => {
1116
+ this.awaitingBatchAck = false;
865
1117
  if (!resp || resp.status !== 200) {
866
1118
  this.onerror(resp && resp.status);
867
1119
  this.closeAndRetry(1011, "internal server error", false);
1120
+ } else if (this.batchBuffer.length > 0) {
1121
+ this.batchSend(this.batchBuffer);
1122
+ this.batchBuffer = [];
868
1123
  }
869
1124
  });
870
1125
  }
@@ -874,19 +1129,22 @@
874
1129
  }
875
1130
  this.readyState = SOCKET_STATES.closed;
876
1131
  let opts = Object.assign({ code: 1e3, reason: void 0, wasClean: true }, { code, reason, wasClean });
1132
+ this.batchBuffer = [];
1133
+ clearTimeout(this.currentBatchTimer);
1134
+ this.currentBatchTimer = null;
877
1135
  if (typeof CloseEvent !== "undefined") {
878
1136
  this.onclose(new CloseEvent("close", opts));
879
1137
  } else {
880
1138
  this.onclose(opts);
881
1139
  }
882
1140
  }
883
- ajax(method, body, onCallerTimeout, callback) {
1141
+ ajax(method, headers, body, onCallerTimeout, callback) {
884
1142
  let req;
885
1143
  let ontimeout = () => {
886
1144
  this.reqs.delete(req);
887
1145
  onCallerTimeout();
888
1146
  };
889
- req = Ajax.request(method, this.endpointURL(), "application/json", body, this.timeout, ontimeout, (resp) => {
1147
+ req = Ajax.request(method, this.endpointURL(), headers, body, this.timeout, ontimeout, (resp) => {
890
1148
  this.reqs.delete(req);
891
1149
  if (this.isActive()) {
892
1150
  callback(resp);
@@ -915,6 +1173,7 @@
915
1173
  return callback({ join_ref, ref, topic, event, payload });
916
1174
  }
917
1175
  },
1176
+ // private
918
1177
  binaryEncode(message) {
919
1178
  let { join_ref, ref, event, topic, payload } = message;
920
1179
  let metaLength = this.META_LENGTH + join_ref.length + ref.length + topic.length + event.length;
@@ -998,14 +1257,21 @@
998
1257
  this.channels = [];
999
1258
  this.sendBuffer = [];
1000
1259
  this.ref = 0;
1260
+ this.fallbackRef = null;
1001
1261
  this.timeout = opts.timeout || DEFAULT_TIMEOUT;
1002
1262
  this.transport = opts.transport || global.WebSocket || LongPoll;
1263
+ this.primaryPassedHealthCheck = false;
1264
+ this.longPollFallbackMs = opts.longPollFallbackMs;
1265
+ this.fallbackTimer = null;
1266
+ this.sessionStore = opts.sessionStorage || global && global.sessionStorage;
1003
1267
  this.establishedConnections = 0;
1004
1268
  this.defaultEncoder = serializer_default.encode.bind(serializer_default);
1005
1269
  this.defaultDecoder = serializer_default.decode.bind(serializer_default);
1006
1270
  this.closeWasClean = false;
1271
+ this.disconnecting = false;
1007
1272
  this.binaryType = opts.binaryType || "arraybuffer";
1008
1273
  this.connectClock = 1;
1274
+ this.pageHidden = false;
1009
1275
  if (this.transport !== LongPoll) {
1010
1276
  this.encode = opts.encode || this.defaultEncoder;
1011
1277
  this.decode = opts.decode || this.defaultDecoder;
@@ -1027,6 +1293,16 @@
1027
1293
  this.connect();
1028
1294
  }
1029
1295
  });
1296
+ phxWindow.addEventListener("visibilitychange", () => {
1297
+ if (document.visibilityState === "hidden") {
1298
+ this.pageHidden = true;
1299
+ } else {
1300
+ this.pageHidden = false;
1301
+ if (!this.isConnected()) {
1302
+ this.teardown(() => this.connect());
1303
+ }
1304
+ }
1305
+ });
1030
1306
  }
1031
1307
  this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 3e4;
1032
1308
  this.rejoinAfterMs = (tries) => {
@@ -1044,6 +1320,11 @@
1044
1320
  }
1045
1321
  };
1046
1322
  this.logger = opts.logger || null;
1323
+ if (!this.logger && opts.debug) {
1324
+ this.logger = (kind, msg, data) => {
1325
+ console.log(`${kind}: ${msg}`, data);
1326
+ };
1327
+ }
1047
1328
  this.longpollerTimeout = opts.longpollerTimeout || 2e4;
1048
1329
  this.params = closure(opts.params || {});
1049
1330
  this.endPoint = `${endPoint}/${TRANSPORTS.websocket}`;
@@ -1052,28 +1333,56 @@
1052
1333
  this.heartbeatTimer = null;
1053
1334
  this.pendingHeartbeatRef = null;
1054
1335
  this.reconnectTimer = new Timer(() => {
1336
+ if (this.pageHidden) {
1337
+ this.log("Not reconnecting as page is hidden!");
1338
+ this.teardown();
1339
+ return;
1340
+ }
1055
1341
  this.teardown(() => this.connect());
1056
1342
  }, this.reconnectAfterMs);
1343
+ this.authToken = opts.authToken;
1057
1344
  }
1345
+ /**
1346
+ * Returns the LongPoll transport reference
1347
+ */
1058
1348
  getLongPollTransport() {
1059
1349
  return LongPoll;
1060
1350
  }
1351
+ /**
1352
+ * Disconnects and replaces the active transport
1353
+ *
1354
+ * @param {Function} newTransport - The new transport class to instantiate
1355
+ *
1356
+ */
1061
1357
  replaceTransport(newTransport) {
1062
1358
  this.connectClock++;
1063
1359
  this.closeWasClean = true;
1360
+ clearTimeout(this.fallbackTimer);
1064
1361
  this.reconnectTimer.reset();
1065
- this.sendBuffer = [];
1066
1362
  if (this.conn) {
1067
1363
  this.conn.close();
1068
1364
  this.conn = null;
1069
1365
  }
1070
1366
  this.transport = newTransport;
1071
1367
  }
1368
+ /**
1369
+ * Returns the socket protocol
1370
+ *
1371
+ * @returns {string}
1372
+ */
1072
1373
  protocol() {
1073
1374
  return location.protocol.match(/^https/) ? "wss" : "ws";
1074
1375
  }
1376
+ /**
1377
+ * The fully qualified socket url
1378
+ *
1379
+ * @returns {string}
1380
+ */
1075
1381
  endPointURL() {
1076
- let uri = Ajax.appendParams(Ajax.appendParams(this.endPoint, this.params()), { vsn: this.vsn });
1382
+ let uri = Ajax.appendParams(
1383
+ Ajax.appendParams(this.endPoint, this.params()),
1384
+ { vsn: this.vsn }
1385
+ );
1077
1386
  if (uri.charAt(0) !== "/") {
1078
1387
  return uri;
1079
1388
  }
@@ -1082,56 +1391,110 @@
1082
1391
  }
1083
1392
  return `${this.protocol()}://${location.host}${uri}`;
1084
1393
  }
1394
+ /**
1395
+ * Disconnects the socket
1396
+ *
1397
+ * See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes for valid status codes.
1398
+ *
1399
+ * @param {Function} callback - Optional callback which is called after socket is disconnected.
1400
+ * @param {integer} code - A status code for disconnection (Optional).
1401
+ * @param {string} reason - A textual description of the reason to disconnect. (Optional)
1402
+ */
1085
1403
  disconnect(callback, code, reason) {
1086
1404
  this.connectClock++;
1405
+ this.disconnecting = true;
1087
1406
  this.closeWasClean = true;
1407
+ clearTimeout(this.fallbackTimer);
1088
1408
  this.reconnectTimer.reset();
1089
- this.teardown(callback, code, reason);
1090
- }
1409
+ this.teardown(() => {
1410
+ this.disconnecting = false;
1411
+ callback && callback();
1412
+ }, code, reason);
1413
+ }
1414
+ /**
1415
+ *
1416
+ * @param {Object} params - The params to send when connecting, for example `{user_id: userToken}`
1417
+ *
1418
+ * Passing params to connect is deprecated; pass them in the Socket constructor instead:
1419
+ * `new Socket("/socket", {params: {user_id: userToken}})`.
1420
+ */
1091
1421
  connect(params) {
1092
1422
  if (params) {
1093
1423
  console && console.log("passing params to connect is deprecated. Instead pass :params to the Socket constructor");
1094
1424
  this.params = closure(params);
1095
1425
  }
1096
- if (this.conn) {
1426
+ if (this.conn && !this.disconnecting) {
1097
1427
  return;
1098
1428
  }
1099
- this.connectClock++;
1100
- this.closeWasClean = false;
1101
- this.conn = new this.transport(this.endPointURL());
1102
- this.conn.binaryType = this.binaryType;
1103
- this.conn.timeout = this.longpollerTimeout;
1104
- this.conn.onopen = () => this.onConnOpen();
1105
- this.conn.onerror = (error) => this.onConnError(error);
1106
- this.conn.onmessage = (event) => this.onConnMessage(event);
1107
- this.conn.onclose = (event) => this.onConnClose(event);
1429
+ if (this.longPollFallbackMs && this.transport !== LongPoll) {
1430
+ this.connectWithFallback(LongPoll, this.longPollFallbackMs);
1431
+ } else {
1432
+ this.transportConnect();
1433
+ }
1108
1434
  }
1435
+ /**
1436
+ * Logs the message. Override `this.logger` for specialized logging. noops by default
1437
+ * @param {string} kind
1438
+ * @param {string} msg
1439
+ * @param {Object} data
1440
+ */
1109
1441
  log(kind, msg, data) {
1110
- this.logger(kind, msg, data);
1442
+ this.logger && this.logger(kind, msg, data);
1111
1443
  }
1444
+ /**
1445
+ * Returns true if a logger has been set on this socket.
1446
+ */
1112
1447
  hasLogger() {
1113
1448
  return this.logger !== null;
1114
1449
  }
1450
+ /**
1451
+ * Registers callbacks for connection open events
1452
+ *
1453
+ * @example socket.onOpen(function(){ console.info("the socket was opened") })
1454
+ *
1455
+ * @param {Function} callback
1456
+ */
1115
1457
  onOpen(callback) {
1116
1458
  let ref = this.makeRef();
1117
1459
  this.stateChangeCallbacks.open.push([ref, callback]);
1118
1460
  return ref;
1119
1461
  }
1462
+ /**
1463
+ * Registers callbacks for connection close events
1464
+ * @param {Function} callback
1465
+ */
1120
1466
  onClose(callback) {
1121
1467
  let ref = this.makeRef();
1122
1468
  this.stateChangeCallbacks.close.push([ref, callback]);
1123
1469
  return ref;
1124
1470
  }
1471
+ /**
1472
+ * Registers callbacks for connection error events
1473
+ *
1474
+ * @example socket.onError(function(error){ alert("An error occurred") })
1475
+ *
1476
+ * @param {Function} callback
1477
+ */
1125
1478
  onError(callback) {
1126
1479
  let ref = this.makeRef();
1127
1480
  this.stateChangeCallbacks.error.push([ref, callback]);
1128
1481
  return ref;
1129
1482
  }
1483
+ /**
1484
+ * Registers callbacks for connection message events
1485
+ * @param {Function} callback
1486
+ */
1130
1487
  onMessage(callback) {
1131
1488
  let ref = this.makeRef();
1132
1489
  this.stateChangeCallbacks.message.push([ref, callback]);
1133
1490
  return ref;
1134
1491
  }
1492
+ /**
1493
+ * Pings the server and invokes the callback with the RTT in milliseconds
1494
+ * @param {Function} callback
1495
+ *
1496
+ * Returns true if the ping was pushed or false if unable to be pushed.
1497
+ */
1135
1498
  ping(callback) {
1136
1499
  if (!this.isConnected()) {
1137
1500
  return false;
@@ -1147,20 +1510,92 @@
1147
1510
  });
1148
1511
  return true;
1149
1512
  }
1513
+ /**
1514
+ * @private
1515
+ */
1516
+ transportConnect() {
1517
+ this.connectClock++;
1518
+ this.closeWasClean = false;
1519
+ let protocols = void 0;
1520
+ if (this.authToken) {
1521
+ protocols = ["phoenix", `${AUTH_TOKEN_PREFIX}${btoa(this.authToken).replace(/=/g, "")}`];
1522
+ }
1523
+ this.conn = new this.transport(this.endPointURL(), protocols);
1524
+ this.conn.binaryType = this.binaryType;
1525
+ this.conn.timeout = this.longpollerTimeout;
1526
+ this.conn.onopen = () => this.onConnOpen();
1527
+ this.conn.onerror = (error) => this.onConnError(error);
1528
+ this.conn.onmessage = (event) => this.onConnMessage(event);
1529
+ this.conn.onclose = (event) => this.onConnClose(event);
1530
+ }
1531
+ getSession(key) {
1532
+ return this.sessionStore && this.sessionStore.getItem(key);
1533
+ }
1534
+ storeSession(key, val) {
1535
+ this.sessionStore && this.sessionStore.setItem(key, val);
1536
+ }
1537
+ connectWithFallback(fallbackTransport, fallbackThreshold = 2500) {
1538
+ clearTimeout(this.fallbackTimer);
1539
+ let established = false;
1540
+ let primaryTransport = true;
1541
+ let openRef, errorRef;
1542
+ let fallback = (reason) => {
1543
+ this.log("transport", `falling back to ${fallbackTransport.name}...`, reason);
1544
+ this.off([openRef, errorRef]);
1545
+ primaryTransport = false;
1546
+ this.replaceTransport(fallbackTransport);
1547
+ this.transportConnect();
1548
+ };
1549
+ if (this.getSession(`phx:fallback:${fallbackTransport.name}`)) {
1550
+ return fallback("memorized");
1551
+ }
1552
+ this.fallbackTimer = setTimeout(fallback, fallbackThreshold);
1553
+ errorRef = this.onError((reason) => {
1554
+ this.log("transport", "error", reason);
1555
+ if (primaryTransport && !established) {
1556
+ clearTimeout(this.fallbackTimer);
1557
+ fallback(reason);
1558
+ }
1559
+ });
1560
+ if (this.fallbackRef) {
1561
+ this.off([this.fallbackRef]);
1562
+ }
1563
+ this.fallbackRef = this.onOpen(() => {
1564
+ established = true;
1565
+ if (!primaryTransport) {
1566
+ if (!this.primaryPassedHealthCheck) {
1567
+ this.storeSession(`phx:fallback:${fallbackTransport.name}`, "true");
1568
+ }
1569
+ return this.log("transport", `established ${fallbackTransport.name} fallback`);
1570
+ }
1571
+ clearTimeout(this.fallbackTimer);
1572
+ this.fallbackTimer = setTimeout(fallback, fallbackThreshold);
1573
+ this.ping((rtt) => {
1574
+ this.log("transport", "connected to primary after", rtt);
1575
+ this.primaryPassedHealthCheck = true;
1576
+ clearTimeout(this.fallbackTimer);
1577
+ });
1578
+ });
1579
+ this.transportConnect();
1580
+ }
1150
1581
  clearHeartbeats() {
1151
1582
  clearTimeout(this.heartbeatTimer);
1152
1583
  clearTimeout(this.heartbeatTimeoutTimer);
1153
1584
  }
1154
1585
  onConnOpen() {
1155
1586
  if (this.hasLogger())
1156
- this.log("transport", `connected to ${this.endPointURL()}`);
1587
+ this.log("transport", `${this.transport.name} connected to ${this.endPointURL()}`);
1157
1588
  this.closeWasClean = false;
1589
+ this.disconnecting = false;
1158
1590
  this.establishedConnections++;
1159
1591
  this.flushSendBuffer();
1160
1592
  this.reconnectTimer.reset();
1161
1593
  this.resetHeartbeat();
1162
1594
  this.stateChangeCallbacks.open.forEach(([, callback]) => callback());
1163
1595
  }
1596
+ /**
1597
+ * @private
1598
+ */
1164
1599
  heartbeatTimeout() {
1165
1600
  if (this.pendingHeartbeatRef) {
1166
1601
  this.pendingHeartbeatRef = null;
@@ -1184,7 +1619,11 @@
1184
1619
  if (!this.conn) {
1185
1620
  return callback && callback();
1186
1621
  }
1622
+ let connectClock = this.connectClock;
1187
1623
  this.waitForBufferDone(() => {
1624
+ if (connectClock !== this.connectClock) {
1625
+ return;
1626
+ }
1188
1627
  if (this.conn) {
1189
1628
  if (code) {
1190
1629
  this.conn.close(code, reason || "");
@@ -1193,6 +1632,9 @@
1193
1632
  }
1194
1633
  }
1195
1634
  this.waitForSocketClosed(() => {
1635
+ if (connectClock !== this.connectClock) {
1636
+ return;
1637
+ }
1196
1638
  if (this.conn) {
1197
1639
  this.conn.onopen = function() {
1198
1640
  };
@@ -1227,6 +1669,9 @@
1227
1669
  }, 150 * tries);
1228
1670
  }
1229
1671
  onConnClose(event) {
1672
+ if (this.conn)
1673
+ this.conn.onclose = () => {
1674
+ };
1230
1675
  let closeCode = event && event.code;
1231
1676
  if (this.hasLogger())
1232
1677
  this.log("transport", "close", event);
@@ -1237,6 +1682,9 @@
1237
1682
  }
1238
1683
  this.stateChangeCallbacks.close.forEach(([, callback]) => callback(event));
1239
1684
  }
1685
+ /**
1686
+ * @private
1687
+ */
1240
1688
  onConnError(error) {
1241
1689
  if (this.hasLogger())
1242
1690
  this.log("transport", error);
@@ -1249,6 +1697,9 @@
1249
1697
  this.triggerChanError();
1250
1698
  }
1251
1699
  }
1700
+ /**
1701
+ * @private
1702
+ */
1252
1703
  triggerChanError() {
1253
1704
  this.channels.forEach((channel) => {
1254
1705
  if (!(channel.isErrored() || channel.isLeaving() || channel.isClosed())) {
@@ -1256,6 +1707,9 @@
1256
1707
  }
1257
1708
  });
1258
1709
  }
1710
+ /**
1711
+ * @returns {string}
1712
+ */
1259
1713
  connectionState() {
1260
1714
  switch (this.conn && this.conn.readyState) {
1261
1715
  case SOCKET_STATES.connecting:
@@ -1268,13 +1722,27 @@
1268
1722
  return "closed";
1269
1723
  }
1270
1724
  }
1725
+ /**
1726
+ * @returns {boolean}
1727
+ */
1271
1728
  isConnected() {
1272
1729
  return this.connectionState() === "open";
1273
1730
  }
1731
+ /**
1732
+ * @private
1733
+ *
1734
+ * @param {Channel}
1735
+ */
1274
1736
  remove(channel) {
1275
1737
  this.off(channel.stateChangeRefs);
1276
- this.channels = this.channels.filter((c) => c.joinRef() !== channel.joinRef());
1277
- }
1738
+ this.channels = this.channels.filter((c) => c !== channel);
1739
+ }
1740
+ /**
1741
+ * Removes `onOpen`, `onClose`, `onError,` and `onMessage` registrations.
1742
+ *
1743
+ * @param {refs} - list of refs returned by calls to
1744
+ * `onOpen`, `onClose`, `onError,` and `onMessage`
1745
+ */
1278
1746
  off(refs) {
1279
1747
  for (let key in this.stateChangeCallbacks) {
1280
1748
  this.stateChangeCallbacks[key] = this.stateChangeCallbacks[key].filter(([ref]) => {
@@ -1282,11 +1750,21 @@
1282
1750
  });
1283
1751
  }
1284
1752
  }
1753
+ /**
1754
+ * Initiates a new channel for the given topic
1755
+ *
1756
+ * @param {string} topic
1757
+ * @param {Object} chanParams - Parameters for the channel
1758
+ * @returns {Channel}
1759
+ */
1285
1760
  channel(topic, chanParams = {}) {
1286
1761
  let chan = new Channel(topic, chanParams, this);
1287
1762
  this.channels.push(chan);
1288
1763
  return chan;
1289
1764
  }
1765
+ /**
1766
+ * @param {Object} data
1767
+ */
1290
1768
  push(data) {
1291
1769
  if (this.hasLogger()) {
1292
1770
  let { topic, event, payload, ref, join_ref } = data;
@@ -1298,6 +1776,10 @@
1298
1776
  this.sendBuffer.push(() => this.encode(data, (result) => this.conn.send(result)));
1299
1777
  }
1300
1778
  }
1779
+ /**
1780
+ * Return the next message ref, accounting for overflows
1781
+ * @returns {string}
1782
+ */
1301
1783
  makeRef() {
1302
1784
  let newRef = this.ref + 1;
1303
1785
  if (newRef === this.ref) {
@@ -1354,7 +1836,7 @@
1354
1836
  }
1355
1837
  };
1356
1838
 
1357
- // assets/node_modules/phoenix_live_view/priv/static/phoenix_live_view.esm.js
1839
+ // node_modules/phoenix_live_view/priv/static/phoenix_live_view.esm.js
1358
1840
  var CONSECUTIVE_RELOADS = "consecutive-reloads";
1359
1841
  var MAX_RELOADS = 10;
1360
1842
  var RELOAD_JITTER_MIN = 5e3;
@@ -1367,7 +1849,8 @@
1367
1849
  "phx-keydown-loading",
1368
1850
  "phx-keyup-loading",
1369
1851
  "phx-blur-loading",
1370
- "phx-focus-loading"
1852
+ "phx-focus-loading",
1853
+ "phx-hook-loading"
1371
1854
  ];
1372
1855
  var PHX_COMPONENT = "data-phx-component";
1373
1856
  var PHX_LIVE_LINK = "data-phx-link";
@@ -1383,17 +1866,23 @@
1383
1866
  var PHX_ACTIVE_ENTRY_REFS = "data-phx-active-refs";
1384
1867
  var PHX_LIVE_FILE_UPDATED = "phx:live-file:updated";
1385
1868
  var PHX_SKIP = "data-phx-skip";
1869
+ var PHX_MAGIC_ID = "data-phx-id";
1386
1870
  var PHX_PRUNE = "data-phx-prune";
1387
1871
  var PHX_PAGE_LOADING = "page-loading";
1388
1872
  var PHX_CONNECTED_CLASS = "phx-connected";
1389
- var PHX_DISCONNECTED_CLASS = "phx-loading";
1873
+ var PHX_LOADING_CLASS = "phx-loading";
1390
1874
  var PHX_NO_FEEDBACK_CLASS = "phx-no-feedback";
1391
1875
  var PHX_ERROR_CLASS = "phx-error";
1876
+ var PHX_CLIENT_ERROR_CLASS = "phx-client-error";
1877
+ var PHX_SERVER_ERROR_CLASS = "phx-server-error";
1392
1878
  var PHX_PARENT_ID = "data-phx-parent-id";
1393
1879
  var PHX_MAIN = "data-phx-main";
1394
1880
  var PHX_ROOT_ID = "data-phx-root-id";
1881
+ var PHX_VIEWPORT_TOP = "viewport-top";
1882
+ var PHX_VIEWPORT_BOTTOM = "viewport-bottom";
1395
1883
  var PHX_TRIGGER_ACTION = "trigger-action";
1396
1884
  var PHX_FEEDBACK_FOR = "feedback-for";
1885
+ var PHX_FEEDBACK_GROUP = "feedback-group";
1397
1886
  var PHX_HAS_FOCUSED = "phx-has-focused";
1398
1887
  var FOCUSABLE_INPUTS = ["text", "textarea", "number", "email", "password", "search", "tel", "url", "date", "time", "datetime-local", "color", "range"];
1399
1888
  var CHECKABLE_INPUTS = ["checkbox", "radio"];
@@ -1410,6 +1899,8 @@
1410
1899
  var PHX_DEBOUNCE = "debounce";
1411
1900
  var PHX_THROTTLE = "throttle";
1412
1901
  var PHX_UPDATE = "update";
1902
+ var PHX_STREAM = "stream";
1903
+ var PHX_STREAM_REF = "data-phx-stream";
1413
1904
  var PHX_KEY = "key";
1414
1905
  var PHX_PRIVATE = "phxPrivate";
1415
1906
  var PHX_AUTO_RECOVER = "auto-recover";
@@ -1431,11 +1922,13 @@
1431
1922
  };
1432
1923
  var DYNAMICS = "d";
1433
1924
  var STATIC = "s";
1925
+ var ROOT = "r";
1434
1926
  var COMPONENTS = "c";
1435
1927
  var EVENTS = "e";
1436
1928
  var REPLY = "r";
1437
1929
  var TITLE = "t";
1438
1930
  var TEMPLATES = "p";
1931
+ var STREAM = "stream";
1439
1932
  var EntryUploader = class {
1440
1933
  constructor(entry, chunkSize, liveSocket2) {
1441
1934
  this.liveSocket = liveSocket2;
@@ -1443,11 +1936,16 @@
1443
1936
  this.offset = 0;
1444
1937
  this.chunkSize = chunkSize;
1445
1938
  this.chunkTimer = null;
1939
+ this.errored = false;
1446
1940
  this.uploadChannel = liveSocket2.channel(`lvu:${entry.ref}`, { token: entry.metadata() });
1447
1941
  }
1448
1942
  error(reason) {
1449
- clearTimeout(this.chunkTimer);
1943
+ if (this.errored) {
1944
+ return;
1945
+ }
1450
1946
  this.uploadChannel.leave();
1947
+ this.errored = true;
1948
+ clearTimeout(this.chunkTimer);
1451
1949
  this.entry.error(reason);
1452
1950
  }
1453
1951
  upload() {
@@ -1479,7 +1977,7 @@
1479
1977
  if (!this.isDone()) {
1480
1978
  this.chunkTimer = setTimeout(() => this.readNextChunk(), this.liveSocket.getLatencySim() || 0);
1481
1979
  }
1482
- });
1980
+ }).receive("error", ({ reason }) => this.error(reason));
1483
1981
  }
1484
1982
  };
1485
1983
  var logError = (msg, obj) => console.error && console.error(msg, obj);
@@ -1603,122 +2101,456 @@
1603
2101
  }
1604
2102
  };
1605
2103
  var browser_default = Browser;
1606
- var DOM = {
1607
- byId(id) {
1608
- return document.getElementById(id) || logError(`no id found for ${id}`);
1609
- },
1610
- removeClass(el, className) {
1611
- el.classList.remove(className);
1612
- if (el.classList.length === 0) {
1613
- el.removeAttribute("class");
2104
+ var ARIA = {
2105
+ focusMain() {
2106
+ let target = document.querySelector("main h1, main, h1");
2107
+ if (target) {
2108
+ let origTabIndex = target.tabIndex;
2109
+ target.tabIndex = -1;
2110
+ target.focus();
2111
+ target.tabIndex = origTabIndex;
1614
2112
  }
1615
2113
  },
1616
- all(node, query, callback) {
1617
- if (!node) {
1618
- return [];
2114
+ anyOf(instance, classes) {
2115
+ return classes.find((name) => instance instanceof name);
2116
+ },
2117
+ isFocusable(el, interactiveOnly) {
2118
+ return el instanceof HTMLAnchorElement && el.rel !== "ignore" || el instanceof HTMLAreaElement && el.href !== void 0 || !el.disabled && this.anyOf(el, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, HTMLButtonElement]) || el instanceof HTMLIFrameElement || (el.tabIndex > 0 || !interactiveOnly && el.getAttribute("tabindex") !== null && el.getAttribute("aria-hidden") !== "true");
2119
+ },
2120
+ attemptFocus(el, interactiveOnly) {
2121
+ if (this.isFocusable(el, interactiveOnly)) {
2122
+ try {
2123
+ el.focus();
2124
+ } catch (e) {
2125
+ }
1619
2126
  }
1620
- let array = Array.from(node.querySelectorAll(query));
1621
- return callback ? array.forEach(callback) : array;
2127
+ return !!document.activeElement && document.activeElement.isSameNode(el);
1622
2128
  },
1623
- childNodeLength(html) {
1624
- let template = document.createElement("template");
1625
- template.innerHTML = html;
1626
- return template.content.childElementCount;
2129
+ focusFirstInteractive(el) {
2130
+ let child = el.firstElementChild;
2131
+ while (child) {
2132
+ if (this.attemptFocus(child, true) || this.focusFirstInteractive(child, true)) {
2133
+ return true;
2134
+ }
2135
+ child = child.nextElementSibling;
2136
+ }
1627
2137
  },
1628
- isUploadInput(el) {
1629
- return el.type === "file" && el.getAttribute(PHX_UPLOAD_REF) !== null;
2138
+ focusFirst(el) {
2139
+ let child = el.firstElementChild;
2140
+ while (child) {
2141
+ if (this.attemptFocus(child) || this.focusFirst(child)) {
2142
+ return true;
2143
+ }
2144
+ child = child.nextElementSibling;
2145
+ }
1630
2146
  },
1631
- findUploadInputs(node) {
1632
- return this.all(node, `input[type="file"][${PHX_UPLOAD_REF}]`);
2147
+ focusLast(el) {
2148
+ let child = el.lastElementChild;
2149
+ while (child) {
2150
+ if (this.attemptFocus(child) || this.focusLast(child)) {
2151
+ return true;
2152
+ }
2153
+ child = child.previousElementSibling;
2154
+ }
2155
+ }
2156
+ };
2157
+ var aria_default = ARIA;
2158
+ var focusStack = [];
2159
+ var default_transition_time = 200;
2160
+ var JS = {
2161
+ exec(eventType, phxEvent, view, sourceEl, defaults) {
2162
+ let [defaultKind, defaultArgs] = defaults || [null, { callback: defaults && defaults.callback }];
2163
+ let commands = phxEvent.charAt(0) === "[" ? JSON.parse(phxEvent) : [[defaultKind, defaultArgs]];
2164
+ commands.forEach(([kind, args]) => {
2165
+ if (kind === defaultKind && defaultArgs.data) {
2166
+ args.data = Object.assign(args.data || {}, defaultArgs.data);
2167
+ args.callback = args.callback || defaultArgs.callback;
2168
+ }
2169
+ this.filterToEls(sourceEl, args).forEach((el) => {
2170
+ this[`exec_${kind}`](eventType, phxEvent, view, sourceEl, el, args);
2171
+ });
2172
+ });
1633
2173
  },
1634
- findComponentNodeList(node, cid) {
1635
- return this.filterWithinSameLiveView(this.all(node, `[${PHX_COMPONENT}="${cid}"]`), node);
2174
+ isVisible(el) {
2175
+ return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length > 0);
1636
2176
  },
1637
- isPhxDestroyed(node) {
1638
- return node.id && DOM.private(node, "destroyed") ? true : false;
2177
+ isInViewport(el) {
2178
+ const rect = el.getBoundingClientRect();
2179
+ const windowHeight = window.innerHeight || document.documentElement.clientHeight;
2180
+ const windowWidth = window.innerWidth || document.documentElement.clientWidth;
2181
+ return rect.right > 0 && rect.bottom > 0 && rect.left < windowWidth && rect.top < windowHeight;
1639
2182
  },
1640
- wantsNewTab(e) {
1641
- let wantsNewTab = e.ctrlKey || e.shiftKey || e.metaKey || e.button && e.button === 1;
1642
- return wantsNewTab || e.target.getAttribute("target") === "_blank";
2183
+ exec_exec(eventType, phxEvent, view, sourceEl, el, { attr, to }) {
2184
+ let nodes = to ? dom_default.all(document, to) : [sourceEl];
2185
+ nodes.forEach((node) => {
2186
+ let encodedJS = node.getAttribute(attr);
2187
+ if (!encodedJS) {
2188
+ throw new Error(`expected ${attr} to contain JS command on "${to}"`);
2189
+ }
2190
+ view.liveSocket.execJS(node, encodedJS, eventType);
2191
+ });
1643
2192
  },
1644
- isUnloadableFormSubmit(e) {
1645
- return !e.defaultPrevented && !this.wantsNewTab(e);
2193
+ exec_dispatch(eventType, phxEvent, view, sourceEl, el, { to, event, detail, bubbles }) {
2194
+ detail = detail || {};
2195
+ detail.dispatcher = sourceEl;
2196
+ dom_default.dispatchEvent(el, event, { detail, bubbles });
1646
2197
  },
1647
- isNewPageHref(href, currentLocation) {
1648
- let url;
1649
- try {
1650
- url = new URL(href);
1651
- } catch (e) {
1652
- try {
1653
- url = new URL(href, currentLocation);
1654
- } catch (e2) {
1655
- return true;
2198
+ exec_push(eventType, phxEvent, view, sourceEl, el, args) {
2199
+ let { event, data, target, page_loading, loading, value, dispatcher, callback } = args;
2200
+ let pushOpts = { loading, value, target, page_loading: !!page_loading };
2201
+ let targetSrc = eventType === "change" && dispatcher ? dispatcher : sourceEl;
2202
+ let phxTarget = target || targetSrc.getAttribute(view.binding("target")) || targetSrc;
2203
+ view.withinTargets(phxTarget, (targetView, targetCtx) => {
2204
+ if (!targetView.isConnected()) {
2205
+ return;
1656
2206
  }
1657
- }
1658
- if (url.host === currentLocation.host && url.protocol === currentLocation.protocol) {
1659
- if (url.pathname === currentLocation.pathname && url.search === currentLocation.search) {
1660
- return url.hash === "" && !url.href.endsWith("#");
2207
+ if (eventType === "change") {
2208
+ let { newCid, _target } = args;
2209
+ _target = _target || (dom_default.isFormInput(sourceEl) ? sourceEl.name : void 0);
2210
+ if (_target) {
2211
+ pushOpts._target = _target;
2212
+ }
2213
+ targetView.pushInput(sourceEl, targetCtx, newCid, event || phxEvent, pushOpts, callback);
2214
+ } else if (eventType === "submit") {
2215
+ let { submitter } = args;
2216
+ targetView.submitForm(sourceEl, targetCtx, event || phxEvent, submitter, pushOpts, callback);
2217
+ } else {
2218
+ targetView.pushEvent(eventType, sourceEl, targetCtx, event || phxEvent, data, pushOpts, callback);
1661
2219
  }
1662
- }
1663
- return true;
2220
+ });
1664
2221
  },
1665
- markPhxChildDestroyed(el) {
1666
- if (this.isPhxChild(el)) {
1667
- el.setAttribute(PHX_SESSION, "");
1668
- }
1669
- this.putPrivate(el, "destroyed", true);
2222
+ exec_navigate(eventType, phxEvent, view, sourceEl, el, { href, replace }) {
2223
+ view.liveSocket.historyRedirect(href, replace ? "replace" : "push");
1670
2224
  },
1671
- findPhxChildrenInFragment(html, parentId) {
1672
- let template = document.createElement("template");
1673
- template.innerHTML = html;
1674
- return this.findPhxChildren(template.content, parentId);
2225
+ exec_patch(eventType, phxEvent, view, sourceEl, el, { href, replace }) {
2226
+ view.liveSocket.pushHistoryPatch(href, replace ? "replace" : "push", sourceEl);
1675
2227
  },
1676
- isIgnored(el, phxUpdate) {
1677
- return (el.getAttribute(phxUpdate) || el.getAttribute("data-phx-update")) === "ignore";
2228
+ exec_focus(eventType, phxEvent, view, sourceEl, el) {
2229
+ window.requestAnimationFrame(() => aria_default.attemptFocus(el));
1678
2230
  },
1679
- isPhxUpdate(el, phxUpdate, updateTypes) {
1680
- return el.getAttribute && updateTypes.indexOf(el.getAttribute(phxUpdate)) >= 0;
2231
+ exec_focus_first(eventType, phxEvent, view, sourceEl, el) {
2232
+ window.requestAnimationFrame(() => aria_default.focusFirstInteractive(el) || aria_default.focusFirst(el));
1681
2233
  },
1682
- findPhxSticky(el) {
1683
- return this.all(el, `[${PHX_STICKY}]`);
2234
+ exec_push_focus(eventType, phxEvent, view, sourceEl, el) {
2235
+ window.requestAnimationFrame(() => focusStack.push(el || sourceEl));
1684
2236
  },
1685
- findPhxChildren(el, parentId) {
1686
- return this.all(el, `${PHX_VIEW_SELECTOR}[${PHX_PARENT_ID}="${parentId}"]`);
2237
+ exec_pop_focus(eventType, phxEvent, view, sourceEl, el) {
2238
+ window.requestAnimationFrame(() => {
2239
+ const el2 = focusStack.pop();
2240
+ if (el2) {
2241
+ el2.focus();
2242
+ }
2243
+ });
1687
2244
  },
1688
- findParentCIDs(node, cids) {
1689
- let initial = new Set(cids);
1690
- let parentCids = cids.reduce((acc, cid) => {
1691
- let selector = `[${PHX_COMPONENT}="${cid}"] [${PHX_COMPONENT}]`;
1692
- this.filterWithinSameLiveView(this.all(node, selector), node).map((el) => parseInt(el.getAttribute(PHX_COMPONENT))).forEach((childCID) => acc.delete(childCID));
1693
- return acc;
1694
- }, initial);
1695
- return parentCids.size === 0 ? new Set(cids) : parentCids;
2245
+ exec_add_class(eventType, phxEvent, view, sourceEl, el, { names, transition, time }) {
2246
+ this.addOrRemoveClasses(el, names, [], transition, time, view);
1696
2247
  },
1697
- filterWithinSameLiveView(nodes, parent) {
1698
- if (parent.querySelector(PHX_VIEW_SELECTOR)) {
1699
- return nodes.filter((el) => this.withinSameLiveView(el, parent));
1700
- } else {
1701
- return nodes;
1702
- }
2248
+ exec_remove_class(eventType, phxEvent, view, sourceEl, el, { names, transition, time }) {
2249
+ this.addOrRemoveClasses(el, [], names, transition, time, view);
1703
2250
  },
1704
- withinSameLiveView(node, parent) {
1705
- while (node = node.parentNode) {
1706
- if (node.isSameNode(parent)) {
1707
- return true;
1708
- }
1709
- if (node.getAttribute(PHX_SESSION) !== null) {
1710
- return false;
2251
+ exec_toggle_class(eventType, phxEvent, view, sourceEl, el, { to, names, transition, time }) {
2252
+ this.toggleClasses(el, names, transition, time, view);
2253
+ },
2254
+ exec_toggle_attr(eventType, phxEvent, view, sourceEl, el, { attr: [attr, val1, val2] }) {
2255
+ if (el.hasAttribute(attr)) {
2256
+ if (val2 !== void 0) {
2257
+ if (el.getAttribute(attr) === val1) {
2258
+ this.setOrRemoveAttrs(el, [[attr, val2]], []);
2259
+ } else {
2260
+ this.setOrRemoveAttrs(el, [[attr, val1]], []);
2261
+ }
2262
+ } else {
2263
+ this.setOrRemoveAttrs(el, [], [attr]);
1711
2264
  }
2265
+ } else {
2266
+ this.setOrRemoveAttrs(el, [[attr, val1]], []);
1712
2267
  }
1713
2268
  },
1714
- private(el, key) {
1715
- return el[PHX_PRIVATE] && el[PHX_PRIVATE][key];
2269
+ exec_transition(eventType, phxEvent, view, sourceEl, el, { time, transition }) {
2270
+ this.addOrRemoveClasses(el, [], [], transition, time, view);
1716
2271
  },
1717
- deletePrivate(el, key) {
1718
- el[PHX_PRIVATE] && delete el[PHX_PRIVATE][key];
2272
+ exec_toggle(eventType, phxEvent, view, sourceEl, el, { display, ins, outs, time }) {
2273
+ this.toggle(eventType, view, el, display, ins, outs, time);
1719
2274
  },
1720
- putPrivate(el, key, value) {
1721
- if (!el[PHX_PRIVATE]) {
2275
+ exec_show(eventType, phxEvent, view, sourceEl, el, { display, transition, time }) {
2276
+ this.show(eventType, view, el, display, transition, time);
2277
+ },
2278
+ exec_hide(eventType, phxEvent, view, sourceEl, el, { display, transition, time }) {
2279
+ this.hide(eventType, view, el, display, transition, time);
2280
+ },
2281
+ exec_set_attr(eventType, phxEvent, view, sourceEl, el, { attr: [attr, val] }) {
2282
+ this.setOrRemoveAttrs(el, [[attr, val]], []);
2283
+ },
2284
+ exec_remove_attr(eventType, phxEvent, view, sourceEl, el, { attr }) {
2285
+ this.setOrRemoveAttrs(el, [], [attr]);
2286
+ },
2287
+ show(eventType, view, el, display, transition, time) {
2288
+ if (!this.isVisible(el)) {
2289
+ this.toggle(eventType, view, el, display, transition, null, time);
2290
+ }
2291
+ },
2292
+ hide(eventType, view, el, display, transition, time) {
2293
+ if (this.isVisible(el)) {
2294
+ this.toggle(eventType, view, el, display, null, transition, time);
2295
+ }
2296
+ },
2297
+ toggle(eventType, view, el, display, ins, outs, time) {
2298
+ time = time || default_transition_time;
2299
+ let [inClasses, inStartClasses, inEndClasses] = ins || [[], [], []];
2300
+ let [outClasses, outStartClasses, outEndClasses] = outs || [[], [], []];
2301
+ if (inClasses.length > 0 || outClasses.length > 0) {
2302
+ if (this.isVisible(el)) {
2303
+ let onStart = () => {
2304
+ this.addOrRemoveClasses(el, outStartClasses, inClasses.concat(inStartClasses).concat(inEndClasses));
2305
+ window.requestAnimationFrame(() => {
2306
+ this.addOrRemoveClasses(el, outClasses, []);
2307
+ window.requestAnimationFrame(() => this.addOrRemoveClasses(el, outEndClasses, outStartClasses));
2308
+ });
2309
+ };
2310
+ el.dispatchEvent(new Event("phx:hide-start"));
2311
+ view.transition(time, onStart, () => {
2312
+ this.addOrRemoveClasses(el, [], outClasses.concat(outEndClasses));
2313
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = "none");
2314
+ el.dispatchEvent(new Event("phx:hide-end"));
2315
+ });
2316
+ } else {
2317
+ if (eventType === "remove") {
2318
+ return;
2319
+ }
2320
+ let onStart = () => {
2321
+ this.addOrRemoveClasses(el, inStartClasses, outClasses.concat(outStartClasses).concat(outEndClasses));
2322
+ let stickyDisplay = display || this.defaultDisplay(el);
2323
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = stickyDisplay);
2324
+ window.requestAnimationFrame(() => {
2325
+ this.addOrRemoveClasses(el, inClasses, []);
2326
+ window.requestAnimationFrame(() => this.addOrRemoveClasses(el, inEndClasses, inStartClasses));
2327
+ });
2328
+ };
2329
+ el.dispatchEvent(new Event("phx:show-start"));
2330
+ view.transition(time, onStart, () => {
2331
+ this.addOrRemoveClasses(el, [], inClasses.concat(inEndClasses));
2332
+ el.dispatchEvent(new Event("phx:show-end"));
2333
+ });
2334
+ }
2335
+ } else {
2336
+ if (this.isVisible(el)) {
2337
+ window.requestAnimationFrame(() => {
2338
+ el.dispatchEvent(new Event("phx:hide-start"));
2339
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = "none");
2340
+ el.dispatchEvent(new Event("phx:hide-end"));
2341
+ });
2342
+ } else {
2343
+ window.requestAnimationFrame(() => {
2344
+ el.dispatchEvent(new Event("phx:show-start"));
2345
+ let stickyDisplay = display || this.defaultDisplay(el);
2346
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = stickyDisplay);
2347
+ el.dispatchEvent(new Event("phx:show-end"));
2348
+ });
2349
+ }
2350
+ }
2351
+ },
2352
+ toggleClasses(el, classes, transition, time, view) {
2353
+ window.requestAnimationFrame(() => {
2354
+ let [prevAdds, prevRemoves] = dom_default.getSticky(el, "classes", [[], []]);
2355
+ let newAdds = classes.filter((name) => prevAdds.indexOf(name) < 0 && !el.classList.contains(name));
2356
+ let newRemoves = classes.filter((name) => prevRemoves.indexOf(name) < 0 && el.classList.contains(name));
2357
+ this.addOrRemoveClasses(el, newAdds, newRemoves, transition, time, view);
2358
+ });
2359
+ },
2360
+ addOrRemoveClasses(el, adds, removes, transition, time, view) {
2361
+ time = time || default_transition_time;
2362
+ let [transitionRun, transitionStart, transitionEnd] = transition || [[], [], []];
2363
+ if (transitionRun.length > 0) {
2364
+ let onStart = () => {
2365
+ this.addOrRemoveClasses(el, transitionStart, [].concat(transitionRun).concat(transitionEnd));
2366
+ window.requestAnimationFrame(() => {
2367
+ this.addOrRemoveClasses(el, transitionRun, []);
2368
+ window.requestAnimationFrame(() => this.addOrRemoveClasses(el, transitionEnd, transitionStart));
2369
+ });
2370
+ };
2371
+ let onDone = () => this.addOrRemoveClasses(el, adds.concat(transitionEnd), removes.concat(transitionRun).concat(transitionStart));
2372
+ return view.transition(time, onStart, onDone);
2373
+ }
2374
+ window.requestAnimationFrame(() => {
2375
+ let [prevAdds, prevRemoves] = dom_default.getSticky(el, "classes", [[], []]);
2376
+ let keepAdds = adds.filter((name) => prevAdds.indexOf(name) < 0 && !el.classList.contains(name));
2377
+ let keepRemoves = removes.filter((name) => prevRemoves.indexOf(name) < 0 && el.classList.contains(name));
2378
+ let newAdds = prevAdds.filter((name) => removes.indexOf(name) < 0).concat(keepAdds);
2379
+ let newRemoves = prevRemoves.filter((name) => adds.indexOf(name) < 0).concat(keepRemoves);
2380
+ dom_default.putSticky(el, "classes", (currentEl) => {
2381
+ currentEl.classList.remove(...newRemoves);
2382
+ currentEl.classList.add(...newAdds);
2383
+ return [newAdds, newRemoves];
2384
+ });
2385
+ });
2386
+ },
2387
+ setOrRemoveAttrs(el, sets, removes) {
2388
+ let [prevSets, prevRemoves] = dom_default.getSticky(el, "attrs", [[], []]);
2389
+ let alteredAttrs = sets.map(([attr, _val]) => attr).concat(removes);
2390
+ let newSets = prevSets.filter(([attr, _val]) => !alteredAttrs.includes(attr)).concat(sets);
2391
+ let newRemoves = prevRemoves.filter((attr) => !alteredAttrs.includes(attr)).concat(removes);
2392
+ dom_default.putSticky(el, "attrs", (currentEl) => {
2393
+ newRemoves.forEach((attr) => currentEl.removeAttribute(attr));
2394
+ newSets.forEach(([attr, val]) => currentEl.setAttribute(attr, val));
2395
+ return [newSets, newRemoves];
2396
+ });
2397
+ },
2398
+ hasAllClasses(el, classes) {
2399
+ return classes.every((name) => el.classList.contains(name));
2400
+ },
2401
+ isToggledOut(el, outClasses) {
2402
+ return !this.isVisible(el) || this.hasAllClasses(el, outClasses);
2403
+ },
2404
+ filterToEls(sourceEl, { to }) {
2405
+ return to ? dom_default.all(document, to) : [sourceEl];
2406
+ },
2407
+ defaultDisplay(el) {
2408
+ return { tr: "table-row", td: "table-cell" }[el.tagName.toLowerCase()] || "block";
2409
+ }
2410
+ };
2411
+ var js_default = JS;
2412
+ var DOM = {
2413
+ byId(id) {
2414
+ return document.getElementById(id) || logError(`no id found for ${id}`);
2415
+ },
2416
+ removeClass(el, className) {
2417
+ el.classList.remove(className);
2418
+ if (el.classList.length === 0) {
2419
+ el.removeAttribute("class");
2420
+ }
2421
+ },
2422
+ all(node, query, callback) {
2423
+ if (!node) {
2424
+ return [];
2425
+ }
2426
+ let array = Array.from(node.querySelectorAll(query));
2427
+ return callback ? array.forEach(callback) : array;
2428
+ },
2429
+ childNodeLength(html) {
2430
+ let template = document.createElement("template");
2431
+ template.innerHTML = html;
2432
+ return template.content.childElementCount;
2433
+ },
2434
+ isUploadInput(el) {
2435
+ return el.type === "file" && el.getAttribute(PHX_UPLOAD_REF) !== null;
2436
+ },
2437
+ isAutoUpload(inputEl) {
2438
+ return inputEl.hasAttribute("data-phx-auto-upload");
2439
+ },
2440
+ findUploadInputs(node) {
2441
+ const formId = node.id;
2442
+ const inputsOutsideForm = this.all(document, `input[type="file"][${PHX_UPLOAD_REF}][form="${formId}"]`);
2443
+ return this.all(node, `input[type="file"][${PHX_UPLOAD_REF}]`).concat(inputsOutsideForm);
2444
+ },
2445
+ findComponentNodeList(node, cid) {
2446
+ return this.filterWithinSameLiveView(this.all(node, `[${PHX_COMPONENT}="${cid}"]`), node);
2447
+ },
2448
+ isPhxDestroyed(node) {
2449
+ return node.id && DOM.private(node, "destroyed") ? true : false;
2450
+ },
2451
+ wantsNewTab(e) {
2452
+ let wantsNewTab = e.ctrlKey || e.shiftKey || e.metaKey || e.button && e.button === 1;
2453
+ let isDownload = e.target instanceof HTMLAnchorElement && e.target.hasAttribute("download");
2454
+ let isTargetBlank = e.target.hasAttribute("target") && e.target.getAttribute("target").toLowerCase() === "_blank";
2455
+ let isTargetNamedTab = e.target.hasAttribute("target") && !e.target.getAttribute("target").startsWith("_");
2456
+ return wantsNewTab || isTargetBlank || isDownload || isTargetNamedTab;
2457
+ },
2458
+ isUnloadableFormSubmit(e) {
2459
+ let isDialogSubmit = e.target && e.target.getAttribute("method") === "dialog" || e.submitter && e.submitter.getAttribute("formmethod") === "dialog";
2460
+ if (isDialogSubmit) {
2461
+ return false;
2462
+ } else {
2463
+ return !e.defaultPrevented && !this.wantsNewTab(e);
2464
+ }
2465
+ },
2466
+ isNewPageClick(e, currentLocation) {
2467
+ let href = e.target instanceof HTMLAnchorElement ? e.target.getAttribute("href") : null;
2468
+ let url;
2469
+ if (e.defaultPrevented || href === null || this.wantsNewTab(e)) {
2470
+ return false;
2471
+ }
2472
+ if (href.startsWith("mailto:") || href.startsWith("tel:")) {
2473
+ return false;
2474
+ }
2475
+ if (e.target.isContentEditable) {
2476
+ return false;
2477
+ }
2478
+ try {
2479
+ url = new URL(href);
2480
+ } catch (e2) {
2481
+ try {
2482
+ url = new URL(href, currentLocation);
2483
+ } catch (e3) {
2484
+ return true;
2485
+ }
2486
+ }
2487
+ if (url.host === currentLocation.host && url.protocol === currentLocation.protocol) {
2488
+ if (url.pathname === currentLocation.pathname && url.search === currentLocation.search) {
2489
+ return url.hash === "" && !url.href.endsWith("#");
2490
+ }
2491
+ }
2492
+ return url.protocol.startsWith("http");
2493
+ },
2494
+ markPhxChildDestroyed(el) {
2495
+ if (this.isPhxChild(el)) {
2496
+ el.setAttribute(PHX_SESSION, "");
2497
+ }
2498
+ this.putPrivate(el, "destroyed", true);
2499
+ },
2500
+ findPhxChildrenInFragment(html, parentId) {
2501
+ let template = document.createElement("template");
2502
+ template.innerHTML = html;
2503
+ return this.findPhxChildren(template.content, parentId);
2504
+ },
2505
+ isIgnored(el, phxUpdate) {
2506
+ return (el.getAttribute(phxUpdate) || el.getAttribute("data-phx-update")) === "ignore";
2507
+ },
2508
+ isPhxUpdate(el, phxUpdate, updateTypes) {
2509
+ return el.getAttribute && updateTypes.indexOf(el.getAttribute(phxUpdate)) >= 0;
2510
+ },
2511
+ findPhxSticky(el) {
2512
+ return this.all(el, `[${PHX_STICKY}]`);
2513
+ },
2514
+ findPhxChildren(el, parentId) {
2515
+ return this.all(el, `${PHX_VIEW_SELECTOR}[${PHX_PARENT_ID}="${parentId}"]`);
2516
+ },
2517
+ findExistingParentCIDs(node, cids) {
2518
+ let parentCids = /* @__PURE__ */ new Set();
2519
+ let childrenCids = /* @__PURE__ */ new Set();
2520
+ cids.forEach((cid) => {
2521
+ this.filterWithinSameLiveView(this.all(node, `[${PHX_COMPONENT}="${cid}"]`), node).forEach((parent) => {
2522
+ parentCids.add(cid);
2523
+ this.all(parent, `[${PHX_COMPONENT}]`).map((el) => parseInt(el.getAttribute(PHX_COMPONENT))).forEach((childCID) => childrenCids.add(childCID));
2524
+ });
2525
+ });
2526
+ childrenCids.forEach((childCid) => parentCids.delete(childCid));
2527
+ return parentCids;
2528
+ },
2529
+ filterWithinSameLiveView(nodes, parent) {
2530
+ if (parent.querySelector(PHX_VIEW_SELECTOR)) {
2531
+ return nodes.filter((el) => this.withinSameLiveView(el, parent));
2532
+ } else {
2533
+ return nodes;
2534
+ }
2535
+ },
2536
+ withinSameLiveView(node, parent) {
2537
+ while (node = node.parentNode) {
2538
+ if (node.isSameNode(parent)) {
2539
+ return true;
2540
+ }
2541
+ if (node.getAttribute(PHX_SESSION) !== null) {
2542
+ return false;
2543
+ }
2544
+ }
2545
+ },
2546
+ private(el, key) {
2547
+ return el[PHX_PRIVATE] && el[PHX_PRIVATE][key];
2548
+ },
2549
+ deletePrivate(el, key) {
2550
+ el[PHX_PRIVATE] && delete el[PHX_PRIVATE][key];
2551
+ },
2552
+ putPrivate(el, key, value) {
2553
+ if (!el[PHX_PRIVATE]) {
1722
2554
  el[PHX_PRIVATE] = {};
1723
2555
  }
1724
2556
  el[PHX_PRIVATE][key] = value;
@@ -1760,7 +2592,11 @@
1760
2592
  return callback();
1761
2593
  case "blur":
1762
2594
  if (this.once(el, "debounce-blur")) {
1763
- el.addEventListener("blur", () => callback());
2595
+ el.addEventListener("blur", () => {
2596
+ if (asyncFilter()) {
2597
+ callback();
2598
+ }
2599
+ });
1764
2600
  }
1765
2601
  return;
1766
2602
  default:
@@ -1781,12 +2617,12 @@
1781
2617
  return false;
1782
2618
  } else {
1783
2619
  callback();
1784
- this.putPrivate(el, THROTTLED, true);
1785
- setTimeout(() => {
2620
+ const t = setTimeout(() => {
1786
2621
  if (asyncFilter()) {
1787
2622
  this.triggerCycle(el, DEBOUNCE_TRIGGER);
1788
2623
  }
1789
2624
  }, timeout);
2625
+ this.putPrivate(el, THROTTLED, t);
1790
2626
  }
1791
2627
  } else {
1792
2628
  setTimeout(() => {
@@ -1806,7 +2642,10 @@
1806
2642
  });
1807
2643
  }
1808
2644
  if (this.once(el, "bind-debounce")) {
1809
- el.addEventListener("blur", () => this.triggerCycle(el, DEBOUNCE_TRIGGER));
2645
+ el.addEventListener("blur", () => {
2646
+ clearTimeout(this.private(el, THROTTLED));
2647
+ this.triggerCycle(el, DEBOUNCE_TRIGGER);
2648
+ });
1810
2649
  }
1811
2650
  }
1812
2651
  },
@@ -1834,20 +2673,72 @@
1834
2673
  this.putPrivate(el, key, [currentCycle, trigger]);
1835
2674
  return currentCycle;
1836
2675
  },
1837
- discardError(container, el, phxFeedbackFor) {
1838
- let field = el.getAttribute && el.getAttribute(phxFeedbackFor);
1839
- let input = field && container.querySelector(`[id="${field}"], [name="${field}"], [name="${field}[]"]`);
1840
- if (!input) {
1841
- return;
2676
+ maybeAddPrivateHooks(el, phxViewportTop, phxViewportBottom) {
2677
+ if (el.hasAttribute && (el.hasAttribute(phxViewportTop) || el.hasAttribute(phxViewportBottom))) {
2678
+ el.setAttribute("data-phx-hook", "Phoenix.InfiniteScroll");
1842
2679
  }
1843
- if (!(this.private(input, PHX_HAS_FOCUSED) || this.private(input, PHX_HAS_SUBMITTED))) {
1844
- el.classList.add(PHX_NO_FEEDBACK_CLASS);
2680
+ },
2681
+ isFeedbackContainer(el, phxFeedbackFor) {
2682
+ return el.hasAttribute && el.hasAttribute(phxFeedbackFor);
2683
+ },
2684
+ maybeHideFeedback(container, feedbackContainers, phxFeedbackFor, phxFeedbackGroup) {
2685
+ const feedbackResults = {};
2686
+ feedbackContainers.forEach((el) => {
2687
+ if (!container.contains(el))
2688
+ return;
2689
+ const feedback = el.getAttribute(phxFeedbackFor);
2690
+ if (!feedback) {
2691
+ js_default.addOrRemoveClasses(el, [], [PHX_NO_FEEDBACK_CLASS]);
2692
+ return;
2693
+ }
2694
+ if (feedbackResults[feedback] === true) {
2695
+ this.hideFeedback(el);
2696
+ return;
2697
+ }
2698
+ feedbackResults[feedback] = this.shouldHideFeedback(container, feedback, phxFeedbackGroup);
2699
+ if (feedbackResults[feedback] === true) {
2700
+ this.hideFeedback(el);
2701
+ }
2702
+ });
2703
+ },
2704
+ hideFeedback(container) {
2705
+ js_default.addOrRemoveClasses(container, [PHX_NO_FEEDBACK_CLASS], []);
2706
+ },
2707
+ shouldHideFeedback(container, nameOrGroup, phxFeedbackGroup) {
2708
+ const query = `[name="${nameOrGroup}"],
2709
+ [name="${nameOrGroup}[]"],
2710
+ [${phxFeedbackGroup}="${nameOrGroup}"]`;
2711
+ let focused = false;
2712
+ DOM.all(container, query, (input) => {
2713
+ if (this.private(input, PHX_HAS_FOCUSED) || this.private(input, PHX_HAS_SUBMITTED)) {
2714
+ focused = true;
2715
+ }
2716
+ });
2717
+ return !focused;
2718
+ },
2719
+ feedbackSelector(input, phxFeedbackFor, phxFeedbackGroup) {
2720
+ let query = `[${phxFeedbackFor}="${input.name}"],
2721
+ [${phxFeedbackFor}="${input.name.replace(/\[\]$/, "")}"]`;
2722
+ if (input.getAttribute(phxFeedbackGroup)) {
2723
+ query += `,[${phxFeedbackFor}="${input.getAttribute(phxFeedbackGroup)}"]`;
1845
2724
  }
2725
+ return query;
2726
+ },
2727
+ resetForm(form, phxFeedbackFor, phxFeedbackGroup) {
2728
+ Array.from(form.elements).forEach((input) => {
2729
+ let query = this.feedbackSelector(input, phxFeedbackFor, phxFeedbackGroup);
2730
+ this.deletePrivate(input, PHX_HAS_FOCUSED);
2731
+ this.deletePrivate(input, PHX_HAS_SUBMITTED);
2732
+ this.all(document, query, (feedbackEl) => {
2733
+ js_default.addOrRemoveClasses(feedbackEl, [PHX_NO_FEEDBACK_CLASS], []);
2734
+ });
2735
+ });
1846
2736
  },
1847
- showError(inputEl, phxFeedbackFor) {
1848
- if (inputEl.id || inputEl.name) {
1849
- this.all(inputEl.form, `[${phxFeedbackFor}="${inputEl.id}"], [${phxFeedbackFor}="${inputEl.name}"]`, (el) => {
1850
- this.removeClass(el, PHX_NO_FEEDBACK_CLASS);
2737
+ showError(inputEl, phxFeedbackFor, phxFeedbackGroup) {
2738
+ if (inputEl.name) {
2739
+ let query = this.feedbackSelector(inputEl, phxFeedbackFor, phxFeedbackGroup);
2740
+ this.all(document, query, (el) => {
2741
+ js_default.addOrRemoveClasses(el, [], [PHX_NO_FEEDBACK_CLASS]);
1851
2742
  });
1852
2743
  }
1853
2744
  },
@@ -1857,11 +2748,19 @@
1857
2748
  isPhxSticky(node) {
1858
2749
  return node.getAttribute && node.getAttribute(PHX_STICKY) !== null;
1859
2750
  },
2751
+ isChildOfAny(el, parents) {
2752
+ return !!parents.find((parent) => parent.contains(el));
2753
+ },
1860
2754
  firstPhxChild(el) {
1861
2755
  return this.isPhxChild(el) ? el : this.all(el, `[${PHX_PARENT_ID}]`)[0];
1862
2756
  },
1863
2757
  dispatchEvent(target, name, opts = {}) {
1864
- let bubbles = opts.bubbles === void 0 ? true : !!opts.bubbles;
2758
+ let defaultBubble = true;
2759
+ let isUploadTarget = target.nodeName === "INPUT" && target.type === "file";
2760
+ if (isUploadTarget && name === "click") {
2761
+ defaultBubble = false;
2762
+ }
2763
+ let bubbles = opts.bubbles === void 0 ? defaultBubble : !!opts.bubbles;
1865
2764
  let eventOpts = { bubbles, cancelable: true, detail: opts.detail || {} };
1866
2765
  let event = name === "click" ? new MouseEvent("click", eventOpts) : new CustomEvent(name, eventOpts);
1867
2766
  target.dispatchEvent(event);
@@ -1876,20 +2775,27 @@
1876
2775
  }
1877
2776
  },
1878
2777
  mergeAttrs(target, source, opts = {}) {
1879
- let exclude = opts.exclude || [];
2778
+ let exclude = new Set(opts.exclude || []);
1880
2779
  let isIgnored = opts.isIgnored;
1881
2780
  let sourceAttrs = source.attributes;
1882
2781
  for (let i = sourceAttrs.length - 1; i >= 0; i--) {
1883
2782
  let name = sourceAttrs[i].name;
1884
- if (exclude.indexOf(name) < 0) {
1885
- target.setAttribute(name, source.getAttribute(name));
1886
- }
2783
+ if (!exclude.has(name)) {
2784
+ const sourceValue = source.getAttribute(name);
2785
+ if (target.getAttribute(name) !== sourceValue && (!isIgnored || isIgnored && name.startsWith("data-"))) {
2786
+ target.setAttribute(name, sourceValue);
2787
+ }
2788
+ } else {
2789
+ if (name === "value" && target.value === source.value) {
2790
+ target.setAttribute("value", source.getAttribute(name));
2791
+ }
2792
+ }
1887
2793
  }
1888
2794
  let targetAttrs = target.attributes;
1889
2795
  for (let i = targetAttrs.length - 1; i >= 0; i--) {
1890
2796
  let name = targetAttrs[i].name;
1891
2797
  if (isIgnored) {
1892
- if (name.startsWith("data-") && !source.hasAttribute(name)) {
2798
+ if (name.startsWith("data-") && !source.hasAttribute(name) && ![PHX_REF, PHX_REF_SRC].includes(name)) {
1893
2799
  target.removeAttribute(name);
1894
2800
  }
1895
2801
  } else {
@@ -1913,13 +2819,13 @@
1913
2819
  return el.setSelectionRange && (el.type === "text" || el.type === "textarea");
1914
2820
  },
1915
2821
  restoreFocus(focused, selectionStart, selectionEnd) {
2822
+ if (focused instanceof HTMLSelectElement) {
2823
+ focused.focus();
2824
+ }
1916
2825
  if (!DOM.isTextualInput(focused)) {
1917
2826
  return;
1918
2827
  }
1919
2828
  let wasFocused = focused.matches(":focus");
1920
- if (focused.readOnly) {
1921
- focused.blur();
1922
- }
1923
2829
  if (!wasFocused) {
1924
2830
  focused.focus();
1925
2831
  }
@@ -1968,7 +2874,7 @@
1968
2874
  container.childNodes.forEach((childNode) => {
1969
2875
  if (!childNode.id) {
1970
2876
  let isEmptyTextNode = childNode.nodeType === Node.TEXT_NODE && childNode.nodeValue.trim() === "";
1971
- if (!isEmptyTextNode) {
2877
+ if (!isEmptyTextNode && childNode.nodeType !== Node.COMMENT_NODE) {
1972
2878
  logError(`only HTML element tags with an id are allowed inside containers with phx-update.
1973
2879
 
1974
2880
  removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
@@ -2043,7 +2949,13 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2043
2949
  let isPreflighted = preflightedRefs.indexOf(LiveUploader.genFileRef(file)) >= 0;
2044
2950
  return isPreflighted && this.isActive(fileEl, file);
2045
2951
  }
2046
- constructor(fileEl, file, view) {
2952
+ static isPreflightInProgress(file) {
2953
+ return file._preflightInProgress === true;
2954
+ }
2955
+ static markPreflightInProgress(file) {
2956
+ file._preflightInProgress = true;
2957
+ }
2958
+ constructor(fileEl, file, view, autoUpload) {
2047
2959
  this.ref = LiveUploader.genFileRef(file);
2048
2960
  this.fileEl = fileEl;
2049
2961
  this.file = file;
@@ -2057,6 +2969,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2057
2969
  };
2058
2970
  this._onElUpdated = this.onElUpdated.bind(this);
2059
2971
  this.fileEl.addEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated);
2972
+ this.autoUpload = autoUpload;
2060
2973
  }
2061
2974
  metadata() {
2062
2975
  return this.meta;
@@ -2078,7 +2991,11 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2078
2991
  }
2079
2992
  }
2080
2993
  }
2994
+ isCancelled() {
2995
+ return this._isCancelled;
2996
+ }
2081
2997
  cancel() {
2998
+ this.file._preflightInProgress = false;
2082
2999
  this._isCancelled = true;
2083
3000
  this._isDone = true;
2084
3001
  this._onDone();
@@ -2089,7 +3006,12 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2089
3006
  error(reason = "failed") {
2090
3007
  this.fileEl.removeEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated);
2091
3008
  this.view.pushFileProgress(this.fileEl, this.ref, { error: reason });
2092
- LiveUploader.clearFiles(this.fileEl);
3009
+ if (!this.isAutoUpload()) {
3010
+ LiveUploader.clearFiles(this.fileEl);
3011
+ }
3012
+ }
3013
+ isAutoUpload() {
3014
+ return this.autoUpload;
2093
3015
  }
2094
3016
  onDone(callback) {
2095
3017
  this._onDone = () => {
@@ -2100,6 +3022,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2100
3022
  onElUpdated() {
2101
3023
  let activeRefs = this.fileEl.getAttribute(PHX_ACTIVE_ENTRY_REFS).split(",");
2102
3024
  if (activeRefs.indexOf(this.ref) === -1) {
3025
+ LiveUploader.untrackFile(this.fileEl, this.file);
2103
3026
  this.cancel();
2104
3027
  }
2105
3028
  }
@@ -2110,7 +3033,8 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2110
3033
  relative_path: this.file.webkitRelativePath,
2111
3034
  size: this.file.size,
2112
3035
  type: this.file.type,
2113
- ref: this.ref
3036
+ ref: this.ref,
3037
+ meta: typeof this.file.meta === "function" ? this.file.meta() : void 0
2114
3038
  };
2115
3039
  }
2116
3040
  uploader(uploaders) {
@@ -2165,6 +3089,9 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2165
3089
  entry.relative_path = file.webkitRelativePath;
2166
3090
  entry.type = file.type;
2167
3091
  entry.size = file.size;
3092
+ if (typeof file.meta === "function") {
3093
+ entry.meta = file.meta();
3094
+ }
2168
3095
  fileData[uploadRef].push(entry);
2169
3096
  });
2170
3097
  return fileData;
@@ -2177,12 +3104,15 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2177
3104
  static untrackFile(inputEl, file) {
2178
3105
  dom_default.putPrivate(inputEl, "files", dom_default.private(inputEl, "files").filter((f) => !Object.is(f, file)));
2179
3106
  }
2180
- static trackFiles(inputEl, files) {
3107
+ static trackFiles(inputEl, files, dataTransfer) {
2181
3108
  if (inputEl.getAttribute("multiple") !== null) {
2182
3109
  let newFiles = files.filter((file) => !this.activeFiles(inputEl).find((f) => Object.is(f, file)));
2183
- dom_default.putPrivate(inputEl, "files", this.activeFiles(inputEl).concat(newFiles));
3110
+ dom_default.updatePrivate(inputEl, "files", [], (existing) => existing.concat(newFiles));
2184
3111
  inputEl.value = null;
2185
3112
  } else {
3113
+ if (dataTransfer && dataTransfer.files.length > 0) {
3114
+ inputEl.files = dataTransfer.files;
3115
+ }
2186
3116
  dom_default.putPrivate(inputEl, "files", files);
2187
3117
  }
2188
3118
  }
@@ -2198,29 +3128,47 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2198
3128
  return Array.from(fileInputs).filter((input) => this.filesAwaitingPreflight(input).length > 0);
2199
3129
  }
2200
3130
  static filesAwaitingPreflight(input) {
2201
- return this.activeFiles(input).filter((f) => !UploadEntry.isPreflighted(input, f));
3131
+ return this.activeFiles(input).filter((f) => !UploadEntry.isPreflighted(input, f) && !UploadEntry.isPreflightInProgress(f));
3132
+ }
3133
+ static markPreflightInProgress(entries) {
3134
+ entries.forEach((entry) => UploadEntry.markPreflightInProgress(entry.file));
2202
3135
  }
2203
3136
  constructor(inputEl, view, onComplete) {
3137
+ this.autoUpload = dom_default.isAutoUpload(inputEl);
2204
3138
  this.view = view;
2205
3139
  this.onComplete = onComplete;
2206
- this._entries = Array.from(LiveUploader.filesAwaitingPreflight(inputEl) || []).map((file) => new UploadEntry(inputEl, file, view));
3140
+ this._entries = Array.from(LiveUploader.filesAwaitingPreflight(inputEl) || []).map((file) => new UploadEntry(inputEl, file, view, this.autoUpload));
3141
+ LiveUploader.markPreflightInProgress(this._entries);
2207
3142
  this.numEntriesInProgress = this._entries.length;
2208
3143
  }
3144
+ isAutoUpload() {
3145
+ return this.autoUpload;
3146
+ }
2209
3147
  entries() {
2210
3148
  return this._entries;
2211
3149
  }
2212
3150
  initAdapterUpload(resp, onError, liveSocket2) {
2213
3151
  this._entries = this._entries.map((entry) => {
2214
- entry.zipPostFlight(resp);
2215
- entry.onDone(() => {
3152
+ if (entry.isCancelled()) {
2216
3153
  this.numEntriesInProgress--;
2217
3154
  if (this.numEntriesInProgress === 0) {
2218
3155
  this.onComplete();
2219
3156
  }
2220
- });
3157
+ } else {
3158
+ entry.zipPostFlight(resp);
3159
+ entry.onDone(() => {
3160
+ this.numEntriesInProgress--;
3161
+ if (this.numEntriesInProgress === 0) {
3162
+ this.onComplete();
3163
+ }
3164
+ });
3165
+ }
2221
3166
  return entry;
2222
3167
  });
2223
3168
  let groupedEntries = this._entries.reduce((acc, entry) => {
3169
+ if (!entry.meta) {
3170
+ return acc;
3171
+ }
2224
3172
  let { name, callback } = entry.uploader(liveSocket2.uploaders);
2225
3173
  acc[name] = acc[name] || { callback, entries: [] };
2226
3174
  acc[name].entries.push(entry);
@@ -2232,60 +3180,6 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2232
3180
  }
2233
3181
  }
2234
3182
  };
2235
- var ARIA = {
2236
- focusMain() {
2237
- let target = document.querySelector("main h1, main, h1");
2238
- if (target) {
2239
- let origTabIndex = target.tabIndex;
2240
- target.tabIndex = -1;
2241
- target.focus();
2242
- target.tabIndex = origTabIndex;
2243
- }
2244
- },
2245
- anyOf(instance, classes) {
2246
- return classes.find((name) => instance instanceof name);
2247
- },
2248
- isFocusable(el, interactiveOnly) {
2249
- return el instanceof HTMLAnchorElement && el.rel !== "ignore" || el instanceof HTMLAreaElement && el.href !== void 0 || !el.disabled && this.anyOf(el, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, HTMLButtonElement]) || el instanceof HTMLIFrameElement || (el.tabIndex > 0 || !interactiveOnly && el.tabIndex === 0 && el.getAttribute("tabindex") !== null && el.getAttribute("aria-hidden") !== "true");
2250
- },
2251
- attemptFocus(el, interactiveOnly) {
2252
- if (this.isFocusable(el, interactiveOnly)) {
2253
- try {
2254
- el.focus();
2255
- } catch (e) {
2256
- }
2257
- }
2258
- return !!document.activeElement && document.activeElement.isSameNode(el);
2259
- },
2260
- focusFirstInteractive(el) {
2261
- let child = el.firstElementChild;
2262
- while (child) {
2263
- if (this.attemptFocus(child, true) || this.focusFirstInteractive(child, true)) {
2264
- return true;
2265
- }
2266
- child = child.nextElementSibling;
2267
- }
2268
- },
2269
- focusFirst(el) {
2270
- let child = el.firstElementChild;
2271
- while (child) {
2272
- if (this.attemptFocus(child) || this.focusFirst(child)) {
2273
- return true;
2274
- }
2275
- child = child.nextElementSibling;
2276
- }
2277
- },
2278
- focusLast(el) {
2279
- let child = el.lastElementChild;
2280
- while (child) {
2281
- if (this.attemptFocus(child) || this.focusLast(child)) {
2282
- return true;
2283
- }
2284
- child = child.previousElementSibling;
2285
- }
2286
- }
2287
- };
2288
- var aria_default = ARIA;
2289
3183
  var Hooks = {
2290
3184
  LiveFileUpload: {
2291
3185
  activeRefs() {
@@ -2337,6 +3231,143 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2337
3231
  }
2338
3232
  }
2339
3233
  };
3234
+ var findScrollContainer = (el) => {
3235
+ if (["HTML", "BODY"].indexOf(el.nodeName.toUpperCase()) >= 0)
3236
+ return null;
3237
+ if (["scroll", "auto"].indexOf(getComputedStyle(el).overflowY) >= 0)
3238
+ return el;
3239
+ return findScrollContainer(el.parentElement);
3240
+ };
3241
+ var scrollTop = (scrollContainer) => {
3242
+ if (scrollContainer) {
3243
+ return scrollContainer.scrollTop;
3244
+ } else {
3245
+ return document.documentElement.scrollTop || document.body.scrollTop;
3246
+ }
3247
+ };
3248
+ var bottom = (scrollContainer) => {
3249
+ if (scrollContainer) {
3250
+ return scrollContainer.getBoundingClientRect().bottom;
3251
+ } else {
3252
+ return window.innerHeight || document.documentElement.clientHeight;
3253
+ }
3254
+ };
3255
+ var top = (scrollContainer) => {
3256
+ if (scrollContainer) {
3257
+ return scrollContainer.getBoundingClientRect().top;
3258
+ } else {
3259
+ return 0;
3260
+ }
3261
+ };
3262
+ var isAtViewportTop = (el, scrollContainer) => {
3263
+ let rect = el.getBoundingClientRect();
3264
+ return rect.top >= top(scrollContainer) && rect.left >= 0 && rect.top <= bottom(scrollContainer);
3265
+ };
3266
+ var isAtViewportBottom = (el, scrollContainer) => {
3267
+ let rect = el.getBoundingClientRect();
3268
+ return rect.right >= top(scrollContainer) && rect.left >= 0 && rect.bottom <= bottom(scrollContainer);
3269
+ };
3270
+ var isWithinViewport = (el, scrollContainer) => {
3271
+ let rect = el.getBoundingClientRect();
3272
+ return rect.top >= top(scrollContainer) && rect.left >= 0 && rect.top <= bottom(scrollContainer);
3273
+ };
3274
+ Hooks.InfiniteScroll = {
3275
+ mounted() {
3276
+ this.scrollContainer = findScrollContainer(this.el);
3277
+ let scrollBefore = scrollTop(this.scrollContainer);
3278
+ let topOverran = false;
3279
+ let throttleInterval = 500;
3280
+ let pendingOp = null;
3281
+ let onTopOverrun = this.throttle(throttleInterval, (topEvent, firstChild) => {
3282
+ pendingOp = () => true;
3283
+ this.liveSocket.execJSHookPush(this.el, topEvent, { id: firstChild.id, _overran: true }, () => {
3284
+ pendingOp = null;
3285
+ });
3286
+ });
3287
+ let onFirstChildAtTop = this.throttle(throttleInterval, (topEvent, firstChild) => {
3288
+ pendingOp = () => firstChild.scrollIntoView({ block: "start" });
3289
+ this.liveSocket.execJSHookPush(this.el, topEvent, { id: firstChild.id }, () => {
3290
+ pendingOp = null;
3291
+ window.requestAnimationFrame(() => {
3292
+ if (!isWithinViewport(firstChild, this.scrollContainer)) {
3293
+ firstChild.scrollIntoView({ block: "start" });
3294
+ }
3295
+ });
3296
+ });
3297
+ });
3298
+ let onLastChildAtBottom = this.throttle(throttleInterval, (bottomEvent, lastChild) => {
3299
+ pendingOp = () => lastChild.scrollIntoView({ block: "end" });
3300
+ this.liveSocket.execJSHookPush(this.el, bottomEvent, { id: lastChild.id }, () => {
3301
+ pendingOp = null;
3302
+ window.requestAnimationFrame(() => {
3303
+ if (!isWithinViewport(lastChild, this.scrollContainer)) {
3304
+ lastChild.scrollIntoView({ block: "end" });
3305
+ }
3306
+ });
3307
+ });
3308
+ });
3309
+ this.onScroll = (_e) => {
3310
+ let scrollNow = scrollTop(this.scrollContainer);
3311
+ if (pendingOp) {
3312
+ scrollBefore = scrollNow;
3313
+ return pendingOp();
3314
+ }
3315
+ let rect = this.el.getBoundingClientRect();
3316
+ let topEvent = this.el.getAttribute(this.liveSocket.binding("viewport-top"));
3317
+ let bottomEvent = this.el.getAttribute(this.liveSocket.binding("viewport-bottom"));
3318
+ let lastChild = this.el.lastElementChild;
3319
+ let firstChild = this.el.firstElementChild;
3320
+ let isScrollingUp = scrollNow < scrollBefore;
3321
+ let isScrollingDown = scrollNow > scrollBefore;
3322
+ if (isScrollingUp && topEvent && !topOverran && rect.top >= 0) {
3323
+ topOverran = true;
3324
+ onTopOverrun(topEvent, firstChild);
3325
+ } else if (isScrollingDown && topOverran && rect.top <= 0) {
3326
+ topOverran = false;
3327
+ }
3328
+ if (topEvent && isScrollingUp && isAtViewportTop(firstChild, this.scrollContainer)) {
3329
+ onFirstChildAtTop(topEvent, firstChild);
3330
+ } else if (bottomEvent && isScrollingDown && isAtViewportBottom(lastChild, this.scrollContainer)) {
3331
+ onLastChildAtBottom(bottomEvent, lastChild);
3332
+ }
3333
+ scrollBefore = scrollNow;
3334
+ };
3335
+ if (this.scrollContainer) {
3336
+ this.scrollContainer.addEventListener("scroll", this.onScroll);
3337
+ } else {
3338
+ window.addEventListener("scroll", this.onScroll);
3339
+ }
3340
+ },
3341
+ destroyed() {
3342
+ if (this.scrollContainer) {
3343
+ this.scrollContainer.removeEventListener("scroll", this.onScroll);
3344
+ } else {
3345
+ window.removeEventListener("scroll", this.onScroll);
3346
+ }
3347
+ },
3348
+ throttle(interval, callback) {
3349
+ let lastCallAt = 0;
3350
+ let timer;
3351
+ return (...args) => {
3352
+ let now = Date.now();
3353
+ let remainingTime = interval - (now - lastCallAt);
3354
+ if (remainingTime <= 0 || remainingTime > interval) {
3355
+ if (timer) {
3356
+ clearTimeout(timer);
3357
+ timer = null;
3358
+ }
3359
+ lastCallAt = now;
3360
+ callback(...args);
3361
+ } else if (!timer) {
3362
+ timer = setTimeout(() => {
3363
+ lastCallAt = Date.now();
3364
+ timer = null;
3365
+ callback(...args);
3366
+ }, remainingTime);
3367
+ }
3368
+ };
3369
+ }
3370
+ };
2340
3371
  var hooks_default = Hooks;
2341
3372
  var DOMPostMorphRestorer = class {
2342
3373
  constructor(containerBefore, containerAfter, updateType) {
@@ -2603,6 +3634,8 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2603
3634
  } else {
2604
3635
  toNode = toElement(toNode);
2605
3636
  }
3637
+ } else if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE$1) {
3638
+ toNode = toNode.firstElementChild;
2606
3639
  }
2607
3640
  var getNodeKey = options.getNodeKey || defaultGetNodeKey;
2608
3641
  var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;
@@ -2612,6 +3645,10 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2612
3645
  var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;
2613
3646
  var onNodeDiscarded = options.onNodeDiscarded || noop;
2614
3647
  var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;
3648
+ var skipFromChildren = options.skipFromChildren || noop;
3649
+ var addChild = options.addChild || function(parent, child) {
3650
+ return parent.appendChild(child);
3651
+ };
2615
3652
  var childrenOnly = options.childrenOnly === true;
2616
3653
  var fromNodesLookup = /* @__PURE__ */ Object.create(null);
2617
3654
  var keyedRemovalList = [];
@@ -2712,6 +3749,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2712
3749
  }
2713
3750
  }
2714
3751
  function morphChildren(fromEl, toEl) {
3752
+ var skipFrom = skipFromChildren(fromEl, toEl);
2715
3753
  var curToNodeChild = toEl.firstChild;
2716
3754
  var curFromNodeChild = fromEl.firstChild;
2717
3755
  var curToNodeKey;
@@ -2723,7 +3761,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2723
3761
  while (curToNodeChild) {
2724
3762
  toNextSibling = curToNodeChild.nextSibling;
2725
3763
  curToNodeKey = getNodeKey(curToNodeChild);
2726
- while (curFromNodeChild) {
3764
+ while (!skipFrom && curFromNodeChild) {
2727
3765
  fromNextSibling = curFromNodeChild.nextSibling;
2728
3766
  if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {
2729
3767
  curToNodeChild = toNextSibling;
@@ -2748,6 +3786,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2748
3786
  removeNode(curFromNodeChild, fromEl, true);
2749
3787
  }
2750
3788
  curFromNodeChild = matchingFromEl;
3789
+ curFromNodeKey = getNodeKey(curFromNodeChild);
2751
3790
  }
2752
3791
  } else {
2753
3792
  isCompatible = false;
@@ -2780,7 +3819,9 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2780
3819
  curFromNodeChild = fromNextSibling;
2781
3820
  }
2782
3821
  if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {
2783
- fromEl.appendChild(matchingFromEl);
3822
+ if (!skipFrom) {
3823
+ addChild(fromEl, matchingFromEl);
3824
+ }
2784
3825
  morphEl(matchingFromEl, curToNodeChild);
2785
3826
  } else {
2786
3827
  var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);
@@ -2791,7 +3832,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2791
3832
  if (curToNodeChild.actualize) {
2792
3833
  curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);
2793
3834
  }
2794
- fromEl.appendChild(curToNodeChild);
3835
+ addChild(fromEl, curToNodeChild);
2795
3836
  handleNodeAdded(curToNodeChild);
2796
3837
  }
2797
3838
  }
@@ -2867,15 +3908,20 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2867
3908
  }
2868
3909
  });
2869
3910
  }
2870
- constructor(view, container, id, html, targetCID) {
3911
+ constructor(view, container, id, html, streams, targetCID) {
2871
3912
  this.view = view;
2872
3913
  this.liveSocket = view.liveSocket;
2873
3914
  this.container = container;
2874
3915
  this.id = id;
2875
3916
  this.rootID = view.root.id;
2876
3917
  this.html = html;
3918
+ this.streams = streams;
3919
+ this.streamInserts = {};
3920
+ this.streamComponentRestore = {};
2877
3921
  this.targetCID = targetCID;
2878
3922
  this.cidPatch = isCid(this.targetCID);
3923
+ this.pendingRemoves = [];
3924
+ this.phxRemove = this.liveSocket.binding("remove");
2879
3925
  this.callbacks = {
2880
3926
  beforeadded: [],
2881
3927
  beforeupdated: [],
@@ -2900,11 +3946,12 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2900
3946
  this.callbacks[`after${kind}`].forEach((callback) => callback(...args));
2901
3947
  }
2902
3948
  markPrunableContentForRemoval() {
2903
- dom_default.all(this.container, "[phx-update=append] > *, [phx-update=prepend] > *", (el) => {
3949
+ let phxUpdate = this.liveSocket.binding(PHX_UPDATE);
3950
+ dom_default.all(this.container, `[${phxUpdate}=append] > *, [${phxUpdate}=prepend] > *`, (el) => {
2904
3951
  el.setAttribute(PHX_PRUNE, "");
2905
3952
  });
2906
3953
  }
2907
- perform() {
3954
+ perform(isJoinPatch) {
2908
3955
  let { view, liveSocket: liveSocket2, container, html } = this;
2909
3956
  let targetContainer = this.isCIDPatch() ? this.targetCIDContainer(html) : container;
2910
3957
  if (this.isCIDPatch() && !targetContainer) {
@@ -2914,30 +3961,63 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2914
3961
  let { selectionStart, selectionEnd } = focused && dom_default.hasSelectionRange(focused) ? focused : {};
2915
3962
  let phxUpdate = liveSocket2.binding(PHX_UPDATE);
2916
3963
  let phxFeedbackFor = liveSocket2.binding(PHX_FEEDBACK_FOR);
3964
+ let phxFeedbackGroup = liveSocket2.binding(PHX_FEEDBACK_GROUP);
2917
3965
  let disableWith = liveSocket2.binding(PHX_DISABLE_WITH);
3966
+ let phxViewportTop = liveSocket2.binding(PHX_VIEWPORT_TOP);
3967
+ let phxViewportBottom = liveSocket2.binding(PHX_VIEWPORT_BOTTOM);
2918
3968
  let phxTriggerExternal = liveSocket2.binding(PHX_TRIGGER_ACTION);
2919
- let phxRemove = liveSocket2.binding("remove");
2920
3969
  let added = [];
3970
+ let feedbackContainers = [];
2921
3971
  let updates = [];
2922
3972
  let appendPrependUpdates = [];
2923
- let pendingRemoves = [];
2924
3973
  let externalFormTriggered = null;
2925
- let diffHTML = liveSocket2.time("premorph container prep", () => {
2926
- return this.buildDiffHTML(container, html, phxUpdate, targetContainer);
2927
- });
2928
- this.trackBefore("added", container);
2929
- this.trackBefore("updated", container, container);
2930
- liveSocket2.time("morphdom", () => {
2931
- morphdom_esm_default(targetContainer, diffHTML, {
2932
- childrenOnly: targetContainer.getAttribute(PHX_COMPONENT) === null,
3974
+ function morph(targetContainer2, source, withChildren = false) {
3975
+ morphdom_esm_default(targetContainer2, source, {
3976
+ childrenOnly: targetContainer2.getAttribute(PHX_COMPONENT) === null && !withChildren,
2933
3977
  getNodeKey: (node) => {
2934
- return dom_default.isPhxDestroyed(node) ? null : node.id;
3978
+ if (dom_default.isPhxDestroyed(node)) {
3979
+ return null;
3980
+ }
3981
+ if (isJoinPatch) {
3982
+ return node.id;
3983
+ }
3984
+ return node.id || node.getAttribute && node.getAttribute(PHX_MAGIC_ID);
3985
+ },
3986
+ skipFromChildren: (from) => {
3987
+ return from.getAttribute(phxUpdate) === PHX_STREAM;
3988
+ },
3989
+ addChild: (parent, child) => {
3990
+ let { ref, streamAt } = this.getStreamInsert(child);
3991
+ if (ref === void 0) {
3992
+ return parent.appendChild(child);
3993
+ }
3994
+ this.setStreamRef(child, ref);
3995
+ if (streamAt === 0) {
3996
+ parent.insertAdjacentElement("afterbegin", child);
3997
+ } else if (streamAt === -1) {
3998
+ parent.appendChild(child);
3999
+ } else if (streamAt > 0) {
4000
+ let sibling = Array.from(parent.children)[streamAt];
4001
+ parent.insertBefore(child, sibling);
4002
+ }
2935
4003
  },
2936
4004
  onBeforeNodeAdded: (el) => {
4005
+ dom_default.maybeAddPrivateHooks(el, phxViewportTop, phxViewportBottom);
2937
4006
  this.trackBefore("added", el);
2938
- return el;
4007
+ let morphedEl = el;
4008
+ if (!isJoinPatch && this.streamComponentRestore[el.id]) {
4009
+ morphedEl = this.streamComponentRestore[el.id];
4010
+ delete this.streamComponentRestore[el.id];
4011
+ morph.call(this, morphedEl, el, true);
4012
+ }
4013
+ return morphedEl;
2939
4014
  },
2940
4015
  onNodeAdded: (el) => {
4016
+ if (el.getAttribute) {
4017
+ this.maybeReOrderStream(el, true);
4018
+ }
4019
+ if (dom_default.isFeedbackContainer(el, phxFeedbackFor))
4020
+ feedbackContainers.push(el);
2941
4021
  if (el instanceof HTMLImageElement && el.srcset) {
2942
4022
  el.srcset = el.srcset;
2943
4023
  } else if (el instanceof HTMLVideoElement && el.autoplay) {
@@ -2946,27 +4026,20 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2946
4026
  if (dom_default.isNowTriggerFormExternal(el, phxTriggerExternal)) {
2947
4027
  externalFormTriggered = el;
2948
4028
  }
2949
- dom_default.discardError(targetContainer, el, phxFeedbackFor);
2950
4029
  if (dom_default.isPhxChild(el) && view.ownsElement(el) || dom_default.isPhxSticky(el) && view.ownsElement(el.parentNode)) {
2951
4030
  this.trackAfter("phxChildAdded", el);
2952
4031
  }
2953
4032
  added.push(el);
2954
4033
  },
2955
- onNodeDiscarded: (el) => {
2956
- if (dom_default.isPhxChild(el) || dom_default.isPhxSticky(el)) {
2957
- liveSocket2.destroyViewByEl(el);
2958
- }
2959
- this.trackAfter("discarded", el);
2960
- },
4034
+ onNodeDiscarded: (el) => this.onNodeDiscarded(el),
2961
4035
  onBeforeNodeDiscarded: (el) => {
2962
4036
  if (el.getAttribute && el.getAttribute(PHX_PRUNE) !== null) {
2963
4037
  return true;
2964
4038
  }
2965
- if (el.parentNode !== null && dom_default.isPhxUpdate(el.parentNode, phxUpdate, ["append", "prepend"]) && el.id) {
4039
+ if (el.parentElement !== null && el.id && dom_default.isPhxUpdate(el.parentElement, phxUpdate, [PHX_STREAM, "append", "prepend"])) {
2966
4040
  return false;
2967
4041
  }
2968
- if (el.getAttribute && el.getAttribute(phxRemove)) {
2969
- pendingRemoves.push(el);
4042
+ if (this.maybePendingRemove(el)) {
2970
4043
  return false;
2971
4044
  }
2972
4045
  if (this.skipCIDSibling(el)) {
@@ -2979,10 +4052,17 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2979
4052
  externalFormTriggered = el;
2980
4053
  }
2981
4054
  updates.push(el);
4055
+ this.maybeReOrderStream(el, false);
2982
4056
  },
2983
4057
  onBeforeElUpdated: (fromEl, toEl) => {
4058
+ dom_default.maybeAddPrivateHooks(toEl, phxViewportTop, phxViewportBottom);
4059
+ if (dom_default.isFeedbackContainer(fromEl, phxFeedbackFor) || dom_default.isFeedbackContainer(toEl, phxFeedbackFor)) {
4060
+ feedbackContainers.push(fromEl);
4061
+ feedbackContainers.push(toEl);
4062
+ }
2984
4063
  dom_default.cleanChildNodes(toEl, phxUpdate);
2985
4064
  if (this.skipCIDSibling(toEl)) {
4065
+ this.maybeReOrderStream(fromEl);
2986
4066
  return false;
2987
4067
  }
2988
4068
  if (dom_default.isPhxSticky(fromEl)) {
@@ -2990,7 +4070,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
2990
4070
  }
2991
4071
  if (dom_default.isIgnored(fromEl, phxUpdate) || fromEl.form && fromEl.form.isSameNode(externalFormTriggered)) {
2992
4072
  this.trackBefore("updated", fromEl, toEl);
2993
- dom_default.mergeAttrs(fromEl, toEl, { isIgnored: true });
4073
+ dom_default.mergeAttrs(fromEl, toEl, { isIgnored: dom_default.isIgnored(fromEl, phxUpdate) });
2994
4074
  updates.push(fromEl);
2995
4075
  dom_default.applyStickyOperations(fromEl);
2996
4076
  return false;
@@ -3017,9 +4097,9 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3017
4097
  return false;
3018
4098
  }
3019
4099
  dom_default.copyPrivates(toEl, fromEl);
3020
- dom_default.discardError(targetContainer, toEl, phxFeedbackFor);
3021
4100
  let isFocusedFormEl = focused && fromEl.isSameNode(focused) && dom_default.isFormInput(fromEl);
3022
- if (isFocusedFormEl && fromEl.type !== "hidden") {
4101
+ let focusedSelectChanged = isFocusedFormEl && this.isChangedSelect(fromEl, toEl);
4102
+ if (isFocusedFormEl && fromEl.type !== "hidden" && !focusedSelectChanged) {
3023
4103
  this.trackBefore("updated", fromEl, toEl);
3024
4104
  dom_default.mergeFocusedInput(fromEl, toEl);
3025
4105
  dom_default.syncAttrsToProps(fromEl);
@@ -3027,6 +4107,9 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3027
4107
  dom_default.applyStickyOperations(fromEl);
3028
4108
  return false;
3029
4109
  } else {
4110
+ if (focusedSelectChanged) {
4111
+ fromEl.blur();
4112
+ }
3030
4113
  if (dom_default.isPhxUpdate(toEl, phxUpdate, ["append", "prepend"])) {
3031
4114
  appendPrependUpdates.push(new DOMPostMorphRestorer(fromEl, toEl, toEl.getAttribute(phxUpdate)));
3032
4115
  }
@@ -3037,19 +4120,137 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3037
4120
  }
3038
4121
  }
3039
4122
  });
4123
+ }
4124
+ this.trackBefore("added", container);
4125
+ this.trackBefore("updated", container, container);
4126
+ liveSocket2.time("morphdom", () => {
4127
+ this.streams.forEach(([ref, inserts, deleteIds, reset]) => {
4128
+ inserts.forEach(([key, streamAt, limit]) => {
4129
+ this.streamInserts[key] = { ref, streamAt, limit, reset };
4130
+ });
4131
+ if (reset !== void 0) {
4132
+ dom_default.all(container, `[${PHX_STREAM_REF}="${ref}"]`, (child) => {
4133
+ this.removeStreamChildElement(child);
4134
+ });
4135
+ }
4136
+ deleteIds.forEach((id) => {
4137
+ let child = container.querySelector(`[id="${id}"]`);
4138
+ if (child) {
4139
+ this.removeStreamChildElement(child);
4140
+ }
4141
+ });
4142
+ });
4143
+ if (isJoinPatch) {
4144
+ dom_default.all(this.container, `[${phxUpdate}=${PHX_STREAM}]`, (el) => {
4145
+ this.liveSocket.owner(el, (view2) => {
4146
+ if (view2 === this.view) {
4147
+ Array.from(el.children).forEach((child) => {
4148
+ this.removeStreamChildElement(child);
4149
+ });
4150
+ }
4151
+ });
4152
+ });
4153
+ }
4154
+ morph.call(this, targetContainer, html);
3040
4155
  });
3041
4156
  if (liveSocket2.isDebugEnabled()) {
3042
4157
  detectDuplicateIds();
4158
+ Array.from(document.querySelectorAll("input[name=id]")).forEach((node) => {
4159
+ if (node.form) {
4160
+ console.error('Detected an input with name="id" inside a form! This will cause problems when patching the DOM.\n', node);
4161
+ }
4162
+ });
3043
4163
  }
3044
4164
  if (appendPrependUpdates.length > 0) {
3045
4165
  liveSocket2.time("post-morph append/prepend restoration", () => {
3046
4166
  appendPrependUpdates.forEach((update) => update.perform());
3047
4167
  });
3048
4168
  }
4169
+ dom_default.maybeHideFeedback(targetContainer, feedbackContainers, phxFeedbackFor, phxFeedbackGroup);
3049
4170
  liveSocket2.silenceEvents(() => dom_default.restoreFocus(focused, selectionStart, selectionEnd));
3050
4171
  dom_default.dispatchEvent(document, "phx:update");
3051
4172
  added.forEach((el) => this.trackAfter("added", el));
3052
4173
  updates.forEach((el) => this.trackAfter("updated", el));
4174
+ this.transitionPendingRemoves();
4175
+ if (externalFormTriggered) {
4176
+ liveSocket2.unload();
4177
+ Object.getPrototypeOf(externalFormTriggered).submit.call(externalFormTriggered);
4178
+ }
4179
+ return true;
4180
+ }
4181
+ onNodeDiscarded(el) {
4182
+ if (dom_default.isPhxChild(el) || dom_default.isPhxSticky(el)) {
4183
+ this.liveSocket.destroyViewByEl(el);
4184
+ }
4185
+ this.trackAfter("discarded", el);
4186
+ }
4187
+ maybePendingRemove(node) {
4188
+ if (node.getAttribute && node.getAttribute(this.phxRemove) !== null) {
4189
+ this.pendingRemoves.push(node);
4190
+ return true;
4191
+ } else {
4192
+ return false;
4193
+ }
4194
+ }
4195
+ removeStreamChildElement(child) {
4196
+ if (this.streamInserts[child.id]) {
4197
+ this.streamComponentRestore[child.id] = child;
4198
+ child.remove();
4199
+ } else {
4200
+ if (!this.maybePendingRemove(child)) {
4201
+ child.remove();
4202
+ this.onNodeDiscarded(child);
4203
+ }
4204
+ }
4205
+ }
4206
+ getStreamInsert(el) {
4207
+ let insert = el.id ? this.streamInserts[el.id] : {};
4208
+ return insert || {};
4209
+ }
4210
+ setStreamRef(el, ref) {
4211
+ dom_default.putSticky(el, PHX_STREAM_REF, (el2) => el2.setAttribute(PHX_STREAM_REF, ref));
4212
+ }
4213
+ maybeReOrderStream(el, isNew) {
4214
+ let { ref, streamAt, reset } = this.getStreamInsert(el);
4215
+ if (streamAt === void 0) {
4216
+ return;
4217
+ }
4218
+ this.setStreamRef(el, ref);
4219
+ if (!reset && !isNew) {
4220
+ return;
4221
+ }
4222
+ if (!el.parentElement) {
4223
+ return;
4224
+ }
4225
+ if (streamAt === 0) {
4226
+ el.parentElement.insertBefore(el, el.parentElement.firstElementChild);
4227
+ } else if (streamAt > 0) {
4228
+ let children = Array.from(el.parentElement.children);
4229
+ let oldIndex = children.indexOf(el);
4230
+ if (streamAt >= children.length - 1) {
4231
+ el.parentElement.appendChild(el);
4232
+ } else {
4233
+ let sibling = children[streamAt];
4234
+ if (oldIndex > streamAt) {
4235
+ el.parentElement.insertBefore(el, sibling);
4236
+ } else {
4237
+ el.parentElement.insertBefore(el, sibling.nextElementSibling);
4238
+ }
4239
+ }
4240
+ }
4241
+ this.maybeLimitStream(el);
4242
+ }
4243
+ maybeLimitStream(el) {
4244
+ let { limit } = this.getStreamInsert(el);
4245
+ let children = limit !== null && Array.from(el.parentElement.children);
4246
+ if (limit && limit < 0 && children.length > limit * -1) {
4247
+ children.slice(0, children.length + limit).forEach((child) => this.removeStreamChildElement(child));
4248
+ } else if (limit && limit >= 0 && children.length > limit) {
4249
+ children.slice(limit).forEach((child) => this.removeStreamChildElement(child));
4250
+ }
4251
+ }
4252
+ transitionPendingRemoves() {
4253
+ let { pendingRemoves, liveSocket: liveSocket2 } = this;
3053
4254
  if (pendingRemoves.length > 0) {
3054
4255
  liveSocket2.transitionRemoves(pendingRemoves);
3055
4256
  liveSocket2.requestDOMUpdate(() => {
@@ -3063,17 +4264,26 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3063
4264
  this.trackAfter("transitionsDiscarded", pendingRemoves);
3064
4265
  });
3065
4266
  }
3066
- if (externalFormTriggered) {
3067
- liveSocket2.unload();
3068
- externalFormTriggered.submit();
4267
+ }
4268
+ isChangedSelect(fromEl, toEl) {
4269
+ if (!(fromEl instanceof HTMLSelectElement) || fromEl.multiple) {
4270
+ return false;
3069
4271
  }
3070
- return true;
4272
+ if (fromEl.options.length !== toEl.options.length) {
4273
+ return true;
4274
+ }
4275
+ let fromSelected = fromEl.selectedOptions[0];
4276
+ let toSelected = toEl.selectedOptions[0];
4277
+ if (fromSelected && fromSelected.hasAttribute("selected")) {
4278
+ toSelected.setAttribute("selected", fromSelected.getAttribute("selected"));
4279
+ }
4280
+ return !fromEl.isEqualNode(toEl);
3071
4281
  }
3072
4282
  isCIDPatch() {
3073
4283
  return this.cidPatch;
3074
4284
  }
3075
4285
  skipCIDSibling(el) {
3076
- return el.nodeType === Node.ELEMENT_NODE && el.getAttribute(PHX_SKIP) !== null;
4286
+ return el.nodeType === Node.ELEMENT_NODE && el.hasAttribute(PHX_SKIP);
3077
4287
  }
3078
4288
  targetCIDContainer(html) {
3079
4289
  if (!this.isCIDPatch()) {
@@ -3086,29 +4296,98 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3086
4296
  return first && first.parentNode;
3087
4297
  }
3088
4298
  }
3089
- buildDiffHTML(container, html, phxUpdate, targetContainer) {
3090
- let isCIDPatch = this.isCIDPatch();
3091
- let isCIDWithSingleRoot = isCIDPatch && targetContainer.getAttribute(PHX_COMPONENT) === this.targetCID.toString();
3092
- if (!isCIDPatch || isCIDWithSingleRoot) {
3093
- return html;
3094
- } else {
3095
- let diffContainer = null;
3096
- let template = document.createElement("template");
3097
- diffContainer = dom_default.cloneNode(targetContainer);
3098
- let [firstComponent, ...rest] = dom_default.findComponentNodeList(diffContainer, this.targetCID);
3099
- template.innerHTML = html;
3100
- rest.forEach((el) => el.remove());
3101
- Array.from(diffContainer.childNodes).forEach((child) => {
3102
- if (child.id && child.nodeType === Node.ELEMENT_NODE && child.getAttribute(PHX_COMPONENT) !== this.targetCID.toString()) {
3103
- child.setAttribute(PHX_SKIP, "");
3104
- child.innerHTML = "";
4299
+ indexOf(parent, child) {
4300
+ return Array.from(parent.children).indexOf(child);
4301
+ }
4302
+ };
4303
+ var VOID_TAGS = /* @__PURE__ */ new Set([
4304
+ "area",
4305
+ "base",
4306
+ "br",
4307
+ "col",
4308
+ "command",
4309
+ "embed",
4310
+ "hr",
4311
+ "img",
4312
+ "input",
4313
+ "keygen",
4314
+ "link",
4315
+ "meta",
4316
+ "param",
4317
+ "source",
4318
+ "track",
4319
+ "wbr"
4320
+ ]);
4321
+ var quoteChars = /* @__PURE__ */ new Set(["'", '"']);
4322
+ var modifyRoot = (html, attrs, clearInnerHTML) => {
4323
+ let i = 0;
4324
+ let insideComment = false;
4325
+ let beforeTag, afterTag, tag, tagNameEndsAt, id, newHTML;
4326
+ let lookahead = html.match(/^(\s*(?:<!--.*?-->\s*)*)<([^\s\/>]+)/);
4327
+ if (lookahead === null) {
4328
+ throw new Error(`malformed html ${html}`);
4329
+ }
4330
+ i = lookahead[0].length;
4331
+ beforeTag = lookahead[1];
4332
+ tag = lookahead[2];
4333
+ tagNameEndsAt = i;
4334
+ for (i; i < html.length; i++) {
4335
+ if (html.charAt(i) === ">") {
4336
+ break;
4337
+ }
4338
+ if (html.charAt(i) === "=") {
4339
+ let isId = html.slice(i - 3, i) === " id";
4340
+ i++;
4341
+ let char = html.charAt(i);
4342
+ if (quoteChars.has(char)) {
4343
+ let attrStartsAt = i;
4344
+ i++;
4345
+ for (i; i < html.length; i++) {
4346
+ if (html.charAt(i) === char) {
4347
+ break;
4348
+ }
3105
4349
  }
3106
- });
3107
- Array.from(template.content.childNodes).forEach((el) => diffContainer.insertBefore(el, firstComponent));
3108
- firstComponent.remove();
3109
- return diffContainer.outerHTML;
4350
+ if (isId) {
4351
+ id = html.slice(attrStartsAt + 1, i);
4352
+ break;
4353
+ }
4354
+ }
3110
4355
  }
3111
4356
  }
4357
+ let closeAt = html.length - 1;
4358
+ insideComment = false;
4359
+ while (closeAt >= beforeTag.length + tag.length) {
4360
+ let char = html.charAt(closeAt);
4361
+ if (insideComment) {
4362
+ if (char === "-" && html.slice(closeAt - 3, closeAt) === "<!-") {
4363
+ insideComment = false;
4364
+ closeAt -= 4;
4365
+ } else {
4366
+ closeAt -= 1;
4367
+ }
4368
+ } else if (char === ">" && html.slice(closeAt - 2, closeAt) === "--") {
4369
+ insideComment = true;
4370
+ closeAt -= 3;
4371
+ } else if (char === ">") {
4372
+ break;
4373
+ } else {
4374
+ closeAt -= 1;
4375
+ }
4376
+ }
4377
+ afterTag = html.slice(closeAt + 1, html.length);
4378
+ let attrsStr = Object.keys(attrs).map((attr) => attrs[attr] === true ? attr : `${attr}="${attrs[attr]}"`).join(" ");
4379
+ if (clearInnerHTML) {
4380
+ let idAttrStr = id ? ` id="${id}"` : "";
4381
+ if (VOID_TAGS.has(tag)) {
4382
+ newHTML = `<${tag}${idAttrStr}${attrsStr === "" ? "" : " "}${attrsStr}/>`;
4383
+ } else {
4384
+ newHTML = `<${tag}${idAttrStr}${attrsStr === "" ? "" : " "}${attrsStr}></${tag}>`;
4385
+ }
4386
+ } else {
4387
+ let rest = html.slice(tagNameEndsAt, closeAt + 1);
4388
+ newHTML = `<${tag}${attrsStr === "" ? "" : " "}${attrsStr}${rest}`;
4389
+ }
4390
+ return [newHTML, beforeTag, afterTag];
3112
4391
  };
3113
4392
  var Rendered = class {
3114
4393
  static extract(diff) {
@@ -3121,19 +4400,21 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3121
4400
  constructor(viewId, rendered) {
3122
4401
  this.viewId = viewId;
3123
4402
  this.rendered = {};
4403
+ this.magicId = 0;
3124
4404
  this.mergeDiff(rendered);
3125
4405
  }
3126
4406
  parentViewId() {
3127
4407
  return this.viewId;
3128
4408
  }
3129
4409
  toString(onlyCids) {
3130
- return this.recursiveToString(this.rendered, this.rendered[COMPONENTS], onlyCids);
4410
+ let [str, streams] = this.recursiveToString(this.rendered, this.rendered[COMPONENTS], onlyCids, true, {});
4411
+ return [str, streams];
3131
4412
  }
3132
- recursiveToString(rendered, components = rendered[COMPONENTS], onlyCids) {
4413
+ recursiveToString(rendered, components = rendered[COMPONENTS], onlyCids, changeTracking, rootAttrs) {
3133
4414
  onlyCids = onlyCids ? new Set(onlyCids) : null;
3134
- let output = { buffer: "", components, onlyCids };
3135
- this.toOutputBuffer(rendered, null, output);
3136
- return output.buffer;
4415
+ let output = { buffer: "", components, onlyCids, streams: /* @__PURE__ */ new Set() };
4416
+ this.toOutputBuffer(rendered, null, output, changeTracking, rootAttrs);
4417
+ return [output.buffer, output.streams];
3137
4418
  }
3138
4419
  componentCIDs(diff) {
3139
4420
  return Object.keys(diff[COMPONENTS] || {}).map((i) => parseInt(i));
@@ -3147,6 +4428,11 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3147
4428
  getComponent(diff, cid) {
3148
4429
  return diff[COMPONENTS][cid];
3149
4430
  }
4431
+ resetRender(cid) {
4432
+ if (this.rendered[COMPONENTS][cid]) {
4433
+ this.rendered[COMPONENTS][cid].reset = true;
4434
+ }
4435
+ }
3150
4436
  mergeDiff(diff) {
3151
4437
  let newc = diff[COMPONENTS];
3152
4438
  let cache = {};
@@ -3177,10 +4463,10 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3177
4463
  tdiff = oldc[-scid];
3178
4464
  }
3179
4465
  stat = tdiff[STATIC];
3180
- ndiff = this.cloneMerge(tdiff, cdiff);
4466
+ ndiff = this.cloneMerge(tdiff, cdiff, true);
3181
4467
  ndiff[STATIC] = stat;
3182
4468
  } else {
3183
- ndiff = cdiff[STATIC] !== void 0 ? cdiff : this.cloneMerge(oldc[cid] || {}, cdiff);
4469
+ ndiff = cdiff[STATIC] !== void 0 || oldc[cid] === void 0 ? cdiff : this.cloneMerge(oldc[cid], cdiff, false);
3184
4470
  }
3185
4471
  cache[cid] = ndiff;
3186
4472
  return ndiff;
@@ -3198,26 +4484,40 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3198
4484
  for (let key in source) {
3199
4485
  let val = source[key];
3200
4486
  let targetVal = target[key];
3201
- if (isObject(val) && val[STATIC] === void 0 && isObject(targetVal)) {
4487
+ let isObjVal = isObject(val);
4488
+ if (isObjVal && val[STATIC] === void 0 && isObject(targetVal)) {
3202
4489
  this.doMutableMerge(targetVal, val);
3203
4490
  } else {
3204
4491
  target[key] = val;
3205
4492
  }
3206
4493
  }
4494
+ if (target[ROOT]) {
4495
+ target.newRender = true;
4496
+ }
3207
4497
  }
3208
- cloneMerge(target, source) {
4498
+ cloneMerge(target, source, pruneMagicId) {
3209
4499
  let merged = __spreadValues(__spreadValues({}, target), source);
3210
4500
  for (let key in merged) {
3211
4501
  let val = source[key];
3212
4502
  let targetVal = target[key];
3213
4503
  if (isObject(val) && val[STATIC] === void 0 && isObject(targetVal)) {
3214
- merged[key] = this.cloneMerge(targetVal, val);
4504
+ merged[key] = this.cloneMerge(targetVal, val, pruneMagicId);
4505
+ } else if (val === void 0 && isObject(targetVal)) {
4506
+ merged[key] = this.cloneMerge(targetVal, {}, pruneMagicId);
3215
4507
  }
3216
4508
  }
4509
+ if (pruneMagicId) {
4510
+ delete merged.magicId;
4511
+ delete merged.newRender;
4512
+ } else if (target[ROOT]) {
4513
+ merged.newRender = true;
4514
+ }
3217
4515
  return merged;
3218
4516
  }
3219
4517
  componentToString(cid) {
3220
- return this.recursiveCIDToString(this.rendered[COMPONENTS], cid);
4518
+ let [str, streams] = this.recursiveCIDToString(this.rendered[COMPONENTS], cid, null);
4519
+ let [strippedHTML, _before, _after] = modifyRoot(str, {});
4520
+ return [strippedHTML, streams];
3221
4521
  }
3222
4522
  pruneCIDs(cids) {
3223
4523
  cids.forEach((cid) => delete this.rendered[COMPONENTS][cid]);
@@ -3235,380 +4535,197 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
3235
4535
  return part;
3236
4536
  }
3237
4537
  }
3238
- toOutputBuffer(rendered, templates, output) {
4538
+ nextMagicID() {
4539
+ this.magicId++;
4540
+ return `m${this.magicId}-${this.parentViewId()}`;
4541
+ }
4542
+ toOutputBuffer(rendered, templates, output, changeTracking, rootAttrs = {}) {
3239
4543
  if (rendered[DYNAMICS]) {
3240
4544
  return this.comprehensionToBuffer(rendered, templates, output);
3241
4545
  }
3242
4546
  let { [STATIC]: statics } = rendered;
3243
4547
  statics = this.templateStatic(statics, templates);
3244
- output.buffer += statics[0];
3245
- for (let i = 1; i < statics.length; i++) {
3246
- this.dynamicToBuffer(rendered[i - 1], templates, output);
3247
- output.buffer += statics[i];
3248
- }
3249
- }
3250
- comprehensionToBuffer(rendered, templates, output) {
3251
- let { [DYNAMICS]: dynamics, [STATIC]: statics } = rendered;
3252
- statics = this.templateStatic(statics, templates);
3253
- let compTemplates = templates || rendered[TEMPLATES];
3254
- for (let d = 0; d < dynamics.length; d++) {
3255
- let dynamic = dynamics[d];
3256
- output.buffer += statics[0];
3257
- for (let i = 1; i < statics.length; i++) {
3258
- this.dynamicToBuffer(dynamic[i - 1], compTemplates, output);
3259
- output.buffer += statics[i];
3260
- }
3261
- }
3262
- }
3263
- dynamicToBuffer(rendered, templates, output) {
3264
- if (typeof rendered === "number") {
3265
- output.buffer += this.recursiveCIDToString(output.components, rendered, output.onlyCids);
3266
- } else if (isObject(rendered)) {
3267
- this.toOutputBuffer(rendered, templates, output);
3268
- } else {
3269
- output.buffer += rendered;
3270
- }
3271
- }
3272
- recursiveCIDToString(components, cid, onlyCids) {
3273
- let component = components[cid] || logError(`no component for CID ${cid}`, components);
3274
- let template = document.createElement("template");
3275
- template.innerHTML = this.recursiveToString(component, components, onlyCids);
3276
- let container = template.content;
3277
- let skip = onlyCids && !onlyCids.has(cid);
3278
- let [hasChildNodes, hasChildComponents] = Array.from(container.childNodes).reduce(([hasNodes, hasComponents], child, i) => {
3279
- if (child.nodeType === Node.ELEMENT_NODE) {
3280
- if (child.getAttribute(PHX_COMPONENT)) {
3281
- return [hasNodes, true];
3282
- }
3283
- child.setAttribute(PHX_COMPONENT, cid);
3284
- if (!child.id) {
3285
- child.id = `${this.parentViewId()}-${cid}-${i}`;
3286
- }
3287
- if (skip) {
3288
- child.setAttribute(PHX_SKIP, "");
3289
- child.innerHTML = "";
3290
- }
3291
- return [true, hasComponents];
3292
- } else {
3293
- if (child.nodeValue.trim() !== "") {
3294
- logError(`only HTML element tags are allowed at the root of components.
3295
-
3296
- got: "${child.nodeValue.trim()}"
3297
-
3298
- within:
3299
- `, template.innerHTML.trim());
3300
- child.replaceWith(this.createSpan(child.nodeValue, cid));
3301
- return [true, hasComponents];
3302
- } else {
3303
- child.remove();
3304
- return [hasNodes, hasComponents];
3305
- }
3306
- }
3307
- }, [false, false]);
3308
- if (!hasChildNodes && !hasChildComponents) {
3309
- logError("expected at least one HTML element tag inside a component, but the component is empty:\n", template.innerHTML.trim());
3310
- return this.createSpan("", cid).outerHTML;
3311
- } else if (!hasChildNodes && hasChildComponents) {
3312
- logError("expected at least one HTML element tag directly inside a component, but only subcomponents were found. A component must render at least one HTML tag directly inside itself.", template.innerHTML.trim());
3313
- return template.innerHTML;
3314
- } else {
3315
- return template.innerHTML;
3316
- }
3317
- }
3318
- createSpan(text, cid) {
3319
- let span = document.createElement("span");
3320
- span.innerText = text;
3321
- span.setAttribute(PHX_COMPONENT, cid);
3322
- return span;
3323
- }
3324
- };
3325
- var viewHookID = 1;
3326
- var ViewHook = class {
3327
- static makeID() {
3328
- return viewHookID++;
3329
- }
3330
- static elementID(el) {
3331
- return el.phxHookId;
3332
- }
3333
- constructor(view, el, callbacks) {
3334
- this.__view = view;
3335
- this.liveSocket = view.liveSocket;
3336
- this.__callbacks = callbacks;
3337
- this.__listeners = /* @__PURE__ */ new Set();
3338
- this.__isDisconnected = false;
3339
- this.el = el;
3340
- this.el.phxHookId = this.constructor.makeID();
3341
- for (let key in this.__callbacks) {
3342
- this[key] = this.__callbacks[key];
3343
- }
3344
- }
3345
- __mounted() {
3346
- this.mounted && this.mounted();
3347
- }
3348
- __updated() {
3349
- this.updated && this.updated();
3350
- }
3351
- __beforeUpdate() {
3352
- this.beforeUpdate && this.beforeUpdate();
3353
- }
3354
- __destroyed() {
3355
- this.destroyed && this.destroyed();
3356
- }
3357
- __reconnected() {
3358
- if (this.__isDisconnected) {
3359
- this.__isDisconnected = false;
3360
- this.reconnected && this.reconnected();
4548
+ let isRoot = rendered[ROOT];
4549
+ let prevBuffer = output.buffer;
4550
+ if (isRoot) {
4551
+ output.buffer = "";
3361
4552
  }
3362
- }
3363
- __disconnected() {
3364
- this.__isDisconnected = true;
3365
- this.disconnected && this.disconnected();
3366
- }
3367
- pushEvent(event, payload = {}, onReply = function() {
3368
- }) {
3369
- return this.__view.pushHookEvent(null, event, payload, onReply);
3370
- }
3371
- pushEventTo(phxTarget, event, payload = {}, onReply = function() {
3372
- }) {
3373
- return this.__view.withinTargets(phxTarget, (view, targetCtx) => {
3374
- return view.pushHookEvent(targetCtx, event, payload, onReply);
3375
- });
3376
- }
3377
- handleEvent(event, callback) {
3378
- let callbackRef = (customEvent, bypass) => bypass ? event : callback(customEvent.detail);
3379
- window.addEventListener(`phx:${event}`, callbackRef);
3380
- this.__listeners.add(callbackRef);
3381
- return callbackRef;
3382
- }
3383
- removeHandleEvent(callbackRef) {
3384
- let event = callbackRef(null, true);
3385
- window.removeEventListener(`phx:${event}`, callbackRef);
3386
- this.__listeners.delete(callbackRef);
3387
- }
3388
- upload(name, files) {
3389
- return this.__view.dispatchUploads(name, files);
3390
- }
3391
- uploadTo(phxTarget, name, files) {
3392
- return this.__view.withinTargets(phxTarget, (view) => view.dispatchUploads(name, files));
3393
- }
3394
- __cleanup__() {
3395
- this.__listeners.forEach((callbackRef) => this.removeHandleEvent(callbackRef));
3396
- }
3397
- };
3398
- var focusStack = null;
3399
- var JS = {
3400
- exec(eventType, phxEvent, view, sourceEl, defaults) {
3401
- let [defaultKind, defaultArgs] = defaults || [null, {}];
3402
- let commands = phxEvent.charAt(0) === "[" ? JSON.parse(phxEvent) : [[defaultKind, defaultArgs]];
3403
- commands.forEach(([kind, args]) => {
3404
- if (kind === defaultKind && defaultArgs.data) {
3405
- args.data = Object.assign(args.data || {}, defaultArgs.data);
3406
- }
3407
- this.filterToEls(sourceEl, args).forEach((el) => {
3408
- this[`exec_${kind}`](eventType, phxEvent, view, sourceEl, el, args);
3409
- });
3410
- });
3411
- },
3412
- isVisible(el) {
3413
- return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length > 0);
3414
- },
3415
- exec_dispatch(eventType, phxEvent, view, sourceEl, el, { to, event, detail, bubbles }) {
3416
- detail = detail || {};
3417
- detail.dispatcher = sourceEl;
3418
- dom_default.dispatchEvent(el, event, { detail, bubbles });
3419
- },
3420
- exec_push(eventType, phxEvent, view, sourceEl, el, args) {
3421
- if (!view.isConnected()) {
3422
- return;
3423
- }
3424
- let { event, data, target, page_loading, loading, value, dispatcher } = args;
3425
- let pushOpts = { loading, value, target, page_loading: !!page_loading };
3426
- let targetSrc = eventType === "change" && dispatcher ? dispatcher : sourceEl;
3427
- let phxTarget = target || targetSrc.getAttribute(view.binding("target")) || targetSrc;
3428
- view.withinTargets(phxTarget, (targetView, targetCtx) => {
3429
- if (eventType === "change") {
3430
- let { newCid, _target, callback } = args;
3431
- _target = _target || (dom_default.isFormInput(sourceEl) ? sourceEl.name : void 0);
3432
- if (_target) {
3433
- pushOpts._target = _target;
3434
- }
3435
- targetView.pushInput(sourceEl, targetCtx, newCid, event || phxEvent, pushOpts, callback);
3436
- } else if (eventType === "submit") {
3437
- targetView.submitForm(sourceEl, targetCtx, event || phxEvent, pushOpts);
3438
- } else {
3439
- targetView.pushEvent(eventType, sourceEl, targetCtx, event || phxEvent, data, pushOpts);
3440
- }
3441
- });
3442
- },
3443
- exec_navigate(eventType, phxEvent, view, sourceEl, el, { href, replace }) {
3444
- view.liveSocket.historyRedirect(href, replace ? "replace" : "push");
3445
- },
3446
- exec_patch(eventType, phxEvent, view, sourceEl, el, { href, replace }) {
3447
- view.liveSocket.pushHistoryPatch(href, replace ? "replace" : "push", sourceEl);
3448
- },
3449
- exec_focus(eventType, phxEvent, view, sourceEl, el) {
3450
- window.requestAnimationFrame(() => aria_default.attemptFocus(el));
3451
- },
3452
- exec_focus_first(eventType, phxEvent, view, sourceEl, el) {
3453
- window.requestAnimationFrame(() => aria_default.focusFirstInteractive(el) || aria_default.focusFirst(el));
3454
- },
3455
- exec_push_focus(eventType, phxEvent, view, sourceEl, el) {
3456
- window.requestAnimationFrame(() => focusStack = el || sourceEl);
3457
- },
3458
- exec_pop_focus(eventType, phxEvent, view, sourceEl, el) {
3459
- window.requestAnimationFrame(() => {
3460
- if (focusStack) {
3461
- focusStack.focus();
3462
- }
3463
- focusStack = null;
3464
- });
3465
- },
3466
- exec_add_class(eventType, phxEvent, view, sourceEl, el, { names, transition, time }) {
3467
- this.addOrRemoveClasses(el, names, [], transition, time, view);
3468
- },
3469
- exec_remove_class(eventType, phxEvent, view, sourceEl, el, { names, transition, time }) {
3470
- this.addOrRemoveClasses(el, [], names, transition, time, view);
3471
- },
3472
- exec_transition(eventType, phxEvent, view, sourceEl, el, { time, transition }) {
3473
- let [transition_start, running, transition_end] = transition;
3474
- let onStart = () => this.addOrRemoveClasses(el, transition_start.concat(running), []);
3475
- let onDone = () => this.addOrRemoveClasses(el, transition_end, transition_start.concat(running));
3476
- view.transition(time, onStart, onDone);
3477
- },
3478
- exec_toggle(eventType, phxEvent, view, sourceEl, el, { display, ins, outs, time }) {
3479
- this.toggle(eventType, view, el, display, ins, outs, time);
3480
- },
3481
- exec_show(eventType, phxEvent, view, sourceEl, el, { display, transition, time }) {
3482
- this.show(eventType, view, el, display, transition, time);
3483
- },
3484
- exec_hide(eventType, phxEvent, view, sourceEl, el, { display, transition, time }) {
3485
- this.hide(eventType, view, el, display, transition, time);
3486
- },
3487
- exec_set_attr(eventType, phxEvent, view, sourceEl, el, { attr: [attr, val] }) {
3488
- this.setOrRemoveAttrs(el, [[attr, val]], []);
3489
- },
3490
- exec_remove_attr(eventType, phxEvent, view, sourceEl, el, { attr }) {
3491
- this.setOrRemoveAttrs(el, [], [attr]);
3492
- },
3493
- show(eventType, view, el, display, transition, time) {
3494
- if (!this.isVisible(el)) {
3495
- this.toggle(eventType, view, el, display, transition, null, time);
3496
- }
3497
- },
3498
- hide(eventType, view, el, display, transition, time) {
3499
- if (this.isVisible(el)) {
3500
- this.toggle(eventType, view, el, display, null, transition, time);
3501
- }
3502
- },
3503
- toggle(eventType, view, el, display, ins, outs, time) {
3504
- let [inClasses, inStartClasses, inEndClasses] = ins || [[], [], []];
3505
- let [outClasses, outStartClasses, outEndClasses] = outs || [[], [], []];
3506
- if (inClasses.length > 0 || outClasses.length > 0) {
3507
- if (this.isVisible(el)) {
3508
- let onStart = () => {
3509
- this.addOrRemoveClasses(el, outStartClasses, inClasses.concat(inStartClasses).concat(inEndClasses));
3510
- window.requestAnimationFrame(() => {
3511
- this.addOrRemoveClasses(el, outClasses, []);
3512
- window.requestAnimationFrame(() => this.addOrRemoveClasses(el, outEndClasses, outStartClasses));
3513
- });
3514
- };
3515
- el.dispatchEvent(new Event("phx:hide-start"));
3516
- view.transition(time, onStart, () => {
3517
- this.addOrRemoveClasses(el, [], outClasses.concat(outEndClasses));
3518
- dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = "none");
3519
- el.dispatchEvent(new Event("phx:hide-end"));
3520
- });
3521
- } else {
3522
- if (eventType === "remove") {
3523
- return;
3524
- }
3525
- let onStart = () => {
3526
- this.addOrRemoveClasses(el, inStartClasses, outClasses.concat(outStartClasses).concat(outEndClasses));
3527
- dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = display || "block");
3528
- window.requestAnimationFrame(() => {
3529
- this.addOrRemoveClasses(el, inClasses, []);
3530
- window.requestAnimationFrame(() => this.addOrRemoveClasses(el, inEndClasses, inStartClasses));
3531
- });
3532
- };
3533
- el.dispatchEvent(new Event("phx:show-start"));
3534
- view.transition(time, onStart, () => {
3535
- this.addOrRemoveClasses(el, [], inClasses.concat(inEndClasses));
3536
- el.dispatchEvent(new Event("phx:show-end"));
3537
- });
3538
- }
3539
- } else {
3540
- if (this.isVisible(el)) {
3541
- window.requestAnimationFrame(() => {
3542
- el.dispatchEvent(new Event("phx:hide-start"));
3543
- dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = "none");
3544
- el.dispatchEvent(new Event("phx:hide-end"));
3545
- });
4553
+ if (changeTracking && isRoot && !rendered.magicId) {
4554
+ rendered.newRender = true;
4555
+ rendered.magicId = this.nextMagicID();
4556
+ }
4557
+ output.buffer += statics[0];
4558
+ for (let i = 1; i < statics.length; i++) {
4559
+ this.dynamicToBuffer(rendered[i - 1], templates, output, changeTracking);
4560
+ output.buffer += statics[i];
4561
+ }
4562
+ if (isRoot) {
4563
+ let skip = false;
4564
+ let attrs;
4565
+ if (changeTracking || rendered.magicId) {
4566
+ skip = changeTracking && !rendered.newRender;
4567
+ attrs = __spreadValues({ [PHX_MAGIC_ID]: rendered.magicId }, rootAttrs);
3546
4568
  } else {
3547
- window.requestAnimationFrame(() => {
3548
- el.dispatchEvent(new Event("phx:show-start"));
3549
- dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = display || "block");
3550
- el.dispatchEvent(new Event("phx:show-end"));
3551
- });
4569
+ attrs = rootAttrs;
4570
+ }
4571
+ if (skip) {
4572
+ attrs[PHX_SKIP] = true;
3552
4573
  }
4574
+ let [newRoot, commentBefore, commentAfter] = modifyRoot(output.buffer, attrs, skip);
4575
+ rendered.newRender = false;
4576
+ output.buffer = prevBuffer + commentBefore + newRoot + commentAfter;
3553
4577
  }
3554
- },
3555
- addOrRemoveClasses(el, adds, removes, transition, time, view) {
3556
- let [transition_run, transition_start, transition_end] = transition || [[], [], []];
3557
- if (transition_run.length > 0) {
3558
- let onStart = () => this.addOrRemoveClasses(el, transition_start.concat(transition_run), []);
3559
- let onDone = () => this.addOrRemoveClasses(el, adds.concat(transition_end), removes.concat(transition_run).concat(transition_start));
3560
- return view.transition(time, onStart, onDone);
4578
+ }
4579
+ comprehensionToBuffer(rendered, templates, output) {
4580
+ let { [DYNAMICS]: dynamics, [STATIC]: statics, [STREAM]: stream } = rendered;
4581
+ let [_ref, _inserts, deleteIds, reset] = stream || [null, {}, [], null];
4582
+ statics = this.templateStatic(statics, templates);
4583
+ let compTemplates = templates || rendered[TEMPLATES];
4584
+ for (let d = 0; d < dynamics.length; d++) {
4585
+ let dynamic = dynamics[d];
4586
+ output.buffer += statics[0];
4587
+ for (let i = 1; i < statics.length; i++) {
4588
+ let changeTracking = false;
4589
+ this.dynamicToBuffer(dynamic[i - 1], compTemplates, output, changeTracking);
4590
+ output.buffer += statics[i];
4591
+ }
3561
4592
  }
3562
- window.requestAnimationFrame(() => {
3563
- let [prevAdds, prevRemoves] = dom_default.getSticky(el, "classes", [[], []]);
3564
- let keepAdds = adds.filter((name) => prevAdds.indexOf(name) < 0 && !el.classList.contains(name));
3565
- let keepRemoves = removes.filter((name) => prevRemoves.indexOf(name) < 0 && el.classList.contains(name));
3566
- let newAdds = prevAdds.filter((name) => removes.indexOf(name) < 0).concat(keepAdds);
3567
- let newRemoves = prevRemoves.filter((name) => adds.indexOf(name) < 0).concat(keepRemoves);
3568
- dom_default.putSticky(el, "classes", (currentEl) => {
3569
- currentEl.classList.remove(...newRemoves);
3570
- currentEl.classList.add(...newAdds);
3571
- return [newAdds, newRemoves];
3572
- });
4593
+ if (stream !== void 0 && (rendered[DYNAMICS].length > 0 || deleteIds.length > 0 || reset)) {
4594
+ delete rendered[STREAM];
4595
+ rendered[DYNAMICS] = [];
4596
+ output.streams.add(stream);
4597
+ }
4598
+ }
4599
+ dynamicToBuffer(rendered, templates, output, changeTracking) {
4600
+ if (typeof rendered === "number") {
4601
+ let [str, streams] = this.recursiveCIDToString(output.components, rendered, output.onlyCids);
4602
+ output.buffer += str;
4603
+ output.streams = /* @__PURE__ */ new Set([...output.streams, ...streams]);
4604
+ } else if (isObject(rendered)) {
4605
+ this.toOutputBuffer(rendered, templates, output, changeTracking, {});
4606
+ } else {
4607
+ output.buffer += rendered;
4608
+ }
4609
+ }
4610
+ recursiveCIDToString(components, cid, onlyCids) {
4611
+ let component = components[cid] || logError(`no component for CID ${cid}`, components);
4612
+ let attrs = { [PHX_COMPONENT]: cid };
4613
+ let skip = onlyCids && !onlyCids.has(cid);
4614
+ component.newRender = !skip;
4615
+ component.magicId = `c${cid}-${this.parentViewId()}`;
4616
+ let changeTracking = !component.reset;
4617
+ let [html, streams] = this.recursiveToString(component, components, onlyCids, changeTracking, attrs);
4618
+ delete component.reset;
4619
+ return [html, streams];
4620
+ }
4621
+ };
4622
+ var viewHookID = 1;
4623
+ var ViewHook = class {
4624
+ static makeID() {
4625
+ return viewHookID++;
4626
+ }
4627
+ static elementID(el) {
4628
+ return el.phxHookId;
4629
+ }
4630
+ constructor(view, el, callbacks) {
4631
+ this.__view = view;
4632
+ this.liveSocket = view.liveSocket;
4633
+ this.__callbacks = callbacks;
4634
+ this.__listeners = /* @__PURE__ */ new Set();
4635
+ this.__isDisconnected = false;
4636
+ this.el = el;
4637
+ this.el.phxHookId = this.constructor.makeID();
4638
+ for (let key in this.__callbacks) {
4639
+ this[key] = this.__callbacks[key];
4640
+ }
4641
+ }
4642
+ __mounted() {
4643
+ this.mounted && this.mounted();
4644
+ }
4645
+ __updated() {
4646
+ this.updated && this.updated();
4647
+ }
4648
+ __beforeUpdate() {
4649
+ this.beforeUpdate && this.beforeUpdate();
4650
+ }
4651
+ __destroyed() {
4652
+ this.destroyed && this.destroyed();
4653
+ }
4654
+ __reconnected() {
4655
+ if (this.__isDisconnected) {
4656
+ this.__isDisconnected = false;
4657
+ this.reconnected && this.reconnected();
4658
+ }
4659
+ }
4660
+ __disconnected() {
4661
+ this.__isDisconnected = true;
4662
+ this.disconnected && this.disconnected();
4663
+ }
4664
+ pushEvent(event, payload = {}, onReply = function() {
4665
+ }) {
4666
+ return this.__view.pushHookEvent(this.el, null, event, payload, onReply);
4667
+ }
4668
+ pushEventTo(phxTarget, event, payload = {}, onReply = function() {
4669
+ }) {
4670
+ return this.__view.withinTargets(phxTarget, (view, targetCtx) => {
4671
+ return view.pushHookEvent(this.el, targetCtx, event, payload, onReply);
3573
4672
  });
3574
- },
3575
- setOrRemoveAttrs(el, sets, removes) {
3576
- let [prevSets, prevRemoves] = dom_default.getSticky(el, "attrs", [[], []]);
3577
- let alteredAttrs = sets.map(([attr, _val]) => attr).concat(removes);
3578
- let newSets = prevSets.filter(([attr, _val]) => !alteredAttrs.includes(attr)).concat(sets);
3579
- let newRemoves = prevRemoves.filter((attr) => !alteredAttrs.includes(attr)).concat(removes);
3580
- dom_default.putSticky(el, "attrs", (currentEl) => {
3581
- newRemoves.forEach((attr) => currentEl.removeAttribute(attr));
3582
- newSets.forEach(([attr, val]) => currentEl.setAttribute(attr, val));
3583
- return [newSets, newRemoves];
4673
+ }
4674
+ handleEvent(event, callback) {
4675
+ let callbackRef = (customEvent, bypass) => bypass ? event : callback(customEvent.detail);
4676
+ window.addEventListener(`phx:${event}`, callbackRef);
4677
+ this.__listeners.add(callbackRef);
4678
+ return callbackRef;
4679
+ }
4680
+ removeHandleEvent(callbackRef) {
4681
+ let event = callbackRef(null, true);
4682
+ window.removeEventListener(`phx:${event}`, callbackRef);
4683
+ this.__listeners.delete(callbackRef);
4684
+ }
4685
+ upload(name, files) {
4686
+ return this.__view.dispatchUploads(null, name, files);
4687
+ }
4688
+ uploadTo(phxTarget, name, files) {
4689
+ return this.__view.withinTargets(phxTarget, (view, targetCtx) => {
4690
+ view.dispatchUploads(targetCtx, name, files);
3584
4691
  });
3585
- },
3586
- hasAllClasses(el, classes) {
3587
- return classes.every((name) => el.classList.contains(name));
3588
- },
3589
- isToggledOut(el, outClasses) {
3590
- return !this.isVisible(el) || this.hasAllClasses(el, outClasses);
3591
- },
3592
- filterToEls(sourceEl, { to }) {
3593
- return to ? dom_default.all(document, to) : [sourceEl];
4692
+ }
4693
+ __cleanup__() {
4694
+ this.__listeners.forEach((callbackRef) => this.removeHandleEvent(callbackRef));
3594
4695
  }
3595
4696
  };
3596
- var js_default = JS;
3597
- var serializeForm = (form, meta, onlyNames = []) => {
3598
- let formData = new FormData(form);
3599
- let toRemove = [];
4697
+ var serializeForm = (form, metadata, onlyNames = []) => {
4698
+ const _a2 = metadata, { submitter } = _a2, meta = __objRest(_a2, ["submitter"]);
4699
+ let injectedElement;
4700
+ if (submitter && submitter.name) {
4701
+ const input = document.createElement("input");
4702
+ input.type = "hidden";
4703
+ const formId = submitter.getAttribute("form");
4704
+ if (formId) {
4705
+ input.setAttribute("form", formId);
4706
+ }
4707
+ input.name = submitter.name;
4708
+ input.value = submitter.value;
4709
+ submitter.parentElement.insertBefore(input, submitter);
4710
+ injectedElement = input;
4711
+ }
4712
+ const formData = new FormData(form);
4713
+ const toRemove = [];
3600
4714
  formData.forEach((val, key, _index) => {
3601
4715
  if (val instanceof File) {
3602
4716
  toRemove.push(key);
3603
4717
  }
3604
4718
  });
3605
4719
  toRemove.forEach((key) => formData.delete(key));
3606
- let params = new URLSearchParams();
4720
+ const params = new URLSearchParams();
3607
4721
  for (let [key, val] of formData.entries()) {
3608
4722
  if (onlyNames.length === 0 || onlyNames.indexOf(key) >= 0) {
3609
4723
  params.append(key, val);
3610
4724
  }
3611
4725
  }
4726
+ if (submitter && injectedElement) {
4727
+ submitter.parentElement.removeChild(injectedElement);
4728
+ }
3612
4729
  for (let metaKey in meta) {
3613
4730
  params.append(metaKey, meta[metaKey]);
3614
4731
  }
@@ -3627,7 +4744,7 @@ within:
3627
4744
  this.childJoins = 0;
3628
4745
  this.loaderTimer = null;
3629
4746
  this.pendingDiffs = [];
3630
- this.pruningCIDs = [];
4747
+ this.pendingForms = /* @__PURE__ */ new Set();
3631
4748
  this.redirect = false;
3632
4749
  this.href = null;
3633
4750
  this.joinCount = this.parent ? this.parent.joinCount - 1 : 0;
@@ -3640,14 +4757,15 @@ within:
3640
4757
  };
3641
4758
  this.pendingJoinOps = this.parent ? null : [];
3642
4759
  this.viewHooks = {};
3643
- this.uploaders = {};
3644
4760
  this.formSubmits = [];
3645
4761
  this.children = this.parent ? null : {};
3646
4762
  this.root.children[this.id] = {};
4763
+ this.formsForRecovery = {};
3647
4764
  this.channel = this.liveSocket.channel(`lv:${this.id}`, () => {
4765
+ let url = this.href && this.expandURL(this.href);
3648
4766
  return {
3649
- redirect: this.redirect ? this.href : void 0,
3650
- url: this.redirect ? void 0 : this.href || void 0,
4767
+ redirect: this.redirect ? url : void 0,
4768
+ url: this.redirect ? void 0 : url || void 0,
3651
4769
  params: this.connectParams(liveReferer),
3652
4770
  session: this.getSession(),
3653
4771
  static: this.getStatic(),
@@ -3705,7 +4823,7 @@ within:
3705
4823
  this.channel.leave().receive("ok", onFinished).receive("error", onFinished).receive("timeout", onFinished);
3706
4824
  }
3707
4825
  setContainerClasses(...classes) {
3708
- this.el.classList.remove(PHX_CONNECTED_CLASS, PHX_DISCONNECTED_CLASS, PHX_ERROR_CLASS);
4826
+ this.el.classList.remove(PHX_CONNECTED_CLASS, PHX_LOADING_CLASS, PHX_ERROR_CLASS, PHX_CLIENT_ERROR_CLASS, PHX_SERVER_ERROR_CLASS);
3709
4827
  this.el.classList.add(...classes);
3710
4828
  }
3711
4829
  showLoader(timeout) {
@@ -3716,7 +4834,7 @@ within:
3716
4834
  for (let id in this.viewHooks) {
3717
4835
  this.viewHooks[id].__disconnected();
3718
4836
  }
3719
- this.setContainerClasses(PHX_DISCONNECTED_CLASS);
4837
+ this.setContainerClasses(PHX_LOADING_CLASS);
3720
4838
  }
3721
4839
  }
3722
4840
  execAll(binding) {
@@ -3739,19 +4857,19 @@ within:
3739
4857
  }) {
3740
4858
  this.liveSocket.transition(time, onStart, onDone);
3741
4859
  }
3742
- withinTargets(phxTarget, callback) {
4860
+ withinTargets(phxTarget, callback, dom = document, viewEl) {
3743
4861
  if (phxTarget instanceof HTMLElement || phxTarget instanceof SVGElement) {
3744
4862
  return this.liveSocket.owner(phxTarget, (view) => callback(view, phxTarget));
3745
4863
  }
3746
4864
  if (isCid(phxTarget)) {
3747
- let targets = dom_default.findComponentNodeList(this.el, phxTarget);
4865
+ let targets = dom_default.findComponentNodeList(viewEl || this.el, phxTarget);
3748
4866
  if (targets.length === 0) {
3749
4867
  logError(`no component found matching phx-target of ${phxTarget}`);
3750
4868
  } else {
3751
4869
  callback(this, parseInt(phxTarget));
3752
4870
  }
3753
4871
  } else {
3754
- let targets = Array.from(document.querySelectorAll(phxTarget));
4872
+ let targets = Array.from(dom.querySelectorAll(phxTarget));
3755
4873
  if (targets.length === 0) {
3756
4874
  logError(`nothing found matching the phx-target selector "${phxTarget}"`);
3757
4875
  }
@@ -3762,12 +4880,12 @@ within:
3762
4880
  this.log(type, () => ["", clone(rawDiff)]);
3763
4881
  let { diff, reply, events, title } = Rendered.extract(rawDiff);
3764
4882
  callback({ diff, reply, events });
3765
- if (title) {
4883
+ if (typeof title === "string") {
3766
4884
  window.requestAnimationFrame(() => dom_default.putTitle(title));
3767
4885
  }
3768
4886
  }
3769
4887
  onJoin(resp) {
3770
- let { rendered, container } = resp;
4888
+ let { rendered, container, liveview_version } = resp;
3771
4889
  if (container) {
3772
4890
  let [tag, attrs] = container;
3773
4891
  this.el = dom_default.replaceRootContainer(this.el, tag, attrs);
@@ -3775,24 +4893,21 @@ within:
3775
4893
  this.childJoins = 0;
3776
4894
  this.joinPending = true;
3777
4895
  this.flash = null;
4896
+ if (this.root === this) {
4897
+ this.formsForRecovery = this.getFormsForRecovery();
4898
+ }
4899
+ if (liveview_version !== this.liveSocket.version()) {
4900
+ console.error(`LiveView asset version mismatch. JavaScript version ${this.liveSocket.version()} vs. server ${liveview_version}. To avoid issues, please ensure that your assets use the same version as the server.`);
4901
+ }
3778
4902
  browser_default.dropLocal(this.liveSocket.localStorage, window.location.pathname, CONSECUTIVE_RELOADS);
3779
4903
  this.applyDiff("mount", rendered, ({ diff, events }) => {
3780
4904
  this.rendered = new Rendered(this.id, diff);
3781
- let html = this.renderContainer(null, "join");
4905
+ let [html, streams] = this.renderContainer(null, "join");
3782
4906
  this.dropPendingRefs();
3783
- let forms = this.formsForRecovery(html);
3784
4907
  this.joinCount++;
3785
- if (forms.length > 0) {
3786
- forms.forEach(([form, newForm, newCid], i) => {
3787
- this.pushFormRecovery(form, newCid, (resp2) => {
3788
- if (i === forms.length - 1) {
3789
- this.onJoinComplete(resp2, html, events);
3790
- }
3791
- });
3792
- });
3793
- } else {
3794
- this.onJoinComplete(resp, html, events);
3795
- }
4908
+ this.maybeRecoverForms(html, () => {
4909
+ this.onJoinComplete(resp, html, streams, events);
4910
+ });
3796
4911
  });
3797
4912
  }
3798
4913
  dropPendingRefs() {
@@ -3801,9 +4916,9 @@ within:
3801
4916
  el.removeAttribute(PHX_REF_SRC);
3802
4917
  });
3803
4918
  }
3804
- onJoinComplete({ live_patch }, html, events) {
4919
+ onJoinComplete({ live_patch }, html, streams, events) {
3805
4920
  if (this.joinCount > 1 || this.parent && !this.parent.isJoinPending()) {
3806
- return this.applyJoinPatch(live_patch, html, events);
4921
+ return this.applyJoinPatch(live_patch, html, streams, events);
3807
4922
  }
3808
4923
  let newChildren = dom_default.findPhxChildrenInFragment(html, this.id).filter((toEl) => {
3809
4924
  let fromEl = toEl.id && this.el.querySelector(`[id="${toEl.id}"]`);
@@ -3811,18 +4926,21 @@ within:
3811
4926
  if (phxStatic) {
3812
4927
  toEl.setAttribute(PHX_STATIC, phxStatic);
3813
4928
  }
4929
+ if (fromEl) {
4930
+ fromEl.setAttribute(PHX_ROOT_ID, this.root.id);
4931
+ }
3814
4932
  return this.joinChild(toEl);
3815
4933
  });
3816
4934
  if (newChildren.length === 0) {
3817
4935
  if (this.parent) {
3818
- this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, events)]);
4936
+ this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, streams, events)]);
3819
4937
  this.parent.ackJoin(this);
3820
4938
  } else {
3821
4939
  this.onAllChildJoinsComplete();
3822
- this.applyJoinPatch(live_patch, html, events);
4940
+ this.applyJoinPatch(live_patch, html, streams, events);
3823
4941
  }
3824
4942
  } else {
3825
- this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, events)]);
4943
+ this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, streams, events)]);
3826
4944
  }
3827
4945
  }
3828
4946
  attachTrueDocEl() {
@@ -3830,16 +4948,30 @@ within:
3830
4948
  this.el.setAttribute(PHX_ROOT_ID, this.root.id);
3831
4949
  }
3832
4950
  execNewMounted() {
4951
+ let phxViewportTop = this.binding(PHX_VIEWPORT_TOP);
4952
+ let phxViewportBottom = this.binding(PHX_VIEWPORT_BOTTOM);
4953
+ dom_default.all(this.el, `[${phxViewportTop}], [${phxViewportBottom}]`, (hookEl) => {
4954
+ if (this.ownsElement(hookEl)) {
4955
+ dom_default.maybeAddPrivateHooks(hookEl, phxViewportTop, phxViewportBottom);
4956
+ this.maybeAddNewHook(hookEl);
4957
+ }
4958
+ });
3833
4959
  dom_default.all(this.el, `[${this.binding(PHX_HOOK)}], [data-phx-${PHX_HOOK}]`, (hookEl) => {
3834
- this.maybeAddNewHook(hookEl);
4960
+ if (this.ownsElement(hookEl)) {
4961
+ this.maybeAddNewHook(hookEl);
4962
+ }
4963
+ });
4964
+ dom_default.all(this.el, `[${this.binding(PHX_MOUNTED)}]`, (el) => {
4965
+ if (this.ownsElement(el)) {
4966
+ this.maybeMounted(el);
4967
+ }
3835
4968
  });
3836
- dom_default.all(this.el, `[${this.binding(PHX_MOUNTED)}]`, (el) => this.maybeMounted(el));
3837
4969
  }
3838
- applyJoinPatch(live_patch, html, events) {
4970
+ applyJoinPatch(live_patch, html, streams, events) {
3839
4971
  this.attachTrueDocEl();
3840
- let patch = new DOMPatch(this, this.el, this.id, html, null);
4972
+ let patch = new DOMPatch(this, this.el, this.id, html, streams, null);
3841
4973
  patch.markPrunableContentForRemoval();
3842
- this.performPatch(patch, false);
4974
+ this.performPatch(patch, false, true);
3843
4975
  this.joinNewChildren();
3844
4976
  this.execNewMounted();
3845
4977
  this.joinPending = false;
@@ -3878,12 +5010,15 @@ within:
3878
5010
  newHook.__mounted();
3879
5011
  }
3880
5012
  }
3881
- performPatch(patch, pruneCids) {
5013
+ performPatch(patch, pruneCids, isJoinPatch = false) {
3882
5014
  let removedEls = [];
3883
5015
  let phxChildrenAdded = false;
3884
5016
  let updatedHookIds = /* @__PURE__ */ new Set();
3885
5017
  patch.after("added", (el) => {
3886
5018
  this.liveSocket.triggerDOM("onNodeAdded", [el]);
5019
+ let phxViewportTop = this.binding(PHX_VIEWPORT_TOP);
5020
+ let phxViewportBottom = this.binding(PHX_VIEWPORT_BOTTOM);
5021
+ dom_default.maybeAddPrivateHooks(el, phxViewportTop, phxViewportBottom);
3887
5022
  this.maybeAddNewHook(el);
3888
5023
  if (el.getAttribute) {
3889
5024
  this.maybeMounted(el);
@@ -3913,7 +5048,7 @@ within:
3913
5048
  }
3914
5049
  });
3915
5050
  patch.after("transitionsDiscarded", (els) => this.afterElementsRemoved(els, pruneCids));
3916
- patch.perform();
5051
+ patch.perform(isJoinPatch);
3917
5052
  this.afterElementsRemoved(removedEls, pruneCids);
3918
5053
  return phxChildrenAdded;
3919
5054
  }
@@ -3940,6 +5075,33 @@ within:
3940
5075
  joinNewChildren() {
3941
5076
  dom_default.findPhxChildren(this.el, this.id).forEach((el) => this.joinChild(el));
3942
5077
  }
5078
+ maybeRecoverForms(html, callback) {
5079
+ const phxChange = this.binding("change");
5080
+ const oldForms = this.root.formsForRecovery;
5081
+ let template = document.createElement("template");
5082
+ template.innerHTML = html;
5083
+ const rootEl = template.content.firstElementChild;
5084
+ rootEl.id = this.id;
5085
+ rootEl.setAttribute(PHX_ROOT_ID, this.root.id);
5086
+ rootEl.setAttribute(PHX_SESSION, this.getSession());
5087
+ rootEl.setAttribute(PHX_STATIC, this.getStatic());
5088
+ rootEl.setAttribute(PHX_PARENT_ID, this.parent ? this.parent.id : null);
5089
+ const formsToRecover = dom_default.all(template.content, "form").filter((newForm) => newForm.id && oldForms[newForm.id]).filter((newForm) => !this.pendingForms.has(newForm.id)).filter((newForm) => oldForms[newForm.id].getAttribute(phxChange) === newForm.getAttribute(phxChange)).map((newForm) => {
5090
+ return [oldForms[newForm.id], newForm];
5091
+ });
5092
+ if (formsToRecover.length === 0) {
5093
+ return callback();
5094
+ }
5095
+ formsToRecover.forEach(([oldForm, newForm], i) => {
5096
+ this.pendingForms.add(newForm.id);
5097
+ this.pushFormRecovery(oldForm, newForm, template.content, () => {
5098
+ this.pendingForms.delete(newForm.id);
5099
+ if (i === formsToRecover.length - 1) {
5100
+ callback();
5101
+ }
5102
+ });
5103
+ });
5104
+ }
3943
5105
  getChildById(id) {
3944
5106
  return this.root.children[this.id][id];
3945
5107
  }
@@ -3983,6 +5145,8 @@ within:
3983
5145
  }
3984
5146
  }
3985
5147
  onAllChildJoinsComplete() {
5148
+ this.pendingForms.clear();
5149
+ this.formsForRecovery = {};
3986
5150
  this.joinCallback(() => {
3987
5151
  this.pendingJoinOps.forEach(([view, op]) => {
3988
5152
  if (!view.isDestroyed()) {
@@ -4000,7 +5164,7 @@ within:
4000
5164
  let phxChildrenAdded = false;
4001
5165
  if (this.rendered.isComponentOnlyDiff(diff)) {
4002
5166
  this.liveSocket.time("component patch complete", () => {
4003
- let parentCids = dom_default.findParentCIDs(this.el, this.rendered.componentCIDs(diff));
5167
+ let parentCids = dom_default.findExistingParentCIDs(this.el, this.rendered.componentCIDs(diff));
4004
5168
  parentCids.forEach((parentCID) => {
4005
5169
  if (this.componentPatch(this.rendered.getComponent(diff, parentCID), parentCID)) {
4006
5170
  phxChildrenAdded = true;
@@ -4009,8 +5173,8 @@ within:
4009
5173
  });
4010
5174
  } else if (!isEmpty(diff)) {
4011
5175
  this.liveSocket.time("full patch complete", () => {
4012
- let html = this.renderContainer(diff, "update");
4013
- let patch = new DOMPatch(this, this.el, this.id, html, null);
5176
+ let [html, streams] = this.renderContainer(diff, "update");
5177
+ let patch = new DOMPatch(this, this.el, this.id, html, streams, null);
4014
5178
  phxChildrenAdded = this.performPatch(patch, true);
4015
5179
  });
4016
5180
  }
@@ -4022,16 +5186,16 @@ within:
4022
5186
  renderContainer(diff, kind) {
4023
5187
  return this.liveSocket.time(`toString diff (${kind})`, () => {
4024
5188
  let tag = this.el.tagName;
4025
- let cids = diff ? this.rendered.componentCIDs(diff).concat(this.pruningCIDs) : null;
4026
- let html = this.rendered.toString(cids);
4027
- return `<${tag}>${html}</${tag}>`;
5189
+ let cids = diff ? this.rendered.componentCIDs(diff) : null;
5190
+ let [html, streams] = this.rendered.toString(cids);
5191
+ return [`<${tag}>${html}</${tag}>`, streams];
4028
5192
  });
4029
5193
  }
4030
5194
  componentPatch(diff, cid) {
4031
5195
  if (isEmpty(diff))
4032
5196
  return false;
4033
- let html = this.rendered.componentToString(cid);
4034
- let patch = new DOMPatch(this, this.el, this.id, html, cid);
5197
+ let [html, streams] = this.rendered.componentToString(cid);
5198
+ let patch = new DOMPatch(this, this.el, this.id, html, streams, cid);
4035
5199
  let childrenAdded = this.performPatch(patch, true);
4036
5200
  return childrenAdded;
4037
5201
  }
@@ -4140,9 +5304,18 @@ within:
4140
5304
  });
4141
5305
  }
4142
5306
  onJoinError(resp) {
4143
- if (resp.reason === "unauthorized" || resp.reason === "stale") {
5307
+ if (resp.reason === "reload") {
5308
+ this.log("error", () => [`failed mount with ${resp.status}. Falling back to page request`, resp]);
5309
+ if (this.isMain()) {
5310
+ this.onRedirect({ to: this.href });
5311
+ }
5312
+ return;
5313
+ } else if (resp.reason === "unauthorized" || resp.reason === "stale") {
4144
5314
  this.log("error", () => ["unauthorized live_redirect. Falling back to page request", resp]);
4145
- return this.onRedirect({ to: this.href });
5315
+ if (this.isMain()) {
5316
+ this.onRedirect({ to: this.href });
5317
+ }
5318
+ return;
4146
5319
  }
4147
5320
  if (resp.redirect || resp.live_redirect) {
4148
5321
  this.joinPending = false;
@@ -4154,6 +5327,7 @@ within:
4154
5327
  if (resp.live_redirect) {
4155
5328
  return this.onLiveRedirect(resp.live_redirect);
4156
5329
  }
5330
+ this.displayError([PHX_LOADING_CLASS, PHX_ERROR_CLASS, PHX_SERVER_ERROR_CLASS]);
4157
5331
  this.log("error", () => ["unable to join", resp]);
4158
5332
  if (this.liveSocket.isConnected()) {
4159
5333
  this.liveSocket.reloadWithJitter(this);
@@ -4181,15 +5355,19 @@ within:
4181
5355
  this.log("error", () => ["view crashed", reason]);
4182
5356
  }
4183
5357
  if (!this.liveSocket.isUnloaded()) {
4184
- this.displayError();
5358
+ if (this.liveSocket.isConnected()) {
5359
+ this.displayError([PHX_LOADING_CLASS, PHX_ERROR_CLASS, PHX_SERVER_ERROR_CLASS]);
5360
+ } else {
5361
+ this.displayError([PHX_LOADING_CLASS, PHX_ERROR_CLASS, PHX_CLIENT_ERROR_CLASS]);
5362
+ }
4185
5363
  }
4186
5364
  }
4187
- displayError() {
5365
+ displayError(classes) {
4188
5366
  if (this.isMain()) {
4189
5367
  dom_default.dispatchEvent(window, "phx:page-loading-start", { detail: { to: this.href, kind: "error" } });
4190
5368
  }
4191
5369
  this.showLoader();
4192
- this.setContainerClasses(PHX_DISCONNECTED_CLASS, PHX_ERROR_CLASS);
5370
+ this.setContainerClasses(...classes);
4193
5371
  this.execAll(this.binding("disconnected"));
4194
5372
  }
4195
5373
  pushWithReply(refGenerator, event, payload, onReply = function() {
@@ -4218,35 +5396,44 @@ within:
4218
5396
  if (resp.live_redirect) {
4219
5397
  this.onLiveRedirect(resp.live_redirect);
4220
5398
  }
4221
- if (ref !== null) {
4222
- this.undoRefs(ref);
4223
- }
4224
5399
  onLoadingDone();
4225
5400
  onReply(resp, hookReply);
4226
5401
  };
4227
5402
  if (resp.diff) {
4228
5403
  this.liveSocket.requestDOMUpdate(() => {
4229
5404
  this.applyDiff("update", resp.diff, ({ diff, reply, events }) => {
5405
+ if (ref !== null) {
5406
+ this.undoRefs(ref);
5407
+ }
4230
5408
  this.update(diff, events);
4231
5409
  finish(reply);
4232
5410
  });
4233
5411
  });
4234
5412
  } else {
5413
+ if (ref !== null) {
5414
+ this.undoRefs(ref);
5415
+ }
4235
5416
  finish(null);
4236
5417
  }
4237
5418
  });
4238
5419
  });
4239
5420
  }
4240
- undoRefs(ref) {
5421
+ undoRefs(ref, onlyEls) {
5422
+ onlyEls = onlyEls ? new Set(onlyEls) : null;
4241
5423
  if (!this.isConnected()) {
4242
5424
  return;
4243
5425
  }
4244
5426
  dom_default.all(document, `[${PHX_REF_SRC}="${this.id}"][${PHX_REF}="${ref}"]`, (el) => {
5427
+ if (onlyEls && !onlyEls.has(el)) {
5428
+ return;
5429
+ }
5430
+ el.dispatchEvent(new CustomEvent("phx:unlock", { bubbles: true, cancelable: false }));
4245
5431
  let disabledVal = el.getAttribute(PHX_DISABLED);
5432
+ let readOnlyVal = el.getAttribute(PHX_READONLY);
4246
5433
  el.removeAttribute(PHX_REF);
4247
5434
  el.removeAttribute(PHX_REF_SRC);
4248
- if (el.getAttribute(PHX_READONLY) !== null) {
4249
- el.readOnly = false;
5435
+ if (readOnlyVal !== null) {
5436
+ el.readOnly = readOnlyVal === "true" ? true : false;
4250
5437
  el.removeAttribute(PHX_READONLY);
4251
5438
  }
4252
5439
  if (disabledVal !== null) {
@@ -4276,10 +5463,14 @@ within:
4276
5463
  if (opts.loading) {
4277
5464
  elements = elements.concat(dom_default.all(document, opts.loading));
4278
5465
  }
4279
- elements.forEach((el) => {
4280
- el.classList.add(`phx-${event}-loading`);
5466
+ for (let el of elements) {
4281
5467
  el.setAttribute(PHX_REF, newRef);
4282
5468
  el.setAttribute(PHX_REF_SRC, this.el.id);
5469
+ if (opts.submitter && !(el === opts.submitter || el === opts.form)) {
5470
+ continue;
5471
+ }
5472
+ el.classList.add(`phx-${event}-loading`);
5473
+ el.dispatchEvent(new CustomEvent(`phx:${event}-loading`, { bubbles: true, cancelable: false }));
4283
5474
  let disableText = el.getAttribute(disableWith);
4284
5475
  if (disableText !== null) {
4285
5476
  if (!el.getAttribute(PHX_DISABLE_WITH_RESTORE)) {
@@ -4288,9 +5479,10 @@ within:
4288
5479
  if (disableText !== "") {
4289
5480
  el.innerText = disableText;
4290
5481
  }
5482
+ el.setAttribute(PHX_DISABLED, el.getAttribute(PHX_DISABLED) || el.disabled);
4291
5483
  el.setAttribute("disabled", "");
4292
5484
  }
4293
- });
5485
+ }
4294
5486
  return [newRef, elements, opts];
4295
5487
  }
4296
5488
  componentID(el) {
@@ -4301,7 +5493,7 @@ within:
4301
5493
  if (isCid(targetCtx)) {
4302
5494
  return targetCtx;
4303
5495
  }
4304
- let cidOrSelector = target.getAttribute(this.binding("target"));
5496
+ let cidOrSelector = opts.target || target.getAttribute(this.binding("target"));
4305
5497
  if (isCid(cidOrSelector)) {
4306
5498
  return parseInt(cidOrSelector);
4307
5499
  } else if (targetCtx && (cidOrSelector !== null || opts.target)) {
@@ -4319,12 +5511,12 @@ within:
4319
5511
  return null;
4320
5512
  }
4321
5513
  }
4322
- pushHookEvent(targetCtx, event, payload, onReply) {
5514
+ pushHookEvent(el, targetCtx, event, payload, onReply) {
4323
5515
  if (!this.isConnected()) {
4324
5516
  this.log("hook", () => ["unable to push hook event. LiveView not connected", event, payload]);
4325
5517
  return false;
4326
5518
  }
4327
- let [ref, els, opts] = this.putRef([], "hook");
5519
+ let [ref, els, opts] = this.putRef([el], "hook");
4328
5520
  this.pushWithReply(() => [ref, els, opts], "event", {
4329
5521
  type: "hook",
4330
5522
  event,
@@ -4344,7 +5536,7 @@ within:
4344
5536
  meta[name.replace(prefix, "")] = el.getAttribute(name);
4345
5537
  }
4346
5538
  }
4347
- if (el.value !== void 0) {
5539
+ if (el.value !== void 0 && !(el instanceof HTMLFormElement)) {
4348
5540
  if (!meta) {
4349
5541
  meta = {};
4350
5542
  }
@@ -4363,13 +5555,13 @@ within:
4363
5555
  }
4364
5556
  return meta;
4365
5557
  }
4366
- pushEvent(type, el, targetCtx, phxEvent, meta, opts = {}) {
5558
+ pushEvent(type, el, targetCtx, phxEvent, meta, opts = {}, onReply) {
4367
5559
  this.pushWithReply(() => this.putRef([el], type, opts), "event", {
4368
5560
  type,
4369
5561
  event: phxEvent,
4370
5562
  value: this.extractMeta(el, meta, opts.value),
4371
5563
  cid: this.targetComponentID(el, targetCtx, opts)
4372
- });
5564
+ }, (resp, reply) => onReply && onReply(reply));
4373
5565
  }
4374
5566
  pushFileProgress(fileEl, entryRef, progress, onReply = function() {
4375
5567
  }) {
@@ -4385,13 +5577,17 @@ within:
4385
5577
  }
4386
5578
  pushInput(inputEl, targetCtx, forceCid, phxEvent, opts, callback) {
4387
5579
  let uploads;
4388
- let cid = isCid(forceCid) ? forceCid : this.targetComponentID(inputEl.form, targetCtx);
5580
+ let cid = isCid(forceCid) ? forceCid : this.targetComponentID(inputEl.form, targetCtx, opts);
4389
5581
  let refGenerator = () => this.putRef([inputEl, inputEl.form], "change", opts);
4390
5582
  let formData;
5583
+ let meta = this.extractMeta(inputEl.form);
5584
+ if (inputEl instanceof HTMLButtonElement) {
5585
+ meta.submitter = inputEl;
5586
+ }
4391
5587
  if (inputEl.getAttribute(this.binding("change"))) {
4392
- formData = serializeForm(inputEl.form, { _target: opts._target }, [inputEl.name]);
5588
+ formData = serializeForm(inputEl.form, __spreadValues({ _target: opts._target }, meta), [inputEl.name]);
4393
5589
  } else {
4394
- formData = serializeForm(inputEl.form, { _target: opts._target });
5590
+ formData = serializeForm(inputEl.form, __spreadValues({ _target: opts._target }, meta));
4395
5591
  }
4396
5592
  if (dom_default.isUploadInput(inputEl) && inputEl.files && inputEl.files.length > 0) {
4397
5593
  LiveUploader.trackFiles(inputEl, Array.from(inputEl.files));
@@ -4405,13 +5601,15 @@ within:
4405
5601
  cid
4406
5602
  };
4407
5603
  this.pushWithReply(refGenerator, "event", event, (resp) => {
4408
- dom_default.showError(inputEl, this.liveSocket.binding(PHX_FEEDBACK_FOR));
4409
- if (dom_default.isUploadInput(inputEl) && inputEl.getAttribute("data-phx-auto-upload") !== null) {
5604
+ dom_default.showError(inputEl, this.liveSocket.binding(PHX_FEEDBACK_FOR), this.liveSocket.binding(PHX_FEEDBACK_GROUP));
5605
+ if (dom_default.isUploadInput(inputEl) && dom_default.isAutoUpload(inputEl)) {
4410
5606
  if (LiveUploader.filesAwaitingPreflight(inputEl).length > 0) {
4411
5607
  let [ref, _els] = refGenerator();
5608
+ this.undoRefs(ref, [inputEl.form]);
4412
5609
  this.uploadFiles(inputEl.form, targetCtx, ref, cid, (_uploads) => {
4413
5610
  callback && callback(resp);
4414
5611
  this.triggerAwaitingSubmit(inputEl.form);
5612
+ this.undoRefs(ref);
4415
5613
  });
4416
5614
  }
4417
5615
  } else {
@@ -4475,18 +5673,22 @@ within:
4475
5673
  formEl.setAttribute(this.binding(PHX_PAGE_LOADING), "");
4476
5674
  return this.putRef([formEl].concat(disables).concat(buttons).concat(inputs), "submit", opts);
4477
5675
  }
4478
- pushFormSubmit(formEl, targetCtx, phxEvent, opts, onReply) {
4479
- let refGenerator = () => this.disableForm(formEl, opts);
5676
+ pushFormSubmit(formEl, targetCtx, phxEvent, submitter, opts, onReply) {
5677
+ let refGenerator = () => this.disableForm(formEl, __spreadProps(__spreadValues({}, opts), { form: formEl, submitter }));
4480
5678
  let cid = this.targetComponentID(formEl, targetCtx);
4481
5679
  if (LiveUploader.hasUploadsInProgress(formEl)) {
4482
5680
  let [ref, _els] = refGenerator();
4483
- let push = () => this.pushFormSubmit(formEl, targetCtx, phxEvent, opts, onReply);
5681
+ let push = () => this.pushFormSubmit(formEl, targetCtx, phxEvent, submitter, opts, onReply);
4484
5682
  return this.scheduleSubmit(formEl, ref, opts, push);
4485
5683
  } else if (LiveUploader.inputsAwaitingPreflight(formEl).length > 0) {
4486
5684
  let [ref, els] = refGenerator();
4487
5685
  let proxyRefGen = () => [ref, els, opts];
4488
- this.uploadFiles(formEl, targetCtx, ref, cid, (_uploads) => {
4489
- let formData = serializeForm(formEl, {});
5686
+ this.uploadFiles(formEl, targetCtx, ref, cid, (uploads) => {
5687
+ if (LiveUploader.inputsAwaitingPreflight(formEl).length > 0) {
5688
+ return this.undoRefs(ref);
5689
+ }
5690
+ let meta = this.extractMeta(formEl);
5691
+ let formData = serializeForm(formEl, __spreadValues({ submitter }, meta));
4490
5692
  this.pushWithReply(proxyRefGen, "event", {
4491
5693
  type: "form",
4492
5694
  event: phxEvent,
@@ -4494,8 +5696,9 @@ within:
4494
5696
  cid
4495
5697
  }, onReply);
4496
5698
  });
4497
- } else {
4498
- let formData = serializeForm(formEl, {});
5699
+ } else if (!(formEl.hasAttribute(PHX_REF) && formEl.classList.contains("phx-submit-loading"))) {
5700
+ let meta = this.extractMeta(formEl);
5701
+ let formData = serializeForm(formEl, __spreadValues({ submitter }, meta));
4499
5702
  this.pushWithReply(refGenerator, "event", {
4500
5703
  type: "form",
4501
5704
  event: phxEvent,
@@ -4515,8 +5718,11 @@ within:
4515
5718
  onComplete();
4516
5719
  }
4517
5720
  });
4518
- this.uploaders[inputEl] = uploader;
4519
5721
  let entries = uploader.entries().map((entry) => entry.toPreflightPayload());
5722
+ if (entries.length === 0) {
5723
+ numFileInputsInProgress--;
5724
+ return;
5725
+ }
4520
5726
  let payload = {
4521
5727
  ref: inputEl.getAttribute(PHX_UPLOAD_REF),
4522
5728
  entries,
@@ -4525,10 +5731,17 @@ within:
4525
5731
  this.log("upload", () => ["sending preflight request", payload]);
4526
5732
  this.pushWithReply(null, "allow_upload", payload, (resp) => {
4527
5733
  this.log("upload", () => ["got preflight response", resp]);
4528
- if (resp.error) {
5734
+ uploader.entries().forEach((entry) => {
5735
+ if (resp.entries && !resp.entries[entry.ref]) {
5736
+ this.handleFailedEntryPreflight(entry.ref, "failed preflight", uploader);
5737
+ }
5738
+ });
5739
+ if (resp.error || Object.keys(resp.entries).length === 0) {
4529
5740
  this.undoRefs(ref);
4530
- let [entry_ref, reason] = resp.error;
4531
- this.log("upload", () => [`error for entry ${entry_ref}`, reason]);
5741
+ let errors = resp.error || [];
5742
+ errors.map(([entry_ref, reason]) => {
5743
+ this.handleFailedEntryPreflight(entry_ref, reason, uploader);
5744
+ });
4532
5745
  } else {
4533
5746
  let onError = (callback) => {
4534
5747
  this.channel.onError(() => {
@@ -4542,8 +5755,20 @@ within:
4542
5755
  });
4543
5756
  });
4544
5757
  }
4545
- dispatchUploads(name, filesOrBlobs) {
4546
- let inputs = dom_default.findUploadInputs(this.el).filter((el) => el.name === name);
5758
+ handleFailedEntryPreflight(uploadRef, reason, uploader) {
5759
+ if (uploader.isAutoUpload()) {
5760
+ let entry = uploader.entries().find((entry2) => entry2.ref === uploadRef.toString());
5761
+ if (entry) {
5762
+ entry.cancel();
5763
+ }
5764
+ } else {
5765
+ uploader.entries().map((entry) => entry.cancel());
5766
+ }
5767
+ this.log("upload", () => [`error for entry ${uploadRef}`, reason]);
5768
+ }
5769
+ dispatchUploads(targetCtx, name, filesOrBlobs) {
5770
+ let targetElement = this.targetCtxElement(targetCtx) || this.el;
5771
+ let inputs = dom_default.findUploadInputs(targetElement).filter((el) => el.name === name);
4547
5772
  if (inputs.length === 0) {
4548
5773
  logError(`no live file inputs found matching the name "${name}"`);
4549
5774
  } else if (inputs.length > 1) {
@@ -4552,18 +5777,44 @@ within:
4552
5777
  dom_default.dispatchEvent(inputs[0], PHX_TRACK_UPLOADS, { detail: { files: filesOrBlobs } });
4553
5778
  }
4554
5779
  }
4555
- pushFormRecovery(form, newCid, callback) {
4556
- this.liveSocket.withinOwners(form, (view, targetCtx) => {
4557
- let input = form.elements[0];
4558
- let phxEvent = form.getAttribute(this.binding(PHX_AUTO_RECOVER)) || form.getAttribute(this.binding("change"));
4559
- js_default.exec("change", phxEvent, view, input, ["push", { _target: input.name, newCid, callback }]);
4560
- });
5780
+ targetCtxElement(targetCtx) {
5781
+ if (isCid(targetCtx)) {
5782
+ let [target] = dom_default.findComponentNodeList(this.el, targetCtx);
5783
+ return target;
5784
+ } else if (targetCtx) {
5785
+ return targetCtx;
5786
+ } else {
5787
+ return null;
5788
+ }
5789
+ }
5790
+ pushFormRecovery(oldForm, newForm, templateDom, callback) {
5791
+ const phxChange = this.binding("change");
5792
+ const phxTarget = newForm.getAttribute(this.binding("target")) || newForm;
5793
+ const phxEvent = newForm.getAttribute(this.binding(PHX_AUTO_RECOVER)) || newForm.getAttribute(this.binding("change"));
5794
+ const inputs = Array.from(oldForm.elements).filter((el) => dom_default.isFormInput(el) && el.name && !el.hasAttribute(phxChange));
5795
+ if (inputs.length === 0) {
5796
+ return;
5797
+ }
5798
+ inputs.forEach((input2) => input2.hasAttribute(PHX_UPLOAD_REF) && LiveUploader.clearFiles(input2));
5799
+ let input = inputs.find((el) => el.type !== "hidden") || inputs[0];
5800
+ let pending = 0;
5801
+ this.withinTargets(phxTarget, (targetView, targetCtx) => {
5802
+ const cid = this.targetComponentID(newForm, targetCtx);
5803
+ pending++;
5804
+ targetView.pushInput(input, targetCtx, cid, phxEvent, { _target: input.name }, () => {
5805
+ pending--;
5806
+ if (pending === 0) {
5807
+ callback();
5808
+ }
5809
+ });
5810
+ }, templateDom, templateDom);
4561
5811
  }
4562
5812
  pushLinkPatch(href, targetEl, callback) {
4563
5813
  let linkRef = this.liveSocket.setPendingLink(href);
4564
5814
  let refGen = targetEl ? () => this.putRef([targetEl], "click") : null;
4565
5815
  let fallback = () => this.liveSocket.redirect(window.location.href);
4566
- let push = this.pushWithReply(refGen, "live_patch", { url: href }, (resp) => {
5816
+ let url = href.startsWith("/") ? `${location.protocol}//${location.host}${href}` : href;
5817
+ let push = this.pushWithReply(refGen, "live_patch", { url }, (resp) => {
4567
5818
  this.liveSocket.requestDOMUpdate(() => {
4568
5819
  if (resp.link_redirect) {
4569
5820
  this.liveSocket.replaceMain(href, null, callback, linkRef);
@@ -4582,38 +5833,33 @@ within:
4582
5833
  fallback();
4583
5834
  }
4584
5835
  }
4585
- formsForRecovery(html) {
5836
+ getFormsForRecovery() {
4586
5837
  if (this.joinCount === 0) {
4587
- return [];
5838
+ return {};
4588
5839
  }
4589
5840
  let phxChange = this.binding("change");
4590
- let template = document.createElement("template");
4591
- template.innerHTML = html;
4592
- return dom_default.all(this.el, `form[${phxChange}]`).filter((form) => form.id && this.ownsElement(form)).filter((form) => form.elements.length > 0).filter((form) => form.getAttribute(this.binding(PHX_AUTO_RECOVER)) !== "ignore").map((form) => {
4593
- let newForm = template.content.querySelector(`form[id="${form.id}"][${phxChange}="${form.getAttribute(phxChange)}"]`);
4594
- if (newForm) {
4595
- return [form, newForm, this.targetComponentID(newForm)];
4596
- } else {
4597
- return [form, null, null];
4598
- }
4599
- }).filter(([form, newForm, newCid]) => newForm);
5841
+ return dom_default.all(this.el, `form[${phxChange}]`).filter((form) => form.id).filter((form) => form.elements.length > 0).filter((form) => form.getAttribute(this.binding(PHX_AUTO_RECOVER)) !== "ignore").map((form) => form.cloneNode(true)).reduce((acc, form) => {
5842
+ acc[form.id] = form;
5843
+ return acc;
5844
+ }, {});
4600
5845
  }
4601
5846
  maybePushComponentsDestroyed(destroyedCIDs) {
4602
5847
  let willDestroyCIDs = destroyedCIDs.filter((cid) => {
4603
5848
  return dom_default.findComponentNodeList(this.el, cid).length === 0;
4604
5849
  });
4605
5850
  if (willDestroyCIDs.length > 0) {
4606
- this.pruningCIDs.push(...willDestroyCIDs);
5851
+ willDestroyCIDs.forEach((cid) => this.rendered.resetRender(cid));
4607
5852
  this.pushWithReply(null, "cids_will_destroy", { cids: willDestroyCIDs }, () => {
4608
- this.pruningCIDs = this.pruningCIDs.filter((cid) => willDestroyCIDs.indexOf(cid) !== -1);
4609
- let completelyDestroyCIDs = willDestroyCIDs.filter((cid) => {
4610
- return dom_default.findComponentNodeList(this.el, cid).length === 0;
4611
- });
4612
- if (completelyDestroyCIDs.length > 0) {
4613
- this.pushWithReply(null, "cids_destroyed", { cids: completelyDestroyCIDs }, (resp) => {
4614
- this.rendered.pruneCIDs(resp.cids);
5853
+ this.liveSocket.requestDOMUpdate(() => {
5854
+ let completelyDestroyCIDs = willDestroyCIDs.filter((cid) => {
5855
+ return dom_default.findComponentNodeList(this.el, cid).length === 0;
4615
5856
  });
4616
- }
5857
+ if (completelyDestroyCIDs.length > 0) {
5858
+ this.pushWithReply(null, "cids_destroyed", { cids: completelyDestroyCIDs }, (resp) => {
5859
+ this.rendered.pruneCIDs(resp.cids);
5860
+ });
5861
+ }
5862
+ });
4617
5863
  });
4618
5864
  }
4619
5865
  }
@@ -4621,14 +5867,15 @@ within:
4621
5867
  let parentViewEl = el.closest(PHX_VIEW_SELECTOR);
4622
5868
  return el.getAttribute(PHX_PARENT_ID) === this.id || parentViewEl && parentViewEl.id === this.id || !parentViewEl && this.isDead;
4623
5869
  }
4624
- submitForm(form, targetCtx, phxEvent, opts = {}) {
5870
+ submitForm(form, targetCtx, phxEvent, submitter, opts = {}) {
4625
5871
  dom_default.putPrivate(form, PHX_HAS_SUBMITTED, true);
4626
- let phxFeedback = this.liveSocket.binding(PHX_FEEDBACK_FOR);
4627
- let inputs = Array.from(form.elements);
5872
+ const phxFeedbackFor = this.liveSocket.binding(PHX_FEEDBACK_FOR);
5873
+ const phxFeedbackGroup = this.liveSocket.binding(PHX_FEEDBACK_GROUP);
5874
+ const inputs = Array.from(form.elements);
4628
5875
  inputs.forEach((input) => dom_default.putPrivate(input, PHX_HAS_SUBMITTED, true));
4629
5876
  this.liveSocket.blurActiveElement(this);
4630
- this.pushFormSubmit(form, targetCtx, phxEvent, opts, () => {
4631
- inputs.forEach((input) => dom_default.showError(input, phxFeedback));
5877
+ this.pushFormSubmit(form, targetCtx, phxEvent, submitter, opts, () => {
5878
+ inputs.forEach((input) => dom_default.showError(input, phxFeedbackFor, phxFeedbackGroup));
4632
5879
  this.liveSocket.restorePreviouslyActiveFocus();
4633
5880
  });
4634
5881
  }
@@ -4677,7 +5924,13 @@ within:
4677
5924
  this.localStorage = opts.localStorage || window.localStorage;
4678
5925
  this.sessionStorage = opts.sessionStorage || window.sessionStorage;
4679
5926
  this.boundTopLevelEvents = false;
4680
- this.domCallbacks = Object.assign({ onNodeAdded: closure2(), onBeforeElUpdated: closure2() }, opts.dom || {});
5927
+ this.serverCloseRef = null;
5928
+ this.domCallbacks = Object.assign({
5929
+ onPatchStart: closure2(),
5930
+ onPatchEnd: closure2(),
5931
+ onNodeAdded: closure2(),
5932
+ onBeforeElUpdated: closure2()
5933
+ }, opts.dom || {});
4681
5934
  this.transitions = new TransitionSet();
4682
5935
  window.addEventListener("pagehide", (_e) => {
4683
5936
  this.unloaded = true;
@@ -4688,6 +5941,9 @@ within:
4688
5941
  }
4689
5942
  });
4690
5943
  }
5944
+ version() {
5945
+ return "0.20.17";
5946
+ }
4691
5947
  isProfileEnabled() {
4692
5948
  return this.sessionStorage.getItem(PHX_LV_PROFILE) === "true";
4693
5949
  }
@@ -4747,6 +6003,10 @@ within:
4747
6003
  }
4748
6004
  disconnect(callback) {
4749
6005
  clearTimeout(this.reloadWithJitterTimer);
6006
+ if (this.serverCloseRef) {
6007
+ this.socket.off(this.serverCloseRef);
6008
+ this.serverCloseRef = null;
6009
+ }
4750
6010
  this.socket.disconnect(callback);
4751
6011
  }
4752
6012
  replaceTransport(transport) {
@@ -4757,6 +6017,11 @@ within:
4757
6017
  execJS(el, encodedJS, eventType = null) {
4758
6018
  this.owner(el, (view) => js_default.exec(eventType, encodedJS, view, el));
4759
6019
  }
6020
+ execJSHookPush(el, phxEvent, data, callback) {
6021
+ this.withinOwners(el, (view) => {
6022
+ js_default.exec("hook", phxEvent, view, el, ["push", { data, callback }]);
6023
+ });
6024
+ }
4760
6025
  unload() {
4761
6026
  if (this.unloaded) {
4762
6027
  return;
@@ -4908,7 +6173,7 @@ within:
4908
6173
  return rootsFound;
4909
6174
  }
4910
6175
  redirect(to, flash) {
4911
- this.disconnect();
6176
+ this.unload();
4912
6177
  browser_default.redirect(to, flash);
4913
6178
  }
4914
6179
  replaceMain(href, flash, callback = null, linkRef = this.setPendingLink(href)) {
@@ -4919,26 +6184,28 @@ within:
4919
6184
  this.main.destroy();
4920
6185
  this.main = this.newRootView(newMainEl, flash, liveReferer);
4921
6186
  this.main.setRedirect(href);
4922
- this.transitionRemoves();
6187
+ this.transitionRemoves(null, true);
4923
6188
  this.main.join((joinCount, onDone) => {
4924
6189
  if (joinCount === 1 && this.commitPendingLink(linkRef)) {
4925
6190
  this.requestDOMUpdate(() => {
4926
6191
  dom_default.findPhxSticky(document).forEach((el) => newMainEl.appendChild(el));
4927
6192
  this.outgoingMainEl.replaceWith(newMainEl);
4928
6193
  this.outgoingMainEl = null;
4929
- callback && requestAnimationFrame(callback);
6194
+ callback && callback(linkRef);
4930
6195
  onDone();
4931
6196
  });
4932
6197
  }
4933
6198
  });
4934
6199
  }
4935
- transitionRemoves(elements) {
6200
+ transitionRemoves(elements, skipSticky) {
4936
6201
  let removeAttr = this.binding("remove");
4937
6202
  elements = elements || dom_default.all(document, `[${removeAttr}]`);
6203
+ if (skipSticky) {
6204
+ const stickies = dom_default.findPhxSticky(document) || [];
6205
+ elements = elements.filter((el) => !dom_default.isChildOfAny(el, stickies));
6206
+ }
4938
6207
  elements.forEach((el) => {
4939
- if (document.body.contains(el)) {
4940
- this.execJS(el, el.getAttribute(removeAttr), "remove");
4941
- }
6208
+ this.execJS(el, el.getAttribute(removeAttr), "remove");
4942
6209
  });
4943
6210
  }
4944
6211
  isPhxView(el) {
@@ -5024,10 +6291,7 @@ within:
5024
6291
  return;
5025
6292
  }
5026
6293
  this.boundTopLevelEvents = true;
5027
- this.socket.onClose((event) => {
5028
- if (event && event.code === 1001) {
5029
- return this.unload();
5030
- }
6294
+ this.serverCloseRef = this.socket.onClose((event) => {
5031
6295
  if (event && event.code === 1e3 && this.main) {
5032
6296
  return this.reloadWithJitter(this.main);
5033
6297
  }
@@ -5048,7 +6312,7 @@ within:
5048
6312
  if (!dead) {
5049
6313
  this.bindForms();
5050
6314
  }
5051
- this.bind({ keyup: "keyup", keydown: "keydown" }, (e, type, view, targetEl, phxEvent, eventTarget) => {
6315
+ this.bind({ keyup: "keyup", keydown: "keydown" }, (e, type, view, targetEl, phxEvent, phxTarget) => {
5052
6316
  let matchKey = targetEl.getAttribute(this.binding(PHX_KEY));
5053
6317
  let pressedKey = e.key && e.key.toLowerCase();
5054
6318
  if (matchKey && matchKey.toLowerCase() !== pressedKey) {
@@ -5057,13 +6321,13 @@ within:
5057
6321
  let data = __spreadValues({ key: e.key }, this.eventMeta(type, e, targetEl));
5058
6322
  js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
5059
6323
  });
5060
- this.bind({ blur: "focusout", focus: "focusin" }, (e, type, view, targetEl, phxEvent, eventTarget) => {
5061
- if (!eventTarget) {
6324
+ this.bind({ blur: "focusout", focus: "focusin" }, (e, type, view, targetEl, phxEvent, phxTarget) => {
6325
+ if (!phxTarget) {
5062
6326
  let data = __spreadValues({ key: e.key }, this.eventMeta(type, e, targetEl));
5063
6327
  js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
5064
6328
  }
5065
6329
  });
5066
- this.bind({ blur: "blur", focus: "focus" }, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {
6330
+ this.bind({ blur: "blur", focus: "focus" }, (e, type, view, targetEl, phxEvent, phxTarget) => {
5067
6331
  if (phxTarget === "window") {
5068
6332
  let data = this.eventMeta(type, e, targetEl);
5069
6333
  js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
@@ -5080,7 +6344,7 @@ within:
5080
6344
  if (!dropTarget || dropTarget.disabled || files.length === 0 || !(dropTarget.files instanceof FileList)) {
5081
6345
  return;
5082
6346
  }
5083
- LiveUploader.trackFiles(dropTarget, files);
6347
+ LiveUploader.trackFiles(dropTarget, files, e.dataTransfer);
5084
6348
  dropTarget.dispatchEvent(new Event("input", { bubbles: true }));
5085
6349
  });
5086
6350
  this.on(PHX_TRACK_UPLOADS, (e) => {
@@ -5144,26 +6408,22 @@ within:
5144
6408
  }
5145
6409
  }
5146
6410
  bindClicks() {
5147
- window.addEventListener("click", (e) => this.clickStartedAtTarget = e.target);
5148
- this.bindClick("click", "click", false);
5149
- this.bindClick("mousedown", "capture-click", true);
6411
+ window.addEventListener("mousedown", (e) => this.clickStartedAtTarget = e.target);
6412
+ this.bindClick("click", "click");
5150
6413
  }
5151
- bindClick(eventName, bindingName, capture) {
6414
+ bindClick(eventName, bindingName) {
5152
6415
  let click = this.binding(bindingName);
5153
6416
  window.addEventListener(eventName, (e) => {
5154
6417
  let target = null;
5155
- if (capture) {
5156
- target = e.target.matches(`[${click}]`) ? e.target : e.target.querySelector(`[${click}]`);
5157
- } else {
5158
- let clickStartedAtTarget = this.clickStartedAtTarget || e.target;
5159
- target = closestPhxBinding(clickStartedAtTarget, click);
5160
- this.dispatchClickAway(e, clickStartedAtTarget);
5161
- this.clickStartedAtTarget = null;
5162
- }
6418
+ if (e.detail === 0)
6419
+ this.clickStartedAtTarget = e.target;
6420
+ let clickStartedAtTarget = this.clickStartedAtTarget || e.target;
6421
+ target = closestPhxBinding(clickStartedAtTarget, click);
6422
+ this.dispatchClickAway(e, clickStartedAtTarget);
6423
+ this.clickStartedAtTarget = null;
5163
6424
  let phxEvent = target && target.getAttribute(click);
5164
6425
  if (!phxEvent) {
5165
- let href = e.target instanceof HTMLAnchorElement ? e.target.getAttribute("href") : null;
5166
- if (!capture && href !== null && !dom_default.wantsNewTab(e) && dom_default.isNewPageHref(href, window.location)) {
6426
+ if (dom_default.isNewPageClick(e, window.location)) {
5167
6427
  this.unload();
5168
6428
  }
5169
6429
  return;
@@ -5171,20 +6431,23 @@ within:
5171
6431
  if (target.getAttribute("href") === "#") {
5172
6432
  e.preventDefault();
5173
6433
  }
6434
+ if (target.hasAttribute(PHX_REF)) {
6435
+ return;
6436
+ }
5174
6437
  this.debounce(target, e, "click", () => {
5175
6438
  this.withinOwners(target, (view) => {
5176
6439
  js_default.exec("click", phxEvent, view, target, ["push", { data: this.eventMeta("click", e, target) }]);
5177
6440
  });
5178
6441
  });
5179
- }, capture);
6442
+ }, false);
5180
6443
  }
5181
6444
  dispatchClickAway(e, clickStartedAt) {
5182
6445
  let phxClickAway = this.binding("click-away");
5183
6446
  dom_default.all(document, `[${phxClickAway}]`, (el) => {
5184
6447
  if (!(el.isSameNode(clickStartedAt) || el.contains(clickStartedAt))) {
5185
- this.withinOwners(e.target, (view) => {
6448
+ this.withinOwners(el, (view) => {
5186
6449
  let phxEvent = el.getAttribute(phxClickAway);
5187
- if (js_default.isVisible(el)) {
6450
+ if (js_default.isVisible(el) && js_default.isInViewport(el)) {
5188
6451
  js_default.exec("click", phxEvent, view, el, ["push", { data: this.eventMeta("click", e, e.target) }]);
5189
6452
  }
5190
6453
  });
@@ -5211,6 +6474,7 @@ within:
5211
6474
  }
5212
6475
  let { type, id, root, scroll } = event.state || {};
5213
6476
  let href = window.location.href;
6477
+ dom_default.dispatchEvent(window, "phx:navigate", { detail: { href, patch: type === "patch", pop: true } });
5214
6478
  this.requestDOMUpdate(() => {
5215
6479
  if (this.main.isConnected() && (type === "patch" && id === this.main.id)) {
5216
6480
  this.main.pushLinkPatch(href, null, () => {
@@ -5232,7 +6496,7 @@ within:
5232
6496
  if (!type || !this.isConnected() || !this.main || dom_default.wantsNewTab(e)) {
5233
6497
  return;
5234
6498
  }
5235
- let href = target.href;
6499
+ let href = target.href instanceof SVGAnimatedString ? target.href.baseVal : target.href;
5236
6500
  let linkState = target.getAttribute(PHX_LINK_STATE);
5237
6501
  e.preventDefault();
5238
6502
  e.stopImmediatePropagation();
@@ -5273,7 +6537,7 @@ within:
5273
6537
  return callback ? callback(done) : done;
5274
6538
  }
5275
6539
  pushHistoryPatch(href, linkState, targetEl) {
5276
- if (!this.isConnected()) {
6540
+ if (!this.isConnected() || !this.main.isMain()) {
5277
6541
  return browser_default.redirect(href);
5278
6542
  }
5279
6543
  this.withPageLoading({ to: href, kind: "patch" }, (done) => {
@@ -5288,10 +6552,11 @@ within:
5288
6552
  return;
5289
6553
  }
5290
6554
  browser_default.pushState(linkState, { type: "patch", id: this.main.id }, href);
6555
+ dom_default.dispatchEvent(window, "phx:navigate", { detail: { patch: true, href, pop: false } });
5291
6556
  this.registerNewLocation(window.location);
5292
6557
  }
5293
6558
  historyRedirect(href, linkState, flash) {
5294
- if (!this.isConnected()) {
6559
+ if (!this.isConnected() || !this.main.isMain()) {
5295
6560
  return browser_default.redirect(href, flash);
5296
6561
  }
5297
6562
  if (/^\/$|^\/[^\/]+.*$/.test(href)) {
@@ -5300,9 +6565,12 @@ within:
5300
6565
  }
5301
6566
  let scroll = window.scrollY;
5302
6567
  this.withPageLoading({ to: href, kind: "redirect" }, (done) => {
5303
- this.replaceMain(href, flash, () => {
5304
- browser_default.pushState(linkState, { type: "redirect", id: this.main.id, scroll }, href);
5305
- this.registerNewLocation(window.location);
6568
+ this.replaceMain(href, flash, (linkRef) => {
6569
+ if (linkRef === this.linkRef) {
6570
+ browser_default.pushState(linkState, { type: "redirect", id: this.main.id, scroll }, href);
6571
+ dom_default.dispatchEvent(window, "phx:navigate", { detail: { href, patch: false, pop: false } });
6572
+ this.registerNewLocation(window.location);
6573
+ }
5306
6574
  done();
5307
6575
  });
5308
6576
  });
@@ -5350,7 +6618,7 @@ within:
5350
6618
  e.preventDefault();
5351
6619
  e.target.disabled = true;
5352
6620
  this.withinOwners(e.target, (view) => {
5353
- js_default.exec("submit", phxEvent, view, e.target, ["push", {}]);
6621
+ js_default.exec("submit", phxEvent, view, e.target, ["push", { submitter: e.submitter }]);
5354
6622
  });
5355
6623
  }, false);
5356
6624
  for (let type of ["change", "input"]) {
@@ -5370,7 +6638,7 @@ within:
5370
6638
  let currentIterations = iterations;
5371
6639
  iterations++;
5372
6640
  let { at, type: lastType } = dom_default.private(input, "prev-iteration") || {};
5373
- if (at === currentIterations - 1 && type !== lastType) {
6641
+ if (at === currentIterations - 1 && type === "change" && lastType === "input") {
5374
6642
  return;
5375
6643
  }
5376
6644
  dom_default.putPrivate(input, "prev-iteration", { at: currentIterations, type });
@@ -5385,6 +6653,16 @@ within:
5385
6653
  });
5386
6654
  }, false);
5387
6655
  }
6656
+ this.on("reset", (e) => {
6657
+ let form = e.target;
6658
+ dom_default.resetForm(form, this.binding(PHX_FEEDBACK_FOR), this.binding(PHX_FEEDBACK_GROUP));
6659
+ let input = Array.from(form.elements).find((el) => el.type === "reset");
6660
+ if (input) {
6661
+ window.requestAnimationFrame(() => {
6662
+ input.dispatchEvent(new Event("input", { bubbles: true, cancelable: false }));
6663
+ });
6664
+ }
6665
+ });
5388
6666
  }
5389
6667
  debounce(el, event, eventType, callback) {
5390
6668
  if (eventType === "blur" || eventType === "focusout") {
@@ -5418,7 +6696,6 @@ within:
5418
6696
  constructor() {
5419
6697
  this.transitions = /* @__PURE__ */ new Set();
5420
6698
  this.pendingOps = [];
5421
- this.reset();
5422
6699
  }
5423
6700
  reset() {
5424
6701
  this.transitions.forEach((timer) => {
@@ -5439,9 +6716,7 @@ within:
5439
6716
  let timer = setTimeout(() => {
5440
6717
  this.transitions.delete(timer);
5441
6718
  onDone();
5442
- if (this.size() === 0) {
5443
- this.flushPendingOps();
5444
- }
6719
+ this.flushPendingOps();
5445
6720
  }, time);
5446
6721
  this.transitions.add(timer);
5447
6722
  }
@@ -5452,20 +6727,26 @@ within:
5452
6727
  return this.transitions.size;
5453
6728
  }
5454
6729
  flushPendingOps() {
5455
- this.pendingOps.forEach((op) => op());
5456
- this.pendingOps = [];
6730
+ if (this.size() > 0) {
6731
+ return;
6732
+ }
6733
+ let op = this.pendingOps.shift();
6734
+ if (op) {
6735
+ op();
6736
+ this.flushPendingOps();
6737
+ }
5457
6738
  }
5458
6739
  };
5459
6740
 
5460
- // assets/js/app.js
6741
+ // js/app.js
5461
6742
  var import_nprogress = __toESM(require_nprogress());
5462
6743
  var _a;
5463
6744
  var Hooks2 = (_a = window.Hooks) != null ? _a : {};
5464
6745
  var scrollAt = () => {
5465
- let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
6746
+ let scrollTop2 = document.documentElement.scrollTop || document.body.scrollTop;
5466
6747
  let scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
5467
6748
  let clientHeight = document.documentElement.clientHeight;
5468
- return scrollTop / (scrollHeight - clientHeight) * 100;
6749
+ return scrollTop2 / (scrollHeight - clientHeight) * 100;
5469
6750
  };
5470
6751
  Hooks2.InfiniteScroll = {
5471
6752
  page() {
@@ -5487,7 +6768,8 @@ within:
5487
6768
  var csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");
5488
6769
  var liveSocket = new LiveSocket("/live", Socket, {
5489
6770
  hooks: Hooks2,
5490
- params: { _csrf_token: csrfToken }
6771
+ params: { _csrf_token: csrfToken },
6772
+ uploaders: window.Uploaders || {}
5491
6773
  });
5492
6774
  window.addEventListener("phx:page-loading-start", (info) => import_nprogress.default.start());
5493
6775
  window.addEventListener("phx:page-loading-stop", (info) => import_nprogress.default.done());