ruflet_core 0.0.17 → 0.0.18

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 (188) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -30
  3. data/lib/ruflet/version.rb +1 -1
  4. data/lib/ruflet_core.rb +9 -7
  5. data/lib/ruflet_protocol/ruflet/protocol.rb +28 -54
  6. data/lib/ruflet_ui/ruflet/app.rb +1 -1
  7. data/lib/ruflet_ui/ruflet/colors.rb +4 -50
  8. data/lib/ruflet_ui/ruflet/control.rb +69 -64
  9. data/lib/ruflet_ui/ruflet/dsl.rb +10 -3
  10. data/lib/ruflet_ui/ruflet/icons/cupertino/cupertino_icons.rb +2 -2
  11. data/lib/ruflet_ui/ruflet/icons/cupertino_icon_lookup.rb +1 -0
  12. data/lib/ruflet_ui/ruflet/icons/icon_constant_names.rb +29 -0
  13. data/lib/ruflet_ui/ruflet/icons/material/material_icons.rb +2 -2
  14. data/lib/ruflet_ui/ruflet/icons/material_icon_lookup.rb +1 -0
  15. data/lib/ruflet_ui/ruflet/page.rb +308 -526
  16. data/lib/ruflet_ui/ruflet/ui/control_factory.rb +68 -12
  17. data/lib/ruflet_ui/ruflet/ui/control_registry.rb +13 -6
  18. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoactionsheet_control.rb +8 -45
  19. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoactionsheetaction_control.rb +45 -2
  20. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoactivityindicator_control.rb +43 -2
  21. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoalertdialog_control.rb +0 -5
  22. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinobutton_control.rb +59 -4
  23. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinocheckbox_control.rb +56 -1
  24. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinocontextmenu_control.rb +1 -3
  25. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinocontextmenuaction_control.rb +1 -1
  26. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinodatepicker_control.rb +53 -8
  27. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinodialogaction_control.rb +1 -1
  28. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinofilledbutton_control.rb +59 -4
  29. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinolisttile_control.rb +54 -9
  30. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinonavigationbar_control.rb +47 -9
  31. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinopicker_control.rb +52 -10
  32. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoradio_control.rb +52 -1
  33. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinosegmentedbutton_control.rb +49 -1
  34. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoslider_control.rb +50 -5
  35. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoslidingsegmentedbutton_control.rb +46 -1
  36. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoswitch_control.rb +61 -1
  37. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinotextfield_control.rb +145 -1
  38. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinotimerpicker_control.rb +47 -12
  39. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinotintedbutton_control.rb +59 -4
  40. data/lib/ruflet_ui/ruflet/ui/controls/materials/alertdialog_control.rb +8 -45
  41. data/lib/ruflet_ui/ruflet/ui/controls/materials/appbar_control.rb +8 -40
  42. data/lib/ruflet_ui/ruflet/ui/controls/materials/audio_control.rb +16 -5
  43. data/lib/ruflet_ui/ruflet/ui/controls/materials/autocomplete_control.rb +44 -1
  44. data/lib/ruflet_ui/ruflet/ui/controls/materials/badge_control.rb +0 -7
  45. data/lib/ruflet_ui/ruflet/ui/controls/materials/banner_control.rb +0 -7
  46. data/lib/ruflet_ui/ruflet/ui/controls/materials/bottomappbar_control.rb +8 -47
  47. data/lib/ruflet_ui/ruflet/ui/controls/materials/bottomsheet_control.rb +0 -3
  48. data/lib/ruflet_ui/ruflet/ui/controls/materials/button_control.rb +8 -54
  49. data/lib/ruflet_ui/ruflet/ui/controls/materials/card_control.rb +8 -50
  50. data/lib/ruflet_ui/ruflet/ui/controls/materials/chart_controls.rb +19 -0
  51. data/lib/ruflet_ui/ruflet/ui/controls/materials/checkbox_control.rb +9 -60
  52. data/lib/ruflet_ui/ruflet/ui/controls/materials/chip_control.rb +7 -81
  53. data/lib/ruflet_ui/ruflet/ui/controls/materials/circleavatar_control.rb +7 -56
  54. data/lib/ruflet_ui/ruflet/ui/controls/materials/container_control.rb +9 -65
  55. data/lib/ruflet_ui/ruflet/ui/controls/materials/contextmenu_control.rb +50 -4
  56. data/lib/ruflet_ui/ruflet/ui/controls/materials/datacell_control.rb +1 -3
  57. data/lib/ruflet_ui/ruflet/ui/controls/materials/datacolumn_control.rb +1 -3
  58. data/lib/ruflet_ui/ruflet/ui/controls/materials/datatable_control.rb +7 -88
  59. data/lib/ruflet_ui/ruflet/ui/controls/materials/datepicker_control.rb +9 -55
  60. data/lib/ruflet_ui/ruflet/ui/controls/materials/daterangepicker_control.rb +8 -58
  61. data/lib/ruflet_ui/ruflet/ui/controls/materials/divider_control.rb +0 -9
  62. data/lib/ruflet_ui/ruflet/ui/controls/materials/dropdown_control.rb +8 -93
  63. data/lib/ruflet_ui/ruflet/ui/controls/materials/dropdownm2_control.rb +9 -103
  64. data/lib/ruflet_ui/ruflet/ui/controls/materials/dropdownoption_control.rb +0 -2
  65. data/lib/ruflet_ui/ruflet/ui/controls/materials/expansionpanel_control.rb +7 -51
  66. data/lib/ruflet_ui/ruflet/ui/controls/materials/expansionpanellist_control.rb +46 -5
  67. data/lib/ruflet_ui/ruflet/ui/controls/materials/expansiontile_control.rb +69 -3
  68. data/lib/ruflet_ui/ruflet/ui/controls/materials/filledbutton_control.rb +54 -2
  69. data/lib/ruflet_ui/ruflet/ui/controls/materials/fillediconbutton_control.rb +67 -5
  70. data/lib/ruflet_ui/ruflet/ui/controls/materials/filledtonalbutton_control.rb +54 -2
  71. data/lib/ruflet_ui/ruflet/ui/controls/materials/filledtonaliconbutton_control.rb +67 -5
  72. data/lib/ruflet_ui/ruflet/ui/controls/materials/floatingactionbutton_control.rb +7 -69
  73. data/lib/ruflet_ui/ruflet/ui/controls/materials/iconbutton_control.rb +8 -68
  74. data/lib/ruflet_ui/ruflet/ui/controls/materials/listtile_control.rb +8 -74
  75. data/lib/ruflet_ui/ruflet/ui/controls/materials/map_control.rb +99 -0
  76. data/lib/ruflet_ui/ruflet/ui/controls/materials/menuitembutton_control.rb +8 -56
  77. data/lib/ruflet_ui/ruflet/ui/controls/materials/navigationbar_control.rb +7 -59
  78. data/lib/ruflet_ui/ruflet/ui/controls/materials/navigationdrawer_control.rb +0 -7
  79. data/lib/ruflet_ui/ruflet/ui/controls/materials/navigationrail_control.rb +7 -73
  80. data/lib/ruflet_ui/ruflet/ui/controls/materials/outlinedbutton_control.rb +51 -2
  81. data/lib/ruflet_ui/ruflet/ui/controls/materials/outlinediconbutton_control.rb +67 -5
  82. data/lib/ruflet_ui/ruflet/ui/controls/materials/popupmenubutton_control.rb +8 -61
  83. data/lib/ruflet_ui/ruflet/ui/controls/materials/popupmenuitem_control.rb +0 -6
  84. data/lib/ruflet_ui/ruflet/ui/controls/materials/progressbar_control.rb +8 -56
  85. data/lib/ruflet_ui/ruflet/ui/controls/materials/progressring_control.rb +8 -56
  86. data/lib/ruflet_ui/ruflet/ui/controls/materials/radio_control.rb +9 -54
  87. data/lib/ruflet_ui/ruflet/ui/controls/materials/rangeslider_control.rb +7 -62
  88. data/lib/ruflet_ui/ruflet/ui/controls/materials/reorderablelistview_control.rb +9 -64
  89. data/lib/ruflet_ui/ruflet/ui/controls/materials/ruflet_controls.rb +5 -26
  90. data/lib/ruflet_ui/ruflet/ui/controls/materials/searchbar_control.rb +80 -11
  91. data/lib/ruflet_ui/ruflet/ui/controls/materials/segment_control.rb +0 -3
  92. data/lib/ruflet_ui/ruflet/ui/controls/materials/segmentedbutton_control.rb +7 -64
  93. data/lib/ruflet_ui/ruflet/ui/controls/materials/selectionarea_control.rb +1 -3
  94. data/lib/ruflet_ui/ruflet/ui/controls/materials/slider_control.rb +9 -60
  95. data/lib/ruflet_ui/ruflet/ui/controls/materials/snackbar_control.rb +37 -4
  96. data/lib/ruflet_ui/ruflet/ui/controls/materials/snackbaraction_control.rb +38 -0
  97. data/lib/ruflet_ui/ruflet/ui/controls/materials/spinkit_control.rb +25 -0
  98. data/lib/ruflet_ui/ruflet/ui/controls/materials/submenubutton_control.rb +52 -7
  99. data/lib/ruflet_ui/ruflet/ui/controls/materials/switch_control.rb +9 -61
  100. data/lib/ruflet_ui/ruflet/ui/controls/materials/tab_control.rb +0 -7
  101. data/lib/ruflet_ui/ruflet/ui/controls/materials/tabbar_control.rb +8 -64
  102. data/lib/ruflet_ui/ruflet/ui/controls/materials/tabbarview_control.rb +43 -5
  103. data/lib/ruflet_ui/ruflet/ui/controls/materials/tabs_control.rb +7 -47
  104. data/lib/ruflet_ui/ruflet/ui/controls/materials/textbutton_control.rb +51 -2
  105. data/lib/ruflet_ui/ruflet/ui/controls/materials/textfield_control.rb +9 -132
  106. data/lib/ruflet_ui/ruflet/ui/controls/materials/timepicker_control.rb +10 -34
  107. data/lib/ruflet_ui/ruflet/ui/controls/materials/verticaldivider_control.rb +0 -9
  108. data/lib/ruflet_ui/ruflet/ui/controls/materials/video_control.rb +9 -42
  109. data/lib/ruflet_ui/ruflet/ui/controls/materials/webview_control.rb +1 -114
  110. data/lib/ruflet_ui/ruflet/ui/controls/ruflet_controls.rb +13 -28
  111. data/lib/ruflet_ui/ruflet/ui/controls/shared/animatedswitcher_control.rb +46 -4
  112. data/lib/ruflet_ui/ruflet/ui/controls/shared/arc_control.rb +1 -0
  113. data/lib/ruflet_ui/ruflet/ui/controls/shared/browsercontextmenu_control.rb +1 -12
  114. data/lib/ruflet_ui/ruflet/ui/controls/shared/canvas_control.rb +8 -54
  115. data/lib/ruflet_ui/ruflet/ui/controls/shared/circle_control.rb +1 -0
  116. data/lib/ruflet_ui/ruflet/ui/controls/shared/column_control.rb +9 -51
  117. data/lib/ruflet_ui/ruflet/ui/controls/shared/dismissible_control.rb +52 -12
  118. data/lib/ruflet_ui/ruflet/ui/controls/shared/draggable_control.rb +1 -7
  119. data/lib/ruflet_ui/ruflet/ui/controls/shared/dragtarget_control.rb +1 -3
  120. data/lib/ruflet_ui/ruflet/ui/controls/shared/fletapp_control.rb +9 -46
  121. data/lib/ruflet_ui/ruflet/ui/controls/shared/gesturedetector_control.rb +9 -113
  122. data/lib/ruflet_ui/ruflet/ui/controls/shared/gridview_control.rb +57 -13
  123. data/lib/ruflet_ui/ruflet/ui/controls/shared/hero_control.rb +9 -40
  124. data/lib/ruflet_ui/ruflet/ui/controls/shared/icon_control.rb +9 -48
  125. data/lib/ruflet_ui/ruflet/ui/controls/shared/interactiveviewer_control.rb +9 -53
  126. data/lib/ruflet_ui/ruflet/ui/controls/shared/line_control.rb +1 -0
  127. data/lib/ruflet_ui/ruflet/ui/controls/shared/listview_control.rb +57 -11
  128. data/lib/ruflet_ui/ruflet/ui/controls/shared/markdown_control.rb +9 -54
  129. data/lib/ruflet_ui/ruflet/ui/controls/shared/oval_control.rb +1 -0
  130. data/lib/ruflet_ui/ruflet/ui/controls/shared/page_control.rb +9 -58
  131. data/lib/ruflet_ui/ruflet/ui/controls/shared/pagelet_control.rb +9 -48
  132. data/lib/ruflet_ui/ruflet/ui/controls/shared/pageview_control.rb +50 -4
  133. data/lib/ruflet_ui/ruflet/ui/controls/shared/path_control.rb +1 -0
  134. data/lib/ruflet_ui/ruflet/ui/controls/shared/placeholder_control.rb +44 -9
  135. data/lib/ruflet_ui/ruflet/ui/controls/shared/points_control.rb +1 -0
  136. data/lib/ruflet_ui/ruflet/ui/controls/shared/rect_control.rb +1 -0
  137. data/lib/ruflet_ui/ruflet/ui/controls/shared/reorderabledraghandle_control.rb +43 -4
  138. data/lib/ruflet_ui/ruflet/ui/controls/shared/responsiverow_control.rb +47 -15
  139. data/lib/ruflet_ui/ruflet/ui/controls/shared/rotatedbox_control.rb +97 -0
  140. data/lib/ruflet_ui/ruflet/ui/controls/shared/row_control.rb +9 -51
  141. data/lib/ruflet_ui/ruflet/ui/controls/shared/ruflet_controls.rb +5 -0
  142. data/lib/ruflet_ui/ruflet/ui/controls/shared/safearea_control.rb +48 -4
  143. data/lib/ruflet_ui/ruflet/ui/{services/ruflet → controls/shared}/screenshot_control.rb +18 -2
  144. data/lib/ruflet_ui/ruflet/ui/controls/shared/semantics_control.rb +65 -1
  145. data/lib/ruflet_ui/ruflet/ui/controls/shared/shadermask_control.rb +9 -41
  146. data/lib/ruflet_ui/ruflet/ui/controls/shared/shimmer_control.rb +9 -44
  147. data/lib/ruflet_ui/ruflet/ui/controls/shared/stack_control.rb +44 -1
  148. data/lib/ruflet_ui/ruflet/ui/controls/shared/text_control.rb +9 -35
  149. data/lib/ruflet_ui/ruflet/ui/controls/shared/transparentpointer_control.rb +9 -38
  150. data/lib/ruflet_ui/ruflet/ui/controls/shared/view_control.rb +63 -1
  151. data/lib/ruflet_ui/ruflet/ui/controls/shared/window_control.rb +40 -66
  152. data/lib/ruflet_ui/ruflet/ui/controls/shared/windowdragarea_control.rb +45 -4
  153. data/lib/ruflet_ui/ruflet/ui/material_control_methods.rb +61 -17
  154. data/lib/ruflet_ui/ruflet/ui/material_control_registry.rb +22 -0
  155. data/lib/ruflet_ui/ruflet/ui/services/ruflet/accelerometer_control.rb +1 -0
  156. data/lib/ruflet_ui/ruflet/ui/services/ruflet/audio_recorder_control.rb +14 -36
  157. data/lib/ruflet_ui/ruflet/ui/services/ruflet/barometer_control.rb +1 -0
  158. data/lib/ruflet_ui/ruflet/ui/services/ruflet/battery_control.rb +1 -0
  159. data/lib/ruflet_ui/ruflet/ui/services/ruflet/camera_control.rb +9 -42
  160. data/lib/ruflet_ui/ruflet/ui/services/ruflet/clipboard_control.rb +31 -0
  161. data/lib/ruflet_ui/ruflet/ui/services/ruflet/connectivity_control.rb +1 -0
  162. data/lib/ruflet_ui/ruflet/ui/services/ruflet/filepicker_control.rb +1 -0
  163. data/lib/ruflet_ui/ruflet/ui/services/ruflet/flashlight_control.rb +1 -0
  164. data/lib/ruflet_ui/ruflet/ui/services/ruflet/geolocator_control.rb +14 -30
  165. data/lib/ruflet_ui/ruflet/ui/services/ruflet/gyroscope_control.rb +1 -0
  166. data/lib/ruflet_ui/ruflet/ui/services/ruflet/hapticfeedback_control.rb +1 -0
  167. data/lib/ruflet_ui/ruflet/ui/services/ruflet/magnetometer_control.rb +1 -0
  168. data/lib/ruflet_ui/ruflet/ui/services/ruflet/permissionhandler_control.rb +5 -20
  169. data/lib/ruflet_ui/ruflet/ui/services/ruflet/screenbrightness_control.rb +1 -0
  170. data/lib/ruflet_ui/ruflet/ui/services/ruflet/securestorage_control.rb +22 -49
  171. data/lib/ruflet_ui/ruflet/ui/services/ruflet/semanticsservice_control.rb +20 -15
  172. data/lib/ruflet_ui/ruflet/ui/services/ruflet/shakedetector_control.rb +1 -0
  173. data/lib/ruflet_ui/ruflet/ui/services/ruflet/share_control.rb +1 -0
  174. data/lib/ruflet_ui/ruflet/ui/services/ruflet/sharedpreferences_control.rb +1 -0
  175. data/lib/ruflet_ui/ruflet/ui/services/ruflet/storagepaths_control.rb +1 -0
  176. data/lib/ruflet_ui/ruflet/ui/services/ruflet/urllauncher_control.rb +1 -0
  177. data/lib/ruflet_ui/ruflet/ui/services/ruflet/useraccelerometer_control.rb +1 -0
  178. data/lib/ruflet_ui/ruflet/ui/services/ruflet/wakelock_control.rb +1 -0
  179. data/lib/ruflet_ui/ruflet/ui/services/ruflet_services.rb +0 -7
  180. data/lib/ruflet_ui/ruflet/ui/shared_control_forwarders.rb +20 -1
  181. data/lib/ruflet_ui.rb +12 -10
  182. metadata +7 -8
  183. data/lib/ruflet_ui/ruflet/types/animation.rb +0 -110
  184. data/lib/ruflet_ui/ruflet/ui/controls/materials/codeeditor_control.rb +0 -93
  185. data/lib/ruflet_ui/ruflet/ui/controls/materials/map_controls.rb +0 -311
  186. data/lib/ruflet_ui/ruflet/ui/controls/materials/rive_control.rb +0 -87
  187. data/lib/ruflet_ui/ruflet/ui/controls/materials/spinkit_controls.rb +0 -109
  188. data/lib/ruflet_ui/ruflet/ui/services/ruflet/tester_control.rb +0 -126
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82d5d751f2b92b2b740414e315e1b1f9e88389eb0423ec0ce7a040710e191d59
4
- data.tar.gz: 728109060a0cff6ca564e2bfa5d7620e5f8182dc05cf82b571ae3280e7e0a015
3
+ metadata.gz: c28a07c8c32f3e0eca58c3d1400f06453275690b4e9f75d3a868a4507e78c74a
4
+ data.tar.gz: 71f044a3c2f0464571e4d16f779e1f751c42f4e16ea3aeeecb3036b596269022
5
5
  SHA512:
