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,986 +0,0 @@
|
|
|
1
|
-
import { Application } from "@hotwired/stimulus"
|
|
2
|
-
import SheetController from "../../app/assets/javascripts/shadcn/controllers/sheet_controller.js"
|
|
3
|
-
import { setupController, cleanupController, click, wait, nextFrame, keydown, waitForPortal, getFocusableElements, waitForEvent } from '../helpers/stimulus-test-helper.js'
|
|
4
|
-
|
|
5
|
-
describe("SheetController", () => {
|
|
6
|
-
let application
|
|
7
|
-
let element
|
|
8
|
-
let controller
|
|
9
|
-
|
|
10
|
-
const createSheetHTML = (options = {}) => {
|
|
11
|
-
const {
|
|
12
|
-
open = false,
|
|
13
|
-
side = "right"
|
|
14
|
-
} = options
|
|
15
|
-
|
|
16
|
-
const openAttr = open ? 'data-shadcn--sheet-open-value="true"' : ''
|
|
17
|
-
|
|
18
|
-
return `
|
|
19
|
-
<div data-controller="shadcn--sheet"
|
|
20
|
-
${openAttr}
|
|
21
|
-
data-shadcn--sheet-side-value="${side}">
|
|
22
|
-
<button data-shadcn--sheet-target="trigger" data-action="click->shadcn--sheet#toggle">
|
|
23
|
-
Open Sheet
|
|
24
|
-
</button>
|
|
25
|
-
|
|
26
|
-
<template data-shadcn--sheet-target="template">
|
|
27
|
-
<div data-shadcn--sheet-target="overlay" data-state="closed" hidden
|
|
28
|
-
data-action="click->shadcn--sheet#close"
|
|
29
|
-
class="fixed inset-0 z-50 bg-black/80">
|
|
30
|
-
</div>
|
|
31
|
-
<div data-shadcn--sheet-target="content" data-state="closed" hidden
|
|
32
|
-
data-side="${side}"
|
|
33
|
-
class="fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out">
|
|
34
|
-
<button data-action="click->shadcn--sheet#close" class="close-button">
|
|
35
|
-
Close
|
|
36
|
-
</button>
|
|
37
|
-
<input type="text" class="first-input" placeholder="First input" />
|
|
38
|
-
<button class="action-button">Action</button>
|
|
39
|
-
<a href="#" class="link">Link</a>
|
|
40
|
-
<input type="text" class="last-input" placeholder="Last input" />
|
|
41
|
-
</div>
|
|
42
|
-
</template>
|
|
43
|
-
</div>
|
|
44
|
-
`
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
beforeEach(async () => {
|
|
48
|
-
application = Application.start()
|
|
49
|
-
application.register("shadcn--sheet", SheetController)
|
|
50
|
-
document.body.innerHTML = createSheetHTML()
|
|
51
|
-
|
|
52
|
-
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
53
|
-
|
|
54
|
-
element = document.querySelector('[data-controller="shadcn--sheet"]')
|
|
55
|
-
controller = application.getControllerForElementAndIdentifier(element, "shadcn--sheet")
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
afterEach(() => {
|
|
59
|
-
if (application) {
|
|
60
|
-
application.stop()
|
|
61
|
-
}
|
|
62
|
-
document.body.innerHTML = ""
|
|
63
|
-
// Restore body overflow
|
|
64
|
-
document.body.style.overflow = ""
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
describe("initialization", () => {
|
|
68
|
-
test("connects successfully", () => {
|
|
69
|
-
expect(controller).not.toBeNull()
|
|
70
|
-
expect(controller).toBeDefined()
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
test("initializes with default values", () => {
|
|
74
|
-
expect(controller.openValue).toBe(false)
|
|
75
|
-
expect(controller.sideValue).toBe("right")
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
test("initializes with custom side value", async () => {
|
|
79
|
-
application.stop()
|
|
80
|
-
document.body.innerHTML = createSheetHTML({ side: "left" })
|
|
81
|
-
|
|
82
|
-
application = Application.start()
|
|
83
|
-
application.register("shadcn--sheet", SheetController)
|
|
84
|
-
|
|
85
|
-
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
86
|
-
|
|
87
|
-
element = document.querySelector('[data-controller="shadcn--sheet"]')
|
|
88
|
-
controller = application.getControllerForElementAndIdentifier(element, "shadcn--sheet")
|
|
89
|
-
|
|
90
|
-
expect(controller.sideValue).toBe("left")
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
test("can be controlled via openValue", async () => {
|
|
94
|
-
// Verify that setting openValue directly works
|
|
95
|
-
expect(controller.openValue).toBe(false)
|
|
96
|
-
|
|
97
|
-
// Manually trigger open by calling the method
|
|
98
|
-
controller.open()
|
|
99
|
-
|
|
100
|
-
await nextFrame()
|
|
101
|
-
await nextFrame()
|
|
102
|
-
|
|
103
|
-
expect(controller.openValue).toBe(true)
|
|
104
|
-
const portal = document.querySelector(".shadcn-sheet-portal")
|
|
105
|
-
expect(portal).toBeTruthy()
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
test("does not create portal on initialization when closed", () => {
|
|
109
|
-
const portal = document.querySelector(".shadcn-sheet-portal")
|
|
110
|
-
expect(portal).toBeNull()
|
|
111
|
-
})
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
describe("opening and closing", () => {
|
|
115
|
-
test("opens sheet when toggle is called", async () => {
|
|
116
|
-
const trigger = element.querySelector('[data-shadcn--sheet-target="trigger"]')
|
|
117
|
-
click(trigger)
|
|
118
|
-
|
|
119
|
-
await nextFrame()
|
|
120
|
-
|
|
121
|
-
expect(controller.openValue).toBe(true)
|
|
122
|
-
|
|
123
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
124
|
-
expect(portal).toBeTruthy()
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
test("closes sheet when toggle is called again", async () => {
|
|
128
|
-
const trigger = element.querySelector('[data-shadcn--sheet-target="trigger"]')
|
|
129
|
-
|
|
130
|
-
// Open
|
|
131
|
-
click(trigger)
|
|
132
|
-
await nextFrame()
|
|
133
|
-
expect(controller.openValue).toBe(true)
|
|
134
|
-
|
|
135
|
-
// Close
|
|
136
|
-
click(trigger)
|
|
137
|
-
await nextFrame()
|
|
138
|
-
expect(controller.openValue).toBe(false)
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
test("open() does nothing if already open", async () => {
|
|
142
|
-
const trigger = element.querySelector('[data-shadcn--sheet-target="trigger"]')
|
|
143
|
-
|
|
144
|
-
click(trigger)
|
|
145
|
-
await nextFrame()
|
|
146
|
-
|
|
147
|
-
const portal = document.querySelector(".shadcn-sheet-portal")
|
|
148
|
-
const portalHTML = portal.innerHTML
|
|
149
|
-
|
|
150
|
-
// Try to open again
|
|
151
|
-
controller.open()
|
|
152
|
-
await nextFrame()
|
|
153
|
-
|
|
154
|
-
// Portal should be the same
|
|
155
|
-
const samePortal = document.querySelector(".shadcn-sheet-portal")
|
|
156
|
-
expect(samePortal.innerHTML).toBe(portalHTML)
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
test("close() does nothing if already closed", async () => {
|
|
160
|
-
expect(controller.openValue).toBe(false)
|
|
161
|
-
|
|
162
|
-
controller.close()
|
|
163
|
-
await nextFrame()
|
|
164
|
-
|
|
165
|
-
expect(controller.openValue).toBe(false)
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
test("opens and closes multiple times", async () => {
|
|
169
|
-
const trigger = element.querySelector('[data-shadcn--sheet-target="trigger"]')
|
|
170
|
-
|
|
171
|
-
for (let i = 0; i < 3; i++) {
|
|
172
|
-
// Open
|
|
173
|
-
click(trigger)
|
|
174
|
-
await nextFrame()
|
|
175
|
-
expect(controller.openValue).toBe(true)
|
|
176
|
-
|
|
177
|
-
// Close
|
|
178
|
-
click(trigger)
|
|
179
|
-
await nextFrame()
|
|
180
|
-
expect(controller.openValue).toBe(false)
|
|
181
|
-
}
|
|
182
|
-
})
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
describe("portal rendering", () => {
|
|
186
|
-
test("creates portal element when opening", async () => {
|
|
187
|
-
controller.open()
|
|
188
|
-
await nextFrame()
|
|
189
|
-
|
|
190
|
-
const portal = document.querySelector(".shadcn-sheet-portal")
|
|
191
|
-
expect(portal).toBeTruthy()
|
|
192
|
-
expect(portal.parentElement).toBe(document.body)
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
test("portal contains overlay and content", async () => {
|
|
196
|
-
controller.open()
|
|
197
|
-
await nextFrame()
|
|
198
|
-
|
|
199
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
200
|
-
const overlay = portal.querySelector('[data-shadcn--sheet-target="overlay"]')
|
|
201
|
-
const content = portal.querySelector('[data-shadcn--sheet-target="content"]')
|
|
202
|
-
|
|
203
|
-
expect(overlay).toBeTruthy()
|
|
204
|
-
expect(content).toBeTruthy()
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
test("portal content includes template elements", async () => {
|
|
208
|
-
controller.open()
|
|
209
|
-
await nextFrame()
|
|
210
|
-
|
|
211
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
212
|
-
|
|
213
|
-
// Check that key elements from template are present in portal
|
|
214
|
-
expect(portal.querySelector('.close-button')).toBeTruthy()
|
|
215
|
-
expect(portal.querySelector('.first-input')).toBeTruthy()
|
|
216
|
-
expect(portal.querySelector('.action-button')).toBeTruthy()
|
|
217
|
-
expect(portal.querySelector('.link')).toBeTruthy()
|
|
218
|
-
expect(portal.querySelector('.last-input')).toBeTruthy()
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
test("removes portal after closing with delay", async () => {
|
|
222
|
-
controller.open()
|
|
223
|
-
await nextFrame()
|
|
224
|
-
|
|
225
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
226
|
-
expect(portal).toBeTruthy()
|
|
227
|
-
|
|
228
|
-
controller.close()
|
|
229
|
-
|
|
230
|
-
// Portal should still exist immediately after close
|
|
231
|
-
const portalAfterClose = document.querySelector(".shadcn-sheet-portal")
|
|
232
|
-
expect(portalAfterClose).toBeTruthy()
|
|
233
|
-
|
|
234
|
-
// Wait for the 300ms delay
|
|
235
|
-
await wait(350)
|
|
236
|
-
|
|
237
|
-
const portalAfterDelay = document.querySelector(".shadcn-sheet-portal")
|
|
238
|
-
expect(portalAfterDelay).toBeNull()
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
test("reuses portal if it exists", async () => {
|
|
242
|
-
controller.open()
|
|
243
|
-
await nextFrame()
|
|
244
|
-
|
|
245
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
246
|
-
const portalReference = portal
|
|
247
|
-
|
|
248
|
-
controller.close()
|
|
249
|
-
await wait(50) // Close but don't wait for removal
|
|
250
|
-
|
|
251
|
-
controller.open()
|
|
252
|
-
await nextFrame()
|
|
253
|
-
|
|
254
|
-
// Portal reference should be different since it was removed
|
|
255
|
-
// But the class name should be the same
|
|
256
|
-
const newPortal = document.querySelector(".shadcn-sheet-portal")
|
|
257
|
-
expect(newPortal.className).toBe(portalReference.className)
|
|
258
|
-
})
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
describe("side positioning", () => {
|
|
262
|
-
const sides = ["top", "right", "bottom", "left"]
|
|
263
|
-
|
|
264
|
-
sides.forEach(side => {
|
|
265
|
-
test(`renders with side="${side}"`, async () => {
|
|
266
|
-
application.stop()
|
|
267
|
-
document.body.innerHTML = createSheetHTML({ side })
|
|
268
|
-
|
|
269
|
-
application = Application.start()
|
|
270
|
-
application.register("shadcn--sheet", SheetController)
|
|
271
|
-
|
|
272
|
-
await nextFrame()
|
|
273
|
-
|
|
274
|
-
element = document.querySelector('[data-controller="shadcn--sheet"]')
|
|
275
|
-
controller = application.getControllerForElementAndIdentifier(element, "shadcn--sheet")
|
|
276
|
-
|
|
277
|
-
controller.open()
|
|
278
|
-
await nextFrame()
|
|
279
|
-
|
|
280
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
281
|
-
const content = portal.querySelector('[data-shadcn--sheet-target="content"]')
|
|
282
|
-
|
|
283
|
-
expect(content.dataset.side).toBe(side)
|
|
284
|
-
})
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
test("defaults to right side when not specified", () => {
|
|
288
|
-
expect(controller.sideValue).toBe("right")
|
|
289
|
-
})
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
describe("overlay and content state", () => {
|
|
293
|
-
test("sets overlay state to open when opening", async () => {
|
|
294
|
-
controller.open()
|
|
295
|
-
await nextFrame()
|
|
296
|
-
|
|
297
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
298
|
-
const overlay = portal.querySelector('[data-shadcn--sheet-target="overlay"]')
|
|
299
|
-
|
|
300
|
-
expect(overlay.dataset.state).toBe("open")
|
|
301
|
-
expect(overlay.hidden).toBe(false)
|
|
302
|
-
})
|
|
303
|
-
|
|
304
|
-
test("sets content state to open when opening", async () => {
|
|
305
|
-
controller.open()
|
|
306
|
-
await nextFrame()
|
|
307
|
-
|
|
308
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
309
|
-
const content = portal.querySelector('[data-shadcn--sheet-target="content"]')
|
|
310
|
-
|
|
311
|
-
expect(content.dataset.state).toBe("open")
|
|
312
|
-
expect(content.hidden).toBe(false)
|
|
313
|
-
})
|
|
314
|
-
|
|
315
|
-
test("sets overlay state to closed when closing", async () => {
|
|
316
|
-
controller.open()
|
|
317
|
-
await nextFrame()
|
|
318
|
-
|
|
319
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
320
|
-
const overlay = portal.querySelector('[data-shadcn--sheet-target="overlay"]')
|
|
321
|
-
|
|
322
|
-
controller.close()
|
|
323
|
-
|
|
324
|
-
expect(overlay.dataset.state).toBe("closed")
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
test("sets content state to closed when closing", async () => {
|
|
328
|
-
controller.open()
|
|
329
|
-
await nextFrame()
|
|
330
|
-
|
|
331
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
332
|
-
const content = portal.querySelector('[data-shadcn--sheet-target="content"]')
|
|
333
|
-
|
|
334
|
-
controller.close()
|
|
335
|
-
|
|
336
|
-
expect(content.dataset.state).toBe("closed")
|
|
337
|
-
})
|
|
338
|
-
})
|
|
339
|
-
|
|
340
|
-
describe("focus management", () => {
|
|
341
|
-
test("focuses first focusable element when opening", async () => {
|
|
342
|
-
controller.open()
|
|
343
|
-
await nextFrame()
|
|
344
|
-
await nextFrame() // Wait for focus
|
|
345
|
-
|
|
346
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
347
|
-
const closeButton = portal.querySelector('.close-button')
|
|
348
|
-
|
|
349
|
-
expect(document.activeElement).toBe(closeButton)
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
test("focuses content if no focusable elements", async () => {
|
|
353
|
-
application.stop()
|
|
354
|
-
|
|
355
|
-
const htmlWithNoFocusable = `
|
|
356
|
-
<div data-controller="shadcn--sheet">
|
|
357
|
-
<button data-shadcn--sheet-target="trigger" data-action="click->shadcn--sheet#toggle">
|
|
358
|
-
Open
|
|
359
|
-
</button>
|
|
360
|
-
<template data-shadcn--sheet-target="template">
|
|
361
|
-
<div data-shadcn--sheet-target="overlay" data-state="closed" hidden></div>
|
|
362
|
-
<div data-shadcn--sheet-target="content" data-state="closed" hidden tabindex="-1">
|
|
363
|
-
<div>No focusable elements</div>
|
|
364
|
-
</div>
|
|
365
|
-
</template>
|
|
366
|
-
</div>
|
|
367
|
-
`
|
|
368
|
-
|
|
369
|
-
document.body.innerHTML = htmlWithNoFocusable
|
|
370
|
-
|
|
371
|
-
application = Application.start()
|
|
372
|
-
application.register("shadcn--sheet", SheetController)
|
|
373
|
-
|
|
374
|
-
await nextFrame()
|
|
375
|
-
|
|
376
|
-
element = document.querySelector('[data-controller="shadcn--sheet"]')
|
|
377
|
-
controller = application.getControllerForElementAndIdentifier(element, "shadcn--sheet")
|
|
378
|
-
|
|
379
|
-
controller.open()
|
|
380
|
-
await nextFrame()
|
|
381
|
-
await nextFrame()
|
|
382
|
-
|
|
383
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
384
|
-
const content = portal.querySelector('[data-shadcn--sheet-target="content"]')
|
|
385
|
-
|
|
386
|
-
expect(document.activeElement).toBe(content)
|
|
387
|
-
})
|
|
388
|
-
|
|
389
|
-
test("stores and restores previous active element", async () => {
|
|
390
|
-
const trigger = element.querySelector('[data-shadcn--sheet-target="trigger"]')
|
|
391
|
-
trigger.focus()
|
|
392
|
-
|
|
393
|
-
expect(document.activeElement).toBe(trigger)
|
|
394
|
-
|
|
395
|
-
controller.open()
|
|
396
|
-
await nextFrame()
|
|
397
|
-
await nextFrame()
|
|
398
|
-
|
|
399
|
-
// Focus should have moved
|
|
400
|
-
expect(document.activeElement).not.toBe(trigger)
|
|
401
|
-
|
|
402
|
-
controller.close()
|
|
403
|
-
await nextFrame()
|
|
404
|
-
|
|
405
|
-
// Focus should be restored
|
|
406
|
-
expect(document.activeElement).toBe(trigger)
|
|
407
|
-
})
|
|
408
|
-
|
|
409
|
-
test("traps focus with Tab key", async () => {
|
|
410
|
-
controller.open()
|
|
411
|
-
await nextFrame()
|
|
412
|
-
|
|
413
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
414
|
-
const content = portal.querySelector('[data-shadcn--sheet-target="content"]')
|
|
415
|
-
const focusableElements = getFocusableElements(content)
|
|
416
|
-
const firstElement = focusableElements[0]
|
|
417
|
-
const lastElement = focusableElements[focusableElements.length - 1]
|
|
418
|
-
|
|
419
|
-
// Focus last element
|
|
420
|
-
lastElement.focus()
|
|
421
|
-
expect(document.activeElement).toBe(lastElement)
|
|
422
|
-
|
|
423
|
-
// Tab forward should wrap to first
|
|
424
|
-
keydown(document, 'Tab')
|
|
425
|
-
await nextFrame()
|
|
426
|
-
|
|
427
|
-
expect(document.activeElement).toBe(firstElement)
|
|
428
|
-
})
|
|
429
|
-
|
|
430
|
-
test("traps focus with Shift+Tab key", async () => {
|
|
431
|
-
controller.open()
|
|
432
|
-
await nextFrame()
|
|
433
|
-
|
|
434
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
435
|
-
const content = portal.querySelector('[data-shadcn--sheet-target="content"]')
|
|
436
|
-
const focusableElements = getFocusableElements(content)
|
|
437
|
-
const firstElement = focusableElements[0]
|
|
438
|
-
const lastElement = focusableElements[focusableElements.length - 1]
|
|
439
|
-
|
|
440
|
-
// Focus first element
|
|
441
|
-
firstElement.focus()
|
|
442
|
-
expect(document.activeElement).toBe(firstElement)
|
|
443
|
-
|
|
444
|
-
// Shift+Tab backward should wrap to last
|
|
445
|
-
keydown(document, 'Tab', { shiftKey: true })
|
|
446
|
-
await nextFrame()
|
|
447
|
-
|
|
448
|
-
expect(document.activeElement).toBe(lastElement)
|
|
449
|
-
})
|
|
450
|
-
|
|
451
|
-
test("does not trap focus in middle of focusable elements", async () => {
|
|
452
|
-
controller.open()
|
|
453
|
-
await nextFrame()
|
|
454
|
-
|
|
455
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
456
|
-
const content = portal.querySelector('[data-shadcn--sheet-target="content"]')
|
|
457
|
-
const focusableElements = getFocusableElements(content)
|
|
458
|
-
const firstElement = focusableElements[0]
|
|
459
|
-
const secondElement = focusableElements[1]
|
|
460
|
-
|
|
461
|
-
// Focus first element
|
|
462
|
-
firstElement.focus()
|
|
463
|
-
|
|
464
|
-
// Tab should move to second element naturally
|
|
465
|
-
keydown(document, 'Tab')
|
|
466
|
-
|
|
467
|
-
// The actual focus move is handled by browser, we just check preventDefault wasn't called
|
|
468
|
-
// In a real scenario, focus would move to secondElement
|
|
469
|
-
expect(document.activeElement).toBe(firstElement) // Focus hasn't moved yet in jsdom
|
|
470
|
-
})
|
|
471
|
-
})
|
|
472
|
-
|
|
473
|
-
describe("escape key", () => {
|
|
474
|
-
test("closes sheet when Escape is pressed", async () => {
|
|
475
|
-
controller.open()
|
|
476
|
-
await nextFrame()
|
|
477
|
-
|
|
478
|
-
expect(controller.openValue).toBe(true)
|
|
479
|
-
|
|
480
|
-
keydown(document, 'Escape')
|
|
481
|
-
await nextFrame()
|
|
482
|
-
|
|
483
|
-
expect(controller.openValue).toBe(false)
|
|
484
|
-
})
|
|
485
|
-
|
|
486
|
-
test("Escape key handler is added when opening", async () => {
|
|
487
|
-
let eventListenerAdded = false
|
|
488
|
-
const originalAdd = document.addEventListener
|
|
489
|
-
|
|
490
|
-
document.addEventListener = function(event, handler) {
|
|
491
|
-
if (event === 'keydown') {
|
|
492
|
-
eventListenerAdded = true
|
|
493
|
-
}
|
|
494
|
-
return originalAdd.apply(this, arguments)
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
controller.open()
|
|
498
|
-
await nextFrame()
|
|
499
|
-
|
|
500
|
-
expect(eventListenerAdded).toBe(true)
|
|
501
|
-
|
|
502
|
-
document.addEventListener = originalAdd
|
|
503
|
-
})
|
|
504
|
-
|
|
505
|
-
test("Escape key handler is removed when closing", async () => {
|
|
506
|
-
controller.open()
|
|
507
|
-
await nextFrame()
|
|
508
|
-
|
|
509
|
-
let eventListenerRemoved = false
|
|
510
|
-
const originalRemove = document.removeEventListener
|
|
511
|
-
|
|
512
|
-
document.removeEventListener = function(event) {
|
|
513
|
-
if (event === 'keydown') {
|
|
514
|
-
eventListenerRemoved = true
|
|
515
|
-
}
|
|
516
|
-
return originalRemove.apply(this, arguments)
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
controller.close()
|
|
520
|
-
|
|
521
|
-
expect(eventListenerRemoved).toBe(true)
|
|
522
|
-
|
|
523
|
-
document.removeEventListener = originalRemove
|
|
524
|
-
})
|
|
525
|
-
|
|
526
|
-
test("other keys do not close sheet", async () => {
|
|
527
|
-
controller.open()
|
|
528
|
-
await nextFrame()
|
|
529
|
-
|
|
530
|
-
expect(controller.openValue).toBe(true)
|
|
531
|
-
|
|
532
|
-
keydown(document, 'Enter')
|
|
533
|
-
expect(controller.openValue).toBe(true)
|
|
534
|
-
|
|
535
|
-
keydown(document, 'Space')
|
|
536
|
-
expect(controller.openValue).toBe(true)
|
|
537
|
-
|
|
538
|
-
keydown(document, 'a')
|
|
539
|
-
expect(controller.openValue).toBe(true)
|
|
540
|
-
})
|
|
541
|
-
})
|
|
542
|
-
|
|
543
|
-
describe("overlay click", () => {
|
|
544
|
-
test("closes sheet when overlay is clicked", async () => {
|
|
545
|
-
controller.open()
|
|
546
|
-
await nextFrame()
|
|
547
|
-
|
|
548
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
549
|
-
const overlay = portal.querySelector('[data-shadcn--sheet-target="overlay"]')
|
|
550
|
-
|
|
551
|
-
expect(controller.openValue).toBe(true)
|
|
552
|
-
|
|
553
|
-
click(overlay)
|
|
554
|
-
await nextFrame()
|
|
555
|
-
|
|
556
|
-
expect(controller.openValue).toBe(false)
|
|
557
|
-
})
|
|
558
|
-
|
|
559
|
-
test("re-attaches close event listeners to buttons in portal", async () => {
|
|
560
|
-
controller.open()
|
|
561
|
-
await nextFrame()
|
|
562
|
-
|
|
563
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
564
|
-
const closeButton = portal.querySelector('.close-button')
|
|
565
|
-
|
|
566
|
-
expect(controller.openValue).toBe(true)
|
|
567
|
-
|
|
568
|
-
click(closeButton)
|
|
569
|
-
await nextFrame()
|
|
570
|
-
|
|
571
|
-
expect(controller.openValue).toBe(false)
|
|
572
|
-
})
|
|
573
|
-
|
|
574
|
-
test("multiple close buttons all work", async () => {
|
|
575
|
-
application.stop()
|
|
576
|
-
|
|
577
|
-
const htmlWithMultipleClose = `
|
|
578
|
-
<div data-controller="shadcn--sheet">
|
|
579
|
-
<button data-shadcn--sheet-target="trigger" data-action="click->shadcn--sheet#toggle">
|
|
580
|
-
Open
|
|
581
|
-
</button>
|
|
582
|
-
<template data-shadcn--sheet-target="template">
|
|
583
|
-
<div data-shadcn--sheet-target="overlay" data-state="closed" hidden></div>
|
|
584
|
-
<div data-shadcn--sheet-target="content" data-state="closed" hidden>
|
|
585
|
-
<button data-action="click->shadcn--sheet#close" class="close-1">Close 1</button>
|
|
586
|
-
<button data-action="click->shadcn--sheet#close" class="close-2">Close 2</button>
|
|
587
|
-
<button data-action="click->shadcn--sheet#close" class="close-3">Close 3</button>
|
|
588
|
-
</div>
|
|
589
|
-
</template>
|
|
590
|
-
</div>
|
|
591
|
-
`
|
|
592
|
-
|
|
593
|
-
document.body.innerHTML = htmlWithMultipleClose
|
|
594
|
-
|
|
595
|
-
application = Application.start()
|
|
596
|
-
application.register("shadcn--sheet", SheetController)
|
|
597
|
-
|
|
598
|
-
await nextFrame()
|
|
599
|
-
|
|
600
|
-
element = document.querySelector('[data-controller="shadcn--sheet"]')
|
|
601
|
-
controller = application.getControllerForElementAndIdentifier(element, "shadcn--sheet")
|
|
602
|
-
|
|
603
|
-
// Test each close button
|
|
604
|
-
const closeButtons = ['.close-1', '.close-2', '.close-3']
|
|
605
|
-
|
|
606
|
-
for (const selector of closeButtons) {
|
|
607
|
-
controller.open()
|
|
608
|
-
await nextFrame()
|
|
609
|
-
|
|
610
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
611
|
-
const closeButton = portal.querySelector(selector)
|
|
612
|
-
|
|
613
|
-
click(closeButton)
|
|
614
|
-
await nextFrame()
|
|
615
|
-
|
|
616
|
-
expect(controller.openValue).toBe(false)
|
|
617
|
-
}
|
|
618
|
-
})
|
|
619
|
-
})
|
|
620
|
-
|
|
621
|
-
describe("body scroll lock", () => {
|
|
622
|
-
test("locks body scroll when opening", async () => {
|
|
623
|
-
expect(document.body.style.overflow).toBe("")
|
|
624
|
-
|
|
625
|
-
controller.open()
|
|
626
|
-
await nextFrame()
|
|
627
|
-
|
|
628
|
-
expect(document.body.style.overflow).toBe("hidden")
|
|
629
|
-
})
|
|
630
|
-
|
|
631
|
-
test("unlocks body scroll when closing", async () => {
|
|
632
|
-
controller.open()
|
|
633
|
-
await nextFrame()
|
|
634
|
-
|
|
635
|
-
expect(document.body.style.overflow).toBe("hidden")
|
|
636
|
-
|
|
637
|
-
controller.close()
|
|
638
|
-
await nextFrame()
|
|
639
|
-
|
|
640
|
-
expect(document.body.style.overflow).toBe("")
|
|
641
|
-
})
|
|
642
|
-
|
|
643
|
-
test("restores body scroll even if closed multiple times", async () => {
|
|
644
|
-
controller.open()
|
|
645
|
-
await nextFrame()
|
|
646
|
-
|
|
647
|
-
controller.close()
|
|
648
|
-
controller.close()
|
|
649
|
-
controller.close()
|
|
650
|
-
|
|
651
|
-
expect(document.body.style.overflow).toBe("")
|
|
652
|
-
})
|
|
653
|
-
})
|
|
654
|
-
|
|
655
|
-
describe("event dispatch", () => {
|
|
656
|
-
test("dispatches opened event when opening", async () => {
|
|
657
|
-
const eventPromise = waitForEvent(element, "shadcn--sheet:opened")
|
|
658
|
-
|
|
659
|
-
controller.open()
|
|
660
|
-
|
|
661
|
-
const event = await eventPromise
|
|
662
|
-
expect(event).toBeDefined()
|
|
663
|
-
})
|
|
664
|
-
|
|
665
|
-
test("dispatches closed event when closing", async () => {
|
|
666
|
-
controller.open()
|
|
667
|
-
await nextFrame()
|
|
668
|
-
|
|
669
|
-
const eventPromise = waitForEvent(element, "shadcn--sheet:closed")
|
|
670
|
-
|
|
671
|
-
controller.close()
|
|
672
|
-
|
|
673
|
-
const event = await eventPromise
|
|
674
|
-
expect(event).toBeDefined()
|
|
675
|
-
})
|
|
676
|
-
|
|
677
|
-
test("opened event is dispatched after portal is created", async () => {
|
|
678
|
-
let portalExists = false
|
|
679
|
-
|
|
680
|
-
element.addEventListener("shadcn--sheet:opened", () => {
|
|
681
|
-
portalExists = document.querySelector(".shadcn-sheet-portal") !== null
|
|
682
|
-
})
|
|
683
|
-
|
|
684
|
-
controller.open()
|
|
685
|
-
await nextFrame()
|
|
686
|
-
|
|
687
|
-
expect(portalExists).toBe(true)
|
|
688
|
-
})
|
|
689
|
-
|
|
690
|
-
test("closed event is dispatched immediately on close", async () => {
|
|
691
|
-
controller.open()
|
|
692
|
-
await nextFrame()
|
|
693
|
-
|
|
694
|
-
let openValueOnEvent = null
|
|
695
|
-
|
|
696
|
-
element.addEventListener("shadcn--sheet:closed", () => {
|
|
697
|
-
openValueOnEvent = controller.openValue
|
|
698
|
-
})
|
|
699
|
-
|
|
700
|
-
controller.close()
|
|
701
|
-
await nextFrame()
|
|
702
|
-
|
|
703
|
-
expect(openValueOnEvent).toBe(false)
|
|
704
|
-
})
|
|
705
|
-
})
|
|
706
|
-
|
|
707
|
-
describe("cleanup on disconnect", () => {
|
|
708
|
-
test("closes sheet when controller disconnects", async () => {
|
|
709
|
-
controller.open()
|
|
710
|
-
await nextFrame()
|
|
711
|
-
|
|
712
|
-
expect(controller.openValue).toBe(true)
|
|
713
|
-
|
|
714
|
-
controller.disconnect()
|
|
715
|
-
|
|
716
|
-
expect(controller.openValue).toBe(false)
|
|
717
|
-
})
|
|
718
|
-
|
|
719
|
-
test("removes portal when controller disconnects", async () => {
|
|
720
|
-
controller.open()
|
|
721
|
-
await nextFrame()
|
|
722
|
-
|
|
723
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
724
|
-
expect(portal).toBeTruthy()
|
|
725
|
-
|
|
726
|
-
controller.disconnect()
|
|
727
|
-
|
|
728
|
-
const portalAfterDisconnect = document.querySelector(".shadcn-sheet-portal")
|
|
729
|
-
expect(portalAfterDisconnect).toBeNull()
|
|
730
|
-
})
|
|
731
|
-
|
|
732
|
-
test("removes keydown listener when controller disconnects", async () => {
|
|
733
|
-
controller.open()
|
|
734
|
-
await nextFrame()
|
|
735
|
-
|
|
736
|
-
let listenerRemoved = false
|
|
737
|
-
const originalRemove = document.removeEventListener
|
|
738
|
-
|
|
739
|
-
document.removeEventListener = function(event) {
|
|
740
|
-
if (event === 'keydown') {
|
|
741
|
-
listenerRemoved = true
|
|
742
|
-
}
|
|
743
|
-
return originalRemove.apply(this, arguments)
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
controller.disconnect()
|
|
747
|
-
|
|
748
|
-
expect(listenerRemoved).toBe(true)
|
|
749
|
-
|
|
750
|
-
document.removeEventListener = originalRemove
|
|
751
|
-
})
|
|
752
|
-
|
|
753
|
-
test("unlocks body scroll when controller disconnects", async () => {
|
|
754
|
-
controller.open()
|
|
755
|
-
await nextFrame()
|
|
756
|
-
|
|
757
|
-
expect(document.body.style.overflow).toBe("hidden")
|
|
758
|
-
|
|
759
|
-
controller.disconnect()
|
|
760
|
-
|
|
761
|
-
expect(document.body.style.overflow).toBe("")
|
|
762
|
-
})
|
|
763
|
-
|
|
764
|
-
test("disconnect while closed does not cause errors", () => {
|
|
765
|
-
expect(controller.openValue).toBe(false)
|
|
766
|
-
|
|
767
|
-
expect(() => {
|
|
768
|
-
controller.disconnect()
|
|
769
|
-
}).not.toThrow()
|
|
770
|
-
})
|
|
771
|
-
})
|
|
772
|
-
|
|
773
|
-
describe("edge cases", () => {
|
|
774
|
-
test("handles rapid open/close calls", async () => {
|
|
775
|
-
controller.open()
|
|
776
|
-
controller.close()
|
|
777
|
-
controller.open()
|
|
778
|
-
controller.close()
|
|
779
|
-
controller.open()
|
|
780
|
-
|
|
781
|
-
await nextFrame()
|
|
782
|
-
|
|
783
|
-
expect(controller.openValue).toBe(true)
|
|
784
|
-
|
|
785
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
786
|
-
expect(portal).toBeTruthy()
|
|
787
|
-
})
|
|
788
|
-
|
|
789
|
-
test("handles missing template target gracefully", async () => {
|
|
790
|
-
application.stop()
|
|
791
|
-
|
|
792
|
-
const htmlWithoutTemplate = `
|
|
793
|
-
<div data-controller="shadcn--sheet">
|
|
794
|
-
<button data-shadcn--sheet-target="trigger" data-action="click->shadcn--sheet#toggle">
|
|
795
|
-
Open
|
|
796
|
-
</button>
|
|
797
|
-
</div>
|
|
798
|
-
`
|
|
799
|
-
|
|
800
|
-
document.body.innerHTML = htmlWithoutTemplate
|
|
801
|
-
|
|
802
|
-
application = Application.start()
|
|
803
|
-
application.register("shadcn--sheet", SheetController)
|
|
804
|
-
|
|
805
|
-
await nextFrame()
|
|
806
|
-
|
|
807
|
-
element = document.querySelector('[data-controller="shadcn--sheet"]')
|
|
808
|
-
controller = application.getControllerForElementAndIdentifier(element, "shadcn--sheet")
|
|
809
|
-
|
|
810
|
-
expect(() => {
|
|
811
|
-
controller.open()
|
|
812
|
-
}).not.toThrow()
|
|
813
|
-
|
|
814
|
-
// Should not create portal without template
|
|
815
|
-
await nextFrame()
|
|
816
|
-
const portal = document.querySelector(".shadcn-sheet-portal")
|
|
817
|
-
expect(portal).toBeNull()
|
|
818
|
-
})
|
|
819
|
-
|
|
820
|
-
test("handles empty template content", async () => {
|
|
821
|
-
application.stop()
|
|
822
|
-
|
|
823
|
-
const htmlWithEmptyTemplate = `
|
|
824
|
-
<div data-controller="shadcn--sheet">
|
|
825
|
-
<button data-shadcn--sheet-target="trigger" data-action="click->shadcn--sheet#toggle">
|
|
826
|
-
Open
|
|
827
|
-
</button>
|
|
828
|
-
<template data-shadcn--sheet-target="template"></template>
|
|
829
|
-
</div>
|
|
830
|
-
`
|
|
831
|
-
|
|
832
|
-
document.body.innerHTML = htmlWithEmptyTemplate
|
|
833
|
-
|
|
834
|
-
application = Application.start()
|
|
835
|
-
application.register("shadcn--sheet", SheetController)
|
|
836
|
-
|
|
837
|
-
await nextFrame()
|
|
838
|
-
|
|
839
|
-
element = document.querySelector('[data-controller="shadcn--sheet"]')
|
|
840
|
-
controller = application.getControllerForElementAndIdentifier(element, "shadcn--sheet")
|
|
841
|
-
|
|
842
|
-
controller.open()
|
|
843
|
-
await nextFrame()
|
|
844
|
-
|
|
845
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
846
|
-
expect(portal).toBeTruthy()
|
|
847
|
-
expect(portal.innerHTML).toBe("")
|
|
848
|
-
})
|
|
849
|
-
|
|
850
|
-
test("handles focus trap when no focusable elements exist", async () => {
|
|
851
|
-
application.stop()
|
|
852
|
-
|
|
853
|
-
const htmlNoFocusable = `
|
|
854
|
-
<div data-controller="shadcn--sheet">
|
|
855
|
-
<button data-shadcn--sheet-target="trigger" data-action="click->shadcn--sheet#toggle">
|
|
856
|
-
Open
|
|
857
|
-
</button>
|
|
858
|
-
<template data-shadcn--sheet-target="template">
|
|
859
|
-
<div data-shadcn--sheet-target="overlay" data-state="closed" hidden></div>
|
|
860
|
-
<div data-shadcn--sheet-target="content" data-state="closed" hidden tabindex="-1">
|
|
861
|
-
<div>No focusable content</div>
|
|
862
|
-
</div>
|
|
863
|
-
</template>
|
|
864
|
-
</div>
|
|
865
|
-
`
|
|
866
|
-
|
|
867
|
-
document.body.innerHTML = htmlNoFocusable
|
|
868
|
-
|
|
869
|
-
application = Application.start()
|
|
870
|
-
application.register("shadcn--sheet", SheetController)
|
|
871
|
-
|
|
872
|
-
await nextFrame()
|
|
873
|
-
|
|
874
|
-
element = document.querySelector('[data-controller="shadcn--sheet"]')
|
|
875
|
-
controller = application.getControllerForElementAndIdentifier(element, "shadcn--sheet")
|
|
876
|
-
|
|
877
|
-
controller.open()
|
|
878
|
-
await nextFrame()
|
|
879
|
-
|
|
880
|
-
// Should not throw when trying to trap focus
|
|
881
|
-
expect(() => {
|
|
882
|
-
keydown(document, 'Tab')
|
|
883
|
-
}).not.toThrow()
|
|
884
|
-
})
|
|
885
|
-
|
|
886
|
-
test("portal removal timeout is cleared properly", async () => {
|
|
887
|
-
controller.open()
|
|
888
|
-
await nextFrame()
|
|
889
|
-
|
|
890
|
-
controller.close()
|
|
891
|
-
|
|
892
|
-
// Open again before timeout completes
|
|
893
|
-
await wait(150) // Half of the 300ms timeout
|
|
894
|
-
controller.open()
|
|
895
|
-
await nextFrame()
|
|
896
|
-
|
|
897
|
-
// Portal should exist
|
|
898
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
899
|
-
expect(portal).toBeTruthy()
|
|
900
|
-
})
|
|
901
|
-
|
|
902
|
-
test("previousActiveElement is null-safe", async () => {
|
|
903
|
-
// Don't focus anything
|
|
904
|
-
document.activeElement?.blur()
|
|
905
|
-
|
|
906
|
-
controller.open()
|
|
907
|
-
await nextFrame()
|
|
908
|
-
|
|
909
|
-
controller.close()
|
|
910
|
-
await nextFrame()
|
|
911
|
-
|
|
912
|
-
// Should not throw even without previous active element
|
|
913
|
-
expect(controller.openValue).toBe(false)
|
|
914
|
-
})
|
|
915
|
-
})
|
|
916
|
-
|
|
917
|
-
describe("accessibility", () => {
|
|
918
|
-
test("overlay has appropriate class for backdrop", async () => {
|
|
919
|
-
controller.open()
|
|
920
|
-
await nextFrame()
|
|
921
|
-
|
|
922
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
923
|
-
const overlay = portal.querySelector('[data-shadcn--sheet-target="overlay"]')
|
|
924
|
-
|
|
925
|
-
expect(overlay.className).toContain("bg-black/80")
|
|
926
|
-
expect(overlay.className).toContain("fixed")
|
|
927
|
-
expect(overlay.className).toContain("inset-0")
|
|
928
|
-
})
|
|
929
|
-
|
|
930
|
-
test("content has appropriate positioning classes", async () => {
|
|
931
|
-
controller.open()
|
|
932
|
-
await nextFrame()
|
|
933
|
-
|
|
934
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
935
|
-
const content = portal.querySelector('[data-shadcn--sheet-target="content"]')
|
|
936
|
-
|
|
937
|
-
expect(content.className).toContain("fixed")
|
|
938
|
-
expect(content.className).toContain("z-50")
|
|
939
|
-
})
|
|
940
|
-
|
|
941
|
-
test("content maintains data-side attribute", async () => {
|
|
942
|
-
application.stop()
|
|
943
|
-
document.body.innerHTML = createSheetHTML({ side: "left" })
|
|
944
|
-
|
|
945
|
-
application = Application.start()
|
|
946
|
-
application.register("shadcn--sheet", SheetController)
|
|
947
|
-
|
|
948
|
-
await nextFrame()
|
|
949
|
-
|
|
950
|
-
element = document.querySelector('[data-controller="shadcn--sheet"]')
|
|
951
|
-
controller = application.getControllerForElementAndIdentifier(element, "shadcn--sheet")
|
|
952
|
-
|
|
953
|
-
controller.open()
|
|
954
|
-
await nextFrame()
|
|
955
|
-
|
|
956
|
-
const portal = await waitForPortal(".shadcn-sheet-portal")
|
|
957
|
-
const content = portal.querySelector('[data-shadcn--sheet-target="content"]')
|
|
958
|
-
|
|
959
|
-
expect(content.dataset.side).toBe("left")
|
|
960
|
-
})
|
|
961
|
-
})
|
|
962
|
-
|
|
963
|
-
describe("snapshots", () => {
|
|
964
|
-
test("renders closed sheet correctly", () => {
|
|
965
|
-
expect(element.innerHTML).toMatchSnapshot()
|
|
966
|
-
})
|
|
967
|
-
|
|
968
|
-
test("renders with different sides", async () => {
|
|
969
|
-
const sides = ["top", "right", "bottom", "left"]
|
|
970
|
-
|
|
971
|
-
for (const side of sides) {
|
|
972
|
-
application.stop()
|
|
973
|
-
document.body.innerHTML = createSheetHTML({ side })
|
|
974
|
-
|
|
975
|
-
application = Application.start()
|
|
976
|
-
application.register("shadcn--sheet", SheetController)
|
|
977
|
-
|
|
978
|
-
await nextFrame()
|
|
979
|
-
|
|
980
|
-
element = document.querySelector('[data-controller="shadcn--sheet"]')
|
|
981
|
-
|
|
982
|
-
expect(element.innerHTML).toMatchSnapshot(`side-${side}`)
|
|
983
|
-
}
|
|
984
|
-
})
|
|
985
|
-
})
|
|
986
|
-
})
|