├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── contributors.md ├── index.css ├── index.js ├── package.json ├── preview.png ├── qa.md ├── src ├── button.js ├── checkbox.js ├── color.js ├── custom.js ├── interval.js ├── range.js ├── select.js ├── switch.js ├── text.js ├── textarea.js └── value.js ├── test.js └── theme ├── control.js ├── dat.js ├── dragon.js ├── flat.js ├── json.js ├── lucy.js ├── merka.js ├── none.js ├── pages.js └── typer.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | bundle.js 36 | demo 37 | 38 | images -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test.js 2 | *.jpg 3 | *.gif 4 | demo 5 | *.png 6 | *.md 7 | images -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jeremy Freeman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # settings-panel [![unstable](http://badges.github.io/stability-badges/dist/unstable.svg)](http://github.com/badges/stability-badges) 2 | 3 | Simple settings panel for your app, demo or tests. 4 | 5 | [![settings-panel](https://raw.githubusercontent.com/dy/settings-panel/gh-pages/images/preview.png "settings-panel")](http://dy.github.io/settings-panel/) 6 | 7 | In the preview there is a _typer_ theme, for other themes or customizations see [demo](http://dy.github.io/settings-panel/). 8 | 9 | ## Usage 10 | 11 | [![npm install settings-panel](https://nodei.co/npm/settings-panel.png?mini=true)](https://npmjs.org/package/settings-panel/) 12 | 13 | ```javascript 14 | var createPanel = require('settings-panel') 15 | 16 | var panel = createPanel([ 17 | {type: 'range', label: 'my range', min: 0, max: 100, value: 20}, 18 | {type: 'range', label: 'log range', min: 0.1, max: 100, value: 20, scale: 'log'}, 19 | {type: 'text', label: 'my text', value: 'my cool setting', help: 'why this is cool'}, 20 | {type: 'checkbox', label: 'my checkbox', value: true}, 21 | {type: 'color', label: 'my color', format: 'rgb', value: 'rgb(10,200,0)', change: value => console.log(value)}, 22 | {type: 'button', label: 'gimme an alert', change: () => alert('hello!')}, 23 | {type: 'select', label: 'select one', options: ['option 1', 'option 2'], value: 'option 1'} 24 | ], 25 | { 26 | title: 'Settings', 27 | style: 'position: absolute; right: 0; z-index: 1' 28 | } 29 | ); 30 | ``` 31 | 32 | [**Run this in requirebin**](http://requirebin.com/?gist=21fc39f7f206ca50a4d5cd7298f8b9f8) 33 | 34 | ## API 35 | 36 | `const Panel = require('settings-panel')` 37 |
**`let panel = new Panel(fields, options?)`** 38 | 39 | The first argument is a list of fields or object with id/field pairs. Each field may have following properties: 40 | 41 | * `type` one of `range` • `interval` • `checkbox` • `color` • `select` • `switch` • `raw` • `textarea` • `text` or any `` type. If undefined, type will be detected from the value. 42 | * `id` used as key to identify the field. If undefined, the label will be used instead. 43 | * `label` label for the input. If label is false, it will be hidden. 44 | * `value` current value of the field. 45 | * `default` explicitly defines default value, if differs from the initial value. 46 | * `orientation` defines position of a label relative to the input, one of `top`, `left`, `right`, `bottom`. Redefines `options.orientation`. 47 | * `style` appends additinal style to the field, can be a css object or css string. 48 | * `hidden` defines whether field should be visually hidden, but present as a value. 49 | * `disabled` just disables the input, making it inactive. 50 | * `input` callback, invoked if value changed. 51 | * `init` invoked once component is set up. 52 | * `change` invoked each time the field value changed, whether through `input` or API. 53 | * `before` and `after` define an html to display before or after the element, can be a string, an element or a function returning one of the two. That may come handy in displaying help, info or validation messages, separators, additional buttons, range limits etc - anything related to the element. 54 | * `title` will display text in tooltip. 55 | 56 | For example, 57 | 58 | ```javascript 59 | {type: 'checkbox', label: 'My Checkbox', value: true, input: value => {}} 60 | ``` 61 | 62 | Some types have additional properties: 63 | 64 | - `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 `value` must be the same and only `steps` is permitted (since the step size is not constant on a log scale). It also takes `precision` optional parameter for the displayed value. 65 | - `interval` obeys the same semantics as `range` inputs, except the input and ouput is a two-element array corresponding to the low/high bounds, e.g. `value: [1, 7.5]`. 66 | - `color` can specify a `format` as either `rgb` • `hex` • `array` 67 | - `select`, `switch` and `checkbox` can specify `options`, either as an `Array` (in which case the value is the same as the option text) or as an object containing key/value pairs (in which case the key/value pair maps to value value/label pairs). 68 | - `text` and `textarea` can specify `placeholder`. 69 | - `raw` can define `content` method, returning HTML string, element or documentFragment. 70 | 71 | #### options 72 | 73 | ```js 74 | // element to which to append the panel 75 | container: document.body, 76 | 77 | // a title to add to the top of the panel 78 | title: 'Settings', 79 | 80 | // specifies label position relative to the input: `top` • `left` • `bottom` • `right` 81 | orientation: 'left', 82 | 83 | // collapse by clicking on title 84 | collapsible: false, 85 | 86 | // use a theme, see `theme` folder. 87 | // available themes: typer, flat, control, dragon 88 | theme: require('settings-panel/theme/none'), 89 | 90 | //theme customization, can redefine theme defaults 91 | palette: ['black', 'white'], 92 | labelWidth: '9em', 93 | inputHeight: '1.6em', 94 | fontFamily: 'sans-serif', 95 | fontSize: 13, 96 | 97 | //additional css, aside from the theme’s one. Useful for custom styling 98 | css: '', 99 | 100 | //appends additional className to the panel element. 101 | className: '' 102 | ``` 103 | 104 |
105 |
**`panel.on(event, callback)`** 106 | 107 | Attach callback to `change`, `input` or `init` event. 108 | 109 | The callback will recieve `name`, `data` and `state` arguments: 110 | 111 | ```javascript 112 | panel.on('change', (name, value, state) => { 113 | // name === 'my checkbox' 114 | // value === false 115 | // state === {'my checkbox': false, 'my range': 75, ...} 116 | }); 117 | ``` 118 | 119 |
120 |
**`panel.get(name?)`** 121 | 122 | Get the value of a field defined by `name`. Or get full list of values, if `name` is undefined. 123 | 124 |
125 |
**`panel.set(name, value|options)`** 126 | 127 | Update specific field, with value or field options. You can also pass an object or array to update multiple fields: 128 | 129 | ```js 130 | panel.set({ 'my range': { min: -100, value: 200}, 'my color': '#fff' }); 131 | ``` 132 | 133 |
134 |
**`panel.update(options?)`** 135 | 136 | Rerender panel with new options. Options may include values for the theme, like `palette`, `fontSize`, `fontFamily`, `labelWidth`, `padding` etc, see specific theme file for possible options. 137 | 138 |
139 | 140 | ## Spotted in the wild 141 | 142 | > [plot-grid](https://dy.github.io/plot-grid)
143 | > [app-audio](https://dy.github.io/app-audio)
144 | > [gl-waveform](https://dy.github.io/gl-waveform)
145 | 146 | ## See also 147 | 148 | > [control-panel](https://github.com/freeman-lab/control-panel) — original forked settings panel.
149 | > [oui](https://github.com/wearekuva/oui) — sci-ish panel.
150 | > [dat.gui](https://github.com/dataarts/dat.gui) — other oldschool settings panel.
151 | > [quicksettings](https://github.com/bit101/quicksettings) — an alternative versatile settings panel.
152 | > [dis-gui](https://github.com/wwwtyro/dis-gui) — remake on dat.gui.
153 | -------------------------------------------------------------------------------- /contributors.md: -------------------------------------------------------------------------------- 1 | # collaborators 2 | 3 | `settings-panel` is only possible due to the excellent work of the following collaborators: 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
freeman-labgithub/freeman-lab
rreussergithub/rreusser
dfcreativegithub/dfcreative
-------------------------------------------------------------------------------- /index.css: -------------------------------------------------------------------------------- 1 | .settings-panel { 2 | position: relative; 3 | -webkit-user-select: none; 4 | -moz-user-select: none; 5 | -ms-user-select: none; 6 | user-select: none; 7 | cursor: default; 8 | text-align: left; 9 | box-sizing: border-box; 10 | font-family: sans-serif; 11 | font-size: 1rem; 12 | width: 32em; 13 | max-width: 100%; 14 | padding: 1em; 15 | } 16 | 17 | .settings-panel [hidden] { 18 | display: none!important; 19 | } 20 | 21 | .settings-panel * { 22 | box-sizing: border-box; 23 | } 24 | 25 | .settings-panel svg { 26 | fill: currentColor; 27 | max-width: 100%; 28 | max-height: 100%; 29 | display: inline-block; 30 | } 31 | 32 | .settings-panel input, 33 | .settings-panel button, 34 | .settings-panel textarea, 35 | .settings-panel select { 36 | font-family: inherit; 37 | font-size: inherit; 38 | } 39 | 40 | .settings-panel textarea { 41 | max-height: 8em; 42 | } 43 | 44 | 45 | .settings-panel a { 46 | color: inherit; 47 | } 48 | 49 | /** Basic layout */ 50 | .settings-panel-field { 51 | position: relative; 52 | padding: .25em; 53 | display: table; 54 | width: 100%; 55 | } 56 | .settings-panel-field:last-child { 57 | margin-bottom: 0; 58 | } 59 | .settings-panel-label { 60 | left: 0; 61 | display: table-cell; 62 | line-height: 1.2; 63 | vertical-align: baseline; 64 | padding-top: 0; 65 | max-width: 100%; 66 | } 67 | .settings-panel-input { 68 | display: table-cell; 69 | vertical-align: baseline; 70 | position: relative; 71 | white-space: nowrap; 72 | } 73 | 74 | .settings-panel-orientation-left .settings-panel-label { 75 | width: 9em; 76 | padding-right: .5em; 77 | } 78 | .settings-panel-orientation-right .settings-panel-label { 79 | display: block; 80 | margin-right: 0; 81 | float: right; 82 | width: 9em; 83 | padding-top: .4em; 84 | padding-left: .5em; 85 | } 86 | .settings-panel-orientation-right .settings-panel-label + .settings-panel-input { 87 | display: block; 88 | width: calc(100% - 9em); 89 | } 90 | .settings-panel-orientation-top .settings-panel-label { 91 | display: block; 92 | width: 100%; 93 | margin-right: 0; 94 | padding-top: 0; 95 | line-height: 1.5; 96 | } 97 | .settings-panel-orientation-top .settings-panel-label + .settings-panel-input { 98 | display: block; 99 | width: 100%; 100 | padding: 0; 101 | } 102 | .settings-panel-orientation-bottom .settings-panel-label { 103 | display: block; 104 | width: 100%; 105 | margin-right: 0; 106 | padding: 0; 107 | line-height: 1.5; 108 | border-top: 2.5em solid transparent; 109 | } 110 | .settings-panel-orientation-bottom .settings-panel-label + .settings-panel-input { 111 | width: 100%; 112 | position: absolute; 113 | top: 0; 114 | } 115 | 116 | .settings-panel-orientation-left > .settings-panel-label { 117 | width: 9em; 118 | display: table-cell; 119 | } 120 | 121 | .settings-panel-title { 122 | font-size: 1.6em; 123 | line-height: 1.25; 124 | margin-top: 0; 125 | margin-bottom: 0; 126 | padding: .25em .25em; 127 | text-align: center; 128 | } 129 | .settings-panel--collapsible .settings-panel-title { 130 | cursor: pointer; 131 | } 132 | .settings-panel--collapsed > *:not(.settings-panel-title) { 133 | display: none!important; 134 | } 135 | 136 | 137 | /** Button */ 138 | .settings-panel-field--button { 139 | display: inline-block; 140 | } 141 | .settings-panel-field--button .settings-panel-input { 142 | display: block; 143 | text-align: center; 144 | } 145 | .settings-panel-button { 146 | vertical-align: baseline; 147 | line-height: 1; 148 | min-height: 2em; 149 | padding: .2em 1em; 150 | width: 100%; 151 | cursor: pointer; 152 | } 153 | 154 | 155 | /** Default text and alike style */ 156 | .settings-panel-text { 157 | height: 2em; 158 | width: 100%; 159 | vertical-align: baseline; 160 | } 161 | .settings-panel-textarea { 162 | width: 100%; 163 | display: block; 164 | vertical-align: top; /* allowable as we use autoheight */ 165 | min-height: 2em; 166 | } 167 | 168 | /** Checkbox style */ 169 | .settings-panel-field--checkbox .settings-panel-input { 170 | line-height: 2em; 171 | } 172 | .settings-panel-checkbox-group { 173 | border: none; 174 | -webkit-appearance: none; 175 | -moz-appearance: none; 176 | -o-appearance: none; 177 | appearance: none; 178 | margin: 0; 179 | padding: 0; 180 | white-space: normal; 181 | } 182 | .settings-panel-checkbox { 183 | display: inline-block; 184 | vertical-align: middle; 185 | width: 1.2em; 186 | height: 1.2em; 187 | line-height: 1.2em; 188 | margin: -.15em .25em 0 0; 189 | } 190 | .settings-panel-checkbox-label { 191 | display: inline-block; 192 | vertical-align: baseline; 193 | -webkit-user-select: none; 194 | -moz-user-select: none; 195 | -ms-user-select: none; 196 | user-select: none; 197 | line-height: 1.2; 198 | margin-right: 1em; 199 | } 200 | .settings-panel-checkbox-group .settings-panel-checkbox-label:last-child { 201 | margin-right: 0; 202 | } 203 | 204 | 205 | /** Color picker style */ 206 | .settings-panel-color { 207 | position: relative; 208 | width: 2em; 209 | height: 2em; 210 | position: absolute; 211 | top: 0; 212 | bottom: 0; 213 | margin: auto; 214 | } 215 | .settings-panel-color-value { 216 | width: 100%; 217 | height: 2em; 218 | padding: 0 0 0 2.5em; 219 | } 220 | .settings-panel .Scp { 221 | -webkit-user-select: none; 222 | -moz-user-select: none; 223 | -ms-user-select: none; 224 | user-select: none; 225 | position: absolute; 226 | z-index: 10; 227 | cursor: pointer; 228 | bottom: -120px; 229 | } 230 | .settings-panel .Scp-saturation { 231 | position: relative; 232 | width: calc(100% - 25px); 233 | height: 100%; 234 | background: linear-gradient(to right, #fff 0%, #f00 100%); 235 | float: left; 236 | } 237 | .settings-panel .Scp-brightness { 238 | width: 100%; 239 | height: 100%; 240 | background: linear-gradient(to top, #000 0%, rgba(255,255,255,0) 100%); 241 | } 242 | .settings-panel .Scp-sbSelector { 243 | border: 1px solid; 244 | position: absolute; 245 | width: 14px; 246 | height: 14px; 247 | background: #fff; 248 | border-radius: 10px; 249 | top: -7px; 250 | left: -7px; 251 | box-sizing: border-box; 252 | z-index: 10; 253 | } 254 | .settings-panel .Scp-hue { 255 | width: 20px; 256 | height: 100%; 257 | position: relative; 258 | float: left; 259 | background: linear-gradient(to bottom, #f00 0%, #f0f 17%, #00f 34%, #0ff 50%, #0f0 67%, #ff0 84%, #f00 100%); 260 | } 261 | .settings-panel .Scp-hSelector { 262 | position: absolute; 263 | background: #fff; 264 | border-bottom: 1px solid #000; 265 | right: -3px; 266 | width: 10px; 267 | height: 2px; 268 | } 269 | 270 | 271 | 272 | /** Interval style */ 273 | .settings-panel-interval { 274 | position: relative; 275 | -webkit-appearance: none; 276 | display: inline-block; 277 | vertical-align: top; 278 | height: 2em; 279 | margin: 0px 0; 280 | width: 70%; 281 | background: #ddd; 282 | cursor: ew-resize; 283 | -webkit-touch-callout: none; 284 | -webkit-user-select: none; 285 | -khtml-user-select: none; 286 | -moz-user-select: none; 287 | -ms-user-select: none; 288 | user-select: none; 289 | } 290 | .settings-panel-interval-handle { 291 | background: #7a4; 292 | position: absolute; 293 | top: 0; 294 | bottom: 0; 295 | min-width: 1px; 296 | } 297 | .settings-panel.settings-panel-interval-dragging * { 298 | -webkit-touch-callout: none !important; 299 | -webkit-user-select: none !important; 300 | -khtml-user-select: none !important; 301 | -moz-user-select: none !important; 302 | -ms-user-select: none !important; 303 | user-select: none !important; 304 | 305 | cursor: ew-resize !important; 306 | } 307 | 308 | .settings-panel-interval + .settings-panel-value { 309 | right: 0; 310 | padding-left: .5em; 311 | } 312 | 313 | 314 | 315 | /** Select style */ 316 | .settings-panel-select { 317 | display: inline-block; 318 | width: 100%; 319 | height: 2em; 320 | vertical-align: baseline; 321 | } 322 | 323 | /** Value style */ 324 | .settings-panel-value { 325 | -webkit-appearance: none; 326 | -moz-appearance: none; 327 | -o-appearance: none; 328 | appearance: none; 329 | min-width: 3em; 330 | padding: 0 0 0 0em; 331 | display: inline-block; 332 | vertical-align: baseline; 333 | cursor: text; 334 | height: 2em; 335 | border: none; 336 | border-radius: 0; 337 | outline: none; 338 | font-family: inherit; 339 | background: none; 340 | color: inherit; 341 | width: 15%; 342 | } 343 | .settings-panel-value:focus { 344 | outline: 0; 345 | box-shadow: 0; 346 | } 347 | .settings-panel-value-tip { 348 | display: none; 349 | } 350 | 351 | /** Range style */ 352 | .settings-panel-range { 353 | width: 85%; 354 | padding: 0; 355 | margin: 0px 0; 356 | height: 2em; 357 | vertical-align: top; 358 | } 359 | .settings-panel-range + .settings-panel-value { 360 | padding-left: .5em; 361 | width: 15%; 362 | } 363 | 364 | .settings-panel-switch { 365 | -webkit-appearance: none; 366 | -moz-appearance: none; 367 | appearance: none; 368 | border: none; 369 | display: block; 370 | vertical-align: baseline; 371 | padding: 0; 372 | margin: 0; 373 | line-height: 2em; 374 | } 375 | .settings-panel-switch-input { 376 | margin: 0; 377 | vertical-align: middle; 378 | width: 1.2em; 379 | height: 1.2em; 380 | cursor: pointer; 381 | margin-right: .25em; 382 | } 383 | .settings-panel-switch-label { 384 | display: inline-block; 385 | vertical-align: baseline; 386 | line-height: 1.2; 387 | margin-right: 1em; 388 | } 389 | 390 | 391 | .settings-panel hr { 392 | border: none; 393 | height: 0; 394 | margin: .5em 0; 395 | border-bottom: 1px dotted; 396 | } 397 | 398 | .settings-panel-field--disabled { 399 | opacity: .5; 400 | pointer-events: none; 401 | } 402 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module settings-panel 3 | */ 4 | 'use strict'; 5 | 6 | const Emitter = require('events').EventEmitter; 7 | const inherits = require('inherits'); 8 | const extend = require('just-extend'); 9 | const css = require('dom-css'); 10 | const uid = require('get-uid'); 11 | const fs = require('fs'); 12 | const insertCss = require('insert-styles'); 13 | const isPlainObject = require('is-plain-obj'); 14 | const format = require('param-case'); 15 | const px = require('add-px-to-style'); 16 | const scopeCss = require('scope-css'); 17 | 18 | module.exports = Panel 19 | 20 | 21 | insertCss(fs.readFileSync(__dirname + '/index.css', 'utf-8')); 22 | 23 | 24 | /** 25 | * @constructor 26 | */ 27 | function Panel (items, opts) { 28 | if (!(this instanceof Panel)) return new Panel(items, opts) 29 | 30 | extend(this, opts); 31 | 32 | //ensure container 33 | if (this.container === undefined) this.container = document.body || document.documentElement; 34 | 35 | this.container.classList.add('settings-panel-container'); 36 | 37 | //create element 38 | if (!this.id) this.id = uid(); 39 | this.element = document.createElement('div') 40 | this.element.className = 'settings-panel settings-panel-' + this.id; 41 | if (this.className) this.element.className += ' ' + this.className; 42 | 43 | //create title 44 | if (this.title) { 45 | this.titleEl = this.element.appendChild(document.createElement('h2')); 46 | this.titleEl.className = 'settings-panel-title'; 47 | } 48 | 49 | //create collapse button 50 | if (this.collapsible && this.title) { 51 | // this.collapseEl = this.element.appendChild(document.createElement('div')); 52 | // this.collapseEl.className = 'settings-panel-collapse'; 53 | this.element.classList.add('settings-panel--collapsible'); 54 | this.titleEl.addEventListener('click', () => { 55 | if (this.collapsed) { 56 | this.collapsed = false; 57 | this.element.classList.remove('settings-panel--collapsed'); 58 | } 59 | else { 60 | this.collapsed = true; 61 | this.element.classList.add('settings-panel--collapsed'); 62 | } 63 | }); 64 | } 65 | 66 | //state is values of items 67 | this.state = {}; 68 | 69 | //items is all items settings 70 | this.items = {}; 71 | 72 | //create fields 73 | this.set(items); 74 | 75 | if (this.container) { 76 | this.container.appendChild(this.element) 77 | } 78 | 79 | //create theme style 80 | this.update(); 81 | } 82 | 83 | inherits(Panel, Emitter); 84 | 85 | 86 | /** 87 | * Set item value/options 88 | */ 89 | Panel.prototype.set = function (name, value) { 90 | //handle list of properties 91 | if (Array.isArray(name)) { 92 | let items = name; 93 | items.forEach((item) => { 94 | this.set(item.id || item.label, item); 95 | }); 96 | 97 | return this; 98 | } 99 | 100 | //handle plain object 101 | if (isPlainObject(name)) { 102 | let items = name; 103 | let list = []; 104 | for (let key in items) { 105 | if (!isPlainObject(items[key])) { 106 | items[key] = {value: items[key]}; 107 | } 108 | if (items[key].id == null) items[key].id = key; 109 | list.push(items[key]); 110 | } 111 | list = list.sort((a, b) => (a.order||0) - (b.order||0)); 112 | 113 | return this.set(list); 114 | } 115 | 116 | //format name 117 | name = name || ''; 118 | name = name.replace(/\-/g,'dash-'); 119 | name = format(name); 120 | 121 | if (name) { 122 | var item = this.items[name]; 123 | if (!item) item = this.items[name] = { id: name, panel: this }; 124 | } 125 | //noname items should not be saved in state 126 | else { 127 | var item = {id: null, panel: this}; 128 | } 129 | 130 | var initialValue = item.value; 131 | var isBefore = item.before; 132 | var isAfter = item.after; 133 | 134 | if (isPlainObject(value)) { 135 | item = extend(item, value); 136 | } 137 | else { 138 | //ignore nothing-changed set 139 | if (value === item.value && value !== undefined) return this; 140 | item.value = value; 141 | } 142 | 143 | if (item.value === undefined) item.value = item.default; 144 | 145 | if (name) this.state[name] = item.value; 146 | 147 | //define label via name 148 | if (item.label === undefined && item.id) { 149 | item.label = item.id; 150 | } 151 | 152 | //detect type 153 | if (!item.type) { 154 | if (item.value && Array.isArray(item.value)) { 155 | if (typeof item.value[0] === 'string') { 156 | item.type = 'checkbox'; 157 | } 158 | else { 159 | item.type = 'interval' 160 | } 161 | } else if (item.scale || item.max || item.steps || item.step || typeof item.value === 'number') { 162 | item.type = 'range' 163 | } else if (item.options) { 164 | if (Array.isArray(item.options) && item.options.join('').length < 90 ) { 165 | item.type = 'switch' 166 | } 167 | else { 168 | item.type = 'select' 169 | } 170 | } else if (item.format) { 171 | item.type = 'color' 172 | } else if (typeof item.value === 'boolean') { 173 | item.type = 'checkbox' 174 | } else if (item.content != null) { 175 | item.type = 'raw' 176 | } else { 177 | if (item.value && (item.value.length > 140 || /\n/.test(item.value))) { 178 | item.type = 'textarea' 179 | } 180 | else { 181 | item.type = 'text' 182 | } 183 | } 184 | } 185 | 186 | var field, fieldId; 187 | 188 | if (item.id != null) { 189 | fieldId = 'settings-panel-field-' + item.id; 190 | field = this.element.querySelector('#' + fieldId); 191 | } 192 | 193 | //create field container 194 | if (!field) { 195 | field = document.createElement('div'); 196 | if (fieldId != null) field.id = fieldId; 197 | this.element.appendChild(field); 198 | item.field = field; 199 | } 200 | else { 201 | //clean previous before/after 202 | if (isBefore) { 203 | this.element.removeChild(field.prevSibling); 204 | } 205 | if (isAfter) { 206 | this.element.removeChild(field.nextSibling); 207 | } 208 | } 209 | 210 | field.className = 'settings-panel-field settings-panel-field--' + item.type; 211 | 212 | if (item.orientation) field.className += ' settings-panel-orientation-' + item.orientation; 213 | 214 | if (item.className) field.className += ' ' + item.className; 215 | 216 | if (item.style) { 217 | if (isPlainObject(item.style)) { 218 | css(field, item.style); 219 | } 220 | else if (typeof item.style === 'string') { 221 | field.style.cssText = item.style; 222 | } 223 | } 224 | else if (item.style !== undefined) { 225 | field.style = null; 226 | } 227 | 228 | if (item.hidden) { 229 | field.setAttribute('hidden', true); 230 | } 231 | else { 232 | field.removeAttribute('hidden'); 233 | } 234 | 235 | //createe container for the input 236 | let inputContainer = field.querySelector('.settings-panel-input'); 237 | 238 | if (!inputContainer) { 239 | inputContainer = document.createElement('div'); 240 | inputContainer.className = 'settings-panel-input'; 241 | item.container = inputContainer; 242 | field.appendChild(inputContainer); 243 | } 244 | 245 | if (item.disabled) field.className += ' settings-panel-field--disabled'; 246 | 247 | let components = this.components; 248 | let component = item.component; 249 | 250 | if (!component) { 251 | item.component = component = (components[item.type] || components.text)(item); 252 | 253 | if (component.on) { 254 | component.on('init', (data) => { 255 | item.value = data 256 | if (item.id) this.state[item.id] = item.value; 257 | let state = extend({}, this.state); 258 | 259 | item.init && item.init(data, state) 260 | this.emit('init', item.id, data, state) 261 | item.change && item.change(data, state) 262 | this.emit('change', item.id, data, state) 263 | }); 264 | 265 | component.on('input', (data) => { 266 | item.value = data 267 | if (item.id) this.state[item.id] = item.value; 268 | let state = extend({}, this.state); 269 | 270 | item.input && item.input(data, state) 271 | this.emit('input', item.id, data, state) 272 | item.change && item.change(data, state) 273 | this.emit('change', item.id, data, state) 274 | }); 275 | 276 | component.on('action', () => { 277 | let state = extend({}, this.state); 278 | item.action && item.action(state); 279 | }); 280 | 281 | component.on('change', (data) => { 282 | item.value = data 283 | if (item.id) this.state[item.id] = item.value; 284 | let state = extend({}, this.state); 285 | 286 | item.change && item.change(data, state) 287 | this.emit('change', item.id, data, state) 288 | }); 289 | } 290 | } 291 | else { 292 | component.update(item); 293 | } 294 | 295 | //create field label 296 | if (component.label !== false && (item.label || item.label === '')) { 297 | let label = field.querySelector('.settings-panel-label'); 298 | if (!label) { 299 | label = document.createElement('label') 300 | label.className = 'settings-panel-label'; 301 | field.insertBefore(label, inputContainer); 302 | } 303 | 304 | label.htmlFor = item.id; 305 | label.innerHTML = item.label; 306 | label.title = item.title || item.label; 307 | } 308 | 309 | //handle after and before 310 | // if (item.before) { 311 | // let before = item.before; 312 | // if (before instanceof Function) { 313 | // before = item.before.call(item, component); 314 | // } 315 | // if (before instanceof HTMLElement) { 316 | // this.element.insertBefore(before, field); 317 | // } 318 | // else { 319 | // field.insertAdjacentHTML('beforebegin', before); 320 | // } 321 | // } 322 | // if (item.after) { 323 | // let after = item.after; 324 | // if (after instanceof Function) { 325 | // after = item.after.call(item, component); 326 | // } 327 | // if (after instanceof HTMLElement) { 328 | // this.element.insertBefore(after, field.nextSibling); 329 | // } 330 | // else { 331 | // field.insertAdjacentHTML('afterend', after); 332 | // } 333 | // } 334 | 335 | //emit change 336 | if (initialValue !== item.value) { 337 | this.emit('change', item.id, item.value, this.state) 338 | } 339 | 340 | return this; 341 | } 342 | 343 | 344 | /** 345 | * Return property value or a list 346 | */ 347 | Panel.prototype.get = function (name) { 348 | if (name == null) return this.state; 349 | return this.state[name]; 350 | } 351 | 352 | 353 | /** 354 | * Update theme 355 | */ 356 | Panel.prototype.update = function (opts) { 357 | extend(this, opts); 358 | 359 | //FIXME: decide whether we have to reset these params 360 | // if (opts && opts.theme) { 361 | // if (opts.theme.fontSize) this.fontSize = opts.theme.fontSize; 362 | // if (opts.theme.inputHeight) this.inputHeight = opts.theme.inputHeight; 363 | // if (opts.theme.fontFamily) this.fontFamily = opts.theme.fontFamily; 364 | // if (opts.theme.labelWidth) this.labelWidth = opts.theme.labelWidth; 365 | // if (opts.theme.palette) this.palette = opts.theme.palette; 366 | // } 367 | 368 | //update title, if any 369 | if (this.titleEl) this.titleEl.innerHTML = this.title; 370 | 371 | //update orientation 372 | this.element.classList.remove('settings-panel-orientation-top'); 373 | this.element.classList.remove('settings-panel-orientation-bottom'); 374 | this.element.classList.remove('settings-panel-orientation-left'); 375 | this.element.classList.remove('settings-panel-orientation-right'); 376 | this.element.classList.add('settings-panel-orientation-' + this.orientation); 377 | 378 | //apply style 379 | let cssStr = ''; 380 | if (this.theme instanceof Function) { 381 | cssStr = this.theme.call(this, this); 382 | } 383 | else if (typeof this.theme === 'string') { 384 | cssStr = this.theme; 385 | } 386 | 387 | //append extra css 388 | if (this.css) { 389 | if (this.css instanceof Function) { 390 | cssStr += this.css.call(this, this); 391 | } 392 | else if (typeof this.css === 'string') { 393 | cssStr += this.css; 394 | } 395 | } 396 | 397 | //scope each rule 398 | cssStr = scopeCss(cssStr || '', '.settings-panel-' + this.id) || ''; 399 | 400 | insertCss(cssStr.trim(), { 401 | id: this.id 402 | }); 403 | 404 | if (this.style) { 405 | if (isPlainObject(this.style)) { 406 | css(this.element, this.style); 407 | } 408 | else if (typeof this.style === 'string') { 409 | this.element.style.cssText = this.style; 410 | } 411 | } 412 | else if (this.style !== undefined) { 413 | this.element.style = null; 414 | } 415 | 416 | return this; 417 | } 418 | 419 | //instance theme 420 | Panel.prototype.theme = require('./theme/none'); 421 | 422 | /** 423 | * Registered components 424 | */ 425 | Panel.prototype.components = { 426 | range: require('./src/range'), 427 | 428 | button: require('./src/button'), 429 | text: require('./src/text'), 430 | textarea: require('./src/textarea'), 431 | 432 | checkbox: require('./src/checkbox'), 433 | toggle: require('./src/checkbox'), 434 | 435 | switch: require('./src/switch'), 436 | 437 | color: require('./src/color'), 438 | 439 | interval: require('./src/interval'), 440 | multirange: require('./src/interval'), 441 | 442 | custom: require('./src/custom'), 443 | raw: require('./src/custom'), 444 | 445 | select: require('./src/select') 446 | }; 447 | 448 | 449 | /** 450 | * Additional class name 451 | */ 452 | Panel.prototype.className; 453 | 454 | 455 | /** 456 | * Additional visual setup 457 | */ 458 | Panel.prototype.orientation = 'left'; 459 | 460 | 461 | /** Display collapse button */ 462 | Panel.prototype.collapsible = false; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "settings-panel", 3 | "version": "1.8.17", 4 | "description": "Panel of inputs for parameter setting", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "browserify test.js -g bubleify -o demo/index.js", 8 | "test:browser": "budo test.js -- -g bubleify" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/dfcreative/settings-panel.git" 13 | }, 14 | "keywords": [ 15 | "slider", 16 | "input", 17 | "range", 18 | "checkbox", 19 | "component", 20 | "color-picker", 21 | "xypad", 22 | "dom", 23 | "css", 24 | "settings-panel", 25 | "control-panel", 26 | "options", 27 | "form", 28 | "dat.gui", 29 | "oui", 30 | "ouioui", 31 | "datgui" 32 | ], 33 | "author": "ΔY ", 34 | "contributors": [ 35 | "freeman-lab", 36 | "rreusser" 37 | ], 38 | "license": "MIT", 39 | "bugs": { 40 | "url": "https://github.com/dfcreative/settings-panel/issues" 41 | }, 42 | "browserify": { 43 | "transform": [ 44 | "brfs" 45 | ] 46 | }, 47 | "homepage": "https://github.com/dfcreative/settings-panel#readme", 48 | "dependencies": { 49 | "add-px-to-style": "^1.0.0", 50 | "autosize": "^3.0.15", 51 | "brfs": "^1.4.3", 52 | "color-interpolate": "^1.0.1", 53 | "dom-css": "^2.0.0", 54 | "get-uid": "^1.0.1", 55 | "google-fonts": "^1.0.0", 56 | "inherits": "^2.0.1", 57 | "input-number": "^1.0.5", 58 | "insert-styles": "^1.2.1", 59 | "is-mobile": "^0.2.2", 60 | "is-numeric": "0.0.5", 61 | "is-plain-obj": "^1.1.0", 62 | "just-extend": "^1.1.18", 63 | "mumath": "^2.3.0", 64 | "param-case": "^1.1.2", 65 | "scope-css": "^1.0.0", 66 | "simple-color-picker": "0.0.9", 67 | "tinycolor2": "^1.3.0" 68 | }, 69 | "devDependencies": { 70 | "bubleify": "^0.5.0", 71 | "colormap": "^2.2.0", 72 | "dom-css": "^2.0.1", 73 | "filter-obj": "^1.1.0", 74 | "nice-color-palettes": "^1.0.1", 75 | "sortablejs": "^1.4.2", 76 | "tst": "^1.3.1" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dy/settings-panel/d973e5d88ec6f9e5c88268564425c38075eacc3a/preview.png -------------------------------------------------------------------------------- /qa.md: -------------------------------------------------------------------------------- 1 | # Q: should we separate active color? 2 | + Makes sense for flat 3 | + Makes sense for typer 4 | + Unseful for control 5 | * Bearable for dragon 6 | * Possible to autodetect active color from palette, if undefined. 7 | + Expands palette to full range 8 | + Enables need in component enabler 9 | * funny thing is that many schemes have active color in between edges, it is contrast to all of edge values. 10 | * Also edge values seem good working for labels/title as it increase readability due to contrast, but active color highlights, it does not make things contrast. 11 | * Also too often we don’t need brightness relationship between label/title/bg, because as far bg can be transparent, we can manage to do bw scale via opacity. 12 | ✔~ Therefore initial idea of picking active color from the middle may be good. As far as we avoid `active` option and put things back to single palette. 13 | 14 | # Q: what are good theme principles? 15 | * It should look good in bw and inverse bw 16 | → don’t do bg gray, gray is property of palette, not the step. bg should allow for accents, but be on the edge, like .8, .9 etc. 17 | ? how do we do lighting? b/w or edge palette colors? 18 | * It should be able to reach maximum contrast in bw mode 19 | → 20 | * Bg, active color should be easily changeable 21 | → therefore bg and active are the opposites 22 | * It should respond to interactions: hover, focus, active etc., by highlighting active element. 23 | * It should be able to change scale gracefully, i. e. keeping select size, thumbnails, etc consistent. 24 | 25 | # Q: what is the minimal and easiest way to cover all the use-cases? 26 | * 1. Redefine properties on per-component basis, like label-position, field-style, item-style etc. 27 | * 2. type=group or type=row 28 | + grouping fields into a row 29 | + customizing background 30 | * 3. type=custom 31 | + cover titles h1..h6 32 | + cover br's 33 | * 4. type=panel, for nested panels 34 | - possible to fold into type=custom 35 | + type=custom does not make a big sense, it is rather rare exception, like canvas etc 36 | + grouping tasks 37 | + theme recustomization 38 | + label-position, field-style is solved on per-theme basis 39 | + allows for enabling inner panel 40 | * 5. per-component theme update - let components decide their look based on theme 41 | - firbids user component customization 42 | 43 | # Q: should title be a separate element, or not? 44 | * + it allows for inserting multiple titles 45 | - multiple titles wants be styles in custom way, ideally h2/h3 46 | * - is is discrepancy with control-panel 47 | * + it makes title in code visually easier to find 48 | * - title is not really a field 49 | * - subgroups are better done in subpanels 50 | 51 | # Q: what is the optimal way to organize themes? 52 | * use themes/*.js with functions, returning dynamic css based off variables 53 | * use palette instead of picked colors to allow for dynamic switching without theme change. -------------------------------------------------------------------------------- /src/button.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EventEmitter = require('events').EventEmitter 4 | const inherits = require('inherits') 5 | 6 | module.exports = Button 7 | inherits(Button, EventEmitter) 8 | 9 | function Button (opts) { 10 | if (!(this instanceof Button)) return new Button(opts) 11 | 12 | var input = opts.container.querySelector('.settings-panel-button'); 13 | if (!input) { 14 | this.element = input = opts.container.appendChild(document.createElement('button')) 15 | input.className = 'settings-panel-button'; 16 | input.addEventListener('click', (e) => { 17 | e.preventDefault(); 18 | this.emit('input'); 19 | this.emit('action'); 20 | }) 21 | } 22 | 23 | this.update(opts); 24 | } 25 | 26 | Button.prototype.update = function (opts) { 27 | this.element.innerHTML = opts.value || opts.label; 28 | return this; 29 | }; 30 | 31 | Button.prototype.label = false; -------------------------------------------------------------------------------- /src/checkbox.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EventEmitter = require('events').EventEmitter 4 | const inherits = require('inherits') 5 | const format = require('param-case') 6 | const extend = require('just-extend'); 7 | 8 | module.exports = Checkbox 9 | inherits(Checkbox, EventEmitter) 10 | 11 | function Checkbox (opts) { 12 | if (!(this instanceof Checkbox)) return new Checkbox(opts) 13 | 14 | var that = this; 15 | 16 | if (!this.group) { 17 | this.group = document.createElement('fieldset'); 18 | this.group.className = 'settings-panel-checkbox-group'; 19 | opts.container.appendChild(this.group); 20 | } 21 | 22 | //detect multiple options from array value 23 | if (!opts.options && Array.isArray(opts.value)) { 24 | opts.options = opts.value; 25 | } 26 | 27 | //single checkbox 28 | if (!opts.options) { 29 | let input = this.group.querySelector('.settings-panel-checkbox'); 30 | let label = this.group.querySelector('.settings-panel-checkbox-label'); 31 | if (!input) { 32 | this.element = input = this.group.appendChild(document.createElement('input')); 33 | input.className = 'settings-panel-checkbox'; 34 | this.labelEl = label = this.group.appendChild(document.createElement('label')); 35 | this.labelEl.innerHTML = ' '; 36 | label.className = 'settings-panel-checkbox-label'; 37 | input.onchange = function (data) { 38 | that.emit('input', data.target.checked) 39 | } 40 | setTimeout(function () { 41 | that.emit('init', input.checked) 42 | }) 43 | } 44 | } 45 | //multiple checkboxes 46 | else { 47 | var html = ''; 48 | 49 | if (Array.isArray(opts.options)) { 50 | for (let i = 0; i < opts.options.length; i++) { 51 | let option = opts.options[i] 52 | html += createOption(option, option); 53 | } 54 | } else { 55 | for (let key in opts.options) { 56 | html += createOption(opts.options[key], key); 57 | } 58 | } 59 | 60 | this.group.innerHTML = html; 61 | 62 | this.group.addEventListener('change', () => { 63 | this.emit('input', getState()); 64 | }); 65 | setTimeout(() => { 66 | this.emit('init', getState()); 67 | }); 68 | } 69 | 70 | function getState () { 71 | let v = []; 72 | [].slice.call(that.group.querySelectorAll('.settings-panel-checkbox')).forEach(el => { 73 | if (el.checked) v.push(el.getAttribute('data-value')); 74 | }); 75 | return v; 76 | } 77 | 78 | function createOption (label, value) { 79 | let htmlFor = `settings-panel-${format(opts.panel.id)}-${format(opts.id)}-input-${format(value)}`; 80 | 81 | let html = ``; 82 | return html; 83 | } 84 | 85 | this.update(opts); 86 | } 87 | 88 | Checkbox.prototype.update = function (opts) { 89 | extend(this, opts); 90 | 91 | if (!this.options) { 92 | this.labelEl.htmlFor = this.id 93 | this.element.id = this.id 94 | this.element.type = 'checkbox'; 95 | this.element.checked = !!this.value; 96 | } 97 | else { 98 | if (!Array.isArray(this.value)) this.value = [this.value]; 99 | let els = [].slice.call(this.group.querySelectorAll('.settings-panel-checkbox')); 100 | els.forEach(el => { 101 | if (this.value.indexOf(el.getAttribute('data-value')) >= 0) { 102 | el.checked = true; 103 | } 104 | else { 105 | el.checked = false; 106 | } 107 | }); 108 | } 109 | 110 | this.group.disabled = !!this.disabled; 111 | 112 | return this; 113 | } 114 | -------------------------------------------------------------------------------- /src/color.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EventEmitter = require('events').EventEmitter 4 | const ColorPicker = require('simple-color-picker') 5 | const inherits = require('inherits') 6 | const css = require('dom-css') 7 | const tinycolor = require('tinycolor2') 8 | const formatParam = require('param-case') 9 | const num = require('input-number') 10 | 11 | module.exports = Color 12 | inherits(Color, EventEmitter) 13 | 14 | function Color (opts) { 15 | if (!(this instanceof Color)) return new Color(opts) 16 | 17 | this.update(opts); 18 | } 19 | 20 | Color.prototype.update = function (opts) { 21 | opts.container.innerHTML = ''; 22 | 23 | opts = opts || {} 24 | opts.format = opts.format || 'rgb' 25 | opts.value = opts.value || '#123456'; 26 | var icon = opts.container.appendChild(document.createElement('div')) 27 | //FIXME: this needed to make el vertical-aligned by baseline 28 | icon.innerHTML = ' '; 29 | icon.className = 'settings-panel-color' 30 | 31 | var valueInput = opts.container.appendChild(document.createElement('input')); 32 | valueInput.id = opts.id; 33 | valueInput.className = 'settings-panel-color-value'; 34 | num(valueInput); 35 | valueInput.onchange = () => { 36 | picker.setColor(valueInput.value); 37 | }; 38 | valueInput.oninput = () => { 39 | picker.setColor(valueInput.value); 40 | }; 41 | 42 | if (opts.readonly) { 43 | valueInput.setAttribute('readonly', true); 44 | } 45 | 46 | 47 | var initial = opts.value 48 | switch (opts.format) { 49 | case 'rgb': 50 | initial = tinycolor(initial).toHexString() 51 | break 52 | case 'hex': 53 | initial = tinycolor(initial).toHexString() 54 | break 55 | case 'array': 56 | initial = tinycolor.fromRatio({r: initial[0], g: initial[1], b: initial[2]}).toHexString() 57 | break 58 | default: 59 | break 60 | } 61 | 62 | var picker = new ColorPicker({ 63 | el: icon, 64 | color: initial, 65 | width: 160, 66 | height: 120 67 | }); 68 | 69 | picker.$el.style.display = 'none'; 70 | 71 | if (!opts.readonly) { 72 | icon.onmouseover = function () { 73 | picker.$el.style.display = '' 74 | } 75 | icon.onmouseout = (e) => { 76 | picker.$el.style.display = 'none' 77 | } 78 | } 79 | 80 | setTimeout(() => { 81 | this.emit('init', initial) 82 | }); 83 | 84 | picker.onChange((hex) => { 85 | let v = format(hex); 86 | if (v !== valueInput.value) valueInput.value = v; 87 | css(icon, {backgroundColor: hex}) 88 | this.emit('input', format(hex)) 89 | }) 90 | 91 | function format (hex) { 92 | switch (opts.format) { 93 | case 'rgb': 94 | return tinycolor(hex).toRgbString() 95 | case 'hex': 96 | return tinycolor(hex).toHexString() 97 | case 'array': 98 | var rgb = tinycolor(hex).toRgb() 99 | return [rgb.r / 255, rgb.g / 255, rgb.b / 255].map(function (x) { 100 | return x.toFixed(2) 101 | }) 102 | default: 103 | return hex 104 | } 105 | }; 106 | 107 | return this; 108 | } 109 | -------------------------------------------------------------------------------- /src/custom.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module settings-panel/src/custom 3 | * 4 | * A custom html component 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const EventEmitter = require('events').EventEmitter 10 | const inherits = require('inherits') 11 | const extend = require('just-extend') 12 | 13 | module.exports = Custom 14 | inherits(Custom, EventEmitter) 15 | 16 | function Custom (opts) { 17 | if (!(this instanceof Custom)) return new Custom(opts); 18 | 19 | //FIXME: these guys force unnecessary events, esp if element returns wrong value 20 | // opts.container.addEventListener('input', (e) => { 21 | // this.emit('input', e.target.value); 22 | // }); 23 | // opts.container.addEventListener('change', (e) => { 24 | // this.emit('change', e.target.value); 25 | // }); 26 | 27 | this.update(opts); 28 | } 29 | 30 | Custom.prototype.update = function (opts) { 31 | extend(this, opts); 32 | var el = this.content; 33 | if (this.content instanceof Function) { 34 | el = this.content(this); 35 | if (!el) return; 36 | 37 | if (typeof el === 'string') { 38 | this.container.innerHTML = el; 39 | } 40 | else if (!this.container.contains(el)) { 41 | this.container.appendChild(el); 42 | } 43 | } 44 | else if (typeof this.content === 'string') { 45 | this.container.innerHTML = el; 46 | } 47 | else if (this.content instanceof Element && (!this.container.contains(el))) { 48 | this.container.appendChild(el); 49 | } 50 | else { 51 | //empty content is allowable, in case if user wants to show only label for example 52 | // throw Error('`content` should be a function returning html element or string'); 53 | } 54 | }; -------------------------------------------------------------------------------- /src/interval.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isNumeric = require('is-numeric') 4 | const css = require('dom-css') 5 | const isMobile = require('is-mobile')() 6 | const format = require('param-case') 7 | const clamp = require('mumath/clamp') 8 | const EventEmitter = require('events').EventEmitter 9 | const inherits = require('inherits'); 10 | const precision = require('mumath/precision'); 11 | 12 | module.exports = Range 13 | 14 | inherits(Range, EventEmitter); 15 | 16 | function Range (opts) { 17 | if (!(this instanceof Range)) return new Range(opts); 18 | 19 | this.update(opts); 20 | } 21 | 22 | Range.prototype.update = function (opts) { 23 | var self = this 24 | var scaleValue, scaleValueInverse, logmin, logmax, logsign, input, handle, panel; 25 | 26 | if (!!opts.step && !!opts.steps) { 27 | throw new Error('Cannot specify both step and steps. Got step = ' + opts.step + ', steps = ', opts.steps) 28 | } 29 | 30 | opts.container.innerHTML = ''; 31 | 32 | if (opts.step) { 33 | var prec = precision(opts.step) || 1; 34 | } 35 | else { 36 | var prec = precision( (opts.max - opts.min) / opts.steps ) || 1; 37 | } 38 | 39 | // Create scale functions for converting to/from the desired scale: 40 | if (opts.scale === 'log' || opts.log) { 41 | scaleValue = function (x) { 42 | return logsign * Math.exp(Math.log(logmin) + (Math.log(logmax) - Math.log(logmin)) * x / 100) 43 | } 44 | scaleValueInverse = function (y) { 45 | return (Math.log(y * logsign) - Math.log(logmin)) * 100 / (Math.log(logmax) - Math.log(logmin)) 46 | } 47 | } else { 48 | scaleValue = scaleValueInverse = function (x) { return x } 49 | } 50 | 51 | if (!Array.isArray(opts.value)) { 52 | opts.value = [] 53 | } 54 | if (opts.scale === 'log' || opts.log) { 55 | // Get options or set defaults: 56 | opts.max = (isNumeric(opts.max)) ? opts.max : 100 57 | opts.min = (isNumeric(opts.min)) ? opts.min : 0.1 58 | 59 | // Check if all signs are valid: 60 | if (opts.min * opts.max <= 0) { 61 | throw new Error('Log range min/max must have the same sign and not equal zero. Got min = ' + opts.min + ', max = ' + opts.max) 62 | } else { 63 | // Pull these into separate variables so that opts can define the *slider* mapping 64 | logmin = opts.min 65 | logmax = opts.max 66 | logsign = opts.min > 0 ? 1 : -1 67 | 68 | // Got the sign so force these positive: 69 | logmin = Math.abs(logmin) 70 | logmax = Math.abs(logmax) 71 | 72 | // These are now simply 0-100 to which we map the log range: 73 | opts.min = 0 74 | opts.max = 100 75 | 76 | // Step is invalid for a log range: 77 | if (isNumeric(opts.step)) { 78 | throw new Error('Log may only use steps (integer number of steps), not a step value. Got step =' + opts.step) 79 | } 80 | // Default step is simply 1 in linear slider space: 81 | opts.step = 1 82 | } 83 | 84 | opts.value = [ 85 | scaleValueInverse(isNumeric(opts.value[0]) ? opts.value[0] : scaleValue(opts.min + (opts.max - opts.min) * 0.25)), 86 | scaleValueInverse(isNumeric(opts.value[1]) ? opts.value[1] : scaleValue(opts.min + (opts.max - opts.min) * 0.75)) 87 | ] 88 | 89 | if (scaleValue(opts.value[0]) * scaleValue(opts.max) <= 0 || scaleValue(opts.value[1]) * scaleValue(opts.max) <= 0) { 90 | throw new Error('Log range initial value must have the same sign as min/max and must not equal zero. Got initial value = [' + scaleValue(opts.value[0]) + ', ' + scaleValue(opts.value[1]) + ']') 91 | } 92 | } else { 93 | // If linear, this is much simpler: 94 | opts.max = (isNumeric(opts.max)) ? opts.max : 100 95 | opts.min = (isNumeric(opts.min)) ? opts.min : 0 96 | opts.step = (isNumeric(opts.step)) ? opts.step : (opts.max - opts.min) / 100 97 | 98 | opts.value = [ 99 | isNumeric(opts.value[0]) ? opts.value[0] : (opts.min + opts.max) * 0.25, 100 | isNumeric(opts.value[1]) ? opts.value[1] : (opts.min + opts.max) * 0.75 101 | ] 102 | } 103 | 104 | // If we got a number of steps, use that instead: 105 | if (isNumeric(opts.steps)) { 106 | opts.step = isNumeric(opts.steps) ? (opts.max - opts.min) / opts.steps : opts.step 107 | } 108 | 109 | // Quantize the initial value to the requested step: 110 | opts.value[0] = opts.min + opts.step * Math.round((opts.value[0] - opts.min) / opts.step) 111 | opts.value[1] = opts.min + opts.step * Math.round((opts.value[1] - opts.min) / opts.step) 112 | 113 | 114 | //create DOM 115 | var lValue = require('./value')({ 116 | container: opts.container, 117 | value: scaleValue(opts.value[0]).toFixed(prec), 118 | type: 'text', 119 | left: true, 120 | disabled: opts.disabled, 121 | id: opts.id, 122 | className: 'settings-panel-interval-value settings-panel-interval-value--left', 123 | input: v => { 124 | //TODO 125 | } 126 | }) 127 | 128 | panel = opts.container.parentNode; 129 | 130 | input = opts.container.appendChild(document.createElement('span')) 131 | input.id = 'settings-panel-interval' 132 | input.className = 'settings-panel-interval' 133 | 134 | handle = document.createElement('span') 135 | handle.className = 'settings-panel-interval-handle' 136 | handle.value = 50; 137 | handle.min = 0; 138 | handle.max = 50; 139 | input.appendChild(handle) 140 | 141 | var value = opts.value 142 | 143 | // Display the values: 144 | var rValue = require('./value')({ 145 | container: opts.container, 146 | disabled: opts.disabled, 147 | value: scaleValue(opts.value[1]).toFixed(prec), 148 | type: 'text', 149 | className: 'settings-panel-interval-value settings-panel-interval-value--right', 150 | input: v => { 151 | //TODO 152 | } 153 | }) 154 | 155 | function setHandleCSS () { 156 | let left = ((value[0] - opts.min) / (opts.max - opts.min) * 100); 157 | let right = (100 - (value[1] - opts.min) / (opts.max - opts.min) * 100); 158 | css(handle, { 159 | left: left + '%', 160 | width: (100 - left - right) + '%' 161 | }); 162 | opts.container.style.setProperty('--low', left + '%'); 163 | opts.container.style.setProperty('--high', 100 - right + '%'); 164 | lValue.style.setProperty('--value', left + '%'); 165 | rValue.style.setProperty('--value', 100 - right + '%'); 166 | } 167 | 168 | // Initialize CSS: 169 | setHandleCSS() 170 | // An index to track what's being dragged: 171 | var activeIndex = -1 172 | 173 | function mouseX (ev) { 174 | // Get mouse/touch position in page coords relative to the container: 175 | return (ev.touches && ev.touches[0] || ev).pageX - input.getBoundingClientRect().left 176 | } 177 | 178 | function setActiveValue (fraction) { 179 | if (activeIndex === -1) { 180 | return 181 | } 182 | 183 | // Get the position in the range [0, 1]: 184 | var lofrac = (value[0] - opts.min) / (opts.max - opts.min) 185 | var hifrac = (value[1] - opts.min) / (opts.max - opts.min) 186 | 187 | // Clip against the other bound: 188 | if (activeIndex === 0) { 189 | fraction = Math.min(hifrac, fraction) 190 | } else { 191 | fraction = Math.max(lofrac, fraction) 192 | } 193 | 194 | // Compute and quantize the new value: 195 | var newValue = opts.min + Math.round((opts.max - opts.min) * fraction / opts.step) * opts.step 196 | 197 | // Update value, in linearized coords: 198 | value[activeIndex] = newValue 199 | 200 | // Update and send the event: 201 | setHandleCSS() 202 | input.oninput() 203 | } 204 | 205 | var mousemoveListener = function (ev) { 206 | if (ev.target === input || ev.target === handle) ev.preventDefault() 207 | 208 | var fraction = clamp(mouseX(ev) / input.offsetWidth, 0, 1) 209 | 210 | setActiveValue(fraction) 211 | } 212 | 213 | var mouseupListener = function (ev) { 214 | panel.classList.remove('settings-panel-interval-dragging') 215 | 216 | document.removeEventListener(isMobile ? 'touchmove' : 'mousemove', mousemoveListener) 217 | document.removeEventListener(isMobile ? 'touchend' : 'mouseup', mouseupListener) 218 | 219 | activeIndex = -1 220 | } 221 | 222 | input.addEventListener(isMobile ? 'touchstart' : 'mousedown', function (ev) { 223 | // Tweak control to make dragging experience a little nicer: 224 | panel.classList.add('settings-panel-interval-dragging') 225 | 226 | // Get mouse position fraction: 227 | var fraction = clamp(mouseX(ev) / input.offsetWidth, 0, 1) 228 | 229 | // Get the current fraction of position --> [0, 1]: 230 | var lofrac = (value[0] - opts.min) / (opts.max - opts.min) 231 | var hifrac = (value[1] - opts.min) / (opts.max - opts.min) 232 | 233 | // This is just for making decisions, so perturb it ever 234 | // so slightly just in case the bounds are numerically equal: 235 | lofrac -= Math.abs(opts.max - opts.min) * 1e-15 236 | hifrac += Math.abs(opts.max - opts.min) * 1e-15 237 | 238 | // Figure out which is closer: 239 | var lodiff = Math.abs(lofrac - fraction) 240 | var hidiff = Math.abs(hifrac - fraction) 241 | 242 | activeIndex = lodiff < hidiff ? 0 : 1 243 | 244 | // Attach this to *document* so that we can still drag if the mouse 245 | // passes outside the container: 246 | document.addEventListener(isMobile ? 'touchmove' : 'mousemove', mousemoveListener) 247 | document.addEventListener(isMobile ? 'touchend' : 'mouseup', mouseupListener) 248 | }) 249 | 250 | setTimeout(() => { 251 | var scaledLValue = scaleValue(value[0]) 252 | var scaledRValue = scaleValue(value[1]) 253 | lValue.value = scaledLValue.toFixed(prec) 254 | rValue.value = scaledRValue.toFixed(prec) 255 | this.emit('init', [scaledLValue, scaledRValue]) 256 | }) 257 | 258 | input.oninput = () => { 259 | var scaledLValue = scaleValue(value[0]) 260 | var scaledRValue = scaleValue(value[1]) 261 | lValue.value = scaledLValue.toFixed(prec) 262 | rValue.value = scaledRValue.toFixed(prec) 263 | this.emit('input', [scaledLValue, scaledRValue]) 264 | } 265 | 266 | return this; 267 | } -------------------------------------------------------------------------------- /src/range.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EventEmitter = require('events').EventEmitter 4 | const inherits = require('inherits') 5 | const isNumeric = require('is-numeric') 6 | const css = require('dom-css') 7 | const format = require('param-case') 8 | const precision = require('mumath/precision') 9 | 10 | module.exports = Range 11 | inherits(Range, EventEmitter) 12 | 13 | function Range (opts) { 14 | if (!(this instanceof Range)) return new Range(opts); 15 | 16 | this.update(opts); 17 | } 18 | 19 | Range.prototype.update = function (opts) { 20 | var scaleValue, scaleValueInverse, logmin, logmax, logsign 21 | 22 | if (!!opts.step && !!opts.steps) { 23 | throw new Error('Cannot specify both step and steps. Got step = ' + opts.step + ', steps = ', opts.steps) 24 | } 25 | 26 | opts.container.innerHTML = ''; 27 | 28 | if (!opts.container) opts.container = document.body; 29 | 30 | var input = opts.container.querySelector('.settings-panel-range'); 31 | 32 | if (!input) { 33 | input = opts.container.appendChild(document.createElement('input')) 34 | input.type = 'range' 35 | input.className = 'settings-panel-range' 36 | } 37 | 38 | if (opts.disabled) input.disabled = true; 39 | 40 | if (opts.log) opts.scale = 'log'; 41 | 42 | // Create scale functions for converting to/from the desired scale: 43 | if (opts.scale === 'log') { 44 | scaleValue = function (x) { 45 | return logsign * Math.exp(Math.log(logmin) + (Math.log(logmax) - Math.log(logmin)) * x / 100) 46 | } 47 | scaleValueInverse = function (y) { 48 | return (Math.log(y * logsign) - Math.log(logmin)) * 100 / (Math.log(logmax) - Math.log(logmin)) 49 | } 50 | } else { 51 | scaleValue = scaleValueInverse = function (x) { return x } 52 | } 53 | 54 | // Get initial value: 55 | if (opts.scale === 'log') { 56 | // Get options or set defaults: 57 | opts.max = (isNumeric(opts.max)) ? opts.max : 100 58 | opts.min = (isNumeric(opts.min)) ? opts.min : 0.1 59 | 60 | // Check if all signs are valid: 61 | if (opts.min * opts.max <= 0) { 62 | throw new Error('Log range min/max must have the same sign and not equal zero. Got min = ' + opts.min + ', max = ' + opts.max) 63 | } else { 64 | // Pull these into separate variables so that opts can define the *slider* mapping 65 | logmin = opts.min 66 | logmax = opts.max 67 | logsign = opts.min > 0 ? 1 : -1 68 | 69 | // Got the sign so force these positive: 70 | logmin = Math.abs(logmin) 71 | logmax = Math.abs(logmax) 72 | 73 | // These are now simply 0-100 to which we map the log range: 74 | opts.min = 0 75 | opts.max = 100 76 | 77 | // Step is invalid for a log range: 78 | if (isNumeric(opts.step)) { 79 | throw new Error('Log may only use steps (integer number of steps), not a step value. Got step =' + opts.step) 80 | } 81 | // Default step is simply 1 in linear slider space: 82 | opts.step = 1 83 | } 84 | 85 | opts.value = scaleValueInverse(isNumeric(opts.value) ? opts.value : scaleValue((opts.min + opts.max) * 0.5)) 86 | 87 | if (opts.value * scaleValueInverse(opts.max) <= 0) { 88 | throw new Error('Log range initial value must have the same sign as min/max and must not equal zero. Got initial value = ' + opts.value) 89 | } 90 | } else { 91 | // If linear, this is much simpler: 92 | opts.max = (isNumeric(opts.max)) ? opts.max : 100 93 | opts.min = (isNumeric(opts.min)) ? opts.min : 0 94 | opts.step = (isNumeric(opts.step)) ? opts.step : (opts.max - opts.min) / 100 95 | 96 | opts.value = isNumeric(opts.value) ? opts.value : (opts.min + opts.max) * 0.5 97 | } 98 | 99 | // If we got a number of steps, use that instead: 100 | if (isNumeric(opts.steps)) { 101 | opts.step = isNumeric(opts.steps) ? (opts.max - opts.min) / opts.steps : opts.step 102 | } 103 | 104 | // Quantize the initial value to the requested step: 105 | var initialStep = Math.round((opts.value - opts.min) / opts.step) 106 | opts.value = opts.min + opts.step * initialStep 107 | 108 | //preser container data for display 109 | opts.container.setAttribute('data-min', opts.min); 110 | opts.container.setAttribute('data-max', opts.max); 111 | 112 | if (opts.scale === 'log') { 113 | //FIXME: not every log is of precision 3 114 | var prec = opts.precision != null ? opts.precision : 3; 115 | } 116 | else { 117 | if (opts.step) { 118 | var prec = opts.precision != null ? opts.precision : precision(opts.step); 119 | } 120 | else if (opts.steps) { 121 | var prec = opts.precision != null ? opts.precision : precision( (opts.max - opts.min) / opts.steps ); 122 | } 123 | } 124 | 125 | var value = require('./value')({ 126 | id: opts.id, 127 | container: opts.container, 128 | className: 'settings-panel-range-value', 129 | value: scaleValue(opts.value).toFixed(prec), 130 | type: opts.scale === 'log' ? 'text' : 'number', 131 | min: scaleValue(opts.min), 132 | max: scaleValue(opts.max), 133 | disabled: opts.disabled, 134 | //FIXME: step here might vary 135 | step: opts.step, 136 | input: (v) => { 137 | let scaledValue = scaleValueInverse(v) 138 | input.value = scaledValue; 139 | value.title = input.value; 140 | // value.value = v 141 | this.emit('input', v); 142 | input.setAttribute('value', scaledValue.toFixed(0)) 143 | opts.container.style.setProperty('--value', scaledValue + '%'); 144 | opts.container.style.setProperty('--coef', scaledValue/100); 145 | } 146 | }); 147 | 148 | // Set value on the input itself: 149 | input.min = opts.min 150 | input.max = opts.max 151 | input.step = opts.step 152 | input.value = opts.value 153 | let v = 100 * (opts.value - opts.min) / (opts.max - opts.min); 154 | input.setAttribute('value', v.toFixed(0)) 155 | opts.container.style.setProperty('--value', v + '%'); 156 | opts.container.style.setProperty('--coef', v/100); 157 | 158 | setTimeout(() => { 159 | this.emit('init', parseFloat(value.value)) 160 | }); 161 | 162 | input.oninput = (data) => { 163 | var scaledValue = scaleValue(parseFloat(data.target.value)); 164 | value.value = scaledValue.toFixed(prec); 165 | let v = 100 * (data.target.value - opts.min) / (opts.max - opts.min); 166 | input.setAttribute('value', v.toFixed(0)); 167 | opts.container.style.setProperty('--value', v + '%'); 168 | opts.container.style.setProperty('--coef', v/100); 169 | value.title = scaledValue; 170 | this.emit('input', scaledValue); 171 | } 172 | 173 | return this; 174 | } 175 | -------------------------------------------------------------------------------- /src/select.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var EventEmitter = require('events').EventEmitter 4 | var inherits = require('inherits') 5 | var format = require('param-case') 6 | 7 | module.exports = Select 8 | inherits(Select, EventEmitter) 9 | 10 | function Select (opts) { 11 | if (!(this instanceof Select)) return new Select(opts); 12 | 13 | this.update(opts); 14 | } 15 | 16 | Select.prototype.update = function (opts) { 17 | var i, container, input, downTriangle, upTriangle, key, option, el, keys 18 | 19 | opts.container.innerHTML = ''; 20 | 21 | input = document.createElement('select') 22 | input.id = opts.id 23 | input.className = 'settings-panel-select'; 24 | input.title = opts.title || opts.label; 25 | 26 | if (opts.disabled) input.disabled = true; 27 | 28 | downTriangle = document.createElement('span') 29 | downTriangle.className = 'settings-panel-select-triangle settings-panel-select-triangle--down' 30 | 31 | upTriangle = document.createElement('span') 32 | upTriangle.className = 'settings-panel-select-triangle settings-panel-select-triangle--up' 33 | 34 | if (Array.isArray(opts.options)) { 35 | for (let i = 0; i < opts.options.length; i++) { 36 | option = opts.options[i] 37 | el = document.createElement('option') 38 | el.value = el.textContent = option 39 | if (opts.value === option) { 40 | el.selected = 'selected' 41 | } 42 | input.appendChild(el) 43 | } 44 | } else { 45 | keys = Object.keys(opts.options) 46 | for (let i = 0; i < keys.length; i++) { 47 | key = keys[i] 48 | el = document.createElement('option') 49 | el.value = key 50 | if (opts.value === key) { 51 | el.selected = 'selected' 52 | } 53 | el.textContent = opts.options[key] 54 | input.appendChild(el) 55 | } 56 | } 57 | 58 | opts.container.appendChild(input) 59 | opts.container.appendChild(downTriangle) 60 | opts.container.appendChild(upTriangle) 61 | 62 | setTimeout(() => { 63 | this.emit('init', opts.value) 64 | }) 65 | 66 | input.onchange = (data) => { 67 | this.emit('input', data.target.value) 68 | } 69 | 70 | return this; 71 | } 72 | -------------------------------------------------------------------------------- /src/switch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const inherits = require('inherits'); 4 | const Emitter = require('events').EventEmitter; 5 | const format = require('param-case'); 6 | const extend = require('just-extend'); 7 | 8 | module.exports = Switch; 9 | 10 | inherits(Switch, Emitter); 11 | 12 | function Switch (opts) { 13 | if (!(this instanceof Switch)) return new Switch(opts); 14 | 15 | this.switch = opts.container.querySelector('.settings-panel-switch'); 16 | 17 | if (!this.switch) { 18 | this.switch = document.createElement('fieldset'); 19 | this.switch.className = 'settings-panel-switch'; 20 | opts.container.appendChild(this.switch); 21 | 22 | var html = ''; 23 | 24 | if (Array.isArray(opts.options)) { 25 | for (let i = 0; i < opts.options.length; i++) { 26 | let option = opts.options[i] 27 | html += createOption(option, option); 28 | } 29 | } else { 30 | for (let key in opts.options) { 31 | html += createOption(opts.options[key], key); 32 | } 33 | } 34 | 35 | this.switch.innerHTML = html; 36 | 37 | this.switch.onchange = (e) => { 38 | this.emit('input', e.target.getAttribute('data-value')); 39 | } 40 | 41 | setTimeout(() => { 42 | this.emit('init', opts.value) 43 | }) 44 | } 45 | 46 | this.switch.id = opts.id; 47 | 48 | this.update(opts); 49 | 50 | function createOption (label, value) { 51 | let htmlFor = `settings-panel-${format(opts.panel.id)}-${format(opts.id)}-input-${format(value)}`; 52 | 53 | let html = ``; 54 | return html; 55 | } 56 | } 57 | 58 | Switch.prototype.update = function (opts) { 59 | return this; 60 | } -------------------------------------------------------------------------------- /src/text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EventEmitter = require('events').EventEmitter 4 | const inherits = require('inherits') 5 | const css = require('dom-css') 6 | const num = require('input-number'); 7 | const extend = require('just-extend'); 8 | 9 | module.exports = Text 10 | inherits(Text, EventEmitter) 11 | 12 | function Text (opts) { 13 | if (!(this instanceof Text)) return new Text(opts) 14 | 15 | let element = opts.container.querySelector('.settings-panel-text'); 16 | 17 | if (!element) { 18 | element = opts.container.appendChild(document.createElement('input')); 19 | element.className = 'settings-panel-text'; 20 | num(element); 21 | 22 | if (opts.placeholder) element.placeholder = opts.placeholder; 23 | 24 | this.element = element; 25 | 26 | element.oninput = (data) => { 27 | this.emit('input', data.target.value) 28 | } 29 | setTimeout(() => { 30 | this.emit('init', element.value) 31 | }); 32 | } 33 | 34 | this.update(opts); 35 | } 36 | 37 | Text.prototype.update = function (opts) { 38 | extend(this, opts); 39 | this.element.type = this.type 40 | this.element.id = this.id 41 | this.element.value = this.value || '' 42 | this.element.disabled = !!this.disabled; 43 | return this; 44 | } 45 | -------------------------------------------------------------------------------- /src/textarea.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EventEmitter = require('events').EventEmitter 4 | const inherits = require('inherits') 5 | const css = require('dom-css') 6 | const autosize = require('autosize'); 7 | const extend = require('just-extend'); 8 | 9 | module.exports = Textarea 10 | inherits(Textarea, EventEmitter) 11 | 12 | function Textarea (opts) { 13 | if (!(this instanceof Textarea)) return new Textarea(opts) 14 | 15 | // 16 | let input = opts.container.querySelector('.settings-panel-textarea'); 17 | if (!input) { 18 | input = opts.container.appendChild(document.createElement('textarea')); 19 | input.className = 'settings-panel-textarea'; 20 | 21 | this.element = input; 22 | 23 | setTimeout(() => { 24 | this.emit('init', input.value) 25 | autosize.update(input); 26 | }) 27 | 28 | input.oninput = (data) => { 29 | this.emit('input', data.target.value) 30 | } 31 | 32 | autosize(input); 33 | } 34 | 35 | this.update(opts); 36 | } 37 | 38 | Textarea.prototype.update = function (opts) { 39 | extend(this, opts); 40 | 41 | this.element.rows = this.rows || 1; 42 | this.element.placeholder = this.placeholder || ''; 43 | this.element.id = this.id 44 | 45 | this.element.value = this.value || ''; 46 | 47 | this.element.disabled = !!this.disabled; 48 | 49 | autosize.update(this.element); 50 | 51 | return this; 52 | } -------------------------------------------------------------------------------- /src/value.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const num = require('input-number'); 4 | 5 | module.exports = function (opts) { 6 | opts = opts || {} 7 | var value = document.createElement('input'); 8 | 9 | num(value, opts); 10 | 11 | if (opts.input) { 12 | value.addEventListener('input', function () { 13 | let v = value.value; 14 | if (opts.type === 'number') v = parseFloat(v); 15 | opts.input(v) 16 | }) 17 | } 18 | if (opts.change) { 19 | value.addEventListener('change', function () { 20 | let v = value.value; 21 | if (opts.type === 'number') v = parseFloat(v); 22 | opts.change(v) 23 | }) 24 | } 25 | 26 | if (opts.disabled) value.disabled = true; 27 | 28 | value.value = opts.value 29 | 30 | if (opts.id) value.id = opts.id; 31 | value.className = 'settings-panel-value'; 32 | if (opts.className) value.className += ' ' + opts.className; 33 | opts.container.appendChild(value); 34 | 35 | //add tip holder after value 36 | let tip = opts.container.appendChild(document.createElement('div')); 37 | tip.className = 'settings-panel-value-tip'; 38 | 39 | return value 40 | } 41 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const createPanel = require('./') 2 | const insertCSS = require('insert-styles'); 3 | const css = require('dom-css'); 4 | const Picker = require('simple-color-picker'); 5 | const Sortable = require('sortablejs'); 6 | const color = require('tinycolor2'); 7 | const colormap = require('colormap'); 8 | const colorScales = require('colormap/colorScales'); 9 | let palettes = require('nice-color-palettes/500'); 10 | 11 | let colormaps = {}; 12 | 13 | for (var name in colorScales) { 14 | if (name === 'alpha') continue; 15 | if (name === 'hsv') continue; 16 | if (name === 'rainbow') continue; 17 | if (name === 'rainbow-soft') continue; 18 | if (name === 'phase') continue; 19 | 20 | colormaps[name] = colormap({ 21 | colormap: colorScales[name], 22 | nshades: 16, 23 | format: 'rgbaString' 24 | }); 25 | } 26 | 27 | palettes = palettes 28 | //filter not readable palettes 29 | .filter((palette) => { 30 | return color.isReadable(palette[0], palette.slice(-1)[0], { 31 | level:"AA", size:"large" 32 | }); 33 | }); 34 | 35 | // prepare mobile 36 | var meta = document.createElement('meta') 37 | meta.setAttribute('name', 'viewport') 38 | meta.setAttribute('content', 'width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=0') 39 | document.head.appendChild(meta) 40 | 41 | 42 | insertCSS(` 43 | body { 44 | margin: 0; 45 | position: relative; 46 | min-height: 100vh; 47 | background: url('./images/land.jpg'); 48 | background-position: center top; 49 | background-size: cover; 50 | background-attachment: fixed; 51 | } 52 | .settings-panel-preview { 53 | margin: 2em auto; 54 | } 55 | .sidebar { 56 | display: none; 57 | position: fixed; 58 | top: 0; 59 | right: 0; 60 | bottom: 0; 61 | font-size: 12px; 62 | width: 240px; 63 | box-shadow: -1px 0 3px rgba(0,0,0,.05); 64 | } 65 | @media (min-width: 640px) { 66 | .frame { 67 | min-height: 100vh; 68 | position: relative; 69 | width: calc(100% - 240px); 70 | } 71 | .sidebar { 72 | display: block; 73 | } 74 | } 75 | `); 76 | 77 | 78 | const themes = { 79 | none: require('./theme/none'), 80 | // lucy: require('./theme/lucy'), 81 | control: require('./theme/control'), 82 | dragon: require('./theme/dragon'), 83 | // merka: require('./theme/merka'), 84 | json: require('./theme/json'), 85 | flat: require('./theme/flat'), 86 | typer: require('./theme/typer') 87 | }; 88 | 89 | var frame = document.createElement('div'); 90 | frame.classList.add('frame'); 91 | document.body.appendChild(frame); 92 | 93 | 94 | var panel = createPanel([ 95 | {type: 'switch', label: 'Switch', options: ['One', 'Two', 'Three'], value: 'One'}, 96 | {type: 'range', label: 'Range', min: 0, max: 100, value: 80, help: 'Default slider'}, 97 | // {type: 'range', label: 'Range stepped', min: 0, max: 1, step: 0.2, value: 0.6}, 98 | // {type: 'range', scale: 'log', label: 'Range log', min: 0.01, max: 100, value: 1}, 99 | // {type: 'range', scale: 'log', label: 'Stepped log', min: 0.01, max: 100, steps: 10, value: 1}, 100 | // {type: 'range', scale: 'log', label: 'Range -log', min: -0.01, max: -100, value: -1}, 101 | // {type: 'range', scale: 'log', label: 'Stepped -log', min: -0.01, max: -100, steps: 10, value: -1}, 102 | // {type: 'raw', content: '
'}, 103 | {type: 'interval', label: 'Interval', min: 0, max: 10, value: [3, 7], steps: 20}, 104 | // {type: 'interval', label: 'Log interval', min: 0.1, max: 10, value: [0.1, 1], scale: 'log', steps: 20}, 105 | // {type: 'interval', label: 'Neg log interval', min: -0.1, max: -10, value: [-0.3, -1], scale: 'log', steps: 20}, 106 | {type: 'checkbox', label: 'Checkbox', value: true}, 107 | {type: 'checkbox', label: 'Checkbox group', value: ['b', 'c'], options: { 108 | a: 'Option A', 109 | b: 'Option B', 110 | c: 'Option C' 111 | } 112 | }, 113 | {type: 'text', label: 'Text', value: 'my setting'}, 114 | {type: 'color', label: 'Color', format: 'rgb', value: 'rgb(100,200,100)'}, 115 | // {type: 'color', label: 'Color hex', format: 'hex', value: '#30b2ba'}, 116 | {type: 'select', label: 'Select', options: {state1: 'State One', state2: 'State Two'}, value: 'state1'}, 117 | {type: 'email', label: 'Email', placeholder: 'email'}, 118 | {type: 'text', label: 'Disabled', disabled: true, value: 'disabled value'}, 119 | {type: 'textarea', label: 'Long text', placeholder: 'long text...'}, 120 | {type: 'raw', content: '
'}, 121 | {type: 'button', label: 'Cancel', input: function () { window.alert('hello!') }, style: {width: '50%'}}, 122 | {type: 'button', label: 'Ok', input: function () { window.alert('hello!') }, style: 'width: 50%'}, 123 | // {type: 'switch', label: 'Orientation', options: 'top|left|bottom|right'.split('|'), value: 'left' } 124 | ], { 125 | id: 'preview', 126 | title: 'Example panel', 127 | className: 'settings-panel-preview', 128 | container: frame, 129 | theme: themes.typer 130 | }); 131 | 132 | panel.on('input', function (name, value, data) { 133 | console.log(name, value, data) 134 | }) 135 | 136 | 137 | 138 | //create main form 139 | var settings = createPanel([ 140 | {label: 'Theme', type: 'select', options: Object.keys(themes), value: panel.theme.name, change: v => { 141 | if (!v) return; 142 | panel.update({theme: themes[v]}); 143 | settings.set({ 144 | 'font-size': panel.fontSize, 145 | 'font-family': panel.fontFamily, 146 | 'label-width': panel.labelWidth, 147 | 'input-height': panel.inputHeight, 148 | 'padding': themes[v].padding 149 | }); 150 | settings.set('palette', panel.theme.palette); 151 | }}, 152 | 153 | {label: 'Palette', type: 'raw', id: 'palette', options: palettes, save: false, value: panel.theme.palette, content: function (opts) { 154 | let palette = opts.value || this.value; 155 | 156 | let list = this.field.querySelector('.palette'); 157 | if (!list) { 158 | list = document.createElement('ul'); 159 | list.className = 'palette'; 160 | css(list, { 161 | listStyle: 'none', 162 | margin: '0 0 0', 163 | padding: 0, 164 | minHeight: '2em', 165 | display: 'inline-block' 166 | }); 167 | setTimeout(() => { 168 | this.emit('init', palette); 169 | }); 170 | } 171 | 172 | //add randomize btn 173 | let randomize = list.querySelector('.palette-randomize'); 174 | if (!randomize) { 175 | randomize = document.createElement('li'); 176 | randomize.className = 'palette-randomize'; 177 | randomize.innerHTML = ''; 178 | css(randomize, { 179 | height: '2em', 180 | width: '2em', 181 | float: 'left', 182 | clear: 'left', 183 | position: 'relative', 184 | marginTop: '.5em', 185 | lineHeight: '2em', 186 | textAlign: 'center' 187 | }); 188 | randomize.title = 'Randomize palette'; 189 | randomize.onclick = () => { 190 | palette = palettes[Math.floor(Math.random() * palettes.length)]; 191 | this.emit('change', palette.slice()); 192 | settings.set('palette', palette.slice()); 193 | colormap.firstChild.value = 'custom'; 194 | }; 195 | list.appendChild(randomize); 196 | } 197 | 198 | //add reverse btn 199 | let reverse = list.querySelector('.palette-reverse'); 200 | if (!reverse) { 201 | reverse = document.createElement('li'); 202 | reverse.className = 'palette-reverse'; 203 | reverse.innerHTML = ''; 204 | css(reverse, { 205 | height: '2em', 206 | width: '2em', 207 | float: 'left', 208 | marginTop: '.5em', 209 | lineHeight: '2em', 210 | textAlign: 'center' 211 | }); 212 | reverse.title = 'Reverse palette'; 213 | reverse.onclick = () => { 214 | palette = palette.reverse(); 215 | this.emit('change', palette.slice()); 216 | settings.set('palette', palette.slice()); 217 | colormap.firstChild.value = 'custom'; 218 | }; 219 | list.appendChild(reverse); 220 | } 221 | 222 | //add colormap select 223 | let colormap = list.querySelector('.palette-colormap'); 224 | if (!colormap) { 225 | colormap = document.createElement('li'); 226 | colormap.className = 'palette-colormap'; 227 | colormap.innerHTML = ``; 228 | css(colormap, { 229 | height: '2em', 230 | width: 'auto', 231 | marginLeft: '.5em', 232 | float: 'left', 233 | marginTop: '.5em', 234 | lineHeight: '2em', 235 | textAlign: 'center' 236 | }); 237 | colormap.title = 'Choose colormap'; 238 | colormap.onchange = (e) => { 239 | let cm = e.target.value; 240 | palette = colormaps[cm] || panel.theme.palette; 241 | this.emit('change', palette.slice()); 242 | settings.set('palette', palette.slice()); 243 | }; 244 | list.appendChild(colormap); 245 | } 246 | 247 | //update palette 248 | let els = list.querySelectorAll('.palette-color'); 249 | [].forEach.call(els, (el) => { 250 | list.removeChild(el); 251 | }); 252 | 253 | if (typeof palette === 'string') { 254 | palette = palette.split(','); 255 | } 256 | else if (Array.isArray(palette[0])) { 257 | palette = palette.map((arr) => `rgb(${arr.map(v => v.toFixed(0))})`); 258 | } 259 | 260 | palette.forEach((color) => { 261 | let item = document.createElement('li'); 262 | item.title = color; 263 | item.className = 'palette-color'; 264 | item.setAttribute('data-id', color); 265 | css(item, { 266 | height: '2em', 267 | width: '2em', 268 | float: 'left', 269 | background: color, 270 | cursor: 'move' 271 | }); 272 | list.insertBefore(item, randomize); 273 | 274 | //create picker for each color 275 | let picker = new Picker({ 276 | el: item, 277 | color: color, 278 | height: 162 279 | }); 280 | picker.$el.style.display = 'none'; 281 | item.onmouseout = (e) => { 282 | picker.$el.style.display = 'none' 283 | } 284 | item.onmouseover = function () { 285 | picker.$el.style.display = '' 286 | } 287 | picker.onChange((color) => { 288 | css(item, {background: color}); 289 | item.setAttribute('data-id', color); 290 | opts.sortable && this.emit('change', opts.sortable.toArray()); 291 | }) 292 | }); 293 | 294 | if (opts.sortable) { 295 | opts.sortable.destroy(); 296 | opts.sortable = null; 297 | } 298 | 299 | opts.sortable = new Sortable(list, { 300 | draggable: '.palette-color', 301 | filter: '.Scp', 302 | forceFallback: true, 303 | onUpdate: (e) => { 304 | this.emit('change', opts.sortable.toArray()); 305 | settings.set('palette', opts.sortable.toArray()); 306 | } 307 | }); 308 | 309 | return list; 310 | }, 311 | change: (v) => { 312 | let palette = null; 313 | if (v) { 314 | if (Array.isArray(v)) palette = v; 315 | else palette = v.split(/\s*,\s*/); 316 | } 317 | panel.update({palette: palette}); 318 | } 319 | }, 320 | // {label: 'Active color', type:'color', id: 'active', value: panel.theme.active || panel.theme.palette[0], change: v => { 321 | // panel.update({active: v}); 322 | // }}, 323 | {label: 'Font family', id: 'font-family', type: 'text', value: panel.theme.fontFamily, change: v => { 324 | panel.update({fontFamily: v}); 325 | }}, 326 | {type: 'raw', content: '
'}, 327 | {label: 'Label orientation', type: 'switch', options: {top: '↑', left: '←', bottom: '↓', right: '→'}, value: panel.orientation, change: (v) => { 328 | panel.update({orientation: v}); 329 | if (v === 'top' || v === 'bottom') { 330 | settings.set('label-width', { 331 | disabled: true 332 | }); 333 | } 334 | else { 335 | settings.set({ 336 | 'label-width': { 337 | disabled: false 338 | } 339 | }); 340 | } 341 | } 342 | }, 343 | {label: '1em = ', title: 'Font size', id: 'font-size', type: 'text', value: panel.theme.fontSize, change: (v) => { 344 | panel.update({fontSize: v}); 345 | }, orientation: 'left', style: 'width: 50%; float: left'}, 346 | {type: 'text', label: '        ↨', title: 'Input height, em', id: 'input-height', value: panel.theme.inputHeight, input: (v) => { 347 | panel.update({inputHeight: v}); 348 | }, orientation: 'left', style: 'width: 50%'}, 349 | {type: 'text', label: '       ↔', title: 'Full panel width', id: 'width', value: '32em', input: (v) => { 350 | panel.update({style: `width: ${v};`}) 351 | }, orientation: 'left', style: 'width: 50%; float: left'}, 352 | {label: '       ↦', title: 'Label width', id: 'label-width', type: 'text', value: panel.theme.labelWidth, change: (v) => { 353 | panel.update({labelWidth: v}); 354 | }, orientation: 'left', style: 'width: 50%'}, 355 | {label: 'padding', id: 'padding', type: 'range', min: 0, max: 1, value: 1/5, change: v => { 356 | panel.update({padding: v}) 357 | }}, 358 | {type: 'raw', content: '
'}, 359 | {type: 'button', label: 'Get the code!', input: () => { 360 | alert('code'); 361 | }} 362 | ], { 363 | // theme: require('./theme/dragon'), 364 | id: 'settings', 365 | className: 'sidebar', 366 | orientation: 'top', 367 | labelWidth: '22%', 368 | style: 'background: rgba(253,253,253,.82);' 369 | }); 370 | -------------------------------------------------------------------------------- /theme/control.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module prama/theme/control 3 | * 4 | * Control-panel theme on steroids 5 | */ 6 | 'use strict'; 7 | 8 | const px = require('add-px-to-style'); 9 | const fonts = require('google-fonts'); 10 | const color = require('tinycolor2'); 11 | const scopeCss = require('scope-css'); 12 | const interpolate = require('color-interpolate'); 13 | const none = require('./none'); 14 | 15 | module.exports = control; 16 | 17 | // control.palette = ['#292929', '#e7e7e7']; 18 | control.palette = ['#e7e7e7', '#292929']; 19 | 20 | control.fontSize = '12px'; 21 | control.fontFamily = '"Space Mono", monospace'; 22 | control.labelWidth = '33.3%'; 23 | control.inputHeight = 1.66666; 24 | control.padding = 1/8; 25 | 26 | fonts.add({ 27 | 'Space Mono': 400 28 | }); 29 | 30 | 31 | function control (opts) { 32 | opts = opts || {}; 33 | let fs = opts.fontSize || control.fontSize; 34 | let font = opts.fontFamily || control.fontFamily; 35 | let h = opts.inputHeight || control.inputHeight; 36 | let labelWidth = opts.labelWidth || control.labelWidth; 37 | let padding = opts.padding || control.padding; 38 | 39 | let palette = opts.palette || control.palette; 40 | let pick = interpolate(palette); 41 | 42 | let white = color(pick(.0)).toString(); 43 | let light = color(pick(.1)).toString(); 44 | let gray = color(pick(.75)).toString(); 45 | let dark = color(pick(.95)).toString(); 46 | let black = color(pick(1)).toString(); 47 | 48 | //NOTE: this is in case of scaling palette to black/white range 49 | // let white = color(pick(0.1607843137254902)).toString(); 50 | // let light = color(pick(0.23529411764705882)).toString(); 51 | // let gray = color(pick(0.5705882352941177)).toString(); 52 | // let dark = color(pick(0.7196078431372549)).toString(); 53 | // let black = color(pick(0.9058823529411765)).toString(); 54 | 55 | //none theme defines sizes, the rest (ours) is up to style 56 | return none({ 57 | fontSize: fs, 58 | fontFamily: font, 59 | inputHeight: h, 60 | labelWidth: labelWidth, 61 | padding: padding 62 | }) + ` 63 | :host { 64 | background: ${white}; 65 | font-family: ${font}; 66 | font-size: ${px('font-size',fs)}; 67 | color: ${gray}; 68 | padding: ${h/2}em; 69 | } 70 | 71 | .settings-panel-title { 72 | font-size: 1em; 73 | text-align: center; 74 | font-weight: 400; 75 | text-transform: uppercase; 76 | color: ${black}; 77 | padding-bottom: ${h/3}em; 78 | } 79 | 80 | .settings-panel-label { 81 | color: ${black}; 82 | } 83 | 84 | /** Text */ 85 | .settings-panel-text, 86 | .settings-panel-textarea { 87 | padding-left: ${h/4}em; 88 | border: none; 89 | font-family: inherit; 90 | background: ${light}; 91 | color: inherit; 92 | border-radius: 0; 93 | } 94 | .settings-panel-text:hover, 95 | .settings-panel-textarea:hover, 96 | .settings-panel-text:focus, 97 | .settings-panel-textarea:focus { 98 | outline: none; 99 | color: ${dark}; 100 | } 101 | 102 | 103 | /** Range */ 104 | .settings-panel-range { 105 | -webkit-appearance: none; 106 | -moz-appearance: none; 107 | appearance: none; 108 | background: ${light}; 109 | width: 80%; 110 | border-radius: 0; 111 | } 112 | .settings-panel-range:focus { 113 | outline: none; 114 | } 115 | .settings-panel-range::-webkit-slider-thumb { 116 | -webkit-appearance: none; 117 | height: ${h}em; 118 | width: ${h/2}em; 119 | background: ${gray}; 120 | border: 0; 121 | margin-top: 0px; 122 | } 123 | .settings-panel-range:hover::-webkit-slider-thumb { 124 | background: ${dark}; 125 | } 126 | .settings-panel-range::-moz-range-track { 127 | -moz-appearance: none; 128 | background: none; 129 | } 130 | .settings-panel-range::-moz-range-thumb { 131 | -moz-appearance: none; 132 | background: ${gray}; 133 | border: none; 134 | border-radius: 0px; 135 | height: ${h}em; 136 | width: ${h/2}em; 137 | } 138 | .settings-panel-range:hover::-moz-range-thumb { 139 | background: ${dark}; 140 | } 141 | .settings-panel-range::-ms-track { 142 | background: none; 143 | border: none; 144 | outline: none; 145 | color: transparent; 146 | } 147 | .settings-panel-range::-ms-fill-lower { 148 | background: none; 149 | } 150 | .settings-panel-range::-ms-fill-upper { 151 | background: none; 152 | } 153 | .settings-panel-range::-ms-thumb { 154 | border-radius: 0px; 155 | border: 0; 156 | background: ${gray}; 157 | width: ${h/2}em; 158 | height: ${h}em; 159 | } 160 | .settings-panel-range:hover::-ms-thumb { 161 | background: ${dark}; 162 | } 163 | .settings-panel-range:focus::-ms-fill-lower { 164 | background: none; 165 | outline: none; 166 | } 167 | .settings-panel-range:focus::-ms-fill-upper { 168 | background: none; 169 | outline: none; 170 | } 171 | 172 | 173 | /** Interval */ 174 | .settings-panel-interval-handle { 175 | background: ${gray}; 176 | } 177 | .settings-panel-interval { 178 | background: ${light}; 179 | position: relative; 180 | width: 60%; 181 | } 182 | .settings-panel-interval:hover .settings-panel-interval-handle { 183 | background: ${dark}; 184 | } 185 | 186 | /** Values */ 187 | .settings-panel-value { 188 | background: ${light}; 189 | margin-left: ${h/4}em; 190 | width: calc(20% - ${h/4}em); 191 | padding-left: ${h/4}em; 192 | } 193 | .settings-panel-value:first-child { 194 | margin-left: 0; 195 | margin-right: ${h/4}em; 196 | } 197 | .settings-panel-value:hover, 198 | .settings-panel-value:focus { 199 | color: ${dark}; 200 | } 201 | 202 | 203 | /** Select */ 204 | .settings-panel-select { 205 | font-family: inherit; 206 | background: ${light}; 207 | color: inherit; 208 | padding-left: ${h/4}em; 209 | border-radius: 0; 210 | outline: none; 211 | border: none; 212 | -webkit-appearance: none; 213 | -moz-appearance: none; 214 | -o-appearance:none; 215 | appearance:none; 216 | } 217 | .settings-panel-select:hover, 218 | .settings-panel-select:focus { 219 | color: ${dark}; 220 | } 221 | .settings-panel-select::-ms-expand { 222 | display: none; 223 | } 224 | .settings-panel-select-triangle { 225 | display: block; 226 | } 227 | 228 | 229 | /** Checkbox */ 230 | .settings-panel-checkbox { 231 | display: none; 232 | } 233 | .settings-panel-checkbox-label:before { 234 | content: 'x'; 235 | color: transparent; 236 | position: relative; 237 | display: inline-block; 238 | vertical-align: baseline; 239 | width: ${h*.85}em; 240 | height: ${h*.85}em; 241 | line-height: ${h*.85}em; 242 | background: ${light}; 243 | margin-right: ${h*.25}em; 244 | margin-bottom: ${h*.15}em; 245 | } 246 | .settings-panel-checkbox:checked + .settings-panel-checkbox-label:before { 247 | background: ${gray}; 248 | box-shadow: inset 0 0 0 ${h*.2}em ${light}; 249 | } 250 | .settings-panel-checkbox:checked + .settings-panel-checkbox-label:hover:before { 251 | background: ${dark}; 252 | } 253 | .settings-panel-checkbox-label:last-child:before { 254 | margin-bottom: ${h*.1}em; 255 | } 256 | 257 | 258 | /** Color */ 259 | .settings-panel-color { 260 | position: relative; 261 | width: calc(20% - ${h/4}em); 262 | margin-right: ${h/4}em; 263 | display: inline-block; 264 | vertical-align: baseline; 265 | } 266 | .settings-panel-color-value { 267 | border: none; 268 | padding-left: ${h/4}em; 269 | width: 80%; 270 | font-family: inherit; 271 | background: ${light}; 272 | color: inherit; 273 | border-radius: 0; 274 | } 275 | .settings-panel-color-value:hover, 276 | .settings-panel-color-value:focus { 277 | outline: none; 278 | color: ${dark}; 279 | } 280 | 281 | 282 | /** Button */ 283 | .settings-panel-button { 284 | color: ${black}; 285 | background: ${light}; 286 | text-align: center; 287 | border: none; 288 | } 289 | .settings-panel-button:focus { 290 | outline: none; 291 | } 292 | .settings-panel-button:hover { 293 | background: ${gray}; 294 | } 295 | .settings-panel-button:active { 296 | background: ${dark}; 297 | } 298 | 299 | 300 | /** Switch style */ 301 | .settings-panel-switch { 302 | } 303 | .settings-panel-switch-input { 304 | display: none; 305 | } 306 | .settings-panel-switch-label { 307 | position: relative; 308 | display: inline-block; 309 | padding: 0 ${h/2}em; 310 | margin: 0; 311 | z-index: 2; 312 | text-align: center; 313 | } 314 | .settings-panel-switch-input:checked + .settings-panel-switch-label { 315 | background: ${light}; 316 | color: ${gray}; 317 | } 318 | .settings-panel-switch-input + .settings-panel-switch-label:hover { 319 | color: ${dark}; 320 | } 321 | 322 | /** Decorations */ 323 | ::-webkit-input-placeholder { 324 | color: ${gray}; 325 | } 326 | ::-moz-placeholder { 327 | color: ${gray}; 328 | } 329 | :-ms-input-placeholder { 330 | color: ${gray}; 331 | } 332 | :-moz-placeholder { 333 | color: ${gray}; 334 | } 335 | ::-moz-selection { 336 | color: ${white}; 337 | background: ${dark}; 338 | } 339 | ::selection { 340 | color: ${white}; 341 | background: ${black}; 342 | } 343 | :host hr { 344 | margin: ${h/4}em ${h/8}em; 345 | color: ${light}; 346 | opacity: 1; 347 | } 348 | :host a { 349 | border-bottom: 1px solid ${alpha(gray, .15)}; 350 | } 351 | :host a:hover { 352 | color: ${dark}; 353 | border-bottom: 1px solid ${gray}; 354 | } 355 | `}; 356 | 357 | 358 | function alpha (c, value) { 359 | return color(c).setAlpha(value).toString(); 360 | } 361 | -------------------------------------------------------------------------------- /theme/dat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module prama/theme/dat 3 | * 4 | * dat.gui reproduction 5 | */ 6 | 'use strict'; 7 | 8 | const px = require('add-px-to-style'); 9 | const fonts = require('google-fonts'); 10 | const color = require('tinycolor2'); 11 | const scopeCss = require('scope-css'); 12 | const lerp = require('interpolation-arrays'); 13 | const none = require('./none'); 14 | 15 | module.exports = dat; 16 | 17 | fonts.add({ 18 | 'Roboto': 400 19 | }); 20 | 21 | dat.palette = ['#1b1b1b', '#f7f7f7']; 22 | 23 | dat.fontSize = '12px'; 24 | dat.fontFamily = '"Roboto", sans-serif'; 25 | dat.labelWidth = '33.3%'; 26 | dat.inputHeight = 2; 27 | 28 | function dat (opts) { 29 | opts = opts || {}; 30 | 31 | let h = opts.inputHeight || dat.inputHeight; 32 | let labelWidth = opts.labelWidth || dat.labelWidth; 33 | let fontSize = opts.fontSize || dat.fontSize; 34 | let font = opts.fontFamily || dat.fontFamily; 35 | 36 | let palette = (opts.palette || dat.palette).map(v => color(v).toRgb()); 37 | let pick = lerp(palette); 38 | 39 | let white = color(pick(1)).toString(); 40 | let light = color(pick(.6)).toString(); 41 | let notSoLight = color(pick(.5)).toString(); 42 | let gray = color(pick(.13)).toString(); 43 | let dark = color(pick(.07)).toString(); 44 | let black = color(pick(0)).toString(); 45 | 46 | return none({ 47 | fontSize: fontSize, 48 | fontFamily: font, 49 | inputHeight: h, 50 | labelWidth: labelWidth, 51 | palette: palette 52 | }) + -------------------------------------------------------------------------------- /theme/dragon.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module prama/theme/dragon 3 | * 4 | * Midragonlistic theme based off https://dribbble.com/guidorosso NIMA editor settings 5 | */ 6 | 'use strict'; 7 | 8 | const px = require('add-px-to-style'); 9 | const fonts = require('google-fonts'); 10 | const color = require('tinycolor2'); 11 | const interpolate = require('color-interpolate'); 12 | const none = require('./none'); 13 | 14 | module.exports = dragon; 15 | 16 | fonts.add({ 17 | 'Roboto': 400 18 | }); 19 | 20 | dragon.palette = ['black', 'white']; 21 | dragon.palette = ['#1b1b1b', '#f7f7f7']; 22 | 23 | dragon.fontSize = '12px'; 24 | dragon.fontFamily = '"Roboto", sans-serif'; 25 | dragon.labelWidth = '33.3%'; 26 | dragon.inputHeight = 2; 27 | dragon.padding = 1/5; 28 | 29 | function dragon (opts) { 30 | opts = opts || {}; 31 | 32 | let h = opts.inputHeight || dragon.inputHeight; 33 | let labelWidth = opts.labelWidth || dragon.labelWidth; 34 | let fontSize = opts.fontSize || dragon.fontSize; 35 | let font = opts.fontFamily || dragon.fontFamily; 36 | let padding = opts.padding || dragon.padding; 37 | 38 | let palette = opts.palette || dragon.palette; 39 | let pick = interpolate(palette); 40 | 41 | let white = color(pick(1)).toString(); 42 | let light = color(pick(.65)).toString(); 43 | let notSoLight = color(pick(.45)).toString(); 44 | let gray = color(pick(.13)).toString(); 45 | let dark = color(pick(.07)).toString(); 46 | let black = color(pick(0)).toString(); 47 | 48 | return none({ 49 | fontSize: fontSize, 50 | fontFamily: font, 51 | inputHeight: h, 52 | labelWidth: labelWidth, 53 | palette: [white, black], 54 | padding: padding 55 | }) + ` 56 | :host { 57 | color: ${light}; 58 | background: ${alpha(gray, .93)}; 59 | font-size: ${px('font-size', fontSize)}; 60 | font-family: ${font}; 61 | font-weight: 400; 62 | padding: ${h*.5}em; 63 | } 64 | :host a { 65 | text-decoration: none; 66 | border-bottom: .15em solid ${alpha(white, .2)}; 67 | } 68 | :host a:hover { 69 | text-decoration: none; 70 | border-bottom: .15em solid ${alpha(white, 1)}; 71 | } 72 | 73 | .settings-panel-title { 74 | text-transform: none; 75 | font-weight: 400; 76 | letter-spacing: .05ex; 77 | color: ${white}; 78 | padding: ${h * 2 * padding / 1.333}em ${h * padding / 1.333 }em ${h * 4 * padding / 1.333}em; 79 | } 80 | 81 | .settings-panel-label { 82 | } 83 | 84 | /** Select style */ 85 | .settings-panel-select { 86 | height: ${h}em; 87 | background: none; 88 | outline: none; 89 | border: none; 90 | -webkit-appearance: none; 91 | -moz-appearance: none; 92 | -o-appearance:none; 93 | appearance:none; 94 | width: 100%; 95 | padding-right: 1em; 96 | margin-right: -1em; 97 | color: ${white}; 98 | border-radius: 0; 99 | box-shadow: 0 2px ${dark}; 100 | } 101 | .settings-panel-select::-ms-expand { 102 | display:none; 103 | } 104 | .settings-panel-select-triangle { 105 | content: ''; 106 | border-right: .3em solid transparent; 107 | border-left: .3em solid transparent; 108 | line-height: 2em; 109 | position: relative; 110 | z-index: 1; 111 | vertical-align: middle; 112 | display: inline-block; 113 | width: 0; 114 | text-align: center; 115 | pointer-events: none; 116 | } 117 | .settings-panel-select-triangle--down { 118 | top: 0em; 119 | left: .5em; 120 | border-top: .3em solid ${white}; 121 | border-bottom: .0 transparent; 122 | } 123 | .settings-panel-select-triangle--up { 124 | display: none; 125 | } 126 | .settings-panel-field--select:hover .settings-panel-select, 127 | .settings-panel-select:focus { 128 | box-shadow: 0 2px ${black}; 129 | } 130 | 131 | /** Values */ 132 | .settings-panel-value { 133 | color: ${white}; 134 | } 135 | .settings-panel-value:hover, 136 | .settings-panel-value:focus { 137 | color: ${white}; 138 | } 139 | 140 | /** Text */ 141 | .settings-panel-text, 142 | .settings-panel-textarea { 143 | -webkit-appearance: none; 144 | -moz-appearance: none; 145 | -o-appearance:none; 146 | border: none; 147 | height: ${h}em; 148 | padding: 0; 149 | background: none; 150 | color: ${white}; 151 | width: 100%; 152 | border-radius: 0; 153 | box-shadow: 0 2px ${dark}; 154 | } 155 | .settings-panel-textarea { 156 | padding-top: .35em; 157 | padding-left: 0; 158 | } 159 | 160 | .settings-panel-text:focus, 161 | .settings-panel-textarea:focus, 162 | .settings-panel-text:hover, 163 | .settings-panel-textarea:hover { 164 | outline: none; 165 | color: ${white}; 166 | box-shadow: 0 2px ${black}; 167 | } 168 | 169 | /** Color */ 170 | .settings-panel-color { 171 | height: ${h*.7}em; 172 | width: ${h*.7}em; 173 | top: 0; 174 | bottom: 0; 175 | margin-top: auto; 176 | margin-bottom: auto; 177 | } 178 | .settings-panel-color-value { 179 | -webkit-appearance: none; 180 | -moz-appearance: none; 181 | -o-appearance:none; 182 | border: none; 183 | background: none; 184 | height: ${h}em; 185 | color: ${white}; 186 | box-shadow: 0 2px ${dark}; 187 | padding-left: ${h}em; 188 | width: 100%; 189 | } 190 | .settings-panel-color-value:hover, 191 | .settings-panel-color-value:focus { 192 | outline: none; 193 | color: ${white}; 194 | box-shadow: 0 2px ${black}; 195 | } 196 | 197 | 198 | /** Switch style */ 199 | .settings-panel-switch { 200 | -webkit-appearance: none; 201 | -moz-appearance: none; 202 | appearance: none; 203 | margin-left: -1px; 204 | } 205 | .settings-panel-switch-input { 206 | display: none; 207 | } 208 | .settings-panel-switch-label { 209 | cursor: pointer; 210 | min-height: ${h}em; 211 | padding: 0 ${h/2}em; 212 | margin: 0 2px 2px 0; 213 | line-height: ${h*.999}em; 214 | color: ${notSoLight}; 215 | border-radius: ${h}em; 216 | } 217 | .settings-panel-switch-label:hover { 218 | color: ${white}; 219 | } 220 | .settings-panel-switch-label:last-child { 221 | margin-right: 0; 222 | } 223 | .settings-panel-switch-input:checked + .settings-panel-switch-label { 224 | color: ${white}; 225 | box-shadow: 0 0 0 2px ${notSoLight}; 226 | } 227 | .settings-panel-switch-input:checked + .settings-panel-switch-label:hover { 228 | box-shadow: 0 0 0 2px ${white}; 229 | } 230 | 231 | 232 | /** Checkbox */ 233 | .settings-panel-checkbox { 234 | display: none; 235 | } 236 | .settings-panel-checkbox-label { 237 | position: relative; 238 | margin-top: ${h/6}em; 239 | width: 100%; 240 | color: ${notSoLight}; 241 | margin-bottom: ${h/6}em; 242 | } 243 | .settings-panel-checkbox-label:empty { 244 | margin-left: -${h/4}em; 245 | } 246 | .settings-panel-checkbox-label:after { 247 | content: 'x'; 248 | color: transparent; 249 | position: absolute; 250 | top: -${h*.125}em; 251 | right: 0; 252 | margin-left: ${h/4}em; 253 | margin-top: 0; 254 | width: ${h*1.8}em; 255 | height: ${h*.85}em; 256 | line-height: ${h*.9}em; 257 | border-radius: ${h}em; 258 | margin-bottom: 0; 259 | background: ${dark}; 260 | } 261 | .settings-panel-checkbox:hover + .settings-panel-checkbox-label:after, 262 | .settings-panel-checkbox-label:hover:after { 263 | background: ${black}; 264 | } 265 | .settings-panel-checkbox-label:before { 266 | content: ''; 267 | position: absolute; 268 | border-radius: ${h}em; 269 | width: ${h*.6}em; 270 | height: ${h*.6}em; 271 | top: 0; 272 | right: ${h*1.05}em; 273 | background: ${notSoLight}; 274 | transition: .1s ease-in; 275 | z-index: 1; 276 | } 277 | .settings-panel-checkbox:checked + .settings-panel-checkbox-label:before { 278 | transform: translateX(${h*.9}em); 279 | background: ${white}; 280 | } 281 | 282 | 283 | /** Button */ 284 | .settings-panel-button { 285 | -webkit-appearance: none; 286 | -moz-appearance: none; 287 | appearance: none; 288 | border: none; 289 | outline: none; 290 | padding: ${h*.125}em; 291 | min-height: ${h}em; 292 | line-height: ${h}em; 293 | color: ${white}; 294 | border-radius: ${h}em; 295 | background: none; 296 | text-transform: uppercase; 297 | font-size: .95em; 298 | letter-spacing: .1ex; 299 | box-shadow: inset 0 0 0 2px ${notSoLight}; 300 | } 301 | .settings-panel-button:hover { 302 | color: ${white}; 303 | box-shadow: 0 0 0 2px ${white}; 304 | } 305 | .settings-panel-button:active { 306 | color: ${white}; 307 | } 308 | 309 | 310 | /** Sliders */ 311 | .settings-panel-range { 312 | -webkit-appearance: none; 313 | -moz-appearance: none; 314 | appearance: none; 315 | background: none; 316 | color: ${dark}; 317 | border: 0; 318 | } 319 | .settings-panel-field--range:hover .settings-panel-range, 320 | .settings-panel-range:focus { 321 | color: ${black}; 322 | outline: none; 323 | } 324 | .settings-panel-range::-webkit-slider-runnable-track { 325 | background: none; 326 | height: 2px; 327 | background: ${dark}; 328 | } 329 | .settings-panel-field--range:hover .settings-panel-range::-webkit-slider-runnable-track, 330 | .settings-panel-range:focus::-webkit-slider-runnable-track { 331 | /**background: ${black};*/ 332 | } 333 | .settings-panel-range::-moz-range-track { 334 | background: none; 335 | height: 2px; 336 | color: transparent; 337 | border: none; 338 | outline: none; 339 | background: ${dark}; 340 | } 341 | .settings-panel-field--range:hover .settings-panel-range::-moz-range-track, 342 | .settings-panel-range:focus::-moz-range-track { 343 | background: ${black}; 344 | } 345 | .settings-panel-range::-moz-range-progress { 346 | background: ${notSoLight}; 347 | } 348 | .settings-panel-field--range:hover .settings-panel-range::-moz-range-progress, 349 | .settings-panel-range:focus::-moz-range-progress { 350 | background: ${white}; 351 | } 352 | .settings-panel-range::-ms-track { 353 | height: 2px; 354 | color: transparent; 355 | border: none; 356 | outline: none; 357 | } 358 | .settings-panel-range::-ms-fill-lower { 359 | background: ${notSoLight}; 360 | } 361 | .settings-panel-range::-ms-fill-upper { 362 | background: ${black}; 363 | } 364 | .settings-panel-field--range:hover .settings-panel-range::-ms-fill-lower, 365 | .settings-panel-range:focus::-ms-fill-lower { 366 | background: ${white}; 367 | } 368 | 369 | @supports (--css: variables) { 370 | .settings-panel-range { 371 | --active: ${notSoLight}; 372 | --bg: ${dark}; 373 | --track-background: linear-gradient(to right, var(--active) 0, var(--active) var(--value), var(--bg) 0) no-repeat; 374 | } 375 | .settings-panel-range::-webkit-slider-runnable-track { 376 | background: var(--track-background); 377 | } 378 | .settings-panel-range::-moz-range-track { 379 | background: var(--track-background); 380 | } 381 | .settings-panel-field--range:hover .settings-panel-range, 382 | .settings-panel-range:focus { 383 | --bg: ${black}; 384 | --active: ${white}; 385 | } 386 | } 387 | 388 | .settings-panel-range::-webkit-slider-thumb { 389 | background: ${notSoLight}; 390 | height: ${h/2}em; 391 | width: ${h/2}em; 392 | border-radius: ${h/2}em; 393 | margin-top: -${h/4}em; 394 | border: 0; 395 | position: relative; 396 | top: 1px; 397 | -webkit-appearance: none; 398 | appearance: none; 399 | } 400 | .settings-panel-range:focus::-webkit-slider-thumb, 401 | .settings-panel-range:hover::-webkit-slider-thumb, 402 | .settings-panel-field--range:hover .settings-panel-range::-webkit-slider-thumb { 403 | background: ${white}; 404 | } 405 | .settings-panel-range::-moz-range-thumb { 406 | background: ${notSoLight}; 407 | height: ${h/2}em; 408 | width: ${h/2}em; 409 | border-radius: ${h/2}em; 410 | margin-top: -${h/4}em; 411 | border: 0; 412 | position: relative; 413 | top: 1px; 414 | -webkit-appearance: none; 415 | -moz-appearance: none; 416 | } 417 | .settings-panel-range:focus::-moz-range-thumb, 418 | .settings-panel-range:hover::-moz-range-thumb, 419 | .settings-panel-field--range:hover .settings-panel-range::-moz-range-thumb { 420 | background: ${white}; 421 | } 422 | .settings-panel-range::-ms-thumb { 423 | appearance: none; 424 | outline: 0; 425 | border: none; 426 | position: relative; 427 | top: 1px; 428 | background: ${notSoLight}; 429 | width: ${h/2}em; 430 | height: ${h/2}em; 431 | border-radius: ${h/2}em; 432 | cursor: pointer; 433 | } 434 | .settings-panel-range:focus::-ms-thumb, 435 | .settings-panel-range:hover::-ms-thumb, 436 | .settings-panel-field--range:hover .settings-panel-range::-ms-thumb { 437 | background: ${white}; 438 | } 439 | 440 | .settings-panel-range-value { 441 | text-align: right; 442 | padding: 0; 443 | } 444 | 445 | :host.settings-panel-orientation-top .settings-panel-range, 446 | .settings-panel-orientation-top .settings-panel-range { 447 | width: 100%; 448 | } 449 | :host.settings-panel-orientation-top .settings-panel-range + .settings-panel-value, 450 | .settings-panel-orientation-top .settings-panel-range + .settings-panel-value { 451 | position: absolute; 452 | top: -${h*.8}em; 453 | right: 0%; 454 | text-align: right; 455 | } 456 | 457 | .settings-panel-field--color + .settings-panel-field--range, 458 | .settings-panel-field--color + .settings-panel-field--interval, 459 | .settings-panel-field--textarea + .settings-panel-field--range, 460 | .settings-panel-field--textarea + .settings-panel-field--interval, 461 | .settings-panel-field--text + .settings-panel-field--interval, 462 | .settings-panel-field--text + .settings-panel-field--range { 463 | margin-top: ${h/2.5}em; 464 | } 465 | 466 | 467 | /** Interval */ 468 | .settings-panel-interval { 469 | background: none; 470 | } 471 | .settings-panel-interval:after { 472 | content: ''; 473 | position: absolute; 474 | width: 100%; 475 | left: 0; 476 | bottom: 0; 477 | top: 0; 478 | background: ${dark}; 479 | height: 2px; 480 | margin-top: auto; 481 | margin-bottom: auto; 482 | } 483 | .settings-panel-interval-handle { 484 | position: absolute; 485 | z-index: 1; 486 | height: 2px; 487 | top: 0; 488 | bottom: 0; 489 | margin-top: auto; 490 | margin-bottom: auto; 491 | background: ${notSoLight}; 492 | } 493 | .settings-panel-field--interval:hover .settings-panel-interval:after { 494 | background: ${black}; 495 | } 496 | .settings-panel-field--interval:hover .settings-panel-interval-handle { 497 | background: ${white}; 498 | } 499 | .settings-panel-field--interval:hover .settings-panel-value { 500 | color: ${white}; 501 | } 502 | .settings-panel-interval-value--right { 503 | text-align: right; 504 | } 505 | .settings-panel-interval-handle:after { 506 | content: ''; 507 | position: absolute; 508 | right: -${h/4}em; 509 | top: 0; 510 | bottom: 0; 511 | margin: auto; 512 | height: ${h/2}em; 513 | width: ${h/2}em; 514 | border-radius: ${h/2}em; 515 | background: inherit; 516 | } 517 | .settings-panel-interval-handle:before { 518 | content: ''; 519 | position: absolute; 520 | left: -${h/4}em; 521 | top: 0; 522 | bottom: 0; 523 | margin: auto; 524 | height: ${h/2}em; 525 | width: ${h/2}em; 526 | border-radius: ${h/2}em; 527 | background: inherit; 528 | } 529 | 530 | .settings-panel-interval-dragging .settings-panel-interval-handle { 531 | background: ${white}; 532 | } 533 | 534 | 535 | /** Decorations */ 536 | :host hr { 537 | border: none; 538 | height: 0; 539 | margin: ${h*.25}em 0; 540 | opacity: .333; 541 | border-bottom: 1px dotted ${notSoLight}; 542 | } 543 | ::-webkit-input-placeholder { 544 | color: ${notSoLight}; 545 | } 546 | ::-moz-placeholder { 547 | color: ${notSoLight}; 548 | } 549 | :-ms-input-placeholder { 550 | color: ${notSoLight}; 551 | } 552 | :-moz-placeholder { 553 | color: ${notSoLight}; 554 | } 555 | 556 | ::-moz-selection { 557 | color: ${white}; 558 | background: ${gray}; 559 | } 560 | ::selection { 561 | color: ${white}; 562 | background: ${gray}; 563 | } 564 | .settings-panel-field--disabled { 565 | opacity: .333; 566 | pointer-events: none; 567 | } 568 | `; 569 | 570 | 571 | } 572 | 573 | 574 | function alpha (c, value) { 575 | return color(c).setAlpha(value).toString(); 576 | } 577 | -------------------------------------------------------------------------------- /theme/flat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module prama/theme/flat 3 | * 4 | * Control-panel theme on steroids 5 | */ 6 | 'use strict'; 7 | 8 | const px = require('add-px-to-style'); 9 | const fonts = require('google-fonts'); 10 | const color = require('tinycolor2'); 11 | const scopeCss = require('scope-css'); 12 | const none = require('./none'); 13 | const interpolate = require('color-interpolate'); 14 | 15 | module.exports = flat; 16 | 17 | //uses reflective scheme 18 | flat.palette = ['black', '#fff']; 19 | flat.palette = ['#272727', '#f95759', '#fff']; 20 | // flat.active = '#f95759'; 21 | 22 | flat.fontSize = '14px'; 23 | flat.fontFamily = '"Roboto", sans-serif'; 24 | flat.labelWidth = '33.3%'; 25 | flat.inputHeight = 2; 26 | flat.padding = 1/5; 27 | 28 | fonts.add({ 29 | 'Roboto': 500, 30 | 'Material Icons': 400 31 | }); 32 | 33 | 34 | function flat (opts) { 35 | opts = opts || {}; 36 | let fs = opts.fontSize || flat.fontSize; 37 | let font = opts.fontFamily || flat.fontFamily; 38 | let h = opts.inputHeight || flat.inputHeight; 39 | let labelWidth = opts.labelWidth || flat.labelWidth; 40 | let padding = opts.padding || flat.padding; 41 | 42 | let palette = opts.palette || flat.palette; 43 | let pick = interpolate(palette); 44 | 45 | //NOTE: this is in case of scaling palette to black/white range 46 | let white = tone(1); 47 | let black = tone(0); 48 | let active = opts.active || tone(.5); 49 | 50 | function tone (amt) { 51 | return color(pick(amt)).toString(); 52 | } 53 | 54 | //none theme defines sizes, the rest (ours) is up to style 55 | return none({ 56 | fontSize: fs, 57 | fontFamily: font, 58 | inputHeight: h, 59 | labelWidth: labelWidth, 60 | padding: padding 61 | }) + ` 62 | :host { 63 | background: ${white}; 64 | color: ${black}; 65 | font-family: ${font}; 66 | font-weight: 500; 67 | -webkit-text-size-adjust: 100%; 68 | -webkit-font-smoothing: antialiased; 69 | } 70 | :host a { 71 | text-decoration: none; 72 | border-bottom: 1px solid ${alpha(tone(.0), .2)}; 73 | } 74 | :host a:hover { 75 | text-decoration: none; 76 | border-bottom: 1px solid ${alpha(tone(.0), 1)}; 77 | } 78 | 79 | .settings-panel-title { 80 | color: ${tone(.0)}; 81 | font-family: ${font}; 82 | font-weight: 500; 83 | } 84 | 85 | .settings-panel-label { 86 | color: ${alpha(tone(.0), .666)}; 87 | font-weight: 500; 88 | } 89 | 90 | /** Text */ 91 | .settings-panel-text, 92 | .settings-panel-textarea, 93 | .settings-panel-color-value { 94 | -webkit-appearance: none; 95 | -moz-appearance: none; 96 | -o-appearance: none; 97 | appearance: none; 98 | outline: none; 99 | border: 0; 100 | width: auto; 101 | border-radius: 0; 102 | font-weight: 500; 103 | background: none; 104 | color: ${active}; 105 | box-shadow: 0 1px ${alpha(tone(.0), .2)}; 106 | } 107 | .settings-panel-text:hover, 108 | .settings-panel-color-value:hover, 109 | .settings-panel-textarea:hover { 110 | color: ${active}; 111 | } 112 | .settings-panel-text:focus, 113 | .settings-panel-color-value:focus, 114 | .settings-panel-textarea:focus { 115 | box-shadow: 0 1px ${active}; 116 | 117 | } 118 | 119 | 120 | /** Sliders */ 121 | .settings-panel-range { 122 | -webkit-appearance: none; 123 | -moz-appearance: none; 124 | appearance: none; 125 | background: none; 126 | color: ${tone(0)}; 127 | border: 0; 128 | width: 85%; 129 | margin-right: ${h/4}em; 130 | } 131 | .settings-panel-range + .settings-panel-value { 132 | width: calc(15% - ${h/4}em); 133 | padding-left: 0; 134 | } 135 | .settings-panel-field--range:hover .settings-panel-range, 136 | .settings-panel-range:focus { 137 | outline: none; 138 | } 139 | .settings-panel-range::-webkit-slider-runnable-track { 140 | background: none; 141 | height: 2px; 142 | background: ${active}; 143 | } 144 | .settings-panel-field--range:hover .settings-panel-range::-webkit-slider-runnable-track, 145 | .settings-panel-range:focus::-webkit-slider-runnable-track { 146 | /* background: ${active}; */ 147 | } 148 | .settings-panel-range::-moz-range-track { 149 | background: none; 150 | height: 2px; 151 | background: ${active}; 152 | } 153 | .settings-panel-field--range:hover .settings-panel-range::-moz-range-track, 154 | .settings-panel-range:focus::-moz-range-track { 155 | /* background: ${active}; */ 156 | } 157 | 158 | .settings-panel-range::-ms-track { 159 | height: 2px; 160 | color: transparent; 161 | border: none; 162 | outline: none; 163 | } 164 | .settings-panel-range::-ms-fill-lower { 165 | background: ${active}; 166 | } 167 | .settings-panel-range::-ms-fill-upper { 168 | background: ${alpha(active, .2)}; 169 | } 170 | 171 | @supports (--css: variables) { 172 | .settings-panel-range { 173 | --active: ${active}; 174 | --bg: ${alpha(active, .2)}; 175 | --track-background: linear-gradient(to right, var(--active) 0, var(--active) var(--value), var(--bg) 0) no-repeat; 176 | } 177 | .settings-panel-range::-webkit-slider-runnable-track { 178 | background: var(--track-background); 179 | } 180 | .settings-panel-range::-moz-range-track { 181 | background: var(--track-background); 182 | } 183 | .settings-panel-field--range:hover .settings-panel-range, 184 | .settings-panel-range:focus { 185 | --bg: ${alpha(active, .2)}; 186 | --active: ${active}; 187 | } 188 | } 189 | 190 | .settings-panel-range::-webkit-slider-thumb { 191 | background: ${active}; 192 | height: ${h/2}em; 193 | width: ${h/2}em; 194 | border-radius: ${h/2}em; 195 | margin-top: -${h/4}em; 196 | border: 0; 197 | position: relative; 198 | top: 1px; 199 | -webkit-appearance: none; 200 | appearance: none; 201 | transition: .05s ease-in transform; 202 | transform: scale(1, 1); 203 | transform-origin: center center; 204 | } 205 | .settings-panel-range:focus::-webkit-slider-thumb, 206 | .settings-panel-range::-webkit-slider-thumb:hover { 207 | box-shadow: 0 0 0 0; 208 | transform: scale(1.2, 1.2); 209 | } 210 | .settings-panel-range[value="0"]::-webkit-slider-thumb { 211 | background: ${white}; 212 | box-shadow: inset 0 0 0 1.5px ${active}; 213 | } 214 | .settings-panel-range::-moz-range-thumb { 215 | background: ${active}; 216 | height: ${h/2}em; 217 | width: ${h/2}em; 218 | border-radius: ${h/2}em; 219 | margin-top: -${h/4}em; 220 | border: 0; 221 | position: relative; 222 | top: 1px; 223 | -moz-appearance: none; 224 | appearance: none; 225 | transition: .05s ease-in transform; 226 | transform: scale(1, 1); 227 | transform-origin: center center; 228 | } 229 | .settings-panel-range:focus::-moz-range-thumb, 230 | .settings-panel-range::-moz-range-thumb:hover { 231 | box-shadow: 0 0 0 0; 232 | transform: scale(1.2, 1.2); 233 | } 234 | .settings-panel-range[value="0"]::-moz-range-thumb { 235 | background: ${white}; 236 | box-shadow: inset 0 0 0 1.5px ${active}; 237 | } 238 | .settings-panel-range::-ms-thumb { 239 | background: ${active}; 240 | height: ${h/2}em; 241 | width: ${h/2}em; 242 | border-radius: ${h/2}em; 243 | border: 0; 244 | position: relative; 245 | top: 1px; 246 | appearance: none; 247 | transition: .05s ease-in transform; 248 | transform: scale(1, 1); 249 | transform-origin: center center; 250 | } 251 | .settings-panel-range:focus::-ms-thumb, 252 | .settings-panel-range::-ms-thumb:hover { 253 | box-shadow: 0 0 0 0; 254 | transform: scale(1.2, 1.2); 255 | } 256 | .settings-panel-range[value="0"]::-ms-thumb { 257 | background: ${white}; 258 | box-shadow: inset 0 0 0 1.5px ${active}; 259 | } 260 | 261 | /** Interval */ 262 | .settings-panel-interval { 263 | background: none; 264 | } 265 | .settings-panel-interval:after { 266 | content: ''; 267 | position: absolute; 268 | width: 100%; 269 | left: 0; 270 | bottom: 0; 271 | top: 0; 272 | background: ${alpha(active, .2)}; 273 | height: 2px; 274 | margin-top: auto; 275 | margin-bottom: auto; 276 | } 277 | .settings-panel-interval-handle { 278 | position: absolute; 279 | z-index: 1; 280 | height: 2px; 281 | top: 0; 282 | bottom: 0; 283 | margin-top: auto; 284 | margin-bottom: auto; 285 | background: ${active}; 286 | } 287 | .settings-panel-field--interval:hover .settings-panel-interval:after { 288 | background: ${alpha(active, .2)}; 289 | } 290 | .settings-panel-field--interval:hover .settings-panel-interval-handle { 291 | background: ${active}; 292 | } 293 | .settings-panel-field--interval:hover .settings-panel-value { 294 | color: ${black}; 295 | font-weight: 500; 296 | } 297 | .settings-panel-interval-handle:after, 298 | .settings-panel-interval-handle:before { 299 | content: ''; 300 | position: absolute; 301 | right: -${h/4}em; 302 | top: 0; 303 | bottom: 0; 304 | margin: auto; 305 | height: ${h/2}em; 306 | width: ${h/2}em; 307 | border-radius: ${h/2}em; 308 | background: inherit; 309 | transform: scale(1, 1); 310 | transform-origin: center center; 311 | transition: .05s ease-in transform; 312 | } 313 | .settings-panel-interval-handle:before { 314 | left: -${h/4}em; 315 | right: auto; 316 | } 317 | .settings-panel-interval-dragging .settings-panel-interval-handle:after, 318 | .settings-panel-interval-dragging .settings-panel-interval-handle:before, 319 | .settings-panel-interval:hover .settings-panel-interval-handle:after, 320 | .settings-panel-interval:hover .settings-panel-interval-handle:before { 321 | transform: scale(1.2, 1.2); 322 | } 323 | 324 | 325 | /** Values */ 326 | .settings-panel-value { 327 | color: ${tone(0)}; 328 | font-weight: 500; 329 | } 330 | .settings-panel-value:first-child { 331 | margin-left: 0; 332 | } 333 | .settings-panel-value:hover, 334 | .settings-panel-value:focus { 335 | } 336 | 337 | 338 | /** Select */ 339 | .settings-panel-select { 340 | font-family: inherit; 341 | color: inherit; 342 | border-radius: 0; 343 | outline: none; 344 | border: none; 345 | -webkit-appearance: none; 346 | -moz-appearance: none; 347 | -o-appearance:none; 348 | appearance:none; 349 | font-weight: 500; 350 | padding-right: 2em; 351 | margin-right: -1em; 352 | color: ${active}; 353 | background: none; 354 | line-height: ${h}em; 355 | box-shadow: 0 1px ${alpha(tone(.0), .2)}; 356 | width: auto; 357 | } 358 | .settings-panel-select:hover, 359 | .settings-panel-select:focus { 360 | } 361 | .settings-panel-select::-ms-expand { 362 | display: none; 363 | } 364 | .settings-panel-select-triangle { 365 | content: ''; 366 | border-right: .3em solid transparent; 367 | border-left: .3em solid transparent; 368 | line-height: 2em; 369 | position: relative; 370 | z-index: 1; 371 | vertical-align: middle; 372 | display: inline-block; 373 | width: 0; 374 | text-align: center; 375 | pointer-events: none; 376 | } 377 | .settings-panel-select-triangle--down { 378 | top: 0em; 379 | left: .5em; 380 | border-top: .3em solid ${active}; 381 | border-bottom: .0 transparent; 382 | } 383 | .settings-panel-select-triangle--up { 384 | display: none; 385 | } 386 | .settings-panel-field--select:hover .settings-panel-select, 387 | .settings-panel-select:focus { 388 | } 389 | 390 | 391 | /** Checkbox */ 392 | .settings-panel-checkbox { 393 | display: none; 394 | } 395 | .settings-panel-checkbox-label { 396 | display: inline-block; 397 | color: ${tone(0)}; 398 | position: relative; 399 | margin-right: ${h}em; 400 | /* margin-bottom: ${h/2}em; */ 401 | } 402 | .settings-panel-checkbox-label:before { 403 | /*content: '✓';*/ 404 | font-family: "Material Icons"; 405 | content: ''; 406 | font-weight: bolder; 407 | color: ${alpha(white, 0)}; 408 | display: block; 409 | float: left; 410 | width: ${h*.5}em; 411 | height: ${h*.5}em; 412 | border-radius: .5px; 413 | position: relative; 414 | margin-right: ${h/3}em; 415 | margin-left: 2px; 416 | box-shadow: 0 0 0 2px ${alpha(tone(0), .9)}; 417 | line-height: ${h/2}em; 418 | margin-top: 1px; 419 | text-align: center; 420 | } 421 | .settings-panel-checkbox-label:hover:before { 422 | box-shadow: 0 0 0 2px ${tone(0)}; 423 | } 424 | .settings-panel-checkbox:checked + .settings-panel-checkbox-label { 425 | color: ${active}; 426 | } 427 | .settings-panel-checkbox:checked + .settings-panel-checkbox-label:before { 428 | box-shadow: 0 0 0 2px ${active}; 429 | background: ${active}; 430 | color: ${tone(1)}; 431 | } 432 | .settings-panel-checkbox-label:after { 433 | content: ''; 434 | z-index: 1; 435 | position: absolute; 436 | width: ${h*1.5}em; 437 | height: ${h*1.5}em; 438 | background: ${tone(.1)}; 439 | border-radius: ${h}em; 440 | top: -${h*.45}em; 441 | left: -${h*.5}em; 442 | opacity: 0; 443 | margin-left: 2px; 444 | transform-origin: center center; 445 | transform: scale(.5, .5); 446 | transition: .1s ease-out; 447 | } 448 | .settings-panel-checkbox-label:active:after { 449 | transform: scale(1, 1); 450 | opacity: .08; 451 | } 452 | .settings-panel-checkbox:checked + .settings-panel-checkbox-label:after { 453 | background: ${active}; 454 | } 455 | 456 | 457 | /** Color */ 458 | .settings-panel-color { 459 | height: ${h*.5}em; 460 | width: ${h*.5}em; 461 | display: inline-block; 462 | vertical-align: baseline; 463 | } 464 | .settings-panel-color-value { 465 | border: none; 466 | font-family: inherit; 467 | border-radius: 0; 468 | padding-left: ${h*.75}em; 469 | } 470 | .settings-panel-color-value:hover, 471 | .settings-panel-color-value:focus { 472 | outline: none; 473 | } 474 | 475 | 476 | /** Button */ 477 | .settings-panel-button { 478 | text-align: center; 479 | border: none; 480 | text-transform: uppercase; 481 | color: ${tone(0)}; 482 | font-weight: 500; 483 | background: none; 484 | width: auto; 485 | padding: ${h/3}em ${h/3}em; 486 | min-width: ${h*3}em; 487 | margin-top: -${h/4}em; 488 | margin-bottom: -${h/4}em; 489 | } 490 | .settings-panel-button:focus { 491 | outline: none; 492 | } 493 | .settings-panel-button:hover { 494 | background: ${alpha(tone(0), .08)}; 495 | } 496 | .settings-panel-button:active { 497 | background: ${alpha(tone(0), .333)}; 498 | } 499 | 500 | 501 | /** Switch style */ 502 | .settings-panel-switch { 503 | } 504 | .settings-panel-switch-input { 505 | display: none; 506 | } 507 | .settings-panel-switch-label { 508 | position: relative; 509 | display: inline-block; 510 | margin: 0; 511 | margin-right: ${h*.75}em; 512 | z-index: 2; 513 | text-align: center; 514 | padding: 0 0; 515 | color: ${tone(0)}; 516 | } 517 | .settings-panel-switch-input:checked + .settings-panel-switch-label { 518 | color: ${active}; 519 | } 520 | .settings-panel-switch-input + .settings-panel-switch-label:hover { 521 | } 522 | .settings-panel-switch-label:hover { 523 | color: ${tone(0)}; 524 | } 525 | .settings-panel-switch-label:active { 526 | color: ${tone(0)}; 527 | } 528 | .settings-panel-switch-label:after { 529 | content: ''; 530 | z-index: 1; 531 | position: absolute; 532 | width: ${h*2}em; 533 | height: ${h*2}em; 534 | min-width: 100%; 535 | min-height: 100%; 536 | background: ${tone(.1)}; 537 | border-radius: ${h}em; 538 | top: 50%; 539 | left: 50%; 540 | margin-left: -${h}em; 541 | margin-top: -${h}em; 542 | opacity: 0; 543 | transform-origin: center center; 544 | transform: scale(.5, .5); 545 | transition: .1s ease-out; 546 | } 547 | .settings-panel-switch-label:active:after { 548 | transform: scale(1, 1); 549 | opacity: .08; 550 | } 551 | .settings-panel-checkbox:checked + .settings-panel-switch-label:after { 552 | background: ${active}; 553 | } 554 | 555 | /** Decorations */ 556 | ::-webkit-input-placeholder { 557 | color: ${alpha(active, .5)}; 558 | } 559 | ::-moz-placeholder { 560 | color: ${alpha(active, .5)}; 561 | } 562 | :-ms-input-placeholder { 563 | color: ${alpha(active, .5)}; 564 | } 565 | :-moz-placeholder { 566 | color: ${alpha(active, .5)}; 567 | } 568 | ::-moz-selection { 569 | background: ${active}; 570 | color: ${white}; 571 | } 572 | ::selection { 573 | background: ${active}; 574 | color: ${white}; 575 | } 576 | :host hr { 577 | opacity: 1; 578 | border-bottom: 1px solid ${alpha(tone(.0), .2)}; 579 | margin-left: -${h*.666}em; 580 | margin-right: -${h*.666}em; 581 | margin-top: ${h*.75}em; 582 | } 583 | :host a { 584 | } 585 | :host a:hover { 586 | } 587 | `}; 588 | 589 | 590 | function alpha (c, value) { 591 | return color(c).setAlpha(value).toString(); 592 | } 593 | -------------------------------------------------------------------------------- /theme/json.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module prama/theme/json 3 | * 4 | * Json representation theme 5 | */ 6 | 'use strict'; 7 | 8 | const px = require('add-px-to-style'); 9 | const none = require('./none'); 10 | 11 | module.exports = json; 12 | 13 | json.palette = ['white', 'rgb(200,0,0)', 'rgb(40,40,40)']; 14 | 15 | json.fontSize = '12px'; 16 | json.fontFamily = 'monospace'; 17 | json.labelWidth = 'auto'; 18 | json.inputHeight = 1.5; 19 | 20 | function json (opts) { 21 | opts = opts || {}; 22 | 23 | let h = opts.inputHeight || json.inputHeight; 24 | let labelWidth = opts.labelWidth || json.labelWidth; 25 | let fontSize = opts.fontSize || json.fontSize; 26 | let font = opts.fontFamily || json.fontFamily; 27 | 28 | let palette = opts.palette || json.palette; 29 | 30 | let white = palette[0]; 31 | let black = palette[palette.length - 1]; 32 | let red = palette[palette.length - 2]; 33 | 34 | return none({ 35 | fontSize: fontSize, 36 | fontFamily: font, 37 | inputHeight: h, 38 | labelWidth: labelWidth, 39 | palette: [white, black] 40 | }) + ` 41 | :host { 42 | -webkit-user-select: initial; 43 | -moz-user-select: initial; 44 | -ms-user-select: initial; 45 | user-select: initial; 46 | } 47 | 48 | .settings-panel-title { 49 | margin: 0; 50 | font-size: 1.4em; 51 | } 52 | 53 | .settings-panel-field { 54 | display: inline-block; 55 | } 56 | 57 | .settings-panel-label { 58 | display: inline-block; 59 | width: ${labelWidth}; 60 | color: ${red}; 61 | } 62 | .settings-panel-label:before { 63 | content: '"'; 64 | opacity: .3; 65 | color: ${black}; 66 | } 67 | .settings-panel-label:after { 68 | content: '":'; 69 | opacity: .3; 70 | color: ${black}; 71 | } 72 | .settings-panel-input { 73 | display: inline-block; 74 | min-height: 0; 75 | } 76 | /* 77 | .settings-panel-input:after { 78 | content: ','; 79 | color: ${black}; 80 | opacity: .3; 81 | } 82 | */ 83 | 84 | .settings-panel-range { 85 | display: none; 86 | } 87 | .settings-panel-range + .settings-panel-value { 88 | width: auto; 89 | } 90 | .settings-panel-value { 91 | padding: 0; 92 | } 93 | 94 | 95 | .settings-panel-text, 96 | .settings-panel-color-value, 97 | .settings-panel-select, 98 | .settings-panel-textarea { 99 | -webkit-appearance: none; 100 | -moz-appearance: none; 101 | -o-appearance:none; 102 | border: none; 103 | background: none; 104 | } 105 | 106 | .settings-panel-text:focus, 107 | .settings-panel-color-value:focus, 108 | .settings-panel-textarea:focus { 109 | outline: none; 110 | } 111 | 112 | 113 | .settings-panel-interval { 114 | display: none; 115 | } 116 | .settings-panel-field--interval { 117 | white-space: nowrap; 118 | } 119 | .settings-panel-field--interval .settings-panel-input:before { 120 | content: '['; 121 | opacity: .3; 122 | } 123 | .settings-panel-field--interval .settings-panel-input:after { 124 | content: ']'; 125 | opacity: .3; 126 | } 127 | 128 | /** Decorations */ 129 | :host hr { 130 | margin: 0; 131 | border: none; 132 | height: 0; 133 | } 134 | .settings-panel-field--disabled { 135 | opacity: .333; 136 | pointer-events: none; 137 | } 138 | `; 139 | }; -------------------------------------------------------------------------------- /theme/lucy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module prama/theme/lucy 3 | * 4 | * Round theme 5 | */ 6 | 'use strict'; 7 | 8 | const px = require('add-px-to-style'); 9 | const fonts = require('google-fonts'); 10 | const color = require('tinycolor2'); 11 | const scopeCss = require('scope-css'); 12 | 13 | fonts.add({ 14 | 'Ubuntu Condensed': 400, 15 | 'Ubuntu Mono': true 16 | }); 17 | 18 | module.exports = lucy; 19 | 20 | lucy.palette = [ 21 | '#00FFFA', 22 | '#999', 23 | '#eee', 24 | '#222', 25 | '#333' 26 | ]; 27 | 28 | function lucy () { 29 | let defaultPalette = lucy.palette; 30 | 31 | let palette = this.palette || defaultPalette; 32 | 33 | let bg = palette[4] || defaultPalette[4]; 34 | let primary = palette[0]; 35 | let secondary = palette[1]; 36 | let active = palette[2]; 37 | let dark = palette[3]; 38 | 39 | let mono = '"Ubuntu Mono", monospace'; 40 | let fontSize = this.fontSize || '1em'; 41 | 42 | let css = ` 43 | :host > .prama { 44 | background: none; 45 | overflow: visible; 46 | padding: 0; 47 | } 48 | .popoff-popup { 49 | min-width: 0; 50 | } 51 | .prama.popoff-sidebar .settings-panel { 52 | border-radius: 0; 53 | height: 100%; 54 | } 55 | 56 | .prama-button { 57 | color: ${bg}; 58 | fill: ${bg}; 59 | } 60 | .prama-button:hover { 61 | color: ${active}; 62 | fill: ${active}; 63 | } 64 | 65 | .popoff-close { 66 | color: ${secondary}; 67 | } 68 | .popoff-close:hover { 69 | color: ${active}; 70 | } 71 | `; 72 | 73 | css = scopeCss(css, '.prama-container-' + this.id).trim(); 74 | 75 | //set panel css 76 | this.panel.css = ` 77 | :host { 78 | box-shadow: 0 .5em 3em -1em ${dark}; 79 | color: ${primary}; 80 | background: ${alpha(bg, .9)}; 81 | font-size: ${px('font-size', fontSize)}; 82 | font-family: "Ubuntu Condensed", sans-serif; 83 | padding: 1em 1.5em; 84 | border-radius: .666em; 85 | } 86 | 87 | .settings-panel-title { 88 | font-size: 2.2em; 89 | font-weight: normal; 90 | letter-spacing: 0; 91 | text-transform: none; 92 | text-shadow: 0 0 .666em ${alpha(primary, .2)}; 93 | } 94 | 95 | :host.settings-panel-orientation-top .settings-panel-field, 96 | :host.settings-panel-orientation-bottom .settings-panel-field { 97 | text-align: center; 98 | } 99 | 100 | .settings-panel-label { 101 | vertical-align: top; 102 | padding-top: .5em; 103 | } 104 | :host.settings-panel-orientation-left .settings-panel-label, 105 | :host.settings-panel-orientation-right .settings-panel-label { 106 | width: 6em; 107 | } 108 | :host.settings-panel-orientation-top .settings-panel-label, 109 | :host.settings-panel-orientation-bottom .settings-panel-label { 110 | width: 100%; 111 | } 112 | 113 | .settings-panel-field--interval .settings-panel-input, 114 | .settings-panel-field--range .settings-panel-input { 115 | text-align: left; 116 | } 117 | 118 | .settings-panel-textarea, 119 | .settings-panel-text, 120 | .settings-panel-select { 121 | padding-left: 0em; 122 | padding-right: 0em; 123 | text-align: left; 124 | } 125 | 126 | .settings-panel-textarea:hover, 127 | .settings-panel-text:hover, 128 | .settings-panel-select:hover { 129 | color: ${active}; 130 | } 131 | 132 | /** Inputs fill */ 133 | .settings-panel-interval, 134 | .settings-panel-value, 135 | .settings-panel-select, 136 | .settings-panel-text, 137 | .settings-panel-checkbox-label { 138 | background: none; 139 | font-family: ${mono}; 140 | color: ${secondary}; 141 | } 142 | 143 | /** Panel value */ 144 | .settings-panel-value { 145 | padding-right: 0; 146 | } 147 | .settings-panel-value:focus { 148 | color: ${active}; 149 | } 150 | 151 | 152 | /** Text */ 153 | .settings-panel-text { 154 | border: none; 155 | background: none; 156 | } 157 | 158 | .settings-panel-textarea { 159 | background: none; 160 | border: 0; 161 | } 162 | .settings-panel-text:focus, 163 | .settings-panel-textarea:focus { 164 | outline: none; 165 | color: ${active}; 166 | } 167 | 168 | 169 | /** Select */ 170 | .settings-panel-select { 171 | background: none; 172 | outline: none; 173 | border: none; 174 | -webkit-appearance: none; 175 | -moz-appearance: none; 176 | -o-appearance:none; 177 | appearance:none; 178 | width: auto; 179 | padding-right: 1em; 180 | margin-right: -.5em; 181 | } 182 | .settings-panel-select::-ms-expand { 183 | display:none; 184 | } 185 | .settings-panel-select-triangle { 186 | content: ' '; 187 | border-right: .3em solid transparent; 188 | border-left: .3em solid transparent; 189 | line-height: 2em; 190 | position: relative; 191 | z-index: 1; 192 | vertical-align: middle; 193 | display: inline-block; 194 | width: 0; 195 | text-align: center; 196 | pointer-events: none; 197 | } 198 | .settings-panel-select-triangle--down { 199 | top: 0em; 200 | left: 0; 201 | border-top: .5em solid ${secondary}; 202 | border-bottom: .0 transparent; 203 | } 204 | .settings-panel-select-triangle--up { 205 | display: none; 206 | } 207 | .settings-panel-select:focus { 208 | color: ${active} 209 | } 210 | .settings-panel-select:focus + .settings-panel-select-triangle { 211 | border-top-color: ${active}; 212 | } 213 | 214 | 215 | 216 | /** Switch style */ 217 | .settings-panel-switch { 218 | -webkit-appearance: none; 219 | -moz-appearance: none; 220 | appearance: none; 221 | color: ${secondary}; 222 | font-family: ${mono}; 223 | } 224 | .settings-panel-switch-input { 225 | display: none; 226 | } 227 | .settings-panel-switch-label { 228 | cursor: pointer; 229 | } 230 | .settings-panel-switch-label:hover { 231 | color: ${active}; 232 | } 233 | .settings-panel-switch-label:last-child { 234 | margin-right: 0; 235 | } 236 | .settings-panel-switch-input:checked + .settings-panel-switch-label { 237 | color: ${active}; 238 | font-weight: bold; 239 | } 240 | 241 | 242 | /** Slider */ 243 | .settings-panel-range { 244 | text-align: left; 245 | -webkit-appearance: none; 246 | -moz-appearance: none; 247 | appearance: none; 248 | background: none; 249 | border-radius: 1em; 250 | color: ${secondary}; 251 | margin-left: 15%; 252 | width: 70%; 253 | } 254 | .settings-panel-range:focus { 255 | outline: none; 256 | color: ${primary}; 257 | } 258 | .settings-panel-range::-webkit-slider-runnable-track { 259 | background: ${secondary}; 260 | height: 1px; 261 | } 262 | .settings-panel-range:focus::-webkit-slider-runnable-track { 263 | background: ${active}; 264 | } 265 | .settings-panel-range::-moz-range-track { 266 | background: ${secondary}; 267 | height: 1px; 268 | } 269 | .settings-panel-range:focus::-moz-range-track { 270 | background: ${active}; 271 | } 272 | .settings-panel-range::-ms-fill-lower { 273 | background: ${secondary}; 274 | } 275 | .settings-panel-range::-ms-fill-upper { 276 | background: ${secondary}; 277 | } 278 | 279 | .settings-panel-range::-webkit-slider-thumb { 280 | background: ${active}; 281 | border-radius: 1em; 282 | height: 1em; 283 | width: 1em; 284 | border: 0; 285 | cursor: ew-resize; 286 | -webkit-appearance: none; 287 | appearance: none; 288 | margin-top: -.5em; 289 | } 290 | .settings-panel-range::-moz-range-thumb { 291 | background: ${active}; 292 | border-radius: 1em; 293 | height: 1em; 294 | width: 1em; 295 | border: 0; 296 | cursor: ew-resize; 297 | -webkit-appearance: none; 298 | margin-top: 0px; 299 | } 300 | .settings-panel-range::-ms-thumb { 301 | background: ${secondary}; 302 | border-radius: 1em; 303 | } 304 | .settings-panel-field--interval .settings-panel-value:first-child { 305 | text-align: right; 306 | padding-left: 0; 307 | padding-right: .5em; 308 | } 309 | .settings-panel-interval:after { 310 | content: ''; 311 | position: absolute; 312 | width: 100%; 313 | left: 0; 314 | background: ${secondary}; 315 | height: 1px; 316 | margin-top: 1em; 317 | } 318 | .settings-panel-interval-handle { 319 | position: absolute; 320 | z-index: 1; 321 | height: 1px; 322 | margin-top: 1em; 323 | background: ${active}; 324 | } 325 | .settings-panel-interval-handle:after { 326 | content: ''; 327 | position: absolute; 328 | right: 0; 329 | top: -.5em; 330 | width: 1em; 331 | height: 1em; 332 | border-radius: 1em; 333 | background: inherit; 334 | } 335 | .settings-panel-interval-handle:before { 336 | content: ''; 337 | position: absolute; 338 | left: 0; 339 | top: -.5em; 340 | width: 1em; 341 | height: 1em; 342 | border-radius: 1em; 343 | background: inherit; 344 | } 345 | 346 | .settings-panel-interval-dragging .settings-panel-interval-handle { 347 | background: ${active}; 348 | } 349 | 350 | .settings-panel-field--range .settings-panel-input:before { 351 | content: attr(data-min); 352 | position: absolute; 353 | width: 15%; 354 | color: ${secondary}; 355 | text-align: right; 356 | line-height: 2em; 357 | height: 2em; 358 | padding-right: .5em; 359 | box-sizing: border-box; 360 | } 361 | 362 | 363 | /** Checkbox */ 364 | .settings-panel-field--checkbox .settings-panel-label { 365 | margin-bottom: .5em; 366 | } 367 | .settings-panel-checkbox { 368 | display: none; 369 | } 370 | .settings-panel-checkbox-label { 371 | position: relative; 372 | display: inline-block; 373 | vertical-align: top; 374 | width: 4.5em; 375 | height: 2em; 376 | cursor: pointer; 377 | background: ${secondary}; 378 | border-radius: 2em; 379 | } 380 | .settings-panel-checkbox-label:before { 381 | position: absolute; 382 | content: ""; 383 | height: 1em; 384 | width: 1em; 385 | left: .5em; 386 | bottom: .5em; 387 | background-color: ${active}; 388 | -webkit-transition: .4s; 389 | transition: .4s; 390 | border-radius: 2em; 391 | } 392 | .settings-panel-checkbox:checked + .settings-panel-checkbox-label { 393 | background: ${secondary}; 394 | } 395 | .settings-panel-checkbox:focus + .settings-panel-checkbox-label { 396 | } 397 | .settings-panel-checkbox:checked + .settings-panel-checkbox-label:before { 398 | left: calc(100% - 1.6em); 399 | background-color: ${active}; 400 | } 401 | 402 | 403 | /** Color */ 404 | .settings-panel-color { 405 | height: 1em; 406 | width: 1em; 407 | top: 0; 408 | bottom: 0; 409 | margin-top: auto; 410 | margin-bottom: auto; 411 | } 412 | .settings-panel-color-value { 413 | border: none; 414 | background: none; 415 | color: ${secondary}; 416 | padding-left: 1.5em; 417 | width: 100%; 418 | } 419 | .settings-panel-color-value:focus { 420 | outline: none; 421 | color: ${active}; 422 | } 423 | 424 | 425 | /** Button */ 426 | .settings-panel-button { 427 | -webkit-appearance: none; 428 | -moz-appearance: none; 429 | appearance: none; 430 | border: none; 431 | outline: none; 432 | cursor: pointer; 433 | min-height: 2.5em; 434 | padding: .75em 1.5em; 435 | color: ${primary}; 436 | box-shadow: 0 0 0 2px ${alpha(primary, .6)}; 437 | background: none; 438 | margin-left: 6em; 439 | } 440 | .settings-panel-button:hover { 441 | box-shadow: 0 0 0 2px ${alpha(primary, 1)}; 442 | color: ${primary}; 443 | } 444 | .settings-panel-button:active { 445 | color: ${primary}; 446 | } 447 | 448 | 449 | /** Decorations */ 450 | :host hr { 451 | border: none; 452 | height: 0; 453 | margin: 1.25em 0; 454 | border-bottom: 1px dotted ${alpha(primary, .2)}; 455 | } 456 | ::-webkit-input-placeholder { 457 | color: ${secondary}; 458 | } 459 | ::-moz-placeholder { 460 | color: ${secondary}; 461 | } 462 | :-ms-input-placeholder { 463 | color: ${secondary}; 464 | } 465 | :-moz-placeholder { 466 | color: ${secondary}; 467 | } 468 | ::-moz-selection { 469 | color: ${active}; 470 | background: ${dark}; 471 | } 472 | ::selection { 473 | color: ${active}; 474 | background: ${dark}; 475 | } 476 | `; 477 | 478 | return css; 479 | } 480 | 481 | 482 | function alpha (c, value) { 483 | return color(c).setAlpha(value).toString(); 484 | } 485 | 486 | function darken (c, value) { 487 | return color(c).darken(value*100).toString(); 488 | } 489 | 490 | function lighten (c, value) { 491 | return color(c).lighten(value*100).toString(); 492 | } -------------------------------------------------------------------------------- /theme/merka.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module prama/theme/merka 3 | * 4 | * Rounded corners theme 5 | */ 6 | 'use strict'; -------------------------------------------------------------------------------- /theme/none.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module settings-panel/theme/none 3 | */ 4 | 'use strict'; 5 | 6 | const px = require('add-px-to-style'); 7 | 8 | module.exports = none; 9 | 10 | none.palette = ['white', 'black']; 11 | none.fontSize = 13; 12 | none.fontFamily = 'sans-serif'; 13 | none.labelWidth = '9em'; 14 | none.inputHeight = 2; 15 | none.padding = 1/5; 16 | 17 | function none (opts) { 18 | opts = opts || {}; 19 | let fs = opts.fontSize || none.fontSize; 20 | let font = opts.fontFamily || none.fontFamily; 21 | let h = opts.inputHeight || none.inputHeight; 22 | let labelWidth = opts.labelWidth || none.labelWidth; 23 | let padding = opts.padding || none.padding; 24 | let palette = opts.palette || none.palette; 25 | let white = palette[0]; 26 | let black = palette[palette.length - 1]; 27 | 28 | if (/[-0-9.]*/.test(fs)) fs = parseFloat(fs); 29 | 30 | //just size part 31 | return ` 32 | :host { 33 | background: ${white}; 34 | color: ${black}; 35 | font-family: ${font}; 36 | font-size: ${px('font-size', fs)}; 37 | padding: ${h*2.5*padding}em; 38 | } 39 | 40 | .settings-panel-title { 41 | min-height: ${h}em; 42 | line-height: 1.5; 43 | text-align: left; 44 | font-size: ${px('font-size',fs*1.333)}; 45 | padding: ${h * 2 * padding / 1.333}em ${h * padding / 1.333 }em; 46 | min-height: ${h/1.333}em; 47 | margin: 0; 48 | } 49 | 50 | .settings-panel-field { 51 | padding: ${h * padding}em; 52 | } 53 | 54 | :host.settings-panel-orientation-left .settings-panel-label, 55 | :host .settings-panel-orientation-left .settings-panel-label, 56 | :host.settings-panel-orientation-right .settings-panel-label, 57 | :host .settings-panel-orientation-right .settings-panel-label { 58 | width: ${px('width', labelWidth)}; 59 | } 60 | :host.settings-panel-orientation-bottom .settings-panel-label { 61 | border-top-width: ${h}em; 62 | } 63 | :host.settings-panel-orientation-bottom .settings-panel-label + .settings-panel-input { 64 | top: ${h/8}em; 65 | } 66 | :host.settings-panel-orientation-left .settings-panel-label { 67 | padding-right: ${h/2}em; 68 | } 69 | :host.settings-panel-orientation-right .settings-panel-label { 70 | padding-left: ${h/2}em; 71 | } 72 | :host.settings-panel-orientation-right .settings-panel-label + .settings-panel-input { 73 | width: calc(100% - ${labelWidth}); 74 | } 75 | 76 | .settings-panel-text, 77 | .settings-panel-textarea, 78 | .settings-panel-range, 79 | .settings-panel-interval, 80 | .settings-panel-select, 81 | .settings-panel-color, 82 | .settings-panel-color-value, 83 | .settings-panel-value { 84 | height: ${h}em; 85 | } 86 | 87 | .settings-panel-button, 88 | .settings-panel-input, 89 | .settings-panel-switch, 90 | .settings-panel-checkbox-group, 91 | .settings-panel-switch-label { 92 | min-height: ${h}em; 93 | } 94 | .settings-panel-input, 95 | .settings-panel-switch, 96 | .settings-panel-select, 97 | .settings-panel-checkbox-group, 98 | .settings-panel-switch-label { 99 | line-height: ${h}em; 100 | } 101 | 102 | .settings-panel-switch-label, 103 | .settings-panel-checkbox, 104 | .settings-panel-checkbox-label, 105 | .settings-panel-button { 106 | cursor: pointer; 107 | } 108 | 109 | .settings-panel-range::-webkit-slider-thumb { 110 | cursor: ew-resize; 111 | } 112 | .settings-panel-range::-moz-range-thumb { 113 | cursor: ew-resize; 114 | } 115 | .settings-panel-range::-ms-track { 116 | cursor: ew-resize; 117 | } 118 | .settings-panel-range::-ms-thumb { 119 | cursor: ew-resize; 120 | } 121 | 122 | /* Default triangle styles are from control theme, just set display: block */ 123 | .settings-panel-select-triangle { 124 | display: none; 125 | position: absolute; 126 | border-right: .3em solid transparent; 127 | border-left: .3em solid transparent; 128 | line-height: ${h}em; 129 | right: 2.5%; 130 | height: 0; 131 | z-index: 1; 132 | pointer-events: none; 133 | } 134 | .settings-panel-select-triangle--up { 135 | top: ${h/2}em; 136 | margin-top: -${h/4 + h/24}em; 137 | border-bottom: ${h/4}em solid; 138 | border-top: 0px transparent; 139 | } 140 | .settings-panel-select-triangle--down { 141 | top: ${h/2}em; 142 | margin-top: ${h/24}em; 143 | border-top: ${h/4}em solid; 144 | border-bottom: .0 transparent; 145 | } 146 | 147 | :host hr { 148 | opacity: .5; 149 | 150 | color: ${black} 151 | } 152 | `; 153 | } -------------------------------------------------------------------------------- /theme/pages.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dy/settings-panel/d973e5d88ec6f9e5c88268564425c38075eacc3a/theme/pages.js -------------------------------------------------------------------------------- /theme/typer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module settings-panel/theme/typer 3 | * 4 | * White theme 5 | */ 6 | 'use strict'; 7 | 8 | const px = require('add-px-to-style'); 9 | const fonts = require('google-fonts'); 10 | const color = require('tinycolor2'); 11 | const scopeCss = require('scope-css'); 12 | const interpolate = require('color-interpolate'); 13 | const none = require('./none'); 14 | 15 | 16 | module.exports = typer; 17 | 18 | // typer.palette = ['#4B4E59', '#BCC1C7' ,'#F1F1F3']; 19 | // typer.palette = ['#32393F', '#3F4851', '#49565F', '#ADB7C0', '#F4FBFF']; 20 | // typer.palette = ['#111', '#eee']; 21 | typer.palette = ['white', 'black']; 22 | // typer.palette = ['#fff', '#24D4C0' ,'#21282E']; 23 | typer.active = '#24D4C0'; 24 | 25 | typer.fontSize = 12; 26 | typer.fontFamily = '"Montserrat", sans-serif'; 27 | typer.labelWidth = '9em'; 28 | typer.inputHeight = 2; 29 | typer.padding = 1/5; 30 | 31 | //color balance 32 | typer.bg = .9; 33 | typer.radius = 2; 34 | typer.fg = .08; 35 | 36 | fonts.add({ 37 | 'Montserrat': [400, 600], 38 | 'Material Icons': 400 39 | }); 40 | 41 | 42 | function typer (opts) { 43 | opts = opts || {}; 44 | 45 | let h = opts.inputHeight || typer.inputHeight; 46 | let labelWidth = opts.labelWidth || typer.labelWidth; 47 | let fontSize = opts.fontSize || typer.fontSize; 48 | let font = opts.fontFamily || typer.fontFamily; 49 | let radius = opts.radius || typer.radius; 50 | let padding = opts.padding || typer.padding; 51 | 52 | //background/active tones 53 | let bg = opts.bg || typer.bg; 54 | let fg = opts.fg || typer.fg; 55 | 56 | 57 | //palette 58 | let palette = opts.palette || typer.palette; 59 | let pick = interpolate(palette); 60 | 61 | //obtain palette sorted by brightnes 62 | let nPalette = palette.slice().sort((a, b) => color(a).getLuminance() - color(b).getLuminance()); 63 | let npick = interpolate(nPalette); 64 | 65 | //the color of light/shadow to mix 66 | let light = color.mix('white', nPalette[nPalette.length - 1], 25).toString(); 67 | let shadow = color.mix('black', nPalette[0], 25).toString(); 68 | 69 | let active = opts.active || tone(.5); 70 | 71 | //helpers 72 | function tone (amt) { 73 | if (typeof amt === 'number') { 74 | amt = Math.max(Math.min(amt, 1), 0); 75 | return color(pick(amt)).toString(); 76 | } 77 | return amt; 78 | } 79 | function ntone (amt) { 80 | if (typeof amt === 'number') { 81 | amt = Math.max(Math.min(amt, 1), 0); 82 | return color(npick(amt)).toString(); 83 | } 84 | return amt; 85 | } 86 | function lighten (v, amt, t = tone) { 87 | return color(t(v)).lighten(amt*100); 88 | } 89 | function darken (v, amt, t = tone) { 90 | return color(t(v)).darken(amt*100); 91 | } 92 | function alpha (c, value) { 93 | return color(c).setAlpha(value).toString(); 94 | } 95 | 96 | let inversed = color(palette[0]).getLuminance() > color(palette[palette.length - 1]).getLuminance(); 97 | 98 | 99 | let pop = (v = .9, d = .05, t = tone) => ` 100 | ${text(fg, v)} 101 | background-color: ${t(v)}; 102 | background-image: linear-gradient(to bottom, ${lighten(v, d, t)}, ${darken(v, d, t)}); 103 | box-shadow: inset 1px 0px ${alpha(light, .04)}, inset 0px 1px ${alpha(light, .15)}, inset 0px -1px 1px ${alpha(light, .07)}, 0 1px 2px ${alpha(shadow, .35)}; 104 | `; 105 | let push = (v = .1, d = .05, t = tone) => ` 106 | background: ${t(v)}; 107 | /*background-image: linear-gradient(to bottom, ${darken(v, d, t)}, ${lighten(v, d, t)});*/ 108 | box-shadow: inset 0 1px 2px ${alpha(shadow, .18)}, 0 1px ${alpha(light, .2)}; 109 | color: ${t(1 - fg)}; 110 | text-shadow: none; 111 | `; 112 | let text = (v, bg) => color(tone(v)).getLuminance() > color(tone(bg)).getLuminance() ? ` 113 | color: ${tone(v)}; 114 | background: ${tone(bg)}; 115 | text-shadow: 0 -1px ${color.mix(tone(bg), shadow, 50)}; 116 | ` : ` 117 | color: ${tone(v)}; 118 | background: ${tone(bg)}; 119 | text-shadow: 0 1px ${color.mix(tone(bg), light, 50)}; 120 | `; 121 | 122 | return none({ 123 | fontSize: fontSize, 124 | fontFamily: font, 125 | inputHeight: h, 126 | labelWidth: labelWidth, 127 | palette: [tone(fg), tone(bg)], 128 | padding: padding 129 | }) + ` 130 | :host { 131 | ${text(fg, bg)}; 132 | box-shadow: inset 0 1px ${alpha(light, .15)}, 0 1.5px 11px -2px ${alpha(shadow, .666)}; 133 | border-radius: ${radius*2}px; 134 | } 135 | 136 | :host a { 137 | text-decoration: none; 138 | border-bottom: 1px solid ${alpha(tone(.0), .1)}; 139 | } 140 | :host a:hover { 141 | text-decoration: none; 142 | border-bottom: 1px solid ${alpha(tone(.0), 1)}; 143 | } 144 | 145 | .settings-panel-title { 146 | font-weight: 400; 147 | ${text(fg, bg)}; 148 | background: none; 149 | } 150 | 151 | .settings-panel-label { 152 | ${text(.25, bg)}; 153 | background: none; 154 | } 155 | 156 | .settings-panel-field:hover .settings-panel-label { 157 | color: ${tone(fg)}; 158 | } 159 | 160 | 161 | 162 | /** Values */ 163 | .settings-panel-value { 164 | } 165 | .settings-panel-value:first-child { 166 | } 167 | .settings-panel-value:hover, 168 | .settings-panel-value:focus { 169 | } 170 | 171 | 172 | 173 | /** Sliders */ 174 | .settings-panel-range { 175 | -webkit-appearance: none; 176 | -moz-appearance: none; 177 | appearance: none; 178 | background: none; 179 | border: 0; 180 | } 181 | .settings-panel-field--range:hover .settings-panel-range, 182 | .settings-panel-range:focus { 183 | outline: none; 184 | } 185 | .settings-panel-range::-webkit-slider-runnable-track { 186 | height: .5em; 187 | border-radius: .5em; 188 | ${push(fg, .05)} 189 | } 190 | .settings-panel-range::-moz-range-track { 191 | height: .5em; 192 | border-radius: .5em; 193 | ${push(fg, .05)} 194 | } 195 | .settings-panel-range::-ms-track { 196 | ${push(fg, .05)} 197 | outline: none; 198 | color: transparent; 199 | border: none; 200 | height: .5em; 201 | border-radius: .5em; 202 | } 203 | .settings-panel-range::-ms-fill-lower { 204 | border-radius: .5em; 205 | ${push(fg, .05)} 206 | } 207 | .settings-panel-range::-ms-fill-upper { 208 | border-radius: .5em; 209 | ${push(.93, .05)} 210 | } 211 | 212 | @supports (--css: variables) { 213 | .settings-panel-range { 214 | width: 100%; 215 | --active: ${active}; 216 | --bg: ${tone(.9)}; 217 | --track-background: linear-gradient(to right, var(--active) 0, var(--active) var(--value), var(--bg) 0) no-repeat; 218 | } 219 | .settings-panel-range::-webkit-slider-runnable-track { 220 | background: var(--track-background); 221 | } 222 | .settings-panel-range::-moz-range-track { 223 | background: var(--track-background); 224 | } 225 | .settings-panel-field--range .settings-panel-input { 226 | margin-right: ${h}em; 227 | } 228 | .settings-panel-field--range:hover .settings-panel-range, 229 | .settings-panel-range:focus { 230 | --bg: ${tone(.93)}; 231 | } 232 | .settings-panel-range-value { 233 | display: none; 234 | position: absolute; 235 | top: -${h*1.25}em; 236 | text-align: center; 237 | padding: 0; 238 | color: ${tone(fg)}; 239 | background: ${tone(1)}; 240 | box-shadow: 0 1px 5px -1px ${alpha(shadow, .5)}; 241 | border-radius: ${radius}px; 242 | z-index: 3; 243 | margin-left: ${-h*.65}em; 244 | width: ${h*2}em; 245 | text-shadow: none; 246 | left: calc(var(--value) - var(--coef) * ${h*.8}em); 247 | } 248 | .settings-panel-field--range .settings-panel-value-tip { 249 | position: absolute; 250 | height: 0; 251 | top: -${h*.25}em; 252 | left: calc(var(--value) - var(--coef) * ${h*.8}em); 253 | margin-left: ${h*.1}em; 254 | display: none; 255 | z-index: 3; 256 | border-top: ${h*.3}em solid ${tone(1)}; 257 | border-left: ${h*.3}em solid transparent; 258 | border-right: ${h*.3}em solid transparent; 259 | border-bottom: none; 260 | } 261 | .settings-panel-input:before { 262 | border-top: ${h*.3}em solid ${alpha(shadow, .25)}; 263 | } 264 | .settings-panel-field--range:hover .settings-panel-value-tip, 265 | .settings-panel-range:focus ~ .settings-panel-value-tip { 266 | display: block; 267 | } 268 | .settings-panel-field--range:hover .settings-panel-value, 269 | .settings-panel-range:focus ~ .settings-panel-value { 270 | display: block; 271 | } 272 | } 273 | 274 | .settings-panel-range::-webkit-slider-thumb { 275 | ${pop(fg, -.05)}; 276 | height: ${h*.8}em; 277 | width: ${h*.8}em; 278 | border-radius: ${h*.8}em; 279 | margin-top: -${h*.4}em; 280 | border: 0; 281 | position: relative; 282 | top: .25em; 283 | -webkit-appearance: none; 284 | appearance: none; 285 | z-index: 3; 286 | } 287 | .settings-panel-range:focus::-webkit-slider-thumb, 288 | .settings-panel-range:hover::-webkit-slider-thumb, 289 | .settings-panel-field--range:hover .settings-panel-range::-webkit-slider-thumb { 290 | } 291 | .settings-panel-range::-webkit-slider-thumb:active { 292 | } 293 | 294 | .settings-panel-range::-moz-range-thumb { 295 | ${pop(fg, -.05)}; 296 | height: ${h*.8}em; 297 | width: ${h*.8}em; 298 | border-radius: ${h*.8}em; 299 | margin-top: -${h*.4}em; 300 | border: 0; 301 | position: relative; 302 | top: .25em; 303 | -moz-appearance: none; 304 | appearance: none; 305 | z-index: 3; 306 | } 307 | .settings-panel-range:focus::-moz-range-thumb, 308 | .settings-panel-range::-moz-range-thumb:hover, 309 | .settings-panel-field--range:hover .settings-panel-range::-moz-range-thumb { 310 | } 311 | .settings-panel-range::-moz-range-thumb:active { 312 | } 313 | 314 | .settings-panel-range::-ms-thumb { 315 | ${pop(fg, -.05)}; 316 | height: ${h*.8}em; 317 | width: ${h*.8}em; 318 | border-radius: ${h*.8}em; 319 | border: 0; 320 | position: relative; 321 | top: .25em; 322 | -ms-appearance: none; 323 | appearance: none; 324 | z-index: 3; 325 | } 326 | .settings-panel-range:focus::-ms-thumb, 327 | .settings-panel-range:hover::-ms-thumb, 328 | .settings-panel-field--range:hover .settings-panel-range::-ms-thumb { 329 | } 330 | .settings-panel-range::-ms-thumb:active { 331 | } 332 | 333 | 334 | /** Interval */ 335 | .settings-panel-interval { 336 | background: none; 337 | } 338 | .settings-panel-interval:after { 339 | content: ''; 340 | position: absolute; 341 | width: 100%; 342 | left: 0; 343 | bottom: 0; 344 | top: 0; 345 | height: .5em; 346 | border-radius: .5em; 347 | margin-top: auto; 348 | margin-bottom: auto; 349 | ${push(.9, .05)} 350 | background: ${tone(.9)}; 351 | } 352 | .settings-panel-field--interval:hover .settings-panel-interval:after, 353 | .settings-panel-interval-dragging .settings-panel-interval:after { 354 | background: ${tone(.93)}; 355 | } 356 | .settings-panel-interval-handle { 357 | position: absolute; 358 | z-index: 1; 359 | top: 0; 360 | height: .5em; 361 | bottom: 0; 362 | margin-top: auto; 363 | margin-bottom: auto; 364 | background: ${active}; 365 | } 366 | .settings-panel-interval-handle:after, 367 | .settings-panel-interval-handle:before { 368 | content: ''; 369 | position: absolute; 370 | right: -${h*.4}em; 371 | top: 0; 372 | bottom: 0; 373 | margin: auto; 374 | height: ${h*.8}em; 375 | width: ${h*.8}em; 376 | border-radius: ${h*.8}em; 377 | ${pop(fg, -.05)}; 378 | } 379 | .settings-panel-interval-handle:before { 380 | left: -${h*.4}em; 381 | right: auto; 382 | } 383 | 384 | .settings-panel-field--interval:hover .settings-panel-interval-handle:after, 385 | .settings-panel-field--interval:hover .settings-panel-interval-handle:before, 386 | .settings-panel-interval-dragging .settings-panel-interval-handle:after, 387 | .settings-panel-interval-dragging .settings-panel-interval-handle:before { 388 | ${pop(fg, -.05)}; 389 | } 390 | 391 | @supports (--css: variables) { 392 | .settings-panel-interval { 393 | width: 100%; 394 | } 395 | 396 | .settings-panel-interval-value { 397 | position: absolute; 398 | top: -${h*1.25}em; 399 | text-align: center; 400 | padding: 0; 401 | display: none; 402 | color: ${tone(fg)}; 403 | background: ${tone(1)}; 404 | box-shadow: 0 1px 5px -1px ${alpha(shadow, .5)}; 405 | border-radius: ${radius}px; 406 | z-index: 3; 407 | margin-left: ${-h}em; 408 | width: ${h*2}em; 409 | text-shadow: none; 410 | left: var(--value); 411 | } 412 | 413 | .settings-panel-field--interval .settings-panel-value-tip { 414 | position: absolute; 415 | height: 0; 416 | display: none; 417 | top: -${h*.25}em; 418 | left: var(--low); 419 | margin-left: ${-h*.3}em; 420 | z-index: 3; 421 | border-top: ${h*.3}em solid ${tone(1)}; 422 | border-left: ${h*.3}em solid transparent; 423 | border-right: ${h*.3}em solid transparent; 424 | border-bottom: none; 425 | } 426 | .settings-panel-interval-value--right + .settings-panel-value-tip { 427 | left: var(--high); 428 | } 429 | 430 | .settings-panel-input:before { 431 | border-top: ${h*.3}em solid ${alpha(shadow, .25)}; 432 | } 433 | .settings-panel-field--interval:hover .settings-panel-interval-value, 434 | .settings-panel-interval-dragging .settings-panel-interval-value { 435 | display: block; 436 | } 437 | @media (min-width: 640px) { 438 | .settings-panel-field--interval:hover .settings-panel-value-tip, 439 | .settings-panel-interval-dragging .settings-panel-value-tip { 440 | display: block; 441 | } 442 | } 443 | } 444 | 445 | 446 | /** Switch style */ 447 | .settings-panel-switch { 448 | } 449 | .settings-panel-switch-input { 450 | display: none; 451 | } 452 | .settings-panel-switch-label { 453 | position: relative; 454 | display: inline-block; 455 | padding: 0 ${h/2}em; 456 | margin: 0; 457 | z-index: 2; 458 | text-align: center; 459 | ${pop(bg * .95, .07)}; 460 | color: ${tone(.25)}; 461 | } 462 | .settings-panel-switch-input:checked + .settings-panel-switch-label { 463 | ${push(.95, bg)}; 464 | color: ${tone(fg)}; 465 | } 466 | 467 | .settings-panel-switch-input:first-child + .settings-panel-switch-label { 468 | border-top-left-radius: 2px; 469 | border-bottom-left-radius: 2px; 470 | } 471 | .settings-panel-switch-label:last-child { 472 | border-top-right-radius: 2px; 473 | border-bottom-right-radius: 2px; 474 | } 475 | 476 | .settings-panel-switch-label:hover { 477 | ${pop(bg * .95 + (inversed ? -.07 : .07), .07)}; 478 | } 479 | .settings-panel-switch-label:active { 480 | ${pop(bg * .95 + (inversed ? .07 : -.07), .07)}; 481 | } 482 | 483 | 484 | /** Select */ 485 | .settings-panel-select { 486 | border-radius: ${radius}px; 487 | padding-left: ${h/4}em; 488 | padding-right: ${h/2}em; 489 | margin-right: -${h/8}em; 490 | min-width: 4em; 491 | outline: none; 492 | border: none; 493 | -webkit-appearance: none; 494 | -moz-appearance: none; 495 | -o-appearance:none; 496 | appearance:none; 497 | ${pop(bg * .95, .07)}; 498 | color: ${tone(.25)}; 499 | } 500 | .settings-panel-select:hover, 501 | .settings-panel-select:active, 502 | .settings-panel-select:focus { 503 | ${pop(bg * .95 + (inversed ? -.07 : .07), .07)}; 504 | } 505 | .settings-panel-select::-ms-expand { 506 | display: none; 507 | } 508 | .settings-panel-select-triangle { 509 | color: inherit; 510 | display: block; 511 | transform: scale(.8); 512 | } 513 | 514 | 515 | /** Button */ 516 | .settings-panel-button { 517 | text-align: center; 518 | border: none; 519 | border-radius: ${radius}px; 520 | ${pop(bg * .95, .07)}; 521 | color: ${tone(.25)}; 522 | } 523 | .settings-panel-button:focus { 524 | outline: none; 525 | } 526 | .settings-panel-button:hover { 527 | ${pop(bg * .95 + (inversed ? -.07 : .07), .07)}; 528 | color: ${tone(fg)}; 529 | } 530 | .settings-panel-button:active { 531 | ${push(fg, .05)}; 532 | } 533 | 534 | 535 | /** Text */ 536 | .settings-panel-text, 537 | .settings-panel-textarea { 538 | -webkit-appearance: none; 539 | -moz-appearance: none; 540 | -o-appearance:none; 541 | border: none; 542 | height: ${h}em; 543 | padding: 0; 544 | width: 100%; 545 | border-radius: ${radius}px; 546 | padding-left: .4em; 547 | ${push(bg*.95)}; 548 | color: ${tone(fg)}; 549 | text-shadow: none; 550 | } 551 | .settings-panel-textarea { 552 | padding-top: .35em; 553 | } 554 | 555 | .settings-panel-text:hover, 556 | .settings-panel-textarea:hover, 557 | .settings-panel-text:focus, 558 | .settings-panel-textarea:focus { 559 | ${push(bg * .95 - .04)}; 560 | color: ${tone(fg)}; 561 | outline: none; 562 | } 563 | 564 | /** Color */ 565 | .settings-panel-color { 566 | position: relative; 567 | width: ${h}em; 568 | border-top-left-radius: 3px; 569 | border-bottom-left-radius: 3px; 570 | display: inline-block; 571 | vertical-align: baseline; 572 | box-shadow: 0 1px ${alpha(light, .2)}; 573 | } 574 | .settings-panel-color-value { 575 | -webkit-appearance: none; 576 | -moz-appearance: none; 577 | -o-appearance:none; 578 | border: none; 579 | padding-left: ${h/4}em; 580 | width: calc(100% - ${h}em); 581 | font-family: inherit; 582 | border-top-left-radius: 0; 583 | border-bottom-left-radius: 0; 584 | border-top-right-radius: 3px; 585 | border-bottom-right-radius: 3px; 586 | ${push(bg*.95)}; 587 | color: ${tone(fg)}; 588 | text-shadow: none; 589 | } 590 | .settings-panel-color-value:hover, 591 | .settings-panel-color-value:focus { 592 | outline: none; 593 | ${push(bg * .95 - .04)}; 594 | color: ${tone(fg)}; 595 | } 596 | 597 | 598 | /** Checkbox */ 599 | .settings-panel-checkbox { 600 | display: none; 601 | } 602 | .settings-panel-checkbox-label { 603 | display: inline-block; 604 | ${text(.25, bg)}; 605 | position: relative; 606 | margin-right: ${h}em; 607 | background: none; 608 | } 609 | .settings-panel-checkbox-label:before { 610 | font-family: "Material Icons"; 611 | content: ''; 612 | font-weight: bolder; 613 | font-size: ${h*.75}em; 614 | display: block; 615 | float: left; 616 | width: 2em; 617 | margin-right: -2em; 618 | margin-top: -${h*.1}em; 619 | opacity: 0; 620 | z-index: 1; 621 | position: relative; 622 | color: ${tone(fg)}; 623 | text-shadow: 0 1px 2px ${alpha(shadow, .5)}; 624 | } 625 | .settings-panel-checkbox-label:after { 626 | content: ''; 627 | display: block; 628 | float: left; 629 | margin-top: -${h*.05}em; 630 | width: ${h*.666}em; 631 | height: ${h*.666}em; 632 | border-radius: ${radius}px; 633 | position: relative; 634 | margin-right: ${h/3}em; 635 | line-height: ${h/2}em; 636 | text-align: center; 637 | z-index: 0; 638 | ${push(.915)}; 639 | } 640 | .settings-panel-checkbox-label:hover { 641 | color: ${tone(fg)}; 642 | } 643 | .settings-panel-checkbox-label:hover:after { 644 | ${push(.93, .07)}; 645 | } 646 | .settings-panel-checkbox:checked + .settings-panel-checkbox-label { 647 | } 648 | .settings-panel-checkbox:checked + .settings-panel-checkbox-label:before { 649 | opacity: 1; 650 | } 651 | .settings-panel-checkbox:checked + .settings-panel-checkbox-label:after { 652 | ${push(.93, .1)}; 653 | } 654 | 655 | 656 | /** Decorations */ 657 | ::-webkit-input-placeholder { 658 | color: ${alpha(tone(0), .5)}; 659 | } 660 | ::-moz-placeholder { 661 | color: ${alpha(tone(0), .5)}; 662 | } 663 | :-ms-input-placeholder { 664 | color: ${alpha(tone(0), .5)}; 665 | } 666 | :-moz-placeholder { 667 | color: ${alpha(tone(0), .5)}; 668 | } 669 | ::-moz-selection { 670 | background: ${tone(fg)}; 671 | color: ${tone(bg)}; 672 | } 673 | ::selection { 674 | background: ${tone(fg)}; 675 | color: ${tone(bg)}; 676 | } 677 | :host hr { 678 | border: none; 679 | height: 3px; 680 | border-radius: ${radius}px; 681 | margin: ${h/3}em 0; 682 | ${push(bg * .98, .05)}; 683 | } 684 | `; 685 | }; 686 | --------------------------------------------------------------------------------