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,71 +1,96 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
1
4
  module HakumiComponents
2
5
  module Splitter
3
6
  module Panel
4
- class Component < ViewComponent::Base
5
- def initialize(size: nil, min: nil, max: nil, resizable: true, collapsible: false, **html_options)
6
- @size = size
7
- @min = min
8
- @max = max
9
- @resizable = resizable
10
- @collapsible = collapsible
11
- @html_options = html_options
7
+ class Component < HakumiComponents::BaseComponent
8
+ extend T::Sig
9
+
10
+ SizeInput = T.type_alias { BaseComponent::DimensionInput }
11
+ CollapsibleInput = T.type_alias { T.any(T::Boolean, Types::HtmlAttributes) }
12
+ CollapsibleValue = T.type_alias { T.any(T::Boolean, String) }
13
+
14
+ sig do
15
+ params(
16
+ size: SizeInput,
17
+ min: SizeInput,
18
+ max: SizeInput,
19
+ resizable: T::Boolean,
20
+ collapsible: CollapsibleInput,
21
+ layout: Symbol,
22
+ html_options: Types::HtmlAttributeValue
23
+ ).void
24
+ end
25
+ def initialize(size: nil, min: nil, max: nil, resizable: true, collapsible: false, layout: :horizontal, **html_options)
26
+ @size = T.let(size, SizeInput)
27
+ @min = T.let(min, SizeInput)
28
+ @max = T.let(max, SizeInput)
29
+ @resizable = T.let(resizable, T::Boolean)
30
+ @collapsible = T.let(collapsible, CollapsibleInput)
31
+ @layout = T.let(layout, Symbol)
32
+ @html_options = T.let(html_options, Types::HtmlAttributes)
12
33
  end
13
34
 
14
35
  private
15
36
 
16
- def html_attributes
17
- attrs = @html_options.dup
18
- attrs[:class] = css_classes
19
- attrs[:style] = inline_styles
20
- attrs[:data] ||= {}
21
- attrs[:data][:hakumi__splitter_target] = "panel"
22
- attrs[:data][:hakumi__splitter_size_value] = @size.to_s if @size
23
- attrs[:data][:hakumi__splitter_min_value] = @min.to_s if @min
24
- attrs[:data][:hakumi__splitter_max_value] = @max.to_s if @max
25
- attrs[:data][:hakumi__splitter_resizable_value] = @resizable
26
- attrs[:data][:hakumi__splitter_collapsible_value] = collapsible_config
27
- attrs
37
+ sig { returns(String) }
38
+ def wrapper_classes
39
+ component_classes("splitter-panel", {}, @html_options)
28
40
  end
29
41
 
30
- def css_classes
31
- classes = [ "hakumi-splitter-panel" ]
32
- classes << @html_options[:class] if @html_options[:class]
33
- classes.join(" ")
42
+ sig { returns(Types::HtmlAttributes) }
43
+ def wrapper_attributes
44
+ data = T.let({
45
+ hakumi__splitter_target: "panel",
46
+ hakumi__splitter_resizable_value: @resizable,
47
+ hakumi__splitter_collapsible_value: collapsible_config
48
+ }, Types::DataAttributes)
49
+ data[:hakumi__splitter_size_value] = @size.to_s if @size
50
+ data[:hakumi__splitter_min_value] = @min.to_s if @min
51
+ data[:hakumi__splitter_max_value] = @max.to_s if @max
52
+
53
+ merge_attributes(
54
+ { class: wrapper_classes, style: inline_styles, data: data },
55
+ @html_options.except(:class, :style)
56
+ )
34
57
  end
35
58
 
59
+ sig { returns(T.nilable(String)) }
36
60
  def inline_styles
37
- styles = []
38
-
39
- if @size
40
- size_value = @size.is_a?(String) ? @size : "#{@size}px"
61
+ styles = T.let([], T::Array[String])
62
+ sz = @size
63
+ if sz
64
+ size_value = sz.is_a?(String) ? sz : "#{sz}px"
41
65
  styles << "flex: 0 0 #{size_value}"
42
66
  styles << "width: #{size_value}" if horizontal_layout?
43
67
  styles << "height: #{size_value}" if vertical_layout?
44
68
  else
45
69
  styles << "flex: 1"
46
70
  end
