katalyst-kpop 2.0.9 → 3.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -43
  3. data/app/assets/builds/katalyst/kpop.esm.js +599 -0
  4. data/app/assets/builds/katalyst/kpop.js +479 -519
  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 +45 -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 +15 -0
  25. data/app/javascript/kpop/controllers/close_controller.js +9 -0
  26. data/app/javascript/kpop/controllers/frame_controller.js +189 -0
  27. data/app/javascript/kpop/controllers/modal_controller.js +30 -0
  28. data/app/javascript/kpop/controllers/redirect_controller.js +22 -0
  29. data/app/{assets/javascripts → javascript/kpop}/controllers/scrim_controller.js +76 -72
  30. data/app/javascript/kpop/debug.js +3 -0
  31. data/app/javascript/kpop/modals/content_modal.js +46 -0
  32. data/app/javascript/kpop/modals/frame_modal.js +41 -0
  33. data/app/javascript/kpop/modals/modal.js +69 -0
  34. data/app/javascript/kpop/modals/stream_modal.js +49 -0
  35. data/app/javascript/kpop/modals/stream_renderer.js +15 -0
  36. data/app/views/layouts/kpop.html.erb +1 -1
  37. data/config/importmap.rb +1 -4
  38. data/lib/katalyst/kpop/engine.rb +13 -12
  39. data/lib/katalyst/kpop/matchers/base.rb +18 -0
  40. data/lib/katalyst/kpop/matchers/capybara_matcher.rb +46 -0
  41. data/lib/katalyst/kpop/matchers/capybara_parser.rb +17 -0
  42. data/lib/katalyst/kpop/matchers/chained_matcher.rb +40 -0
  43. data/lib/katalyst/kpop/matchers/frame_matcher.rb +16 -0
  44. data/lib/katalyst/kpop/matchers/modal_matcher.rb +20 -0
  45. data/lib/katalyst/kpop/matchers/redirect_finder.rb +16 -0
  46. data/lib/katalyst/kpop/matchers/redirect_matcher.rb +28 -0
  47. data/lib/katalyst/kpop/matchers/response_matcher.rb +33 -0
  48. data/lib/katalyst/kpop/matchers/stream_matcher.rb +16 -0
  49. data/lib/katalyst/kpop/matchers/title_finder.rb +16 -0
  50. data/lib/katalyst/kpop/matchers/title_matcher.rb +28 -0
  51. data/lib/katalyst/kpop/matchers.rb +79 -0
  52. data/lib/katalyst/kpop/turbo.rb +56 -0
  53. data/lib/katalyst/kpop/version.rb +1 -1
  54. data/lib/katalyst/kpop.rb +4 -0
  55. metadata +90 -15
  56. data/app/assets/builds/katalyst/kpop.css +0 -117
  57. data/app/assets/javascripts/controllers/kpop_controller.js +0 -72
  58. data/app/assets/javascripts/katalyst/kpop.js +0 -9
  59. data/app/assets/stylesheets/katalyst/kpop/_index.scss +0 -2
  60. data/app/assets/stylesheets/katalyst/kpop/_kpop.scss +0 -133
  61. data/app/helpers/kpop/modal.rb +0 -98
  62. data/app/helpers/scrim_helper.rb +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 219158f3706755e88a2275d8ebc3bc20d1b05b0a7d7006d528d0361ae7e987dd
4
- data.tar.gz: ac4cb7baf980d41ecf134b42beae6e1c60ee1ff7266a25173d11bc463353eaaf
3
+ metadata.gz: 5d657aa583bbc374ac12276e50d9d11d85801d6ddf1518cacaeba453893fb0be
4
+ data.tar.gz: 19e213e9b618fb117e8e07ebeb1a5677daa8528f80de07d2f6ff611a208b5b8d
5
5
  SHA512:
6
- metadata.gz: b94b85643fe6035a735c7827de03f38e03408ca40f184e708bcfd9694686060bac4191e4a5e88dd63b7b7bd6be7f4c5ccd9c7b80bd65ff636371e5e342d8ef47
7
- data.tar.gz: 5afc11d9d560f408c94908d9a64ac504a15d9bbd540c690b31f7ceb3678a0455061b7cf1c973a7154b358a9c7d1d1489eb5fed9ed7e5dbdc68f87e1d22f26157
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
- If you are using asset pipeline and import maps then the stimulus controllers
21
- for modals and scrim will be automatically available without configuration.
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
- <%= scrim_tag %>
69
- <%= kpop_frame_tag do %>
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 `kpop_frame_tag`, e.g. using `layout "kpop"`.
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
- <%= modal_link_to "click to open modal", modal_path("example") %>
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
- <%= render_kpop(title: "Modal title") do %>
94
- Modal content
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 `package.json` and `lib/katalyst/kpop/version.rb`
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 };