diff --git a/README.md b/README.md index c95ab55..0b217f2 100644 --- a/README.md +++ b/README.md @@ -43,12 +43,12 @@ var control = require('control-panel') var panel = control([ {type: 'range', label: 'my range', min: 0, max: 100, initial: 20}, {type: 'range', label: 'log range', min: 0.1, max: 100, initial: 20, scale: 'log'}, - {type: 'text', label: 'my text', initial: 'my cool setting'}, + {type: 'text', label: 'my text', initial: 'my cool setting', help: 'why this is cool'}, {type: 'checkbox', label: 'my checkbox', initial: true}, - {type: 'color', label: 'my color', format: 'rgb', initial: 'rgb(10,200,0)'}, + {type: 'color', label: 'my color', format: 'rgb', initial: 'rgb(10,200,0)', input: function (value) {console.log(value);}}, {type: 'button', label: 'gimme an alert', action: function () {alert('hello!');}}, {type: 'select', label: 'select one', options: ['option 1', 'option 2'], initial: 'option 1'} -], +], {theme: 'light', position: 'top-right'} ) ``` @@ -57,13 +57,13 @@ var panel = control([ #### `panel = control([input1, input2, ...], [opts])` -The first argument is a list of inputs. Each one must have a `type` and `label` property, and can have an `initial` property with an initial value. For example, +The first argument is a list of inputs. Each one must have a `type`, `label` and `help` property, and can have an `initial` property with an initial value. Also it may have an `input` callback, which will be invoked if value changed. For example, ```javascript -{type: 'checkbox', label: 'my checkbox', initial: true} +{type: 'checkbox', label: 'my checkbox', initial: true, input: function (value) {}} ``` -Each `type` must be one of `range` • `input` • `checkbox` • `color` • `interval` • `select`. Each `label` must be unique. +Each `type` must be one of `range` • `input` • `checkbox` • `color` • `interval` • `select`. Each `label` must be unique. Some types have additional properties: - Inputs of type `range` can specify a `min`, `max`, and `step` (or integer `steps`). Scale can be either `'linear'` (default) or `'log'`. If a log scale, the sign of `min`, `max`, and `initial` must be the same and only `steps` is permitted (since the step size is not constant on a log scale). diff --git a/components/button.js b/components/button.js index ffcb861..4ee9c7d 100644 --- a/components/button.js +++ b/components/button.js @@ -8,7 +8,7 @@ inherits(Button, EventEmitter) function Button (root, opts, theme, uuid) { if (!(this instanceof Button)) return new Button(root, opts, theme, uuid) - var container = require('./container')(root, opts.label) + var container = require('./container')(root, opts.label, opts.help) require('./label')(container, '', theme) var input = container.appendChild(document.createElement('button')) @@ -23,8 +23,8 @@ function Button (root, opts, theme, uuid) { css(input, { position: 'absolute', textAlign: 'center', - height: '20px', - width: '62%', + height: '2em', + width: '64%', border: 'none', cursor: 'pointer', right: 0, diff --git a/components/checkbox.js b/components/checkbox.js index 29d2470..98daa07 100644 --- a/components/checkbox.js +++ b/components/checkbox.js @@ -1,5 +1,6 @@ var EventEmitter = require('events').EventEmitter var inherits = require('inherits') +var format = require('param-case') module.exports = Checkbox inherits(Checkbox, EventEmitter) @@ -9,17 +10,19 @@ function Checkbox (root, opts, theme, uuid) { opts = opts || {} var self = this - var container = require('./container')(root, opts.label) - require('./label')(container, opts.label, theme) + var id = 'checkbox-' + format(opts.label) + '-' + uuid + + var container = require('./container')(root, opts.label, opts.help) + require('./label')(container, opts.label, theme, id) var input = container.appendChild(document.createElement('input')) - input.id = 'checkbox-' + opts.label + uuid + input.id = id input.type = 'checkbox' input.checked = opts.initial input.className = 'control-panel-checkbox-' + uuid var label = container.appendChild(document.createElement('label')) - label.htmlFor = 'checkbox-' + opts.label + uuid + label.htmlFor = id label.className = 'control-panel-checkbox-' + uuid setTimeout(function () { diff --git a/components/color.js b/components/color.js index 60dc8c5..f516710 100644 --- a/components/color.js +++ b/components/color.js @@ -3,6 +3,7 @@ var ColorPicker = require('simple-color-picker') var inherits = require('inherits') var css = require('dom-css') var tinycolor = require('tinycolor2') +var formatParam = require('param-case') module.exports = Color inherits(Color, EventEmitter) @@ -14,13 +15,25 @@ function Color (root, opts, theme, uuid) { opts.initial = opts.initial || '#123456' var self = this - var container = require('./container')(root, opts.label) - require('./label')(container, opts.label, theme) + var id = 'control-panel-color-value-' + formatParam(opts.label) + '-' + uuid + + var container = require('./container')(root, opts.label, opts.help) + require('./label')(container, opts.label, theme, id) var icon = container.appendChild(document.createElement('span')) + icon.id = 'control-panel-color-' + uuid icon.className = 'control-panel-color-' + uuid - var value = require('./value')(container, '', theme, '46%') + var value = require('./value')(container, { + initial: '', + theme: theme, + width: '50%', + uuid: uuid, + id: id, + change: function (v) { + picker.setColor(v) + } + }) icon.onmouseover = function () { picker.$el.style.display = '' @@ -50,7 +63,7 @@ function Color (root, opts, theme, uuid) { }) css(picker.$el, { - marginTop: '20px', + marginTop: '2em', display: 'none', position: 'absolute' }) @@ -58,8 +71,9 @@ function Color (root, opts, theme, uuid) { css(icon, { position: 'relative', display: 'inline-block', - width: '12.5%', - height: '20px', + verticalAlign: 'top', + width: '13%', + height: '2em', backgroundColor: picker.getHexString() }) @@ -72,7 +86,7 @@ function Color (root, opts, theme, uuid) { }) picker.onChange(function (hex) { - value.innerHTML = format(hex) + value.value = format(hex) css(icon, {backgroundColor: hex}) self.emit('input', format(hex)) }) diff --git a/components/container.js b/components/container.js index e28698f..a77374d 100644 --- a/components/container.js +++ b/components/container.js @@ -1,12 +1,16 @@ var css = require('dom-css') var format = require('param-case') -module.exports = function (root, label) { +module.exports = function (root, label, help) { var container = root.appendChild(document.createElement('div')) container.id = 'control-panel-' + format(label) + container.className = 'control-panel-container' css(container, { position: 'relative', - height: '25px' + minHeight: '2em', + lineHeight: '1.5', + marginBottom: '.5em' }) + if (help) container.setAttribute('data-help', help) return container } diff --git a/components/interval.js b/components/interval.js index ec85c7a..a8fa40d 100644 --- a/components/interval.js +++ b/components/interval.js @@ -2,6 +2,8 @@ var EventEmitter = require('events').EventEmitter var inherits = require('inherits') var isnumeric = require('is-numeric') var css = require('dom-css') +var isMobile = require('is-mobile')() +var format = require('param-case') module.exports = Range inherits(Range, EventEmitter) @@ -15,8 +17,9 @@ function Range (root, opts, theme, uuid) { var self = this var scaleValue, scaleValueInverse, logmin, logmax, logsign, panel, input, handle - var container = require('./container')(root, opts.label) - require('./label')(container, opts.label, theme) + var id = 'control-panel-interval-value-' + format(opts.label) + '-' + uuid + var container = require('./container')(root, opts.label, opts.help) + require('./label')(container, opts.label, theme, id) if (!!opts.step && !!opts.steps) { throw new Error('Cannot specify both step and steps. Got step = ' + opts.step + ', steps = ', opts.steps) @@ -27,6 +30,7 @@ function Range (root, opts, theme, uuid) { }) input = container.appendChild(document.createElement('span')) + input.id = 'control-panel-interval-' + uuid input.className = 'control-panel-interval-' + uuid handle = document.createElement('span') @@ -121,15 +125,29 @@ function Range (root, opts, theme, uuid) { setHandleCSS() // Display the values: - var lValue = require('./value')(container, scaleValue(opts.initial[0]), theme, '11%', true) - var rValue = require('./value')(container, scaleValue(opts.initial[1]), theme, '11%') + var lValue = require('./value')(container, { + initial: scaleValue(opts.initial[0]), + theme: theme, + width: '13%', + type: 'text', + left: true, + id: id, + uuid: uuid + }) + var rValue = require('./value')(container, { + initial: scaleValue(opts.initial[1]), + theme: theme, + width: '13%', + type: 'text', + uuid: uuid + }) // An index to track what's being dragged: var activeIndex = -1 function mouseX (ev) { - // Get mouse position in page coords relative to the container: - return ev.pageX - input.getBoundingClientRect().left + // Get mouse/touch position in page coords relative to the container: + return (ev.touches && ev.touches[0] || ev).pageX - input.getBoundingClientRect().left } function setActiveValue (fraction) { @@ -160,6 +178,8 @@ function Range (root, opts, theme, uuid) { } var mousemoveListener = function (ev) { + if (ev.target === input || ev.target === handle) ev.preventDefault() + var fraction = clamp(mouseX(ev) / input.offsetWidth, 0, 1) setActiveValue(fraction) @@ -167,17 +187,14 @@ function Range (root, opts, theme, uuid) { var mouseupListener = function (ev) { panel.classList.remove('control-panel-interval-dragging') - var fraction = clamp(mouseX(ev) / input.offsetWidth, 0, 1) - - setActiveValue(fraction) - document.removeEventListener('mousemove', mousemoveListener) - document.removeEventListener('mouseup', mouseupListener) + document.removeEventListener(isMobile ? 'touchmove' : 'mousemove', mousemoveListener) + document.removeEventListener(isMobile ? 'touchend' : 'mouseup', mouseupListener) activeIndex = -1 } - input.addEventListener('mousedown', function (ev) { + input.addEventListener(isMobile ? 'touchstart' : 'mousedown', function (ev) { // Tweak control to make dragging experience a little nicer: panel.classList.add('control-panel-interval-dragging') @@ -201,23 +218,23 @@ function Range (root, opts, theme, uuid) { // Attach this to *document* so that we can still drag if the mouse // passes outside the container: - document.addEventListener('mousemove', mousemoveListener) - document.addEventListener('mouseup', mouseupListener) + document.addEventListener(isMobile ? 'touchmove' : 'mousemove', mousemoveListener) + document.addEventListener(isMobile ? 'touchend' : 'mouseup', mouseupListener) }) setTimeout(function () { var scaledLValue = scaleValue(value[0]) var scaledRValue = scaleValue(value[1]) - lValue.innerHTML = scaledLValue - rValue.innerHTML = scaledRValue + lValue.value = scaledLValue + rValue.value = scaledRValue self.emit('initialized', [scaledLValue, scaledRValue]) }) input.oninput = function () { var scaledLValue = scaleValue(value[0]) var scaledRValue = scaleValue(value[1]) - lValue.innerHTML = scaledLValue - rValue.innerHTML = scaledRValue + lValue.value = scaledLValue + rValue.value = scaledRValue self.emit('input', [scaledLValue, scaledRValue]) } } diff --git a/components/label.js b/components/label.js index bf36493..7a5ca99 100644 --- a/components/label.js +++ b/components/label.js @@ -1,12 +1,15 @@ var css = require('dom-css') -module.exports = function (root, text, theme) { - var background = root.appendChild(document.createElement('div')) +module.exports = function (root, text, theme, id) { + var background = root.appendChild(document.createElement('label')) + background.htmlFor = id || text + css(background, { left: 0, width: '36%', + paddingTop: '.45em', display: 'inline-block', - height: '20px', + lineHeight: '1.25', paddingRight: '2%', verticalAlign: 'top' }) @@ -16,7 +19,7 @@ module.exports = function (root, text, theme) { css(label, { color: theme.text1, display: 'inline-block', - verticalAlign: 'sub' + verticalAlign: 'top' }) return label } diff --git a/components/range.js b/components/range.js index ce6688e..9d41ef7 100644 --- a/components/range.js +++ b/components/range.js @@ -2,6 +2,7 @@ var EventEmitter = require('events').EventEmitter var inherits = require('inherits') var isnumeric = require('is-numeric') var css = require('dom-css') +var format = require('param-case') module.exports = Range inherits(Range, EventEmitter) @@ -11,8 +12,10 @@ function Range (root, opts, theme, uuid) { var self = this var scaleValue, scaleValueInverse, logmin, logmax, logsign - var container = require('./container')(root, opts.label) - require('./label')(container, opts.label, theme) + var id = 'control-panel-range-value-' + format(opts.label) + '-' + uuid + + var container = require('./container')(root, opts.label, opts.help) + require('./label')(container, opts.label, theme, id) if (!!opts.step && !!opts.steps) { throw new Error('Cannot specify both step and steps. Got step = ' + opts.step + ', steps = ', opts.steps) @@ -21,6 +24,7 @@ function Range (root, opts, theme, uuid) { var input = container.appendChild(document.createElement('input')) input.type = 'range' input.className = 'control-panel-range-' + uuid + input.id = 'control-panel-range-' + uuid // Create scale functions for converting to/from the desired scale: if (opts.scale === 'log') { @@ -95,10 +99,24 @@ function Range (root, opts, theme, uuid) { input.value = opts.initial css(input, { - width: '47.5%' + width: '50%' }) - var value = require('./value')(container, scaleValue(opts.initial), theme, '11%') + var value = require('./value')(container, { + id: id, + initial: scaleValue(opts.initial), + theme: theme, + width: '13%', + type: opts.scale === 'log' ? 'text' : 'number', + uuid: uuid, + min: scaleValue(opts.min), + max: scaleValue(opts.max), + step: opts.step, + input: function (v) { + input.value = v + value.value = scaleValue(v) + } + }) setTimeout(function () { self.emit('initialized', parseFloat(input.value)) @@ -106,7 +124,7 @@ function Range (root, opts, theme, uuid) { input.oninput = function (data) { var scaledValue = scaleValue(parseFloat(data.target.value)) - value.innerHTML = scaledValue + value.value = scaledValue self.emit('input', scaledValue) } } diff --git a/components/select.js b/components/select.js index 5de2923..a6f532a 100644 --- a/components/select.js +++ b/components/select.js @@ -1,5 +1,6 @@ var EventEmitter = require('events').EventEmitter var inherits = require('inherits') +var format = require('param-case') module.exports = Select inherits(Select, EventEmitter) @@ -9,10 +10,13 @@ function Select (root, opts, theme, uuid) { var self = this var i, container, input, downTriangle, upTriangle, key, option, el, keys - container = require('./container')(root, opts.label) - require('./label')(container, opts.label, theme) + var id = 'control-panel-select-' + format(opts.label) + '-' + uuid + + container = require('./container')(root, opts.label, opts.help) + require('./label')(container, opts.label, theme, id) input = document.createElement('select') + input.id = id input.className = 'control-panel-select-' + uuid + '-dropdown' downTriangle = document.createElement('span') diff --git a/components/styles/base.css b/components/styles/base.css index 0ed323f..e2e53b9 100644 --- a/components/styles/base.css +++ b/components/styles/base.css @@ -1,6 +1,4 @@ .control-panel { - font-family: 'Hack', monospace; - font-size: 11px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; @@ -8,14 +6,36 @@ cursor: default; text-align: left; box-sizing: border-box; + font-family: {{ FONT_FAMILY }}; + font-size: {{ FONT_SIZE }}; } -.control-panel input { - font-family: 'Hack'; - font-size: 11px; +.control-panel * { + box-sizing: border-box; +} + +.control-panel input, +.control-panel button, +.control-panel select { + border-radius: 0; + padding: 0 0 0 .5em; + vertical-align: top; + font-family: inherit; + font-size: inherit; +} + +.control-panel input[type="range"] { + padding: 0; } .control-panel a { color: inherit; text-decoration: none; } + +.control-panel-container:after { + content: attr(data-help); + display: block; + color: {{ HELP_COLOR }}; + margin-left: 36%; +} \ No newline at end of file diff --git a/components/styles/checkbox.css b/components/styles/checkbox.css index c6c9c80..d059670 100644 --- a/components/styles/checkbox.css +++ b/components/styles/checkbox.css @@ -1,26 +1,26 @@ input[type="checkbox"].control-panel-checkbox-{{ UUID }} { - display:none; - cursor: pointer; + display: none; + cursor: pointer; } input[type=checkbox].control-panel-checkbox-{{ UUID }} + label:before { - content: ""; - display: inline-block; - width: 18px; - height: 18px; + content: ""; + display: inline-block; + width: 2em; + height: 2em; padding: 0px; - vertical-align:middle; - margin-right: 8px; - margin-top: 2px; - background-color: {{ BOX_COLOR }}; - border-radius: 0px; - cursor: pointer; + vertical-align: top; + margin-right: .8em; + margin-top: 0; + background-color: {{ BOX_COLOR }}; + border-radius: 0px; + cursor: pointer; } input[type=checkbox]:checked.control-panel-checkbox-{{ UUID }} + label:before { - width: 10px; - height: 10px; + width: 2em; + height: 2em; + box-shadow: inset 0 0 0 .4em {{ BOX_COLOR }}; background-color: {{ ICON_COLOR }}; - border: solid 4px {{ BOX_COLOR }}; - cursor: pointer; + cursor: pointer; } \ No newline at end of file diff --git a/components/styles/interval.css b/components/styles/interval.css index c273c80..e28a694 100644 --- a/components/styles/interval.css +++ b/components/styles/interval.css @@ -1,10 +1,10 @@ .control-panel-interval-{{ UUID }} { -webkit-appearance: none; position: absolute; - height: 20px; + height: 2em; margin: 0px 0; - width: 33%; - left: 52.5%; + width: 36%; + left: 50%; background-color: {{ TRACK_COLOR }}; cursor: ew-resize; @@ -18,7 +18,7 @@ .control-panel-interval-{{ UUID }} .control-panel-interval-handle { background-color: {{ INTERVAL_COLOR }}; position: absolute; - height: 20px; + height: 2em; min-width: 1px; } #control-panel-{{ UUID }}.control-panel-interval-dragging * { diff --git a/components/styles/range.css b/components/styles/range.css index ba6ad53..2119b6d 100644 --- a/components/styles/range.css +++ b/components/styles/range.css @@ -1,155 +1,26 @@ input[type=range].control-panel-range-{{ UUID }} { -webkit-appearance: none; + -moz-appearance: none; + appearance: none; width: 100%; margin: 0px 0; -} -input[type=range].control-panel-range-{{ UUID }}:focus { - outline: none; -} -input[type=range].control-panel-range-{{ UUID }}::-webkit-slider-runnable-track { - width: 100%; - height: 20px; - cursor: ew-resize; - background: {{ TRACK_COLOR }}; -} -input[type=range].control-panel-range-{{ UUID }}::-webkit-slider-thumb { - height: 20px; - width: 10px; - background: {{ THUMB_COLOR }}; - cursor: ew-resize; - -webkit-appearance: none; - margin-top: 0px; -} -input[type=range].control-panel-range-{{ UUID }}:focus::-webkit-slider-runnable-track { - background: {{ TRACK_COLOR }}; - outline: none; -} -input[type=range].control-panel-range-{{ UUID }}::-moz-range-track { - width: 100%; - height: 20px; - cursor: ew-resize; - background: {{ TRACK_COLOR }}; -} -input[type=range].control-panel-range-{{ UUID }}::-moz-range-thumb { - height: 20px; - width: 10px; - background: {{ THUMB_COLOR }}; - cursor: ew-resize; -} -input[type=range].control-panel-range-{{ UUID }}::-ms-track { - width: 100%; - height: 20px; - cursor: ew-resize; - background: transparent; - border-color: transparent; - color: transparent; -} -input[type=range].control-panel-range-{{ UUID }}::-ms-fill-lower { - background: {{ TRACK_COLOR }}; -} -input[type=range].control-panel-range-{{ UUID }}::-ms-fill-upper { - background: {{ TRACK_COLOR }}; -} -input[type=range].control-panel-range-{{ UUID }}::-ms-thumb { - width: 10px; - border-radius: 0px; - background: {{ THUMB_COLOR }}; - cursor: ew-resize; - height: 20px; -} -input[type=range].control-panel-range-{{ UUID }}:focus::-ms-fill-lower { - background: {{ TRACK_COLOR }}; - outline: none; -} -input[type=range].control-panel-range-{{ UUID }}:focus::-ms-fill-upper { - background: {{ TRACK_COLOR }}; - outline: none; -} + height: 2em; -input[type=range].control-panel-range-{{ UUID }} { - -webkit-appearance: none; - width: 100%; - margin: 0px 0; -} -input[type=range].control-panel-range-{{ UUID }}:focus { - outline: none; -} -input[type=range].control-panel-range-{{ UUID }}::-webkit-slider-runnable-track { - width: 100%; - height: 20px; - cursor: ew-resize; - background: {{ TRACK_COLOR }}; -} -input[type=range].control-panel-range-{{ UUID }}::-webkit-slider-thumb { - height: 20px; - width: 10px; - background: {{ THUMB_COLOR }}; - cursor: ew-resize; - -webkit-appearance: none; - margin-top: 0px; -} -input[type=range].control-panel-range-{{ UUID }}:focus::-webkit-slider-runnable-track { - background: {{ TRACK_COLOR }}; - outline: none; -} -input[type=range].control-panel-range-{{ UUID }}::-moz-range-track { - width: 100%; - height: 20px; - cursor: ew-resize; - background: {{ TRACK_COLOR }}; -} -input[type=range].control-panel-range-{{ UUID }}::-moz-range-thumb { - height: 20px; - width: 10px; - background: {{ THUMB_COLOR }}; - cursor: ew-resize; -} -input[type=range].control-panel-range-{{ UUID }}::-ms-track { - width: 100%; - height: 20px; - cursor: ew-resize; - background: transparent; - border-color: transparent; - color: transparent; -} -input[type=range].control-panel-range-{{ UUID }}::-ms-fill-lower { - background: {{ TRACK_COLOR }}; -} -input[type=range].control-panel-range-{{ UUID }}::-ms-fill-upper { - background: {{ TRACK_COLOR }}; -} -input[type=range].control-panel-range-{{ UUID }}::-ms-thumb { - width: 10px; - background: {{ THUMB_COLOR }}; - cursor: ew-resize; - height: 20px; -} -input[type=range].control-panel-range-{{ UUID }}:focus::-ms-fill-lower { - background: {{ TRACK_COLOR }}; - outline: none; -} -input[type=range].control-panel-range-{{ UUID }}:focus::-ms-fill-upper { - background: {{ TRACK_COLOR }}; - outline: none; -} -input[type=range].control-panel-range-{{ UUID }} { - -webkit-appearance: none; - width: 100%; - margin: 0px 0; } input[type=range].control-panel-range-{{ UUID }}:focus { outline: none; } input[type=range].control-panel-range-{{ UUID }}::-webkit-slider-runnable-track { width: 100%; - height: 20px; + height: 2em; cursor: ew-resize; background: {{ TRACK_COLOR }}; } input[type=range].control-panel-range-{{ UUID }}::-webkit-slider-thumb { - height: 20px; - width: 10px; + height: 2em; + width: 1em; background: {{ THUMB_COLOR }}; + border: 0; cursor: ew-resize; -webkit-appearance: none; margin-top: 0px; @@ -160,21 +31,21 @@ input[type=range].control-panel-range-{{ UUID }}:focus::-webkit-slider-runnable- } input[type=range].control-panel-range-{{ UUID }}::-moz-range-track { width: 100%; - height: 20px; + height: 2em; cursor: ew-resize; background: {{ TRACK_COLOR }}; } input[type=range].control-panel-range-{{ UUID }}::-moz-range-thumb { border: 0px solid rgba(0, 0, 0, 0); - height: 20px; - width: 10px; border-radius: 0px; + height: 2em; + width: 1em; background: {{ THUMB_COLOR }}; cursor: ew-resize; } input[type=range].control-panel-range-{{ UUID }}::-ms-track { width: 100%; - height: 20px; + height: 2em; cursor: ew-resize; background: transparent; border-color: transparent; @@ -187,10 +58,12 @@ input[type=range].control-panel-range-{{ UUID }}::-ms-fill-upper { background: {{ TRACK_COLOR }}; } input[type=range].control-panel-range-{{ UUID }}::-ms-thumb { - width: 10px; + width: 1em; + border-radius: 0px; + border: 0; background: {{ THUMB_COLOR }}; cursor: ew-resize; - height: 20px; + height: 2em; } input[type=range].control-panel-range-{{ UUID }}:focus::-ms-fill-lower { background: {{ TRACK_COLOR }}; @@ -199,4 +72,5 @@ input[type=range].control-panel-range-{{ UUID }}:focus::-ms-fill-lower { input[type=range].control-panel-range-{{ UUID }}:focus::-ms-fill-upper { background: {{ TRACK_COLOR }}; outline: none; -} \ No newline at end of file +} + diff --git a/components/styles/select.css b/components/styles/select.css index ea7474e..ebd325b 100644 --- a/components/styles/select.css +++ b/components/styles/select.css @@ -1,9 +1,9 @@ .control-panel-select-{{ UUID }}-dropdown { display: inline-block; position: absolute; - width: 62%; + width: 64%; padding-left: 1.5%; - height: 20px; + height: 2em; border: none; border-radius: 0; outline: none; @@ -20,20 +20,20 @@ } .control-panel-select-{{ UUID }}-triangle { content: ' '; - border-right: 3px solid transparent; - border-left: 3px solid transparent; - line-height: 20px; + border-right: .3em solid transparent; + border-left: .3em solid transparent; + line-height: 2em; position: absolute; right: 2.5%; z-index: 1; } .control-panel-select-{{ UUID }}-triangle--down { - top: 11px; - border-top: 5px solid {{ TEXT_COLOR }}; - border-bottom: 0px transparent; + top: 1.1em; + border-top: .5em solid {{ TEXT_COLOR }}; + border-bottom: .0 transparent; } .control-panel-select-{{ UUID }}-triangle--up { - top: 4px; - border-bottom: 5px solid {{ TEXT_COLOR }}; + top: .4em; + border-bottom: .5em solid {{ TEXT_COLOR }}; border-top: 0px transparent; } diff --git a/components/styles/value.css b/components/styles/value.css new file mode 100644 index 0000000..dd3ffad --- /dev/null +++ b/components/styles/value.css @@ -0,0 +1,23 @@ +.control-panel-value-{{ UUID }} { + -webkit-appearance: none; + -moz-appearance: none; + -o-appearance: none; + appearance: none; + padding: 0 0 0 .5em; + display: inline-block; + cursor: text; + display: inline-block; + position: absolute; + width: 62%; + height: 2em; + border: none; + border-radius: 0; + outline: none; + font-family: inherit; + background-color: {{ BG_COLOR }}; + color: {{ TEXT_COLOR }}; +} +.control-panel-value-{{ UUID }}:focus { + outline: 0; + box-shadow: 0; +} \ No newline at end of file diff --git a/components/text.js b/components/text.js index a1157ee..2d68fbf 100644 --- a/components/text.js +++ b/components/text.js @@ -1,6 +1,7 @@ var EventEmitter = require('events').EventEmitter var inherits = require('inherits') var css = require('dom-css') +var format = require('param-case') module.exports = Text inherits(Text, EventEmitter) @@ -9,11 +10,15 @@ function Text (root, opts, theme, uuid) { if (!(this instanceof Text)) return new Text(root, opts, theme, uuid) var self = this - var container = require('./container')(root, opts.label) - require('./label')(container, opts.label, theme) + var container = require('./container')(root, opts.label, opts.help) + + var id = 'control-panel-text-' + format(opts.label) + '-' + uuid + + require('./label')(container, opts.label, theme, id) var input = container.appendChild(document.createElement('input')) - input.type = 'text' + input.type = opts.type + input.id = id input.className = 'control-panel-text-' + uuid if (opts.initial) input.value = opts.initial @@ -23,9 +28,8 @@ function Text (root, opts, theme, uuid) { css(input, { position: 'absolute', - paddingLeft: '6px', - height: '20px', - width: '59.5%', + height: '2em', + width: '64%', border: 'none', background: theme.background2, color: theme.text2, diff --git a/components/title.js b/components/title.js index 5f7f973..1184f8b 100644 --- a/components/title.js +++ b/components/title.js @@ -9,8 +9,9 @@ module.exports = function (root, text, theme) { textAlign: 'center', color: theme.text2, textTransform: 'uppercase', - height: '20px', - marginBottom: '4px' + lineHeight: '1.2', + marginTop: '0', + marginBottom: '1em' }) return title diff --git a/components/value.js b/components/value.js index e9f3ab9..8b9be41 100644 --- a/components/value.js +++ b/components/value.js @@ -1,37 +1,43 @@ var css = require('dom-css') -module.exports = function (root, text, theme, width, left) { - var background = root.appendChild(document.createElement('div')) - var value = background.appendChild(document.createElement('span')) +module.exports = function (root, opts) { + opts = opts || {} + var value = document.createElement('input') + value.setAttribute('type', opts.type || 'text') - value.innerHTML = text + if (opts.type === 'number') { + if (opts.min != null) value.min = opts.min + if (opts.max != null) value.max = opts.max + if (opts.step != null) value.step = opts.step + else value.step = (opts.max - opts.min) / 100 || 1 + } + + if (opts.input) { + value.addEventListener('input', function () { + opts.input(value.value) + }) + } + if (opts.change) { + value.addEventListener('change', function () { + opts.change(value.value) + }) + } + + value.value = opts.initial + + value.id = opts.id || 'control-panel-value-' + opts.uuid + value.className = 'control-panel-value-' + opts.uuid + root.appendChild(value) var bgcss = { - position: 'absolute', - backgroundColor: theme.background2, - paddingLeft: '1.5%', - height: '20px', - width: width, - display: 'inline-block', - overflow: 'hidden' + width: opts.width } - if (!left) { + if (!opts.left) { bgcss.right = 0 } - css(background, bgcss) - - css(value, { - color: theme.text2, - display: 'inline-block', - userSelect: 'text', - cursor: 'text', - overflow: 'hidden', - lineHeight: '20px', - wordBreak: 'break-all', - height: 20 - }) + css(value, bgcss) return value } diff --git a/example.js b/example.js index 5190d44..bc1d477 100644 --- a/example.js +++ b/example.js @@ -1,7 +1,13 @@ var control = require('./') +// prepare mobile +var meta = document.createElement('meta') +meta.setAttribute('name', 'viewport') +meta.setAttribute('content', 'width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=0') +document.head.appendChild(meta) + var panel = control([ - {type: 'range', label: 'range slider', min: 0, max: 100, initial: 20}, + {type: 'range', label: 'range slider', min: 0, max: 100, initial: 20, help: 'Default slider'}, {type: 'range', label: 'range stepped', min: 0, max: 1, step: 0.2, initial: 0.6}, {type: 'range', scale: 'log', label: 'range slider (log)', min: 0.01, max: 100, initial: 1}, {type: 'range', scale: 'log', label: 'range stepped (log)', min: 0.01, max: 100, steps: 10, initial: 1}, @@ -17,7 +23,8 @@ var panel = control([ {type: 'interval', label: 'neg log interval', min: -0.1, max: -10, initial: [-0.1, -1], scale: 'log', steps: 20}, {type: 'range', label: 'one more', min: 0, max: 10}, {type: 'select', label: 'key/value select', options: {state1: 'State One', state2: 'State Two'}, initial: 'state1'}, - {type: 'select', label: 'array select', options: ['State One', 'State Two'], initial: 'State One'} + {type: 'select', label: 'array select', options: ['State One', 'State Two'], initial: 'State One'}, + {type: 'email', label: 'email'} ], {theme: 'light', title: 'example panel', position: 'top-left', width: 400} ) diff --git a/index.js b/index.js index ab0f00c..43accc9 100644 --- a/index.js +++ b/index.js @@ -6,7 +6,7 @@ var insertcss = require('insert-css') var path = require('path') var isstring = require('is-string') var themes = require('./themes') -var uuid = require('node-uuid') +var uuid = require('get-uid') module.exports = Plate inherits(Plate, EventEmitter) @@ -21,8 +21,8 @@ function Plate (items, opts) { opts.root = opts.root || document.body opts.position = opts.position - var box = document.createElement('div') - var id = uuid.v4() + var box = this.box = document.createElement('form') + var id = uuid() box.className = 'control-panel' box.id = 'control-panel-' + id @@ -33,7 +33,12 @@ function Plate (items, opts) { var buttoncss = fs.readFileSync(path.join(__dirname, 'components', 'styles', 'button.css')) var intervalcss = fs.readFileSync(path.join(__dirname, 'components', 'styles', 'interval.css')) var selectcss = fs.readFileSync(path.join(__dirname, 'components', 'styles', 'select.css')) + var valuecss = fs.readFileSync(path.join(__dirname, 'components', 'styles', 'value.css')) + basecss = String(basecss) + .replace(new RegExp('{{ FONT_FAMILY }}', 'g'), opts.theme.fontFamily) + .replace(new RegExp('{{ FONT_SIZE }}', 'g'), opts.theme.fontSize) + .replace(new RegExp('{{ HELP_COLOR }}', 'g'), opts.theme.foreground1) rangecss = String(rangecss) .replace(new RegExp('{{ THUMB_COLOR }}', 'g'), opts.theme.foreground1) .replace(new RegExp('{{ TRACK_COLOR }}', 'g'), opts.theme.background2) @@ -59,6 +64,10 @@ function Plate (items, opts) { .replace(new RegExp('{{ BG_COLOR }}', 'g'), opts.theme.background2) .replace(new RegExp('{{ BG_COLOR_HOVER }}', 'g'), opts.theme.background2hover) .replace(new RegExp('{{ UUID }}', 'g'), id) + valuecss = String(valuecss) + .replace(new RegExp('{{ TEXT_COLOR }}', 'g'), opts.theme.text2) + .replace(new RegExp('{{ BG_COLOR }}', 'g'), opts.theme.background2) + .replace(new RegExp('{{ UUID }}', 'g'), id) insertcss(basecss) insertcss(rangecss) insertcss(colorcss) @@ -66,12 +75,7 @@ function Plate (items, opts) { insertcss(buttoncss) insertcss(intervalcss) insertcss(selectcss) - - var elem = document.createElement('style') - elem.setAttribute('type', 'text/css') - elem.setAttribute('rel', 'stylesheet') - elem.setAttribute('href', '//cdn.jsdelivr.net/font-hack/2.019/css/hack.min.css') - document.getElementsByTagName('head')[0].appendChild(elem) + insertcss(valuecss) css(box, { background: opts.theme.background1, @@ -81,16 +85,18 @@ function Plate (items, opts) { opacity: 0.95 }) - if (opts.position === 'top-right' || - opts.position === 'top-left' || - opts.position === 'bottom-right' || - opts.position === 'bottom-left') css(box, {position: 'absolute'}) + if (opts.position) { + if (opts.position === 'top-right' || + opts.position === 'top-left' || + opts.position === 'bottom-right' || + opts.position === 'bottom-left') css(box, {position: 'absolute'}) - if (opts.position === 'top-right' || opts.position === 'bottom-right') css(box, {right: 8}) - else css(box, {left: 8}) + if (opts.position === 'top-right' || opts.position === 'bottom-right') css(box, {right: 8}) + else css(box, {left: 8}) - if (opts.position === 'top-right' || opts.position === 'top-left') css(box, {top: 8}) - else css(box, {bottom: 8}) + if (opts.position === 'top-right' || opts.position === 'top-left') css(box, {top: 8}) + else css(box, {bottom: 8}) + } if (opts.title) require('./components/title')(box, opts.title, opts.theme) @@ -105,7 +111,7 @@ function Plate (items, opts) { } var element - var state = {} + var state = this.state = {} items.forEach(function (item) { if (item.type !== 'button') { @@ -114,7 +120,7 @@ function Plate (items, opts) { }) items.forEach(function (item) { - element = components[item.type](box, item, opts.theme, id) + element = (components[item.type] || components.text)(box, item, opts.theme, id) element.on('initialized', function (data) { state[item.label] = data @@ -122,6 +128,7 @@ function Plate (items, opts) { element.on('input', function (data) { state[item.label] = data + item.input && item.input(data, state) self.emit('input', state) }) }) diff --git a/package.json b/package.json index 9805dd1..77843a7 100644 --- a/package.json +++ b/package.json @@ -35,11 +35,12 @@ "dependencies": { "brfs": "^1.4.3", "dom-css": "^2.0.0", + "get-uid": "^1.0.1", "inherits": "^2.0.1", "insert-css": "^0.2.0", + "is-mobile": "^0.2.2", "is-numeric": "0.0.5", "is-string": "^1.0.4", - "node-uuid": "^1.4.7", "param-case": "^1.1.2", "simple-color-picker": "0.0.9", "tinycolor2": "^1.3.0" diff --git a/themes.js b/themes.js index 4079b4d..fadf8a8 100644 --- a/themes.js +++ b/themes.js @@ -1,5 +1,7 @@ module.exports = { light: { + fontFamily: '"Hack", monospace', + fontSize: '11px', background1: 'rgb(227,227,227)', background2: 'rgb(204,204,204)', background2hover: 'rgb(208,208,208)', @@ -9,6 +11,8 @@ module.exports = { }, dark: { + fontFamily: '"Hack", monospace', + fontSize: '11px', background1: 'rgb(35,35,35)', background2: 'rgb(54,54,54)', background2hover: 'rgb(58,58,58)',