diff options
author | silverwind <me@silverwind.io> | 2024-04-03 11:15:06 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-03 09:15:06 +0000 |
commit | 1195be41a13d2198ab644c8558549edd74485510 (patch) | |
tree | 7214f233f4caefa3b727ec116b7471c383faf0f4 /web_src | |
parent | 654cfd1dfbd3f3f1d94addee50b6fe2b018a49c3 (diff) | |
download | gitea-1195be41a13d2198ab644c8558549edd74485510.tar.gz gitea-1195be41a13d2198ab644c8558549edd74485510.zip |
Replace coloris with vanilla-colorful (#30201)
Found [a better color
picker](https://github.com/web-padawan/vanilla-colorful) that [does not
rely](https://github.com/mdbassit/Coloris/issues/139) on
`querySelectorAll` or a global shared instance, and is also around a
third of the size of the previous one.
The popover is handled by tippy.js for which I introduced a new "bare"
theme and it uses a new sibling-based mechanism which should prove
useful later to create tippy popovers via HTML only.
<img width="846" alt="Screenshot 2024-03-31 at 04 03 38"
src="https://github.com/go-gitea/gitea/assets/115237/7639b911-a2d7-4f5c-bffd-a9d84561e747">
Diffstat (limited to 'web_src')
-rw-r--r-- | web_src/css/features/colorpicker.css | 141 | ||||
-rw-r--r-- | web_src/css/modules/tippy.css | 11 | ||||
-rw-r--r-- | web_src/js/features/colorpicker.js | 81 | ||||
-rw-r--r-- | web_src/js/modules/tippy.js | 7 |
4 files changed, 85 insertions, 155 deletions
diff --git a/web_src/css/features/colorpicker.css b/web_src/css/features/colorpicker.css index 0c651cfeb3..b7436783df 100644 --- a/web_src/css/features/colorpicker.css +++ b/web_src/css/features/colorpicker.css @@ -1,10 +1,6 @@ -/* This is a stripped-down version of coloris's CSS tailored to our needs. It does only include - opaqua colors, and if more features like opacity are needed, the CSS needs to be extended - based on upstream: https://github.com/mdbassit/Coloris/blob/main/src/coloris.css. */ - .js-color-picker-input { display: flex; - flex-wrap: wrap; + position: relative; } .js-color-picker-input input { @@ -13,152 +9,39 @@ padding-left: 32px !important; } -.clr-picker { - display: none; - flex-wrap: wrap; - position: absolute; - width: 200px; - z-index: 1002; /* above .ui.modal which has 1001 */ - border-radius: var(--border-radius); - background-color: var(--color-menu); - justify-content: flex-end; - direction: ltr; - box-shadow: 0 5px 20px var(--color-shadow); - user-select: none; -} - -.clr-picker.clr-open { - display: flex; -} - -.clr-gradient { - position: relative; - width: 100%; - height: 100px; - border-radius: 3px 3px 0 0; - background: linear-gradient(rgba(0,0,0,0), #000), linear-gradient(90deg, #fff, currentcolor); /* stylelint-disable-line scale-unlimited/declaration-strict-value */ - cursor: pointer; -} - -.clr-marker { - position: absolute; - width: 12px; - height: 12px; - margin: -6px 0 0 -6px; - border: 1px solid var(--color-white); - border-radius: 50%; - background-color: currentcolor; - cursor: pointer; -} - -.clr-picker input[type="range"]::-webkit-slider-runnable-track { - width: 100%; - height: 16px; -} - -.clr-picker input[type="range"]::-webkit-slider-thumb { - width: 16px; - height: 16px; - -webkit-appearance: none; -} - -.clr-picker input[type="range"]::-moz-range-track { - width: 100%; - height: 16px; - border: 0; -} - -.clr-picker input[type="range"]::-moz-range-thumb { - width: 16px; - height: 16px; - border: 0; -} - -.clr-hue { - background: linear-gradient(to right, #f00 0%, #ff0 16.66%, #0f0 33.33%, #0ff 50%, #00f 66.66%, #f0f 83.33%, #f00 100%); /* stylelint-disable-line scale-unlimited/declaration-strict-value */ - position: relative; - width: calc(100% - 40px); - height: 10px; - margin: 10px 20px; - border-radius: 4px; -} - -.clr-hue input[type="range"] { - position: absolute; - width: calc(100% + 32px); - margin: 0; - background-color: transparent; - opacity: 0; - cursor: pointer; - appearance: none; -} - -.clr-hue div { - position: absolute; - width: 16px; - height: 16px; - left: 0; - top: 50%; - transform: translate(-50%, -50%); - border: 2px solid var(--color-white); - border-radius: 50%; - background-color: currentcolor; - box-shadow: 0 0 1px var(--color-shadow); - pointer-events: none; -} - -.clr-field { - flex: 1; - position: relative; - color: transparent; -} - -.clr-field button { +.js-color-picker-input .preview-square { position: absolute; aspect-ratio: 1; height: 16px; left: 10px; top: 50%; transform: translateY(-50%); - margin: 0; - padding: 0; - border: 0; - color: inherit; - pointer-events: none; border-radius: 2px; background: repeating-linear-gradient(45deg, #aaa 25%, transparent 25%, transparent 75%, #aaa 75%, #aaa), repeating-linear-gradient(45deg, #aaa 25%, #fff 25%, #fff 75%, #aaa 75%, #aaa); /* stylelint-disable-line scale-unlimited/declaration-strict-value */ background-position: 0 0, 4px 4px; background-size: 8px 8px; } -.clr-field button::after { +.js-color-picker-input .preview-square::after { content: ""; - display: block; position: absolute; width: 100%; height: 100%; - left: 0; - top: 0; border-radius: inherit; background-color: currentcolor; } -.clr-marker:focus { - outline: none; +hex-color-picker { + width: 180px; + height: 120px; } -.clr-keyboard-nav .clr-marker:focus, -.clr-keyboard-nav .clr-hue input:focus + div, -.clr-keyboard-nav .clr-alpha input:focus + div { - outline: none; - box-shadow: 0 0 2px 2px var(--color-white); +hex-color-picker::part(hue-pointer), +hex-color-picker::part(saturation-pointer) { + width: 22px; + height: 22px; } -.clr-picker .clr-preview, -.clr-picker .clr-clear, -.clr-picker .clr-swatches, -.clr-picker .clr-format, -.clr-picker .clr-alpha, -.clr-picker .clr-color { - display: none; +hex-color-picker::part(hue) { + flex-basis: 16px; } diff --git a/web_src/css/modules/tippy.css b/web_src/css/modules/tippy.css index 76d36b4293..6ac7c37d93 100644 --- a/web_src/css/modules/tippy.css +++ b/web_src/css/modules/tippy.css @@ -29,6 +29,17 @@ z-index: 1; } +/* bare theme, no styling at all, except box-shadow */ +.tippy-box[data-theme="bare"] { + border: none; + box-shadow: 0 6px 18px var(--color-shadow); +} + +.tippy-box[data-theme="bare"] .tippy-content { + padding: 0; + background: transparent; +} + /* tooltip theme for text tooltips */ .tippy-box[data-theme="tooltip"] { diff --git a/web_src/js/features/colorpicker.js b/web_src/js/features/colorpicker.js index f342598e66..6d00d908c9 100644 --- a/web_src/js/features/colorpicker.js +++ b/web_src/js/features/colorpicker.js @@ -1,31 +1,66 @@ -export async function initColorPickers(selector = '.js-color-picker-input input', opts = {}) { - const inputEls = document.querySelectorAll(selector); - if (!inputEls.length) return; +import {createTippy} from '../modules/tippy.js'; - const [{coloris, init}] = await Promise.all([ - import(/* webpackChunkName: "colorpicker" */'@melloware/coloris'), +export async function initColorPickers() { + const els = document.getElementsByClassName('js-color-picker-input'); + if (!els.length) return; + + await Promise.all([ + import(/* webpackChunkName: "colorpicker" */'vanilla-colorful/hex-color-picker.js'), import(/* webpackChunkName: "colorpicker" */'../../css/features/colorpicker.css'), ]); - init(); - coloris({ - el: selector, - alpha: false, - focusInput: true, - selectInput: false, - ...opts, + for (const el of els) { + initPicker(el); + } +} + +function updateSquare(el, newValue) { + el.style.color = /#[0-9a-f]{6}/i.test(newValue) ? newValue : 'transparent'; +} + +function updatePicker(el, newValue) { + el.setAttribute('color', newValue); +} + +function initPicker(el) { + const input = el.querySelector('input'); + + const square = document.createElement('div'); + square.classList.add('preview-square'); + updateSquare(square, input.value); + el.append(square); + + const picker = document.createElement('hex-color-picker'); + picker.addEventListener('color-changed', (e) => { + input.value = e.detail.value; + input.focus(); + updateSquare(square, e.detail.value); + }); + + input.addEventListener('input', (e) => { + updateSquare(square, e.target.value); + updatePicker(picker, e.target.value); + }); + + createTippy(input, { + trigger: 'focus click', + theme: 'bare', + hideOnClick: true, + content: picker, + placement: 'bottom-start', + interactive: true, + onShow() { + updatePicker(picker, input.value); + }, }); - for (const inputEl of inputEls) { - const parent = inputEl.closest('.js-color-picker-input'); - // prevent tabbing on the color preview `button` inside the input - parent.querySelector('button').tabIndex = -1; - // init precolors - for (const el of parent.querySelectorAll('.precolors .color')) { - el.addEventListener('click', (e) => { - inputEl.value = e.target.getAttribute('data-color-hex'); - inputEl.dispatchEvent(new Event('input', {bubbles: true})); - }); - } + // init precolors + for (const colorEl of el.querySelectorAll('.precolors .color')) { + colorEl.addEventListener('click', (e) => { + const newValue = e.target.getAttribute('data-color-hex'); + input.value = newValue; + input.dispatchEvent(new Event('input', {bubbles: true})); + updateSquare(square, newValue); + }); } } diff --git a/web_src/js/modules/tippy.js b/web_src/js/modules/tippy.js index 220c9e5512..83b28e5745 100644 --- a/web_src/js/modules/tippy.js +++ b/web_src/js/modules/tippy.js @@ -3,11 +3,12 @@ import {isDocumentFragmentOrElementNode} from '../utils/dom.js'; import {formatDatetime} from '../utils/time.js'; const visibleInstances = new Set(); +const arrowSvg = `<svg width="16" height="7"><path d="m0 7 8-7 8 7Z" class="tippy-svg-arrow-outer"/><path d="m0 8 8-7 8 7Z" class="tippy-svg-arrow-inner"/></svg>`; export function createTippy(target, opts = {}) { // the callback functions should be destructured from opts, // because we should use our own wrapper functions to handle them, do not let the user override them - const {onHide, onShow, onDestroy, role, theme, ...other} = opts; + const {onHide, onShow, onDestroy, role, theme, arrow, ...other} = opts; const instance = tippy(target, { appendTo: document.body, @@ -35,9 +36,9 @@ export function createTippy(target, opts = {}) { visibleInstances.add(instance); return onShow?.(instance); }, - arrow: `<svg width="16" height="7"><path d="m0 7 8-7 8 7Z" class="tippy-svg-arrow-outer"/><path d="m0 8 8-7 8 7Z" class="tippy-svg-arrow-inner"/></svg>`, + arrow: arrow || (theme === 'bare' ? false : arrowSvg), role: role || 'menu', // HTML role attribute - theme: theme || role || 'menu', // CSS theme, either "tooltip", "menu" or "box-with-header" + theme: theme || role || 'menu', // CSS theme, either "tooltip", "menu", "box-with-header" or "bare" plugins: [followCursor], ...other, }); |