katalyst-kpop 2.0.9 → 3.0.0.beta.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -43
  3. data/app/assets/builds/katalyst/kpop.esm.js +590 -0
  4. data/app/assets/builds/katalyst/kpop.js +466 -515
  5. data/app/assets/builds/katalyst/kpop.min.js +2 -1
  6. data/app/assets/builds/katalyst/kpop.min.js.map +1 -0
  7. data/app/assets/builds/katalyst/kpop.umd.js +5890 -0
  8. data/app/assets/config/kpop.js +1 -1
  9. data/app/assets/stylesheets/katalyst/kpop/_frame.scss +104 -0
  10. data/app/assets/stylesheets/katalyst/kpop/_modal.scss +95 -0
  11. data/app/assets/stylesheets/katalyst/kpop/_scrim.scss +33 -3
  12. data/app/assets/stylesheets/katalyst/kpop/_side_panel.scss +64 -0
  13. data/app/assets/stylesheets/katalyst/kpop/_variables.scss +25 -0
  14. data/app/assets/stylesheets/katalyst/kpop.scss +6 -1
  15. data/app/components/concerns/kpop/has_html_attributes.rb +78 -0
  16. data/app/components/kpop/frame_component.html.erb +14 -0
  17. data/app/components/kpop/frame_component.rb +46 -0
  18. data/app/components/kpop/modal/title_component.html.erb +6 -0
  19. data/app/components/kpop/modal/title_component.rb +28 -0
  20. data/app/components/kpop/modal_component.html.erb +8 -0
  21. data/app/components/kpop/modal_component.rb +39 -0
  22. data/app/components/scrim_component.rb +32 -0
  23. data/app/helpers/kpop_helper.rb +12 -35
  24. data/app/javascript/kpop/application.js +13 -0
  25. data/app/javascript/kpop/controllers/frame_controller.js +178 -0
  26. data/app/javascript/kpop/controllers/modal_controller.js +30 -0
  27. data/app/{assets/javascripts → javascript/kpop}/controllers/scrim_controller.js +76 -72
  28. data/app/javascript/kpop/debug.js +3 -0
  29. data/app/javascript/kpop/modals/content_modal.js +46 -0
  30. data/app/javascript/kpop/modals/frame_modal.js +41 -0
  31. data/app/javascript/kpop/modals/modal.js +69 -0
  32. data/app/javascript/kpop/modals/stream_modal.js +49 -0
  33. data/app/javascript/kpop/turbo_actions.js +33 -0
  34. data/app/javascript/kpop/utils/stream_renderer.js +15 -0
  35. data/app/views/layouts/kpop.html.erb +1 -1
  36. data/config/importmap.rb +1 -4
  37. data/lib/katalyst/kpop/engine.rb +13 -12
  38. data/lib/katalyst/kpop/matchers/base.rb +18 -0
  39. data/lib/katalyst/kpop/matchers/capybara_matcher.rb +46 -0
  40. data/lib/katalyst/kpop/matchers/capybara_parser.rb +17 -0
  41. data/lib/katalyst/kpop/matchers/chained_matcher.rb +40 -0
  42. data/lib/katalyst/kpop/matchers/frame_matcher.rb +16 -0
  43. data/lib/katalyst/kpop/matchers/modal_matcher.rb +20 -0
  44. data/lib/katalyst/kpop/matchers/redirect_matcher.rb +28 -0
  45. data/lib/katalyst/kpop/matchers/response_matcher.rb +33 -0
  46. data/lib/katalyst/kpop/matchers/stream_matcher.rb +16 -0
  47. data/lib/katalyst/kpop/matchers/title_finder.rb +16 -0
  48. data/lib/katalyst/kpop/matchers/title_matcher.rb +28 -0
  49. data/lib/katalyst/kpop/matchers.rb +79 -0
  50. data/lib/katalyst/kpop/turbo.rb +50 -0
  51. data/lib/katalyst/kpop/version.rb +1 -1
  52. data/lib/katalyst/kpop.rb +4 -0
  53. metadata +88 -15
  54. data/app/assets/builds/katalyst/kpop.css +0 -117
  55. data/app/assets/javascripts/controllers/kpop_controller.js +0 -72
  56. data/app/assets/javascripts/katalyst/kpop.js +0 -9
  57. data/app/assets/stylesheets/katalyst/kpop/_index.scss +0 -2
  58. data/app/assets/stylesheets/katalyst/kpop/_kpop.scss +0 -133
  59. data/app/helpers/kpop/modal.rb +0 -98
  60. data/app/helpers/scrim_helper.rb +0 -13
