improve colorpicker dialog (#1079)

* switch to a user-resizable palette
* allow moving
* remove hideDelay
This commit is contained in:
tophf 2020-10-26 18:03:41 +03:00 committed by GitHub
parent bf40fa81e8
commit 89431615b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 280 additions and 216 deletions

View File

@ -182,6 +182,9 @@
"message": "Theme", "message": "Theme",
"description": "Label for the style editor's CSS theme." "description": "Label for the style editor's CSS theme."
}, },
"colorpickerPaletteHint": {
"message": "Right-click a swatch to cycle through its source lines"
},
"colorpickerSwitchFormatTooltip": { "colorpickerSwitchFormatTooltip": {
"message": "Switch formats: HEX -> RGB -> HSL.\nShift-click to reverse the direction.\nAlso via PgUp (PageUp), PgDn (PageDown) keys.", "message": "Switch formats: HEX -> RGB -> HSL.\nShift-click to reverse the direction.\nAlso via PgUp (PageUp), PgDn (PageDown) 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."
@ -938,6 +941,10 @@
"message": "No styles installed for this site.", "message": "No styles installed for this site.",
"description": "Text displayed when no styles are installed for the current site" "description": "Text displayed when no styles are installed for the current site"
}, },
"numberedLine": {
"message": "Line:",
"description": "Will be followed by one or more line numbers in the editor."
},
"openManage": { "openManage": {
"message": "Manage", "message": "Manage",
"description": "Link to open the manage page." "description": "Link to open the manage page."

View File

@ -23,13 +23,20 @@
tooltip: t('colorpickerTooltip'), tooltip: t('colorpickerTooltip'),
popup: { popup: {
tooltipForSwitcher: t('colorpickerSwitchFormatTooltip'), tooltipForSwitcher: t('colorpickerSwitchFormatTooltip'),
paletteLine: t('numberedLine'),
paletteHint: t('colorpickerPaletteHint'),
hexUppercase: prefs.get('editor.colorpicker.hexUppercase'), hexUppercase: prefs.get('editor.colorpicker.hexUppercase'),
hideDelay: 30e3,
embedderCallback: state => { embedderCallback: state => {
['hexUppercase', 'color'] ['hexUppercase', 'color']
.filter(name => state[name] !== prefs.get('editor.colorpicker.' + name)) .filter(name => state[name] !== prefs.get('editor.colorpicker.' + name))
.forEach(name => prefs.set('editor.colorpicker.' + name, state[name])); .forEach(name => prefs.set('editor.colorpicker.' + name, state[name]));
}, },
get maxHeight() {
return prefs.get('editor.colorpicker.maxHeight');
},
set maxHeight(h) {
prefs.set('editor.colorpicker.maxHeight', h);
},
}, },
}; };
} else { } else {

View File

@ -86,6 +86,7 @@ self.prefs = self.INJECTED === 1 ? self.prefs : (() => {
'editor.colorpicker.hotkey': '', 'editor.colorpicker.hotkey': '',
// last color // last color
'editor.colorpicker.color': '', 'editor.colorpicker.color': '',
'editor.colorpicker.maxHeight': 300,
// Firefox-only chrome.commands.update // Firefox-only chrome.commands.update
'hotkey._execute_browser_action': '', 'hotkey._execute_browser_action': '',

View File

@ -399,7 +399,6 @@ function configDialog(style) {
color: this.va.value || this.va.default, color: this.va.value || this.va.default,
top: this.getBoundingClientRect().bottom - 5, top: this.getBoundingClientRect().bottom - 5,
left: box.getBoundingClientRect().left - 360, left: box.getBoundingClientRect().left - 360,
hideDelay: 1e6,
guessBrightness: box, guessBrightness: box,
callback: onColorChanged, callback: onColorChanged,
}); });

View File

