hakumi_components 0.1.18.pre → 0.1.19.pre

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 (467) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -2
  3. data/README.md +208 -381
  4. data/app/assets/javascripts/hakumi_components.js +40 -34
  5. data/app/assets/stylesheets/hakumi_components.css +1 -1
  6. data/app/components/hakumi_components/admin_panel/component.rb +19 -9
  7. data/app/components/hakumi_components/affix/component.rb +54 -30
  8. data/app/components/hakumi_components/alert/component.html.erb +2 -2
  9. data/app/components/hakumi_components/alert/component.rb +68 -18
  10. data/app/components/hakumi_components/anchor/component.rb +35 -27
  11. data/app/components/hakumi_components/anchor/link/component.html.erb +1 -1
  12. data/app/components/hakumi_components/anchor/link/component.rb +28 -9
  13. data/app/components/hakumi_components/autocomplete/component.html.erb +86 -66
  14. data/app/components/hakumi_components/autocomplete/component.rb +115 -89
  15. data/app/components/hakumi_components/avatar/component.rb +45 -12
  16. data/app/components/hakumi_components/badge/component.rb +99 -45
  17. data/app/components/hakumi_components/base_component.rb +290 -91
  18. data/app/components/hakumi_components/breadcrumb/component.rb +16 -5
  19. data/app/components/hakumi_components/breadcrumb/item/component.html.erb +5 -5
  20. data/app/components/hakumi_components/breadcrumb/item/component.rb +56 -8
  21. data/app/components/hakumi_components/breadcrumb/item/menu_entry.rb +71 -0
  22. data/app/components/hakumi_components/button/component.rb +62 -20
  23. data/app/components/hakumi_components/calendar/component.html.erb +19 -19
  24. data/app/components/hakumi_components/calendar/component.rb +225 -202
  25. data/app/components/hakumi_components/calendar/day_cell.rb +62 -0
  26. data/app/components/hakumi_components/calendar/display_model.rb +237 -0
  27. data/app/components/hakumi_components/calendar/event_entry.rb +54 -0
  28. data/app/components/hakumi_components/calendar/locale_pack.rb +28 -0
  29. data/app/components/hakumi_components/calendar/panel_entry.rb +48 -0
  30. data/app/components/hakumi_components/calendar/week_row.rb +28 -0
  31. data/app/components/hakumi_components/card/component.html.erb +2 -2
  32. data/app/components/hakumi_components/card/component.rb +76 -49
  33. data/app/components/hakumi_components/card/grid/component.html.erb +1 -1
  34. data/app/components/hakumi_components/card/grid/component.rb +23 -17
  35. data/app/components/hakumi_components/card/meta/component.html.erb +8 -8
  36. data/app/components/hakumi_components/card/meta/component.rb +42 -24
  37. data/app/components/hakumi_components/carousel/autoplay_config.rb +27 -0
  38. data/app/components/hakumi_components/carousel/component.rb +85 -67
  39. data/app/components/hakumi_components/carousel/dots_config.rb +36 -0
  40. data/app/components/hakumi_components/cascader/component.html.erb +54 -36
  41. data/app/components/hakumi_components/cascader/component.rb +111 -62
  42. data/app/components/hakumi_components/checkbox/component.html.erb +5 -5
  43. data/app/components/hakumi_components/checkbox/component.rb +40 -6
  44. data/app/components/hakumi_components/checkbox/group/component.html.erb +4 -4
  45. data/app/components/hakumi_components/checkbox/group/component.rb +40 -13
  46. data/app/components/hakumi_components/checkbox/group/option.rb +34 -0
  47. data/app/components/hakumi_components/collapse/component.rb +64 -48
  48. data/app/components/hakumi_components/collapse/panel/component.rb +64 -21
  49. data/app/components/hakumi_components/color_picker/component.html.erb +21 -22
  50. data/app/components/hakumi_components/color_picker/component.rb +159 -49
  51. data/app/components/hakumi_components/color_picker/preset_group.rb +27 -0
  52. data/app/components/hakumi_components/concerns/form_field.rb +169 -68
  53. data/app/components/hakumi_components/concerns/form_field_contract.rb +57 -0
  54. data/app/components/hakumi_components/concerns/form_field_interface.rb +16 -0
  55. data/app/components/hakumi_components/concerns/input_control.rb +111 -0
  56. data/app/components/hakumi_components/concerns/input_control_contract.rb +47 -0
  57. data/app/components/hakumi_components/concerns/input_control_interface.rb +34 -0
  58. data/app/components/hakumi_components/concerns/selection_control.rb +122 -0
  59. data/app/components/hakumi_components/concerns/selection_control_contract.rb +47 -0
  60. data/app/components/hakumi_components/concerns/selection_control_interface.rb +34 -0
  61. data/app/components/hakumi_components/container/component.rb +29 -14
  62. data/app/components/hakumi_components/date_picker/component.html.erb +1 -3
  63. data/app/components/hakumi_components/date_picker/component.rb +130 -16
  64. data/app/components/hakumi_components/date_picker/range_picker.rb +159 -28
  65. data/app/components/hakumi_components/date_picker/shared_rendering.rb +151 -61
  66. data/app/components/hakumi_components/descriptions/component.html.erb +6 -6
  67. data/app/components/hakumi_components/descriptions/component.rb +98 -61
  68. data/app/components/hakumi_components/descriptions/item/component.rb +32 -8
  69. data/app/components/hakumi_components/divider/component.rb +36 -16
  70. data/app/components/hakumi_components/drawer/component.html.erb +2 -2
  71. data/app/components/hakumi_components/drawer/component.rb +112 -29
  72. data/app/components/hakumi_components/dropdown/component.rb +32 -35
  73. data/app/components/hakumi_components/dropdown/divider/component.rb +4 -0
  74. data/app/components/hakumi_components/dropdown/item/component.rb +42 -16
  75. data/app/components/hakumi_components/empty/component.rb +66 -31
  76. data/app/components/hakumi_components/flex/component.rb +39 -21
  77. data/app/components/hakumi_components/float_button/back_top/component.rb +35 -28
  78. data/app/components/hakumi_components/float_button/component.rb +161 -75
  79. data/app/components/hakumi_components/float_button/group/component.rb +85 -98
  80. data/app/components/hakumi_components/float_button/group_cluster/component.rb +46 -54
  81. data/app/components/hakumi_components/float_button/group_spec.rb +259 -0
  82. data/app/components/hakumi_components/float_button/item_spec.rb +115 -0
  83. data/app/components/hakumi_components/float_button/position_config.rb +68 -0
  84. data/app/components/hakumi_components/form/item/component.rb +34 -27
  85. data/app/components/hakumi_components/grid/col/component.rb +88 -17
  86. data/app/components/hakumi_components/grid/row/component.rb +35 -17
  87. data/app/components/hakumi_components/icon/component.rb +49 -49
  88. data/app/components/hakumi_components/image/component.html.erb +7 -7
  89. data/app/components/hakumi_components/image/component.rb +103 -40
  90. data/app/components/hakumi_components/image/preview_group/component.html.erb +1 -1
  91. data/app/components/hakumi_components/image/preview_group/component.rb +41 -36
  92. data/app/components/hakumi_components/image/preview_group/item.rb +48 -0
  93. data/app/components/hakumi_components/input/component.html.erb +1 -1
  94. data/app/components/hakumi_components/input/component.rb +142 -66
  95. data/app/components/hakumi_components/input/password/component.rb +30 -41
  96. data/app/components/hakumi_components/input/text_area/component.html.erb +2 -2
  97. data/app/components/hakumi_components/input/text_area/component.rb +89 -42
  98. data/app/components/hakumi_components/input_number/component.html.erb +5 -5
  99. data/app/components/hakumi_components/input_number/component.rb +137 -55
  100. data/app/components/hakumi_components/layout/component.html.erb +1 -1
  101. data/app/components/hakumi_components/layout/component.rb +23 -16
  102. data/app/components/hakumi_components/layout/content/component.html.erb +1 -1
  103. data/app/components/hakumi_components/layout/content/component.rb +24 -14
  104. data/app/components/hakumi_components/layout/footer/component.html.erb +1 -1
  105. data/app/components/hakumi_components/layout/footer/component.rb +24 -14
  106. data/app/components/hakumi_components/layout/header/component.html.erb +1 -1
  107. data/app/components/hakumi_components/layout/header/component.rb +24 -14
  108. data/app/components/hakumi_components/layout/sider/component.html.erb +1 -1
  109. data/app/components/hakumi_components/layout/sider/component.rb +83 -49
  110. data/app/components/hakumi_components/mentions/coercion.rb +46 -0
  111. data/app/components/hakumi_components/mentions/component.html.erb +6 -6
  112. data/app/components/hakumi_components/mentions/component.rb +131 -67
  113. data/app/components/hakumi_components/mentions/option.rb +30 -0
  114. data/app/components/hakumi_components/menu/component.rb +50 -59
  115. data/app/components/hakumi_components/menu/divider/component.rb +4 -0
  116. data/app/components/hakumi_components/menu/group/component.rb +16 -15
  117. data/app/components/hakumi_components/menu/item/component.rb +42 -19
  118. data/app/components/hakumi_components/menu/sub_menu/component.rb +49 -37
  119. data/app/components/hakumi_components/message/component.rb +62 -27
  120. data/app/components/hakumi_components/modal/component.html.erb +6 -6
  121. data/app/components/hakumi_components/modal/component.rb +93 -40
  122. data/app/components/hakumi_components/modal/confirm/component.html.erb +2 -2
  123. data/app/components/hakumi_components/modal/confirm/component.rb +49 -8
  124. data/app/components/hakumi_components/modal/error/component.rb +8 -39
  125. data/app/components/hakumi_components/modal/info/component.rb +6 -40
  126. data/app/components/hakumi_components/modal/status_component.rb +150 -0
  127. data/app/components/hakumi_components/modal/success/component.rb +6 -40
  128. data/app/components/hakumi_components/modal/warning/component.rb +14 -44
  129. data/app/components/hakumi_components/notification/component.rb +72 -38
  130. data/app/components/hakumi_components/pagination/component.html.erb +7 -11
  131. data/app/components/hakumi_components/pagination/component.rb +105 -72
  132. data/app/components/hakumi_components/pagination/page_item.rb +49 -0
  133. data/app/components/hakumi_components/popconfirm/component.rb +67 -19
  134. data/app/components/hakumi_components/popover/component.html.erb +2 -2
  135. data/app/components/hakumi_components/popover/component.rb +102 -38
  136. data/app/components/hakumi_components/progress/attribute_renderer.rb +354 -0
  137. data/app/components/hakumi_components/progress/circle_geometry.rb +76 -0
  138. data/app/components/hakumi_components/progress/component.html.erb +18 -17
  139. data/app/components/hakumi_components/progress/component.rb +230 -402
  140. data/app/components/hakumi_components/progress/controller_locals_parser.rb +130 -0
  141. data/app/components/hakumi_components/progress/info_tooltip_policy.rb +39 -0
  142. data/app/components/hakumi_components/progress/info_value.rb +75 -0
  143. data/app/components/hakumi_components/progress/status_state.rb +31 -0
  144. data/app/components/hakumi_components/progress/steps_renderer.rb +149 -0
  145. data/app/components/hakumi_components/progress/stroke_gradient_value.rb +90 -0
  146. data/app/components/hakumi_components/qr_code/component.rb +138 -72
  147. data/app/components/hakumi_components/radio/component.html.erb +3 -3
  148. data/app/components/hakumi_components/radio/component.rb +42 -9
  149. data/app/components/hakumi_components/radio/group/component.html.erb +9 -9
  150. data/app/components/hakumi_components/radio/group/component.rb +76 -27
  151. data/app/components/hakumi_components/radio/group/option.rb +43 -0
  152. data/app/components/hakumi_components/rate/component.html.erb +1 -1
  153. data/app/components/hakumi_components/rate/component.rb +55 -9
  154. data/app/components/hakumi_components/result/component.rb +87 -37
  155. data/app/components/hakumi_components/segmented/component.html.erb +1 -1
  156. data/app/components/hakumi_components/segmented/component.rb +81 -118
  157. data/app/components/hakumi_components/segmented/option.rb +191 -0
  158. data/app/components/hakumi_components/select/component.html.erb +19 -19
  159. data/app/components/hakumi_components/select/component.rb +88 -37
  160. data/app/components/hakumi_components/selection_control/coercion.rb +161 -0
  161. data/app/components/hakumi_components/selection_control/entry.rb +22 -0
  162. data/app/components/hakumi_components/selection_control/option.rb +42 -0
  163. data/app/components/hakumi_components/selection_control/option_group.rb +34 -0
  164. data/app/components/hakumi_components/selection_control/tree_node.rb +123 -0
  165. data/app/components/hakumi_components/skeleton/avatar/component.rb +36 -14
  166. data/app/components/hakumi_components/skeleton/avatar_config.rb +79 -0
  167. data/app/components/hakumi_components/skeleton/button/component.rb +32 -14
  168. data/app/components/hakumi_components/skeleton/component.rb +79 -93
  169. data/app/components/hakumi_components/skeleton/image/component.rb +46 -22
  170. data/app/components/hakumi_components/skeleton/input/component.rb +25 -10
  171. data/app/components/hakumi_components/skeleton/node/component.rb +23 -7
  172. data/app/components/hakumi_components/skeleton/paragraph_config.rb +92 -0
  173. data/app/components/hakumi_components/skeleton/title_config.rb +31 -0
  174. data/app/components/hakumi_components/slider/component.html.erb +11 -11
  175. data/app/components/hakumi_components/slider/component.rb +172 -53
  176. data/app/components/hakumi_components/slider/mark.rb +27 -0
  177. data/app/components/hakumi_components/space/compact/component.html.erb +1 -3
  178. data/app/components/hakumi_components/space/compact/component.rb +35 -19
  179. data/app/components/hakumi_components/space/component.html.erb +1 -1
  180. data/app/components/hakumi_components/space/component.rb +75 -39
  181. data/app/components/hakumi_components/spin/component.rb +92 -31
  182. data/app/components/hakumi_components/splitter/component.html.erb +9 -3
  183. data/app/components/hakumi_components/splitter/component.rb +55 -22
  184. data/app/components/hakumi_components/splitter/panel/component.html.erb +1 -3
  185. data/app/components/hakumi_components/splitter/panel/component.rb +64 -39
  186. data/app/components/hakumi_components/statistic/component.rb +143 -59
  187. data/app/components/hakumi_components/steps/component.rb +87 -55
  188. data/app/components/hakumi_components/steps/item/component.rb +59 -20
  189. data/app/components/hakumi_components/switch/component.html.erb +2 -3
  190. data/app/components/hakumi_components/switch/component.rb +62 -36
  191. data/app/components/hakumi_components/table/column/component.rb +80 -23
  192. data/app/components/hakumi_components/table/column_definition.rb +181 -0
  193. data/app/components/hakumi_components/table/column_group/component.rb +32 -21
  194. data/app/components/hakumi_components/table/component.html.erb +22 -31
  195. data/app/components/hakumi_components/table/component.rb +248 -509
  196. data/app/components/hakumi_components/table/concerns/attribute_helpers.rb +22 -0
  197. data/app/components/hakumi_components/table/concerns/cell_rendering.rb +277 -0
  198. data/app/components/hakumi_components/table/concerns/columns.rb +293 -91
  199. data/app/components/hakumi_components/table/concerns/editable.rb +36 -89
  200. data/app/components/hakumi_components/table/concerns/ellipsis.rb +31 -30
  201. data/app/components/hakumi_components/table/concerns/fixed_columns.rb +26 -19
  202. data/app/components/hakumi_components/table/concerns/surface_rendering.rb +365 -0
  203. data/app/components/hakumi_components/table/configs.rb +518 -0
  204. data/app/components/hakumi_components/table/definition_types.rb +40 -0
  205. data/app/components/hakumi_components/table/display_rows.rb +56 -0
  206. data/app/components/hakumi_components/table/ellipsis_config.rb +143 -0
  207. data/app/components/hakumi_components/table/fixed_offset.rb +36 -0
  208. data/app/components/hakumi_components/table/header_cell.rb +23 -0
  209. data/app/components/hakumi_components/table/row_record.rb +60 -0
  210. data/app/components/hakumi_components/table/row_render_state.rb +85 -0
  211. data/app/components/hakumi_components/tabs/component.html.erb +8 -8
  212. data/app/components/hakumi_components/tabs/component.rb +114 -64
  213. data/app/components/hakumi_components/tabs/indicator_config.rb +34 -0
  214. data/app/components/hakumi_components/tabs/item/component.rb +73 -13
  215. data/app/components/hakumi_components/tag/component.rb +142 -47
  216. data/app/components/hakumi_components/tag/group/component.rb +18 -6
  217. data/app/components/hakumi_components/time_picker/component.html.erb +8 -8
  218. data/app/components/hakumi_components/time_picker/component.rb +71 -17
  219. data/app/components/hakumi_components/timeline/component.html.erb +3 -2
  220. data/app/components/hakumi_components/timeline/component.rb +188 -55
  221. data/app/components/hakumi_components/timeline/item/component.html.erb +2 -2
  222. data/app/components/hakumi_components/timeline/item/component.rb +121 -60
  223. data/app/components/hakumi_components/tooltip/component.html.erb +1 -1
  224. data/app/components/hakumi_components/tooltip/component.rb +89 -64
  225. data/app/components/hakumi_components/tour/component.rb +74 -49
  226. data/app/components/hakumi_components/tour/step.rb +86 -0
  227. data/app/components/hakumi_components/transfer/coercion.rb +66 -0
  228. data/app/components/hakumi_components/transfer/component.html.erb +171 -140
  229. data/app/components/hakumi_components/transfer/component.rb +232 -82
  230. data/app/components/hakumi_components/transfer/item.rb +54 -0
  231. data/app/components/hakumi_components/transfer/operations.rb +31 -0
  232. data/app/components/hakumi_components/tree/component.rb +219 -400
  233. data/app/components/hakumi_components/tree/concerns/node_rendering.rb +365 -0
  234. data/app/components/hakumi_components/tree/node.rb +276 -0
  235. data/app/components/hakumi_components/tree_select/component.html.erb +23 -9
  236. data/app/components/hakumi_components/tree_select/component.rb +116 -107
  237. data/app/components/hakumi_components/typography/base_component.rb +69 -36
  238. data/app/components/hakumi_components/typography/link/component.rb +63 -6
  239. data/app/components/hakumi_components/typography/paragraph/component.rb +6 -0
  240. data/app/components/hakumi_components/typography/text/component.rb +4 -0
  241. data/app/components/hakumi_components/typography/title/component.rb +61 -6
  242. data/app/components/hakumi_components/upload/component.html.erb +106 -88
  243. data/app/components/hakumi_components/upload/component.rb +222 -48
  244. data/app/components/hakumi_components/upload/file_entry.rb +53 -0
  245. data/app/controllers/hakumi_components/components_controller.rb +2 -1
  246. data/app/form_builders/hakumi_components/form_builder/contracts.rb +55 -0
  247. data/app/form_builders/hakumi_components/form_builder/field_context.rb +43 -0
  248. data/app/form_builders/hakumi_components/form_builder/model_binding.rb +163 -0
  249. data/app/form_builders/hakumi_components/form_builder.rb +238 -317
  250. data/app/helpers/hakumi_components/form_helper.rb +27 -23
  251. data/app/javascript/hakumi_components/controllers/hakumi/admin_panel_controller.js +5 -7
  252. data/app/javascript/hakumi_components/controllers/hakumi/back_top_controller.js +1 -1
  253. data/app/javascript/hakumi_components/controllers/hakumi/button_controller.js +108 -2
  254. data/app/javascript/hakumi_components/controllers/hakumi/calendar_controller.js +183 -95
  255. data/app/javascript/hakumi_components/controllers/hakumi/color_picker_controller.js +23 -285
  256. data/app/javascript/hakumi_components/controllers/hakumi/date_picker_controller.js +274 -262
  257. data/app/javascript/hakumi_components/controllers/hakumi/float_button_group_controller.js +2 -2
  258. data/app/javascript/hakumi_components/controllers/hakumi/message_controller.js +4 -2
  259. data/app/javascript/hakumi_components/controllers/hakumi/modal_controller.js +119 -125
  260. data/app/javascript/hakumi_components/controllers/hakumi/table/editable.js +291 -0
  261. data/app/javascript/hakumi_components/controllers/hakumi/table_controller.js +166 -366
  262. data/app/javascript/hakumi_components/controllers/hakumi/tabs_controller.js +8 -2
  263. data/app/javascript/hakumi_components/controllers/hakumi/tag_controller.js +27 -25
  264. data/app/javascript/hakumi_components/controllers/hakumi/tag_group_controller.js +19 -18
  265. data/app/javascript/hakumi_components/controllers/hakumi/theme_controller.js +116 -117
  266. data/app/javascript/hakumi_components/controllers/hakumi/transfer_controller.js +17 -1
  267. data/app/javascript/hakumi_components/controllers/hakumi/tree_controller.js +363 -78
  268. data/app/javascript/hakumi_components/controllers/hakumi/typography_controller.js +3 -3
  269. data/app/javascript/hakumi_components/controllers/hakumi/upload_controller.js +320 -204
  270. data/app/javascript/hakumi_components/core/render_component.js +37 -11
  271. data/app/javascript/hakumi_components/utils/color_helper.js +262 -0
  272. data/app/javascript/stylesheets/_base.scss +9 -0
  273. data/app/javascript/stylesheets/_hakumi_components.scss +1 -0
  274. data/app/javascript/stylesheets/components/_breadcrumb.scss +2 -2
  275. data/app/javascript/stylesheets/components/_calendar.scss +13 -13
  276. data/app/javascript/stylesheets/components/_cascader.scss +5 -5
  277. data/app/javascript/stylesheets/components/_checkbox.scss +9 -11
  278. data/app/javascript/stylesheets/components/_color_picker.scss +11 -11
  279. data/app/javascript/stylesheets/components/_date_picker.scss +4 -4
  280. data/app/javascript/stylesheets/components/_descriptions.scss +2 -2
  281. data/app/javascript/stylesheets/components/_drawer.scss +3 -3
  282. data/app/javascript/stylesheets/components/_dropdown.scss +2 -2
  283. data/app/javascript/stylesheets/components/_flex.scss +1 -1
  284. data/app/javascript/stylesheets/components/_float_button.scss +5 -5
  285. data/app/javascript/stylesheets/components/_form_item.scss +92 -0
  286. data/app/javascript/stylesheets/components/_image.scss +15 -15
  287. data/app/javascript/stylesheets/components/_input.scss +23 -113
  288. data/app/javascript/stylesheets/components/_layout.scss +27 -26
  289. data/app/javascript/stylesheets/components/_menu.scss +15 -15
  290. data/app/javascript/stylesheets/components/_modal.scss +13 -13
  291. data/app/javascript/stylesheets/components/_notification.scss +3 -3
  292. data/app/javascript/stylesheets/components/_popover.scss +1 -1
  293. data/app/javascript/stylesheets/components/_segmented.scss +3 -3
  294. data/app/javascript/stylesheets/components/_select.scss +6 -6
  295. data/app/javascript/stylesheets/components/_slider.scss +1 -1
  296. data/app/javascript/stylesheets/components/_spin.scss +2 -2
  297. data/app/javascript/stylesheets/components/_steps.scss +10 -10
  298. data/app/javascript/stylesheets/components/_switch.scss +11 -10
  299. data/app/javascript/stylesheets/components/_table.scss +6 -6
  300. data/app/javascript/stylesheets/components/_tag.scss +2 -2
  301. data/app/javascript/stylesheets/components/_tooltip.scss +4 -4
  302. data/app/javascript/stylesheets/components/_tree_select.scss +3 -3
  303. data/app/javascript/stylesheets/components/_typography.scss +3 -3
  304. data/app/javascript/stylesheets/components/_upload.scss +4 -4
  305. data/app/services/hakumi_components/component_handler.rb +95 -24
  306. data/app/services/hakumi_components/icon/loader.rb +84 -67
  307. data/app/services/hakumi_components/illustrations/loader.rb +35 -27
  308. data/app/views/hakumi/_alert.html.erb +4 -1
  309. data/app/views/hakumi/_drawer.html.erb +1 -1
  310. data/app/views/hakumi/_statistic.html.erb +11 -11
  311. data/lib/generators/hakumi_components/install_generator.rb +14 -1
  312. data/lib/hakumi_components/documentation/models.rb +290 -0
  313. data/lib/hakumi_components/documentation/node.rb +73 -0
  314. data/lib/hakumi_components/documentation.rb +139 -62
  315. data/lib/hakumi_components/engine.rb +17 -11
  316. data/lib/hakumi_components/file_size_checker.rb +215 -0
  317. data/lib/hakumi_components/locales/en.yml +115 -0
  318. data/lib/hakumi_components/rails/attribute_introspection.rb +39 -107
  319. data/lib/hakumi_components/rails/model_reflection.rb +154 -0
  320. data/lib/hakumi_components/rails/validation_introspection.rb +25 -73
  321. data/lib/hakumi_components/rails/validation_mapper.rb +148 -201
  322. data/lib/hakumi_components/rails.rb +2 -0
  323. data/lib/hakumi_components/stylesheet_ownership_checker.rb +191 -0
  324. data/lib/hakumi_components/types/form_field.rb +24 -0
  325. data/lib/hakumi_components/types/html.rb +28 -0
  326. data/lib/hakumi_components/types/rendering.rb +10 -0
  327. data/lib/hakumi_components/types/stimulus.rb +19 -0
  328. data/lib/hakumi_components/types/validation.rb +22 -0
  329. data/lib/hakumi_components/types.rb +13 -0
  330. data/lib/hakumi_components/version.rb +2 -1
  331. data/lib/hakumi_components.rb +26 -6
  332. data/lib/tasks/coverage.rake +1 -0
  333. metadata +100 -136
  334. data/sig/action_view/tag_builder.rbs +0 -24
  335. data/sig/active_support/concern_patch.rbs +0 -7
  336. data/sig/generators/hakumi_components/install_generator.rbs +0 -11
  337. data/sig/hakumi_components/admin_panel/component.rbs +0 -28
  338. data/sig/hakumi_components/affix/component.rbs +0 -44
  339. data/sig/hakumi_components/alert/component.rbs +0 -64
  340. data/sig/hakumi_components/anchor/component.rbs +0 -45
  341. data/sig/hakumi_components/anchor/link/component.rbs +0 -31
  342. data/sig/hakumi_components/autocomplete/component.rbs +0 -68
  343. data/sig/hakumi_components/avatar/component.rbs +0 -44
  344. data/sig/hakumi_components/badge/component.rbs +0 -41
  345. data/sig/hakumi_components/base_component.rbs +0 -82
  346. data/sig/hakumi_components/breadcrumb/component.rbs +0 -21
  347. data/sig/hakumi_components/breadcrumb/item/component.rbs +0 -36
  348. data/sig/hakumi_components/button/component.rbs +0 -56
  349. data/sig/hakumi_components/calendar/component.rbs +0 -41
  350. data/sig/hakumi_components/card/component.rbs +0 -50
  351. data/sig/hakumi_components/card/grid/component.rbs +0 -21
  352. data/sig/hakumi_components/card/meta/component.rbs +0 -25
  353. data/sig/hakumi_components/carousel/component.rbs +0 -88
  354. data/sig/hakumi_components/cascader/component.rbs +0 -52
  355. data/sig/hakumi_components/checkbox/component.rbs +0 -46
  356. data/sig/hakumi_components/checkbox/group/component.rbs +0 -34
  357. data/sig/hakumi_components/collapse/component.rbs +0 -57
  358. data/sig/hakumi_components/collapse/panel/component.rbs +0 -47
  359. data/sig/hakumi_components/color_picker/component.rbs +0 -80
  360. data/sig/hakumi_components/concerns/form_field.rbs +0 -89
  361. data/sig/hakumi_components/container/component.rbs +0 -37
  362. data/sig/hakumi_components/date_picker/component.rbs +0 -72
  363. data/sig/hakumi_components/date_picker/range_picker.rbs +0 -68
  364. data/sig/hakumi_components/date_picker/shared_rendering.rbs +0 -41
  365. data/sig/hakumi_components/descriptions/component.rbs +0 -59
  366. data/sig/hakumi_components/descriptions/item/component.rbs +0 -31
  367. data/sig/hakumi_components/divider/component.rbs +0 -39
  368. data/sig/hakumi_components/drawer/component.rbs +0 -66
  369. data/sig/hakumi_components/dropdown/component.rbs +0 -45
  370. data/sig/hakumi_components/dropdown/divider/component.rbs +0 -11
  371. data/sig/hakumi_components/dropdown/item/component.rbs +0 -45
  372. data/sig/hakumi_components/empty/component.rbs +0 -40
  373. data/sig/hakumi_components/flex/component.rbs +0 -47
  374. data/sig/hakumi_components/float_button/back_top/component.rbs +0 -45
  375. data/sig/hakumi_components/float_button/component.rbs +0 -80
  376. data/sig/hakumi_components/float_button/group/component.rbs +0 -95
  377. data/sig/hakumi_components/float_button/group_cluster/component.rbs +0 -60
  378. data/sig/hakumi_components/form/item/component.rbs +0 -39
  379. data/sig/hakumi_components/form_builder.rbs +0 -37
  380. data/sig/hakumi_components/grid/col/component.rbs +0 -50
  381. data/sig/hakumi_components/grid/row/component.rbs +0 -46
  382. data/sig/hakumi_components/icon/component.rbs +0 -56
  383. data/sig/hakumi_components/image/component.rbs +0 -56
  384. data/sig/hakumi_components/image/preview_group/component.rbs +0 -28
  385. data/sig/hakumi_components/input/component.rbs +0 -71
  386. data/sig/hakumi_components/input/password/component.rbs +0 -27
  387. data/sig/hakumi_components/input/text_area/component.rbs +0 -42
  388. data/sig/hakumi_components/input_number/component.rbs +0 -85
  389. data/sig/hakumi_components/layout/component.rbs +0 -21
  390. data/sig/hakumi_components/layout/content/component.rbs +0 -19
  391. data/sig/hakumi_components/layout/footer/component.rbs +0 -19
  392. data/sig/hakumi_components/layout/header/component.rbs +0 -19
  393. data/sig/hakumi_components/layout/sider/component.rbs +0 -19
  394. data/sig/hakumi_components/mentions/component.rbs +0 -75
  395. data/sig/hakumi_components/menu/component.rbs +0 -52
  396. data/sig/hakumi_components/menu/divider/component.rbs +0 -11
  397. data/sig/hakumi_components/menu/group/component.rbs +0 -20
  398. data/sig/hakumi_components/menu/item/component.rbs +0 -42
  399. data/sig/hakumi_components/menu/sub_menu/component.rbs +0 -37
  400. data/sig/hakumi_components/message/component.rbs +0 -50
  401. data/sig/hakumi_components/modal/component.rbs +0 -76
  402. data/sig/hakumi_components/modal/confirm/component.rbs +0 -32
  403. data/sig/hakumi_components/modal/error/component.rbs +0 -27
  404. data/sig/hakumi_components/modal/info/component.rbs +0 -27
  405. data/sig/hakumi_components/modal/success/component.rbs +0 -27
  406. data/sig/hakumi_components/modal/warning/component.rbs +0 -27
  407. data/sig/hakumi_components/notification/component.rbs +0 -64
  408. data/sig/hakumi_components/pagination/component.rbs +0 -113
  409. data/sig/hakumi_components/popconfirm/component.rbs +0 -52
  410. data/sig/hakumi_components/popover/component.rbs +0 -66
  411. data/sig/hakumi_components/progress/component.rbs +0 -201
  412. data/sig/hakumi_components/qr_code/component.rbs +0 -111
  413. data/sig/hakumi_components/radio/component.rbs +0 -55
  414. data/sig/hakumi_components/radio/group/component.rbs +0 -71
  415. data/sig/hakumi_components/rails/attribute_introspection.rbs +0 -16
  416. data/sig/hakumi_components/rails/validation_introspection.rbs +0 -18
  417. data/sig/hakumi_components/rails/validation_mapper.rbs +0 -53
  418. data/sig/hakumi_components/rails.rbs +0 -6
  419. data/sig/hakumi_components/rate/component.rbs +0 -56
  420. data/sig/hakumi_components/result/component.rbs +0 -65
  421. data/sig/hakumi_components/segmented/component.rbs +0 -72
  422. data/sig/hakumi_components/select/component.rbs +0 -73
  423. data/sig/hakumi_components/skeleton/avatar/component.rbs +0 -23
  424. data/sig/hakumi_components/skeleton/button/component.rbs +0 -24
  425. data/sig/hakumi_components/skeleton/component.rbs +0 -76
  426. data/sig/hakumi_components/skeleton/image/component.rbs +0 -22
  427. data/sig/hakumi_components/skeleton/input/component.rbs +0 -23
  428. data/sig/hakumi_components/skeleton/node/component.rbs +0 -21
  429. data/sig/hakumi_components/slider/component.rbs +0 -85
  430. data/sig/hakumi_components/space/compact/component.rbs +0 -21
  431. data/sig/hakumi_components/space/component.rbs +0 -47
  432. data/sig/hakumi_components/spin/component.rbs +0 -77
  433. data/sig/hakumi_components/splitter/component.rbs +0 -27
  434. data/sig/hakumi_components/splitter/panel/component.rbs +0 -36
  435. data/sig/hakumi_components/statistic/component.rbs +0 -97
  436. data/sig/hakumi_components/steps/component.rbs +0 -82
  437. data/sig/hakumi_components/steps/item/component.rbs +0 -29
  438. data/sig/hakumi_components/switch/component.rbs +0 -52
  439. data/sig/hakumi_components/table/column/component.rbs +0 -57
  440. data/sig/hakumi_components/table/column_group/component.rbs +0 -20
  441. data/sig/hakumi_components/table/component.rbs +0 -279
  442. data/sig/hakumi_components/table/concerns/columns.rbs +0 -95
  443. data/sig/hakumi_components/table/concerns/editable.rbs +0 -40
  444. data/sig/hakumi_components/table/concerns/ellipsis.rbs +0 -27
  445. data/sig/hakumi_components/table/concerns/fixed_columns.rbs +0 -33
  446. data/sig/hakumi_components/tabs/component.rbs +0 -66
  447. data/sig/hakumi_components/tabs/item/component.rbs +0 -29
  448. data/sig/hakumi_components/tag/component.rbs +0 -86
  449. data/sig/hakumi_components/tag/group/component.rbs +0 -24
  450. data/sig/hakumi_components/time_picker/component.rbs +0 -72
  451. data/sig/hakumi_components/timeline/component.rbs +0 -61
  452. data/sig/hakumi_components/timeline/item/component.rbs +0 -70
  453. data/sig/hakumi_components/tooltip/component.rbs +0 -73
  454. data/sig/hakumi_components/tour/component.rbs +0 -84
  455. data/sig/hakumi_components/transfer/component.rbs +0 -75
  456. data/sig/hakumi_components/tree/component.rbs +0 -126
  457. data/sig/hakumi_components/tree_select/component.rbs +0 -122
  458. data/sig/hakumi_components/typography/base_component.rbs +0 -57
  459. data/sig/hakumi_components/typography/link/component.rbs +0 -28
  460. data/sig/hakumi_components/typography/paragraph/component.rbs +0 -13
  461. data/sig/hakumi_components/typography/text/component.rbs +0 -10
  462. data/sig/hakumi_components/typography/title/component.rbs +0 -28
  463. data/sig/hakumi_components/upload/component.rbs +0 -78
  464. data/sig/hakumi_components.rbs +0 -96
  465. data/sig/rails/active_model/validations/comparison_validator.rbs +0 -6
  466. data/sig/rails.rbs +0 -18
  467. data/sig/view_component/base.rbs +0 -28