71
+ style_value = html_style(@html_options)
72
+ styles << style_value if style_value
47
73
 
48
- styles << @html_options[:style] if @html_options[:style]
49
- styles.join("; ")
74
+ styles.join("; ").presence
50
75
  end
51
76
 
77
+ sig { returns(CollapsibleValue) }
52
78
  def collapsible_config
53
- return "false" unless @collapsible
79
+ c = @collapsible
80
+ return false unless c
81
+ return c.to_json if c.is_a?(Hash)
54
82
 
55
- if @collapsible.is_a?(Hash)
56
- @collapsible.to_json
57
- else
58
- "true"
59
- end
83
+ true
60
84
  end
61
85
 
86
+ sig { returns(T::Boolean) }
62
87
  def horizontal_layout?
63
- # This is a simplification - in real implementation we'd get this from parent
64
- true
88
+ @layout == :horizontal
65
89
  end
66
90
 
91
+ sig { returns(T::Boolean) }
67
92
  def vertical_layout?
68
- false
93
+ @layout == :vertical
69
94
  end
70
95
  end
71
96
  end
@@ -1,28 +1,46 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module HakumiComponents
4
5
  module Statistic
5
6
  class Component < HakumiComponents::BaseComponent
7
+ extend T::Sig
8
+
6
9
  include ActionView::Helpers::NumberHelper
7
10
 
8
- DEFAULT_DURATION = 800
9
- DEFAULT_COUNTDOWN_FORMAT = "HH:mm:ss"
11
+ Value = T.type_alias { T.nilable(T.any(Numeric, String, BaseComponent::DateLikeValue)) }
12
+ FormatValue = T.type_alias { T.nilable(String) }
13
+ PrecisionInput = T.type_alias { T.nilable(T.any(Integer, String)) }
14
+ DurationInput = T.type_alias { T.any(Numeric, String) }
15
+
16
+ DEFAULT_DURATION = T.let(800, Integer)
17
+ DEFAULT_COUNTDOWN_FORMAT = T.let("HH:mm:ss", String)
10
18
 
19
+ renders_one :title
20
+ renders_one :prefix
21
+ renders_one :suffix
22
+
23
+ sig do
24
+ params(
25
+ params: BaseComponent::ControllerOptions
26
+ ).returns(HakumiComponents::Types::HtmlAttributes)
27
+ end
11
28
  def self.extract_controller_locals(params)
12
29
  {
13
- title: params[:title],
14
- value: parse_value_param(params[:value]),
15
- precision: parse_integer(params[:precision]),
16
- prefix: params[:prefix],
17
- suffix: params[:suffix],
18
- animated: truthy_param?(params[:animated]),
19
- duration: parse_number(params[:duration]),
20
- countdown: truthy_param?(params[:countdown]),
21
- format: params[:format],
22
- aria_label: params[:aria_label]
30
+ title: html_primitive_param(html_param(params, :title)),
31
+ value: parse_value_param(html_param(params, :value)),
32
+ precision: parse_integer(html_param(params, :precision)),
33
+ prefix: html_primitive_param(html_param(params, :prefix)),
34
+ suffix: html_primitive_param(html_param(params, :suffix)),
35
+ animated: boolean_html_param(html_param(params, :animated)),
36
+ duration: parse_number(html_param(params, :duration)),
37
+ countdown: boolean_html_param(html_param(params, :countdown)),
38
+ format: html_primitive_param(html_param(params, :format)),
39
+ aria_label: html_primitive_param(html_param(params, :aria_label))
23
40
  }
24
41
  end
25
42
 
43
+ sig { params(value: Types::HtmlAttributeValue).returns(Value) }
26
44
  def self.parse_value_param(value)
27
45
  return nil if value.nil?
28
46
  return value if value.is_a?(Numeric)
@@ -32,33 +50,47 @@ module HakumiComponents
32
50
 
33
51
  Float(string_value)
34
52
  rescue ArgumentError
35
- value
53
+ string_value
36
54
  end
37
55
 
56
+ sig { params(value: Types::HtmlAttributeValue).returns(T.nilable(Float)) }
38
57
  def self.parse_number(value)
39
58
  return nil if value.nil?
40
- return value if value.is_a?(Numeric)
59
+ return value.to_f if value.is_a?(Numeric)
60
+ return nil unless value.is_a?(String)
41
61
 