@ -77,9 +77,13 @@
} }
.colorpicker-popup { .colorpicker-popup {
--switcher-width: 30px; --switcher-width: 29px;
position: relative; --sat-height: 120px;
position: fixed;
display: flex;
flex-direction: column;
width: 325px; width: 325px;
max-height: var(--fit-height);
z-index: 1000; z-index: 1000;
transition: opacity .5s; transition: opacity .5s;
color: var(--label-color); color: var(--label-color);
@ -90,17 +94,42 @@
user-select: none; user-select: none;
} }
.colorpicker-popup[data-fading="1"] { .colorpicker-popup[data-moving] {
opacity: .75; opacity: .5;
cursor: move;
} }
.colorpicker-popup[data-fading="2"] { .colorpicker-popup[data-resizing] {
opacity: 0; max-height: 90vh !important;
}
.colorpicker-popup[data-resizable] {
resize: vertical;
overflow: hidden;
}
.colorpicker-popup-mover {
position: absolute;
box-sizing: border-box;
width: calc(var(--switcher-width) - 10px);
height: 50px;
padding: 5px;
margin-top: 5px;
top: var(--sat-height);
right: 3px;
background: repeating-linear-gradient(to right,
currentColor, currentColor 1px, transparent 1px, transparent 3px);
cursor: move;
-webkit-background-clip: content-box;
background-clip: content-box;
opacity: .5;
z-index: 2;
} }
.colorpicker-saturation-container { .colorpicker-saturation-container {
position: relative; position: relative;
height: 120px; height: var(--sat-height);
flex: 0 0 var(--sat-height);
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;
} }
@ -145,7 +174,7 @@
.colorpicker-sliders { .colorpicker-sliders {
position: relative; position: relative;
padding: 10px 0 6px 0; padding: 10px calc(var(--switcher-width) - 12px) 6px 0;
border-top: 1px solid transparent; border-top: 1px solid transparent;
} }
@ -156,10 +185,10 @@
.colorpicker-swatch, .colorpicker-swatch,
.colorpicker-empty { .colorpicker-empty {
position: absolute; position: absolute;
left: 11px; left: 10px;
top: 17px; top: 12px;
width: 30px; width: 38px;
height: 30px; height: 38px;
border-radius: 50%; border-radius: 50%;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid var(--input-border-color); border: 1px solid var(--input-border-color);
@ -349,13 +378,24 @@
} }
.colorpicker-palette:not(:empty) { .colorpicker-palette:not(:empty) {
padding: 0 8px 8px; --swatch-size: 16px;
min-height: 14px; /* same as padding-left in .colorview-swatch */ margin: 0 var(--margin) var(--margin);
max-height: 10vw; min-height: calc(var(--swatch-size) - 4px);
overflow: auto; overflow-y: auto;
box-sizing: content-box; box-sizing: content-box;
} }
.colorpicker-palette .colorview-swatch { .colorpicker-palette .colorview-swatch {
padding-bottom: 14px; /* same as padding-left in .colorview-swatch */ padding: calc(var(--swatch-size) / 2 + 1px);
}
.colorpicker-palette .colorview-swatch::before {
width: var(--swatch-size);
height: var(--swatch-size);
}
.colorpicker-palette-hint {
vertical-align: super;
padding: 0 .5em;
font-weight: bold;
} }

View File