@@ -1,39 +1,72 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module HakumiComponents
4
5
  module Table
5
6
  module Concerns
6
- # Column normalization and management for Table component
7
7
  module Columns
8
8
  extend ActiveSupport::Concern
9
+ extend T::Sig
10
+ extend T::Helpers
9
11
 
10
- # Get all leaf columns (columns without children)
12
+ abstract!
13
+
14
+ ColumnInputValue = T.type_alias { HakumiComponents::Table::DefinitionTypes::DefinitionValue }
15
+ ColumnInputHash = T.type_alias { HakumiComponents::Table::DefinitionTypes::RawRecord }
16
+ ColumnInput = T.type_alias { T.any(Symbol, ColumnInputHash) }
17
+
18
+ sig { abstract.returns(T::Array[ColumnInput]) }
19
+ def columns_data; end
20
+
21
+ sig { abstract.returns(T.nilable(HakumiComponents::Table::Configs::RowSelection)) }
22
+ def row_selection_config; end
23
+
24
+ sig { abstract.returns(T.nilable(HakumiComponents::Table::Configs::Expandable)) }
25
+ def expandable_config; end
26
+
27
+ sig { abstract.returns(T.nilable(HakumiComponents::Table::Configs::RowDrag)) }
28
+ def row_drag_config; end
29
+
30
+ sig { abstract.params(prefix: String).returns(String) }
31
+ def generate_id(prefix = ""); end
32
+
33
+ sig { abstract.params(value: HakumiComponents::Table::DefinitionTypes::WidthValue).returns(T.nilable(String)) }
34
+ def dimension_to_css(value); end
35
+
36
+ sig { abstract.params(leaf_columns: T::Array[HakumiComponents::Table::ColumnDefinition]).void }
37
+ def compute_fixed_offsets(leaf_columns); end
38
+
39
+ sig { returns(T::Array[HakumiComponents::Table::ColumnDefinition]) }
11
40
  def leaf_columns
