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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +92 -74
  3. data/app/assets/builds/katalyst/kpop.esm.js +463 -457
  4. data/app/assets/builds/katalyst/kpop.js +463 -457
  5. data/app/assets/builds/katalyst/kpop.min.js +1 -1
  6. data/app/assets/builds/katalyst/kpop.min.js.map +1 -1
  7. data/app/assets/stylesheets/katalyst/kpop.css +69 -0
  8. data/app/components/kpop/frame_component.html.erb +3 -14
  9. data/app/components/kpop/frame_component.rb +15 -11
  10. data/app/components/kpop/modal_component.html.erb +7 -6
  11. data/app/components/kpop/modal_component.rb +9 -32
  12. data/app/controllers/concerns/katalyst/kpop/frame_request.rb +67 -8
  13. data/app/javascript/kpop/application.js +68 -7
  14. data/app/javascript/kpop/controllers/frame_controller.js +96 -66
  15. data/app/javascript/kpop/modals/content_modal.js +2 -58
  16. data/app/javascript/kpop/modals/frame_modal.js +19 -76
  17. data/app/javascript/kpop/modals/modal.js +96 -49
  18. data/app/javascript/kpop/modals/stream_modal.js +11 -62
  19. data/app/javascript/kpop/utils/debug.js +22 -0
  20. data/app/javascript/kpop/utils/link_observer.js +151 -0
  21. data/app/javascript/kpop/utils/ruleset.js +43 -0
  22. data/app/javascript/kpop/utils/stream_actions.js +21 -0
  23. data/app/views/layouts/kpop/frame.html.erb +3 -1
  24. data/app/views/layouts/kpop/stream.html.erb +3 -0
  25. data/lib/katalyst/kpop/engine.rb +1 -8
  26. data/lib/katalyst/kpop/matchers/modal_matcher.rb +1 -1
  27. data/lib/katalyst/kpop/matchers/src_matcher.rb +33 -0
  28. data/lib/katalyst/kpop/matchers.rb +11 -40
  29. metadata +8 -19
  30. data/app/assets/stylesheets/katalyst/kpop/_frame.scss +0 -90
  31. data/app/assets/stylesheets/katalyst/kpop/_modal.scss +0 -88
  32. data/app/assets/stylesheets/katalyst/kpop/_scrim.scss +0 -46
  33. data/app/assets/stylesheets/katalyst/kpop/_side_panel.scss +0 -64
  34. data/app/assets/stylesheets/katalyst/kpop/_variables.scss +0 -24
  35. data/app/assets/stylesheets/katalyst/kpop.scss +0 -6
  36. data/app/components/kpop/modal/footer_component.rb +0 -21
  37. data/app/components/kpop/modal/header_component.rb +0 -21
  38. data/app/components/kpop/modal/title_component.html.erb +0 -6
  39. data/app/components/kpop/modal/title_component.rb +0 -28
  40. data/app/components/scrim_component.rb +0 -32
  41. data/app/helpers/kpop_helper.rb +0 -32
  42. data/app/javascript/kpop/controllers/modal_controller.js +0 -30
  43. data/app/javascript/kpop/controllers/scrim_controller.js +0 -159
  44. data/app/javascript/kpop/debug.js +0 -3
  45. data/app/javascript/kpop/turbo_actions.js +0 -46
  46. data/app/javascript/kpop/utils/stream_renderer.js +0 -15
  47. data/lib/katalyst/kpop/turbo.rb +0 -49
@@ -1,74 +1,23 @@
1
- import { Turbo } from "@hotwired/turbo-rails";
2
-
3
1
  import { Modal } from "./modal";
4
2
 