6
- metadata.gz: bf40cdafd48b625d189f9625945350873b4936c0668916ce8556e9d07f47368584892b29e57e4fe2e4583230d7ce5c87297848b90c5d01ab1d7ddac0b60df4d2
7
- data.tar.gz: 2d229699ad359fcbc5dc5640dd176227bd0dad3f01d641b9f44ea569b26ccea61926b16c465284bda7e2fb786b514215922b8fdda5aa0a6b5f6baca36c0303fe
6
+ metadata.gz: 38de8d3745bbdf5b217e91de9a6dab2a194757964fa39f7a78bb89c83586fb4f0acee955ae7457221b12a15c4ab7fd18465e2a74af58627c8da25d6c75b62463
7
+ data.tar.gz: 476528dec0a5b55ca3e2a3622d4cf2691162434794a498d544a9da165efb249bca5e612f6ba8f49da52387223441ec34ebe64769f162a78916db0f27bee3dce9
data/README.md CHANGED
@@ -1,31 +1,3 @@
1
- # ruflet_core
1
+ # ruflet
2
2
 
3
- `ruflet_core` provides Ruflet's Ruby UI API: controls, control builders, page
4
- operations, events, services, and application lifecycle behavior.
5
-
6
- Applications normally receive this package through a generated Ruflet project
7
- or through `ruflet_rails`; it is not a standalone application server.
8
-
9
- ```ruby
10
- require "ruflet"
11
-
12
- Ruflet.run do |page|
13
- page.title = "Hello"
14
- page.add(
15
- container(
16
- bgcolor: :surface_container_high,
17
- padding: 24,
18
- content: text("Hello Ruflet", color: "DeepOrange500")
19
- )
20
- )
21
- end
22
- ```
23
-
24
- Color properties accept named colors and hex values. Use Ruby-style symbols
25
- like `:deep_orange_500`, compact strings like `"DeepOrange500"`, spaced names
26
- like `"Deep Orange 500"`, semantic theme names like `:primary_container`, or
27
- hex strings like `"#ff6d00"`. Ruflet normalizes named colors before sending
28
- them to the client.
29
-
30
- Use `ruflet_server` to run a standalone server-driven application. Use
31
- `ruflet_rails` when the UI is hosted by Rails.
3
+ Part of Ruflet monorepo.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ruflet
4
- VERSION = "0.0.17" unless const_defined?(:VERSION)
4
+ VERSION = "0.0.18" unless const_defined?(:VERSION)
5
5
  end