41
+ @leaf_columns = T.let(@leaf_columns, T.nilable(T::Array[HakumiComponents::Table::ColumnDefinition])) if defined?(@leaf_columns)
42
+ @leaf_columns = T.let(nil, T.nilable(T::Array[HakumiComponents::Table::ColumnDefinition])) unless defined?(@leaf_columns)
12
43
  @leaf_columns ||= flatten_leaf_columns(normalized_columns)
13
44
  end
14
45
 
15
- # Count of leaf columns (minimum 1)
46
+ sig { returns(Integer) }
16
47
  def leaf_column_count
17
48
  [ leaf_columns.size, 1 ].max
18
49
  end
19
50
 
20
- # Check if table has selection column
51
+ sig { returns(T::Boolean) }
21
52
  def selection_column?
22
- @row_selection.present?
53
+ !row_selection_config.nil?
23
54
  end
24
55
 
25
- # Check if table has expandable rows
56
+ sig { returns(T::Boolean) }
26
57
  def expandable?
27
- @expandable.present?
58
+ !expandable_config.nil?
28
59
  end
29
60
 
30
- # Check if table has expand column
61
+ sig { returns(T::Boolean) }
31
62
  def expand_column?
32
- expandable? && @expandable[:show_expand_column] != false
63
+ expandable_config&.show_expand_column == true
33
64
  end
