From 1632a8f364672555bce25d35149b17584bc20c65 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Tue, 4 Sep 2018 21:16:40 -0500 Subject: [PATCH 01/45] Add number & range variables. See #492 --- js/usercss.js | 20 +++++++++++++++++-- manage/config-dialog.css | 3 ++- manage/config-dialog.js | 43 ++++++++++++++++++++++++++++++---------- 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/js/usercss.js b/js/usercss.js index 8f5e6eda..c91df4ca 100644 --- a/js/usercss.js +++ b/js/usercss.js @@ -29,7 +29,7 @@ var usercss = (() => { ['version', 0], ]); const MANDATORY_META = ['name', 'namespace', 'version']; - const META_VARS = ['text', 'color', 'checkbox', 'select', 'dropdown', 'image']; + const META_VARS = ['text', 'color', 'checkbox', 'select', 'dropdown', 'image', 'number', 'range']; const META_URLS = [...KNOWN_META.keys()].filter(k => k.endsWith('URL')); const BUILDER = { @@ -203,6 +203,22 @@ var usercss = (() => { break; } + case 'range': { + state.errorPrefix = 'Invalid JSON: '; + parseJSONValue(state); + state.errorPrefix = ''; + // [ start, end, step, default ] + if (Array.isArray(state.value) && state.value.length === 4) { + result.range = state.value; + result.default = state.value[3]; + } else { + // not a range, fallback to text + result.type = 'text'; + result.default = state.value; + } + break; + } + case 'dropdown': case 'image': { if (text[re.lastIndex] !== '{') { @@ -235,7 +251,7 @@ var usercss = (() => { } default: { - // text, color + // text, color, number parseStringToEnd(state); result.default = state.value; } diff --git a/manage/config-dialog.css b/manage/config-dialog.css index 9a895641..30fb6660 100644 --- a/manage/config-dialog.css +++ b/manage/config-dialog.css @@ -94,7 +94,8 @@ flex-shrink: 0; } -.config-value:not(.onoffswitch):not(.select-resizer) > :not(:last-child) { +.config-value:not(.onoffswitch):not(.select-resizer) > :not(:last-child), +.current-value { margin-right: 4px; } diff --git a/manage/config-dialog.js b/manage/config-dialog.js index cd056ebd..796ce88a 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -213,7 +213,7 @@ function configDialog(style) { ]) ]); for (const va of vars) { - let children; + let children, options; switch (va.type) { case 'color': children = [ @@ -259,14 +259,27 @@ function configDialog(style) { break; default: - children = [ - va.input = $create('input.config-value', { - va, - type: 'text', - onchange: updateVarOnChange, - oninput: updateVarOnInput, - }), - ]; + options = { + va, + type: va.type, + onchange: updateVarOnChange, + oninput: updateVarOnInput, + }; + if (va.type === 'range') { + options.min = va.range[0]; + options.max = va.range[1]; + options.step = va.range[2]; + options.value = va.default; + children = [ + $create('span.current-value', {textContent: va.value}), + va.input = $create('input.config-value', options), + ]; + } else { + children = [ + va.input = $create('input.config-value', options), + ]; + } + break; } @@ -287,6 +300,9 @@ function configDialog(style) { function updateVarOnChange() { this.va.value = this.type !== 'checkbox' ? this.value : this.checked ? '1' : '0'; + if (this.type === 'range') { + $('.current-value', this.parentNode).textContent = this.va.value; + } } function updateVarOnInput(event, debounced = false) { @@ -307,9 +323,14 @@ function configDialog(style) { } } else if (va.type === 'checkbox') { va.input.checked = Number(value); - } else { - va.input.value = value; } + if (va.type === 'range') { + const span = $('.current-value', va.input.parentNode); + if (span) { + span.textContent = value; + } + } + va.input.value = value; if (!prefs.get('config.autosave')) { renderValueState(va); } From 74892a313078a872ce1a871cd96c7d64027ac778 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Tue, 4 Sep 2018 21:17:36 -0500 Subject: [PATCH 02/45] Add select all text on focus. See #492 --- manage/config-dialog.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 796ce88a..121c0e36 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -264,6 +264,7 @@ function configDialog(style) { type: va.type, onchange: updateVarOnChange, oninput: updateVarOnInput, + onfocus: focusInput, }; if (va.type === 'range') { options.min = va.range[0]; @@ -313,6 +314,10 @@ function configDialog(style) { } } + function focusInput(event) { + event.target.select(); + } + function renderValues(varsToRender = vars) { for (const va of varsToRender) { const value = isDefault(va) ? va.default : va.value; From 9b483d993bf246bb08e6c77e947b32745649e09b Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Tue, 4 Sep 2018 21:18:08 -0500 Subject: [PATCH 03/45] Add default select option. See #492 --- js/usercss.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/js/usercss.js b/js/usercss.js index c91df4ca..cd34f6ed 100644 --- a/js/usercss.js +++ b/js/usercss.js @@ -177,6 +177,7 @@ var usercss = (() => { result.label = state.value; const {re, type, text} = state; + let deflt; switch (type === 'image' && state.key === 'var' ? '@image@var' : type) { case 'checkbox': { @@ -195,11 +196,24 @@ var usercss = (() => { parseJSONValue(state); state.errorPrefix = ''; if (Array.isArray(state.value)) { - result.options = state.value.map(text => createOption(text)); + result.options = state.value.map(text => { + if (text.endsWith('*')) { + text = text.replace(/\*$/, ''); + deflt = text; + } + return createOption(text); + }); } else { - result.options = Object.keys(state.value).map(k => createOption(k, state.value[k])); + result.options = Object.keys(state.value).map(k => { + if (k.endsWith('*')) { + state.value[k] = state.value[k].replace(/\*$/, ''); + k = k.replace(/\*$/, ''); + deflt = k; + } + return createOption(k, state.value[k]); + }); } - result.default = (result.options[0] || {}).name || ''; + result.default = typeof deflt !== 'undefined' ? deflt : (result.options[0] || {}).name || ''; break; } From 95f9f9c5d4e432df7a37e6624ce04007cbba58c1 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Wed, 5 Sep 2018 07:28:31 -0500 Subject: [PATCH 04/45] Rename to defaultValue --- js/usercss.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/usercss.js b/js/usercss.js index cd34f6ed..6fabf2a5 100644 --- a/js/usercss.js +++ b/js/usercss.js @@ -177,7 +177,7 @@ var usercss = (() => { result.label = state.value; const {re, type, text} = state; - let deflt; + let defaultValue; switch (type === 'image' && state.key === 'var' ? '@image@var' : type) { case 'checkbox': { @@ -199,7 +199,7 @@ var usercss = (() => { result.options = state.value.map(text => { if (text.endsWith('*')) { text = text.replace(/\*$/, ''); - deflt = text; + defaultValue = text; } return createOption(text); }); @@ -208,12 +208,12 @@ var usercss = (() => { if (k.endsWith('*')) { state.value[k] = state.value[k].replace(/\*$/, ''); k = k.replace(/\*$/, ''); - deflt = k; + defaultValue = k; } return createOption(k, state.value[k]); }); } - result.default = typeof deflt !== 'undefined' ? deflt : (result.options[0] || {}).name || ''; + result.default = typeof defaultValue !== 'undefined' ? defaultValue : (result.options[0] || {}).name || ''; break; } From 3fd3c53bd9eed1a6f14b2042b1bdf82e20c38c31 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Wed, 5 Sep 2018 07:28:55 -0500 Subject: [PATCH 05/45] Rename select all text function --- manage/config-dialog.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 121c0e36..3a1ae9e7 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -264,7 +264,7 @@ function configDialog(style) { type: va.type, onchange: updateVarOnChange, oninput: updateVarOnInput, - onfocus: focusInput, + onfocus: selectAllOnFocus, }; if (va.type === 'range') { options.min = va.range[0]; @@ -314,7 +314,7 @@ function configDialog(style) { } } - function focusInput(event) { + function selectAllOnFocus(event) { event.target.select(); } From e2b7f194c3534b82a217e09c7e4ca4a20c544d7e Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Wed, 5 Sep 2018 07:38:22 -0500 Subject: [PATCH 06/45] Don't set value on all types of input --- manage/config-dialog.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 3a1ae9e7..bbfe0c20 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -328,14 +328,15 @@ function configDialog(style) { } } else if (va.type === 'checkbox') { va.input.checked = Number(value); - } - if (va.type === 'range') { + } else if (va.type === 'range') { const span = $('.current-value', va.input.parentNode); + va.input.value = value; if (span) { span.textContent = value; } + } else { + va.input.value = value; } - va.input.value = value; if (!prefs.get('config.autosave')) { renderValueState(va); } From 1edfda5417c9d885d725f9d8b9bb6a0db1c3c4e7 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Wed, 5 Sep 2018 07:43:45 -0500 Subject: [PATCH 07/45] Wrap default & remove unnecessary break --- manage/config-dialog.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index bbfe0c20..5c5ae723 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -213,7 +213,7 @@ function configDialog(style) { ]) ]); for (const va of vars) { - let children, options; + let children; switch (va.type) { case 'color': children = [ @@ -258,8 +258,8 @@ function configDialog(style) { ]; break; - default: - options = { + default: { + const options = { va, type: va.type, onchange: updateVarOnChange, @@ -280,8 +280,8 @@ function configDialog(style) { va.input = $create('input.config-value', options), ]; } + } - break; } resetter = resetter.cloneNode(true); From 1dc24fac1bf0c4adda448bd69eb142473e3fd56c Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Wed, 5 Sep 2018 07:47:01 -0500 Subject: [PATCH 08/45] Use string slice instead of replace --- js/usercss.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/usercss.js b/js/usercss.js index 6fabf2a5..3ad4f514 100644 --- a/js/usercss.js +++ b/js/usercss.js @@ -198,7 +198,7 @@ var usercss = (() => { if (Array.isArray(state.value)) { result.options = state.value.map(text => { if (text.endsWith('*')) { - text = text.replace(/\*$/, ''); + text = text.slice(0, -1); defaultValue = text; } return createOption(text); @@ -206,8 +206,8 @@ var usercss = (() => { } else { result.options = Object.keys(state.value).map(k => { if (k.endsWith('*')) { - state.value[k] = state.value[k].replace(/\*$/, ''); - k = k.replace(/\*$/, ''); + state.value[k] = state.value[k].slice(0, -1); + k = k.slice(0, -1); defaultValue = k; } return createOption(k, state.value[k]); From d9c748d92f8f396f5f1464309ebaf7788854d90b Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Thu, 6 Sep 2018 00:20:19 -0500 Subject: [PATCH 09/45] Change range settings to [default, min, max, step] --- js/usercss.js | 6 +++--- manage/config-dialog.css | 23 +++++++++++++++++++++-- manage/config-dialog.js | 21 +++++++++++++++------ 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/js/usercss.js b/js/usercss.js index 3ad4f514..efc0933b 100644 --- a/js/usercss.js +++ b/js/usercss.js @@ -221,10 +221,10 @@ var usercss = (() => { state.errorPrefix = 'Invalid JSON: '; parseJSONValue(state); state.errorPrefix = ''; - // [ start, end, step, default ] - if (Array.isArray(state.value) && state.value.length === 4) { + // [default, start, end, step] (start, end & step are optional) + if (Array.isArray(state.value) && state.value.length) { + result.default = state.value.shift(); result.range = state.value; - result.default = state.value[3]; } else { // not a range, fallback to text result.type = 'text'; diff --git a/manage/config-dialog.css b/manage/config-dialog.css index 30fb6660..8bf15177 100644 --- a/manage/config-dialog.css +++ b/manage/config-dialog.css @@ -94,11 +94,30 @@ flex-shrink: 0; } -.config-value:not(.onoffswitch):not(.select-resizer) > :not(:last-child), -.current-value { +.config-value:not(.onoffswitch):not(.select-resizer) > :not(:last-child) { margin-right: 4px; } +.current-value { + outline: 1px solid rgba(128, 128, 128, 0.5); + padding: 2px 4px; + margin-right: 4px; +} + +.config-range span { + line-height: 22px; +} + +.current-range:before { + content: attr(data-min); + padding-right: .25em; +} + +.current-range:after { + content: attr(data-max); + padding-left: .25em; +} + .config-body label:not(.nondefault) .config-reset-icon { visibility: hidden; } diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 5c5ae723..a2d96007 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -266,14 +266,23 @@ function configDialog(style) { oninput: updateVarOnInput, onfocus: selectAllOnFocus, }; + const dataset = {}; if (va.type === 'range') { - options.min = va.range[0]; - options.max = va.range[1]; - options.step = va.range[2]; options.value = va.default; + if (typeof va.range[0] !== 'undefined') { + options.min = dataset.min = va.range[0]; + } + if (typeof va.range[1] !== 'undefined') { + options.max = dataset.max = va.range[1]; + } + if (va.range[2]) { + options.step = va.range[2]; + } children = [ $create('span.current-value', {textContent: va.value}), - va.input = $create('input.config-value', options), + $create('span.current-range', {dataset}, [ + va.input = $create('input.config-value', options), + ]) ]; } else { children = [ @@ -302,7 +311,7 @@ function configDialog(style) { function updateVarOnChange() { this.va.value = this.type !== 'checkbox' ? this.value : this.checked ? '1' : '0'; if (this.type === 'range') { - $('.current-value', this.parentNode).textContent = this.va.value; + $('.current-value', this.closest('.config-range')).textContent = this.va.value; } } @@ -329,7 +338,7 @@ function configDialog(style) { } else if (va.type === 'checkbox') { va.input.checked = Number(value); } else if (va.type === 'range') { - const span = $('.current-value', va.input.parentNode); + const span = $('.current-value', va.input.closest('.config-range')); va.input.value = value; if (span) { span.textContent = value; From 22911972778b57cba55dfc30cdc0a8a9aa587b52 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Fri, 7 Sep 2018 08:47:14 -0500 Subject: [PATCH 10/45] UserCSS Number variable now accepts a range --- js/usercss.js | 1 + manage/config-dialog.css | 2 +- manage/config-dialog.js | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/js/usercss.js b/js/usercss.js index efc0933b..9b126f16 100644 --- a/js/usercss.js +++ b/js/usercss.js @@ -217,6 +217,7 @@ var usercss = (() => { break; } + case 'number': case 'range': { state.errorPrefix = 'Invalid JSON: '; parseJSONValue(state); diff --git a/manage/config-dialog.css b/manage/config-dialog.css index 8bf15177..97b8a0bf 100644 --- a/manage/config-dialog.css +++ b/manage/config-dialog.css @@ -104,7 +104,7 @@ margin-right: 4px; } -.config-range span { +.config-number span, .config-range span { line-height: 22px; } diff --git a/manage/config-dialog.js b/manage/config-dialog.js index a2d96007..46bf7f0e 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -267,7 +267,8 @@ function configDialog(style) { onfocus: selectAllOnFocus, }; const dataset = {}; - if (va.type === 'range') { + if (va.type === 'range' || va.type === 'number') { + children = va.type === 'range' ? [$create('span.current-value', {textContent: va.value})] : []; options.value = va.default; if (typeof va.range[0] !== 'undefined') { options.min = dataset.min = va.range[0]; @@ -278,12 +279,11 @@ function configDialog(style) { if (va.range[2]) { options.step = va.range[2]; } - children = [ - $create('span.current-value', {textContent: va.value}), + children.push( $create('span.current-range', {dataset}, [ va.input = $create('input.config-value', options), ]) - ]; + ); } else { children = [ va.input = $create('input.config-value', options), From 1a8d628be59caf9c6527d0f79bfa87beabdd4ac9 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Sat, 8 Sep 2018 07:22:03 -0500 Subject: [PATCH 11/45] Clamp number input from user --- manage/config-dialog.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 46bf7f0e..32101edd 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -310,11 +310,26 @@ function configDialog(style) { function updateVarOnChange() { this.va.value = this.type !== 'checkbox' ? this.value : this.checked ? '1' : '0'; + if (this.type === 'number') { + this.value = this.va.value = clampValue(this.value, this.va.range); + } if (this.type === 'range') { $('.current-value', this.closest('.config-range')).textContent = this.va.value; } } + // Clamp input[type=number] to a valid range + function clampValue(value, [min = 0, max = 100, step]) { + if (value < min) { + return min; + } else if (value > max) { + return max; + } + const remainder = value % (step || 1); + // Don't restrict to integer values if step is undefined. + return typeof step !== 'undefined' && remainder !== 0 ? value - remainder : value; + } + function updateVarOnInput(event, debounced = false) { if (debounced) { event.target.dispatchEvent(new Event('change', {bubbles: true})); From f6998de6ecb7f7ccc8a4564ae85048c77101cb9b Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Sat, 8 Sep 2018 09:34:13 -0500 Subject: [PATCH 12/45] Include units in number & range variable --- js/usercss.js | 12 ++++++++++-- manage/config-dialog.js | 43 +++++++++++++++++++++++------------------ 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/js/usercss.js b/js/usercss.js index 9b126f16..35067d08 100644 --- a/js/usercss.js +++ b/js/usercss.js @@ -222,10 +222,15 @@ var usercss = (() => { state.errorPrefix = 'Invalid JSON: '; parseJSONValue(state); state.errorPrefix = ''; - // [default, start, end, step] (start, end & step are optional) + // [default, start, end, step, units] (start, end, step & units are optional) if (Array.isArray(state.value) && state.value.length) { result.default = state.value.shift(); - result.range = state.value; + const nonDigit = /[^\d.+-]/; + // label may be placed anywhere after default value + const labelIndex = state.value.findIndex(item => nonDigit.test(item)); + // but should not contain any numbers '4px' => 'px' + result.units = labelIndex < 0 ? '' : state.value.splice(labelIndex, 1)[0].toString().replace(/[\d.+-]/g, ''); + result.range = state.value.filter(item => !nonDigit.test(item)); } else { // not a range, fallback to text result.type = 'text'; @@ -572,6 +577,9 @@ var usercss = (() => { // TODO: handle customized image return va.options.find(o => o.name === va[prop]).value; } + if ((va.type === 'number' || va.type === 'range') && va.units) { + return va[prop] + va.units; + } return va[prop]; } diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 32101edd..196f6313 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -201,6 +201,27 @@ function configDialog(style) { return va.value === null || va.value === undefined || va.value === va.default; } + function handleRangeAndNumberInputs(va, options) { + const dataset = {}; + options.value = va.default; + if (typeof va.range[0] !== 'undefined') { + options.min = dataset.min = va.range[0]; + } + if (typeof va.range[1] !== 'undefined') { + options.max = dataset.max = va.range[1]; + } + if (va.range[2]) { + options.step = va.range[2]; + } + const children = va.type === 'range' ? [$create('span.current-value', {textContent: va.value + va.units})] : []; + children.push( + $create(`span.current-${va.type}`, {dataset}, [ + va.input = $create('input.config-value', options), + ]) + ); + return children; + } + function buildConfigForm() { let resetter = $create('a.config-reset-icon', {href: '#'}, [ @@ -266,24 +287,8 @@ function configDialog(style) { oninput: updateVarOnInput, onfocus: selectAllOnFocus, }; - const dataset = {}; if (va.type === 'range' || va.type === 'number') { - children = va.type === 'range' ? [$create('span.current-value', {textContent: va.value})] : []; - options.value = va.default; - if (typeof va.range[0] !== 'undefined') { - options.min = dataset.min = va.range[0]; - } - if (typeof va.range[1] !== 'undefined') { - options.max = dataset.max = va.range[1]; - } - if (va.range[2]) { - options.step = va.range[2]; - } - children.push( - $create('span.current-range', {dataset}, [ - va.input = $create('input.config-value', options), - ]) - ); + children = handleRangeAndNumberInputs(va, options); } else { children = [ va.input = $create('input.config-value', options), @@ -314,7 +319,7 @@ function configDialog(style) { this.value = this.va.value = clampValue(this.value, this.va.range); } if (this.type === 'range') { - $('.current-value', this.closest('.config-range')).textContent = this.va.value; + $('.current-value', this.closest('.config-range')).textContent = this.va.value + (this.va.units || ''); } } @@ -356,7 +361,7 @@ function configDialog(style) { const span = $('.current-value', va.input.closest('.config-range')); va.input.value = value; if (span) { - span.textContent = value; + span.textContent = value + (va.units || ''); } } else { va.input.value = value; From ae6e2647a37689c203287e68f6df6e8edcf62851 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Sat, 8 Sep 2018 09:46:29 -0500 Subject: [PATCH 13/45] Add proper number & range validation --- _locales/en/messages.json | 9 +++++++++ js/usercss.js | 9 +++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 0e9e5938..1f2a3c1b 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1051,6 +1051,15 @@ }, "description": "Error displayed when the value of @var color is invalid" }, + "styleMetaErrorRangeOrNumber": { + "message": "Invalid @var $type$: value must be an array containing at least one number at index zero", + "description": "Error displayed when the value of @var number or @var range is invalid", + "placeholders": { + "type": { + "content": "$1" + } + } + }, "styleMetaErrorPreprocessor": { "message": "Unsupported @preprocessor: $preprocessor$", "placeholders": { diff --git a/js/usercss.js b/js/usercss.js index 35067d08..c57eb8e9 100644 --- a/js/usercss.js +++ b/js/usercss.js @@ -231,10 +231,6 @@ var usercss = (() => { // but should not contain any numbers '4px' => 'px' result.units = labelIndex < 0 ? '' : state.value.splice(labelIndex, 1)[0].toString().replace(/[\d.+-]/g, ''); result.range = state.value.filter(item => !nonDigit.test(item)); - } else { - // not a range, fallback to text - result.type = 'text'; - result.default = state.value; } break; } @@ -617,6 +613,11 @@ var usercss = (() => { throw new Error(chrome.i18n.getMessage('styleMetaErrorCheckbox')); } else if (va.type === 'color') { va[value] = colorConverter.format(colorConverter.parse(va[value]), 'rgb'); + } else if ( + (va.type === 'number' || va.type === 'range') && + !(typeof va[value] === 'number' || Array.isArray(va.range)) + ) { + throw new Error(chrome.i18n.getMessage('styleMetaErrorRangeOrNumber', va.type)); } } From 7cd4380ee44a52a81f4cc2051c38d36dca69c017 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Sun, 9 Sep 2018 07:37:25 -0500 Subject: [PATCH 14/45] Ensure range default & value types match --- js/usercss.js | 2 +- manage/config-dialog.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/js/usercss.js b/js/usercss.js index c57eb8e9..aef83144 100644 --- a/js/usercss.js +++ b/js/usercss.js @@ -224,7 +224,7 @@ var usercss = (() => { state.errorPrefix = ''; // [default, start, end, step, units] (start, end, step & units are optional) if (Array.isArray(state.value) && state.value.length) { - result.default = state.value.shift(); + result.default = parseFloat(state.value.shift()); const nonDigit = /[^\d.+-]/; // label may be placed anywhere after default value const labelIndex = state.value.findIndex(item => nonDigit.test(item)); diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 196f6313..ae920966 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -314,12 +314,13 @@ function configDialog(style) { } function updateVarOnChange() { - this.va.value = this.type !== 'checkbox' ? this.value : this.checked ? '1' : '0'; if (this.type === 'number') { this.value = this.va.value = clampValue(this.value, this.va.range); - } - if (this.type === 'range') { + } else if (this.type === 'range') { $('.current-value', this.closest('.config-range')).textContent = this.va.value + (this.va.units || ''); + this.va.value = parseFloat(this.value); + } else { + this.va.value = this.type !== 'checkbox' ? this.value : this.checked ? '1' : '0'; } } From a277800868b4215f870cbf7a65d2e7a851fc2edc Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Sun, 16 Sep 2018 17:17:15 -0500 Subject: [PATCH 15/45] Fix all the things --- js/usercss.js | 41 ++++++++++++++++------------ manage/config-dialog.css | 3 +- manage/config-dialog.js | 59 ++++++++++++++++++++++------------------ 3 files changed, 57 insertions(+), 46 deletions(-) diff --git a/js/usercss.js b/js/usercss.js index aef83144..edaff43b 100644 --- a/js/usercss.js +++ b/js/usercss.js @@ -177,7 +177,6 @@ var usercss = (() => { result.label = state.value; const {re, type, text} = state; - let defaultValue; switch (type === 'image' && state.key === 'var' ? '@image@var' : type) { case 'checkbox': { @@ -195,25 +194,34 @@ var usercss = (() => { state.errorPrefix = 'Invalid JSON: '; parseJSONValue(state); state.errorPrefix = ''; + const extractDefault = text => { + if (text.endsWith('*')) { + return text.slice(0, -1); + } + return false; + }; if (Array.isArray(state.value)) { result.options = state.value.map(text => { - if (text.endsWith('*')) { - text = text.slice(0, -1); - defaultValue = text; + const isDefault = extractDefault(text); + if (isDefault) { + result.default = isDefault; } - return createOption(text); + return createOption(isDefault || text); }); } else { result.options = Object.keys(state.value).map(k => { - if (k.endsWith('*')) { - state.value[k] = state.value[k].slice(0, -1); - k = k.slice(0, -1); - defaultValue = k; + const isDefault = extractDefault(k); + const value = state.value[k]; + if (isDefault) { + k = isDefault; + result.default = k; } - return createOption(k, state.value[k]); + return createOption(k, value); }); } - result.default = typeof defaultValue !== 'undefined' ? defaultValue : (result.options[0] || {}).name || ''; + if (result.default === null) { + result.default = (result.options[0] || {}).name || ''; + } break; } @@ -224,13 +232,10 @@ var usercss = (() => { state.errorPrefix = ''; // [default, start, end, step, units] (start, end, step & units are optional) if (Array.isArray(state.value) && state.value.length) { - result.default = parseFloat(state.value.shift()); - const nonDigit = /[^\d.+-]/; + result.default = state.value.shift(); // label may be placed anywhere after default value - const labelIndex = state.value.findIndex(item => nonDigit.test(item)); - // but should not contain any numbers '4px' => 'px' - result.units = labelIndex < 0 ? '' : state.value.splice(labelIndex, 1)[0].toString().replace(/[\d.+-]/g, ''); - result.range = state.value.filter(item => !nonDigit.test(item)); + result.units = (state.value.find(i => typeof i === 'string') || '').replace(/[\d.+-]/g, ''); + result.range = state.value.filter(i => typeof i === 'number'); } break; } @@ -615,7 +620,7 @@ var usercss = (() => { va[value] = colorConverter.format(colorConverter.parse(va[value]), 'rgb'); } else if ( (va.type === 'number' || va.type === 'range') && - !(typeof va[value] === 'number' || Array.isArray(va.range)) + (typeof va[value] !== 'number' || !Array.isArray(va.range)) ) { throw new Error(chrome.i18n.getMessage('styleMetaErrorRangeOrNumber', va.type)); } diff --git a/manage/config-dialog.css b/manage/config-dialog.css index 97b8a0bf..da1fd7d0 100644 --- a/manage/config-dialog.css +++ b/manage/config-dialog.css @@ -99,10 +99,9 @@ } .current-value { - outline: 1px solid rgba(128, 128, 128, 0.5); padding: 2px 4px; margin-right: 4px; -} + } .config-number span, .config-range span { line-height: 22px; diff --git a/manage/config-dialog.js b/manage/config-dialog.js index ae920966..6ff8ceac 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -201,25 +201,18 @@ function configDialog(style) { return va.value === null || va.value === undefined || va.value === va.default; } - function handleRangeAndNumberInputs(va, options) { + function rangeToProps(range = []) { const dataset = {}; - options.value = va.default; - if (typeof va.range[0] !== 'undefined') { - options.min = dataset.min = va.range[0]; + if (range.length > 0) { + dataset.min = range[0]; } - if (typeof va.range[1] !== 'undefined') { - options.max = dataset.max = va.range[1]; + if (range.length > 1) { + dataset.max = range[1]; } - if (va.range[2]) { - options.step = va.range[2]; + if (range[2]) { + dataset.step = range[2]; } - const children = va.type === 'range' ? [$create('span.current-value', {textContent: va.value + va.units})] : []; - children.push( - $create(`span.current-${va.type}`, {dataset}, [ - va.input = $create('input.config-value', options), - ]) - ); - return children; + return dataset; } function buildConfigForm() { @@ -279,6 +272,21 @@ function configDialog(style) { ]; break; + case 'range': + case 'number': { + children = [ + va.type === 'range' && $create('span.current-value', {textContent: va.value + va.units}), + va.input = $create('input.config-value', { + va, + type: va.type, + value: va.default, + ...rangeToProps(va.range), + onchange: updateVarOnChange + }) + ]; + break; + } + default: { const options = { va, @@ -287,13 +295,9 @@ function configDialog(style) { oninput: updateVarOnInput, onfocus: selectAllOnFocus, }; - if (va.type === 'range' || va.type === 'number') { - children = handleRangeAndNumberInputs(va, options); - } else { - children = [ - va.input = $create('input.config-value', options), - ]; - } + children = [ + va.input = $create('input.config-value', options), + ]; } } @@ -314,26 +318,29 @@ function configDialog(style) { } function updateVarOnChange() { + console.log('updateVar', this.type) if (this.type === 'number') { this.value = this.va.value = clampValue(this.value, this.va.range); } else if (this.type === 'range') { - $('.current-value', this.closest('.config-range')).textContent = this.va.value + (this.va.units || ''); this.va.value = parseFloat(this.value); + $('.current-value', this.closest('.config-range')).textContent = this.va.value + (this.va.units || ''); } else { this.va.value = this.type !== 'checkbox' ? this.value : this.checked ? '1' : '0'; } + console.log(this.va.value); } // Clamp input[type=number] to a valid range function clampValue(value, [min = 0, max = 100, step]) { if (value < min) { return min; - } else if (value > max) { + } + if (value > max) { return max; } - const remainder = value % (step || 1); + const inv = 1 / (step || 1); // Don't restrict to integer values if step is undefined. - return typeof step !== 'undefined' && remainder !== 0 ? value - remainder : value; + return typeof step !== 'undefined' ? Math.floor(inv * value) / inv : value; } function updateVarOnInput(event, debounced = false) { From 06629224c6887b450c04c6c606a1d1897041d679 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Sun, 16 Sep 2018 17:25:46 -0500 Subject: [PATCH 16/45] Minor cleanup --- manage/config-dialog.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 6ff8ceac..63f62fd1 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -273,7 +273,7 @@ function configDialog(style) { break; case 'range': - case 'number': { + case 'number': children = [ va.type === 'range' && $create('span.current-value', {textContent: va.value + va.units}), va.input = $create('input.config-value', { @@ -285,7 +285,6 @@ function configDialog(style) { }) ]; break; - } default: { const options = { From 568fc336e7f014d5fb10ea65d11b731b6041d807 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Sun, 16 Sep 2018 17:30:20 -0500 Subject: [PATCH 17/45] Remove console.logs --- manage/config-dialog.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 63f62fd1..29e9f017 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -317,7 +317,6 @@ function configDialog(style) { } function updateVarOnChange() { - console.log('updateVar', this.type) if (this.type === 'number') { this.value = this.va.value = clampValue(this.value, this.va.range); } else if (this.type === 'range') { @@ -326,7 +325,6 @@ function configDialog(style) { } else { this.va.value = this.type !== 'checkbox' ? this.value : this.checked ? '1' : '0'; } - console.log(this.va.value); } // Clamp input[type=number] to a valid range From 18815523481077f4643f01c30b1805929e90013b Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Sun, 16 Sep 2018 18:22:50 -0500 Subject: [PATCH 18/45] Remove va.range & add min/max/step to va directly --- js/usercss.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/js/usercss.js b/js/usercss.js index edaff43b..45a7c762 100644 --- a/js/usercss.js +++ b/js/usercss.js @@ -232,10 +232,13 @@ var usercss = (() => { state.errorPrefix = ''; // [default, start, end, step, units] (start, end, step & units are optional) if (Array.isArray(state.value) && state.value.length) { - result.default = state.value.shift(); - // label may be placed anywhere after default value + // label may be placed anywhere result.units = (state.value.find(i => typeof i === 'string') || '').replace(/[\d.+-]/g, ''); - result.range = state.value.filter(i => typeof i === 'number'); + const range = state.value.filter(i => typeof i === 'number'); + result.default = range[0]; + result.min = range[1]; + result.max = range[2]; + result.step = range[3]; } break; } @@ -618,10 +621,7 @@ var usercss = (() => { throw new Error(chrome.i18n.getMessage('styleMetaErrorCheckbox')); } else if (va.type === 'color') { va[value] = colorConverter.format(colorConverter.parse(va[value]), 'rgb'); - } else if ( - (va.type === 'number' || va.type === 'range') && - (typeof va[value] !== 'number' || !Array.isArray(va.range)) - ) { + } else if ((va.type === 'number' || va.type === 'range') && typeof va[value] !== 'number') { throw new Error(chrome.i18n.getMessage('styleMetaErrorRangeOrNumber', va.type)); } } From 1ba0173422aa122c904525ad4bc9456f8ed8f704 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Sun, 16 Sep 2018 18:23:50 -0500 Subject: [PATCH 19/45] Remove rangeToProps & fix clampValue functions --- manage/config-dialog.js | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 29e9f017..fb4eb153 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -201,20 +201,6 @@ function configDialog(style) { return va.value === null || va.value === undefined || va.value === va.default; } - function rangeToProps(range = []) { - const dataset = {}; - if (range.length > 0) { - dataset.min = range[0]; - } - if (range.length > 1) { - dataset.max = range[1]; - } - if (range[2]) { - dataset.step = range[2]; - } - return dataset; - } - function buildConfigForm() { let resetter = $create('a.config-reset-icon', {href: '#'}, [ @@ -280,7 +266,9 @@ function configDialog(style) { va, type: va.type, value: va.default, - ...rangeToProps(va.range), + min: va.min, + max: va.max, + step: va.step, onchange: updateVarOnChange }) ]; @@ -318,7 +306,7 @@ function configDialog(style) { function updateVarOnChange() { if (this.type === 'number') { - this.value = this.va.value = clampValue(this.value, this.va.range); + this.value = this.va.value = clampValue(this.value, this.va); } else if (this.type === 'range') { this.va.value = parseFloat(this.value); $('.current-value', this.closest('.config-range')).textContent = this.va.value + (this.va.units || ''); @@ -328,16 +316,16 @@ function configDialog(style) { } // Clamp input[type=number] to a valid range - function clampValue(value, [min = 0, max = 100, step]) { - if (value < min) { - return min; + function clampValue(value, va) { + if (value < (va.min || 0)) { + return va.min; } - if (value > max) { - return max; + if (value > (va.max || 100)) { + return va.max; } - const inv = 1 / (step || 1); + const inv = 1 / (va.step || 1); // Don't restrict to integer values if step is undefined. - return typeof step !== 'undefined' ? Math.floor(inv * value) / inv : value; + return typeof va.step !== 'undefined' ? Math.floor(inv * value) / inv : value; } function updateVarOnInput(event, debounced = false) { From 2a2191049f5d02824a53a696995ad935541a44c2 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Sun, 16 Sep 2018 18:41:48 -0500 Subject: [PATCH 20/45] Don't allow step to be set to zero --- js/usercss.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/usercss.js b/js/usercss.js index 45a7c762..1cfdd142 100644 --- a/js/usercss.js +++ b/js/usercss.js @@ -238,7 +238,7 @@ var usercss = (() => { result.default = range[0]; result.min = range[1]; result.max = range[2]; - result.step = range[3]; + result.step = range[3] || 1; } break; } From 153a066fbe9ff0bdec7cb9d7051a69306bbe1d78 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Sun, 16 Sep 2018 18:56:45 -0500 Subject: [PATCH 21/45] Fix clampValue issue if min/max are not defined --- manage/config-dialog.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index fb4eb153..2e2f0988 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -317,11 +317,13 @@ function configDialog(style) { // Clamp input[type=number] to a valid range function clampValue(value, va) { - if (value < (va.min || 0)) { - return va.min; + const min = va.min || 0; + const max = va.max || 100; + if (value < min) { + return min; } - if (value > (va.max || 100)) { - return va.max; + if (value > max) { + return max; } const inv = 1 / (va.step || 1); // Don't restrict to integer values if step is undefined. From 7d533d4d4ae4b4bacb7b362f9dd1a88f0196f8f3 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Mon, 17 Sep 2018 22:10:43 -0500 Subject: [PATCH 22/45] Fix select default option extraction --- js/usercss.js | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/js/usercss.js b/js/usercss.js index 1cfdd142..64a708a6 100644 --- a/js/usercss.js +++ b/js/usercss.js @@ -194,30 +194,18 @@ var usercss = (() => { state.errorPrefix = 'Invalid JSON: '; parseJSONValue(state); state.errorPrefix = ''; - const extractDefault = text => { - if (text.endsWith('*')) { - return text.slice(0, -1); + const extractDefaultOption = (key, value) => { + if (key.endsWith('*')) { + const option = createOption(key.slice(0, -1), value); + result.default = option.name; + return option; } - return false; + return createOption(key, value); }; if (Array.isArray(state.value)) { - result.options = state.value.map(text => { - const isDefault = extractDefault(text); - if (isDefault) { - result.default = isDefault; - } - return createOption(isDefault || text); - }); + result.options = state.value.map(k => extractDefaultOption(k)); } else { - result.options = Object.keys(state.value).map(k => { - const isDefault = extractDefault(k); - const value = state.value[k]; - if (isDefault) { - k = isDefault; - result.default = k; - } - return createOption(k, value); - }); + result.options = Object.keys(state.value).map(k => extractDefaultOption(k, state.value[k])); } if (result.default === null) { result.default = (result.options[0] || {}).name || ''; From d136fdbafa1727a83a7cd9015723a41d4f65e28c Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Mon, 17 Sep 2018 22:12:36 -0500 Subject: [PATCH 23/45] Allow including null in number/range variables --- js/usercss.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/usercss.js b/js/usercss.js index 64a708a6..54d41526 100644 --- a/js/usercss.js +++ b/js/usercss.js @@ -222,7 +222,7 @@ var usercss = (() => { if (Array.isArray(state.value) && state.value.length) { // label may be placed anywhere result.units = (state.value.find(i => typeof i === 'string') || '').replace(/[\d.+-]/g, ''); - const range = state.value.filter(i => typeof i === 'number'); + const range = state.value.filter(i => typeof i === 'number' || i == null); result.default = range[0]; result.min = range[1]; result.max = range[2]; From 7be33a9237c0d68bb76902d0b39128b83f5d7e9c Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Mon, 17 Sep 2018 22:22:57 -0500 Subject: [PATCH 24/45] Fix default values --- js/usercss.js | 2 +- manage/config-dialog.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/js/usercss.js b/js/usercss.js index 54d41526..720ac3fb 100644 --- a/js/usercss.js +++ b/js/usercss.js @@ -226,7 +226,7 @@ var usercss = (() => { result.default = range[0]; result.min = range[1]; result.max = range[2]; - result.step = range[3] || 1; + result.step = range[3] == 0 ? 1 : range[3]; } break; } diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 2e2f0988..d749fada 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -317,8 +317,8 @@ function configDialog(style) { // Clamp input[type=number] to a valid range function clampValue(value, va) { - const min = va.min || 0; - const max = va.max || 100; + const min = typeof va.min === 'undefined' ? 0 : va.min; + const max = typeof va.max === 'undefined' ? 100 : va.max; if (value < min) { return min; } From ff0145a1299844334cc303ce61e9371011642e03 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Mon, 17 Sep 2018 22:49:44 -0500 Subject: [PATCH 25/45] Fix clamp to step floor --- manage/config-dialog.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index d749fada..4bfe4669 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -325,9 +325,15 @@ function configDialog(style) { if (value > max) { return max; } - const inv = 1 / (va.step || 1); // Don't restrict to integer values if step is undefined. - return typeof va.step !== 'undefined' ? Math.floor(inv * value) / inv : value; + if (typeof va.step === 'undefined') { + return value; + } + const step = va.step || 1; + const precision = (step.toString().split('.')[1] || 0).length + 1; + const inv = 1 / step; + // ECMA-262 only requires a precision of up to 21 significant digits + return Number((Math.floor(inv * value) / inv).toPrecision(precision > 21 ? 21 : precision)); } function updateVarOnInput(event, debounced = false) { From 0678a6b302f32914dddc89d149d6fa9ac95cee46 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Mon, 17 Sep 2018 23:05:15 -0500 Subject: [PATCH 26/45] Remove unnecessary css --- manage/config-dialog.css | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/manage/config-dialog.css b/manage/config-dialog.css index da1fd7d0..abdcd89d 100644 --- a/manage/config-dialog.css +++ b/manage/config-dialog.css @@ -107,16 +107,6 @@ line-height: 22px; } -.current-range:before { - content: attr(data-min); - padding-right: .25em; -} - -.current-range:after { - content: attr(data-max); - padding-left: .25em; -} - .config-body label:not(.nondefault) .config-reset-icon { visibility: hidden; } From 314ec99e5cf5f45cdc85082e5e203e99408f24bb Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Mon, 17 Sep 2018 23:08:16 -0500 Subject: [PATCH 27/45] More suggested changes --- manage/config-dialog.js | 53 +++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 4bfe4669..9c9910fd 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -197,8 +197,12 @@ function configDialog(style) { renderValues(); } + function isNullOrUndefined(value) { + return value === null || value === undefined; + } + function isDefault(va) { - return va.value === null || va.value === undefined || va.value === va.default; + return isNullOrUndefined(va.value) || va.value === va.default; } function buildConfigForm() { @@ -259,34 +263,41 @@ function configDialog(style) { break; case 'range': - case 'number': - children = [ - va.type === 'range' && $create('span.current-value', {textContent: va.value + va.units}), - va.input = $create('input.config-value', { - va, - type: va.type, - value: va.default, - min: va.min, - max: va.max, - step: va.step, - onchange: updateVarOnChange - }) - ]; - break; - - default: { + case 'number': { const options = { va, type: va.type, + onfocus: va.type === 'number' ? selectAllOnFocus : null, onchange: updateVarOnChange, - oninput: updateVarOnInput, - onfocus: selectAllOnFocus, + oninput: updateVarOnInput }; + if (!isNullOrUndefined(va.min)) { + options.min = va.min; + } + if (!isNullOrUndefined(va.max)) { + options.max = va.max; + } + if (!isNullOrUndefined(va.step)) { + options.step = va.step; + } children = [ - va.input = $create('input.config-value', options), + va.type === 'range' && $create('span.current-value'), + va.input = $create('input.config-value', options) ]; + break; } + default: + children = [ + va.input = $create('input.config-value', { + va, + type: va.type, + onchange: updateVarOnChange, + oninput: updateVarOnInput, + onfocus: selectAllOnFocus, + }), + ]; + } resetter = resetter.cloneNode(true); @@ -308,7 +319,7 @@ function configDialog(style) { if (this.type === 'number') { this.value = this.va.value = clampValue(this.value, this.va); } else if (this.type === 'range') { - this.va.value = parseFloat(this.value); + this.va.value = Number(this.value); $('.current-value', this.closest('.config-range')).textContent = this.va.value + (this.va.units || ''); } else { this.va.value = this.type !== 'checkbox' ? this.value : this.checked ? '1' : '0'; From 375e0ec44827c877a694fcc0ed3f5d7ed300aec6 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Tue, 18 Sep 2018 07:29:35 -0500 Subject: [PATCH 28/45] Switch to isNumber function --- manage/config-dialog.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 9c9910fd..b7f8f5f9 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -197,12 +197,12 @@ function configDialog(style) { renderValues(); } - function isNullOrUndefined(value) { - return value === null || value === undefined; + function isNumber(value) { + return value !== '' && !isNaN(Number(value)); } function isDefault(va) { - return isNullOrUndefined(va.value) || va.value === va.default; + return va.value === null || va.value === undefined || va.value === va.default; } function buildConfigForm() { @@ -271,13 +271,13 @@ function configDialog(style) { onchange: updateVarOnChange, oninput: updateVarOnInput }; - if (!isNullOrUndefined(va.min)) { + if (isNumber(va.min)) { options.min = va.min; } - if (!isNullOrUndefined(va.max)) { + if (isNumber(va.max)) { options.max = va.max; } - if (!isNullOrUndefined(va.step)) { + if (isNumber(va.step) && isFinite(Number(va.step))) { options.step = va.step; } children = [ From 7fff67068367b18c4ddb6d49bd01949f22c6573c Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Tue, 18 Sep 2018 07:45:53 -0500 Subject: [PATCH 29/45] Fix clamp undefined min/max --- manage/config-dialog.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index b7f8f5f9..8f2ba6b1 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -328,9 +328,8 @@ function configDialog(style) { // Clamp input[type=number] to a valid range function clampValue(value, va) { - const min = typeof va.min === 'undefined' ? 0 : va.min; - const max = typeof va.max === 'undefined' ? 100 : va.max; - if (value < min) { + const max = isNumber(va.max) ? va.max : 100; + if (isNumber(va.min) && value < va.min) { return min; } if (value > max) { From f35077afceb90d58cbc18b9661958ac475a4fd70 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Tue, 18 Sep 2018 07:46:54 -0500 Subject: [PATCH 30/45] Fix clamp undefined step --- manage/config-dialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 8f2ba6b1..fe665098 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -336,7 +336,7 @@ function configDialog(style) { return max; } // Don't restrict to integer values if step is undefined. - if (typeof va.step === 'undefined') { + if (!isNumber(va.step)) { return value; } const step = va.step || 1; From 15a05214080f2df71372c802e1172e892c7a3255 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Tue, 18 Sep 2018 08:12:34 -0500 Subject: [PATCH 31/45] Fix clamp function --- manage/config-dialog.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index fe665098..d638de1f 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -327,7 +327,7 @@ function configDialog(style) { } // Clamp input[type=number] to a valid range - function clampValue(value, va) { + function clampValue(value, va, break = false) { const max = isNumber(va.max) ? va.max : 100; if (isNumber(va.min) && value < va.min) { return min; @@ -336,14 +336,14 @@ function configDialog(style) { return max; } // Don't restrict to integer values if step is undefined. - if (!isNumber(va.step)) { + if (!isNumber(va.step) || break) { return value; } const step = va.step || 1; - const precision = (step.toString().split('.')[1] || 0).length + 1; - const inv = 1 / step; - // ECMA-262 only requires a precision of up to 21 significant digits - return Number((Math.floor(inv * value) / inv).toPrecision(precision > 21 ? 21 : precision)); + const scale = 10 ** (step.toString().split('.')[1] || '').length; + value = Math.round((value * scale) / (step * scale)) * (step * scale) / scale; + // clamp modified value; skip step check + return clampValue(value, va, true); } function updateVarOnInput(event, debounced = false) { From 2c7742e56fd26aaa96bf82d619298b0e0994a86f Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Tue, 18 Sep 2018 08:14:00 -0500 Subject: [PATCH 32/45] Check for null in isNumber --- manage/config-dialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index d638de1f..1eb27b03 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -198,7 +198,7 @@ function configDialog(style) { } function isNumber(value) { - return value !== '' && !isNaN(Number(value)); + return value !== null && value !== '' && !isNaN(Number(value)); } function isDefault(va) { From f2c973096bcfeb5e0219fb8696fa8c3350f89fb8 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Tue, 18 Sep 2018 08:17:22 -0500 Subject: [PATCH 33/45] Fix clamp function --- manage/config-dialog.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 1eb27b03..d22040eb 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -328,12 +328,11 @@ function configDialog(style) { // Clamp input[type=number] to a valid range function clampValue(value, va, break = false) { - const max = isNumber(va.max) ? va.max : 100; if (isNumber(va.min) && value < va.min) { - return min; + return va.min; } - if (value > max) { - return max; + if (isNumber(va.max) && value > va.max) { + return va.max; } // Don't restrict to integer values if step is undefined. if (!isNumber(va.step) || break) { From 75d2ef2cc1e729cbe7b37c6d9ab410bccfed13db Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Wed, 19 Sep 2018 06:39:54 -0500 Subject: [PATCH 34/45] Fix strict equals --- js/usercss.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/usercss.js b/js/usercss.js index 720ac3fb..d8a5809d 100644 --- a/js/usercss.js +++ b/js/usercss.js @@ -222,11 +222,11 @@ var usercss = (() => { if (Array.isArray(state.value) && state.value.length) { // label may be placed anywhere result.units = (state.value.find(i => typeof i === 'string') || '').replace(/[\d.+-]/g, ''); - const range = state.value.filter(i => typeof i === 'number' || i == null); + const range = state.value.filter(i => typeof i === 'number' || i === null); result.default = range[0]; result.min = range[1]; result.max = range[2]; - result.step = range[3] == 0 ? 1 : range[3]; + result.step = range[3] === 0 ? 1 : range[3]; } break; } From a4d2e3dd9c82c056a58b3c1897f116fa26d661fc Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Wed, 19 Sep 2018 06:40:20 -0500 Subject: [PATCH 35/45] Remove misleading comment --- js/usercss.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/usercss.js b/js/usercss.js index d8a5809d..220acef7 100644 --- a/js/usercss.js +++ b/js/usercss.js @@ -263,7 +263,7 @@ var usercss = (() => { } default: { - // text, color, number + // text, color parseStringToEnd(state); result.default = state.value; } From d00e5f4ddbddd16d0f680893fb1acc56e9730a28 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Wed, 19 Sep 2018 07:09:10 -0500 Subject: [PATCH 36/45] Change clamp function order & only call onblur --- manage/config-dialog.js | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index d22040eb..e5a3e9a3 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -268,6 +268,7 @@ function configDialog(style) { va, type: va.type, onfocus: va.type === 'number' ? selectAllOnFocus : null, + onblur: va.type === 'number' ? updateVarOnBlur : null, onchange: updateVarOnChange, oninput: updateVarOnInput }; @@ -316,9 +317,7 @@ function configDialog(style) { } function updateVarOnChange() { - if (this.type === 'number') { - this.value = this.va.value = clampValue(this.value, this.va); - } else if (this.type === 'range') { + if (this.type === 'range') { this.va.value = Number(this.value); $('.current-value', this.closest('.config-range')).textContent = this.va.value + (this.va.units || ''); } else { @@ -326,23 +325,26 @@ function configDialog(style) { } } + // Applied to input[type=number] + function updateVarOnBlur() { + this.value = this.va.value = clampValue(this.value, this.va); + } + // Clamp input[type=number] to a valid range - function clampValue(value, va, break = false) { + function clampValue(value, va) { + // Don't restrict to integer values if step is undefined. + if (isNumber(va.step)) { + const step = va.step || 1; + const scale = 10 ** (step.toString().split('.')[1] || '').length; + value = Math.round((value * scale) / (step * scale)) * (step * scale) / scale; + } if (isNumber(va.min) && value < va.min) { return va.min; } if (isNumber(va.max) && value > va.max) { return va.max; } - // Don't restrict to integer values if step is undefined. - if (!isNumber(va.step) || break) { - return value; - } - const step = va.step || 1; - const scale = 10 ** (step.toString().split('.')[1] || '').length; - value = Math.round((value * scale) / (step * scale)) * (step * scale) / scale; - // clamp modified value; skip step check - return clampValue(value, va, true); + return value; } function updateVarOnInput(event, debounced = false) { From 40a3abbee8069ddfa0841d8e786096dbbf7a9bc4 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Wed, 19 Sep 2018 07:09:36 -0500 Subject: [PATCH 37/45] Fix isNumber function --- manage/config-dialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index e5a3e9a3..34559c44 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -198,7 +198,7 @@ function configDialog(style) { } function isNumber(value) { - return value !== null && value !== '' && !isNaN(Number(value)); + return typeof value === 'number'; } function isDefault(va) { From 5af74a9d80d5988b4d17e6507db98bdcffbaa1f8 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Wed, 19 Sep 2018 07:30:15 -0500 Subject: [PATCH 38/45] Remove isNumber function --- manage/config-dialog.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 34559c44..4698c38d 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -197,10 +197,6 @@ function configDialog(style) { renderValues(); } - function isNumber(value) { - return typeof value === 'number'; - } - function isDefault(va) { return va.value === null || va.value === undefined || va.value === va.default; } @@ -272,13 +268,13 @@ function configDialog(style) { onchange: updateVarOnChange, oninput: updateVarOnInput }; - if (isNumber(va.min)) { + if (typeof va.min === 'number') { options.min = va.min; } - if (isNumber(va.max)) { + if (typeof va.max === 'number') { options.max = va.max; } - if (isNumber(va.step) && isFinite(Number(va.step))) { + if (typeof va.step === 'number' && isFinite(va.step)) { options.step = va.step; } children = [ @@ -333,15 +329,15 @@ function configDialog(style) { // Clamp input[type=number] to a valid range function clampValue(value, va) { // Don't restrict to integer values if step is undefined. - if (isNumber(va.step)) { + if (typeof va.step === 'number') { const step = va.step || 1; const scale = 10 ** (step.toString().split('.')[1] || '').length; value = Math.round((value * scale) / (step * scale)) * (step * scale) / scale; } - if (isNumber(va.min) && value < va.min) { + if (typeof va.min === 'number' && value < va.min) { return va.min; } - if (isNumber(va.max) && value > va.max) { + if (typeof va.max === 'number' && value > va.max) { return va.max; } return value; From 40f580a0b2259e62e56f2e23bb1e77bd6b1a16f0 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Wed, 19 Sep 2018 07:44:51 -0500 Subject: [PATCH 39/45] Change stored number value on change --- manage/config-dialog.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 4698c38d..697e1740 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -316,6 +316,9 @@ function configDialog(style) { if (this.type === 'range') { this.va.value = Number(this.value); $('.current-value', this.closest('.config-range')).textContent = this.va.value + (this.va.units || ''); + } else if (this.type === 'number') { + // clamp stored value + this.va.value = clampValue(this.value, this.va); } else { this.va.value = this.type !== 'checkbox' ? this.value : this.checked ? '1' : '0'; } @@ -323,6 +326,7 @@ function configDialog(style) { // Applied to input[type=number] function updateVarOnBlur() { + // clamp value visible to user this.value = this.va.value = clampValue(this.value, this.va); } From ec16a0c1e0e8494fe30dcf6cf60be3260e8a440f Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Wed, 19 Sep 2018 08:27:29 -0500 Subject: [PATCH 40/45] Remove onblur, don't save invalid inputs --- manage/config-dialog.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 697e1740..eae129a3 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -264,7 +264,6 @@ function configDialog(style) { va, type: va.type, onfocus: va.type === 'number' ? selectAllOnFocus : null, - onblur: va.type === 'number' ? updateVarOnBlur : null, onchange: updateVarOnChange, oninput: updateVarOnInput }; @@ -317,19 +316,15 @@ function configDialog(style) { this.va.value = Number(this.value); $('.current-value', this.closest('.config-range')).textContent = this.va.value + (this.va.units || ''); } else if (this.type === 'number') { - // clamp stored value - this.va.value = clampValue(this.value, this.va); + const value = Number(this.value); + if (clampValue(value, this.va) === value) { + this.va.value = value; + } } else { this.va.value = this.type !== 'checkbox' ? this.value : this.checked ? '1' : '0'; } } - // Applied to input[type=number] - function updateVarOnBlur() { - // clamp value visible to user - this.value = this.va.value = clampValue(this.value, this.va); - } - // Clamp input[type=number] to a valid range function clampValue(value, va) { // Don't restrict to integer values if step is undefined. From a0ffdb7645fe9f2218aa0ed90521c04e323b9006 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Wed, 19 Sep 2018 08:31:52 -0500 Subject: [PATCH 41/45] Update current value in updateRangeCurrentValue --- manage/config-dialog.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index eae129a3..9aa78911 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -314,7 +314,7 @@ function configDialog(style) { function updateVarOnChange() { if (this.type === 'range') { this.va.value = Number(this.value); - $('.current-value', this.closest('.config-range')).textContent = this.va.value + (this.va.units || ''); + updateRangeCurrentValue(va, this.va.value); } else if (this.type === 'number') { const value = Number(this.value); if (clampValue(value, this.va) === value) { @@ -342,6 +342,13 @@ function configDialog(style) { return value; } + function updateRangeCurrentValue(va, value) { + const span = $('.current-value', va.input.closest('.config-range')); + if (span) { + span.textContent = value + (va.units || ''); + } + } + function updateVarOnInput(event, debounced = false) { if (debounced) { event.target.dispatchEvent(new Event('change', {bubbles: true})); @@ -365,11 +372,8 @@ function configDialog(style) { } else if (va.type === 'checkbox') { va.input.checked = Number(value); } else if (va.type === 'range') { - const span = $('.current-value', va.input.closest('.config-range')); va.input.value = value; - if (span) { - span.textContent = value + (va.units || ''); - } + updateRangeCurrentValue(va, va.input.value); } else { va.input.value = value; } From c18671c91c4ad7c7385c375768d7f4956ec7886c Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Wed, 19 Sep 2018 08:35:52 -0500 Subject: [PATCH 42/45] Fix undefined value --- manage/config-dialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 9aa78911..efd720b0 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -314,7 +314,7 @@ function configDialog(style) { function updateVarOnChange() { if (this.type === 'range') { this.va.value = Number(this.value); - updateRangeCurrentValue(va, this.va.value); + updateRangeCurrentValue(this.va, this.va.value); } else if (this.type === 'number') { const value = Number(this.value); if (clampValue(value, this.va) === value) { From 93dee41c1110135f4e7eee852114e37bc7a16c87 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Thu, 20 Sep 2018 02:39:24 -0500 Subject: [PATCH 43/45] Don't re-render input while focused --- manage/config-dialog.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index efd720b0..65734a5f 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -363,6 +363,9 @@ function configDialog(style) { function renderValues(varsToRender = vars) { for (const va of varsToRender) { + if (va.input === document.activeElement) { + continue; + } const value = isDefault(va) ? va.default : va.value; if (va.type === 'color') { va.input.style.backgroundColor = value; From b0fbdfb6e9a0ca57f997bf13057185761ea9a974 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Thu, 20 Sep 2018 03:34:37 -0500 Subject: [PATCH 44/45] Update number input on blur --- manage/config-dialog.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 65734a5f..c314f2e0 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -264,6 +264,7 @@ function configDialog(style) { va, type: va.type, onfocus: va.type === 'number' ? selectAllOnFocus : null, + onblur: va.type === 'number' ? updateVarOnBlur : null, onchange: updateVarOnChange, oninput: updateVarOnInput }; @@ -311,15 +312,23 @@ function configDialog(style) { } } + function updateVarOnBlur(e = {}) { + const value = Number(this.value); + const clamped = clampValue(value, this.va); + if (clamped === value) { + this.va.value = value; + } + if (e.type === 'blur') { + this.value = clamped; + } + } + function updateVarOnChange() { if (this.type === 'range') { this.va.value = Number(this.value); updateRangeCurrentValue(this.va, this.va.value); } else if (this.type === 'number') { - const value = Number(this.value); - if (clampValue(value, this.va) === value) { - this.va.value = value; - } + updateVarOnBlur.call(this); } else { this.va.value = this.type !== 'checkbox' ? this.value : this.checked ? '1' : '0'; } From 5f60c519ce4edff6b8245c5000dbd3c814667671 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Thu, 20 Sep 2018 04:25:45 -0500 Subject: [PATCH 45/45] Alas, poor clamp! I knew it well --- manage/config-dialog.js | 35 +++++++---------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/manage/config-dialog.js b/manage/config-dialog.js index c314f2e0..4e0cbab0 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -266,7 +266,8 @@ function configDialog(style) { onfocus: va.type === 'number' ? selectAllOnFocus : null, onblur: va.type === 'number' ? updateVarOnBlur : null, onchange: updateVarOnChange, - oninput: updateVarOnInput + oninput: updateVarOnInput, + required: true }; if (typeof va.min === 'number') { options.min = va.min; @@ -312,15 +313,8 @@ function configDialog(style) { } } - function updateVarOnBlur(e = {}) { - const value = Number(this.value); - const clamped = clampValue(value, this.va); - if (clamped === value) { - this.va.value = value; - } - if (e.type === 'blur') { - this.value = clamped; - } + function updateVarOnBlur() { + this.value = isDefault(this.va) ? this.va.default : this.va.value; } function updateVarOnChange() { @@ -328,29 +322,14 @@ function configDialog(style) { this.va.value = Number(this.value); updateRangeCurrentValue(this.va, this.va.value); } else if (this.type === 'number') { - updateVarOnBlur.call(this); + if (this.reportValidity()) { + this.va.value = Number(this.value); + } } else { this.va.value = this.type !== 'checkbox' ? this.value : this.checked ? '1' : '0'; } } - // Clamp input[type=number] to a valid range - function clampValue(value, va) { - // Don't restrict to integer values if step is undefined. - if (typeof va.step === 'number') { - const step = va.step || 1; - const scale = 10 ** (step.toString().split('.')[1] || '').length; - value = Math.round((value * scale) / (step * scale)) * (step * scale) / scale; - } - if (typeof va.min === 'number' && value < va.min) { - return va.min; - } - if (typeof va.max === 'number' && value > va.max) { - return va.max; - } - return value; - } - function updateRangeCurrentValue(va, value) { const span = $('.current-value', va.input.closest('.config-range')); if (span) {