glimmer-dsl-web 0.0.6 → 0.0.7

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.
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2023 Andy Maleh
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -19,12 +19,8 @@
19
19
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
- # require 'glimmer/web/event_listener_proxy'
23
- require 'glimmer/web/property_owner'
24
22
  require 'glimmer/web/listener_proxy'
25
23
 
26
- # TODO implement menu (which delays building it till render using add_content_on_render)
27
-
28
24
  module Glimmer
29
25
  module Web
30
26
  class ElementProxy
@@ -68,11 +64,16 @@ module Glimmer
68
64
  end
69
65
 
70
66
  include Glimmer
71
- include PropertyOwner
72
67
 
73
68
  Event = Struct.new(:widget, keyword_init: true)
74
69
 
75
70
  GLIMMER_ATTRIBUTES = [:parent]
71
+ FORMAT_DATETIME = '%Y-%m-%dT%H:%M'
72
+ FORMAT_DATE = '%Y-%m-%d'
73
+ FORMAT_TIME = '%H:%M'
74
+ REGEX_FORMAT_DATETIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/
75
+ REGEX_FORMAT_DATE = /^\d{4}-\d{2}-\d{2}$/
76
+ REGEX_FORMAT_TIME = /^\d{2}:\d{2}$/
76
77
 
77
78
  attr_reader :keyword, :parent, :args, :options, :children, :enabled, :foreground, :background, :removed?, :rendered
78
79
  alias rendered? rendered
@@ -286,360 +287,6 @@ module Glimmer
286
287
  {}
287
288
  end
288
289
 
