katalyst-kpop 3.0.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ba034915f355f7aa8bb751cedd8f78ddb2a62254e3dce4fb39763526bc8c502
4
- data.tar.gz: 3b9423e5dea3b54b612d37350db344eaf515f8f856af93395c7665ed66e939e8
3
+ metadata.gz: 03aad19fa0124683dcce293a98a6250ea4c1e210bafd68d67a9b3c4cfd66fc61
4
+ data.tar.gz: 1e5478cf9f7b29b96c0f53c70706c82588e12b15201346bbcdee532472d0c7c4
5
5
  SHA512:
6
- metadata.gz: e738568e4e3b69bd18e35301cbf67b0cbc56887dd6042e5e99879662321df1bad85dbbd64b4fe2e9bc7eb062333f57cd6a94d0575dd8b820525e5a2d20104639
7
- data.tar.gz: eb02e83b46938f174c428ec63aa77d23e6f3cb68d05dd773a20a3ac1380d5ed819434fdb26424088bbe75c1c78cbf34fa58498b39cc1bc2ff2857272d55f2499
6
+ metadata.gz: a09bff5f02f868871c4060a918596baae20922cc50372bcf259274c1f235dcb9995df424f9c3abc54681984fc9ae7449282b6915392fa4765537a0317762f7a4
7
+ data.tar.gz: edcc98f30e71f29331e7cfe5c64ff487a941937dee3ffa812c30e296fe0b60c01c64dd3de60105fed214a7115af76eb01b43cee51227b624da6fd6810c9dddb2
data/README.md CHANGED
@@ -54,12 +54,13 @@ kpop provides helpers to add a basic scrim and modal target frame. These should
54
54
 
55
55
  To show a modal you need to add content to the kpop turbo frame. You can do this in several ways:
56
56
  1. Use `content_for :kpop` in an HTML response to inject content into the kpop frame (see `yield :kpop` above)
57
- 2. Use `layout "kpop"` in your controller to wrap your turbo response in a kpop frame
57
+ 2. Respond to a turbo frame request from the kpop frame component.
58
58
 
59
59
  You can generate a link that will cause a modal to show using the `kpop_link_to` helper.
60
60
 
61
61
  `kpop_link_to`'s are similar to a `link_to` in rails, but it will navigate to the given URL within the modal turbo
62
- frame. The targeted action will need to generate content in a `Kpop::FrameComponent`, e.g. using `layout "kpop"`.
62
+ frame. The targeted action will need to generate content in a `Kpop::FrameComponent`, e.g. by responding to a turbo
63
+ frame request with the ID `kpop`.
63
64
 
64
65
  ```html
65
66
  <!-- app/views/homepage/index.html.erb -->
@@ -73,6 +74,31 @@ frame. The targeted action will need to generate content in a `Kpop::FrameCompon
73
74
  <% end %>
