shadcn-rails 0.1.0 → 0.2.1
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 +69 -2
- data/README.md +102 -1398
- data/__mocks__/@floating-ui/dom.js +67 -0
- data/app/assets/javascripts/shadcn/controllers/base_menu_controller.js +266 -0
- data/app/assets/javascripts/shadcn/controllers/combobox_controller.js +34 -8
- data/app/assets/javascripts/shadcn/controllers/command_controller.js +5 -1
- data/app/assets/javascripts/shadcn/controllers/context_menu_controller.js +64 -135
- data/app/assets/javascripts/shadcn/controllers/dropdown_controller.js +56 -186
- data/app/assets/javascripts/shadcn/controllers/hover_card_controller.js +29 -55
- data/app/assets/javascripts/shadcn/controllers/menubar_controller.js +10 -7
- data/app/assets/javascripts/shadcn/controllers/navigation_menu_controller.js +10 -6
- data/app/assets/javascripts/shadcn/controllers/popover_controller.js +35 -60
- data/app/assets/javascripts/shadcn/controllers/select_controller.js +37 -17
- data/app/assets/javascripts/shadcn/controllers/sidebar_controller.js +24 -14
- data/app/assets/javascripts/shadcn/controllers/tooltip_controller.js +28 -59
- data/app/assets/javascripts/shadcn/index.js +9 -1
- data/app/assets/javascripts/shadcn/utils/floating.js +179 -0
- data/app/assets/stylesheets/shadcn/base.css +32 -0
- data/app/assets/stylesheets/shadcn/components.css +12 -0
- data/app/components/shadcn/accordion_component.html.erb +8 -0
- data/app/components/shadcn/accordion_component.rb +6 -15
- data/app/components/shadcn/alert_component.html.erb +6 -0
- data/app/components/shadcn/alert_component.rb +0 -18
- data/app/components/shadcn/alert_dialog_component.html.erb +12 -0
- data/app/components/shadcn/alert_dialog_component.rb +7 -27
- data/app/components/shadcn/aspect_ratio_component.html.erb +7 -0
- data/app/components/shadcn/aspect_ratio_component.rb +4 -19
- data/app/components/shadcn/avatar_component.html.erb +20 -0
- data/app/components/shadcn/avatar_component.rb +8 -36
- data/app/components/shadcn/badge_component.html.erb +1 -0
- data/app/components/shadcn/badge_component.rb +0 -11
- data/app/components/shadcn/base_component.rb +15 -2
- data/app/components/shadcn/breadcrumb_component.html.erb +5 -0
- data/app/components/shadcn/breadcrumb_component.rb +6 -16
- data/app/components/shadcn/button_component.html.erb +18 -0
- data/app/components/shadcn/button_component.rb +1 -41
- data/app/components/shadcn/card_component.html.erb +8 -0
- data/app/components/shadcn/card_component.rb +2 -6
- data/app/components/shadcn/checkbox_component.html.erb +32 -0
- data/app/components/shadcn/checkbox_component.rb +4 -43
- data/app/components/shadcn/collapsible_component.html.erb +8 -0
- data/app/components/shadcn/collapsible_component.rb +6 -15
- data/app/components/shadcn/command_list_component.rb +29 -14
- data/app/components/shadcn/context_menu_checkbox_item_component.rb +76 -0
- data/app/components/shadcn/context_menu_component.html.erb +11 -0
- data/app/components/shadcn/context_menu_component.rb +6 -26
- data/app/components/shadcn/context_menu_content_component.rb +37 -14
- data/app/components/shadcn/context_menu_item_component.rb +3 -2
- data/app/components/shadcn/context_menu_radio_group_component.rb +42 -0
- data/app/components/shadcn/context_menu_radio_item_component.rb +76 -0
- data/app/components/shadcn/dialog_component.html.erb +14 -0
- data/app/components/shadcn/dialog_component.rb +8 -29
- data/app/components/shadcn/drawer_component.html.erb +12 -0
- data/app/components/shadcn/drawer_component.rb +7 -27
- data/app/components/shadcn/dropdown_menu_checkbox_item_component.rb +76 -0
- data/app/components/shadcn/dropdown_menu_component.html.erb +14 -0
- data/app/components/shadcn/dropdown_menu_component.rb +9 -29
- data/app/components/shadcn/dropdown_menu_content_component.rb +45 -16
- data/app/components/shadcn/dropdown_menu_radio_group_component.rb +42 -0
- data/app/components/shadcn/dropdown_menu_radio_item_component.rb +76 -0
- data/app/components/shadcn/field_component.rb +7 -8
- data/app/components/shadcn/hover_card_component.html.erb +12 -0
- data/app/components/shadcn/hover_card_component.rb +7 -26
- data/app/components/shadcn/input_component.html.erb +18 -0
- data/app/components/shadcn/input_component.rb +2 -27
- data/app/components/shadcn/input_otp_component.rb +3 -3
- data/app/components/shadcn/kbd_component.html.erb +1 -0
- data/app/components/shadcn/kbd_component.rb +3 -10
- data/app/components/shadcn/label_component.html.erb +3 -0
- data/app/components/shadcn/label_component.rb +2 -18
- data/app/components/shadcn/menubar_component.html.erb +6 -0
- data/app/components/shadcn/menubar_component.rb +4 -15
- data/app/components/shadcn/menubar_content_component.rb +45 -20
- data/app/components/shadcn/menubar_sub_content_component.rb +21 -8
- data/app/components/shadcn/native_select_component.html.erb +22 -0
- data/app/components/shadcn/native_select_component.rb +9 -39
- data/app/components/shadcn/navigation_menu_component.html.erb +6 -0
- data/app/components/shadcn/navigation_menu_component.rb +4 -15
- data/app/components/shadcn/pagination_component.html.erb +5 -0
- data/app/components/shadcn/pagination_component.rb +11 -15
- data/app/components/shadcn/popover_component.html.erb +15 -0
- data/app/components/shadcn/popover_component.rb +10 -30
- data/app/components/shadcn/progress_component.html.erb +13 -0
- data/app/components/shadcn/progress_component.rb +6 -26
- data/app/components/shadcn/radio_group_component.html.erb +8 -0
- data/app/components/shadcn/radio_group_component.rb +12 -26
- data/app/components/shadcn/radio_group_item_component.rb +32 -6
- data/app/components/shadcn/resizable_panel_group_component.rb +27 -16
- data/app/components/shadcn/scroll_area_component.html.erb +7 -0
- data/app/components/shadcn/scroll_area_component.rb +4 -16
- data/app/components/shadcn/select_component.html.erb +46 -0
- data/app/components/shadcn/select_component.rb +29 -86
- data/app/components/shadcn/separator_component.html.erb +5 -0
- data/app/components/shadcn/separator_component.rb +6 -14
- data/app/components/shadcn/sheet_component.html.erb +12 -0
- data/app/components/shadcn/sheet_component.rb +7 -27
- data/app/components/shadcn/sidebar_component.rb +2 -2
- data/app/components/shadcn/skeleton_component.html.erb +1 -0
- data/app/components/shadcn/skeleton_component.rb +4 -2
- data/app/components/shadcn/slider_component.html.erb +12 -0
- data/app/components/shadcn/slider_component.rb +2 -21
- data/app/components/shadcn/spinner_component.html.erb +18 -0
- data/app/components/shadcn/spinner_component.rb +2 -30
- data/app/components/shadcn/switch_component.html.erb +72 -0
- data/app/components/shadcn/switch_component.rb +4 -82
- data/app/components/shadcn/table_component.html.erb +9 -0
- data/app/components/shadcn/table_component.rb +2 -10
- data/app/components/shadcn/tabs_component.html.erb +8 -0
- data/app/components/shadcn/tabs_component.rb +4 -17
- data/app/components/shadcn/textarea_component.html.erb +13 -0
- data/app/components/shadcn/textarea_component.rb +6 -22
- data/app/components/shadcn/toast_component.html.erb +36 -0
- data/app/components/shadcn/toast_component.rb +6 -54
- data/app/components/shadcn/toggle_component.html.erb +12 -0
- data/app/components/shadcn/toggle_component.rb +6 -21
- data/app/components/shadcn/toggle_group_component.html.erb +14 -0
- data/app/components/shadcn/toggle_group_component.rb +6 -29
- data/app/components/shadcn/tooltip_component.html.erb +20 -0
- data/app/components/shadcn/tooltip_component.rb +13 -38
- data/lib/generators/shadcn/add/USAGE +24 -0
- data/lib/generators/shadcn/add/add_generator.rb +279 -0
- data/lib/generators/shadcn/install/USAGE +22 -0
- data/lib/generators/shadcn/install/install_generator.rb +8 -3
- data/lib/generators/shadcn/install/templates/initializer.rb.tt +7 -27
- data/lib/generators/shadcn/install/templates/shadcn.yml.tt +15 -31
- data/lib/shadcn/rails/version.rb +1 -1
- metadata +54 -42
- data/.dockerignore +0 -40
- data/CLAUDE.md +0 -463
- data/PROGRESS.md +0 -485
- data/Rakefile +0 -29
- data/__tests__/controllers/__snapshots__/calendar_controller.test.js.snap +0 -13
- data/__tests__/controllers/__snapshots__/popover_controller.test.js.snap +0 -46
- data/__tests__/controllers/__snapshots__/sheet_controller.test.js.snap +0 -111
- data/__tests__/controllers/__snapshots__/tabs_controller.test.js.snap +0 -27
- data/__tests__/controllers/accordion_controller.test.js +0 -904
- data/__tests__/controllers/calendar_controller.test.js +0 -1370
- data/__tests__/controllers/carousel_controller.test.js +0 -912
- data/__tests__/controllers/checkbox_controller.test.js +0 -454
- data/__tests__/controllers/collapsible_controller.test.js +0 -407
- data/__tests__/controllers/combobox_controller.test.js +0 -966
- data/__tests__/controllers/context_menu_controller.test.js +0 -627
- data/__tests__/controllers/date_picker_controller.test.js +0 -636
- data/__tests__/controllers/dialog_controller.test.js +0 -878
- data/__tests__/controllers/drawer_controller.test.js +0 -995
- data/__tests__/controllers/menubar_controller.test.js +0 -736
- data/__tests__/controllers/navigation_menu_controller.test.js +0 -598
- data/__tests__/controllers/popover_controller.test.js +0 -1007
- data/__tests__/controllers/radio_group_controller.test.js +0 -640
- data/__tests__/controllers/resizable_controller.test.js +0 -680
- data/__tests__/controllers/select_controller.test.js +0 -674
- data/__tests__/controllers/sheet_controller.test.js +0 -986
- data/__tests__/controllers/slider_controller.test.js +0 -1036
- data/__tests__/controllers/switch_controller.test.js +0 -424
- data/__tests__/controllers/tabs_controller.test.js +0 -907
- data/__tests__/controllers/toggle_group_controller.test.js +0 -839
- data/__tests__/controllers/tooltip_controller.test.js +0 -808
- data/__tests__/helpers/stimulus-test-helper.js +0 -203
- data/babel.config.cjs +0 -5
- data/bin/console +0 -11
- data/bin/setup +0 -8
- data/jest.config.js +0 -19
- data/jest.setup.js +0 -8
- data/lib/generators/shadcn/component/component_generator.rb +0 -188
- data/lib/generators/shadcn/theme/theme_generator.rb +0 -128
- data/package-lock.json +0 -7415
- data/package.json +0 -68
- data/rollup.config.js +0 -29
|
@@ -1,598 +0,0 @@
|
|
|
1
|
-
import { Application } from "@hotwired/stimulus"
|
|
2
|
-
import NavigationMenuController from "../../app/assets/javascripts/shadcn/controllers/navigation_menu_controller.js"
|
|
3
|
-
import { setupController, cleanupController, click, nextFrame, wait } from '../helpers/stimulus-test-helper.js'
|
|
4
|
-
|
|
5
|
-
describe("NavigationMenuController", () => {
|
|
6
|
-
let application
|
|
7
|
-
let element
|
|
8
|
-
let controller
|
|
9
|
-
|
|
10
|
-
afterEach(() => {
|
|
11
|
-
cleanupController(application)
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
describe("basic rendering and initialization", () => {
|
|
15
|
-
const basicHTML = `
|
|
16
|
-
<nav data-controller="shadcn--navigation-menu"
|
|
17
|
-
data-shadcn--navigation-menu-open-index-value="-1"
|
|
18
|
-
data-shadcn--navigation-menu-delay-duration-value="200"
|
|
19
|
-
data-shadcn--navigation-menu-skip-delay-duration-value="300">
|
|
20
|
-
<ul data-shadcn--navigation-menu-target="list">
|
|
21
|
-
<li data-shadcn--navigation-menu-target="item">
|
|
22
|
-
<button data-shadcn--navigation-menu-target="trigger"
|
|
23
|
-
data-action="click->shadcn--navigation-menu#toggle mouseenter->shadcn--navigation-menu#hoverOpen mouseleave->shadcn--navigation-menu#hoverClose"
|
|
24
|
-
aria-expanded="false">Products</button>
|
|
25
|
-
<div data-shadcn--navigation-menu-target="content" hidden>
|
|
26
|
-
<a href="/product1">Product 1</a>
|
|
27
|
-
</div>
|
|
28
|
-
</li>
|
|
29
|
-
<li data-shadcn--navigation-menu-target="item">
|
|
30
|
-
<button data-shadcn--navigation-menu-target="trigger"
|
|
31
|
-
data-action="click->shadcn--navigation-menu#toggle mouseenter->shadcn--navigation-menu#hoverOpen mouseleave->shadcn--navigation-menu#hoverClose"
|
|
32
|
-
aria-expanded="false">Services</button>
|
|
33
|
-
<div data-shadcn--navigation-menu-target="content" hidden>
|
|
34
|
-
<a href="/service1">Service 1</a>
|
|
35
|
-
</div>
|
|
36
|
-
</li>
|
|
37
|
-
</ul>
|
|
38
|
-
</nav>
|
|
39
|
-
`
|
|
40
|
-
|
|
41
|
-
beforeEach(async () => {
|
|
42
|
-
const setup = await setupController(NavigationMenuController, basicHTML, 'shadcn--navigation-menu')
|
|
43
|
-
application = setup.application
|
|
44
|
-
element = setup.element
|
|
45
|
-
controller = setup.controller
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
test("initializes with closed state", () => {
|
|
49
|
-
expect(controller.openIndexValue).toBe(-1)
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
test("initializes isOpen to false", () => {
|
|
53
|
-
expect(controller.isOpen).toBe(false)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
test("initializes with default delay values", () => {
|
|
57
|
-
expect(controller.delayDurationValue).toBe(200)
|
|
58
|
-
expect(controller.skipDelayDurationValue).toBe(300)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
test("has list target", () => {
|
|
62
|
-
expect(controller.hasListTarget).toBe(true)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
test("has item targets", () => {
|
|
66
|
-
expect(controller.itemTargets.length).toBe(2)
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
test("has trigger targets", () => {
|
|
70
|
-
expect(controller.triggerTargets.length).toBe(2)
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
test("has content targets", () => {
|
|
74
|
-
expect(controller.contentTargets.length).toBe(2)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
test("all content is initially hidden", () => {
|
|
78
|
-
controller.contentTargets.forEach(content => {
|
|
79
|
-
expect(content.hidden).toBe(true)
|
|
80
|
-
})
|
|
81
|
-
})
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
describe("toggle functionality", () => {
|
|
85
|
-
const toggleHTML = `
|
|
86
|
-
<nav data-controller="shadcn--navigation-menu"
|
|
87
|
-
data-shadcn--navigation-menu-open-index-value="-1">
|
|
88
|
-
<ul data-shadcn--navigation-menu-target="list">
|
|
89
|
-
<li data-shadcn--navigation-menu-target="item">
|
|
90
|
-
<button data-shadcn--navigation-menu-target="trigger"
|
|
91
|
-
data-action="click->shadcn--navigation-menu#toggle"
|
|
92
|
-
aria-expanded="false">Products</button>
|
|
93
|
-
<div data-shadcn--navigation-menu-target="content" hidden>Content 1</div>
|
|
94
|
-
</li>
|
|
95
|
-
<li data-shadcn--navigation-menu-target="item">
|
|
96
|
-
<button data-shadcn--navigation-menu-target="trigger"
|
|
97
|
-
data-action="click->shadcn--navigation-menu#toggle"
|
|
98
|
-
aria-expanded="false">Services</button>
|
|
99
|
-
<div data-shadcn--navigation-menu-target="content" hidden>Content 2</div>
|
|
100
|
-
</li>
|
|
101
|
-
</ul>
|
|
102
|
-
</nav>
|
|
103
|
-
`
|
|
104
|
-
|
|
105
|
-
beforeEach(async () => {
|
|
106
|
-
const setup = await setupController(NavigationMenuController, toggleHTML, 'shadcn--navigation-menu')
|
|
107
|
-
application = setup.application
|
|
108
|
-
element = setup.element
|
|
109
|
-
controller = setup.controller
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
test("opens item on toggle", async () => {
|
|
113
|
-
const trigger = controller.triggerTargets[0]
|
|
114
|
-
controller.toggle({ currentTarget: trigger, preventDefault: jest.fn() })
|
|
115
|
-
await nextFrame()
|
|
116
|
-
|
|
117
|
-
expect(controller.openIndexValue).toBe(0)
|
|
118
|
-
expect(controller.isOpen).toBe(true)
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
test("sets aria-expanded to true", async () => {
|
|
122
|
-
const trigger = controller.triggerTargets[0]
|
|
123
|
-
controller.toggle({ currentTarget: trigger, preventDefault: jest.fn() })
|
|
124
|
-
await nextFrame()
|
|
125
|
-
|
|
126
|
-
expect(trigger.getAttribute("aria-expanded")).toBe("true")
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
test("shows content when opened", async () => {
|
|
130
|
-
const trigger = controller.triggerTargets[0]
|
|
131
|
-
controller.toggle({ currentTarget: trigger, preventDefault: jest.fn() })
|
|
132
|
-
await nextFrame()
|
|
133
|
-
|
|
134
|
-
const content = controller.contentTargets[0]
|
|
135
|
-
expect(content.hidden).toBe(false)
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
test("sets content data-state to open", async () => {
|
|
139
|
-
const trigger = controller.triggerTargets[0]
|
|
140
|
-
controller.toggle({ currentTarget: trigger, preventDefault: jest.fn() })
|
|
141
|
-
await nextFrame()
|
|
142
|
-
|
|
143
|
-
const content = controller.contentTargets[0]
|
|
144
|
-
expect(content.dataset.state).toBe("open")
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
test("closes item on second toggle", async () => {
|
|
148
|
-
const trigger = controller.triggerTargets[0]
|
|
149
|
-
controller.toggle({ currentTarget: trigger, preventDefault: jest.fn() })
|
|
150
|
-
await nextFrame()
|
|
151
|
-
controller.toggle({ currentTarget: trigger, preventDefault: jest.fn() })
|
|
152
|
-
await nextFrame()
|
|
153
|
-
|
|
154
|
-
expect(controller.openIndexValue).toBe(-1)
|
|
155
|
-
expect(controller.isOpen).toBe(false)
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
test("switches to different item on toggle", async () => {
|
|
159
|
-
const trigger1 = controller.triggerTargets[0]
|
|
160
|
-
const trigger2 = controller.triggerTargets[1]
|
|
161
|
-
|
|
162
|
-
controller.toggle({ currentTarget: trigger1, preventDefault: jest.fn() })
|
|
163
|
-
await nextFrame()
|
|
164
|
-
|
|
165
|
-
expect(controller.openIndexValue).toBe(0)
|
|
166
|
-
|
|
167
|
-
controller.toggle({ currentTarget: trigger2, preventDefault: jest.fn() })
|
|
168
|
-
await nextFrame()
|
|
169
|
-
|
|
170
|
-
expect(controller.openIndexValue).toBe(1)
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
test("sets wasClickOpened flag on toggle", async () => {
|
|
174
|
-
const trigger = controller.triggerTargets[0]
|
|
175
|
-
controller.toggle({ currentTarget: trigger, preventDefault: jest.fn() })
|
|
176
|
-
await nextFrame()
|
|
177
|
-
|
|
178
|
-
expect(controller.wasClickOpened).toBe(true)
|
|
179
|
-
})
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
describe("openItem and closeItem", () => {
|
|
183
|
-
const itemHTML = `
|
|
184
|
-
<nav data-controller="shadcn--navigation-menu"
|
|
185
|
-
data-shadcn--navigation-menu-open-index-value="-1">
|
|
186
|
-
<ul data-shadcn--navigation-menu-target="list">
|
|
187
|
-
<li data-shadcn--navigation-menu-target="item">
|
|
188
|
-
<button data-shadcn--navigation-menu-target="trigger" aria-expanded="false">Menu 1</button>
|
|
189
|
-
<div data-shadcn--navigation-menu-target="content" hidden>Content 1</div>
|
|
190
|
-
</li>
|
|
191
|
-
<li data-shadcn--navigation-menu-target="item">
|
|
192
|
-
<button data-shadcn--navigation-menu-target="trigger" aria-expanded="false">Menu 2</button>
|
|
193
|
-
<div data-shadcn--navigation-menu-target="content" hidden>Content 2</div>
|
|
194
|
-
</li>
|
|
195
|
-
</ul>
|
|
196
|
-
</nav>
|
|
197
|
-
`
|
|
198
|
-
|
|
199
|
-
beforeEach(async () => {
|
|
200
|
-
const setup = await setupController(NavigationMenuController, itemHTML, 'shadcn--navigation-menu')
|
|
201
|
-
application = setup.application
|
|
202
|
-
element = setup.element
|
|
203
|
-
controller = setup.controller
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
test("openItem opens specified index", async () => {
|
|
207
|
-
controller.openItem(0)
|
|
208
|
-
await nextFrame()
|
|
209
|
-
|
|
210
|
-
expect(controller.openIndexValue).toBe(0)
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
test("openItem ignores invalid index (negative)", async () => {
|
|
214
|
-
controller.openItem(-1)
|
|
215
|
-
await nextFrame()
|
|
216
|
-
|
|
217
|
-
expect(controller.openIndexValue).toBe(-1)
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
test("openItem ignores invalid index (too high)", async () => {
|
|
221
|
-
controller.openItem(99)
|
|
222
|
-
await nextFrame()
|
|
223
|
-
|
|
224
|
-
expect(controller.openIndexValue).toBe(-1)
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
test("openItem closes previous item when opening new one", async () => {
|
|
228
|
-
controller.openItem(0)
|
|
229
|
-
await nextFrame()
|
|
230
|
-
|
|
231
|
-
controller.openItem(1)
|
|
232
|
-
await nextFrame()
|
|
233
|
-
|
|
234
|
-
const trigger0 = controller.triggerTargets[0]
|
|
235
|
-
const content0 = controller.contentTargets[0]
|
|
236
|
-
|
|
237
|
-
expect(trigger0.getAttribute("aria-expanded")).toBe("false")
|
|
238
|
-
expect(content0.dataset.state).toBe("closed")
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
test("closeItem closes specified index", async () => {
|
|
242
|
-
controller.openItem(0)
|
|
243
|
-
await nextFrame()
|
|
244
|
-
|
|
245
|
-
controller.closeItem(0)
|
|
246
|
-
await nextFrame()
|
|
247
|
-
|
|
248
|
-
const trigger = controller.triggerTargets[0]
|
|
249
|
-
expect(trigger.getAttribute("aria-expanded")).toBe("false")
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
test("sets motion direction when switching items", async () => {
|
|
253
|
-
controller.openItem(0)
|
|
254
|
-
await nextFrame()
|
|
255
|
-
|
|
256
|
-
controller.openItem(1)
|
|
257
|
-
await nextFrame()
|
|
258
|
-
|
|
259
|
-
const content1 = controller.contentTargets[1]
|
|
260
|
-
expect(content1.dataset.motion).toBe("from-end")
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
test("sets opposite motion direction", async () => {
|
|
264
|
-
controller.openItem(1)
|
|
265
|
-
await nextFrame()
|
|
266
|
-
|
|
267
|
-
controller.openItem(0)
|
|
268
|
-
await nextFrame()
|
|
269
|
-
|
|
270
|
-
const content0 = controller.contentTargets[0]
|
|
271
|
-
expect(content0.dataset.motion).toBe("from-start")
|
|
272
|
-
})
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
describe("closeAll", () => {
|
|
276
|
-
const closeAllHTML = `
|
|
277
|
-
<nav data-controller="shadcn--navigation-menu"
|
|
278
|
-
data-shadcn--navigation-menu-open-index-value="-1">
|
|
279
|
-
<ul data-shadcn--navigation-menu-target="list">
|
|
280
|
-
<li data-shadcn--navigation-menu-target="item">
|
|
281
|
-
<button data-shadcn--navigation-menu-target="trigger" aria-expanded="false">Menu</button>
|
|
282
|
-
<div data-shadcn--navigation-menu-target="content" hidden>Content</div>
|
|
283
|
-
</li>
|
|
284
|
-
</ul>
|
|
285
|
-
</nav>
|
|
286
|
-
`
|
|
287
|
-
|
|
288
|
-
beforeEach(async () => {
|
|
289
|
-
const setup = await setupController(NavigationMenuController, closeAllHTML, 'shadcn--navigation-menu')
|
|
290
|
-
application = setup.application
|
|
291
|
-
element = setup.element
|
|
292
|
-
controller = setup.controller
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
test("resets openIndexValue to -1", async () => {
|
|
296
|
-
controller.openItem(0)
|
|
297
|
-
await nextFrame()
|
|
298
|
-
|
|
299
|
-
controller.closeAll()
|
|
300
|
-
await nextFrame()
|
|
301
|
-
|
|
302
|
-
expect(controller.openIndexValue).toBe(-1)
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
test("resets isOpen to false", async () => {
|
|
306
|
-
controller.openItem(0)
|
|
307
|
-
await nextFrame()
|
|
308
|
-
|
|
309
|
-
controller.closeAll()
|
|
310
|
-
await nextFrame()
|
|
311
|
-
|
|
312
|
-
expect(controller.isOpen).toBe(false)
|
|
313
|
-
})
|
|
314
|
-
|
|
315
|
-
test("resets wasClickOpened to false", async () => {
|
|
316
|
-
const trigger = controller.triggerTargets[0]
|
|
317
|
-
controller.toggle({ currentTarget: trigger, preventDefault: jest.fn() })
|
|
318
|
-
await nextFrame()
|
|
319
|
-
|
|
320
|
-
controller.closeAll()
|
|
321
|
-
await nextFrame()
|
|
322
|
-
|
|
323
|
-
expect(controller.wasClickOpened).toBe(false)
|
|
324
|
-
})
|
|
325
|
-
|
|
326
|
-
test("sets all triggers to aria-expanded false", async () => {
|
|
327
|
-
controller.openItem(0)
|
|
328
|
-
await nextFrame()
|
|
329
|
-
|
|
330
|
-
controller.closeAll()
|
|
331
|
-
await nextFrame()
|
|
332
|
-
|
|
333
|
-
controller.triggerTargets.forEach(trigger => {
|
|
334
|
-
expect(trigger.getAttribute("aria-expanded")).toBe("false")
|
|
335
|
-
})
|
|
336
|
-
})
|
|
337
|
-
})
|
|
338
|
-
|
|
339
|
-
describe("keyboard navigation", () => {
|
|
340
|
-
const keyboardHTML = `
|
|
341
|
-
<nav data-controller="shadcn--navigation-menu"
|
|
342
|
-
data-shadcn--navigation-menu-open-index-value="-1">
|
|
343
|
-
<ul data-shadcn--navigation-menu-target="list">
|
|
344
|
-
<li data-shadcn--navigation-menu-target="item">
|
|
345
|
-
<button data-shadcn--navigation-menu-target="trigger" aria-expanded="false">Menu 1</button>
|
|
346
|
-
<div data-shadcn--navigation-menu-target="content" hidden>Content 1</div>
|
|
347
|
-
</li>
|
|
348
|
-
<li data-shadcn--navigation-menu-target="item">
|
|
349
|
-
<button data-shadcn--navigation-menu-target="trigger" aria-expanded="false">Menu 2</button>
|
|
350
|
-
<div data-shadcn--navigation-menu-target="content" hidden>Content 2</div>
|
|
351
|
-
</li>
|
|
352
|
-
<li data-shadcn--navigation-menu-target="item">
|
|
353
|
-
<button data-shadcn--navigation-menu-target="trigger" aria-expanded="false">Menu 3</button>
|
|
354
|
-
<div data-shadcn--navigation-menu-target="content" hidden>Content 3</div>
|
|
355
|
-
</li>
|
|
356
|
-
</ul>
|
|
357
|
-
</nav>
|
|
358
|
-
`
|
|
359
|
-
|
|
360
|
-
beforeEach(async () => {
|
|
361
|
-
const setup = await setupController(NavigationMenuController, keyboardHTML, 'shadcn--navigation-menu')
|
|
362
|
-
application = setup.application
|
|
363
|
-
element = setup.element
|
|
364
|
-
controller = setup.controller
|
|
365
|
-
|
|
366
|
-
// Open the first menu to enable keyboard navigation
|
|
367
|
-
controller.openItem(0)
|
|
368
|
-
await nextFrame()
|
|
369
|
-
})
|
|
370
|
-
|
|
371
|
-
test("ArrowRight navigates to next item", async () => {
|
|
372
|
-
controller.handleKeydown({ key: "ArrowRight", preventDefault: jest.fn() })
|
|
373
|
-
await nextFrame()
|
|
374
|
-
|
|
375
|
-
expect(controller.openIndexValue).toBe(1)
|
|
376
|
-
})
|
|
377
|
-
|
|
378
|
-
test("ArrowRight wraps to first item", async () => {
|
|
379
|
-
controller.openItem(2)
|
|
380
|
-
await nextFrame()
|
|
381
|
-
|
|
382
|
-
controller.handleKeydown({ key: "ArrowRight", preventDefault: jest.fn() })
|
|
383
|
-
await nextFrame()
|
|
384
|
-
|
|
385
|
-
expect(controller.openIndexValue).toBe(0)
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
test("ArrowLeft navigates to previous item", async () => {
|
|
389
|
-
controller.openItem(1)
|
|
390
|
-
await nextFrame()
|
|
391
|
-
|
|
392
|
-
controller.handleKeydown({ key: "ArrowLeft", preventDefault: jest.fn() })
|
|
393
|
-
await nextFrame()
|
|
394
|
-
|
|
395
|
-
expect(controller.openIndexValue).toBe(0)
|
|
396
|
-
})
|
|
397
|
-
|
|
398
|
-
test("ArrowLeft wraps to last item", async () => {
|
|
399
|
-
controller.handleKeydown({ key: "ArrowLeft", preventDefault: jest.fn() })
|
|
400
|
-
await nextFrame()
|
|
401
|
-
|
|
402
|
-
expect(controller.openIndexValue).toBe(2)
|
|
403
|
-
})
|
|
404
|
-
|
|
405
|
-
test("Escape closes all menus", async () => {
|
|
406
|
-
controller.handleKeydown({ key: "Escape", preventDefault: jest.fn() })
|
|
407
|
-
await nextFrame()
|
|
408
|
-
|
|
409
|
-
expect(controller.isOpen).toBe(false)
|
|
410
|
-
expect(controller.openIndexValue).toBe(-1)
|
|
411
|
-
})
|
|
412
|
-
|
|
413
|
-
test("prevents default on navigation keys", () => {
|
|
414
|
-
const preventDefault = jest.fn()
|
|
415
|
-
controller.handleKeydown({ key: "ArrowRight", preventDefault })
|
|
416
|
-
expect(preventDefault).toHaveBeenCalled()
|
|
417
|
-
|
|
418
|
-
preventDefault.mockClear()
|
|
419
|
-
controller.handleKeydown({ key: "ArrowLeft", preventDefault })
|
|
420
|
-
expect(preventDefault).toHaveBeenCalled()
|
|
421
|
-
})
|
|
422
|
-
})
|
|
423
|
-
|
|
424
|
-
describe("click outside handling", () => {
|
|
425
|
-
const clickOutsideHTML = `
|
|
426
|
-
<nav data-controller="shadcn--navigation-menu"
|
|
427
|
-
data-shadcn--navigation-menu-open-index-value="-1">
|
|
428
|
-
<ul data-shadcn--navigation-menu-target="list">
|
|
429
|
-
<li data-shadcn--navigation-menu-target="item">
|
|
430
|
-
<button data-shadcn--navigation-menu-target="trigger" aria-expanded="false">Menu</button>
|
|
431
|
-
<div data-shadcn--navigation-menu-target="content" hidden>Content</div>
|
|
432
|
-
</li>
|
|
433
|
-
</ul>
|
|
434
|
-
</nav>
|
|
435
|
-
`
|
|
436
|
-
|
|
437
|
-
beforeEach(async () => {
|
|
438
|
-
const setup = await setupController(NavigationMenuController, clickOutsideHTML, 'shadcn--navigation-menu')
|
|
439
|
-
application = setup.application
|
|
440
|
-
element = setup.element
|
|
441
|
-
controller = setup.controller
|
|
442
|
-
})
|
|
443
|
-
|
|
444
|
-
test("closes on click outside", async () => {
|
|
445
|
-
controller.openItem(0)
|
|
446
|
-
await nextFrame()
|
|
447
|
-
|
|
448
|
-
const outsideElement = document.createElement("div")
|
|
449
|
-
document.body.appendChild(outsideElement)
|
|
450
|
-
|
|
451
|
-
controller.handleClickOutside({ target: outsideElement })
|
|
452
|
-
await nextFrame()
|
|
453
|
-
|
|
454
|
-
expect(controller.isOpen).toBe(false)
|
|
455
|
-
|
|
456
|
-
document.body.removeChild(outsideElement)
|
|
457
|
-
})
|
|
458
|
-
|
|
459
|
-
test("does not close on click inside", async () => {
|
|
460
|
-
controller.openItem(0)
|
|
461
|
-
await nextFrame()
|
|
462
|
-
|
|
463
|
-
controller.handleClickOutside({ target: element })
|
|
464
|
-
await nextFrame()
|
|
465
|
-
|
|
466
|
-
expect(controller.isOpen).toBe(true)
|
|
467
|
-
})
|
|
468
|
-
})
|
|
469
|
-
|
|
470
|
-
describe("timer management", () => {
|
|
471
|
-
const timerHTML = `
|
|
472
|
-
<nav data-controller="shadcn--navigation-menu"
|
|
473
|
-
data-shadcn--navigation-menu-open-index-value="-1"
|
|
474
|
-
data-shadcn--navigation-menu-delay-duration-value="50"
|
|
475
|
-
data-shadcn--navigation-menu-skip-delay-duration-value="50">
|
|
476
|
-
<ul data-shadcn--navigation-menu-target="list">
|
|
477
|
-
<li data-shadcn--navigation-menu-target="item">
|
|
478
|
-
<button data-shadcn--navigation-menu-target="trigger"
|
|
479
|
-
data-action="mouseenter->shadcn--navigation-menu#hoverOpen mouseleave->shadcn--navigation-menu#hoverClose"
|
|
480
|
-
aria-expanded="false">Menu</button>
|
|
481
|
-
<div data-shadcn--navigation-menu-target="content" hidden>Content</div>
|
|
482
|
-
</li>
|
|
483
|
-
</ul>
|
|
484
|
-
</nav>
|
|
485
|
-
`
|
|
486
|
-
|
|
487
|
-
beforeEach(async () => {
|
|
488
|
-
const setup = await setupController(NavigationMenuController, timerHTML, 'shadcn--navigation-menu')
|
|
489
|
-
application = setup.application
|
|
490
|
-
element = setup.element
|
|
491
|
-
controller = setup.controller
|
|
492
|
-
})
|
|
493
|
-
|
|
494
|
-
test("clearTimers clears open timer", () => {
|
|
495
|
-
controller.openTimer = setTimeout(() => {}, 1000)
|
|
496
|
-
controller.clearTimers()
|
|
497
|
-
|
|
498
|
-
expect(controller.openTimer).toBeNull()
|
|
499
|
-
})
|
|
500
|
-
|
|
501
|
-
test("clearTimers clears close timer", () => {
|
|
502
|
-
controller.closeTimer = setTimeout(() => {}, 1000)
|
|
503
|
-
controller.clearTimers()
|
|
504
|
-
|
|
505
|
-
expect(controller.closeTimer).toBeNull()
|
|
506
|
-
})
|
|
507
|
-
})
|
|
508
|
-
|
|
509
|
-
describe("disconnect cleanup", () => {
|
|
510
|
-
const disconnectHTML = `
|
|
511
|
-
<nav data-controller="shadcn--navigation-menu"
|
|
512
|
-
data-shadcn--navigation-menu-open-index-value="-1">
|
|
513
|
-
<ul data-shadcn--navigation-menu-target="list">
|
|
514
|
-
<li data-shadcn--navigation-menu-target="item">
|
|
515
|
-
<button data-shadcn--navigation-menu-target="trigger" aria-expanded="false">Menu</button>
|
|
516
|
-
<div data-shadcn--navigation-menu-target="content" hidden>Content</div>
|
|
517
|
-
</li>
|
|
518
|
-
</ul>
|
|
519
|
-
</nav>
|
|
520
|
-
`
|
|
521
|
-
|
|
522
|
-
beforeEach(async () => {
|
|
523
|
-
const setup = await setupController(NavigationMenuController, disconnectHTML, 'shadcn--navigation-menu')
|
|
524
|
-
application = setup.application
|
|
525
|
-
element = setup.element
|
|
526
|
-
controller = setup.controller
|
|
527
|
-
})
|
|
528
|
-
|
|
529
|
-
test("closes all on disconnect", async () => {
|
|
530
|
-
controller.openItem(0)
|
|
531
|
-
await nextFrame()
|
|
532
|
-
|
|
533
|
-
controller.disconnect()
|
|
534
|
-
await nextFrame()
|
|
535
|
-
|
|
536
|
-
expect(controller.openIndexValue).toBe(-1)
|
|
537
|
-
})
|
|
538
|
-
|
|
539
|
-
test("clears timers on disconnect", () => {
|
|
540
|
-
controller.openTimer = setTimeout(() => {}, 1000)
|
|
541
|
-
controller.closeTimer = setTimeout(() => {}, 1000)
|
|
542
|
-
|
|
543
|
-
controller.disconnect()
|
|
544
|
-
|
|
545
|
-
expect(controller.openTimer).toBeNull()
|
|
546
|
-
expect(controller.closeTimer).toBeNull()
|
|
547
|
-
})
|
|
548
|
-
})
|
|
549
|
-
|
|
550
|
-
describe("viewport functionality", () => {
|
|
551
|
-
const viewportHTML = `
|
|
552
|
-
<nav data-controller="shadcn--navigation-menu"
|
|
553
|
-
data-shadcn--navigation-menu-open-index-value="-1">
|
|
554
|
-
<ul data-shadcn--navigation-menu-target="list">
|
|
555
|
-
<li data-shadcn--navigation-menu-target="item">
|
|
556
|
-
<button data-shadcn--navigation-menu-target="trigger" aria-expanded="false">Menu</button>
|
|
557
|
-
<div data-shadcn--navigation-menu-target="content" hidden style="width: 200px; height: 100px;">
|
|
558
|
-
<p>Content here</p>
|
|
559
|
-
</div>
|
|
560
|
-
</li>
|
|
561
|
-
</ul>
|
|
562
|
-
<div data-shadcn--navigation-menu-target="viewport" hidden></div>
|
|
563
|
-
</nav>
|
|
564
|
-
`
|
|
565
|
-
|
|
566
|
-
beforeEach(async () => {
|
|
567
|
-
const setup = await setupController(NavigationMenuController, viewportHTML, 'shadcn--navigation-menu')
|
|
568
|
-
application = setup.application
|
|
569
|
-
element = setup.element
|
|
570
|
-
controller = setup.controller
|
|
571
|
-
})
|
|
572
|
-
|
|
573
|
-
test("has viewport target", () => {
|
|
574
|
-
expect(controller.hasViewportTarget).toBe(true)
|
|
575
|
-
})
|
|
576
|
-
|
|
577
|
-
test("shows viewport when item opened", async () => {
|
|
578
|
-
controller.openItem(0)
|
|
579
|
-
await nextFrame()
|
|
580
|
-
|
|
581
|
-
expect(controller.viewportTarget.hidden).toBe(false)
|
|
582
|
-
})
|
|
583
|
-
|
|
584
|
-
test("sets viewport data-state to open", async () => {
|
|
585
|
-
controller.openItem(0)
|
|
586
|
-
await nextFrame()
|
|
587
|
-
|
|
588
|
-
expect(controller.viewportTarget.dataset.state).toBe("open")
|
|
589
|
-
})
|
|
590
|
-
|
|
591
|
-
test("copies content to viewport", async () => {
|
|
592
|
-
controller.openItem(0)
|
|
593
|
-
await nextFrame()
|
|
594
|
-
|
|
595
|
-
expect(controller.viewportTarget.innerHTML).toContain("Content here")
|
|
596
|
-
})
|
|
597
|
-
})
|
|
598
|
-
})
|