shadcn-rails 0.2.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 +66 -2
- data/README.md +21 -8
- data/__mocks__/@floating-ui/dom.js +67 -0
- data/app/assets/javascripts/shadcn/controllers/combobox_controller.js +23 -2
- data/app/assets/javascripts/shadcn/controllers/context_menu_controller.js +4 -31
- data/app/assets/javascripts/shadcn/controllers/dropdown_controller.js +32 -41
- data/app/assets/javascripts/shadcn/controllers/hover_card_controller.js +29 -55
- data/app/assets/javascripts/shadcn/controllers/popover_controller.js +29 -54
- data/app/assets/javascripts/shadcn/controllers/select_controller.js +26 -8
- data/app/assets/javascripts/shadcn/controllers/tooltip_controller.js +28 -59
- data/app/assets/javascripts/shadcn/index.js +7 -1
- data/app/assets/javascripts/shadcn/utils/floating.js +179 -0
- data/app/assets/stylesheets/shadcn/base.css +32 -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/context_menu_component.html.erb +11 -0
- data/app/components/shadcn/context_menu_component.rb +6 -26
- 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_component.html.erb +14 -0
- data/app/components/shadcn/dropdown_menu_component.rb +9 -29
- 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/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/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 +6 -80
- 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 +47 -45
- data/.dockerignore +0 -40
- data/CLAUDE.md +0 -612
- data/PROGRESS.md +0 -495
- data/Rakefile +0 -95
- 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 -971
- data/__tests__/controllers/context_menu_controller.test.js +0 -905
- 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 -737
- data/__tests__/controllers/navigation_menu_controller.test.js +0 -599
- data/__tests__/controllers/popover_controller.test.js +0 -982
- 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 -678
- 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/bump +0 -321
- data/bin/console +0 -11
- data/bin/release +0 -205
- data/bin/setup +0 -8
- data/bin/test +0 -75
- 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 -7438
- data/package.json +0 -71
- data/rollup.config.js +0 -29
|
@@ -1,878 +0,0 @@
|
|
|
1
|
-
import { Application } from "@hotwired/stimulus"
|
|
2
|
-
import DialogController from '../../app/assets/javascripts/shadcn/controllers/dialog_controller.js'
|
|
3
|
-
import {
|
|
4
|
-
setupController,
|
|
5
|
-
cleanupController,
|
|
6
|
-
click,
|
|
7
|
-
wait,
|
|
8
|
-
nextFrame,
|
|
9
|
-
keydown,
|
|
10
|
-
waitForPortal,
|
|
11
|
-
getFocusableElements,
|
|
12
|
-
waitForEvent
|
|
13
|
-
} from '../helpers/stimulus-test-helper.js'
|
|
14
|
-
|
|
15
|
-
describe('DialogController', () => {
|
|
16
|
-
let application
|
|
17
|
-
let element
|
|
18
|
-
let controller
|
|
19
|
-
|
|
20
|
-
const html = `
|
|
21
|
-
<div data-controller="shadcn--dialog" data-shadcn--dialog-open-value="false" data-shadcn--dialog-modal-value="true">
|
|
22
|
-
<button data-shadcn--dialog-target="trigger" data-action="click->shadcn--dialog#toggle">Open Dialog</button>
|
|
23
|
-
<template data-shadcn--dialog-target="template">
|
|
24
|
-
<div data-shadcn--dialog-target="overlay" class="fixed inset-0 bg-black/50" hidden></div>
|
|
25
|
-
<div data-shadcn--dialog-target="content" role="dialog" aria-modal="true" hidden>
|
|
26
|
-
<h2>Dialog Title</h2>
|
|
27
|
-
<p>Dialog content goes here.</p>
|
|
28
|
-
<button data-action="click->shadcn--dialog#close">Close</button>
|
|
29
|
-
<input type="text" placeholder="First input">
|
|
30
|
-
<input type="text" placeholder="Second input">
|
|
31
|
-
<button>Submit</button>
|
|
32
|
-
</div>
|
|
33
|
-
</template>
|
|
34
|
-
</div>
|
|
35
|
-
`
|
|
36
|
-
|
|
37
|
-
beforeEach(async () => {
|
|
38
|
-
// Reset body overflow before each test
|
|
39
|
-
document.body.style.overflow = ''
|
|
40
|
-
|
|
41
|
-
const setup = await setupController(DialogController, html, 'shadcn--dialog')
|
|
42
|
-
application = setup.application
|
|
43
|
-
element = setup.element
|
|
44
|
-
controller = setup.controller
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
afterEach(() => {
|
|
48
|
-
cleanupController(application)
|
|
49
|
-
// Clean up any remaining portals
|
|
50
|
-
document.querySelectorAll('.shadcn-dialog-portal').forEach(portal => portal.remove())
|
|
51
|
-
// Reset body overflow
|
|
52
|
-
document.body.style.overflow = ''
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
describe('Value Initialization', () => {
|
|
56
|
-
test('initializes with default open value as false', () => {
|
|
57
|
-
expect(controller.openValue).toBe(false)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
test('initializes with default modal value as true', () => {
|
|
61
|
-
expect(controller.modalValue).toBe(true)
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
test('respects open value during initialization', async () => {
|
|
65
|
-
const openHtml = html.replace('data-shadcn--dialog-open-value="false"', 'data-shadcn--dialog-open-value="true"')
|
|
66
|
-
cleanupController(application)
|
|
67
|
-
|
|
68
|
-
// When initialized with open=true, the connect() method calls open()
|
|
69
|
-
// However, there's a check at the start of open() that returns if openValue is already true
|
|
70
|
-
// This means the dialog won't actually open during initialization with this implementation
|
|
71
|
-
// The test verifies the value is set correctly, even if the dialog doesn't render
|
|
72
|
-
const setup = await setupController(DialogController, openHtml, 'shadcn--dialog')
|
|
73
|
-
application = setup.application
|
|
74
|
-
element = setup.element
|
|
75
|
-
controller = setup.controller
|
|
76
|
-
|
|
77
|
-
expect(controller.openValue).toBe(true)
|
|
78
|
-
|
|
79
|
-
// Due to the guard in open(), initialization with open=true doesn't create the portal
|
|
80
|
-
// To actually open it, we need to call toggle() or close then open
|
|
81
|
-
controller.toggle() // This closes it
|
|
82
|
-
expect(controller.openValue).toBe(false)
|
|
83
|
-
|
|
84
|
-
controller.toggle() // This opens it
|
|
85
|
-
await nextFrame()
|
|
86
|
-
expect(controller.openValue).toBe(true)
|
|
87
|
-
|
|
88
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
89
|
-
expect(portal).toBeTruthy()
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
test('can be initialized with modal value as false', async () => {
|
|
93
|
-
const nonModalHtml = html.replace('data-shadcn--dialog-modal-value="true"', 'data-shadcn--dialog-modal-value="false"')
|
|
94
|
-
cleanupController(application)
|
|
95
|
-
|
|
96
|
-
const setup = await setupController(DialogController, nonModalHtml, 'shadcn--dialog')
|
|
97
|
-
application = setup.application
|
|
98
|
-
controller = setup.controller
|
|
99
|
-
|
|
100
|
-
expect(controller.modalValue).toBe(false)
|
|
101
|
-
})
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
describe('Portal Rendering', () => {
|
|
105
|
-
test('creates portal in body when dialog opens', async () => {
|
|
106
|
-
const trigger = element.querySelector('[data-shadcn--dialog-target="trigger"]')
|
|
107
|
-
click(trigger)
|
|
108
|
-
await nextFrame()
|
|
109
|
-
|
|
110
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
111
|
-
expect(portal).toBeTruthy()
|
|
112
|
-
expect(portal.parentElement).toBe(document.body)
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
test('portal contains overlay element', async () => {
|
|
116
|
-
const trigger = element.querySelector('[data-shadcn--dialog-target="trigger"]')
|
|
117
|
-
click(trigger)
|
|
118
|
-
await nextFrame()
|
|
119
|
-
|
|
120
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
121
|
-
const overlay = portal.querySelector('[data-shadcn--dialog-target="overlay"]')
|
|
122
|
-
expect(overlay).toBeTruthy()
|
|
123
|
-
expect(overlay.classList.contains('fixed')).toBe(true)
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
test('portal contains content element', async () => {
|
|
127
|
-
const trigger = element.querySelector('[data-shadcn--dialog-target="trigger"]')
|
|
128
|
-
click(trigger)
|
|
129
|
-
await nextFrame()
|
|
130
|
-
|
|
131
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
132
|
-
const content = portal.querySelector('[data-shadcn--dialog-target="content"]')
|
|
133
|
-
expect(content).toBeTruthy()
|
|
134
|
-
expect(content.getAttribute('role')).toBe('dialog')
|
|
135
|
-
expect(content.getAttribute('aria-modal')).toBe('true')
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
test('removes hidden attribute from overlay when opened', async () => {
|
|
139
|
-
const trigger = element.querySelector('[data-shadcn--dialog-target="trigger"]')
|
|
140
|
-
click(trigger)
|
|
141
|
-
await nextFrame()
|
|
142
|
-
|
|
143
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
144
|
-
const overlay = portal.querySelector('[data-shadcn--dialog-target="overlay"]')
|
|
145
|
-
expect(overlay.hasAttribute('hidden')).toBe(false)
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
test('removes hidden attribute from content when opened', async () => {
|
|
149
|
-
const trigger = element.querySelector('[data-shadcn--dialog-target="trigger"]')
|
|
150
|
-
click(trigger)
|
|
151
|
-
await nextFrame()
|
|
152
|
-
|
|
153
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
154
|
-
const content = portal.querySelector('[data-shadcn--dialog-target="content"]')
|
|
155
|
-
expect(content.hasAttribute('hidden')).toBe(false)
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
test('sets data-state="open" on overlay', async () => {
|
|
159
|
-
const trigger = element.querySelector('[data-shadcn--dialog-target="trigger"]')
|
|
160
|
-
click(trigger)
|
|
161
|
-
await nextFrame()
|
|
162
|
-
|
|
163
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
164
|
-
const overlay = portal.querySelector('[data-shadcn--dialog-target="overlay"]')
|
|
165
|
-
expect(overlay.dataset.state).toBe('open')
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
test('sets data-state="open" on content', async () => {
|
|
169
|
-
const trigger = element.querySelector('[data-shadcn--dialog-target="trigger"]')
|
|
170
|
-
click(trigger)
|
|
171
|
-
await nextFrame()
|
|
172
|
-
|
|
173
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
174
|
-
const content = portal.querySelector('[data-shadcn--dialog-target="content"]')
|
|
175
|
-
expect(content.dataset.state).toBe('open')
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
test('removes portal from DOM when dialog closes', async () => {
|
|
179
|
-
const trigger = element.querySelector('[data-shadcn--dialog-target="trigger"]')
|
|
180
|
-
click(trigger)
|
|
181
|
-
await nextFrame()
|
|
182
|
-
|
|
183
|
-
let portal = await waitForPortal('.shadcn-dialog-portal')
|
|
184
|
-
expect(portal).toBeTruthy()
|
|
185
|
-
|
|
186
|
-
controller.close()
|
|
187
|
-
await wait(250) // Wait for animation timeout (200ms + buffer)
|
|
188
|
-
|
|
189
|
-
portal = document.querySelector('.shadcn-dialog-portal')
|
|
190
|
-
expect(portal).toBeNull()
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
test('sets data-state="closed" when closing', async () => {
|
|
194
|
-
const trigger = element.querySelector('[data-shadcn--dialog-target="trigger"]')
|
|
195
|
-
click(trigger)
|
|
196
|
-
await nextFrame()
|
|
197
|
-
|
|
198
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
199
|
-
const overlay = portal.querySelector('[data-shadcn--dialog-target="overlay"]')
|
|
200
|
-
const content = portal.querySelector('[data-shadcn--dialog-target="content"]')
|
|
201
|
-
|
|
202
|
-
controller.close()
|
|
203
|
-
|
|
204
|
-
expect(overlay.dataset.state).toBe('closed')
|
|
205
|
-
expect(content.dataset.state).toBe('closed')
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
test('only creates portal once on multiple opens', async () => {
|
|
209
|
-
const trigger = element.querySelector('[data-shadcn--dialog-target="trigger"]')
|
|
210
|
-
|
|
211
|
-
click(trigger)
|
|
212
|
-
await nextFrame()
|
|
213
|
-
let portals = document.querySelectorAll('.shadcn-dialog-portal')
|
|
214
|
-
expect(portals.length).toBe(1)
|
|
215
|
-
|
|
216
|
-
controller.close()
|
|
217
|
-
await wait(250)
|
|
218
|
-
|
|
219
|
-
click(trigger)
|
|
220
|
-
await nextFrame()
|
|
221
|
-
portals = document.querySelectorAll('.shadcn-dialog-portal')
|
|
222
|
-
expect(portals.length).toBe(1)
|
|
223
|
-
})
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
describe('Open/Close/Toggle', () => {
|
|
227
|
-
test('opens dialog when toggle is called on closed dialog', async () => {
|
|
228
|
-
expect(controller.openValue).toBe(false)
|
|
229
|
-
|
|
230
|
-
controller.toggle()
|
|
231
|
-
await nextFrame()
|
|
232
|
-
|
|
233
|
-
expect(controller.openValue).toBe(true)
|
|
234
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
235
|
-
expect(portal).toBeTruthy()
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
test('closes dialog when toggle is called on open dialog', async () => {
|
|
239
|
-
controller.open()
|
|
240
|
-
await nextFrame()
|
|
241
|
-
expect(controller.openValue).toBe(true)
|
|
242
|
-
|
|
243
|
-
controller.toggle()
|
|
244
|
-
expect(controller.openValue).toBe(false)
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
test('open method sets openValue to true', async () => {
|
|
248
|
-
controller.open()
|
|
249
|
-
await nextFrame()
|
|
250
|
-
expect(controller.openValue).toBe(true)
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
test('close method sets openValue to false', async () => {
|
|
254
|
-
controller.open()
|
|
255
|
-
await nextFrame()
|
|
256
|
-
expect(controller.openValue).toBe(true)
|
|
257
|
-
|
|
258
|
-
controller.close()
|
|
259
|
-
expect(controller.openValue).toBe(false)
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
test('trigger button click toggles dialog', async () => {
|
|
263
|
-
const trigger = element.querySelector('[data-shadcn--dialog-target="trigger"]')
|
|
264
|
-
|
|
265
|
-
click(trigger)
|
|
266
|
-
await nextFrame()
|
|
267
|
-
expect(controller.openValue).toBe(true)
|
|
268
|
-
|
|
269
|
-
click(trigger)
|
|
270
|
-
expect(controller.openValue).toBe(false)
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
test('does not open again if already open', async () => {
|
|
274
|
-
controller.open()
|
|
275
|
-
await nextFrame()
|
|
276
|
-
const firstPortal = controller.portal
|
|
277
|
-
|
|
278
|
-
controller.open()
|
|
279
|
-
await nextFrame()
|
|
280
|
-
|
|
281
|
-
expect(controller.portal).toBe(firstPortal)
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
test('does not close again if already closed', () => {
|
|
285
|
-
expect(controller.openValue).toBe(false)
|
|
286
|
-
controller.close()
|
|
287
|
-
expect(controller.openValue).toBe(false)
|
|
288
|
-
})
|
|
289
|
-
})
|
|
290
|
-
|
|
291
|
-
describe('Focus Management', () => {
|
|
292
|
-
test('focuses first focusable element when dialog opens', async () => {
|
|
293
|
-
const trigger = element.querySelector('[data-shadcn--dialog-target="trigger"]')
|
|
294
|
-
click(trigger)
|
|
295
|
-
await nextFrame()
|
|
296
|
-
|
|
297
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
298
|
-
const focusableElements = getFocusableElements(portal)
|
|
299
|
-
|
|
300
|
-
expect(document.activeElement).toBe(focusableElements[0])
|
|
301
|
-
})
|
|
302
|
-
|
|
303
|
-
test('stores previous active element before opening', async () => {
|
|
304
|
-
const trigger = element.querySelector('[data-shadcn--dialog-target="trigger"]')
|
|
305
|
-
trigger.focus()
|
|
306
|
-
expect(document.activeElement).toBe(trigger)
|
|
307
|
-
|
|
308
|
-
click(trigger)
|
|
309
|
-
await nextFrame()
|
|
310
|
-
|
|
311
|
-
expect(controller.previousActiveElement).toBe(trigger)
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
test('returns focus to previous element when dialog closes', async () => {
|
|
315
|
-
const trigger = element.querySelector('[data-shadcn--dialog-target="trigger"]')
|
|
316
|
-
trigger.focus()
|
|
317
|
-
|
|
318
|
-
click(trigger)
|
|
319
|
-
await nextFrame()
|
|
320
|
-
|
|
321
|
-
controller.close()
|
|
322
|
-
await nextFrame()
|
|
323
|
-
|
|
324
|
-
expect(document.activeElement).toBe(trigger)
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
test('focuses content element if no focusable elements exist', async () => {
|
|
328
|
-
const noFocusHtml = `
|
|
329
|
-
<div data-controller="shadcn--dialog">
|
|
330
|
-
<button data-shadcn--dialog-target="trigger" data-action="click->shadcn--dialog#toggle">Open</button>
|
|
331
|
-
<template data-shadcn--dialog-target="template">
|
|
332
|
-
<div data-shadcn--dialog-target="overlay" class="fixed inset-0 bg-black/50"></div>
|
|
333
|
-
<div data-shadcn--dialog-target="content" role="dialog" aria-modal="true" tabindex="-1">
|
|
334
|
-
<p>No focusable elements</p>
|
|
335
|
-
</div>
|
|
336
|
-
</template>
|
|
337
|
-
</div>
|
|
338
|
-
`
|
|
339
|
-
|
|
340
|
-
cleanupController(application)
|
|
341
|
-
const setup = await setupController(DialogController, noFocusHtml, 'shadcn--dialog')
|
|
342
|
-
application = setup.application
|
|
343
|
-
element = setup.element
|
|
344
|
-
controller = setup.controller
|
|
345
|
-
|
|
346
|
-
const trigger = element.querySelector('[data-shadcn--dialog-target="trigger"]')
|
|
347
|
-
click(trigger)
|
|
348
|
-
await nextFrame()
|
|
349
|
-
|
|
350
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
351
|
-
const content = portal.querySelector('[data-shadcn--dialog-target="content"]')
|
|
352
|
-
|
|
353
|
-
// Content should be focused
|
|
354
|
-
expect(document.activeElement).toBe(content)
|
|
355
|
-
})
|
|
356
|
-
})
|
|
357
|
-
|
|
358
|
-
describe('Focus Trap', () => {
|
|
359
|
-
test('traps Tab key to cycle through focusable elements', async () => {
|
|
360
|
-
controller.open()
|
|
361
|
-
await nextFrame()
|
|
362
|
-
|
|
363
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
364
|
-
const focusableElements = Array.from(getFocusableElements(portal))
|
|
365
|
-
|
|
366
|
-
// First element should be focused initially
|
|
367
|
-
expect(document.activeElement).toBe(focusableElements[0])
|
|
368
|
-
|
|
369
|
-
// Tab to second element
|
|
370
|
-
keydown(document, 'Tab')
|
|
371
|
-
focusableElements[1].focus()
|
|
372
|
-
expect(document.activeElement).toBe(focusableElements[1])
|
|
373
|
-
|
|
374
|
-
// Tab to third element
|
|
375
|
-
keydown(document, 'Tab')
|
|
376
|
-
focusableElements[2].focus()
|
|
377
|
-
expect(document.activeElement).toBe(focusableElements[2])
|
|
378
|
-
})
|
|
379
|
-
|
|
380
|
-
test('cycles focus to first element when Tab on last element', async () => {
|
|
381
|
-
controller.open()
|
|
382
|
-
await nextFrame()
|
|
383
|
-
|
|
384
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
385
|
-
const focusableElements = Array.from(getFocusableElements(portal))
|
|
386
|
-
const lastElement = focusableElements[focusableElements.length - 1]
|
|
387
|
-
const firstElement = focusableElements[0]
|
|
388
|
-
|
|
389
|
-
// Focus last element
|
|
390
|
-
lastElement.focus()
|
|
391
|
-
expect(document.activeElement).toBe(lastElement)
|
|
392
|
-
|
|
393
|
-
// Tab should cycle to first
|
|
394
|
-
const event = new KeyboardEvent('keydown', {
|
|
395
|
-
key: 'Tab',
|
|
396
|
-
bubbles: true,
|
|
397
|
-
cancelable: true
|
|
398
|
-
})
|
|
399
|
-
document.dispatchEvent(event)
|
|
400
|
-
|
|
401
|
-
if (event.defaultPrevented) {
|
|
402
|
-
firstElement.focus()
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
expect(document.activeElement).toBe(firstElement)
|
|
406
|
-
})
|
|
407
|
-
|
|
408
|
-
test('cycles focus to last element when Shift+Tab on first element', async () => {
|
|
409
|
-
controller.open()
|
|
410
|
-
await nextFrame()
|
|
411
|
-
|
|
412
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
413
|
-
const focusableElements = Array.from(getFocusableElements(portal))
|
|
414
|
-
const firstElement = focusableElements[0]
|
|
415
|
-
const lastElement = focusableElements[focusableElements.length - 1]
|
|
416
|
-
|
|
417
|
-
// First element should be focused initially
|
|
418
|
-
expect(document.activeElement).toBe(firstElement)
|
|
419
|
-
|
|
420
|
-
// Shift+Tab should cycle to last
|
|
421
|
-
const event = new KeyboardEvent('keydown', {
|
|
422
|
-
key: 'Tab',
|
|
423
|
-
shiftKey: true,
|
|
424
|
-
bubbles: true,
|
|
425
|
-
cancelable: true
|
|
426
|
-
})
|
|
427
|
-
document.dispatchEvent(event)
|
|
428
|
-
|
|
429
|
-
if (event.defaultPrevented) {
|
|
430
|
-
lastElement.focus()
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
expect(document.activeElement).toBe(lastElement)
|
|
434
|
-
})
|
|
435
|
-
|
|
436
|
-
test('does not trap focus when modal is false', async () => {
|
|
437
|
-
const nonModalHtml = html.replace('data-shadcn--dialog-modal-value="true"', 'data-shadcn--dialog-modal-value="false"')
|
|
438
|
-
cleanupController(application)
|
|
439
|
-
|
|
440
|
-
const setup = await setupController(DialogController, nonModalHtml, 'shadcn--dialog')
|
|
441
|
-
application = setup.application
|
|
442
|
-
controller = setup.controller
|
|
443
|
-
|
|
444
|
-
controller.open()
|
|
445
|
-
await nextFrame()
|
|
446
|
-
|
|
447
|
-
// Tab event should not be trapped
|
|
448
|
-
const event = new KeyboardEvent('keydown', {
|
|
449
|
-
key: 'Tab',
|
|
450
|
-
bubbles: true,
|
|
451
|
-
cancelable: true
|
|
452
|
-
})
|
|
453
|
-
document.dispatchEvent(event)
|
|
454
|
-
|
|
455
|
-
expect(event.defaultPrevented).toBe(false)
|
|
456
|
-
})
|
|
457
|
-
})
|
|
458
|
-
|
|
459
|
-
describe('Keyboard Navigation', () => {
|
|
460
|
-
test('closes dialog when Escape key is pressed', async () => {
|
|
461
|
-
controller.open()
|
|
462
|
-
await nextFrame()
|
|
463
|
-
expect(controller.openValue).toBe(true)
|
|
464
|
-
|
|
465
|
-
keydown(document, 'Escape')
|
|
466
|
-
expect(controller.openValue).toBe(false)
|
|
467
|
-
})
|
|
468
|
-
|
|
469
|
-
test('does not close when other keys are pressed', async () => {
|
|
470
|
-
controller.open()
|
|
471
|
-
await nextFrame()
|
|
472
|
-
expect(controller.openValue).toBe(true)
|
|
473
|
-
|
|
474
|
-
keydown(document, 'Enter')
|
|
475
|
-
expect(controller.openValue).toBe(true)
|
|
476
|
-
|
|
477
|
-
keydown(document, 'ArrowDown')
|
|
478
|
-
expect(controller.openValue).toBe(true)
|
|
479
|
-
|
|
480
|
-
keydown(document, 'Space')
|
|
481
|
-
expect(controller.openValue).toBe(true)
|
|
482
|
-
})
|
|
483
|
-
|
|
484
|
-
test('only responds to Escape when dialog is open', () => {
|
|
485
|
-
expect(controller.openValue).toBe(false)
|
|
486
|
-
|
|
487
|
-
keydown(document, 'Escape')
|
|
488
|
-
|
|
489
|
-
// Should not throw error or cause issues
|
|
490
|
-
expect(controller.openValue).toBe(false)
|
|
491
|
-
})
|
|
492
|
-
})
|
|
493
|
-
|
|
494
|
-
describe('Overlay Click', () => {
|
|
495
|
-
test('closes dialog when overlay is clicked', async () => {
|
|
496
|
-
controller.open()
|
|
497
|
-
await nextFrame()
|
|
498
|
-
|
|
499
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
500
|
-
const overlay = portal.querySelector('[data-shadcn--dialog-target="overlay"]')
|
|
501
|
-
|
|
502
|
-
expect(controller.openValue).toBe(true)
|
|
503
|
-
click(overlay)
|
|
504
|
-
expect(controller.openValue).toBe(false)
|
|
505
|
-
})
|
|
506
|
-
|
|
507
|
-
test('does not close when content is clicked', async () => {
|
|
508
|
-
controller.open()
|
|
509
|
-
await nextFrame()
|
|
510
|
-
|
|
511
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
512
|
-
const content = portal.querySelector('[data-shadcn--dialog-target="content"]')
|
|
513
|
-
|
|
514
|
-
expect(controller.openValue).toBe(true)
|
|
515
|
-
click(content)
|
|
516
|
-
expect(controller.openValue).toBe(true)
|
|
517
|
-
})
|
|
518
|
-
})
|
|
519
|
-
|
|
520
|
-
describe('Body Scroll Lock', () => {
|
|
521
|
-
test('sets body overflow to hidden when modal dialog opens', async () => {
|
|
522
|
-
expect(document.body.style.overflow).toBe('')
|
|
523
|
-
|
|
524
|
-
controller.open()
|
|
525
|
-
await nextFrame()
|
|
526
|
-
|
|
527
|
-
expect(document.body.style.overflow).toBe('hidden')
|
|
528
|
-
})
|
|
529
|
-
|
|
530
|
-
test('restores body overflow when modal dialog closes', async () => {
|
|
531
|
-
controller.open()
|
|
532
|
-
await nextFrame()
|
|
533
|
-
expect(document.body.style.overflow).toBe('hidden')
|
|
534
|
-
|
|
535
|
-
controller.close()
|
|
536
|
-
expect(document.body.style.overflow).toBe('')
|
|
537
|
-
})
|
|
538
|
-
|
|
539
|
-
test('does not set body overflow when modal is false', async () => {
|
|
540
|
-
const nonModalHtml = html.replace('data-shadcn--dialog-modal-value="true"', 'data-shadcn--dialog-modal-value="false"')
|
|
541
|
-
cleanupController(application)
|
|
542
|
-
|
|
543
|
-
const setup = await setupController(DialogController, nonModalHtml, 'shadcn--dialog')
|
|
544
|
-
application = setup.application
|
|
545
|
-
controller = setup.controller
|
|
546
|
-
|
|
547
|
-
controller.open()
|
|
548
|
-
await nextFrame()
|
|
549
|
-
|
|
550
|
-
expect(document.body.style.overflow).toBe('')
|
|
551
|
-
})
|
|
552
|
-
|
|
553
|
-
test('restores body overflow even if closed early', async () => {
|
|
554
|
-
controller.open()
|
|
555
|
-
await nextFrame()
|
|
556
|
-
expect(document.body.style.overflow).toBe('hidden')
|
|
557
|
-
|
|
558
|
-
// Close immediately without waiting
|
|
559
|
-
controller.close()
|
|
560
|
-
expect(document.body.style.overflow).toBe('')
|
|
561
|
-
})
|
|
562
|
-
})
|
|
563
|
-
|
|
564
|
-
describe('ARIA Attributes', () => {
|
|
565
|
-
test('content has role="dialog"', async () => {
|
|
566
|
-
controller.open()
|
|
567
|
-
await nextFrame()
|
|
568
|
-
|
|
569
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
570
|
-
const content = portal.querySelector('[data-shadcn--dialog-target="content"]')
|
|
571
|
-
|
|
572
|
-
expect(content.getAttribute('role')).toBe('dialog')
|
|
573
|
-
})
|
|
574
|
-
|
|
575
|
-
test('content has aria-modal="true"', async () => {
|
|
576
|
-
controller.open()
|
|
577
|
-
await nextFrame()
|
|
578
|
-
|
|
579
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
580
|
-
const content = portal.querySelector('[data-shadcn--dialog-target="content"]')
|
|
581
|
-
|
|
582
|
-
expect(content.getAttribute('aria-modal')).toBe('true')
|
|
583
|
-
})
|
|
584
|
-
})
|
|
585
|
-
|
|
586
|
-
describe('Event Dispatching', () => {
|
|
587
|
-
test('dispatches opened event when dialog opens', async () => {
|
|
588
|
-
const eventPromise = waitForEvent(element, 'shadcn--dialog:opened')
|
|
589
|
-
|
|
590
|
-
controller.open()
|
|
591
|
-
await nextFrame()
|
|
592
|
-
|
|
593
|
-
const event = await eventPromise
|
|
594
|
-
expect(event).toBeTruthy()
|
|
595
|
-
expect(event.type).toBe('shadcn--dialog:opened')
|
|
596
|
-
})
|
|
597
|
-
|
|
598
|
-
test('dispatches closed event when dialog closes', async () => {
|
|
599
|
-
controller.open()
|
|
600
|
-
await nextFrame()
|
|
601
|
-
|
|
602
|
-
const eventPromise = waitForEvent(element, 'shadcn--dialog:closed')
|
|
603
|
-
controller.close()
|
|
604
|
-
|
|
605
|
-
const event = await eventPromise
|
|
606
|
-
expect(event).toBeTruthy()
|
|
607
|
-
expect(event.type).toBe('shadcn--dialog:closed')
|
|
608
|
-
})
|
|
609
|
-
|
|
610
|
-
test('does not dispatch opened event if already open', async () => {
|
|
611
|
-
controller.open()
|
|
612
|
-
await nextFrame()
|
|
613
|
-
|
|
614
|
-
let eventCount = 0
|
|
615
|
-
element.addEventListener('shadcn--dialog:opened', () => eventCount++)
|
|
616
|
-
|
|
617
|
-
controller.open()
|
|
618
|
-
await nextFrame()
|
|
619
|
-
|
|
620
|
-
expect(eventCount).toBe(0)
|
|
621
|
-
})
|
|
622
|
-
|
|
623
|
-
test('does not dispatch closed event if already closed', () => {
|
|
624
|
-
let eventCount = 0
|
|
625
|
-
element.addEventListener('shadcn--dialog:closed', () => eventCount++)
|
|
626
|
-
|
|
627
|
-
controller.close()
|
|
628
|
-
|
|
629
|
-
expect(eventCount).toBe(0)
|
|
630
|
-
})
|
|
631
|
-
})
|
|
632
|
-
|
|
633
|
-
describe('Close Actions', () => {
|
|
634
|
-
test('closes dialog when close button is clicked', async () => {
|
|
635
|
-
controller.open()
|
|
636
|
-
await nextFrame()
|
|
637
|
-
|
|
638
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
639
|
-
const closeButton = portal.querySelector('[data-action*="shadcn--dialog#close"]')
|
|
640
|
-
|
|
641
|
-
expect(controller.openValue).toBe(true)
|
|
642
|
-
click(closeButton)
|
|
643
|
-
expect(controller.openValue).toBe(false)
|
|
644
|
-
})
|
|
645
|
-
|
|
646
|
-
test('wires up close actions on portal elements', async () => {
|
|
647
|
-
const closeHtml = `
|
|
648
|
-
<div data-controller="shadcn--dialog">
|
|
649
|
-
<button data-shadcn--dialog-target="trigger" data-action="click->shadcn--dialog#toggle">Open</button>
|
|
650
|
-
<template data-shadcn--dialog-target="template">
|
|
651
|
-
<div data-shadcn--dialog-target="overlay"></div>
|
|
652
|
-
<div data-shadcn--dialog-target="content" role="dialog">
|
|
653
|
-
<button data-action="click->shadcn--dialog#close">Close 1</button>
|
|
654
|
-
<button data-action="click->shadcn--dialog#close">Close 2</button>
|
|
655
|
-
</div>
|
|
656
|
-
</template>
|
|
657
|
-
</div>
|
|
658
|
-
`
|
|
659
|
-
|
|
660
|
-
cleanupController(application)
|
|
661
|
-
const setup = await setupController(DialogController, closeHtml, 'shadcn--dialog')
|
|
662
|
-
application = setup.application
|
|
663
|
-
controller = setup.controller
|
|
664
|
-
|
|
665
|
-
controller.open()
|
|
666
|
-
await nextFrame()
|
|
667
|
-
|
|
668
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
669
|
-
const closeButtons = portal.querySelectorAll('[data-action*="shadcn--dialog#close"]')
|
|
670
|
-
|
|
671
|
-
expect(closeButtons.length).toBe(2)
|
|
672
|
-
|
|
673
|
-
click(closeButtons[1])
|
|
674
|
-
expect(controller.openValue).toBe(false)
|
|
675
|
-
})
|
|
676
|
-
})
|
|
677
|
-
|
|
678
|
-
describe('Cleanup and Disconnect', () => {
|
|
679
|
-
test('removes portal when controller disconnects', async () => {
|
|
680
|
-
controller.open()
|
|
681
|
-
await nextFrame()
|
|
682
|
-
|
|
683
|
-
let portal = await waitForPortal('.shadcn-dialog-portal')
|
|
684
|
-
expect(portal).toBeTruthy()
|
|
685
|
-
|
|
686
|
-
controller.disconnect()
|
|
687
|
-
|
|
688
|
-
portal = document.querySelector('.shadcn-dialog-portal')
|
|
689
|
-
expect(portal).toBeNull()
|
|
690
|
-
})
|
|
691
|
-
|
|
692
|
-
test('restores body overflow when controller disconnects', async () => {
|
|
693
|
-
controller.open()
|
|
694
|
-
await nextFrame()
|
|
695
|
-
expect(document.body.style.overflow).toBe('hidden')
|
|
696
|
-
|
|
697
|
-
controller.disconnect()
|
|
698
|
-
expect(document.body.style.overflow).toBe('')
|
|
699
|
-
})
|
|
700
|
-
|
|
701
|
-
test('removes event listeners when controller disconnects', async () => {
|
|
702
|
-
controller.open()
|
|
703
|
-
await nextFrame()
|
|
704
|
-
|
|
705
|
-
controller.disconnect()
|
|
706
|
-
|
|
707
|
-
// Try to trigger events - should not cause errors
|
|
708
|
-
keydown(document, 'Escape')
|
|
709
|
-
keydown(document, 'Tab')
|
|
710
|
-
|
|
711
|
-
// Should not throw errors
|
|
712
|
-
expect(true).toBe(true)
|
|
713
|
-
})
|
|
714
|
-
|
|
715
|
-
test('closes dialog and cleans up when disconnecting', async () => {
|
|
716
|
-
controller.open()
|
|
717
|
-
await nextFrame()
|
|
718
|
-
expect(controller.openValue).toBe(true)
|
|
719
|
-
|
|
720
|
-
controller.disconnect()
|
|
721
|
-
|
|
722
|
-
expect(document.body.style.overflow).toBe('')
|
|
723
|
-
const portal = document.querySelector('.shadcn-dialog-portal')
|
|
724
|
-
expect(portal).toBeNull()
|
|
725
|
-
})
|
|
726
|
-
})
|
|
727
|
-
|
|
728
|
-
describe('openValueChanged Callback', () => {
|
|
729
|
-
test('triggers when open() is called', async () => {
|
|
730
|
-
expect(controller.openValue).toBe(false)
|
|
731
|
-
|
|
732
|
-
// open() sets openValue to true, which triggers openValueChanged
|
|
733
|
-
controller.open()
|
|
734
|
-
await nextFrame()
|
|
735
|
-
|
|
736
|
-
expect(controller.openValue).toBe(true)
|
|
737
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
738
|
-
expect(portal).toBeTruthy()
|
|
739
|
-
})
|
|
740
|
-
|
|
741
|
-
test('triggers when close() is called', async () => {
|
|
742
|
-
controller.open()
|
|
743
|
-
await nextFrame()
|
|
744
|
-
expect(controller.openValue).toBe(true)
|
|
745
|
-
|
|
746
|
-
// close() sets openValue to false, which triggers openValueChanged
|
|
747
|
-
controller.close()
|
|
748
|
-
expect(controller.openValue).toBe(false)
|
|
749
|
-
|
|
750
|
-
// Body overflow should be restored
|
|
751
|
-
expect(document.body.style.overflow).toBe('')
|
|
752
|
-
})
|
|
753
|
-
|
|
754
|
-
test('value can be changed programmatically from closed to open', async () => {
|
|
755
|
-
expect(controller.openValue).toBe(false)
|
|
756
|
-
|
|
757
|
-
// The openValueChanged callback will call open() when value changes to true
|
|
758
|
-
// But open() checks if already open and returns early
|
|
759
|
-
// So we need to use the public API: toggle() or open()
|
|
760
|
-
controller.open()
|
|
761
|
-
await nextFrame()
|
|
762
|
-
|
|
763
|
-
expect(controller.openValue).toBe(true)
|
|
764
|
-
const portal = await waitForPortal('.shadcn-dialog-portal')
|
|
765
|
-
expect(portal).toBeTruthy()
|
|
766
|
-
})
|
|
767
|
-
})
|
|
768
|
-
|
|
769
|
-
describe('Edge Cases', () => {
|
|
770
|
-
test('handles rapid open/close calls', async () => {
|
|
771
|
-
controller.open()
|
|
772
|
-
controller.close()
|
|
773
|
-
controller.open()
|
|
774
|
-
controller.close()
|
|
775
|
-
await nextFrame()
|
|
776
|
-
|
|
777
|
-
expect(controller.openValue).toBe(false)
|
|
778
|
-
})
|
|
779
|
-
|
|
780
|
-
test('handles missing template target gracefully', async () => {
|
|
781
|
-
const noTemplateHtml = `
|
|
782
|
-
<div data-controller="shadcn--dialog">
|
|
783
|
-
<button data-action="click->shadcn--dialog#toggle">Open</button>
|
|
784
|
-
</div>
|
|
785
|
-
`
|
|
786
|
-
|
|
787
|
-
cleanupController(application)
|
|
788
|
-
const setup = await setupController(DialogController, noTemplateHtml, 'shadcn--dialog')
|
|
789
|
-
application = setup.application
|
|
790
|
-
controller = setup.controller
|
|
791
|
-
|
|
792
|
-
// Should not throw error
|
|
793
|
-
controller.open()
|
|
794
|
-
await nextFrame()
|
|
795
|
-
|
|
796
|
-
const portal = document.querySelector('.shadcn-dialog-portal')
|
|
797
|
-
expect(portal).toBeNull()
|
|
798
|
-
})
|
|
799
|
-
|
|
800
|
-
test('handles missing overlay target', async () => {
|
|
801
|
-
const noOverlayHtml = `
|
|
802
|
-
<div data-controller="shadcn--dialog">
|
|
803
|
-
<button data-shadcn--dialog-target="trigger" data-action="click->shadcn--dialog#toggle">Open</button>
|
|
804
|
-
<template data-shadcn--dialog-target="template">
|
|
805
|
-
<div data-shadcn--dialog-target="content" role="dialog">
|
|
806
|
-
<p>Content</p>
|
|
807
|
-
</div>
|
|
808
|
-
</template>
|
|
809
|
-
</div>
|
|
810
|
-
`
|
|
811
|
-
|
|
812
|
-
cleanupController(application)
|
|
813
|
-
const setup = await setupController(DialogController, noOverlayHtml, 'shadcn--dialog')
|
|
814
|
-
application = setup.application
|
|
815
|
-
controller = setup.controller
|
|
816
|
-
|
|
817
|
-
controller.open()
|
|
818
|
-
await nextFrame()
|
|
819
|
-
|
|
820
|
-
// Should not throw error
|
|
821
|
-
expect(controller.openValue).toBe(true)
|
|
822
|
-
})
|
|
823
|
-
|
|
824
|
-
test('handles multiple dialogs on same page', async () => {
|
|
825
|
-
const multiDialogHtml = `
|
|
826
|
-
<div>
|
|
827
|
-
<div data-controller="shadcn--dialog">
|
|
828
|
-
<button data-shadcn--dialog-target="trigger" data-action="click->shadcn--dialog#toggle">Open 1</button>
|
|
829
|
-
<template data-shadcn--dialog-target="template">
|
|
830
|
-
<div data-shadcn--dialog-target="overlay" class="overlay-1"></div>
|
|
831
|
-
<div data-shadcn--dialog-target="content" role="dialog" class="content-1">
|
|
832
|
-
<button data-action="click->shadcn--dialog#close">Close 1</button>
|
|
833
|
-
</div>
|
|
834
|
-
</template>
|
|
835
|
-
</div>
|
|
836
|
-
<div data-controller="shadcn--dialog">
|
|
837
|
-
<button data-shadcn--dialog-target="trigger" data-action="click->shadcn--dialog#toggle">Open 2</button>
|
|
838
|
-
<template data-shadcn--dialog-target="template">
|
|
839
|
-
<div data-shadcn--dialog-target="overlay" class="overlay-2"></div>
|
|
840
|
-
<div data-shadcn--dialog-target="content" role="dialog" class="content-2">
|
|
841
|
-
<button data-action="click->shadcn--dialog#close">Close 2</button>
|
|
842
|
-
</div>
|
|
843
|
-
</template>
|
|
844
|
-
</div>
|
|
845
|
-
</div>
|
|
846
|
-
`
|
|
847
|
-
|
|
848
|
-
cleanupController(application)
|
|
849
|
-
document.body.innerHTML = multiDialogHtml
|
|
850
|
-
|
|
851
|
-
const app = Application.start()
|
|
852
|
-
app.register('shadcn--dialog', DialogController)
|
|
853
|
-
|
|
854
|
-
await nextFrame()
|
|
855
|
-
|
|
856
|
-
const dialogs = document.querySelectorAll('[data-controller="shadcn--dialog"]')
|
|
857
|
-
const trigger1 = dialogs[0].querySelector('[data-shadcn--dialog-target="trigger"]')
|
|
858
|
-
const trigger2 = dialogs[1].querySelector('[data-shadcn--dialog-target="trigger"]')
|
|
859
|
-
|
|
860
|
-
click(trigger1)
|
|
861
|
-
await nextFrame()
|
|
862
|
-
|
|
863
|
-
let portals = document.querySelectorAll('.shadcn-dialog-portal')
|
|
864
|
-
expect(portals.length).toBe(1)
|
|
865
|
-
expect(portals[0].querySelector('.content-1')).toBeTruthy()
|
|
866
|
-
|
|
867
|
-
click(trigger2)
|
|
868
|
-
await nextFrame()
|
|
869
|
-
|
|
870
|
-
portals = document.querySelectorAll('.shadcn-dialog-portal')
|
|
871
|
-
expect(portals.length).toBe(2)
|
|
872
|
-
expect(portals[1].querySelector('.content-2')).toBeTruthy()
|
|
873
|
-
|
|
874
|
-
app.stop()
|
|
875
|
-
document.querySelectorAll('.shadcn-dialog-portal').forEach(p => p.remove())
|
|
876
|
-
})
|
|
877
|
-
})
|
|
878
|
-
})
|