data/lib/ruflet_core.rb CHANGED
@@ -11,9 +11,9 @@ module Ruflet
11
11
  module_function
12
12
 
13
13
  def run(entrypoint = nil, host: "0.0.0.0", port: nil, &block)
14
- port = normalize_run_port(port || ENV["RUFLET_PORT"] || 8550)
15
14
  callback = entrypoint || block
16
15
  raise ArgumentError, "Ruflet.run requires a callable entrypoint or block" unless callback.respond_to?(:call)
16
+ port = resolved_run_port(port)
17
17
 
18
18
  interceptor = @run_interceptors_mutex.synchronize { @run_interceptors.last }
19
19
  if interceptor
@@ -32,16 +32,18 @@ module Ruflet
32
32
  end.start
33
33
  end
34
34
 
35
+ def resolved_run_port(port)
36
+ raw = port.nil? ? ENV["RUFLET_PORT"] : port
37
+ return 8550 if raw.nil? || raw.to_s.strip.empty?
38
+
39
+ value = raw.to_i
40
+ value >= 0 ? value : 8550
41
+ end
42
+
35
43
  def with_run_interceptor(interceptor)
36
44
  @run_interceptors_mutex.synchronize { @run_interceptors << interceptor }
37
45
  yield
38
46
  ensure
