playbook_ui 18.2.0 → 18.3.0.pre.alpha.PLAY3342advancedtablereactmulticolumnselectablerow21082

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 (466) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Components/CollapsibleTrail.tsx +8 -1
  3. data/app/pb_kits/playbook/pb_advanced_table/Components/RegularTableView.tsx +39 -8
  4. data/app/pb_kits/playbook/pb_advanced_table/Components/TableActionBar.tsx +62 -2
  5. data/app/pb_kits/playbook/pb_advanced_table/Components/VirtualizedTableView.tsx +24 -4
  6. data/app/pb_kits/playbook/pb_advanced_table/SubKits/TableHeader.tsx +105 -66
  7. data/app/pb_kits/playbook/pb_advanced_table/Utilities/RowUtils.ts +29 -0
  8. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +83 -5
  9. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +3 -0
  10. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.html.erb +1 -1
  11. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.rb +8 -0
  12. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +1100 -1
  13. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility.jsx +1 -1
  14. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility.md +5 -2
  15. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_custom.md +3 -1
  16. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_multi_select_all.jsx +82 -0
  17. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_multi_select_all.md +3 -0
  18. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_visibility_with_state.md +3 -1
  19. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_group_separators.jsx +67 -0
  20. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_group_separators.md +7 -0
  21. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_group_separators_rails.html.erb +78 -0
  22. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_group_separators_rails.md +3 -0
  23. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_selectable_rows.jsx +177 -58
  24. data/app/pb_kits/playbook/pb_advanced_table/docs/_playground.json +17 -2
  25. data/app/pb_kits/playbook/pb_advanced_table/docs/_playground.overrides.json +8 -1
  26. data/app/pb_kits/playbook/pb_advanced_table/docs/advanced_table_mock_data_group_separators.json +328 -0
  27. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +3 -0
  28. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +2 -0
  29. data/app/pb_kits/playbook/pb_advanced_table/index.js +39 -2
  30. data/app/pb_kits/playbook/pb_advanced_table/kit.schema.json +44 -16
  31. data/app/pb_kits/playbook/pb_advanced_table/table_body.html.erb +2 -2
  32. data/app/pb_kits/playbook/pb_advanced_table/table_body.rb +53 -23
  33. data/app/pb_kits/playbook/pb_advanced_table/table_header.rb +1 -1
  34. data/app/pb_kits/playbook/pb_advanced_table/table_row.html.erb +1 -1
  35. data/app/pb_kits/playbook/pb_advanced_table/table_row.rb +2 -0
  36. data/app/pb_kits/playbook/pb_avatar/_avatar.tsx +3 -1
  37. data/app/pb_kits/playbook/pb_avatar/kit.schema.json +23 -10
  38. data/app/pb_kits/playbook/pb_background/docs/_playground.json +1 -0
  39. data/app/pb_kits/playbook/pb_background/kit.schema.json +10 -3
  40. data/app/pb_kits/playbook/pb_badge/docs/_playground.json +2 -0
  41. data/app/pb_kits/playbook/pb_badge/kit.schema.json +27 -13
  42. data/app/pb_kits/playbook/pb_body/docs/_playground.json +3 -3
  43. data/app/pb_kits/playbook/pb_body/kit.schema.json +14 -5
  44. data/app/pb_kits/playbook/pb_button/_button.tsx +3 -1
  45. data/app/pb_kits/playbook/pb_button/button.rb +3 -0
  46. data/app/pb_kits/playbook/pb_button/button.test.js +154 -0
  47. data/app/pb_kits/playbook/pb_button/docs/_button_options_rails.md +5 -0
  48. data/app/pb_kits/playbook/pb_button/docs/_button_options_react.md +9 -0
  49. data/app/pb_kits/playbook/pb_button/docs/_playground.json +4 -2
  50. data/app/pb_kits/playbook/pb_button/docs/_playground.overrides.json +1 -1
  51. data/app/pb_kits/playbook/pb_button/docs/example.yml +1 -1
  52. data/app/pb_kits/playbook/pb_button/kit.schema.json +31 -27
  53. data/app/pb_kits/playbook/pb_caption/kit.schema.json +17 -7
  54. data/app/pb_kits/playbook/pb_card/docs/_playground.json +1 -0
  55. data/app/pb_kits/playbook/pb_card/kit.schema.json +2 -1
  56. data/app/pb_kits/playbook/pb_checkbox/docs/_playground.json +6 -1
  57. data/app/pb_kits/playbook/pb_checkbox/kit.schema.json +10 -5
  58. data/app/pb_kits/playbook/pb_collapsible/child_kits/CollapsibleMain.tsx +4 -4
  59. data/app/pb_kits/playbook/pb_collapsible/collapsible.test.js +81 -2
  60. data/app/pb_kits/playbook/pb_contact/_contact.tsx +4 -1
  61. data/app/pb_kits/playbook/pb_contact/docs/_playground.json +1 -0
  62. data/app/pb_kits/playbook/pb_contact/kit.schema.json +3 -1
  63. data/app/pb_kits/playbook/pb_copy_button/docs/_playground.json +5 -1
  64. data/app/pb_kits/playbook/pb_copy_button/kit.schema.json +10 -5
  65. data/app/pb_kits/playbook/pb_currency/docs/_playground.json +1 -1
  66. data/app/pb_kits/playbook/pb_currency/kit.schema.json +6 -2
  67. data/app/pb_kits/playbook/pb_dashboard_value/docs/_playground.json +2 -2
  68. data/app/pb_kits/playbook/pb_dashboard_value/kit.schema.json +4 -2
  69. data/app/pb_kits/playbook/pb_date/_date.tsx +3 -1
  70. data/app/pb_kits/playbook/pb_date/date.test.js +1 -1
  71. data/app/pb_kits/playbook/pb_date/docs/_playground.json +1 -0
  72. data/app/pb_kits/playbook/pb_date/kit.schema.json +17 -7
  73. data/app/pb_kits/playbook/pb_date_picker/date_picker.test.js +4 -0
  74. data/app/pb_kits/playbook/pb_date_picker/docs/_playground.json +4 -1
  75. data/app/pb_kits/playbook/pb_date_picker/kit.schema.json +32 -14
  76. data/app/pb_kits/playbook/pb_date_range_inline/_date_range_inline.tsx +3 -1
  77. data/app/pb_kits/playbook/pb_date_range_inline/docs/_playground.json +1 -0
  78. data/app/pb_kits/playbook/pb_date_range_inline/kit.schema.json +3 -1
  79. data/app/pb_kits/playbook/pb_date_range_stacked/_date_range_stacked.tsx +3 -1
  80. data/app/pb_kits/playbook/pb_date_range_stacked/kit.schema.json +1 -0
  81. data/app/pb_kits/playbook/pb_date_stacked/_date_stacked.tsx +4 -1
  82. data/app/pb_kits/playbook/pb_date_stacked/date_stacked.test.js +1 -0
  83. data/app/pb_kits/playbook/pb_date_stacked/kit.schema.json +1 -0
  84. data/app/pb_kits/playbook/pb_date_time/_date_time.tsx +3 -1
  85. data/app/pb_kits/playbook/pb_date_time/dateTime.test.js +1 -0
  86. data/app/pb_kits/playbook/pb_date_time/docs/_playground.json +2 -1
  87. data/app/pb_kits/playbook/pb_date_time/kit.schema.json +3 -1
  88. data/app/pb_kits/playbook/pb_date_time_stacked/_date_time_stacked.tsx +2 -1
  89. data/app/pb_kits/playbook/pb_date_time_stacked/docs/_playground.json +1 -0
  90. data/app/pb_kits/playbook/pb_date_time_stacked/kit.schema.json +3 -1
  91. data/app/pb_kits/playbook/pb_date_year_stacked/_date_year_stacked.tsx +3 -1
  92. data/app/pb_kits/playbook/pb_date_year_stacked/date_year_stacked.test.js +1 -0
  93. data/app/pb_kits/playbook/pb_date_year_stacked/kit.schema.json +1 -0
  94. data/app/pb_kits/playbook/pb_detail/docs/_playground.json +2 -1
  95. data/app/pb_kits/playbook/pb_detail/kit.schema.json +2 -1
  96. data/app/pb_kits/playbook/pb_dialog/docs/_playground.json +1 -1
  97. data/app/pb_kits/playbook/pb_dialog/kit.schema.json +22 -9
  98. data/app/pb_kits/playbook/pb_distribution_bar/docs/_playground.json +2 -2
  99. data/app/pb_kits/playbook/pb_distribution_bar/kit.schema.json +2 -1
  100. data/app/pb_kits/playbook/pb_draggable/utilities/touchDrag.ts +10 -0
  101. data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +112 -0
  102. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +121 -15
  103. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_draggable_pills.html.erb +51 -0
  104. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_draggable_pills.jsx +62 -0
  105. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_draggable_pills_rails.md +9 -0
  106. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_draggable_pills_react.md +9 -0
  107. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_form.jsx +50 -0
  108. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_form_react.md +9 -0
  109. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_option_creation.html.erb +16 -0
  110. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_option_creation.jsx +27 -0
  111. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_option_creation_rails.md +9 -0
  112. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_option_creation_react.md +9 -0
  113. data/app/pb_kits/playbook/pb_dropdown/docs/_playground.json +39 -3
  114. data/app/pb_kits/playbook/pb_dropdown/docs/_playground.overrides.json +13 -2
  115. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +5 -0
  116. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +4 -1
  117. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +2 -2
  118. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +24 -1
  119. data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +235 -1
  120. data/app/pb_kits/playbook/pb_dropdown/dropdown_creation.test.jsx +414 -0
  121. data/app/pb_kits/playbook/pb_dropdown/dropdown_form.test.jsx +173 -0
  122. data/app/pb_kits/playbook/pb_dropdown/dropdown_index.test.js +545 -0
  123. data/app/pb_kits/playbook/pb_dropdown/hooks/useHandleOnKeydown.tsx +22 -5
  124. data/app/pb_kits/playbook/pb_dropdown/index.js +260 -37
  125. data/app/pb_kits/playbook/pb_dropdown/keyboard_accessibility.js +3 -1
  126. data/app/pb_kits/playbook/pb_dropdown/kit.schema.json +59 -9
  127. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownContainer.tsx +43 -25
  128. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownFormFields.tsx +83 -0
  129. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownOption.tsx +13 -5
  130. data/app/pb_kits/playbook/pb_dropdown/subcomponents/MultiSelectTriggerDisplay.tsx +336 -31
  131. data/app/pb_kits/playbook/pb_dropdown/utilities/optionCreation.ts +21 -0
  132. data/app/pb_kits/playbook/pb_dropdown/utilities/pillReorder.js +281 -0
  133. data/app/pb_kits/playbook/pb_dropdown/utilities/quickpickDateSync.test.ts +24 -0
  134. data/app/pb_kits/playbook/pb_dropdown/utilities/usePillDropIndicator.ts +144 -0
  135. data/app/pb_kits/playbook/pb_empty_state/docs/_playground.json +1 -1
  136. data/app/pb_kits/playbook/pb_empty_state/kit.schema.json +2 -1
  137. data/app/pb_kits/playbook/pb_file_upload/docs/_playground.json +3 -2
  138. data/app/pb_kits/playbook/pb_file_upload/kit.schema.json +8 -3
  139. data/app/pb_kits/playbook/pb_filter/Filter/SortMenu.tsx +8 -1
  140. data/app/pb_kits/playbook/pb_filter/docs/_filter_one_click.html.erb +38 -0
  141. data/app/pb_kits/playbook/pb_filter/docs/_filter_one_click.jsx +69 -0
  142. data/app/pb_kits/playbook/pb_filter/docs/_filter_one_click.md +1 -0
  143. data/app/pb_kits/playbook/pb_filter/docs/example.yml +2 -0
  144. data/app/pb_kits/playbook/pb_filter/docs/index.js +2 -1
  145. data/app/pb_kits/playbook/pb_filter/filter.html.erb +4 -4
  146. data/app/pb_kits/playbook/pb_filter/filter.rb +28 -0
  147. data/app/pb_kits/playbook/pb_filter/filter.test.js +41 -0
  148. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_playground.json +4 -4
  149. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/fixed_confirmation_toast.test.js +1 -2
  150. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/kit.schema.json +6 -3
  151. data/app/pb_kits/playbook/pb_flex/docs/_playground.json +3 -0
  152. data/app/pb_kits/playbook/pb_flex/kit.schema.json +27 -12
  153. data/app/pb_kits/playbook/pb_form/docs/_description_react.md +3 -0
  154. data/app/pb_kits/playbook/pb_form/docs/_form_default_values.jsx +898 -0
  155. data/app/pb_kits/playbook/pb_form/docs/_form_default_values_react.md +3 -0
  156. data/app/pb_kits/playbook/pb_form/docs/_form_defaults.jsx +879 -0
  157. data/app/pb_kits/playbook/pb_form/docs/_form_defaults_react.md +29 -0
  158. data/app/pb_kits/playbook/pb_form/docs/_form_loading.jsx +75 -0
  159. data/app/pb_kits/playbook/pb_form/docs/{_form_loading.md → _form_loading_rails.md} +1 -0
  160. data/app/pb_kits/playbook/pb_form/docs/_form_loading_react.md +1 -0
  161. data/app/pb_kits/playbook/pb_form/docs/_form_model_values.jsx +147 -0
  162. data/app/pb_kits/playbook/pb_form/docs/_form_model_values_react.md +16 -0
  163. data/app/pb_kits/playbook/pb_form/docs/_form_multi_column.jsx +164 -0
  164. data/app/pb_kits/playbook/pb_form/docs/_form_multi_column_react.md +3 -0
  165. data/app/pb_kits/playbook/pb_form/docs/_form_validation.jsx +1057 -0
  166. data/app/pb_kits/playbook/pb_form/docs/_form_validation_react.md +26 -0
  167. data/app/pb_kits/playbook/pb_form/docs/example.yml +7 -0
  168. data/app/pb_kits/playbook/pb_form/form.rb +1 -1
  169. data/app/pb_kits/playbook/pb_form/kit.schema.json +1 -6
  170. data/app/pb_kits/playbook/pb_form/pb_form_validation.js +1 -0
  171. data/app/pb_kits/playbook/pb_form_group/_form_group.scss +1 -4
  172. data/app/pb_kits/playbook/pb_form_pill/docs/_playground.json +3 -0
  173. data/app/pb_kits/playbook/pb_form_pill/kit.schema.json +6 -3
  174. data/app/pb_kits/playbook/pb_full_screen/_full_screen.scss +6 -0
  175. data/app/pb_kits/playbook/pb_full_screen/_full_screen.tsx +11 -2
  176. data/app/pb_kits/playbook/pb_full_screen/docs/_full_screen_advanced_table_sticky_header.jsx +73 -0
  177. data/app/pb_kits/playbook/pb_full_screen/docs/_full_screen_advanced_table_sticky_header.md +1 -0
  178. data/app/pb_kits/playbook/pb_full_screen/docs/_playground.json +3 -3
  179. data/app/pb_kits/playbook/pb_full_screen/docs/example.yml +1 -0
  180. data/app/pb_kits/playbook/pb_full_screen/docs/index.js +1 -0
  181. data/app/pb_kits/playbook/pb_full_screen/full_screen.test.jsx +4 -2
  182. data/app/pb_kits/playbook/pb_full_screen/kit.schema.json +8 -4
  183. data/app/pb_kits/playbook/pb_hashtag/docs/_playground.json +2 -2
  184. data/app/pb_kits/playbook/pb_hashtag/kit.schema.json +2 -1
  185. data/app/pb_kits/playbook/pb_highlight/kit.schema.json +2 -1
  186. data/app/pb_kits/playbook/pb_icon/_icon.tsx +6 -1
  187. data/app/pb_kits/playbook/pb_icon/docs/_playground.json +2 -2
  188. data/app/pb_kits/playbook/pb_icon/kit.schema.json +31 -13
  189. data/app/pb_kits/playbook/pb_icon_circle/_icon_circle.tsx +3 -1
  190. data/app/pb_kits/playbook/pb_icon_circle/docs/_playground.json +2 -2
  191. data/app/pb_kits/playbook/pb_icon_circle/kit.schema.json +3 -1
  192. data/app/pb_kits/playbook/pb_icon_stat_value/docs/_playground.json +3 -2
  193. data/app/pb_kits/playbook/pb_icon_stat_value/kit.schema.json +4 -2
  194. data/app/pb_kits/playbook/pb_image/_image.tsx +5 -3
  195. data/app/pb_kits/playbook/pb_image/docs/_playground.json +27 -27
  196. data/app/pb_kits/playbook/pb_image/kit.schema.json +57 -24
  197. data/app/pb_kits/playbook/pb_label_pill/_label_pill.tsx +3 -1
  198. data/app/pb_kits/playbook/pb_label_pill/kit.schema.json +1 -0
  199. data/app/pb_kits/playbook/pb_label_value/docs/_playground.json +2 -2
  200. data/app/pb_kits/playbook/pb_label_value/kit.schema.json +2 -1
  201. data/app/pb_kits/playbook/pb_layout/docs/_playground.json +2 -2
  202. data/app/pb_kits/playbook/pb_layout/kit.schema.json +43 -20
  203. data/app/pb_kits/playbook/pb_legend/_legend.tsx +3 -1
  204. data/app/pb_kits/playbook/pb_legend/docs/_playground.json +2 -2
  205. data/app/pb_kits/playbook/pb_legend/kit.schema.json +3 -1
  206. data/app/pb_kits/playbook/pb_lightbox/_lightbox.tsx +2 -2
  207. data/app/pb_kits/playbook/pb_lightbox/kit.schema.json +84 -0
  208. data/app/pb_kits/playbook/pb_link/docs/_playground.json +6 -3
  209. data/app/pb_kits/playbook/pb_link/kit.schema.json +14 -6
  210. data/app/pb_kits/playbook/pb_list/_list_item.tsx +6 -3
  211. data/app/pb_kits/playbook/pb_list/docs/_playground.json +2 -2
  212. data/app/pb_kits/playbook/pb_list/kit.schema.json +2 -1
  213. data/app/pb_kits/playbook/pb_loading_inline/docs/_playground.json +0 -1
  214. data/app/pb_kits/playbook/pb_loading_inline/kit.schema.json +4 -1
  215. data/app/pb_kits/playbook/pb_map/docs/_map_default.md +22 -3
  216. data/app/pb_kits/playbook/pb_map/kit.schema.json +4 -6
  217. data/app/pb_kits/playbook/pb_message/kit.schema.json +2 -1
  218. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +4 -1
  219. data/app/pb_kits/playbook/pb_multiple_users/docs/_playground.json +2 -1
  220. data/app/pb_kits/playbook/pb_multiple_users/kit.schema.json +19 -11
  221. data/app/pb_kits/playbook/pb_multiple_users_stacked/docs/_playground.json +1 -0
  222. data/app/pb_kits/playbook/pb_multiple_users_stacked/kit.schema.json +2 -1
  223. data/app/pb_kits/playbook/pb_nav/_nav_item.test.js +17 -0
  224. data/app/pb_kits/playbook/pb_nav/docs/_playground.json +2 -0
  225. data/app/pb_kits/playbook/pb_nav/kit.schema.json +4 -2
  226. data/app/pb_kits/playbook/pb_online_status/_online_status.tsx +3 -1
  227. data/app/pb_kits/playbook/pb_online_status/kit.schema.json +1 -0
  228. data/app/pb_kits/playbook/pb_overlay/docs/_playground.json +3 -3
  229. data/app/pb_kits/playbook/pb_overlay/kit.schema.json +22 -12
  230. data/app/pb_kits/playbook/pb_pagination/kit.schema.json +6 -3
  231. data/app/pb_kits/playbook/pb_passphrase/docs/_playground.json +6 -4
  232. data/app/pb_kits/playbook/pb_passphrase/kit.schema.json +10 -5
  233. data/app/pb_kits/playbook/pb_pb_bar_graph/pbbargraph.test.jsx +1 -2
  234. data/app/pb_kits/playbook/pb_pb_circle_chart/pb_circle_chart.test.jsx +1 -2
  235. data/app/pb_kits/playbook/pb_pb_gauge_chart/kit.schema.json +2 -1
  236. data/app/pb_kits/playbook/pb_pb_gauge_chart/pb_gauge_chart.test.jsx +1 -2
  237. data/app/pb_kits/playbook/pb_pb_line_graph/pb_line_graph.test.jsx +1 -2
  238. data/app/pb_kits/playbook/pb_person/_person.tsx +3 -1
  239. data/app/pb_kits/playbook/pb_person/kit.schema.json +1 -0
  240. data/app/pb_kits/playbook/pb_person_contact/docs/_playground.json +3 -3
  241. data/app/pb_kits/playbook/pb_person_contact/kit.schema.json +2 -1
  242. data/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.scss +3 -7
  243. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_validation.html.erb +1 -1
  244. data/app/pb_kits/playbook/pb_phone_number_input/docs/_playground.json +3 -0
  245. data/app/pb_kits/playbook/pb_phone_number_input/img/flags.webp +0 -0
  246. data/app/pb_kits/playbook/pb_phone_number_input/img/flags@2x.webp +0 -0
  247. data/app/pb_kits/playbook/pb_phone_number_input/img/globe.webp +0 -0
  248. data/app/pb_kits/playbook/pb_phone_number_input/img/globe@2x.webp +0 -0
  249. data/app/pb_kits/playbook/pb_phone_number_input/intlTelInput.scss +13 -6
  250. data/app/pb_kits/playbook/pb_phone_number_input/kit.schema.json +6 -3
  251. data/app/pb_kits/playbook/pb_phone_number_input/phone_number_input_assets.test.js +61 -0
  252. data/app/pb_kits/playbook/pb_pill/_pill.tsx +3 -1
  253. data/app/pb_kits/playbook/pb_pill/kit.schema.json +1 -0
  254. data/app/pb_kits/playbook/pb_popover/docs/_playground.json +1 -2
  255. data/app/pb_kits/playbook/pb_popover/index.ts +47 -8
  256. data/app/pb_kits/playbook/pb_popover/kit.schema.json +18 -9
  257. data/app/pb_kits/playbook/pb_popover/popover_index.test.js +456 -0
  258. data/app/pb_kits/playbook/pb_progress_pills/docs/_playground.json +1 -0
  259. data/app/pb_kits/playbook/pb_progress_pills/kit.schema.json +2 -1
  260. data/app/pb_kits/playbook/pb_progress_simple/docs/_playground.json +3 -2
  261. data/app/pb_kits/playbook/pb_progress_simple/kit.schema.json +4 -2
  262. data/app/pb_kits/playbook/pb_progress_step/_progress_step_item.tsx +3 -1
  263. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_playground.json +10 -3
  264. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_playground.overrides.json +1 -1
  265. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_required_indicator.md +2 -2
  266. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_default.md +3 -4
  267. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_extensions.md +1 -1
  268. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_markdown_support.md +1 -1
  269. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_required_indicator.html.erb +9 -0
  270. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_required_indicator.md +3 -0
  271. data/app/pb_kits/playbook/pb_rich_text_editor/docs/example.yml +1 -0
  272. data/app/pb_kits/playbook/pb_rich_text_editor/kit.schema.json +18 -9
  273. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.html.erb +3 -7
  274. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor_rails.js +26 -21
  275. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor_rails.test.js +85 -1
  276. data/app/pb_kits/playbook/pb_selectable_card/docs/_playground.json +1 -0
  277. data/app/pb_kits/playbook/pb_selectable_card/kit.schema.json +4 -2
  278. data/app/pb_kits/playbook/pb_selectable_card_icon/_selectable_card_icon.scss +0 -9
  279. data/app/pb_kits/playbook/pb_selectable_card_icon/docs/_playground.json +1 -0
  280. data/app/pb_kits/playbook/pb_selectable_card_icon/docs/_selectable_card_icon_custom.jsx +1 -1
  281. data/app/pb_kits/playbook/pb_selectable_card_icon/kit.schema.json +2 -1
  282. data/app/pb_kits/playbook/pb_selectable_icon/_selectable_icon.scss +0 -7
  283. data/app/pb_kits/playbook/pb_source/_source.tsx +3 -1
  284. data/app/pb_kits/playbook/pb_source/docs/_playground.json +1 -0
  285. data/app/pb_kits/playbook/pb_source/kit.schema.json +3 -1
  286. data/app/pb_kits/playbook/pb_source/source.rb +1 -1
  287. data/app/pb_kits/playbook/pb_star_rating/docs/_playground.json +1 -0
  288. data/app/pb_kits/playbook/pb_star_rating/kit.schema.json +15 -7
  289. data/app/pb_kits/playbook/pb_stat_change/_stat_change.tsx +3 -1
  290. data/app/pb_kits/playbook/pb_stat_change/docs/_playground.json +1 -0
  291. data/app/pb_kits/playbook/pb_stat_change/kit.schema.json +3 -1
  292. data/app/pb_kits/playbook/pb_stat_value/docs/_playground.json +3 -1
  293. data/app/pb_kits/playbook/pb_stat_value/kit.schema.json +2 -1
  294. data/app/pb_kits/playbook/pb_table/docs/_playground.json +5 -1
  295. data/app/pb_kits/playbook/pb_table/kit.schema.json +28 -11
  296. data/app/pb_kits/playbook/pb_text_input/docs/_playground.json +3 -0
  297. data/app/pb_kits/playbook/pb_text_input/kit.schema.json +22 -9
  298. data/app/pb_kits/playbook/pb_textarea/_textarea.tsx +4 -1
  299. data/app/pb_kits/playbook/pb_time/kit.schema.json +13 -5
  300. data/app/pb_kits/playbook/pb_time_picker/docs/_playground.json +1 -0
  301. data/app/pb_kits/playbook/pb_time_picker/kit.schema.json +2 -1
  302. data/app/pb_kits/playbook/pb_time_range_inline/_time_range_inline.tsx +3 -1
  303. data/app/pb_kits/playbook/pb_time_range_inline/kit.schema.json +13 -5
  304. data/app/pb_kits/playbook/pb_time_stacked/_time_stacked.tsx +3 -1
  305. data/app/pb_kits/playbook/pb_time_stacked/kit.schema.json +1 -0
  306. data/app/pb_kits/playbook/pb_timeline/_item.tsx +3 -1
  307. data/app/pb_kits/playbook/pb_timestamp/_timestamp.tsx +3 -1
  308. data/app/pb_kits/playbook/pb_timestamp/kit.schema.json +1 -0
  309. data/app/pb_kits/playbook/pb_timestamp/timestamp.test.js +1 -0
  310. data/app/pb_kits/playbook/pb_title/kit.schema.json +23 -10
  311. data/app/pb_kits/playbook/pb_title_count/docs/_playground.json +1 -0
  312. data/app/pb_kits/playbook/pb_title_count/kit.schema.json +2 -1
  313. data/app/pb_kits/playbook/pb_tooltip/docs/_playground.json +5 -1
  314. data/app/pb_kits/playbook/pb_tooltip/kit.schema.json +10 -5
  315. data/app/pb_kits/playbook/pb_typeahead/_typeahead.scss +73 -0
  316. data/app/pb_kits/playbook/pb_typeahead/_typeahead.test.jsx +285 -12
  317. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +22 -14
  318. data/app/pb_kits/playbook/pb_typeahead/components/PillRenderer.tsx +15 -11
  319. data/app/pb_kits/playbook/pb_typeahead/components/ValueContainer.tsx +29 -30
  320. data/app/pb_kits/playbook/pb_typeahead/components/usePillDropIndicator.ts +155 -0
  321. data/app/pb_kits/playbook/pb_typeahead/docs/_playground.json +5 -5
  322. data/app/pb_kits/playbook/pb_typeahead/docs/_playground.overrides.json +2 -2
  323. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills.html.erb +0 -1
  324. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills.jsx +0 -1
  325. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills_rails.md +3 -3
  326. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills_react.md +7 -0
  327. data/app/pb_kits/playbook/pb_typeahead/kit.schema.json +3 -3
  328. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +3 -3
  329. data/app/pb_kits/playbook/pb_user/_user.tsx +3 -1
  330. data/app/pb_kits/playbook/pb_user/docs/_playground.json +3 -1
  331. data/app/pb_kits/playbook/pb_user/kit.schema.json +20 -9
  332. data/app/pb_kits/playbook/pb_user_badge/_user_badge.tsx +3 -1
  333. data/app/pb_kits/playbook/pb_user_badge/kit.schema.json +1 -0
  334. data/app/pb_kits/playbook/pb_weekday_stacked/_weekday_stacked.tsx +3 -1
  335. data/app/pb_kits/playbook/pb_weekday_stacked/docs/_playground.json +1 -0
  336. data/app/pb_kits/playbook/pb_weekday_stacked/kit.schema.json +3 -1
  337. data/app/pb_kits/playbook/pb_weekday_stacked/weekday_stacked.test.jsx +1 -0
  338. data/app/pb_kits/playbook/utilities/test/globalProps/onClick.test.js +471 -0
  339. data/app/pb_kits/playbook/utilities/test-utils.js +0 -1
  340. data/dist/ai/all-schemas.json +986 -388
  341. data/dist/ai/external-dependencies.json +1 -1
  342. data/dist/ai/forms.json +2 -2
  343. data/dist/ai/index.json +11 -3
  344. data/dist/ai/kits/advanced_table.schema.json +44 -16
  345. data/dist/ai/kits/avatar.schema.json +22 -9
  346. data/dist/ai/kits/background.schema.json +10 -3
  347. data/dist/ai/kits/badge.schema.json +26 -12
  348. data/dist/ai/kits/body.schema.json +14 -5
  349. data/dist/ai/kits/button.schema.json +29 -25
  350. data/dist/ai/kits/caption.schema.json +17 -7
  351. data/dist/ai/kits/card.schema.json +2 -1
  352. data/dist/ai/kits/checkbox.schema.json +10 -5
  353. data/dist/ai/kits/contact.schema.json +3 -1
  354. data/dist/ai/kits/copy_button.schema.json +10 -5
  355. data/dist/ai/kits/currency.schema.json +6 -2
  356. data/dist/ai/kits/dashboard_value.schema.json +4 -2
  357. data/dist/ai/kits/date.schema.json +16 -6
  358. data/dist/ai/kits/date_picker.schema.json +31 -13
  359. data/dist/ai/kits/date_range_inline.schema.json +3 -1
  360. data/dist/ai/kits/date_range_stacked.schema.json +1 -0
  361. data/dist/ai/kits/date_stacked.schema.json +1 -0
  362. data/dist/ai/kits/date_time.schema.json +3 -1
  363. data/dist/ai/kits/date_time_stacked.schema.json +3 -1
  364. data/dist/ai/kits/date_year_stacked.schema.json +1 -0
  365. data/dist/ai/kits/detail.schema.json +2 -1
  366. data/dist/ai/kits/dialog.schema.json +22 -9
  367. data/dist/ai/kits/distribution_bar.schema.json +2 -1
  368. data/dist/ai/kits/dropdown.schema.json +98 -18
  369. data/dist/ai/kits/empty_state.schema.json +2 -1
  370. data/dist/ai/kits/file_upload.schema.json +8 -3
  371. data/dist/ai/kits/fixed_confirmation_toast.schema.json +6 -3
  372. data/dist/ai/kits/flex.schema.json +27 -12
  373. data/dist/ai/kits/form.schema.json +1 -6
  374. data/dist/ai/kits/form_pill.schema.json +6 -3
  375. data/dist/ai/kits/full_screen.schema.json +8 -4
  376. data/dist/ai/kits/hashtag.schema.json +2 -1
  377. data/dist/ai/kits/highlight.schema.json +2 -1
  378. data/dist/ai/kits/icon.schema.json +31 -13
  379. data/dist/ai/kits/icon_circle.schema.json +3 -1
  380. data/dist/ai/kits/icon_stat_value.schema.json +4 -2
  381. data/dist/ai/kits/image.schema.json +57 -24
  382. data/dist/ai/kits/label_pill.schema.json +1 -0
  383. data/dist/ai/kits/label_value.schema.json +2 -1
  384. data/dist/ai/kits/layout.schema.json +43 -20
  385. data/dist/ai/kits/legend.schema.json +3 -1
  386. data/dist/ai/kits/lightbox.schema.json +86 -0
  387. data/dist/ai/kits/link.schema.json +14 -6
  388. data/dist/ai/kits/list.schema.json +2 -1
  389. data/dist/ai/kits/loading_inline.schema.json +4 -1
  390. data/dist/ai/kits/map.schema.json +4 -7
  391. data/dist/ai/kits/message.schema.json +2 -1
  392. data/dist/ai/kits/multiple_users.schema.json +19 -11
  393. data/dist/ai/kits/multiple_users_stacked.schema.json +2 -1
  394. data/dist/ai/kits/nav.schema.json +4 -2
  395. data/dist/ai/kits/online_status.schema.json +1 -0
  396. data/dist/ai/kits/overlay.schema.json +22 -12
  397. data/dist/ai/kits/pagination.schema.json +6 -3
  398. data/dist/ai/kits/passphrase.schema.json +10 -5
  399. data/dist/ai/kits/pb_gauge_chart.schema.json +2 -1
  400. data/dist/ai/kits/person.schema.json +1 -0
  401. data/dist/ai/kits/person_contact.schema.json +2 -1
  402. data/dist/ai/kits/phone_number_input.schema.json +6 -3
  403. data/dist/ai/kits/pill.schema.json +1 -0
  404. data/dist/ai/kits/popover.schema.json +17 -8
  405. data/dist/ai/kits/progress_pills.schema.json +2 -1
  406. data/dist/ai/kits/progress_simple.schema.json +4 -2
  407. data/dist/ai/kits/rich_text_editor.schema.json +19 -10
  408. data/dist/ai/kits/selectable_card.schema.json +4 -2
  409. data/dist/ai/kits/selectable_card_icon.schema.json +2 -1
  410. data/dist/ai/kits/source.schema.json +3 -1
  411. data/dist/ai/kits/star_rating.schema.json +15 -7
  412. data/dist/ai/kits/stat_change.schema.json +3 -1
  413. data/dist/ai/kits/stat_value.schema.json +2 -1
  414. data/dist/ai/kits/table.schema.json +28 -11
  415. data/dist/ai/kits/text_input.schema.json +22 -9
  416. data/dist/ai/kits/time.schema.json +12 -4
  417. data/dist/ai/kits/time_picker.schema.json +2 -1
  418. data/dist/ai/kits/time_range_inline.schema.json +13 -5
  419. data/dist/ai/kits/time_stacked.schema.json +1 -0
  420. data/dist/ai/kits/timestamp.schema.json +1 -0
  421. data/dist/ai/kits/title.schema.json +23 -10
  422. data/dist/ai/kits/title_count.schema.json +2 -1
  423. data/dist/ai/kits/tooltip.schema.json +10 -5
  424. data/dist/ai/kits/typeahead.schema.json +3 -3
  425. data/dist/ai/kits/user.schema.json +20 -9
  426. data/dist/ai/kits/user_badge.schema.json +1 -0
  427. data/dist/ai/kits/weekday_stacked.schema.json +3 -1
  428. data/dist/ai/playgrounds/advanced_table.json +1 -1
  429. data/dist/ai/playgrounds/dropdown.json +1 -1
  430. data/dist/ai/playgrounds/index.json +1 -1
  431. data/dist/ai/playgrounds/rich_text_editor.json +1 -1
  432. data/dist/ai/playgrounds/typeahead.json +1 -1
  433. data/dist/ai/visual-index.json +1 -1
  434. data/dist/chunks/_pagination-DZ0eQuH2.js +1 -0
  435. data/dist/chunks/{_pb_line_graph-Du7xE9vl.js → _pb_line_graph-DyvH5Zj1.js} +1 -1
  436. data/dist/chunks/{_detail-Bsvl694i.js → _tooltip-CFbtqu-E.js} +2 -2
  437. data/dist/chunks/_typeahead-CkiAf8zx.js +1 -0
  438. data/dist/chunks/{globalProps-B120NbSJ.js → globalProps-xfGTpdLP.js} +1 -1
  439. data/dist/chunks/index-4jrZjexD.js +1 -0
  440. data/dist/chunks/index-BAy61y8S.js +1 -0
  441. data/dist/chunks/index-Cb_SDipk.js +1 -0
  442. data/dist/chunks/index-D3RdsDp7.js +1 -0
  443. data/dist/chunks/index-D8PZIesy.js +1 -0
  444. data/dist/chunks/index-DIoc-aua.js +1 -0
  445. data/dist/chunks/index-qex65UMn.js +1 -0
  446. data/dist/chunks/lib-Cu_i0y0k.js +29 -0
  447. data/dist/chunks/{time_picker_helper-CYGg0UQ0.js → time_picker_helper-B82MORju.js} +1 -1
  448. data/dist/chunks/vendor.js +2 -2
  449. data/dist/menu.yml +3 -3
  450. data/dist/playbook-rails-react-bindings.js +1 -1
  451. data/dist/playbook-rails.js +1 -1
  452. data/dist/playbook.css +1 -1
  453. data/lib/playbook/changelog_generator.rb +47 -21
  454. data/lib/playbook/connect_message_generator.rb +15 -2
  455. data/lib/playbook/version.rb +2 -2
  456. metadata +99 -23
  457. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills.md +0 -7
  458. data/dist/chunks/_table-Bf6LGyL9.js +0 -1
  459. data/dist/chunks/_typeahead-w6S5Vx4b.js +0 -1
  460. data/dist/chunks/lib-Buo6zHEw.js +0 -29
  461. /data/app/pb_kits/playbook/pb_form/docs/{_description.md → _description_rails.md} +0 -0
  462. /data/app/pb_kits/playbook/pb_form/docs/{_form_default_values.md → _form_default_values_rails.md} +0 -0
  463. /data/app/pb_kits/playbook/pb_form/docs/{_form_defaults.md → _form_defaults_rails.md} +0 -0
  464. /data/app/pb_kits/playbook/pb_form/docs/{_form_model_values.md → _form_model_values_rails.md} +0 -0
  465. /data/app/pb_kits/playbook/pb_form/docs/{_form_multi_column.md → _form_multi_column_rails.md} +0 -0
  466. /data/app/pb_kits/playbook/pb_form/docs/{_form_validation.md → _form_validation_rails.md} +0 -0
