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,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
|
-
*
|
|
42
|
-
*
|
|
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
|
|
45
|
-
* @param
|
|
8
|
+
* @param {Kpop__FrameController} frame
|
|
9
|
+
* @param {Turbo.StreamElement} action
|
|
46
10
|
*/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
e.preventDefault();
|
|
11
|
+
static async open(frame, action) {
|
|
12
|
+
const animate = !frame.isOpen;
|
|
51
13
|
|
|
52
|
-
frame.dismiss({ animate:
|
|
53
|
-
Turbo.visit(e.detail.url);
|
|
14
|
+
await frame.dismiss({ animate, reason: "turbo-stream.kpop_open" });
|
|
54
15
|
|
|
55
|
-
|
|
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.
|
|
69
|
-
|
|
18
|
+
const dialog = frame.element.querySelector("dialog");
|
|
19
|
+
const src = dialog.dataset.src;
|
|
70
20
|
|
|
71
|
-
|
|
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
|
-
<%=
|
|
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>
|
data/lib/katalyst/kpop/engine.rb
CHANGED
|
@@ -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.
|
|
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
|
|
|
@@ -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
|
|
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 –
|
|
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
|
|
40
|
-
def
|
|
41
|
-
matcher = ChainedMatcher.new(ResponseMatcher, CapybaraParser,
|
|
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
|
|
21
|
+
# Passes if `response` contains a kpop turbo frame src set.
|
|
49
22
|
# Supports matching on:
|
|
50
23
|
# * id – turbo frame id
|
|
51
|
-
# *
|
|
24
|
+
# * location - modal location (path)
|
|
52
25
|
#
|
|
53
|
-
# @example Matching
|
|
54
|
-
# expect(response).to
|
|
55
|
-
def
|
|
56
|
-
|
|
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:
|
|
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.
|
|
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/
|
|
93
|
-
- app/javascript/kpop/utils/
|
|
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
|
-
}
|