shadcn_phlexcomponents 0.1.9 → 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 (256) hide show
  1. checksums.yaml +4 -4
  2. data/app/javascript/controllers/accordion_controller.ts +136 -0
  3. data/app/javascript/controllers/alert_dialog_controller.ts +12 -0
  4. data/app/javascript/controllers/avatar_controller.ts +24 -0
  5. data/app/javascript/controllers/checkbox_controller.ts +41 -0
  6. data/app/javascript/controllers/collapsible_controller.ts +52 -0
  7. data/app/javascript/controllers/combobox_controller.ts +376 -0
  8. data/app/javascript/controllers/command_controller.ts +301 -0
  9. data/app/javascript/controllers/date_picker_controller.ts +334 -0
  10. data/app/javascript/controllers/date_range_picker_controller.ts +253 -0
  11. data/app/javascript/controllers/dialog_controller.ts +115 -0
  12. data/app/javascript/controllers/dropdown_menu_controller.ts +309 -0
  13. data/app/javascript/controllers/dropdown_menu_sub_controller.ts +152 -0
  14. data/app/javascript/controllers/form_field_controller.ts +27 -0
  15. data/app/javascript/controllers/hover_card_controller.ts +103 -0
  16. data/app/javascript/controllers/{loading_button_controller.js → loading_button_controller.ts} +7 -2
  17. data/app/javascript/controllers/popover_controller.ts +118 -0
  18. data/app/javascript/controllers/progress_controller.ts +23 -0
  19. data/app/javascript/controllers/radio_group_controller.ts +113 -0
  20. data/app/javascript/controllers/select_controller.ts +341 -0
  21. data/app/javascript/controllers/{sidebar_controller.js → sidebar_controller.ts} +6 -2
  22. data/app/javascript/controllers/sidebar_trigger_controller.ts +21 -0
  23. data/app/javascript/controllers/slider_controller.ts +114 -0
  24. data/app/javascript/controllers/switch_controller.ts +37 -0
  25. data/app/javascript/controllers/tabs_controller.ts +87 -0
  26. data/app/javascript/controllers/theme_switcher_controller.ts +40 -0
  27. data/app/javascript/controllers/toast_container_controller.ts +67 -0
  28. data/app/javascript/controllers/toast_controller.ts +34 -0
  29. data/app/javascript/controllers/toggle_controller.ts +28 -0
  30. data/app/javascript/controllers/toggle_group_controller.ts +28 -0
  31. data/app/javascript/controllers/tooltip_controller.ts +110 -0
  32. data/app/javascript/shadcn_phlexcomponents.ts +61 -0
  33. data/app/javascript/utils/command.ts +544 -0
  34. data/app/javascript/utils/floating_ui.ts +196 -0
  35. data/app/javascript/utils/index.ts +417 -0
  36. data/app/stylesheets/date_picker.css +81 -101
  37. data/app/stylesheets/nouislider.css +173 -0
  38. data/app/stylesheets/tw-animate.css +486 -0
  39. data/lib/install/install_shadcn_phlexcomponents.rb +16 -3
  40. data/lib/shadcn_phlexcomponents/alias.rb +6 -1
  41. data/lib/shadcn_phlexcomponents/components/accordion.rb +130 -0
  42. data/lib/shadcn_phlexcomponents/components/alert.rb +59 -0
  43. data/lib/shadcn_phlexcomponents/components/alert_dialog.rb +279 -0
  44. data/lib/shadcn_phlexcomponents/components/{aspect_ratio/aspect_ratio.rb → aspect_ratio.rb} +2 -2
  45. data/lib/shadcn_phlexcomponents/components/avatar.rb +63 -0
  46. data/lib/shadcn_phlexcomponents/components/badge.rb +35 -0
  47. data/lib/shadcn_phlexcomponents/components/base.rb +48 -7
  48. data/lib/shadcn_phlexcomponents/components/breadcrumb.rb +150 -0
  49. data/lib/shadcn_phlexcomponents/components/button.rb +49 -0
  50. data/lib/shadcn_phlexcomponents/components/card.rb +88 -0
  51. data/lib/shadcn_phlexcomponents/components/{checkbox/checkbox.rb → checkbox.rb} +18 -14
  52. data/lib/shadcn_phlexcomponents/components/{checkbox_group/checkbox_group.rb → checkbox_group.rb} +7 -8
  53. data/lib/shadcn_phlexcomponents/components/collapsible.rb +90 -0
  54. data/lib/shadcn_phlexcomponents/components/combobox.rb +428 -0
  55. data/lib/shadcn_phlexcomponents/components/command.rb +381 -0
  56. data/lib/shadcn_phlexcomponents/components/date_picker.rb +208 -0
  57. data/lib/shadcn_phlexcomponents/components/date_range_picker.rb +143 -0
  58. data/lib/shadcn_phlexcomponents/components/dialog.rb +236 -0
  59. data/lib/shadcn_phlexcomponents/components/dropdown_menu.rb +283 -0
  60. data/lib/shadcn_phlexcomponents/components/dropdown_menu_sub.rb +136 -0
  61. data/lib/shadcn_phlexcomponents/components/form/form_checkbox.rb +6 -7
  62. data/lib/shadcn_phlexcomponents/components/form/form_checkbox_group.rb +2 -2
  63. data/lib/shadcn_phlexcomponents/components/form/form_combobox.rb +64 -0
  64. data/lib/shadcn_phlexcomponents/components/form/form_date_picker.rb +3 -4
  65. data/lib/shadcn_phlexcomponents/components/form/form_date_range_picker.rb +27 -41
  66. data/lib/shadcn_phlexcomponents/components/form/form_error.rb +1 -1
  67. data/lib/shadcn_phlexcomponents/components/form/form_helpers.rb +43 -8
  68. data/lib/shadcn_phlexcomponents/components/form/form_hint.rb +1 -1
  69. data/lib/shadcn_phlexcomponents/components/form/form_input.rb +3 -4
  70. data/lib/shadcn_phlexcomponents/components/form/form_radio_group.rb +4 -5
  71. data/lib/shadcn_phlexcomponents/components/form/form_select.rb +3 -4
  72. data/lib/shadcn_phlexcomponents/components/form/form_slider.rb +91 -0
  73. data/lib/shadcn_phlexcomponents/components/form/form_switch.rb +7 -6
  74. data/lib/shadcn_phlexcomponents/components/form/form_textarea.rb +3 -4
  75. data/lib/shadcn_phlexcomponents/components/{form/form.rb → form.rb} +36 -4
  76. data/lib/shadcn_phlexcomponents/components/hover_card.rb +111 -0
  77. data/lib/shadcn_phlexcomponents/components/input.rb +31 -0
  78. data/lib/shadcn_phlexcomponents/components/label.rb +16 -0
  79. data/lib/shadcn_phlexcomponents/components/{link/link.rb → link.rb} +10 -3
  80. data/lib/shadcn_phlexcomponents/components/{loading_button/loading_button.rb → loading_button.rb} +9 -2
  81. data/lib/shadcn_phlexcomponents/components/pagination.rb +166 -0
  82. data/lib/shadcn_phlexcomponents/components/popover.rb +116 -0
  83. data/lib/shadcn_phlexcomponents/components/{progress/progress.rb → progress.rb} +4 -4
  84. data/lib/shadcn_phlexcomponents/components/radio_group.rb +155 -0
  85. data/lib/shadcn_phlexcomponents/components/select.rb +406 -0
  86. data/lib/shadcn_phlexcomponents/components/{separator/separator.rb → separator.rb} +9 -8
  87. data/lib/shadcn_phlexcomponents/components/sheet.rb +243 -0
  88. data/lib/shadcn_phlexcomponents/components/{skeleton/skeleton.rb → skeleton.rb} +1 -1
  89. data/lib/shadcn_phlexcomponents/components/slider.rb +72 -0
  90. data/lib/shadcn_phlexcomponents/components/switch.rb +75 -0
  91. data/lib/shadcn_phlexcomponents/components/table.rb +140 -0
  92. data/lib/shadcn_phlexcomponents/components/tabs.rb +135 -0
  93. data/lib/shadcn_phlexcomponents/components/textarea.rb +24 -0
  94. data/lib/shadcn_phlexcomponents/components/toast.rb +153 -0
  95. data/lib/shadcn_phlexcomponents/components/{toast/toast_container.rb → toast_container.rb} +23 -4
  96. data/lib/shadcn_phlexcomponents/components/toggle.rb +54 -0
  97. data/lib/shadcn_phlexcomponents/components/tooltip.rb +132 -0
  98. data/lib/shadcn_phlexcomponents/engine.rb +1 -5
  99. data/lib/shadcn_phlexcomponents/initializers/shadcn_phlexcomponents.rb +25 -0
  100. data/lib/shadcn_phlexcomponents/version.rb +1 -1
  101. data/lib/tasks/install.rake +1 -1
  102. metadata +83 -167
  103. data/app/javascript/controllers/accordion_controller.js +0 -124
  104. data/app/javascript/controllers/alert_dialog_controller.js +0 -21
  105. data/app/javascript/controllers/avatar_controller.js +0 -15
  106. data/app/javascript/controllers/checkbox_controller.js +0 -28
  107. data/app/javascript/controllers/collapsible_controller.js +0 -35
  108. data/app/javascript/controllers/combobox_controller.js +0 -54
  109. data/app/javascript/controllers/date_picker_controller.js +0 -253
  110. data/app/javascript/controllers/date_range_picker_controller.js +0 -344
  111. data/app/javascript/controllers/dialog_controller.js +0 -114
  112. data/app/javascript/controllers/dropdown_menu_controller.js +0 -171
  113. data/app/javascript/controllers/form_field_controller.js +0 -24
  114. data/app/javascript/controllers/hover_card_controller.js +0 -21
  115. data/app/javascript/controllers/popover_controller.js +0 -113
  116. data/app/javascript/controllers/progress_controller.js +0 -14
  117. data/app/javascript/controllers/radio_group_controller.js +0 -90
  118. data/app/javascript/controllers/select_controller.js +0 -274
  119. data/app/javascript/controllers/sidebar_trigger_controller.js +0 -15
  120. data/app/javascript/controllers/switch_controller.js +0 -24
  121. data/app/javascript/controllers/tabs_controller.js +0 -73
  122. data/app/javascript/controllers/theme_switcher_controller.js +0 -32
  123. data/app/javascript/controllers/toast_container_controller.js +0 -22
  124. data/app/javascript/controllers/toast_controller.js +0 -45
  125. data/app/javascript/controllers/tooltip_controller.js +0 -40
  126. data/app/javascript/shadcn_phlexcomponents.js +0 -53
  127. data/app/javascript/utils.js +0 -184
  128. data/app/stylesheets/choices.css +0 -324
  129. data/app/stylesheets/tailwindcss-animate.css +0 -318
  130. data/lib/shadcn_phlexcomponents/components/accordion/accordion.rb +0 -38
  131. data/lib/shadcn_phlexcomponents/components/accordion/accordion_content.rb +0 -30
  132. data/lib/shadcn_phlexcomponents/components/accordion/accordion_item.rb +0 -26
  133. data/lib/shadcn_phlexcomponents/components/accordion/accordion_trigger.rb +0 -46
  134. data/lib/shadcn_phlexcomponents/components/alert/alert.rb +0 -40
  135. data/lib/shadcn_phlexcomponents/components/alert/alert_description.rb +0 -11
  136. data/lib/shadcn_phlexcomponents/components/alert/alert_title.rb +0 -11
  137. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog.rb +0 -60
  138. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_action.rb +0 -22
  139. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_action_to.rb +0 -40
  140. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_cancel.rb +0 -22
  141. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_content.rb +0 -40
  142. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_description.rb +0 -22
  143. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_footer.rb +0 -11
  144. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_header.rb +0 -11
  145. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_title.rb +0 -22
  146. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_trigger.rb +0 -50
  147. data/lib/shadcn_phlexcomponents/components/avatar/avatar.rb +0 -31
  148. data/lib/shadcn_phlexcomponents/components/avatar/avatar_fallback.rb +0 -21
  149. data/lib/shadcn_phlexcomponents/components/avatar/avatar_image.rb +0 -19
  150. data/lib/shadcn_phlexcomponents/components/badge/badge.rb +0 -30
  151. data/lib/shadcn_phlexcomponents/components/breadcrumb/breadcrumb.rb +0 -53
  152. data/lib/shadcn_phlexcomponents/components/breadcrumb/breadcrumb_ellipsis.rb +0 -23
  153. data/lib/shadcn_phlexcomponents/components/breadcrumb/breadcrumb_item.rb +0 -11
  154. data/lib/shadcn_phlexcomponents/components/breadcrumb/breadcrumb_link.rb +0 -7
  155. data/lib/shadcn_phlexcomponents/components/breadcrumb/breadcrumb_page.rb +0 -21
  156. data/lib/shadcn_phlexcomponents/components/breadcrumb/breadcrumb_separator.rb +0 -26
  157. data/lib/shadcn_phlexcomponents/components/button/button.rb +0 -53
  158. data/lib/shadcn_phlexcomponents/components/card/card.rb +0 -31
  159. data/lib/shadcn_phlexcomponents/components/card/card_content.rb +0 -11
  160. data/lib/shadcn_phlexcomponents/components/card/card_description.rb +0 -11
  161. data/lib/shadcn_phlexcomponents/components/card/card_footer.rb +0 -11
  162. data/lib/shadcn_phlexcomponents/components/card/card_header.rb +0 -11
  163. data/lib/shadcn_phlexcomponents/components/card/card_title.rb +0 -11
  164. data/lib/shadcn_phlexcomponents/components/collapsible/collapsible.rb +0 -31
  165. data/lib/shadcn_phlexcomponents/components/collapsible/collapsible_content.rb +0 -24
  166. data/lib/shadcn_phlexcomponents/components/collapsible/collapsible_trigger.rb +0 -50
  167. data/lib/shadcn_phlexcomponents/components/date_picker/date_picker.rb +0 -87
  168. data/lib/shadcn_phlexcomponents/components/date_picker/date_picker_content.rb +0 -45
  169. data/lib/shadcn_phlexcomponents/components/date_picker/date_picker_trigger.rb +0 -64
  170. data/lib/shadcn_phlexcomponents/components/date_range_picker/date_range_picker.rb +0 -105
  171. data/lib/shadcn_phlexcomponents/components/date_range_picker/date_range_picker_content.rb +0 -9
  172. data/lib/shadcn_phlexcomponents/components/date_range_picker/date_range_picker_trigger.rb +0 -9
  173. data/lib/shadcn_phlexcomponents/components/dialog/dialog.rb +0 -52
  174. data/lib/shadcn_phlexcomponents/components/dialog/dialog_close.rb +0 -42
  175. data/lib/shadcn_phlexcomponents/components/dialog/dialog_content.rb +0 -54
  176. data/lib/shadcn_phlexcomponents/components/dialog/dialog_description.rb +0 -22
  177. data/lib/shadcn_phlexcomponents/components/dialog/dialog_footer.rb +0 -11
  178. data/lib/shadcn_phlexcomponents/components/dialog/dialog_header.rb +0 -11
  179. data/lib/shadcn_phlexcomponents/components/dialog/dialog_title.rb +0 -22
  180. data/lib/shadcn_phlexcomponents/components/dialog/dialog_trigger.rb +0 -50
  181. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu.rb +0 -50
  182. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu_content.rb +0 -52
  183. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu_item.rb +0 -56
  184. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu_item_to.rb +0 -28
  185. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu_label.rb +0 -11
  186. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu_separator.rb +0 -20
  187. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu_trigger.rb +0 -57
  188. data/lib/shadcn_phlexcomponents/components/hover_card/hover_card.rb +0 -33
  189. data/lib/shadcn_phlexcomponents/components/hover_card/hover_card_content.rb +0 -32
  190. data/lib/shadcn_phlexcomponents/components/hover_card/hover_card_trigger.rb +0 -44
  191. data/lib/shadcn_phlexcomponents/components/input/input.rb +0 -32
  192. data/lib/shadcn_phlexcomponents/components/label/label.rb +0 -14
  193. data/lib/shadcn_phlexcomponents/components/pagination/pagination.rb +0 -38
  194. data/lib/shadcn_phlexcomponents/components/pagination/pagination_ellipsis.rb +0 -24
  195. data/lib/shadcn_phlexcomponents/components/pagination/pagination_link.rb +0 -34
  196. data/lib/shadcn_phlexcomponents/components/pagination/pagination_next.rb +0 -32
  197. data/lib/shadcn_phlexcomponents/components/pagination/pagination_previous.rb +0 -32
  198. data/lib/shadcn_phlexcomponents/components/popover/popover.rb +0 -34
  199. data/lib/shadcn_phlexcomponents/components/popover/popover_content.rb +0 -40
  200. data/lib/shadcn_phlexcomponents/components/popover/popover_trigger.rb +0 -50
  201. data/lib/shadcn_phlexcomponents/components/radio_group/radio_group.rb +0 -88
  202. data/lib/shadcn_phlexcomponents/components/radio_group/radio_group_item.rb +0 -66
  203. data/lib/shadcn_phlexcomponents/components/select/select.rb +0 -194
  204. data/lib/shadcn_phlexcomponents/components/select/select_content.rb +0 -64
  205. data/lib/shadcn_phlexcomponents/components/select/select_group.rb +0 -23
  206. data/lib/shadcn_phlexcomponents/components/select/select_item.rb +0 -59
  207. data/lib/shadcn_phlexcomponents/components/select/select_label.rb +0 -24
  208. data/lib/shadcn_phlexcomponents/components/select/select_trigger.rb +0 -56
  209. data/lib/shadcn_phlexcomponents/components/sheet/sheet.rb +0 -53
  210. data/lib/shadcn_phlexcomponents/components/sheet/sheet_close.rb +0 -42
  211. data/lib/shadcn_phlexcomponents/components/sheet/sheet_content.rb +0 -65
  212. data/lib/shadcn_phlexcomponents/components/sheet/sheet_description.rb +0 -22
  213. data/lib/shadcn_phlexcomponents/components/sheet/sheet_footer.rb +0 -11
  214. data/lib/shadcn_phlexcomponents/components/sheet/sheet_header.rb +0 -11
  215. data/lib/shadcn_phlexcomponents/components/sheet/sheet_title.rb +0 -22
  216. data/lib/shadcn_phlexcomponents/components/sheet/sheet_trigger.rb +0 -50
  217. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar.rb +0 -108
  218. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_container.rb +0 -11
  219. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_content.rb +0 -11
  220. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_footer.rb +0 -11
  221. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_group.rb +0 -11
  222. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_group_content.rb +0 -11
  223. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_group_label.rb +0 -16
  224. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_header.rb +0 -11
  225. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_inset.rb +0 -15
  226. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_menu.rb +0 -11
  227. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_menu_button.rb +0 -61
  228. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_menu_item.rb +0 -9
  229. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_menu_sub.rb +0 -14
  230. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_menu_sub_button.rb +0 -48
  231. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_menu_sub_item.rb +0 -9
  232. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_trigger.rb +0 -40
  233. data/lib/shadcn_phlexcomponents/components/switch/switch.rb +0 -66
  234. data/lib/shadcn_phlexcomponents/components/table/table.rb +0 -75
  235. data/lib/shadcn_phlexcomponents/components/table/table_body.rb +0 -11
  236. data/lib/shadcn_phlexcomponents/components/table/table_caption.rb +0 -11
  237. data/lib/shadcn_phlexcomponents/components/table/table_cell.rb +0 -11
  238. data/lib/shadcn_phlexcomponents/components/table/table_footer.rb +0 -11
  239. data/lib/shadcn_phlexcomponents/components/table/table_head.rb +0 -14
  240. data/lib/shadcn_phlexcomponents/components/table/table_header.rb +0 -11
  241. data/lib/shadcn_phlexcomponents/components/table/table_row.rb +0 -11
  242. data/lib/shadcn_phlexcomponents/components/tabs/tabs.rb +0 -38
  243. data/lib/shadcn_phlexcomponents/components/tabs/tabs_content.rb +0 -35
  244. data/lib/shadcn_phlexcomponents/components/tabs/tabs_list.rb +0 -23
  245. data/lib/shadcn_phlexcomponents/components/tabs/tabs_trigger.rb +0 -45
  246. data/lib/shadcn_phlexcomponents/components/textarea/textarea.rb +0 -29
  247. data/lib/shadcn_phlexcomponents/components/toast/toast.rb +0 -101
  248. data/lib/shadcn_phlexcomponents/components/toast/toast_action.rb +0 -39
  249. data/lib/shadcn_phlexcomponents/components/toast/toast_action_to.rb +0 -28
  250. data/lib/shadcn_phlexcomponents/components/toast/toast_content.rb +0 -11
  251. data/lib/shadcn_phlexcomponents/components/toast/toast_description.rb +0 -11
  252. data/lib/shadcn_phlexcomponents/components/toast/toast_title.rb +0 -11
  253. data/lib/shadcn_phlexcomponents/components/tooltip/tooltip.rb +0 -34
  254. data/lib/shadcn_phlexcomponents/components/tooltip/tooltip_content.rb +0 -39
  255. data/lib/shadcn_phlexcomponents/components/tooltip/tooltip_trigger.rb +0 -48
  256. /data/lib/shadcn_phlexcomponents/components/{theme_switcher/theme_switcher.rb → theme_switcher.rb} +0 -0