289
- def effective_observation_request_to_event_mapping
290
- default_observation_request_to_event_mapping.merge(observation_request_to_event_mapping)
291
- end
292
-
293
- def default_observation_request_to_event_mapping
294
- myself = self
295
- mouse_event_handler = -> (event_listener) {
296
- -> (event) {
297
- # TODO generalize this solution to all widgets that support key presses
298
- event.define_singleton_method(:widget) {myself}
299
- event.define_singleton_method(:button, &event.method(:which))
300
- event.define_singleton_method(:count) {1} # TODO support double-click count of 2 in the future by using ondblclick
301
- event.define_singleton_method(:x, &event.method(:page_x))
302
- event.define_singleton_method(:y, &event.method(:page_y))
303
- doit = true
304
- event.define_singleton_method(:doit=) do |value|
305
- doit = value
306
- end
307
- event.define_singleton_method(:doit) { doit }
308
-
309
- if event.which == 1
310
- # event.prevent # TODO consider if this is needed
311
- event_listener.call(event)
312
- end
313
-
314
- # TODO Imlement doit properly for all different kinds of events
315
- # unless doit
316
- # event.prevent
317
- # event.stop
318
- # event.stop_immediate
319
- # end
320
- }
321
- }
322
- mouse_move_event_handler = -> (event_listener) {
323
- -> (event) {
324
- # TODO generalize this solution to all widgets that support key presses
325
- event.define_singleton_method(:widget) {myself}
326
- event.define_singleton_method(:button, &event.method(:which))
327
- event.define_singleton_method(:count) {1} # TODO support double-click count of 2 in the future by using ondblclick
328
- event.define_singleton_method(:x, &event.method(:page_x))
329
- event.define_singleton_method(:y, &event.method(:page_y))
330
- doit = true
331
- event.define_singleton_method(:doit=) do |value|
332
- doit = value
333
- end
334
- event.define_singleton_method(:doit) { doit }
335
-
336
- event_listener.call(event)
337
-
338
- # TODO Imlement doit properly for all different kinds of events
339
- # unless doit
340
- # event.prevent
341
- # event.stop
342
- # event.stop_immediate
343
- # end
344
- }
345
- }
346
- context_menu_handler = -> (event_listener) {
347
- -> (event) {
348
- # TODO generalize this solution to all widgets that support key presses
349
- event.define_singleton_method(:widget) {myself}
350
- event.define_singleton_method(:button, &event.method(:which))
351
- event.define_singleton_method(:count) {1} # TODO support double-click count of 2 in the future by using ondblclick
352
- event.define_singleton_method(:x, &event.method(:page_x))
353
- event.define_singleton_method(:y, &event.method(:page_y))
354
- doit = true
355
- event.define_singleton_method(:doit=) do |value|
356
- doit = value
357
- end
358
- event.define_singleton_method(:doit) { doit }
359
-
360
- if event.which == 3
361
- event.prevent
362
- event_listener.call(event)
363
- end
364
- # TODO Imlement doit properly for all different kinds of events
365
- # unless doit
366
- # event.prevent
367
- # event.stop
368
- # event.stop_immediate
369
- # end
370
- }
371
- }
372
- {
373
- 'on_focus_gained' => {
374
- event: 'focus',
375
- },
376
- 'on_focus_lost' => {
377
- event: 'blur',
378
- },
379
- 'on_mouse_move' => [
380
- {
381
- event: 'mousemove',
382
- event_handler: mouse_move_event_handler,
383
- },
384
- ],
385
- 'on_mouse_up' => [
386
- {
387
- event: 'mouseup',
388
- event_handler: mouse_event_handler,
389
- },
390
- {
391
- event: 'contextmenu',
392
- event_handler: context_menu_handler,
393
- },
394
- ],
395
- 'on_mouse_down' => [
396
- {
397
- event: 'mousedown',
398
- event_handler: mouse_event_handler,
399
- },
400
- {
401
- event: 'contextmenu',
402
- event_handler: context_menu_handler,
403
- },
404
- ],
405
- 'on_swt_mouseup' => [
406
- {
407
- event: 'mouseup',
408
- event_handler: mouse_event_handler,
409
- },
410
- {
411
- event: 'contextmenu',
412
- event_handler: context_menu_handler,
413
- },
414
- ],
415
- 'on_swt_mousedown' => [
416
- {
417
- event: 'mousedown',
418
- event_handler: mouse_event_handler,
419
- },
420
- {
421
- event: 'contextmenu',
422
- event_handler: context_menu_handler,
423
- },
424
- ],
425
- 'on_key_pressed' => {
426
- event: 'keypress',
427
- event_handler: -> (event_listener) {
428
- -> (event) {
429
- event.define_singleton_method(:widget) {myself}
430
- event.define_singleton_method(:keyLocation) do
431
- location = `#{event.to_n}.originalEvent.location`
432
- JS_LOCATION_TO_SWT_KEY_LOCATION_MAP[location] || location
433
- end
434
- event.define_singleton_method(:key_location, &event.method(:keyLocation))
435
- event.define_singleton_method(:keyCode) {
436
- JS_KEY_CODE_TO_SWT_KEY_CODE_MAP[event.which] || event.which
437
- }
438
- event.define_singleton_method(:key_code, &event.method(:keyCode))
439
- event.define_singleton_method(:character) {event.which.chr}
440
- event.define_singleton_method(:stateMask) do
441
- state_mask = 0
442
- state_mask |= SWTProxy[:alt] if event.alt_key
443
- state_mask |= SWTProxy[:ctrl] if event.ctrl_key
444
- state_mask |= SWTProxy[:shift] if event.shift_key
445
- state_mask |= SWTProxy[:command] if event.meta_key
446
- state_mask
447
- end
448
- event.define_singleton_method(:state_mask, &event.method(:stateMask))
449
- doit = true
450
- event.define_singleton_method(:doit=) do |value|
451
- doit = value
452
- end
453
- event.define_singleton_method(:doit) { doit }
454
- event_listener.call(event)
455
-
456
- # TODO Fix doit false, it's not stopping input
457
- unless doit
458
- event.prevent
459
- event.prevent_default
460
- event.stop_propagation
461
- event.stop_immediate_propagation
462
- end
463
-
464
- doit
465
- }
466
- } },
467
- 'on_key_released' => {
468
- event: 'keyup',
469
- event_handler: -> (event_listener) {
470
- -> (event) {
471
- event.define_singleton_method(:keyLocation) do
472
- location = `#{event.to_n}.originalEvent.location`
473
- JS_LOCATION_TO_SWT_KEY_LOCATION_MAP[location] || location
474
- end
475
- event.define_singleton_method(:key_location, &event.method(:keyLocation))
476
- event.define_singleton_method(:widget) {myself}
477
- event.define_singleton_method(:keyCode) {
478
- JS_KEY_CODE_TO_SWT_KEY_CODE_MAP[event.which] || event.which
479
- }
480
- event.define_singleton_method(:key_code, &event.method(:keyCode))
481
- event.define_singleton_method(:character) {event.which.chr}
482
- event.define_singleton_method(:stateMask) do
483
- state_mask = 0
484
- state_mask |= SWTProxy[:alt] if event.alt_key
485
- state_mask |= SWTProxy[:ctrl] if event.ctrl_key
486
- state_mask |= SWTProxy[:shift] if event.shift_key
487
- state_mask |= SWTProxy[:command] if event.meta_key
488
- state_mask
489
- end
490
- event.define_singleton_method(:state_mask, &event.method(:stateMask))
491
- doit = true
492
- event.define_singleton_method(:doit=) do |value|
493
- doit = value
494
- end
495
- event.define_singleton_method(:doit) { doit }
496
- event_listener.call(event)
497
-
498
- # TODO Fix doit false, it's not stopping input
499
- unless doit
500
- event.prevent
501
- event.prevent_default
502
- event.stop_propagation
503
- event.stop_immediate_propagation
504
- end
505
-
506
- doit
507
- }
508
- }
509
- },
510
- 'on_swt_keydown' => [
511
- {
512
- event: 'keypress',
513
- event_handler: -> (event_listener) {
514
- -> (event) {
515
- event.define_singleton_method(:keyLocation) do
516
- location = `#{event.to_n}.originalEvent.location`
517
- JS_LOCATION_TO_SWT_KEY_LOCATION_MAP[location] || location
518
- end
519
- event.define_singleton_method(:key_location, &event.method(:keyLocation))
520
- event.define_singleton_method(:keyCode) {
521
- JS_KEY_CODE_TO_SWT_KEY_CODE_MAP[event.which] || event.which
522
- }
523
- event.define_singleton_method(:key_code, &event.method(:keyCode))
524
- event.define_singleton_method(:widget) {myself}
525
- event.define_singleton_method(:character) {event.which.chr}
526
- event.define_singleton_method(:stateMask) do
527
- state_mask = 0
528
- state_mask |= SWTProxy[:alt] if event.alt_key
529
- state_mask |= SWTProxy[:ctrl] if event.ctrl_key
530
- state_mask |= SWTProxy[:shift] if event.shift_key
531
- state_mask |= SWTProxy[:command] if event.meta_key
532
- state_mask
533
- end
534
- event.define_singleton_method(:state_mask, &event.method(:stateMask))
535
- doit = true
536
- event.define_singleton_method(:doit=) do |value|
537
- doit = value
538
- end
539
- event.define_singleton_method(:doit) { doit }
540
- event_listener.call(event)
541
-
542
- # TODO Fix doit false, it's not stopping input
543
- unless doit
544
- event.prevent
545
- event.prevent_default
546
- event.stop_propagation
547
- event.stop_immediate_propagation
548
- end
549
-
550
- doit
551
- }
552
- }
553
- },
554
- {
555
- event: 'keydown',
556
- event_handler: -> (event_listener) {
557
- -> (event) {
558
- event.define_singleton_method(:keyLocation) do
559
- location = `#{event.to_n}.originalEvent.location`
560
- JS_LOCATION_TO_SWT_KEY_LOCATION_MAP[location] || location
561
- end
562
- event.define_singleton_method(:key_location, &event.method(:keyLocation))
563
- event.define_singleton_method(:keyCode) {
564
- JS_KEY_CODE_TO_SWT_KEY_CODE_MAP[event.which] || event.which
565
- }
566
- event.define_singleton_method(:key_code, &event.method(:keyCode))
567
- event.define_singleton_method(:widget) {myself}
568
- event.define_singleton_method(:character) {event.which.chr}
569
- event.define_singleton_method(:stateMask) do
570
- state_mask = 0
571
- state_mask |= SWTProxy[:alt] if event.alt_key
572
- state_mask |= SWTProxy[:ctrl] if event.ctrl_key
573
- state_mask |= SWTProxy[:shift] if event.shift_key
574
- state_mask |= SWTProxy[:command] if event.meta_key
575
- state_mask
576
- end
577
- event.define_singleton_method(:state_mask, &event.method(:stateMask))
578
- doit = true
579
- event.define_singleton_method(:doit=) do |value|
580
- doit = value
581
- end
582
- event.define_singleton_method(:doit) { doit }
583
- event_listener.call(event) if event.which != 13 && (event.which == 127 || event.which <= 40)
584
-
585
- # TODO Fix doit false, it's not stopping input
586
- unless doit
587
- event.prevent
588
- event.prevent_default
589
- event.stop_propagation
590
- event.stop_immediate_propagation
591
- end
592
- doit
593
- }
594
- }
595
- }
596
- ],
597
- 'on_swt_keyup' => {
598
- event: 'keyup',
599
- event_handler: -> (event_listener) {
600
- -> (event) {
601
- event.define_singleton_method(:keyLocation) do
602
- location = `#{event.to_n}.originalEvent.location`
603
- JS_LOCATION_TO_SWT_KEY_LOCATION_MAP[location] || location
604
- end
605
- event.define_singleton_method(:key_location, &event.method(:keyLocation))
606
- event.define_singleton_method(:widget) {myself}
607
- event.define_singleton_method(:keyCode) {
608
- JS_KEY_CODE_TO_SWT_KEY_CODE_MAP[event.which] || event.which
609
- }
610
- event.define_singleton_method(:key_code, &event.method(:keyCode))
611
- event.define_singleton_method(:character) {event.which.chr}
612
- event.define_singleton_method(:stateMask) do
613
- state_mask = 0
614
- state_mask |= SWTProxy[:alt] if event.alt_key
615
- state_mask |= SWTProxy[:ctrl] if event.ctrl_key
616
- state_mask |= SWTProxy[:shift] if event.shift_key
617
- state_mask |= SWTProxy[:command] if event.meta_key
618
- state_mask
619
- end
620
- event.define_singleton_method(:state_mask, &event.method(:stateMask))
621
- doit = true
622
- event.define_singleton_method(:doit=) do |value|
623
- doit = value
624
- end
625
- event.define_singleton_method(:doit) { doit }
626
- event_listener.call(event)
627
-
628
- # TODO Fix doit false, it's not stopping input
629
- unless doit
630
- event.prevent
631
- event.prevent_default
632
- event.stop_propagation
633
- event.stop_immediate_propagation
634
- end
635
-
636
- doit
637
- }
638
- }
639
- },
640
- }
641
- end
642
-
643
290
  def name
