UNPKG

oda-framework

Version:

It's an ES Progressive Framework based on the technology of Web Components and designed especially for creating custom UI/UX of any complexity for web and cross-platform PWA mobile applications.

475 lines (474 loc) 21.3 kB
let Picker = function(options) { this.settings = { parent: undefined, orientation: 'right', x: 'auto', y: 'auto' }; if (options instanceof HTMLElement) { this.settings.parent = options; } else { for (let name in options) { this.settings[name] = options[name]; } } this.sliders = { 'picker_selector': { down: false }, 'picker_hue': { down: false, vertical: true }, 'picker_opacity': { down: false, vertical: true }, }; this.colour = this.color = { hue: 0, saturation: 1, value: 1, alpha: 1, hsl: function() { let h = this.hue; let l = (2 - this.saturation) * this.value; let s = this.saturation * this.value; s /= l <= 1 ? l : 2 - l; l /= 2; s *= 100; l *= 100; return { h: Math.round(h || 0), s: Math.round(s || 0), l: Math.round(l), toString: function() { return 'hsl(' + this.h + ', ' + this.s + '%, ' + this.l + '%)'; } }; }, hsla: function() { let hsl = this.hsl(); hsl.a = this.alpha.toFixed(2); hsl.toString = function() { return 'hsla(' + this.h + ', ' + this.s + '%, ' + this.l + '%, ' + this.a + ')'; }; return hsl; }, rgb: function() { let r, g, b; let h = this.hue; let s = this.saturation; let v = this.value; h /= 60; let i = Math.floor(h); let f = h - i; let p = v * (1 - s); let q = v * (1 - s * f); let t = v * (1 - s * (1 - f)); r = [v, q, p, p, t, v][i]; g = [t, v, v, q, p, p][i]; b = [p, p, t, v, v, q][i]; return { r: Math.floor(r * 255), g: Math.floor(g * 255), b: Math.floor(b * 255), toString: function() { return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')'; } }; }, rgba: function() { let rgb = this.rgb() rgb.a = this.alpha; rgb.toString = function() { return 'rgba(' + this.r + ', ' + this.g + ', ' + this.b + ', ' + this.a + ')'; }; return rgb; }, hex: function() { let rgb = this.rgb(); function to_hex(c) { let hex = c.toString(16); return hex.length == 1 ? '0' + hex : hex; } return { r: to_hex(rgb.r), g: to_hex(rgb.g), b: to_hex(rgb.b), toString: function() { return '#' + this.r + this.g + this.b; } } } }; this.on_done = null; this.on_change = null; }; Picker.prototype.css = { wrapper: { selector: '#picker_wrapper', zIndex: 99, background: '#f2f2f2', position: 'absolute', whiteSpace: 'nowrap', padding: '10px', cursor: 'default', fontFamily: 'sans-serif', fontWeight: '100', display: 'inline-block', boxShadow: '0 0 10px 1px rgba(0,0,0,0.4)', overflow: 'visible', textAlign: 'left', fontSize: '16px' }, colour_picker: { selector: '#picker_selector', width: '180px', height: '150px', position: 'relative', background: 'hsl(0, 100%, 50%)', display: 'inline-block', border: '1px solid #ccc' }, saturation_overlay: { selector: '#picker_saturation', width: '180px', height: '150px', position: 'absolute', background: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAACWCAYAAAB3qaIPAAADB0lEQVR4Ae3SQQrCMABE0cb7n9nYIgUXIW7ExecJUkmlOjNvzDmfx3HM831fPz/fZ/d1dW91tvv+7t4vn/Wv3/Gf335WPazOdrvs7n191hhjPk7IXhrINAB0ZkpBrgaA5iDVANCpOYUBmoFUA0Cn5hQGaAZSDQCdmlMYoBlINQB0ak5hgGYg1QDQqTmFAZqBVANAp+YUBmgGUg0AnZpTGKAZSDUAdGpOYYBmINUA0Kk5hQGagVQDQKfmFAZoBlINAJ2aUxigGUg1AHRqTmGAZiDVANCpOYUBmoFUA0Cn5hQGaAZSDQCdmlMYoBlINQB0ak5hgGYg1QDQqTmFAZqBVANAp+YUBmgGUg0AnZpTGKAZSDUAdGpOYYBmINUA0Kk5hQGagVQDQKfmFAZoBlINAJ2aUxigGUg1AHRqTmGAZiDVANCpOYUBmoFUA0Cn5hQGaAZSDQCdmlMYoBlINQB0ak5hgGYg1QDQqTmFAZqBVANAp+YUBmgGUg0AnZpTGKAZSDUAdGpOYYBmINUA0Kk5hQGagVQDQKfmFAZoBlINAJ2aUxigGUg1AHRqTmGAZiDVANCpOYUBmoFUA0Cn5hQGaAZSDQCdmlMYoBlINQB0ak5hgGYg1QDQqTmFAZqBVANAp+YUBmgGUg0AnZpTGKAZSDUAdGpOYYBmINUA0Kk5hQGagVQDQKfmFAZoBlINAJ2aUxigGUg1AHRqTmGAZiDVANCpOYUBmoFUA0Cn5hQGaAZSDQCdmlMYoBlINQB0ak5hgGYg1QDQqTmFAZqBVANAp+YUBmgGUg0AnZpTGKAZSDUAdGpOYYBmINUA0Kk5hQGagVQDQKfmFAZoBlINAJ2aUxigGUg1AHRqTmGAZiDVANCpOYUBmoFUA0Cn5hQGaAZSDQCdmlMYoBlINQB0ak5hgGYg1QDQqTmFAZqBVANAp+YUBmgGUg0AnZpTGKAZSDUAdGpOYYBmINUA0Kk5hQGagVQDQKfmFAZoBlINAJ2aUxigGUg1AHRqTmGAZiDVANCpOYUBmoFUAy+AUbcs6wwU4wAAAABJRU5ErkJggg==)' }, value_overlay: { selector: '#picker_value', width: '180px', height: '150px', position: 'absolute', background: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAACWCAYAAAB3qaIPAAADFUlEQVR4Ae3WgQ3AMAzDsHbY/ycPa95QmQ9kC0bWchIIJbAPyxPigXJ5Au/hH6mdBBIJjNAWOlEliEmA0DxIJeDlSNUJxkJzIJUAoVN1giE0B1IJ+KFTdYKx0BxIJUDoVJ1gvBwcSCVgoVN1giE0B1IJeDlSdYKx0BxIJUDoVJ1gCM2BVAJ+6FSdYCw0B1IJEDpVJxgvBwdSCVjoVJ1gCM2BVAKETtUJxg/NgVQCFjpVJxhCcyCVgJcjVScYC82BVAKETtUJxsvBgVQCFjpVJxhCcyCVAKFTdYLxQ3MglYCFTtUJhtAcSCXg5UjVCcZCcyCVAKFTdYIhNAdSCfihU3WCsdAcSCVA6FSdYLwcHEglYKFTdYIhNAdSCXg5UnWCsdAcSCVA6FSdYAjNgVQCfuhUnWAsNAdSCRA6VScYLwcHUglY6FSdYAjNgVQChE7VCcYPzYFUAhY6VScYQnMglYCXI1UnGAvNgVQChE7VCcbLwYFUAhY6VScYQnMglQChU3WC8UNzIJWAhU7VCYbQHEgl4OVI1QnGQnMglQChU3WCITQHUgn4oVN1grHQHEglQOhUnWC8HBxIJWChU3WCITQHUgkQOlUnGD80B1IJWOhUnWAIzYFUAl6OVJ1gLDQHUgkQOlUnGC8HB1IJWOhUnWAIzYFUAoRO1QnGD82BVAIWOlUnGEJzIJWAlyNVJxgLzYFUAoRO1QmG0BxIJeCHTtUJxkJzIJUAoVN1gvFycCCVgIVO1QmG0BxIJeDlSNUJxkJzIJUAoVN1giE0B1IJ+KFTdYKx0BxIJUDoVJ1gvBwcSCVgoVN1giE0B1IJEDpVJxg/NAdSCVjoVJ1gCM2BVAJejlSdYCw0B1IJEDpVJxgvBwdSCVjoVJ1gCM2BVAKETtUJxg/NgVQCFjpVJxhCcyCVgJcjVScYC82BVAKETtUJhtAcSCXgh07VCcZCcyCVAKFTdYLxcnAglYCFTtUJhtAcSCXg5UjVCcZCcyCVAKFTdYIhNAdSCfihU3WCsdAcSCVA6FSdYLwcHEglsA/NlyICc3UCP/PSBEmeyvh+AAAAAElFTkSuQmCC)' }, hue_slider: { selector: '#picker_hue', width: '16px', height: '150px', position: 'relative', display: 'inline-block', marginLeft: '10px', background: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAACWCAYAAADe8ajeAAABUklEQVRoBe2ZwQ4CMQhEIXJR//9T1biXyp1DJT4SzOKxRcLM0AJdXWJLwJ+J3EF3IhUOb+0jxDkcyD9qXpGHJ1R58nDyMMtA/6OnS95oGdVDFurQnlnWN/bjcEPQF9vD4RckbUzssTHIbtsr+4+NPa9yf8j9I8Tvw/6Q8QhxDvGj1x6yCl3o5bq5j5LbJmzP7hP9OExqEMwLOGSHUVe5fx72h9w/Qvws94eMR4hziB+9/pBxDmGHKgc70ausg30iEGHbJX+3wR2yLWdFhAM5dCu5BRdlVM5RFqwrEntECTTnFipEmdsmp0Gw9jLKfgFXd8fWZbgTEevvEO43xeCOuIDD/pDxCPE8xFXuDxnncByGGpFd+IMLdlTOihrs1ed5ttDDc4+/wbLtoTuEv9DYGSHjorCTWYXK/SHjHI7DcGMmFwouhxElqUEwP6Eoui5sof8A9nlhGxrI9UUAAAAASUVORK5CYII=)', border: '1px solid #ccc' }, opacity_slider: { selector: '#picker_opacity', width: '16px', height: '150px', position: 'relative', display: 'inline-block', marginLeft: '10px', background: '#f00', border: '1px solid #ccc' }, opacity_slider_overlay: { selector: '#picker_opacity_fade', width: '16px', height: '150px', display: 'inline-block', position: 'absolute', background: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAACWCAYAAADXGgikAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEBklEQVRo3q1ay6HUQAyz8+ZKIZzTAn3QB4VQB4Vs6qAMcdnAZCLJDvAu7C/J2JZl2SbiH//y9XptEYGIyOU7RETu+36+/n3N/KMREdt8wfR6Izecb4KIyDFdlPMX7LTs/SBPSnKiUJ+N6cnsByCnwmzGfIIUtsb7NyCm5Ji+TOE8a1oC+LR6dr7qOA4WiWRhzOX4QT5fT3uLQhbHjyXswXwQBR4uETrDyFDHbnxzKDMB6zGJCWBhjAVUYVCYDIkUaeLzP1A+jmMzjjvTeUXjDYlVGOX3w0AXDbMoDlhOQEVoTed05NExAQ0/RIdQIIAT1QkqE26ITQCf3RHffMCSjZoQDW6MlQ8+TJzX9zduZCeQ9jLOHAYwsVyEpYpRVmY3mr/P9bVyYkUol2zMZjmjp5z5gD5p33eJAcaJigMoBhQnuvIOVRc6hEL9MIqnsahQEyoOhArlMNB1Ja/0wWYKClY++NLgg+gwEtRTHEodobBaeaP/lVC6SiVVMkVhRiqBoRCXTmApgRFP8mM0dKGq2hc+kJS277ut0KtCUeShkqtd2qT4GELiZcEPti4owZEMZMPJWJInsmfCw3bngkQnacOgFGe/8NVx4XEccJSnkKhQaBuOyg+SD1J0JQqByZxoK7BLtlHEutSNQ3BAGL14A9KH6gUa72Mcx5GCNLH0CzSx5qYLBtZQ7fHpxFRQrUxhhJLN7sUiURUSKnU3Qxhp/LGp2pgPkivVEKb7hwTwTdTDWR+w6Yal9c3goCWyUDSkpcgKw4PJkKgycCP9wvyvHURV0t/KPBiVEqo640GF2li/QEG08EEyTswH1RhOI3U54BKlihNL7TTMILKTXBhmglOVtmCVCcXR0RVZ7SlOAvjumsxJH1Az3TRPlfoLKkdB5VmZ4wbT2UBmVIPprDJzLW0VH9zgrhQKuppZlbatMZCLkw8UlZ/9guKC7CCxzM5hMIAGT/zuG13GKQFWFpaWOcOM/lsspfQBTCNGFYpzVLobJIAf7pjTPFHOVDvduwx1NUuruDI7kyyl3qGKa2dNdBuJKhS6rnZjk6xK5ubf+kDO1mZ9QLXye35QjkSr8WeKzJRD2apCJ+udwwzpU62RHBLdlD+dWmeDJygtpZBYLTDL1tcl1WVgmQBe/2OeiILWW41nmIZLNuTdjWd0mq54aEY4jdR5OpQTO51re8fSmh/YbHzrAxkN1y+EUCqPFxSWI7ubrnDDOCUwWKmXPqiOmx2p+2g1Us3SWANOt18J4KeZI618cPuNS6bWfyUYZobmeDHCLOuyGAtt3TVRq9SpdG6lspsrpwkdlboqpcuV+tkvyE3PtF+IipXDVGQbxq1oOl2pt7Wxsl/uF8L1yswHGX4h5fYL+Qv+btsS2cFCRgAAAABJRU5ErkJggg==)' }, colour_selector: { selector: '.picker_selector', width: '10px', height: '10px', position: 'absolute', display: 'inline-block', borderRadius: '20px', cursor: 'pointer', border: '2px solid #fff', boxShadow: '0 0 3px 1px #67b9ff', background: '#f00', left: '173px', top: '-7px' }, slider_bar: { selector: '.picker_slider_bar', width: '100%', height: '10px', position: 'absolute', top: '-7px', borderRadius: '2px', cursor: 'pointer', border: '2px solid #fff', boxShadow: '0 0 3px 1px #67b9ff', marginLeft: '-2px', background: '#f00', fontSize: '16px' }, sample: { selector: '#picker_sample', width: '180px', height: '24px', background: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAIUlEQVR42mM4c+bMf3yYEGAYNWBYGEBIASELRg0YFgYAAMoUr64OfmpAAAAAAElFTkSuQmCC)', display: 'inline-block', position: 'relative', marginTop: '10px', border: '1px solid #aaa', }, sample_colour: { selector: '#picker_sample_colour', width: '100%', height: '100%', background: 'rgba(255,0,0,0.4)', position: 'absolute', }, done_button: { selector: '#picker_done', width: '54px', height: '22px', lineHeight: '22px', background: '#e2e2e2', display: 'inline-block', border: '1px solid #ccc', marginLeft: '10px', textAlign: 'center', color: '#777', position: 'absolute', right: '7px', bottom: '11px', cursor: 'pointer', boxShadow: '0 0 3px 1px #eee' }, sample_value: { selector: '#picker_sample_value', width: '180px', height: '18px', display: 'inline-block', position: 'relative', marginTop: '10px', border: '1px solid #aaa', fontSize: '14px' }, copy_button: { selector: '#picker_sample_copy', width: '54px', height: '22px', lineHeight: '22px', background: '#e2e2e2', display: 'inline-block', border: '1px solid #ccc', marginLeft: '10px', textAlign: 'center', color: '#777', position: 'absolute', right: '7px', bottom: '44px', cursor: 'pointer', boxShadow: '0 0 3px 1px #eee' } }; Picker.prototype.apply_style = function() { for (let name in this.css) { let element = this.css[name]; let tags = this._shadow.querySelectorAll(element.selector); if (!tags.length) continue; let i = tags.length; while (i--) { let tag = tags[i]; for (let name in element) { if (name == 'selector') continue; let property = element[name]; tag.style[name] = property; } } } }; Picker.prototype.show = function(color) { let wrapper = this._shadow.getElementById('picker_wrapper'); if (wrapper) { wrapper.style.display = 'inline-block'; //return; } let html = ` <div id="picker_wrapper"> <div id="picker_selector"> <div id="picker_saturation"></div> <div id="picker_value"></div> <div class="picker_selector"></div> </div> <div id="picker_hue" class="picker_slider"> <div class="picker_slider_bar"></div> </div> <div id="picker_opacity" class="picker_slider"> <div id="picker_opacity_fade"></div> <div class="picker_slider_bar"></div> </div> <br> <div> <div id="picker_sample_value"></div> <div id="picker_sample_copy">copy</div> </div> <div id="picker_sample"> <div id="picker_sample_colour"></div> </div> <div id="picker_done">ok</div> </div> ` let parent = this.settings.parent; if (parent.style.position != 'absolute') { parent.style.position = 'relative'; } parent.innerHTML = html; this.apply_style(); this.bind_events(); let h, l, s, a; if (color) { let _color = color.replace('hsla(', '').replace(/%/g, '').replace(')', '').split(',') h = Number(_color[0]); s = Number(_color[1]) / 100; l = (Number(_color[2]) / 100) * 2; a = Number(_color[3]); s = l <= 1 ? s * l : s * (2 - l); this.color.hue = h; this.color.saturation = 2 * s / (l + s); this.color.saturation = this.color.saturation > 1 ? 1 : this.color.saturation; this.color.value = s / this.color.saturation; this.color.value = this.color.value > 1 ? 1 : this.color.value; this.color.alpha = a; let hue_select = this._shadow.getElementById('picker_hue'); let height = Number(hue_select.style.height.replace('px', '')); let hue_slider = this._shadow.querySelector('#picker_hue .picker_slider_bar'); hue_slider.style.top = Math.round(height - (height / 360 * this.color.hue)) + 'px'; let opacity_select = this._shadow.getElementById('picker_opacity'); height = Number(opacity_select.style.height.replace('px', '')); let opacity_slider = this._shadow.querySelector('#picker_opacity .picker_slider_bar'); opacity_slider.style.top = Math.round(height - (height * a)) + 'px'; let colour_select = this._shadow.getElementById('picker_selector'); colour_select.style.background = 'hsl(' + this.colour.hue + ', 100%, 50%)'; } this.update_sample(color); this.update_picker_slider(color ? `hsl(${h}, ${l * 200}%, ${s * 100}%)` : ''); this.update_hue_slider(); this.update_opacity_slider(); }; Picker.prototype.hide = function() { let element = this._shadow.getElementById('picker_wrapper'); if (element) { element.style.display = 'none'; } }; Picker.prototype.update_picker_selector = function(element, x, y) { this.colour.saturation = x / (element.offsetWidth - 2); this.colour.value = 1 - y / (element.offsetHeight - 2); this.update_opacity_slider(); this.update_sample(); this.update_picker_slider(); }; Picker.prototype.update_picker_hue = function(element, x, y) { this.colour.hue = (1 - y / (element.offsetHeight - 2)) * 360; this.update_selector_hue(); this.update_sample(); this.update_hue_slider(); }; Picker.prototype.update_picker_opacity = function(element, x, y) { this.colour.alpha = 1 - y / (element.offsetHeight - 2); this.update_sample(); this.update_opacity_slider(); }; Picker.prototype.update_sample = function(color) { let sample = this._shadow.getElementById('picker_sample_colour'); sample.style.background = color || this.colour.hsla().toString(); let value = this._shadow.getElementById('picker_sample_value'); value.innerHTML = color || this.colour.hsla().toString(); this.update_opacity_hue(); if (this.on_change) { this.on_change(this.colour); } }; Picker.prototype.update_selector_hue = function() { let picker = this._shadow.getElementById('picker_selector'); picker.style.background = 'hsl(' + this.colour.hue + ', 100%, 50%)'; this.update_picker_slider(); this.update_opacity_slider(); }; Picker.prototype.update_opacity_hue = function() { let picker = this._shadow.getElementById('picker_opacity'); picker.style.background = this.colour.hsl().toString(); }; Picker.prototype.update_picker_slider = function(color) { let slider = this._shadow.querySelector('#picker_selector .picker_selector'); slider.style.background = color || this.colour.hsl().toString(); }; Picker.prototype.update_hue_slider = function() { let slider = this._shadow.querySelector('#picker_hue .picker_slider_bar'); slider.style.background = 'hsl(' + this.colour.hue + ', 100%, 50%)'; }; Picker.prototype.update_opacity_slider = function() { let slider = this._shadow.querySelector('#picker_opacity .picker_slider_bar'); slider.style.background = this.colour.hsla().toString(); }; Picker.prototype.mouse_move = function(e, element, _this, override) { let rect = element.getBoundingClientRect(); let x = e.clientX - rect.left; let y = e.clientY - rect.top; if (override || (_this.sliders[element.id] && _this.sliders[element.id].down)) { let slider_info = _this.sliders[element.id]; let slider = element.querySelectorAll('.picker_selector')[0] || element.querySelectorAll('.picker_slider_bar')[0]; if (!slider) return; if (!slider_info.vertical) { x = Math.min(Math.max(x - slider.offsetWidth / 2, -(slider.offsetWidth / 2)), element.offsetWidth - slider.offsetWidth / 2 - 2); slider.style.left = x + 'px'; } y = Math.min(Math.max(y - slider.offsetHeight / 2, -(slider.offsetHeight / 2)), element.offsetHeight - slider.offsetHeight / 2 - 2); slider.style.top = y + 'px'; if (_this['update_' + element.id]) { _this['update_' + element.id](element, x + slider.offsetWidth / 2, y + slider.offsetHeight / 2); } } }; Picker.prototype.bind_events = function() { let wrapper = this._shadow.getElementById('picker_wrapper'); let done = this._shadow.getElementById('picker_done'); let copy = this._shadow.getElementById('picker_sample_copy'); let value = this._shadow.getElementById('picker_sample_value'); let colour_select = this._shadow.getElementById('picker_selector'); let hue_select = this._shadow.getElementById('picker_hue'); let opacity_select = this._shadow.getElementById('picker_opacity'); let picker_slider = this._shadow.querySelector('#picker_selector .picker_selector'); let hue_slider = this._shadow.querySelector('#picker_hue .picker_slider_bar'); let opacity_slider = this._shadow.querySelector('#picker_opacity .picker_slider_bar'); let _this = this; colour_select.onmousemove = hue_select.onmousemove = opacity_select.onmousemove = function(e) { _this.mouse_move(e, this, _this); e.preventDefault(); }; colour_select.onmousedown = hue_select.onmousedown = opacity_select.onmousedown = function(e) { _this.sliders[this.id].down = true; _this.mouse_move(e, this, _this, true); e.preventDefault(); }; picker_slider.onmousedown = hue_slider.onmousedown = opacity_slider.onmousedown = function() { _this.sliders[this.parentNode.id].down = true; }; wrapper.onclick = wrapper.onmousedown = function(e) { e.stopPropagation(); e.preventDefault(); return false; }; this._shadow.ownerDocument.onmouseup = function() { for (let name in _this.sliders) { _this.sliders[name].down = false; } }; document.onclick = function(e) { if (this._parent && this._parent != _this.settings.parent) { _this.hide(); } }; done.onclick = function() { _this.done(); }; copy.onclick = async () => { await navigator.clipboard.writeText(value.innerHTML); }; }; Picker.prototype.done = function() { this.hide(); if (this.on_done) { this.on_done(this.colour); } }; export default Picker;