@ -14,6 +14,9 @@
{hex: '#ff00ff', start: .83}, {hex: '#ff00ff', start: .83},
{hex: '#ff0000', start: 1} {hex: '#ff0000', start: 1}
]; ];
const MIN_HEIGHT = 220;
const MARGIN = 8;
let maxHeight = '0px';
let HSV = {}; let HSV = {};
let currentFormat; let currentFormat;
@ -23,14 +26,18 @@
let shown = false; let shown = false;
let options = {}; let options = {};
let $root; let /** @type {HTMLElement} */ $root;
let $sat, $satPointer; let /** @type {HTMLElement} */ $sat;
let $hue, $hueKnob; let /** @type {HTMLElement} */ $satPointer;
let $opacity, $opacityBar, $opacityKnob; let /** @type {HTMLElement} */ $hue;
let $swatch; let /** @type {HTMLElement} */ $hueKnob;
let $formatChangeButton; let /** @type {HTMLElement} */ $opacity;
let $hexCode; let /** @type {HTMLElement} */ $opacityBar;
let $palette; let /** @type {HTMLElement} */ $opacityKnob;
let /** @type {HTMLElement} */ $swatch;
let /** @type {HTMLElement} */ $formatChangeButton;
let /** @type {HTMLElement} */ $hexCode;
let /** @type {HTMLElement} */ $palette;
const $inputGroups = {}; const $inputGroups = {};
const $inputs = {}; const $inputs = {};
const $rgb = {}; const $rgb = {};
@ -45,15 +52,13 @@
saturation: false, saturation: false,
hue: false, hue: false,
opacity: false, opacity: false,
popup: false,
}; };
let prevFocusedElement; let prevFocusedElement;
let lastOutputColor; let lastOutputColor;
let userActivity; let userActivity;
let timerCloseColorPicker;
let timerFadeColorPicker;
const PUBLIC_API = { const PUBLIC_API = {
$root, $root,
show, show,
@ -67,106 +72,107 @@
//region DOM //region DOM
function init() { function init() {
// simplified createElement /** @returns {HTMLElement} */
function $(a, b) { function $(cls, props = {}, children = []) {
const cls = typeof a === 'string' || Array.isArray(a) ? a : ''; if (Array.isArray(props) || typeof props === 'string' || props instanceof Node) {
const props = b || a; children = props;
const {tag = 'div', children} = props || {}; props = {};
const el = document.createElement(tag);
el.className = (Array.isArray(cls) ? cls : [cls])
.map(c => (c ? CSS_PREFIX + c : ''))
.join(' ');
if (!props) {
return el;
} }
for (const child of Array.isArray(children) ? children : [children]) { const el = document.createElement(props.tag || 'div');
if (child) { el.className = toArray(cls).map(c => c ? CSS_PREFIX + c : '').join(' ');
el.appendChild(child instanceof Node ? child : document.createTextNode(child)); el.append(...toArray(children));
} if (props) delete props.tag;
}
delete props.tag;
delete props.children;
return Object.assign(el, props); return Object.assign(el, props);
} }
const alphaPattern = /^\s*(0+\.?|0*\.\d+|0*1\.?|0*1\.0*)?\s*$/.source; const alphaPattern = /^\s*(0+\.?|0*\.\d+|0*1\.?|0*1\.0*)?\s*$/.source;
$root = $('popup', {children: [ $root = $('popup', {
$sat = $('saturation-container', {children: [ oninput: setFromInputs,
$('saturation', {children: [ onkeydown: setFromKeyboard,
$('value', {children: [ }, [
$sat = $('saturation-container', {
onmousedown: onSaturationMouseDown,
onmouseup: onSaturationMouseUp,
}, [
$('saturation', [
$('value', [
$satPointer = $('drag-pointer'), $satPointer = $('drag-pointer'),
]}), ]),
]}), ]),
]}), ]),
$('sliders', {children: [ $('popup-mover', {onmousedown: onPopupMoveStart}),
$('hue', {children: [ $('sliders', [
$hue = $('hue-container', {children: [ $('hue', {onmousedown: onHueMouseDown}, [
$hueKnob = $('hue-knob'), $hue = $('hue-container', [
]}), $hueKnob = $('hue-knob', {onmousedown: onHueKnobMouseDown}),
]}), ]),
$('opacity', {children: [ ]),
$opacity = $('opacity-container', {children: [ $('opacity', [
$opacity = $('opacity-container', {onmousedown: onOpacityMouseDown}, [
$opacityBar = $('opacity-bar'), $opacityBar = $('opacity-bar'),
$opacityKnob = $('opacity-knob'), $opacityKnob = $('opacity-knob', {onmousedown: onOpacityKnobMouseDown}),
]}), ]),
]}), ]),
$('empty'), $('empty'),
$swatch = $('swatch'), $swatch = $('swatch'),
]}), ]),
$(['input-container', 'hex'], {children: [ $(['input-container', 'hex'], [
$inputGroups.hex = $(['input-group', 'hex'], {children: [ $inputGroups.hex = $(['input-group', 'hex'], [
$(['input-field', 'hex'], {children: [ $(['input-field', 'hex'], [
$hexCode = $('input', {tag: 'input', type: 'text', spellcheck: false, $hexCode = $('input', {tag: 'input', type: 'text', spellcheck: false,
pattern: /^\s*#([a-fA-F\d]{3}([a-fA-F\d]([a-fA-F\d]{2}([a-fA-F\d]{2})?)?)?)\s*$/.source pattern: /^\s*#([a-fA-F\d]{3}([a-fA-F\d]([a-fA-F\d]{2}([a-fA-F\d]{2})?)?)?)\s*$/.source
}), }),
$('title', {children: [ $('title', [
$hexLettercase.true = $('title-action', {textContent: 'HEX'}), $hexLettercase.true = $('title-action', {onclick: onHexLettercaseClicked}, 'HEX'),
'\xA0/\xA0', '\xA0/\xA0',
$hexLettercase.false = $('title-action', {textContent: 'hex'}), $hexLettercase.false = $('title-action', {onclick: onHexLettercaseClicked}, 'hex'),
]}), ]),
]}), ]),
]}), ]),
$inputGroups.rgb = $(['input-group', 'rgb'], {children: [ $inputGroups.rgb = $(['input-group', 'rgb'], [
$(['input-field', 'rgb-r'], {children: [ $(['input-field', 'rgb-r'], [
$rgb.r = $('input', {tag: 'input', type: 'number', min: 0, max: 255, step: 1}), $rgb.r = $('input', {tag: 'input', type: 'number', min: 0, max: 255, step: 1}),
$('title', {textContent: 'R'}), $('title', 'R'),
]}), ]),
$(['input-field', 'rgb-g'], {children: [ $(['input-field', 'rgb-g'], [
$rgb.g = $('input', {tag: 'input', type: 'number', min: 0, max: 255, step: 1}), $rgb.g = $('input', {tag: 'input', type: 'number', min: 0, max: 255, step: 1}),
$('title', {textContent: 'G'}), $('title', 'G'),
]}), ]),
$(['input-field', 'rgb-b'], {children: [ $(['input-field', 'rgb-b'], [
$rgb.b = $('input', {tag: 'input', type: 'number', min: 0, max: 255, step: 1}), $rgb.b = $('input', {tag: 'input', type: 'number', min: 0, max: 255, step: 1}),
$('title', {textContent: 'B'}), $('title', 'B'),
]}), ]),
$(['input-field', 'rgb-a'], {children: [ $(['input-field', 'rgb-a'], [
$rgb.a = $('input', {tag: 'input', type: 'text', pattern: alphaPattern, spellcheck: false}), $rgb.a = $('input', {tag: 'input', type: 'text', pattern: alphaPattern, spellcheck: false}),
$('title', {textContent: 'A'}), $('title', 'A'),
]}), ]),
]}), ]),
$inputGroups.hsl = $(['input-group', 'hsl'], {children: [ $inputGroups.hsl = $(['input-group', 'hsl'], [
$(['input-field', 'hsl-h'], {children: [ $(['input-field', 'hsl-h'], [
$hsl.h = $('input', {tag: 'input', type: 'number', step: 1}), $hsl.h = $('input', {tag: 'input', type: 'number', step: 1}),
$('title', {textContent: 'H'}), $('title', 'H'),
]}), ]),
$(['input-field', 'hsl-s'], {children: [ $(['input-field', 'hsl-s'], [
$hsl.s = $('input', {tag: 'input', type: 'number', min: 0, max: 100, step: 1}), $hsl.s = $('input', {tag: 'input', type: 'number', min: 0, max: 100, step: 1}),
$('title', {textContent: 'S'}), $('title', 'S'),
]}), ]),
$(['input-field', 'hsl-l'], {children: [ $(['input-field', 'hsl-l'], [
$hsl.l = $('input', {tag: 'input', type: 'number', min: 0, max: 100, step: 1}), $hsl.l = $('input', {tag: 'input', type: 'number', min: 0, max: 100, step: 1}),
$('title', {textContent: 'L'}), $('title', 'L'),
]}), ]),
$(['input-field', 'hsl-a'], {children: [ $(['input-field', 'hsl-a'], [
$hsl.a = $('input', {tag: 'input', type: 'text', pattern: alphaPattern, spellcheck: false}), $hsl.a = $('input', {tag: 'input', type: 'text', pattern: alphaPattern, spellcheck: false}),
$('title', {textContent: 'A'}), $('title', 'A'),
]}), ]),
]}), ]),
$('format-change', {children: [ $('format-change', [
$formatChangeButton = $('format-change-button', {textContent: '↔'}), $formatChangeButton = $('format-change-button', {onclick: setFromFormatElement}, '↔'),
]}), ]),
]}), ]),
$palette = $('palette'), $palette = $('palette', {
]}); onclick: onPaletteClicked,
oncontextmenu: onPaletteClicked,
}),
]);
$inputs.hex = [$hexCode]; $inputs.hex = [$hexCode];
$inputs.rgb = [$rgb.r, $rgb.g, $rgb.b, $rgb.a]; $inputs.rgb = [$rgb.r, $rgb.g, $rgb.b, $rgb.a];
@ -184,7 +190,7 @@
}); });
HUE_COLORS.forEach(color => Object.assign(color, colorConverter.parse(color.hex))); HUE_COLORS.forEach(color => Object.assign(color, colorConverter.parse(color.hex)));
$root.style.setProperty('--margin', MARGIN + 'px');
initialized = true; initialized = true;
} }
@ -202,16 +208,12 @@
userActivity = 0; userActivity = 0;
lastOutputColor = opt.color || ''; lastOutputColor = opt.color || '';
$formatChangeButton.title = opt.tooltipForSwitcher || ''; $formatChangeButton.title = opt.tooltipForSwitcher || '';
opt.hideDelay = Math.max(0, opt.hideDelay) || 2000; maxHeight = `${opt.maxHeight || 300}px`;
$root.className = $root.className.replace(new RegExp(CSS_PREFIX + 'theme-\\S+\\s*'), '') + $root.className = $root.className.replace(new RegExp(CSS_PREFIX + 'theme-\\S+\\s*'), '') +
' ' + CSS_PREFIX + 'theme-' + ' ' + CSS_PREFIX + 'theme-' +
(opt.theme === 'dark' || opt.theme === 'light' ? opt.theme : (opt.theme === 'dark' || opt.theme === 'light' ? opt.theme :
guessTheme()); guessTheme());
$root.style = `
display: block !important;
position: fixed !important;
`;
document.body.appendChild($root); document.body.appendChild($root);
shown = true; shown = true;
@ -220,13 +222,22 @@
setFromColor(opt.color); setFromColor(opt.color);
setFromHexLettercaseElement(); setFromHexLettercaseElement();
if (!isNaN(options.left) && !isNaN(options.top)) {
reposition();
}
if (Array.isArray(options.palette)) { if (Array.isArray(options.palette)) {
// Might need to clear a lot of elements so this is known to be faster than textContent = '' // Might need to clear a lot of elements so this is known to be faster than textContent = ''
while ($palette.firstChild) $palette.firstChild.remove(); while ($palette.firstChild) $palette.firstChild.remove();
$palette.append(...(options.palette)); $palette.append(...options.palette);
if (options.palette.length) {
$root.dataset.resizable = '';
$root.addEventListener('mousedown', onPopupResizeStart);
fitPaletteHeight();
} else {
delete $root.dataset.resizable;
$root.removeEventListener('mousedown', onPopupResizeStart);
}
}
if (!isNaN(options.left) && !isNaN(options.top)) {
reposition();
} }
} }
@ -299,8 +310,9 @@
event.preventDefault(); event.preventDefault();
const w = $sat.offsetWidth; const w = $sat.offsetWidth;
const h = $sat.offsetHeight; const h = $sat.offsetHeight;
const deltaX = event.clientX - parseFloat($root.style.left); const bb = $root.getBoundingClientRect();
const deltaY = event.clientY - parseFloat($root.style.top); const deltaX = event.clientX - bb.left;
const deltaY = event.clientY - bb.top;
const x = dragging.saturationPointerPos.x = constrain(0, w, deltaX); const x = dragging.saturationPointerPos.x = constrain(0, w, deltaX);
const y = dragging.saturationPointerPos.y = constrain(0, h, deltaY); const y = dragging.saturationPointerPos.y = constrain(0, h, deltaY);
@ -546,6 +558,52 @@
//endregion //endregion
//region Event listeners //region Event listeners
/** @param {MouseEvent} event */
function onPopupMoveStart(event) {
if (!event.button && !hasModifiers(event)) {
captureMouse(event, 'popup');
$root.dataset.moving = '';
const [x, y] = ($root.style.transform.match(/[-.\d]+/g) || []).map(parseFloat);
dragging.popupX = event.clientX - (x || 0);
dragging.popupY = event.clientY - (y || 0);
document.addEventListener('mouseup', onPopupMoveEnd);
}
}
/** @param {MouseEvent} event */
function onPopupMove({clientX: x, clientY: y}) {
$root.style.transform = `translate(${x - dragging.popupX}px, ${y - dragging.popupY}px)`;
}
/** @param {MouseEvent} event */
function onPopupMoveEnd(event) {
if (!event.button) {
document.addEventListener('mouseup', onPopupMoveEnd);
delete $root.dataset.moving;
}
}
/** @param {MouseEvent} event */
function onPopupResizeStart(event) {
if (event.target === $root && !event.button && !hasModifiers(event)) {
document.addEventListener('mouseup', onPopupResizeEnd);
$root.dataset.resizing = '';
}
}
/** @param {MouseEvent} event */
function onPopupResizeEnd(event) {
if (!event.button) {
delete $root.dataset.resizing;
document.removeEventListener('mouseup', onPopupResizeEnd);
if (maxHeight !== $root.style.height) {
maxHeight = $root.style.height;
PUBLIC_API.options.maxHeight = parseFloat(maxHeight);
fitPaletteHeight();
}
}
}
function onHexLettercaseClicked() { function onHexLettercaseClicked() {
options.hexUppercase = !options.hexUppercase; options.hexUppercase = !options.hexUppercase;
setFromHexLettercaseElement(); setFromHexLettercaseElement();
@ -583,19 +641,19 @@
/** @param {MouseEvent} e */ /** @param {MouseEvent} e */
function onPaletteClicked(e) { function onPaletteClicked(e) {
if (e.target !== e.currentTarget) { if (e.target !== e.currentTarget && e.target.__color) {
e.preventDefault();
if (!e.button && setColor(e.target.__color)) { if (!e.button && setColor(e.target.__color)) {
userActivity = performance.now(); userActivity = performance.now();
colorpickerCallback(); colorpickerCallback();
} else if (e.button === 2 && options.paletteCallback) { } else if (e.button && options.paletteCallback) {
e.preventDefault(); // suppress the default context menu
options.paletteCallback(e.target); options.paletteCallback(e.target);
} }
} }
} }
function onMouseUp(event) { function onMouseUp(event) {
releaseMouse(event, ['saturation', 'hue', 'opacity']); releaseMouse(event, ['saturation', 'hue', 'opacity', 'popup']);
if (onMouseDown.outsideClick) { if (onMouseDown.outsideClick) {
if (!prevFocusedElement) hide(); if (!prevFocusedElement) hide();
} }
@ -610,33 +668,15 @@
} }
function onMouseMove(event) { function onMouseMove(event) {
if (event.button !== 0) { if (event.button) return;
return; if (dragging.saturation) setFromSaturationElement(event);
} if (dragging.hue) setFromHueElement(event);
if (dragging.saturation) { if (dragging.opacity) setFromOpacityElement(event);
setFromSaturationElement(event); if (dragging.popup) onPopupMove(event);
} else if (dragging.hue) {
setFromHueElement(event);
} else if (dragging.opacity) {
setFromOpacityElement(event);
}
}
function stopSnoozing() {
clearTimeout(timerCloseColorPicker);
clearTimeout(timerFadeColorPicker);
if ($root.dataset.fading) {
delete $root.dataset.fading;
}
}
function snooze() {
clearTimeout(timerFadeColorPicker);
timerFadeColorPicker = setTimeout(fade, options.hideDelay / 2);
} }
function onKeyDown(e) { function onKeyDown(e) {
if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) { if (!hasModifiers(e)) {
switch (e.key) { switch (e.key) {
case 'Enter': case 'Enter':
case 'Escape': case 'Escape':
@ -688,13 +728,17 @@
if (!mode) { if (!mode) {
return; return;
} }
for (const m of (Array.isArray(mode) ? mode : [mode])) { for (const m of toArray(mode)) {
dragging[m] = true; dragging[m] = true;
} }
userActivity = performance.now(); userActivity = performance.now();
return true; return true;
} }
function hasModifiers(e) {
return e.shiftKey || e.ctrlKey || e.altKey || e.metaKey;
}
function releaseMouse(event, mode) { function releaseMouse(event, mode) {
if (event && event.button !== 0) { if (event && event.button !== 0) {
return; return;
@ -704,7 +748,7 @@
if (!mode) { if (!mode) {
return; return;
} }
for (const m of (Array.isArray(mode) ? mode : [mode])) { for (const m of toArray(mode)) {
dragging[m] = false; dragging[m] = false;
} }
userActivity = performance.now(); userActivity = performance.now();
@ -719,48 +763,13 @@
window.addEventListener('keydown', onKeyDown, true); window.addEventListener('keydown', onKeyDown, true);
window.addEventListener('mousedown', onMouseDown, true); window.addEventListener('mousedown', onMouseDown, true);
window.addEventListener('close-colorpicker-popup', onCloseRequest, true); window.addEventListener('close-colorpicker-popup', onCloseRequest, true);
$root.addEventListener('input', setFromInputs);
$root.addEventListener('keydown', setFromKeyboard);
$formatChangeButton.addEventListener('click', setFromFormatElement);
$sat.addEventListener('mousedown', onSaturationMouseDown);
$sat.addEventListener('mouseup', onSaturationMouseUp);
$hueKnob.addEventListener('mousedown', onHueKnobMouseDown);
$opacityKnob.addEventListener('mousedown', onOpacityKnobMouseDown);
$hue.addEventListener('mousedown', onHueMouseDown);
$opacity.addEventListener('mousedown', onOpacityMouseDown);
$hexLettercase.true.addEventListener('click', onHexLettercaseClicked);
$hexLettercase.false.addEventListener('click', onHexLettercaseClicked);
$palette.addEventListener('click', onPaletteClicked);
$palette.addEventListener('contextmenu', onPaletteClicked);
stopSnoozing();
if (!options.isShortCut) {
$root.addEventListener('mouseleave', snooze);
$root.addEventListener('mouseenter', stopSnoozing);
timerFadeColorPicker = setTimeout(fade, options.hideDelay / 2);
}
} }
function unregisterEvents() { function unregisterEvents() {
window.removeEventListener('keydown', onKeyDown, true); window.removeEventListener('keydown', onKeyDown, true);
window.removeEventListener('mousedown', onMouseDown, true); window.removeEventListener('mousedown', onMouseDown, true);
window.removeEventListener('close-colorpicker-popup', onCloseRequest, true); window.removeEventListener('close-colorpicker-popup', onCloseRequest, true);
$root.removeEventListener('mouseleave', snooze);
$root.removeEventListener('mouseenter', stopSnoozing);
$root.removeEventListener('input', setFromInputs);
$formatChangeButton.removeEventListener('click', setFromFormatElement);
$sat.removeEventListener('mousedown', onSaturationMouseDown);
$sat.removeEventListener('mouseup', onSaturationMouseUp);
$hueKnob.removeEventListener('mousedown', onHueKnobMouseDown);
$opacityKnob.removeEventListener('mousedown', onOpacityKnobMouseDown);
$hue.removeEventListener('mousedown', onHueMouseDown);
$opacity.removeEventListener('mousedown', onOpacityMouseDown);
$hexLettercase.true.removeEventListener('click', onHexLettercaseClicked);
$hexLettercase.false.removeEventListener('click', onHexLettercaseClicked);
$palette.removeEventListener('click', onPaletteClicked);
$palette.removeEventListener('contextmenu', onPaletteClicked);
releaseMouse(); releaseMouse();
stopSnoozing();
} }
//endregion //endregion
@ -816,25 +825,13 @@
const maxRightUnobscured = options.left <= maxRight ? maxRight : options.left - width; const maxRightUnobscured = options.left <= maxRight ? maxRight : options.left - width;
const left = constrain(0, Math.max(0, maxRightUnobscured), options.left); const left = constrain(0, Math.max(0, maxRightUnobscured), options.left);
const top = constrain(0, Math.max(0, maxTopUnobscured), options.top); const top = constrain(0, Math.max(0, maxTopUnobscured), options.top);
$root.style.setProperty('left', left + 'px', 'important'); $root.style.left = left + 'px';
$root.style.setProperty('top', top + 'px', 'important'); $root.style.top = top + 'px';
} }
function fade({fadingStage = 1} = {}) { function fitPaletteHeight() {
const timeInactive = performance.now() - userActivity; const fit = MIN_HEIGHT + $palette.scrollHeight + MARGIN;
const delay = options.hideDelay / 2; $root.style.setProperty('--fit-height', Math.min(fit, parseFloat(maxHeight)) + 'px');
if (userActivity && timeInactive < delay) {
timerFadeColorPicker = setTimeout(fade, delay - timeInactive, 2);
clearTimeout(timerCloseColorPicker);
delete $root.dataset.fading;
return;
}
$root.dataset.fading = fadingStage;
if (fadingStage === 1) {
timerFadeColorPicker = setTimeout(fade, Math.max(0, delay - 500), {fadingStage: 2});
} else {
timerCloseColorPicker = setTimeout(hide, 500);
}
} }
function maybeFocus(el) { function maybeFocus(el) {
@ -887,6 +884,10 @@
} }
} }
function toArray(val) {
return !val ? [] : Array.isArray(val) ? val : [val];
}
//endregion //endregion
}; };

View File

@ -533,7 +533,7 @@
prevColor: data.color || '', prevColor: data.color || '',
callback: popupOnChange, callback: popupOnChange,
palette: makePalette(state), palette: makePalette(state),
paletteCallback: el => paletteCallback(state, el), paletteCallback,
})); }));
} }
@ -562,8 +562,9 @@
if (!markedSpans) return; if (!markedSpans) return;
for (const {from, marker: m} of markedSpans) { for (const {from, marker: m} of markedSpans) {
if (from == null || m.className !== COLORVIEW_CLASS) continue; if (from == null || m.className !== COLORVIEW_CLASS) continue;
nums = palette.get(m.color); const color = m.color.toLowerCase();
if (!nums) palette.set(m.color, (nums = [])); nums = palette.get(color);
if (!nums) palette.set(color, (nums = []));
nums.push(i); nums.push(i);
} }
}); });
@ -571,29 +572,37 @@
if (palette.size > 1 || nums && nums.length > 1) { if (palette.size > 1 || nums && nums.length > 1) {
const old = new Map((options.popup.palette || []).map(el => [el.__color, el])); const old = new Map((options.popup.palette || []).map(el => [el.__color, el]));
for (const [color, data] of palette) { for (const [color, data] of palette) {
res.push(old.get(color) || makePaletteSwatch(color, data)); res.push(old.get(color) || makePaletteSwatch(color, data, options.popup.paletteLine));
} }
res.push(Object.assign(document.createElement('span'), {
className: 'colorpicker-palette-hint',
title: options.popup.paletteHint,
textContent: '?',
}));
} }
return res; return res;
} }
function makePaletteSwatch(color, nums) { function makePaletteSwatch(color, nums, label) {
const s = nums.join(', '); const s = nums.join(', ');
const el = document.createElement('div'); const el = document.createElement('div');
el.className = COLORVIEW_SWATCH_CLASS; el.className = COLORVIEW_SWATCH_CLASS;
el.style.cssText = COLORVIEW_SWATCH_CSS + color; el.style.cssText = COLORVIEW_SWATCH_CSS + color;
el.title = color + (!s ? '' : `\nLine: ${s.length > 50 ? s.replace(/([^,]+,\s){10}/g, '$&\n') : s}`); // break down long lists: 10 per line
el.title = `${color}\n${label} ${s.length > 50 ? s.replace(/([^,]+,\s){10}/g, '$&\n') : s}`;
el.__color = color; el.__color = color;
return el; return el;
} }
function paletteCallback({cm}, el) { function paletteCallback(el) {
const {cm} = this;
const lines = el.title.split('\n')[1].match(/\d+/g).map(Number); const lines = el.title.split('\n')[1].match(/\d+/g).map(Number);
const curLine = cm.getCursor().line + 1; const i = lines.indexOf(cm.getCursor().line + 1) + 1;
const i = lines.indexOf(curLine) + 1; const line = (lines[i] || lines[0]) - 1;
const pos = {line: (lines[i] || curLine) - 1, ch: 0}; const vpm = cm.options.viewportMargin;
cm.scrollIntoView(pos, cm.defaultTextHeight()); const inView = line >= cm.display.viewFrom - vpm && line <= cm.display.viewTo - vpm;
cm.setCursor(pos); cm.scrollIntoView(line, inView ? cm.defaultTextHeight() : cm.display.wrapper.clientHeight / 2);
cm.setCursor(line);
} }
//endregion //endregion