644
291
  self.class.name.split('::').last.underscore.sub(/_proxy$/, '').gsub('_', '-')
645
292
  end
@@ -767,35 +414,6 @@ module Glimmer
767
414
  listener.register
768
415
  listeners_for(keyword) << listener
769
416
  listener
770
- # return unless effective_observation_request_to_event_mapping.keys.include?(keyword)
771
- # event = nil
772
- # delegate = nil
773
- # effective_observation_request_to_event_mapping[keyword].to_collection.each do |mapping|
774
- # observation_requests[keyword] ||= Set.new
775
- # observation_requests[keyword] << original_event_listener
776
- # event = mapping[:event]
777
- # event_handler = mapping[:event_handler]
778
- # event_element_css_selector = mapping[:event_element_css_selector]
779
- # potential_event_listener = event_handler&.call(original_event_listener)
780
- # event_listener = potential_event_listener || original_event_listener
781
- # async_event_listener = proc do |event|
782
- ## TODO look into the issue with using async::task.new here. maybe put it in event listener (like not being able to call preventDefault or return false successfully )
783
- ## maybe consider pushing inside the widget classes instead where needed only or implement universal doit support correctly to bypass this issue
784
- ## Async::Task.new do
785
- # @@widget_handling_listener = self
786
- ## TODO also make sure to disable all widgets for suspension
787
- # event_listener.call(event) unless dialog_ancestor&.event_handling_suspended?
788
- # @widget_handling_listener = nil
789
- ## end
790
- # end
791
- # the_listener_dom_element = event_element_css_selector ? Element[event_element_css_selector] : listener_dom_element
792
- # unless the_listener_dom_element.empty?
793
- # the_listener_dom_element.on(event, &async_event_listener)
794
- ## TODO ensure uniqueness of insertion (perhaps adding equals/hash method to event listener proxy)
795
- #
796
- # event_listener_proxies << EventListenerProxy.new(element_proxy: self, selector: selector, dom_element: the_listener_dom_element, event: event, listener: async_event_listener, original_event_listener: original_event_listener)
797
- # end
798
- # end
799
417
  end