34
65
 
35
- # Get normalized columns with special columns inserted
66
+ sig { returns(T::Array[HakumiComponents::Table::ColumnDefinition]) }
36
67
  def normalized_columns
68
+ @normalized_columns = T.let(@normalized_columns, T.nilable(T::Array[HakumiComponents::Table::ColumnDefinition])) if defined?(@normalized_columns)
69
+ @normalized_columns = T.let(nil, T.nilable(T::Array[HakumiComponents::Table::ColumnDefinition])) unless defined?(@normalized_columns)
37
70
  @normalized_columns ||= begin
38
71
  columns = normalize_columns(columns_data)
39
72
  columns = insert_drag_handle_column(columns)
@@ -47,10 +80,9 @@ module HakumiComponents
47
80
 
48
81
  private
49
82
 
50
- # Normalize an array of column definitions
83
+ sig { params(columns: T::Array[ColumnInput]).returns(T::Array[HakumiComponents::Table::ColumnDefinition]) }
51
84
  def normalize_columns(columns)
52
- Array(columns).filter_map do |column|
53
- column = column.public_send(:to_h) if column.respond_to?(:to_h)
85
+ columns.filter_map do |column|
54
86
  if column == HakumiComponents::Table::Component::SELECTION_COLUMN
55
87
  selection_column_definition
