hibiki_rails 0.18.0 → 0.19.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 +4 -4
- data/CHANGELOG.md +40 -0
- data/app/assets/javascripts/hibiki.js +100 -11
- data/lib/hibiki/rails/channel.rb +31 -13
- data/lib/hibiki/rails/helpers.rb +7 -15
- data/lib/hibiki/rails/modifiers.rb +30 -0
- data/lib/hibiki/rails/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0ab077d178d71a16ddf8d0f5706179f7fbac93c4d851aae6e3ccda4ef0e4b674
|
|
4
|
+
data.tar.gz: 409e833560a3393d2ca3c0121c2e958f3d8bb986a0b62aaaed653fe0c0f06af3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 552f5913948b16d8ed030b1eb12c35f7f28f0f109f31807dbbaf86f9b2320c3929c959c2872800785b635b9c05229df0ebe5c218e4429b490442ee2cb81ba133
|
|
7
|
+
data.tar.gz: 0ff3466d160bd5074a8d02530c87222fd8dcc5aeed43b033fb8ff3549decd55423286e1757511d352f39a5df47a4d60198dd0efb9fa254479febf43f1ea7d8c6
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,46 @@ The gem and the npm package are released in lockstep and share these version
|
|
|
4
4
|
numbers — `app/assets/javascripts/hibiki.js` is a single copy served both ways,
|
|
5
5
|
so importmap and bundler apps always resolve identical client code.
|
|
6
6
|
|
|
7
|
+
## 0.19.0 — 2026-10-12
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
**History entries.** `transmit_url` takes `history: :push`, and each move of
|
|
12
|
+
the URL becomes an entry in the browser's history. Use it for state that
|
|
13
|
+
reads as a place, such as a wizard's step. The default, `history: :replace`,
|
|
14
|
+
is unchanged.
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
transmit_url(history: :push) { wizard_path(step: @step.value) }
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**The `popstate` event.** `on(:visit, event: :popstate)` on the island root
|
|
21
|
+
answers Back and Forward over those entries. The client performs the action
|
|
22
|
+
with the query of the address the browser moved to.
|
|
23
|
+
|
|
24
|
+
```erb
|
|
25
|
+
<%= island WizardChannel, **on(:visit, event: :popstate) do |cid| %>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
def visit(data) = turn_to(data["step"])
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
- The params come from the address bar. Check them as you would a subscribe
|
|
33
|
+
param.
|
|
34
|
+
- A pushed URL becomes an entry only when it answers an action of this tab.
|
|
35
|
+
The first URL a channel sends, and one that follows another session's
|
|
36
|
+
write, replace as before.
|
|
37
|
+
- If the action refuses the address, or changes nothing, the bar is put back
|
|
38
|
+
to the URL the channel last sent. No entry is added.
|
|
39
|
+
- The payload is the flat query. A key ending in `[]` arrives as an array.
|
|
40
|
+
- `popstate` takes no `debounce:` or `fallback:`.
|
|
41
|
+
- Without a `popstate` action, Back over a pushed entry reloads the page at
|
|
42
|
+
that address. The page shows the right state if it reads the same params
|
|
43
|
+
on its first render, as a mirrored URL already requires.
|
|
44
|
+
- Coming back to a pushed entry from another page also reloads it.
|
|
45
|
+
- A page that has pushed an entry is left out of Turbo's snapshot cache.
|
|
46
|
+
|
|
7
47
|
## 0.18.0 — 2026-10-11
|
|
8
48
|
|
|
9
49
|
### Added
|
|
@@ -93,7 +93,9 @@
|
|
|
93
93
|
//
|
|
94
94
|
// The left side of `->` is a hibiki event name: click, change, input,
|
|
95
95
|
// and submit are delegated DOM listeners; `visible` is a pseudo-event
|
|
96
|
-
// backed by an IntersectionObserver (the element entering the viewport)
|
|
96
|
+
// backed by an IntersectionObserver (the element entering the viewport);
|
|
97
|
+
// `popstate` is the window's, heard for the history entries a pushed
|
|
98
|
+
// `{ url }` frame made.
|
|
97
99
|
// Everything that is not "which event" is a sibling attribute, so the
|
|
98
100
|
// token grammar never has to grow.
|
|
99
101
|
//
|
|
@@ -185,6 +187,27 @@ let consumer
|
|
|
185
187
|
// island pins nothing even if disconnect never ran.
|
|
186
188
|
const islands = new WeakMap()
|
|
187
189
|
|
|
190
|
+
// History entries a pushed `{ url }` frame made, by the key in their state,
|
|
191
|
+
// and the one window listener that answers Back and Forward over them.
|
|
192
|
+
//
|
|
193
|
+
// Turbo hears popstate first, whatever the listener's phase: the browser
|
|
194
|
+
// runs a window's listeners in the order they were added. It answers an
|
|
195
|
+
// entry carrying its own state with a restore visit, and an entry without
|
|
196
|
+
// it by adopting it in place. So these entries carry only the key. The
|
|
197
|
+
// island that made one answers it (pop). An entry whose island has left
|
|
198
|
+
// the page, or declares no `popstate`, is loaded afresh: Turbo has by then
|
|
199
|
+
// filed a snapshot of the page on show under the entry's address, and a
|
|
200
|
+
// new document is the one thing that leaves none of that behind.
|
|
201
|
+
//
|
|
202
|
+
// Always listening, not from the first push: after a reload the browser
|
|
203
|
+
// still counts the entries the old document pushed as this document's, and
|
|
204
|
+
// answers Back over them with a popstate no island here made.
|
|
205
|
+
const pushers = new Map()
|
|
206
|
+
window.addEventListener("popstate", (event) => {
|
|
207
|
+
const key = event.state?.hibiki
|
|
208
|
+
if (key && !pushers.get(key)?.pop?.(event)) window.location.reload()
|
|
209
|
+
})
|
|
210
|
+
|
|
188
211
|
// The generic island CONTAINING element, or undefined. The walk performOn
|
|
189
212
|
// uses, exported for the motion module's `own` policy — which reads the
|
|
190
213
|
// island's trip records, so a ChannelController subclass (not in the map)
|
|
@@ -310,6 +333,9 @@ export class ChannelController extends Controller {
|
|
|
310
333
|
this.settledAt = -Infinity
|
|
311
334
|
this.lastControl = null
|
|
312
335
|
this.queued = []
|
|
336
|
+
// Marks the history entries this island made; see replaceUrl().
|
|
337
|
+
this.historyKey = `${Date.now()}-${Math.random()}`
|
|
338
|
+
this.popping = undefined
|
|
313
339
|
this.setState("connecting")
|
|
314
340
|
}
|
|
315
341
|
|
|
@@ -339,6 +365,8 @@ export class ChannelController extends Controller {
|
|
|
339
365
|
|
|
340
366
|
disconnect() {
|
|
341
367
|
this.aborted = true
|
|
368
|
+
pushers.delete(this.historyKey)
|
|
369
|
+
this.popping = undefined // the bar is the next page's now
|
|
342
370
|
this.settleAll()
|
|
343
371
|
this.subscription?.unsubscribe()
|
|
344
372
|
this.subscription = undefined
|
|
@@ -503,6 +531,7 @@ export class ChannelController extends Controller {
|
|
|
503
531
|
}
|
|
504
532
|
|
|
505
533
|
settle(seq) {
|
|
534
|
+
if (this.popping === seq) this.popped()
|
|
506
535
|
const record = this.busy.get(seq)
|
|
507
536
|
if (!record) return
|
|
508
537
|
clearTimeout(record.ceiling)
|
|
@@ -595,7 +624,8 @@ export class ChannelController extends Controller {
|
|
|
595
624
|
// textContent assignment keeps values text-only and preserves each
|
|
596
625
|
// site's own tag/classes, so per-placeholder styling survives updates.
|
|
597
626
|
//
|
|
598
|
-
// { url } — mirror graph state into the address bar (transmit_url)
|
|
627
|
+
// { url } — mirror graph state into the address bar (transmit_url);
|
|
628
|
+
// with `push`, as a history entry.
|
|
599
629
|
//
|
|
600
630
|
// { html } — a fragment: swap it in by its root id. The swap is holdable
|
|
601
631
|
// the way Turbo's is: `hibiki:before-render` bubbles from the island
|
|
@@ -607,7 +637,7 @@ export class ChannelController extends Controller {
|
|
|
607
637
|
//
|
|
608
638
|
// Anything else is not ours to interpret. Subclasses may override, but
|
|
609
639
|
// should call super (or handle `value`) to keep reactive values live.
|
|
610
|
-
received({ html, value, url }) {
|
|
640
|
+
received({ html, value, url, push }) {
|
|
611
641
|
if (value) {
|
|
612
642
|
const selector = `[data-hibiki-value="${CSS.escape(value.name)}"]`
|
|
613
643
|
for (const site of document.querySelectorAll(selector)) {
|
|
@@ -615,7 +645,7 @@ export class ChannelController extends Controller {
|
|
|
615
645
|
}
|
|
616
646
|
return
|
|
617
647
|
}
|
|
618
|
-
if (url !== undefined) return this.replaceUrl(url)
|
|
648
|
+
if (url !== undefined) return this.replaceUrl(url, push)
|
|
619
649
|
if (!html) return
|
|
620
650
|
const template = document.createElement("template")
|
|
621
651
|
template.innerHTML = html
|
|
@@ -633,15 +663,48 @@ export class ChannelController extends Controller {
|
|
|
633
663
|
return event.detail.render()
|
|
634
664
|
}
|
|
635
665
|
|
|
636
|
-
// replaceState
|
|
637
|
-
//
|
|
638
|
-
//
|
|
639
|
-
//
|
|
640
|
-
//
|
|
641
|
-
|
|
666
|
+
// replaceState unless the channel asked for entries: the URL is a mirror
|
|
667
|
+
// of graph state, and Back leaves the page normally. Same-origin only, so
|
|
668
|
+
// a channel can move the bar solely within its own app (replaceState
|
|
669
|
+
// would throw on a cross-origin URL; refusing keeps it silent and
|
|
670
|
+
// intentional).
|
|
671
|
+
//
|
|
672
|
+
// A pushed URL becomes an entry only when it answers this tab's own
|
|
673
|
+
// action, so neither the first frame nor another session's write adds
|
|
674
|
+
// one, and never while a Back is being answered: an action that refuses
|
|
675
|
+
// the popped URL corrects the bar in place. The entry it leaves and the
|
|
676
|
+
// one it makes carry this island's key in place of Turbo's state; see
|
|
677
|
+
// `pushers`. The page is also kept out of Turbo's snapshot cache from
|
|
678
|
+
// then on, since Turbo no longer knows which address a snapshot is of.
|
|
679
|
+
replaceUrl(url, push = false) {
|
|
642
680
|
const resolved = new URL(url, window.location.href)
|
|
643
681
|
if (resolved.origin !== window.location.origin) return
|
|
644
|
-
|
|
682
|
+
this.url = resolved.href
|
|
683
|
+
if (!push || this.popping !== undefined || !this.own()) {
|
|
684
|
+
return history.replaceState(history.state, "", resolved)
|
|
685
|
+
}
|
|
686
|
+
if (resolved.href === window.location.href) return
|
|
687
|
+
pushers.set(this.historyKey, this)
|
|
688
|
+
window.Turbo?.cache?.exemptPageFromCache?.()
|
|
689
|
+
history.replaceState(this.entry(), "")
|
|
690
|
+
history.pushState(this.entry(), "", resolved)
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// The state of an entry this island answers for: what is there now,
|
|
694
|
+
// less Turbo's part.
|
|
695
|
+
entry() {
|
|
696
|
+
const { turbo, ...rest } = history.state ?? {}
|
|
697
|
+
return { ...rest, hibiki: this.historyKey }
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// The Back being answered is over. An action that changed nothing sent no
|
|
701
|
+
// URL, so the bar still shows the popped one: put back the last the
|
|
702
|
+
// channel gave, which is the state the page is in.
|
|
703
|
+
popped() {
|
|
704
|
+
this.popping = undefined
|
|
705
|
+
if (this.url && this.url !== window.location.href) {
|
|
706
|
+
history.replaceState(history.state, "", this.url)
|
|
707
|
+
}
|
|
645
708
|
}
|
|
646
709
|
|
|
647
710
|
// `static channel = "..."` wins; otherwise infer Rails-style from the
|
|
@@ -733,6 +796,7 @@ const KEYS = {
|
|
|
733
796
|
}
|
|
734
797
|
const KEYED = '[data-hibiki-on*="keydown."]'
|
|
735
798
|
const HOTKEYED = '[data-hibiki-on*="@window"]'
|
|
799
|
+
const POPPED = '[data-hibiki-on*="popstate->"]'
|
|
736
800
|
const ISLAND = '[data-controller~="hibiki"]'
|
|
737
801
|
|
|
738
802
|
// A key press that is a gesture: not part of an IME composition (Safari
|
|
@@ -1218,6 +1282,31 @@ export default class HibikiController extends ChannelController {
|
|
|
1218
1282
|
}
|
|
1219
1283
|
}
|
|
1220
1284
|
|
|
1285
|
+
// Back or Forward landed on an entry this island made, or the one it
|
|
1286
|
+
// first pushed from: the control declaring `popstate`, the root as a
|
|
1287
|
+
// rule, performs its action with the entry's query. Turbo has adopted the
|
|
1288
|
+
// entry by now and written its own state there, which would make the
|
|
1289
|
+
// next visit to it a restore; the entry's own state goes back. False
|
|
1290
|
+
// when nothing here answers, and the page is loaded afresh instead.
|
|
1291
|
+
pop(event) {
|
|
1292
|
+
const control = [this.element, ...this.element.querySelectorAll(POPPED)].find(
|
|
1293
|
+
(element) => element.closest(ISLAND) === this.element && element.matches(POPPED)
|
|
1294
|
+
)
|
|
1295
|
+
const token = control?.dataset.hibikiOn.split(/\s+/).find((t) => t.startsWith("popstate->"))
|
|
1296
|
+
if (!token) return false
|
|
1297
|
+
history.replaceState(event.state, "")
|
|
1298
|
+
const payload = control.dataset.hibikiWith ? JSON.parse(control.dataset.hibikiWith) : {}
|
|
1299
|
+
const query = new URLSearchParams(window.location.search)
|
|
1300
|
+
for (const key of new Set(query.keys())) {
|
|
1301
|
+
const all = query.getAll(key)
|
|
1302
|
+
payload[payloadKey(key)] = key.endsWith("[]") ? all : all.at(-1)
|
|
1303
|
+
}
|
|
1304
|
+
this.flush()
|
|
1305
|
+
this.popping = this.perform(token.slice(10), payload)
|
|
1306
|
+
if (this.popping === undefined) this.popped() // offline: nothing will answer
|
|
1307
|
+
return true
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1221
1310
|
// A matched key is the control's gesture, so its default goes. A fallback
|
|
1222
1311
|
// control on an island that is not ready stands aside: a key has no
|
|
1223
1312
|
// native destination, but its default may be the form's own submit.
|
data/lib/hibiki/rails/channel.rb
CHANGED
|
@@ -30,11 +30,23 @@ module Hibiki
|
|
|
30
30
|
# other public method would be picked up by Channel#action_methods and
|
|
31
31
|
# become client-invocable.
|
|
32
32
|
module Channel
|
|
33
|
+
# #transmit_url's history option, allowlisted so a typo names itself.
|
|
34
|
+
HISTORIES = %i[replace push].freeze
|
|
35
|
+
private_constant :HISTORIES
|
|
36
|
+
|
|
33
37
|
def self.included(base)
|
|
34
38
|
base.extend(ClassMethods)
|
|
35
39
|
base.include(Broadcasts)
|
|
36
40
|
end
|
|
37
41
|
|
|
42
|
+
# What a history mode adds to the `{ url: }` frame.
|
|
43
|
+
def self.url_frame(history)
|
|
44
|
+
return history == :push ? { push: true } : {} if HISTORIES.include?(history)
|
|
45
|
+
|
|
46
|
+
raise ArgumentError,
|
|
47
|
+
"transmit_url history #{history.inspect} must be one of #{HISTORIES.map(&:inspect).join(', ')}"
|
|
48
|
+
end
|
|
49
|
+
|
|
38
50
|
module ClassMethods
|
|
39
51
|
# Lifecycle hooks, never client-invocable actions. Performing
|
|
40
52
|
# "build_graph" would rebuild the graph and leak the old root;
|
|
@@ -177,24 +189,30 @@ module Hibiki
|
|
|
177
189
|
end
|
|
178
190
|
|
|
179
191
|
# Mirror graph state into the page's address bar: wraps the block in
|
|
180
|
-
# an effect that transmits `{ url: }
|
|
181
|
-
#
|
|
182
|
-
#
|
|
183
|
-
#
|
|
184
|
-
#
|
|
185
|
-
#
|
|
186
|
-
#
|
|
187
|
-
#
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
# the
|
|
191
|
-
|
|
192
|
+
# an effect that transmits `{ url: }`, and the packaged client moves
|
|
193
|
+
# the bar to it. Compute a same-origin path ("/songs?page=2" — the
|
|
194
|
+
# client refuses anything else), and omit params that hold their
|
|
195
|
+
# defaults, or the bar grows noise on the first transmit. Pair it with
|
|
196
|
+
# first-paint code that READS those params (controller +
|
|
197
|
+
# subscribe-params seeding), so the mirrored URL reloads to the state
|
|
198
|
+
# it names. Equality-gated like #transmit_value: re-runs track every
|
|
199
|
+
# signal read, re-sends only when the URL actually moved.
|
|
200
|
+
#
|
|
201
|
+
# history: :replace (the default) is replaceState: the URL is a MIRROR
|
|
202
|
+
# of state, not a history entry, so Back leaves the page normally.
|
|
203
|
+
# history: :push makes each move an entry, for state that reads as a
|
|
204
|
+
# place (a wizard's step). Back then needs an answer: put
|
|
205
|
+
# `on(:visit, event: :popstate)` on the island root, and the client
|
|
206
|
+
# performs that action with the popped URL's query params. They come
|
|
207
|
+
# from the address bar, so treat them like a subscribe param.
|
|
208
|
+
def transmit_url(history: :replace, &compute)
|
|
209
|
+
frame = Channel.url_frame(history)
|
|
192
210
|
last = Object.new
|
|
193
211
|
Hibiki::Effect.new do
|
|
194
212
|
url = compute.call.to_s
|
|
195
213
|
unless url == last
|
|
196
214
|
last = url
|
|
197
|
-
transmit({ url: })
|
|
215
|
+
transmit({ url: }.merge(frame))
|
|
198
216
|
end
|
|
199
217
|
end
|
|
200
218
|
end
|
data/lib/hibiki/rails/helpers.rb
CHANGED
|
@@ -6,6 +6,7 @@ require "securerandom"
|
|
|
6
6
|
|
|
7
7
|
require_relative "dom_props"
|
|
8
8
|
require_relative "keys"
|
|
9
|
+
require_relative "modifiers"
|
|
9
10
|
|
|
10
11
|
module Hibiki
|
|
11
12
|
module Rails
|
|
@@ -187,6 +188,10 @@ module Hibiki
|
|
|
187
188
|
# `{ name => value }` — a checkbox its checked state, a multi-select
|
|
188
189
|
# its selected values — and a submitted form contributes its FormData.
|
|
189
190
|
#
|
|
191
|
+
# `event: :popstate` goes on the island root and answers Back and
|
|
192
|
+
# Forward over the entries `transmit_url(history: :push)` made; the
|
|
193
|
+
# payload is the popped URL's query.
|
|
194
|
+
#
|
|
190
195
|
# Everything else is a per-control modifier, kept out of the token
|
|
191
196
|
# grammar so the `->` left side stays purely "which event":
|
|
192
197
|
#
|
|
@@ -221,9 +226,10 @@ module Hibiki
|
|
|
221
226
|
action = Helpers.event_name(action)
|
|
222
227
|
events = Array(event).map { Helpers.event(it) }
|
|
223
228
|
debounce = DEFAULT_INPUT_DEBOUNCE if debounce.nil? && events.include?("input")
|
|
229
|
+
Modifiers.popstate!(debounce:, fallback:) if events.include?("popstate")
|
|
224
230
|
tokens = events.map { "#{it}->#{action}" }.join(" ")
|
|
225
231
|
{ data: { hibiki_on: tokens }
|
|
226
|
-
.merge(
|
|
232
|
+
.merge(Modifiers.attributes(with:, debounce:, confirm:, reset:, fallback:)) }
|
|
227
233
|
end
|
|
228
234
|
|
|
229
235
|
# Placeholder for a single reactive value: `<%= reactive :doubled, 0 %>`
|
|
@@ -305,20 +311,6 @@ module Hibiki
|
|
|
305
311
|
def island_stream(channel, cid, content)
|
|
306
312
|
safe_join([turbo_stream_from(channel.channel_name, cid), content])
|
|
307
313
|
end
|
|
308
|
-
|
|
309
|
-
# #on's per-control modifiers, kept out of the token grammar. Each is
|
|
310
|
-
# omitted when it matches the client's own default, so the common call
|
|
311
|
-
# still stamps exactly one attribute.
|
|
312
|
-
def on_modifiers(with:, debounce:, confirm:, reset:, fallback:)
|
|
313
|
-
{
|
|
314
|
-
hibiki_with: (JSON.generate(with) unless with.nil?),
|
|
315
|
-
# nonzero? returns the integer itself, so 0 compacts away.
|
|
316
|
-
hibiki_debounce: (Integer(debounce).nonzero? if debounce),
|
|
317
|
-
hibiki_confirm: confirm&.to_s,
|
|
318
|
-
hibiki_reset: ("false" if reset == false),
|
|
319
|
-
hibiki_fallback: ("true" if fallback)
|
|
320
|
-
}.compact
|
|
321
|
-
end
|
|
322
314
|
end
|
|
323
315
|
end
|
|
324
316
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Hibiki
|
|
6
|
+
module Rails
|
|
7
|
+
# Helpers#on's per-control modifiers, kept out of the token grammar.
|
|
8
|
+
module Modifiers
|
|
9
|
+
# The attributes beside hibiki_on. Each is omitted when it matches the
|
|
10
|
+
# client's own default, so the common call still stamps exactly one.
|
|
11
|
+
def self.attributes(with:, debounce:, confirm:, reset:, fallback:)
|
|
12
|
+
{
|
|
13
|
+
hibiki_with: (JSON.generate(with) unless with.nil?),
|
|
14
|
+
# nonzero? returns the integer itself, so 0 compacts away.
|
|
15
|
+
hibiki_debounce: (Integer(debounce).nonzero? if debounce),
|
|
16
|
+
hibiki_confirm: confirm&.to_s,
|
|
17
|
+
hibiki_reset: ("false" if reset == false),
|
|
18
|
+
hibiki_fallback: ("true" if fallback)
|
|
19
|
+
}.compact
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Back is not a gesture on a control: it has nothing to wait for and
|
|
23
|
+
# no native destination to fall back to.
|
|
24
|
+
def self.popstate!(**modifiers)
|
|
25
|
+
given = modifiers.compact.keys
|
|
26
|
+
raise ArgumentError, "a popstate event takes no #{given.join(' or ')}" if given.any?
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/hibiki/rails/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hibiki_rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.19.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- planetaska
|
|
@@ -189,6 +189,7 @@ files:
|
|
|
189
189
|
- lib/hibiki/rails/helpers.rb
|
|
190
190
|
- lib/hibiki/rails/instrumentation.rb
|
|
191
191
|
- lib/hibiki/rails/keys.rb
|
|
192
|
+
- lib/hibiki/rails/modifiers.rb
|
|
192
193
|
- lib/hibiki/rails/nested_actions.rb
|
|
193
194
|
- lib/hibiki/rails/reactive_form.rb
|
|
194
195
|
- lib/hibiki/rails/registry.rb
|