Skip to content

Commit af84363

Browse files
committed
refactor(command): split dialog controller, use Stimulus outlets
Introduce ruby-ui--command-dialog controller for the trigger/template wrapper and keep ruby-ui--command for the cloned dialog instance. The trigger declares a ruby-ui--command outlet matched by a marker attribute on the cloned dialog wrapper. Outlet connect/disconnect callbacks track the active dialog, replacing the static class field and avoiding both querySelector and same-identifier dual-personality controller code. - New: command_dialog_controller.js (trigger + content target + outlet) - Strip open/openValue/content target from command_controller.js - Rename trigger actions to ruby-ui--command-dialog#open - Add ruby_ui__command_dialog_instance marker on cloned dialog
1 parent 7725ed7 commit af84363

8 files changed

Lines changed: 89 additions & 95 deletions

File tree

docs/app/javascript/controllers/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ application.register("ruby-ui--combobox", RubyUi__ComboboxController)
4040
import RubyUi__CommandController from "./ruby_ui/command_controller"
4141
application.register("ruby-ui--command", RubyUi__CommandController)
4242

43+
import RubyUi__CommandDialogController from "./ruby_ui/command_dialog_controller"
44+
application.register("ruby-ui--command-dialog", RubyUi__CommandDialogController)
45+
4346
import RubyUi__ContextMenuController from "./ruby_ui/context_menu_controller"
4447
application.register("ruby-ui--context-menu", RubyUi__ContextMenuController)
4548

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

Lines changed: 5 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,7 @@ import Fuse from "fuse.js";
33

