fron-ui 1.0.0rc2
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 +7 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +38 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/.yardopts +8 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +105 -0
- data/Rakefile +37 -0
- data/Readme.md +4 -0
- data/db.json +192 -0
- data/fron-ui.gemspec +21 -0
- data/lib/fron-ui.rb +1 -0
- data/lib/fron_ui.rb +5 -0
- data/lib/fron_ui/version.rb +7 -0
- data/opal/fron-ui/base.rb +49 -0
- data/opal/fron-ui/behaviors/action.rb +40 -0
- data/opal/fron-ui/behaviors/actions.rb +40 -0
- data/opal/fron-ui/behaviors/confirmation.rb +23 -0
- data/opal/fron-ui/behaviors/dropdown.rb +27 -0
- data/opal/fron-ui/behaviors/file.rb +48 -0
- data/opal/fron-ui/behaviors/intendable_children.rb +76 -0
- data/opal/fron-ui/behaviors/keydown.rb +31 -0
- data/opal/fron-ui/behaviors/loop.rb +41 -0
- data/opal/fron-ui/behaviors/render.rb +30 -0
- data/opal/fron-ui/behaviors/rest.rb +121 -0
- data/opal/fron-ui/behaviors/selectable_children.rb +67 -0
- data/opal/fron-ui/behaviors/serialize.rb +32 -0
- data/opal/fron-ui/behaviors/shortcuts.rb +35 -0
- data/opal/fron-ui/behaviors/state.rb +56 -0
- data/opal/fron-ui/behaviors/transition.rb +63 -0
- data/opal/fron-ui/components/action.rb +18 -0
- data/opal/fron-ui/components/box.rb +17 -0
- data/opal/fron-ui/components/button.rb +61 -0
- data/opal/fron-ui/components/calendar.rb +129 -0
- data/opal/fron-ui/components/checkbox.rb +57 -0
- data/opal/fron-ui/components/chooser.rb +246 -0
- data/opal/fron-ui/components/color_panel.rb +235 -0
- data/opal/fron-ui/components/color_picker.rb +111 -0
- data/opal/fron-ui/components/container.rb +61 -0
- data/opal/fron-ui/components/date_picker.rb +141 -0
- data/opal/fron-ui/components/drag.rb +76 -0
- data/opal/fron-ui/components/dropdown.rb +72 -0
- data/opal/fron-ui/components/icon.rb +29 -0
- data/opal/fron-ui/components/image.rb +77 -0
- data/opal/fron-ui/components/input.rb +30 -0
- data/opal/fron-ui/components/label.rb +9 -0
- data/opal/fron-ui/components/list.rb +34 -0
- data/opal/fron-ui/components/loader.rb +63 -0
- data/opal/fron-ui/components/modal.rb +0 -0
- data/opal/fron-ui/components/notifications.rb +73 -0
- data/opal/fron-ui/components/number.rb +202 -0
- data/opal/fron-ui/components/progress.rb +52 -0
- data/opal/fron-ui/components/slider.rb +47 -0
- data/opal/fron-ui/components/tabs.rb +149 -0
- data/opal/fron-ui/components/textarea.rb +13 -0
- data/opal/fron-ui/components/time.rb +65 -0
- data/opal/fron-ui/components/title.rb +34 -0
- data/opal/fron-ui/examples/blog/index.rb +289 -0
- data/opal/fron-ui/examples/comments/components/comment.rb +75 -0
- data/opal/fron-ui/examples/comments/components/comments.rb +93 -0
- data/opal/fron-ui/examples/comments/components/footer.rb +36 -0
- data/opal/fron-ui/examples/comments/components/header.rb +35 -0
- data/opal/fron-ui/examples/comments/components/list.rb +12 -0
- data/opal/fron-ui/examples/comments/index.rb +6 -0
- data/opal/fron-ui/examples/contacts/components/contacts.rb +100 -0
- data/opal/fron-ui/examples/contacts/components/details.rb +92 -0
- data/opal/fron-ui/examples/contacts/components/item.rb +46 -0
- data/opal/fron-ui/examples/contacts/components/list.rb +10 -0
- data/opal/fron-ui/examples/contacts/components/sidebar.rb +30 -0
- data/opal/fron-ui/examples/contacts/index.rb +6 -0
- data/opal/fron-ui/examples/editor/index.rb +164 -0
- data/opal/fron-ui/examples/kitchensink/index.rb +193 -0
- data/opal/fron-ui/examples/todos/components/item.rb +84 -0
- data/opal/fron-ui/examples/todos/components/options.rb +26 -0
- data/opal/fron-ui/examples/todos/components/todos.rb +145 -0
- data/opal/fron-ui/examples/todos/index.rb +6 -0
- data/opal/fron-ui/examples/webshop/index.rb +0 -0
- data/opal/fron-ui/fonts/ionicons.rb +2954 -0
- data/opal/fron-ui/fonts/open_sans.rb +19 -0
- data/opal/fron-ui/lib/collection.rb +138 -0
- data/opal/fron-ui/lib/date.rb +23 -0
- data/opal/fron-ui/lib/debounce.rb +14 -0
- data/opal/fron-ui/lib/image_loader.rb +13 -0
- data/opal/fron-ui/lib/lorem.rb +93 -0
- data/opal/fron-ui/lib/nil.rb +29 -0
- data/opal/fron-ui/lib/record.rb +23 -0
- data/opal/fron-ui/lib/state_serializer.rb +129 -0
- data/opal/fron-ui/lib/storage.rb +57 -0
- data/opal/fron-ui/spec/setup.rb +40 -0
- data/opal/fron-ui/ui.rb +40 -0
- data/opal/fron-ui/utils/theme_roller.rb +63 -0
- data/opal/fron-ui/vendor/autoprefixer.js +21114 -0
- data/opal/fron-ui/vendor/marked.js +1291 -0
- data/opal/fron-ui/vendor/md5.js +274 -0
- data/opal/fron-ui/vendor/moment.js +3083 -0
- data/opal/fron-ui/vendor/uuid.js +92 -0
- data/opal/fron_ui.rb +13 -0
- data/spec/behaviors/action_spec.rb +34 -0
- data/spec/behaviors/actions_spec.rb +38 -0
- data/spec/behaviors/confirmation_spec.rb +23 -0
- data/spec/behaviors/dropdown_spec.rb +32 -0
- data/spec/behaviors/render_spec.rb +20 -0
- data/spec/behaviors/rest_spec.rb +70 -0
- data/spec/behaviors/selectable_children_spec.rb +40 -0
- data/spec/behaviors/serialize_spec.rb +34 -0
- data/spec/components/action_spec.rb +7 -0
- data/spec/components/base_spec.rb +19 -0
- data/spec/components/box_spec.rb +7 -0
- data/spec/components/button_spec.rb +9 -0
- data/spec/components/calendar_spec.rb +58 -0
- data/spec/components/checkbox_spec.rb +20 -0
- data/spec/components/chooser_spec.rb +75 -0
- data/spec/components/color_panel_spec.rb +49 -0
- data/spec/components/color_picker_spec.rb +41 -0
- data/spec/components/container_spec.rb +23 -0
- data/spec/components/date_picker_spec.rb +71 -0
- data/spec/components/drag_spec.rb +20 -0
- data/spec/components/dropdown_spec.rb +33 -0
- data/spec/components/image_spec.rb +33 -0
- data/spec/components/input_spec.rb +8 -0
- data/spec/components/list_spec.rb +10 -0
- data/spec/components/loader_spec.rb +9 -0
- data/spec/components/notifications_spec.rb +17 -0
- data/spec/components/number_spec.rb +64 -0
- data/spec/components/progress_spec.rb +23 -0
- data/spec/components/slider_spec.rb +25 -0
- data/spec/components/tabs_spec.rb +50 -0
- data/spec/components/textarea_spec.rb +7 -0
- data/spec/components/time_spec.rb +37 -0
- data/spec/components/title_spec.rb +11 -0
- data/spec/examples/comments_spec.rb +72 -0
- data/spec/examples/todos_spec.rb +39 -0
- data/spec/lib/collection_spec.rb +38 -0
- data/spec/lib/lorem_spec.rb +55 -0
- data/spec/lib/state_serializer_spec.rb +58 -0
- data/spec/lib/storage_spec.rb +39 -0
- data/spec/spec_helper.rb +1 -0
- metadata +223 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module UI
|
|
2
|
+
# Simple Icon component
|
|
3
|
+
#
|
|
4
|
+
# @author Gusztáv Szikszai
|
|
5
|
+
# @since 0.1.0
|
|
6
|
+
class Icon < UI::Base
|
|
7
|
+
tag 'ui-icon'
|
|
8
|
+
|
|
9
|
+
style justifyContent: :center,
|
|
10
|
+
display: 'inline-flex',
|
|
11
|
+
alignItems: :center,
|
|
12
|
+
fontWeight: :normal,
|
|
13
|
+
'&:not([clickable])' => { pointerEvents: :none },
|
|
14
|
+
'&[clickable]' => { cursor: :pointer,
|
|
15
|
+
'&:hover' => { color: -> { colors.focus } } }
|
|
16
|
+
|
|
17
|
+
# Sets the glyph
|
|
18
|
+
#
|
|
19
|
+
# @param value [String] The glyph
|
|
20
|
+
def glyph=(value)
|
|
21
|
+
self[:glyph] = value
|
|
22
|
+
self[:class] = "ion-#{value}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def glyph
|
|
26
|
+
self[:glyph]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
require 'fron-ui/components/loader'
|
|
2
|
+
|
|
3
|
+
module UI
|
|
4
|
+
# Component for displaying an image, with
|
|
5
|
+
# a transition after it loads.
|
|
6
|
+
#
|
|
7
|
+
# @author Gusztáv Szikszai
|
|
8
|
+
# @since 0.1.0
|
|
9
|
+
class Image < Fron::Component
|
|
10
|
+
tag 'ui-image'
|
|
11
|
+
|
|
12
|
+
component :img, :img
|
|
13
|
+
component :loader, UI::Loader
|
|
14
|
+
|
|
15
|
+
style borderRadius: -> { (theme.border_radius * 2).em },
|
|
16
|
+
background: -> { dampen colors.background, 0.05 },
|
|
17
|
+
display: 'inline-block',
|
|
18
|
+
position: :relative,
|
|
19
|
+
'ui-loader' => { color: -> { dampen colors.background, 0.2 },
|
|
20
|
+
position: :absolute,
|
|
21
|
+
bottom: 0,
|
|
22
|
+
right: 0,
|
|
23
|
+
left: 0,
|
|
24
|
+
top: 0 },
|
|
25
|
+
img: { transition: 'opacity 320ms',
|
|
26
|
+
borderRadius: :inherit,
|
|
27
|
+
height: :inherit,
|
|
28
|
+
width: :inherit,
|
|
29
|
+
opacity: 0,
|
|
30
|
+
'&.loaded' => { opacity: 1 } }
|
|
31
|
+
|
|
32
|
+
# Initializes the component
|
|
33
|
+
# and listens on the load event
|
|
34
|
+
# because it doesn't bubble.
|
|
35
|
+
def initialize
|
|
36
|
+
super
|
|
37
|
+
@img.on(:load) { loaded }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Adds the loaded class
|
|
41
|
+
def loaded
|
|
42
|
+
@img.add_class :loaded
|
|
43
|
+
@loader.loading = false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Sets the width of the image
|
|
47
|
+
#
|
|
48
|
+
# @param value [Numeric] The width
|
|
49
|
+
def width=(value)
|
|
50
|
+
@style.width = value
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Sets the height of the image
|
|
54
|
+
#
|
|
55
|
+
# @param value [Numeric] The height
|
|
56
|
+
def height=(value)
|
|
57
|
+
@style.height = value
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Returns the src attribute of the image
|
|
61
|
+
#
|
|
62
|
+
# @return [String] The attribute
|
|
63
|
+
def src
|
|
64
|
+
@img[:src]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Sets the src of the image
|
|
68
|
+
#
|
|
69
|
+
# @param value [String] The URL
|
|
70
|
+
def src=(value)
|
|
71
|
+
return if !value || @img[:src] == value
|
|
72
|
+
@img.remove_class :loaded
|
|
73
|
+
@loader.loading = true
|
|
74
|
+
timeout(320) { @img[:src] = value }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module UI
|
|
2
|
+
# Input component, basically the input element wrapped
|
|
3
|
+
# and styled.
|
|
4
|
+
#
|
|
5
|
+
# @author Gusztáv Szikszai
|
|
6
|
+
# @since 0.1.0
|
|
7
|
+
class Input < Base
|
|
8
|
+
tag 'input'
|
|
9
|
+
|
|
10
|
+
defaults type: 'text'
|
|
11
|
+
|
|
12
|
+
attribute_accessor :placeholder
|
|
13
|
+
attribute_accessor :readonly, default: false
|
|
14
|
+
|
|
15
|
+
style borderRadius: -> { theme.border_radius.em },
|
|
16
|
+
color: -> { readable_color(colors.input) },
|
|
17
|
+
padding: -> { "0 #{theme.spacing.em}" },
|
|
18
|
+
fontFamily: -> { theme.font_family },
|
|
19
|
+
lineHeight: -> { theme.size.em },
|
|
20
|
+
background: -> { colors.input },
|
|
21
|
+
height: -> { theme.size.em },
|
|
22
|
+
fontSize: :inherit,
|
|
23
|
+
border: 0,
|
|
24
|
+
'&:focus' => {
|
|
25
|
+
boxShadow: -> { theme.focus_box_shadow.call },
|
|
26
|
+
borderColor: :transparent,
|
|
27
|
+
outline: :none
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module UI
|
|
2
|
+
# List component
|
|
3
|
+
#
|
|
4
|
+
# Features:
|
|
5
|
+
# * Empty message display in an :after element
|
|
6
|
+
# * Striped backgrounds for items
|
|
7
|
+
# * Overflow auto
|
|
8
|
+
#
|
|
9
|
+
# @author Gusztáv Szikszai
|
|
10
|
+
# @since 0.1.0
|
|
11
|
+
class List < Collection
|
|
12
|
+
tag 'ui-list'
|
|
13
|
+
|
|
14
|
+
style overflow: :auto,
|
|
15
|
+
display: :block,
|
|
16
|
+
'> *' => { display: :block,
|
|
17
|
+
'& + *' => { borderTop: -> { "1px solid #{dampen colors.background, 0.025}" } } },
|
|
18
|
+
'&:empty' => { padding: -> { theme.spacing.em },
|
|
19
|
+
justifyContent: :center,
|
|
20
|
+
alignItems: :center,
|
|
21
|
+
textAlign: :center,
|
|
22
|
+
display: :flex },
|
|
23
|
+
'&:empty:after' => { content: 'attr(empty_message)',
|
|
24
|
+
fontSize: 2.em,
|
|
25
|
+
opacity: 0.25 }
|
|
26
|
+
|
|
27
|
+
# Sets the flex value
|
|
28
|
+
#
|
|
29
|
+
# @param value [Numeric] The value
|
|
30
|
+
def flex=(value)
|
|
31
|
+
@style.flex = value
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# rubocop:disable DoubleNegation
|
|
2
|
+
|
|
3
|
+
module UI
|
|
4
|
+
# Loader component
|
|
5
|
+
#
|
|
6
|
+
# @author Gusztáv Szikszai
|
|
7
|
+
# @since 0.1.0
|
|
8
|
+
class Loader < Base
|
|
9
|
+
tag 'ui-loader'
|
|
10
|
+
|
|
11
|
+
component :div, 'ui-loader-div'
|
|
12
|
+
|
|
13
|
+
keyframes 'ui-loader', '0%' => { transform: 'rotate(0deg)' },
|
|
14
|
+
'50%' => { transform: 'rotate(179.9deg)' },
|
|
15
|
+
'100%' => { transform: 'rotate(359.9deg)' }
|
|
16
|
+
|
|
17
|
+
style transition: 'opacity 320ms',
|
|
18
|
+
justifyContent: :center,
|
|
19
|
+
alignItems: :center,
|
|
20
|
+
minHeight: 2.em,
|
|
21
|
+
minWidth: 2.em,
|
|
22
|
+
display: :flex,
|
|
23
|
+
color: -> { colors.primary },
|
|
24
|
+
opacity: 0,
|
|
25
|
+
pointerEvents: :none,
|
|
26
|
+
'&.loading' => {
|
|
27
|
+
opacity: 1
|
|
28
|
+
},
|
|
29
|
+
'ui-loader-div' => {
|
|
30
|
+
animation: 'ui-loader 1.2s infinite linear',
|
|
31
|
+
border: '0.15em solid currentColor',
|
|
32
|
+
borderTop: '0.15em solid transparent',
|
|
33
|
+
borderBottom: '0.15em solid transparent',
|
|
34
|
+
display: :flex,
|
|
35
|
+
justifyContent: :center,
|
|
36
|
+
alignItems: :center,
|
|
37
|
+
borderRadius: '50%',
|
|
38
|
+
height: 2.em,
|
|
39
|
+
width: 2.em,
|
|
40
|
+
'&:before' => {
|
|
41
|
+
content: '""',
|
|
42
|
+
border: '0.15em solid currentColor',
|
|
43
|
+
borderRadius: '50%',
|
|
44
|
+
height: 1.em,
|
|
45
|
+
width: 1.em
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# Sets the loading flag
|
|
50
|
+
#
|
|
51
|
+
# @param value [Boolean] The value
|
|
52
|
+
def loading=(value)
|
|
53
|
+
toggle_class :loading, !!value
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Returns the loading flag
|
|
57
|
+
#
|
|
58
|
+
# @return [Boolean] The value
|
|
59
|
+
def loading
|
|
60
|
+
has_class(:loading)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
File without changes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
module UI
|
|
2
|
+
# Component for displaying notifications.
|
|
3
|
+
#
|
|
4
|
+
# @author Gusztáv Szikszai
|
|
5
|
+
# @since 0.1.0
|
|
6
|
+
class Notifications < Base
|
|
7
|
+
# Notification component-
|
|
8
|
+
#
|
|
9
|
+
# @author Gusztáv Szikszai
|
|
10
|
+
# @since 0.1.0
|
|
11
|
+
class Notification < Base
|
|
12
|
+
include UI::Behaviors::Transition
|
|
13
|
+
|
|
14
|
+
tag 'ui-notification'
|
|
15
|
+
|
|
16
|
+
style background: -> { rgba colors.primary, 0.85 },
|
|
17
|
+
borderRadius: -> { theme.border_radius.em },
|
|
18
|
+
color: -> { readable_color colors.primary },
|
|
19
|
+
fontSize: 1.2.em,
|
|
20
|
+
fontWeight: 600,
|
|
21
|
+
display: :block,
|
|
22
|
+
lineHeight: 1,
|
|
23
|
+
span: {
|
|
24
|
+
display: :block,
|
|
25
|
+
padding: -> { theme.spacing.em },
|
|
26
|
+
paddingBottom: -> { (theme.spacing * 1.2).em }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
component :span, :span
|
|
30
|
+
|
|
31
|
+
transition :show, duration: '500ms',
|
|
32
|
+
frames: { '0%' => { opacity: 0 },
|
|
33
|
+
'100%' => { opacity: 1 } },
|
|
34
|
+
callback: :hide
|
|
35
|
+
|
|
36
|
+
transition :hide, duration: '500ms',
|
|
37
|
+
delay: '3s',
|
|
38
|
+
frames: { '0%' => { opacity: 1 },
|
|
39
|
+
'100%' => { opacity: 0 } },
|
|
40
|
+
callback: :remove!
|
|
41
|
+
|
|
42
|
+
# Initializes the component, starting
|
|
43
|
+
# the show transition.
|
|
44
|
+
def initialize
|
|
45
|
+
super
|
|
46
|
+
transition! :show
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Hides the component with the hide transition.
|
|
50
|
+
def hide
|
|
51
|
+
transition! :hide
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
tag 'ui-notifications'
|
|
56
|
+
|
|
57
|
+
style position: :absolute,
|
|
58
|
+
bottom: 1.5.em,
|
|
59
|
+
left: 1.5.em,
|
|
60
|
+
'> * + *' => { marginTop: -> { (theme.spacing / 1.5).em } }
|
|
61
|
+
|
|
62
|
+
# Pushes a new notification with the given
|
|
63
|
+
# message into the queue.
|
|
64
|
+
#
|
|
65
|
+
# @param message [String] The message
|
|
66
|
+
def push(message)
|
|
67
|
+
noti = Notification.new
|
|
68
|
+
noti.span.text = message
|
|
69
|
+
noti >> self
|
|
70
|
+
noti
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
require 'fron/utils/drag'
|
|
2
|
+
|
|
3
|
+
module UI
|
|
4
|
+
# Range input element. This element is editable just like a normal
|
|
5
|
+
# input element can, the difference is that this element is draggable.
|
|
6
|
+
# When dragging the value is incremeted or decremented depending on the
|
|
7
|
+
# direction of the drag. The element can be limited to an upper and a lower value.
|
|
8
|
+
#
|
|
9
|
+
# @attr_reader drag [Fron::Drag] The drag instance
|
|
10
|
+
# @attr affix [String] The affix (px, %, em, ...)
|
|
11
|
+
# @attr label [String] The label to be displayed
|
|
12
|
+
# @attr step [Float] The step modifier to apply to the step, default to 1
|
|
13
|
+
# @attr min [Float] The minimal possible value, defaults to -infinity
|
|
14
|
+
# @attr max [Float] The maximum possible value, defaults to infinity
|
|
15
|
+
# @attr round [Float] The rounding value, defaults to 2
|
|
16
|
+
#
|
|
17
|
+
# @author Gusztáv Szikszai
|
|
18
|
+
# @since 0.1.0
|
|
19
|
+
class NumberRange < Base
|
|
20
|
+
# Input component
|
|
21
|
+
class Input < Fron::Component
|
|
22
|
+
tag 'ui-number-range-input'
|
|
23
|
+
|
|
24
|
+
attribute_accessor :label
|
|
25
|
+
attribute_accessor :affix
|
|
26
|
+
|
|
27
|
+
style borderRadius: -> { theme.border_radius.em },
|
|
28
|
+
lineHeight: -> { theme.size.em },
|
|
29
|
+
background: -> { colors.input },
|
|
30
|
+
height: -> { theme.size.em },
|
|
31
|
+
textAlign: :center,
|
|
32
|
+
display: :block,
|
|
33
|
+
fontWeight: 600,
|
|
34
|
+
'&:before' => { content: 'attr(label)',
|
|
35
|
+
marginRight: 0.2.em,
|
|
36
|
+
fontWeight: :normal },
|
|
37
|
+
'&:after' => { content: 'attr(affix)' },
|
|
38
|
+
'&:focus' => { boxShadow: -> { theme.focus_box_shadow.call },
|
|
39
|
+
outline: :none }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
extend Forwardable
|
|
43
|
+
|
|
44
|
+
attr_reader :drag
|
|
45
|
+
|
|
46
|
+
tag 'ui-number-range'
|
|
47
|
+
|
|
48
|
+
component :input, Input, contenteditable: true
|
|
49
|
+
|
|
50
|
+
def_delegators :@input, :affix=, :affix, :label=, :label
|
|
51
|
+
|
|
52
|
+
on :keydown, :keydown
|
|
53
|
+
on :input, :input
|
|
54
|
+
on :mousemove, :on_mouse_move
|
|
55
|
+
on :mousedown, :on_mouse_down
|
|
56
|
+
|
|
57
|
+
attribute_accessor :step, default: 1, coerce: :to_f
|
|
58
|
+
attribute_accessor :round, default: 1, coerce: :to_f
|
|
59
|
+
attribute_accessor :max, default: Float::INFINITY, coerce: :to_f
|
|
60
|
+
attribute_accessor :min, default: -Float::INFINITY, coerce: :to_f
|
|
61
|
+
|
|
62
|
+
style color: -> { readable_color(colors.input) },
|
|
63
|
+
minWidth: -> { (theme.size * 5).em },
|
|
64
|
+
display: 'inline-block',
|
|
65
|
+
position: :relative,
|
|
66
|
+
'&:after, &:before' => { borderStyle: :solid,
|
|
67
|
+
position: :absolute,
|
|
68
|
+
marginTop: -0.3.em,
|
|
69
|
+
content: "''",
|
|
70
|
+
opacity: 0.5,
|
|
71
|
+
top: '50%',
|
|
72
|
+
height: 0,
|
|
73
|
+
width: 0 },
|
|
74
|
+
'&:after' => { borderColor: 'transparent currentColor transparent transparent',
|
|
75
|
+
borderWidth: '0.35em 0.4em 0.35em 0',
|
|
76
|
+
left: 0.75.em },
|
|
77
|
+
'&:before' => { borderColor: 'transparent transparent transparent currentColor',
|
|
78
|
+
borderWidth: '0.35em 0 0.35em 0.4em',
|
|
79
|
+
right: 0.75.em }
|
|
80
|
+
|
|
81
|
+
# Creates a new instance
|
|
82
|
+
def initialize
|
|
83
|
+
super
|
|
84
|
+
@input.on(:blur) { blur }
|
|
85
|
+
self.value = 0
|
|
86
|
+
setup_drag
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def _attribute_changed
|
|
90
|
+
reset_value
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Returns the value of the field
|
|
94
|
+
#
|
|
95
|
+
# @return [Float] The value of the field
|
|
96
|
+
def value
|
|
97
|
+
@value.to_f
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def _value(value)
|
|
101
|
+
value = value.to_f.clamp(min, max)
|
|
102
|
+
return if @value == value
|
|
103
|
+
@value = value
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Sets the value of the field
|
|
107
|
+
#
|
|
108
|
+
# @param value [Float] The value
|
|
109
|
+
def value=(value)
|
|
110
|
+
_value value
|
|
111
|
+
@input.text = format "%.#{round}f", @value
|
|
112
|
+
trigger 'change'
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
# Resets the value when attribute changes
|
|
118
|
+
def reset_value
|
|
119
|
+
self.value = value
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Sets up dragging.
|
|
123
|
+
def setup_drag
|
|
124
|
+
@drag = Fron::Drag.new self, 0
|
|
125
|
+
|
|
126
|
+
@drag.on('start') { @start_value = value }
|
|
127
|
+
@drag.on('move') { on_drag_move }
|
|
128
|
+
@drag.on('end') { on_drag_end }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Runs when drag end
|
|
132
|
+
def on_drag_end
|
|
133
|
+
DOM::Document.body.style.cursor = ''
|
|
134
|
+
blur
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Runs when drag moves
|
|
138
|
+
def on_drag_move
|
|
139
|
+
DOM::Document.body.style.cursor = 'move'
|
|
140
|
+
value = (@start_value - @drag.diff.x * step).round(round)
|
|
141
|
+
self.value = value if @value != value
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Runs when input changes. It sets the value if the emelement
|
|
145
|
+
# is not empty, otherwise it inserts a non-width space to prevent
|
|
146
|
+
# caret jumping in chrome.
|
|
147
|
+
def input
|
|
148
|
+
@input.html = '' if text.strip == ''
|
|
149
|
+
_value @input.html
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Runs when key is pressed. Prevents hitting the enter key.
|
|
153
|
+
#
|
|
154
|
+
# :reek:FeatureEnvy
|
|
155
|
+
#
|
|
156
|
+
# @param event [Event] The event
|
|
157
|
+
def keydown(event)
|
|
158
|
+
return unless event.key == :enter
|
|
159
|
+
event.prevent_default
|
|
160
|
+
@input.blur
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Runs when the element is blurred. Sets the value form
|
|
164
|
+
# the elements text.
|
|
165
|
+
def blur
|
|
166
|
+
self.value = text
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Runs on pointer move. Sets the cursor to move when not
|
|
170
|
+
# in the center of the element
|
|
171
|
+
#
|
|
172
|
+
# @param event [Event] The event
|
|
173
|
+
def on_mouse_move(event)
|
|
174
|
+
@style.cursor = in_select_region?(event.page_x) ? '' : 'move'
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Runs on pointer down. Prevents propagation so dragging
|
|
178
|
+
# cannot start.
|
|
179
|
+
#
|
|
180
|
+
# :reek:FeatureEnvy
|
|
181
|
+
#
|
|
182
|
+
# @param event [Event] The event
|
|
183
|
+
def on_mouse_down(event)
|
|
184
|
+
@input.focus
|
|
185
|
+
if in_select_region?(event.page_x)
|
|
186
|
+
event.stop_immediate_propagation
|
|
187
|
+
else
|
|
188
|
+
event.prevent_default
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Returns form to position if it is in the region that
|
|
193
|
+
# allow selection.
|
|
194
|
+
#
|
|
195
|
+
# @param position [type] The position (horizontal) to be checked
|
|
196
|
+
#
|
|
197
|
+
# @return [Boolean] True if the position is in the region otherwise false.
|
|
198
|
+
def in_select_region?(position)
|
|
199
|
+
(left + width / 2 - position).abs <= width / 4.5
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|