5
3
  export class StreamModal extends Modal {
6
- constructor(id, action) {
7
- super(id);
8
-
9
- this.action = action;
10
- }
11
-
12
- /**
13
- * When the modal opens, push a state event for the current location so that
14
- * the user can dismiss the modal by navigating back.
15
- *
16
- * @returns {Promise<void>}
17
- */
18
- async open() {
19
- await super.open();
20
-
21
- window.history.pushState({ kpop: true, id: this.id }, "", window.location);
22
- }
23
-
24
- /**
25
- * On dismiss, pop the state event that was pushed when the modal opened,
26
- * then clear any modals from the turbo frame element.
27
- *
28
- * @returns {Promise<void>}
29
- */
30
- async dismiss() {
31
- await super.dismiss();
32
-
33
- if (this.isCurrentLocation) {
34
- await this.pop("popstate", () => window.history.back());
35
- }
36
-
37
- this.frameElement.innerHTML = "";
38
- }
39
-
40
4
  /**
41
- * On navigation from inside the modal, dismiss the modal first so that the
42
- * modal does not appear in the history stack.
5
+ * When a turbo-stream[action=kpop_open] element is rendered, it runs this
6
+ * method to load the modal template as a StreamModal.
43
7
  *
44
- * @param frame TurboFrame element
45
- * @param e Turbo navigation event
8
+ * @param {Kpop__FrameController} frame
9
+ * @param {Turbo.StreamElement} action
46
10
  */
47
- beforeVisit(frame, e) {
48
- super.beforeVisit(frame, e);
49
-
50
- e.preventDefault();
11
+ static async open(frame, action) {
12
+ const animate = !frame.isOpen;
51
13
 
52
- frame.dismiss({ animate: false }).then(() => {
53
- Turbo.visit(e.detail.url);
14
+ await frame.dismiss({ animate, reason: "turbo-stream.kpop_open" });
54
15
 
55
- this.debug("before-visit-end");
56
- });
57
- }
58
-
59
- /**
60
- * If the user pops state, dismiss the modal.
61
- *
62
- * @param frame FrameController
63
- * @param e history event
64
- */
65
- popstate(frame, e) {
66
- super.popstate(frame, e);
16
+ frame.element.append(action.templateContent);
67
17
 
68
- frame.dismiss({ animate: true, reason: "popstate" });
69
- }
18
+ const dialog = frame.element.querySelector("dialog");
19
+ const src = dialog.dataset.src;
70
20
 
71
- get isCurrentLocation() {
72
- return window.history.state?.kpop && window.history.state?.id === this.id;
21
+ await frame.open(new StreamModal(frame, dialog, src), { animate });
73
22
  }
74
23
  }
@@ -0,0 +1,22 @@
1
+ let enabled = false;
2
+
3
+ const debug = function (receiver) {
4
+ if (enabled) {
5
+ return console.debug.bind(console, "[%s] %s", receiver);
6
+ } else {
7
+ return noop;
8
+ }
9
+ };
10
+
11
+ const noop = () => {};
12
+
13
+ Object.defineProperty(debug, "enabled", {
14
+ get: function () {
15
+ return enabled;
16
+ },
17
+ set: function (debug) {
18
+ enabled = debug;
19
+ },
20
+ });
21
+
22
+ export default debug;
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Based on Turbo's LinkObserver, checks links on mouse-over and focus to see
3
+ * whether they should open in modals. If they should, then sets the
4
+ * data-turbo-frame attribute so it will be prefetched and opened in the context
5
+ * of the kpop turbo frame.
6
+ */
7
+ export default class LinkObserver {
8
+ started = false;
9
+
10
+ constructor(delegate, eventTarget) {
11
+ this.delegate = delegate;
12
+ this.eventTarget = eventTarget;
13
+ }
14
+
15
+ start() {
16
+ if (this.started) return;
17
+ if (this.eventTarget.readyState === "loading") {
18
+ this.eventTarget.addEventListener("DOMContentLoaded", this.#enable, {
19
+ once: true,
20
+ });
21
+ } else {
22
+ this.#enable();
23
+ }
24
+ }
25
+
26
+ stop() {
27
+ if (!this.started) return;
28
+ this.eventTarget.removeEventListener("mouseenter", this.#addKpopLink, {
29
+ capture: true,
30
+ passive: true,
31
+ });
32
+ this.eventTarget.removeEventListener(
33
+ "turbo:before-prefetch",
34
+ this.#addKpopLink,
35
+ {
36
+ capture: true,
37
+ passive: true,
38
+ },
39
+ );
40
+ this.eventTarget.removeEventListener("focusin", this.#addKpopLink, {
41
+ capture: true,
42
+ passive: true,
43
+ });
44
+ this.eventTarget.removeEventListener("mouseleave", this.#removeKpopLink, {
45
+ capture: true,
46
+ passive: true,
47
+ });
48
+ this.eventTarget.removeEventListener("focusout", this.#removeKpopLink, {
49
+ capture: true,
50
+ passive: true,
51
+ });
52
+ this.started = false;
53
+ }
54
+
55
+ #enable = () => {
56
+ if (this.started) return;
57
+ this.started = true;
58
+ this.eventTarget.addEventListener("mouseenter", this.#addKpopLink, {
59
+ capture: true,
60
+ passive: true,
61
+ });
62
+ this.eventTarget.addEventListener("focusin", this.#addKpopLink, {
63
+ capture: true,
64
+ passive: true,
65
+ });
66
+ this.eventTarget.addEventListener(
67
+ "turbo:before-prefetch",
68
+ this.#addKpopLink,
69
+ {
70
+ capture: true,
71
+ passive: true,
72
+ },
73
+ );
74
+ this.eventTarget.addEventListener("mouseleave", this.#removeKpopLink, {
75
+ capture: true,
76
+ passive: true,
77
+ });
78
+ this.eventTarget.addEventListener("focusout", this.#removeKpopLink, {
79
+ capture: true,
80
+ passive: true,
81
+ });
82
+ };
83
+
84
+ #addKpopLink = (event) => {
85
+ const target = event.target;
86
+ const isLink =
87
+ target.matches &&
88
+ target.matches(
89
+ "a[href]:not([target^=_]):not([download]):not([data-turbo-frame]",
90
+ );
91
+ if (isLink && this.#isPrefetchable(target)) {
92
+ const link = target;
93
+ const location = getLocationForLink(link);
94
+ if (this.delegate.isModalLink(link, location)) {
95
+ link.dataset.turboFrame = "kpop";
96
+ }
97
+ }
98
+ };
99
+
100
+ #removeKpopLink = (event) => {
101
+ const target = event.target;
102
+ const isLink =
103
+ target.matches && target.matches("a[href][data-turbo-frame='kpop']");
104
+ if (isLink) {
105
+ delete target.dataset.turboFrame;
106
+ }
107
+ };
108
+
109
+ #isPrefetchable(link) {
110
+ const href = link.getAttribute("href");
111
+ if (!href) return false;
112
+ if (unfetchableLink(link)) return false;
113
+ if (linkToTheSamePage(link)) return false;
114
+ if (linkOptsOut(link)) return false;
115
+ if (nonSafeLink(link)) return false;
116
+ return true;
117
+ }
118
+ }
119
+
120
+ function getLocationForLink(link) {
121
+ return new URL(link.getAttribute("href").toString(), document.baseURI);
122
+ }
123
+
124
+ const unfetchableLink = (link) =>
125
+ link.origin !== document.location.origin ||
126
+ !["http:", "https:"].includes(link.protocol) ||
127
+ link.hasAttribute("target");
128
+
129
+ const linkToTheSamePage = (link) =>
130
+ link.pathname + link.search ===
131
+ document.location.pathname + document.location.search ||
132
+ link.href.startsWith("#");
133
+
134
+ const linkOptsOut = (link) => {
135
+ return link.getAttribute("data-turbo") === "false";
136
+ };
137
+
138
+ const nonSafeLink = (link) => {
139
+ const turboMethod = link.getAttribute("data-turbo-method");
140
+ if (turboMethod && turboMethod.toLowerCase() !== "get") return true;
141
+ if (isUJS(link)) return true;
142
+ if (link.hasAttribute("data-turbo-confirm")) return true;
143
+ if (link.hasAttribute("data-turbo-stream")) return true;
144
+ return false;
145
+ };
146
+
147
+ const isUJS = (link) =>
148
+ link.hasAttribute("data-remote") ||
149
+ link.hasAttribute("data-behavior") ||
150
+ link.hasAttribute("data-confirm") ||
151
+ link.hasAttribute("data-method");
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Similar to Hotwire's PathConfiguration.json, this class compiles a list of
3
+ * rules to check link hrefs against so that we can identify links that
4
+ * should open in a KPOP modal.
5
+ *
6
+ * Unlike Hotwire Native, we can't intercept 303s in the browser before they
7
+ * load. Browser sandbox prevents us from inspecting the location of redirect
8
+ * requests so we can only intercept links that match modals directly.
9
+ *
10
+ * For posts and redirects, we need server support (flash modals, streams).
11
+ */
12
+ export default class Ruleset {
13
+ constructor(rules = []) {
14
+ this.rules = [];
15
+
16
+ rules.forEach((rule) => {
17
+ this.#compileRule(rule);
18
+ });
19
+ }
20
+
21
+ /**
22
+ * Returns properties for the given URL
23
+ *
24
+ * @param {URL} location
25
+ * @returns {} properties
26
+ */
27
+ properties(location) {
28
+ return this.rules.reduce((c, f) => f(location, c), {});
29
+ }
30
+
31
+ #compileRule({ patterns, properties }) {
32
+ patterns.forEach((pattern) => {
33
+ this.rules.push(locationMatcher(new RegExp(pattern), properties));
34
+ });
35
+ }
36
+ }
37
+
38
+ function locationMatcher(re, properties) {
39
+ return (location, accumulator) =>
40
+ re.test(location.pathname)
41
+ ? { ...accumulator, ...properties }
42
+ : accumulator;
43
+ }
@@ -0,0 +1,21 @@
1
+ import { Turbo } from "@hotwired/turbo-rails";
2
+
3
+ import { StreamModal } from "../modals/stream_modal";
4
+
5
+ export default class StreamActions {
6
+ start() {
7
+ Turbo.StreamActions.kpop_open = openStreamModal;
8
+ }
9
+
10
+ stop() {
11
+ delete Turbo.StreamActions.kpop_open;
12
+ }
13
+ }
14
+
15
+ function openStreamModal() {
16
+ const frame = this.targetElements[0]?.kpop;
17
+
18
+ if (frame) {
19
+ StreamModal.open(frame, this).then(() => {});
20
+ }
21
+ }
@@ -11,7 +11,9 @@
11
11
  </head>
