katalyst-kpop 2.0.9 → 3.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 +17 -43
- data/app/assets/builds/katalyst/kpop.esm.js +599 -0
- data/app/assets/builds/katalyst/kpop.js +479 -519
- data/app/assets/builds/katalyst/kpop.min.js +2 -1
- data/app/assets/builds/katalyst/kpop.min.js.map +1 -0
- data/app/assets/builds/katalyst/kpop.umd.js +5890 -0
- data/app/assets/config/kpop.js +1 -1
- data/app/assets/stylesheets/katalyst/kpop/_frame.scss +104 -0
- data/app/assets/stylesheets/katalyst/kpop/_modal.scss +95 -0
- data/app/assets/stylesheets/katalyst/kpop/_scrim.scss +33 -3
- data/app/assets/stylesheets/katalyst/kpop/_side_panel.scss +64 -0
- data/app/assets/stylesheets/katalyst/kpop/_variables.scss +25 -0
- data/app/assets/stylesheets/katalyst/kpop.scss +6 -1
- data/app/components/concerns/kpop/has_html_attributes.rb +78 -0
- data/app/components/kpop/frame_component.html.erb +14 -0
- data/app/components/kpop/frame_component.rb +45 -0
- data/app/components/kpop/modal/title_component.html.erb +6 -0
- data/app/components/kpop/modal/title_component.rb +28 -0
- data/app/components/kpop/modal_component.html.erb +8 -0
- data/app/components/kpop/modal_component.rb +39 -0
- data/app/components/scrim_component.rb +32 -0
- data/app/helpers/kpop_helper.rb +12 -35
- data/app/javascript/kpop/application.js +15 -0
- data/app/javascript/kpop/controllers/close_controller.js +9 -0
- data/app/javascript/kpop/controllers/frame_controller.js +189 -0
- data/app/javascript/kpop/controllers/modal_controller.js +30 -0
- data/app/javascript/kpop/controllers/redirect_controller.js +22 -0
- data/app/{assets/javascripts → javascript/kpop}/controllers/scrim_controller.js +76 -72
- data/app/javascript/kpop/debug.js +3 -0
- data/app/javascript/kpop/modals/content_modal.js +46 -0
- data/app/javascript/kpop/modals/frame_modal.js +41 -0
- data/app/javascript/kpop/modals/modal.js +69 -0
- data/app/javascript/kpop/modals/stream_modal.js +49 -0
- data/app/javascript/kpop/modals/stream_renderer.js +15 -0
- data/app/views/layouts/kpop.html.erb +1 -1
- data/config/importmap.rb +1 -4
- data/lib/katalyst/kpop/engine.rb +13 -12
- data/lib/katalyst/kpop/matchers/base.rb +18 -0
- data/lib/katalyst/kpop/matchers/capybara_matcher.rb +46 -0
- data/lib/katalyst/kpop/matchers/capybara_parser.rb +17 -0
- data/lib/katalyst/kpop/matchers/chained_matcher.rb +40 -0
- data/lib/katalyst/kpop/matchers/frame_matcher.rb +16 -0
- data/lib/katalyst/kpop/matchers/modal_matcher.rb +20 -0
- data/lib/katalyst/kpop/matchers/redirect_finder.rb +16 -0
- data/lib/katalyst/kpop/matchers/redirect_matcher.rb +28 -0
- data/lib/katalyst/kpop/matchers/response_matcher.rb +33 -0
- data/lib/katalyst/kpop/matchers/stream_matcher.rb +16 -0
- data/lib/katalyst/kpop/matchers/title_finder.rb +16 -0
- data/lib/katalyst/kpop/matchers/title_matcher.rb +28 -0
- data/lib/katalyst/kpop/matchers.rb +79 -0
- data/lib/katalyst/kpop/turbo.rb +56 -0
- data/lib/katalyst/kpop/version.rb +1 -1
- data/lib/katalyst/kpop.rb +4 -0
- metadata +90 -15
- data/app/assets/builds/katalyst/kpop.css +0 -117
- data/app/assets/javascripts/controllers/kpop_controller.js +0 -72
- data/app/assets/javascripts/katalyst/kpop.js +0 -9
- data/app/assets/stylesheets/katalyst/kpop/_index.scss +0 -2
- data/app/assets/stylesheets/katalyst/kpop/_kpop.scss +0 -133
- data/app/helpers/kpop/modal.rb +0 -98
- data/app/helpers/scrim_helper.rb +0 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5d657aa583bbc374ac12276e50d9d11d85801d6ddf1518cacaeba453893fb0be
|
|
4
|
+
data.tar.gz: 19e213e9b618fb117e8e07ebeb1a5677daa8528f80de07d2f6ff611a208b5b8d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5bbacf659d0a520fa43115cf376bcf1f3c58aa64b619c183fcfcb4554a42868b3d2bcbf160a92d42061e9afd1d9339be6f9598d694e57a403840d78a5cb8699c
|
|
7
|
+
data.tar.gz: ba50a3724ddddb3b95c13918e93ae5dcf590f555da584f83276b218c8b747dc897a6f5d7bcf25015c6f16753f6a60c5b366cd843251f0668dd1c540ef0132ea6
|
data/README.md
CHANGED
|
@@ -17,8 +17,16 @@ kpop supports installation of javascript dependencies with either import maps or
|
|
|
17
17
|
|
|
18
18
|
### Stimulus controllers
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
kpop assumes that you are using importmaps to manage javascript dependencies.
|
|
21
|
+
|
|
22
|
+
Add the following to your Stimulus `controllers/index.js`:
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
import kpop from "@katalyst/kpop";
|
|
26
|
+
application.load(kpop);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
This will ensure that kpop is loaded and registered with Stimulus.
|
|
22
30
|
|
|
23
31
|
### Stylesheets
|
|
24
32
|
|
|
@@ -30,43 +38,13 @@ Import stylesheets through using SASS using asset pipeline:
|
|
|
30
38
|
@use "katalyst/kpop";
|
|
31
39
|
```
|
|
32
40
|
|
|
33
|
-
You can also load a precompiled version from the gem directly:
|
|
34
|
-
|
|
35
|
-
```erb
|
|
36
|
-
<%# app/views/layouts/application.html.erb #>
|
|
37
|
-
|
|
38
|
-
<%= stylesheet_link_tag "katalyst/kpop" %>
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### Yarn
|
|
42
|
-
|
|
43
|
-
If you are not using import maps, you can add the yarn package to your project:
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
$ yarn add "@katalyst-interactive/kpop"
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### Import kpop styles
|
|
50
|
-
```css
|
|
51
|
-
/* application.scss */
|
|
52
|
-
|
|
53
|
-
@import "~@katalyst-interactive/kpop";
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### Import kpop stimulus controllers
|
|
57
|
-
```js
|
|
58
|
-
/* application.js */
|
|
59
|
-
import kpop from "@katalyst-interactive/kpop"
|
|
60
|
-
application.load(kpop)
|
|
61
|
-
```
|
|
62
|
-
|
|
63
41
|
## Usage
|
|
64
42
|
|
|
65
43
|
kpop provides helpers to add a basic scrim and modal target frame. These should be placed inside the body:
|
|
66
44
|
```html
|
|
67
45
|
<body>
|
|
68
|
-
<%=
|
|
69
|
-
<%=
|
|
46
|
+
<%= render ScrimComponent.new %>
|
|
47
|
+
<%= render Kpop::FrameComponent.new do %>
|
|
70
48
|
<%= yield :kpop %>
|
|
71
49
|
<% end %>
|
|
72
50
|
</body>
|
|
@@ -81,32 +59,28 @@ To show a modal you need to add content to the kpop turbo frame. You can do this
|
|
|
81
59
|
You can generate a link that will cause a modal to show using the `kpop_link_to` helper.
|
|
82
60
|
|
|
83
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
|
|
84
|
-
frame. The targeted action will need to generate content in a `
|
|
62
|
+
frame. The targeted action will need to generate content in a `Kpop::FrameComponent`, e.g. using `layout "kpop"`.
|
|
85
63
|
|
|
86
64
|
```html
|
|
87
65
|
<!-- app/views/homepage/index.html.erb -->
|
|
88
|
-
<%=
|
|
66
|
+
<%= kpop_link_to "click to open modal", modal_path("example") %>
|
|
89
67
|
```
|
|
90
68
|
|
|
91
69
|
```html
|
|
92
70
|
<!-- app/views/modals/show.html.erb -->
|
|
93
|
-
<%=
|
|
94
|
-
|
|
71
|
+
<%= Kpop::Modal(title: "Modal title") do %>
|
|
72
|
+
Modal content
|
|
95
73
|
<% end %>
|
|
96
74
|
```
|
|
97
75
|
|
|
98
|
-
Note that, because kpop modals render in a turbo frame, if you want to navigate the parent frame you will need to use
|
|
99
|
-
`target: "_top"` on your links and forms, or add `target: "_top"` to the `kpop_frame_tag` call in your body.
|
|
100
|
-
|
|
101
76
|
## Development
|
|
102
77
|
|
|
103
78
|
Releases need to be distributed to rubygems.org and npmjs.org. To do this, you need to have accounts with both providers
|
|
104
79
|
and be added as a collaborator to the kpop gem and npm packages.
|
|
105
80
|
|
|
106
|
-
1. Update the version in `
|
|
81
|
+
1. Update the version in `lib/katalyst/kpop/version.rb`
|
|
107
82
|
2. Ensure that `rake` passes (format and tests)
|
|
108
83
|
3. Tag a release and push to rubygems.org by running `rake release`
|
|
109
|
-
4. Push the new version to npmjs.org by running `yarn publish`
|
|
110
84
|
|
|
111
85
|
## License
|
|
112
86
|
|
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
|
2
|
+
import { Turbo } from '@hotwired/turbo-rails';
|
|
3
|
+
|
|
4
|
+
class Kpop__CloseController extends Controller {
|
|
5
|
+
static outlets = ["kpop--frame"];
|
|
6
|
+
|
|
7
|
+
kpopFrameOutletConnected(frame) {
|
|
8
|
+
frame.dismiss();
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class Modal {
|
|
13
|
+
constructor(id) {
|
|
14
|
+
this.id = id;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async open() {
|
|
18
|
+
this.debug("open");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async dismiss() {
|
|
22
|
+
this.debug(`dismiss`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
beforeVisit(frame, e) {
|
|
26
|
+
this.debug(`before-visit`, e.detail.url);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
popstate(frame, e) {
|
|
30
|
+
this.debug(`popstate`, e.state);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async pop(event, callback) {
|
|
34
|
+
this.debug(`pop`);
|
|
35
|
+
|
|
36
|
+
const promise = new Promise((resolve) => {
|
|
37
|
+
window.addEventListener(
|
|
38
|
+
event,
|
|
39
|
+
() => {
|
|
40
|
+
resolve();
|
|
41
|
+
},
|
|
42
|
+
{ once: true }
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
callback();
|
|
47
|
+
|
|
48
|
+
return promise;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get frameElement() {
|
|
52
|
+
return document.getElementById(this.id);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get modalElement() {
|
|
56
|
+
return this.frameElement?.querySelector("[data-controller*='kpop--modal']");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get currentLocationValue() {
|
|
60
|
+
return this.modalElement?.dataset["kpop-ModalCurrentLocationValue"] || "/";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get fallbackLocationValue() {
|
|
64
|
+
return this.modalElement?.dataset["kpop-ModalFallbackLocationValue"] || "/";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get isCurrentLocation() {
|
|
68
|
+
return (
|
|
69
|
+
window.history.state?.turbo && Turbo.session.location.href === this.src
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
debug(event, ...args) {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
class ContentModal extends Modal {
|
|
78
|
+
constructor(id, src = null) {
|
|
79
|
+
super(id);
|
|
80
|
+
|
|
81
|
+
if (src) this.src = src;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async dismiss() {
|
|
85
|
+
await super.dismiss();
|
|
86
|
+
|
|
87
|
+
if (this.visitStarted) {
|
|
88
|
+
this.debug("skipping dismiss, visit started");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!this.isCurrentLocation) {
|
|
92
|
+
this.debug("skipping dismiss, not current location");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return this.pop("turbo:load", () => {
|
|
97
|
+
this.debug("turbo-visit", this.fallbackLocationValue);
|
|
98
|
+
Turbo.visit(this.fallbackLocationValue, { action: "replace" });
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// no specific close action required, this is turbo's responsibility
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
beforeVisit(frame, e) {
|
|
105
|
+
super.beforeVisit(frame, e);
|
|
106
|
+
|
|
107
|
+
this.visitStarted = true;
|
|
108
|
+
|
|
109
|
+
frame.scrimOutlet.hide({ animate: false });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
get src() {
|
|
113
|
+
return new URL(
|
|
114
|
+
this.currentLocationValue.toString(),
|
|
115
|
+
document.baseURI
|
|
116
|
+
).toString();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
class FrameModal extends Modal {
|
|
121
|
+
constructor(id, src) {
|
|
122
|
+
super(id);
|
|
123
|
+
this.src = src;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async dismiss() {
|
|
127
|
+
await super.dismiss();
|
|
128
|
+
|
|
129
|
+
if (!this.isCurrentLocation) {
|
|
130
|
+
this.debug("skipping dismiss, not current location");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
await this.pop("turbo:load", () => window.history.back());
|
|
134
|
+
|
|
135
|
+
// no specific close action required, this is turbo's responsibility
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
beforeVisit(frame, e) {
|
|
139
|
+
super.beforeVisit(frame, e);
|
|
140
|
+
|
|
141
|
+
e.preventDefault();
|
|
142
|
+
|
|
143
|
+
frame.dismiss({ animate: false }).then(() => {
|
|
144
|
+
Turbo.visit(e.detail.url);
|
|
145
|
+
|
|
146
|
+
this.debug("before-visit-end");
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
popstate(frame, e) {
|
|
151
|
+
super.popstate(frame, e);
|
|
152
|
+
|
|
153
|
+
// Turbo will restore modal state, but we need to reset the scrim
|
|
154
|
+
frame.scrimOutlet.hide({ animate: false });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
class StreamModal extends Modal {
|
|
159
|
+
constructor(id, action) {
|
|
160
|
+
super(id);
|
|
161
|
+
|
|
162
|
+
this.action = action;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async open() {
|
|
166
|
+
await super.open();
|
|
167
|
+
|
|
168
|
+
window.history.pushState({ kpop: true, id: this.id }, "", window.location);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async dismiss() {
|
|
172
|
+
await super.dismiss();
|
|
173
|
+
|
|
174
|
+
if (this.isCurrentLocation) {
|
|
175
|
+
await this.pop("popstate", () => window.history.back());
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
this.frameElement.innerHTML = "";
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
beforeVisit(frame, e) {
|
|
182
|
+
super.beforeVisit(frame, e);
|
|
183
|
+
|
|
184
|
+
e.preventDefault();
|
|
185
|
+
|
|
186
|
+
frame.dismiss({ animate: false }).then(() => {
|
|
187
|
+
Turbo.visit(e.detail.url);
|
|
188
|
+
|
|
189
|
+
this.debug("before-visit-end");
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
popstate(frame, e) {
|
|
194
|
+
super.popstate(frame, e);
|
|
195
|
+
|
|
196
|
+
frame.dismiss({ animate: true, reason: "popstate" });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
get isCurrentLocation() {
|
|
200
|
+
return window.history.state?.kpop && window.history.state?.id === this.id;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
class StreamRenderer {
|
|
205
|
+
constructor(frame, action) {
|
|
206
|
+
this.frame = frame;
|
|
207
|
+
this.action = action;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
render() {
|
|
211
|
+
this.frame.src = "";
|
|
212
|
+
this.frame.innerHTML = "";
|
|
213
|
+
this.frame.append(this.action.templateContent);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
Turbo.StreamActions.kpop_open = function () {
|
|
218
|
+
const frame = () => {
|
|
219
|
+
return this.targetElements[0];
|
|
220
|
+
};
|
|
221
|
+
const animate = !frame?.kpop?.openValue;
|
|
222
|
+
|
|
223
|
+
frame()
|
|
224
|
+
.kpop.dismiss({ animate, reason: "before-turbo-stream" })
|
|
225
|
+
.then(() => {
|
|
226
|
+
new StreamRenderer(frame(), this).render();
|
|
227
|
+
frame().kpop.open(new StreamModal(this.target, this), { animate });
|
|
228
|
+
});
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
class Kpop__FrameController extends Controller {
|
|
232
|
+
static outlets = ["scrim"];
|
|
233
|
+
static targets = ["modal"];
|
|
234
|
+
static values = {
|
|
235
|
+
open: Boolean,
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
connect() {
|
|
239
|
+
this.debug("connect", this.element.src);
|
|
240
|
+
|
|
241
|
+
this.element.kpop = this;
|
|
242
|
+
|
|
243
|
+
// restoration visit
|
|
244
|
+
if (this.element.src && this.element.complete) {
|
|
245
|
+
this.debug("new frame modal", this.element.src);
|
|
246
|
+
this.open(new FrameModal(this.element.id, this.element.src), {
|
|
247
|
+
animate: false,
|
|
248
|
+
});
|
|
249
|
+
} else {
|
|
250
|
+
const element = this.element.querySelector(
|
|
251
|
+
"[data-controller*='kpop--modal']"
|
|
252
|
+
);
|
|
253
|
+
if (element) {
|
|
254
|
+
this.debug("new content modal", window.location.pathname);
|
|
255
|
+
this.open(new ContentModal(this.element.id), { animate: false });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
disconnect() {
|
|
261
|
+
this.debug("disconnect");
|
|
262
|
+
|
|
263
|
+
delete this.element.kpop;
|
|
264
|
+
delete this.modal;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
scrimOutletConnected(scrim) {
|
|
268
|
+
this.debug("scrim-connected");
|
|
269
|
+
|
|
270
|
+
this.scrimConnected = true;
|
|
271
|
+
|
|
272
|
+
if (this.openValue) scrim.show({ animate: false });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
openValueChanged(open) {
|
|
276
|
+
this.debug("open-changed", open);
|
|
277
|
+
|
|
278
|
+
this.element.parentElement.style.display = open ? "flex" : "none";
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async open(modal, { animate = true } = {}) {
|
|
282
|
+
if (this.isOpen) {
|
|
283
|
+
this.debug("skip open as already open");
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
this.opening ||= this.#nextAnimationFrame(() => {
|
|
288
|
+
return this.#open(modal, { animate });
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
return this.opening;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async dismiss({ animate = true, reason = "" } = {}) {
|
|
295
|
+
if (!this.isOpen) {
|
|
296
|
+
this.debug("skip dismiss as already closed");
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
this.dismissing ||= this.#nextAnimationFrame(() => {
|
|
301
|
+
return new Promise((resolve) => {
|
|
302
|
+
this.#dismiss({ animate, reason }).then(() => {
|
|
303
|
+
return this.#nextAnimationFrame(resolve);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
return this.dismissing;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// EVENTS
|
|
312
|
+
|
|
313
|
+
popstate(event) {
|
|
314
|
+
this.modal?.popstate(this, event);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
beforeFrameRender(event) {
|
|
318
|
+
this.debug("before-frame-render", event.detail.newFrame.baseURI);
|
|
319
|
+
|
|
320
|
+
event.preventDefault();
|
|
321
|
+
|
|
322
|
+
this.dismiss({ animate: true, reason: "before-frame-render" }).then(() => {
|
|
323
|
+
event.detail.resume();
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
beforeVisit(e) {
|
|
328
|
+
this.debug("before-visit", e.detail.url);
|
|
329
|
+
|
|
330
|
+
// ignore visits to the current frame, these fire when the frame navigates
|
|
331
|
+
if (e.detail.url === this.element.src) return;
|
|
332
|
+
|
|
333
|
+
// ignore unless we're open
|
|
334
|
+
if (!this.isOpen) return;
|
|
335
|
+
|
|
336
|
+
this.modal.beforeVisit(this, e);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
frameLoad(event) {
|
|
340
|
+
this.debug("frame-load");
|
|
341
|
+
|
|
342
|
+
return this.open(new FrameModal(this.element.id, this.element.src), {
|
|
343
|
+
animate: true,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
get isOpen() {
|
|
348
|
+
return this.openValue && !this.dismissing;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async #open(modal, { animate = true } = {}) {
|
|
352
|
+
this.debug("open-start", { animate });
|
|
353
|
+
|
|
354
|
+
const scrim = this.scrimConnected && this.scrimOutlet;
|
|
355
|
+
|
|
356
|
+
this.modal = modal;
|
|
357
|
+
this.openValue = true;
|
|
358
|
+
|
|
359
|
+
modal.open({ animate });
|
|
360
|
+
scrim?.show({ animate });
|
|
361
|
+
|
|
362
|
+
delete this.opening;
|
|
363
|
+
|
|
364
|
+
this.debug("open-end");
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async #dismiss({ animate = true, reason = "" } = {}) {
|
|
368
|
+
this.debug("dismiss-start", { animate, reason });
|
|
369
|
+
|
|
370
|
+
if (!this.modal) {
|
|
371
|
+
console.warn("modal missing on dismiss");
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
await this.scrimOutlet.hide({ animate });
|
|
375
|
+
await this.modal?.dismiss();
|
|
376
|
+
|
|
377
|
+
this.openValue = false;
|
|
378
|
+
this.modal = null;
|
|
379
|
+
delete this.dismissing;
|
|
380
|
+
|
|
381
|
+
this.debug("dismiss-end");
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
#nextAnimationFrame(callback) {
|
|
385
|
+
return new Promise((resolve) => {
|
|
386
|
+
window.requestAnimationFrame(() => {
|
|
387
|
+
resolve(callback());
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
debug(event, ...args) {
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
class Kpop__ModalController extends Controller {
|
|
397
|
+
static values = {
|
|
398
|
+
fallback_location: String,
|
|
399
|
+
layout: String,
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
connect() {
|
|
403
|
+
this.debug("connect");
|
|
404
|
+
|
|
405
|
+
if (this.layoutValue) {
|
|
406
|
+
document.querySelector("#kpop").classList.toggle(this.layoutValue, true);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
disconnect() {
|
|
411
|
+
this.debug("disconnect");
|
|
412
|
+
|
|
413
|
+
if (this.layoutValue) {
|
|
414
|
+
document.querySelector("#kpop").classList.toggle(this.layoutValue, false);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
debug(event, ...args) {
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
class Kpop__RedirectController extends Controller {
|
|
423
|
+
static outlets = ["kpop--frame"];
|
|
424
|
+
static values = {
|
|
425
|
+
path: String,
|
|
426
|
+
target: String,
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
kpopFrameOutletConnected(frame) {
|
|
430
|
+
if (this.targetValue === frame.element.id) {
|
|
431
|
+
frame.dismiss().then(() => {
|
|
432
|
+
document.getElementById(this.targetValue).src = this.pathValue;
|
|
433
|
+
});
|
|
434
|
+
} else {
|
|
435
|
+
Turbo.visit(this.pathValue, { action: "replace" });
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
this.element.remove();
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Scrim controller wraps an element that creates a whole page layer.
|
|
444
|
+
* It is intended to be used behind a modal or nav drawer.
|
|
445
|
+
*
|
|
446
|
+
* If the Scrim element receives a click event, it automatically triggers "scrim:hide".
|
|
447
|
+
*
|
|
448
|
+
* You can show and hide the scrim programmatically by calling show/hide on the controller, e.g. using an outlet.
|
|
449
|
+
*
|
|
450
|
+
* If you need to respond to the scrim showing or hiding you should subscribe to "scrim:show" and "scrim:hide".
|
|
451
|
+
*/
|
|
452
|
+
class ScrimController extends Controller {
|
|
453
|
+
static values = {
|
|
454
|
+
open: Boolean,
|
|
455
|
+
captive: Boolean,
|
|
456
|
+
zIndex: Number,
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
connect() {
|
|
460
|
+
|
|
461
|
+
this.defaultZIndexValue = this.zIndexValue;
|
|
462
|
+
this.defaultCaptiveValue = this.captiveValue;
|
|
463
|
+
|
|
464
|
+
this.element.scrim = this;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
disconnect() {
|
|
468
|
+
|
|
469
|
+
delete this.element.scrim;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async show({
|
|
473
|
+
captive = this.defaultCaptiveValue,
|
|
474
|
+
zIndex = this.defaultZIndexValue,
|
|
475
|
+
top = window.scrollY,
|
|
476
|
+
animate = true,
|
|
477
|
+
} = {}) {
|
|
478
|
+
|
|
479
|
+
// hide the scrim before opening the new one if it's already open
|
|
480
|
+
if (this.openValue) {
|
|
481
|
+
await this.hide({ animate });
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// update internal state
|
|
485
|
+
this.openValue = true;
|
|
486
|
+
|
|
487
|
+
// notify listeners of pending request
|
|
488
|
+
this.dispatch("show", { bubbles: true });
|
|
489
|
+
|
|
490
|
+
// update state, perform style updates
|
|
491
|
+
this.#show(captive, zIndex, top);
|
|
492
|
+
|
|
493
|
+
if (animate) {
|
|
494
|
+
// animate opening
|
|
495
|
+
// this will trigger an animationEnd event via CSS that completes the open
|
|
496
|
+
this.element.dataset.showAnimating = "";
|
|
497
|
+
|
|
498
|
+
await new Promise((resolve) => {
|
|
499
|
+
this.element.addEventListener("animationend", () => resolve(), {
|
|
500
|
+
once: true,
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
delete this.element.dataset.showAnimating;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async hide({ animate = true } = {}) {
|
|
509
|
+
if (!this.openValue || this.element.dataset.hideAnimating) return;
|
|
510
|
+
|
|
511
|
+
// notify listeners of pending request
|
|
512
|
+
this.dispatch("hide", { bubbles: true });
|
|
513
|
+
|
|
514
|
+
if (animate) {
|
|
515
|
+
// set animation state
|
|
516
|
+
// this will trigger an animationEnd event via CSS that completes the hide
|
|
517
|
+
this.element.dataset.hideAnimating = "";
|
|
518
|
+
|
|
519
|
+
await new Promise((resolve) => {
|
|
520
|
+
this.element.addEventListener("animationend", () => resolve(), {
|
|
521
|
+
once: true,
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
delete this.element.dataset.hideAnimating;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
this.#hide();
|
|
529
|
+
|
|
530
|
+
this.openValue = false;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
dismiss(event) {
|
|
534
|
+
|
|
535
|
+
if (!this.captiveValue) this.dispatch("dismiss", { bubbles: true });
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
escape(event) {
|
|
539
|
+
if (
|
|
540
|
+
event.key === "Escape" &&
|
|
541
|
+
!this.captiveValue &&
|
|
542
|
+
!event.defaultPrevented
|
|
543
|
+
) {
|
|
544
|
+
this.dispatch("dismiss", { bubbles: true });
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Clips body to viewport size and sets the z-index
|
|
550
|
+
*/
|
|
551
|
+
#show(captive, zIndex, top) {
|
|
552
|
+
this.captiveValue = captive;
|
|
553
|
+
this.zIndexValue = zIndex;
|
|
554
|
+
this.scrollY = top;
|
|
555
|
+
|
|
556
|
+
this.previousPosition = document.body.style.position;
|
|
557
|
+
this.previousTop = document.body.style.top;
|
|
558
|
+
|
|
559
|
+
this.element.style.zIndex = this.zIndexValue;
|
|
560
|
+
document.body.style.top = `-${top}px`;
|
|
561
|
+
document.body.style.position = "fixed";
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Unclips body from viewport size and unsets the z-index
|
|
566
|
+
*/
|
|
567
|
+
#hide() {
|
|
568
|
+
this.captiveValue = this.defaultCaptiveValue;
|
|
569
|
+
this.zIndexValue = this.defaultZIndexValue;
|
|
570
|
+
|
|
571
|
+
resetStyle(this.element, "z-index", null);
|
|
572
|
+
resetStyle(document.body, "position", null);
|
|
573
|
+
resetStyle(document.body, "top", null);
|
|
574
|
+
|
|
575
|
+
window.scrollTo({ left: 0, top: this.scrollY, behavior: "instant" });
|
|
576
|
+
|
|
577
|
+
delete this.scrollY;
|
|
578
|
+
delete this.previousPosition;
|
|
579
|
+
delete this.previousTop;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function resetStyle(element, property, previousValue) {
|
|
584
|
+
if (previousValue) {
|
|
585
|
+
element.style.setProperty(property, previousValue);
|
|
586
|
+
} else {
|
|
587
|
+
element.style.removeProperty(property);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const Definitions = [
|
|
592
|
+
{ identifier: "kpop--close", controllerConstructor: Kpop__CloseController },
|
|
593
|
+
{ identifier: "kpop--frame", controllerConstructor: Kpop__FrameController },
|
|
594
|
+
{ identifier: "kpop--modal", controllerConstructor: Kpop__ModalController },
|
|
595
|
+
{ identifier: "kpop--redirect", controllerConstructor: Kpop__RedirectController },
|
|
596
|
+
{ identifier: "scrim", controllerConstructor: ScrimController },
|
|
597
|
+
];
|
|
598
|
+
|
|
599
|
+
export { Definitions as default };
|