42
- Float(value) if value.to_s.strip != ""
62
+ Float(value) if value.strip != ""
43
63
  rescue ArgumentError
44
64
  nil
45
65
  end
46
66
 
67
+ sig { params(value: Types::HtmlAttributeValue).returns(T.nilable(Integer)) }
47
68
  def self.parse_integer(value)
48
69
  return nil if value.nil?
49
70
  return value if value.is_a?(Integer)
71
+ return value.to_i if value.is_a?(Float)
72
+ return nil unless value.is_a?(String)
50
73
 
51
- Integer(value) if value.to_s.strip != ""
74
+ Integer(value) if value.strip != ""
52
75
  rescue ArgumentError
53
76
  nil
54
77
  end
55
78
 
56
- def self.truthy_param?(value)
57
- return value if value == true || value == false
58
-
59
- value.to_s == "true"
79
+ sig do
80
+ params(
81
+ title: Types::Renderable,
82
+ value: Value,
83
+ precision: PrecisionInput,
84
+ prefix: Types::Renderable,
85
+ suffix: Types::Renderable,
86
+ animated: T::Boolean,
87
+ duration: DurationInput,
88
+ countdown: T::Boolean,
89
+ format: T.nilable(String),
90
+ aria_label: T.nilable(String),
91
+ html_options: Types::HtmlAttributeValue
92
+ ).void
60
93
  end
61
-
62
94
  def initialize(
63
95
  title: nil,
64
96
  value: nil,
@@ -72,21 +104,23 @@ module HakumiComponents
72
104
  aria_label: nil,
73
105
  **html_options
74
106
  )
75
- @title = title
76
- @value = value
77
- @precision = normalize_precision(precision)
78
- @prefix = prefix
79
- @suffix = suffix
80
- @animated = cast_boolean(animated)
81
- @duration = normalize_duration(duration)
82
- @countdown = cast_boolean(countdown)
83
- @format = normalize_format(format)
84
- @aria_label = normalize_string(aria_label)
85
- @html_options = html_options
107
+ @title = T.let(title, Types::Renderable)
108
+ @value = T.let(value, Value)
109
+ @precision = T.let(normalize_precision(precision), T.nilable(Integer))
110
+ @prefix = T.let(prefix, Types::Renderable)
111
+ @suffix = T.let(suffix, Types::Renderable)
112
+ @animated = T.let(cast_boolean(animated) ? true : false, T::Boolean)
113
+ @duration = T.let(normalize_duration(duration), Float)
114
+ @countdown = T.let(cast_boolean(countdown) ? true : false, T::Boolean)
115
+ @format = T.let(normalize_format(format), T.nilable(String))
116
+ @aria_label = T.let(normalize_string(aria_label), T.nilable(String))
117
+ @html_options = T.let(html_options, Types::HtmlAttributes)
118
+ @numeric_value = T.let(resolve_numeric_value(@value), T.nilable(Float))
86
119
 
87
120
  validate_props!
88
121
  end
89
122
 
123
+ sig { returns(Types::HtmlAttributes) }
90
124
  def wrapper_attributes
