From 89431615b35daa88ba0dec24447bd12496617257 Mon Sep 17 00:00:00 2001 From: tophf Date: Mon, 26 Oct 2020 18:03:41 +0300 Subject: [PATCH] improve colorpicker dialog (#1079) * switch to a user-resizable palette * allow moving * remove hideDelay --- _locales/en/messages.json | 7 + edit/colorpicker-helper.js | 9 +- js/prefs.js | 1 + manage/config-dialog.js | 1 - vendor-overwrites/colorpicker/colorpicker.css | 74 +++- vendor-overwrites/colorpicker/colorpicker.js | 371 +++++++++--------- vendor-overwrites/colorpicker/colorview.js | 33 +- 7 files changed, 280 insertions(+), 216 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 51dddb1a..638e5b3b 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -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." diff --git a/edit/colorpicker-helper.js b/edit/colorpicker-helper.js index 48297a3e..f47a3dfe 100644 --- a/edit/colorpicker-helper.js +++ b/edit/colorpicker-helper.js @@ -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 { diff --git a/js/prefs.js b/js/prefs.js index 36864dcd..c000e394 100644 --- a/js/prefs.js +++ b/js/prefs.js @@ -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': '', diff --git a/manage/config-dialog.js b/manage/config-dialog.js index ed36cef6..9bb3bbd6 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -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, }); diff --git a/vendor-overwrites/colorpicker/colorpicker.css b/vendor-overwrites/colorpicker/colorpicker.css index 965c00d9..aa36e27a 100644 --- a/vendor-overwrites/colorpicker/colorpicker.css +++ b/vendor-overwrites/colorpicker/colorpicker.css @@ -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; } diff --git a/vendor-overwrites/colorpicker/colorpicker.js b/vendor-overwrites/colorpicker/colorpicker.js index b16e7380..f20812f8 100644 --- a/vendor-overwrites/colorpicker/colorpicker.js +++ b/vendor-overwrites/colorpicker/colorpicker.js @@ -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 }; diff --git a/vendor-overwrites/colorpicker/colorview.js b/vendor-overwrites/colorpicker/colorview.js index d205e81f..cf0729f7 100644 --- a/vendor-overwrites/colorpicker/colorview.js +++ b/vendor-overwrites/colorpicker/colorview.js @@ -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