shadcn_phlexcomponents 0.1.11 → 0.1.14

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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/app/javascript/controllers/accordion_controller.ts +65 -62
  3. data/app/javascript/controllers/alert_dialog_controller.ts +12 -0
  4. data/app/javascript/controllers/avatar_controller.ts +7 -2
  5. data/app/javascript/controllers/checkbox_controller.ts +11 -4
  6. data/app/javascript/controllers/collapsible_controller.ts +12 -5
  7. data/app/javascript/controllers/combobox_controller.ts +270 -39
  8. data/app/javascript/controllers/command_controller.ts +223 -51
  9. data/app/javascript/controllers/date_picker_controller.ts +185 -125
  10. data/app/javascript/controllers/date_range_picker_controller.ts +89 -79
  11. data/app/javascript/controllers/dialog_controller.ts +59 -57
  12. data/app/javascript/controllers/dropdown_menu_controller.ts +212 -36
  13. data/app/javascript/controllers/dropdown_menu_sub_controller.ts +31 -29
  14. data/app/javascript/controllers/form_field_controller.ts +6 -1
  15. data/app/javascript/controllers/hover_card_controller.ts +36 -26
  16. data/app/javascript/controllers/loading_button_controller.ts +6 -1
  17. data/app/javascript/controllers/popover_controller.ts +42 -65
  18. data/app/javascript/controllers/progress_controller.ts +9 -3
  19. data/app/javascript/controllers/radio_group_controller.ts +16 -9
  20. data/app/javascript/controllers/select_controller.ts +206 -65
  21. data/app/javascript/controllers/slider_controller.ts +23 -16
  22. data/app/javascript/controllers/switch_controller.ts +11 -4
  23. data/app/javascript/controllers/tabs_controller.ts +26 -18
  24. data/app/javascript/controllers/theme_switcher_controller.ts +6 -1
  25. data/app/javascript/controllers/toast_container_controller.ts +6 -1
  26. data/app/javascript/controllers/toast_controller.ts +7 -1
  27. data/app/javascript/controllers/toggle_controller.ts +28 -0
  28. data/app/javascript/controllers/toggle_group_controller.ts +28 -0
  29. data/app/javascript/controllers/tooltip_controller.ts +43 -31
  30. data/app/javascript/shadcn_phlexcomponents.ts +29 -25
  31. data/app/javascript/utils/command.ts +544 -0
  32. data/app/javascript/utils/floating_ui.ts +196 -0
  33. data/app/javascript/utils/index.ts +417 -0
  34. data/app/stylesheets/date_picker.css +118 -0
  35. data/lib/shadcn_phlexcomponents/alias.rb +3 -0
  36. data/lib/shadcn_phlexcomponents/components/accordion.rb +2 -1
  37. data/lib/shadcn_phlexcomponents/components/alert_dialog.rb +18 -15
  38. data/lib/shadcn_phlexcomponents/components/base.rb +14 -0
  39. data/lib/shadcn_phlexcomponents/components/collapsible.rb +1 -2
  40. data/lib/shadcn_phlexcomponents/components/combobox.rb +87 -57
  41. data/lib/shadcn_phlexcomponents/components/command.rb +77 -47
  42. data/lib/shadcn_phlexcomponents/components/date_picker.rb +25 -81
  43. data/lib/shadcn_phlexcomponents/components/date_range_picker.rb +21 -4
  44. data/lib/shadcn_phlexcomponents/components/dialog.rb +14 -12
  45. data/lib/shadcn_phlexcomponents/components/dropdown_menu.rb +5 -4
  46. data/lib/shadcn_phlexcomponents/components/dropdown_menu_sub.rb +2 -1
  47. data/lib/shadcn_phlexcomponents/components/form/form_combobox.rb +64 -0
  48. data/lib/shadcn_phlexcomponents/components/form.rb +14 -0
  49. data/lib/shadcn_phlexcomponents/components/hover_card.rb +3 -2
  50. data/lib/shadcn_phlexcomponents/components/popover.rb +3 -3
  51. data/lib/shadcn_phlexcomponents/components/select.rb +10 -25
  52. data/lib/shadcn_phlexcomponents/components/sheet.rb +15 -11
  53. data/lib/shadcn_phlexcomponents/components/table.rb +1 -1
  54. data/lib/shadcn_phlexcomponents/components/tabs.rb +1 -1
  55. data/lib/shadcn_phlexcomponents/components/toast_container.rb +1 -1
  56. data/lib/shadcn_phlexcomponents/components/toggle.rb +54 -0
  57. data/lib/shadcn_phlexcomponents/components/tooltip.rb +3 -2
  58. data/lib/shadcn_phlexcomponents/engine.rb +1 -5
  59. data/lib/shadcn_phlexcomponents/version.rb +1 -1
  60. metadata +9 -4
  61. data/app/javascript/controllers/command_root_controller.ts +0 -355
  62. data/app/javascript/controllers/dropdown_menu_root_controller.ts +0 -234
  63. data/app/javascript/utils.ts +0 -437
@@ -1,113 +1,115 @@
1
1
  import { Controller } from '@hotwired/stimulus'