91
125
  attrs = {
92
126
  class: wrapper_classes,
@@ -99,34 +133,48 @@ module HakumiComponents
99
133
  merge_attributes(attrs, @html_options)
100
134
  end
101
135
 
136
+ sig { returns(Types::RenderedContent) }
102
137
  def title_content
138
+ return render_value(title) if title?
139
+
103
140
  render_value(@title)
104
141
  end
105
142
 
143
+ sig { returns(Types::RenderedContent) }
106
144
  def prefix_content
145
+ return render_value(prefix) if prefix?
146
+
107
147
  render_value(@prefix)
108
148
  end
109
149
 
150
+ sig { returns(Types::RenderedContent) }
110
151
  def suffix_content
152
+ return render_value(suffix) if suffix?
153
+
111
154
  render_value(@suffix)
112
155
  end
113
156
 
157
+ sig { returns(Types::RenderedContent) }
114
158
  def display_value
115
- return content if content.present?
159
+ block_content = content
160
+ return block_content if block_content.present?
116
161
  return formatted_countdown_value if countdown?
117
- return formatted_numeric_value if numeric_value?
162
+ return formatted_numeric_value unless @numeric_value.nil?
118
163
 
119
- render_value(@value).presence || "-"
164
+ fallback_display_value
120
165
  end
121
166
 
167
+ sig { returns(T::Boolean) }
122
168
  def countdown?
123
169
  @countdown
124
170
  end
125
171
 
172
+ sig { returns(T::Boolean) }
126
173
  def animated?
127
174
  @animated
128
175
  end
129
176
 
177
+ sig { returns(T.nilable(Integer)) }
130
178
  def countdown_target_timestamp
131
179
  return nil unless countdown?
132
180
 
@@ -136,20 +184,21 @@ module HakumiComponents
136
184
  (target.to_f * 1000).round
137
185
  end
138
186
 
187
+ sig { returns(T.nilable(String)) }
139
188
  def countdown_format
140
189
  return nil unless countdown?
141
190
 
142
191
  @format || DEFAULT_COUNTDOWN_FORMAT
143
192
  end
144
193
 
194
+ sig { returns(T.nilable(Float)) }
145
195
  def numeric_value
146
- return nil unless numeric_value?
147
-
148
196
  @numeric_value
149
197
  end
150
198
 
151
199
  private
152
200
 
201
+ sig { void }
153
202
  def validate_props!
154
203
  if @precision && @precision.negative?
155
204
  raise ArgumentError, "precision must be greater than or equal to 0"
@@ -158,19 +207,21 @@ module HakumiComponents
158
207
  raise ArgumentError, "duration must be greater than 0" if @duration.to_f <= 0
159
208
  end
160
209
 
210
+ sig { returns(String) }
161
211
  def wrapper_classes
162
212
  modifiers = {
163
213
  countdown: countdown?
164
214
  }
165
215
 
166
- class_names("statistic", modifiers, [ @html_options[:class] ])
216
+ component_classes("statistic", modifiers, @html_options)
167
217
  end
168
218
 
219
+ sig { returns(Types::DataAttributes) }
169
220
  def data_attributes
170
221
  {
171
222
  controller: "hakumi--statistic",
172
223
  "hakumi--statistic-animated-value": animated?,
173
- "hakumi--statistic-duration-value": @duration,
224
+ "hakumi--statistic-duration-value": stimulus_duration_value,
174
225
  "hakumi--statistic-precision-value": @precision,
175
226
  "hakumi--statistic-countdown-value": countdown?,
176
227
  "hakumi--statistic-target-timestamp-value": countdown_target_timestamp,
@@ -179,22 +230,27 @@ module HakumiComponents
179
230
  }.compact
180
231
  end
181
232
 
233
+ sig { returns(String) }
182
234
  def formatted_numeric_value
183
- value = numeric_value
235
+ value = @numeric_value
184
236
  return "-" if value.nil?
185
237
 
186
238
  if @precision
187
239
  number_with_precision(value, precision: @precision, delimiter: ",")
188
240
  else
241
+ return number_with_delimiter(value.to_i) if whole_number?(value)
242
+
189
243
  number_with_delimiter(value)
190
244
  end
191
245
  end
192
246
 
247
+ sig { returns(String) }
193
248
  def formatted_countdown_value
194
249
  remaining_ms = countdown_remaining_ms
195
250
  format_duration(remaining_ms, countdown_format || DEFAULT_COUNTDOWN_FORMAT)
196
251
  end
197
252
 
253
+ sig { returns(Integer) }
198
254
  def countdown_remaining_ms
199
255
  target = normalize_countdown_target(@value)
200
256
  return 0 unless target
@@ -203,37 +259,43 @@ module HakumiComponents
203
259
  remaining.positive? ? remaining.round : 0
204
260
  end
205
261
 
206
- def numeric_value?
207
- return @numeric_value if defined?(@numeric_value)
262
+ sig { params(value: Value).returns(T.nilable(Float)) }
263
+ def resolve_numeric_value(value)
264
+ return value.to_f if value.is_a?(Numeric)
265
+ return nil unless value.is_a?(String)
208
266
 
209
- @numeric_value = begin
210
- if @value.is_a?(Numeric)
211
- @value
212
- else
213
- Float(@value)
214
- end
215
- rescue ArgumentError, TypeError
216
- nil
217
- end
267
+ stripped = value.strip
268
+ return nil if stripped.empty?
218
269
 
219
- @numeric_value.present?
270
+ Float(stripped)
271
+ rescue ArgumentError, TypeError
272
+ nil
220
273
  end
221
274
 
275
+ sig { params(value: Value).returns(T.nilable(Time)) }
222
276
  def normalize_countdown_target(value)
223
277
  return nil if value.nil?
224
- return value.to_time if value.respond_to?(:to_time)
278
+ return value if value.is_a?(Time)
279
+ return value.to_time if value.is_a?(Date) || value.is_a?(DateTime)
280
+ return Time.at(value.to_f) if value.is_a?(ActiveSupport::TimeWithZone)
225
281
 
226
282
  if value.is_a?(Numeric)
227
283
  seconds = value > 1_000_000_000_000 ? value / 1000.0 : value
228
284
  return Time.at(seconds)
229
285
  end
230
286
 
231
- parsed = Time.zone ? Time.zone.parse(value.to_s) : Time.parse(value.to_s)
232
- parsed
287
+ return nil unless value.is_a?(String)
288
+
289
+ tz = Time.zone
290
+ parsed = tz ? tz.parse(value.to_s) : Time.parse(value.to_s)
291
+ return nil if parsed.nil?
292
+
293
+ parsed.is_a?(ActiveSupport::TimeWithZone) ? Time.at(parsed.to_f) : parsed
233
294
  rescue ArgumentError, TypeError
234
295
  nil
235
296
  end
236
297
 
298
+ sig { params(milliseconds: Integer, format: String).returns(String) }
237
299
  def format_duration(milliseconds, format)
238
300
  total_seconds = (milliseconds.to_f / 1000).floor
239
301
  total_seconds = 0 if total_seconds.negative?
@@ -244,7 +306,7 @@ module HakumiComponents
244
306
  minutes = (total_seconds / 60) % 60
245
307
  seconds = total_seconds % 60
246
308
 
247
- format_string = format || DEFAULT_COUNTDOWN_FORMAT
309
+ format_string = format
248
310
  use_days = format_string.include?("DD")
249
311
  hours_value = use_days ? hours : hours_total
250
312
 
@@ -255,6 +317,7 @@ module HakumiComponents
255
317
  .gsub("ss", seconds.to_s.rjust(2, "0"))
256
318
  end
257
319
 
320
+ sig { params(value: PrecisionInput).returns(T.nilable(Integer)) }
258
321
  def normalize_precision(value)
259
322
  return nil if value.nil?
260
323
  return value if value.is_a?(Integer)
@@ -264,24 +327,45 @@ module HakumiComponents
264
327
  raise ArgumentError, "precision must be an integer"
265
328
  end
266
329
 
330
+ sig { params(value: DurationInput).returns(Float) }
267
331
  def normalize_duration(value)
268
- return DEFAULT_DURATION if value.nil?
269
- return value if value.is_a?(Numeric)
332
+ return DEFAULT_DURATION.to_f if value.nil?
333
+ return value.to_f if value.is_a?(Numeric)
270
334
 
271
335
  Float(value)
272
336
  rescue ArgumentError, TypeError
273
337
  raise ArgumentError, "duration must be numeric"
274
338
  end
275
339
 
340
+ sig { params(value: T.nilable(String)).returns(T.nilable(String)) }
276
341
  def normalize_format(value)
277
- value.to_s.strip.presence
342
+ value&.strip&.presence
278
343
  end
279
344
 
345
+ sig { params(value: T.nilable(Types::ValidationComparable)).returns(T.nilable(String)) }
280
346
  def normalize_string(value)
281
347
  return nil if value.nil?
282
348
 
283
349
  value.to_s.strip.presence
284
350
  end
351
+
352
+ sig { returns(String) }
353
+ def fallback_display_value
354
+ value = @value
355
+ return "-" if value.nil?
356
+
357
+ value.to_s.presence || "-"
358
+ end
359
+
360
+ sig { returns(Numeric) }
361
+ def stimulus_duration_value
362
+ whole_number?(@duration) ? @duration.to_i : @duration
363
+ end
364
+
365
+ sig { params(value: Float).returns(T::Boolean) }
366
+ def whole_number?(value)
367
+ (value % 1.0).zero?
368
+ end
285
369
  end
286
370
  end
287
371
  end