56
88
  elsif column == HakumiComponents::Table::Component::EXPAND_COLUMN
@@ -61,158 +93,328 @@ module HakumiComponents
61
93
  end
62
94
  end
63
95
 
64
- # Normalize a single column definition
96
+ sig { params(column: ColumnInputHash).returns(T.nilable(HakumiComponents::Table::ColumnDefinition)) }
65
97
  def normalize_column(column)
66
98
  normalized = column.deep_symbolize_keys
67
- return nil if normalized[:hidden]
68
-
69
- normalized[:key] ||= normalized[:data_index] || generate_id("col")
70
- normalized[:ellipsis] = normalize_ellipsis_option(normalized[:ellipsis])
71
- normalized[:responsive] = normalize_responsive_breakpoints(normalized[:responsive])
72
- normalized[:width] = dimension_to_css(normalized[:width])
73
- normalized[:fixed] = normalized[:fixed]&.to_sym if normalized[:fixed]
74
- normalized[:fixed] = nil unless HakumiComponents::Table::Component::FIXED_SIDES.include?(normalized[:fixed])
75
- normalized[:editable] = normalize_column_editable(normalized[:editable])
76
- normalized[:should_cell_update] = normalize_should_cell_update(normalized[:should_cell_update])
77
- if normalized[:children]
78
- normalized[:children] = normalize_columns(normalized[:children])
79
- end
80
- normalized
99
+ return nil if normalized[:hidden] == true
100
+
101
+ key = normalized[:key]
102
+ data_index = normalized[:data_index]
103
+ resolved_key = key.is_a?(String) || key.is_a?(Symbol) ? key : data_index
104
+ resolved_key = generate_id("col") unless resolved_key.is_a?(String) || resolved_key.is_a?(Symbol)
105
+
106
+ fixed = normalized[:fixed]
107
+ fixed = fixed.to_sym if fixed.is_a?(String) || fixed.is_a?(Symbol)
108
+ fixed = nil unless HakumiComponents::Table::Component::FIXED_SIDES.include?(fixed)
109
+
110
+ should_cell_update = normalize_should_cell_update(normalized[:should_cell_update])
111
+ children_input = normalized[:children]
112
+ children = children_input.is_a?(Array) ? normalize_columns(children_input.filter_map { |entry| entry.is_a?(Hash) || entry.is_a?(Symbol) ? entry : nil }) : []
113
+ align = normalized[:align]
114
+ ellipsis = normalized[:ellipsis]
115
+ on_cell = normalized[:on_cell]
116
+ class_name = normalized[:class_name]
117
+ render = normalized[:render]
118
+ title = normalize_definition_value(normalized[:title])
119
+
120
+ HakumiComponents::Table::ColumnDefinition.new(
121
+ title: title,
122
+ key: resolved_key,
123
+ data_index: data_index.is_a?(String) || data_index.is_a?(Symbol) ? data_index : nil,
124
+ align: align.is_a?(Symbol) ? align : nil,
125
+ width: normalize_width(normalized[:width]),
126
+ ellipsis: normalize_ellipsis_input(ellipsis),
127
+ sorter: normalize_sorter(normalized[:sorter]),
128
+ sort_directions: normalize_symbol_array(normalized[:sort_directions]),
129
+ sort_order: normalize_symbol(normalized[:sort_order]),
130
+ default_sort_order: normalize_symbol(normalized[:default_sort_order]),
131
+ filters: normalize_filters(normalized[:filters]),
132
+ filter_multiple: normalized[:filter_multiple] != false,
133
+ filtered_value: normalize_scalar_array(normalized[:filtered_value]),
134
+ default_filtered_value: normalize_scalar_array(normalized[:default_filtered_value]),
135
+ filter_mode: normalize_symbol(normalized[:filter_mode]),
136
+ filter_search: normalize_filter_search(normalized[:filter_search]),
137
+ hidden: false,
138
+ on_cell: on_cell.is_a?(Proc) ? on_cell : nil,
139
+ class_name: class_name.is_a?(String) ? class_name : nil,
140
+ responsive: normalize_responsive_breakpoints(normalized[:responsive]),
141
+ fixed: fixed.is_a?(Symbol) ? fixed : nil,
142
+ editable: normalize_column_editable(normalized[:editable]),
143
+ should_cell_update: should_cell_update,
144
+ drag_handle: normalized[:drag_handle] == true,
145
+ selection: normalized[:selection] == true,
146
+ expand: normalized[:expand] == true,
147
+ render: render.is_a?(Proc) ? render : nil,
148
+ sort_value: normalize_sort_value(normalized[:sort_value]),
149
+ children: children
150
+ )
81
151
  end