@@ -0,0 +1,178 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ import DEBUG from "../debug";
4
+ import { ContentModal } from "../modals/content_modal";
5
+ import { FrameModal } from "../modals/frame_modal";
6
+
7
+ export default class Kpop__FrameController extends Controller {
8
+ static outlets = ["scrim"];
9
+ static targets = ["modal"];
10
+ static values = {
11
+ open: Boolean,
12
+ };
13
+
14
+ connect() {
15
+ this.debug("connect", this.element.src);
16
+
17
+ this.element.kpop = this;
18
+
19
+ // restoration visit
20
+ if (this.element.src && this.element.complete) {
21
+ this.debug("new frame modal", this.element.src);
22
+ this.open(new FrameModal(this.element.id, this.element.src), {
23
+ animate: false,
24
+ });
25
+ } else {
26
+ const element = this.element.querySelector(
27
+ "[data-controller*='kpop--modal']"
28
+ );
29
+ if (element) {
30
+ this.debug("new content modal", window.location.pathname);
31
+ this.open(new ContentModal(this.element.id), { animate: false });
32
+ }
33
+ }
34
+ }
35
+
36
+ disconnect() {
37
+ this.debug("disconnect");
38
+
39
+ delete this.element.kpop;
40
+ delete this.modal;
41
+ }
42
+
43
+ scrimOutletConnected(scrim) {
44
+ this.debug("scrim-connected");
45
+
46
+ this.scrimConnected = true;
47
+
48
+ if (this.openValue) scrim.show({ animate: false });
49
+ }
50
+
51
+ openValueChanged(open) {
52
+ this.debug("open-changed", open);
53
+
54
+ this.element.parentElement.style.display = open ? "flex" : "none";
55
+ }
56
+
57
+ async open(modal, { animate = true } = {}) {
58
+ if (this.isOpen) {
59
+ this.debug("skip open as already open");
60
+ return false;
61
+ }
62
+
63
+ return (this.opening ||= this.#nextFrame(() =>
64
+ this.#open(modal, { animate })
65
+ ));
66
+ }
67
+
68
+ async dismiss({ animate = true, reason = "" } = {}) {
69
+ if (!this.isOpen) {
70
+ this.debug("skip dismiss as already closed");
71
+ return false;
72
+ }
73
+
74
+ return (this.dismissing ||= this.#nextFrame(() =>
75
+ this.#dismiss({ animate, reason })
76
+ ));
77
+ }
78
+
79
+ // EVENTS
80
+
81
+ popstate(event) {
82
+ this.modal?.popstate(this, event);
83
+ }
84
+
85
+ beforeFrameRender(event) {
86
+ this.debug("before-frame-render", event.detail.newFrame.baseURI);
87
+
88
+ event.preventDefault();
89
+
90
+ this.dismiss({ animate: true, reason: "before-frame-render" }).then(() => {
91
+ this.debug("resume-frame-render", event.detail.newFrame.baseURI);
92
+ event.detail.resume();
93
+ });
94
+ }
95
+
96
+ beforeStreamRender(event) {
97
+ this.debug("before-stream-render", event.detail);
98
+
99
+ const resume = event.detail.render;
100
+
101
+ // Defer rendering until dismiss is complete.
102
+ // Dismiss may change history so we need to wait for it to complete to avoid
103
+ // losing DOM changes on restoration visits.
104
+ event.detail.render = (stream) => {
105
+ (this.dismissing || Promise.resolve()).then(() => {
106
+ this.debug("stream-render", stream);
107
+ resume(stream);
108
+ });
109
+ };
110
+ }
111
+
112
+ beforeVisit(e) {
113
+ this.debug("before-visit", e.detail.url);
114
+
115
+ // ignore visits to the current frame, these fire when the frame navigates
116
+ if (e.detail.url === this.element.src) return;
117
+
118
+ // ignore unless we're open
119
+ if (!this.isOpen) return;
120
+
121
+ this.modal.beforeVisit(this, e);
122
+ }
123
+
124
+ frameLoad(event) {
125
+ this.debug("frame-load");
126
+
127
+ return this.open(new FrameModal(this.element.id, this.element.src), {
128
+ animate: true,
129
+ });
130
+ }
131
+
132
+ get isOpen() {
133
+ return this.openValue && !this.dismissing;
134
+ }
135
+
136
+ async #open(modal, { animate = true } = {}) {
137
+ this.debug("open-start", { animate });
138
+
139
+ const scrim = this.scrimConnected && this.scrimOutlet;
140
+
141
+ this.modal = modal;
142
+ this.openValue = true;
143
+
144
+ await modal.open({ animate });
145
+ await scrim?.show({ animate });
146
+
147
+ delete this.opening;
148
+
149
+ this.debug("open-end");
150
+ }
151
+
152
+ async #dismiss({ animate = true, reason = "" } = {}) {
153
+ this.debug("dismiss-start", { animate, reason });
154
+
155
+ if (!this.modal) {
156
+ console.warn("modal missing on dismiss");
157
+ if (DEBUG) debugger;
158
+ }
159
+
160
+ await this.scrimOutlet.hide({ animate });
161
+ await this.modal?.dismiss();
162
+
163
+ this.openValue = false;
164
+ this.modal = null;
165
+ delete this.dismissing;
166
+
167
+ this.debug("dismiss-end");
168
+ }
169
+
170
+ async #nextFrame(callback) {
171
+ // return Promise.resolve().then(callback);
172
+ return new Promise(window.requestAnimationFrame).then(callback);
173
+ }
174
+
175
+ debug(event, ...args) {
176
+ if (DEBUG) console.debug(`FrameController:${event}`, ...args);
177
+ }
178
+ }
@@ -0,0 +1,30 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ import DEBUG from "../debug";
4
+
5
+ export default class Kpop__ModalController extends Controller {
6
+ static values = {
7
+ fallback_location: String,
8
+ layout: String,
9
+ };
10
+
11
+ connect() {
12
+ this.debug("connect");
13
+
14
+ if (this.layoutValue) {
15
+ document.querySelector("#kpop").classList.toggle(this.layoutValue, true);
16
+ }
17
+ }
18
+
19
+ disconnect() {
20
+ this.debug("disconnect");
21
+
22
+ if (this.layoutValue) {
23
+ document.querySelector("#kpop").classList.toggle(this.layoutValue, false);
24
+ }
25
+ }
26
+
27
+ debug(event, ...args) {
28
+ if (DEBUG) console.debug(`ModalController:${event}`, ...args);
29
+ }
30
+ }
@@ -1,6 +1,6 @@
1
1
  import { Controller } from "@hotwired/stimulus";
