turbo_overlay 0.3.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 +7 -0
- data/CHANGELOG.md +436 -0
- data/LICENSE.txt +21 -0
- data/README.md +330 -0
- data/Rakefile +35 -0
- data/app/assets/stylesheets/turbo_overlay.css +234 -0
- data/app/javascript/turbo_overlay/dialog_utils.js +46 -0
- data/app/javascript/turbo_overlay/hint.js +670 -0
- data/app/javascript/turbo_overlay/history.js +184 -0
- data/app/javascript/turbo_overlay/index.js +53 -0
- data/app/javascript/turbo_overlay/options.js +152 -0
- data/app/javascript/turbo_overlay/overlay_controller.js +882 -0
- data/app/javascript/turbo_overlay/popover_position.js +64 -0
- data/app/javascript/turbo_overlay/setup.js +885 -0
- data/app/javascript/turbo_overlay/stack_controller.js +131 -0
- data/app/javascript/turbo_overlay/submit_close.js +49 -0
- data/app/javascript/turbo_overlay/visit.js +52 -0
- data/app/views/layouts/turbo_overlay/drawer.html.erb +5 -0
- data/app/views/layouts/turbo_overlay/hint.html.erb +10 -0
- data/app/views/layouts/turbo_overlay/modal.html.erb +5 -0
- data/app/views/layouts/turbo_overlay/popover.html.erb +5 -0
- data/app/views/turbo_overlay/_drawer.html.erb +49 -0
- data/app/views/turbo_overlay/_hint.html.erb +6 -0
- data/app/views/turbo_overlay/_loading.html.erb +12 -0
- data/app/views/turbo_overlay/_modal.html.erb +46 -0
- data/app/views/turbo_overlay/_popover.html.erb +54 -0
- data/config/importmap.rb +11 -0
- data/lib/generators/turbo_overlay/eject_generator.rb +115 -0
- data/lib/generators/turbo_overlay/install_generator.rb +443 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap3/_confirm.html.erb +13 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap3/_drawer.html.erb +50 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap3/_hint.html.erb +9 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap3/_loading.html.erb +9 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap3/_modal.html.erb +49 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap3/_popover.html.erb +54 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap5/_confirm.html.erb +13 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap5/_drawer.html.erb +55 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap5/_hint.html.erb +9 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap5/_loading.html.erb +9 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap5/_modal.html.erb +58 -0
- data/lib/generators/turbo_overlay/templates/chrome/bootstrap5/_popover.html.erb +53 -0
- data/lib/generators/turbo_overlay/templates/chrome/plain/_confirm.html.erb +14 -0
- data/lib/generators/turbo_overlay/templates/chrome/tailwind/_confirm.html.erb +17 -0
- data/lib/generators/turbo_overlay/templates/chrome/tailwind/_drawer.html.erb +55 -0
- data/lib/generators/turbo_overlay/templates/chrome/tailwind/_hint.html.erb +6 -0
- data/lib/generators/turbo_overlay/templates/chrome/tailwind/_loading.html.erb +9 -0
- data/lib/generators/turbo_overlay/templates/chrome/tailwind/_modal.html.erb +46 -0
- data/lib/generators/turbo_overlay/templates/chrome/tailwind/_popover.html.erb +54 -0
- data/lib/generators/turbo_overlay/templates/initializer.rb.tt +67 -0
- data/lib/turbo_overlay/configuration.rb +226 -0
- data/lib/turbo_overlay/controller.rb +405 -0
- data/lib/turbo_overlay/engine.rb +52 -0
- data/lib/turbo_overlay/helpers/stream_helper.rb +77 -0
- data/lib/turbo_overlay/helpers/view_helper.rb +651 -0
- data/lib/turbo_overlay/version.rb +3 -0
- data/lib/turbo_overlay.rb +20 -0
- metadata +161 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f96f9c76f0dba518bc7f30f1d1301f518bf05c113f2b3cbe3003e8c6be6500dc
|
|
4
|
+
data.tar.gz: 0af6b6007b41538970e9afb5cca4fc2347e4c92aefa8482b3b07d6419bdfb06f
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 60d02ea17dd6f4ed9afab004c1bd53faedc4f3f597bea8dbc7278503bb65a6512b7291a91438da275232aefd8ed1d496c2be39c017f74579288d19aa64f10946
|
|
7
|
+
data.tar.gz: 352be4c2a0084c7811e3ae1c2b84127b60d9dd3163036be4f308f4fd1636c75378e1dec38a6a93a5fdc6ae09f668022ad46fb7aea1045ad485a990f6b9407d44
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
Big iteration cycle ahead of the first public release. Highlights:
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- **Smooth close on form-submit redirects.** Two improvements to the
|
|
9
|
+
close-on-redirect path, both default-on; the existing
|
|
10
|
+
`keep_overlay_open_on_redirect` / per-form opt-out covers both.
|
|
11
|
+
Also fixes a pre-existing flash where Turbo would render the
|
|
12
|
+
redirect target's HTML into the still-open overlay before the
|
|
13
|
+
submit-end handler closed it — same-origin fetch follows preserve
|
|
14
|
+
the `Turbo-Frame` / `X-Turbo-Overlay` headers, so the redirect
|
|
15
|
+
target was being wrapped in overlay layout by the controller
|
|
16
|
+
concern and morphed into the dialog. The per-dialog listener now
|
|
17
|
+
stops the `turbo:before-fetch-response` event before Turbo's
|
|
18
|
+
StreamObserver and FormSubmission can process it.
|
|
19
|
+
- **Same-page redirects morph the host page behind the overlay
|
|
20
|
+
before closing.** When the redirect target's pathname matches the
|
|
21
|
+
URL the overlay was opened from, the gem fetches the target,
|
|
22
|
+
morphs `document.body` (excluding the `overlay_stack_tag`
|
|
23
|
+
container so the open dialog is preserved), updates the URL via
|
|
24
|
+
`history.replaceState`, then animates the close. No more
|
|
25
|
+
flash-of-stale-content while the overlay closes. Modal/drawer
|
|
26
|
+
only; falls back to the existing close-then-visit path when
|
|
27
|
+
another overlay is open in the stack, when the fetch fails, or
|
|
28
|
+
when `Turbo.morphChildren` is unavailable. Uses
|
|
29
|
+
`window.Turbo.morphChildren` (public API in Turbo 8) so morph
|
|
30
|
+
runs through Turbo's own Idiomorph copy and `data-turbo-permanent`
|
|
31
|
+
+ `turbo:before-morph-*` events compose normally — no new
|
|
32
|
+
dependency. The opener URL is captured on the dialog as
|
|
33
|
+
`data-turbo-overlay-opener-url` on Stimulus connect; the morph-
|
|
34
|
+
attribute preservation hook keeps it intact through in-overlay
|
|
35
|
+
form re-renders.
|
|
36
|
+
- **Different-page redirects await the close animation before
|
|
37
|
+
navigating.** Previously `Turbo.visit` fired immediately after
|
|
38
|
+
starting the close, so the new page could paint behind a
|
|
39
|
+
still-closing overlay. The submit-end handler now `await`s the
|
|
40
|
+
overlay close (which returns a `Promise`) before invoking
|
|
41
|
+
`Turbo.visit`. The close `Promise` resolves on `animationend`,
|
|
42
|
+
the 400ms fallback timer, the reduced-motion shortcut, or the
|
|
43
|
+
no-dialog shortcut — same lifecycle as before, just observable.
|
|
44
|
+
- **Form submissions inside an overlay close it on redirect by default.**
|
|
45
|
+
When a descendant form submits and the response is a followed
|
|
46
|
+
redirect (`fetchResponse.redirected === true`), the overlay closes
|
|
47
|
+
and the browser visits the redirect target via `Turbo.visit`. Apps
|
|
48
|
+
previously relying on explicit `turbo_stream.overlay(:close)`
|
|
49
|
+
responses keep working unchanged — the stream-action path runs
|
|
50
|
+
before any redirect would. Validation failures
|
|
51
|
+
(`:unprocessable_entity`, 422) don't redirect, so in-place form
|
|
52
|
+
re-renders are untouched. Two opt-outs, finest-grained wins:
|
|
53
|
+
- **Per-overlay (link helper):**
|
|
54
|
+
`modal_link_to "Wizard", path, keep_overlay_open_on_redirect: true`
|
|
55
|
+
(also on `drawer_link_to` and `popover_link_to`). Round-trips
|
|
56
|
+
through the `X-Turbo-Overlay-Keep-Open` request header to a
|
|
57
|
+
`data-turbo-overlay-keep-open-on-redirect="true"` attribute on
|
|
58
|
+
the `<dialog>`.
|
|
59
|
+
- **Per-form (data attribute):**
|
|
60
|
+
`<form data-turbo-overlay-keep-open-on-redirect="true">` opts
|
|
61
|
+
out a single form while sibling forms in the same overlay still
|
|
62
|
+
close on redirect.
|
|
63
|
+
Detection is a per-dialog `turbo:submit-end` listener installed
|
|
64
|
+
in the gem's Stimulus controller — scoped to descendants of the
|
|
65
|
+
dialog, auto-cleaned on disconnect, never document-scoped. The
|
|
66
|
+
decision lives in `app/javascript/turbo_overlay/submit_close.js`
|
|
67
|
+
(`shouldCloseOnRedirect`) as a pure function. Hosts whose form
|
|
68
|
+
redirects previously fell into a broken frame-replacement state
|
|
69
|
+
(the redirect target had no matching `turbo_overlay_<type>_<id>`
|
|
70
|
+
frame) get correct behavior now.
|
|
71
|
+
- **In-overlay form re-renders now morph the dialog in place.**
|
|
72
|
+
`overlay_response_wrapper` emits a `<turbo-stream action="replace"
|
|
73
|
+
method="morph">` on a frame re-render (form validation failure
|
|
74
|
+
inside an open overlay) instead of a bare `<turbo-frame>`. Idiomorph
|
|
75
|
+
preserves the `<dialog>` node identity, top-layer membership,
|
|
76
|
+
popover anchor coordinates, stack registration, and the
|
|
77
|
+
document-level ESC / outside-click / reflow handlers the per-dialog
|
|
78
|
+
Stimulus controller installed on first open. Previously Turbo
|
|
79
|
+
replaced the frame's contents wholesale; for popovers this detached
|
|
80
|
+
the new dialog from its anchor and re-rendered it centered, and for
|
|
81
|
+
every overlay type it leaked the original controller's document
|
|
82
|
+
handlers. The after_action that sets `text/vnd.turbo-stream.html`
|
|
83
|
+
on initial-open responses now also runs on frame re-renders so
|
|
84
|
+
Turbo processes the embedded `<turbo-stream>`. `request.format`
|
|
85
|
+
stays `:turbo_stream` on re-renders — apps' `respond_to`
|
|
86
|
+
`format.turbo_stream` branches keep running on successful saves;
|
|
87
|
+
implicit `render :edit` calls still find `edit.html.erb` via
|
|
88
|
+
Rails' format-fallback. A `turbo:before-morph-attribute` hook in
|
|
89
|
+
`setup.js` preserves the two attributes the JS owns on overlay
|
|
90
|
+
dialogs (`open` and inline `style`) so idiomorph doesn't strip them.
|
|
91
|
+
- **Auto-installed overlay `layout` proc.** Including
|
|
92
|
+
`TurboOverlay::Controller` now declares `layout -> { turbo_overlay_layout }`
|
|
93
|
+
on its own, so host apps no longer hand-write a `resolve_layout`
|
|
94
|
+
method. The proc also re-installs Turbo's `"turbo_rails/frame"`
|
|
95
|
+
layout for plain turbo-frame requests, so including the concern
|
|
96
|
+
does not regress Turbo's frame-layout optimization. Apps with a
|
|
97
|
+
custom layout method call `turbo_overlay_layout` from it (see
|
|
98
|
+
README "A note on custom layouts").
|
|
99
|
+
- **Bundled layouts moved to `app/views/layouts/turbo_overlay/{modal,drawer,popover,hint}.html.erb`**
|
|
100
|
+
to mirror `turbo_rails/frame.html.erb`. Layout names returned by
|
|
101
|
+
the helper are now `"turbo_overlay/modal"`, `"turbo_overlay/drawer"`,
|
|
102
|
+
`"turbo_overlay/popover"`, `"turbo_overlay/hint"`.
|
|
103
|
+
|
|
104
|
+
### Fixed
|
|
105
|
+
- **Drag-out text selections no longer dismiss the overlay.** Press,
|
|
106
|
+
drag, and release across the dialog boundary used to be reported by
|
|
107
|
+
the browser as a click on the dialog itself (W3C: click target is
|
|
108
|
+
the lowest common ancestor of mousedown and mouseup), which the
|
|
109
|
+
backdrop-click handler treated as a dismissal. The handler now
|
|
110
|
+
consults the mousedown target and suppresses dismissal when the
|
|
111
|
+
selection started inside the dialog content.
|
|
112
|
+
|
|
113
|
+
- **Body-portaled widgets (flatpickr, Select2, Tippy, Tom Select) no
|
|
114
|
+
longer dismiss popovers.** Configure
|
|
115
|
+
`TurboOverlay.configuration.allowed_click_outside_selectors` with
|
|
116
|
+
the widget's portal selectors; clicks inside any matching element
|
|
117
|
+
are ignored by the popover's outside-click handler and the
|
|
118
|
+
modal/drawer backdrop-click handler. Per-overlay override via
|
|
119
|
+
`data-turbo-overlay-allow-click-outside` (CSV) on the dialog.
|
|
120
|
+
Malformed selectors are skipped with a one-time console warning, so
|
|
121
|
+
a single typo can't disable dismissal for the rest of the list.
|
|
122
|
+
|
|
123
|
+
- **Hover prefetch of in-overlay links no longer replaces the open
|
|
124
|
+
overlay.** Turbo's hover prefetch sends the enclosing frame's id
|
|
125
|
+
in the `Turbo-Frame` header. The controller concern was falling
|
|
126
|
+
into its in-overlay form-re-render branch on that header, routing
|
|
127
|
+
the prefetched URL through the overlay layout and returning a
|
|
128
|
+
turbo-stream that morphed the open overlay's contents on receipt.
|
|
129
|
+
The concern now skips the Turbo-Frame fallback when
|
|
130
|
+
`X-Sec-Purpose: prefetch` is set — explicit overlay opens
|
|
131
|
+
(`X-Turbo-Overlay` header) are unaffected. The same guard runs in
|
|
132
|
+
`turbo_overlay_frame_re_render?` so the response Content-Type
|
|
133
|
+
stays consistent with the body. As a complementary bandwidth save,
|
|
134
|
+
dismiss links (`modal_dismiss_link_to` etc.) rendered inside an
|
|
135
|
+
overlay now set `data-turbo-prefetch="false"` since their click
|
|
136
|
+
is preventDefault'd by the Stimulus action.
|
|
137
|
+
- **Closing the last advance overlay no longer leaves Turbo's
|
|
138
|
+
progress bar stuck at the top of the page.** When the entry
|
|
139
|
+
beneath the advance overlay has Turbo's restoration state (e.g.
|
|
140
|
+
the page that was loaded via Turbo Drive before opening any
|
|
141
|
+
overlay), the gem cancels the proposed restore visit — but
|
|
142
|
+
`FetchRequest#perform` had already started on a microtask and
|
|
143
|
+
would call `visit.requestStarted()` after our handler returned,
|
|
144
|
+
scheduling a `.turbo-progress-bar` timer that never gets cleared
|
|
145
|
+
(canceled visits don't fire `visitCompleted`). The gem now stubs
|
|
146
|
+
`visit.requestStarted` to a no-op before canceling and clears
|
|
147
|
+
the stray `aria-busy` attribute that
|
|
148
|
+
`Session#visitStarted` left on `<html>`.
|
|
149
|
+
- **Closing the top of a stacked advance overlay no longer tears
|
|
150
|
+
down the whole stack.** Two compounded bugs:
|
|
151
|
+
(a) The gem's `turbo:before-cache` handler ran
|
|
152
|
+
`tearDownAllOverlays` for every cache snapshot — including the
|
|
153
|
+
one Turbo Drive's `historyPoppedWithEmptyState` triggers
|
|
154
|
+
synchronously on every popstate without a visit. Now gated on a
|
|
155
|
+
`_realVisitInProgress` flag set in `turbo:before-visit` so only
|
|
156
|
+
real visits clear overlays.
|
|
157
|
+
(b) For popstates where the popped entry carries Turbo's own
|
|
158
|
+
restoration state (e.g. backing out of an advance overlay to a
|
|
159
|
+
Turbo-loaded page), Turbo proposes a restore visit that would
|
|
160
|
+
load the cached snapshot and replace the page. The gem now
|
|
161
|
+
cancels that restore visit from `turbo:visit` (action="restore")
|
|
162
|
+
when an advance overlay is involved — leaving the overlay close
|
|
163
|
+
to the popstate handler. Visit cancellation aborts the queued
|
|
164
|
+
render before `cacheSnapshot()` runs, so no `before-cache` side
|
|
165
|
+
effects either.
|
|
166
|
+
|
|
167
|
+
### Removed
|
|
168
|
+
- `modal/drawer/popover/hint.layout_name` configuration and the
|
|
169
|
+
matching `modal_layout_name` / `drawer_layout_name` /
|
|
170
|
+
`popover_layout_name` / `hint_layout_name` controller helpers.
|
|
171
|
+
Layout names are now hard-coded. Host apps that want a different
|
|
172
|
+
layout file can place their own at
|
|
173
|
+
`app/views/layouts/turbo_overlay/modal.html.erb` (Rails view
|
|
174
|
+
precedence wins over the gem's copy).
|
|
175
|
+
|
|
176
|
+
### Added
|
|
177
|
+
- **`turbo_stream.overlay(:close, visit: ...)` — server-driven
|
|
178
|
+
post-close navigation.** Pair a close stream with a `Turbo.visit`
|
|
179
|
+
to the host page that runs after the close animation completes.
|
|
180
|
+
Useful for stream-driven flows where there's no form submission
|
|
181
|
+
to ride a redirect on (ActionCable broadcasts, async job
|
|
182
|
+
completion). Accepts `visit:` (URL) and optional
|
|
183
|
+
`visit_action: :advance | :replace`.
|
|
184
|
+
- **`modal_button_to` / `drawer_button_to` / `popover_button_to` — open
|
|
185
|
+
an overlay from a non-GET action.** `button_to` counterparts to the
|
|
186
|
+
existing link helpers; same option vocabulary. Use when the overlay
|
|
187
|
+
should be the result of a POST/PATCH/PUT/DELETE — creating a record,
|
|
188
|
+
deleting an item, kicking off a wizard. The submit hook reads the
|
|
189
|
+
form's overlay data attrs and emits the same `X-Turbo-Overlay-*`
|
|
190
|
+
request headers a `*_link_to` click would. `advance:` and hint
|
|
191
|
+
options aren't exposed (non-GET doesn't push history; hints are a
|
|
192
|
+
hover-on-link mechanism).
|
|
193
|
+
- **Popovers auto-close when their anchor scrolls out of view.** A
|
|
194
|
+
popover whose trigger isn't visible reads as a floating widget with
|
|
195
|
+
no obvious connection to anything; matching Bootstrap / MUI /
|
|
196
|
+
native iOS UIPopover, the popover now collapses when its anchor
|
|
197
|
+
exits the viewport. Short ~50ms debounce so momentum scrolls that
|
|
198
|
+
briefly clip the edge don't dismiss. The popover continues to track
|
|
199
|
+
the anchor through the dismissal so it slides offscreen alongside
|
|
200
|
+
the trigger rather than getting glued to the viewport.
|
|
201
|
+
- **Popover positioning moved to compositor-thread transforms.**
|
|
202
|
+
Replaced the per-scroll `style.top`/`style.left` writes with a
|
|
203
|
+
single `transform: translate(...)`. Eliminates the one-frame lag
|
|
204
|
+
("springy" feel) on smooth/momentum scrolling. Popover open/close
|
|
205
|
+
keyframes compose with the positioning transform via
|
|
206
|
+
`animation-composition: add`.
|
|
207
|
+
- **`TurboOverlay.visit(url, options)` — open overlays from JavaScript.**
|
|
208
|
+
Programmatic counterpart to `modal_link_to` / `drawer_link_to` /
|
|
209
|
+
`popover_link_to` for non-anchor triggers (Google Maps markers, SVG
|
|
210
|
+
hit regions, custom elements). Full option parity with the link
|
|
211
|
+
helpers; popovers require an `anchor` element for positioning.
|
|
212
|
+
Exposed as a named export from the package and as `window.TurboOverlay`
|
|
213
|
+
for non-bundler callers. Reuses the existing fetch pipeline — same
|
|
214
|
+
headers, same loading placeholder, same lifecycle events.
|
|
215
|
+
- **URL advance for modals and drawers.** Pass `advance: true` on
|
|
216
|
+
`modal_link_to` / `drawer_link_to` (or set
|
|
217
|
+
`c.modal.advance = true` / `c.drawer.advance = true` in the
|
|
218
|
+
initializer) to push a history entry when the overlay opens.
|
|
219
|
+
Browser-back closes the top overlay instead of navigating away
|
|
220
|
+
from the page beneath. Per-link override accepts `true` (push the
|
|
221
|
+
link's href), a String (push a custom URL), or `false` (opt out
|
|
222
|
+
when the type default is on). Popovers and hints never advance —
|
|
223
|
+
they're ephemeral and shouldn't churn browser history.
|
|
224
|
+
- **Popover overlay type.** `popover_link_to "Edit", path` opens its
|
|
225
|
+
target as a non-modal `<dialog>` anchored to the clicked link.
|
|
226
|
+
Per-link `position:`, `align:`, `offset:`; auto-flips on viewport
|
|
227
|
+
overflow; ESC and click-outside dismiss. Opening a second popover
|
|
228
|
+
dismisses the previous one — modals and drawers still stack on top.
|
|
229
|
+
- **Hover hints.** `hint_link_to "User", user_path(@user)` (or
|
|
230
|
+
`hint: true` / `hint_url:` on any overlay link helper) shows a
|
|
231
|
+
preview popover after ~250ms hover. Content comes from a `+hint`
|
|
232
|
+
variant template (`show.html+hint.erb`) that `overlay_stack_tag`
|
|
233
|
+
auto-emits on hintable requests. Piggy-backs on Turbo's hover
|
|
234
|
+
prefetch — one fetch warms navigation and seeds the hint. Falls
|
|
235
|
+
back to its own `fetch()` on prefetch-disabled sites; negative-caches
|
|
236
|
+
no-hint responses; ships a pending placeholder for slow controllers.
|
|
237
|
+
- **Loading state for overlay clicks.** Every modal/drawer/popover/hint
|
|
238
|
+
click drops a placeholder dialog matching the eventual chrome,
|
|
239
|
+
morphed in-place when the real response lands. ESC/backdrop-click
|
|
240
|
+
on a placeholder cancels the in-flight fetch via `AbortController`.
|
|
241
|
+
- **Themed `data-turbo-confirm`.** `register(application, { confirm: true })`
|
|
242
|
+
routes confirm prompts through the gem's themed dialog. Pick modal
|
|
243
|
+
or popover style globally (`config.confirm.style`) or per-link
|
|
244
|
+
(`data-turbo-confirm-style`). Falls back to `window.confirm` when
|
|
245
|
+
the template is absent.
|
|
246
|
+
- **Overlay lifecycle JS events:** `turbo-overlay:shown`,
|
|
247
|
+
`turbo-overlay:before-close`, `turbo-overlay:closed`,
|
|
248
|
+
`turbo-overlay:hint-shown`, `turbo-overlay:hint-ready`. All bubble
|
|
249
|
+
from the dialog (or document, for hint events) so apps can wire
|
|
250
|
+
autofocus, analytics, and cleanup without monkey-patching.
|
|
251
|
+
- **Drawer per-link options.** `position:` (`:left`/`:right`/`:top`/`:bottom`)
|
|
252
|
+
overrides the configured default. `backdrop: false` opens the drawer
|
|
253
|
+
non-modally so the host page stays interactive.
|
|
254
|
+
- **Default close button** in modal/drawer chrome — floating top-right
|
|
255
|
+
when no header, inside the header when `overlay_title` is set.
|
|
256
|
+
Suppress with `<% overlay_close false %>`, `close: false` on the
|
|
257
|
+
link, or a `close: false` partial local.
|
|
258
|
+
- **App-owned chrome partials.** Install drops `_modal.html.erb`,
|
|
259
|
+
`_drawer.html.erb`, `_popover.html.erb`, `_hint.html.erb`, and
|
|
260
|
+
body-only `_confirm.html.erb` + `_loading.html.erb` into
|
|
261
|
+
`app/views/turbo_overlay/`. Tailwind content scanners pick them up
|
|
262
|
+
automatically.
|
|
263
|
+
- **Bootstrap 3 drawer support** as a vanilla dialog styled with BS3
|
|
264
|
+
panel classes.
|
|
265
|
+
- **Dark-mode classes on the Tailwind theme.**
|
|
266
|
+
|
|
267
|
+
### Changed
|
|
268
|
+
- **Single native `<dialog>` JS controller for every theme.** Bootstrap
|
|
269
|
+
themes keep their visual classes but no longer require
|
|
270
|
+
`window.bootstrap` or jQuery — the `<dialog>` element drives
|
|
271
|
+
open/close, stacking, and focus management.
|
|
272
|
+
- **Animations on by default.** Modals fade/scale, drawers slide from
|
|
273
|
+
their configured edge, backdrops fade. All honor
|
|
274
|
+
`prefers-reduced-motion: reduce`.
|
|
275
|
+
- **CSS ships as a real stylesheet asset** (propshaft / sprockets /
|
|
276
|
+
bundler-friendly) — the interim `turbo_overlay_styles` view helper
|
|
277
|
+
is gone.
|
|
278
|
+
- **Stimulus controllers shipped from the gem with importmap auto-pin.**
|
|
279
|
+
Bundler apps reference the gem's `app/javascript` directly or use
|
|
280
|
+
`bin/rails g turbo_overlay:eject --js` to copy locally.
|
|
281
|
+
- **Backdrop click dismisses by default.** Opt out with
|
|
282
|
+
`data-turbo-overlay-backdrop-dismiss-value="false"`.
|
|
283
|
+
- **Stacked overlay links target `_top`** so modal/drawer links inside
|
|
284
|
+
an open overlay stack a new one instead of replacing the current
|
|
285
|
+
frame.
|
|
286
|
+
- **Helpers renamed for namespacing.** `current_overlay_*` →
|
|
287
|
+
`turbo_overlay_*`; `close_button:` → `close:` on link helpers;
|
|
288
|
+
hint predicates moved to the `overlay_` namespace. Hard renames, no
|
|
289
|
+
aliases.
|
|
290
|
+
- **Install footprint shrunk** to one `_loading.html.erb` and one
|
|
291
|
+
`_confirm.html.erb` per theme; chrome-specific overrides via
|
|
292
|
+
`_loading.html+<variant>.erb` / `_confirm.html+<variant>.erb` still
|
|
293
|
+
win when present.
|
|
294
|
+
- **Chrome wrapping moved into `overlay_stack_tag`.** Confirm/loading
|
|
295
|
+
partials are body-only; the chrome wraps them at template-emission
|
|
296
|
+
time. Adds a `loading:` local to chrome partials that drops the
|
|
297
|
+
Stimulus controller wiring, close button, and title/footer slots,
|
|
298
|
+
and switches ARIA to `role="status"`.
|
|
299
|
+
|
|
300
|
+
### Removed
|
|
301
|
+
- `window.bootstrap` and jQuery requirements for the Bootstrap themes.
|
|
302
|
+
- Per-theme overlay controllers (one shared `overlay_controller.js`).
|
|
303
|
+
- Inline `<style>` blocks from every shipped overlay layout.
|
|
304
|
+
- Inline `turbo_overlay_hint do … end` capture helper — the `+hint`
|
|
305
|
+
variant template is the canonical and only path now.
|
|
306
|
+
- Config knobs without real consumers: `OverlayTypeConfig#frame_id`,
|
|
307
|
+
`#stimulus_identifier`, `HintConfig#enabled`, `#template_id`.
|
|
308
|
+
|
|
309
|
+
### Fixed
|
|
310
|
+
- **Form re-render keeps the overlay open** when a submission inside
|
|
311
|
+
an open overlay responds `:unprocessable_entity` — the new dialog
|
|
312
|
+
is re-opened in the same mode after Turbo's frame replacement.
|
|
313
|
+
- **Back/forward navigation no longer restores broken-state overlays.**
|
|
314
|
+
`turbo:before-cache` tears down every overlay frame and aborts
|
|
315
|
+
in-flight fetches so the cached snapshot has no overlay state to
|
|
316
|
+
restore.
|
|
317
|
+
- **Bootstrap5 modal renders with the themed background** — `.modal-dialog`
|
|
318
|
+
is now wrapped in `.modal.d-block.position-static` so Bootstrap's
|
|
319
|
+
`--bs-modal-*` variables cascade.
|
|
320
|
+
- **Hint detection fixes:** `+hint` variant auto-render only fires
|
|
321
|
+
when a real `+hint` sibling file exists on disk; prefetch detection
|
|
322
|
+
uses `X-Sec-Purpose` (the prefix Turbo can actually set); pending
|
|
323
|
+
placeholder dismisses on no-template / errored responses with
|
|
324
|
+
negative caching to prevent stuck spinners.
|
|
325
|
+
- **Popover-style confirm works for `link_to … data-turbo-method`** —
|
|
326
|
+
the originating element is captured on click so the popover has an
|
|
327
|
+
anchor when Turbo's submitter is null.
|
|
328
|
+
- Stacked overlay close animation is now reliable across themes.
|
|
329
|
+
- **Popover inside an open modal is interactive again.** HTML's
|
|
330
|
+
modal-dialog inertness algorithm blocks every non-descendant of the
|
|
331
|
+
topmost modal from receiving input — even top-layer popovers added
|
|
332
|
+
via `showPopover()` afterwards. Popovers opened from inside a modal
|
|
333
|
+
now use `showModal()` themselves (with a transparent `::backdrop`)
|
|
334
|
+
so they become the topmost modal and stay interactive.
|
|
335
|
+
- **Popover and hint positioning no longer drifts toward the viewport
|
|
336
|
+
center.** UA `[popover]` styles include `inset: 0; margin: auto` —
|
|
337
|
+
setting only `top`/`left` left the leftover space to be distributed
|
|
338
|
+
via the auto margins, so the dialog rendered partway between the
|
|
339
|
+
trigger and the viewport edge. Now sets `right: auto; bottom: auto;
|
|
340
|
+
margin: 0` before measuring so the dialog stays anchored.
|
|
341
|
+
- **Popover anchored to wrapping inline link uses the clicked line.**
|
|
342
|
+
`getBoundingClientRect()` on a multi-line `<a>` returns the union
|
|
343
|
+
of every line box. The anchor math now picks the line containing
|
|
344
|
+
the recorded click point.
|
|
345
|
+
- **Popover follows the trigger while a parent overlay animates.**
|
|
346
|
+
Opening a popover while the drawer it's inside was mid-slide-in
|
|
347
|
+
used to anchor against the still-moving rect. The positioner now
|
|
348
|
+
re-runs each frame until the anchor stabilizes (~600ms cap).
|
|
349
|
+
- **Non-modal drawer trigger inside another overlay no longer
|
|
350
|
+
dismisses the parent.** The link helper writes
|
|
351
|
+
`data-turbo-overlay-backdrop="false"` on triggers to signal the
|
|
352
|
+
fetch hook; the bootstrap5 modal chrome's wrapper marker shared
|
|
353
|
+
that exact attribute name, so a bubbled click on the trigger was
|
|
354
|
+
treated as a backdrop click. Chrome marker renamed to
|
|
355
|
+
`data-turbo-overlay-backdrop-zone`.
|
|
356
|
+
|
|
357
|
+
## 0.3.0
|
|
358
|
+
|
|
359
|
+
**Stacking support.** Overlays now stack: open a modal/drawer from inside another and the new one slides on top instead of replacing. Dismissing affects only the topmost overlay; the layer beneath is revealed. This is a breaking internal change — public helper *signatures* are preserved but the underlying transport, layouts, and Stimulus controllers were rewritten.
|
|
360
|
+
|
|
361
|
+
### Added
|
|
362
|
+
- Stacked overlays. Native `<dialog>.showModal()` stacks via the browser top layer; Bootstrap 5/3 themes manually bump z-index per stack depth.
|
|
363
|
+
- `turbo_stream.overlay(:close, scope: :all)` closes every open overlay; `turbo_stream.overlay(:close, id: "...")` targets one by id; `turbo_stream.overlay(:close, scope: :all, type: :modal)` closes all modals only. Default `:close` (no args) still closes the topmost.
|
|
364
|
+
- `modal_link_to` / `drawer_link_to` accept `overlay_id:` to set a stable id for later targeting from server code.
|
|
365
|
+
- `current_overlay_id` controller method (and view helper) — returns the id of the overlay being rendered, useful for `turbo_stream.overlay(:close, id: current_overlay_id)`.
|
|
366
|
+
- `overlay_stack_tag` view helper — emits the single host-page stack container.
|
|
367
|
+
- `config.stack_id` (default `"turbo_overlay_stack"`).
|
|
368
|
+
|
|
369
|
+
### Changed
|
|
370
|
+
- **Transport switched from turbo-frame replacement to turbo-stream append.** Each opened overlay is appended to the host-page stack container and wrapped in its own `<turbo-frame id="turbo_overlay_<type>_<id>">` for in-place form re-rendering.
|
|
371
|
+
- Variant detection now reads the `X-Turbo-Overlay` request header (set by the link helper's JS hook) instead of `Turbo-Frame`.
|
|
372
|
+
- Modal and drawer Stimulus controllers unified into one `turbo-overlay` controller per theme. `turbo-modal` / `turbo-drawer` identifiers no longer used; both are now `turbo-overlay`.
|
|
373
|
+
- `aria-labelledby` ids in shipped layouts now include the overlay id so stacked dialogs don't collide.
|
|
374
|
+
- Stream API: `turbo_stream.overlay(:close)` now means "close the top overlay." Previously it closed every overlay; in single-overlay setups this is observationally identical.
|
|
375
|
+
|
|
376
|
+
### Removed
|
|
377
|
+
- The dual-frame (`turbo_modal` + `turbo_drawer`) install model. `overlay_frame_tags` is retained as a deprecated alias for `overlay_stack_tag` for one minor cycle.
|
|
378
|
+
- Per-type Stimulus controllers (`turbo_modal_controller.js`, `turbo_drawer_controller.js`).
|
|
379
|
+
|
|
380
|
+
### Requires
|
|
381
|
+
- Turbo 8+ (turbo-rails 2+) for `data-turbo-stream="true"` GET request support.
|
|
382
|
+
|
|
383
|
+
### Migration from 0.2.x
|
|
384
|
+
1. Re-run the install generator with `--force` to overwrite the layout files and JS controllers:
|
|
385
|
+
```sh
|
|
386
|
+
bin/rails g turbo_overlay:install --theme <your-theme> --force
|
|
387
|
+
```
|
|
388
|
+
2. In your application layout, replace `<%= overlay_frame_tags %>` with `<%= overlay_stack_tag %>`. (The old helper still works but warns.)
|
|
389
|
+
3. Remove the old per-type Stimulus controller files: `app/javascript/controllers/turbo_modal_controller.js` and `turbo_drawer_controller.js`.
|
|
390
|
+
4. If you customized the modal/drawer layouts, port your changes to the new layout primitives — note the wrapping helper changed from `turbo_frame_tag` to `overlay_response_wrapper(:modal|:drawer)`, the Stimulus identifier changed from `turbo-modal` / `turbo-drawer` to `turbo-overlay`, and `aria-labelledby` ids include `<%= current_overlay_id %>`.
|
|
391
|
+
5. If you have controllers that explicitly responded with `turbo_stream.overlay(:close)` after a successful action — no change needed; it now closes the top overlay (same observed behavior in non-stacked apps).
|
|
392
|
+
|
|
393
|
+
## 0.2.0
|
|
394
|
+
|
|
395
|
+
### Added
|
|
396
|
+
- **Drawer support.** Side-anchored overlay (`:left`, `:right`,
|
|
397
|
+
`:top`, `:bottom`) that mirrors the modal pattern with its own
|
|
398
|
+
frame, request variant, layout, and Stimulus controller.
|
|
399
|
+
- `config.drawer` configuration block. New default: drawer frame
|
|
400
|
+
`turbo_drawer`, variant `:drawer`, layout `turbo_drawer`, position
|
|
401
|
+
`:right`.
|
|
402
|
+
- Controller helpers: `drawer_request?`, `drawer_frame_id`,
|
|
403
|
+
`drawer_layout_name`, plus a generic `overlay_request?` that
|
|
404
|
+
returns true for any open overlay.
|
|
405
|
+
- View helpers: `drawer_link_to`, `drawer_dismiss_link_to`.
|
|
406
|
+
- `overlay_frame_tags` view helper that emits the receiving
|
|
407
|
+
turbo-frames for all configured overlay types in one call. Drop it
|
|
408
|
+
into the application layout once.
|
|
409
|
+
- Drawer themes ship for tailwind, bootstrap5, and plain. Bootstrap 3
|
|
410
|
+
is intentionally not shipped (no native drawer/offcanvas
|
|
411
|
+
primitive); the generator auto-skips drawer install for that theme.
|
|
412
|
+
|
|
413
|
+
### Changed
|
|
414
|
+
- **Install generator unified.** `bin/rails g turbo_overlay:install`
|
|
415
|
+
now installs both modal and drawer by default. Pass `--skip-modal`
|
|
416
|
+
or `--skip-drawer` to install only one. `install_drawer` removed —
|
|
417
|
+
one entry point handles every combination.
|
|
418
|
+
- Initializer template now includes both `config.modal` and
|
|
419
|
+
`config.drawer` blocks.
|
|
420
|
+
- Generator injects `<%= overlay_frame_tags %>` instead of an
|
|
421
|
+
explicit `turbo_frame_tag`. Both forms are still detected so
|
|
422
|
+
re-runs are idempotent.
|
|
423
|
+
|
|
424
|
+
## 0.1.0
|
|
425
|
+
|
|
426
|
+
### Added
|
|
427
|
+
- Initial release.
|
|
428
|
+
- Controller concern that detects overlay-frame requests, sets a
|
|
429
|
+
`request.variant`, and exposes `modal_request?` / `modal_layout_name`.
|
|
430
|
+
- View helpers `modal_link_to`, `modal_dismiss_link_to`, and generic
|
|
431
|
+
`overlay_title` / `overlay_footer` content helpers.
|
|
432
|
+
- Stream helper for `turbo_stream.overlay(:close)`. Polymorphic —
|
|
433
|
+
closes any open overlay regardless of type.
|
|
434
|
+
- Install generator with four shipped themes for modals: `tailwind`,
|
|
435
|
+
`bootstrap5`, `bootstrap3`, `plain`. Auto-injects the modal frame
|
|
436
|
+
and Stimulus controller wiring.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joel Schneider
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|