82
152
 
83
- # Insert drag handle column if needed
153
+ sig { params(columns: T::Array[HakumiComponents::Table::ColumnDefinition]).returns(T::Array[HakumiComponents::Table::ColumnDefinition]) }
84
154
  def insert_drag_handle_column(columns)
85
155
  return columns unless drag_handle_column?
156
+ return columns if columns.any?(&:drag_handle?)
86
157
 
87
- if columns.any? { |col| col.is_a?(Hash) && col[:drag_handle] }
88
- columns
89
- else
90
- [ drag_handle_column_definition, *columns ]
91
- end
158
+ [ drag_handle_column_definition, *columns ]
92
159
  end
93
160
 
94
- # Insert selection column if needed
161
+ sig { params(columns: T::Array[HakumiComponents::Table::ColumnDefinition]).returns(T::Array[HakumiComponents::Table::ColumnDefinition]) }
95
162
  def insert_selection_column(columns)
96
163
  return columns unless selection_column?
164
+ return columns if columns.any?(&:selection?)
97
165
 
98
- if columns.any? { |col| col.is_a?(Hash) && col[:selection] }
99
- columns
100
- elsif columns.any? { |col| col == HakumiComponents::Table::Component::SELECTION_COLUMN }
101
- columns.map { |col| col == HakumiComponents::Table::Component::SELECTION_COLUMN ? selection_column_definition : col }
102
- else
103
- [ selection_column_definition, *columns ]
104
- end
166
+ [ selection_column_definition, *columns ]
105
167
  end
106
168
 
107
- # Insert expand column if needed
169
+ sig { params(columns: T::Array[HakumiComponents::Table::ColumnDefinition]).returns(T::Array[HakumiComponents::Table::ColumnDefinition]) }
108
170
  def insert_expand_column(columns)
109
171
  return columns unless expand_column?
172
+ return columns if columns.any?(&:expand?)
110
173
 
111
- if columns.any? { |col| col.is_a?(Hash) && col[:expand] }
112
- columns
113
- elsif columns.any? { |col| col == HakumiComponents::Table::Component::EXPAND_COLUMN }
114
- columns.map { |col| col == HakumiComponents::Table::Component::EXPAND_COLUMN ? expand_column_definition : col }
115
- else
116
- [ expand_column_definition, *columns ]
117
- end
174
+ [ expand_column_definition, *columns ]
118
175
  end
119
176
 
120
- # Definition for selection column
177
+ sig { returns(HakumiComponents::Table::ColumnDefinition) }
121
178
  def selection_column_definition
122
- {
179
+ HakumiComponents::Table::ColumnDefinition.new(
123
180
  key: "__selection__",
124
181
  title: "",
125
182
  selection: true,
126
183
  width: "48px",
127
184
  align: :center
128
- }
185
+ )
129
186
  end
130
187
 
131
- # Definition for expand column
188
+ sig { returns(HakumiComponents::Table::ColumnDefinition) }
132
189
  def expand_column_definition
133
- {
190
+ HakumiComponents::Table::ColumnDefinition.new(
134
191
  key: "__expand__",
135
192
  title: "",
136
193
  expand: true,
137
194
  width: "48px",
138
195
  align: :center
139
- }
196
+ )
140
197
  end
141
198
 
142
- # Check if drag handle column should be added
199
+ sig { returns(T::Boolean) }
143
200
  def drag_handle_column?
144
- @row_drag&.dig(:handle)
201
+ row_drag_config&.handle == true
145
202
  end
146
203
 
147
- # Definition for drag handle column
204
+ sig { returns(HakumiComponents::Table::ColumnDefinition) }
148
205
  def drag_handle_column_definition
149
- {
206
+ HakumiComponents::Table::ColumnDefinition.new(
150
207
  key: "__drag__",
151
208
  title: "",
152
209
  drag_handle: true,
153
210
  width: "48px",
154
211
  align: :center
155
- }
212
+ )
156
213
  end
157
214
 
158
- # Get first data column (non-special column)
215
+ sig { returns(T.nilable(HakumiComponents::Table::ColumnDefinition)) }
159
216
  def first_data_column
160
- @first_data_column ||= leaf_columns.find { |col| !col[:selection] && !col[:expand] }
217
+ @first_data_column = T.let(@first_data_column, T.nilable(HakumiComponents::Table::ColumnDefinition)) if defined?(@first_data_column)
218
+ @first_data_column = T.let(nil, T.nilable(HakumiComponents::Table::ColumnDefinition)) unless defined?(@first_data_column)
219
+ @first_data_column ||= leaf_columns.find { |column| !column.selection? && !column.expand? && !column.drag_handle? }
161
220
  end
162
221
 
163
- # Recursively flatten all leaf columns
222
+ sig { params(columns: T::Array[HakumiComponents::Table::ColumnDefinition]).returns(T::Array[HakumiComponents::Table::ColumnDefinition]) }
164
223
  def flatten_leaf_columns(columns)
165
224
  columns.flat_map do |column|
166
- children = column[:children]
167
- if children.is_a?(Array) && children.present?
168
- flatten_leaf_columns(children)
169
- else
170
- [ column ]
171
- end
225
+ column.children.empty? ? [ column ] : flatten_leaf_columns(column.children)
172
226
  end
173
227
  end