800
418
 
801
419
  def remove_event_listener_proxies
@@ -806,26 +424,26 @@ module Glimmer
806
424
  end
807
425
 
808
426
  def data_bind(property, model_binding)
809
- element_binding_parameters = [self, property]
427
+ element_binding_translator = value_converters_for_input_type(type)[:model_to_view]
428
+ element_binding_parameters = [self, property, element_binding_translator]
810
429
  element_binding = DataBinding::ElementBinding.new(*element_binding_parameters)
811
430
  element_binding.call(model_binding.evaluate_property)
812
431
  #TODO make this options observer dependent and all similar observers in element specific data binding handlers
813
432
  element_binding.observe(model_binding)
814
433
  unless model_binding.binding_options[:read_only]
815
434
  # TODO add guards against nil cases for hash below
816
- listener_keyword = data_binding_element_keyword_to_property_listener_map[keyword][property]
817
- data_binding_read_listener = lambda do |event|
818
- model_binding.call(send(property))
435
+ listener_keyword = data_binding_listener_for_element_and_property(keyword, property)
436
+ if listener_keyword
437
+ data_binding_read_listener = lambda do |event|
438
+ view_property_value = send(property)
439
+ converted_view_property_value = value_converters_for_input_type(type)[:view_to_model].call(view_property_value, model_binding.evaluate_property)
440
+ model_binding.call(converted_view_property_value)
441
+ end
442
+ handle_observation_request(listener_keyword, data_binding_read_listener)
819
443
  end
