shadcn_phlexcomponents 0.1.11 → 0.1.16
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/app/javascript/controllers/accordion_controller.js +107 -0
- data/app/javascript/controllers/alert_dialog_controller.js +7 -0
- data/app/javascript/controllers/avatar_controller.js +14 -0
- data/app/javascript/controllers/checkbox_controller.js +29 -0
- data/app/javascript/controllers/collapsible_controller.js +39 -0
- data/app/javascript/controllers/combobox_controller.js +278 -0
- data/app/javascript/controllers/command_controller.js +207 -0
- data/app/javascript/controllers/date_picker_controller.js +258 -0
- data/app/javascript/controllers/date_range_picker_controller.js +200 -0
- data/app/javascript/controllers/dialog_controller.js +83 -0
- data/app/javascript/controllers/dropdown_menu_controller.js +238 -0
- data/app/javascript/controllers/dropdown_menu_sub_controller.js +118 -0
- data/app/javascript/controllers/form_field_controller.js +20 -0
- data/app/javascript/controllers/hover_card_controller.js +73 -0
- data/app/javascript/controllers/loading_button_controller.js +14 -0
- data/app/javascript/controllers/popover_controller.js +90 -0
- data/app/javascript/controllers/progress_controller.js +14 -0
- data/app/javascript/controllers/radio_group_controller.js +80 -0
- data/app/javascript/controllers/select_controller.js +265 -0
- data/app/javascript/controllers/sidebar_controller.js +29 -0
- data/app/javascript/controllers/sidebar_trigger_controller.js +15 -0
- data/app/javascript/controllers/slider_controller.js +82 -0
- data/app/javascript/controllers/switch_controller.js +26 -0
- data/app/javascript/controllers/tabs_controller.js +66 -0
- data/app/javascript/controllers/theme_switcher_controller.js +32 -0
- data/app/javascript/controllers/toast_container_controller.js +48 -0
- data/app/javascript/controllers/toast_controller.js +22 -0
- data/app/javascript/controllers/toggle_controller.js +20 -0
- data/app/javascript/controllers/toggle_group_controller.js +20 -0
- data/app/javascript/controllers/tooltip_controller.js +79 -0
- data/app/javascript/shadcn_phlexcomponents.js +60 -0
- data/app/javascript/utils/command.js +448 -0
- data/app/javascript/utils/floating_ui.js +160 -0
- data/app/javascript/utils/index.js +288 -0
- data/app/stylesheets/date_picker.css +118 -0
- data/app/typescript/controllers/accordion_controller.ts +136 -0
- data/app/typescript/controllers/alert_dialog_controller.ts +12 -0
- data/app/{javascript → typescript}/controllers/avatar_controller.ts +7 -2
- data/app/{javascript → typescript}/controllers/checkbox_controller.ts +11 -4
- data/app/{javascript → typescript}/controllers/collapsible_controller.ts +12 -5
- data/app/typescript/controllers/combobox_controller.ts +376 -0
- data/app/typescript/controllers/command_controller.ts +301 -0
- data/app/{javascript → typescript}/controllers/date_picker_controller.ts +185 -125
- data/app/{javascript → typescript}/controllers/date_range_picker_controller.ts +89 -79
- data/app/{javascript → typescript}/controllers/dialog_controller.ts +59 -57
- data/app/typescript/controllers/dropdown_menu_controller.ts +309 -0
- data/app/{javascript → typescript}/controllers/dropdown_menu_sub_controller.ts +31 -29
- data/app/{javascript → typescript}/controllers/form_field_controller.ts +6 -1
- data/app/{javascript → typescript}/controllers/hover_card_controller.ts +36 -26
- data/app/{javascript → typescript}/controllers/loading_button_controller.ts +6 -1
- data/app/{javascript → typescript}/controllers/popover_controller.ts +42 -65
- data/app/{javascript → typescript}/controllers/progress_controller.ts +9 -3
- data/app/{javascript → typescript}/controllers/radio_group_controller.ts +16 -9
- data/app/typescript/controllers/select_controller.ts +341 -0
- data/app/{javascript → typescript}/controllers/slider_controller.ts +23 -16
- data/app/{javascript → typescript}/controllers/switch_controller.ts +11 -4
- data/app/{javascript → typescript}/controllers/tabs_controller.ts +26 -18
- data/app/{javascript → typescript}/controllers/theme_switcher_controller.ts +6 -1
- data/app/{javascript → typescript}/controllers/toast_container_controller.ts +6 -1
- data/app/{javascript → typescript}/controllers/toast_controller.ts +7 -1
- data/app/typescript/controllers/toggle_controller.ts +28 -0
- data/app/typescript/controllers/toggle_group_controller.ts +28 -0
- data/app/{javascript → typescript}/controllers/tooltip_controller.ts +43 -31
- data/app/typescript/shadcn_phlexcomponents.ts +61 -0
- data/app/typescript/utils/command.ts +544 -0
- data/app/typescript/utils/floating_ui.ts +196 -0
- data/app/typescript/utils/index.ts +424 -0
- data/lib/install/install_shadcn_phlexcomponents.rb +10 -3
- data/lib/shadcn_phlexcomponents/alias.rb +3 -0
- data/lib/shadcn_phlexcomponents/components/accordion.rb +2 -1
- data/lib/shadcn_phlexcomponents/components/alert_dialog.rb +18 -15
- data/lib/shadcn_phlexcomponents/components/base.rb +14 -0
- data/lib/shadcn_phlexcomponents/components/collapsible.rb +1 -2
- data/lib/shadcn_phlexcomponents/components/combobox.rb +87 -57
- data/lib/shadcn_phlexcomponents/components/command.rb +77 -47
- data/lib/shadcn_phlexcomponents/components/date_picker.rb +25 -81
- data/lib/shadcn_phlexcomponents/components/date_range_picker.rb +21 -4
- data/lib/shadcn_phlexcomponents/components/dialog.rb +14 -12
- data/lib/shadcn_phlexcomponents/components/dropdown_menu.rb +5 -4
- data/lib/shadcn_phlexcomponents/components/dropdown_menu_sub.rb +2 -1
- data/lib/shadcn_phlexcomponents/components/form/form_combobox.rb +64 -0
- data/lib/shadcn_phlexcomponents/components/form.rb +14 -0
- data/lib/shadcn_phlexcomponents/components/hover_card.rb +3 -2
- data/lib/shadcn_phlexcomponents/components/popover.rb +3 -3
- data/lib/shadcn_phlexcomponents/components/select.rb +10 -25
- data/lib/shadcn_phlexcomponents/components/sheet.rb +15 -11
- data/lib/shadcn_phlexcomponents/components/table.rb +1 -1
- data/lib/shadcn_phlexcomponents/components/tabs.rb +1 -1
- data/lib/shadcn_phlexcomponents/components/toast_container.rb +1 -1
- data/lib/shadcn_phlexcomponents/components/toggle.rb +54 -0
- data/lib/shadcn_phlexcomponents/components/tooltip.rb +3 -2
- data/lib/shadcn_phlexcomponents/engine.rb +1 -5
- data/lib/shadcn_phlexcomponents/version.rb +1 -1
- metadata +71 -32
- data/app/javascript/controllers/accordion_controller.ts +0 -133
- data/app/javascript/controllers/combobox_controller.ts +0 -145
- data/app/javascript/controllers/command_controller.ts +0 -129
- data/app/javascript/controllers/command_root_controller.ts +0 -355
- data/app/javascript/controllers/dropdown_menu_controller.ts +0 -133
- data/app/javascript/controllers/dropdown_menu_root_controller.ts +0 -234
- data/app/javascript/controllers/select_controller.ts +0 -200
- data/app/javascript/shadcn_phlexcomponents.ts +0 -57
- data/app/javascript/utils.ts +0 -437
- /data/app/{javascript → typescript}/controllers/sidebar_controller.ts +0 -0
- /data/app/{javascript → typescript}/controllers/sidebar_trigger_controller.ts +0 -0
@@ -1,113 +1,115 @@
|
|
1
1
|
import { Controller } from '@hotwired/stimulus'
|
2
|
-
import { useClickOutside } from 'stimulus-use'
|
3
2
|
import {
|
4
|
-
|
5
|
-
openWithOverlay,
|
6
|
-
closeWithOverlay,
|
3
|
+
focusElement,
|
7
4
|
focusTrigger,
|
8
5
|
showContent,
|
9
6
|
hideContent,
|
10
7
|
getFocusableElements,
|
8
|
+
anyNestedComponentsOpen,
|
9
|
+
handleTabNavigation,
|
11
10
|
} from '../utils'
|
12
11
|
|
13
|
-
|
14
|
-
|
12
|
+
const DialogController = class extends Controller<HTMLElement> {
|
13
|
+
// targets
|
14
|
+
static targets = ['trigger', 'content', 'overlay']
|
15
|
+
declare readonly triggerTarget: HTMLElement
|
16
|
+
declare readonly contentTarget: HTMLElement
|
17
|
+
declare readonly overlayTarget: HTMLElement
|
18
|
+
|
19
|
+
// values
|
15
20
|
static values = {
|
16
21
|
isOpen: Boolean,
|
17
22
|
}
|
18
|
-
|
19
|
-
declare readonly triggerTarget: HTMLElement
|
20
|
-
declare readonly contentTarget: HTMLElement
|
21
|
-
declare readonly hasContentTarget: boolean
|
22
23
|
declare isOpenValue: boolean
|
24
|
+
|
25
|
+
// custom properties
|
26
|
+
declare trigger: HTMLElement
|
23
27
|
declare DOMKeydownListener: (event: KeyboardEvent) => void
|
28
|
+
declare DOMClickListener: (event: MouseEvent) => void
|
24
29
|
|
25
30
|
connect() {
|
26
31
|
this.DOMKeydownListener = this.onDOMKeydown.bind(this)
|
27
|
-
|
32
|
+
this.DOMClickListener = this.onDOMClick.bind(this)
|
28
33
|
}
|
29
34
|
|
30
35
|
open() {
|
31
36
|
this.isOpenValue = true
|
32
|
-
|
33
|
-
setTimeout(() => {
|
34
|
-
this.onOpenFocusedElement().focus()
|
35
|
-
}, ON_OPEN_FOCUS_DELAY)
|
36
|
-
}
|
37
|
-
|
38
|
-
onOpenFocusedElement() {
|
39
|
-
const focusableElements = getFocusableElements(this.contentTarget)
|
40
|
-
return focusableElements[0]
|
41
37
|
}
|
42
38
|
|
43
39
|
close() {
|
44
40
|
this.isOpenValue = false
|
45
41
|
}
|
46
42
|
|
47
|
-
onDOMKeydown(event: KeyboardEvent) {
|
48
|
-
if (!this.isOpenValue) return
|
49
|
-
|
50
|
-
const key = event.key
|
51
|
-
|
52
|
-
if (key === 'Escape') {
|
53
|
-
this.close()
|
54
|
-
} else if (key === 'Tab') {
|
55
|
-
const focusableElements = getFocusableElements(this.contentTarget)
|
56
|
-
|
57
|
-
const firstElement = focusableElements[0]
|
58
|
-
const lastElement = focusableElements[focusableElements.length - 1]
|
59
|
-
|
60
|
-
// If Shift + Tab pressed on first element, go to last element
|
61
|
-
if (event.shiftKey && document.activeElement === firstElement) {
|
62
|
-
event.preventDefault()
|
63
|
-
lastElement.focus()
|
64
|
-
}
|
65
|
-
// If Tab pressed on last element, go to first element
|
66
|
-
else if (!event.shiftKey && document.activeElement === lastElement) {
|
67
|
-
event.preventDefault()
|
68
|
-
firstElement.focus()
|
69
|
-
}
|
70
|
-
}
|
71
|
-
}
|
72
|
-
|
73
43
|
isOpenValueChanged(isOpen: boolean, previousIsOpen: boolean) {
|
74
44
|
if (isOpen) {
|
75
|
-
openWithOverlay(this.contentTarget.id)
|
76
|
-
|
77
45
|
showContent({
|
78
46
|
trigger: this.triggerTarget,
|
79
47
|
content: this.contentTarget,
|
80
48
|
contentContainer: this.contentTarget,
|
49
|
+
appendToBody: true,
|
50
|
+
overlay: this.overlayTarget,
|
81
51
|
})
|
82
52
|
|
53
|
+
const focusableElements = getFocusableElements(this.contentTarget)
|
54
|
+
focusElement(focusableElements[0])
|
55
|
+
|
83
56
|
this.setupEventListeners()
|
84
57
|
} else {
|
85
|
-
closeWithOverlay(this.contentTarget.id)
|
86
|
-
|
87
58
|
hideContent({
|
88
59
|
trigger: this.triggerTarget,
|
89
60
|
content: this.contentTarget,
|
90
61
|
contentContainer: this.contentTarget,
|
62
|
+
overlay: this.overlayTarget,
|
91
63
|
})
|
92
64
|
|
93
|
-
this.cleanupEventListeners()
|
94
|
-
|
95
|
-
// Only focus trigger when is previously opened
|
96
65
|
if (previousIsOpen) {
|
97
66
|
focusTrigger(this.triggerTarget)
|
98
67
|
}
|
68
|
+
|
69
|
+
this.cleanupEventListeners()
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
disconnect() {
|
74
|
+
this.cleanupEventListeners()
|
75
|
+
}
|
76
|
+
|
77
|
+
protected onDOMClick(event: MouseEvent) {
|
78
|
+
if (!this.isOpenValue) return
|
79
|
+
|
80
|
+
const target = event.target as HTMLElement
|
81
|
+
if (target === this.triggerTarget) return
|
82
|
+
if (this.contentTarget.contains(target)) return
|
83
|
+
|
84
|
+
const shouldClose = !anyNestedComponentsOpen(this.contentTarget)
|
85
|
+
if (shouldClose) this.close()
|
86
|
+
}
|
87
|
+
|
88
|
+
onDOMKeydown(event: KeyboardEvent) {
|
89
|
+
if (!this.isOpenValue) return
|
90
|
+
|
91
|
+
const key = event.key
|
92
|
+
|
93
|
+
if (key === 'Escape') {
|
94
|
+
const shouldClose = !anyNestedComponentsOpen(this.contentTarget)
|
95
|
+
if (shouldClose) this.close()
|
96
|
+
} else if (key === 'Tab') {
|
97
|
+
handleTabNavigation(this.contentTarget, event)
|
99
98
|
}
|
100
99
|
}
|
101
100
|
|
102
101
|
setupEventListeners() {
|
103
102
|
document.addEventListener('keydown', this.DOMKeydownListener)
|
103
|
+
document.addEventListener('pointerdown', this.DOMClickListener)
|
104
104
|
}
|
105
105
|
|
106
106
|
cleanupEventListeners() {
|
107
107
|
document.removeEventListener('keydown', this.DOMKeydownListener)
|
108
|
-
|
109
|
-
|
110
|
-
disconnect() {
|
111
|
-
this.cleanupEventListeners()
|
108
|
+
document.removeEventListener('pointerdown', this.DOMClickListener)
|
112
109
|
}
|
113
110
|
}
|
111
|
+
|
112
|
+
type Dialog = InstanceType<typeof DialogController>
|
113
|
+
|
114
|
+
export { DialogController }
|
115
|
+
export type { Dialog }
|
@@ -0,0 +1,309 @@
|
|
1
|
+
import { DropdownMenuSub } from './dropdown_menu_sub_controller'
|
2
|
+
import { Select } from './select_controller'
|
3
|
+
import { Controller } from '@hotwired/stimulus'
|
4
|
+
import { useClickOutside } from 'stimulus-use'
|
5
|
+
import { initFloatingUi } from '../utils/floating_ui'
|
6
|
+
import {
|
7
|
+
getSameLevelItems,
|
8
|
+
focusTrigger,
|
9
|
+
hideContent,
|
10
|
+
showContent,
|
11
|
+
lockScroll,
|
12
|
+
unlockScroll,
|
13
|
+
getStimulusInstance,
|
14
|
+
onClickOutside,
|
15
|
+
getNextEnabledIndex,
|
16
|
+
getPreviousEnabledIndex,
|
17
|
+
focusElement,
|
18
|
+
} from '../utils'
|
19
|
+
|
20
|
+
const onKeydown = (controller: DropdownMenu | Select, event: KeyboardEvent) => {
|
21
|
+
const key = event.key
|
22
|
+
|
23
|
+
if (['Tab', 'Enter', ' '].includes(key)) event.preventDefault()
|
24
|
+
if (key === 'Home') {
|
25
|
+
controller.focusItemByIndex(null, 0)
|
26
|
+
} else if (key === 'End') {
|
27
|
+
controller.focusItemByIndex(null, controller.items.length - 1)
|
28
|
+
} else if (key === 'Escape') {
|
29
|
+
controller.close()
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
const focusItemByIndex = (
|
34
|
+
controller: DropdownMenu | Select,
|
35
|
+
event: KeyboardEvent | null = null,
|
36
|
+
index: number | null = null,
|
37
|
+
) => {
|
38
|
+
if (event !== null) {
|
39
|
+
const key = event.key
|
40
|
+
|
41
|
+
if (key === 'ArrowUp') {
|
42
|
+
controller.items[controller.items.length - 1].focus()
|
43
|
+
} else {
|
44
|
+
controller.items[0].focus()
|
45
|
+
}
|
46
|
+
} else if (index !== null) {
|
47
|
+
controller.items[index].focus()
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
const DropdownMenuController = class extends Controller<HTMLElement> {
|
52
|
+
// targets
|
53
|
+
static targets = ['trigger', 'contentContainer', 'content', 'item']
|
54
|
+
declare readonly triggerTarget: HTMLElement
|
55
|
+
declare readonly contentContainerTarget: HTMLElement
|
56
|
+
declare readonly contentTarget: HTMLElement
|
57
|
+
declare readonly itemTargets: HTMLElement[]
|
58
|
+
|
59
|
+
// values
|
60
|
+
static values = {
|
61
|
+
isOpen: Boolean,
|
62
|
+
}
|
63
|
+
declare isOpenValue: boolean
|
64
|
+
|
65
|
+
// custom properties
|
66
|
+
declare closestContentSelector: string
|
67
|
+
declare items: HTMLElement[]
|
68
|
+
declare subMenuControllers: DropdownMenuSub[]
|
69
|
+
declare DOMKeydownListener: (event: KeyboardEvent) => void
|
70
|
+
declare cleanup: () => void
|
71
|
+
|
72
|
+
connect() {
|
73
|
+
this.closestContentSelector =
|
74
|
+
'[data-dropdown-menu-target="content"], [data-dropdown-menu-sub-target="content"]'
|
75
|
+
this.items = getSameLevelItems({
|
76
|
+
content: this.contentTarget,
|
77
|
+
items: this.itemTargets,
|
78
|
+
closestContentSelector: this.closestContentSelector,
|
79
|
+
})
|
80
|
+
useClickOutside(this, { element: this.contentTarget, dispatchEvent: false })
|
81
|
+
this.DOMKeydownListener = this.onDOMKeydown.bind(this)
|
82
|
+
}
|
83
|
+
|
84
|
+
toggle(event: MouseEvent) {
|
85
|
+
if (this.isOpenValue) {
|
86
|
+
this.close()
|
87
|
+
} else {
|
88
|
+
this.open(event)
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
open(event: MouseEvent | KeyboardEvent) {
|
93
|
+
this.isOpenValue = true
|
94
|
+
|
95
|
+
// Sub menus are not connected to the DOM yet when dropdown menu is connected.
|
96
|
+
// So we initialize them here instead of in connect().
|
97
|
+
if (this.subMenuControllers === undefined) {
|
98
|
+
const subMenuControllers = [] as DropdownMenuSub[]
|
99
|
+
|
100
|
+
const subMenus = Array.from(
|
101
|
+
this.contentTarget.querySelectorAll(
|
102
|
+
'[data-shadcn-phlexcomponents="dropdown-menu-sub"]',
|
103
|
+
),
|
104
|
+
) as HTMLElement[]
|
105
|
+
|
106
|
+
subMenus.forEach((subMenu) => {
|
107
|
+
const subMenuController = getStimulusInstance<DropdownMenuSub>(
|
108
|
+
'dropdown-menu-sub',
|
109
|
+
subMenu,
|
110
|
+
)
|
111
|
+
|
112
|
+
if (subMenuController) {
|
113
|
+
subMenuControllers.push(subMenuController)
|
114
|
+
}
|
115
|
+
})
|
116
|
+
|
117
|
+
this.subMenuControllers = subMenuControllers
|
118
|
+
}
|
119
|
+
|
120
|
+
let elementToFocus = null as HTMLElement | null
|
121
|
+
|
122
|
+
if (event instanceof KeyboardEvent) {
|
123
|
+
const key = event.key
|
124
|
+
|
125
|
+
if (['ArrowDown', 'Enter', ' '].includes(key)) {
|
126
|
+
elementToFocus = this.items[0]
|
127
|
+
}
|
128
|
+
} else {
|
129
|
+
elementToFocus = this.contentTarget
|
130
|
+
}
|
131
|
+
|
132
|
+
focusElement(elementToFocus)
|
133
|
+
}
|
134
|
+
|
135
|
+
close() {
|
136
|
+
this.isOpenValue = false
|
137
|
+
this.subMenuControllers.forEach((subMenuController) => {
|
138
|
+
subMenuController.closeImmediately()
|
139
|
+
})
|
140
|
+
}
|
141
|
+
|
142
|
+
focusItem(event: MouseEvent | KeyboardEvent) {
|
143
|
+
const item = event.currentTarget as HTMLElement
|
144
|
+
let items = [] as HTMLElement[]
|
145
|
+
const content = item.closest(this.closestContentSelector) as HTMLElement
|
146
|
+
|
147
|
+
const isSubMenu =
|
148
|
+
content.dataset.shadcnPhlexcomponents === 'dropdown-menu-sub-content'
|
149
|
+
|
150
|
+
if (isSubMenu) {
|
151
|
+
const subMenu = content.closest(
|
152
|
+
'[data-shadcn-phlexcomponents="dropdown-menu-sub"]',
|
153
|
+
)
|
154
|
+
const subMenuController = this.subMenuControllers.find(
|
155
|
+
(subMenuController) => subMenuController.element == subMenu,
|
156
|
+
)
|
157
|
+
if (subMenuController) {
|
158
|
+
items = subMenuController.items
|
159
|
+
}
|
160
|
+
} else {
|
161
|
+
items = this.items
|
162
|
+
}
|
163
|
+
|
164
|
+
const index = items.indexOf(item)
|
165
|
+
|
166
|
+
if (event instanceof KeyboardEvent) {
|
167
|
+
const key = event.key
|
168
|
+
let newIndex = 0
|
169
|
+
|
170
|
+
if (key === 'ArrowUp') {
|
171
|
+
newIndex = getPreviousEnabledIndex({
|
172
|
+
items,
|
173
|
+
currentIndex: index,
|
174
|
+
wrapAround: false,
|
175
|
+
})
|
176
|
+
} else {
|
177
|
+
newIndex = getNextEnabledIndex({
|
178
|
+
items,
|
179
|
+
currentIndex: index,
|
180
|
+
wrapAround: false,
|
181
|
+
})
|
182
|
+
}
|
183
|
+
|
184
|
+
items[newIndex].focus()
|
185
|
+
} else {
|
186
|
+
// item mouseover event
|
187
|
+
items[index].focus()
|
188
|
+
}
|
189
|
+
|
190
|
+
// Close submenus on the same level
|
191
|
+
const subMenusInContent = Array.from(
|
192
|
+
content.querySelectorAll(
|
193
|
+
'[data-shadcn-phlexcomponents="dropdown-menu-sub"]',
|
194
|
+
),
|
195
|
+
) as HTMLElement[]
|
196
|
+
|
197
|
+
subMenusInContent.forEach((subMenu) => {
|
198
|
+
const subMenuController = this.subMenuControllers.find(
|
199
|
+
(subMenuController) => subMenuController.element == subMenu,
|
200
|
+
)
|
201
|
+
|
202
|
+
if (subMenuController) {
|
203
|
+
subMenuController.closeImmediately()
|
204
|
+
}
|
205
|
+
})
|
206
|
+
}
|
207
|
+
|
208
|
+
onItemFocus(event: FocusEvent) {
|
209
|
+
const item = event.currentTarget as HTMLElement
|
210
|
+
item.tabIndex = 0
|
211
|
+
}
|
212
|
+
|
213
|
+
onItemBlur(event: FocusEvent) {
|
214
|
+
const item = event.currentTarget as HTMLElement
|
215
|
+
item.tabIndex = -1
|
216
|
+
}
|
217
|
+
|
218
|
+
focusItemByIndex(
|
219
|
+
event: KeyboardEvent | null = null,
|
220
|
+
index: number | null = null,
|
221
|
+
) {
|
222
|
+
focusItemByIndex(this, event, index)
|
223
|
+
}
|
224
|
+
|
225
|
+
focusContent(event: MouseEvent) {
|
226
|
+
const item = event.currentTarget as HTMLElement
|
227
|
+
const content = item.closest(this.closestContentSelector) as HTMLElement
|
228
|
+
content.focus()
|
229
|
+
}
|
230
|
+
|
231
|
+
select(event: MouseEvent | KeyboardEvent) {
|
232
|
+
if (event instanceof KeyboardEvent) {
|
233
|
+
const key = event.key
|
234
|
+
const item = (event.currentTarget || event.target) as HTMLElement
|
235
|
+
|
236
|
+
// For rails button_to
|
237
|
+
if (item && (key === 'Enter' || key === ' ')) {
|
238
|
+
item.click()
|
239
|
+
}
|
240
|
+
}
|
241
|
+
|
242
|
+
this.close()
|
243
|
+
}
|
244
|
+
|
245
|
+
clickOutside(event: MouseEvent) {
|
246
|
+
onClickOutside(this, event)
|
247
|
+
}
|
248
|
+
|
249
|
+
isOpenValueChanged(isOpen: boolean, previousIsOpen: boolean) {
|
250
|
+
if (isOpen) {
|
251
|
+
lockScroll(this.contentTarget.id)
|
252
|
+
|
253
|
+
showContent({
|
254
|
+
trigger: this.triggerTarget,
|
255
|
+
content: this.contentTarget,
|
256
|
+
contentContainer: this.contentContainerTarget,
|
257
|
+
setEqualWidth: false,
|
258
|
+
})
|
259
|
+
|
260
|
+
this.cleanup = initFloatingUi({
|
261
|
+
referenceElement: this.triggerTarget,
|
262
|
+
floatingElement: this.contentContainerTarget,
|
263
|
+
side: this.contentTarget.dataset.side,
|
264
|
+
align: this.contentTarget.dataset.align,
|
265
|
+
sideOffset: 4,
|
266
|
+
})
|
267
|
+
|
268
|
+
this.setupEventListeners()
|
269
|
+
} else {
|
270
|
+
unlockScroll(this.contentTarget.id)
|
271
|
+
|
272
|
+
hideContent({
|
273
|
+
trigger: this.triggerTarget,
|
274
|
+
content: this.contentTarget,
|
275
|
+
contentContainer: this.contentContainerTarget,
|
276
|
+
})
|
277
|
+
|
278
|
+
if (previousIsOpen) {
|
279
|
+
focusTrigger(this.triggerTarget)
|
280
|
+
}
|
281
|
+
|
282
|
+
this.cleanupEventListeners()
|
283
|
+
}
|
284
|
+
}
|
285
|
+
|
286
|
+
disconnect() {
|
287
|
+
this.cleanupEventListeners()
|
288
|
+
}
|
289
|
+
|
290
|
+
protected onDOMKeydown(event: KeyboardEvent) {
|
291
|
+
if (!this.isOpenValue) return
|
292
|
+
|
293
|
+
onKeydown(this, event)
|
294
|
+
}
|
295
|
+
|
296
|
+
protected setupEventListeners() {
|
297
|
+
document.addEventListener('keydown', this.DOMKeydownListener)
|
298
|
+
}
|
299
|
+
|
300
|
+
protected cleanupEventListeners() {
|
301
|
+
if (this.cleanup) this.cleanup()
|
302
|
+
document.removeEventListener('keydown', this.DOMKeydownListener)
|
303
|
+
}
|
304
|
+
}
|
305
|
+
|
306
|
+
type DropdownMenu = InstanceType<typeof DropdownMenuController>
|
307
|
+
|
308
|
+
export { DropdownMenuController, onKeydown, focusItemByIndex }
|
309
|
+
export type { DropdownMenu }
|
@@ -1,27 +1,31 @@
|
|
1
1
|
import { Controller } from '@hotwired/stimulus'
|
2
|
+
import { initFloatingUi } from '../utils/floating_ui'
|
2
3
|
import {
|
3
|
-
initFloatingUi,
|
4
4
|
ON_OPEN_FOCUS_DELAY,
|
5
5
|
getSameLevelItems,
|
6
6
|
showContent,
|
7
7
|
hideContent,
|
8
|
+
getStimulusInstance,
|
8
9
|
} from '../utils'
|
9
10
|
|
10
|
-
|
11
|
+
const DropdownMenuSubController = class extends Controller<HTMLElement> {
|
12
|
+
// targets
|
11
13
|
static targets = ['trigger', 'contentContainer', 'content']
|
14
|
+
declare readonly triggerTarget: HTMLElement
|
15
|
+
declare readonly contentContainerTarget: HTMLElement
|
16
|
+
declare readonly contentTarget: HTMLElement
|
12
17
|
|
18
|
+
// values
|
13
19
|
static values = {
|
14
20
|
isOpen: Boolean,
|
15
21
|
}
|
16
|
-
|
17
22
|
declare isOpenValue: boolean
|
18
|
-
|
19
|
-
|
20
|
-
declare readonly contentTarget: HTMLElement
|
21
|
-
declare DOMKeydownListener: (event: KeyboardEvent) => void
|
22
|
-
declare cleanup: () => void
|
23
|
+
|
24
|
+
// custom properties
|
23
25
|
declare closeTimeout: number
|
24
26
|
declare items: HTMLElement[]
|
27
|
+
declare DOMKeydownListener: (event: KeyboardEvent) => void
|
28
|
+
declare cleanup: () => void
|
25
29
|
|
26
30
|
connect() {
|
27
31
|
this.items = getSameLevelItems({
|
@@ -31,14 +35,10 @@ export default class extends Controller<HTMLElement> {
|
|
31
35
|
'[data-dropdown-menu-target="item"], [data-dropdown-menu-sub-target="trigger"]',
|
32
36
|
),
|
33
37
|
),
|
34
|
-
closestContentSelector:
|
38
|
+
closestContentSelector: '[data-dropdown-menu-sub-target="content"]',
|
35
39
|
})
|
36
40
|
}
|
37
41
|
|
38
|
-
closestContentSelector() {
|
39
|
-
return '[data-dropdown-menu-sub-target="content"]'
|
40
|
-
}
|
41
|
-
|
42
42
|
open(event: MouseEvent | KeyboardEvent | null = null) {
|
43
43
|
clearTimeout(this.closeTimeout)
|
44
44
|
this.isOpenValue = true
|
@@ -60,7 +60,7 @@ export default class extends Controller<HTMLElement> {
|
|
60
60
|
}, 250)
|
61
61
|
}
|
62
62
|
|
63
|
-
closeOnLeftKeydown(
|
63
|
+
closeOnLeftKeydown() {
|
64
64
|
this.closeImmediately()
|
65
65
|
this.triggerTarget.focus()
|
66
66
|
}
|
@@ -87,22 +87,17 @@ export default class extends Controller<HTMLElement> {
|
|
87
87
|
if (parentContent) {
|
88
88
|
const subMenu = parentContent.closest(
|
89
89
|
'[data-shadcn-phlexcomponents="dropdown-menu-sub"]',
|
90
|
-
)
|
90
|
+
) as HTMLElement
|
91
91
|
|
92
92
|
if (subMenu) {
|
93
|
-
const subMenuController =
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
)
|
93
|
+
const subMenuController = getStimulusInstance<DropdownMenuSub>(
|
94
|
+
'dropdown-menu-sub',
|
95
|
+
subMenu,
|
96
|
+
)
|
98
97
|
|
99
98
|
if (subMenuController) {
|
100
|
-
// @ts-ignore
|
101
99
|
subMenuController.closeImmediately()
|
102
100
|
setTimeout(() => {
|
103
|
-
// @ts-ignore
|
104
|
-
// weird bug where focus goes to body element after closing, and setting focus
|
105
|
-
// manually doesn't work
|
106
101
|
subMenuController.triggerTarget.focus()
|
107
102
|
}, 100)
|
108
103
|
}
|
@@ -114,11 +109,7 @@ export default class extends Controller<HTMLElement> {
|
|
114
109
|
this.isOpenValue = false
|
115
110
|
}
|
116
111
|
|
117
|
-
|
118
|
-
if (this.cleanup) this.cleanup()
|
119
|
-
}
|
120
|
-
|
121
|
-
isOpenValueChanged(isOpen: boolean, previousIsOpen: boolean) {
|
112
|
+
isOpenValueChanged(isOpen: boolean) {
|
122
113
|
if (isOpen) {
|
123
114
|
showContent({
|
124
115
|
trigger: this.triggerTarget,
|
@@ -141,10 +132,21 @@ export default class extends Controller<HTMLElement> {
|
|
141
132
|
contentContainer: this.contentContainerTarget,
|
142
133
|
})
|
143
134
|
})
|
135
|
+
|
136
|
+
this.cleanupEventListeners()
|
144
137
|
}
|
145
138
|
}
|
146
139
|
|
147
140
|
disconnect() {
|
148
141
|
this.cleanupEventListeners()
|
149
142
|
}
|
143
|
+
|
144
|
+
protected cleanupEventListeners() {
|
145
|
+
if (this.cleanup) this.cleanup()
|
146
|
+
}
|
150
147
|
}
|
148
|
+
|
149
|
+
type DropdownMenuSub = InstanceType<typeof DropdownMenuSubController>
|
150
|
+
|
151
|
+
export { DropdownMenuSubController }
|
152
|
+
export type { DropdownMenuSub }
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { Controller } from '@hotwired/stimulus'
|
2
2
|
|
3
|
-
|
3
|
+
const FormFieldController = class extends Controller {
|
4
4
|
connect() {
|
5
5
|
const hintContainer = this.element.querySelector('[data-remove-label]')
|
6
6
|
const labelContainer = this.element.querySelector('[data-remove-hint]')
|
@@ -20,3 +20,8 @@ export default class extends Controller {
|
|
20
20
|
}
|
21
21
|
}
|
22
22
|
}
|
23
|
+
|
24
|
+
type FormField = InstanceType<typeof FormFieldController>
|
25
|
+
|
26
|
+
export { FormFieldController }
|
27
|
+
export type { FormField }
|
@@ -1,20 +1,25 @@
|
|
1
1
|
import { Controller } from '@hotwired/stimulus'
|
2
2
|
import { useHover } from 'stimulus-use'
|
3
|
-
import { initFloatingUi
|
3
|
+
import { initFloatingUi } from '../utils/floating_ui'
|
4
|
+
import { showContent, hideContent } from '../utils'
|
4
5
|
|
5
|
-
|
6
|
+
const HoverCardController = class extends Controller<HTMLElement> {
|
7
|
+
// targets
|
6
8
|
static targets = ['trigger', 'content', 'contentContainer']
|
9
|
+
declare readonly triggerTarget: HTMLElement
|
10
|
+
declare readonly contentTarget: HTMLElement
|
11
|
+
declare readonly contentContainerTarget: HTMLElement
|
12
|
+
|
13
|
+
// values
|
7
14
|
static values = {
|
8
15
|
isOpen: Boolean,
|
9
16
|
}
|
10
|
-
|
11
17
|
declare isOpenValue: boolean
|
12
|
-
|
13
|
-
|
14
|
-
declare readonly contentContainerTarget: HTMLElement
|
15
|
-
declare cleanup: () => void
|
18
|
+
|
19
|
+
// custom properties
|
16
20
|
declare closeTimeout: number
|
17
21
|
declare DOMKeydownListener: (event: KeyboardEvent) => void
|
22
|
+
declare cleanup: () => void
|
18
23
|
|
19
24
|
connect() {
|
20
25
|
this.DOMKeydownListener = this.onDOMKeydown.bind(this)
|
@@ -42,25 +47,6 @@ export default class extends Controller<HTMLElement> {
|
|
42
47
|
this.close()
|
43
48
|
}
|
44
49
|
|
45
|
-
setupEventListeners() {
|
46
|
-
document.addEventListener('keydown', this.DOMKeydownListener)
|
47
|
-
}
|
48
|
-
|
49
|
-
cleanupEventListeners() {
|
50
|
-
if (this.cleanup) this.cleanup()
|
51
|
-
document.removeEventListener('keydown', this.DOMKeydownListener)
|
52
|
-
}
|
53
|
-
|
54
|
-
onDOMKeydown(event: KeyboardEvent) {
|
55
|
-
if (!this.isOpenValue) return
|
56
|
-
|
57
|
-
const key = event.key
|
58
|
-
|
59
|
-
if (key === 'Escape') {
|
60
|
-
this.close()
|
61
|
-
}
|
62
|
-
}
|
63
|
-
|
64
50
|
isOpenValueChanged(isOpen: boolean) {
|
65
51
|
if (isOpen) {
|
66
52
|
showContent({
|
@@ -90,4 +76,28 @@ export default class extends Controller<HTMLElement> {
|
|
90
76
|
disconnect() {
|
91
77
|
this.cleanupEventListeners()
|
92
78
|
}
|
79
|
+
|
80
|
+
protected setupEventListeners() {
|
81
|
+
document.addEventListener('keydown', this.DOMKeydownListener)
|
82
|
+
}
|
83
|
+
|
84
|
+
protected cleanupEventListeners() {
|
85
|
+
if (this.cleanup) this.cleanup()
|
86
|
+
document.removeEventListener('keydown', this.DOMKeydownListener)
|
87
|
+
}
|
88
|
+
|
89
|
+
protected onDOMKeydown(event: KeyboardEvent) {
|
90
|
+
if (!this.isOpenValue) return
|
91
|
+
|
92
|
+
const key = event.key
|
93
|
+
|
94
|
+
if (key === 'Escape') {
|
95
|
+
this.close()
|
96
|
+
}
|
97
|
+
}
|
93
98
|
}
|
99
|
+
|
100
|
+
type HoverCard = InstanceType<typeof HoverCardController>
|
101
|
+
|
102
|
+
export { HoverCardController }
|
103
|
+
export type { HoverCard }
|