39
47
  @run_interceptors_mutex.synchronize { @run_interceptors.delete(interceptor) }
40
48
  end
41
-
42
- def normalize_run_port(value)
43
- Integer(value)
44
- rescue ArgumentError, TypeError
45
- 8550
46
- end
47
49
  end
@@ -2,6 +2,13 @@
2
2
 
3
3
  module Ruflet
4
4
  module Protocol
5
+ # Flet transports these values as MessagePack extension objects. String
6
+ # subclasses preserve Ruflet's existing, convenient Ruby API while giving
7
+ # the wire codec the type information required by the Flutter client.
8
+ class DateTimeValue < String; end
9
+ class TimeOfDayValue < String; end
10
+ class DurationValue < String; end
11
+
5
12
  ACTIONS = {
6
13
  register_client: 1,
7
14
  patch_control: 2,
@@ -19,14 +26,26 @@ module Ruflet
19
26
 
20
27
  module_function
21
28
 
22
- def pack_message(action:, payload:)
23
- [action_code(action), payload]
29
+ def date_time(value)
30
+ return value if value.nil? || value.is_a?(DateTimeValue)
31
+
32
+ DateTimeValue.new(value.to_s)
33
+ end
34
+
35
+ def time_of_day(value)
36
+ return value if value.nil? || value.is_a?(TimeOfDayValue)
37
+
38
+ TimeOfDayValue.new(value.to_s)
24
39
  end
25
40
 
26
- def action_code(action)
27
- return action if action.is_a?(Integer) || action.is_a?(String)
41
+ def duration(value)
42
+ return value if value.nil? || value.is_a?(DurationValue)
28
43
 
29
- ACTIONS.fetch(action)
44
+ DurationValue.new(value.to_s)
45
+ end
46
+
47
+ def pack_message(action:, payload:)
48
+ [action, payload]
30
49
  end
31
50
 
32
51
  def normalize_register_payload(payload)
@@ -39,12 +58,7 @@ module Ruflet
39
58
  "height" => page["height"],
40
59
  "platform" => page["platform"],
41
60
  "platform_brightness" => page["platform_brightness"],
42
- # The Flutter client reports the host OS in "platform" even inside a
43
- # browser, so "web" is the only reliable way to tell a web client from a
44
- # native one. (pwa/wasm passed through for completeness.)
45
- "web" => page["web"],
46
- "pwa" => page["pwa"],
47
- "wasm" => page["wasm"],
61
+ "window" => page["window"] || {},
48
62
  "media" => page["media"] || {}
49
63
  }