820
- handle_observation_request(listener_keyword, data_binding_read_listener)
821
444
  end
822
445
  end
823
446
 
824
- def set_attribute(attribute_name, *args)
825
- apply_property_type_converters(attribute_name, args)
826
- super(attribute_name, *args) # PropertyOwner
827
- end
828
-
829
447
  def respond_to_missing?(method_name, include_private = false)
830
448
  # TODO consider doing more correct checking of availability of properties/methods using native `` ticks
831
449
  property_name = property_name_for(method_name)
@@ -870,73 +488,19 @@ module Glimmer
870
488
  self
871
489
  end
872
490
 
873
- def apply_property_type_converters(attribute_name, args)
874
- if args.count == 1
875
- value = args.first
876
- converter = property_type_converters[attribute_name.to_sym]
877
- args[0] = converter.call(value) if converter
878
- end
879
- # if args.count == 1 && args.first.is_a?(ColorProxy)
880
- # g_color = args.first
881
- # args[0] = g_color.swt_color
882
- # end
491
+ def data_binding_listener_for_element_and_property(element_keyword, property)
492
+ data_binding_property_listener_map_for_element(element_keyword)[property]
883
493
  end
884
494
 
885
- def property_type_converters
886
- color_converter = proc do |value|
887
- if value.is_a?(Symbol) || value.is_a?(String)
888
- ColorProxy.new(value)
889
- else
890
- value
891
- end
892
- end
893
- @property_type_converters ||= {
894
- :background => color_converter,
895
- # :background_image => proc do |value|
896
- # if value.is_a?(String)
897
- # if value.start_with?('uri:classloader')
898
- # value = value.sub(/^uri\:classloader\:\//, '')
899
- # object = java.lang.Object.new
900
- # value = object.java_class.resource_as_stream(value)
901
- # value = java.io.BufferedInputStream.new(value)
902
- # end
903
- # image_data = ImageData.new(value)
904
- # on_event_Resize do |resize_event|
905
- # new_image_data = image_data.scaledTo(@swt_widget.getSize.x, @swt_widget.getSize.y)
906
- # @swt_widget.getBackgroundImage&.remove
907
- # @swt_widget.setBackgroundImage(Image.new(@swt_widget.getDisplay, new_image_data))
908
- # end
909
- # Image.new(@swt_widget.getDisplay, image_data)
910
- # else
911
- # value
912
- # end
913
- # end,
914
- :foreground => color_converter,
915
- # :font => proc do |value|
916
- # if value.is_a?(Hash)
917
- # font_properties = value
918
- # FontProxy.new(self, font_properties).swt_font
919
- # else
920
- # value
921
- # end
922
- # end,
923
- :text => proc do |value|
924
- # if swt_widget.is_a?(Browser)
925
- # value.to_s
926
- # else
927
- value.to_s
928
- # end
929
- end,
930
- # :visible => proc do |value|
931
- # !!value
932
- # end,
933
- }
495
+ def data_binding_property_listener_map_for_element(element_keyword)
496
+ data_binding_element_keyword_to_property_listener_map[element_keyword] || {}
934
497
  end
935
498
 
936
499
  def data_binding_element_keyword_to_property_listener_map