12
12
  <body>
13
13
  <%= render Kpop::FrameComponent.new do %>
14
- <%= yield %>
14
+ <%= render(Kpop::ModalComponent.new(title: content_for(:title), modal_class: content_for(:modal_class))) do %>
15
+ <%= yield %>
16
+ <% end %>
15
17
  <% end %>
16
18
  </body>
17
19
  </html>
@@ -0,0 +1,3 @@
1
+ <%= render(Kpop::ModalComponent.new(title: content_for(:title), modal_class: content_for(:modal_class))) do %>
2
+ <%= yield %>
3
+ <% end %>
@@ -11,7 +11,6 @@ module Katalyst
11
11
  isolate_namespace Katalyst::Kpop
12
12
  config.eager_load_namespaces << Katalyst::Kpop
13
13
  config.autoload_once_paths = %W(
14
- #{root}/app/helpers
15
14
  #{root}/app/controllers
16
15
  #{root}/app/controllers/concerns
17
16
  )
@@ -25,15 +24,9 @@ module Katalyst
25
24
  end
26
25
  end
27
26
 
28
- initializer "kpop.helpers", before: :load_config_initializers do
29
- ::Turbo::Streams::TagBuilder.define_method(:kpop) do
30
- Katalyst::Kpop::Turbo::TagBuilder.new(self)
31
- end
32
-
27
+ initializer "kpop.frame", before: :load_config_initializers do
33
28
  ActiveSupport.on_load(:action_controller_base) do
