ruflet_core 0.0.14 → 0.0.16
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.
- checksums.yaml +4 -4
- data/README.md +18 -2
- data/lib/ruflet/version.rb +1 -1
- data/lib/ruflet_core.rb +8 -1
- data/lib/ruflet_protocol/ruflet/protocol.rb +8 -1
- data/lib/ruflet_ui/ruflet/colors.rb +18 -4
- data/lib/ruflet_ui/ruflet/control.rb +33 -1
- data/lib/ruflet_ui/ruflet/dsl.rb +13 -0
- data/lib/ruflet_ui/ruflet/page.rb +122 -14
- data/lib/ruflet_ui/ruflet/ui/controls/materials/codeeditor_control.rb +93 -0
- data/lib/ruflet_ui/ruflet/ui/controls/materials/rive_control.rb +87 -0
- data/lib/ruflet_ui/ruflet/ui/controls/materials/ruflet_controls.rb +5 -0
- data/lib/ruflet_ui/ruflet/ui/controls/materials/spinkit_controls.rb +109 -0
- data/lib/ruflet_ui/ruflet/ui/controls/materials/webview_control.rb +114 -1
- data/lib/ruflet_ui/ruflet/ui/controls/ruflet_controls.rb +7 -1
- data/lib/ruflet_ui/ruflet/ui/controls/shared/window_control.rb +28 -4
- data/lib/ruflet_ui/ruflet/ui/material_control_methods.rb +58 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/audio_recorder_control.rb +4 -2
- data/lib/ruflet_ui/ruflet/ui/shared_control_forwarders.rb +13 -0
- data/lib/ruflet_ui.rb +6 -2
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 888ec7addc07079a2ef3b649047b8747d031b446489d3015fb96cb5c2101b339
|
|
4
|
+
data.tar.gz: 342aac065292f59865cf01ace0347531803b41692eac2d3453fe5751c773fe0a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2ef5114b3cc6389dafae6ab528990b7be92c0bc2367c62fc2eb0920feaec0dccc17400df7961df5b162bfbc539c2489818f77ac1dd91a040ebb87e1964016906
|
|
7
|
+
data.tar.gz: 9ad45dc326587bb2a601733b3c1efef6199c9d38424e8a82979d7b9f357fa44ffb579f19de30a3e2c8b2dd78681baf867b7555e9c21883f1f60dd44b7f4f50e0
|
data/README.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ruflet_core
|
|
2
2
|
|
|
3
|
-
|
|
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(text("Hello Ruflet"))
|
|
15
|
+
end
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Use `ruflet_server` to run a standalone server-driven application. Use
|
|
19
|
+
`ruflet_rails` when the UI is hosted by Rails.
|
data/lib/ruflet/version.rb
CHANGED
data/lib/ruflet_core.rb
CHANGED
|
@@ -10,7 +10,8 @@ module Ruflet
|
|
|
10
10
|
|
|
11
11
|
module_function
|
|
12
12
|
|
|
13
|
-
def run(entrypoint = nil, host: "0.0.0.0", port:
|
|
13
|
+
def run(entrypoint = nil, host: "0.0.0.0", port: nil, &block)
|
|
14
|
+
port = normalize_run_port(port || ENV["RUFLET_PORT"] || 8550)
|
|
14
15
|
callback = entrypoint || block
|
|
15
16
|
raise ArgumentError, "Ruflet.run requires a callable entrypoint or block" unless callback.respond_to?(:call)
|
|
16
17
|
|
|
@@ -37,4 +38,10 @@ module Ruflet
|
|
|
37
38
|
ensure
|
|
38
39
|
@run_interceptors_mutex.synchronize { @run_interceptors.delete(interceptor) }
|
|
39
40
|
end
|
|
41
|
+
|
|
42
|
+
def normalize_run_port(value)
|
|
43
|
+
Integer(value)
|
|
44
|
+
rescue ArgumentError, TypeError
|
|
45
|
+
8550
|
|
46
|
+
end
|
|
40
47
|
end
|
|
@@ -39,6 +39,12 @@ module Ruflet
|
|
|
39
39
|
"height" => page["height"],
|
|
40
40
|
"platform" => page["platform"],
|
|
41
41
|
"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"],
|
|
42
48
|
"media" => page["media"] || {}
|
|
43
49
|
}
|
|
44
50
|
end
|
|
@@ -97,7 +103,8 @@ module Ruflet
|
|
|
97
103
|
}
|
|
98
104
|
end
|
|
99
105
|
|
|
100
|
-
def register_response(session_id:, page_patch:
|
|
106
|
+
def register_response(session_id:, page_patch: nil, error: nil)
|
|
107
|
+
page_patch ||= {}
|
|
101
108
|
{
|
|
102
109
|
"session_id" => session_id,
|
|
103
110
|
"page_patch" => page_patch,
|
|
@@ -151,11 +151,25 @@ module Ruflet
|
|
|
151
151
|
end
|
|
152
152
|
|
|
153
153
|
def self.normalize_color(color)
|
|
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)
|
|
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)
|
|
157
157
|
|
|
158
|
-
color.to_s
|
|
158
|
+
canonicalize(color.to_s)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Canonicalizes a named color into flet's wire format. Flet color names are
|
|
162
|
+
# lowercase with no separators ("bluegrey", "deeporange", "red500"), so we
|
|
163
|
+
# strip underscores/whitespace and downcase. Hex values (#... / 0x...) and
|
|
164
|
+
# the optional ",opacity" suffix are preserved untouched.
|
|
165
|
+
def self.canonicalize(value)
|
|
166
|
+
return value unless value.is_a?(String)
|
|
167
|
+
|
|
168
|
+
color, separator, opacity = value.partition(",")
|
|
169
|
+
color = color.strip.downcase
|
|
170
|
+
color = color.delete("_ \t\n") unless color.start_with?("#") || color.start_with?("0x")
|
|
171
|
+
|
|
172
|
+
"#{color}#{separator}#{opacity}"
|
|
159
173
|
end
|
|
160
174
|
|
|
161
175
|
BASE_PREFIX = {
|
|
@@ -5,6 +5,7 @@ require "securerandom"
|
|
|
5
5
|
rescue LoadError
|
|
6
6
|
nil
|
|
7
7
|
end
|
|
8
|
+
require_relative "colors"
|
|
8
9
|
require_relative "icon_data"
|
|
9
10
|
require_relative "icons/material_icon_lookup"
|
|
10
11
|
require_relative "icons/cupertino_icon_lookup"
|
|
@@ -47,6 +48,37 @@ module Ruflet
|
|
|
47
48
|
@handlers.key?(normalized_event_name(event_name))
|
|
48
49
|
end
|
|
49
50
|
|
|
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
|
|
80
|
+
end
|
|
81
|
+
|
|
50
82
|
def to_patch
|
|
51
83
|
wire_type = schema_wire_type_for_class
|
|
52
84
|
if wire_type.nil?
|
|
@@ -167,7 +199,7 @@ module Ruflet
|
|
|
167
199
|
|
|
168
200
|
def normalize_color_prop(key, value)
|
|
169
201
|
return value unless value.is_a?(String)
|
|
170
|
-
return value
|
|
202
|
+
return Ruflet::Colors.canonicalize(value) if color_prop_key?(key)
|
|
171
203
|
|
|
172
204
|
value
|
|
173
205
|
end
|
data/lib/ruflet_ui/ruflet/dsl.rb
CHANGED
|
@@ -68,6 +68,15 @@ module Ruflet
|
|
|
68
68
|
def autocompletesuggestion(key = nil, **props) = _pending_app.autocompletesuggestion(key, **props)
|
|
69
69
|
def context_menu(content = nil, **props) = _pending_app.context_menu(content, **props)
|
|
70
70
|
def contextmenu(content = nil, **props) = _pending_app.contextmenu(content, **props)
|
|
71
|
+
def autofill_group(content = nil, **props) = _pending_app.autofill_group(content, **props)
|
|
72
|
+
def autofillgroup(content = nil, **props) = _pending_app.autofillgroup(content, **props)
|
|
73
|
+
def hero(content = nil, **props) = _pending_app.hero(content, **props)
|
|
74
|
+
def overlay(children = nil, **props) = _pending_app.overlay(children, **props)
|
|
75
|
+
def shader_mask(content = nil, **props) = _pending_app.shader_mask(content, **props)
|
|
76
|
+
def shadermask(content = nil, **props) = _pending_app.shadermask(content, **props)
|
|
77
|
+
def shimmer(content = nil, **props) = _pending_app.shimmer(content, **props)
|
|
78
|
+
def text_span(text = nil, **props) = _pending_app.text_span(text, **props)
|
|
79
|
+
def textspan(text = nil, **props) = _pending_app.textspan(text, **props)
|
|
71
80
|
def keyboard_listener(content = nil, **props) = _pending_app.keyboard_listener(content, **props)
|
|
72
81
|
def keyboardlistener(content = nil, **props) = _pending_app.keyboardlistener(content, **props)
|
|
73
82
|
def gesture_detector(**props, &block) = _pending_app.gesture_detector(**props, &block)
|
|
@@ -302,6 +311,10 @@ module Ruflet
|
|
|
302
311
|
def web_view(**props) = _pending_app.web_view(**props)
|
|
303
312
|
def webview(**props) = _pending_app.webview(**props)
|
|
304
313
|
def video(**props) = _pending_app.video(**props)
|
|
314
|
+
def spinkit(**variant) = _pending_app.spinkit(**variant)
|
|
315
|
+
def code_editor(value = nil, **props) = _pending_app.code_editor(value, **props)
|
|
316
|
+
def codeeditor(value = nil, **props) = _pending_app.codeeditor(value, **props)
|
|
317
|
+
def rive(src = nil, **props) = _pending_app.rive(src, **props)
|
|
305
318
|
def fab(content = nil, **props) = _pending_app.fab(content, **props)
|
|
306
319
|
def cupertino_button(content = nil, **props) = _pending_app.cupertino_button(content, **props)
|
|
307
320
|
def cupertinobutton(content = nil, **props) = _pending_app.cupertinobutton(content, **props)
|
|
@@ -244,6 +244,42 @@ module Ruflet
|
|
|
244
244
|
@view_props["bgcolor"] = normalize_value("bgcolor", value)
|
|
245
245
|
end
|
|
246
246
|
|
|
247
|
+
# Client-reported page properties. The Flutter client sends these in its
|
|
248
|
+
# register payload (see Protocol.normalize_register_payload), where they are
|
|
249
|
+
# stored in @client_details; expose them as readers so apps can do
|
|
250
|
+
# `page.width`, `page.platform`, etc. without reaching into client_details.
|
|
251
|
+
def width
|
|
252
|
+
client_reported_prop("width")
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def height
|
|
256
|
+
client_reported_prop("height")
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def platform
|
|
260
|
+
client_reported_prop("platform")
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def platform_brightness
|
|
264
|
+
client_reported_prop("platform_brightness")
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def web
|
|
268
|
+
client_reported_prop("web")
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def pwa
|
|
272
|
+
client_reported_prop("pwa")
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def wasm
|
|
276
|
+
client_reported_prop("wasm")
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def media
|
|
280
|
+
client_reported_prop("media")
|
|
281
|
+
end
|
|
282
|
+
|
|
247
283
|
def add(*controls, appbar: nil, bottom_appbar: nil, floating_action_button: nil, navigation_bar: nil, dialog: nil, snack_bar: nil, bottom_sheet: nil)
|
|
248
284
|
controls = controls.flatten
|
|
249
285
|
visited = Set.new
|
|
@@ -412,6 +448,10 @@ module Ruflet
|
|
|
412
448
|
@page_event_handlers["view_pop"] = handler
|
|
413
449
|
end
|
|
414
450
|
|
|
451
|
+
def on_resize=(handler)
|
|
452
|
+
@page_event_handlers["resize"] = handler
|
|
453
|
+
end
|
|
454
|
+
|
|
415
455
|
def on(event_name, &block)
|
|
416
456
|
@page_event_handlers[event_name.to_s.sub(/\Aon_/, "")] = block
|
|
417
457
|
self
|
|
@@ -533,7 +573,7 @@ module Ruflet
|
|
|
533
573
|
call_id = "call_#{Ruflet::Control.generate_id}"
|
|
534
574
|
if on_result.respond_to?(:call)
|
|
535
575
|
@invoke_waiters_mutex.synchronize { @invoke_callbacks[call_id] = on_result }
|
|
536
|
-
|
|
576
|
+
if embedded_async_timeout_available? && !timeout.nil?
|
|
537
577
|
Thread.new(call_id, timeout.to_f) do |pending_call_id, invoke_timeout|
|
|
538
578
|
sleep([invoke_timeout, 0.0].max + 0.1)
|
|
539
579
|
callback = @invoke_waiters_mutex.synchronize { @invoke_callbacks.delete(pending_call_id) }
|
|
@@ -1157,12 +1197,15 @@ module Ruflet
|
|
|
1157
1197
|
def close_dialog(dialog_control)
|
|
1158
1198
|
return self unless dialog_control
|
|
1159
1199
|
|
|
1160
|
-
before_dialog_count = @dialogs_container.props["controls"].length
|
|
1161
1200
|
dialog_control.props["open"] = false
|
|
1162
1201
|
@dialog = nil if @dialog.equal?(dialog_control)
|
|
1163
1202
|
remove_dialog_tracking(dialog_control)
|
|
1164
1203
|
refresh_dialogs_container!
|
|
1165
|
-
|
|
1204
|
+
# Patch the dialogs container in place. Forcing a full view re-render
|
|
1205
|
+
# here would remount the whole overlay — fatal while another dialog
|
|
1206
|
+
# (e.g. the form behind a nested picker) is still open. The empty case
|
|
1207
|
+
# is handled inside push_dialogs_update!.
|
|
1208
|
+
push_dialogs_update!
|
|
1166
1209
|
self
|
|
1167
1210
|
end
|
|
1168
1211
|
|
|
@@ -1231,7 +1274,17 @@ module Ruflet
|
|
|
1231
1274
|
if page_control_target?(target)
|
|
1232
1275
|
if name.to_s == "route_change"
|
|
1233
1276
|
route_from_event = extract_route(data)
|
|
1277
|
+
# Dialogs (including pickers) belong to the view that opened them.
|
|
1278
|
+
# Navigating away must dismiss them, or they ghost onto the next
|
|
1279
|
+
# view — the picker that "reappears after going home".
|
|
1280
|
+
dismiss_tracked_dialogs! if route_from_event && route_from_event != @page_props["route"]
|
|
1234
1281
|
@page_props["route"] = route_from_event if route_from_event
|
|
1282
|
+
elsif name.to_s == "resize"
|
|
1283
|
+
# The client reports the live page size via the "resize" event. Store
|
|
1284
|
+
# it so `page.width`/`page.height` reflect the real viewport — without
|
|
1285
|
+
# this, responsive layouts collapse on clients (e.g. embedded/iOS)
|
|
1286
|
+
# that don't know their size at the initial handshake.
|
|
1287
|
+
store_reported_page_size(data)
|
|
1235
1288
|
end
|
|
1236
1289
|
dispatch_page_event(name: name, data: data)
|
|
1237
1290
|
return
|
|
@@ -1240,11 +1293,16 @@ module Ruflet
|
|
|
1240
1293
|
control = @wire_index[target.to_i] || @control_index[target.to_s]
|
|
1241
1294
|
return unless control
|
|
1242
1295
|
|
|
1243
|
-
event = Event.new(name: name, target: target, raw_data: data, page: self, control: control)
|
|
1296
|
+
event = Ruflet::Event.new(name: name, target: target, raw_data: data, page: self, control: control)
|
|
1244
1297
|
apply_event_value_to_control(control, event) if %w[change select select_change].include?(name.to_s)
|
|
1245
|
-
|
|
1298
|
+
# Material/Cupertino pickers dismiss themselves on the client once a
|
|
1299
|
+
# value is confirmed, but only send a value event — never a close. Mark
|
|
1300
|
+
# the dialog closed here so show_dialog can reopen it next time.
|
|
1301
|
+
mark_picker_dialog_closed(control, name)
|
|
1246
1302
|
if dialog_close_event?(control, name) && remove_dialog_tracking(control)
|
|
1247
|
-
|
|
1303
|
+
# Patch the container in place; never force a full view re-render that
|
|
1304
|
+
# would remount a still-open parent dialog (the nested-picker case).
|
|
1305
|
+
push_dialogs_update!
|
|
1248
1306
|
end
|
|
1249
1307
|
|
|
1250
1308
|
control.emit(name, event)
|
|
@@ -1289,6 +1347,16 @@ module Ruflet
|
|
|
1289
1347
|
|
|
1290
1348
|
private
|
|
1291
1349
|
|
|
1350
|
+
def client_reported_prop(name)
|
|
1351
|
+
return @page_props[name] if @page_props.key?(name)
|
|
1352
|
+
|
|
1353
|
+
@client_details[name]
|
|
1354
|
+
end
|
|
1355
|
+
|
|
1356
|
+
def embedded_async_timeout_available?
|
|
1357
|
+
!Object.const_defined?(:RUFLET_EMBEDDED_FAKE_THREAD)
|
|
1358
|
+
end
|
|
1359
|
+
|
|
1292
1360
|
def invoke_and_wait(control_or_id, method_name, args: nil, timeout: 10)
|
|
1293
1361
|
control_id =
|
|
1294
1362
|
if page_control_target?(control_or_id)
|
|
@@ -1540,6 +1608,15 @@ module Ruflet
|
|
|
1540
1608
|
end
|
|
1541
1609
|
end
|
|
1542
1610
|
|
|
1611
|
+
def store_reported_page_size(data)
|
|
1612
|
+
return unless data.is_a?(Hash)
|
|
1613
|
+
|
|
1614
|
+
width = data["width"] || data[:width]
|
|
1615
|
+
height = data["height"] || data[:height]
|
|
1616
|
+
@page_props["width"] = width unless width.nil?
|
|
1617
|
+
@page_props["height"] = height unless height.nil?
|
|
1618
|
+
end
|
|
1619
|
+
|
|
1543
1620
|
def dispatch_page_event(name:, data:)
|
|
1544
1621
|
handler = @page_event_handlers[name.to_s.sub(/\Aon_/, "")]
|
|
1545
1622
|
return unless handler.respond_to?(:call)
|
|
@@ -1634,15 +1711,15 @@ module Ruflet
|
|
|
1634
1711
|
end
|
|
1635
1712
|
end
|
|
1636
1713
|
|
|
1637
|
-
def push_dialogs_update!
|
|
1714
|
+
def push_dialogs_update!
|
|
1638
1715
|
refresh_control_indexes!
|
|
1639
1716
|
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1717
|
+
# Once the dialogs container is mounted, every change — opening, closing,
|
|
1718
|
+
# even down to no dialogs at all — is an in-place patch of its controls
|
|
1719
|
+
# list. Re-sending the view (or the container as a whole object) would
|
|
1720
|
+
# replace the live container instance on the Flutter side, detaching its
|
|
1721
|
+
# listeners and breaking any other dialog still open. Only the very first
|
|
1722
|
+
# dialog, before the container has a wire id, needs a view patch to mount.
|
|
1646
1723
|
if @dialogs_container.wire_id
|
|
1647
1724
|
send_message(Protocol::ACTIONS[:patch_control], {
|
|
1648
1725
|
"id" => @dialogs_container.wire_id,
|
|
@@ -1670,6 +1747,36 @@ module Ruflet
|
|
|
1670
1747
|
name == "dismiss" || (%w[change select select_change].include?(name) && @dialogs.include?(control) && control.props["open"] == false)
|
|
1671
1748
|
end
|
|
1672
1749
|
|
|
1750
|
+
# Picker dialogs that auto-dismiss on the client after a selection. Their
|
|
1751
|
+
# confirm sends a value event (change/select), not a close, so the server
|
|
1752
|
+
# must flip `open` to false or show_dialog's open-guard blocks reopening.
|
|
1753
|
+
PICKER_DIALOG_TYPES = %w[
|
|
1754
|
+
datepicker daterangepicker timepicker
|
|
1755
|
+
cupertinodatepicker cupertinotimerpicker
|
|
1756
|
+
].freeze
|
|
1757
|
+
|
|
1758
|
+
def picker_dialog?(control)
|
|
1759
|
+
PICKER_DIALOG_TYPES.include?(control.type.to_s.tr("_", "").downcase)
|
|
1760
|
+
end
|
|
1761
|
+
|
|
1762
|
+
def mark_picker_dialog_closed(control, name)
|
|
1763
|
+
return unless picker_dialog?(control)
|
|
1764
|
+
return unless %w[change select select_change dismiss].include?(name.to_s)
|
|
1765
|
+
|
|
1766
|
+
control.props["open"] = false
|
|
1767
|
+
end
|
|
1768
|
+
|
|
1769
|
+
# Close and untrack every dialog currently shown. Called on navigation so
|
|
1770
|
+
# a dialog opened in one view does not linger as an overlay on the next.
|
|
1771
|
+
def dismiss_tracked_dialogs!
|
|
1772
|
+
return if @dialogs.empty?
|
|
1773
|
+
|
|
1774
|
+
@dialogs.each { |dialog| dialog.props["open"] = false }
|
|
1775
|
+
@dialogs.clear
|
|
1776
|
+
refresh_dialogs_container!
|
|
1777
|
+
push_dialogs_update! if @dialogs_container_mounted
|
|
1778
|
+
end
|
|
1779
|
+
|
|
1673
1780
|
def remove_dialog_tracking(control)
|
|
1674
1781
|
return false unless @dialogs.include?(control)
|
|
1675
1782
|
|
|
@@ -1734,6 +1841,7 @@ module Ruflet
|
|
|
1734
1841
|
existing = service_by_type(type)
|
|
1735
1842
|
return [existing, false] if existing
|
|
1736
1843
|
|
|
1844
|
+
# `service` already syncs via add_service -> push_services_update!.
|
|
1737
1845
|
[service(type), true]
|
|
1738
1846
|
end
|
|
1739
1847
|
|
|
@@ -1821,7 +1929,7 @@ module Ruflet
|
|
|
1821
1929
|
call_id = "call_#{Ruflet::Control.generate_id}"
|
|
1822
1930
|
if on_result.respond_to?(:call)
|
|
1823
1931
|
@invoke_waiters_mutex.synchronize { @invoke_callbacks[call_id] = on_result }
|
|
1824
|
-
|
|
1932
|
+
if embedded_async_timeout_available? && !timeout.nil?
|
|
1825
1933
|
Thread.new(call_id, timeout.to_f) do |pending_call_id, invoke_timeout|
|
|
1826
1934
|
sleep([invoke_timeout, 0.0].max + 0.1)
|
|
1827
1935
|
callback = @invoke_waiters_mutex.synchronize { @invoke_callbacks.delete(pending_call_id) }
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruflet
|
|
4
|
+
module UI
|
|
5
|
+
module Controls
|
|
6
|
+
module RufletComponents
|
|
7
|
+
# CodeEditor control — parity with Flet's CodeEditor extension
|
|
8
|
+
# (https://flet.dev/docs/controls/codeeditor/).
|
|
9
|
+
#
|
|
10
|
+
# Properties: value, language, code_theme, text_style, padding, selection,
|
|
11
|
+
# gutter_style, autocomplete, autocomplete_words, issues, read_only,
|
|
12
|
+
# autofocus, plus the usual layout props.
|
|
13
|
+
# Events: on_change, on_selection_change, on_focus, on_blur.
|
|
14
|
+
# Methods (invoked over the wire on a mounted control): focus, fold_at,
|
|
15
|
+
# fold_comment_at_line_zero, fold_imports.
|
|
16
|
+
#
|
|
17
|
+
# `language` accepts a highlight.js identifier (e.g. "python", "ruby",
|
|
18
|
+
# "javascript"); `code_theme` accepts a highlight.js theme name shared
|
|
19
|
+
# with Markdown (e.g. "atom-one-light", "atom-one-dark", "monokai-sublime").
|
|
20
|
+
class CodeEditorControl < Ruflet::Control
|
|
21
|
+
TYPE = "CodeEditor".freeze
|
|
22
|
+
WIRE = "CodeEditor".freeze
|
|
23
|
+
|
|
24
|
+
def initialize(id: nil, adaptive: nil, autocomplete: nil, autocomplete_words: nil,
|
|
25
|
+
autofocus: nil, badge: nil, code_theme: nil, col: nil, data: nil,
|
|
26
|
+
disabled: nil, expand: nil, expand_loose: nil, gutter_style: nil,
|
|
27
|
+
height: nil, issues: nil, key: nil, language: nil, opacity: nil,
|
|
28
|
+
padding: nil, read_only: nil, rtl: nil, selection: nil,
|
|
29
|
+
text_style: nil, tooltip: nil, value: nil, visible: nil, width: nil,
|
|
30
|
+
on_blur: nil, on_change: nil, on_focus: nil, on_selection_change: nil)
|
|
31
|
+
props = {}
|
|
32
|
+
props[:adaptive] = adaptive unless adaptive.nil?
|
|
33
|
+
props[:autocomplete] = autocomplete unless autocomplete.nil?
|
|
34
|
+
props[:autocomplete_words] = autocomplete_words unless autocomplete_words.nil?
|
|
35
|
+
props[:autofocus] = autofocus unless autofocus.nil?
|
|
36
|
+
props[:badge] = badge unless badge.nil?
|
|
37
|
+
props[:code_theme] = code_theme unless code_theme.nil?
|
|
38
|
+
props[:col] = col unless col.nil?
|
|
39
|
+
props[:data] = data unless data.nil?
|
|
40
|
+
props[:disabled] = disabled unless disabled.nil?
|
|
41
|
+
props[:expand] = expand unless expand.nil?
|
|
42
|
+
props[:expand_loose] = expand_loose unless expand_loose.nil?
|
|
43
|
+
props[:gutter_style] = gutter_style unless gutter_style.nil?
|
|
44
|
+
props[:height] = height unless height.nil?
|
|
45
|
+
props[:issues] = issues unless issues.nil?
|
|
46
|
+
props[:key] = key unless key.nil?
|
|
47
|
+
props[:language] = language unless language.nil?
|
|
48
|
+
props[:opacity] = opacity unless opacity.nil?
|
|
49
|
+
props[:padding] = padding unless padding.nil?
|
|
50
|
+
props[:read_only] = read_only unless read_only.nil?
|
|
51
|
+
props[:rtl] = rtl unless rtl.nil?
|
|
52
|
+
props[:selection] = selection unless selection.nil?
|
|
53
|
+
props[:text_style] = text_style unless text_style.nil?
|
|
54
|
+
props[:tooltip] = tooltip unless tooltip.nil?
|
|
55
|
+
props[:value] = value unless value.nil?
|
|
56
|
+
props[:visible] = visible unless visible.nil?
|
|
57
|
+
props[:width] = width unless width.nil?
|
|
58
|
+
props[:on_blur] = on_blur unless on_blur.nil?
|
|
59
|
+
props[:on_change] = on_change unless on_change.nil?
|
|
60
|
+
props[:on_focus] = on_focus unless on_focus.nil?
|
|
61
|
+
props[:on_selection_change] = on_selection_change unless on_selection_change.nil?
|
|
62
|
+
super(type: TYPE, id: id, **props)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Request focus for the editor.
|
|
66
|
+
def focus = invoke_editor_method("focus")
|
|
67
|
+
|
|
68
|
+
# Fold the code block that starts at the given line number.
|
|
69
|
+
def fold_at(line_number)
|
|
70
|
+
invoke_editor_method("fold_at", { "line_number" => line_number.to_i })
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Fold the comment block at line 0 (e.g. a license header).
|
|
74
|
+
def fold_comment_at_line_zero = invoke_editor_method("fold_comment_at_line_zero")
|
|
75
|
+
|
|
76
|
+
# Fold all import sections.
|
|
77
|
+
def fold_imports = invoke_editor_method("fold_imports")
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def invoke_editor_method(name, args = nil, timeout: 10, on_result: nil)
|
|
82
|
+
page = runtime_page
|
|
83
|
+
unless page && wire_id
|
|
84
|
+
raise "CodeEditor ##{id} is not mounted yet — add it to the page before calling #{name}."
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
page.invoke(self, name, args: args, timeout: timeout, on_result: on_result)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruflet
|
|
4
|
+
module UI
|
|
5
|
+
module Controls
|
|
6
|
+
module RufletComponents
|
|
7
|
+
# Rive control — parity with Flet's Rive extension
|
|
8
|
+
# (https://flet.dev/docs/controls/rive/). Renders a Rive
|
|
9
|
+
# (https://rive.app) animation from a `.riv` file.
|
|
10
|
+
#
|
|
11
|
+
# Properties: src, placeholder, artboard, alignment, enable_antialiasing,
|
|
12
|
+
# use_artboard_size, fit, speed_multiplier, animations, state_machines,
|
|
13
|
+
# headers, clip_rect, plus the usual layout props.
|
|
14
|
+
# No events or methods — playback is driven by `animations` /
|
|
15
|
+
# `state_machines` and `speed_multiplier`.
|
|
16
|
+
#
|
|
17
|
+
# `src` is required and may be a network URL (e.g.
|
|
18
|
+
# "https://cdn.rive.app/animations/vehicles.riv") or a bundled asset path.
|
|
19
|
+
#
|
|
20
|
+
# Note: the Flet API names the artboard property `artboard`, but the
|
|
21
|
+
# underlying renderer reads `art_board` / `use_art_board_size` on the wire.
|
|
22
|
+
# Both spellings are accepted here and normalized to the wire keys.
|
|
23
|
+
class RiveControl < Ruflet::Control
|
|
24
|
+
TYPE = "Rive".freeze
|
|
25
|
+
WIRE = "Rive".freeze
|
|
26
|
+
|
|
27
|
+
def initialize(id: nil, adaptive: nil, alignment: nil, animate_offset: nil,
|
|
28
|
+
animate_opacity: nil, animate_position: nil, animate_rotation: nil,
|
|
29
|
+
animate_scale: nil, animations: nil, art_board: nil, artboard: nil,
|
|
30
|
+
aspect_ratio: nil, badge: nil, bottom: nil, clip_rect: nil, col: nil,
|
|
31
|
+
data: nil, disabled: nil, enable_antialiasing: nil, expand: nil,
|
|
32
|
+
expand_loose: nil, fit: nil, headers: nil, height: nil, key: nil,
|
|
33
|
+
left: nil, offset: nil, opacity: nil, placeholder: nil, right: nil,
|
|
34
|
+
rotate: nil, rtl: nil, scale: nil, speed_multiplier: nil, src: nil,
|
|
35
|
+
state_machines: nil, tooltip: nil, top: nil, use_art_board_size: nil,
|
|
36
|
+
use_artboard_size: nil, visible: nil, width: nil)
|
|
37
|
+
# Accept both the Flet-style names and the wire keys.
|
|
38
|
+
art_board = artboard if art_board.nil?
|
|
39
|
+
use_art_board_size = use_artboard_size if use_art_board_size.nil?
|
|
40
|
+
|
|
41
|
+
props = {}
|
|
42
|
+
props[:adaptive] = adaptive unless adaptive.nil?
|
|
43
|
+
props[:alignment] = alignment unless alignment.nil?
|
|
44
|
+
props[:animate_offset] = animate_offset unless animate_offset.nil?
|
|
45
|
+
props[:animate_opacity] = animate_opacity unless animate_opacity.nil?
|
|
46
|
+
props[:animate_position] = animate_position unless animate_position.nil?
|
|
47
|
+
props[:animate_rotation] = animate_rotation unless animate_rotation.nil?
|
|
48
|
+
props[:animate_scale] = animate_scale unless animate_scale.nil?
|
|
49
|
+
props[:animations] = animations unless animations.nil?
|
|
50
|
+
props[:art_board] = art_board unless art_board.nil?
|
|
51
|
+
props[:aspect_ratio] = aspect_ratio unless aspect_ratio.nil?
|
|
52
|
+
props[:badge] = badge unless badge.nil?
|
|
53
|
+
props[:bottom] = bottom unless bottom.nil?
|
|
54
|
+
props[:clip_rect] = clip_rect unless clip_rect.nil?
|
|
55
|
+
props[:col] = col unless col.nil?
|
|
56
|
+
props[:data] = data unless data.nil?
|
|
57
|
+
props[:disabled] = disabled unless disabled.nil?
|
|
58
|
+
props[:enable_antialiasing] = enable_antialiasing unless enable_antialiasing.nil?
|
|
59
|
+
props[:expand] = expand unless expand.nil?
|
|
60
|
+
props[:expand_loose] = expand_loose unless expand_loose.nil?
|
|
61
|
+
props[:fit] = fit unless fit.nil?
|
|
62
|
+
props[:headers] = headers unless headers.nil?
|
|
63
|
+
props[:height] = height unless height.nil?
|
|
64
|
+
props[:key] = key unless key.nil?
|
|
65
|
+
props[:left] = left unless left.nil?
|
|
66
|
+
props[:offset] = offset unless offset.nil?
|
|
67
|
+
props[:opacity] = opacity unless opacity.nil?
|
|
68
|
+
props[:placeholder] = placeholder unless placeholder.nil?
|
|
69
|
+
props[:right] = right unless right.nil?
|
|
70
|
+
props[:rotate] = rotate unless rotate.nil?
|
|
71
|
+
props[:rtl] = rtl unless rtl.nil?
|
|
72
|
+
props[:scale] = scale unless scale.nil?
|
|
73
|
+
props[:speed_multiplier] = speed_multiplier unless speed_multiplier.nil?
|
|
74
|
+
props[:src] = src unless src.nil?
|
|
75
|
+
props[:state_machines] = state_machines unless state_machines.nil?
|
|
76
|
+
props[:tooltip] = tooltip unless tooltip.nil?
|
|
77
|
+
props[:top] = top unless top.nil?
|
|
78
|
+
props[:use_art_board_size] = use_art_board_size unless use_art_board_size.nil?
|
|
79
|
+
props[:visible] = visible unless visible.nil?
|
|
80
|
+
props[:width] = width unless width.nil?
|
|
81
|
+
super(type: TYPE, id: id, **props)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -15,6 +15,7 @@ require_relative "card_control"
|
|
|
15
15
|
require_relative "checkbox_control"
|
|
16
16
|
require_relative "chip_control"
|
|
17
17
|
require_relative "circleavatar_control"
|
|
18
|
+
require_relative "codeeditor_control"
|
|
18
19
|
require_relative "container_control"
|
|
19
20
|
require_relative "contextmenu_control"
|
|
20
21
|
require_relative "datacell_control"
|
|
@@ -57,6 +58,7 @@ require_relative "radio_control"
|
|
|
57
58
|
require_relative "radiogroup_control"
|
|
58
59
|
require_relative "rangeslider_control"
|
|
59
60
|
require_relative "reorderablelistview_control"
|
|
61
|
+
require_relative "rive_control"
|
|
60
62
|
require_relative "searchbar_control"
|
|
61
63
|
require_relative "segment_control"
|
|
62
64
|
require_relative "segmentedbutton_control"
|
|
@@ -120,6 +122,8 @@ module Ruflet
|
|
|
120
122
|
"chip" => RufletComponents::ChipControl,
|
|
121
123
|
"circle_avatar" => RufletComponents::CircleAvatarControl,
|
|
122
124
|
"circleavatar" => RufletComponents::CircleAvatarControl,
|
|
125
|
+
"code_editor" => RufletComponents::CodeEditorControl,
|
|
126
|
+
"codeeditor" => RufletComponents::CodeEditorControl,
|
|
123
127
|
"container" => RufletComponents::ContainerControl,
|
|
124
128
|
"context_menu" => RufletComponents::ContextMenuControl,
|
|
125
129
|
"contextmenu" => RufletComponents::ContextMenuControl,
|
|
@@ -235,6 +239,7 @@ module Ruflet
|
|
|
235
239
|
"rangeslider" => RufletComponents::RangeSliderControl,
|
|
236
240
|
"reorderable_list_view" => RufletComponents::ReorderableListViewControl,
|
|
237
241
|
"reorderablelistview" => RufletComponents::ReorderableListViewControl,
|
|
242
|
+
"rive" => RufletComponents::RiveControl,
|
|
238
243
|
"search_bar" => RufletComponents::SearchBarControl,
|
|
239
244
|
"searchbar" => RufletComponents::SearchBarControl,
|
|
240
245
|
"segment" => RufletComponents::SegmentControl,
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruflet
|
|
4
|
+
module UI
|
|
5
|
+
module Controls
|
|
6
|
+
module RufletComponents
|
|
7
|
+
# Base for every flet_spinkit variant (https://flet.dev/docs/controls/spinkit/).
|
|
8
|
+
# Each concrete spinner is a LayoutControl with its own wire name
|
|
9
|
+
# ("SpinKitRotatingCircle", "SpinKitWave", ...). The Dart widget reads
|
|
10
|
+
# color/size/duration for all of them plus the optional line_width /
|
|
11
|
+
# border_width / item_count / wave_type used by a few variants, so those
|
|
12
|
+
# are accepted on every spinner (the client ignores the ones a variant
|
|
13
|
+
# doesn't use, matching the upstream implementation).
|
|
14
|
+
class SpinKitControl < Ruflet::Control
|
|
15
|
+
def initialize(id: nil, align: nil, animate_align: nil, animate_offset: nil, animate_opacity: nil, animate_position: nil, animate_rotation: nil, animate_scale: nil, animate_size: nil, aspect_ratio: nil, badge: nil, border_width: nil, bottom: nil, col: nil, color: nil, data: nil, disabled: nil, duration: nil, expand: nil, expand_loose: nil, height: nil, item_count: nil, key: nil, left: nil, line_width: nil, offset: nil, opacity: nil, right: nil, rotate: nil, rtl: nil, scale: nil, size: nil, tooltip: nil, top: nil, visible: nil, wave_type: nil, width: nil, on_animation_end: nil)
|
|
16
|
+
raise ArgumentError, "spinkit size must be greater than or equal to 0" unless size.nil? || size >= 0
|
|
17
|
+
|
|
18
|
+
props = {}
|
|
19
|
+
props[:align] = align unless align.nil?
|
|
20
|
+
props[:animate_align] = animate_align unless animate_align.nil?
|
|
21
|
+
props[:animate_offset] = animate_offset unless animate_offset.nil?
|
|
22
|
+
props[:animate_opacity] = animate_opacity unless animate_opacity.nil?
|
|
23
|
+
props[:animate_position] = animate_position unless animate_position.nil?
|
|
24
|
+
props[:animate_rotation] = animate_rotation unless animate_rotation.nil?
|
|
25
|
+
props[:animate_scale] = animate_scale unless animate_scale.nil?
|
|
26
|
+
props[:animate_size] = animate_size unless animate_size.nil?
|
|
27
|
+
props[:aspect_ratio] = aspect_ratio unless aspect_ratio.nil?
|
|
28
|
+
props[:badge] = badge unless badge.nil?
|
|
29
|
+
props[:border_width] = border_width unless border_width.nil?
|
|
30
|
+
props[:bottom] = bottom unless bottom.nil?
|
|
31
|
+
props[:col] = col unless col.nil?
|
|
32
|
+
props[:color] = color unless color.nil?
|
|
33
|
+
props[:data] = data unless data.nil?
|
|
34
|
+
props[:disabled] = disabled unless disabled.nil?
|
|
35
|
+
props[:duration] = duration unless duration.nil?
|
|
36
|
+
props[:expand] = expand unless expand.nil?
|
|
37
|
+
props[:expand_loose] = expand_loose unless expand_loose.nil?
|
|
38
|
+
props[:height] = height unless height.nil?
|
|
39
|
+
props[:item_count] = item_count unless item_count.nil?
|
|
40
|
+
props[:key] = key unless key.nil?
|
|
41
|
+
props[:left] = left unless left.nil?
|
|
42
|
+
props[:line_width] = line_width unless line_width.nil?
|
|
43
|
+
props[:offset] = offset unless offset.nil?
|
|
44
|
+
props[:opacity] = opacity unless opacity.nil?
|
|
45
|
+
props[:right] = right unless right.nil?
|
|
46
|
+
props[:rotate] = rotate unless rotate.nil?
|
|
47
|
+
props[:rtl] = rtl unless rtl.nil?
|
|
48
|
+
props[:scale] = scale unless scale.nil?
|
|
49
|
+
props[:size] = size unless size.nil?
|
|
50
|
+
props[:tooltip] = tooltip unless tooltip.nil?
|
|
51
|
+
props[:top] = top unless top.nil?
|
|
52
|
+
props[:visible] = visible unless visible.nil?
|
|
53
|
+
props[:wave_type] = wave_type unless wave_type.nil?
|
|
54
|
+
props[:width] = width unless width.nil?
|
|
55
|
+
props[:on_animation_end] = on_animation_end unless on_animation_end.nil?
|
|
56
|
+
super(type: self.class::TYPE, id: id, **props)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# wire name ("_c") => ruflet type key. Mirrors flet_spinkit's 30 controls.
|
|
61
|
+
SPINKIT_WIRE_TO_TYPE = {
|
|
62
|
+
"SpinKitRotatingCircle" => "spinkit_rotating_circle",
|
|
63
|
+
"SpinKitRotatingPlain" => "spinkit_rotating_plain",
|
|
64
|
+
"SpinKitDoubleBounce" => "spinkit_double_bounce",
|
|
65
|
+
"SpinKitWave" => "spinkit_wave",
|
|
66
|
+
"SpinKitWanderingCubes" => "spinkit_wandering_cubes",
|
|
67
|
+
"SpinKitFadingFour" => "spinkit_fading_four",
|
|
68
|
+
"SpinKitFadingCube" => "spinkit_fading_cube",
|
|
69
|
+
"SpinKitPulse" => "spinkit_pulse",
|
|
70
|
+
"SpinKitChasingDots" => "spinkit_chasing_dots",
|
|
71
|
+
"SpinKitThreeBounce" => "spinkit_three_bounce",
|
|
72
|
+
"SpinKitCircle" => "spinkit_circle",
|
|
73
|
+
"SpinKitCubeGrid" => "spinkit_cube_grid",
|
|
74
|
+
"SpinKitFadingCircle" => "spinkit_fading_circle",
|
|
75
|
+
"SpinKitFoldingCube" => "spinkit_folding_cube",
|
|
76
|
+
"SpinKitPumpingHeart" => "spinkit_pumping_heart",
|
|
77
|
+
"SpinKitHourGlass" => "spinkit_hour_glass",
|
|
78
|
+
"SpinKitPouringHourGlass" => "spinkit_pouring_hour_glass",
|
|
79
|
+
"SpinKitPouringHourGlassRefined" => "spinkit_pouring_hour_glass_refined",
|
|
80
|
+
"SpinKitFadingGrid" => "spinkit_fading_grid",
|
|
81
|
+
"SpinKitRing" => "spinkit_ring",
|
|
82
|
+
"SpinKitRipple" => "spinkit_ripple",
|
|
83
|
+
"SpinKitDualRing" => "spinkit_dual_ring",
|
|
84
|
+
"SpinKitSpinningCircle" => "spinkit_spinning_circle",
|
|
85
|
+
"SpinKitSpinningLines" => "spinkit_spinning_lines",
|
|
86
|
+
"SpinKitSquareCircle" => "spinkit_square_circle",
|
|
87
|
+
"SpinKitThreeInOut" => "spinkit_three_in_out",
|
|
88
|
+
"SpinKitDancingSquare" => "spinkit_dancing_square",
|
|
89
|
+
"SpinKitPianoWave" => "spinkit_piano_wave",
|
|
90
|
+
"SpinKitPulsingGrid" => "spinkit_pulsing_grid",
|
|
91
|
+
"SpinKitWaveSpinner" => "spinkit_wave_spinner"
|
|
92
|
+
}.freeze
|
|
93
|
+
|
|
94
|
+
# type key => control class, e.g. "spinkit_wave" => SpinKitWaveControl.
|
|
95
|
+
SPINKIT_CONTROLS = {}
|
|
96
|
+
|
|
97
|
+
SPINKIT_WIRE_TO_TYPE.each do |wire, type_key|
|
|
98
|
+
klass = Class.new(SpinKitControl)
|
|
99
|
+
klass.const_set(:TYPE, type_key)
|
|
100
|
+
klass.const_set(:WIRE, wire)
|
|
101
|
+
const_set("#{wire}Control", klass)
|
|
102
|
+
SPINKIT_CONTROLS[type_key] = klass
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
SPINKIT_CONTROLS.freeze
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -4,11 +4,33 @@ module Ruflet
|
|
|
4
4
|
module UI
|
|
5
5
|
module Controls
|
|
6
6
|
module RufletComponents
|
|
7
|
+
# WebView control — parity with Flet's WebView
|
|
8
|
+
# (https://flet.dev/docs/controls/webview/).
|
|
9
|
+
#
|
|
10
|
+
# Properties: url, bgcolor, prevent_links, plus the usual layout props.
|
|
11
|
+
# Events: on_page_started, on_page_ended, on_web_resource_error,
|
|
12
|
+
# on_progress, on_url_change, on_scroll, on_console_message,
|
|
13
|
+
# on_javascript_alert_dialog.
|
|
14
|
+
# Methods (invoked over the wire on a mounted control): reload, go_back,
|
|
15
|
+
# go_forward, can_go_back, can_go_forward, run_javascript, load_html,
|
|
16
|
+
# load_request, load_file, scroll_to, scroll_by, clear_cache,
|
|
17
|
+
# clear_local_storage, enable_zoom, disable_zoom, set_javascript_mode,
|
|
18
|
+
# get_current_url, get_title, get_user_agent.
|
|
19
|
+
#
|
|
20
|
+
# Platform note: the native webview (and therefore run_javascript and the
|
|
21
|
+
# events/methods) runs on iOS, Android and macOS. On web it falls back to
|
|
22
|
+
# an <iframe>, which cannot run the methods and which most external sites
|
|
23
|
+
# block via X-Frame-Options/CSP — embed your own same-origin pages there.
|
|
7
24
|
class WebViewControl < Ruflet::Control
|
|
8
25
|
TYPE = "WebView".freeze
|
|
9
26
|
WIRE = "WebView".freeze
|
|
10
27
|
|
|
11
|
-
def initialize(id: nil, bgcolor: nil, data: nil, enable_javascript: nil, expand: nil,
|
|
28
|
+
def initialize(id: nil, bgcolor: nil, data: nil, enable_javascript: nil, expand: nil,
|
|
29
|
+
height: nil, key: nil, method: nil, opacity: nil, prevent_links: nil,
|
|
30
|
+
rtl: nil, tooltip: nil, url: nil, visible: nil, width: nil,
|
|
31
|
+
on_page_ended: nil, on_page_started: nil, on_web_resource_error: nil,
|
|
32
|
+
on_progress: nil, on_url_change: nil, on_scroll: nil,
|
|
33
|
+
on_console_message: nil, on_javascript_alert_dialog: nil)
|
|
12
34
|
props = {}
|
|
13
35
|
props[:bgcolor] = bgcolor unless bgcolor.nil?
|
|
14
36
|
props[:data] = data unless data.nil?
|
|
@@ -18,6 +40,7 @@ module Ruflet
|
|
|
18
40
|
props[:key] = key unless key.nil?
|
|
19
41
|
props[:method] = method unless method.nil?
|
|
20
42
|
props[:opacity] = opacity unless opacity.nil?
|
|
43
|
+
props[:prevent_links] = prevent_links unless prevent_links.nil?
|
|
21
44
|
props[:rtl] = rtl unless rtl.nil?
|
|
22
45
|
props[:tooltip] = tooltip unless tooltip.nil?
|
|
23
46
|
props[:url] = url unless url.nil?
|
|
@@ -26,8 +49,98 @@ module Ruflet
|
|
|
26
49
|
props[:on_page_ended] = on_page_ended unless on_page_ended.nil?
|
|
27
50
|
props[:on_page_started] = on_page_started unless on_page_started.nil?
|
|
28
51
|
props[:on_web_resource_error] = on_web_resource_error unless on_web_resource_error.nil?
|
|
52
|
+
props[:on_progress] = on_progress unless on_progress.nil?
|
|
53
|
+
props[:on_url_change] = on_url_change unless on_url_change.nil?
|
|
54
|
+
props[:on_scroll] = on_scroll unless on_scroll.nil?
|
|
55
|
+
props[:on_console_message] = on_console_message unless on_console_message.nil?
|
|
56
|
+
props[:on_javascript_alert_dialog] = on_javascript_alert_dialog unless on_javascript_alert_dialog.nil?
|
|
29
57
|
super(type: TYPE, id: id, **props)
|
|
30
58
|
end
|
|
59
|
+
|
|
60
|
+
# --- Navigation --------------------------------------------------
|
|
61
|
+
|
|
62
|
+
def reload = invoke_webview_method("reload")
|
|
63
|
+
def go_back = invoke_webview_method("go_back")
|
|
64
|
+
def go_forward = invoke_webview_method("go_forward")
|
|
65
|
+
|
|
66
|
+
def can_go_back(timeout: 10, &on_result)
|
|
67
|
+
invoke_webview_method("can_go_back", timeout: timeout, on_result: on_result)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def can_go_forward(timeout: 10, &on_result)
|
|
71
|
+
invoke_webview_method("can_go_forward", timeout: timeout, on_result: on_result)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# --- Loading content ---------------------------------------------
|
|
75
|
+
|
|
76
|
+
def load_request(url, method: "get")
|
|
77
|
+
invoke_webview_method("load_request", { "url" => url.to_s, "method" => method.to_s })
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def load_html(value, base_url: nil)
|
|
81
|
+
args = { "value" => value.to_s }
|
|
82
|
+
args["base_url"] = base_url.to_s unless base_url.nil?
|
|
83
|
+
invoke_webview_method("load_html", args)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def load_file(path)
|
|
87
|
+
invoke_webview_method("load_file", { "path" => path.to_s })
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# --- JavaScript injection ---------------------------------------
|
|
91
|
+
|
|
92
|
+
# Run arbitrary JS inside the page — e.g. hide a node so a native
|
|
93
|
+
# control can take its place:
|
|
94
|
+
# webview.run_javascript("document.getElementById('banner').remove()")
|
|
95
|
+
def run_javascript(value)
|
|
96
|
+
invoke_webview_method("run_javascript", { "value" => value.to_s })
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def set_javascript_mode(mode)
|
|
100
|
+
invoke_webview_method("set_javascript_mode", { "mode" => mode.to_s })
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# --- Scrolling ---------------------------------------------------
|
|
104
|
+
|
|
105
|
+
def scroll_to(x, y)
|
|
106
|
+
invoke_webview_method("scroll_to", { "x" => x.to_i, "y" => y.to_i })
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def scroll_by(x, y)
|
|
110
|
+
invoke_webview_method("scroll_by", { "x" => x.to_i, "y" => y.to_i })
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# --- Storage / zoom ----------------------------------------------
|
|
114
|
+
|
|
115
|
+
def clear_cache = invoke_webview_method("clear_cache")
|
|
116
|
+
def clear_local_storage = invoke_webview_method("clear_local_storage")
|
|
117
|
+
def enable_zoom = invoke_webview_method("enable_zoom")
|
|
118
|
+
def disable_zoom = invoke_webview_method("disable_zoom")
|
|
119
|
+
|
|
120
|
+
# --- Introspection (result delivered to the block) ---------------
|
|
121
|
+
|
|
122
|
+
def get_current_url(timeout: 10, &on_result)
|
|
123
|
+
invoke_webview_method("get_current_url", timeout: timeout, on_result: on_result)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def get_title(timeout: 10, &on_result)
|
|
127
|
+
invoke_webview_method("get_title", timeout: timeout, on_result: on_result)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def get_user_agent(timeout: 10, &on_result)
|
|
131
|
+
invoke_webview_method("get_user_agent", timeout: timeout, on_result: on_result)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def invoke_webview_method(name, args = nil, timeout: 10, on_result: nil)
|
|
137
|
+
page = runtime_page
|
|
138
|
+
unless page && wire_id
|
|
139
|
+
raise "WebView ##{id} is not mounted yet — add it to the page before calling #{name}."
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
page.invoke(self, name, args: args, timeout: timeout, on_result: on_result)
|
|
143
|
+
end
|
|
31
144
|
end
|
|
32
145
|
end
|
|
33
146
|
end
|
|
@@ -39,6 +39,7 @@ require_relative "materials/card_control"
|
|
|
39
39
|
require_relative "materials/checkbox_control"
|
|
40
40
|
require_relative "materials/chip_control"
|
|
41
41
|
require_relative "materials/circleavatar_control"
|
|
42
|
+
require_relative "materials/codeeditor_control"
|
|
42
43
|
require_relative "materials/container_control"
|
|
43
44
|
require_relative "materials/contextmenu_control"
|
|
44
45
|
require_relative "materials/datacell_control"
|
|
@@ -58,6 +59,7 @@ require_relative "materials/filledbutton_control"
|
|
|
58
59
|
require_relative "materials/fillediconbutton_control"
|
|
59
60
|
require_relative "materials/filledtonalbutton_control"
|
|
60
61
|
require_relative "materials/filledtonaliconbutton_control"
|
|
62
|
+
require_relative "materials/spinkit_controls"
|
|
61
63
|
require_relative "materials/floatingactionbutton_control"
|
|
62
64
|
require_relative "materials/iconbutton_control"
|
|
63
65
|
require_relative "materials/listtile_control"
|
|
@@ -81,6 +83,7 @@ require_relative "materials/radio_control"
|
|
|
81
83
|
require_relative "materials/radiogroup_control"
|
|
82
84
|
require_relative "materials/rangeslider_control"
|
|
83
85
|
require_relative "materials/reorderablelistview_control"
|
|
86
|
+
require_relative "materials/rive_control"
|
|
84
87
|
require_relative "materials/searchbar_control"
|
|
85
88
|
require_relative "materials/segment_control"
|
|
86
89
|
require_relative "materials/segmentedbutton_control"
|
|
@@ -206,6 +209,8 @@ module Ruflet
|
|
|
206
209
|
"circle" => RufletComponents::CircleControl,
|
|
207
210
|
"circle_avatar" => RufletComponents::CircleAvatarControl,
|
|
208
211
|
"circleavatar" => RufletComponents::CircleAvatarControl,
|
|
212
|
+
"code_editor" => RufletComponents::CodeEditorControl,
|
|
213
|
+
"codeeditor" => RufletComponents::CodeEditorControl,
|
|
209
214
|
"color" => RufletComponents::ColorControl,
|
|
210
215
|
"column" => RufletComponents::ColumnControl,
|
|
211
216
|
"container" => RufletComponents::ContainerControl,
|
|
@@ -408,6 +413,7 @@ module Ruflet
|
|
|
408
413
|
"reorderable_list_view" => RufletComponents::ReorderableListViewControl,
|
|
409
414
|
"reorderabledraghandle" => RufletComponents::ReorderableDragHandleControl,
|
|
410
415
|
"reorderablelistview" => RufletComponents::ReorderableListViewControl,
|
|
416
|
+
"rive" => RufletComponents::RiveControl,
|
|
411
417
|
"responsive_row" => RufletComponents::ResponsiveRowControl,
|
|
412
418
|
"responsiverow" => RufletComponents::ResponsiveRowControl,
|
|
413
419
|
"row" => RufletComponents::RowControl,
|
|
@@ -464,7 +470,7 @@ module Ruflet
|
|
|
464
470
|
"window" => RufletComponents::WindowControl,
|
|
465
471
|
"window_drag_area" => RufletComponents::WindowDragAreaControl,
|
|
466
472
|
"windowdragarea" => RufletComponents::WindowDragAreaControl,
|
|
467
|
-
}.freeze
|
|
473
|
+
}.merge(RufletComponents::SPINKIT_CONTROLS).freeze
|
|
468
474
|
end
|
|
469
475
|
end
|
|
470
476
|
end
|
|
@@ -50,10 +50,28 @@ module Ruflet
|
|
|
50
50
|
super(type: TYPE, id: id, **props)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
def wait_until_ready_to_show(timeout: 10, on_result: nil)
|
|
54
|
+
invoke_window_method("wait_until_ready_to_show", timeout: timeout, on_result: on_result)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def to_front(timeout: 10, on_result: nil)
|
|
58
|
+
invoke_window_method("to_front", timeout: timeout, on_result: on_result)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def center(timeout: 10, on_result: nil)
|
|
62
|
+
invoke_window_method("center", timeout: timeout, on_result: on_result)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def close(timeout: 10, on_result: nil)
|
|
66
|
+
invoke_window_method("close", timeout: timeout, on_result: on_result)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def destroy(timeout: 10, on_result: nil)
|
|
70
|
+
invoke_window_method("destroy", timeout: timeout, on_result: on_result)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def start_dragging(timeout: 10, on_result: nil)
|
|
74
|
+
invoke_window_method("start_dragging", timeout: timeout, on_result: on_result)
|
|
57
75
|
end
|
|
58
76
|
|
|
59
77
|
def start_resizing(edge, timeout: 10, on_result: nil)
|
|
@@ -65,6 +83,12 @@ module Ruflet
|
|
|
65
83
|
on_result: on_result
|
|
66
84
|
)
|
|
67
85
|
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def invoke_window_method(method_name, timeout:, on_result:)
|
|
90
|
+
runtime_page&.invoke(self, method_name, timeout: timeout, on_result: on_result)
|
|
91
|
+
end
|
|
68
92
|
end
|
|
69
93
|
end
|
|
70
94
|
end
|
|
@@ -81,6 +81,39 @@ module Ruflet
|
|
|
81
81
|
build_widget(:contextmenu, **mapped)
|
|
82
82
|
end
|
|
83
83
|
def contextmenu(content = nil, **props) = context_menu(content, **props)
|
|
84
|
+
def autofill_group(content = nil, **props)
|
|
85
|
+
mapped = props.dup
|
|
86
|
+
mapped[:content] = content unless content.nil?
|
|
87
|
+
build_widget(:autofillgroup, **mapped)
|
|
88
|
+
end
|
|
89
|
+
def autofillgroup(content = nil, **props) = autofill_group(content, **props)
|
|
90
|
+
def hero(content = nil, **props)
|
|
91
|
+
mapped = props.dup
|
|
92
|
+
mapped[:content] = content unless content.nil?
|
|
93
|
+
build_widget(:hero, **mapped)
|
|
94
|
+
end
|
|
95
|
+
def overlay(children = nil, **props)
|
|
96
|
+
mapped = props.dup
|
|
97
|
+
mapped[:controls] = children unless children.nil?
|
|
98
|
+
build_widget(:overlay, **mapped)
|
|
99
|
+
end
|
|
100
|
+
def shader_mask(content = nil, **props)
|
|
101
|
+
mapped = props.dup
|
|
102
|
+
mapped[:content] = content unless content.nil?
|
|
103
|
+
build_widget(:shadermask, **mapped)
|
|
104
|
+
end
|
|
105
|
+
def shadermask(content = nil, **props) = shader_mask(content, **props)
|
|
106
|
+
def shimmer(content = nil, **props)
|
|
107
|
+
mapped = props.dup
|
|
108
|
+
mapped[:content] = content unless content.nil?
|
|
109
|
+
build_widget(:shimmer, **mapped)
|
|
110
|
+
end
|
|
111
|
+
def text_span(text = nil, **props)
|
|
112
|
+
mapped = props.dup
|
|
113
|
+
mapped[:text] = text unless text.nil?
|
|
114
|
+
build_widget(:textspan, **mapped)
|
|
115
|
+
end
|
|
116
|
+
def textspan(text = nil, **props) = text_span(text, **props)
|
|
84
117
|
def keyboard_listener(content = nil, **props)
|
|
85
118
|
mapped = props.dup
|
|
86
119
|
mapped[:content] = content unless content.nil?
|
|
@@ -606,6 +639,31 @@ module Ruflet
|
|
|
606
639
|
def webview(**props) = web_view(**props)
|
|
607
640
|
def video(**props) = build_widget(:video, **props)
|
|
608
641
|
|
|
642
|
+
# spinkit(wave: { color: "red", size: 50 }) — one variant keyword whose
|
|
643
|
+
# value is the props hash. See https://flet.dev/docs/controls/spinkit/
|
|
644
|
+
def spinkit(**variant)
|
|
645
|
+
raise ArgumentError, "spinkit expects exactly one variant, e.g. spinkit(wave: { color: ... })" unless variant.size == 1
|
|
646
|
+
|
|
647
|
+
name, props = variant.first
|
|
648
|
+
props ||= {}
|
|
649
|
+
raise ArgumentError, "spinkit #{name} options must be a Hash" unless props.is_a?(Hash)
|
|
650
|
+
|
|
651
|
+
build_widget(:"spinkit_#{name}", **props)
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
def code_editor(value = nil, **props)
|
|
655
|
+
mapped = props.dup
|
|
656
|
+
mapped[:value] = value unless value.nil?
|
|
657
|
+
build_widget(:codeeditor, **mapped)
|
|
658
|
+
end
|
|
659
|
+
def codeeditor(value = nil, **props) = code_editor(value, **props)
|
|
660
|
+
|
|
661
|
+
def rive(src = nil, **props)
|
|
662
|
+
mapped = props.dup
|
|
663
|
+
mapped[:src] = src unless src.nil?
|
|
664
|
+
build_widget(:rive, **mapped)
|
|
665
|
+
end
|
|
666
|
+
|
|
609
667
|
def fab(content = nil, **props)
|
|
610
668
|
mapped = normalize_fab_props(props.dup, content)
|
|
611
669
|
build_widget(:floatingactionbutton, **mapped)
|
|
@@ -8,7 +8,8 @@ module Ruflet
|
|
|
8
8
|
TYPE = "audiorecorder".freeze
|
|
9
9
|
WIRE = "AudioRecorder".freeze
|
|
10
10
|
|
|
11
|
-
def initialize(id: nil, configuration:
|
|
11
|
+
def initialize(id: nil, configuration: nil, data: nil, key: nil, on_state_change: nil, on_stream: nil, on_upload: nil)
|
|
12
|
+
configuration ||= {}
|
|
12
13
|
props = {}
|
|
13
14
|
props[:configuration] = configuration unless configuration.nil?
|
|
14
15
|
props[:data] = data unless data.nil?
|
|
@@ -43,7 +44,8 @@ module Ruflet
|
|
|
43
44
|
)
|
|
44
45
|
end
|
|
45
46
|
|
|
46
|
-
def start_recording(output_path: nil, configuration:
|
|
47
|
+
def start_recording(output_path: nil, configuration: nil, upload: nil, timeout: 10, on_result: nil)
|
|
48
|
+
configuration ||= {}
|
|
47
49
|
invoke_audio_recorder(
|
|
48
50
|
"start_recording",
|
|
49
51
|
args: compact_args(
|
|
@@ -26,6 +26,15 @@ module Ruflet
|
|
|
26
26
|
def autocompletesuggestion(key = nil, **props) = control_delegate.autocompletesuggestion(key, **props)
|
|
27
27
|
def context_menu(content = nil, **props) = control_delegate.context_menu(content, **props)
|
|
28
28
|
def contextmenu(content = nil, **props) = control_delegate.contextmenu(content, **props)
|
|
29
|
+
def autofill_group(content = nil, **props) = control_delegate.autofill_group(content, **props)
|
|
30
|
+
def autofillgroup(content = nil, **props) = control_delegate.autofillgroup(content, **props)
|
|
31
|
+
def hero(content = nil, **props) = control_delegate.hero(content, **props)
|
|
32
|
+
def overlay(children = nil, **props) = control_delegate.overlay(children, **props)
|
|
33
|
+
def shader_mask(content = nil, **props) = control_delegate.shader_mask(content, **props)
|
|
34
|
+
def shadermask(content = nil, **props) = control_delegate.shadermask(content, **props)
|
|
35
|
+
def shimmer(content = nil, **props) = control_delegate.shimmer(content, **props)
|
|
36
|
+
def text_span(text = nil, **props) = control_delegate.text_span(text, **props)
|
|
37
|
+
def textspan(text = nil, **props) = control_delegate.textspan(text, **props)
|
|
29
38
|
def keyboard_listener(content = nil, **props) = control_delegate.keyboard_listener(content, **props)
|
|
30
39
|
def keyboardlistener(content = nil, **props) = control_delegate.keyboardlistener(content, **props)
|
|
31
40
|
def gesture_detector(**props, &block) = control_delegate.gesture_detector(**props, &block)
|
|
@@ -261,6 +270,10 @@ module Ruflet
|
|
|
261
270
|
def web_view(**props) = control_delegate.web_view(**props)
|
|
262
271
|
def webview(**props) = control_delegate.webview(**props)
|
|
263
272
|
def video(**props) = control_delegate.video(**props)
|
|
273
|
+
def spinkit(**variant) = control_delegate.spinkit(**variant)
|
|
274
|
+
def code_editor(value = nil, **props) = control_delegate.code_editor(value, **props)
|
|
275
|
+
def codeeditor(value = nil, **props) = control_delegate.codeeditor(value, **props)
|
|
276
|
+
def rive(src = nil, **props) = control_delegate.rive(src, **props)
|
|
264
277
|
def cupertino_button(content = nil, **props) = control_delegate.cupertino_button(content, **props)
|
|
265
278
|
def cupertinobutton(content = nil, **props) = control_delegate.cupertinobutton(content, **props)
|
|
266
279
|
def cupertino_filled_button(content = nil, **props) = control_delegate.cupertino_filled_button(content, **props)
|
data/lib/ruflet_ui.rb
CHANGED
|
@@ -140,7 +140,7 @@ module Ruflet
|
|
|
140
140
|
private
|
|
141
141
|
|
|
142
142
|
def control_delegate
|
|
143
|
-
Ruflet::
|
|
143
|
+
Ruflet::WidgetBuilder.new
|
|
144
144
|
end
|
|
145
145
|
end
|
|
146
146
|
end
|
|
@@ -154,8 +154,12 @@ module Kernel
|
|
|
154
154
|
def app(**opts, &block) = Ruflet::DSL.app(**opts, &block)
|
|
155
155
|
def page(**props, &block) = Ruflet::DSL.page(**props, &block)
|
|
156
156
|
|
|
157
|
+
# Bare widget helpers (view, text, container, …) build real controls anywhere
|
|
158
|
+
# — top-level scripts and Ruflet.run blocks — with no builder to instantiate.
|
|
159
|
+
# The framework owns this plumbing so dev code stays free of it; a fresh
|
|
160
|
+
# builder per call keeps the helpers stateless.
|
|
157
161
|
def control_delegate
|
|
158
|
-
Ruflet::
|
|
162
|
+
Ruflet::WidgetBuilder.new
|
|
159
163
|
end
|
|
160
164
|
|
|
161
165
|
if Ruflet::UI::SharedControlForwarders.respond_to?(:instance_methods)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruflet_core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.16
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- AdamMusa
|
|
@@ -83,6 +83,7 @@ files:
|
|
|
83
83
|
- lib/ruflet_ui/ruflet/ui/controls/materials/checkbox_control.rb
|
|
84
84
|
- lib/ruflet_ui/ruflet/ui/controls/materials/chip_control.rb
|
|
85
85
|
- lib/ruflet_ui/ruflet/ui/controls/materials/circleavatar_control.rb
|
|
86
|
+
- lib/ruflet_ui/ruflet/ui/controls/materials/codeeditor_control.rb
|
|
86
87
|
- lib/ruflet_ui/ruflet/ui/controls/materials/container_control.rb
|
|
87
88
|
- lib/ruflet_ui/ruflet/ui/controls/materials/contextmenu_control.rb
|
|
88
89
|
- lib/ruflet_ui/ruflet/ui/controls/materials/datacell_control.rb
|
|
@@ -125,6 +126,7 @@ files:
|
|
|
125
126
|
- lib/ruflet_ui/ruflet/ui/controls/materials/radiogroup_control.rb
|
|
126
127
|
- lib/ruflet_ui/ruflet/ui/controls/materials/rangeslider_control.rb
|
|
127
128
|
- lib/ruflet_ui/ruflet/ui/controls/materials/reorderablelistview_control.rb
|
|
129
|
+
- lib/ruflet_ui/ruflet/ui/controls/materials/rive_control.rb
|
|
128
130
|
- lib/ruflet_ui/ruflet/ui/controls/materials/ruflet_controls.rb
|
|
129
131
|
- lib/ruflet_ui/ruflet/ui/controls/materials/searchbar_control.rb
|
|
130
132
|
- lib/ruflet_ui/ruflet/ui/controls/materials/segment_control.rb
|
|
@@ -132,6 +134,7 @@ files:
|
|
|
132
134
|
- lib/ruflet_ui/ruflet/ui/controls/materials/selectionarea_control.rb
|
|
133
135
|
- lib/ruflet_ui/ruflet/ui/controls/materials/slider_control.rb
|
|
134
136
|
- lib/ruflet_ui/ruflet/ui/controls/materials/snackbar_control.rb
|
|
137
|
+
- lib/ruflet_ui/ruflet/ui/controls/materials/spinkit_controls.rb
|
|
135
138
|
- lib/ruflet_ui/ruflet/ui/controls/materials/submenubutton_control.rb
|
|
136
139
|
- lib/ruflet_ui/ruflet/ui/controls/materials/switch_control.rb
|
|
137
140
|
- lib/ruflet_ui/ruflet/ui/controls/materials/tab_control.rb
|
|
@@ -233,7 +236,8 @@ files:
|
|
|
233
236
|
- lib/ruflet_ui/ruflet/ui/shared_control_forwarders.rb
|
|
234
237
|
- lib/ruflet_ui/ruflet/ui/widget_builder.rb
|
|
235
238
|
homepage: https://github.com/AdamMusa/Ruflet
|
|
236
|
-
licenses:
|
|
239
|
+
licenses:
|
|
240
|
+
- MIT
|
|
237
241
|
metadata: {}
|
|
238
242
|
rdoc_options: []
|
|
239
243
|
require_paths:
|