katalyst-kpop 3.4.0 → 4.0.0.beta.1
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 +4 -4
- data/README.md +92 -74
- data/app/assets/builds/katalyst/kpop.esm.js +463 -457
- data/app/assets/builds/katalyst/kpop.js +463 -457
- data/app/assets/builds/katalyst/kpop.min.js +1 -1
- data/app/assets/builds/katalyst/kpop.min.js.map +1 -1
- data/app/assets/stylesheets/katalyst/kpop.css +69 -0
- data/app/components/kpop/frame_component.html.erb +3 -14
- data/app/components/kpop/frame_component.rb +15 -11
- data/app/components/kpop/modal_component.html.erb +7 -6
- data/app/components/kpop/modal_component.rb +9 -32
- data/app/controllers/concerns/katalyst/kpop/frame_request.rb +67 -8
- data/app/javascript/kpop/application.js +68 -7
- data/app/javascript/kpop/controllers/frame_controller.js +96 -66
- data/app/javascript/kpop/modals/content_modal.js +2 -58
- data/app/javascript/kpop/modals/frame_modal.js +19 -76
- data/app/javascript/kpop/modals/modal.js +96 -49
- data/app/javascript/kpop/modals/stream_modal.js +11 -62
- data/app/javascript/kpop/utils/debug.js +22 -0
- data/app/javascript/kpop/utils/link_observer.js +151 -0
- data/app/javascript/kpop/utils/ruleset.js +43 -0
- data/app/javascript/kpop/utils/stream_actions.js +21 -0
- data/app/views/layouts/kpop/frame.html.erb +3 -1
- data/app/views/layouts/kpop/stream.html.erb +3 -0
- data/lib/katalyst/kpop/engine.rb +1 -8
- data/lib/katalyst/kpop/matchers/modal_matcher.rb +1 -1
- data/lib/katalyst/kpop/matchers/src_matcher.rb +33 -0
- data/lib/katalyst/kpop/matchers.rb +11 -40
- metadata +8 -19
- data/app/assets/stylesheets/katalyst/kpop/_frame.scss +0 -90
- data/app/assets/stylesheets/katalyst/kpop/_modal.scss +0 -88
- data/app/assets/stylesheets/katalyst/kpop/_scrim.scss +0 -46
- data/app/assets/stylesheets/katalyst/kpop/_side_panel.scss +0 -64
- data/app/assets/stylesheets/katalyst/kpop/_variables.scss +0 -24
- data/app/assets/stylesheets/katalyst/kpop.scss +0 -6
- data/app/components/kpop/modal/footer_component.rb +0 -21
- data/app/components/kpop/modal/header_component.rb +0 -21
- data/app/components/kpop/modal/title_component.html.erb +0 -6
- data/app/components/kpop/modal/title_component.rb +0 -28
- data/app/components/scrim_component.rb +0 -32
- data/app/helpers/kpop_helper.rb +0 -32
- data/app/javascript/kpop/controllers/modal_controller.js +0 -30
- data/app/javascript/kpop/controllers/scrim_controller.js +0 -159
- data/app/javascript/kpop/debug.js +0 -3
- data/app/javascript/kpop/turbo_actions.js +0 -46
- data/app/javascript/kpop/utils/stream_renderer.js +0 -15
- data/lib/katalyst/kpop/turbo.rb +0 -49
|
@@ -1,135 +1,153 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
import { Turbo as Turbo$1 } from '@hotwired/turbo-rails';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
constructor(id) {
|
|
6
|
-
this.id = id;
|
|
7
|
-
}
|
|
4
|
+
let enabled = false;
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
const debug = function (receiver) {
|
|
7
|
+
if (enabled) {
|
|
8
|
+
return console.debug.bind(console, "[%s] %s", receiver);
|
|
9
|
+
} else {
|
|
10
|
+
return noop;
|
|
11
11
|
}
|
|
12
|
+
};
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
this.debug(`dismiss`);
|
|
15
|
-
}
|
|
14
|
+
const noop = () => {};
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
Object.defineProperty(debug, "enabled", {
|
|
17
|
+
get: function () {
|
|
18
|
+
return enabled;
|
|
19
|
+
},
|
|
20
|
+
set: function (debug) {
|
|
21
|
+
enabled = debug;
|
|
22
|
+
},
|
|
23
|
+
});
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
class Modal {
|
|
26
|
+
constructor(frame, dialog, src = null) {
|
|
27
|
+
this.frame = frame;
|
|
28
|
+
this.element = dialog;
|
|
29
|
+
this.uri = new URL(src || dialog.dataset.src, window.location.origin);
|
|
23
30
|
}
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
this.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
window.addEventListener(
|
|
30
|
-
event,
|
|
31
|
-
() => {
|
|
32
|
-
resolve();
|
|
33
|
-
},
|
|
34
|
-
{ once: true },
|
|
35
|
-
);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
callback();
|
|
39
|
-
|
|
40
|
-
return promise;
|
|
32
|
+
connect() {
|
|
33
|
+
this.element.addEventListener("cancel", this.cancel);
|
|
34
|
+
this.element.addEventListener("close", this.close);
|
|
35
|
+
this.element.addEventListener("mousedown", this.scrim);
|
|
41
36
|
}
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
disconnect() {
|
|
39
|
+
this.element.removeEventListener("cancel", this.cancel);
|
|
40
|
+
this.element.removeEventListener("close", this.close);
|
|
41
|
+
this.element.removeEventListener("mousedown", this.scrim);
|
|
45
42
|
}
|
|
46
43
|
|
|
47
|
-
get
|
|
48
|
-
return this.
|
|
44
|
+
get src() {
|
|
45
|
+
return this.uri.pathname + this.uri.search + this.uri.hash;
|
|
49
46
|
}
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
48
|
+
cancel = (e) => {
|
|
49
|
+
this.debug("event:cancel", e);
|
|
54
50
|
|
|
55
|
-
|
|
56
|
-
return this.modalElement?.dataset["kpop-ModalCurrentLocationValue"] || "/";
|
|
57
|
-
}
|
|
51
|
+
e.preventDefault();
|
|
58
52
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
53
|
+
this.frame.dismiss({ animate: true, reason: "dialog:cancel" });
|
|
54
|
+
};
|
|
62
55
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
window.history.state?.turbo && Turbo$1.session.location.href === this.src
|
|
66
|
-
);
|
|
67
|
-
}
|
|
56
|
+
close = (e) => {
|
|
57
|
+
this.debug("event:close", e);
|
|
68
58
|
|
|
69
|
-
|
|
70
|
-
}
|
|
59
|
+
this.frame.clear({ reason: "dialog:close" });
|
|
60
|
+
};
|
|
71
61
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
62
|
+
scrim = (e) => {
|
|
63
|
+
if (e.target.tagName === "DIALOG") {
|
|
64
|
+
this.debug("event:scrim", e);
|
|
75
65
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
66
|
+
this.frame.dismiss({ animate: true, reason: "dialog:scrim" });
|
|
67
|
+
}
|
|
68
|
+
};
|
|
80
69
|
|
|
81
|
-
|
|
82
|
-
|
|
70
|
+
async open({ animate = true } = {}) {
|
|
71
|
+
this.debug("open-start", animate);
|
|
83
72
|
|
|
84
|
-
|
|
73
|
+
await animation(this.element, animate, () => this.element.showModal());
|
|
74
|
+
|
|
75
|
+
this.debug("open-end");
|
|
85
76
|
}
|
|
86
77
|
|
|
87
78
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* remove the content from the dom and replace the current history state with
|
|
91
|
-
* the fallback location, if set.
|
|
92
|
-
*
|
|
93
|
-
* If there is no fallback location, we may be showing a stream modal that was
|
|
94
|
-
* injected and cached by turbo. In this case, we clear the frame element and
|
|
95
|
-
* do not change history.
|
|
79
|
+
* Modals are closed by animating out the modal then removing the modal
|
|
80
|
+
* element from the wrapping frame.
|
|
96
81
|
*
|
|
97
82
|
* @returns {Promise<void>}
|
|
98
83
|
*/
|
|
99
|
-
async dismiss() {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
await super.dismiss();
|
|
84
|
+
async dismiss({ animate = true } = {}) {
|
|
85
|
+
this.debug("dismiss-start", animate);
|
|
103
86
|
|
|
104
|
-
|
|
105
|
-
this.
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
if (!this.isCurrentLocation) {
|
|
109
|
-
this.debug("skipping dismiss, not current location");
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
87
|
+
await animation(this.element, animate, () =>
|
|
88
|
+
this.element.removeAttribute("open"),
|
|
89
|
+
);
|
|
112
90
|
|
|
113
|
-
this.
|
|
91
|
+
this.debug("dismiss-end");
|
|
114
92
|
|
|
115
|
-
|
|
116
|
-
window.history.replaceState(window.history.state, "", fallbackLocation);
|
|
117
|
-
}
|
|
93
|
+
this.element.close();
|
|
118
94
|
}
|
|
119
95
|
|
|
96
|
+
/**
|
|
97
|
+
* When user navigates from inside a modal, dismiss the modal first so
|
|
98
|
+
* that the modal does not appear in the history stack.
|
|
99
|
+
*
|
|
100
|
+
* @param frame FrameController
|
|
101
|
+
* @param e Turbo navigation event
|
|
102
|
+
*/
|
|
120
103
|
beforeVisit(frame, e) {
|
|
121
|
-
|
|
104
|
+
this.debug(`before-visit`, e.detail.url);
|
|
122
105
|
|
|
123
|
-
this.
|
|
106
|
+
this.frame.clear();
|
|
107
|
+
}
|
|
124
108
|
|
|
125
|
-
|
|
109
|
+
static get debug() {
|
|
110
|
+
return debug(this.name);
|
|
126
111
|
}
|
|
127
112
|
|
|
128
|
-
get
|
|
129
|
-
return
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
113
|
+
get debug() {
|
|
114
|
+
return debug(this.constructor.name);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function animation(el, animate, trigger) {
|
|
119
|
+
if (!animate) return trigger();
|
|
120
|
+
|
|
121
|
+
const duration = animationDuration(el);
|
|
122
|
+
|
|
123
|
+
return new Promise((resolve) => {
|
|
124
|
+
const resolver = () => {
|
|
125
|
+
el.removeEventListener("animationend", resolver, { once: true });
|
|
126
|
+
clearTimeout(timeout);
|
|
127
|
+
el.toggleAttribute("animate", false);
|
|
128
|
+
resolve();
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
el.addEventListener("animationend", resolver, { once: true });
|
|
132
|
+
const timeout = setTimeout(resolver, duration);
|
|
133
|
+
|
|
134
|
+
el.toggleAttribute("animate", animate);
|
|
135
|
+
trigger();
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function animationDuration(el, defaultValue = "0.2s") {
|
|
140
|
+
const value =
|
|
141
|
+
getComputedStyle(el).getPropertyValue("--animation-duration") ||
|
|
142
|
+
defaultValue;
|
|
143
|
+
const num = parseFloat(value);
|
|
144
|
+
if (value.endsWith("ms")) return num;
|
|
145
|
+
return num * 1000;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
class ContentModal extends Modal {
|
|
149
|
+
static connect(frame, dialog) {
|
|
150
|
+
frame.open(new ContentModal(frame, dialog), { animate: false });
|
|
133
151
|
}
|
|
134
152
|
}
|
|
135
153
|
|
|
@@ -138,25 +156,27 @@ class FrameModal extends Modal {
|
|
|
138
156
|
* When the FrameController detects a frame element on connect, it runs this
|
|
139
157
|
* method to sanity check the frame src and restore the modal state.
|
|
140
158
|
*
|
|
141
|
-
* @param frame
|
|
142
|
-
* @param
|
|
159
|
+
* @param {Kpop__FrameController} frame
|
|
160
|
+
* @param {HTMLDialogElement} dialog
|
|
161
|
+
* @param {String} src
|
|
143
162
|
*/
|
|
144
|
-
static connect(frame,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
163
|
+
static connect(frame, dialog, src) {
|
|
164
|
+
// restoration visit
|
|
165
|
+
this.debug("restore", src);
|
|
166
|
+
return frame.open(new FrameModal(frame, dialog, src), { animate: false });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* When the FrameController detects a frame load event, it runs this
|
|
171
|
+
* method to open the modal.
|
|
172
|
+
*
|
|
173
|
+
* @param {Kpop__FrameController} frame
|
|
174
|
+
* @param {HTMLDialogElement} dialog
|
|
175
|
+
* @param {String} src
|
|
176
|
+
*/
|
|
177
|
+
static load(frame, dialog, src) {
|
|
178
|
+
this.debug("load", src);
|
|
179
|
+
return frame.open(new FrameModal(frame, dialog, src), { animate: true });
|
|
160
180
|
}
|
|
161
181
|
|
|
162
182
|
/**
|
|
@@ -179,71 +199,12 @@ class FrameModal extends Modal {
|
|
|
179
199
|
element.src = "";
|
|
180
200
|
}
|
|
181
201
|
|
|
182
|
-
if (element.src === location) {
|
|
183
|
-
this.debug("skipping navigate as already on location");
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (element.src && element.src !== window.location.href) {
|
|
188
|
-
console.warn(
|
|
189
|
-
"kpop: frame src doesn't match window",
|
|
190
|
-
element.src,
|
|
191
|
-
window.location.href,
|
|
192
|
-
location,
|
|
193
|
-
);
|
|
194
|
-
frame.clear();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
202
|
this.debug("navigate to", location);
|
|
198
203
|
resolve();
|
|
199
204
|
}
|
|
200
|
-
|
|
201
|
-
constructor(id, src) {
|
|
202
|
-
super(id);
|
|
203
|
-
this.src = src;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* FrameModals are closed by running pop state and awaiting the turbo:load
|
|
208
|
-
* event that follows on history restoration.
|
|
209
|
-
*
|
|
210
|
-
* @returns {Promise<void>}
|
|
211
|
-
*/
|
|
212
|
-
async dismiss() {
|
|
213
|
-
await super.dismiss();
|
|
214
|
-
|
|
215
|
-
if (!this.isCurrentLocation) {
|
|
216
|
-
this.debug("skipping dismiss, not current location");
|
|
217
|
-
} else {
|
|
218
|
-
await this.pop("turbo:load", () => window.history.back());
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// no specific close action required, this is turbo's responsibility
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* When user navigates from inside a Frame modal, dismiss the modal first so
|
|
226
|
-
* that the modal does not appear in the history stack.
|
|
227
|
-
*
|
|
228
|
-
* @param frame FrameController
|
|
229
|
-
* @param e Turbo navigation event
|
|
230
|
-
*/
|
|
231
|
-
beforeVisit(frame, e) {
|
|
232
|
-
super.beforeVisit(frame, e);
|
|
233
|
-
|
|
234
|
-
e.preventDefault();
|
|
235
|
-
|
|
236
|
-
frame.dismiss({ animate: false }).then(() => {
|
|
237
|
-
Turbo$1.visit(e.detail.url);
|
|
238
|
-
|
|
239
|
-
this.debug("before-visit-end");
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
205
|
}
|
|
243
206
|
|
|
244
207
|
class Kpop__FrameController extends Controller {
|
|
245
|
-
static outlets = ["scrim"];
|
|
246
|
-
static targets = ["modal"];
|
|
247
208
|
static values = {
|
|
248
209
|
open: Boolean,
|
|
249
210
|
};
|
|
@@ -256,12 +217,14 @@ class Kpop__FrameController extends Controller {
|
|
|
256
217
|
// allow our code to intercept frame navigation requests before dom changes
|
|
257
218
|
installNavigationInterception(this);
|
|
258
219
|
|
|
259
|
-
|
|
220
|
+
const dialog = this.element.querySelector("dialog");
|
|
221
|
+
|
|
222
|
+
if (this.element.src && dialog) {
|
|
260
223
|
this.debug("new frame modal", this.element.src);
|
|
261
|
-
FrameModal.connect(this, this.element);
|
|
262
|
-
} else if (
|
|
263
|
-
this.debug("new content modal",
|
|
264
|
-
ContentModal.connect(this,
|
|
224
|
+
FrameModal.connect(this, dialog, this.element.src).then(() => {});
|
|
225
|
+
} else if (dialog) {
|
|
226
|
+
this.debug("new content modal", dialog);
|
|
227
|
+
ContentModal.connect(this, dialog);
|
|
265
228
|
} else {
|
|
266
229
|
this.debug("no modal");
|
|
267
230
|
this.clear();
|
|
@@ -275,24 +238,18 @@ class Kpop__FrameController extends Controller {
|
|
|
275
238
|
delete this.modal;
|
|
276
239
|
}
|
|
277
240
|
|
|
278
|
-
scrimOutletConnected(scrim) {
|
|
279
|
-
this.debug("scrim-connected");
|
|
280
|
-
|
|
281
|
-
this.scrimConnected = true;
|
|
282
|
-
|
|
283
|
-
if (this.openValue) {
|
|
284
|
-
scrim.show({ animate: false });
|
|
285
|
-
} else {
|
|
286
|
-
scrim.hide({ animate: false });
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
241
|
openValueChanged(open) {
|
|
291
242
|
this.debug("open-changed", open);
|
|
292
|
-
|
|
293
|
-
this.element.parentElement.style.display = open ? "flex" : "none";
|
|
294
243
|
}
|
|
295
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Animate an attached modal into the foreground. Returns a promise that
|
|
247
|
+
* resolves when the animation is complete.
|
|
248
|
+
*
|
|
249
|
+
* @param modal
|
|
250
|
+
* @param animate
|
|
251
|
+
* @returns {Promise<Boolean>}
|
|
252
|
+
*/
|
|
296
253
|
async open(modal, { animate = true } = {}) {
|
|
297
254
|
if (this.isOpen) {
|
|
298
255
|
this.debug("skip open as already open");
|
|
@@ -302,12 +259,25 @@ class Kpop__FrameController extends Controller {
|
|
|
302
259
|
|
|
303
260
|
await this.dismissing;
|
|
304
261
|
|
|
305
|
-
return (this.opening ||=
|
|
306
|
-
|
|
307
|
-
|
|
262
|
+
return (this.opening ||= Promise.resolve().then(() => {
|
|
263
|
+
modal.connect();
|
|
264
|
+
return this.#open(modal, { animate });
|
|
265
|
+
}));
|
|
308
266
|
}
|
|
309
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Cause a modal to hide. Returns a promise that will resolve when the
|
|
270
|
+
* animation (if requested) is finished.
|
|
271
|
+
*
|
|
272
|
+
* If the modal is already animating out, returns the existing promise instead.
|
|
273
|
+
*
|
|
274
|
+
* @param {Boolean} animate
|
|
275
|
+
* @param {String} reason
|
|
276
|
+
* @returns {Promise}
|
|
277
|
+
*/
|
|
310
278
|
async dismiss({ animate = true, reason = "" } = {}) {
|
|
279
|
+
this.debug("event:dismiss", reason);
|
|
280
|
+
|
|
311
281
|
if (!this.isOpen) {
|
|
312
282
|
this.debug("skip dismiss as already closed");
|
|
313
283
|
return false;
|
|
@@ -315,36 +285,30 @@ class Kpop__FrameController extends Controller {
|
|
|
315
285
|
|
|
316
286
|
await this.opening;
|
|
317
287
|
|
|
318
|
-
return (this.dismissing ||= this.#
|
|
319
|
-
this.#dismiss({ animate, reason }),
|
|
320
|
-
));
|
|
288
|
+
return (this.dismissing ||= this.#dismiss({ animate, reason }));
|
|
321
289
|
}
|
|
322
290
|
|
|
323
|
-
|
|
291
|
+
/**
|
|
292
|
+
* Clean up after a modal is finished dismissing.
|
|
293
|
+
*/
|
|
294
|
+
clear({ reason = "" } = {}) {
|
|
295
|
+
this.debug("event:clear", reason);
|
|
296
|
+
|
|
324
297
|
// clear the src from the frame (if any)
|
|
325
298
|
this.element.src = "";
|
|
299
|
+
this.element.innerHTML = "";
|
|
326
300
|
|
|
327
|
-
//
|
|
328
|
-
this.modalElements.forEach((element) => element.remove());
|
|
329
|
-
|
|
330
|
-
// mark the modal as hidden (will hide scrim on connect)
|
|
301
|
+
// mark the modal as hidden
|
|
331
302
|
this.openValue = false;
|
|
332
303
|
|
|
333
|
-
// close the scrim, if connected
|
|
334
|
-
if (this.scrimConnected) {
|
|
335
|
-
return this.scrimOutlet.hide({ animate: false });
|
|
336
|
-
}
|
|
337
|
-
|
|
338
304
|
// unset modal
|
|
339
|
-
this.modal
|
|
305
|
+
if (this.modal) this.modal.disconnect();
|
|
306
|
+
delete this.modal;
|
|
307
|
+
delete this.dismissing;
|
|
340
308
|
}
|
|
341
309
|
|
|
342
310
|
// EVENTS
|
|
343
311
|
|
|
344
|
-
popstate(event) {
|
|
345
|
-
this.modal?.popstate(this, event);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
312
|
/**
|
|
349
313
|
* Incoming frame render, dismiss the current modal (if any) first.
|
|
350
314
|
*
|
|
@@ -388,53 +352,72 @@ class Kpop__FrameController extends Controller {
|
|
|
388
352
|
// ignore visits to the current frame, these fire when the frame navigates
|
|
389
353
|
if (e.detail.url === this.element.src) return;
|
|
390
354
|
|
|
355
|
+
const url = new URL(e.detail.url.toString(), document.baseURI);
|
|
356
|
+
if (url.pathname === "/resume_historical_location") {
|
|
357
|
+
e.preventDefault();
|
|
358
|
+
return this.dismiss();
|
|
359
|
+
}
|
|
360
|
+
|
|
391
361
|
// ignore unless we're open
|
|
392
362
|
if (!this.isOpen) return;
|
|
393
363
|
|
|
394
364
|
this.modal.beforeVisit(this, e);
|
|
395
365
|
}
|
|
396
366
|
|
|
397
|
-
frameLoad(
|
|
367
|
+
frameLoad(e) {
|
|
398
368
|
this.debug("frame-load");
|
|
399
369
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
window.addEventListener(
|
|
403
|
-
"turbo:visit",
|
|
404
|
-
(e) => {
|
|
405
|
-
this.open(modal, { animate: true });
|
|
406
|
-
},
|
|
407
|
-
{ once: true },
|
|
370
|
+
FrameModal.load(this, e.target.firstElementChild, e.target.src).then(
|
|
371
|
+
() => {},
|
|
408
372
|
);
|
|
409
373
|
}
|
|
410
374
|
|
|
411
|
-
|
|
412
|
-
|
|
375
|
+
/**
|
|
376
|
+
* Outgoing fetch request. Capture the initiator so we can return focus if it causes a modal to show.
|
|
377
|
+
*/
|
|
378
|
+
beforeFetchRequest() {
|
|
379
|
+
const focusElement = document.activeElement;
|
|
380
|
+
|
|
381
|
+
if (focusElement === document.body) {
|
|
382
|
+
delete this.lastFetchFocusRef;
|
|
383
|
+
} else {
|
|
384
|
+
this.lastFetchFocusRef = new WeakRef(focusElement);
|
|
385
|
+
}
|
|
413
386
|
}
|
|
414
387
|
|
|
415
|
-
get
|
|
416
|
-
return this.
|
|
388
|
+
get isOpen() {
|
|
389
|
+
return this.openValue && !this.dismissing;
|
|
417
390
|
}
|
|
418
391
|
|
|
419
392
|
async #open(modal, { animate = true } = {}) {
|
|
420
393
|
this.debug("open-start", { animate });
|
|
421
394
|
|
|
422
|
-
|
|
395
|
+
this.previousFocusRef =
|
|
396
|
+
document.activeElement === document.body
|
|
397
|
+
? this.lastFetchFocusRef
|
|
398
|
+
: new WeakRef(document.activeElement);
|
|
399
|
+
this.debug("capture focus", this.previousFocusRef?.deref());
|
|
423
400
|
|
|
424
401
|
this.modal = modal;
|
|
425
402
|
this.openValue = true;
|
|
426
403
|
|
|
404
|
+
// Set turbo-frame[src] without causing a load event
|
|
405
|
+
this.element.delegate.sourceURL = this.modal.src;
|
|
406
|
+
|
|
427
407
|
await modal.open({ animate });
|
|
428
|
-
await scrim?.show({ animate });
|
|
429
408
|
|
|
430
409
|
delete this.opening;
|
|
431
410
|
|
|
432
411
|
this.debug("open-end");
|
|
433
412
|
|
|
413
|
+
autofocus(this.modal?.element)?.focus();
|
|
414
|
+
|
|
434
415
|
// Detect https://github.com/hotwired/turbo-rails/issues/580
|
|
435
416
|
if (Turbo.session.view.forceReloaded) {
|
|
436
417
|
console.error("Turbo-Frame response is incompatible with current page");
|
|
437
418
|
}
|
|
419
|
+
|
|
420
|
+
return true;
|
|
438
421
|
}
|
|
439
422
|
|
|
440
423
|
async #dismiss({ animate = true, reason = "" } = {}) {
|
|
@@ -450,12 +433,13 @@ class Kpop__FrameController extends Controller {
|
|
|
450
433
|
console.warn("modal missing on dismiss");
|
|
451
434
|
}
|
|
452
435
|
|
|
453
|
-
await this.
|
|
454
|
-
await this.modal?.dismiss();
|
|
436
|
+
await this.modal?.dismiss({ animate });
|
|
455
437
|
|
|
456
|
-
this.
|
|
457
|
-
|
|
458
|
-
|
|
438
|
+
this.clear();
|
|
439
|
+
|
|
440
|
+
this.previousFocusRef?.deref()?.focus();
|
|
441
|
+
this.debug("restore focus", this.previousFocusRef?.deref());
|
|
442
|
+
delete this.previousFocusRef;
|
|
459
443
|
|
|
460
444
|
this.debug("dismiss-end");
|
|
461
445
|
}
|
|
@@ -464,7 +448,8 @@ class Kpop__FrameController extends Controller {
|
|
|
464
448
|
return new Promise(window.requestAnimationFrame).then(callback);
|
|
465
449
|
}
|
|
466
450
|
|
|
467
|
-
debug(
|
|
451
|
+
get debug() {
|
|
452
|
+
return debug("FrameController");
|
|
468
453
|
}
|
|
469
454
|
}
|
|
470
455
|
|
|
@@ -512,296 +497,317 @@ function installNavigationInterception(controller) {
|
|
|
512
497
|
};
|
|
513
498
|
}
|
|
514
499
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
fallback_location: String,
|
|
518
|
-
layout: String,
|
|
519
|
-
};
|
|
520
|
-
|
|
521
|
-
connect() {
|
|
522
|
-
this.debug("connect");
|
|
500
|
+
function autofocus(container) {
|
|
501
|
+
if (!container) return null;
|
|
523
502
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
disconnect() {
|
|
530
|
-
this.debug("disconnect");
|
|
531
|
-
|
|
532
|
-
if (this.layoutValue) {
|
|
533
|
-
document.querySelector("#kpop").classList.toggle(this.layoutValue, false);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
debug(event, ...args) {
|
|
538
|
-
}
|
|
503
|
+
return (
|
|
504
|
+
container.querySelector("[autofocus]") ??
|
|
505
|
+
container.querySelector("button:not([disabled])")
|
|
506
|
+
);
|
|
539
507
|
}
|
|
540
508
|
|
|
541
509
|
/**
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
* You can show and hide the scrim programmatically by calling show/hide on the controller, e.g. using an outlet.
|
|
548
|
-
*
|
|
549
|
-
* If you need to respond to the scrim showing or hiding you should subscribe to "scrim:show" and "scrim:hide".
|
|
510
|
+
* Based on Turbo's LinkObserver, checks links on mouse-over and focus to see
|
|
511
|
+
* whether they should open in modals. If they should, then sets the
|
|
512
|
+
* data-turbo-frame attribute so it will be prefetched and opened in the context
|
|
513
|
+
* of the kpop turbo frame.
|
|
550
514
|
*/
|
|
551
|
-
class
|
|
552
|
-
|
|
553
|
-
open: Boolean,
|
|
554
|
-
captive: Boolean,
|
|
555
|
-
zIndex: Number,
|
|
556
|
-
};
|
|
557
|
-
|
|
558
|
-
connect() {
|
|
515
|
+
class LinkObserver {
|
|
516
|
+
started = false;
|
|
559
517
|
|
|
560
|
-
|
|
561
|
-
this.
|
|
562
|
-
|
|
563
|
-
this.element.scrim = this;
|
|
518
|
+
constructor(delegate, eventTarget) {
|
|
519
|
+
this.delegate = delegate;
|
|
520
|
+
this.eventTarget = eventTarget;
|
|
564
521
|
}
|
|
565
522
|
|
|
566
|
-
|
|
523
|
+
start() {
|
|
524
|
+
if (this.started) return;
|
|
525
|
+
if (this.eventTarget.readyState === "loading") {
|
|
526
|
+
this.eventTarget.addEventListener("DOMContentLoaded", this.#enable, {
|
|
527
|
+
once: true,
|
|
528
|
+
});
|
|
529
|
+
} else {
|
|
530
|
+
this.#enable();
|
|
531
|
+
}
|
|
532
|
+
}
|
|
567
533
|
|
|
568
|
-
|
|
534
|
+
stop() {
|
|
535
|
+
if (!this.started) return;
|
|
536
|
+
this.eventTarget.removeEventListener("mouseenter", this.#addKpopLink, {
|
|
537
|
+
capture: true,
|
|
538
|
+
passive: true,
|
|
539
|
+
});
|
|
540
|
+
this.eventTarget.removeEventListener(
|
|
541
|
+
"turbo:before-prefetch",
|
|
542
|
+
this.#addKpopLink,
|
|
543
|
+
{
|
|
544
|
+
capture: true,
|
|
545
|
+
passive: true,
|
|
546
|
+
},
|
|
547
|
+
);
|
|
548
|
+
this.eventTarget.removeEventListener("focusin", this.#addKpopLink, {
|
|
549
|
+
capture: true,
|
|
550
|
+
passive: true,
|
|
551
|
+
});
|
|
552
|
+
this.eventTarget.removeEventListener("mouseleave", this.#removeKpopLink, {
|
|
553
|
+
capture: true,
|
|
554
|
+
passive: true,
|
|
555
|
+
});
|
|
556
|
+
this.eventTarget.removeEventListener("focusout", this.#removeKpopLink, {
|
|
557
|
+
capture: true,
|
|
558
|
+
passive: true,
|
|
559
|
+
});
|
|
560
|
+
this.started = false;
|
|
569
561
|
}
|
|
570
562
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
563
|
+
#enable = () => {
|
|
564
|
+
if (this.started) return;
|
|
565
|
+
this.started = true;
|
|
566
|
+
this.eventTarget.addEventListener("mouseenter", this.#addKpopLink, {
|
|
567
|
+
capture: true,
|
|
568
|
+
passive: true,
|
|
569
|
+
});
|
|
570
|
+
this.eventTarget.addEventListener("focusin", this.#addKpopLink, {
|
|
571
|
+
capture: true,
|
|
572
|
+
passive: true,
|
|
573
|
+
});
|
|
574
|
+
this.eventTarget.addEventListener(
|
|
575
|
+
"turbo:before-prefetch",
|
|
576
|
+
this.#addKpopLink,
|
|
577
|
+
{
|
|
578
|
+
capture: true,
|
|
579
|
+
passive: true,
|
|
580
|
+
},
|
|
581
|
+
);
|
|
582
|
+
this.eventTarget.addEventListener("mouseleave", this.#removeKpopLink, {
|
|
583
|
+
capture: true,
|
|
584
|
+
passive: true,
|
|
585
|
+
});
|
|
586
|
+
this.eventTarget.addEventListener("focusout", this.#removeKpopLink, {
|
|
587
|
+
capture: true,
|
|
588
|
+
passive: true,
|
|
589
|
+
});
|
|
590
|
+
};
|
|
577
591
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
592
|
+
#addKpopLink = (event) => {
|
|
593
|
+
const target = event.target;
|
|
594
|
+
const isLink =
|
|
595
|
+
target.matches &&
|
|
596
|
+
target.matches(
|
|
597
|
+
"a[href]:not([target^=_]):not([download]):not([data-turbo-frame]",
|
|
598
|
+
);
|
|
599
|
+
if (isLink && this.#isPrefetchable(target)) {
|
|
600
|
+
const link = target;
|
|
601
|
+
const location = getLocationForLink(link);
|
|
602
|
+
if (this.delegate.isModalLink(link, location)) {
|
|
603
|
+
link.dataset.turboFrame = "kpop";
|
|
604
|
+
}
|
|
581
605
|
}
|
|
606
|
+
};
|
|
582
607
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
// update state, perform style updates
|
|
590
|
-
this.#show(captive, zIndex, top);
|
|
591
|
-
|
|
592
|
-
if (animate) {
|
|
593
|
-
// animate opening
|
|
594
|
-
// this will trigger an animationEnd event via CSS that completes the open
|
|
595
|
-
this.element.dataset.showAnimating = "";
|
|
596
|
-
|
|
597
|
-
await new Promise((resolve) => {
|
|
598
|
-
this.element.addEventListener("animationend", () => resolve(), {
|
|
599
|
-
once: true,
|
|
600
|
-
});
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
delete this.element.dataset.showAnimating;
|
|
608
|
+
#removeKpopLink = (event) => {
|
|
609
|
+
const target = event.target;
|
|
610
|
+
const isLink =
|
|
611
|
+
target.matches && target.matches("a[href][data-turbo-frame='kpop']");
|
|
612
|
+
if (isLink) {
|
|
613
|
+
delete target.dataset.turboFrame;
|
|
604
614
|
}
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
async hide({ animate = true } = {}) {
|
|
608
|
-
if (!this.openValue || this.element.dataset.hideAnimating) return;
|
|
615
|
+
};
|
|
609
616
|
|
|
610
|
-
|
|
611
|
-
|
|
617
|
+
#isPrefetchable(link) {
|
|
618
|
+
const href = link.getAttribute("href");
|
|
619
|
+
if (!href) return false;
|
|
620
|
+
if (unfetchableLink(link)) return false;
|
|
621
|
+
if (linkToTheSamePage(link)) return false;
|
|
622
|
+
if (linkOptsOut(link)) return false;
|
|
623
|
+
if (nonSafeLink(link)) return false;
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
612
627
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
this.element.dataset.hideAnimating = "";
|
|
628
|
+
function getLocationForLink(link) {
|
|
629
|
+
return new URL(link.getAttribute("href").toString(), document.baseURI);
|
|
630
|
+
}
|
|
617
631
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
});
|
|
632
|
+
const unfetchableLink = (link) =>
|
|
633
|
+
link.origin !== document.location.origin ||
|
|
634
|
+
!["http:", "https:"].includes(link.protocol) ||
|
|
635
|
+
link.hasAttribute("target");
|
|
623
636
|
|
|
624
|
-
|
|
625
|
-
|
|
637
|
+
const linkToTheSamePage = (link) =>
|
|
638
|
+
link.pathname + link.search ===
|
|
639
|
+
document.location.pathname + document.location.search ||
|
|
640
|
+
link.href.startsWith("#");
|
|
626
641
|
|
|
627
|
-
|
|
642
|
+
const linkOptsOut = (link) => {
|
|
643
|
+
return link.getAttribute("data-turbo") === "false";
|
|
644
|
+
};
|
|
628
645
|
|
|
629
|
-
|
|
630
|
-
|
|
646
|
+
const nonSafeLink = (link) => {
|
|
647
|
+
const turboMethod = link.getAttribute("data-turbo-method");
|
|
648
|
+
if (turboMethod && turboMethod.toLowerCase() !== "get") return true;
|
|
649
|
+
if (isUJS(link)) return true;
|
|
650
|
+
if (link.hasAttribute("data-turbo-confirm")) return true;
|
|
651
|
+
if (link.hasAttribute("data-turbo-stream")) return true;
|
|
652
|
+
return false;
|
|
653
|
+
};
|
|
631
654
|
|
|
632
|
-
|
|
655
|
+
const isUJS = (link) =>
|
|
656
|
+
link.hasAttribute("data-remote") ||
|
|
657
|
+
link.hasAttribute("data-behavior") ||
|
|
658
|
+
link.hasAttribute("data-confirm") ||
|
|
659
|
+
link.hasAttribute("data-method");
|
|
633
660
|
|
|
634
|
-
|
|
635
|
-
|
|
661
|
+
/**
|
|
662
|
+
* Similar to Hotwire's PathConfiguration.json, this class compiles a list of
|
|
663
|
+
* rules to check link hrefs against so that we can identify links that
|
|
664
|
+
* should open in a KPOP modal.
|
|
665
|
+
*
|
|
666
|
+
* Unlike Hotwire Native, we can't intercept 303s in the browser before they
|
|
667
|
+
* load. Browser sandbox prevents us from inspecting the location of redirect
|
|
668
|
+
* requests so we can only intercept links that match modals directly.
|
|
669
|
+
*
|
|
670
|
+
* For posts and redirects, we need server support (flash modals, streams).
|
|
671
|
+
*/
|
|
672
|
+
class Ruleset {
|
|
673
|
+
constructor(rules = []) {
|
|
674
|
+
this.rules = [];
|
|
636
675
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
!this.captiveValue &&
|
|
641
|
-
!event.defaultPrevented
|
|
642
|
-
) {
|
|
643
|
-
this.dispatch("dismiss", { bubbles: true });
|
|
644
|
-
}
|
|
676
|
+
rules.forEach((rule) => {
|
|
677
|
+
this.#compileRule(rule);
|
|
678
|
+
});
|
|
645
679
|
}
|
|
646
680
|
|
|
647
681
|
/**
|
|
648
|
-
*
|
|
682
|
+
* Returns properties for the given URL
|
|
683
|
+
*
|
|
684
|
+
* @param {URL} location
|
|
685
|
+
* @returns {} properties
|
|
649
686
|
*/
|
|
650
|
-
|
|
651
|
-
this.
|
|
652
|
-
this.zIndexValue = zIndex;
|
|
653
|
-
this.scrollY = top;
|
|
654
|
-
|
|
655
|
-
this.element.style.zIndex = this.zIndexValue;
|
|
656
|
-
document.body.style.top = `-${top}px`;
|
|
657
|
-
document.body.style.position = "fixed";
|
|
658
|
-
document.body.style.paddingRight = `-${this.scrollPadding}px`;
|
|
659
|
-
|
|
660
|
-
if (document.body.scrollHeight > window.innerHeight) {
|
|
661
|
-
document.body.style.overflowY = "scroll";
|
|
662
|
-
}
|
|
687
|
+
properties(location) {
|
|
688
|
+
return this.rules.reduce((c, f) => f(location, c), {});
|
|
663
689
|
}
|
|
664
690
|
|
|
691
|
+
#compileRule({ patterns, properties }) {
|
|
692
|
+
patterns.forEach((pattern) => {
|
|
693
|
+
this.rules.push(locationMatcher(new RegExp(pattern), properties));
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function locationMatcher(re, properties) {
|
|
699
|
+
return (location, accumulator) =>
|
|
700
|
+
re.test(location.pathname)
|
|
701
|
+
? { ...accumulator, ...properties }
|
|
702
|
+
: accumulator;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
class StreamModal extends Modal {
|
|
665
706
|
/**
|
|
666
|
-
*
|
|
707
|
+
* When a turbo-stream[action=kpop_open] element is rendered, it runs this
|
|
708
|
+
* method to load the modal template as a StreamModal.
|
|
709
|
+
*
|
|
710
|
+
* @param {Kpop__FrameController} frame
|
|
711
|
+
* @param {Turbo.StreamElement} action
|
|
667
712
|
*/
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
713
|
+
static async open(frame, action) {
|
|
714
|
+
const animate = !frame.isOpen;
|
|
715
|
+
|
|
716
|
+
await frame.dismiss({ animate, reason: "turbo-stream.kpop_open" });
|
|
671
717
|
|
|
672
|
-
|
|
673
|
-
document.body.style.removeProperty("position");
|
|
674
|
-
document.body.style.removeProperty("top");
|
|
675
|
-
document.body.style.removeProperty("overflow-y");
|
|
718
|
+
frame.element.append(action.templateContent);
|
|
676
719
|
|
|
677
|
-
|
|
720
|
+
const dialog = frame.element.querySelector("dialog");
|
|
721
|
+
const src = dialog.dataset.src;
|
|
678
722
|
|
|
679
|
-
|
|
723
|
+
await frame.open(new StreamModal(frame, dialog, src), { animate });
|
|
680
724
|
}
|
|
681
725
|
}
|
|
682
726
|
|
|
683
|
-
class
|
|
684
|
-
|
|
685
|
-
|
|
727
|
+
class StreamActions {
|
|
728
|
+
start() {
|
|
729
|
+
Turbo$1.StreamActions.kpop_open = openStreamModal;
|
|
730
|
+
}
|
|
686
731
|
|
|
687
|
-
|
|
732
|
+
stop() {
|
|
733
|
+
delete Turbo$1.StreamActions.kpop_open;
|
|
688
734
|
}
|
|
735
|
+
}
|
|
689
736
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
* the user can dismiss the modal by navigating back.
|
|
693
|
-
*
|
|
694
|
-
* @returns {Promise<void>}
|
|
695
|
-
*/
|
|
696
|
-
async open() {
|
|
697
|
-
await super.open();
|
|
737
|
+
function openStreamModal() {
|
|
738
|
+
const frame = this.targetElements[0]?.kpop;
|
|
698
739
|
|
|
699
|
-
|
|
740
|
+
if (frame) {
|
|
741
|
+
StreamModal.open(frame, this).then(() => {});
|
|
700
742
|
}
|
|
743
|
+
}
|
|
701
744
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
*
|
|
706
|
-
* @returns {Promise<void>}
|
|
707
|
-
*/
|
|
708
|
-
async dismiss() {
|
|
709
|
-
await super.dismiss();
|
|
745
|
+
const controllers = [
|
|
746
|
+
{ identifier: "kpop--frame", controllerConstructor: Kpop__FrameController },
|
|
747
|
+
];
|
|
710
748
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
749
|
+
class Application {
|
|
750
|
+
static configure(config = {}) {
|
|
751
|
+
this.instance ||= new this(config);
|
|
752
|
+
debug.enabled = this.instance.debug;
|
|
753
|
+
return this.instance;
|
|
754
|
+
}
|
|
714
755
|
|
|
715
|
-
|
|
756
|
+
constructor({ rules = [], debug = false } = {}) {
|
|
757
|
+
this.config = { rules, debug };
|
|
758
|
+
this.ruleset = new Ruleset(rules);
|
|
759
|
+
this.linkObserver = new LinkObserver(this, document);
|
|
760
|
+
this.streamActions = new StreamActions();
|
|
716
761
|
}
|
|
717
762
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
*
|
|
722
|
-
* @param frame TurboFrame element
|
|
723
|
-
* @param e Turbo navigation event
|
|
724
|
-
*/
|
|
725
|
-
beforeVisit(frame, e) {
|
|
726
|
-
super.beforeVisit(frame, e);
|
|
763
|
+
start() {
|
|
764
|
+
this.streamActions.start();
|
|
765
|
+
this.linkObserver.start();
|
|
727
766
|
|
|
728
|
-
|
|
767
|
+
window.addEventListener(
|
|
768
|
+
"turbo:before-fetch-request",
|
|
769
|
+
addKpopToRequestHeaders,
|
|
770
|
+
);
|
|
729
771
|
|
|
730
|
-
|
|
731
|
-
|
|
772
|
+
if (this.debug) {
|
|
773
|
+
document.addEventListener("focusin", debugFocusIn);
|
|
774
|
+
document.addEventListener("focusout", debugFocusOut);
|
|
775
|
+
}
|
|
732
776
|
|
|
733
|
-
|
|
734
|
-
});
|
|
777
|
+
return this;
|
|
735
778
|
}
|
|
736
779
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
super.popstate(frame, e);
|
|
780
|
+
stop() {
|
|
781
|
+
window.removeEventListener(
|
|
782
|
+
"turbo:before-fetch-request",
|
|
783
|
+
addKpopToRequestHeaders,
|
|
784
|
+
);
|
|
785
|
+
document.removeEventListener("focusin", debugFocusIn);
|
|
786
|
+
document.removeEventListener("focusout", debugFocusOut);
|
|
745
787
|
|
|
746
|
-
|
|
788
|
+
this.streamActions.stop();
|
|
789
|
+
this.linkObserver.stop();
|
|
747
790
|
}
|
|
748
791
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
class StreamRenderer {
|
|
755
|
-
constructor(frame, action) {
|
|
756
|
-
this.frame = frame;
|
|
757
|
-
this.action = action;
|
|
792
|
+
isModalLink(link, location) {
|
|
793
|
+
const properties = this.ruleset.properties(location);
|
|
794
|
+
return properties.context === "modal";
|
|
758
795
|
}
|
|
759
796
|
|
|
760
|
-
|
|
761
|
-
this.
|
|
762
|
-
this.frame.innerHTML = "";
|
|
763
|
-
this.frame.append(this.action.templateContent);
|
|
797
|
+
get debug() {
|
|
798
|
+
return Boolean(this.config.debug);
|
|
764
799
|
}
|
|
765
800
|
}
|
|
766
801
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
Turbo$1.StreamActions.kpop_open = function () {
|
|
772
|
-
const animate = !kpop(this).openValue;
|
|
802
|
+
const debugFocusIn = (e) => debug("Application")("focus", e.target);
|
|
803
|
+
const debugFocusOut = (e) => debug("Application")("blur", e.target);
|
|
773
804
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
.then(() => {
|
|
777
|
-
new StreamRenderer(this.targetElements[0], this).render();
|
|
778
|
-
kpop(this)?.open(new StreamModal(this.target, this), { animate });
|
|
779
|
-
});
|
|
780
|
-
};
|
|
805
|
+
const addKpopToRequestHeaders = (e) => {
|
|
806
|
+
const headers = e.detail.fetchOptions.headers;
|
|
781
807
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
};
|
|
785
|
-
|
|
786
|
-
Turbo$1.StreamActions.kpop_redirect_to = function () {
|
|
787
|
-
if (this.dataset.turboFrame === this.target) {
|
|
788
|
-
const a = document.createElement("A");
|
|
789
|
-
a.setAttribute("data-turbo-action", "replace");
|
|
790
|
-
this.targetElements[0].delegate.linkClickIntercepted(
|
|
791
|
-
a,
|
|
792
|
-
this.getAttribute("href"),
|
|
793
|
-
);
|
|
794
|
-
} else {
|
|
795
|
-
Turbo$1.visit(this.getAttribute("href"), {
|
|
796
|
-
action: this.dataset.turboAction,
|
|
797
|
-
});
|
|
808
|
+
if (headers["Accept"]?.includes("text/vnd.turbo-stream.html")) {
|
|
809
|
+
headers["Kpop-Available"] = "true";
|
|
798
810
|
}
|
|
799
811
|
};
|
|
800
812
|
|
|
801
|
-
|
|
802
|
-
{ identifier: "kpop--frame", controllerConstructor: Kpop__FrameController },
|
|
803
|
-
{ identifier: "kpop--modal", controllerConstructor: Kpop__ModalController },
|
|
804
|
-
{ identifier: "scrim", controllerConstructor: ScrimController },
|
|
805
|
-
];
|
|
806
|
-
|
|
807
|
-
export { Definitions as default };
|
|
813
|
+
export { controllers, Application as default };
|