34
29
  include Katalyst::Kpop::FrameRequest
35
-
36
- helper Katalyst::Kpop::Engine.helpers
37
30
  end
38
31
  end
39
32
 
@@ -6,7 +6,7 @@ module Katalyst
6
6
  # @api private
7
7
  class ModalMatcher < CapybaraMatcher
8
8
  def initialize
9
- super("[data-controller*='kpop--modal']")
9
+ super("dialog")
10
10
  end
11
11
 
12
12
  def description
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Katalyst
4
+ module Kpop
5
+ module Matchers
6
+ # @api private
7
+ class SrcMatcher < Base
8
+ def description
9
+ "contain a kpop frame with src #{expected.inspect}"
10
+ end
11
+
12
+ def match(expected, actual) # rubocop:disable Naming/PredicateMethod
13
+ case expected
14
+ when String
15
+ actual[:src].to_s.eql?(expected)
16
+ when Regexp
17
+ actual[:src].to_s.match?(expected)
18
+ else
19
+ raise ArgumentError, expected
20
+ end
21
+ end
22
+
23
+ def failure_message
24
+ "expected a kpop frame with src #{expected.inspect} but received #{actual.native.to_html.inspect} instead"
25
+ end
26
+
27
+ def failure_message_when_negated
28
+ "expected not to find a kpop frame with src #{expected}"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -4,58 +4,29 @@ module Katalyst
4
4
  module Kpop
5
5
  module Matchers
6
6
  # @api public
