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",
"description": "Label for the style editor's CSS theme."
},
"colorpickerPaletteHint": {
"message": "Right-click a swatch to cycle through its source lines"
},
"colorpickerSwitchFormatTooltip": {
"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."
@ -938,6 +941,10 @@
"message": "No styles installed for this 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": {
"message": "Manage",
"description": "Link to open the manage page."

View File

@ -23,13 +23,20 @@
tooltip: t('colorpickerTooltip'),
popup: {
tooltipForSwitcher: t('colorpickerSwitchFormatTooltip'),
paletteLine: t('numberedLine'),
paletteHint: t('colorpickerPaletteHint'),
hexUppercase: prefs.get('editor.colorpicker.hexUppercase'),
hideDelay: 30e3,
embedderCallback: state => {
['hexUppercase', 'color']
.filter(name => state[name] !== prefs.get('editor.colorpicker.' + 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 {

View File

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

View File

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

View File

@ -77,9 +77,13 @@
}
.colorpicker-popup {
--switcher-width: 30px;
position: relative;
--switcher-width: 29px;
--sat-height: 120px;
position: fixed;
display: flex;
flex-direction: column;
width: 325px;
max-height: var(--fit-height);
z-index: 1000;
transition: opacity .5s;
color: var(--label-color);
@ -90,17 +94,42 @@
user-select: none;
}
.colorpicker-popup[data-fading="1"] {
opacity: .75;
.colorpicker-popup[data-moving] {
opacity: .5;
cursor: move;
}
.colorpicker-popup[data-fading="2"] {
opacity: 0;
.colorpicker-popup[data-resizing] {
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 {
position: relative;
height: 120px;
height: var(--sat-height);
flex: 0 0 var(--sat-height);
overflow: hidden;
cursor: pointer;
}
@ -145,7 +174,7 @@
.colorpicker-sliders {
position: relative;
padding: 10px 0 6px 0;
padding: 10px calc(var(--switcher-width) - 12px) 6px 0;
border-top: 1px solid transparent;
}
@ -156,10 +185,10 @@
.colorpicker-swatch,
.colorpicker-empty {
position: absolute;
left: 11px;
top: 17px;
width: 30px;
height: 30px;
left: 10px;
top: 12px;
width: 38px;
height: 38px;
border-radius: 50%;
box-sizing: border-box;
border: 1px solid var(--input-border-color);
@ -349,13 +378,24 @@
}
.colorpicker-palette:not(:empty) {
padding: 0 8px 8px;
min-height: 14px; /* same as padding-left in .colorview-swatch */
max-height: 10vw;
overflow: auto;
--swatch-size: 16px;
margin: 0 var(--margin) var(--margin);
min-height: calc(var(--swatch-size) - 4px);
overflow-y: auto;
box-sizing: content-box;
}
.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: '#ff0000', start: 1}
];
const MIN_HEIGHT = 220;
const MARGIN = 8;
let maxHeight = '0px';
let HSV = {};
let currentFormat;
@ -23,14 +26,18 @@
let shown = false;
let options = {};
let $root;
let $sat, $satPointer;
let $hue, $hueKnob;
let $opacity, $opacityBar, $opacityKnob;
let $swatch;
let $formatChangeButton;
let $hexCode;
let $palette;
let /** @type {HTMLElement} */ $root;
let /** @type {HTMLElement} */ $sat;
let /** @type {HTMLElement} */ $satPointer;
let /** @type {HTMLElement} */ $hue;
let /** @type {HTMLElement} */ $hueKnob;
let /** @type {HTMLElement} */ $opacity;
let /** @type {HTMLElement} */ $opacityBar;
let /** @type {HTMLElement} */ $opacityKnob;
let /** @type {HTMLElement} */ $swatch;
let /** @type {HTMLElement} */ $formatChangeButton;
let /** @type {HTMLElement} */ $hexCode;
let /** @type {HTMLElement} */ $palette;
const $inputGroups = {};
const $inputs = {};
const $rgb = {};
@ -45,15 +52,13 @@
saturation: false,
hue: false,
opacity: false,
popup: false,
};
let prevFocusedElement;
let lastOutputColor;
let userActivity;
let timerCloseColorPicker;
let timerFadeColorPicker;
const PUBLIC_API = {
$root,
show,
@ -67,106 +72,107 @@
//region DOM
function init() {
// simplified createElement
function $(a, b) {
const cls = typeof a === 'string' || Array.isArray(a) ? a : '';
const props = b || a;
const {tag = 'div', children} = props || {};
const el = document.createElement(tag);
el.className = (Array.isArray(cls) ? cls : [cls])
.map(c => (c ? CSS_PREFIX + c : ''))
.join(' ');
if (!props) {
return el;
/** @returns {HTMLElement} */
function $(cls, props = {}, children = []) {
if (Array.isArray(props) || typeof props === 'string' || props instanceof Node) {
children = props;
props = {};
}
for (const child of Array.isArray(children) ? children : [children]) {
if (child) {
el.appendChild(child instanceof Node ? child : document.createTextNode(child));
}
}
delete props.tag;
delete props.children;
const el = document.createElement(props.tag || 'div');
el.className = toArray(cls).map(c => c ? CSS_PREFIX + c : '').join(' ');
el.append(...toArray(children));
if (props) delete props.tag;
return Object.assign(el, props);
}
const alphaPattern = /^\s*(0+\.?|0*\.\d+|0*1\.?|0*1\.0*)?\s*$/.source;
$root = $('popup', {children: [
$sat = $('saturation-container', {children: [
$('saturation', {children: [
$('value', {children: [
$root = $('popup', {
oninput: setFromInputs,
onkeydown: setFromKeyboard,
}, [
$sat = $('saturation-container', {
onmousedown: onSaturationMouseDown,
onmouseup: onSaturationMouseUp,
}, [
$('saturation', [
$('value', [
$satPointer = $('drag-pointer'),
]}),
]}),
]}),
$('sliders', {children: [
$('hue', {children: [
$hue = $('hue-container', {children: [
$hueKnob = $('hue-knob'),
]}),
]}),
$('opacity', {children: [
$opacity = $('opacity-container', {children: [
]),
]),
]),
$('popup-mover', {onmousedown: onPopupMoveStart}),
$('sliders', [
$('hue', {onmousedown: onHueMouseDown}, [
$hue = $('hue-container', [
$hueKnob = $('hue-knob', {onmousedown: onHueKnobMouseDown}),
]),
]),
$('opacity', [
$opacity = $('opacity-container', {onmousedown: onOpacityMouseDown}, [
$opacityBar = $('opacity-bar'),
$opacityKnob = $('opacity-knob'),
]}),
]}),
$opacityKnob = $('opacity-knob', {onmousedown: onOpacityKnobMouseDown}),
]),
]),
$('empty'),
$swatch = $('swatch'),
]}),
$(['input-container', 'hex'], {children: [
$inputGroups.hex = $(['input-group', 'hex'], {children: [
$(['input-field', 'hex'], {children: [
]),
$(['input-container', 'hex'], [
$inputGroups.hex = $(['input-group', 'hex'], [
$(['input-field', 'hex'], [
$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
}),
$('title', {children: [
$hexLettercase.true = $('title-action', {textContent: 'HEX'}),
$('title', [
$hexLettercase.true = $('title-action', {onclick: onHexLettercaseClicked}, 'HEX'),
'\xA0/\xA0',
$hexLettercase.false = $('title-action', {textContent: 'hex'}),
]}),
]}),
]}),
$inputGroups.rgb = $(['input-group', 'rgb'], {children: [
$(['input-field', 'rgb-r'], {children: [
$hexLettercase.false = $('title-action', {onclick: onHexLettercaseClicked}, 'hex'),
]),
]),
]),
$inputGroups.rgb = $(['input-group', 'rgb'], [
$(['input-field', 'rgb-r'], [
$rgb.r = $('input', {tag: 'input', type: 'number', min: 0, max: 255, step: 1}),
$('title', {textContent: 'R'}),
]}),
$(['input-field', 'rgb-g'], {children: [
$('title', 'R'),
]),
$(['input-field', 'rgb-g'], [
$rgb.g = $('input', {tag: 'input', type: 'number', min: 0, max: 255, step: 1}),
$('title', {textContent: 'G'}),
]}),
$(['input-field', 'rgb-b'], {children: [
$('title', 'G'),
]),
$(['input-field', 'rgb-b'], [
$rgb.b = $('input', {tag: 'input', type: 'number', min: 0, max: 255, step: 1}),
$('title', {textContent: 'B'}),
]}),
$(['input-field', 'rgb-a'], {children: [
$('title', 'B'),
]),
$(['input-field', 'rgb-a'], [
$rgb.a = $('input', {tag: 'input', type: 'text', pattern: alphaPattern, spellcheck: false}),
$('title', {textContent: 'A'}),
]}),
]}),
$inputGroups.hsl = $(['input-group', 'hsl'], {children: [
$(['input-field', 'hsl-h'], {children: [
$('title', 'A'),
]),
]),
$inputGroups.hsl = $(['input-group', 'hsl'], [
$(['input-field', 'hsl-h'], [
$hsl.h = $('input', {tag: 'input', type: 'number', step: 1}),
$('title', {textContent: 'H'}),
]}),
$(['input-field', 'hsl-s'], {children: [
$('title', 'H'),
]),
$(['input-field', 'hsl-s'], [
$hsl.s = $('input', {tag: 'input', type: 'number', min: 0, max: 100, step: 1}),
$('title', {textContent: 'S'}),
]}),
$(['input-field', 'hsl-l'], {children: [
$('title', 'S'),
]),
$(['input-field', 'hsl-l'], [
$hsl.l = $('input', {tag: 'input', type: 'number', min: 0, max: 100, step: 1}),
$('title', {textContent: 'L'}),
]}),
$(['input-field', 'hsl-a'], {children: [
$('title', 'L'),
]),
$(['input-field', 'hsl-a'], [
$hsl.a = $('input', {tag: 'input', type: 'text', pattern: alphaPattern, spellcheck: false}),
$('title', {textContent: 'A'}),
]}),
]}),
$('format-change', {children: [
$formatChangeButton = $('format-change-button', {textContent: '↔'}),
]}),
]}),
$palette = $('palette'),
]});
$('title', 'A'),
]),
]),
$('format-change', [
$formatChangeButton = $('format-change-button', {onclick: setFromFormatElement}, '↔'),
]),
]),
$palette = $('palette', {
onclick: onPaletteClicked,
oncontextmenu: onPaletteClicked,
}),
]);
$inputs.hex = [$hexCode];
$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)));
$root.style.setProperty('--margin', MARGIN + 'px');
initialized = true;
}
@ -202,16 +208,12 @@
userActivity = 0;
lastOutputColor = opt.color || '';
$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*'), '') +
' ' + CSS_PREFIX + 'theme-' +
(opt.theme === 'dark' || opt.theme === 'light' ? opt.theme :
guessTheme());
$root.style = `
display: block !important;
position: fixed !important;
`;
document.body.appendChild($root);
shown = true;
@ -220,13 +222,22 @@
setFromColor(opt.color);
setFromHexLettercaseElement();
if (!isNaN(options.left) && !isNaN(options.top)) {
reposition();
}
if (Array.isArray(options.palette)) {
// Might need to clear a lot of elements so this is known to be faster than textContent = ''
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();
const w = $sat.offsetWidth;
const h = $sat.offsetHeight;
const deltaX = event.clientX - parseFloat($root.style.left);
const deltaY = event.clientY - parseFloat($root.style.top);
const bb = $root.getBoundingClientRect();
const deltaX = event.clientX - bb.left;
const deltaY = event.clientY - bb.top;
const x = dragging.saturationPointerPos.x = constrain(0, w, deltaX);
const y = dragging.saturationPointerPos.y = constrain(0, h, deltaY);
@ -546,6 +558,52 @@
//endregion
//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() {
options.hexUppercase = !options.hexUppercase;
setFromHexLettercaseElement();
@ -583,19 +641,19 @@
/** @param {MouseEvent} e */
function onPaletteClicked(e) {
if (e.target !== e.currentTarget) {
e.preventDefault();
if (e.target !== e.currentTarget && e.target.__color) {
if (!e.button && setColor(e.target.__color)) {
userActivity = performance.now();
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);
}
}
}
function onMouseUp(event) {
releaseMouse(event, ['saturation', 'hue', 'opacity']);
releaseMouse(event, ['saturation', 'hue', 'opacity', 'popup']);
if (onMouseDown.outsideClick) {
if (!prevFocusedElement) hide();
}
@ -610,33 +668,15 @@
}
function onMouseMove(event) {
if (event.button !== 0) {
return;
}
if (dragging.saturation) {
setFromSaturationElement(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);
if (event.button) return;
if (dragging.saturation) setFromSaturationElement(event);
if (dragging.hue) setFromHueElement(event);
if (dragging.opacity) setFromOpacityElement(event);
if (dragging.popup) onPopupMove(event);
}
function onKeyDown(e) {
if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
if (!hasModifiers(e)) {
switch (e.key) {
case 'Enter':
case 'Escape':
@ -688,13 +728,17 @@
if (!mode) {
return;
}
for (const m of (Array.isArray(mode) ? mode : [mode])) {
for (const m of toArray(mode)) {
dragging[m] = true;
}
userActivity = performance.now();
return true;
}
function hasModifiers(e) {
return e.shiftKey || e.ctrlKey || e.altKey || e.metaKey;
}
function releaseMouse(event, mode) {
if (event && event.button !== 0) {
return;
@ -704,7 +748,7 @@
if (!mode) {
return;
}
for (const m of (Array.isArray(mode) ? mode : [mode])) {
for (const m of toArray(mode)) {
dragging[m] = false;
}
userActivity = performance.now();
@ -719,48 +763,13 @@
window.addEventListener('keydown', onKeyDown, true);
window.addEventListener('mousedown', onMouseDown, 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() {
window.removeEventListener('keydown', onKeyDown, true);
window.removeEventListener('mousedown', onMouseDown, 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();
stopSnoozing();
}
//endregion
@ -816,25 +825,13 @@
const maxRightUnobscured = options.left <= maxRight ? maxRight : options.left - width;
const left = constrain(0, Math.max(0, maxRightUnobscured), options.left);
const top = constrain(0, Math.max(0, maxTopUnobscured), options.top);
$root.style.setProperty('left', left + 'px', 'important');
$root.style.setProperty('top', top + 'px', 'important');
$root.style.left = left + 'px';
$root.style.top = top + 'px';
}
function fade({fadingStage = 1} = {}) {
const timeInactive = performance.now() - userActivity;
const delay = options.hideDelay / 2;
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 fitPaletteHeight() {
const fit = MIN_HEIGHT + $palette.scrollHeight + MARGIN;
$root.style.setProperty('--fit-height', Math.min(fit, parseFloat(maxHeight)) + 'px');
}
function maybeFocus(el) {
@ -887,6 +884,10 @@
}
}
function toArray(val) {
return !val ? [] : Array.isArray(val) ? val : [val];
}
//endregion
};

View File

@ -533,7 +533,7 @@
prevColor: data.color || '',
callback: popupOnChange,
palette: makePalette(state),
paletteCallback: el => paletteCallback(state, el),
paletteCallback,
}));
}
@ -562,8 +562,9 @@
if (!markedSpans) return;
for (const {from, marker: m} of markedSpans) {
if (from == null || m.className !== COLORVIEW_CLASS) continue;
nums = palette.get(m.color);
if (!nums) palette.set(m.color, (nums = []));
const color = m.color.toLowerCase();
nums = palette.get(color);
if (!nums) palette.set(color, (nums = []));
nums.push(i);
}
});
@ -571,29 +572,37 @@
if (palette.size > 1 || nums && nums.length > 1) {
const old = new Map((options.popup.palette || []).map(el => [el.__color, el]));
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;
}
function makePaletteSwatch(color, nums) {
function makePaletteSwatch(color, nums, label) {
const s = nums.join(', ');
const el = document.createElement('div');
el.className = COLORVIEW_SWATCH_CLASS;
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;
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 curLine = cm.getCursor().line + 1;
const i = lines.indexOf(curLine) + 1;
const pos = {line: (lines[i] || curLine) - 1, ch: 0};
cm.scrollIntoView(pos, cm.defaultTextHeight());
cm.setCursor(pos);
const i = lines.indexOf(cm.getCursor().line + 1) + 1;
const line = (lines[i] || lines[0]) - 1;
const vpm = cm.options.viewportMargin;
const inView = line >= cm.display.viewFrom - vpm && line <= cm.display.viewTo - vpm;
cm.scrollIntoView(line, inView ? cm.defaultTextHeight() : cm.display.wrapper.clientHeight / 2);
cm.setCursor(line);
}
//endregion