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,57 @@
|
|
|
1
|
+
module UI
|
|
2
|
+
# Checkbox component.
|
|
3
|
+
#
|
|
4
|
+
# Features:
|
|
5
|
+
# * checked and checked=
|
|
6
|
+
# * actionable
|
|
7
|
+
#
|
|
8
|
+
# @attr value [Boolean] The value
|
|
9
|
+
#
|
|
10
|
+
# @author Gusztáv Szikszai
|
|
11
|
+
# @since 0.1.0
|
|
12
|
+
class Checkbox < Action
|
|
13
|
+
extend Forwardable
|
|
14
|
+
|
|
15
|
+
tag 'ui-checkbox'
|
|
16
|
+
|
|
17
|
+
def_delegators :@input, :checked, :checked=
|
|
18
|
+
|
|
19
|
+
component :input, :input, tabindex: -1, type: :checkbox
|
|
20
|
+
component :label, :label, tabindex: -1 do
|
|
21
|
+
component :icon, UI::Icon, glyph: :checkmark
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
style height: -> { theme.size.em },
|
|
25
|
+
width: -> { theme.size.em },
|
|
26
|
+
display: 'inline-block',
|
|
27
|
+
input: { display: :none,
|
|
28
|
+
'&:checked + label' => { 'ui-icon' => { transform: 'scale(1)',
|
|
29
|
+
opacity: 1 } } },
|
|
30
|
+
label: { transition: 'opacity 320ms, transform 320ms',
|
|
31
|
+
borderRadius: -> { theme.border_radius.em },
|
|
32
|
+
color: -> { readable_color colors.input },
|
|
33
|
+
background: -> { colors.input },
|
|
34
|
+
justifyContent: :center,
|
|
35
|
+
alignItems: :center,
|
|
36
|
+
cursor: :pointer,
|
|
37
|
+
height: :inherit,
|
|
38
|
+
display: :flex,
|
|
39
|
+
width: :inherit,
|
|
40
|
+
'ui-icon' => { transform: 'scale(0.4) rotate(45deg)',
|
|
41
|
+
transition: :inherit,
|
|
42
|
+
fontSize: 1.em,
|
|
43
|
+
opacity: 0 } },
|
|
44
|
+
'&:focus label' => { boxShadow: -> { theme.focus_box_shadow.call } }
|
|
45
|
+
|
|
46
|
+
on :click, :action
|
|
47
|
+
|
|
48
|
+
# Toggles the checkbox
|
|
49
|
+
def action
|
|
50
|
+
self.checked = !checked
|
|
51
|
+
trigger :change
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
alias value= checked=
|
|
55
|
+
alias value checked
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
require 'fron-ui/components/dropdown'
|
|
2
|
+
require 'fron-ui/components/input'
|
|
3
|
+
|
|
4
|
+
module UI
|
|
5
|
+
# Chooser component
|
|
6
|
+
#
|
|
7
|
+
# Features
|
|
8
|
+
# * Select an item from the dropdown
|
|
9
|
+
# * Allows multiple selection (multiple attribute)
|
|
10
|
+
# * Search the items from the input
|
|
11
|
+
# * Navigate / select with keyboard
|
|
12
|
+
#
|
|
13
|
+
# @author Gusztáv Szikszai
|
|
14
|
+
# @since 0.1.0
|
|
15
|
+
class Chooser < Base
|
|
16
|
+
# Item for the chooser
|
|
17
|
+
class Item < Fron::Component
|
|
18
|
+
include ::Record
|
|
19
|
+
|
|
20
|
+
tag 'ui-chooser-item'
|
|
21
|
+
|
|
22
|
+
style display: :block,
|
|
23
|
+
padding: -> { (theme.spacing / 2).em }
|
|
24
|
+
|
|
25
|
+
# Renders the item
|
|
26
|
+
def render
|
|
27
|
+
self.text = label
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def label
|
|
31
|
+
@data[:value]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Retunrs the value
|
|
35
|
+
#
|
|
36
|
+
# @return [String] The value
|
|
37
|
+
def value
|
|
38
|
+
text
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# List for the chooser
|
|
43
|
+
class List < Collection
|
|
44
|
+
include UI::Behaviors::SelectableChildren
|
|
45
|
+
include UI::Behaviors::IntendableChildren
|
|
46
|
+
|
|
47
|
+
tag 'ui-chooser-list'
|
|
48
|
+
|
|
49
|
+
style background: -> { colors.input },
|
|
50
|
+
borderRadius: -> { theme.border_radius.em },
|
|
51
|
+
color: -> { readable_color colors.input },
|
|
52
|
+
overflow: :auto,
|
|
53
|
+
'> *.selected' => { background: -> { colors.primary },
|
|
54
|
+
color: -> { readable_color colors.primary },
|
|
55
|
+
'&:first-child' => { borderTopLeftRadius: :inherit,
|
|
56
|
+
borderTopRightRadius: :inherit },
|
|
57
|
+
'&:last-child' => { borderBottomLeftRadius: :inherit,
|
|
58
|
+
borderBottomRightRadius: :inherit } }
|
|
59
|
+
|
|
60
|
+
# Selects the currently intended item
|
|
61
|
+
def select_intended
|
|
62
|
+
select intended
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
include UI::Behaviors::Dropdown
|
|
67
|
+
include UI::Behaviors::Keydown
|
|
68
|
+
extend Forwardable
|
|
69
|
+
|
|
70
|
+
tag 'ui-chooser'
|
|
71
|
+
|
|
72
|
+
component :input, UI::Input
|
|
73
|
+
component :dropdown, UI::Dropdown do
|
|
74
|
+
component :list, List, base: Item, deselectable: true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
dropdown :input, :dropdown
|
|
78
|
+
|
|
79
|
+
def_delegators :dropdown, :list
|
|
80
|
+
def_delegators :input, :placeholder, :placeholder=, :blur, :active?, :focus
|
|
81
|
+
def_delegators :list, :base, :base=, :key, :key=, :multiple, :multiple=,
|
|
82
|
+
:intend_next, :intend_previous, :select_intended, :items,
|
|
83
|
+
:select_first, :select_last, :deselectable, :deselectable=
|
|
84
|
+
|
|
85
|
+
style position: :relative,
|
|
86
|
+
color: -> { readable_color colors.input },
|
|
87
|
+
input: { cursor: :pointer,
|
|
88
|
+
width: '100%' },
|
|
89
|
+
'ui-dropdown' => { left: 0,
|
|
90
|
+
right: 0,
|
|
91
|
+
maxHeight: 300.px,
|
|
92
|
+
overflowY: :auto },
|
|
93
|
+
'&:not([focused]):after,
|
|
94
|
+
&:not([searchable]):after' => { background: -> { "linear-gradient(90deg, #{rgba(colors.input, 0)}, #{colors.input} 70%)" },
|
|
95
|
+
borderBottomRightRadius: :inherit,
|
|
96
|
+
borderTopRightRadius: :inherit,
|
|
97
|
+
pointerEvents: :none,
|
|
98
|
+
position: :absolute,
|
|
99
|
+
content: '""',
|
|
100
|
+
width: 4.em,
|
|
101
|
+
bottom: 0.15.em,
|
|
102
|
+
right: 0.15.em,
|
|
103
|
+
top: 0.15.em },
|
|
104
|
+
'&:before' => { borderStyle: :solid,
|
|
105
|
+
position: :absolute,
|
|
106
|
+
marginTop: -0.2.em,
|
|
107
|
+
content: "''",
|
|
108
|
+
opacity: 0.5,
|
|
109
|
+
top: '50%',
|
|
110
|
+
height: 0,
|
|
111
|
+
pointerEvents: :none,
|
|
112
|
+
width: 0,
|
|
113
|
+
borderColor: 'currentColor transparent transparent transparent',
|
|
114
|
+
borderWidth: '0.4em 0.35em 0 0.35em',
|
|
115
|
+
right: 0.75.em,
|
|
116
|
+
zIndex: 1 }
|
|
117
|
+
|
|
118
|
+
keydown :esc, :blur
|
|
119
|
+
keydown :up, :intend_previous
|
|
120
|
+
keydown :down, :intend_next
|
|
121
|
+
|
|
122
|
+
on :input, :filter
|
|
123
|
+
on :selected_change, :selected_changed
|
|
124
|
+
on :keydown, :keydown
|
|
125
|
+
|
|
126
|
+
# Initilaizes the component, setting
|
|
127
|
+
# up not bubbling events on the input
|
|
128
|
+
def initialize
|
|
129
|
+
super
|
|
130
|
+
@dropdown.on :mousedown, &:prevent_default
|
|
131
|
+
|
|
132
|
+
self.searchable = true
|
|
133
|
+
@input.on(:focus) do
|
|
134
|
+
self[:focused] = ''
|
|
135
|
+
show_items
|
|
136
|
+
empty_input
|
|
137
|
+
end
|
|
138
|
+
@input.on(:blur) do
|
|
139
|
+
remove_attribute :focused
|
|
140
|
+
update_input
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def keydown(event)
|
|
145
|
+
return unless [:space, :enter].include?(event.key)
|
|
146
|
+
result = select_intended
|
|
147
|
+
event.stop if result
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Sets the searchable value, by setting
|
|
151
|
+
# the inputs to read only attribute.
|
|
152
|
+
#
|
|
153
|
+
# @param value [Boolean] The value
|
|
154
|
+
def searchable=(value)
|
|
155
|
+
@input.readonly = !value
|
|
156
|
+
if @input.readonly
|
|
157
|
+
remove_attribute :searchable
|
|
158
|
+
else
|
|
159
|
+
self[:searchable] = ''
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Returns if the component is searchable or not
|
|
164
|
+
#
|
|
165
|
+
# @return [Boolean] The value
|
|
166
|
+
def searchable
|
|
167
|
+
!@input.readonly
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Sets the items
|
|
171
|
+
#
|
|
172
|
+
# @param items [Array<Hash>] The items
|
|
173
|
+
def items=(items)
|
|
174
|
+
items = items.each_with_index.map { |item, index| { id: index, value: item } } unless items[0].is_a?(Hash)
|
|
175
|
+
list.items = items
|
|
176
|
+
update_input
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Empties the input
|
|
180
|
+
def empty_input
|
|
181
|
+
return if @input.readonly
|
|
182
|
+
@input.value = ''
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Updates the input after
|
|
186
|
+
# an item has been selected
|
|
187
|
+
def selected_changed
|
|
188
|
+
return if searchable
|
|
189
|
+
update_input
|
|
190
|
+
trigger :change
|
|
191
|
+
@input.blur unless multiple
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Updates the input text with
|
|
195
|
+
# the selected items values
|
|
196
|
+
def update_input
|
|
197
|
+
timeout(320) { show_items }
|
|
198
|
+
@input.value = Array(label).join(', ')
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Shows the items
|
|
202
|
+
def show_items
|
|
203
|
+
list.children.each { |item| item.remove_class :hidden }
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Filters the items by the input label
|
|
207
|
+
def filter
|
|
208
|
+
list.children.each do |item|
|
|
209
|
+
item.toggle_class :hidden, !(item.label =~ Regexp.new(@input.value, 'i'))
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Returns the value
|
|
214
|
+
#
|
|
215
|
+
# @return The value
|
|
216
|
+
def value
|
|
217
|
+
selected_items(:value)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def selected_items(property)
|
|
221
|
+
selected = list.selected
|
|
222
|
+
return nil unless selected
|
|
223
|
+
if selected.is_a?(Array)
|
|
224
|
+
return nil if selected.empty?
|
|
225
|
+
selected.map { |item| item.send(property) }
|
|
226
|
+
else
|
|
227
|
+
selected.send(property)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def label
|
|
232
|
+
selected_items(:label)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def value=(new_value)
|
|
236
|
+
return if attribute?(:focused)
|
|
237
|
+
values = Array(new_value)
|
|
238
|
+
return if Array(value) == values
|
|
239
|
+
list.deselect
|
|
240
|
+
values.each do |val|
|
|
241
|
+
list.select list.children.find { |child| child.value == val }
|
|
242
|
+
end
|
|
243
|
+
update_input
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
require 'fron-ui/components/container'
|
|
2
|
+
require 'fron-ui/components/drag'
|
|
3
|
+
|
|
4
|
+
require 'native'
|
|
5
|
+
|
|
6
|
+
module Color
|
|
7
|
+
class RGB
|
|
8
|
+
def css_rgba(alpha)
|
|
9
|
+
alpha = format('%.1f', alpha)
|
|
10
|
+
"rgba(#{red.round},#{green.round},#{blue.round},#{alpha})"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module UI
|
|
16
|
+
# Color selector component with a draggable field for
|
|
17
|
+
# selecting the saturation / value and
|
|
18
|
+
# a slider for selecting hue.
|
|
19
|
+
#
|
|
20
|
+
# @attr_reader [Integer] hue The hue of the represented color
|
|
21
|
+
# @attr_reader [Integer] value The value of the represented color
|
|
22
|
+
# @attr_reader [Integer] saturation The saturation of the represented color
|
|
23
|
+
#
|
|
24
|
+
# @author Gusztáv Szikszai
|
|
25
|
+
# @since 0.1.0
|
|
26
|
+
class ColorPanel < UI::Container
|
|
27
|
+
extend Forwardable
|
|
28
|
+
|
|
29
|
+
CHECKER = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wkUCDAEK+8FsQAAADNJREFUKM9jPHPmDAM2YGxsjFWciYFEMKqBGMD4//9/rBJnz54d8p7G5TecGhgZGQfIDwBAQAuAAa2FpwAAAABJRU5ErkJggg=='
|
|
30
|
+
|
|
31
|
+
tag 'ui-color-wheel'
|
|
32
|
+
|
|
33
|
+
defaults direction: :column
|
|
34
|
+
|
|
35
|
+
style borderRadius: -> { theme.border_radius.em },
|
|
36
|
+
padding: -> { (theme.spacing * 2).em },
|
|
37
|
+
background: -> { colors.input },
|
|
38
|
+
'ui-drag' => { boxShadow: '0 0 1px 1px rgba(0,0,0,0.2) inset',
|
|
39
|
+
cursor: :pointer },
|
|
40
|
+
'> ui-drag' => {
|
|
41
|
+
backgroundPosition: :center,
|
|
42
|
+
height: 1.2.em,
|
|
43
|
+
'ui-drag-handle' => { transform: 'translateX(-50%)',
|
|
44
|
+
borderRadius: 0.5.em,
|
|
45
|
+
top: -0.25.em,
|
|
46
|
+
bottom: -0.25.em,
|
|
47
|
+
width: 0.5.em,
|
|
48
|
+
height: 'auto' }
|
|
49
|
+
},
|
|
50
|
+
'ui-container' => {
|
|
51
|
+
'ui-drag:first-of-type' => { backgroundImage: 'linear-gradient(0deg, #000 0%, transparent 100%),
|
|
52
|
+
linear-gradient(90deg, #fff 0%, rgba(255,255,255,0) 100%)',
|
|
53
|
+
width: 14.em,
|
|
54
|
+
height: 14.em },
|
|
55
|
+
'ui-drag:last-of-type' => { backgroundImage: 'linear-gradient(to bottom, #ff0000 0%, #ffff00 17%, #00ff00 33%,
|
|
56
|
+
#00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%)',
|
|
57
|
+
width: 1.2.em,
|
|
58
|
+
'ui-drag-handle' => { transform: 'translateY(-50%)',
|
|
59
|
+
borderRadius: 0.5.em,
|
|
60
|
+
right: -0.25.em,
|
|
61
|
+
left: -0.25.em,
|
|
62
|
+
height: 0.5.em,
|
|
63
|
+
width: 'auto' } }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
component :container, UI::Container, direction: :row do
|
|
67
|
+
component :recta, UI::Drag
|
|
68
|
+
component :hued, UI::Drag, horizontal: false
|
|
69
|
+
end
|
|
70
|
+
component :alphad, UI::Drag, vertical: false
|
|
71
|
+
|
|
72
|
+
on :change, 'ui-drag', :change
|
|
73
|
+
on :mousedown, :stop
|
|
74
|
+
|
|
75
|
+
def_delegators :container, :recta, :hued
|
|
76
|
+
|
|
77
|
+
attr_reader :hue, :saturation, :value
|
|
78
|
+
|
|
79
|
+
# Initializes the component with default
|
|
80
|
+
# values for hue, value and saturation
|
|
81
|
+
def initialize
|
|
82
|
+
super
|
|
83
|
+
|
|
84
|
+
@hue = 0
|
|
85
|
+
@value = 100
|
|
86
|
+
@saturation = 0
|
|
87
|
+
@alpha = 0
|
|
88
|
+
|
|
89
|
+
render
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def stop(event)
|
|
93
|
+
event.stop
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Handles changes from the fields, sets the
|
|
97
|
+
# hue, value and saturation from on the fields.
|
|
98
|
+
#
|
|
99
|
+
# @param event [DOM::Event] The event
|
|
100
|
+
def change(event)
|
|
101
|
+
event.stop
|
|
102
|
+
hue = hued.value_y * 360
|
|
103
|
+
value = (1 - recta.value_y) * 100
|
|
104
|
+
saturation = recta.value_x * 100
|
|
105
|
+
alpha = @alphad.value_x * 100
|
|
106
|
+
return if hue == @hue &&
|
|
107
|
+
value == @value &&
|
|
108
|
+
saturation == @saturation &&
|
|
109
|
+
alpha == @alpha
|
|
110
|
+
@hue = hue
|
|
111
|
+
@value = value
|
|
112
|
+
@saturation = saturation
|
|
113
|
+
@alpha = alpha
|
|
114
|
+
render
|
|
115
|
+
trigger :change
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Sets the hue to the given value
|
|
119
|
+
#
|
|
120
|
+
# @param value [Integer] Integer between 0 and 360
|
|
121
|
+
def hue=(value)
|
|
122
|
+
@hue = value
|
|
123
|
+
hued.value_y = value / 360
|
|
124
|
+
render
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Sets the saturation to the given value
|
|
128
|
+
#
|
|
129
|
+
# @param value [Integer] Integer between 0 and 100
|
|
130
|
+
def saturation=(value)
|
|
131
|
+
@saturation = value
|
|
132
|
+
recta.value_x = value / 100
|
|
133
|
+
render
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Sets the value to the given value
|
|
137
|
+
#
|
|
138
|
+
# @param value [Integer] Integer between 0 and 100
|
|
139
|
+
def value=(value)
|
|
140
|
+
@value = value
|
|
141
|
+
recta.value_y = (100 - value) / 100
|
|
142
|
+
render
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def alpha=(value)
|
|
146
|
+
@alpha = value
|
|
147
|
+
@alphad.value_x = value / 100
|
|
148
|
+
render
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def to_css
|
|
152
|
+
if @alpha.round == 100
|
|
153
|
+
'#' + color.hex.upcase
|
|
154
|
+
else
|
|
155
|
+
color.css_rgba(@alpha / 100)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
private
|
|
160
|
+
|
|
161
|
+
# Returns the color
|
|
162
|
+
#
|
|
163
|
+
# :reek:UncommunicativeVariableName
|
|
164
|
+
#
|
|
165
|
+
# @return [Color::RGB] The color
|
|
166
|
+
def color
|
|
167
|
+
s = saturation / 100
|
|
168
|
+
v = value / 100
|
|
169
|
+
h = hue
|
|
170
|
+
|
|
171
|
+
c = v * s
|
|
172
|
+
x = c * (1 - ((h / 60).modulo(2) - 1).abs)
|
|
173
|
+
m = v - c
|
|
174
|
+
|
|
175
|
+
r, g, b = if (0...60).cover?(h)
|
|
176
|
+
[c, x, 0]
|
|
177
|
+
elsif (60...120).cover?(h)
|
|
178
|
+
[x, c, 0]
|
|
179
|
+
elsif (120...180).cover?(h)
|
|
180
|
+
[0, c, x]
|
|
181
|
+
elsif (180...240).cover?(h)
|
|
182
|
+
[0, x, c]
|
|
183
|
+
elsif (240...300).cover?(h)
|
|
184
|
+
[x, 0, c]
|
|
185
|
+
elsif (300..360).cover?(h)
|
|
186
|
+
[c, 0, x]
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
Color::RGB.from_fraction(r + m, g + m, b + m)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Sets the color from a string
|
|
193
|
+
#
|
|
194
|
+
# :reek:UncommunicativeVariableName
|
|
195
|
+
#
|
|
196
|
+
# @param color [String] The hex or CSS representation
|
|
197
|
+
def color=(color)
|
|
198
|
+
if (md = color.match(/rgba\((\d{1,3}),(\d{1,3}),(\d{1,3}),(\d+\.\d+)\)/))
|
|
199
|
+
color = Color::RGB.new(md[1].to_i, md[2].to_i, md[3].to_i)
|
|
200
|
+
alpha = md[4].to_f * 100
|
|
201
|
+
else
|
|
202
|
+
alpha = 100
|
|
203
|
+
end
|
|
204
|
+
color = Color::RGB.by_css(color) unless color.is_a? Color
|
|
205
|
+
rgb = color.to_rgb.to_a
|
|
206
|
+
r, g, b = rgb
|
|
207
|
+
|
|
208
|
+
cmax = rgb.max
|
|
209
|
+
cmin = rgb.min
|
|
210
|
+
|
|
211
|
+
delta = cmax - cmin
|
|
212
|
+
|
|
213
|
+
self.hue = if delta == 0
|
|
214
|
+
0
|
|
215
|
+
elsif cmax == r
|
|
216
|
+
60 * ((g - b) / delta).modulo(6)
|
|
217
|
+
elsif cmax == g
|
|
218
|
+
60 * (((b - r) / delta) + 2)
|
|
219
|
+
elsif cmax == b
|
|
220
|
+
60 * (((r - g) / delta) + 4)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
self.saturation = (cmax == 0 ? 0 : delta / cmax) * 100
|
|
224
|
+
self.value = cmax * 100
|
|
225
|
+
self.alpha = alpha
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Renders the component
|
|
229
|
+
def render
|
|
230
|
+
rgb = "#{color.red.round}, #{color.green.round}, #{color.blue.round}"
|
|
231
|
+
alphad.style.background = "linear-gradient(90deg, rgba(#{rgb},0), rgba(#{rgb},1)), url(#{CHECKER})"
|
|
232
|
+
recta.style.backgroundColor = "hsl(#{hue}, 100%, 50%)"
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|