improve colorpicker dialog (#1079)
* switch to a user-resizable palette * allow moving * remove hideDelay
This commit is contained in:
parent
bf40fa81e8
commit
89431615b3
|
@ -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."
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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': '',
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user