overlastic 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e71d0d0cb84a1e1576f09f7e7329ceaf6601dd46cc2045caba2e2aec3ee4f5ba
4
+ data.tar.gz: fa2a39ad530363af3d3d1b9b04a07f95049d7865774d1684f8e50ac2aa88d9ec
5
+ SHA512:
6
+ metadata.gz: c136cb0313643c9fa6c15e73c93e2ad0725920f10548ef848ae784a2418dcefb971d25b45bf393bb2c63e2ca71455e6c7dc24c2f54c6187e23ff3fd3934888e9
7
+ data.tar.gz: d906bc365ffa890bd4543700cccd7eb200accbdea7086fb8701161cf6239608ac7633ec874794ff8bf7068ac75bd58f2f506414f34ae36ddf3c7389b88758d73
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2022 Martin Zamuner
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # Overlastic
2
+
3
+ Load any page inside an overlay (dialog modal, slide-out pane, or whatever else floats your boat). As easy as replacing `link_to` with `link_to_dialog`.
4
+
5
+
6
+ ## Installation
7
+
8
+ This gem requires a modern Rails application running [turbo-rails](https://github.com/hotwired/turbo-rails). It supports both import map and node setups.
9
+
10
+ 1. Add the `overlastic` gem to your Gemfile: `gem "overlastic"`
11
+ 2. Run `./bin/bundle install`
12
+ 3. Run `./bin/rails overlastic:install`
13
+
14
+
15
+ ## Usage
16
+
17
+ Most of the time you'll just need to replace a `link_to` with one of the overlay helpers:
18
+
19
+ ```erb
20
+ <%= link_to_dialog "Open dialog", edit_article_path %>
21
+ <%= link_to_pane "Open slide-out pane", edit_article_path %>
22
+ <%= link_to_overlay "Open default overlay type", edit_article_path %>
23
+ <%= link_to_overlay "Open dialog", edit_article_path, overlay_type: :dialog %>
24
+ ```
25
+
26
+ They work just as `link_to` and accept the same options. You can also pass locals to the overlay view:
27
+
28
+ ```erb
29
+ <%= link_to_dialog "Open dialog", edit_article_path, overlay_args: { title: "Dialog title" } %>
30
+ ```
31
+
32
+ By default, overlays stack on top of each other. You can instead replace the last one or the whole stack:
33
+
34
+ ```erb
35
+ <%= link_to_dialog "Open dialog", edit_article_path, overlay_action: :replace_last %>
36
+ <%= link_to_dialog "Open dialog", edit_article_path, overlay_action: :replace_all %>
37
+ ```
38
+
39
+ Sometimes, you may want to alter the content depending on whether it's inside an overlay or not. Overlastic defines a new `:overlay` request variant that you can use to create custom partials like `_form.html+overlay.erb` or inside a controller like so:
40
+
41
+ ```rb
42
+ respond_to do |format|
43
+ format.html.overlay { render :custom_view }
44
+ format.html
45
+ end
46
+ ```
47
+
48
+
49
+ ## Configuration
50
+
51
+ ```rb
52
+ # config/initializers/overlastic.rb
53
+
54
+ Overlastic.configure do |config|
55
+ config.overlay_types = %i[dialog pane]
56
+ config.default_overlay = :dialog
57
+ config.default_action = :stack
58
+
59
+ # You can define a custom partial for each overlay type
60
+ config.dialog_overlay_view_path = "shared/overlays/dialog"
61
+ config.dialog_overlay_view_path = "shared/overlays/pane"
62
+ end
63
+ ```
64
+
65
+ ## Development
66
+
67
+ <details>
68
+ <summary>Roadmap</summary><br>
69
+
70
+ - Generator to allow easy customization of the overlay views
71
+ - Handle 4xx responses (p.e. validation errors) when submitting forms inside an overlay
72
+ </details>
73
+
74
+ <details>
75
+ <summary>Running the demo application</summary><br>
76
+
77
+ - First you need to install dependencies with `bundle && yarn && yarn build`
78
+ - Then you need to setup the DB with `./bin/rails db:migrate`
79
+ - Lastly you can run the demo app with `./bin/rails server --port 3000`
80
+ </details>
81
+
82
+ <details>
83
+ <summary>Running the tests</summary><br>
84
+
85
+ - You can run the whole suite with `./bin/test`
86
+ </details>
87
+
88
+
89
+ ## License
90
+
91
+ Overlastic is released under the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require "bundler/setup"
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+
5
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
6
+ load "rails/tasks/engine.rake"
7
+ load "rails/tasks/statistics.rake"
8
+
9
+ Rake::TestTask.new do |test|
10
+ test.libs << "test"
11
+ test.test_files = FileList["test/**/*_test.rb"]
12
+ test.warning = false
13
+ end
14
+
15
+ task default: :test
@@ -0,0 +1,263 @@
1
+ function _toConsumableArray(arr) {
2
+ if (Array.isArray(arr)) {
3
+ for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) {
4
+ arr2[i] = arr[i];
5
+ }
6
+ return arr2;
7
+ } else {
8
+ return Array.from(arr);
9
+ }
10
+ }
11
+
12
+ var hasPassiveEvents = false;
13
+
14
+ if (typeof window !== "undefined") {
15
+ var passiveTestOptions = {
16
+ get passive() {
17
+ hasPassiveEvents = true;
18
+ return undefined;
19
+ }
20
+ };
21
+ window.addEventListener("testPassive", null, passiveTestOptions);
22
+ window.removeEventListener("testPassive", null, passiveTestOptions);
23
+ }
24
+
25
+ var isIosDevice = typeof window !== "undefined" && window.navigator && window.navigator.platform && (/iP(ad|hone|od)/.test(window.navigator.platform) || window.navigator.platform === "MacIntel" && window.navigator.maxTouchPoints > 1);
26
+
27
+ var locks = [];
28
+
29
+ var documentListenerAdded = false;
30
+
31
+ var initialClientY = -1;
32
+
33
+ var previousBodyOverflowSetting = void 0;
34
+
35
+ var previousBodyPosition = void 0;
36
+
37
+ var previousBodyPaddingRight = void 0;
38
+
39
+ var allowTouchMove = function allowTouchMove(el) {
40
+ return locks.some((function(lock) {
41
+ if (lock.options.allowTouchMove && lock.options.allowTouchMove(el)) {
42
+ return true;
43
+ }
44
+ return false;
45
+ }));
46
+ };
47
+
48
+ var preventDefault = function preventDefault(rawEvent) {
49
+ var e = rawEvent || window.event;
50
+ if (allowTouchMove(e.target)) {
51
+ return true;
52
+ }
53
+ if (e.touches.length > 1) return true;
54
+ if (e.preventDefault) e.preventDefault();
55
+ return false;
56
+ };
57
+
58
+ var setOverflowHidden = function setOverflowHidden(options) {
59
+ if (previousBodyPaddingRight === undefined) {
60
+ var _reserveScrollBarGap = !!options && options.reserveScrollBarGap === true;
61
+ var scrollBarGap = window.innerWidth - document.documentElement.clientWidth;
62
+ if (_reserveScrollBarGap && scrollBarGap > 0) {
63
+ var computedBodyPaddingRight = parseInt(window.getComputedStyle(document.body).getPropertyValue("padding-right"), 10);
64
+ previousBodyPaddingRight = document.body.style.paddingRight;
65
+ document.body.style.paddingRight = computedBodyPaddingRight + scrollBarGap + "px";
66
+ }
67
+ }
68
+ if (previousBodyOverflowSetting === undefined) {
69
+ previousBodyOverflowSetting = document.body.style.overflow;
70
+ document.body.style.overflow = "hidden";
71
+ }
72
+ };
73
+
74
+ var restoreOverflowSetting = function restoreOverflowSetting() {
75
+ if (previousBodyPaddingRight !== undefined) {
76
+ document.body.style.paddingRight = previousBodyPaddingRight;
77
+ previousBodyPaddingRight = undefined;
78
+ }
79
+ if (previousBodyOverflowSetting !== undefined) {
80
+ document.body.style.overflow = previousBodyOverflowSetting;
81
+ previousBodyOverflowSetting = undefined;
82
+ }
83
+ };
84
+
85
+ var setPositionFixed = function setPositionFixed() {
86
+ return window.requestAnimationFrame((function() {
87
+ if (previousBodyPosition === undefined) {
88
+ previousBodyPosition = {
89
+ position: document.body.style.position,
90
+ top: document.body.style.top,
91
+ left: document.body.style.left
92
+ };
93
+ var _window = window, scrollY = _window.scrollY, scrollX = _window.scrollX, innerHeight = _window.innerHeight;
94
+ document.body.style.position = "fixed";
95
+ document.body.style.top = -scrollY;
96
+ document.body.style.left = -scrollX;
97
+ setTimeout((function() {
98
+ return window.requestAnimationFrame((function() {
99
+ var bottomBarHeight = innerHeight - window.innerHeight;
100
+ if (bottomBarHeight && scrollY >= innerHeight) {
101
+ document.body.style.top = -(scrollY + bottomBarHeight);
102
+ }
103
+ }));
104
+ }), 300);
105
+ }
106
+ }));
107
+ };
108
+
109
+ var restorePositionSetting = function restorePositionSetting() {
110
+ if (previousBodyPosition !== undefined) {
111
+ var y = -parseInt(document.body.style.top, 10);
112
+ var x = -parseInt(document.body.style.left, 10);
113
+ document.body.style.position = previousBodyPosition.position;
114
+ document.body.style.top = previousBodyPosition.top;
115
+ document.body.style.left = previousBodyPosition.left;
116
+ window.scrollTo(x, y);
117
+ previousBodyPosition = undefined;
118
+ }
119
+ };
120
+
121
+ var isTargetElementTotallyScrolled = function isTargetElementTotallyScrolled(targetElement) {
122
+ return targetElement ? targetElement.scrollHeight - targetElement.scrollTop <= targetElement.clientHeight : false;
123
+ };
124
+
125
+ var handleScroll = function handleScroll(event, targetElement) {
126
+ var clientY = event.targetTouches[0].clientY - initialClientY;
127
+ if (allowTouchMove(event.target)) {
128
+ return false;
129
+ }
130
+ if (targetElement && targetElement.scrollTop === 0 && clientY > 0) {
131
+ return preventDefault(event);
132
+ }
133
+ if (isTargetElementTotallyScrolled(targetElement) && clientY < 0) {
134
+ return preventDefault(event);
135
+ }
136
+ event.stopPropagation();
137
+ return true;
138
+ };
139
+
140
+ var disableBodyScroll$1 = function disableBodyScroll(targetElement, options) {
141
+ if (!targetElement) {
142
+ console.error("disableBodyScroll unsuccessful - targetElement must be provided when calling disableBodyScroll on IOS devices.");
143
+ return;
144
+ }
145
+ if (locks.some((function(lock) {
146
+ return lock.targetElement === targetElement;
147
+ }))) {
148
+ return;
149
+ }
150
+ var lock = {
151
+ targetElement: targetElement,
152
+ options: options || {}
153
+ };
154
+ locks = [].concat(_toConsumableArray(locks), [ lock ]);
155
+ if (isIosDevice) {
156
+ setPositionFixed();
157
+ } else {
158
+ setOverflowHidden(options);
159
+ }
160
+ if (isIosDevice) {
161
+ targetElement.ontouchstart = function(event) {
162
+ if (event.targetTouches.length === 1) {
163
+ initialClientY = event.targetTouches[0].clientY;
164
+ }
165
+ };
166
+ targetElement.ontouchmove = function(event) {
167
+ if (event.targetTouches.length === 1) {
168
+ handleScroll(event, targetElement);
169
+ }
170
+ };
171
+ if (!documentListenerAdded) {
172
+ document.addEventListener("touchmove", preventDefault, hasPassiveEvents ? {
173
+ passive: false
174
+ } : undefined);
175
+ documentListenerAdded = true;
176
+ }
177
+ }
178
+ };
179
+
180
+ var enableBodyScroll$1 = function enableBodyScroll(targetElement) {
181
+ if (!targetElement) {
182
+ console.error("enableBodyScroll unsuccessful - targetElement must be provided when calling enableBodyScroll on IOS devices.");
183
+ return;
184
+ }
185
+ locks = locks.filter((function(lock) {
186
+ return lock.targetElement !== targetElement;
187
+ }));
188
+ if (isIosDevice) {
189
+ targetElement.ontouchstart = null;
190
+ targetElement.ontouchmove = null;
191
+ if (documentListenerAdded && locks.length === 0) {
192
+ document.removeEventListener("touchmove", preventDefault, hasPassiveEvents ? {
193
+ passive: false
194
+ } : undefined);
195
+ documentListenerAdded = false;
196
+ }
197
+ }
198
+ if (isIosDevice) {
199
+ restorePositionSetting();
200
+ } else {
201
+ restoreOverflowSetting();
202
+ }
203
+ };
204
+
205
+ addEventListener("click", (event => {
206
+ window._overlasticTarget = event.target.closest("[data-turbo-frame*=overlay]");
207
+ }));
208
+
209
+ addEventListener("turbo:before-fetch-request", (event => {
210
+ if (!window._overlasticTarget) return;
211
+ const target = window._overlasticTarget;
212
+ const type = target?.dataset?.overlayType;
213
+ const args = target?.dataset?.overlayArgs;
214
+ if (type) {
215
+ event.detail.fetchOptions.headers["Overlay-Type"] = type;
216
+ }
217
+ if (args) {
218
+ event.detail.fetchOptions.headers["Overlay-Args"] = args;
219
+ }
220
+ delete window._overlasticTarget;
221
+ }));
222
+
223
+ class DialogElement extends HTMLElement {
224
+ connectedCallback() {
225
+ disableBodyScroll(this);
226
+ this.addEventListener("click", (event => this.close(event, true)));
227
+ this.querySelector(".overlastic-close").addEventListener("click", (event => this.close(event)));
228
+ }
229
+ close(event, self = false) {
230
+ if (self && event.target !== this) return;
231
+ enableBodyScroll(this);
232
+ setTimeout((() => {
233
+ this.remove();
234
+ }), 5);
235
+ }
236
+ }
237
+
238
+ customElements.define("overlastic-dialog", DialogElement);
239
+
240
+ class PaneElement extends DialogElement {
241
+ connectedCallback() {
242
+ super.connectedCallback();
243
+ const lastVisit = Turbo.navigator.history.location;
244
+ if (!window.modalVisitStack) {
245
+ window.modalVisitStack = [];
246
+ }
247
+ window.modalVisitStack.push(lastVisit);
248
+ Turbo.navigator.history.push(new URL(this.parentElement.src));
249
+ }
250
+ close(event, self = false) {
251
+ if (self && event.target !== this) return;
252
+ super.close(event, self);
253
+ if (window.modalVisitStack.length > 0) {
254
+ Turbo.navigator.history.replace(window.modalVisitStack.pop());
255
+ }
256
+ }
257
+ }
258
+
259
+ customElements.define("overlastic-pane", PaneElement);
260
+
261
+ window.disableBodyScroll = disableBodyScroll$1;
262
+
263
+ window.enableBodyScroll = enableBodyScroll$1;
@@ -0,0 +1,2 @@
1
+ var e=!1;if("undefined"!=typeof window){var t={get passive(){e=!0}};window.addEventListener("testPassive",null,t),window.removeEventListener("testPassive",null,t)}var o="undefined"!=typeof window&&window.navigator&&window.navigator.platform&&(/iP(ad|hone|od)/.test(window.navigator.platform)||"MacIntel"===window.navigator.platform&&window.navigator.maxTouchPoints>1),n=[],i=!1,r=-1,l=void 0,d=void 0,s=void 0,a=function(e){return n.some((function(t){return!(!t.options.allowTouchMove||!t.options.allowTouchMove(e))}))},c=function(e){var t=e||window.event;return!!a(t.target)||(t.touches.length>1||(t.preventDefault&&t.preventDefault(),!1))};addEventListener("click",(e=>{window._overlasticTarget=e.target.closest("[data-turbo-frame*=overlay]")})),addEventListener("turbo:before-fetch-request",(e=>{if(!window._overlasticTarget)return;const t=window._overlasticTarget,o=t?.dataset?.overlayType,n=t?.dataset?.overlayArgs;o&&(e.detail.fetchOptions.headers["Overlay-Type"]=o),n&&(e.detail.fetchOptions.headers["Overlay-Args"]=n),delete window._overlasticTarget}));class u extends HTMLElement{connectedCallback(){disableBodyScroll(this),this.addEventListener("click",(e=>this.close(e,!0))),this.querySelector(".overlastic-close").addEventListener("click",(e=>this.close(e)))}close(e,t=!1){t&&e.target!==this||(enableBodyScroll(this),setTimeout((()=>{this.remove()}),5))}}customElements.define("overlastic-dialog",u);customElements.define("overlastic-pane",class extends u{connectedCallback(){super.connectedCallback();const e=Turbo.navigator.history.location;window.modalVisitStack||(window.modalVisitStack=[]),window.modalVisitStack.push(e),Turbo.navigator.history.push(new URL(this.parentElement.src))}close(e,t=!1){t&&e.target!==this||(super.close(e,t),window.modalVisitStack.length>0&&Turbo.navigator.history.replace(window.modalVisitStack.pop()))}}),window.disableBodyScroll=function(t,u){if(t){if(!n.some((function(e){return e.targetElement===t}))){var v={targetElement:t,options:u||{}};n=[].concat(function(e){if(Array.isArray(e)){for(var t=0,o=Array(e.length);t<e.length;t++)o[t]=e[t];return o}return Array.from(e)}(n),[v]),o?window.requestAnimationFrame((function(){if(void 0===d){d={position:document.body.style.position,top:document.body.style.top,left:document.body.style.left};var e=window,t=e.scrollY,o=e.scrollX,n=e.innerHeight;document.body.style.position="fixed",document.body.style.top=-t,document.body.style.left=-o,setTimeout((function(){return window.requestAnimationFrame((function(){var e=n-window.innerHeight;e&&t>=n&&(document.body.style.top=-(t+e))}))}),300)}})):function(e){if(void 0===s){var t=!!e&&!0===e.reserveScrollBarGap,o=window.innerWidth-document.documentElement.clientWidth;if(t&&o>0){var n=parseInt(window.getComputedStyle(document.body).getPropertyValue("padding-right"),10);s=document.body.style.paddingRight,document.body.style.paddingRight=n+o+"px"}}void 0===l&&(l=document.body.style.overflow,document.body.style.overflow="hidden")}(u),o&&(t.ontouchstart=function(e){1===e.targetTouches.length&&(r=e.targetTouches[0].clientY)},t.ontouchmove=function(e){1===e.targetTouches.length&&function(e,t){var o=e.targetTouches[0].clientY-r;!a(e.target)&&(t&&0===t.scrollTop&&o>0||function(e){return!!e&&e.scrollHeight-e.scrollTop<=e.clientHeight}(t)&&o<0?c(e):e.stopPropagation())}(e,t)},i||(document.addEventListener("touchmove",c,e?{passive:!1}:void 0),i=!0))}}else console.error("disableBodyScroll unsuccessful - targetElement must be provided when calling disableBodyScroll on IOS devices.")},window.enableBodyScroll=function(t){t?(n=n.filter((function(e){return e.targetElement!==t})),o&&(t.ontouchstart=null,t.ontouchmove=null,i&&0===n.length&&(document.removeEventListener("touchmove",c,e?{passive:!1}:void 0),i=!1)),o?function(){if(void 0!==d){var e=-parseInt(document.body.style.top,10),t=-parseInt(document.body.style.left,10);document.body.style.position=d.position,document.body.style.top=d.top,document.body.style.left=d.left,window.scrollTo(t,e),d=void 0}}():(void 0!==s&&(document.body.style.paddingRight=s,s=void 0),void 0!==l&&(document.body.style.overflow=l,l=void 0))):console.error("enableBodyScroll unsuccessful - targetElement must be provided when calling enableBodyScroll on IOS devices.")};
2
+ //# sourceMappingURL=overlastic.min.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlastic.min.js","sources":["../../../node_modules/body-scroll-lock/lib/bodyScrollLock.esm.js","../../javascript/overlastic/clickInterceptor.js","../../javascript/overlastic/dialogElement.js","../../javascript/overlastic/paneElement.js","../../javascript/overlastic/index.js"],"sourcesContent":["function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }\n\n// Older browsers don't support event options, feature detect it.\n\n// Adopted and modified solution from Bohdan Didukh (2017)\n// https://stackoverflow.com/questions/41594997/ios-10-safari-prevent-scrolling-behind-a-fixed-overlay-and-maintain-scroll-posi\n\nvar hasPassiveEvents = false;\nif (typeof window !== 'undefined') {\n var passiveTestOptions = {\n get passive() {\n hasPassiveEvents = true;\n return undefined;\n }\n };\n window.addEventListener('testPassive', null, passiveTestOptions);\n window.removeEventListener('testPassive', null, passiveTestOptions);\n}\n\nvar isIosDevice = typeof window !== 'undefined' && window.navigator && window.navigator.platform && (/iP(ad|hone|od)/.test(window.navigator.platform) || window.navigator.platform === 'MacIntel' && window.navigator.maxTouchPoints > 1);\n\n\nvar locks = [];\nvar documentListenerAdded = false;\nvar initialClientY = -1;\nvar previousBodyOverflowSetting = void 0;\nvar previousBodyPosition = void 0;\nvar previousBodyPaddingRight = void 0;\n\n// returns true if `el` should be allowed to receive touchmove events.\nvar allowTouchMove = function allowTouchMove(el) {\n return locks.some(function (lock) {\n if (lock.options.allowTouchMove && lock.options.allowTouchMove(el)) {\n return true;\n }\n\n return false;\n });\n};\n\nvar preventDefault = function preventDefault(rawEvent) {\n var e = rawEvent || window.event;\n\n // For the case whereby consumers adds a touchmove event listener to document.\n // Recall that we do document.addEventListener('touchmove', preventDefault, { passive: false })\n // in disableBodyScroll - so if we provide this opportunity to allowTouchMove, then\n // the touchmove event on document will break.\n if (allowTouchMove(e.target)) {\n return true;\n }\n\n // Do not prevent if the event has more than one touch (usually meaning this is a multi touch gesture like pinch to zoom).\n if (e.touches.length > 1) return true;\n\n if (e.preventDefault) e.preventDefault();\n\n return false;\n};\n\nvar setOverflowHidden = function setOverflowHidden(options) {\n // If previousBodyPaddingRight is already set, don't set it again.\n if (previousBodyPaddingRight === undefined) {\n var _reserveScrollBarGap = !!options && options.reserveScrollBarGap === true;\n var scrollBarGap = window.innerWidth - document.documentElement.clientWidth;\n\n if (_reserveScrollBarGap && scrollBarGap > 0) {\n var computedBodyPaddingRight = parseInt(window.getComputedStyle(document.body).getPropertyValue('padding-right'), 10);\n previousBodyPaddingRight = document.body.style.paddingRight;\n document.body.style.paddingRight = computedBodyPaddingRight + scrollBarGap + 'px';\n }\n }\n\n // If previousBodyOverflowSetting is already set, don't set it again.\n if (previousBodyOverflowSetting === undefined) {\n previousBodyOverflowSetting = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n }\n};\n\nvar restoreOverflowSetting = function restoreOverflowSetting() {\n if (previousBodyPaddingRight !== undefined) {\n document.body.style.paddingRight = previousBodyPaddingRight;\n\n // Restore previousBodyPaddingRight to undefined so setOverflowHidden knows it\n // can be set again.\n previousBodyPaddingRight = undefined;\n }\n\n if (previousBodyOverflowSetting !== undefined) {\n document.body.style.overflow = previousBodyOverflowSetting;\n\n // Restore previousBodyOverflowSetting to undefined\n // so setOverflowHidden knows it can be set again.\n previousBodyOverflowSetting = undefined;\n }\n};\n\nvar setPositionFixed = function setPositionFixed() {\n return window.requestAnimationFrame(function () {\n // If previousBodyPosition is already set, don't set it again.\n if (previousBodyPosition === undefined) {\n previousBodyPosition = {\n position: document.body.style.position,\n top: document.body.style.top,\n left: document.body.style.left\n };\n\n // Update the dom inside an animation frame \n var _window = window,\n scrollY = _window.scrollY,\n scrollX = _window.scrollX,\n innerHeight = _window.innerHeight;\n\n document.body.style.position = 'fixed';\n document.body.style.top = -scrollY;\n document.body.style.left = -scrollX;\n\n setTimeout(function () {\n return window.requestAnimationFrame(function () {\n // Attempt to check if the bottom bar appeared due to the position change\n var bottomBarHeight = innerHeight - window.innerHeight;\n if (bottomBarHeight && scrollY >= innerHeight) {\n // Move the content further up so that the bottom bar doesn't hide it\n document.body.style.top = -(scrollY + bottomBarHeight);\n }\n });\n }, 300);\n }\n });\n};\n\nvar restorePositionSetting = function restorePositionSetting() {\n if (previousBodyPosition !== undefined) {\n // Convert the position from \"px\" to Int\n var y = -parseInt(document.body.style.top, 10);\n var x = -parseInt(document.body.style.left, 10);\n\n // Restore styles\n document.body.style.position = previousBodyPosition.position;\n document.body.style.top = previousBodyPosition.top;\n document.body.style.left = previousBodyPosition.left;\n\n // Restore scroll\n window.scrollTo(x, y);\n\n previousBodyPosition = undefined;\n }\n};\n\n// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#Problems_and_solutions\nvar isTargetElementTotallyScrolled = function isTargetElementTotallyScrolled(targetElement) {\n return targetElement ? targetElement.scrollHeight - targetElement.scrollTop <= targetElement.clientHeight : false;\n};\n\nvar handleScroll = function handleScroll(event, targetElement) {\n var clientY = event.targetTouches[0].clientY - initialClientY;\n\n if (allowTouchMove(event.target)) {\n return false;\n }\n\n if (targetElement && targetElement.scrollTop === 0 && clientY > 0) {\n // element is at the top of its scroll.\n return preventDefault(event);\n }\n\n if (isTargetElementTotallyScrolled(targetElement) && clientY < 0) {\n // element is at the bottom of its scroll.\n return preventDefault(event);\n }\n\n event.stopPropagation();\n return true;\n};\n\nexport var disableBodyScroll = function disableBodyScroll(targetElement, options) {\n // targetElement must be provided\n if (!targetElement) {\n // eslint-disable-next-line no-console\n console.error('disableBodyScroll unsuccessful - targetElement must be provided when calling disableBodyScroll on IOS devices.');\n return;\n }\n\n // disableBodyScroll must not have been called on this targetElement before\n if (locks.some(function (lock) {\n return lock.targetElement === targetElement;\n })) {\n return;\n }\n\n var lock = {\n targetElement: targetElement,\n options: options || {}\n };\n\n locks = [].concat(_toConsumableArray(locks), [lock]);\n\n if (isIosDevice) {\n setPositionFixed();\n } else {\n setOverflowHidden(options);\n }\n\n if (isIosDevice) {\n targetElement.ontouchstart = function (event) {\n if (event.targetTouches.length === 1) {\n // detect single touch.\n initialClientY = event.targetTouches[0].clientY;\n }\n };\n targetElement.ontouchmove = function (event) {\n if (event.targetTouches.length === 1) {\n // detect single touch.\n handleScroll(event, targetElement);\n }\n };\n\n if (!documentListenerAdded) {\n document.addEventListener('touchmove', preventDefault, hasPassiveEvents ? { passive: false } : undefined);\n documentListenerAdded = true;\n }\n }\n};\n\nexport var clearAllBodyScrollLocks = function clearAllBodyScrollLocks() {\n if (isIosDevice) {\n // Clear all locks ontouchstart/ontouchmove handlers, and the references.\n locks.forEach(function (lock) {\n lock.targetElement.ontouchstart = null;\n lock.targetElement.ontouchmove = null;\n });\n\n if (documentListenerAdded) {\n document.removeEventListener('touchmove', preventDefault, hasPassiveEvents ? { passive: false } : undefined);\n documentListenerAdded = false;\n }\n\n // Reset initial clientY.\n initialClientY = -1;\n }\n\n if (isIosDevice) {\n restorePositionSetting();\n } else {\n restoreOverflowSetting();\n }\n\n locks = [];\n};\n\nexport var enableBodyScroll = function enableBodyScroll(targetElement) {\n if (!targetElement) {\n // eslint-disable-next-line no-console\n console.error('enableBodyScroll unsuccessful - targetElement must be provided when calling enableBodyScroll on IOS devices.');\n return;\n }\n\n locks = locks.filter(function (lock) {\n return lock.targetElement !== targetElement;\n });\n\n if (isIosDevice) {\n targetElement.ontouchstart = null;\n targetElement.ontouchmove = null;\n\n if (documentListenerAdded && locks.length === 0) {\n document.removeEventListener('touchmove', preventDefault, hasPassiveEvents ? { passive: false } : undefined);\n documentListenerAdded = false;\n }\n }\n\n if (isIosDevice) {\n restorePositionSetting();\n } else {\n restoreOverflowSetting();\n }\n};\n\n","addEventListener(\"click\", event => {\n window._overlasticTarget = event.target.closest(\"[data-turbo-frame*=overlay]\")\n})\n\naddEventListener(\"turbo:before-fetch-request\", event => {\n if (!window._overlasticTarget) return\n\n const target = window._overlasticTarget\n const type = target?.dataset?.overlayType\n const args = target?.dataset?.overlayArgs\n\n if (type) {\n event.detail.fetchOptions.headers[\"Overlay-Type\"] = type\n }\n\n if (args) {\n event.detail.fetchOptions.headers[\"Overlay-Args\"] = args\n }\n\n delete window._overlasticTarget\n})\n","export default class DialogElement extends HTMLElement {\n connectedCallback() {\n disableBodyScroll(this)\n\n this.addEventListener(\"click\", event => this.close(event, true))\n this.querySelector(\".overlastic-close\").addEventListener(\"click\", event => this.close(event))\n }\n\n close(event, self = false) {\n if (self && event.target !== this) return\n\n enableBodyScroll(this)\n\n // Avoid removing before sending dispatching other events (like form submissions)\n setTimeout(() => {\n this.remove()\n }, 5)\n }\n}\n\ncustomElements.define(\"overlastic-dialog\", DialogElement)\n","import DialogElement from \"./dialogElement\"\n\nclass PaneElement extends DialogElement {\n connectedCallback() {\n super.connectedCallback()\n\n const lastVisit = Turbo.navigator.history.location\n\n if (!window.modalVisitStack) {\n window.modalVisitStack = []\n }\n\n window.modalVisitStack.push(lastVisit)\n Turbo.navigator.history.push(new URL(this.parentElement.src))\n }\n\n close(event, self = false) {\n if (self && event.target !== this) return\n\n super.close(event, self)\n\n if (window.modalVisitStack.length > 0) {\n Turbo.navigator.history.replace(window.modalVisitStack.pop())\n }\n }\n}\n\ncustomElements.define(\"overlastic-pane\", PaneElement)\n","import { disableBodyScroll, enableBodyScroll } from \"body-scroll-lock\"\n\nimport \"./clickInterceptor\"\nimport \"./dialogElement\"\nimport \"./paneElement\"\n\nwindow.disableBodyScroll = disableBodyScroll\nwindow.enableBodyScroll = enableBodyScroll\n"],"names":["hasPassiveEvents","window","passiveTestOptions","passive","addEventListener","removeEventListener","isIosDevice","navigator","platform","test","maxTouchPoints","locks","documentListenerAdded","initialClientY","previousBodyOverflowSetting","previousBodyPosition","previousBodyPaddingRight","allowTouchMove","el","some","lock","options","preventDefault","rawEvent","e","event","target","touches","length","_overlasticTarget","closest","type","dataset","overlayType","args","overlayArgs","detail","fetchOptions","headers","DialogElement","HTMLElement","[object Object]","disableBodyScroll","this","close","querySelector","self","enableBodyScroll","setTimeout","remove","customElements","define","super","connectedCallback","lastVisit","Turbo","history","location","modalVisitStack","push","URL","parentElement","src","replace","pop","targetElement","concat","arr","Array","isArray","i","arr2","from","_toConsumableArray","requestAnimationFrame","undefined","position","document","body","style","top","left","_window","scrollY","scrollX","innerHeight","bottomBarHeight","_reserveScrollBarGap","reserveScrollBarGap","scrollBarGap","innerWidth","documentElement","clientWidth","computedBodyPaddingRight","parseInt","getComputedStyle","getPropertyValue","paddingRight","overflow","setOverflowHidden","ontouchstart","targetTouches","clientY","ontouchmove","scrollTop","scrollHeight","clientHeight","isTargetElementTotallyScrolled","stopPropagation","handleScroll","console","error","filter","y","x","scrollTo","restorePositionSetting"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ module Overlastic::Concerns::OverlayHandling
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_action :add_overlay_variant
6
+
7
+ private
8
+
9
+ def add_overlay_variant
10
+ request.variant = :overlay if helpers.current_overlay_name.present?
11
+ end
12
+
13
+ def render(*args, &block)
14
+ if request.variant.overlay?
15
+ super html: helpers.render_overlay { render_to_string(*args, &block) }
16
+ else
17
+ super
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,55 @@
1
+ module Overlastic::NavigationHelper
2
+ def link_to_overlay(name = nil, options = nil, html_options = nil, &block)
3
+ method_type = __callee__.to_s.delete_prefix("link_to_")
4
+ method_type = nil if method_type == "overlay"
5
+
6
+ if block_given?
7
+ options ||= {}
8
+ options = options.stringify_keys
9
+ options["data"] ||= {}
10
+
11
+ type = options.delete("overlay_type") || method_type
12
+ options["data"][:overlay_type] = type if type.present?
13
+
14
+ action = options.delete("overlay_action") || Overlastic.configuration.default_action
15
+ options["data"][:turbo_frame] = turbo_frame_from_overlastic_action(action)
16
+
17
+ args = options.delete("overlay_args")
18
+ options["data"][:overlay_args] = args.to_json if args.present?
19
+
20
+ link_to(name, options, &block)
21
+ else
22
+ html_options ||= {}
23
+ html_options = html_options.stringify_keys
24
+ html_options["data"] ||= {}
25
+
26
+ type = html_options.delete("overlay_type") || method_type
27
+ html_options["data"][:overlay_type] = type if type.present?
28
+
29
+ action = html_options.delete("overlay_action") || Overlastic.configuration.default_action
30
+ html_options["data"][:turbo_frame] = turbo_frame_from_overlastic_action(action)
31
+
32
+ args = html_options.delete("overlay_args")
33
+ html_options["data"][:overlay_args] = args.to_json if args.present?
34
+
35
+ link_to(name, options, html_options, &block)
36
+ end
37
+ end
38
+
39
+ Overlastic.configuration.overlay_types.each do |overlay_type|
40
+ alias_method :"link_to_#{overlay_type}", :link_to_overlay
41
+ end
42
+
43
+ private
44
+
45
+ def turbo_frame_from_overlastic_action(action)
46
+ case action
47
+ when :stack
48
+ next_overlay_name
49
+ when :replace_last
50
+ current_overlay_name
51
+ when :replace_all
52
+ :overlay1
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,27 @@
1
+ module Overlastic::OverlaysHelper
2
+ def overlastic_tag
3
+ turbo_frame_tag :overlay1, target: :_top
4
+ end
5
+
6
+ def current_overlay_name
7
+ return unless request.headers["Turbo-Frame"].to_s.starts_with?("overlay")
8
+
9
+ request.headers["Turbo-Frame"].to_sym
10
+ end
11
+
12
+ def next_overlay_name
13
+ current_number = current_overlay_name.to_s.scan(/\d+/)&.first.to_i
14
+
15
+ "overlay#{current_number + 1}".to_sym
16
+ end
17
+
18
+ def render_overlay(locals = {}, &block)
19
+ string = capture(&block)
20
+ type = request.headers["Overlay-Type"] || Overlastic.configuration.default_overlay
21
+ args_header = request.headers["Overlay-Args"]
22
+ overlay_args = JSON.parse(request.headers["Overlay-Args"]) if args_header.present?
23
+ locals.merge! overlay_args.to_h.symbolize_keys
24
+
25
+ render(Overlastic.configuration.public_send(:"#{type}_overlay_view_path"), locals) { string }
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ addEventListener("click", event => {
2
+ window._overlasticTarget = event.target.closest("[data-turbo-frame*=overlay]")
3
+ })
4
+
5
+ addEventListener("turbo:before-fetch-request", event => {
6
+ if (!window._overlasticTarget) return
7
+
8
+ const target = window._overlasticTarget
9
+ const type = target?.dataset?.overlayType
10
+ const args = target?.dataset?.overlayArgs
11
+
12
+ if (type) {
13
+ event.detail.fetchOptions.headers["Overlay-Type"] = type
14
+ }
15
+
16
+ if (args) {
17
+ event.detail.fetchOptions.headers["Overlay-Args"] = args
18
+ }
19
+
20
+ delete window._overlasticTarget
21
+ })
@@ -0,0 +1,21 @@
1
+ export default class DialogElement extends HTMLElement {
2
+ connectedCallback() {
3
+ disableBodyScroll(this)
4
+
5
+ this.addEventListener("click", event => this.close(event, true))
6
+ this.querySelector(".overlastic-close").addEventListener("click", event => this.close(event))
7
+ }
8
+
9
+ close(event, self = false) {
10
+ if (self && event.target !== this) return
11
+
12
+ enableBodyScroll(this)
13
+
14
+ // Avoid removing before sending dispatching other events (like form submissions)
15
+ setTimeout(() => {
16
+ this.remove()
17
+ }, 5)
18
+ }
19
+ }
20
+
21
+ customElements.define("overlastic-dialog", DialogElement)
@@ -0,0 +1,8 @@
1
+ import { disableBodyScroll, enableBodyScroll } from "body-scroll-lock"
2
+
3
+ import "./clickInterceptor"
4
+ import "./dialogElement"
5
+ import "./paneElement"
6
+
7
+ window.disableBodyScroll = disableBodyScroll
8
+ window.enableBodyScroll = enableBodyScroll
@@ -0,0 +1,28 @@
1
+ import DialogElement from "./dialogElement"
2
+
3
+ class PaneElement extends DialogElement {
4
+ connectedCallback() {
5
+ super.connectedCallback()
6
+
7
+ const lastVisit = Turbo.navigator.history.location
8
+
9
+ if (!window.modalVisitStack) {
10
+ window.modalVisitStack = []
11
+ }
12
+
13
+ window.modalVisitStack.push(lastVisit)
14
+ Turbo.navigator.history.push(new URL(this.parentElement.src))
15
+ }
16
+
17
+ close(event, self = false) {
18
+ if (self && event.target !== this) return
19
+
20
+ super.close(event, self)
21
+
22
+ if (window.modalVisitStack.length > 0) {
23
+ Turbo.navigator.history.replace(window.modalVisitStack.pop())
24
+ }
25
+ }
26
+ }
27
+
28
+ customElements.define("overlastic-pane", PaneElement)
@@ -0,0 +1,28 @@
1
+ <% title ||= "" %>
2
+
3
+ <%= turbo_frame_tag current_overlay_name do %>
4
+ <overlastic-dialog style="height:736px;background-color:rgba(107, 114, 128, 0.5);justify-content:center;align-items:center;width: 100vw;display:flex;z-index:50;left:0px;top:0px;position:fixed;box-sizing:border-box;border-width:0px;border-style:solid;border-color:rgb(229, 231, 235);">
5
+ <div style="width: 50%;justify-content:center;align-items:center;flex-direction:column;max-width:672px;height:662.398px;display:flex;box-sizing:border-box;border-width:0px;border-style:solid;border-color:rgb(229, 231, 235);">
6
+ <div style="padding-top:32px;padding-bottom:32px;box-shadow:rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.1) 0px 2px 4px -2px;background-color:rgb(255, 255, 255);overflow:hidden;box-sizing:border-box;border-width:0px;border-style:solid;border-color:rgb(229, 231, 235);">
7
+ <div style="padding-left:32px;padding-right:32px;justify-content:space-between;align-items:center;display:flex;box-sizing:border-box;border-width:0px;border-style:solid;border-color:rgb(229, 231, 235);">
8
+ <label style="color:rgb(55, 65, 81);font-weight:600;font-size:18px;line-height:28px;text-align:center;box-sizing:border-box;border-width:0px;border-style:solid;border-color:rgb(229, 231, 235);">
9
+ <%= title %>
10
+ </label>
11
+
12
+ <span class="overlastic-close" style="color:rgb(75, 85, 99);cursor:pointer;box-sizing:border-box;border-width:0px;border-style:solid;border-color:rgb(229, 231, 235);">
13
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" style="width: 1.25rem;height:20px;display:block;vertical-align:middle;box-sizing:border-box;border-width:0px;border-style:solid;border-color:rgb(229, 231, 235);fill:currentColor;">
14
+ <!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
15
+ <path d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z" style="box-sizing:border-box;border-width:0px;border-style:solid;border-color:rgb(229, 231, 235);"></path>
16
+ </svg>
17
+ </span>
18
+ </div>
19
+
20
+ <div style="padding-left:32px;padding-right:32px;overscroll-behavior:contain;overflow-y: auto;max-height:calc(100% - 20px);position:relative;box-sizing:border-box;border-width:0px;border-style:solid;border-color:rgb(229, 231, 235);">
21
+ <%= yield %>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </overlastic-dialog>
26
+
27
+ <%= turbo_frame_tag next_overlay_name, target: :_top %>
28
+ <% end %>
@@ -0,0 +1,22 @@
1
+ <%= turbo_frame_tag current_overlay_name do %>
2
+ <overlastic-pane style="background-color:rgba(107, 114, 128, 0.5);justify-content:flex-end;width: 100vw;height:736px;display:flex;z-index:40;left:0px;top:0px;position:fixed;box-sizing:border-box;border-width:0px;border-style:solid;border-color:rgb(229, 231, 235);">
3
+ <div style="width: 66.6667%;align-items:flex-end;flex-direction:column;animation:0.2s ease 0s 1 normal both running modal;height:736px;display:flex;box-sizing:border-box;border-width:0px;border-style:solid;border-color:rgb(229, 231, 235);">
4
+ <div style="box-shadow:rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.1) 0px 2px 4px -2px;padding-top:20px;padding-bottom:20px;background-color:rgb(249, 250, 251);border-top-left-radius:8px;border-bottom-left-radius:8px;overflow:hidden;width: 100%;height:736px;box-sizing:border-box;border-width:0px;border-style:solid;border-color:rgb(229, 231, 235);">
5
+ <div style="padding-left:20px;padding-right:20px;justify-content:space-between;align-items:center;display:flex;margin-bottom:12px;box-sizing:border-box;border-width:0px;border-style:solid;border-color:rgb(229, 231, 235);">
6
+ <span class="overlastic-close" style="color:rgb(75, 85, 99);cursor:pointer;box-sizing:border-box;border-width:0px;border-style:solid;border-color:rgb(229, 231, 235);">
7
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" style="width: 2rem;height:32px;display:block;vertical-align:middle;box-sizing:border-box;border-width:0px;border-style:solid;border-color:rgb(229, 231, 235);fill:currentColor;">
8
+ <!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
9
+ <path d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z" style="box-sizing:border-box;border-width:0px;border-style:solid;border-color:rgb(229, 231, 235);"></path>
10
+ </svg>
11
+ </span>
12
+ </div>
13
+
14
+ <div style="padding-left:32px;padding-right:32px;overscroll-behavior:contain;overflow-y: auto;max-height:calc(100% - 56px);position:relative;box-sizing:border-box;border-width:0px;border-style:solid;border-color:rgb(229, 231, 235);">
15
+ <%= yield %>
16
+ </div>
17
+ </div>
18
+ </div>
19
+ </overlastic-pane>
20
+
21
+ <%= turbo_frame_tag next_overlay_name, target: :_top %>
22
+ <% end %>
@@ -0,0 +1,28 @@
1
+ <% title ||= "" %>
2
+
3
+ <%= turbo_frame_tag current_overlay_name do %>
4
+ <overlastic-dialog class="fixed top-0 left-0 z-50 flex justify-center w-screen h-screen-safe items-center bg-gray-500 bg-opacity-50">
5
+ <div class="w-11/12 sm:w-5/6 lg:w-1/2 max-w-2xl h-[90vh] flex flex-col justify-center items-center">
6
+ <div class="py-4 sm:py-8 bg-white shadow-md overflow-hidden">
7
+ <div class="px-4 sm:px-8 flex justify-between items-center">
8
+ <label class="text-lg text-gray-700 text-center font-semibold">
9
+ <%= title %>
10
+ </label>
11
+
12
+ <span class="overlastic-close cursor-pointer text-gray-600">
13
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" class="w-5 h-5 fill-current">
14
+ <!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
15
+ <path d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"/>
16
+ </svg>
17
+ </span>
18
+ </div>
19
+
20
+ <div class="px-4 sm:px-8 relative overflow-y-auto overscroll-contain max-h-[calc(100%-1.25rem)]">
21
+ <%= yield %>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </overlastic-dialog>
26
+
27
+ <%= turbo_frame_tag next_overlay_name, target: :_top %>
28
+ <% end %>
@@ -0,0 +1,22 @@
1
+ <%= turbo_frame_tag current_overlay_name do %>
2
+ <overlastic-pane class="fixed top-0 left-0 z-40 flex justify-end w-screen h-screen bg-gray-500 bg-opacity-50">
3
+ <div class="w-full sm:w-5/6 lg:w-8/12 h-full flex flex-col items-end animate-modal">
4
+ <div id="split-container" class="py-5 bg-gray-50 rounded-l-lg shadow-md overflow-hidden w-full h-full">
5
+ <div class="px-5 mb-3 flex justify-between items-center">
6
+ <span class="overlastic-close cursor-pointer text-gray-600">
7
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" class="w-8 h-8 fill-current">
8
+ <!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
9
+ <path d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"/>
10
+ </svg>
11
+ </span>
12
+ </div>
13
+
14
+ <div class="px-8 relative overflow-y-auto overscroll-contain max-h-[calc(100%-3.5rem)]">
15
+ <%= yield %>
16
+ </div>
17
+ </div>
18
+ </div>
19
+ </overlastic-pane>
20
+
21
+ <%= turbo_frame_tag next_overlay_name, target: :_top %>
22
+ <% end %>
@@ -0,0 +1,8 @@
1
+ say "Import Overlastic"
2
+ append_to_file "app/javascript/application.js", %(import "overlastic"\n)
3
+
4
+ say "Pin Overlastic"
5
+ append_to_file "config/importmap.rb", %(pin "overlastic", to: "overlastic.min.js", preload: true\n)
6
+
7
+ say "Add Overlastic tag in application layout"
8
+ insert_into_file Rails.root.join("app/views/layouts/application.html.erb"), "\n\n <%= overlastic_tag %>", before: /\s*<\/body>/
@@ -0,0 +1,5 @@
1
+ say "Add Overlastic JS include tag in application layout"
2
+ insert_into_file Rails.root.join("app/views/layouts/application.html.erb"), "\n <%= javascript_include_tag \"overlastic.min\", \"data-turbo-track\": \"reload\", defer: true %>", before: /\s*<\/head>/
3
+
4
+ say "Add Overlastic tag in application layout"
5
+ insert_into_file Rails.root.join("app/views/layouts/application.html.erb"), "\n\n <%= overlastic_tag %>", before: /\s*<\/body>/
@@ -0,0 +1,26 @@
1
+ module Overlastic
2
+ class Configuration
3
+ attr_accessor :overlay_types, :default_overlay, :default_action
4
+
5
+ def initialize
6
+ self.overlay_types = %i[dialog pane]
7
+ self.default_overlay = :dialog
8
+ self.default_action = :stack
9
+ end
10
+
11
+ def overlay_types=(types)
12
+ overlay_types&.each do |overlay_type|
13
+ undef :"#{overlay_type}_overlay_view_path"
14
+ undef :"#{overlay_type}_overlay_view_path="
15
+ end
16
+
17
+ @overlay_types = types
18
+
19
+ overlay_types.each do |overlay_type|
20
+ self.class.attr_accessor :"#{overlay_type}_overlay_view_path"
21
+
22
+ public_send :"#{overlay_type}_overlay_view_path=", "shared/overlays/#{overlay_type}"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ require "rails/engine"
2
+
3
+ module Overlastic
4
+ class Engine < Rails::Engine
5
+ isolate_namespace Overlastic
6
+
7
+ config.eager_load_namespaces << Overlastic
8
+ config.autoload_once_paths = %W(
9
+ #{root}/app/controllers
10
+ #{root}/app/controllers/concerns
11
+ #{root}/app/helpers
12
+ )
13
+
14
+ initializer "overlastic.assets" do
15
+ if Rails.application.config.respond_to?(:assets)
16
+ Rails.application.config.assets.precompile += %w[overlastic.min.js]
17
+ end
18
+ end
19
+
20
+ initializer "overlastic.helpers", before: :load_config_initializers do
21
+ ActiveSupport.on_load(:action_controller_base) do
22
+ include Overlastic::Concerns::OverlayHandling
23
+
24
+ helper Overlastic::Engine.helpers
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ module Overlastic
2
+ VERSION = "0.1.0"
3
+ end
data/lib/overlastic.rb ADDED
@@ -0,0 +1,16 @@
1
+ require "overlastic/engine"
2
+ require "overlastic/configuration"
3
+
4
+ module Overlastic
5
+ extend ActiveSupport::Autoload
6
+
7
+ class << self
8
+ def configuration
9
+ @configuration ||= Overlastic::Configuration.new
10
+ end
11
+
12
+ def configure
13
+ yield(configuration)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ def run_overlastic_install_template(path)
2
+ system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{File.expand_path("../install/#{path}.rb", __dir__)}"
3
+ end
4
+
5
+ namespace :overlastic do
6
+ desc "Install Overlastic into the app"
7
+ task :install do
8
+ if Rails.root.join("config/importmap.rb").exist?
9
+ Rake::Task["overlastic:install:importmap"].invoke
10
+ else
11
+ Rake::Task["overlastic:install:node"].invoke
12
+ end
13
+ end
14
+
15
+ namespace :install do
16
+ desc "Install Overlastic into the app with asset pipeline"
17
+ task :importmap do
18
+ run_overlastic_install_template "overlastic_with_importmap"
19
+ end
20
+
21
+ desc "Install Overlastic into the app with node"
22
+ task :node do
23
+ run_overlastic_install_template "overlastic_with_node"
24
+ end
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: overlastic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Martin Zamuner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-09-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activejob
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 6.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 6.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: actionpack
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 6.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 6.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: railties
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 6.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 6.0.0
55
+ description:
56
+ email: martinzamuner@gmail.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - MIT-LICENSE
62
+ - README.md
63
+ - Rakefile
64
+ - app/assets/javascripts/overlastic.js
65
+ - app/assets/javascripts/overlastic.min.js
66
+ - app/assets/javascripts/overlastic.min.js.map
67
+ - app/controllers/overlastic/concerns/overlay_handling.rb
68
+ - app/helpers/overlastic/navigation_helper.rb
69
+ - app/helpers/overlastic/overlays_helper.rb
70
+ - app/javascript/overlastic/clickInterceptor.js
71
+ - app/javascript/overlastic/dialogElement.js
72
+ - app/javascript/overlastic/index.js
73
+ - app/javascript/overlastic/paneElement.js
74
+ - app/views/shared/overlays/_dialog.html.erb
75
+ - app/views/shared/overlays/_pane.html.erb
76
+ - app/views/shared/overlays/tailwind/_dialog.html.erb
77
+ - app/views/shared/overlays/tailwind/_pane.html.erb
78
+ - lib/install/overlastic_with_importmap.rb
79
+ - lib/install/overlastic_with_node.rb
80
+ - lib/overlastic.rb
81
+ - lib/overlastic/configuration.rb
82
+ - lib/overlastic/engine.rb
83
+ - lib/overlastic/version.rb
84
+ - lib/tasks/overlastic_tasks.rake
85
+ homepage: https://github.com/martinzamuner/overlastic
86
+ licenses:
87
+ - MIT
88
+ metadata: {}
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: 2.6.0
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubygems_version: 3.2.22
105
+ signing_key:
106
+ specification_version: 4
107
+ summary: Fantastically easy overlays using Hotwire.
108
+ test_files: []