2
2
 
3
- const DEBUG = false;
3
+ import DEBUG from "../debug";
4
4
 
5
5
  /**
6
6
  * Scrim controller wraps an element that creates a whole page layer.
@@ -8,8 +8,7 @@ const DEBUG = false;
8
8
  *
9
9
  * If the Scrim element receives a click event, it automatically triggers "scrim:hide".
10
10
  *
11
- * You can show and hide the scrim programmatically by sending "scrim:request:show" and "scrim:request:hide" events to
12
- * the window or by calling the provided methods.
11
+ * You can show and hide the scrim programmatically by calling show/hide on the controller, e.g. using an outlet.
13
12
  *
14
13
  * If you need to respond to the scrim showing or hiding you should subscribe to "scrim:show" and "scrim:hide".
15
14
  */
@@ -20,109 +19,113 @@ export default class ScrimController extends Controller {
20
19
  zIndex: Number,
21
20
  };
22
21
 
23
- /**
24
- * Show the scrim element. Returns true if successful.
25
- */
26
- static showScrim({
27
- dismiss = true,
28
- zIndex = undefined,
29
- top = undefined,
30
- } = {}) {
31
- return window.dispatchEvent(
32
- new CustomEvent("scrim:request:show", {
33
- cancelable: true,
34
- detail: { captive: !dismiss, zIndex: zIndex, top: top },
35
- })
36
- );
37
- }
38
-
39
- /**
40
- * Hide the scrim element. Returns true if successful.
41
- */
42
- static hideScrim() {
43
- return window.dispatchEvent(
44
- new CustomEvent("scrim:request:hide", { cancelable: true })
45
- );
46
- }
47
-
48
22
  connect() {
23
+ if (DEBUG) console.debug("scrim:connect");
24
+
49
25
  this.defaultZIndexValue = this.zIndexValue;
50
26
  this.defaultCaptiveValue = this.captiveValue;
27
+
28
+ this.element.scrim = this;
51
29
  }