2
- import { useClickOutside } from 'stimulus-use'
3
2
  import {
4
- ON_OPEN_FOCUS_DELAY,
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
- export default class extends Controller<HTMLElement> {
14
- static targets = ['trigger', 'content']
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
- useClickOutside(this, { element: this.contentTarget })
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 }
@@ -1,42 +1,113 @@
1
- import DropdownMenuSubController from './dropdown_menu_sub_controller'
2
- import DropdownMenuRootController from './dropdown_menu_root_controller'
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'
3
19
 
4
- export default class extends DropdownMenuRootController {
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
5
60
  static values = {
6
61
  isOpen: Boolean,
7
- setEqualWidth: { type: Boolean, default: false },
8
- closestContentSelector: {
9
- type: String,
10
- default:
11
- '[data-dropdown-menu-target="content"], [data-dropdown-menu-sub-target="content"]',
12
- },
13
62
  }
63
+ declare isOpenValue: boolean
14
64
 
65
+ // custom properties
66
+ declare closestContentSelector: string
67
+ declare items: HTMLElement[]
68
+ declare subMenuControllers: DropdownMenuSub[]
15
69
  declare DOMKeydownListener: (event: KeyboardEvent) => void
16
- declare subMenuControllers: DropdownMenuSubController[]
70
+ declare cleanup: () => void
17
71
 
18
72
  connect() {
19
- super.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)
20
82
  }
21
83
 
22
- onOpen(_event: MouseEvent | KeyboardEvent) {
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
+
23
95
  // Sub menus are not connected to the DOM yet when dropdown menu is connected.
24
96
  // So we initialize them here instead of in connect().
25
97
  if (this.subMenuControllers === undefined) {
26
- let subMenuControllers = [] as DropdownMenuSubController[]
98
+ const subMenuControllers = [] as DropdownMenuSub[]
27
99
 
28
100
  const subMenus = Array.from(
29
101
  this.contentTarget.querySelectorAll(
30
102
  '[data-shadcn-phlexcomponents="dropdown-menu-sub"]',
31
103
  ),
32
- )
104
+ ) as HTMLElement[]
33
105
 
34
106
  subMenus.forEach((subMenu) => {
35
- const subMenuController =
36
- window.Stimulus.getControllerForElementAndIdentifier(
37
- subMenu,
38
- 'dropdown-menu-sub',
39
- ) as DropdownMenuSubController
107
+ const subMenuController = getStimulusInstance<DropdownMenuSub>(
108
+ 'dropdown-menu-sub',
109
+ subMenu,
110
+ )
40
111
 
41
112
  if (subMenuController) {
42
113
  subMenuControllers.push(subMenuController)
@@ -45,14 +116,33 @@ export default class extends DropdownMenuRootController {
45
116
 
46
117
  this.subMenuControllers = subMenuControllers
47
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
+ })
48
140
  }
49
141
 
50
142
  focusItem(event: MouseEvent | KeyboardEvent) {
51
143
  const item = event.currentTarget as HTMLElement
52
144
  let items = [] as HTMLElement[]
53
- const content = item.closest(
54
- this.closestContentSelectorValue,
55
- ) as HTMLElement
145
+ const content = item.closest(this.closestContentSelector) as HTMLElement
56
146
 
57
147
  const isSubMenu =
58
148
  content.dataset.shadcnPhlexcomponents === 'dropdown-menu-sub-content'
@@ -71,22 +161,24 @@ export default class extends DropdownMenuRootController {
71
161
  items = this.items
72
162
  }
73
163
 
74
- let index = items.indexOf(item)
164
+ const index = items.indexOf(item)
75
165
 
76
166
  if (event instanceof KeyboardEvent) {
77
167
  const key = event.key
78
168
  let newIndex = 0
79
169
 
80
170
  if (key === 'ArrowUp') {
81
- newIndex = index - 1
82
- if (newIndex < 0) {
83
- newIndex = 0
84
- }
171
+ newIndex = getPreviousEnabledIndex({
172
+ items,
173
+ currentIndex: index,
174
+ wrapAround: false,
175
+ })
85
176
  } else {
86
- newIndex = index + 1
87
- if (newIndex > items.length - 1) {
88
- newIndex = items.length - 1
89
- }
177
+ newIndex = getNextEnabledIndex({
178
+ items,
179
+ currentIndex: index,
180
+ wrapAround: false,
181
+ })
90
182
  }
91
183
 
92
184
  items[newIndex].focus()
@@ -113,13 +205,30 @@ export default class extends DropdownMenuRootController {
113
205
  })
114
206
  }
115
207
 
116
- onClose() {
117
- this.subMenuControllers.forEach((subMenuController) => {
118
- subMenuController.closeImmediately()
119
- })
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)
120
223
  }
121
224
 
122
- onSelect(event: MouseEvent | KeyboardEvent) {
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) {
123
232
  if (event instanceof KeyboardEvent) {
124
233
  const key = event.key
125
234
  const item = (event.currentTarget || event.target) as HTMLElement
@@ -129,5 +238,72 @@ export default class extends DropdownMenuRootController {
129
238
  item.click()
130
239
  }
131
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)
132
303
  }
133
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
- export default class extends Controller<HTMLElement> {
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
- declare readonly triggerTarget: HTMLElement
19
- declare readonly contentContainerTarget: HTMLElement
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: this.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(event: KeyboardEvent) {
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
- window.Stimulus.getControllerForElementAndIdentifier(
95
- subMenu,
96
- 'dropdown-menu-sub',
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
- cleanupEventListeners() {
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
- export default class extends Controller {
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 }