kopia lustrzana https://github.com/shoelace-style/shoelace
use HSB grid for color picker; fixes #762
rodzic
139073dc3e
commit
79306e0618
|
|
@ -20,6 +20,8 @@ _During the beta period, these restrictions may be relaxed in the event of a mis
|
||||||
- Improved behavior of clearable and password toggle buttons in `<sl-input>` and `<sl-select>` [#745](https://github.com/shoelace-style/shoelace/issues/745)
|
- Improved behavior of clearable and password toggle buttons in `<sl-input>` and `<sl-select>` [#745](https://github.com/shoelace-style/shoelace/issues/745)
|
||||||
- Improved performance of `<sl-select>` by caching menu items instead of traversing for them each time
|
- Improved performance of `<sl-select>` by caching menu items instead of traversing for them each time
|
||||||
- Improved drag utility so initial click/touch events can be accepted [#758](https://github.com/shoelace-style/shoelace/issues/758)
|
- Improved drag utility so initial click/touch events can be accepted [#758](https://github.com/shoelace-style/shoelace/issues/758)
|
||||||
|
- Improved `<sl-color-picker>` to use an HSB grid instead of HSL to be more consistent with existing color picker implementations [#762](https://github.com/shoelace-style/shoelace/issues/762)
|
||||||
|
- Improved `<sl-color-picker>` so the cursor is hidden and the preview is larger when dragging the grid
|
||||||
- Refactored `<sl-menu>` to be more performant by caching menu items on slot change
|
- Refactored `<sl-menu>` to be more performant by caching menu items on slot change
|
||||||
- Reverted form submit logic [#718](https://github.com/shoelace-style/shoelace/issues/718)
|
- Reverted form submit logic [#718](https://github.com/shoelace-style/shoelace/issues/718)
|
||||||
- Updated the `disabled` attribute so it reflects in `<sl-dropdown>` [#741](https://github.com/shoelace-style/shoelace/discussions/741)
|
- Updated the `disabled` attribute so it reflects in `<sl-dropdown>` [#741](https://github.com/shoelace-style/shoelace/discussions/741)
|
||||||
|
|
|
||||||
|
|
@ -39,14 +39,8 @@ export default css`
|
||||||
.color-picker__grid {
|
.color-picker__grid {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: var(--grid-height);
|
height: var(--grid-height);
|
||||||
background-image: linear-gradient(
|
background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 100%),
|
||||||
to bottom,
|
linear-gradient(to right, #fff 0%, rgba(255, 255, 255, 0) 100%);
|
||||||
hsl(0, 0%, 100%) 0%,
|
|
||||||
hsla(0, 0%, 100%, 0) 50%,
|
|
||||||
hsla(0, 0%, 0%, 0) 50%,
|
|
||||||
hsl(0, 0%, 0%) 100%
|
|
||||||
),
|
|
||||||
linear-gradient(to right, hsl(0, 0%, 50%) 0%, hsla(0, 0%, 50%, 0) 100%);
|
|
||||||
border-top-left-radius: var(--sl-border-radius-medium);
|
border-top-left-radius: var(--sl-border-radius-medium);
|
||||||
border-top-right-radius: var(--sl-border-radius-medium);
|
border-top-right-radius: var(--sl-border-radius-medium);
|
||||||
cursor: crosshair;
|
cursor: crosshair;
|
||||||
|
|
@ -61,6 +55,12 @@ export default css`
|
||||||
border: solid 2px white;
|
border: solid 2px white;
|
||||||
margin-top: calc(var(--grid-handle-size) / -2);
|
margin-top: calc(var(--grid-handle-size) / -2);
|
||||||
margin-left: calc(var(--grid-handle-size) / -2);
|
margin-left: calc(var(--grid-handle-size) / -2);
|
||||||
|
transition: var(--sl-transition-fast) transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker__grid-handle--dragging {
|
||||||
|
cursor: none;
|
||||||
|
transform: scale(1.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-picker__grid-handle${focusVisibleSelector} {
|
.color-picker__grid-handle${focusVisibleSelector} {
|
||||||
|
|
@ -133,10 +133,10 @@ export default css`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 3.25rem;
|
width: 2.25rem;
|
||||||
height: 2.25rem;
|
height: 2.25rem;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: var(--sl-input-border-radius-medium);
|
border-radius: var(--sl-border-radius-circle);
|
||||||
background: none;
|
background: none;
|
||||||
margin-left: var(--sl-spacing-small);
|
margin-left: var(--sl-spacing-small);
|
||||||
cursor: copy;
|
cursor: copy;
|
||||||
|
|
|
||||||
|
|
@ -93,10 +93,12 @@ export default class SlColorPicker extends LitElement {
|
||||||
private lastValueEmitted: string;
|
private lastValueEmitted: string;
|
||||||
private readonly localize = new LocalizeController(this);
|
private readonly localize = new LocalizeController(this);
|
||||||
|
|
||||||
|
@state() private isDraggingGridHandle = false;
|
||||||
@state() private inputValue = '';
|
@state() private inputValue = '';
|
||||||
@state() private hue = 0;
|
@state() private hue = 0;
|
||||||
@state() private saturation = 100;
|
@state() private saturation = 100;
|
||||||
@state() private lightness = 100;
|
@state() private lightness = 100;
|
||||||
|
@state() private brightness = 100;
|
||||||
@state() private alpha = 100;
|
@state() private alpha = 100;
|
||||||
|
|
||||||
/** The current color. */
|
/** The current color. */
|
||||||
|
|
@ -211,6 +213,14 @@ export default class SlColorPicker extends LitElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBrightness(lightness: number) {
|
||||||
|
return clamp(-1 * ((200 * lightness) / (this.saturation - 200)), 0, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLightness(brightness: number) {
|
||||||
|
return clamp(((((200 - this.saturation) * brightness) / 100) * 5) / 10, 0, 100);
|
||||||
|
}
|
||||||
|
|
||||||
/** Checks for validity and shows the browser's validation message if the control is invalid. */
|
/** Checks for validity and shows the browser's validation message if the control is invalid. */
|
||||||
reportValidity() {
|
reportValidity() {
|
||||||
// If the input is invalid, show the dropdown so the browser can focus on it
|
// If the input is invalid, show the dropdown so the browser can focus on it
|
||||||
|
|
@ -262,14 +272,13 @@ export default class SlColorPicker extends LitElement {
|
||||||
handle.focus();
|
handle.focus();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
drag(
|
drag(container, {
|
||||||
container,
|
onMove: x => {
|
||||||
x => {
|
|
||||||
this.alpha = clamp((x / width) * 100, 0, 100);
|
this.alpha = clamp((x / width) * 100, 0, 100);
|
||||||
this.syncValues();
|
this.syncValues();
|
||||||
},
|
},
|
||||||
{ initialEvent: event }
|
initialEvent: event
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHueDrag(event: PointerEvent) {
|
handleHueDrag(event: PointerEvent) {
|
||||||
|
|
@ -280,14 +289,13 @@ export default class SlColorPicker extends LitElement {
|
||||||
handle.focus();
|
handle.focus();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
drag(
|
drag(container, {
|
||||||
container,
|
onMove: x => {
|
||||||
x => {
|
|
||||||
this.hue = clamp((x / width) * 360, 0, 360);
|
this.hue = clamp((x / width) * 360, 0, 360);
|
||||||
this.syncValues();
|
this.syncValues();
|
||||||
},
|
},
|
||||||
{ initialEvent: event }
|
initialEvent: event
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleGridDrag(event: PointerEvent) {
|
handleGridDrag(event: PointerEvent) {
|
||||||
|
|
@ -298,17 +306,19 @@ export default class SlColorPicker extends LitElement {
|
||||||
handle.focus();
|
handle.focus();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
drag(
|
this.isDraggingGridHandle = true;
|
||||||
grid,
|
|
||||||
(x, y) => {
|
drag(grid, {
|
||||||
|
onMove: (x, y) => {
|
||||||
this.saturation = clamp((x / width) * 100, 0, 100);
|
this.saturation = clamp((x / width) * 100, 0, 100);
|
||||||
this.lightness = clamp(100 - (y / height) * 100, 0, 100);
|
this.brightness = clamp(100 - (y / height) * 100, 0, 100);
|
||||||
|
this.lightness = this.getLightness(this.brightness);
|
||||||
|
|
||||||
this.syncValues();
|
this.syncValues();
|
||||||
},
|
},
|
||||||
{
|
onStop: () => (this.isDraggingGridHandle = false),
|
||||||
initialEvent: event
|
initialEvent: event
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAlphaKeyDown(event: KeyboardEvent) {
|
handleAlphaKeyDown(event: KeyboardEvent) {
|
||||||
|
|
@ -384,13 +394,17 @@ export default class SlColorPicker extends LitElement {
|
||||||
|
|
||||||
if (event.key === 'ArrowUp') {
|
if (event.key === 'ArrowUp') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.lightness = clamp(this.lightness + increment, 0, 100);
|
this.brightness = clamp(this.brightness + increment, 0, 100);
|
||||||
|
this.lightness = this.getLightness(this.brightness);
|
||||||
|
console.log(this.lightness, this.brightness);
|
||||||
this.syncValues();
|
this.syncValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === 'ArrowDown') {
|
if (event.key === 'ArrowDown') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.lightness = clamp(this.lightness - increment, 0, 100);
|
this.brightness = clamp(this.brightness - increment, 0, 100);
|
||||||
|
this.lightness = this.getLightness(this.brightness);
|
||||||
|
console.log(this.lightness, this.brightness);
|
||||||
this.syncValues();
|
this.syncValues();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -543,6 +557,7 @@ export default class SlColorPicker extends LitElement {
|
||||||
this.hue = newColor.hsla.h;
|
this.hue = newColor.hsla.h;
|
||||||
this.saturation = newColor.hsla.s;
|
this.saturation = newColor.hsla.s;
|
||||||
this.lightness = newColor.hsla.l;
|
this.lightness = newColor.hsla.l;
|
||||||
|
this.brightness = this.getBrightness(newColor.hsla.l);
|
||||||
this.alpha = this.opacity ? newColor.hsla.a * 100 : 100;
|
this.alpha = this.opacity ? newColor.hsla.a * 100 : 100;
|
||||||
|
|
||||||
this.syncValues();
|
this.syncValues();
|
||||||
|
|
@ -623,6 +638,7 @@ export default class SlColorPicker extends LitElement {
|
||||||
this.hue = newColor.hsla.h;
|
this.hue = newColor.hsla.h;
|
||||||
this.saturation = newColor.hsla.s;
|
this.saturation = newColor.hsla.s;
|
||||||
this.lightness = newColor.hsla.l;
|
this.lightness = newColor.hsla.l;
|
||||||
|
this.brightness = this.getBrightness(newColor.hsla.l);
|
||||||
this.alpha = newColor.hsla.a * 100;
|
this.alpha = newColor.hsla.a * 100;
|
||||||
} else {
|
} else {
|
||||||
this.inputValue = oldValue;
|
this.inputValue = oldValue;
|
||||||
|
|
@ -636,8 +652,8 @@ export default class SlColorPicker extends LitElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const x = this.saturation;
|
const gridHandleX = this.saturation;
|
||||||
const y = 100 - this.lightness;
|
const gridHandleY = 100 - this.brightness;
|
||||||
|
|
||||||
const colorPicker = html`
|
const colorPicker = html`
|
||||||
<div
|
<div
|
||||||
|
|
@ -668,10 +684,13 @@ export default class SlColorPicker extends LitElement {
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
part="grid-handle"
|
part="grid-handle"
|
||||||
class="color-picker__grid-handle"
|
class=${classMap({
|
||||||
|
'color-picker__grid-handle': true,
|
||||||
|
'color-picker__grid-handle--dragging': this.isDraggingGridHandle
|
||||||
|
})}
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
top: `${y}%`,
|
top: `${gridHandleY}%`,
|
||||||
left: `${x}%`,
|
left: `${gridHandleX}%`,
|
||||||
backgroundColor: `hsla(${this.hue}deg, ${this.saturation}%, ${this.lightness}%)`
|
backgroundColor: `hsla(${this.hue}deg, ${this.saturation}%, ${this.lightness}%)`
|
||||||
})}
|
})}
|
||||||
role="application"
|
role="application"
|
||||||
|
|
|
||||||
|
|
@ -44,15 +44,12 @@ export default class SlImageComparer extends LitElement {
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
drag(
|
drag(this.base, {
|
||||||
this.base,
|
onMove: x => {
|
||||||
x => {
|
|
||||||
this.position = parseFloat(clamp((x / width) * 100, 0, 100).toFixed(2));
|
this.position = parseFloat(clamp((x / width) * 100, 0, 100).toFixed(2));
|
||||||
},
|
},
|
||||||
{
|
|
||||||
initialEvent: event
|
initialEvent: event
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown(event: KeyboardEvent) {
|
handleKeyDown(event: KeyboardEvent) {
|
||||||
|
|
|
||||||
|
|
@ -108,9 +108,8 @@ export default class SlSplitPanel extends LitElement {
|
||||||
// Prevent text selection when dragging
|
// Prevent text selection when dragging
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
drag(
|
drag(this, {
|
||||||
this,
|
onMove: (x, y) => {
|
||||||
(x, y) => {
|
|
||||||
let newPositionInPixels = this.vertical ? y : x;
|
let newPositionInPixels = this.vertical ? y : x;
|
||||||
|
|
||||||
// Flip for end panels
|
// Flip for end panels
|
||||||
|
|
@ -142,10 +141,8 @@ export default class SlSplitPanel extends LitElement {
|
||||||
|
|
||||||
this.position = clamp(this.pixelsToPercentage(newPositionInPixels), 0, 100);
|
this.position = clamp(this.pixelsToPercentage(newPositionInPixels), 0, 100);
|
||||||
},
|
},
|
||||||
{
|
|
||||||
initialEvent: event
|
initialEvent: event
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown(event: KeyboardEvent) {
|
handleKeyDown(event: KeyboardEvent) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
interface DragOptions {
|
interface DragOptions {
|
||||||
|
/** Callback that runs as dragging occurs. */
|
||||||
|
onMove: (x: number, y: number) => void;
|
||||||
|
/** Callback that runs when dragging stops. */
|
||||||
|
onStop: () => void;
|
||||||
/**
|
/**
|
||||||
* When an initial event is passed, the first drag will be triggered immediately using the coordinates therein. This
|
* When an initial event is passed, the first drag will be triggered immediately using the coordinates therein. This
|
||||||
* is useful when the drag is initiated by a mousedown/touchstart event but you want the initial "click" to activate
|
* is useful when the drag is initiated by a mousedown/touchstart event but you want the initial "click" to activate
|
||||||
|
|
@ -7,7 +11,7 @@ interface DragOptions {
|
||||||
initialEvent: PointerEvent;
|
initialEvent: PointerEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function drag(container: HTMLElement, onMove: (x: number, y: number) => void, options?: Partial<DragOptions>) {
|
export function drag(container: HTMLElement, options?: Partial<DragOptions>) {
|
||||||
function move(pointerEvent: PointerEvent) {
|
function move(pointerEvent: PointerEvent) {
|
||||||
const dims = container.getBoundingClientRect();
|
const dims = container.getBoundingClientRect();
|
||||||
const defaultView = container.ownerDocument.defaultView!;
|
const defaultView = container.ownerDocument.defaultView!;
|
||||||
|
|
@ -16,12 +20,18 @@ export function drag(container: HTMLElement, onMove: (x: number, y: number) => v
|
||||||
const x = pointerEvent.pageX - offsetX;
|
const x = pointerEvent.pageX - offsetX;
|
||||||
const y = pointerEvent.pageY - offsetY;
|
const y = pointerEvent.pageY - offsetY;
|
||||||
|
|
||||||
onMove(x, y);
|
if (options?.onMove) {
|
||||||
|
options.onMove(x, y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop() {
|
function stop() {
|
||||||
document.removeEventListener('pointermove', move);
|
document.removeEventListener('pointermove', move);
|
||||||
document.removeEventListener('pointerup', stop);
|
document.removeEventListener('pointerup', stop);
|
||||||
|
|
||||||
|
if (options?.onStop) {
|
||||||
|
options.onStop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('pointermove', move, { passive: true });
|
document.addEventListener('pointermove', move, { passive: true });
|
||||||
|
|
|
||||||
Ładowanie…
Reference in New Issue