52
30
 
53
- show(request) {
54
- if (DEBUG) console.debug("request show scrim");
31
+ disconnect() {
32
+ if (DEBUG) console.debug("scrim:disconnect");
55
33
 
56
- // hide the scrim before opening the new one if it's already open
57
- if (this.openValue) this.hide(request);
34
+ delete this.element.scrim;
35
+ }
58
36
 
59
- // if the scrim is still open, abort
60
- if (this.openValue) return;
37
+ async show({
38
+ captive = this.defaultCaptiveValue,
39
+ zIndex = this.defaultZIndexValue,
40
+ top = window.scrollY,
41
+ animate = true,
42
+ } = {}) {
43
+ if (DEBUG) console.debug("scrim:before-show");
61
44
 
62
- // update internal state to break event cycles
45
+ // hide the scrim before opening the new one if it's already open
46
+ if (this.openValue) {
47
+ await this.hide({ animate });
48
+ }
49
+
50
+ // update internal state
63
51
  this.openValue = true;
64
52
 
65
53
  // notify listeners of pending request
66
- const event = this.dispatch("show", { bubbles: true, cancelable: true });
54
+ this.dispatch("show", { bubbles: true });
67
55
 
68
- // if notification was cancelled, update request and abort
69
- if (event.defaultPrevented) {
70
- this.openValue = false;
71
- request.preventDefault();
72
- return;
73
- }
56
+ if (DEBUG) console.debug("scrim:show-start");
57
+
58
+ // update state, perform style updates
59
+ this.#show(captive, zIndex, top);
74
60
 
75
- if (DEBUG) console.debug("show scrim");
61
+ if (animate) {
62
+ // animate opening
63
+ // this will trigger an animationEnd event via CSS that completes the open
64
+ this.element.dataset.showAnimating = "";
76
65
 
77
- // perform show updates
78
- this.#show(request.detail);
79
- }
66
+ await new Promise((resolve) => {
67
+ this.element.addEventListener("animationend", () => resolve(), {
68
+ once: true,
69
+ });
70
+ });
71
+
72
+ delete this.element.dataset.showAnimating;
73
+ }
80
74
 
81
- hide(request) {
82
- if (!this.openValue) return;
75
+ if (DEBUG) console.debug("scrim:show-end");
76
+ }
83
77
 
84
- if (DEBUG) console.debug("request hide scrim");
78
+ async hide({ animate = true } = {}) {
79
+ if (!this.openValue || this.element.dataset.hideAnimating) return;
85
80
 
86
- // update internal state to break event cycles
87
- this.openValue = false;
81
+ if (DEBUG) console.debug("scrim:before-hide");
88
82
 
89
83
  // notify listeners of pending request
90
- const event = this.dispatch("hide", { bubbles: true, cancelable: true });
84
+ this.dispatch("hide", { bubbles: true });
91
85
 
92
- // if notification was cancelled, update request and abort
93
- if (event.defaultPrevented) {
94
- this.openValue = true;
95
- request.preventDefault();
96
- return;
97
- }
86
+ if (DEBUG) console.debug("scrim:hide-start");
98
87
 
99
- if (DEBUG) console.debug("hide scrim");
88
+ if (animate) {
89
+ // set animation state
90
+ // this will trigger an animationEnd event via CSS that completes the hide
91
+ this.element.dataset.hideAnimating = "";
92
+
93
+ await new Promise((resolve) => {
94
+ this.element.addEventListener("animationend", () => resolve(), {
95
+ once: true,
96
+ });
97
+ });
98
+
99
+ delete this.element.dataset.hideAnimating;
100
+ }
100
101
 
101
- // update state, perform style updates
102
102
  this.#hide();
103
+
104
+ this.openValue = false;
105
+
106
+ if (DEBUG) console.debug("scrim:hide-end");
103
107
  }
104
108
 
105
109
  dismiss(event) {
106
- if (!this.captiveValue) this.hide(event);
107
- }
110
+ if (DEBUG) console.debug("scrim:dismiss");
108
111
 
109
- escape(event) {
110
- if (event.key === "Escape" && !this.captiveValue && !event.defaultPrevented)
111
- this.hide(event);
112
+ if (!this.captiveValue) this.dispatch("dismiss", { bubbles: true });
112
113
  }
113
114
 
114
- disconnect() {
115
- super.disconnect();
115
+ escape(event) {
116
+ if (
117
+ event.key === "Escape" &&
118
+ !this.captiveValue &&
119
+ !event.defaultPrevented
120
+ ) {
121
+ this.dispatch("dismiss", { bubbles: true });
122
+ }
116
123
  }
117
124
 
118
125
  /**
119
126
  * Clips body to viewport size and sets the z-index
120
127
  */
121
- #show({
122
- captive = this.defaultCaptiveValue,
123
- zIndex = this.defaultZIndexValue,
124
- top = window.scrollY,
125
- }) {
128
+ #show(captive, zIndex, top) {
126
129
  this.captiveValue = captive;
127
130
  this.zIndexValue = zIndex;
128
131
  this.scrollY = top;
@@ -145,6 +148,7 @@ export default class ScrimController extends Controller {
145
148
  resetStyle(this.element, "z-index", null);
146
149
  resetStyle(document.body, "position", null);
147
150
  resetStyle(document.body, "top", null);
151
+
148
152
  window.scrollTo({ left: 0, top: this.scrollY, behavior: "instant" });
149
153
 
150
154
  delete this.scrollY;
@@ -0,0 +1,3 @@
1
+ const DEBUG = false;
2
+
3
+ export default DEBUG;
@@ -0,0 +1,46 @@
1
+ import { Turbo } from "@hotwired/turbo-rails";
2
+
3
+ import { Modal } from "./modal";
4
+
5
+ export class ContentModal extends Modal {
6
+ constructor(id, src = null) {
7
+ super(id);
8
+
9
+ if (src) this.src = src;
10
+ }
11
+
12
+ async dismiss() {
13
+ await super.dismiss();
14
+
15
+ if (this.visitStarted) {
16
+ this.debug("skipping dismiss, visit started");
17
+ return;
18
+ }
19
+ if (!this.isCurrentLocation) {
20
+ this.debug("skipping dismiss, not current location");
21
+ return;
22
+ }
23
+
24
+ return this.pop("turbo:load", () => {
25
+ this.debug("turbo-visit", this.fallbackLocationValue);
26
+ Turbo.visit(this.fallbackLocationValue, { action: "replace" });
27
+ });
28
+
29
+ // no specific close action required, this is turbo's responsibility
30
+ }
31
+
32
+ beforeVisit(frame, e) {
33
+ super.beforeVisit(frame, e);
34
+
35
+ this.visitStarted = true;
36
+
37
+ frame.scrimOutlet.hide({ animate: false });
38
+ }
39
+
40
+ get src() {
41
+ return new URL(
42
+ this.currentLocationValue.toString(),
43
+ document.baseURI
44
+ ).toString();
45
+ }
46
+ }
@@ -0,0 +1,41 @@
1
+ import { Turbo } from "@hotwired/turbo-rails";
2
+
3
+ import { Modal } from "./modal";
4
+
5
+ export class FrameModal extends Modal {
6
+ constructor(id, src) {
7
+ super(id);
8
+ this.src = src;
9
+ }
10
+
11
+ async dismiss() {
12
+ await super.dismiss();
13
+
14
+ if (!this.isCurrentLocation) {
15
+ this.debug("skipping dismiss, not current location");
16
+ }
17
+
18
+ await this.pop("turbo:load", () => window.history.back());
19
+
20
+ // no specific close action required, this is turbo's responsibility
21
+ }
22
+
23
+ beforeVisit(frame, e) {
24
+ super.beforeVisit(frame, e);
25
+
26
+ e.preventDefault();
27
+
28
+ frame.dismiss({ animate: false }).then(() => {
29
+ Turbo.visit(e.detail.url);
30
+
31
+ this.debug("before-visit-end");
32
+ });
33
+ }
34
+
35
+ popstate(frame, e) {
36
+ super.popstate(frame, e);
37
+
38
+ // Turbo will restore modal state, but we need to reset the scrim
39
+ frame.scrimOutlet.hide({ animate: false });
40
+ }
41
+ }
@@ -0,0 +1,69 @@
1
+ import { Turbo } from "@hotwired/turbo-rails";
2
+
3
+ import DEBUG from "../debug";
4
+
5
+ export class Modal {
6
+ constructor(id) {
7
+ this.id = id;
8
+ }
9
+
10
+ async open() {
11
+ this.debug("open");
12
+ }
13
+
14
+ async dismiss() {
15
+ this.debug(`dismiss`);
16
+ }
17
+
18
+ beforeVisit(frame, e) {
19
+ this.debug(`before-visit`, e.detail.url);
20
+ }
21
+
22
+ popstate(frame, e) {
23
+ this.debug(`popstate`, e.state);
24
+ }
25
+
26
+ async pop(event, callback) {
27
+ this.debug(`pop`);
28
+
29
+ const promise = new Promise((resolve) => {
30
+ window.addEventListener(
31
+ event,
32
+ () => {
33
+ resolve();
34
+ },
35
+ { once: true }
36
+ );
37
+ });
38
+
39
+ callback();
40
+
41
+ return promise;
42
+ }
43
+
44
+ get frameElement() {
45
+ return document.getElementById(this.id);
46
+ }
47
+
48
+ get modalElement() {
49
+ return this.frameElement?.querySelector("[data-controller*='kpop--modal']");
50
+ }
51
+
52
+ get currentLocationValue() {
53
+ return this.modalElement?.dataset["kpop-ModalCurrentLocationValue"] || "/";
54
+ }
55
+
56
+ get fallbackLocationValue() {
57
+ return this.modalElement?.dataset["kpop-ModalFallbackLocationValue"] || "/";
58
+ }
59
+
60
+ get isCurrentLocation() {
61
+ return (
62
+ window.history.state?.turbo && Turbo.session.location.href === this.src
63
+ );
64
+ }
65
+
66
+ debug(event, ...args) {
67
+ if (DEBUG) console.debug(`${this.constructor.name}:${event}`, ...args);
68
+ }
69
+ }
@@ -0,0 +1,49 @@
1
+ import { Turbo } from "@hotwired/turbo-rails";
2
+
3
+ import { Modal } from "./modal";
4
+
5
+ export class StreamModal extends Modal {
6
+ constructor(id, action) {
7
+ super(id);
8
+
9
+ this.action = action;
10
+ }
11
+
12
+ async open() {
13
+ await super.open();
14
+
15
+ window.history.pushState({ kpop: true, id: this.id }, "", window.location);
16
+ }
17
+
18
+ async dismiss() {
19
+ await super.dismiss();
20
+
21
+ if (this.isCurrentLocation) {
22
+ await this.pop("popstate", () => window.history.back());
23
+ }
24
+
25
+ this.frameElement.innerHTML = "";
26
+ }
27
+
28
+ beforeVisit(frame, e) {
29
+ super.beforeVisit(frame, e);
30
+
31
+ e.preventDefault();
32
+
33
+ frame.dismiss({ animate: false }).then(() => {
34
+ Turbo.visit(e.detail.url);
35
+
36
+ this.debug("before-visit-end");
37
+ });
38
+ }
39
+
40
+ popstate(frame, e) {
41
+ super.popstate(frame, e);
42
+
43
+ frame.dismiss({ animate: true, reason: "popstate" });
44
+ }
45
+
46
+ get isCurrentLocation() {
47
+ return window.history.state?.kpop && window.history.state?.id === this.id;
48
+ }
49
+ }
@@ -0,0 +1,33 @@
1
+ import { Turbo } from "@hotwired/turbo-rails";
2
+
3
+ import { StreamModal } from "./modals/stream_modal";
4
+ import { StreamRenderer } from "./utils/stream_renderer";
5
+
6
+ function kpop(action) {
7
+ return action.targetElements[0]?.kpop;
8
+ }
9
+
10
+ Turbo.StreamActions.kpop_open = function () {
11
+ const animate = !kpop(this).openValue;
12
+
13
+ kpop(this)
14
+ ?.dismiss({ animate, reason: "before-turbo-stream" })
15
+ .then(() => {
16
+ new StreamRenderer(this.targetElements[0], this).render();
17
+ kpop(this)?.open(new StreamModal(this.target, this), { animate });
18
+ });
19
+ };
20
+
21
+ Turbo.StreamActions.kpop_dismiss = function () {
22
+ kpop(this)?.dismiss({ reason: "turbo_stream.kpop.dismiss" });
23
+ };
24
+
25
+ Turbo.StreamActions.kpop_redirect_to = function () {
26
+ if (this.dataset.turboFrame === this.target) {
27
+ this.targetElements[0].src = this.getAttribute("href");
28
+ } else {
29
+ Turbo.visit(this.getAttribute("href"), {
30
+ action: this.dataset.turboAction,
31
+ });
32
+ }
33
+ };
@@ -0,0 +1,15 @@
1
+ import DEBUG from "../debug";
2
+
3
+ export class StreamRenderer {
4
+ constructor(frame, action) {
5
+ this.frame = frame;
6
+ this.action = action;
7
+ }
8
+
9
+ render() {
10
+ if (DEBUG) console.debug("stream-renderer:render");
11
+ this.frame.src = "";
12
+ this.frame.innerHTML = "";
13
+ this.frame.append(this.action.templateContent);
14
+ }
15
+ }
@@ -1,4 +1,4 @@
1
1
  <%# controller layout, for use with turbo responses %>
2
- <%= kpop_frame_tag do %>
2
+ <%= render Kpop::FrameComponent.new do %>
3
3
  <%= yield %>
4
4
  <% end %>
data/config/importmap.rb CHANGED
@@ -1,6 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- pin_all_from Katalyst::Kpop::Engine.root.join("app/assets/javascripts/controllers"),
4
- under: "controllers",
5
- # preload in tests so that we don't start clicking before controllers load
6
- preload: Rails.env.test?
3
+ pin "@katalyst/kpop", to: "katalyst/kpop.js", preload: true