more keyboard control in colorpicker popup

hex mode similar to chrome-devtools:
* Ctrl-Up/Down for R channel
* Shift-Up/Down for G channel
* Alt-Up/Down for B channel
* Up/Down treats the entire color as a hex integer e.g. #00f -> #010

rgb/hsl modes similar to chrome-devtools:
* Ctrl-Up/Down steps by 100 (alpha: by 1)
* Shift-Up/Down steps by 10 (alpha: by .1)
* Up/Down steps by 1  (alpha: by .01)

* PgUp/PgDn to switch format
* Tab to switch format when at the first/last input field
This commit is contained in:
tophf 2017-11-22 01:15:52 +03:00
parent b99391887d
commit aa2fef7f72
2 changed files with 81 additions and 9 deletions

View File

@ -176,7 +176,7 @@
"description": "Label for the style editor's CSS theme." "description": "Label for the style editor's CSS theme."
}, },
"colorpickerSwitchFormatTooltip": { "colorpickerSwitchFormatTooltip": {
"message": "Switch formats: HEX -> RGB -> HSL", "message": "Switch formats: HEX -> RGB -> HSL.\nShift-click to reverse the direction.\nAlso via PgUp (PageUp), PgDn (PageDown), Tab keys.",
"description": "Tooltip for the switch button in the color picker popup in the style editor." "description": "Tooltip for the switch button in the color picker popup in the style editor."
}, },
"colorpickerTooltip": { "colorpickerTooltip": {

View File

@ -211,7 +211,6 @@ CodeMirror.defineExtension('colorpicker', function () {
reposition(); reposition();
setFromColor(opt.color); setFromColor(opt.color);
setFromHexLettercaseElement(); setFromHexLettercaseElement();
$inputs[currentFormat][0].focus();
} }
function hide() { function hide() {
@ -312,11 +311,13 @@ CodeMirror.defineExtension('colorpicker', function () {
renderInputs(); renderInputs();
} }
function setFromFormatElement() { function setFromFormatElement({shiftKey}) {
userActivity = performance.now(); userActivity = performance.now();
const nextFormat = {hex: 'rgb', rgb: 'hsl', hsl: 'hex'}[currentFormat];
HSV.a = isNaN(HSV.a) ? 1 : HSV.a; HSV.a = isNaN(HSV.a) ? 1 : HSV.a;
switchInputGroup(nextFormat); const formats = ['hex', 'rgb', 'hsl'];
const dir = shiftKey ? -1 : 1;
const total = formats.length;
switchInputGroup(formats[(formats.indexOf(currentFormat) + dir + total) % total]);
renderInputs(); renderInputs();
} }
@ -336,6 +337,78 @@ CodeMirror.defineExtension('colorpicker', function () {
} }
} }
function setFromKeyboard(event) {
const {which, ctrlKey: ctrl, altKey: alt, shiftKey: shift, metaKey: meta} = event;
switch (which) {
case 9: // Tab
case 33: // PgUp
case 34: // PgDn
if (!ctrl && !alt && !meta) {
const el = document.activeElement;
const inputs = $inputs[currentFormat];
if (which !== 9 && !shift ||
el === inputs[0] && shift ||
el === inputs[inputs.length - 1] && !shift) {
event.preventDefault();
setFromFormatElement({shift: which === 33 || shift});
}
}
return;
case 38: // Up
case 40: // Down
if (!event.metaKey &&
document.activeElement.localName === 'input' &&
document.activeElement.checkValidity()) {
setFromKeyboardIncrement(event);
}
return;
}
}
function setFromKeyboardIncrement(event) {
const el = document.activeElement;
const {which, ctrlKey: ctrl, altKey: alt, shiftKey: shift} = event;
const dir = which === 38 ? 1 : -1;
let value, newValue;
if (currentFormat === 'hex') {
value = el.value.trim();
const isShort = value.length <= 5;
const [r, g, b, a = ''] = el.value.match(isShort ? /[\da-f]/g : /[\da-f]{2}/g);
let ceiling, data;
if (!ctrl && !shift && !alt) {
ceiling = isShort ? 0xFFF : 0xFFFFFF;
data = [[true, r + g + b]];
} else {
ceiling = isShort ? 15 : 255;
data = [[ctrl, r], [shift, g], [alt, b]];
}
newValue = '#' + data.map(([affected, part]) => {
part = constrain(0, ceiling, parseInt(part, 16) + dir * (affected ? 1 : 0));
return (part + ceiling + 1).toString(16).slice(1);
}).join('') + a;
newValue = options.hexUppercase ? newValue.toUpperCase() : newValue.toLowerCase();
} else if (!alt) {
const delta =
shift && !ctrl ? 10 :
ctrl && !shift ? 100 :
1;
value = parseFloat(el.value);
const isHue = el === $inputs.hsl[0];
const isAlpha = el === $inputs[currentFormat][3];
const min = isHue ? -360 : 0;
const max = isHue ? 360 : isAlpha ? 1 : currentFormat === 'rgb' ? 255 : 100;
const scale = isAlpha ? .01 : 1;
newValue = constrain(min, max, value + delta * scale * dir);
newValue = isAlpha ? alphaToString(newValue) : newValue;
}
event.preventDefault();
userActivity = performance.now();
if (newValue !== undefined && newValue !== value) {
el.value = newValue;
setFromColor($inputs.color);
}
}
function validateInput(el) { function validateInput(el) {
const isAlpha = el === $inputs[currentFormat][3]; const isAlpha = el === $inputs[currentFormat][3];
let isValid = (isAlpha || el.value.trim()) && el.checkValidity(); let isValid = (isAlpha || el.value.trim()) && el.checkValidity();
@ -377,6 +450,7 @@ CodeMirror.defineExtension('colorpicker', function () {
} }
} }
$inputGroups[format].dataset.active = ''; $inputGroups[format].dataset.active = '';
$inputs[format][0].focus();
currentFormat = format; currentFormat = format;
} }
@ -572,6 +646,7 @@ CodeMirror.defineExtension('colorpicker', function () {
$root.addEventListener('mouseleave', snooze); $root.addEventListener('mouseleave', snooze);
$root.addEventListener('mouseenter', stopSnoozing); $root.addEventListener('mouseenter', stopSnoozing);
$root.addEventListener('input', setFromInputs); $root.addEventListener('input', setFromInputs);
$root.addEventListener('keydown', setFromKeyboard);
$formatChangeButton.addEventListener('click', setFromFormatElement); $formatChangeButton.addEventListener('click', setFromFormatElement);
$sat.addEventListener('mousedown', onSaturationMouseDown); $sat.addEventListener('mousedown', onSaturationMouseDown);
$sat.addEventListener('mouseup', onSaturationMouseUp); $sat.addEventListener('mouseup', onSaturationMouseUp);
@ -747,10 +822,7 @@ CodeMirror.defineExtension('colorpicker', function () {
} }
function alphaToString(a = HSV.a) { function alphaToString(a = HSV.a) {
return isNaN(a) ? '' : return isNaN(a) ? '' : (a + .5e-6).toFixed(7).slice(0, -1).replace(/^0(?=\.[1-9])|^1\.0+?$|\.?0+$/g, '');
a.toString().slice(0, 8)
.replace(/(\.[^0]*)0+$/, '$1')
.replace(/^1$/, '');
} }
//endregion //endregion