174
228
 
175
- # Count leaf columns recursively
229
+ sig { params(columns: T::Array[HakumiComponents::Table::ColumnDefinition]).returns(Integer) }
176
230
  def leaf_count(columns)
177
- columns.sum do |column|
178
- children = column[:children]
179
- children.is_a?(Array) && children.present? ? leaf_count(children) : 1
180
- end
231
+ columns.sum { |column| column.children.empty? ? 1 : leaf_count(column.children) }
181
232
  end
182
233
 
183
- # Calculate maximum depth of column hierarchy
234
+ sig { params(columns: T::Array[HakumiComponents::Table::ColumnDefinition], depth: Integer).returns(Integer) }
184
235
  def max_depth(columns, depth = 1)
185
236
  columns.map do |column|
186
- children = column[:children]
187
- if children.is_a?(Array) && children.present?
188
- max_depth(children, depth + 1)
189
- else
190
- depth
191
- end
237
+ column.children.empty? ? depth : max_depth(column.children, depth + 1)
192
238
  end.max || depth
193
239
  end
194
240
 
195
- # Build header rows for rendering
241
+ sig { params(columns: T::Array[HakumiComponents::Table::ColumnDefinition]).returns(T::Array[T::Array[HakumiComponents::Table::HeaderCell]]) }
196
242
  def build_header_rows(columns)
197
243
  depth = max_depth(columns)
198
- rows = Array.new(depth) { [] }
244
+ rows = T.let(Array.new(depth) { [] }, T::Array[T::Array[HakumiComponents::Table::HeaderCell]])
199
245
  build_rows_recursive(columns, 0, rows, depth)
200
246
  rows
201
247
  end
202
248
 
203
- # Recursively build header rows
249
+ sig { params(columns: T::Array[HakumiComponents::Table::ColumnDefinition], level: Integer, rows: T::Array[T::Array[HakumiComponents::Table::HeaderCell]], max_depth: Integer).void }
204
250
  def build_rows_recursive(columns, level, rows, max_depth)
251
+ row = rows[level]
252
+ return if row.nil?
253
+
205
254
  columns.each do |column|
206
- children = column[:children]
207
- if children.is_a?(Array) && children.present?
208
- col_span = leaf_count(children)
209
- rows[level] << { column: column, col_span: col_span, row_span: 1 }
210
- build_rows_recursive(children, level + 1, rows, max_depth)
255
+ if column.children.empty?
256
+ row << HakumiComponents::Table::HeaderCell.new(column: column, col_span: 1, row_span: max_depth - level)
211
257
  else
212
- rows[level] << { column: column, col_span: 1, row_span: max_depth - level }
258
+ row << HakumiComponents::Table::HeaderCell.new(column: column, col_span: leaf_count(column.children), row_span: 1)
259
+ build_rows_recursive(column.children, level + 1, rows, max_depth)
213
260
  end
214
261
  end
215
262
  end
263
+
264
+ sig { params(value: HakumiComponents::Table::DefinitionTypes::RawEntry).returns(HakumiComponents::Table::DefinitionTypes::WidthValue) }
265
+ def normalize_width(value)
266
+ return value if value.is_a?(String) || value.is_a?(Numeric)
267
+ nil
268
+ end
269
+
270
+ sig { params(value: HakumiComponents::Table::DefinitionTypes::RawEntry).returns(HakumiComponents::Table::DefinitionTypes::SorterValue) }
271
+ def normalize_sorter(value)
272
+ return value if value.is_a?(String) || value.is_a?(Symbol) || value == true || value == false
273
+
274
+ nil
275
+ end
276
+
277
+ sig { params(value: HakumiComponents::Table::DefinitionTypes::RawEntry).returns(T.nilable(T::Array[Symbol])) }
278
+ def normalize_symbol_array(value)
279
+ return nil unless value.is_a?(Array)
280
+
281
+ entries = value.filter_map { |entry| entry.is_a?(String) || entry.is_a?(Symbol) ? entry.to_sym : nil }
282
+ entries.presence
283
+ end
284
+
285
+ sig { params(value: HakumiComponents::Table::DefinitionTypes::RawEntry).returns(T.nilable(Symbol)) }
286
+ def normalize_symbol(value)
287
+ return value if value.is_a?(Symbol)
288
+ return value.to_sym if value.is_a?(String) && value.present?
289
+
290
+ nil
291
+ end
292
+
293
+ sig { params(value: HakumiComponents::Table::DefinitionTypes::RawEntry).returns(HakumiComponents::Table::DefinitionTypes::SearchValue) }
294
+ def normalize_filter_search(value)
295
+ return value if value.is_a?(String) || value.is_a?(Symbol) || value == true || value == false
296
+
297
+ nil
298
+ end
299
+
300
+ sig { params(value: HakumiComponents::Table::DefinitionTypes::RawEntry).returns(T.nilable(HakumiComponents::Table::DefinitionTypes::ScalarList)) }
301
+ def normalize_scalar_array(value)
302
+ return nil unless value.is_a?(Array)
303
+
304
+ entries = value.filter_map do |entry|
305
+ entry if entry.is_a?(String) || entry.is_a?(Symbol) || entry.is_a?(Numeric)
306
+ end
307
+ entries.presence
308
+ end
309
+
310
+ sig { params(value: HakumiComponents::Table::DefinitionTypes::RawEntry).returns(T.nilable(String)) }
311
+ def normalize_should_cell_update(value)
312
+ case value
313
+ when Symbol
314
+ value.to_s
315
+ when String
316
+ value.strip.presence
317
+ end
318
+ end
319
+
320
+ sig { params(value: HakumiComponents::Table::DefinitionTypes::RawEntry).returns(HakumiComponents::Table::DefinitionTypes::SortValue) }
321
+ def normalize_sort_value(value)
322
+ return value if value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(Proc)
323
+
324
+ nil
325
+ end
326
+
327
+ sig { params(value: HakumiComponents::Table::DefinitionTypes::RawEntry).returns(T.nilable(HakumiComponents::Table::DefinitionTypes::DefinitionValue)) }
328
+ def normalize_definition_value(value)
329
+ return value if value.nil? || value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(Numeric) || value == true || value == false || value.is_a?(Proc)
330
+ return normalize_definition_array(value) if value.is_a?(Array)
331
+ return normalize_definition_hash(value) if value.is_a?(Hash)
332
+
333
+ nil
334
+ end
335
+
336
+ sig { params(value: HakumiComponents::Table::DefinitionTypes::RawEntry).returns(T.nilable(HakumiComponents::Table::Configs::ColumnEditable)) }
337
+ def normalize_column_editable(value)
338
+ return nil unless value.is_a?(Hash) || value == true || value == false || value.is_a?(HakumiComponents::Table::Configs::ColumnEditable)
339
+
340
+ HakumiComponents::Table::Configs::ColumnEditable.coerce(value)
341
+ end
342
+
343
+ sig { params(value: HakumiComponents::Table::DefinitionTypes::RawEntry).returns(T.nilable(T::Array[HakumiComponents::Table::DefinitionTypes::DefinitionHash])) }
344
+ def normalize_filters(value)
345
+ return nil unless value.is_a?(Array)
346
+
347
+ filters = value.filter_map do |entry|
348
+ next unless entry.is_a?(Hash)
349
+
350
+ normalized = entry.deep_symbolize_keys
351
+ filter = T.let({}, HakumiComponents::Table::DefinitionTypes::DefinitionHash)
352
+ normalized.each do |key, item|
353
+ normalized_item = normalize_definition_value(item)
354
+ next if normalized_item.nil? && !item.nil?
355
+
356
+ filter[key] = normalized_item
357
+ end
358
+ filter
359
+ end
360
+ filters.presence
361
+ end
362
+
363
+ sig { params(value: HakumiComponents::Table::DefinitionTypes::RawEntry).returns(T.nilable(T::Array[Symbol])) }
364
+ def normalize_responsive_breakpoints(value)
365
+ return nil if value.nil?
366
+
367
+ breakpoints = Array(value).filter_map do |breakpoint|
368
+ sym = breakpoint.to_s.downcase.to_sym
369
+ sym if HakumiComponents::Table::Component::RESPONSIVE_BREAKPOINTS.include?(sym)
370
+ end
371
+ breakpoints.presence
372
+ end
373
+
374
+ sig { params(value: HakumiComponents::Table::DefinitionTypes::RawEntry).returns(T.nilable(HakumiComponents::Table::EllipsisConfig)) }
375
+ def normalize_ellipsis_input(value)
376
+ return nil unless value == true || value == false || value.is_a?(String) || value.is_a?(Hash)
377
+
378
+ normalized = if value.is_a?(Hash)
379
+ value.each_with_object(T.let({}, HakumiComponents::Table::EllipsisConfig::InputHash)) do |(key, entry), result|
380
+ next unless key.is_a?(String) || key.is_a?(Symbol)
381
+
382
+ coerced_entry = normalize_definition_value(entry)
383
+ next if coerced_entry.nil? && !entry.nil?
384
+
385
+ result[key] = coerced_entry
386
+ end
387
+ else
388
+ value
389
+ end
390
+
391
+ HakumiComponents::Table::EllipsisConfig.from_input(normalized)
392
+ end
393
+
394
+ sig { params(value: HakumiComponents::Table::DefinitionTypes::RawCollection).returns(HakumiComponents::Table::DefinitionTypes::RawCollection) }
395
+ def normalize_definition_array(value)
396
+ normalized = T.let([], HakumiComponents::Table::DefinitionTypes::RawCollection)
397
+ value.each do |entry|
398
+ next unless entry.is_a?(String) || entry.is_a?(Symbol) || entry.is_a?(Numeric) || entry == true || entry == false || entry.is_a?(Proc)
399
+
400
+ normalized << entry
401
+ end
402
+ normalized
403
+ end
404
+
405
+ sig { params(value: HakumiComponents::Table::DefinitionTypes::RawRecord).returns(HakumiComponents::Table::DefinitionTypes::DefinitionHash) }
406
+ def normalize_definition_hash(value)
407
+ normalized = T.let({}, HakumiComponents::Table::DefinitionTypes::DefinitionHash)
408
+ value.each do |key, entry|
409
+ next unless key.is_a?(String) || key.is_a?(Symbol)
410
+
411
+ normalized_entry = normalize_definition_value(entry)
412
+ next if normalized_entry.nil? && !entry.nil?
413
+
414
+ normalized[key.to_sym] = normalized_entry
415
+ end
416
+ normalized
417
+ end
216
418
  end