@@ -0,0 +1,544 @@
1
+ import { Command } from '../controllers/command_controller'
2
+ import {
3
+ Combobox,
4
+ ComboboxController,
5
+ } from '../controllers/combobox_controller'
6
+ import { getNextEnabledIndex, getPreviousEnabledIndex } from '.'
7
+
8
+ const scrollToItem = (controller: Command | Combobox, index: number) => {
9
+ const item = controller.filteredItems[index]
10
+ const itemRect = item.getBoundingClientRect()
11
+ const listContainerRect =
12
+ controller.listContainerTarget.getBoundingClientRect()
13
+ let newScrollTop = null as number | null
14
+
15
+ const maxScrollTop =
16
+ controller.listContainerTarget.scrollHeight -
17
+ controller.listContainerTarget.clientHeight
18
+
19
+ // scroll to bottom
20
+ if (itemRect.bottom - listContainerRect.bottom > 0) {
21
+ if (index === controller.filteredItems.length - 1) {
22
+ newScrollTop = maxScrollTop
23
+ } else {
24
+ newScrollTop =
25
+ controller.listContainerTarget.scrollTop +
26
+ (itemRect.bottom - listContainerRect.bottom)
27
+ }
28
+ } else if (listContainerRect.top - itemRect.top > 0) {
29
+ // scroll to top
30
+ if (index === 0) {
31
+ newScrollTop = 0
32
+ } else {
33
+ newScrollTop =
34
+ controller.listContainerTarget.scrollTop -
35
+ (listContainerRect.top - itemRect.top)
36
+ }
37
+ }
38
+
39
+ if (newScrollTop !== null) {
40
+ controller.scrollingViaKeyboard = true
41
+
42
+ if (newScrollTop >= 0 && newScrollTop <= maxScrollTop) {
43
+ controller.listContainerTarget.scrollTop = newScrollTop
44
+ }
45
+
46
+ // Clear the flag after scroll settles
47
+ clearTimeout(controller.keyboardScrollTimeout)
48
+ controller.keyboardScrollTimeout = window.setTimeout(() => {
49
+ controller.scrollingViaKeyboard = false
50
+ }, 200)
51
+ }
52
+ }
53
+
54
+ const highlightItem = (
55
+ controller: Command | Combobox,
56
+ event: MouseEvent | KeyboardEvent | null = null,
57
+ index: number | null = null,
58
+ ) => {
59
+ if (event !== null) {
60
+ if (event instanceof KeyboardEvent) {
61
+ const key = event.key
62
+ const item = controller.filteredItems.find(
63
+ (i) => i.dataset.highlighted === 'true',
64
+ )
65
+
66
+ if (item) {
67
+ const index = controller.filteredItems.indexOf(item)
68
+
69
+ let newIndex = 0
70
+ if (key === 'ArrowUp') {
71
+ newIndex = getPreviousEnabledIndex({
72
+ items: controller.filteredItems,
73
+ currentIndex: index,
74
+ filterFn: (item: HTMLElement) =>
75
+ item.dataset.disabled === undefined,
76
+ wrapAround: false,
77
+ })
78
+ } else {
79
+ newIndex = getNextEnabledIndex({
80
+ items: controller.filteredItems,
81
+ currentIndex: index,
82
+ filterFn: (item: HTMLElement) =>
83
+ item.dataset.disabled === undefined,
84
+ wrapAround: false,
85
+ })
86
+ }
87
+
88
+ controller.highlightItemByIndex(newIndex)
89
+ controller.scrollToItem(newIndex)
90
+ } else {
91
+ if (key === 'ArrowUp') {
92
+ controller.highlightItemByIndex(controller.filteredItems.length - 1)
93
+ } else {
94
+ controller.highlightItemByIndex(0)
95
+ }
96
+ }
97
+ } else {
98
+ // mouse event
99
+ if (controller.scrollingViaKeyboard) {
100
+ event.stopImmediatePropagation()
101
+ return
102
+ } else {
103
+ const item = event.currentTarget as HTMLElement
104
+ const index = controller.filteredItems.indexOf(item)
105
+ controller.highlightItemByIndex(index)
106
+ }
107
+ }
108
+ } else if (index !== null) {
109
+ controller.highlightItemByIndex(index)
110
+ }
111
+ }
112
+
113
+ const highlightItemByIndex = (
114
+ controller: Command | Combobox,
115
+ index: number,
116
+ ) => {
117
+ controller.filteredItems.forEach((item, i) => {
118
+ if (i === index) {
119
+ item.dataset.highlighted = 'true'
120
+ } else {
121
+ item.dataset.highlighted = 'false'
122
+ }
123
+ })
124
+ }
125
+
126
+ const filteredItemsChanged = (
127
+ controller: Command | Combobox,
128
+ filteredItemIndexes: number[],
129
+ ) => {
130
+ if (controller.orderedItems) {
131
+ const filteredItems = filteredItemIndexes.map(
132
+ (i) => controller.orderedItems[i],
133
+ )
134
+
135
+ // 1. Toggle visibility of items
136
+ controller.orderedItems.forEach((item) => {
137
+ if (filteredItems.includes(item)) {
138
+ item.ariaHidden = 'false'
139
+ item.classList.remove('hidden')
140
+ } else {
141
+ item.ariaHidden = 'true'
142
+ item.classList.add('hidden')
143
+ }
144
+ })
145
+
146
+ // 2. Get groups based on order of filtered items
147
+ const groupIds = filteredItems.map((item) => item.dataset.groupId)
148
+ const uniqueGroupIds = [...new Set(groupIds)].filter((groupId) => !!groupId)
149
+ const orderedGroups = uniqueGroupIds.map((groupId) => {
150
+ return controller.listTarget.querySelector(
151
+ `[aria-labelledby=${groupId}]`,
152
+ ) as HTMLElement
153
+ })
154
+
155
+ // 3. Append items and groups based on filtered items
156
+ const appendedGroupIds = [] as string[]
157
+
158
+ filteredItems.forEach((item) => {
159
+ const groupId = item.dataset.groupId
160
+
161
+ if (groupId) {
162
+ const group = orderedGroups.find(
163
+ (g) => g.getAttribute('aria-labelledby') === groupId,
164
+ )
165
+
166
+ if (group) {
167
+ group.appendChild(item)
168
+
169
+ if (!appendedGroupIds.includes(groupId)) {
170
+ controller.listTarget.appendChild(group)
171
+ appendedGroupIds.push(groupId)
172
+ }
173
+ }
174
+ } else {
175
+ controller.listTarget.appendChild(item)
176
+ }
177
+ })
178
+
179
+ // 4. Toggle visibility of groups
180
+ controller.groupTargets.forEach((group) => {
181
+ const itemsCount = group.querySelectorAll(
182
+ `[data-${controller.identifier}-target=item][aria-hidden=false]`,
183
+ ).length
184
+ if (itemsCount > 0) {
185
+ group.classList.remove('hidden')
186
+ } else {
187
+ group.classList.add('hidden')
188
+ }
189
+ })
190
+
191
+ // 5. Move remote items to the end
192
+ const remoteItems = Array.from(
193
+ controller.element.querySelectorAll(
194
+ `[data-shadcn-phlexcomponents="${controller.identifier}-item"][data-remote='true']`,
195
+ ),
196
+ )
197
+ remoteItems.forEach((i) => {
198
+ const isInsideGroup =
199
+ i.parentElement?.dataset?.shadcnPhlexcomponents ===
200
+ `${controller.identifier}-group`
201
+
202
+ if (isInsideGroup) {
203
+ const isRemoteGroup = i.parentElement.dataset.remote === 'true'
204
+
205
+ // Move group to last
206
+ if (isRemoteGroup) {
207
+ controller.listTarget.appendChild(i.parentElement)
208
+ }
209
+ } else {
210
+ // Move item to last
211
+ controller.listTarget.appendChild(i)
212
+ }
213
+ })
214
+
215
+ // 6. Assign filteredItems based on the order it is displayed in the DOM
216
+ controller.filteredItems = Array.from(
217
+ controller.listTarget.querySelectorAll(
218
+ `[data-${controller.identifier}-target=item][aria-hidden=false]`,
219
+ ),
220
+ )
221
+
222
+ // 7. Highlight first item
223
+ controller.highlightItemByIndex(0)
224
+
225
+ // 8. Toggle visibility of empty
226
+ if (controller.isDirty && !controller.isLoading) {
227
+ if (controller.filteredItems.length > 0) {
228
+ hideEmpty(controller)
229
+ } else {
230
+ showEmpty(controller)
231
+ }
232
+ }
233
+ }
234
+ }
235
+
236
+ const setItemsGroupId = (controller: Command | Combobox) => {
237
+ controller.itemTargets.forEach((item) => {
238
+ const parent = item.parentElement
239
+
240
+ if (parent?.dataset[`${controller.identifier}Target`] === 'group') {
241
+ item.dataset.groupId = parent.getAttribute('aria-labelledby') as string
242
+ }
243
+ })
244
+ }
245
+
246
+ const search = (controller: Command | Combobox, event: InputEvent) => {
247
+ const input = event.target as HTMLInputElement
248
+ const value = input.value.trim()
249
+
250
+ if (value.length > 0) {
251
+ const results = controller.fuse.search(value)
252
+
253
+ // Don't show disabled items when filtering
254
+ let filteredItemIndexes = results.map((result) => result.refIndex)
255
+ filteredItemIndexes = filteredItemIndexes.filter((index) => {
256
+ const item = controller.orderedItems[index]
257
+ return item.dataset.disabled === undefined
258
+ })
259
+
260
+ if (controller.searchPath) {
261
+ hideSelectedRemoteItems(controller)
262
+ showLoading(controller)
263
+ hideList(controller)
264
+ hideEmpty(controller)
265
+ controller.filteredItemIndexesValue = filteredItemIndexes
266
+ performRemoteSearch(controller, value)
267
+ } else {
268
+ controller.filteredItemIndexesValue = filteredItemIndexes
269
+ }
270
+ } else {
271
+ if (controller.searchPath) {
272
+ showSelectedRemoteItems(controller)
273
+ }
274
+ controller.filteredItemIndexesValue = Array.from(
275
+ { length: controller.orderedItems.length },
276
+ (_, i) => i,
277
+ )
278
+ }
279
+ }
280
+
281
+ const performRemoteSearch = async (
282
+ controller: Command | Combobox,
283
+ query: string,
284
+ ) => {
285
+ // Cancel previous request
286
+ if (controller.abortController) {
287
+ controller.abortController.abort()
288
+ }
289
+
290
+ // Create new abort controller
291
+ controller.abortController = new AbortController()
292
+
293
+ try {
294
+ const response = await fetch(`${controller.searchPath}?q=${query}`, {
295
+ signal: controller.abortController.signal,
296
+ headers: {
297
+ Accept: 'application/json',
298
+ 'Content-Type': 'application/json',
299
+ },
300
+ })
301
+
302
+ if (!response.ok) {
303
+ throw new Error(`HTTP error! status: ${response.status}`)
304
+ }
305
+
306
+ const data = await response.json()
307
+ renderRemoteResults(controller, data)
308
+ showList(controller)
309
+ } catch (error) {
310
+ if (error instanceof Error && error.name !== 'AbortError') {
311
+ console.error('Remote search error:', error)
312
+ showError(controller)
313
+ }
314
+ } finally {
315
+ hideLoading(controller)
316
+ }
317
+ }
318
+
319
+ const renderRemoteResults = (
320
+ controller: Command | Combobox,
321
+ data: { html: string; group?: string }[],
322
+ ) => {
323
+ console.log('data', data)
324
+ data.forEach((item) => {
325
+ const tempDiv = document.createElement('div')
326
+ tempDiv.innerHTML = item.html
327
+ const itemEl = tempDiv.firstElementChild as HTMLElement
328
+ itemEl.dataset.remote = 'true'
329
+ itemEl.ariaHidden = 'false'
330
+
331
+ if (controller instanceof ComboboxController) {
332
+ // Don't append same item
333
+ if (controller.selectedValue === itemEl.dataset.value) {
334
+ const item = controller.itemTargets.find(
335
+ (i) => i.dataset.value === controller.selectedValue,
336
+ )
337
+ if (item) {
338
+ item.classList.remove('hidden')
339
+ item.ariaHidden = 'false'
340
+ }
341
+
342
+ return
343
+ }
344
+ }
345
+
346
+ const group = item.group
347
+
348
+ if (group) {
349
+ const groupEl = controller.groupTargets.find((g) => {
350
+ const label = g.querySelector(
351
+ `[data-shadcn-phlexcomponents="${controller.identifier}-label"]`,
352
+ ) as HTMLElement
353
+ if (!label) return false
354
+ return label.textContent === group
355
+ })
356
+
357
+ if (groupEl) {
358
+ groupEl.classList.remove('hidden')
359
+ groupEl.append(itemEl)
360
+ } else {
361
+ const template = controller.element.querySelector(
362
+ 'template',
363
+ ) as HTMLTemplateElement
364
+
365
+ const clone = template.content.cloneNode(true) as HTMLElement
366
+ const groupEl = clone.querySelector(
367
+ `[data-shadcn-phlexcomponents="${controller.identifier}-group"]`,
368
+ ) as HTMLElement
369
+ const groupId = crypto.randomUUID()
370
+ const label = clone.querySelector(
371
+ `[data-shadcn-phlexcomponents="${controller.identifier}-label"]`,
372
+ ) as HTMLElement
373
+ label.textContent = group
374
+ label.id = groupId
375
+ groupEl.setAttribute('aria-labelledby', groupId)
376
+ groupEl.dataset.remote = 'true'
377
+ groupEl.append(itemEl)
378
+ controller.listTarget.append(clone)
379
+ }
380
+ } else {
381
+ controller.listTarget.append(itemEl)
382
+ }
383
+ })
384
+
385
+ // Update filtered items for keyboard navigation
386
+ controller.filteredItems = Array.from(
387
+ controller.listTarget.querySelectorAll(
388
+ `[data-${controller.identifier}-target="item"][aria-hidden=false]`,
389
+ ),
390
+ )
391
+
392
+ controller.highlightItemByIndex(0)
393
+
394
+ console.log('controller.filteredItems', controller.filteredItems)
395
+ if (controller.filteredItems.length > 0) {
396
+ hideEmpty(controller)
397
+ } else {
398
+ showEmpty(controller)
399
+ }
400
+ }
401
+
402
+ const clearRemoteResults = (controller: Command | Combobox) => {
403
+ const remoteGroups = Array.from(
404
+ controller.element.querySelectorAll(
405
+ `[data-shadcn-phlexcomponents="${controller.identifier}-group"][data-remote='true']`,
406
+ ),
407
+ )
408
+
409
+ remoteGroups.forEach((g) => {
410
+ const containsSelected = g.querySelector('[aria-selected="true"]')
411
+
412
+ if (!containsSelected) {
413
+ g.remove()
414
+ }
415
+ })
416
+
417
+ const remoteItems = Array.from(
418
+ controller.element.querySelectorAll(
419
+ `[data-shadcn-phlexcomponents="${controller.identifier}-item"][data-remote='true']:not([aria-selected="true"])`,
420
+ ),
421
+ )
422
+
423
+ remoteItems.forEach((i) => i.remove())
424
+ }
425
+
426
+ const resetState = (controller: Command | Combobox) => {
427
+ controller.searchInputTarget.value = ''
428
+
429
+ if (controller.searchPath) {
430
+ clearRemoteResults(controller)
431
+ showSelectedRemoteItems(controller)
432
+ }
433
+
434
+ controller.filteredItemIndexesValue = Array.from(
435
+ { length: controller.orderedItems.length },
436
+ (_, i) => i,
437
+ )
438
+ }
439
+
440
+ const showLoading = (controller: Command | Combobox) => {
441
+ controller.isLoading = true
442
+ controller.loadingTarget.classList.remove('hidden')
443
+ }
444
+
445
+ const hideLoading = (controller: Command | Combobox) => {
446
+ controller.isLoading = false
447
+ controller.loadingTarget.classList.add('hidden')
448
+ }
449
+
450
+ const showList = (controller: Command | Combobox) => {
451
+ controller.listTarget.classList.remove('hidden')
452
+ }
453
+
454
+ const hideList = (controller: Command | Combobox) => {
455
+ controller.listTarget.classList.add('hidden')
456
+ }
457
+
458
+ const showError = (controller: Command | Combobox) => {
459
+ controller.errorTarget.classList.remove('hidden')
460
+ }
461
+
462
+ const hideError = (controller: Command | Combobox) => {
463
+ controller.errorTarget.classList.add('hidden')
464
+ }
465
+
466
+ const showEmpty = (controller: Command | Combobox) => {
467
+ controller.emptyTarget.classList.remove('hidden')
468
+ }
469
+
470
+ const hideEmpty = (controller: Command | Combobox) => {
471
+ controller.emptyTarget.classList.add('hidden')
472
+ }
473
+
474
+ const showSelectedRemoteItems = (controller: Command | Combobox) => {
475
+ const remoteItems = Array.from(
476
+ controller.element.querySelectorAll(
477
+ `[data-shadcn-phlexcomponents="${controller.identifier}-item"][data-remote='true']`,
478
+ ),
479
+ )
480
+
481
+ remoteItems.forEach((i) => {
482
+ const isInsideGroup =
483
+ i.parentElement?.dataset?.shadcnPhlexcomponents ===
484
+ `${controller.identifier}-group`
485
+
486
+ if (isInsideGroup) {
487
+ const isRemoteGroup = i.parentElement.dataset.remote === 'true'
488
+
489
+ if (isRemoteGroup) {
490
+ i.parentElement.classList.remove('hidden')
491
+ }
492
+ }
493
+
494
+ i.ariaHidden = 'false'
495
+ i.classList.remove('hidden')
496
+ })
497
+ }
498
+
499
+ const hideSelectedRemoteItems = (controller: Command | Combobox) => {
500
+ const remoteItems = Array.from(
501
+ controller.element.querySelectorAll(
502
+ `[data-shadcn-phlexcomponents="${controller.identifier}-item"][data-remote='true']`,
503
+ ),
504
+ )
505
+
506
+ remoteItems.forEach((i) => {
507
+ const isInsideGroup =
508
+ i.parentElement?.dataset?.shadcnPhlexcomponents ===
509
+ `${controller.identifier}-group`
510
+
511
+ if (isInsideGroup) {
512
+ const isRemoteGroup = i.parentElement.dataset.remote === 'true'
513
+
514
+ if (isRemoteGroup) {
515
+ i.parentElement.classList.add('hidden')
516
+ }
517
+ }
518
+
519
+ i.ariaHidden = 'true'
520
+ i.classList.add('hidden')
521
+ })
522
+ }
523
+
524
+ export {
525
+ scrollToItem,
526
+ highlightItem,
527
+ highlightItemByIndex,
528
+ filteredItemsChanged,
529
+ setItemsGroupId,
530
+ search,
531
+ performRemoteSearch,
532
+ clearRemoteResults,
533
+ resetState,
534
+ showLoading,
535
+ hideLoading,
536
+ showList,
537
+ hideList,
538
+ showError,
539
+ hideError,
540
+ showEmpty,
541
+ hideEmpty,
542
+ showSelectedRemoteItems,
543
+ hideSelectedRemoteItems,
544
+ }