7
- # Passes if `response` contains a turbo response with a kpop dismiss action.
8
- #
9
- # @example
10
- # expect(response).to kpop_dismiss
11
- def kpop_dismiss(id: "kpop")
12
- ChainedMatcher.new(ResponseMatcher,
13
- CapybaraParser,
14
- StreamMatcher.new(id:, action: "kpop_dismiss"))
15
- end
16
-
17
- # @api public
18
- # Passes if `response` contains a turbo response with a kpop redirect to
19
- # the provided `target`.
20
- #
21
- # @example Matching a path against a turbo response containing a kpop redirect
22
- # expect(response).to kpop_redirect_to("/path/to/resource")
23
- def kpop_redirect_to(target, id: "kpop")
24
- raise ArgumentError, "Invalid target: nil" unless target
25
-
26
- ChainedMatcher.new(ResponseMatcher,
27
- CapybaraParser,
28
- StreamMatcher.new(id:, action: "kpop_redirect_to"),
29
- RedirectMatcher.new(target))
30
- end
31
-
32
- # @api public
33
- # Passes if `response` contains a turbo stream response with a kpop modal.
7
+ # Passes if `response` contains a turbo frame with a kpop modal.
34
8
  # Supports matching on:
35
- # * id – kpop frame id
9
+ # * id – turbo frame id
36
10
  # * title - modal title
37
11
  #
38
12
  # @example Matching turbo stream response with a Shopping Cart modal
39
- # expect(response).to render_kpop_stream(title: "Shopping Cart")
40
- def render_kpop_stream(id: "kpop", title: nil)
41
- matcher = ChainedMatcher.new(ResponseMatcher, CapybaraParser, StreamMatcher.new(id:, action: "kpop_open"),
42
- ModalMatcher)
13
+ # expect(response).to render_kpop_frame(title: "Shopping Cart")
14
+ def render_kpop_frame(id: "kpop", title: nil)
15
+ matcher = ChainedMatcher.new(ResponseMatcher, CapybaraParser, FrameMatcher.new(id:), ModalMatcher)
43
16
  matcher << TitleFinder << TitleMatcher.new(title) if title.present?
44
17
  matcher
45
18
  end
46
19
 
47
20
  # @api public
48
- # Passes if `response` contains a turbo frame with a kpop modal.
21
+ # Passes if `response` contains a kpop turbo frame src set.
49
22
  # Supports matching on:
50
23
  # * id – turbo frame id
51
- # * title - modal title
24
+ # * location - modal location (path)
52
25
  #
53
- # @example Matching turbo stream response with a Shopping Cart modal
54
- # expect(response).to render_kpop_frame(title: "Shopping Cart")
55
- def render_kpop_frame(id: "kpop", title: nil)
56
- matcher = ChainedMatcher.new(ResponseMatcher, CapybaraParser, FrameMatcher.new(id:), ModalMatcher)
57
- matcher << TitleFinder << TitleMatcher.new(title) if title.present?
58
- matcher
26
+ # @example Matching a response that will async load `/cart` as a modal
27
+ # expect(response).to have_kpop_src("/cart")
28
+ def have_kpop_src(location, id: "kpop") # rubocop:disable Naming/PredicatePrefix
29
+ ChainedMatcher.new(ResponseMatcher, CapybaraParser, FrameMatcher.new(id:), SrcMatcher.new(location))
59
30
  end
60
31
  end
61
32
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: katalyst-kpop
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.4.0
4
+ version: 4.0.0.beta.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Katalyst Interactive
@@ -63,35 +63,24 @@ files:
63
63
  - app/assets/builds/katalyst/kpop.min.js
64
64
  - app/assets/builds/katalyst/kpop.min.js.map
65
65
  - app/assets/config/katalyst-kpop.js
66
- - app/assets/stylesheets/katalyst/kpop.scss
67
- - app/assets/stylesheets/katalyst/kpop/_frame.scss
68
- - app/assets/stylesheets/katalyst/kpop/_modal.scss
69
- - app/assets/stylesheets/katalyst/kpop/_scrim.scss
70
- - app/assets/stylesheets/katalyst/kpop/_side_panel.scss
71
- - app/assets/stylesheets/katalyst/kpop/_variables.scss
66
+ - app/assets/stylesheets/katalyst/kpop.css
72
67
  - app/components/kpop/frame_component.html.erb
73
68
  - app/components/kpop/frame_component.rb
74
- - app/components/kpop/modal/footer_component.rb
75
- - app/components/kpop/modal/header_component.rb
76
- - app/components/kpop/modal/title_component.html.erb
77
- - app/components/kpop/modal/title_component.rb
78
69
  - app/components/kpop/modal_component.html.erb
79
70
  - app/components/kpop/modal_component.rb
80
- - app/components/scrim_component.rb
81
71
  - app/controllers/concerns/katalyst/kpop/frame_request.rb
82
- - app/helpers/kpop_helper.rb
83
72
  - app/javascript/kpop/application.js
84
73
  - app/javascript/kpop/controllers/frame_controller.js