217
419
  end
218
420
  end
@@ -1,119 +1,66 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module HakumiComponents
4
5
  module Table
5
6
  module Concerns
6
- # Editable cell and row functionality for Table component
7
7
  module Editable
8
8
  extend ActiveSupport::Concern
9
+ extend T::Sig
10
+ extend T::Helpers
9
11
 
10
- EDITABLE_MODES = %i[cell row].freeze
11
- EDITABLE_TRIGGERS = %i[click dblclick].freeze
12
- EDITABLE_INPUT_TYPES = %i[text number textarea select].freeze
12
+ include AttributeHelpers
13
13
 
14
- # Returns JSON configuration for the table-level editable settings
15
- # to be passed to the Stimulus controller.
16
- #
17
- # @return [String, nil] JSON string or nil if not editable
18
- def editable_config_json
19
- return nil unless @editable
20
-
21
- @editable.to_json
22
- end
23
-
24
- private
25
-
26
- # Normalizes the table-level editable configuration.
27
- # Accepts boolean, symbol (:cell or :row), or hash with options.
28
- # Returns nil if editing is disabled, or a hash with normalized options.
29
- #
30
- # @param value [Boolean, Symbol, Hash, nil] editable configuration
31
- # @return [Hash, nil] normalized configuration
32
- def normalize_editable(value)
33
- return nil if value.nil? || value == false
34
-
35
- case value
36
- when true
37
- { mode: :cell, trigger: :click }
38
- when Symbol
39
- mode = EDITABLE_MODES.include?(value) ? value : :cell
40
- { mode: mode, trigger: :click }
41
- when Hash
42
- normalized = value.deep_symbolize_keys
43
- mode = normalized[:mode]&.to_sym
44
- normalized[:mode] = EDITABLE_MODES.include?(mode) ? mode : :cell
45
- trigger = normalized[:trigger]&.to_sym
46
- normalized[:trigger] = EDITABLE_TRIGGERS.include?(trigger) ? trigger : :click
47
- normalized
48
- end
49
- end
14
+ abstract!
50
15
 
51
- # Normalizes column-level editable configuration.
52
- # Column editable can override table-level settings.
53
- #
54
- # @param value [Boolean, Hash, nil] column editable configuration
55
- # @return [Hash, nil] normalized configuration
56
- def normalize_column_editable(value)
57
- return nil if value.nil? || value == false
58
- return {} if value == true
59
- return nil unless value.is_a?(Hash)
16
+ sig { abstract.returns(T.nilable(HakumiComponents::Table::Configs::Editable)) }
17
+ def table_editable_config; end
60
18
 
61
- normalized = value.deep_symbolize_keys
62
- input_type = normalized[:type]&.to_sym || normalized[:input_type]&.to_sym
63
- normalized[:input_type] = EDITABLE_INPUT_TYPES.include?(input_type) ? input_type : :text
64
- normalized.except(:type)
19
+ sig { returns(T.nilable(String)) }
20
+ def editable_config_json
21
+ config = table_editable_config
22
+ config ? config.to_h.to_json : nil
65
23
  end
66
24
 
67
- def normalize_should_cell_update(value)
68
- case value
69
- when Symbol
70
- value.to_s
71
- when String
72
- value.strip.presence
73
- end
74
- end
25
+ private
75
26
 
76
- # Applies editable attributes to cell
27
+ sig { params(attrs: Types::HtmlAttributes, column: HakumiComponents::Table::ColumnDefinition).void }
77
28
  def apply_editable_attributes!(attrs, column)
78
29
  payload = cell_editable_payload(column)
79
30
  return unless payload
80
31
 
81
32
  attrs[:class] = [ attrs[:class], "hakumi-table-cell-editable" ].compact.join(" ")
82
- attrs[:data] ||= {}
83
- attrs[:data][:editable] = true
84
- attrs[:data][:editableConfig] = payload.to_json
85
- attrs[:data][:action] = "click->hakumi--table#editCell"
33
+ data_attrs = existing_data_attributes(attrs)
34
+ data_attrs[:editable] = true
35
+ data_attrs[:editable_config] = payload.to_json
36
+ data_attrs[:action] = payload[:trigger] == :dblclick ? "dblclick->hakumi--table#editCell" : "click->hakumi--table#editCell"
37
+ attrs[:data] = data_attrs
86
38
  end
87
39
 
40
+ sig { params(attrs: Types::HtmlAttributes, column: HakumiComponents::Table::ColumnDefinition).void }
88
41
  def apply_should_cell_update_attributes!(attrs, column)
89
- hook = column[:should_cell_update]
42
+ hook = column.should_cell_update
90
43
  return unless hook.present?
91
44
 
92
- attrs[:data] ||= {}
93
- attrs[:data][:should_cell_update] = hook
45
+ data_attrs = existing_data_attributes(attrs)
46
+ data_attrs[:should_cell_update] = hook
47
+ attrs[:data] = data_attrs
94
48
  end
95
49
 
96
- # Returns the editable payload for a specific cell/column.
97
- # Used to add data-editable-config attribute to editable cells.
98
- #
99
- # @param column [Hash] the column definition
100
- # @return [Hash, nil] editable configuration for the cell
50
+ sig { params(column: HakumiComponents::Table::ColumnDefinition).returns(T.nilable(HakumiComponents::Table::Configs::Editable::Payload)) }
101
51
  def cell_editable_payload(column)
102
- return nil if column[:selection] || column[:expand]
103
- return nil unless column[:data_index]
104
-
105
- column_config = column[:editable]
106
-
107
- # Column must explicitly opt-in to be editable
108
- return nil if column_config.nil? || column_config == false
109
-
110
- # Merge table-level and column-level config
111
- base_config = @editable&.dup || {}
112
- base_config.merge!(column_config) if column_config.is_a?(Hash)
113
-
114
- base_config[:data_index] = column[:data_index]
115
- base_config[:key] = column[:key]
116
- base_config
52
+ return nil if column.selection? || column.expand?
53
+ data_index = column.data_index
54
+ return nil if data_index.nil?
55
+
56
+ column_config = column.editable
57
+ return nil unless column_config
58
+
59
+ base_config = table_editable_config&.to_h || { mode: :cell, trigger: :click }
60
+ payload = T.let(base_config.merge(column_config.to_h), HakumiComponents::Table::Configs::Editable::Payload)
61
+ payload[:data_index] = data_index
62
+ payload[:key] = column.key
63
+ payload
117
64
  end
118
65
  end
119
66
  end