937
500
  @data_binding_element_keyword_to_property_listener_map ||= {
938
501
  'input' => {
939
502
  'value' => 'oninput',
503
+ 'checked' => 'oninput',
940
504
  },
941
505
  'select' => {
942
506
  'value' => 'onchange',
@@ -947,8 +511,106 @@ module Glimmer
947
511
  }
948
512
  end
949
513
 
514
+ def value_converters_for_input_type(input_type)
515
+ input_value_converters[input_type] || {model_to_view: ->(value, old_value) {value}, view_to_model: ->(value, old_value) {value}}
516
+ end
517
+
518
+ def input_value_converters
519
+ @input_value_converters ||= {
520
+ 'number' => {
521
+ model_to_view: -> (value, old_value) { value.to_s },
522
+ view_to_model: -> (value, old_value) {
523
+ value.include?('.') ? value.to_f : value.to_i
524
+ },
525
+ },
526
+ 'range' => {
527
+ model_to_view: -> (value, old_value) { value.to_s },
528
+ view_to_model: -> (value, old_value) {
529
+ value.include?('.') ? value.to_f : value.to_i
530
+ },
531
+ },
532
+ 'datetime-local' => {
533
+ model_to_view: -> (value, old_value) {
534
+ if value.respond_to?(:strftime)
535
+ value.strftime(FORMAT_DATETIME)
536
+ elsif value.is_a?(String) && valid_js_date_string?(value)
537
+ value
538
+ else
539
+ old_value
540
+ end
541
+ },
542
+ view_to_model: -> (value, old_value) {
543
+ if value.to_s.empty?
544
+ nil
545
+ else
546
+ date = Native(`new Date(Date.parse(#{value}))`)
547
+ year = Native.call(date, 'getFullYear')
548
+ month = Native.call(date, 'getMonth') + 1
549
+ day = Native.call(date, 'getDate')
550
+ hour = Native.call(date, 'getHours')
551
+ minute = Native.call(date, 'getMinutes')
552
+ Time.new(year, month, day, hour, minute)
553
+ end
554
+ },
555
+ },
556
+ 'date' => {
557
+ model_to_view: -> (value, old_value) {
558
+ if value.respond_to?(:strftime)
559
+ value.strftime(FORMAT_DATE)
560
+ elsif value.is_a?(String) && valid_js_date_string?(value)
561
+ value
562
+ else
563
+ old_value
564
+ end
565
+ },
566
+ view_to_model: -> (value, old_value) {
567
+ if value.to_s.empty?
568
+ nil
569
+ else
570
+ year, month, day = value.split('-')
571
+ if old_value
572
+ Time.new(year, month, day, old_value.hour, old_value.min)
573
+ else
574
+ Time.new(year, month, day)
575
+ end
576
+ end
577
+ },
578
+ },
579
+ 'time' => {
580
+ model_to_view: -> (value, old_value) {
581
+ if value.respond_to?(:strftime)
582
+ value.strftime(FORMAT_TIME)
583
+ elsif value.is_a?(String) && valid_js_date_string?(value)
584
+ value
585
+ else
586
+ old_value
587
+ end
588
+ },
589
+ view_to_model: -> (value, old_value) {
590
+ if value.to_s.empty?
591
+ nil
592
+ else
593
+ hour, minute = value.split(':')
594
+ if old_value
595
+ Time.new(old_value.year, old_value.month, old_value.day, hour, minute)
596
+ else
597
+ now = Time.now
598
+ Time.new(now.year, now.month, now.day, hour, minute)
599
+ end
600
+ end
601
+ },
602
+ },
603
+ }
604
+ end
605
+
950
606
  private
951
607
 
608
+ def valid_js_date_string?(string)
609
+ [REGEX_FORMAT_DATETIME, REGEX_FORMAT_DATE, REGEX_FORMAT_TIME].any? do |format|
610
+ string.match(format)
611
+ end
612
+ end
613
+
952
614
  def css_cursor
953
615
  SWT_CURSOR_TO_CSS_CURSOR_MAP[@cursor]
954
616
  end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2023 Andy Maleh
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2023 Andy Maleh
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
data/lib/glimmer/web.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2023 Andy Maleh
1
+ # Copyright (c) 2023-2024 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the
@@ -1,4 +1,3 @@
1
- # TODO double check if the latest Opal implemented everything below already
2
1
  require 'date'
3
2
  require 'time'
4
3
 
@@ -13,6 +12,10 @@ class DateTime < Date
13
12
  end
14
13
  end
15
14
  end
15
+
16
+ def now
17
+ Time.now.to_datetime
18
+ end
16
19
 
17
20
  def to_date
18
21
  @time.to_date