Skip to content

Commit 49a1103

Browse files
committed
[Bug Fix] Wrap Toggle in span so hidden input is a Stimulus target; ThemeToggle uses wrapper kwarg to compose controllers
1 parent ddaa88d commit 49a1103

6 files changed

Lines changed: 51 additions & 24 deletions

File tree

docs/app/javascript/controllers/ruby_ui/theme_toggle_controller.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Controller } from "@hotwired/stimulus"
22

33
// Connects to data-controller="ruby-ui--theme-toggle"
4-
// Expects to sit on the same element as ruby-ui--toggle and listen to its
4+
// Sits on the same wrapper as ruby-ui--toggle. Listens for the toggle's
55
// ruby-ui--toggle:change event. pressed = dark mode.
66
export default class extends Controller {
77
connect() {
@@ -30,9 +30,9 @@ export default class extends Controller {
3030
html.classList.add("light")
3131
html.classList.remove("dark")
3232
}
33+
// Flip the sibling Toggle controller's pressed value; it will propagate
34+
// aria-pressed / data-state to the button target.
3335
const dark = theme === "dark"
34-
this.element.setAttribute("aria-pressed", dark ? "true" : "false")
35-
this.element.dataset.state = dark ? "on" : "off"
3636
this.element.setAttribute("data-ruby-ui--toggle-pressed-value", dark ? "true" : "false")
3737
}
3838
}

docs/app/javascript/controllers/ruby_ui/toggle_controller.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
import { Controller } from "@hotwired/stimulus"
22

33
// Connects to data-controller="ruby-ui--toggle"
4+
// Sits on a wrapper element; the visible <button> and optional hidden <input>
5+
// are descendants so Stimulus can target them.
46
export default class extends Controller {
5-
static targets = ["input"]
7+
static targets = ["button", "input"]
68
static values = {
79
pressed: Boolean,
810
value: String,
911
unpressedValue: String
1012
}
1113

1214
toggle(event) {
13-
if (this.element.disabled) return
15+
if (this.buttonTarget.disabled) return
1416
this.pressedValue = !this.pressedValue
1517
}
1618

1719
pressedValueChanged(current, previous) {
18-
this.element.setAttribute("aria-pressed", current ? "true" : "false")
19-
this.element.dataset.state = current ? "on" : "off"
20+
if (this.hasButtonTarget) {
21+
this.buttonTarget.setAttribute("aria-pressed", current ? "true" : "false")
22+
this.buttonTarget.dataset.state = current ? "on" : "off"
23+
}
2024

2125
if (this.hasInputTarget) {
2226
this.inputTarget.value = current ? this.valueValue : this.unpressedValueValue

gem/lib/ruby_ui/theme_toggle/theme_toggle.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ def view_template(&block)
77
variant: :default,
88
size: :default,
99
aria: {label: "Toggle theme"},
10-
data: {
11-
controller: "ruby-ui--toggle ruby-ui--theme-toggle",
12-
action: "ruby-ui--toggle:change->ruby-ui--theme-toggle#apply"
10+
wrapper: {
11+
data: {
12+
controller: "ruby-ui--theme-toggle",
13+
action: "ruby-ui--toggle:change->ruby-ui--theme-toggle#apply"
14+
}
1315
},
1416
**attrs,
1517
&block

gem/lib/ruby_ui/theme_toggle/theme_toggle_controller.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Controller } from "@hotwired/stimulus"
22

33
// Connects to data-controller="ruby-ui--theme-toggle"
4-
// Expects to sit on the same element as ruby-ui--toggle and listen to its
4+
// Sits on the same wrapper as ruby-ui--toggle. Listens for the toggle's
55
// ruby-ui--toggle:change event. pressed = dark mode.
66
export default class extends Controller {
77
connect() {
@@ -30,9 +30,9 @@ export default class extends Controller {
3030
html.classList.add("light")
3131
html.classList.remove("dark")
3232
}
33+
// Flip the sibling Toggle controller's pressed value; it will propagate
34+
// aria-pressed / data-state to the button target.
3335
const dark = theme === "dark"
34-
this.element.setAttribute("aria-pressed", dark ? "true" : "false")
35-
this.element.dataset.state = dark ? "on" : "off"
3636
this.element.setAttribute("data-ruby-ui--toggle-pressed-value", dark ? "true" : "false")
3737
}
3838
}

gem/lib/ruby_ui/toggle/toggle.rb

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def initialize(
3636
variant: :default,
3737
size: :default,
3838
disabled: false,
39+
wrapper: {},
3940
**attrs
4041
)
4142
@pressed = pressed
@@ -45,16 +46,36 @@ def initialize(
4546
@variant = variant.to_sym
4647
@size = size.to_sym
4748
@disabled = disabled
49+
@wrapper = wrapper
4850
super(**attrs)
4951
end
5052

5153
def view_template(&block)
52-
button(**attrs, &block)
53-
render_hidden_input if @name
54+
span(**wrapper_attrs) do
55+
button(**attrs, &block)
56+
render_hidden_input if @name
57+
end
5458
end
5559

5660
private
5761

62+
def wrapper_attrs
63+
mix(wrapper_default_attrs, @wrapper)
64+
end
65+
66+
def wrapper_default_attrs
67+
{
68+
class: "contents",
69+
data: {
70+
controller: "ruby-ui--toggle",
71+
action: "click->ruby-ui--toggle#toggle",
72+
"ruby-ui--toggle-pressed-value": @pressed.to_s,
73+
"ruby-ui--toggle-value-value": @value.to_s,
74+
"ruby-ui--toggle-unpressed-value-value": @unpressed_value.to_s
75+
}
76+
}
77+
end
78+
5879
def render_hidden_input
5980
input(
6081
type: "hidden",
@@ -71,11 +92,7 @@ def default_attrs
7192
aria: {pressed: @pressed.to_s},
7293
data: {
7394
state: @pressed ? "on" : "off",
74-
controller: "ruby-ui--toggle",
75-
action: "click->ruby-ui--toggle#toggle",
76-
"ruby-ui--toggle-pressed-value": @pressed.to_s,
77-
"ruby-ui--toggle-value-value": @value.to_s,
78-
"ruby-ui--toggle-unpressed-value-value": @unpressed_value.to_s
95+
"ruby-ui--toggle-target": "button"
7996
},
8097
class: self.class.classes_for(variant: @variant, size: @size)
8198
)

gem/lib/ruby_ui/toggle/toggle_controller.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
import { Controller } from "@hotwired/stimulus"
22

33
// Connects to data-controller="ruby-ui--toggle"
4+
// Sits on a wrapper element; the visible <button> and optional hidden <input>
5+
// are descendants so Stimulus can target them.
46
export default class extends Controller {
5-
static targets = ["input"]
7+
static targets = ["button", "input"]
68
static values = {
79
pressed: Boolean,
810
value: String,
911
unpressedValue: String
1012
}
1113

1214
toggle(event) {
13-
if (this.element.disabled) return
15+
if (this.buttonTarget.disabled) return
1416
this.pressedValue = !this.pressedValue
1517
}
1618

1719
pressedValueChanged(current, previous) {
18-
this.element.setAttribute("aria-pressed", current ? "true" : "false")
19-
this.element.dataset.state = current ? "on" : "off"
20+
if (this.hasButtonTarget) {
21+
this.buttonTarget.setAttribute("aria-pressed", current ? "true" : "false")
22+
this.buttonTarget.dataset.state = current ? "on" : "off"
23+
}
2024

2125
if (this.hasInputTarget) {
2226
this.inputTarget.value = current ? this.valueValue : this.unpressedValueValue

0 commit comments

Comments
 (0)