74
75
  ```
75
76
 
77
+ ### Turbo Frame Layout
78
+
79
+ Turbo Frame navigation responses use a layout to add a basic document structure. The `turbo-rails` gem provides the
80
+ `turbo_rails/frame` layout, and kpop provides a similar `kpop/frame` layout. If a turbo frame response is requested with
81
+ the `kpop` ID, the `kpop/frame` layout will be used automatically. You can provide an alternative by setting `layout`
82
+ in your controller, as usual.
83
+
84
+ Turbo 8 assumes that the frame response will be a complete HTML document, including a `<head>` and `<body>`, and the
85
+ Turbo Visits use the response head to deduce whether the navigation can be snap-shotted or not. This logic lives in
86
+ `@hotwire/turbo` in the `Turbo.Visit` constructor:
87
+
88
+ ```javascript
89
+ this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
90
+ ```
91
+
92
+ If the page is not the same, then the visit is not snap-shotted. Detection looks at turbo-tracked elements in the page
93
+ head to make this decision, and history navigation with frames will not work unless the headers are compatible.
94
+
95
+ As a consequence of this logic, it's really important that the layout used for kpop frame responses is compatible with
96
+ the application layout. Kpop provides a "sensible default" that includes stylesheets and javascripts, but if your
97
+ application doesn't use the same structure as the Rails default, you'll need to provide your own layout.
98
+
99
+ If you're experiencing 'strange history behaviour', it's worth putting a breakpoint on the turbo `isSamePage`
100
+ calculation to check that the headers are compatible.
101
+
76
102
  ## Development
77
103
 
78
104
  Releases need to be distributed to rubygems.org and npmjs.org. To do this, you need to have accounts with both providers
@@ -1,5 +1,5 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
- import { Turbo } from '@hotwired/turbo-rails';
2
+ import { Turbo as Turbo$1 } from '@hotwired/turbo-rails';
3
3
 
4
4
  class Modal {
5
5
  constructor(id) {
@@ -62,7 +62,7 @@ class Modal {
62
62
 
63
63
  get isCurrentLocation() {
64
64
  return (
65
- window.history.state?.turbo && Turbo.session.location.href === this.src
65
+ window.history.state?.turbo && Turbo$1.session.location.href === this.src
66
66
  );
67
67
  }
68
68
 
@@ -161,7 +161,7 @@ class FrameModal extends Modal {
161
161
 
162
162
  /**
163
163
  * When a user clicks a kpop link, turbo intercepts the click and calls
164
- * navigateFrame on the turbo frame controller before setting the TurboFrame
164
+ * #navigateFrame on the turbo frame controller before setting the TurboFrame
165
165
  * element's src attribute. KPOP intercepts this call and calls this method
166
166
  * first so we cancel problematic navigations that might cache invalid states.
167
167
  *
@@ -234,7 +234,7 @@ class FrameModal extends Modal {
234
234
  e.preventDefault();
235
235
 
236
236
  frame.dismiss({ animate: false }).then(() => {
237
- Turbo.visit(e.detail.url);
237
+ Turbo$1.visit(e.detail.url);
238
238
 
239
239
  this.debug("before-visit-end");
240
240
  });
@@ -463,9 +463,9 @@ class Kpop__FrameController extends Controller {
463
463
  /**
464
464
  * Monkey patch for Turbo#FrameController.
465
465
  *
466
- * Intercept calls to navigateFrame(element, location) and ensures that src is
467
- * cleared if the frame is busy so that we don't restore an in-progress src on
468
- * restoration visits.
466
+ * Intercept calls to linkClickIntercepted(element, location) and ensures
467
+ * that src is cleared if the frame is busy so that we don't restore an
468
+ * in-progress src on restoration visits.
469
469
  *
470
470
  * See Turbo issue: https://github.com/hotwired/turbo/issues/1055
471
471
  *
@@ -475,18 +475,30 @@ function installNavigationInterception(controller) {
475
475
  const TurboFrameController =
476
476
  controller.element.delegate.constructor.prototype;
477
477
 
478
- if (TurboFrameController._navigateFrame) return;
479
-
480
- TurboFrameController._navigateFrame = TurboFrameController.navigateFrame;
481
- TurboFrameController.navigateFrame = function (element, url, submitter) {
482
- const frame = this.findFrameElement(element, submitter);
478
+ if (TurboFrameController._linkClickIntercepted) return;
479
+
480
+ TurboFrameController._linkClickIntercepted =
481
+ TurboFrameController.linkClickIntercepted;
482
+ TurboFrameController.linkClickIntercepted = function (element, location) {
483
+ // #findFrameElement
484
+ const id =
485
+ element?.getAttribute("data-turbo-frame") ||
486
+ this.element.getAttribute("target");
487
+ let frame = document.getElementById(id);
488
+ if (!(frame instanceof Turbo.FrameElement)) {
489
+ frame = this.element;
490
+ }
483
491
 
484
492
  if (frame.kpop) {
485
- FrameModal.visit(url, frame.kpop, frame, () => {
486
- TurboFrameController._navigateFrame.call(this, element, url, submitter);
493
+ FrameModal.visit(location, frame.kpop, frame, () => {
494
+ TurboFrameController._linkClickIntercepted.call(
495
+ this,
496
+ element,
497
+ location,
498
+ );
487
499
  });
488
500
  } else {
489
- TurboFrameController._navigateFrame.call(this, element, url, submitter);
501
+ TurboFrameController._linkClickIntercepted.call(this, element, location);
490
502
  }
491
503
  };
492
504
  }
@@ -714,7 +726,7 @@ class StreamModal extends Modal {
714
726
  e.preventDefault();
715
727
 
716
728
  frame.dismiss({ animate: false }).then(() => {
717
- Turbo.visit(e.detail.url);
729
+ Turbo$1.visit(e.detail.url);
718
730
 
719
731
  this.debug("before-visit-end");
720
732
  });
@@ -754,7 +766,7 @@ function kpop(action) {
754
766
  return action.targetElements[0]?.kpop;
755
767
  }
756
768
 
757
- Turbo.StreamActions.kpop_open = function () {
769
+ Turbo$1.StreamActions.kpop_open = function () {
758
770
  const animate = !kpop(this).openValue;
759
771
 
760
772
  kpop(this)
@@ -765,17 +777,20 @@ Turbo.StreamActions.kpop_open = function () {
765
777
  });
766
778
  };
767
779
 
768
- Turbo.StreamActions.kpop_dismiss = function () {
780
+ Turbo$1.StreamActions.kpop_dismiss = function () {
769
781
  kpop(this)?.dismiss({ reason: "turbo_stream.kpop.dismiss" });
770
782
  };
771
783
 
772
- Turbo.StreamActions.kpop_redirect_to = function () {
784
+ Turbo$1.StreamActions.kpop_redirect_to = function () {
773
785
  if (this.dataset.turboFrame === this.target) {
774
786
  const a = document.createElement("A");
775
787
  a.setAttribute("data-turbo-action", "replace");
776
- this.targetElements[0].delegate.navigateFrame(a, this.getAttribute("href"));
788
+ this.targetElements[0].delegate.linkClickIntercepted(
789
+ a,
790
+ this.getAttribute("href"),
791
+ );
777
792
  } else {
778
- Turbo.visit(this.getAttribute("href"), {
793
+ Turbo$1.visit(this.getAttribute("href"), {
779
794
  action: this.dataset.turboAction,
780
795
  });
781
796
  }
@@ -1,5 +1,5 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
- import { Turbo } from '@hotwired/turbo-rails';
2
+ import { Turbo as Turbo$1 } from '@hotwired/turbo-rails';
3
3
 
4
4
  class Modal {
5
5
  constructor(id) {
@@ -62,7 +62,7 @@ class Modal {
62
62
 
63
63
  get isCurrentLocation() {
64
64
  return (
65
- window.history.state?.turbo && Turbo.session.location.href === this.src
65
+ window.history.state?.turbo && Turbo$1.session.location.href === this.src
66
66
  );
67
67
  }
68
68
 
@@ -161,7 +161,7 @@ class FrameModal extends Modal {
161
161
 
162
162
  /**
163
163
  * When a user clicks a kpop link, turbo intercepts the click and calls
164
- * navigateFrame on the turbo frame controller before setting the TurboFrame
164
+ * #navigateFrame on the turbo frame controller before setting the TurboFrame
165
165
  * element's src attribute. KPOP intercepts this call and calls this method
166
166
  * first so we cancel problematic navigations that might cache invalid states.
167
167
  *
@@ -234,7 +234,7 @@ class FrameModal extends Modal {
234
234
  e.preventDefault();
235
235
 
236
236
  frame.dismiss({ animate: false }).then(() => {
237
- Turbo.visit(e.detail.url);
237
+ Turbo$1.visit(e.detail.url);
238
238
 
239
239
  this.debug("before-visit-end");
240
240
  });
@@ -463,9 +463,9 @@ class Kpop__FrameController extends Controller {
463
463
  /**
464
464
  * Monkey patch for Turbo#FrameController.
465
465
  *
466
- * Intercept calls to navigateFrame(element, location) and ensures that src is
467
- * cleared if the frame is busy so that we don't restore an in-progress src on
468
- * restoration visits.
466
+ * Intercept calls to linkClickIntercepted(element, location) and ensures
467
+ * that src is cleared if the frame is busy so that we don't restore an
468
+ * in-progress src on restoration visits.
469
469
  *
470
470
  * See Turbo issue: https://github.com/hotwired/turbo/issues/1055
471
471
  *
@@ -475,18 +475,30 @@ function installNavigationInterception(controller) {
475
475
  const TurboFrameController =
476
476
  controller.element.delegate.constructor.prototype;
477
477
 
478
- if (TurboFrameController._navigateFrame) return;
479
-
480
- TurboFrameController._navigateFrame = TurboFrameController.navigateFrame;
481
- TurboFrameController.navigateFrame = function (element, url, submitter) {
482
- const frame = this.findFrameElement(element, submitter);
478
+ if (TurboFrameController._linkClickIntercepted) return;
479
+
480
+ TurboFrameController._linkClickIntercepted =
481
+ TurboFrameController.linkClickIntercepted;
482
+ TurboFrameController.linkClickIntercepted = function (element, location) {
483
+ // #findFrameElement
484
+ const id =
485
+ element?.getAttribute("data-turbo-frame") ||
486
+ this.element.getAttribute("target");
487
+ let frame = document.getElementById(id);
488
+ if (!(frame instanceof Turbo.FrameElement)) {
489
+ frame = this.element;
490
+ }
483
491
 
484
492
  if (frame.kpop) {
485
- FrameModal.visit(url, frame.kpop, frame, () => {
486
- TurboFrameController._navigateFrame.call(this, element, url, submitter);
493
+ FrameModal.visit(location, frame.kpop, frame, () => {
494
+ TurboFrameController._linkClickIntercepted.call(
495
+ this,
496
+ element,
497
+ location,
498
+ );
487
499
  });
488
500
  } else {
489
- TurboFrameController._navigateFrame.call(this, element, url, submitter);
501
+ TurboFrameController._linkClickIntercepted.call(this, element, location);
490
502
  }
491
503
  };
492
504
  }
@@ -714,7 +726,7 @@ class StreamModal extends Modal {
714
726
  e.preventDefault();
715
727
 
716
728
  frame.dismiss({ animate: false }).then(() => {
717
- Turbo.visit(e.detail.url);
729
+ Turbo$1.visit(e.detail.url);
718
730
 
719
731
  this.debug("before-visit-end");
720
732
  });
@@ -754,7 +766,7 @@ function kpop(action) {
754
766
  return action.targetElements[0]?.kpop;
755
767
  }
756
768
 
757
- Turbo.StreamActions.kpop_open = function () {
769
+ Turbo$1.StreamActions.kpop_open = function () {
758
770
  const animate = !kpop(this).openValue;
759
771
 
760
772
  kpop(this)
@@ -765,17 +777,20 @@ Turbo.StreamActions.kpop_open = function () {
765
777
  });
766
778
  };
767
779
 
768
- Turbo.StreamActions.kpop_dismiss = function () {
780
+ Turbo$1.StreamActions.kpop_dismiss = function () {
769
781
  kpop(this)?.dismiss({ reason: "turbo_stream.kpop.dismiss" });
770
782
  };
771
783
 
772
- Turbo.StreamActions.kpop_redirect_to = function () {
784
+ Turbo$1.StreamActions.kpop_redirect_to = function () {
773
785
  if (this.dataset.turboFrame === this.target) {
774
786
  const a = document.createElement("A");
775
787
  a.setAttribute("data-turbo-action", "replace");
776
- this.targetElements[0].delegate.navigateFrame(a, this.getAttribute("href"));
788
+ this.targetElements[0].delegate.linkClickIntercepted(
789
+ a,
790
+ this.getAttribute("href"),
791
+ );
777
792
  } else {
778
- Turbo.visit(this.getAttribute("href"), {
793
+ Turbo$1.visit(this.getAttribute("href"), {
779
794
  action: this.dataset.turboAction,
780
795
  });
781
796
  }
@@ -1,2 +1,2 @@
1
- import{Controller as e}from"@hotwired/stimulus";import{Turbo as t}from"@hotwired/turbo-rails";class i{constructor(e){this.id=e}async open(){this.debug("open")}async dismiss(){this.debug("dismiss")}beforeVisit(e,t){this.debug("before-visit",t.detail.url)}popstate(e,t){this.debug("popstate",t.state)}async pop(e,t){this.debug("pop");const i=new Promise((t=>{window.addEventListener(e,(()=>{t()}),{once:!0})}));return t(),i}get frameElement(){return document.getElementById(this.id)}get controller(){return this.frameElement?.kpop}get modalElement(){return this.frameElement?.querySelector("[data-controller*='kpop--modal']")}get currentLocationValue(){return this.modalElement?.dataset["kpop-ModalCurrentLocationValue"]||"/"}get fallbackLocationValue(){return this.modalElement?.dataset["kpop-ModalFallbackLocationValue"]}get isCurrentLocation(){return window.history.state?.turbo&&t.session.location.href===this.src}static debug(e,...t){}debug(e,...t){}}class s extends i{static connect(e,t){e.open(new s(t.id),{animate:!1})}constructor(e,t=null){super(e),t&&(this.src=t)}async dismiss(){const e=this.fallbackLocationValue;await super.dismiss(),this.visitStarted?this.debug("skipping dismiss, visit started"):this.isCurrentLocation?(this.frameElement.innerHTML="",e&&window.history.replaceState(window.history.state,"",e)):this.debug("skipping dismiss, not current location")}beforeVisit(e,t){super.beforeVisit(e,t),this.visitStarted=!0,e.scrimOutlet.hide({animate:!1})}get src(){return new URL(this.currentLocationValue.toString(),document.baseURI).toString()}}class n extends i{static connect(e,t){const i=new n(t.id,t.src);return i.isCurrentLocation?(this.debug("restore",t.src),e.open(i,{animate:!1})):(console.warn("kpop: restored frame src doesn't match window href",i.src,window.location.href),e.clear())}static visit(e,t,i,s){i.hasAttribute("busy")&&(this.debug("clearing src to cancel turbo request"),i.src=""),i.src!==e?(i.src&&i.src!==window.location.href&&(console.warn("kpop: frame src doesn't match window",i.src,window.location.href,e),t.clear()),this.debug("navigate to",e),s()):this.debug("skipping navigate as already on location")}constructor(e,t){super(e),this.src=t}async dismiss(){await super.dismiss(),this.isCurrentLocation?await this.pop("turbo:load",(()=>window.history.back())):this.debug("skipping dismiss, not current location")}beforeVisit(e,i){super.beforeVisit(e,i),i.preventDefault(),e.dismiss({animate:!1}).then((()=>{t.visit(i.detail.url),this.debug("before-visit-end")}))}}class a extends e{static outlets=["scrim"];static targets=["modal"];static values={open:Boolean};connect(){this.debug("connect",this.element.src),this.element.kpop=this,function(e){const t=e.element.delegate.constructor.prototype;if(t._navigateFrame)return;t._navigateFrame=t.navigateFrame,t.navigateFrame=function(e,i,s){const a=this.findFrameElement(e,s);a.kpop?n.visit(i,a.kpop,a,(()=>{t._navigateFrame.call(this,e,i,s)})):t._navigateFrame.call(this,e,i,s)}}(this),this.element.src&&this.element.complete?(this.debug("new frame modal",this.element.src),n.connect(this,this.element)):this.modalElements.length>0?(this.debug("new content modal",window.location.pathname),s.connect(this,this.element)):(this.debug("no modal"),this.clear())}disconnect(){this.debug("disconnect"),delete this.element.kpop,delete this.modal}scrimOutletConnected(e){this.debug("scrim-connected"),this.scrimConnected=!0,this.openValue?e.show({animate:!1}):e.hide({animate:!1})}openValueChanged(e){this.debug("open-changed",e),this.element.parentElement.style.display=e?"flex":"none"}async open(e,{animate:t=!0}={}){return this.isOpen?(this.debug("skip open as already open"),this.modal||=e,!1):(await this.dismissing,this.opening||=this.#e((()=>this.#t(e,{animate:t}))))}async dismiss({animate:e=!0,reason:t=""}={}){return this.isOpen?(await this.opening,this.dismissing||=this.#e((()=>this.#i({animate:e,reason:t})))):(this.debug("skip dismiss as already closed"),!1)}async clear(){if(this.element.src="",this.modalElements.forEach((e=>e.remove())),this.openValue=!1,this.scrimConnected)return this.scrimOutlet.hide({animate:!1});this.modal=null}popstate(e){this.modal?.popstate(this,e)}beforeFrameRender(e){this.debug("before-frame-render",e.detail.newFrame.baseURI),e.preventDefault(),this.dismiss({animate:!0,reason:"before-frame-render"}).then((()=>{this.debug("resume-frame-render",e.detail.newFrame.baseURI),e.detail.resume()}))}beforeStreamRender(e){this.debug("before-stream-render",e.detail);const t=e.detail.render;e.detail.render=e=>{(this.dismissing||Promise.resolve()).then((()=>{this.debug("stream-render",e),t(e)}))}}beforeVisit(e){this.debug("before-visit",e.detail.url),e.detail.url!==this.element.src&&this.isOpen&&this.modal.beforeVisit(this,e)}frameLoad(e){this.debug("frame-load");const t=new n(this.element.id,this.element.src);window.addEventListener("turbo:visit",(e=>{this.open(t,{animate:!0})}),{once:!0})}get isOpen(){return this.openValue&&!this.dismissing}get modalElements(){return this.element.querySelectorAll("[data-controller*='kpop--modal']")}async#t(e,{animate:t=!0}={}){this.debug("open-start",{animate:t});const i=this.scrimConnected&&this.scrimOutlet;this.modal=e,this.openValue=!0,await e.open({animate:t}),await(i?.show({animate:t})),delete this.opening,this.debug("open-end")}async#i({animate:e=!0,reason:t=""}={}){this.debug("dismiss-start",{animate:e,reason:t}),this.element.isConnected?(this.modal||console.warn("modal missing on dismiss"),await this.scrimOutlet.hide({animate:e}),await(this.modal?.dismiss()),this.openValue=!1,this.modal=null,delete this.dismissing,this.debug("dismiss-end")):this.debug("skip dismiss, element detached")}async#e(e){return new Promise(window.requestAnimationFrame).then(e)}debug(e,...t){}}class o extends e{static values={open:Boolean,captive:Boolean,zIndex:Number};connect(){this.defaultZIndexValue=this.zIndexValue,this.defaultCaptiveValue=this.captiveValue,this.element.scrim=this}disconnect(){delete this.element.scrim}async show({captive:e=this.defaultCaptiveValue,zIndex:t=this.defaultZIndexValue,top:i=window.scrollY,animate:s=!0}={}){this.openValue&&await this.hide({animate:s}),this.openValue=!0,this.dispatch("show",{bubbles:!0}),this.#s(e,t,i),s&&(this.element.dataset.showAnimating="",await new Promise((e=>{this.element.addEventListener("animationend",(()=>e()),{once:!0})})),delete this.element.dataset.showAnimating)}async hide({animate:e=!0}={}){this.openValue&&!this.element.dataset.hideAnimating&&(this.dispatch("hide",{bubbles:!0}),e&&(this.element.dataset.hideAnimating="",await new Promise((e=>{this.element.addEventListener("animationend",(()=>e()),{once:!0})})),delete this.element.dataset.hideAnimating),this.#n(),this.openValue=!1)}dismiss(e){this.captiveValue||this.dispatch("dismiss",{bubbles:!0})}escape(e){"Escape"!==e.key||this.captiveValue||e.defaultPrevented||this.dispatch("dismiss",{bubbles:!0})}#s(e,t,i){this.captiveValue=e,this.zIndexValue=t,this.scrollY=i,this.previousPosition=document.body.style.position,this.previousTop=document.body.style.top,this.element.style.zIndex=this.zIndexValue,document.body.style.top=`-${i}px`,document.body.style.position="fixed"}#n(){this.captiveValue=this.defaultCaptiveValue,this.zIndexValue=this.defaultZIndexValue,r(this.element,"z-index",null),r(document.body,"position",null),r(document.body,"top",null),window.scrollTo({left:0,top:this.scrollY,behavior:"instant"}),delete this.scrollY,delete this.previousPosition,delete this.previousTop}}function r(e,t,i){i?e.style.setProperty(t,i):e.style.removeProperty(t)}class d extends i{constructor(e,t){super(e),this.action=t}async open(){await super.open(),window.history.pushState({kpop:!0,id:this.id},"",window.location)}async dismiss(){await super.dismiss(),this.isCurrentLocation&&await this.pop("popstate",(()=>window.history.back())),this.frameElement.innerHTML=""}beforeVisit(e,i){super.beforeVisit(e,i),i.preventDefault(),e.dismiss({animate:!1}).then((()=>{t.visit(i.detail.url),this.debug("before-visit-end")}))}popstate(e,t){super.popstate(e,t),e.dismiss({animate:!0,reason:"popstate"})}get isCurrentLocation(){return window.history.state?.kpop&&window.history.state?.id===this.id}}class l{constructor(e,t){this.frame=e,this.action=t}render(){this.frame.src="",this.frame.innerHTML="",this.frame.append(this.action.templateContent)}}function h(e){return e.targetElements[0]?.kpop}t.StreamActions.kpop_open=function(){const e=!h(this).openValue;h(this)?.dismiss({animate:e,reason:"before-turbo-stream"}).then((()=>{new l(this.targetElements[0],this).render(),h(this)?.open(new d(this.target,this),{animate:e})}))},t.StreamActions.kpop_dismiss=function(){h(this)?.dismiss({reason:"turbo_stream.kpop.dismiss"})},t.StreamActions.kpop_redirect_to=function(){if(this.dataset.turboFrame===this.target){const e=document.createElement("A");e.setAttribute("data-turbo-action","replace"),this.targetElements[0].delegate.navigateFrame(e,this.getAttribute("href"))}else t.visit(this.getAttribute("href"),{action:this.dataset.turboAction})};const c=[{identifier:"kpop--frame",controllerConstructor:a},{identifier:"kpop--modal",controllerConstructor:class extends e{static values={fallback_location:String,layout:String};connect(){this.debug("connect"),this.layoutValue&&document.querySelector("#kpop").classList.toggle(this.layoutValue,!0)}disconnect(){this.debug("disconnect"),this.layoutValue&&document.querySelector("#kpop").classList.toggle(this.layoutValue,!1)}debug(e,...t){}}},{identifier:"scrim",controllerConstructor:o}];export{c as default};
1
+ import{Controller as e}from"@hotwired/stimulus";import{Turbo as t}from"@hotwired/turbo-rails";class i{constructor(e){this.id=e}async open(){this.debug("open")}async dismiss(){this.debug("dismiss")}beforeVisit(e,t){this.debug("before-visit",t.detail.url)}popstate(e,t){this.debug("popstate",t.state)}async pop(e,t){this.debug("pop");const i=new Promise((t=>{window.addEventListener(e,(()=>{t()}),{once:!0})}));return t(),i}get frameElement(){return document.getElementById(this.id)}get controller(){return this.frameElement?.kpop}get modalElement(){return this.frameElement?.querySelector("[data-controller*='kpop--modal']")}get currentLocationValue(){return this.modalElement?.dataset["kpop-ModalCurrentLocationValue"]||"/"}get fallbackLocationValue(){return this.modalElement?.dataset["kpop-ModalFallbackLocationValue"]}get isCurrentLocation(){return window.history.state?.turbo&&t.session.location.href===this.src}static debug(e,...t){}debug(e,...t){}}class s extends i{static connect(e,t){e.open(new s(t.id),{animate:!1})}constructor(e,t=null){super(e),t&&(this.src=t)}async dismiss(){const e=this.fallbackLocationValue;await super.dismiss(),this.visitStarted?this.debug("skipping dismiss, visit started"):this.isCurrentLocation?(this.frameElement.innerHTML="",e&&window.history.replaceState(window.history.state,"",e)):this.debug("skipping dismiss, not current location")}beforeVisit(e,t){super.beforeVisit(e,t),this.visitStarted=!0,e.scrimOutlet.hide({animate:!1})}get src(){return new URL(this.currentLocationValue.toString(),document.baseURI).toString()}}class n extends i{static connect(e,t){const i=new n(t.id,t.src);return i.isCurrentLocation?(this.debug("restore",t.src),e.open(i,{animate:!1})):(console.warn("kpop: restored frame src doesn't match window href",i.src,window.location.href),e.clear())}static visit(e,t,i,s){i.hasAttribute("busy")&&(this.debug("clearing src to cancel turbo request"),i.src=""),i.src!==e?(i.src&&i.src!==window.location.href&&(console.warn("kpop: frame src doesn't match window",i.src,window.location.href,e),t.clear()),this.debug("navigate to",e),s()):this.debug("skipping navigate as already on location")}constructor(e,t){super(e),this.src=t}async dismiss(){await super.dismiss(),this.isCurrentLocation?await this.pop("turbo:load",(()=>window.history.back())):this.debug("skipping dismiss, not current location")}beforeVisit(e,i){super.beforeVisit(e,i),i.preventDefault(),e.dismiss({animate:!1}).then((()=>{t.visit(i.detail.url),this.debug("before-visit-end")}))}}class a extends e{static outlets=["scrim"];static targets=["modal"];static values={open:Boolean};connect(){this.debug("connect",this.element.src),this.element.kpop=this,function(e){const t=e.element.delegate.constructor.prototype;if(t._linkClickIntercepted)return;t._linkClickIntercepted=t.linkClickIntercepted,t.linkClickIntercepted=function(e,i){const s=e?.getAttribute("data-turbo-frame")||this.element.getAttribute("target");let a=document.getElementById(s);a instanceof Turbo.FrameElement||(a=this.element),a.kpop?n.visit(i,a.kpop,a,(()=>{t._linkClickIntercepted.call(this,e,i)})):t._linkClickIntercepted.call(this,e,i)}}(this),this.element.src&&this.element.complete?(this.debug("new frame modal",this.element.src),n.connect(this,this.element)):this.modalElements.length>0?(this.debug("new content modal",window.location.pathname),s.connect(this,this.element)):(this.debug("no modal"),this.clear())}disconnect(){this.debug("disconnect"),delete this.element.kpop,delete this.modal}scrimOutletConnected(e){this.debug("scrim-connected"),this.scrimConnected=!0,this.openValue?e.show({animate:!1}):e.hide({animate:!1})}openValueChanged(e){this.debug("open-changed",e),this.element.parentElement.style.display=e?"flex":"none"}async open(e,{animate:t=!0}={}){return this.isOpen?(this.debug("skip open as already open"),this.modal||=e,!1):(await this.dismissing,this.opening||=this.#e((()=>this.#t(e,{animate:t}))))}async dismiss({animate:e=!0,reason:t=""}={}){return this.isOpen?(await this.opening,this.dismissing||=this.#e((()=>this.#i({animate:e,reason:t})))):(this.debug("skip dismiss as already closed"),!1)}async clear(){if(this.element.src="",this.modalElements.forEach((e=>e.remove())),this.openValue=!1,this.scrimConnected)return this.scrimOutlet.hide({animate:!1});this.modal=null}popstate(e){this.modal?.popstate(this,e)}beforeFrameRender(e){this.debug("before-frame-render",e.detail.newFrame.baseURI),e.preventDefault(),this.dismiss({animate:!0,reason:"before-frame-render"}).then((()=>{this.debug("resume-frame-render",e.detail.newFrame.baseURI),e.detail.resume()}))}beforeStreamRender(e){this.debug("before-stream-render",e.detail);const t=e.detail.render;e.detail.render=e=>{(this.dismissing||Promise.resolve()).then((()=>{this.debug("stream-render",e),t(e)}))}}beforeVisit(e){this.debug("before-visit",e.detail.url),e.detail.url!==this.element.src&&this.isOpen&&this.modal.beforeVisit(this,e)}frameLoad(e){this.debug("frame-load");const t=new n(this.element.id,this.element.src);window.addEventListener("turbo:visit",(e=>{this.open(t,{animate:!0})}),{once:!0})}get isOpen(){return this.openValue&&!this.dismissing}get modalElements(){return this.element.querySelectorAll("[data-controller*='kpop--modal']")}async#t(e,{animate:t=!0}={}){this.debug("open-start",{animate:t});const i=this.scrimConnected&&this.scrimOutlet;this.modal=e,this.openValue=!0,await e.open({animate:t}),await(i?.show({animate:t})),delete this.opening,this.debug("open-end")}async#i({animate:e=!0,reason:t=""}={}){this.debug("dismiss-start",{animate:e,reason:t}),this.element.isConnected?(this.modal||console.warn("modal missing on dismiss"),await this.scrimOutlet.hide({animate:e}),await(this.modal?.dismiss()),this.openValue=!1,this.modal=null,delete this.dismissing,this.debug("dismiss-end")):this.debug("skip dismiss, element detached")}async#e(e){return new Promise(window.requestAnimationFrame).then(e)}debug(e,...t){}}class o extends e{static values={open:Boolean,captive:Boolean,zIndex:Number};connect(){this.defaultZIndexValue=this.zIndexValue,this.defaultCaptiveValue=this.captiveValue,this.element.scrim=this}disconnect(){delete this.element.scrim}async show({captive:e=this.defaultCaptiveValue,zIndex:t=this.defaultZIndexValue,top:i=window.scrollY,animate:s=!0}={}){this.openValue&&await this.hide({animate:s}),this.openValue=!0,this.dispatch("show",{bubbles:!0}),this.#s(e,t,i),s&&(this.element.dataset.showAnimating="",await new Promise((e=>{this.element.addEventListener("animationend",(()=>e()),{once:!0})})),delete this.element.dataset.showAnimating)}async hide({animate:e=!0}={}){this.openValue&&!this.element.dataset.hideAnimating&&(this.dispatch("hide",{bubbles:!0}),e&&(this.element.dataset.hideAnimating="",await new Promise((e=>{this.element.addEventListener("animationend",(()=>e()),{once:!0})})),delete this.element.dataset.hideAnimating),this.#n(),this.openValue=!1)}dismiss(e){this.captiveValue||this.dispatch("dismiss",{bubbles:!0})}escape(e){"Escape"!==e.key||this.captiveValue||e.defaultPrevented||this.dispatch("dismiss",{bubbles:!0})}#s(e,t,i){this.captiveValue=e,this.zIndexValue=t,this.scrollY=i,this.previousPosition=document.body.style.position,this.previousTop=document.body.style.top,this.element.style.zIndex=this.zIndexValue,document.body.style.top=`-${i}px`,document.body.style.position="fixed"}#n(){this.captiveValue=this.defaultCaptiveValue,this.zIndexValue=this.defaultZIndexValue,r(this.element,"z-index",null),r(document.body,"position",null),r(document.body,"top",null),window.scrollTo({left:0,top:this.scrollY,behavior:"instant"}),delete this.scrollY,delete this.previousPosition,delete this.previousTop}}function r(e,t,i){i?e.style.setProperty(t,i):e.style.removeProperty(t)}class d extends i{constructor(e,t){super(e),this.action=t}async open(){await super.open(),window.history.pushState({kpop:!0,id:this.id},"",window.location)}async dismiss(){await super.dismiss(),this.isCurrentLocation&&await this.pop("popstate",(()=>window.history.back())),this.frameElement.innerHTML=""}beforeVisit(e,i){super.beforeVisit(e,i),i.preventDefault(),e.dismiss({animate:!1}).then((()=>{t.visit(i.detail.url),this.debug("before-visit-end")}))}popstate(e,t){super.popstate(e,t),e.dismiss({animate:!0,reason:"popstate"})}get isCurrentLocation(){return window.history.state?.kpop&&window.history.state?.id===this.id}}class l{constructor(e,t){this.frame=e,this.action=t}render(){this.frame.src="",this.frame.innerHTML="",this.frame.append(this.action.templateContent)}}function c(e){return e.targetElements[0]?.kpop}t.StreamActions.kpop_open=function(){const e=!c(this).openValue;c(this)?.dismiss({animate:e,reason:"before-turbo-stream"}).then((()=>{new l(this.targetElements[0],this).render(),c(this)?.open(new d(this.target,this),{animate:e})}))},t.StreamActions.kpop_dismiss=function(){c(this)?.dismiss({reason:"turbo_stream.kpop.dismiss"})},t.StreamActions.kpop_redirect_to=function(){if(this.dataset.turboFrame===this.target){const e=document.createElement("A");e.setAttribute("data-turbo-action","replace"),this.targetElements[0].delegate.linkClickIntercepted(e,this.getAttribute("href"))}else t.visit(this.getAttribute("href"),{action:this.dataset.turboAction})};const h=[{identifier:"kpop--frame",controllerConstructor:a},{identifier:"kpop--modal",controllerConstructor:class extends e{static values={fallback_location:String,layout:String};connect(){this.debug("connect"),this.layoutValue&&document.querySelector("#kpop").classList.toggle(this.layoutValue,!0)}disconnect(){this.debug("disconnect"),this.layoutValue&&document.querySelector("#kpop").classList.toggle(this.layoutValue,!1)}debug(e,...t){}}},{identifier:"scrim",controllerConstructor:o}];export{h as default};
2
2
  //# sourceMappingURL=kpop.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"kpop.min.js","sources":["../../../javascript/kpop/modals/modal.js","../../../javascript/kpop/modals/content_modal.js","../../../javascript/kpop/modals/frame_modal.js","../../../javascript/kpop/controllers/frame_controller.js","../../../javascript/kpop/controllers/scrim_controller.js","../../../javascript/kpop/modals/stream_modal.js","../../../javascript/kpop/utils/stream_renderer.js","../../../javascript/kpop/turbo_actions.js","../../../javascript/kpop/application.js","../../../javascript/kpop/controllers/modal_controller.js"],"sourcesContent":["import { Turbo } from \"@hotwired/turbo-rails\";\n\nimport DEBUG from \"../debug\";\n\nexport class Modal {\n constructor(id) {\n this.id = id;\n }\n\n async open() {\n this.debug(\"open\");\n }\n\n async dismiss() {\n this.debug(`dismiss`);\n }\n\n beforeVisit(frame, e) {\n this.debug(`before-visit`, e.detail.url);\n }\n\n popstate(frame, e) {\n this.debug(`popstate`, e.state);\n }\n\n async pop(event, callback) {\n this.debug(`pop`);\n\n const promise = new Promise((resolve) => {\n window.addEventListener(\n event,\n () => {\n resolve();\n },\n { once: true },\n );\n });\n\n callback();\n\n return promise;\n }\n\n get frameElement() {\n return document.getElementById(this.id);\n }\n\n get controller() {\n return this.frameElement?.kpop;\n }\n\n get modalElement() {\n return this.frameElement?.querySelector(\"[data-controller*='kpop--modal']\");\n }\n\n get currentLocationValue() {\n return this.modalElement?.dataset[\"kpop-ModalCurrentLocationValue\"] || \"/\";\n }\n\n get fallbackLocationValue() {\n return this.modalElement?.dataset[\"kpop-ModalFallbackLocationValue\"];\n }\n\n get isCurrentLocation() {\n return (\n window.history.state?.turbo && Turbo.session.location.href === this.src\n );\n }\n\n static debug(event, ...args) {\n if (DEBUG) console.debug(`${this.name}:${event}`, ...args);\n }\n\n debug(event, ...args) {\n if (DEBUG) console.debug(`${this.constructor.name}:${event}`, ...args);\n }\n}\n","import { Turbo } from \"@hotwired/turbo-rails\";\n\nimport { Modal } from \"./modal\";\n\nexport class ContentModal extends Modal {\n static connect(frame, element) {\n frame.open(new ContentModal(element.id), { animate: false });\n }\n\n constructor(id, src = null) {\n super(id);\n\n if (src) this.src = src;\n }\n\n /**\n * When the modal is dismissed we can't rely on a back navigation to close the\n * modal as the user may have navigated to a different location. Instead we\n * remove the content from the dom and replace the current history state with\n * the fallback location, if set.\n *\n * If there is no fallback location, we may be showing a stream modal that was\n * injected and cached by turbo. In this case, we clear the frame element and\n * do not change history.\n *\n * @returns {Promise<void>}\n */\n async dismiss() {\n const fallbackLocation = this.fallbackLocationValue;\n\n await super.dismiss();\n\n if (this.visitStarted) {\n this.debug(\"skipping dismiss, visit started\");\n return;\n }\n if (!this.isCurrentLocation) {\n this.debug(\"skipping dismiss, not current location\");\n return;\n }\n\n this.frameElement.innerHTML = \"\";\n\n if (fallbackLocation) {\n window.history.replaceState(window.history.state, \"\", fallbackLocation);\n }\n }\n\n beforeVisit(frame, e) {\n super.beforeVisit(frame, e);\n\n this.visitStarted = true;\n\n frame.scrimOutlet.hide({ animate: false });\n }\n\n get src() {\n return new URL(\n this.currentLocationValue.toString(),\n document.baseURI,\n ).toString();\n }\n}\n","import { Turbo } from \"@hotwired/turbo-rails\";\n\nimport { Modal } from \"./modal\";\n\nexport class FrameModal extends Modal {\n /**\n * When the FrameController detects a frame element on connect, it runs this\n * method to sanity check the frame src and restore the modal state.\n *\n * @param frame FrameController\n * @param element TurboFrame element\n */\n static connect(frame, element) {\n const modal = new FrameModal(element.id, element.src);\n\n // state reconciliation for turbo restore of invalid frames\n if (modal.isCurrentLocation) {\n // restoration visit\n this.debug(\"restore\", element.src);\n return frame.open(modal, { animate: false });\n } else {\n console.warn(\n \"kpop: restored frame src doesn't match window href\",\n modal.src,\n window.location.href,\n );\n return frame.clear();\n }\n }\n\n /**\n * When a user clicks a kpop link, turbo intercepts the click and calls\n * navigateFrame on the turbo frame controller before setting the TurboFrame\n * element's src attribute. KPOP intercepts this call and calls this method\n * first so we cancel problematic navigations that might cache invalid states.\n *\n * @param location URL requested by turbo\n * @param frame FrameController\n * @param element TurboFrame element\n * @param resolve continuation chain\n */\n static visit(location, frame, element, resolve) {\n // Ensure that turbo doesn't cache the frame in a loading state by cancelling\n // the current request (if any) by clearing the src.\n // Known issue: this won't work if the frame was previously rendering a useful src.\n if (element.hasAttribute(\"busy\")) {\n this.debug(\"clearing src to cancel turbo request\");\n element.src = \"\";\n }\n\n if (element.src === location) {\n this.debug(\"skipping navigate as already on location\");\n return;\n }\n\n if (element.src && element.src !== window.location.href) {\n console.warn(\n \"kpop: frame src doesn't match window\",\n element.src,\n window.location.href,\n location,\n );\n frame.clear();\n }\n\n this.debug(\"navigate to\", location);\n resolve();\n }\n\n constructor(id, src) {\n super(id);\n this.src = src;\n }\n\n /**\n * FrameModals are closed by running pop state and awaiting the turbo:load\n * event that follows on history restoration.\n *\n * @returns {Promise<void>}\n */\n async dismiss() {\n await super.dismiss();\n\n if (!this.isCurrentLocation) {\n this.debug(\"skipping dismiss, not current location\");\n } else {\n await this.pop(\"turbo:load\", () => window.history.back());\n }\n\n // no specific close action required, this is turbo's responsibility\n }\n\n /**\n * When user navigates from inside a Frame modal, dismiss the modal first so\n * that the modal does not appear in the history stack.\n *\n * @param frame FrameController\n * @param e Turbo navigation event\n */\n beforeVisit(frame, e) {\n super.beforeVisit(frame, e);\n\n e.preventDefault();\n\n frame.dismiss({ animate: false }).then(() => {\n Turbo.visit(e.detail.url);\n\n this.debug(\"before-visit-end\");\n });\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nimport DEBUG from \"../debug\";\nimport { ContentModal } from \"../modals/content_modal\";\nimport { FrameModal } from \"../modals/frame_modal\";\n\nexport default class Kpop__FrameController extends Controller {\n static outlets = [\"scrim\"];\n static targets = [\"modal\"];\n static values = {\n open: Boolean,\n };\n\n connect() {\n this.debug(\"connect\", this.element.src);\n\n this.element.kpop = this;\n\n // allow our code to intercept frame navigation requests before dom changes\n installNavigationInterception(this);\n\n if (this.element.src && this.element.complete) {\n this.debug(\"new frame modal\", this.element.src);\n FrameModal.connect(this, this.element);\n } else if (this.modalElements.length > 0) {\n this.debug(\"new content modal\", window.location.pathname);\n ContentModal.connect(this, this.element);\n } else {\n this.debug(\"no modal\");\n this.clear();\n }\n }\n\n disconnect() {\n this.debug(\"disconnect\");\n\n delete this.element.kpop;\n delete this.modal;\n }\n\n scrimOutletConnected(scrim) {\n this.debug(\"scrim-connected\");\n\n this.scrimConnected = true;\n\n if (this.openValue) {\n scrim.show({ animate: false });\n } else {\n scrim.hide({ animate: false });\n }\n }\n\n openValueChanged(open) {\n this.debug(\"open-changed\", open);\n\n this.element.parentElement.style.display = open ? \"flex\" : \"none\";\n }\n\n async open(modal, { animate = true } = {}) {\n if (this.isOpen) {\n this.debug(\"skip open as already open\");\n this.modal ||= modal;\n return false;\n }\n\n await this.dismissing;\n\n return (this.opening ||= this.#nextFrame(() =>\n this.#open(modal, { animate }),\n ));\n }\n\n async dismiss({ animate = true, reason = \"\" } = {}) {\n if (!this.isOpen) {\n this.debug(\"skip dismiss as already closed\");\n return false;\n }\n\n await this.opening;\n\n return (this.dismissing ||= this.#nextFrame(() =>\n this.#dismiss({ animate, reason }),\n ));\n }\n\n async clear() {\n // clear the src from the frame (if any)\n this.element.src = \"\";\n\n // remove any open modal(s)\n this.modalElements.forEach((element) => element.remove());\n\n // mark the modal as hidden (will hide scrim on connect)\n this.openValue = false;\n\n // close the scrim, if connected\n if (this.scrimConnected) {\n return this.scrimOutlet.hide({ animate: false });\n }\n\n // unset modal\n this.modal = null;\n }\n\n // EVENTS\n\n popstate(event) {\n this.modal?.popstate(this, event);\n }\n\n /**\n * Incoming frame render, dismiss the current modal (if any) first.\n *\n * We're starting the actual visit\n *\n * @param event turbo:before-render\n */\n beforeFrameRender(event) {\n this.debug(\"before-frame-render\", event.detail.newFrame.baseURI);\n\n event.preventDefault();\n\n this.dismiss({ animate: true, reason: \"before-frame-render\" }).then(() => {\n this.debug(\"resume-frame-render\", event.detail.newFrame.baseURI);\n event.detail.resume();\n });\n }\n\n beforeStreamRender(event) {\n this.debug(\"before-stream-render\", event.detail);\n\n const resume = event.detail.render;\n\n // Defer rendering until dismiss is complete.\n // Dismiss may change history so we need to wait for it to complete to avoid\n // losing DOM changes on restoration visits.\n event.detail.render = (stream) => {\n (this.dismissing || Promise.resolve()).then(() => {\n this.debug(\"stream-render\", stream);\n resume(stream);\n });\n };\n }\n\n beforeVisit(e) {\n this.debug(\"before-visit\", e.detail.url);\n\n // ignore visits to the current frame, these fire when the frame navigates\n if (e.detail.url === this.element.src) return;\n\n // ignore unless we're open\n if (!this.isOpen) return;\n\n this.modal.beforeVisit(this, e);\n }\n\n frameLoad(event) {\n this.debug(\"frame-load\");\n\n const modal = new FrameModal(this.element.id, this.element.src);\n\n window.addEventListener(\n \"turbo:visit\",\n (e) => {\n this.open(modal, { animate: true });\n },\n { once: true },\n );\n }\n\n get isOpen() {\n return this.openValue && !this.dismissing;\n }\n\n get modalElements() {\n return this.element.querySelectorAll(\"[data-controller*='kpop--modal']\");\n }\n\n async #open(modal, { animate = true } = {}) {\n this.debug(\"open-start\", { animate });\n\n const scrim = this.scrimConnected && this.scrimOutlet;\n\n this.modal = modal;\n this.openValue = true;\n\n await modal.open({ animate });\n await scrim?.show({ animate });\n\n delete this.opening;\n\n this.debug(\"open-end\");\n }\n\n async #dismiss({ animate = true, reason = \"\" } = {}) {\n this.debug(\"dismiss-start\", { animate, reason });\n\n // if this element is detached then we've experienced a turbo navigation\n if (!this.element.isConnected) {\n this.debug(\"skip dismiss, element detached\");\n return;\n }\n\n if (!this.modal) {\n console.warn(\"modal missing on dismiss\");\n if (DEBUG) debugger;\n }\n\n await this.scrimOutlet.hide({ animate });\n await this.modal?.dismiss();\n\n this.openValue = false;\n this.modal = null;\n delete this.dismissing;\n\n this.debug(\"dismiss-end\");\n }\n\n async #nextFrame(callback) {\n return new Promise(window.requestAnimationFrame).then(callback);\n }\n\n debug(event, ...args) {\n if (DEBUG) console.debug(`FrameController:${event}`, ...args);\n }\n}\n\n/**\n * Monkey patch for Turbo#FrameController.\n *\n * Intercept calls to navigateFrame(element, location) and ensures that src is\n * cleared if the frame is busy so that we don't restore an in-progress src on\n * restoration visits.\n *\n * See Turbo issue: https://github.com/hotwired/turbo/issues/1055\n *\n * @param controller FrameController\n */\nfunction installNavigationInterception(controller) {\n const TurboFrameController =\n controller.element.delegate.constructor.prototype;\n\n if (TurboFrameController._navigateFrame) return;\n\n TurboFrameController._navigateFrame = TurboFrameController.navigateFrame;\n TurboFrameController.navigateFrame = function (element, url, submitter) {\n const frame = this.findFrameElement(element, submitter);\n\n if (frame.kpop) {\n FrameModal.visit(url, frame.kpop, frame, () => {\n TurboFrameController._navigateFrame.call(this, element, url, submitter);\n });\n } else {\n TurboFrameController._navigateFrame.call(this, element, url, submitter);\n }\n };\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nimport DEBUG from \"../debug\";\n\n/**\n * Scrim controller wraps an element that creates a whole page layer.\n * It is intended to be used behind a modal or nav drawer.\n *\n * If the Scrim element receives a click event, it automatically triggers \"scrim:hide\".\n *\n * You can show and hide the scrim programmatically by calling show/hide on the controller, e.g. using an outlet.\n *\n * If you need to respond to the scrim showing or hiding you should subscribe to \"scrim:show\" and \"scrim:hide\".\n */\nexport default class ScrimController extends Controller {\n static values = {\n open: Boolean,\n captive: Boolean,\n zIndex: Number,\n };\n\n connect() {\n if (DEBUG) console.debug(\"scrim:connect\");\n\n this.defaultZIndexValue = this.zIndexValue;\n this.defaultCaptiveValue = this.captiveValue;\n\n this.element.scrim = this;\n }\n\n disconnect() {\n if (DEBUG) console.debug(\"scrim:disconnect\");\n\n delete this.element.scrim;\n }\n\n async show({\n captive = this.defaultCaptiveValue,\n zIndex = this.defaultZIndexValue,\n top = window.scrollY,\n animate = true,\n } = {}) {\n if (DEBUG) console.debug(\"scrim:before-show\");\n\n // hide the scrim before opening the new one if it's already open\n if (this.openValue) {\n await this.hide({ animate });\n }\n\n // update internal state\n this.openValue = true;\n\n // notify listeners of pending request\n this.dispatch(\"show\", { bubbles: true });\n\n if (DEBUG) console.debug(\"scrim:show-start\");\n\n // update state, perform style updates\n this.#show(captive, zIndex, top);\n\n if (animate) {\n // animate opening\n // this will trigger an animationEnd event via CSS that completes the open\n this.element.dataset.showAnimating = \"\";\n\n await new Promise((resolve) => {\n this.element.addEventListener(\"animationend\", () => resolve(), {\n once: true,\n });\n });\n\n delete this.element.dataset.showAnimating;\n }\n\n if (DEBUG) console.debug(\"scrim:show-end\");\n }\n\n async hide({ animate = true } = {}) {\n if (!this.openValue || this.element.dataset.hideAnimating) return;\n\n if (DEBUG) console.debug(\"scrim:before-hide\");\n\n // notify listeners of pending request\n this.dispatch(\"hide\", { bubbles: true });\n\n if (DEBUG) console.debug(\"scrim:hide-start\");\n\n if (animate) {\n // set animation state\n // this will trigger an animationEnd event via CSS that completes the hide\n this.element.dataset.hideAnimating = \"\";\n\n await new Promise((resolve) => {\n this.element.addEventListener(\"animationend\", () => resolve(), {\n once: true,\n });\n });\n\n delete this.element.dataset.hideAnimating;\n }\n\n this.#hide();\n\n this.openValue = false;\n\n if (DEBUG) console.debug(\"scrim:hide-end\");\n }\n\n dismiss(event) {\n if (DEBUG) console.debug(\"scrim:dismiss\");\n\n if (!this.captiveValue) this.dispatch(\"dismiss\", { bubbles: true });\n }\n\n escape(event) {\n if (\n event.key === \"Escape\" &&\n !this.captiveValue &&\n !event.defaultPrevented\n ) {\n this.dispatch(\"dismiss\", { bubbles: true });\n }\n }\n\n /**\n * Clips body to viewport size and sets the z-index\n */\n #show(captive, zIndex, top) {\n this.captiveValue = captive;\n this.zIndexValue = zIndex;\n this.scrollY = top;\n\n this.previousPosition = document.body.style.position;\n this.previousTop = document.body.style.top;\n\n this.element.style.zIndex = this.zIndexValue;\n document.body.style.top = `-${top}px`;\n document.body.style.position = \"fixed\";\n }\n\n /**\n * Unclips body from viewport size and unsets the z-index\n */\n #hide() {\n this.captiveValue = this.defaultCaptiveValue;\n this.zIndexValue = this.defaultZIndexValue;\n\n resetStyle(this.element, \"z-index\", null);\n resetStyle(document.body, \"position\", null);\n resetStyle(document.body, \"top\", null);\n\n window.scrollTo({ left: 0, top: this.scrollY, behavior: \"instant\" });\n\n delete this.scrollY;\n delete this.previousPosition;\n delete this.previousTop;\n }\n}\n\nfunction resetStyle(element, property, previousValue) {\n if (previousValue) {\n element.style.setProperty(property, previousValue);\n } else {\n element.style.removeProperty(property);\n }\n}\n","import { Turbo } from \"@hotwired/turbo-rails\";\n\nimport { Modal } from \"./modal\";\n\nexport class StreamModal extends Modal {\n constructor(id, action) {\n super(id);\n\n this.action = action;\n }\n\n /**\n * When the modal opens, push a state event for the current location so that\n * the user can dismiss the modal by navigating back.\n *\n * @returns {Promise<void>}\n */\n async open() {\n await super.open();\n\n window.history.pushState({ kpop: true, id: this.id }, \"\", window.location);\n }\n\n /**\n * On dismiss, pop the state event that was pushed when the modal opened,\n * then clear any modals from the turbo frame element.\n *\n * @returns {Promise<void>}\n */\n async dismiss() {\n await super.dismiss();\n\n if (this.isCurrentLocation) {\n await this.pop(\"popstate\", () => window.history.back());\n }\n\n this.frameElement.innerHTML = \"\";\n }\n\n /**\n * On navigation from inside the modal, dismiss the modal first so that the\n * modal does not appear in the history stack.\n *\n * @param frame TurboFrame element\n * @param e Turbo navigation event\n */\n beforeVisit(frame, e) {\n super.beforeVisit(frame, e);\n\n e.preventDefault();\n\n frame.dismiss({ animate: false }).then(() => {\n Turbo.visit(e.detail.url);\n\n this.debug(\"before-visit-end\");\n });\n }\n\n /**\n * If the user pops state, dismiss the modal.\n *\n * @param frame FrameController\n * @param e history event\n */\n popstate(frame, e) {\n super.popstate(frame, e);\n\n frame.dismiss({ animate: true, reason: \"popstate\" });\n }\n\n get isCurrentLocation() {\n return window.history.state?.kpop && window.history.state?.id === this.id;\n }\n}\n","import DEBUG from \"../debug\";\n\nexport class StreamRenderer {\n constructor(frame, action) {\n this.frame = frame;\n this.action = action;\n }\n\n render() {\n if (DEBUG) console.debug(\"stream-renderer:render\");\n this.frame.src = \"\";\n this.frame.innerHTML = \"\";\n this.frame.append(this.action.templateContent);\n }\n}\n","import { Turbo } from \"@hotwired/turbo-rails\";\n\nimport DEBUG from \"./debug\";\n\nimport { StreamModal } from \"./modals/stream_modal\";\nimport { StreamRenderer } from \"./utils/stream_renderer\";\n\nfunction kpop(action) {\n return action.targetElements[0]?.kpop;\n}\n\nTurbo.StreamActions.kpop_open = function () {\n const animate = !kpop(this).openValue;\n\n kpop(this)\n ?.dismiss({ animate, reason: \"before-turbo-stream\" })\n .then(() => {\n new StreamRenderer(this.targetElements[0], this).render();\n kpop(this)?.open(new StreamModal(this.target, this), { animate });\n });\n};\n\nTurbo.StreamActions.kpop_dismiss = function () {\n kpop(this)?.dismiss({ reason: \"turbo_stream.kpop.dismiss\" });\n};\n\nTurbo.StreamActions.kpop_redirect_to = function () {\n if (this.dataset.turboFrame === this.target) {\n if (DEBUG)\n console.debug(\n `kpop: redirecting ${this.target} to ${this.getAttribute(\"href\")}`,\n );\n const a = document.createElement(\"A\");\n a.setAttribute(\"data-turbo-action\", \"replace\");\n this.targetElements[0].delegate.navigateFrame(a, this.getAttribute(\"href\"));\n } else {\n if (DEBUG)\n console.debug(`kpop: redirecting to ${this.getAttribute(\"href\")}`);\n Turbo.visit(this.getAttribute(\"href\"), {\n action: this.dataset.turboAction,\n });\n }\n};\n","import FrameController from \"../kpop/controllers/frame_controller\";\nimport ModalController from \"../kpop/controllers/modal_controller\";\nimport ScrimController from \"../kpop/controllers/scrim_controller\";\n\nimport \"./turbo_actions\";\n\nconst Definitions = [\n { identifier: \"kpop--frame\", controllerConstructor: FrameController },\n { identifier: \"kpop--modal\", controllerConstructor: ModalController },\n { identifier: \"scrim\", controllerConstructor: ScrimController },\n];\n\nexport { Definitions as default };\n","import { Controller } from \"@hotwired/stimulus\";\n\nimport DEBUG from \"../debug\";\n\nexport default class Kpop__ModalController extends Controller {\n static values = {\n fallback_location: String,\n layout: String,\n };\n\n connect() {\n this.debug(\"connect\");\n\n if (this.layoutValue) {\n document.querySelector(\"#kpop\").classList.toggle(this.layoutValue, true);\n }\n }\n\n disconnect() {\n this.debug(\"disconnect\");\n\n if (this.layoutValue) {\n document.querySelector(\"#kpop\").classList.toggle(this.layoutValue, false);\n }\n }\n\n debug(event, ...args) {\n if (DEBUG) console.debug(`ModalController:${event}`, ...args);\n }\n}\n"],"names":["Modal","constructor","id","this","open","debug","dismiss","beforeVisit","frame","e","detail","url","popstate","state","pop","event","callback","promise","Promise","resolve","window","addEventListener","once","frameElement","document","getElementById","controller","kpop","modalElement","querySelector","currentLocationValue","dataset","fallbackLocationValue","isCurrentLocation","history","turbo","Turbo","session","location","href","src","args","ContentModal","connect","element","animate","super","fallbackLocation","visitStarted","innerHTML","replaceState","scrimOutlet","hide","URL","toString","baseURI","FrameModal","modal","console","warn","clear","visit","hasAttribute","back","preventDefault","then","Kpop__FrameController","Controller","static","Boolean","TurboFrameController","delegate","prototype","_navigateFrame","navigateFrame","submitter","findFrameElement","call","installNavigationInterception","complete","modalElements","length","pathname","disconnect","scrimOutletConnected","scrim","scrimConnected","openValue","show","openValueChanged","parentElement","style","display","isOpen","dismissing","opening","nextFrame","reason","forEach","remove","beforeFrameRender","newFrame","resume","beforeStreamRender","render","stream","frameLoad","querySelectorAll","isConnected","requestAnimationFrame","ScrimController","captive","zIndex","Number","defaultZIndexValue","zIndexValue","defaultCaptiveValue","captiveValue","top","scrollY","dispatch","bubbles","showAnimating","hideAnimating","escape","key","defaultPrevented","previousPosition","body","position","previousTop","resetStyle","scrollTo","left","behavior","property","previousValue","setProperty","removeProperty","StreamModal","action","pushState","StreamRenderer","append","templateContent","targetElements","StreamActions","kpop_open","target","kpop_dismiss","kpop_redirect_to","turboFrame","a","createElement","setAttribute","getAttribute","turboAction","Definitions","identifier","controllerConstructor","FrameController","fallback_location","String","layout","layoutValue","classList","toggle"],"mappings":"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"}
