require 'browser/animation_frame' module Browser; module DOM class Document < Element # @!attribute [r] active_element # @return [Element] the element with focus def active_element DOM(`#@native.activeElement`) end end class Element # Show the element. # # @param what [Symbol] how to display it def show(what = :block) style[:display] = what self end # Hide the element. def hide style[:display] = :none self end def visible? # Let's check if we want to lie about the real visibility of an element. # It could be wise to lie about it when it's in a process of animation... if !@virtually_visible.nil? @virtually_visible else style![:display] != :none end end # Toggle the visibility of the element, hide it if it's shown, show it if # it's hidden. def toggle(what = :block) if visible? hide else show(what) end self end # Set the focus on the element. def focus `#@native.focus()` self end # Blur the focus from the element. def blur `#@native.blur()` self end # Check if the element is focused. def focused? `#@native.hasFocus` end # Queue the block to happen when currently queued animations finish or during # the next animation frame. def animation_queue &block promise = Promise.new promise_resolve = proc do @animation_promise = nil if @animation_promise == promise promise.resolve end @animation_promise = (@animation_promise || Promise.value(true)).then do animation_frame do yield promise_resolve end promise end end # Transform an element smoothly using CSS transitions, jQuery style. Yield # a block afterwards if it's provided. def animate(properties, duration: 0.4.s, easing: :ease, resolve: false, &block) animation_queue(resolve) do |res| duration = 0.6.s if duration == :slow duration = 0.2.s if duration == :fast original_value = style['transition'] style['transition'] = [original_value, *properties.keys.map do |key| "#{key} #{duration} #{easing}" end].compact.join(", ") properties.each do |key, value| style[key] = value end promise = Promise.new one :transitionend do |*args| style['transition'] = original_value yield(*args) if block_given? res.call end end self end # Show a hidden element with a "fade in" animation. Yield a block afterwards. def fade_in(**kwargs, &block) animation_queue do |resolve| if !visible? @virtually_visible = true show style[:opacity] = 0.0 animate opacity: 1.0, **kwargs do |*args| @virtually_visible = nil style[:opacity] = nil yield(*args) if block_given? end end resolve.call end self end # Hide a visible element with a "fade out" animation. Yield a block afterwards. def fade_out(**kwargs, &block) animation_queue do |resolve| if visible? @virtually_visible = false style[:opacity] = 1.0 animate opacity: 0.0, **kwargs do |*args| @virtually_visible = nil style[:opacity] = nil hide yield(*args) if block_given? end end resolve.call end self end # Toggle a visibility of an element with a "fade in"/"fade out" animation. Yield # a block afterwards. def fade_toggle(**kwargs, &block) if visible? fade_out(**kwargs, &block) else fade_in(**kwargs, &block) end self end # Show a hidden element with a "slide down" animation. Yield a block afterwards. def slide_down(**kwargs, &block) animation_queue do |resolve| if !visible? @virtually_visible = true show height = size.height orig_height = style[:height] style[:height] = 0.px animate height: height.px, **kwargs do |*args| @virtually_visible = nil style[:height] = orig_height yield(*args) if block_given? end end resolve.call end self end # Hide a visible element with a "slide up" animation. Yield a block afterwards. def slide_up(**kwargs, &block) animation_queue do |resolve| if visible? @virtually_visible = false orig_height = style[:height] animate height: 0.px, **kwargs do |*args| @virtually_visible = nil style[:height] = orig_height hide yield(*args) if block_given? end end resolve.call end self end # Toggle a visibility of an element with a "slide up"/"slide down" animation. # Yield a block afterwards. def slide_toggle(**kwargs, &block) if visible? slide_up(**kwargs, &block) else slide_down(**kwargs, &block) end self end end end; end