@@ -358,7 +358,7 @@ test('enablePillReorder false does not render drag handles', () => {
358
358
  expect(kit.querySelector('.pb_typeahead_pill_drag_handle')).not.toBeInTheDocument()
359
359
  })
360
360
 
361
- test('enablePillReorder renders drag handles and numbered pills', () => {
361
+ test('enablePillReorder renders in-pill grip icons and numbered pills by default', () => {
362
362
  render(
363
363
  <Typeahead
364
364
  data={{ testid: 'reorder-test' }}
@@ -371,10 +371,11 @@ test('enablePillReorder renders drag handles and numbered pills', () => {
371
371
  )
372
372
 
373
373
  const kit = screen.getByTestId('reorder-test')
374
- const handles = kit.querySelectorAll('.pb_typeahead_pill_drag_handle')
375
- const pills = kit.querySelectorAll('.pb_form_pill_kit.pb_form_pill_primary')
374
+ // FormPill uses pb_form_pill_kit_icon when the in-pill grip is present
375
+ const pills = kit.querySelectorAll('.pb_form_pill_kit_icon.pb_form_pill_primary')
376
376
 
377
- expect(handles.length).toBe(3)
377
+ expect(kit.querySelector('.pb_typeahead_pill_drag_handle')).not.toBeInTheDocument()
378
+ expect(kit.querySelectorAll('.pb_form_pill_kit_icon .pb_form_pill_icon').length).toBe(3)
378
379
  expect(pills.length).toBe(3)
379
380
  expect(pills[0]).toHaveTextContent('1. Orange')
380
381
  expect(pills[1]).toHaveTextContent('2. Red')
@@ -397,15 +398,21 @@ test('keyboard reorder moves pill position when enablePillReorder is true', () =
397
398
  )
398
399
 
399
400
  const kit = screen.getByTestId('keyboard-reorder-test')
400
- const pills = kit.querySelectorAll('.pb_form_pill_kit.pb_form_pill_primary')
401
+ const pills = kit.querySelectorAll('.pb_form_pill_kit_icon.pb_form_pill_primary')
401
402
 
402
403
  pills[1].focus()
403
- fireEvent.keyDown(pills[1], { key: 'ArrowLeft', ctrlKey: true, shiftKey: true })
404
+ fireEvent.keyDown(pills[1], { key: 'ArrowLeft' })
404
405
 
405
406
  expect(handleChange).toHaveBeenCalled()
406
407
  const reorderedValue = handleChange.mock.calls[0][0]
407
408
  expect(reorderedValue[0].label).toBe('Red')
408
409
  expect(reorderedValue[1].label).toBe('Orange')
410
+
411
+ fireEvent.keyDown(pills[1], { key: 'ArrowRight' })
412
+
413
+ const movedRightValue = handleChange.mock.calls[1][0]
414
+ expect(movedRightValue[1].label).toBe('Green')
415
+ expect(movedRightValue[2].label).toBe('Red')
409
416
  })
410
417
 
411
418
  test('remove still works when enablePillReorder is true', () => {
@@ -432,14 +439,13 @@ test('remove still works when enablePillReorder is true', () => {
432
439
  expect(updatedValue.length).toBe(1)
433
440
  })
434
441
 
435
- test('pillDragHandle false hides grip and enables whole-pill pointer drag', () => {
442
+ test('default whole-pill drag hides external handle and disables HTML5 draggable', () => {
436
443
  render(
437
444
  <Typeahead
438
445
  data={{ testid: 'no-handle-reorder-test' }}
439
446
  enablePillReorder
440
447
  isMulti
441
448
  options={options}
442
- pillDragHandle={false}
443
449
  value={[options[0], options[1], options[2]]}
444
450
  />
445
451
  )
@@ -453,8 +459,26 @@ test('pillDragHandle false hides grip and enables whole-pill pointer drag', () =
453
459
  })
454
460
  })
455
461
 
456
- test('pill reorder commit dispatches custom event on drop', () => {
462
+ test('externalDragHandle true renders external grip handles', () => {
463
+ render(
464
+ <Typeahead
465
+ data={{ testid: 'external-handle-test' }}
466
+ enablePillReorder
467
+ externalDragHandle
468
+ isMulti
469
+ options={options}
470
+ value={[options[0], options[1]]}
471
+ />
472
+ )
473
+
474
+ const kit = screen.getByTestId('external-handle-test')
475
+ expect(kit.querySelectorAll('.pb_typeahead_pill_drag_handle').length).toBe(2)
476
+ expect(kit.querySelectorAll('.pb_form_pill_kit_icon .pb_form_pill_icon').length).toBe(0)
477
+ })
478
+
479
+ test('dragging a pill shows a ghost and drop line, then commits on drop', () => {
457
480
  const reorderHandler = jest.fn()
481
+ const originalElementFromPoint = document.elementFromPoint
458
482
 
459
483
  render(
460
484
  <Typeahead
@@ -474,19 +498,141 @@ test('pill reorder commit dispatches custom event on drop', () => {
474
498
 
475
499
  const kit = screen.getByTestId('reorder-event-test')
476
500
  const draggableItems = kit.querySelectorAll('.pb_draggable_item')
501
+ draggableItems.forEach((item, index) => {
502
+ item.getBoundingClientRect = () => ({ left: index * 100, right: index * 100 + 100, width: 100, top: 0, bottom: 30, height: 30 })
503
+ })
504
+ // pb_draggable hit-testing (via axe polyfill) needs elementFromPoint in jsdom
505
+ document.elementFromPoint = (x) => draggableItems[Math.min(Math.floor(x / 100), draggableItems.length - 1)] || null
506
+
507
+ fireEvent.mouseDown(draggableItems[0], { button: 0, clientX: 10, clientY: 10 })
508
+ fireEvent.mouseMove(document, { clientX: 30, clientY: 10 })
509
+ fireEvent.mouseMove(document, { clientX: 120, clientY: 10 })
510
+
511
+ expect(document.querySelector('.pb_typeahead_pill_drag_preview')).toBeInTheDocument()
512
+ expect(kit.querySelector('.pb_typeahead_draggable_pills')).toHaveClass('active')
513
+ expect(kit.querySelector('.pb_typeahead_drop_before, .pb_typeahead_drop_after')).not.toBeInTheDocument()
514
+
515
+ // Slightly below the pill row still targets a middle position; far below targets nothing
516
+ fireEvent.mouseMove(document, { clientX: 220, clientY: 40 })
517
+ expect(draggableItems[2]).toHaveClass('pb_typeahead_drop_before')
518
+ fireEvent.mouseMove(document, { clientX: 220, clientY: 1000 })
519
+ expect(kit.querySelector('.pb_typeahead_drop_before, .pb_typeahead_drop_after')).not.toBeInTheDocument()
477
520
 
478
- fireEvent.dragStart(draggableItems[2])
479
- fireEvent.dragEnter(draggableItems[0])
480
- fireEvent.dragEnd(draggableItems[2])
521
+ fireEvent.mouseMove(document, { clientX: 280, clientY: 10 })
522
+ expect(draggableItems[2]).toHaveClass('pb_typeahead_drop_after')
481
523
 
524
+ // Past the last pill (e.g. over the input) still targets the end
525
+ fireEvent.mouseMove(document, { clientX: 450, clientY: 20 })
526
+ expect(draggableItems[2]).toHaveClass('pb_typeahead_drop_after')
527
+
528
+ fireEvent.mouseUp(document, { clientX: 450, clientY: 20 })
529
+
530
+ expect(document.querySelector('.pb_typeahead_pill_drag_preview')).not.toBeInTheDocument()
482
531
  expect(reorderHandler).toHaveBeenCalled()
532
+ expect(reorderHandler.mock.calls[0][0].detail.map((item) => item.label)).toEqual(['Red', 'Green', 'Orange'])
483
533
 
534
+ document.elementFromPoint = originalElementFromPoint
484
535
  document.removeEventListener(
485
536
  'pb-typeahead-kit-reorder-event-typeahead-result-option-reorder',
486
537
  reorderHandler,
487
538
  )
488
539
  })
489
540
 
541
+ test('a single move that starts the drag still seeds the ghost and commits on drop', () => {
542
+ const reorderHandler = jest.fn()
543
+ const originalElementFromPoint = document.elementFromPoint
544
+
545
+ render(
546
+ <Typeahead
547
+ data={{ testid: 'single-move-reorder-test' }}
548
+ enablePillReorder
549
+ id="single-move-reorder-typeahead"
550
+ isMulti
551
+ options={options}
552
+ value={[options[0], options[1], options[2]]}
553
+ />
554
+ )
555
+
556
+ document.addEventListener(
557
+ 'pb-typeahead-kit-single-move-reorder-typeahead-result-option-reorder',
558
+ reorderHandler,
559
+ )
560
+
561
+ const kit = screen.getByTestId('single-move-reorder-test')
562
+ const draggableItems = kit.querySelectorAll('.pb_draggable_item')
563
+ draggableItems.forEach((item, index) => {
564
+ item.getBoundingClientRect = () => ({ left: index * 100, right: index * 100 + 100, width: 100, top: 0, bottom: 30, height: 30 })
565
+ })
566
+ document.elementFromPoint = (x) => draggableItems[Math.min(Math.floor(x / 100), draggableItems.length - 1)] || null
567
+
568
+ // One move both starts the drag and is the only pointer sample before drop
569
+ fireEvent.mouseDown(draggableItems[0], { button: 0, buttons: 1, clientX: 10, clientY: 10 })
570
+ fireEvent.mouseMove(document, { buttons: 1, clientX: 280, clientY: 10 })
571
+
572
+ expect(document.querySelector('.pb_typeahead_pill_drag_preview')).toBeInTheDocument()
573
+ expect(draggableItems[2]).toHaveClass('pb_typeahead_drop_after')
574
+
575
+ fireEvent.mouseUp(document, { clientX: 280, clientY: 10 })
576
+
577
+ expect(reorderHandler).toHaveBeenCalled()
578
+ expect(reorderHandler.mock.calls[0][0].detail.map((item) => item.label)).toEqual(['Red', 'Green', 'Orange'])
579
+
580
+ document.elementFromPoint = originalElementFromPoint
581
+ document.removeEventListener(
582
+ 'pb-typeahead-kit-single-move-reorder-typeahead-result-option-reorder',
583
+ reorderHandler,
584
+ )
585
+ })
586
+
587
+ test('dragging from an external handle shows a ghost and drop line, then commits on drop', () => {
588
+ const reorderHandler = jest.fn()
589
+ const originalElementFromPoint = document.elementFromPoint
590
+
591
+ render(
592
+ <Typeahead
593
+ data={{ testid: 'external-handle-drag-test' }}
594
+ enablePillReorder
595
+ externalDragHandle
596
+ id="external-handle-drag-typeahead"
597
+ isMulti
598
+ options={options}
599
+ value={[options[0], options[1], options[2]]}
600
+ />
601
+ )
602
+
603
+ document.addEventListener(
604
+ 'pb-typeahead-kit-external-handle-drag-typeahead-result-option-reorder',
605
+ reorderHandler,
606
+ )
607
+
608
+ const kit = screen.getByTestId('external-handle-drag-test')
609
+ const draggableItems = kit.querySelectorAll('.pb_draggable_item')
610
+ const handles = kit.querySelectorAll('.pb_typeahead_pill_drag_handle')
611
+ draggableItems.forEach((item, index) => {
612
+ item.getBoundingClientRect = () => ({ left: index * 100, right: index * 100 + 100, width: 100, top: 0, bottom: 30, height: 30 })
613
+ })
614
+ document.elementFromPoint = (x) => draggableItems[Math.min(Math.floor(x / 100), draggableItems.length - 1)] || null
615
+
616
+ fireEvent.mouseDown(handles[0], { button: 0, clientX: 10, clientY: 10 })
617
+ // First move starts the drag and mounts the drop-indicator effect; later moves hit its listeners
618
+ fireEvent.mouseMove(document, { clientX: 30, clientY: 10 })
619
+ fireEvent.mouseMove(document, { clientX: 280, clientY: 10 })
620
+ expect(document.querySelector('.pb_typeahead_pill_drag_preview')).toBeInTheDocument()
621
+ expect(draggableItems[2]).toHaveClass('pb_typeahead_drop_after')
622
+
623
+ fireEvent.mouseUp(document, { clientX: 280, clientY: 10 })
624
+
625
+ expect(document.querySelector('.pb_typeahead_pill_drag_preview')).not.toBeInTheDocument()
626
+ expect(reorderHandler).toHaveBeenCalled()
627
+ expect(reorderHandler.mock.calls[0][0].detail.map((item) => item.label)).toEqual(['Red', 'Green', 'Orange'])
628
+
629
+ document.elementFromPoint = originalElementFromPoint
630
+ document.removeEventListener(
631
+ 'pb-typeahead-kit-external-handle-drag-typeahead-result-option-reorder',
632
+ reorderHandler,
633
+ )
634
+ })
635
+
490
636
  test('publishes data-default-value from initial defaultValue', () => {
491
637
  render(
492
638
  <Typeahead
@@ -630,3 +776,130 @@ test(':set calls onChange but does not dispatch result-option-select', async ()
630
776
  selectHandler,
631
777
  )
632
778
  })
779
+
780
+
781
+ test('required Typeahead in validated form shows its own error and stays mounted after selection', async () => {
782
+ render(
783
+ <form data-pb-form-validation="true">
784
+ <Typeahead
785
+ data={{ testid: 'required-validated-typeahead' }}
786
+ isMulti={false}
787
+ name="favorite_color"
788
+ options={options}
789
+ required
790
+ validation={{ message: 'Please select a color' }}
791
+ />
792
+ </form>
793
+ )
794
+
795
+ const kit = screen.getByTestId('required-validated-typeahead')
796
+ const requiredInput = kit.querySelector('input[required]')
797
+ expect(requiredInput).toBeInTheDocument()
798
+
799
+ act(() => {
800
+ requiredInput.dispatchEvent(new Event('invalid', { bubbles: false, cancelable: true }))
801
+ })
802
+
803
+ await waitFor(() => {
804
+ expect(kit.querySelector('.pb_body_kit_negative')).toHaveTextContent('Please select a color')
805
+ })
806
+
807
+ // PbFormValidation must not own this input — no injected sibling error on the
808
+ // react-select RequiredInput parent (that path caused removeChild crashes).
809
+ const injectedSibling = Array.from(requiredInput.parentElement.children).find(
810
+ (child) => child.classList?.contains('pb_body_kit_negative')
811
+ )
812
+ expect(injectedSibling).toBeUndefined()
813
+
814
+ const control = kit.querySelector('.typeahead-kit-select__control')
815
+ fireEvent.mouseDown(control)
816
+
817
+ await waitFor(() => {
818
+ expect(document.querySelector('.typeahead-kit-select__option')).toBeInTheDocument()
819
+ })
820
+
821
+ const option = document.querySelector('.typeahead-kit-select__option')
822
+ fireEvent.click(option)
823
+
824
+ await waitFor(() => {
825
+ const selectedKit = screen.getByTestId('required-validated-typeahead')
826
+ expect(selectedKit.querySelector('.typeahead-kit-select__single-value')).toHaveTextContent('Orange')
827
+ expect(selectedKit.querySelector('.pb_body_kit_negative')).not.toBeInTheDocument()
828
+ })
829
+ })
830
+
831
+ test('required Typeahead restores validation error after clear and resubmit', async () => {
832
+ render(
833
+ <form data-pb-form-validation="true">
834
+ <Typeahead
835
+ data={{ testid: 'required-clear-resubmit-typeahead' }}
836
+ defaultValue={options[0]}
837
+ isMulti={false}
838
+ name="favorite_color"
839
+ options={options}
840
+ required
841
+ validation={{ message: 'Please select a color' }}
842
+ />
843
+ </form>
844
+ )
845
+
846
+ const kit = screen.getByTestId('required-clear-resubmit-typeahead')
847
+ const clearIndicator = kit.querySelector('.typeahead-kit-select__clear-indicator')
848
+ expect(clearIndicator).toBeInTheDocument()
849
+ fireEvent.mouseDown(clearIndicator)
850
+
851
+ await waitFor(() => {
852
+ expect(kit.querySelector('input[required]')).toBeInTheDocument()
853
+ })
854
+
855
+ const requiredInput = kit.querySelector('input[required]')
856
+ act(() => {
857
+ requiredInput.dispatchEvent(new Event('invalid', { bubbles: false, cancelable: true }))
858
+ })
859
+
860
+ await waitFor(() => {
861
+ expect(kit.querySelector('.pb_body_kit_negative')).toHaveTextContent('Please select a color')
862
+ })
863
+ })
864
+
865
+ test('required Typeahead suppresses native invalid UI only inside validated Playbook forms', () => {
866
+ const { unmount } = render(
867
+ <form data-pb-form-validation="true">
868
+ <Typeahead
869
+ data={{ testid: 'native-invalid-validated' }}
870
+ isMulti={false}
871
+ name="color"
872
+ options={options}
873
+ required
874
+ />
875
+ </form>
876
+ )
877
+
878
+ let requiredInput = screen.getByTestId('native-invalid-validated').querySelector('input[required]')
879
+ const validatedEvent = new Event('invalid', { bubbles: false, cancelable: true })
880
+ act(() => {
881
+ requiredInput.dispatchEvent(validatedEvent)
882
+ })
883
+ expect(validatedEvent.defaultPrevented).toBe(true)
884
+
885
+ unmount()
886
+
887
+ render(
888
+ <form>
889
+ <Typeahead
890
+ data={{ testid: 'native-invalid-unvalidated' }}
891
+ isMulti={false}
892
+ name="color"
893
+ options={options}
894
+ required
895
+ />
896
+ </form>
897
+ )
898
+
899
+ requiredInput = screen.getByTestId('native-invalid-unvalidated').querySelector('input[required]')
900
+ const unvalidatedEvent = new Event('invalid', { bubbles: false, cancelable: true })
901
+ act(() => {
902
+ requiredInput.dispatchEvent(unvalidatedEvent)
903
+ })
904
+ expect(unvalidatedEvent.defaultPrevented).toBe(false)
905
+ })
@@ -93,8 +93,8 @@ type TypeaheadProps = {
93
93
  clearOnContextChange?: boolean
94
94
  preserveSearchInput?: boolean
95
95
  enablePillReorder?: boolean
96
+ externalDragHandle?: boolean
96
97
  showPillIndex?: boolean
97
- pillDragHandle?: boolean
98
98
  // Passed through to react-select; merged when menu is portaled to `document.body`.
99
99
  styles?: TypeaheadStylesConfig
100
100
  } & GlobalProps
@@ -145,11 +145,14 @@ const Typeahead = forwardRef<HTMLInputElement, TypeaheadProps>(
145
145
  clearOnContextChange = false,
146
146
  preserveSearchInput = false, // Default to false to maintain backward compatibility
147
147
  enablePillReorder = false,
148
+ externalDragHandle = false,
148
149
  showPillIndex = false,
149
- pillDragHandle = true,
150
150
  styles: stylesProp,
151
151
  ...props
152
- }: TypeaheadProps) => {
152
+ }: TypeaheadProps, _ref) => {
153
+ // Accept the ref without attaching it, preserving the existing ref behavior.
154
+ void _ref
155
+
153
156
  // State to manage the input value when preserveSearchInput is true
154
157
  const [inputValue, setInputValue] = useState("")
155
158
  // State to track if form has been submitted to control validation display for react rendered rails kit
@@ -541,20 +544,25 @@ const resolvedLoadOptions =
541
544
 
542
545
  const [contextValue, setContextValue] = useState("")
543
546
 
544
- // Add listener for form validation to track when validation should be shown (needed for react rendered rails kit)
547
+ // Track form submit validation for react-rendered Typeahead (own error UI).
548
+ // When inside pb_form_with validate: true, suppress the browser's native
549
+ // invalid UI — PbFormValidation skips this kit so it must not double-show.
545
550
  useEffect(() => {
546
551
  const handleInvalid = (event: Event) => {
547
552
  const target = event.target as HTMLInputElement
548
- const typeaheadContainer = target.closest(
549
- '[data-pb-react-component="Typeahead"]',
550
- )
553
+ if (!kitContainerRef.current?.contains(target)) return
551
554
 
552
- if (typeaheadContainer) {
553
- // Check if this invalid event is specifically for our typeahead by comparing names so we do not have to require ids
554
- const invalidInputName = target.name || target.getAttribute("name")
555
- if (invalidInputName === name) {
556
- setFormSubmitted(true)
557
- }
555
+ // Match by name so we do not have to require ids
556
+ const invalidInputName = target.name || target.getAttribute("name")
557
+ if (invalidInputName !== name) return
558
+
559
+ setFormSubmitted(true)
560
+
561
+ const validatedForm = target.closest(
562
+ 'form[data-pb-form-validation="true"]',
563
+ )
564
+ if (validatedForm) {
565
+ event.preventDefault()
558
566
  }
559
567
  }
560
568
  document.addEventListener("invalid", handleInvalid, true)
@@ -743,9 +751,9 @@ const resolvedLoadOptions =
743
751
  classNamePrefix="typeahead-kit-select"
744
752
  enablePillReorder={isPillReorderEnabled}
745
753
  error={errorDisplay}
754
+ externalDragHandle={externalDragHandle}
746
755
  isDisabled={disabled}
747
756
  onChange={handleOnChange}
748
- pillDragHandle={pillDragHandle}
749
757
  ref={selectRef}
750
758
  showPillIndex={showPillIndex}
751
759
  />
@@ -33,15 +33,16 @@ type PillColorType =
33
33
  type PillRendererProps = {
34
34
  data: SelectValueType
35
35
  dark?: boolean
36
+ externalDragHandle?: boolean
36
37
  index?: number
37
38
  isFocused?: boolean
38
39
  multiKit?: string
39
40
  onKeyboardReorder?: (direction: 'left' | 'right') => void
40
41
  onRemove?: (event: React.MouseEvent | React.KeyboardEvent) => void
41
42
  pillColor?: PillColorType
42
- pillDragHandle?: boolean
43
43
  removeProps?: { onClick?: (event: React.MouseEvent | React.KeyboardEvent) => void }
44
44
  selectProps?: Record<string, unknown>
45
+ showDragIcon?: boolean
45
46
  showPillIndex?: boolean
46
47
  totalCount?: number
47
48
  truncate?: 'none' | 1 | 2 | 3 | 4 | 5
@@ -51,14 +52,15 @@ type PillRendererProps = {
51
52
  const PillRenderer = ({
52
53
  data,
53
54
  dark,
55
+ externalDragHandle = false,
54
56
  index = 0,
55
57
  isFocused = false,
56
58
  multiKit = '',
57
59
  onKeyboardReorder,
58
60
  onRemove,
59
61
  pillColor,
60
- pillDragHandle = false,
61
62
  removeProps,
63
+ showDragIcon = false,
62
64
  showPillIndex = false,
63
65
  totalCount = 0,
64
66
  truncate,
@@ -68,8 +70,9 @@ const PillRenderer = ({
68
70
  const baseLabel = data.label || data.name || ''
69
71
  const label = showPillIndex ? `${index + 1}. ${baseLabel}` : baseLabel
70
72
  const pillClassName = isFocused ? 'pb_form_pill_or_badge_focused' : ''
73
+ const pillIcon = showDragIcon ? 'grip-dots-vertical' : ''
71
74
  const positionLabel = totalCount > 0
72
- ? `${baseLabel}, position ${index + 1} of ${totalCount}, press Control+Shift+Arrow keys to reorder`
75
+ ? `${baseLabel}, position ${index + 1} of ${totalCount}, press Left or Right Arrow keys to reorder`
73
76
  : baseLabel
74
77
 
75
78
  const handleRemove = (event: React.MouseEvent | React.KeyboardEvent) => {
@@ -85,14 +88,13 @@ const PillRenderer = ({
85
88
  event.preventDefault()
86
89
  event.stopPropagation()
87
90
  handleRemove(event)
88
- } else if (event.key === 'ArrowLeft' && event.ctrlKey && event.shiftKey) {
91
+ } else if (onKeyboardReorder && (event.key === 'ArrowLeft' || event.key === 'ArrowRight')) {
89
92
  event.preventDefault()
90
93
  event.stopPropagation()
91
- onKeyboardReorder?.('left')
92
- } else if (event.key === 'ArrowRight' && event.ctrlKey && event.shiftKey) {
93
- event.preventDefault()
94
- event.stopPropagation()
95
- onKeyboardReorder?.('right')
94
+ const pill = event.currentTarget
95
+ onKeyboardReorder(event.key === 'ArrowLeft' ? 'left' : 'right')
96
+ // Reordering moves the pill's DOM node, which drops focus; restore it after the update
97
+ requestAnimationFrame(() => pill.focus())
96
98
  } else if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
97
99
  const selectInput = event.currentTarget.closest('.pb_typeahead_kit')?.querySelector('input')
98
100
  if (selectInput instanceof HTMLInputElement) {
@@ -105,7 +107,7 @@ const PillRenderer = ({
105
107
 
106
108
  const pillContent = (
107
109
  <>
108
- {pillDragHandle && (
110
+ {externalDragHandle && (
109
111
  <span
110
112
  aria-hidden="true"
111
113
  className="pb_draggable_handle pb_typeahead_pill_drag_handle"
@@ -144,6 +146,7 @@ const PillRenderer = ({
144
146
  color={pillColor}
145
147
  dark={dark}
146
148
  htmlOptions={{ 'aria-label': positionLabel, onKeyDown: handleKeyDown }}
149
+ icon={pillIcon}
147
150
  marginRight="xs"
148
151
  name={label}
149
152
  size={multiKit === 'smallPill' ? 'small' : ''}
@@ -161,6 +164,7 @@ const PillRenderer = ({
161
164
  color={pillColor}
162
165
  dark={dark}
163
166
  htmlOptions={{ 'aria-label': positionLabel, onKeyDown: handleKeyDown }}
167
+ icon={pillIcon}
164
168
  marginRight="xs"
165
169
  name=""
166
170
  size={multiKit === 'smallPill' ? 'small' : ''}
@@ -173,7 +177,7 @@ const PillRenderer = ({
173
177
  </>
174
178
  )
175
179
 
176
- if (pillDragHandle) {
180
+ if (externalDragHandle) {
177
181
  return (
178
182
  <span className="pb_typeahead_pill_with_handle">
179
183
  {pillContent}