50
64
  end
@@ -64,51 +78,11 @@ module Ruflet
64
78
  }
65
79
  end
66
80
 
67
- def normalize_patch_control_payload(payload)
68
- {
69
- "id" => payload["id"] || payload[:id],
70
- "patch" => Array(payload["patch"] || payload[:patch])
71
- }
72
- end
73
-
74
- def normalize_invoke_method_payload(payload)
75
- {
76
- "control_id" => payload["control_id"] || payload[:control_id],
77
- "call_id" => payload["call_id"] || payload[:call_id],
78
- "name" => payload["name"] || payload[:name],
79
- "args" => payload.key?("args") ? payload["args"] : payload[:args],
80
- "timeout" => payload.key?("timeout") || payload.key?(:timeout) ? (payload["timeout"] || payload[:timeout]) : 10
81
- }
82
- end
83
-
84
- def normalize_invoke_method_result_payload(payload)
85
- {
86
- "control_id" => payload["control_id"] || payload[:control_id],
87
- "call_id" => payload["call_id"] || payload[:call_id],
88
- "result" => payload.key?("result") ? payload["result"] : payload[:result],
89
- "error" => payload.key?("error") ? payload["error"] : payload[:error]
90
- }
91
- end
92
-
93
- def normalize_session_crashed_payload(payload)
94
- {
95
- "message" => payload["message"] || payload[:message].to_s
96
- }
97
- end
98
-
99
- def normalize_python_output_payload(payload)
100
- {
101
- "text" => payload["text"] || payload[:text].to_s,
102
- "is_stderr" => payload.key?("is_stderr") || payload.key?(:is_stderr) ? !!(payload["is_stderr"] || payload[:is_stderr]) : false
103
- }
104
- end
105
-
106
- def register_response(session_id:, page_patch: nil, error: nil)
107
- page_patch ||= {}
81
+ def register_response(session_id:)
108
82
  {
109
83
  "session_id" => session_id,
110
- "page_patch" => page_patch,
111
- "error" => error
84
+ "page_patch" => {},
85
+ "error" => ""
112
86
  }
113
87
  end
114
88
  end
@@ -21,7 +21,7 @@ module Ruflet
21
21
 
22
22
  def normalize_port(value)
23
23
  port = value.to_i
24
- port > 0 ? port : 8550
24
+ port >= 0 ? port : 8550
25
25
  end
26
26
  end
27
27
  end
@@ -151,57 +151,11 @@ module Ruflet
151
151
  end
152
152
 
153
153
  def self.normalize_color(color)
154
- return canonicalize(color.to_s) if color.is_a?(Symbol)
155
- return canonicalize(color) if color.is_a?(String)
156
- return canonicalize(color.to_s) unless color.respond_to?(:to_s)
154
+ return color.to_s if color.is_a?(Symbol)
155
+ return color if color.is_a?(String)
156
+ return color.to_s unless color.respond_to?(:to_s)
157
157
 
158
- canonicalize(color.to_s)
159
- end
160
-
161
- def self.normalize_property(key, value)
162
- normalize_value(value, color_key?(key))
163
- end
164
-
165
- def self.color_key?(key)
166
- name = key.to_s
167
- name == "color" ||
168
- name == "colors" ||
169
- name.end_with?("bgcolor") ||
170
- name.end_with?("_color") ||
171
- name.end_with?("_colors") ||
172
- name == "color_scheme_seed"
173
- end
174
-
175
- def self.normalize_value(value, color_context = false)
176
- case value
177
- when String
178
- color_context ? canonicalize(value) : value
179
- when Symbol
180
- color_context ? canonicalize(value.to_s) : value
181
- when Array
182
- value.map { |nested| normalize_value(nested, color_context) }
183
- when Hash
184
- value.each_with_object({}) do |(nested_key, nested_value), result|
185
- result[nested_key] = normalize_value(nested_value, color_context || color_key?(nested_key))
186
- end
187
- else
188
- value
189
- end
190
- end
191
-
192
- # Canonicalizes a named color into flet's wire format. Flet color names are
193
- # lowercase with no separators ("bluegrey", "deeporange", "red500"), so we
194
- # strip underscores, hyphens, and whitespace before downcasing. Hex values
195
- # (#... / 0x...) are downcased without separator stripping; the optional
196
- # ",opacity" suffix is preserved untouched.
197
- def self.canonicalize(value)
198
- return value unless value.is_a?(String)
199
-
200
- color, separator, opacity = value.partition(",")
201
- color = color.strip.downcase
202
- color = color.gsub(/[_\-\s]+/, "") unless color.start_with?("#") || color.start_with?("0x")
203
-
204
- "#{color}#{separator}#{opacity}"
158
+ color.to_s
205
159
  end