1
+ {"version":3,"file":"kpop.min.js","sources":["../../../javascript/kpop/modals/modal.js","../../../javascript/kpop/modals/content_modal.js","../../../javascript/kpop/modals/frame_modal.js","../../../javascript/kpop/controllers/frame_controller.js","../../../javascript/kpop/controllers/scrim_controller.js","../../../javascript/kpop/modals/stream_modal.js","../../../javascript/kpop/utils/stream_renderer.js","../../../javascript/kpop/turbo_actions.js","../../../javascript/kpop/application.js","../../../javascript/kpop/controllers/modal_controller.js"],"sourcesContent":["import { Turbo } from \"@hotwired/turbo-rails\";\n\nimport DEBUG from \"../debug\";\n\nexport class Modal {\n constructor(id) {\n this.id = id;\n }\n\n async open() {\n this.debug(\"open\");\n }\n\n async dismiss() {\n this.debug(`dismiss`);\n }\n\n beforeVisit(frame, e) {\n this.debug(`before-visit`, e.detail.url);\n }\n\n popstate(frame, e) {\n this.debug(`popstate`, e.state);\n }\n\n async pop(event, callback) {\n this.debug(`pop`);\n\n const promise = new Promise((resolve) => {\n window.addEventListener(\n event,\n () => {\n resolve();\n },\n { once: true },\n );\n });\n\n callback();\n\n return promise;\n }\n\n get frameElement() {\n return document.getElementById(this.id);\n }\n\n get controller() {\n return this.frameElement?.kpop;\n }\n\n get modalElement() {\n return this.frameElement?.querySelector(\"[data-controller*='kpop--modal']\");\n }\n\n get currentLocationValue() {\n return this.modalElement?.dataset[\"kpop-ModalCurrentLocationValue\"] || \"/\";\n }\n\n get fallbackLocationValue() {\n return this.modalElement?.dataset[\"kpop-ModalFallbackLocationValue\"];\n }\n\n get isCurrentLocation() {\n return (\n window.history.state?.turbo && Turbo.session.location.href === this.src\n );\n }\n\n static debug(event, ...args) {\n if (DEBUG) console.debug(`${this.name}:${event}`, ...args);\n }\n\n debug(event, ...args) {\n if (DEBUG) console.debug(`${this.constructor.name}:${event}`, ...args);\n }\n}\n","import { Turbo } from \"@hotwired/turbo-rails\";\n\nimport { Modal } from \"./modal\";\n\nexport class ContentModal extends Modal {\n static connect(frame, element) {\n frame.open(new ContentModal(element.id), { animate: false });\n }\n\n constructor(id, src = null) {\n super(id);\n\n if (src) this.src = src;\n }\n\n /**\n * When the modal is dismissed we can't rely on a back navigation to close the\n * modal as the user may have navigated to a different location. Instead we\n * remove the content from the dom and replace the current history state with\n * the fallback location, if set.\n *\n * If there is no fallback location, we may be showing a stream modal that was\n * injected and cached by turbo. In this case, we clear the frame element and\n * do not change history.\n *\n * @returns {Promise<void>}\n */\n async dismiss() {\n const fallbackLocation = this.fallbackLocationValue;\n\n await super.dismiss();\n\n if (this.visitStarted) {\n this.debug(\"skipping dismiss, visit started\");\n return;\n }\n if (!this.isCurrentLocation) {\n this.debug(\"skipping dismiss, not current location\");\n return;\n }\n\n this.frameElement.innerHTML = \"\";\n\n if (fallbackLocation) {\n window.history.replaceState(window.history.state, \"\", fallbackLocation);\n }\n }\n\n beforeVisit(frame, e) {\n super.beforeVisit(frame, e);\n\n this.visitStarted = true;\n\n frame.scrimOutlet.hide({ animate: false });\n }\n\n get src() {\n return new URL(\n this.currentLocationValue.toString(),\n document.baseURI,\n ).toString();\n }\n}\n","import { Turbo } from \"@hotwired/turbo-rails\";\n\nimport { Modal } from \"./modal\";\n\nexport class FrameModal extends Modal {\n /**\n * When the FrameController detects a frame element on connect, it runs this\n * method to sanity check the frame src and restore the modal state.\n *\n * @param frame FrameController\n * @param element TurboFrame element\n */\n static connect(frame, element) {\n const modal = new FrameModal(element.id, element.src);\n\n // state reconciliation for turbo restore of invalid frames\n if (modal.isCurrentLocation) {\n // restoration visit\n this.debug(\"restore\", element.src);\n return frame.open(modal, { animate: false });\n } else {\n console.warn(\n \"kpop: restored frame src doesn't match window href\",\n modal.src,\n window.location.href,\n );\n return frame.clear();\n }\n }\n\n /**\n * When a user clicks a kpop link, turbo intercepts the click and calls\n * #navigateFrame on the turbo frame controller before setting the TurboFrame\n * element's src attribute. KPOP intercepts this call and calls this method\n * first so we cancel problematic navigations that might cache invalid states.\n *\n * @param location URL requested by turbo\n * @param frame FrameController\n * @param element TurboFrame element\n * @param resolve continuation chain\n */\n static visit(location, frame, element, resolve) {\n // Ensure that turbo doesn't cache the frame in a loading state by cancelling\n // the current request (if any) by clearing the src.\n // Known issue: this won't work if the frame was previously rendering a useful src.\n if (element.hasAttribute(\"busy\")) {\n this.debug(\"clearing src to cancel turbo request\");\n element.src = \"\";\n }\n\n if (element.src === location) {\n this.debug(\"skipping navigate as already on location\");\n return;\n }\n\n if (element.src && element.src !== window.location.href) {\n console.warn(\n \"kpop: frame src doesn't match window\",\n element.src,\n window.location.href,\n location,\n );\n frame.clear();\n }\n\n this.debug(\"navigate to\", location);\n resolve();\n }\n\n constructor(id, src) {\n super(id);\n this.src = src;\n }\n\n /**\n * FrameModals are closed by running pop state and awaiting the turbo:load\n * event that follows on history restoration.\n *\n * @returns {Promise<void>}\n */\n async dismiss() {\n await super.dismiss();\n\n if (!this.isCurrentLocation) {\n this.debug(\"skipping dismiss, not current location\");\n } else {\n await this.pop(\"turbo:load\", () => window.history.back());\n }\n\n // no specific close action required, this is turbo's responsibility\n }\n\n /**\n * When user navigates from inside a Frame modal, dismiss the modal first so\n * that the modal does not appear in the history stack.\n *\n * @param frame FrameController\n * @param e Turbo navigation event\n */\n beforeVisit(frame, e) {\n super.beforeVisit(frame, e);\n\n e.preventDefault();\n\n frame.dismiss({ animate: false }).then(() => {\n Turbo.visit(e.detail.url);\n\n this.debug(\"before-visit-end\");\n });\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nimport DEBUG from \"../debug\";\nimport { ContentModal } from \"../modals/content_modal\";\nimport { FrameModal } from \"../modals/frame_modal\";\n\nexport default class Kpop__FrameController extends Controller {\n static outlets = [\"scrim\"];\n static targets = [\"modal\"];\n static values = {\n open: Boolean,\n };\n\n connect() {\n this.debug(\"connect\", this.element.src);\n\n this.element.kpop = this;\n\n // allow our code to intercept frame navigation requests before dom changes\n installNavigationInterception(this);\n\n if (this.element.src && this.element.complete) {\n this.debug(\"new frame modal\", this.element.src);\n FrameModal.connect(this, this.element);\n } else if (this.modalElements.length > 0) {\n this.debug(\"new content modal\", window.location.pathname);\n ContentModal.connect(this, this.element);\n } else {\n this.debug(\"no modal\");\n this.clear();\n }\n }\n\n disconnect() {\n this.debug(\"disconnect\");\n\n delete this.element.kpop;\n delete this.modal;\n }\n\n scrimOutletConnected(scrim) {\n this.debug(\"scrim-connected\");\n\n this.scrimConnected = true;\n\n if (this.openValue) {\n scrim.show({ animate: false });\n } else {\n scrim.hide({ animate: false });\n }\n }\n\n openValueChanged(open) {\n this.debug(\"open-changed\", open);\n\n this.element.parentElement.style.display = open ? \"flex\" : \"none\";\n }\n\n async open(modal, { animate = true } = {}) {\n if (this.isOpen) {\n this.debug(\"skip open as already open\");\n this.modal ||= modal;\n return false;\n }\n\n await this.dismissing;\n\n return (this.opening ||= this.#nextFrame(() =>\n this.#open(modal, { animate }),\n ));\n }\n\n async dismiss({ animate = true, reason = \"\" } = {}) {\n if (!this.isOpen) {\n this.debug(\"skip dismiss as already closed\");\n return false;\n }\n\n await this.opening;\n\n return (this.dismissing ||= this.#nextFrame(() =>\n this.#dismiss({ animate, reason }),\n ));\n }\n\n async clear() {\n // clear the src from the frame (if any)\n this.element.src = \"\";\n\n // remove any open modal(s)\n this.modalElements.forEach((element) => element.remove());\n\n // mark the modal as hidden (will hide scrim on connect)\n this.openValue = false;\n\n // close the scrim, if connected\n if (this.scrimConnected) {\n return this.scrimOutlet.hide({ animate: false });\n }\n\n // unset modal\n this.modal = null;\n }\n\n // EVENTS\n\n popstate(event) {\n this.modal?.popstate(this, event);\n }\n\n /**\n * Incoming frame render, dismiss the current modal (if any) first.\n *\n * We're starting the actual visit\n *\n * @param event turbo:before-render\n */\n beforeFrameRender(event) {\n this.debug(\"before-frame-render\", event.detail.newFrame.baseURI);\n\n event.preventDefault();\n\n this.dismiss({ animate: true, reason: \"before-frame-render\" }).then(() => {\n this.debug(\"resume-frame-render\", event.detail.newFrame.baseURI);\n event.detail.resume();\n });\n }\n\n beforeStreamRender(event) {\n this.debug(\"before-stream-render\", event.detail);\n\n const resume = event.detail.render;\n\n // Defer rendering until dismiss is complete.\n // Dismiss may change history so we need to wait for it to complete to avoid\n // losing DOM changes on restoration visits.\n event.detail.render = (stream) => {\n (this.dismissing || Promise.resolve()).then(() => {\n this.debug(\"stream-render\", stream);\n resume(stream);\n });\n };\n }\n\n beforeVisit(e) {\n this.debug(\"before-visit\", e.detail.url);\n\n // ignore visits to the current frame, these fire when the frame navigates\n if (e.detail.url === this.element.src) return;\n\n // ignore unless we're open\n if (!this.isOpen) return;\n\n this.modal.beforeVisit(this, e);\n }\n\n frameLoad(event) {\n this.debug(\"frame-load\");\n\n const modal = new FrameModal(this.element.id, this.element.src);\n\n window.addEventListener(\n \"turbo:visit\",\n (e) => {\n this.open(modal, { animate: true });\n },\n { once: true },\n );\n }\n\n get isOpen() {\n return this.openValue && !this.dismissing;\n }\n\n get modalElements() {\n return this.element.querySelectorAll(\"[data-controller*='kpop--modal']\");\n }\n\n async #open(modal, { animate = true } = {}) {\n this.debug(\"open-start\", { animate });\n\n const scrim = this.scrimConnected && this.scrimOutlet;\n\n this.modal = modal;\n this.openValue = true;\n\n await modal.open({ animate });\n await scrim?.show({ animate });\n\n delete this.opening;\n\n this.debug(\"open-end\");\n }\n\n async #dismiss({ animate = true, reason = \"\" } = {}) {\n this.debug(\"dismiss-start\", { animate, reason });\n\n // if this element is detached then we've experienced a turbo navigation\n if (!this.element.isConnected) {\n this.debug(\"skip dismiss, element detached\");\n return;\n }\n\n if (!this.modal) {\n console.warn(\"modal missing on dismiss\");\n if (DEBUG) debugger;\n }\n\n await this.scrimOutlet.hide({ animate });\n await this.modal?.dismiss();\n\n this.openValue = false;\n this.modal = null;\n delete this.dismissing;\n\n this.debug(\"dismiss-end\");\n }\n\n async #nextFrame(callback) {\n return new Promise(window.requestAnimationFrame).then(callback);\n }\n\n debug(event, ...args) {\n if (DEBUG) console.debug(`FrameController:${event}`, ...args);\n }\n}\n\n/**\n * Monkey patch for Turbo#FrameController.\n *\n * Intercept calls to linkClickIntercepted(element, location) and ensures\n * that src is cleared if the frame is busy so that we don't restore an\n * in-progress src on restoration visits.\n *\n * See Turbo issue: https://github.com/hotwired/turbo/issues/1055\n *\n * @param controller FrameController\n */\nfunction installNavigationInterception(controller) {\n const TurboFrameController =\n controller.element.delegate.constructor.prototype;\n\n if (TurboFrameController._linkClickIntercepted) return;\n\n TurboFrameController._linkClickIntercepted =\n TurboFrameController.linkClickIntercepted;\n TurboFrameController.linkClickIntercepted = function (element, location) {\n // #findFrameElement\n const id =\n element?.getAttribute(\"data-turbo-frame\") ||\n this.element.getAttribute(\"target\");\n let frame = document.getElementById(id);\n if (!(frame instanceof Turbo.FrameElement)) {\n frame = this.element;\n }\n\n if (frame.kpop) {\n FrameModal.visit(location, frame.kpop, frame, () => {\n TurboFrameController._linkClickIntercepted.call(\n this,\n element,\n location,\n );\n });\n } else {\n TurboFrameController._linkClickIntercepted.call(this, element, location);\n }\n };\n}\n","import { Controller } from \"@hotwired/stimulus\";\n\nimport DEBUG from \"../debug\";\n\n/**\n * Scrim controller wraps an element that creates a whole page layer.\n * It is intended to be used behind a modal or nav drawer.\n *\n * If the Scrim element receives a click event, it automatically triggers \"scrim:hide\".\n *\n * You can show and hide the scrim programmatically by calling show/hide on the controller, e.g. using an outlet.\n *\n * If you need to respond to the scrim showing or hiding you should subscribe to \"scrim:show\" and \"scrim:hide\".\n */\nexport default class ScrimController extends Controller {\n static values = {\n open: Boolean,\n captive: Boolean,\n zIndex: Number,\n };\n\n connect() {\n if (DEBUG) console.debug(\"scrim:connect\");\n\n this.defaultZIndexValue = this.zIndexValue;\n this.defaultCaptiveValue = this.captiveValue;\n\n this.element.scrim = this;\n }\n\n disconnect() {\n if (DEBUG) console.debug(\"scrim:disconnect\");\n\n delete this.element.scrim;\n }\n\n async show({\n captive = this.defaultCaptiveValue,\n zIndex = this.defaultZIndexValue,\n top = window.scrollY,\n animate = true,\n } = {}) {\n if (DEBUG) console.debug(\"scrim:before-show\");\n\n // hide the scrim before opening the new one if it's already open\n if (this.openValue) {\n await this.hide({ animate });\n }\n\n // update internal state\n this.openValue = true;\n\n // notify listeners of pending request\n this.dispatch(\"show\", { bubbles: true });\n\n if (DEBUG) console.debug(\"scrim:show-start\");\n\n // update state, perform style updates\n this.#show(captive, zIndex, top);\n\n if (animate) {\n // animate opening\n // this will trigger an animationEnd event via CSS that completes the open\n this.element.dataset.showAnimating = \"\";\n\n await new Promise((resolve) => {\n this.element.addEventListener(\"animationend\", () => resolve(), {\n once: true,\n });\n });\n\n delete this.element.dataset.showAnimating;\n }\n\n if (DEBUG) console.debug(\"scrim:show-end\");\n }\n\n async hide({ animate = true } = {}) {\n if (!this.openValue || this.element.dataset.hideAnimating) return;\n\n if (DEBUG) console.debug(\"scrim:before-hide\");\n\n // notify listeners of pending request\n this.dispatch(\"hide\", { bubbles: true });\n\n if (DEBUG) console.debug(\"scrim:hide-start\");\n\n if (animate) {\n // set animation state\n // this will trigger an animationEnd event via CSS that completes the hide\n this.element.dataset.hideAnimating = \"\";\n\n await new Promise((resolve) => {\n this.element.addEventListener(\"animationend\", () => resolve(), {\n once: true,\n });\n });\n\n delete this.element.dataset.hideAnimating;\n }\n\n this.#hide();\n\n this.openValue = false;\n\n if (DEBUG) console.debug(\"scrim:hide-end\");\n }\n\n dismiss(event) {\n if (DEBUG) console.debug(\"scrim:dismiss\");\n\n if (!this.captiveValue) this.dispatch(\"dismiss\", { bubbles: true });\n }\n\n escape(event) {\n if (\n event.key === \"Escape\" &&\n !this.captiveValue &&\n !event.defaultPrevented\n ) {\n this.dispatch(\"dismiss\", { bubbles: true });\n }\n }\n\n /**\n * Clips body to viewport size and sets the z-index\n */\n #show(captive, zIndex, top) {\n this.captiveValue = captive;\n this.zIndexValue = zIndex;\n this.scrollY = top;\n\n this.previousPosition = document.body.style.position;\n this.previousTop = document.body.style.top;\n\n this.element.style.zIndex = this.zIndexValue;\n document.body.style.top = `-${top}px`;\n document.body.style.position = \"fixed\";\n }\n\n /**\n * Unclips body from viewport size and unsets the z-index\n */\n #hide() {\n this.captiveValue = this.defaultCaptiveValue;\n this.zIndexValue = this.defaultZIndexValue;\n\n resetStyle(this.element, \"z-index\", null);\n resetStyle(document.body, \"position\", null);\n resetStyle(document.body, \"top\", null);\n\n window.scrollTo({ left: 0, top: this.scrollY, behavior: \"instant\" });\n\n delete this.scrollY;\n delete this.previousPosition;\n delete this.previousTop;\n }\n}\n\nfunction resetStyle(element, property, previousValue) {\n if (previousValue) {\n element.style.setProperty(property, previousValue);\n } else {\n element.style.removeProperty(property);\n }\n}\n","import { Turbo } from \"@hotwired/turbo-rails\";\n\nimport { Modal } from \"./modal\";\n\nexport class StreamModal extends Modal {\n constructor(id, action) {\n super(id);\n\n this.action = action;\n }\n\n /**\n * When the modal opens, push a state event for the current location so that\n * the user can dismiss the modal by navigating back.\n *\n * @returns {Promise<void>}\n */\n async open() {\n await super.open();\n\n window.history.pushState({ kpop: true, id: this.id }, \"\", window.location);\n }\n\n /**\n * On dismiss, pop the state event that was pushed when the modal opened,\n * then clear any modals from the turbo frame element.\n *\n * @returns {Promise<void>}\n */\n async dismiss() {\n await super.dismiss();\n\n if (this.isCurrentLocation) {\n await this.pop(\"popstate\", () => window.history.back());\n }\n\n this.frameElement.innerHTML = \"\";\n }\n\n /**\n * On navigation from inside the modal, dismiss the modal first so that the\n * modal does not appear in the history stack.\n *\n * @param frame TurboFrame element\n * @param e Turbo navigation event\n */\n beforeVisit(frame, e) {\n super.beforeVisit(frame, e);\n\n e.preventDefault();\n\n frame.dismiss({ animate: false }).then(() => {\n Turbo.visit(e.detail.url);\n\n this.debug(\"before-visit-end\");\n });\n }\n\n /**\n * If the user pops state, dismiss the modal.\n *\n * @param frame FrameController\n * @param e history event\n */\n popstate(frame, e) {\n super.popstate(frame, e);\n\n frame.dismiss({ animate: true, reason: \"popstate\" });\n }\n\n get isCurrentLocation() {\n return window.history.state?.kpop && window.history.state?.id === this.id;\n }\n}\n","import DEBUG from \"../debug\";\n\nexport class StreamRenderer {\n constructor(frame, action) {\n this.frame = frame;\n this.action = action;\n }\n\n render() {\n if (DEBUG) console.debug(\"stream-renderer:render\");\n this.frame.src = \"\";\n this.frame.innerHTML = \"\";\n this.frame.append(this.action.templateContent);\n }\n}\n","import { Turbo } from \"@hotwired/turbo-rails\";\n\nimport DEBUG from \"./debug\";\n\nimport { StreamModal } from \"./modals/stream_modal\";\nimport { StreamRenderer } from \"./utils/stream_renderer\";\n\nfunction kpop(action) {\n return action.targetElements[0]?.kpop;\n}\n\nTurbo.StreamActions.kpop_open = function () {\n const animate = !kpop(this).openValue;\n\n kpop(this)\n ?.dismiss({ animate, reason: \"before-turbo-stream\" })\n .then(() => {\n new StreamRenderer(this.targetElements[0], this).render();\n kpop(this)?.open(new StreamModal(this.target, this), { animate });\n });\n};\n\nTurbo.StreamActions.kpop_dismiss = function () {\n kpop(this)?.dismiss({ reason: \"turbo_stream.kpop.dismiss\" });\n};\n\nTurbo.StreamActions.kpop_redirect_to = function () {\n if (this.dataset.turboFrame === this.target) {\n if (DEBUG)\n console.debug(\n `kpop: redirecting ${this.target} to ${this.getAttribute(\"href\")}`,\n );\n const a = document.createElement(\"A\");\n a.setAttribute(\"data-turbo-action\", \"replace\");\n this.targetElements[0].delegate.linkClickIntercepted(\n a,\n this.getAttribute(\"href\"),\n );\n } else {\n if (DEBUG)\n console.debug(`kpop: redirecting to ${this.getAttribute(\"href\")}`);\n Turbo.visit(this.getAttribute(\"href\"), {\n action: this.dataset.turboAction,\n });\n }\n};\n","import FrameController from \"../kpop/controllers/frame_controller\";\nimport ModalController from \"../kpop/controllers/modal_controller\";\nimport ScrimController from \"../kpop/controllers/scrim_controller\";\n\nimport \"./turbo_actions\";\n\nconst Definitions = [\n { identifier: \"kpop--frame\", controllerConstructor: FrameController },\n { identifier: \"kpop--modal\", controllerConstructor: ModalController },\n { identifier: \"scrim\", controllerConstructor: ScrimController },\n];\n\nexport { Definitions as default };\n","import { Controller } from \"@hotwired/stimulus\";\n\nimport DEBUG from \"../debug\";\n\nexport default class Kpop__ModalController extends Controller {\n static values = {\n fallback_location: String,\n layout: String,\n };\n\n connect() {\n this.debug(\"connect\");\n\n if (this.layoutValue) {\n document.querySelector(\"#kpop\").classList.toggle(this.layoutValue, true);\n }\n }\n\n disconnect() {\n this.debug(\"disconnect\");\n\n if (this.layoutValue) {\n document.querySelector(\"#kpop\").classList.toggle(this.layoutValue, false);\n }\n }\n\n debug(event, ...args) {\n if (DEBUG) console.debug(`ModalController:${event}`, ...args);\n }\n}\n"],"names":["Modal","constructor","id","this","open","debug","dismiss","beforeVisit","frame","e","detail","url","popstate","state","pop","event","callback","promise","Promise","resolve","window","addEventListener","once","frameElement","document","getElementById","controller","kpop","modalElement","querySelector","currentLocationValue","dataset","fallbackLocationValue","isCurrentLocation","history","turbo","Turbo","session","location","href","src","args","ContentModal","connect","element","animate","super","fallbackLocation","visitStarted","innerHTML","replaceState","scrimOutlet","hide","URL","toString","baseURI","FrameModal","modal","console","warn","clear","visit","hasAttribute","back","preventDefault","then","Kpop__FrameController","Controller","static","Boolean","TurboFrameController","delegate","prototype","_linkClickIntercepted","linkClickIntercepted","getAttribute","FrameElement","call","installNavigationInterception","complete","modalElements","length","pathname","disconnect","scrimOutletConnected","scrim","scrimConnected","openValue","show","openValueChanged","parentElement","style","display","isOpen","dismissing","opening","nextFrame","reason","forEach","remove","beforeFrameRender","newFrame","resume","beforeStreamRender","render","stream","frameLoad","querySelectorAll","isConnected","requestAnimationFrame","ScrimController","captive","zIndex","Number","defaultZIndexValue","zIndexValue","defaultCaptiveValue","captiveValue","top","scrollY","dispatch","bubbles","showAnimating","hideAnimating","escape","key","defaultPrevented","previousPosition","body","position","previousTop","resetStyle","scrollTo","left","behavior","property","previousValue","setProperty","removeProperty","StreamModal","action","pushState","StreamRenderer","append","templateContent","targetElements","StreamActions","kpop_open","target","kpop_dismiss","kpop_redirect_to","turboFrame","a","createElement","setAttribute","turboAction","Definitions","identifier","controllerConstructor","FrameController","fallback_location","String","layout","layoutValue","classList","toggle"],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Kpop Frame Requests use a different layout than Turbo Frame requests.
4
+ #
5
+ # The layout used is <tt>kpop/frame.html.erb</tt>. If there's a need to customize this layout, an application can
6
+ # supply its own (such as <tt>app/views/layouts/kpop/frame.html.erb</tt>) which will be used instead.
7
+ #
8
+ # This module is automatically included in <tt>ActionController::Base</tt>.
9
+ module Katalyst
10
+ module Kpop
11
+ module FrameRequest
12
+ extend ActiveSupport::Concern
13
+
14
+ class_methods do
15
+ # Example:
16
+ # require_kpop only: %i[new edit] { url_for(resource) }
17
+ def require_kpop(**constraints, &fallback_location)
18
+ define_method(:kpop_fallback_location, fallback_location) if fallback_location
19
+
20
+ before_action :require_kpop, **constraints
21
+ end
22
+ end
23
+
24
+ included do
25
+ layout -> { turbo_frame_layout }
26
+ end
27
+
28
+ private
29
+
30
+ def kpop_frame_request?
31
+ turbo_frame_request_id == "kpop"
32
+ end
33
+
34
+ def require_kpop
35
+ redirect_back(fallback_location: kpop_fallback_location, status: :see_other) unless kpop_frame_request?
36
+ end
37
+
38
+ def turbo_frame_layout
39
+ if kpop_frame_request?
40
+ "kpop/frame"
41
+ elsif turbo_frame_request?
42
+ "turbo_rails/frame"
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -228,9 +228,9 @@ export default class Kpop__FrameController extends Controller {
228
228
  /**
229
229
  * Monkey patch for Turbo#FrameController.
230
230
  *
231
- * Intercept calls to navigateFrame(element, location) and ensures that src is
232
- * cleared if the frame is busy so that we don't restore an in-progress src on
233
- * restoration visits.
231
+ * Intercept calls to linkClickIntercepted(element, location) and ensures
232
+ * that src is cleared if the frame is busy so that we don't restore an
233
+ * in-progress src on restoration visits.
234
234
  *
235
235
  * See Turbo issue: https://github.com/hotwired/turbo/issues/1055
236
236
  *
@@ -240,18 +240,30 @@ function installNavigationInterception(controller) {
240
240
  const TurboFrameController =
241
241
  controller.element.delegate.constructor.prototype;
242
242
 
243
- if (TurboFrameController._navigateFrame) return;
244
-
245
- TurboFrameController._navigateFrame = TurboFrameController.navigateFrame;
246
- TurboFrameController.navigateFrame = function (element, url, submitter) {
247
- const frame = this.findFrameElement(element, submitter);
243
+ if (TurboFrameController._linkClickIntercepted) return;
244
+
245
+ TurboFrameController._linkClickIntercepted =
246
+ TurboFrameController.linkClickIntercepted;
247
+ TurboFrameController.linkClickIntercepted = function (element, location) {
248
+ // #findFrameElement
249
+ const id =
250
+ element?.getAttribute("data-turbo-frame") ||
251
+ this.element.getAttribute("target");
252
+ let frame = document.getElementById(id);
253
+ if (!(frame instanceof Turbo.FrameElement)) {
254
+ frame = this.element;
255
+ }
248
256
 
249
257
  if (frame.kpop) {
250
- FrameModal.visit(url, frame.kpop, frame, () => {
251
- TurboFrameController._navigateFrame.call(this, element, url, submitter);
258
+ FrameModal.visit(location, frame.kpop, frame, () => {
259
+ TurboFrameController._linkClickIntercepted.call(
260
+ this,
261
+ element,
262
+ location,
263
+ );
252
264
  });
253
265
  } else {
254
- TurboFrameController._navigateFrame.call(this, element, url, submitter);
266
+ TurboFrameController._linkClickIntercepted.call(this, element, location);
255
267
  }
256
268
  };
257
269
  }
@@ -30,7 +30,7 @@ export class FrameModal extends Modal {
30
30
 
31
31
  /**
32
32
  * When a user clicks a kpop link, turbo intercepts the click and calls
33
- * navigateFrame on the turbo frame controller before setting the TurboFrame
33
+ * #navigateFrame on the turbo frame controller before setting the TurboFrame
34
34
  * element's src attribute. KPOP intercepts this call and calls this method
35
35
  * first so we cancel problematic navigations that might cache invalid states.
36
36
  *
@@ -32,7 +32,10 @@ Turbo.StreamActions.kpop_redirect_to = function () {
32
32
  );
33
33
  const a = document.createElement("A");
34
34
  a.setAttribute("data-turbo-action", "replace");
35
- this.targetElements[0].delegate.navigateFrame(a, this.getAttribute("href"));
35
+ this.targetElements[0].delegate.linkClickIntercepted(
36
+ a,
37
+ this.getAttribute("href"),
38
+ );
36
39
  } else {
37
40
  if (DEBUG)
38
41
  console.debug(`kpop: redirecting to ${this.getAttribute("href")}`);
@@ -0,0 +1,13 @@
1
+ <html>
2
+ <head>
3
+ <%# include all turbo-track elements so turbo knows it can cache frame visits %>
4
+ <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
5
+ <%= javascript_importmap_tags %>
6
+ <%= yield :head %>
7
+ </head>
8
+ <body>
9
+ <%= render Kpop::FrameComponent.new do %>
10
+ <%= yield %>
11
+ <% end %>
12
+ </body>
13
+ </html>
@@ -6,7 +6,13 @@ require "turbo-rails"
6
6
  module Katalyst
7
7
  module Kpop
8
8
  class Engine < ::Rails::Engine
9
- config.autoload_once_paths = %W(#{root}/app/helpers)
9
+ isolate_namespace Katalyst::Kpop
10
+ config.eager_load_namespaces << Katalyst::Kpop
11
+ config.autoload_once_paths = %W(
12
+ #{root}/app/helpers
13
+ #{root}/app/controllers
14
+ #{root}/app/controllers/concerns
15
+ )
10
16
 
11
17
  initializer "kpop.assets" do
12
18
  config.after_initialize do |app|
@@ -22,6 +28,7 @@ module Katalyst
22
28
  end
23
29
 
24
30
  ActiveSupport.on_load(:action_controller_base) do
31
+ include Katalyst::Kpop::FrameRequest
25
32
  helper Katalyst::Kpop::Engine.helpers
26
33
  end
27
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: katalyst-kpop
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Katalyst Interactive
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-19 00:00:00.000000000 Z
11
+ date: 2024-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: katalyst-html-attributes
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: turbo-rails
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "<"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '2'
33
+ version: '0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "<"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '2'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: view_component
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -81,6 +81,7 @@ files:
81
81
  - app/components/kpop/modal_component.html.erb
82
82
  - app/components/kpop/modal_component.rb
83
83
  - app/components/scrim_component.rb
84
+ - app/controllers/concerns/katalyst/kpop/frame_request.rb
84
85
  - app/helpers/kpop_helper.rb
85
86
  - app/javascript/kpop/application.js
86
87
  - app/javascript/kpop/controllers/frame_controller.js
@@ -93,7 +94,7 @@ files:
93
94
  - app/javascript/kpop/modals/stream_modal.js
94
95
  - app/javascript/kpop/turbo_actions.js
95
96
  - app/javascript/kpop/utils/stream_renderer.js
96
- - app/views/layouts/kpop.html.erb
97
+ - app/views/layouts/kpop/frame.html.erb
97
98
  - config/importmap.rb
98
99
  - config/routes.rb
99
100
  - lib/katalyst/kpop.rb
@@ -1,4 +0,0 @@
1
- <%# controller layout, for use with turbo responses %>
2
- <%= render Kpop::FrameComponent.new do %>
3
- <%= yield %>
4
- <% end %>