44
// Connects to data-controller="ruby-ui--command"
55
export default class extends Controller {
6-
static targets = ["input", "group", "item", "empty", "content"];
7-
8-
static values = {
9-
open: {
10-
type: Boolean,
11-
default: false,
12-
},
13-
};
14-
15-
static openInstance = null;
6+
static targets = ["input", "group", "item", "empty"];
167

178
connect() {
189
this.selectedIndex = -1;
@@ -21,40 +12,9 @@ export default class extends Controller {
2112
return;
2213
}
2314

24-
this.constructor.openInstance = this;
2515
this.inputTarget.focus();
2616
this.searchIndex = this.buildSearchIndex();
2717
this.toggleVisibility(this.emptyTargets, false);
28-
29-
if (this.openValue && this.hasContentTarget) {
30-
this.open();
31-
}
32-
}
33-
34-
disconnect() {
35-
if (this.constructor.openInstance === this) {
36-
this.constructor.openInstance = null;
37-
}
38-
}
39-
40-
open(e) {
41-
if (e) {
42-
e.preventDefault();
43-
}
44-
45-
if (!this.hasContentTarget) {
46-
return;
47-
}
48-
49-
const openInstance = this.constructor.openInstance;
50-
if (openInstance) {
51-
openInstance.focusInput();
52-
return;
53-
}
54-
55-
document.body.insertAdjacentHTML("beforeend", this.contentTarget.innerHTML);
56-
// prevent scroll on body
57-
document.body.classList.add("overflow-hidden");
5818
}
5919

6020
dismiss() {
@@ -64,6 +24,10 @@ export default class extends Controller {
6424
this.element.remove();
6525
}
6626

27+
focusInput() {
28+
this.inputTarget?.focus();
29+
}
30+
6731
filter(e) {
6832
// Deselect any previously selected item
6933
this.deselectAll();
@@ -159,8 +123,4 @@ export default class extends Controller {
159123
this.itemTargets.forEach((item) => this.toggleAriaSelected(item, false));
160124
this.selectedIndex = -1;
161125
}
162-
163-
focusInput() {
164-
this.inputTarget?.focus();
165-
}
166126
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Controller } from "@hotwired/stimulus";
2+
3+
// Connects to data-controller="ruby-ui--command-dialog"
4+
export default class extends Controller {
5+
static targets = ["content"];
6+
static outlets = ["ruby-ui--command"];
7+
8+
rubyUiCommandOutletConnected(controller) {
9+
this.openOutlet = controller;
10+
}
11+
12+
rubyUiCommandOutletDisconnected() {
13+
this.openOutlet = null;
14+
}
15+
16+
open(e) {
17+
if (e) {
18+
e.preventDefault();
19+
}
20+
21+
if (!this.hasContentTarget) {
22+
return;
23+
}
24+
25+
if (this.openOutlet) {
26+
this.openOutlet.focusInput();
27+
return;
28+
}
29+
30+
document.body.insertAdjacentHTML("beforeend", this.contentTarget.innerHTML);
31+
// prevent scroll on body
32+
document.body.classList.add("overflow-hidden");
33+
}
34+
}

gem/lib/ruby_ui/command/command_controller.js

Lines changed: 5 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,7 @@ import Fuse from "fuse.js";
33

44
// Connects to data-controller="ruby-ui--command"
55
export default class extends Controller {
6-
static targets = ["input", "group", "item", "empty", "content"];
7-
8-
static values = {
9-
open: {
10-
type: Boolean,
11-
default: false,
12-
},
13-
};
14-
15-
static openInstance = null;
6+
static targets = ["input", "group", "item", "empty"];
167

178
connect() {
189
this.selectedIndex = -1;
@@ -21,40 +12,9 @@ export default class extends Controller {
2112
return;
2213
}
2314

24-
this.constructor.openInstance = this;
2515
this.inputTarget.focus();
2616
this.searchIndex = this.buildSearchIndex();
2717
this.toggleVisibility(this.emptyTargets, false);
28-
29-
if (this.openValue && this.hasContentTarget) {
30-
this.open();
31-
}
32-
}
33-
34-
disconnect() {
35-
if (this.constructor.openInstance === this) {
36-
this.constructor.openInstance = null;
37-
}
38-
}
39-
40-
open(e) {
41-
if (e) {
42-
e.preventDefault();
43-
}
44-
45-
if (!this.hasContentTarget) {
46-
return;
47-
}
48-
49-
const openInstance = this.constructor.openInstance;
50-
if (openInstance) {
51-
openInstance.focusInput();
52-
return;
53-
}
54-
55-
document.body.insertAdjacentHTML("beforeend", this.contentTarget.innerHTML);
56-
// prevent scroll on body
57-
document.body.classList.add("overflow-hidden");
5818
}
5919

6020
dismiss() {
@@ -64,6 +24,10 @@ export default class extends Controller {
6424
this.element.remove();
6525
}
6626

27+
focusInput() {
28+
this.inputTarget?.focus();
29+
}
30+
6731
filter(e) {
6832
// Deselect any previously selected item
6933
this.deselectAll();
@@ -159,8 +123,4 @@ export default class extends Controller {
159123
this.itemTargets.forEach((item) => this.toggleAriaSelected(item, false));
160124
this.selectedIndex = -1;
161125
}
162-
163-
focusInput() {
164-
this.inputTarget?.focus();
165-
}
166126
}

gem/lib/ruby_ui/command/command_dialog.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ def view_template(&)
1010

1111
def default_attrs
1212
{
13-
data: {controller: "ruby-ui--command"}
13+
data: {
14+
controller: "ruby-ui--command-dialog",
15+
ruby_ui__command_dialog_ruby_ui__command_outlet: "[data-ruby-ui--command-dialog-instance]"
16+
}
1417
}
1518
end
1619
end

gem/lib/ruby_ui/command/command_dialog_content.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ def initialize(size: :md, **attrs)
1717
end
1818

1919
def view_template(&block)
20-
template(data: {ruby_ui__command_target: "content"}) do
21-
div(data: {controller: "ruby-ui--command"}) do
20+
template(data: {ruby_ui__command_dialog_target: "content"}) do
21+
div(data: {controller: "ruby-ui--command", ruby_ui__command_dialog_instance: true}) do
2222
backdrop
2323
div(**attrs, &block)
2424
end
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Controller } from "@hotwired/stimulus";
2+
3+
// Connects to data-controller="ruby-ui--command-dialog"
4+
export default class extends Controller {
5+
static targets = ["content"];
6+
static outlets = ["ruby-ui--command"];
7+
8+
rubyUiCommandOutletConnected(controller) {
9+
this.openOutlet = controller;
10+
}
11+
12+
rubyUiCommandOutletDisconnected() {
13+
this.openOutlet = null;
14+
}
15+
16+
open(e) {
17+
if (e) {
18+
e.preventDefault();
19+
}
20+
21+
if (!this.hasContentTarget) {
22+
return;
23+
}
24+
25+
if (this.openOutlet) {
26+
this.openOutlet.focusInput();
27+
return;
28+
}
29+
30+
document.body.insertAdjacentHTML("beforeend", this.contentTarget.innerHTML);
31+
// prevent scroll on body
32+
document.body.classList.add("overflow-hidden");
33+
}
34+
}

gem/lib/ruby_ui/command/command_dialog_trigger.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class CommandDialogTrigger < Base
88
].freeze
99

1010
def initialize(keybindings: DEFAULT_KEYBINDINGS, **attrs)
11-
@keybindings = keybindings.map { |kb| "#{kb}->ruby-ui--command#open" }
11+
@keybindings = keybindings.map { |kb| "#{kb}->ruby-ui--command-dialog#open" }
1212
super(**attrs)
1313
end
1414

@@ -21,7 +21,7 @@ def view_template(&)
2121
def default_attrs
2222
{
2323
data: {
24-
action: ["click->ruby-ui--command#open", @keybindings.join(" ")]
24+
action: ["click->ruby-ui--command-dialog#open", @keybindings.join(" ")]
2525
}
2626
}
2727
end

0 commit comments

Comments
 (0)