206
160
 
207
161
  BASE_PREFIX = {
@@ -5,7 +5,6 @@ require "securerandom"
5
5
  rescue LoadError
6
6
  nil
7
7
  end
8
- require_relative "colors"
9
8
  require_relative "icon_data"
10
9
  require_relative "icons/material_icon_lookup"
11
10
  require_relative "icons/cupertino_icon_lookup"
@@ -14,6 +13,7 @@ require "set"
14
13
  module Ruflet
15
14
  class Control
16
15
  HOST_EXPANDED_TYPES = %w[view row column].freeze
16
+ SCHEMA_METADATA_CACHE = {}
17
17
 
18
18
  attr_reader :type, :id, :props, :children
19
19
  attr_accessor :wire_id, :runtime_page
@@ -30,7 +30,13 @@ module Ruflet
30
30
  def on(event_name, &block)
31
31
  name = normalized_event_name(event_name)
32
32
  validate_event_name!(name)
33
- @handlers[name] = block
33
+ attach_handler(name, block)
34
+ self
35
+ end
36
+
37
+ def attach_handler(event_name, handler)
38
+ name = normalized_event_name(event_name)
39
+ @handlers[name] = handler if handler.respond_to?(:call)
34
40
  @props["on_#{name}"] = true
35
41
  runtime_page&.update(self, "on_#{name}": true) if wire_id
36
42
  self
@@ -48,35 +54,13 @@ module Ruflet
48
54
  @handlers.key?(normalized_event_name(event_name))
49
55
  end
50
56
 
51
- # Read a prop by name: control["value"] or control[:value].
52
- def [](key)
53
- @props[key.to_s]
54
- end
55
-
56
- # Write a prop by name: control["value"] = "x".
57
- def []=(key, value)
58
- @props[key.to_s] = value
59
- end
60
-
61
- # Convenience dot access to props, mirroring Flet-style controls:
62
- # control.value # => @props["value"] (reads an existing prop)
63
- # control.value = "hello" # => sets @props["value"]
64
- # Reads only resolve props that exist, so typos still raise NoMethodError
65
- # instead of silently returning nil. Defined methods (type, id, props,
66
- # children, on, emit, to_patch, …) are never shadowed.
67
- def method_missing(name, *args, &block)
68
- key = name.to_s
69
- if key.end_with?("=")
70
- return @props[key[0..-2]] = args.first
71
- end
72
- return @props[key] if args.empty? && @props.key?(key)
73
-
74
- super
75
- end
76
-
77
- def respond_to_missing?(name, include_private = false)
78
- key = name.to_s
79
- key.end_with?("=") || @props.key?(key) || super
57
+ # Refresh an existing control without replacing its identity. Page services
58
+ # are long-lived in Flet's ServiceRegistry, so revisiting a view must update
59
+ # both their configuration and their Ruby event handlers.
60
+ def merge_props(values = {})
61
+ normalized = normalize_props(extract_handlers(preprocess_props(values)))
62
+ @props.merge!(normalized)
63
+ self
80
64
  end
81
65
 
82
66
  def to_patch
@@ -84,8 +68,8 @@ module Ruflet
84
68
  if wire_type.nil?
85
69
  compact_type_key = type.delete("_")
86
70
  wire_type = type_map[type] || type_map[compact_type_key]
71
+ wire_type ||= type.split("_").map { |part| part[0].to_s.upcase + part[1..].to_s }.join
87
72
  end
88
- raise ArgumentError, "Unknown control type: #{type}" unless wire_type
89
73
  patch = {
90
74
  "_c" => wire_type,
91
75
  "_i" => wire_id
@@ -108,10 +92,10 @@ module Ruflet
108
92
 
109
93
  class << self
110
94
  def generate_id
111
- if defined?(SecureRandom) && SecureRandom.respond_to?(:hex)
95
+ if Object.const_defined?(:SecureRandom) && SecureRandom.respond_to?(:hex)
112
96
  SecureRandom.hex(4)
113
97
  else
114
- format("%08x", rand(0..0xffff_ffff))
98
+ format("%04x%04x", rand(0..0xffff), rand(0..0xffff))
115
99
  end
116
100
  end
117
101
  end
@@ -134,7 +118,7 @@ module Ruflet
134
118
  def extract_handlers(input)
135
119
  output = input.dup
136
120
  allowed_events = event_names
137
- allowed_events_set = allowed_events.to_set
121
+ allowed_event_lookup = event_name_lookup
138
122
 
139
123
  output.keys.each do |key|
140
124
  key_string = key.to_s
@@ -143,7 +127,7 @@ module Ruflet
143
127
  next if key_string == "on_label_color"
144
128
 
145
129
  event_name = normalized_event_name(key_string)
146
- if allowed_events.any? && !allowed_events_set.include?(event_name)
130
+ if allowed_events.any? && !allowed_event_lookup.key?(event_name)
147
131
  raise ArgumentError, "Unknown event `#{key_string}` for control type `#{type}`"
148
132
  end
149
133
 
@@ -152,22 +136,12 @@ module Ruflet
152
136
  output["on_#{event_name}"] = true
153
137
  end
154
138
 
155
- event_props.each do |prop, event_name|
156
- string_prop = prop.to_s
157
- next unless output.key?(prop) || output.key?(string_prop)
158
- next if allowed_events.any? && !allowed_events_set.include?(event_name)
159
-
160
- handler = output.key?(prop) ? output.delete(prop) : output.delete(string_prop)
161
- @handlers[event_name] = handler if handler.respond_to?(:call)
162
- output["on_#{event_name}"] = true
163
- end
164
-
165
139
  output
166
140
  end
167
141
 
168
142
  def normalize_props(hash)
169
143
  allowed_props = property_names
170
- normalized_allowed = allowed_props.to_set
144
+ allowed_prop_lookup = property_name_lookup
171
145
 
172
146
  hash.each_with_object({}) do |(k, v), result|
173
147
  key = k.to_s
@@ -175,7 +149,7 @@ module Ruflet
175
149
  if strict_schema_enforced?(allowed_props) &&
176
150
  !mapped_key.start_with?("_") &&
177
151
  !mapped_key.start_with?("on_") &&
178
- !normalized_allowed.include?(mapped_key)
152
+ !allowed_prop_lookup.key?(mapped_key)
179
153
  raise ArgumentError, "Unknown attribute `#{mapped_key}` for control type `#{type}`"
180
154
  end
181
155
 
@@ -198,11 +172,14 @@ module Ruflet
198
172
  end
199
173
 
200
174
  def normalize_color_prop(key, value)
201
- Ruflet::Colors.normalize_property(key, value)
175
+ return value unless value.is_a?(String)
176
+ return value.downcase if color_prop_key?(key)
177
+
178
+ value
202
179
  end
203
180
 
204
181
  def color_prop_key?(key)
205
- Ruflet::Colors.color_key?(key)
182
+ key == "color" || key.end_with?("bgcolor") || key.end_with?("_color")
206
183
  end
207
184
 
208
185
  def normalize_icon_prop(key, value)
@@ -232,7 +209,8 @@ module Ruflet
232
209
  end
233
210
 
234
211
  def normalized_event_name(event_name)
235
- event_name.to_s.sub(/\Aon_/, "")
212
+ name = event_name.to_s
213
+ name.start_with?("on_") ? name[3..-1] : name
236
214
  end
237
215
 
238
216
  def validate_event_name!(event_name)
@@ -247,16 +225,39 @@ module Ruflet
247
225
  end
248
226
 
249
227
  def property_names
250
- constructor_keywords_for_schema_class
251
- .reject { |name| name.to_s.start_with?("on_") && name != :on_label_color }
252
- .map(&:to_s)
228
+ schema_metadata[0]
253
229
  end
254
230
 
255
231
  def event_names
256
- constructor_keywords_for_schema_class
232
+ schema_metadata[1]
233
+ end
234
+
235
+ def property_name_lookup
236
+ schema_metadata[2]
237
+ end
238
+
239
+ def event_name_lookup
240
+ schema_metadata[3]
241
+ end
242
+
243
+ def schema_metadata
244
+ cache_key = type
245
+ cached = SCHEMA_METADATA_CACHE[cache_key]
246
+ return cached if cached
247
+
248
+ keywords = constructor_keywords_for_schema_class
249
+ properties = keywords
250
+ .reject { |name| name.to_s.start_with?("on_") && name != :on_label_color }
251
+ .map(&:to_s)
252
+ .freeze
253
+ events = keywords
257
254
  .select { |name| name.to_s.start_with?("on_") }
258
255
  .reject { |name| name == :on_label_color }
259
- .map { |name| name.to_s.sub(/\Aon_/, "") }
256
+ .map { |name| name.to_s[3..-1] }
257
+ .freeze
258
+ property_lookup = properties.each_with_object({}) { |name, lookup| lookup[name] = true }.freeze
259
+ event_lookup = events.each_with_object({}) { |name, lookup| lookup[name] = true }.freeze
260
+ SCHEMA_METADATA_CACHE[cache_key] = [properties, events, property_lookup, event_lookup].freeze
260
261
  end
261
262
 
262
263
  def schema_wire_type_for_class
@@ -291,16 +292,24 @@ module Ruflet
291
292
  def constructor_keywords_for_schema_class
292
293
  schema_class = schema_class_for_validation
293
294
  return [] unless schema_class
295
+ return schema_class::KEYWORDS if schema_class.const_defined?(:KEYWORDS)
294
296
 
295
- schema_class.instance_method(:initialize).parameters
296
- .select { |kind, _| kind == :key || kind == :keyreq }
297
- .map { |_, name| name }
298
- .reject { |name| name == :id }
297
+ keyword_parameters = schema_class.instance_method(:initialize).parameters
298
+ .select { |kind, _| kind == :key || kind == :keyreq }
299
+ # mruby can omit keyword names that are not present in its presymbol
300
+ # table. A partial schema is unsafe because it rejects valid application
301
+ # properties. Explicit KEYWORDS metadata remains strict; otherwise the
302
+ # constructor itself performs initial keyword validation.
303
+ return [] if keyword_parameters.any? { |_, name| name.nil? }
304
+
305
+ keyword_parameters.map { |_, name| name }.reject { |name| name == :id }
299
306
  rescue StandardError
300
307
  []
301
308
  end
302
309
 
303
310
  def has_explicit_initialize_keywords?(klass)
311
+ return true if klass.const_defined?(:KEYWORDS)
312
+
304
313
  params = klass.instance_method(:initialize).parameters
305
314
  params.any? { |kind, _| kind == :key || kind == :keyreq }
306
315
  rescue StandardError
@@ -312,9 +321,5 @@ module Ruflet
312
321
  UI::ControlRegistry::TYPE_MAP
313
322
  end
314
323
 
315
- def event_props
316
- require_relative "ui/control_registry"
317
- UI::ControlRegistry::EVENT_PROPS
318
- end
319
324
  end
320
325
  end
@@ -22,8 +22,10 @@ module Ruflet
22
22
 
23
23
  def default_port
24
24
  raw = ENV["RUFLET_PORT"].to_s
25
+ return 8550 if raw.strip.empty?
26
+
25
27
  value = raw.to_i
26
- value > 0 ? value : 8550
28
+ value >= 0 ? value : 8550
27
29
  end
28
30
 
29
31
  def _pending_app
@@ -176,6 +178,7 @@ module Ruflet
176
178
  def pageview(children = nil, **props) = _pending_app.pageview(children, **props)
177
179
  def progress_ring(**props) = _pending_app.progress_ring(**props)
178
180
  def progressring(**props) = _pending_app.progressring(**props)
181
+ def spinkit(**props) = _pending_app.spinkit(**props)
179
182
  def range_slider(**props) = _pending_app.range_slider(**props)
180
183
  def rangeslider(**props) = _pending_app.rangeslider(**props)
181
184
  def responsive_row(children = nil, **props, &block) = _pending_app.responsive_row(children, **props, &block)
@@ -240,6 +243,9 @@ module Ruflet
240
243
  def slider(**props) = _pending_app.slider(**props)
241
244
  def transparent_pointer(content = nil, **props) = _pending_app.transparent_pointer(content, **props)
242
245
  def transparentpointer(content = nil, **props) = _pending_app.transparentpointer(content, **props)
246
+ def rotated_box(content = nil, **props) = _pending_app.rotated_box(content, **props)
247
+ def rotatedbox(content = nil, **props) = _pending_app.rotatedbox(content, **props)
248
+ def screenshot(content = nil, **props) = _pending_app.screenshot(content, **props)
243
249
  def radio(**props) = _pending_app.radio(**props)
244
250
  def radio_group(content = nil, **props) = _pending_app.radio_group(content, **props)
245
251
  def radiogroup(content = nil, **props) = _pending_app.radiogroup(content, **props)
@@ -247,6 +253,8 @@ module Ruflet
247
253
  def alertdialog(**props) = _pending_app.alertdialog(**props)
248
254
  def snack_bar(content = nil, **props) = _pending_app.snack_bar(content, **props)
249
255
  def snackbar(content = nil, **props) = _pending_app.snackbar(content, **props)
256
+ def snack_bar_action(label = nil, **props) = _pending_app.snack_bar_action(label, **props)
257
+ def snackbaraction(label = nil, **props) = _pending_app.snackbaraction(label, **props)
250
258
  def bottom_sheet(content = nil, **props) = _pending_app.bottom_sheet(content, **props)
251
259
  def bottomsheet(content = nil, **props) = _pending_app.bottomsheet(content, **props)
252
260
  def markdown(value = nil, **props) = _pending_app.markdown(value, **props)
@@ -311,7 +319,6 @@ module Ruflet
311
319
  def web_view(**props) = _pending_app.web_view(**props)
312
320
  def webview(**props) = _pending_app.webview(**props)
313
321
  def video(**props) = _pending_app.video(**props)
314
- def spinkit(**variant) = _pending_app.spinkit(**variant)
315
322
  def code_editor(value = nil, **props) = _pending_app.code_editor(value, **props)
316
323
  def codeeditor(value = nil, **props) = _pending_app.codeeditor(value, **props)
317
324
  def rive(src = nil, **props) = _pending_app.rive(src, **props)
@@ -437,7 +444,7 @@ module Ruflet
437
444
  Ruflet::Server.new(host: host, port: port) do |runtime_page|
438
445
  runtime_page.set_view_props(page_props)
439
446
  runtime_page.add_service(*app_services) if app_services.any?
440
- runtime_page.add(*app_roots)
447
+ app_roots.each { |control| runtime_page.add(control) }
441
448
  end.start
442
449
  end
443
450
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../../icon_data"
4
+ require_relative "../icon_constant_names"
4
5
  require_relative "../cupertino_icon_lookup"
5
6
 
6
7
  module Ruflet
@@ -19,8 +20,7 @@ module Ruflet
19
20
  }
20
21
  else
21
22
  source.keys.each_with_object({}) do |name, result|
22
- text = name.to_s.gsub(/[^a-zA-Z0-9]/, "_").gsub(/_+/, "_").sub(/\A_/, "").sub(/_\z/, "")
23
- text = "ICON_#{text}" if text.match?(/\A\d/)
23
+ text = Ruflet::IconNames.constant_for(name)
24
24
  result[text.upcase.to_sym] = name
25
25
  end
26
26
  end.freeze
@@ -67,6 +67,7 @@ module Ruflet
67
67
  end
68
68
 
69
69
  def load_icon_map
70
+ return COMPILED_ICON_MAP if const_defined?(:COMPILED_ICON_MAP, false)
70
71
  return {} unless File.file?(LOCAL_ICONS_JSON)
71
72
 
72
73
  parse_icons_json(LOCAL_ICONS_JSON)
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruflet
4
+ # Derives Ruby constant names from icon identifiers. Implemented byte-wise
5
+ # rather than with regexps: the tables are built for thousands of icons at
6
+ # load time, and this path must stay fast on every supported runtime.
7
+ module IconNames
8
+ module_function
9
+
10
+ def constant_for(name)
11
+ text = +""
12
+ prev_underscore = false
13
+ name.to_s.each_byte do |byte|
14
+ if (byte >= 48 && byte <= 57) || (byte >= 65 && byte <= 90) || (byte >= 97 && byte <= 122)
15
+ text << byte.chr
16
+ prev_underscore = false
17
+ elsif !prev_underscore
18
+ text << "_"
19
+ prev_underscore = true
20
+ end
21
+ end
22
+ text = text[1..-1] while text.start_with?("_")
23
+ text = text[0...-1] while text.end_with?("_")
24
+ first = text.getbyte(0)
25
+ text = "ICON_#{text}" if first && first >= 48 && first <= 57
26
+ text
27
+ end
28
+ end
29
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../../icon_data"
4
+ require_relative "../icon_constant_names"
4
5
  require_relative "../material_icon_lookup"
5
6
 
6
7
  module Ruflet
@@ -19,8 +20,7 @@ module Ruflet
19
20
  }
20
21
  else
21
22
  source.keys.each_with_object({}) do |name, result|
22
- text = name.to_s.gsub(/[^a-zA-Z0-9]/, "_").gsub(/_+/, "_").sub(/\A_/, "").sub(/_\z/, "")
23
- text = "ICON_#{text}" if text.match?(/\A\d/)
23
+ text = Ruflet::IconNames.constant_for(name)
24
24
  result[text.upcase.to_sym] = name
25
25
  end
26
26
  end.freeze
@@ -67,6 +67,7 @@ module Ruflet
67
67
  end
68
68
 
69
69
  def load_icon_map
70
+ return COMPILED_ICON_MAP if const_defined?(:COMPILED_ICON_MAP, false)
70
71
  return {} unless File.file?(LOCAL_ICONS_JSON)
71
72
 
72
73
  parse_icons_json(LOCAL_ICONS_JSON)