85
- - app/javascript/kpop/controllers/modal_controller.js
86
- - app/javascript/kpop/controllers/scrim_controller.js
87
- - app/javascript/kpop/debug.js
88
74
  - app/javascript/kpop/modals/content_modal.js
89
75
  - app/javascript/kpop/modals/frame_modal.js
90
76
  - app/javascript/kpop/modals/modal.js
91
77
  - app/javascript/kpop/modals/stream_modal.js
92
- - app/javascript/kpop/turbo_actions.js
93
- - app/javascript/kpop/utils/stream_renderer.js
78
+ - app/javascript/kpop/utils/debug.js
79
+ - app/javascript/kpop/utils/link_observer.js
80
+ - app/javascript/kpop/utils/ruleset.js
81
+ - app/javascript/kpop/utils/stream_actions.js
94
82
  - app/views/layouts/kpop/frame.html.erb
83
+ - app/views/layouts/kpop/stream.html.erb
95
84
  - config/importmap.rb
96
85
  - lib/katalyst/kpop.rb
97
86
  - lib/katalyst/kpop/engine.rb
@@ -104,10 +93,10 @@ files:
104
93
  - lib/katalyst/kpop/matchers/modal_matcher.rb
105
94
  - lib/katalyst/kpop/matchers/redirect_matcher.rb
106
95
  - lib/katalyst/kpop/matchers/response_matcher.rb
96
+ - lib/katalyst/kpop/matchers/src_matcher.rb
107
97
  - lib/katalyst/kpop/matchers/stream_matcher.rb
108
98
  - lib/katalyst/kpop/matchers/title_finder.rb
109
99
  - lib/katalyst/kpop/matchers/title_matcher.rb
110
- - lib/katalyst/kpop/turbo.rb
111
100
  homepage: https://github.com/katalyst/kpop
112
101
  licenses:
113
102
  - MIT
@@ -1,90 +0,0 @@
1
- @use "variables" as *;
2
-
3
- .kpop--container {
4
- display: none;
5
-
6
- position: fixed;
7
- left: 0;
8
- top: 0;
9
- right: 0;
10
- bottom: 0;
11
-
12
- align-items: center;
13
- z-index: 100;
14
- pointer-events: none;
15
-
16
- > * {
17
- pointer-events: auto;
18
- }
19
- }
20
-
21
- .kpop--frame {
22
- --opening-animation: slide-in-up;
23
- --closing-animation: slide-out-down;
24
-
25
- position: relative;
26
- display: grid;
27
- margin: 0 auto;
28
-
29
- --min-width: #{$min-width};
30
- --max-width: #{$max-width};
31
- --min-height: #{$min-height};
32
- --max-height: #{$max-height};
33
-
34
- min-width: var(--min-width);
35
- max-width: var(--max-width);
36
- min-height: var(--min-height);
37
- max-height: var(--max-height);
38
-
39
- grid-template-columns: min(var(--max-width), max(var(--min-width), 100%));
40
- grid-template-rows: min(var(--max-height), max(var(--min-height), 100%));
41
- }
42
-
43
- @media (max-width: $mobile-width), (max-height: $mobile-height) {
44
- .kpop--frame {
45
- --min-width: 100dvw;
46
- --max-width: 100dvw;
47
- --min-height: 30dvh;
48
- --max-height: calc(100dvh - 1.5rem);
49
- }
50
- }
51
-
52
- .scrim[data-scrim-open-value="false"] + .kpop--container .kpop--frame {
53
- display: none;
54
- }
55
-
56
- .scrim[data-hide-animating]
57
- + .kpop--container
58
- .kpop--frame[data-kpop--frame-open-value="true"] {
59
- animation: var(--closing-animation);
60
- animation-duration: $duration;
61
- animation-fill-mode: forwards;
62
- }
63
-
64
- .scrim[data-show-animating] + .kpop--container .kpop--frame {
65
- animation: var(--opening-animation);
66
- animation-duration: $duration;
67
- animation-fill-mode: forwards;
68
- }
69
-
70
- @keyframes slide-in-up {
71
- 0% {
72
- transform: translateY(10%);
73
- opacity: 0;
74
- }
75
- 100% {
76
- transform: translateY(0%);
77
- opacity: 1;
78
- }
79
- }
80
-
81
- @keyframes slide-out-down {
82
- 0% {
83
- transform: translateY(0%);
84
- opacity: 1;
85
- }
86
- 100% {
87
- transform: translateY(10%);
88
- opacity: 0;
89
- }
90
- }