glimmer-dsl-web 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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