mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 18:26:05 +08:00
refactor(editor): extract color picker component (#9456)
This commit is contained in:
@@ -1,210 +0,0 @@
|
||||
import type { EditorMenuButton } from '@blocksuite/affine-components/toolbar';
|
||||
import type { ColorScheme, Palette } from '@blocksuite/affine-model';
|
||||
import { resolveColor } from '@blocksuite/affine-model';
|
||||
import { WithDisposable } from '@blocksuite/global/utils';
|
||||
import { html, LitElement } from 'lit';
|
||||
import { property, query, state } from 'lit/decorators.js';
|
||||
import { choose } from 'lit/directives/choose.js';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import type { ColorEvent } from '../panel/color-panel.js';
|
||||
import type { ModeType, PickColorEvent, PickColorType } from './types.js';
|
||||
import { keepColor, preprocessColor, rgbaToHex8 } from './utils.js';
|
||||
|
||||
type Type = 'normal' | 'custom';
|
||||
|
||||
export class EdgelessColorPickerButton extends WithDisposable(LitElement) {
|
||||
readonly #select = (e: ColorEvent) => {
|
||||
this.#pick(e.detail);
|
||||
};
|
||||
|
||||
switchToCustomTab = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (this.colorType === 'palette') {
|
||||
this.colorType = 'normal';
|
||||
}
|
||||
this.tabType = 'custom';
|
||||
// refresh menu's position
|
||||
this.menuButton.show(true);
|
||||
};
|
||||
|
||||
get colorWithoutAlpha() {
|
||||
return this.isCSSVariable ? this.color : keepColor(this.color);
|
||||
}
|
||||
|
||||
get customButtonStyle() {
|
||||
let b = 'transparent';
|
||||
let c = 'transparent';
|
||||
|
||||
if (!this.isCustomColor) {
|
||||
return { '--b': b, '--c': c };
|
||||
}
|
||||
|
||||
if (this.isCSSVariable) {
|
||||
if (!this.color.endsWith('transparent')) {
|
||||
b = 'var(--affine-background-overlay-panel-color)';
|
||||
c = keepColor(
|
||||
rgbaToHex8(
|
||||
preprocessColor(window.getComputedStyle(this))({
|
||||
type: 'normal',
|
||||
value: this.color,
|
||||
}).rgba
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
b = 'var(--affine-background-overlay-panel-color)';
|
||||
c = keepColor(this.color);
|
||||
}
|
||||
|
||||
return { '--b': b, '--c': c };
|
||||
}
|
||||
|
||||
get isCSSVariable() {
|
||||
return this.color.startsWith('--');
|
||||
}
|
||||
|
||||
get isCustomColor() {
|
||||
return !this.palettes
|
||||
.map(({ value }) => resolveColor(value, this.theme))
|
||||
.includes(this.color);
|
||||
}
|
||||
|
||||
get tabContentPadding() {
|
||||
return `${this.tabType === 'custom' ? 0 : 8}px`;
|
||||
}
|
||||
|
||||
#pick(detail: Palette) {
|
||||
this.pick?.({ type: 'start' });
|
||||
this.pick?.({ type: 'pick', detail });
|
||||
this.pick?.({ type: 'end' });
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
this.disposables.addFromEvent(this.menuButton, 'toggle', (e: Event) => {
|
||||
const opened = (e as CustomEvent<boolean>).detail;
|
||||
if (!opened && this.tabType !== 'normal') {
|
||||
this.tabType = 'normal';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
<editor-menu-button
|
||||
.contentPadding=${this.tabContentPadding}
|
||||
.button=${html`
|
||||
<editor-icon-button
|
||||
aria-label=${this.label}
|
||||
.tooltip=${this.tooltip || this.label}
|
||||
>
|
||||
${this.isText
|
||||
? html`
|
||||
<edgeless-text-color-icon
|
||||
.color=${this.colorWithoutAlpha}
|
||||
></edgeless-text-color-icon>
|
||||
`
|
||||
: html`
|
||||
<edgeless-color-button
|
||||
.color=${this.colorWithoutAlpha}
|
||||
.hollowCircle=${this.hollowCircle}
|
||||
></edgeless-color-button>
|
||||
`}
|
||||
</editor-icon-button>
|
||||
`}
|
||||
>
|
||||
${choose(this.tabType, [
|
||||
[
|
||||
'normal',
|
||||
() => html`
|
||||
<div data-orientation="vertical">
|
||||
<slot name="other"></slot>
|
||||
<slot name="separator"></slot>
|
||||
<edgeless-color-panel
|
||||
role="listbox"
|
||||
class=${ifDefined(this.colorPanelClass)}
|
||||
.value=${this.color}
|
||||
.theme=${this.theme}
|
||||
.palettes=${this.palettes}
|
||||
.hollowCircle=${this.hollowCircle}
|
||||
.openColorPicker=${this.switchToCustomTab}
|
||||
.hasTransparent=${false}
|
||||
@select=${this.#select}
|
||||
>
|
||||
<edgeless-color-custom-button
|
||||
slot="custom"
|
||||
style=${styleMap(this.customButtonStyle)}
|
||||
?active=${this.isCustomColor}
|
||||
@click=${this.switchToCustomTab}
|
||||
></edgeless-color-custom-button>
|
||||
</edgeless-color-panel>
|
||||
</div>
|
||||
`,
|
||||
],
|
||||
[
|
||||
'custom',
|
||||
() => html`
|
||||
<edgeless-color-picker
|
||||
class="custom"
|
||||
.pick=${this.pick}
|
||||
.colors=${{
|
||||
type:
|
||||
this.colorType === 'palette' ? 'normal' : this.colorType,
|
||||
modes: this.colors.map(
|
||||
preprocessColor(window.getComputedStyle(this))
|
||||
),
|
||||
}}
|
||||
></edgeless-color-picker>
|
||||
`,
|
||||
],
|
||||
])}
|
||||
</editor-menu-button>
|
||||
`;
|
||||
}
|
||||
|
||||
@property()
|
||||
accessor color!: string;
|
||||
|
||||
@property()
|
||||
accessor colorPanelClass: string | undefined = undefined;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor colors: { type: ModeType; value: string }[] = [];
|
||||
|
||||
@property()
|
||||
accessor colorType: PickColorType = 'palette';
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor hollowCircle: boolean = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor isText!: boolean;
|
||||
|
||||
@property()
|
||||
accessor label!: string;
|
||||
|
||||
@query('editor-menu-button')
|
||||
accessor menuButton!: EditorMenuButton;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor palettes: Palette[] = [];
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor pick!: (event: PickColorEvent) => void;
|
||||
|
||||
@state()
|
||||
accessor tabType: Type = 'normal';
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor theme!: ColorScheme;
|
||||
|
||||
@property()
|
||||
accessor tooltip: string | undefined = undefined;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'edgeless-color-picker-button': EdgelessColorPickerButton;
|
||||
}
|
||||
}
|
||||
@@ -1,679 +0,0 @@
|
||||
import type { Color } from '@blocksuite/affine-model';
|
||||
import { on, once, stopPropagation } from '@blocksuite/affine-shared/utils';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
||||
import { batch, computed, signal } from '@preact/signals-core';
|
||||
import { html, LitElement } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { live } from 'lit/directives/live.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import { AREA_CIRCLE_R, MATCHERS, SLIDER_CIRCLE_R } from './consts.js';
|
||||
import { COLOR_PICKER_STYLE } from './styles.js';
|
||||
import type {
|
||||
Hsva,
|
||||
ModeRgba,
|
||||
ModeTab,
|
||||
ModeType,
|
||||
NavTab,
|
||||
NavType,
|
||||
PickColorEvent,
|
||||
Point,
|
||||
Rgb,
|
||||
} from './types.js';
|
||||
import {
|
||||
bound01,
|
||||
clamp,
|
||||
defaultHsva,
|
||||
eq,
|
||||
hsvaToHex8,
|
||||
hsvaToRgba,
|
||||
linearGradientAt,
|
||||
parseHexToHsva,
|
||||
renderCanvas,
|
||||
rgbaToHex8,
|
||||
rgbaToHsva,
|
||||
rgbToHex,
|
||||
rgbToHsv,
|
||||
} from './utils.js';
|
||||
|
||||
const TABS: NavTab<NavType>[] = [
|
||||
{ type: 'colors', name: 'Colors' },
|
||||
{ type: 'custom', name: 'Custom' },
|
||||
];
|
||||
|
||||
export class EdgelessColorPicker extends SignalWatcher(
|
||||
WithDisposable(LitElement)
|
||||
) {
|
||||
static override styles = COLOR_PICKER_STYLE;
|
||||
|
||||
#alphaRect = new DOMRect();
|
||||
|
||||
readonly #editAlpha = (e: InputEvent) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
const orignalValue = target.value;
|
||||
let value = orignalValue.trim().replace(/[^0-9]/, '');
|
||||
|
||||
const alpha = clamp(0, Number(value), 100);
|
||||
const a = bound01(alpha, 100);
|
||||
const hsva = this.hsva$.peek();
|
||||
|
||||
value = `${alpha}`;
|
||||
if (orignalValue !== value) {
|
||||
target.value = value;
|
||||
}
|
||||
|
||||
if (hsva.a === a) return;
|
||||
|
||||
const x = this.#alphaRect.width * a;
|
||||
this.alphaPosX$.value = x;
|
||||
|
||||
this.#pick();
|
||||
};
|
||||
|
||||
readonly #editHex = (e: KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
|
||||
const target = e.target as HTMLInputElement;
|
||||
|
||||
if (e.key === 'Enter') {
|
||||
const orignalValue = target.value;
|
||||
let value = orignalValue.trim().replace(MATCHERS.other, '');
|
||||
let matched;
|
||||
if (
|
||||
(matched = value.match(MATCHERS.hex3)) ||
|
||||
(matched = value.match(MATCHERS.hex6))
|
||||
) {
|
||||
const oldHsva = this.hsva$.peek();
|
||||
const hsv = parseHexToHsva(matched[1]);
|
||||
const newHsva = { ...oldHsva, ...hsv };
|
||||
|
||||
value = rgbToHex(hsvaToRgba(newHsva));
|
||||
if (orignalValue !== value) {
|
||||
target.value = value;
|
||||
}
|
||||
|
||||
if (eq(newHsva, oldHsva)) return;
|
||||
|
||||
this.#setControlsPos(newHsva);
|
||||
|
||||
this.#pick();
|
||||
} else {
|
||||
target.value = this.hex6WithoutHash$.peek();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#hueRect = new DOMRect();
|
||||
|
||||
#paletteRect = new DOMRect();
|
||||
|
||||
#pick() {
|
||||
const hsva = this.hsva$.peek();
|
||||
const type = this.modeType$.peek();
|
||||
const value = { [type]: hsvaToHex8(hsva) };
|
||||
const key = 'Custom';
|
||||
|
||||
if (type !== 'normal') {
|
||||
const another = type === 'light' ? 'dark' : 'light';
|
||||
const { hsva } = this[`${another}$`].peek();
|
||||
value[another] = hsvaToHex8(hsva);
|
||||
}
|
||||
this.pick?.({ type: 'pick', detail: { key, value: value as Color } });
|
||||
}
|
||||
|
||||
#pickEnd() {
|
||||
this.pick?.({ type: 'end' });
|
||||
}
|
||||
|
||||
#pickStart() {
|
||||
this.pick?.({ type: 'start' });
|
||||
}
|
||||
|
||||
#setAlphaPos(clientX: number) {
|
||||
const { left, width } = this.#alphaRect;
|
||||
const x = clamp(0, clientX - left, width);
|
||||
|
||||
this.alphaPosX$.value = x;
|
||||
}
|
||||
|
||||
#setAlphaPosWithWheel(y: number) {
|
||||
const { width } = this.#alphaRect;
|
||||
const px = this.alphaPosX$.peek();
|
||||
const ax = clamp(0, px + (y * width) / 100, width);
|
||||
|
||||
this.alphaPosX$.value = ax;
|
||||
}
|
||||
|
||||
#setControlsPos({ h, s, v, a }: Hsva) {
|
||||
const hx = this.#hueRect.width * h;
|
||||
const px = this.#paletteRect.width * s;
|
||||
const py = this.#paletteRect.height * (1 - v);
|
||||
const ax = this.#alphaRect.width * a;
|
||||
|
||||
batch(() => {
|
||||
this.huePosX$.value = hx;
|
||||
this.alphaPosX$.value = ax;
|
||||
this.palettePos$.value = { x: px, y: py };
|
||||
});
|
||||
}
|
||||
|
||||
#setHuePos(clientX: number) {
|
||||
const { left, width } = this.#hueRect;
|
||||
const x = clamp(0, clientX - left, width);
|
||||
|
||||
this.huePosX$.value = x;
|
||||
}
|
||||
|
||||
#setHuePosWithWheel(y: number) {
|
||||
const { width } = this.#hueRect;
|
||||
const px = this.huePosX$.peek();
|
||||
const ax = clamp(0, px + (y * width) / 100, width);
|
||||
|
||||
this.huePosX$.value = ax;
|
||||
}
|
||||
|
||||
#setPalettePos(clientX: number, clientY: number) {
|
||||
const { left, top, width, height } = this.#paletteRect;
|
||||
const x = clamp(0, clientX - left, width);
|
||||
const y = clamp(0, clientY - top, height);
|
||||
|
||||
this.palettePos$.value = { x, y };
|
||||
}
|
||||
|
||||
#setPalettePosWithWheel(x: number, y: number) {
|
||||
const { width, height } = this.#paletteRect;
|
||||
const pos = this.palettePos$.peek();
|
||||
const px = clamp(0, pos.x + (x * width) / 100, width);
|
||||
const py = clamp(0, pos.y + (y * height) / 100, height);
|
||||
|
||||
this.palettePos$.value = { x: px, y: py };
|
||||
}
|
||||
|
||||
#setRect({ left, top, width, height }: DOMRect, offset: number) {
|
||||
return new DOMRect(
|
||||
left + offset,
|
||||
top + offset,
|
||||
Math.round(width - offset * 2),
|
||||
Math.round(height - offset * 2)
|
||||
);
|
||||
}
|
||||
|
||||
#setRects() {
|
||||
this.#paletteRect = this.#setRect(
|
||||
this.paletteControl.getBoundingClientRect(),
|
||||
AREA_CIRCLE_R
|
||||
);
|
||||
|
||||
this.#hueRect = this.#setRect(
|
||||
this.hueControl.getBoundingClientRect(),
|
||||
SLIDER_CIRCLE_R
|
||||
);
|
||||
|
||||
this.#alphaRect = this.#setRect(
|
||||
this.alphaControl.getBoundingClientRect(),
|
||||
SLIDER_CIRCLE_R
|
||||
);
|
||||
}
|
||||
|
||||
#switchModeTab(type: ModeType) {
|
||||
this.modeType$.value = type;
|
||||
this.#setControlsPos(this.mode$.peek().hsva);
|
||||
}
|
||||
|
||||
#switchNavTab(type: NavType) {
|
||||
this.navType$.value = type;
|
||||
|
||||
if (type === 'colors') {
|
||||
const mode = this.mode$.peek();
|
||||
this.modes$.value[0].hsva = { ...mode.hsva };
|
||||
this.modeType$.value = 'normal';
|
||||
} else {
|
||||
const [normal, light, dark] = this.modes$.value;
|
||||
light.hsva = { ...normal.hsva };
|
||||
dark.hsva = { ...normal.hsva };
|
||||
this.modeType$.value = 'light';
|
||||
}
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
let clicked = false;
|
||||
let dragged = false;
|
||||
let outed = false;
|
||||
let picked = false;
|
||||
|
||||
let pointerenter: (() => void) | null;
|
||||
let pointermove: (() => void) | null;
|
||||
let pointerout: (() => void) | null;
|
||||
let timerId = 0;
|
||||
|
||||
this.disposables.addFromEvent(this, 'wheel', (e: WheelEvent) => {
|
||||
e.stopPropagation();
|
||||
|
||||
const target = e.composedPath()[0] as HTMLElement;
|
||||
const isInHue = target === this.hueControl;
|
||||
const isInAlpha = !isInHue && target === this.alphaControl;
|
||||
const isInPalette = !isInAlpha && target === this.paletteControl;
|
||||
picked = isInHue || isInAlpha || isInPalette;
|
||||
|
||||
if (timerId) {
|
||||
clearTimeout(timerId);
|
||||
}
|
||||
|
||||
// update target rect
|
||||
if (picked) {
|
||||
if (!timerId) {
|
||||
this.#pickStart();
|
||||
}
|
||||
timerId = window.setTimeout(() => {
|
||||
this.#pickEnd();
|
||||
timerId = 0;
|
||||
}, 110);
|
||||
}
|
||||
|
||||
const update = (x: number, y: number) => {
|
||||
if (!picked) return;
|
||||
|
||||
const absX = Math.abs(x);
|
||||
const absY = Math.abs(y);
|
||||
|
||||
x = Math.sign(x);
|
||||
y = Math.sign(y);
|
||||
|
||||
if (Math.hypot(x, y) === 0) return;
|
||||
|
||||
x *= Math.max(1, Math.log10(absX)) * -1;
|
||||
y *= Math.max(1, Math.log10(absY)) * -1;
|
||||
|
||||
if (isInHue) {
|
||||
this.#setHuePosWithWheel(x | y);
|
||||
}
|
||||
|
||||
if (isInAlpha) {
|
||||
this.#setAlphaPosWithWheel(x | y);
|
||||
}
|
||||
|
||||
if (isInPalette) {
|
||||
this.#setPalettePosWithWheel(x, y);
|
||||
}
|
||||
|
||||
this.#pick();
|
||||
};
|
||||
|
||||
update(e.deltaX, e.deltaY);
|
||||
});
|
||||
|
||||
this.disposables.addFromEvent(this, 'pointerdown', (e: PointerEvent) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (timerId) {
|
||||
clearTimeout(timerId);
|
||||
timerId = 0;
|
||||
}
|
||||
|
||||
// checks pointer enter/out
|
||||
pointerenter = on(this, 'pointerenter', () => (outed = false));
|
||||
pointerout = on(this, 'pointerout', () => (outed = true));
|
||||
// cleanups
|
||||
once(document, 'pointerup', () => {
|
||||
pointerenter?.();
|
||||
pointermove?.();
|
||||
pointerout?.();
|
||||
|
||||
if (picked) {
|
||||
this.#pickEnd();
|
||||
}
|
||||
|
||||
// When dragging the points, the color picker panel should not be triggered to close.
|
||||
if (dragged && outed) {
|
||||
once(document, 'click', stopPropagation, true);
|
||||
}
|
||||
|
||||
pointerenter = pointermove = pointerout = null;
|
||||
clicked = dragged = outed = picked = false;
|
||||
});
|
||||
|
||||
clicked = true;
|
||||
|
||||
const target = e.composedPath()[0] as HTMLElement;
|
||||
const isInHue = target === this.hueControl;
|
||||
const isInAlpha = !isInHue && target === this.alphaControl;
|
||||
const isInPalette = !isInAlpha && target === this.paletteControl;
|
||||
picked = isInHue || isInAlpha || isInPalette;
|
||||
|
||||
// update target rect
|
||||
if (picked) {
|
||||
this.#pickStart();
|
||||
|
||||
const rect = target.getBoundingClientRect();
|
||||
if (isInHue) {
|
||||
this.#hueRect = this.#setRect(rect, SLIDER_CIRCLE_R);
|
||||
} else if (isInAlpha) {
|
||||
this.#alphaRect = this.#setRect(rect, SLIDER_CIRCLE_R);
|
||||
} else if (isInPalette) {
|
||||
this.#paletteRect = this.#setRect(rect, AREA_CIRCLE_R);
|
||||
}
|
||||
}
|
||||
|
||||
const update = (x: number, y: number) => {
|
||||
if (!picked) return;
|
||||
|
||||
if (isInHue) {
|
||||
this.#setHuePos(x);
|
||||
}
|
||||
|
||||
if (isInAlpha) {
|
||||
this.#setAlphaPos(x);
|
||||
}
|
||||
|
||||
if (isInPalette) {
|
||||
this.#setPalettePos(x, y);
|
||||
}
|
||||
|
||||
this.#pick();
|
||||
};
|
||||
|
||||
update(e.x, e.y);
|
||||
|
||||
pointermove = on(document, 'pointermove', (e: PointerEvent) => {
|
||||
if (!clicked) return;
|
||||
if (!dragged) dragged = true;
|
||||
|
||||
update(e.x, e.y);
|
||||
});
|
||||
});
|
||||
this.disposables.addFromEvent(this, 'click', stopPropagation);
|
||||
|
||||
const batches: (() => void)[] = [];
|
||||
const { type, modes } = this.colors;
|
||||
|
||||
// Updates UI states
|
||||
if (['dark', 'light'].includes(type)) {
|
||||
batches.push(() => {
|
||||
this.modeType$.value = type;
|
||||
this.navType$.value = 'custom';
|
||||
});
|
||||
}
|
||||
|
||||
// Updates modes
|
||||
if (modes?.length) {
|
||||
batches.push(() => {
|
||||
let value = defaultHsva();
|
||||
this.modes$.value.forEach((curr, n) => {
|
||||
const m = modes[n];
|
||||
curr.hsva = m ? rgbaToHsva(m.rgba) : value;
|
||||
value = curr.hsva;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Updates controls' positions
|
||||
batches.push(() => {
|
||||
const mode = this.mode$.peek();
|
||||
this.#setControlsPos(mode.hsva);
|
||||
});
|
||||
|
||||
// Updates controls' rects
|
||||
this.#setRects();
|
||||
|
||||
batch(() => batches.forEach(fn => fn()));
|
||||
|
||||
this.updateComplete
|
||||
.then(() => {
|
||||
this.disposables.add(
|
||||
this.hsva$.subscribe((hsva: Hsva) => {
|
||||
const type = this.modeType$.peek();
|
||||
const mode = this.modes$.value.find(mode => mode.type === type);
|
||||
|
||||
if (mode) {
|
||||
mode.hsva = { ...hsva };
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.disposables.add(
|
||||
this.huePosX$.subscribe((x: number) => {
|
||||
const { width } = this.#hueRect;
|
||||
const rgb = linearGradientAt(bound01(x, width));
|
||||
|
||||
// Updates palette canvas
|
||||
renderCanvas(this.canvas, rgb);
|
||||
|
||||
this.hue$.value = rgb;
|
||||
})
|
||||
);
|
||||
|
||||
this.disposables.add(
|
||||
this.hue$.subscribe((rgb: Rgb) => {
|
||||
const hsva = this.hsva$.peek();
|
||||
const h = rgbToHsv(rgb).h;
|
||||
|
||||
this.hsva$.value = { ...hsva, h };
|
||||
})
|
||||
);
|
||||
|
||||
this.disposables.add(
|
||||
this.alphaPosX$.subscribe((x: number) => {
|
||||
const hsva = this.hsva$.peek();
|
||||
const { width } = this.#alphaRect;
|
||||
const a = bound01(x, width);
|
||||
|
||||
this.hsva$.value = { ...hsva, a };
|
||||
})
|
||||
);
|
||||
|
||||
this.disposables.add(
|
||||
this.palettePos$.subscribe(({ x, y }: Point) => {
|
||||
const hsva = this.hsva$.peek();
|
||||
const { width, height } = this.#paletteRect;
|
||||
const s = bound01(x, width);
|
||||
const v = bound01(height - y, height);
|
||||
|
||||
this.hsva$.value = { ...hsva, s, v };
|
||||
})
|
||||
);
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
<header>
|
||||
<nav>
|
||||
${repeat(
|
||||
TABS,
|
||||
tab => tab.type,
|
||||
({ type, name }) => html`
|
||||
<button
|
||||
?active=${type === this.navType$.value}
|
||||
@click=${() => this.#switchNavTab(type)}
|
||||
>
|
||||
${name}
|
||||
</button>
|
||||
`
|
||||
)}
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="modes" ?active=${this.navType$.value === 'custom'}>
|
||||
${repeat(
|
||||
[this.light$.value, this.dark$.value],
|
||||
mode => mode.type,
|
||||
({ type, name, hsva }) => html`
|
||||
<div
|
||||
class="${classMap({ mode: true, [type]: true })}"
|
||||
style=${styleMap({ '--c': hsvaToHex8(hsva) })}
|
||||
>
|
||||
<button
|
||||
?active=${this.modeType$.value === type}
|
||||
@click=${() => this.#switchModeTab(type)}
|
||||
>
|
||||
<div class="color"></div>
|
||||
<div>${name}</div>
|
||||
</button>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div
|
||||
class="color-palette-wrapper"
|
||||
style=${styleMap(this.paletteStyle$.value)}
|
||||
>
|
||||
<canvas></canvas>
|
||||
<div class="color-circle"></div>
|
||||
<div class="color-palette"></div>
|
||||
</div>
|
||||
<div
|
||||
class="color-slider-wrapper hue"
|
||||
style=${styleMap(this.hueStyle$.value)}
|
||||
>
|
||||
<div class="color-circle"></div>
|
||||
<div class="color-slider"></div>
|
||||
</div>
|
||||
<div
|
||||
class="color-slider-wrapper alpha"
|
||||
style=${styleMap(this.alphaStyle$.value)}
|
||||
>
|
||||
<div class="color-circle"></div>
|
||||
<div class="color-slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<label class="field color">
|
||||
<span>#</span>
|
||||
<input
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
minlength="1"
|
||||
maxlength="6"
|
||||
.value=${live(this.hex6WithoutHash$.value)}
|
||||
@keydown=${this.#editHex}
|
||||
@cut=${stopPropagation}
|
||||
@copy=${stopPropagation}
|
||||
@paste=${stopPropagation}
|
||||
/>
|
||||
</label>
|
||||
<label class="field alpha">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
.value=${live(this.alpha100$.value)}
|
||||
@input=${this.#editAlpha}
|
||||
@cut=${stopPropagation}
|
||||
@copy=${stopPropagation}
|
||||
@paste=${stopPropagation}
|
||||
/>
|
||||
<span>%</span>
|
||||
</label>
|
||||
</footer>
|
||||
`;
|
||||
}
|
||||
|
||||
// 0-100
|
||||
accessor alpha100$ = computed(
|
||||
() => `${Math.round(this.hsva$.value.a * 100)}`
|
||||
);
|
||||
|
||||
@query('.color-slider-wrapper.alpha .color-slider')
|
||||
accessor alphaControl!: HTMLDivElement;
|
||||
|
||||
accessor alphaPosX$ = signal<number>(0);
|
||||
|
||||
accessor alphaStyle$ = computed(() => {
|
||||
const x = this.alphaPosX$.value;
|
||||
const rgba = this.rgba$.value;
|
||||
const hex = `#${rgbToHex(rgba)}`;
|
||||
return {
|
||||
'--o': rgba.a,
|
||||
'--s': `${hex}00`,
|
||||
'--c': `${hex}ff`,
|
||||
'--x': `${x}px`,
|
||||
'--r': `${SLIDER_CIRCLE_R}px`,
|
||||
};
|
||||
});
|
||||
|
||||
@query('canvas')
|
||||
accessor canvas!: HTMLCanvasElement;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor colors: { type: ModeType; modes?: ModeRgba[] } = { type: 'normal' };
|
||||
|
||||
accessor dark$ = computed<ModeTab<ModeType>>(() => this.modes$.value[2]);
|
||||
|
||||
// #ffffff
|
||||
accessor hex6$ = computed(() => this.hex8$.value.substring(0, 7));
|
||||
|
||||
// ffffff
|
||||
accessor hex6WithoutHash$ = computed(() => this.hex6$.value.substring(1));
|
||||
|
||||
// #ffffffff
|
||||
accessor hex8$ = computed(() => rgbaToHex8(this.rgba$.value));
|
||||
|
||||
accessor hsva$ = signal<Hsva>(defaultHsva());
|
||||
|
||||
accessor hue$ = signal<Rgb>({ r: 0, g: 0, b: 0 });
|
||||
|
||||
@query('.color-slider-wrapper.hue .color-slider')
|
||||
accessor hueControl!: HTMLDivElement;
|
||||
|
||||
accessor huePosX$ = signal<number>(0);
|
||||
|
||||
accessor hueStyle$ = computed(() => {
|
||||
const x = this.huePosX$.value;
|
||||
const rgb = this.hue$.value;
|
||||
return {
|
||||
'--x': `${x}px`,
|
||||
'--c': `#${rgbToHex(rgb)}`,
|
||||
'--r': `${SLIDER_CIRCLE_R}px`,
|
||||
};
|
||||
});
|
||||
|
||||
accessor light$ = computed<ModeTab<ModeType>>(() => this.modes$.value[1]);
|
||||
|
||||
accessor mode$ = computed<ModeTab<ModeType>>(() => {
|
||||
const modeType = this.modeType$.value;
|
||||
return this.modes$.value.find(mode => mode.type === modeType)!;
|
||||
});
|
||||
|
||||
accessor modes$ = signal<ModeTab<ModeType>[]>([
|
||||
{ type: 'normal', name: 'Normal', hsva: defaultHsva() },
|
||||
{ type: 'light', name: 'Light', hsva: defaultHsva() },
|
||||
{ type: 'dark', name: 'Dark', hsva: defaultHsva() },
|
||||
]);
|
||||
|
||||
accessor modeType$ = signal<ModeType>('normal');
|
||||
|
||||
accessor navType$ = signal<NavType>('colors');
|
||||
|
||||
@query('.color-palette')
|
||||
accessor paletteControl!: HTMLDivElement;
|
||||
|
||||
accessor palettePos$ = signal<Point>({ x: 0, y: 0 });
|
||||
|
||||
accessor paletteStyle$ = computed(() => {
|
||||
const { x, y } = this.palettePos$.value;
|
||||
const c = this.hex6$.value;
|
||||
return {
|
||||
'--c': c,
|
||||
'--x': `${x}px`,
|
||||
'--y': `${y}px`,
|
||||
'--r': `${AREA_CIRCLE_R}px`,
|
||||
};
|
||||
});
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor pick!: (event: PickColorEvent) => void;
|
||||
|
||||
accessor rgba$ = computed(() => hsvaToRgba(this.hsva$.value));
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'edgeless-color-picker': EdgelessColorPicker;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import type { Rgb } from './types.js';
|
||||
|
||||
export const AREA_CIRCLE_R = 12.5;
|
||||
export const SLIDER_CIRCLE_R = 10.5;
|
||||
|
||||
// [Rgb, stop][]
|
||||
export const COLORS: [Rgb, number][] = [
|
||||
[{ r: 1, g: 0, b: 0 }, 0 / 6],
|
||||
[{ r: 1, g: 1, b: 0 }, 1 / 6],
|
||||
[{ r: 0, g: 1, b: 0 }, 2 / 6],
|
||||
[{ r: 0, g: 1, b: 1 }, 3 / 6],
|
||||
[{ r: 0, g: 0, b: 1 }, 4 / 6],
|
||||
[{ r: 1, g: 0, b: 1 }, 5 / 6],
|
||||
[{ r: 1, g: 0, b: 0 }, 1],
|
||||
];
|
||||
|
||||
export const FIRST_COLOR = COLORS[0][0];
|
||||
|
||||
export const MATCHERS = {
|
||||
hex3: /^#?([0-9a-fA-F]{3})$/,
|
||||
hex6: /^#?([0-9a-fA-F]{6})$/,
|
||||
hex4: /^#?([0-9a-fA-F]{4})$/,
|
||||
hex8: /^#?([0-9a-fA-F]{8})$/,
|
||||
other: /[^0-9a-fA-F]/,
|
||||
};
|
||||
@@ -1,75 +0,0 @@
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
export class EdgelessColorCustomButton extends LitElement {
|
||||
static override styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:host([active]):after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
content: '';
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
border: 1.5px solid var(--affine-primary-color);
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.color-custom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
padding: 2px;
|
||||
border: 2px solid transparent;
|
||||
background:
|
||||
linear-gradient(var(--c, transparent), var(--c, transparent))
|
||||
content-box,
|
||||
linear-gradient(var(--b, transparent), var(--b, transparent))
|
||||
padding-box,
|
||||
conic-gradient(
|
||||
from 180deg at 50% 50%,
|
||||
#d21c7e 0deg,
|
||||
#c240f0 30.697514712810516deg,
|
||||
#434af5 62.052921652793884deg,
|
||||
#3cb5f9 93.59999656677246deg,
|
||||
#3ceefa 131.40000343322754deg,
|
||||
#37f7bd 167.40000128746033deg,
|
||||
#2df541 203.39999914169312deg,
|
||||
#e7f738 239.40000772476196deg,
|
||||
#fbaf3e 273.07027101516724deg,
|
||||
#fd904e 300.73712825775146deg,
|
||||
#f64545 329.47510957717896deg,
|
||||
#f040a9 359.0167021751404deg
|
||||
)
|
||||
border-box;
|
||||
}
|
||||
`;
|
||||
|
||||
override render() {
|
||||
return html`<div class="color-unit color-custom"></div>`;
|
||||
}
|
||||
|
||||
@property({ attribute: true, type: Boolean })
|
||||
accessor active: boolean = false;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'edgeless-color-custom-button': EdgelessColorCustomButton;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './button.js';
|
||||
export * from './color-picker.js';
|
||||
export * from './types.js';
|
||||
@@ -1,293 +0,0 @@
|
||||
import { FONT_SM, FONT_XS } from '@blocksuite/affine-shared/styles';
|
||||
import { css } from 'lit';
|
||||
|
||||
export const COLOR_PICKER_STYLE = css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: normal;
|
||||
gap: 12px;
|
||||
min-width: 198px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
padding: 2px;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
align-self: stretch;
|
||||
border-radius: 8px;
|
||||
background: var(--affine-hover-color);
|
||||
}
|
||||
|
||||
nav button {
|
||||
display: flex;
|
||||
padding: 4px 8px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 1 0 0;
|
||||
|
||||
${FONT_XS};
|
||||
color: var(--affine-text-secondary-color);
|
||||
font-weight: 600;
|
||||
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
nav button[active] {
|
||||
color: var(--affine-text-primary-color, #121212);
|
||||
background: var(--affine-background-primary-color);
|
||||
box-shadow: var(--affine-shadow-1);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.modes {
|
||||
display: none;
|
||||
gap: 8px;
|
||||
align-self: stretch;
|
||||
}
|
||||
.modes[active] {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modes .mode {
|
||||
display: flex;
|
||||
padding: 2px;
|
||||
flex-direction: column;
|
||||
flex: 1 0 0;
|
||||
}
|
||||
|
||||
.modes .mode button {
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: 60px;
|
||||
padding: 12px 12px 8px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
align-self: stretch;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--affine-border-color);
|
||||
box-sizing: border-box;
|
||||
|
||||
${FONT_XS};
|
||||
font-weight: 400;
|
||||
color: #8e8d91;
|
||||
}
|
||||
.modes .mode.light button {
|
||||
background: white;
|
||||
}
|
||||
.modes .mode.dark button {
|
||||
background: #141414;
|
||||
}
|
||||
.modes .mode button .color {
|
||||
background: var(--c);
|
||||
flex-shrink: 0;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.modes .mode button[active] {
|
||||
pointer-events: none;
|
||||
outline: 2px solid var(--affine-brand-color, #1e96eb);
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.color-palette-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 170px;
|
||||
}
|
||||
|
||||
.color-palette-wrapper canvas {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.color-palette-wrapper::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.color-circle {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
left: calc(-1 * var(--size) / 2);
|
||||
transform: translate(var(--x, 0), var(--y, 0));
|
||||
background: transparent;
|
||||
border: 0.5px solid #e3e2e4;
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0px 0px 0px 0.5px #e3e3e4 inset;
|
||||
filter: drop-shadow(0px 0px 12px rgba(66, 65, 73, 0.14));
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
.color-circle::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: var(--c);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.color-circle::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: calc(var(--size) - 1px);
|
||||
height: calc(var(--size) - 1px);
|
||||
background: transparent;
|
||||
border-style: solid;
|
||||
border-color: white;
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.color-palette-wrapper {
|
||||
--size: calc(var(--r, 12.5px) * 2);
|
||||
}
|
||||
.color-palette-wrapper .color-circle {
|
||||
top: calc(-1 * var(--size) / 2);
|
||||
}
|
||||
.color-palette-wrapper .color-circle::before {
|
||||
opacity: var(--o, 1);
|
||||
}
|
||||
.color-palette-wrapper .color-circle::after {
|
||||
border-width: 4px;
|
||||
}
|
||||
.color-palette,
|
||||
.color-slider {
|
||||
position: absolute;
|
||||
inset: calc(-1 * var(--size) / 2);
|
||||
}
|
||||
|
||||
.color-slider-wrapper:last-of-type {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.color-slider-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
}
|
||||
.color-slider-wrapper::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.color-slider-wrapper {
|
||||
--size: calc(var(--r, 10.5px) * 2);
|
||||
}
|
||||
.color-slider-wrapper .color-circle::after {
|
||||
border-width: 2px;
|
||||
}
|
||||
.color-slider-wrapper.hue::before {
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
#f00 0%,
|
||||
#ff0 calc(100% / 6),
|
||||
#0f0 calc(200% / 6),
|
||||
#0ff 50%,
|
||||
#00f calc(400% / 6),
|
||||
#f0f calc(500% / 6),
|
||||
#f00 100%
|
||||
);
|
||||
}
|
||||
.color-slider-wrapper.alpha::before {
|
||||
background:
|
||||
linear-gradient(to right, var(--s) 0%, var(--c) 100%),
|
||||
conic-gradient(
|
||||
#fff 25%,
|
||||
#d9d9d9 0deg,
|
||||
#d9d9d9 50%,
|
||||
#fff 0deg,
|
||||
#fff 75%,
|
||||
#d9d9d9 0deg
|
||||
)
|
||||
0% 0% / 8px 8px;
|
||||
}
|
||||
.color-slider-wrapper.alpha .color-circle::before {
|
||||
opacity: var(--o, 1);
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: flex;
|
||||
padding: 7px 9px;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--affine-border-color);
|
||||
background: var(--affine-background-primary-color);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.field.color {
|
||||
width: 132px;
|
||||
}
|
||||
|
||||
.field.alpha {
|
||||
width: 58px;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
${FONT_SM};
|
||||
font-weight: 400;
|
||||
color: var(--affine-text-primary-color);
|
||||
}
|
||||
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
/* Firefox */
|
||||
input[type='number'] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
`;
|
||||
@@ -1,53 +0,0 @@
|
||||
// https://www.w3.org/TR/css-color-4/
|
||||
|
||||
import type { ColorScheme, Palette } from '@blocksuite/affine-model';
|
||||
|
||||
// Red, green, blue. All in the range [0, 1].
|
||||
export type Rgb = {
|
||||
// red 0-1
|
||||
r: number;
|
||||
// green 0-1
|
||||
g: number;
|
||||
// blue 0-1
|
||||
b: number;
|
||||
};
|
||||
|
||||
// Red, green, blue, alpha. All in the range [0, 1].
|
||||
export type Rgba = Rgb & {
|
||||
// alpha 0-1
|
||||
a: number;
|
||||
};
|
||||
|
||||
// Hue, saturation, value. All in the range [0, 1].
|
||||
export type Hsv = {
|
||||
// hue 0-1
|
||||
h: number;
|
||||
// saturation 0-1
|
||||
s: number;
|
||||
// value 0-1
|
||||
v: number;
|
||||
};
|
||||
|
||||
// Hue, saturation, value, alpha. All in the range [0, 1].
|
||||
export type Hsva = Hsv & {
|
||||
// alpha 0-1
|
||||
a: number;
|
||||
};
|
||||
|
||||
export type Point = { x: number; y: number };
|
||||
|
||||
export type NavType = 'colors' | 'custom';
|
||||
|
||||
export type NavTab<Type> = { type: Type; name: string };
|
||||
|
||||
export type ModeType = 'normal' | `${ColorScheme}`;
|
||||
|
||||
export type ModeTab<Type> = NavTab<Type> & { hsva: Hsva };
|
||||
|
||||
export type ModeRgba = { type: ModeType; rgba: Rgba };
|
||||
|
||||
export type PickColorType = 'palette' | ModeType;
|
||||
|
||||
export type PickColorEvent =
|
||||
| { type: 'start' | 'end' }
|
||||
| { type: 'pick'; detail: Palette };
|
||||
@@ -1,312 +0,0 @@
|
||||
// https://www.w3.org/TR/css-color-4/
|
||||
|
||||
import type { Color, ColorScheme } from '@blocksuite/affine-model';
|
||||
|
||||
import { COLORS, FIRST_COLOR } from './consts.js';
|
||||
import type {
|
||||
Hsv,
|
||||
Hsva,
|
||||
ModeType,
|
||||
PickColorType,
|
||||
Point,
|
||||
Rgb,
|
||||
Rgba,
|
||||
} from './types.js';
|
||||
|
||||
export const defaultPoint = (x = 0, y = 0): Point => ({ x, y });
|
||||
|
||||
export const defaultHsva = (): Hsva => ({ ...rgbToHsv(FIRST_COLOR), a: 1 });
|
||||
|
||||
export function linearGradientAt(t: number): Rgb {
|
||||
if (t < 0) return COLORS[0][0];
|
||||
if (t > 1) return COLORS[COLORS.length - 1][0];
|
||||
|
||||
let low = 0;
|
||||
let high = COLORS.length;
|
||||
while (low < high) {
|
||||
const mid = Math.floor((low + high) / 2);
|
||||
const color = COLORS[mid];
|
||||
if (color[1] < t) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
|
||||
if (low === 0) {
|
||||
low = 1;
|
||||
}
|
||||
|
||||
const [rgb0, s0] = COLORS[low - 1];
|
||||
const [rgb1, s1] = COLORS[low];
|
||||
t = (t - s0) / (s1 - s0);
|
||||
|
||||
const [r, g, b] = [
|
||||
lerp(rgb0.r, rgb1.r, t),
|
||||
lerp(rgb0.g, rgb1.g, t),
|
||||
lerp(rgb0.b, rgb1.b, t),
|
||||
];
|
||||
|
||||
return { r, g, b };
|
||||
}
|
||||
|
||||
const lerp = (a: number, b: number, t: number) => a + t * (b - a);
|
||||
|
||||
export const clamp = (min: number, val: number, max: number) =>
|
||||
Math.min(Math.max(min, val), max);
|
||||
|
||||
export const bound01 = (n: number, max: number) => {
|
||||
n = clamp(0, n, max);
|
||||
|
||||
// Handle floating point rounding errors
|
||||
if (Math.abs(n - max) < 0.000001) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Convert into [0, 1] range if it isn't already
|
||||
return (n % max) / max;
|
||||
};
|
||||
|
||||
// Converts an RGB color value to HSV
|
||||
export const rgbToHsv = ({ r, g, b }: Rgb): Hsv => {
|
||||
const v = Math.max(r, g, b); // value
|
||||
const d = v - Math.min(r, g, b);
|
||||
|
||||
if (d === 0) {
|
||||
return { h: 0, s: 0, v };
|
||||
}
|
||||
|
||||
const s = d / v;
|
||||
let h = 0;
|
||||
|
||||
if (v === r) {
|
||||
h = (g - b) / d + (g < b ? 6 : 0);
|
||||
} else if (v === g) {
|
||||
h = (b - r) / d + 2;
|
||||
} else {
|
||||
h = (r - g) / d + 4;
|
||||
}
|
||||
|
||||
h /= 6;
|
||||
|
||||
return { h, s, v };
|
||||
};
|
||||
|
||||
// Converts an HSV color value to RGB
|
||||
export const hsvToRgb = ({ h, s, v }: Hsv): Rgb => {
|
||||
if (h < 0) h = (h + 1) % 1; // wrap
|
||||
h *= 6;
|
||||
s = clamp(0, s, 1);
|
||||
|
||||
const i = Math.floor(h),
|
||||
f = h - i,
|
||||
p = v * (1 - s),
|
||||
q = v * (1 - f * s),
|
||||
t = v * (1 - (1 - f) * s),
|
||||
m = i % 6;
|
||||
|
||||
let rgb = [0, 0, 0];
|
||||
|
||||
if (m === 0) rgb = [v, t, p];
|
||||
else if (m === 1) rgb = [q, v, p];
|
||||
else if (m === 2) rgb = [p, v, t];
|
||||
else if (m === 3) rgb = [p, q, v];
|
||||
else if (m === 4) rgb = [t, p, v];
|
||||
else if (m === 5) rgb = [v, p, q];
|
||||
|
||||
const [r, g, b] = rgb;
|
||||
|
||||
return { r, g, b };
|
||||
};
|
||||
|
||||
// Converts a RGBA color value to HSVA
|
||||
export const rgbaToHsva = (rgba: Rgba): Hsva => ({
|
||||
...rgbToHsv(rgba),
|
||||
a: rgba.a,
|
||||
});
|
||||
|
||||
// Converts an HSVA color value to RGBA
|
||||
export const hsvaToRgba = (hsva: Hsva): Rgba => ({
|
||||
...hsvToRgb(hsva),
|
||||
a: hsva.a,
|
||||
});
|
||||
|
||||
// Converts a RGB color to hex
|
||||
export const rgbToHex = ({ r, g, b }: Rgb) =>
|
||||
[r, g, b]
|
||||
.map(n => n * 255)
|
||||
.map(Math.round)
|
||||
.map(s => s.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
|
||||
// Converts an RGBA color to CSS's hex8 string
|
||||
export const rgbaToHex8 = ({ r, g, b, a }: Rgba) => {
|
||||
const hex = [r, g, b, a]
|
||||
.map(n => n * 255)
|
||||
.map(Math.round)
|
||||
.map(n => n.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
return `#${hex}`;
|
||||
};
|
||||
|
||||
// Converts an HSVA color to CSS's hex8 string
|
||||
export const hsvaToHex8 = (hsva: Hsva) => rgbaToHex8(hsvaToRgba(hsva));
|
||||
|
||||
// Parses an hex string to RGBA.
|
||||
export const parseHexToRgba = (hex: string) => {
|
||||
if (hex.startsWith('#')) {
|
||||
hex = hex.substring(1);
|
||||
}
|
||||
|
||||
const len = hex.length;
|
||||
let arr: string[] = [];
|
||||
|
||||
if (len === 3 || len === 4) {
|
||||
arr = hex.split('').map(s => s.repeat(2));
|
||||
} else if (len === 6 || len === 8) {
|
||||
arr = Array.from<number>({ length: len / 2 })
|
||||
.fill(0)
|
||||
.map((n, i) => n + i * 2)
|
||||
.map(n => hex.substring(n, n + 2));
|
||||
}
|
||||
|
||||
const [r, g, b, a = 1] = arr
|
||||
.map(s => parseInt(s, 16))
|
||||
.map(n => bound01(n, 255));
|
||||
|
||||
return { r, g, b, a };
|
||||
};
|
||||
|
||||
// Parses an hex string to HSVA
|
||||
export const parseHexToHsva = (hex: string) => rgbaToHsva(parseHexToRgba(hex));
|
||||
|
||||
// Compares two hsvs.
|
||||
export const eq = (lhs: Hsv, rhs: Hsv) =>
|
||||
lhs.h === rhs.h && lhs.s === rhs.s && lhs.v === rhs.v;
|
||||
|
||||
export const renderCanvas = (canvas: HTMLCanvasElement, rgb: Rgb) => {
|
||||
const { width, height } = canvas;
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
|
||||
ctx.globalCompositeOperation = 'color';
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
// Saturation: from top to bottom
|
||||
const s = ctx.createLinearGradient(0, 0, 0, height);
|
||||
s.addColorStop(0, '#0000'); // transparent
|
||||
s.addColorStop(1, '#000'); // black
|
||||
|
||||
ctx.fillStyle = s;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
// Value: from left to right
|
||||
const v = ctx.createLinearGradient(0, 0, width, 0);
|
||||
v.addColorStop(0, '#fff'); // white
|
||||
v.addColorStop(1, `#${rgbToHex(rgb)}`); // picked color
|
||||
|
||||
ctx.fillStyle = v;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
};
|
||||
|
||||
// Drops alpha value
|
||||
export const keepColor = (color: string) =>
|
||||
color.length > 7 && !color.endsWith('transparent')
|
||||
? color.substring(0, 7)
|
||||
: color;
|
||||
|
||||
export const parseStringToRgba = (value: string) => {
|
||||
value = value.trim();
|
||||
|
||||
// Compatible old format: `--affine-palette-transparent`
|
||||
if (value.endsWith('transparent')) {
|
||||
return { r: 1, g: 1, b: 1, a: 0 };
|
||||
}
|
||||
|
||||
if (value.startsWith('#')) {
|
||||
return parseHexToRgba(value);
|
||||
}
|
||||
|
||||
if (value.startsWith('rgb')) {
|
||||
const [r, g, b, a = 1] = value
|
||||
.replace(/^rgba?/, '')
|
||||
.replace(/\(|\)/, '')
|
||||
.split(',')
|
||||
.map(s => parseFloat(s.trim()))
|
||||
// In CSS, the alpha is already in the range [0, 1]
|
||||
.map((n, i) => bound01(n, i === 3 ? 1 : 255));
|
||||
|
||||
return { r, g, b, a };
|
||||
}
|
||||
|
||||
return { r: 0, g: 0, b: 0, a: 1 };
|
||||
};
|
||||
|
||||
// Preprocess Color
|
||||
export const preprocessColor = (style: CSSStyleDeclaration) => {
|
||||
return ({ type, value }: { type: ModeType; value: string }) => {
|
||||
if (value.startsWith('--')) {
|
||||
// Compatible old format: `--affine-palette-transparent`
|
||||
value = value.endsWith('transparent')
|
||||
? 'transparent'
|
||||
: style.getPropertyValue(value);
|
||||
}
|
||||
|
||||
const rgba = parseStringToRgba(value);
|
||||
|
||||
return { type, rgba };
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Packs to generate an object with a field name and picked color detail
|
||||
*
|
||||
* @param key - The model's field name
|
||||
* @param detail - The picked color detail
|
||||
* @returns An object
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```json
|
||||
* { 'fillColor': '--affine-palette-shape-yellow' }
|
||||
* { 'fillColor': '#ffffff' }
|
||||
* { 'fillColor': { normal: '#ffffffff' }}
|
||||
* { 'fillColor': { light: '#fff000ff', 'dark': '#0000fff00' }}
|
||||
* ```
|
||||
*/
|
||||
export const packColor = (key: string, color: Color) => {
|
||||
return { [key]: typeof color === 'object' ? { ...color } : color };
|
||||
};
|
||||
|
||||
/**
|
||||
* Packs to generate a color array with the color-scheme
|
||||
*
|
||||
* @param colorScheme - The current color theme
|
||||
* @param value - The color value
|
||||
* @param oldColor - The old color
|
||||
* @returns A color array
|
||||
*/
|
||||
export const packColorsWithColorScheme = (
|
||||
colorScheme: ColorScheme,
|
||||
value: string,
|
||||
oldColor: Color
|
||||
) => {
|
||||
const colors: { type: ModeType; value: string }[] = [
|
||||
{ type: 'normal', value },
|
||||
{ type: 'light', value },
|
||||
{ type: 'dark', value },
|
||||
];
|
||||
let type: PickColorType = 'palette';
|
||||
|
||||
if (typeof oldColor === 'object') {
|
||||
if ('normal' in oldColor) {
|
||||
type = 'normal';
|
||||
colors[0].value = oldColor.normal ?? value;
|
||||
} else {
|
||||
type = colorScheme;
|
||||
colors[1].value = oldColor.light ?? value;
|
||||
colors[2].value = oldColor.dark ?? value;
|
||||
}
|
||||
}
|
||||
|
||||
return { type, colors };
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Color, ColorScheme, Palette } from '@blocksuite/affine-model';
|
||||
import { isTransparent, resolveColor } from '@blocksuite/affine-model';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { ColorEvent } from '@blocksuite/affine-shared/utils';
|
||||
import { css, html, LitElement, nothing, svg, type TemplateResult } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
@@ -8,22 +9,6 @@ import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import isEqual from 'lodash.isequal';
|
||||
|
||||
export class ColorEvent extends Event {
|
||||
detail: Palette;
|
||||
|
||||
constructor(
|
||||
type: string,
|
||||
{
|
||||
detail,
|
||||
composed,
|
||||
bubbles,
|
||||
}: { detail: Palette; composed: boolean; bubbles: boolean }
|
||||
) {
|
||||
super(type, { bubbles, composed });
|
||||
this.detail = detail;
|
||||
}
|
||||
}
|
||||
|
||||
function TransparentIcon(hollowCircle = false) {
|
||||
const CircleIcon: TemplateResult | typeof nothing = hollowCircle
|
||||
? svg`<circle cx="10" cy="10" r="8" fill="white" />`
|
||||
|
||||
@@ -3,11 +3,11 @@ import {
|
||||
DefaultTheme,
|
||||
type StrokeStyle,
|
||||
} from '@blocksuite/affine-model';
|
||||
import type { ColorEvent } from '@blocksuite/affine-shared/utils';
|
||||
import { WithDisposable } from '@blocksuite/global/utils';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import type { ColorEvent } from './color-panel.js';
|
||||
import { type LineStyleEvent, LineStylesPanel } from './line-styles-panel.js';
|
||||
|
||||
export class StrokeStylePanel extends WithDisposable(LitElement) {
|
||||
|
||||
@@ -3,13 +3,13 @@ import {
|
||||
EditPropsStore,
|
||||
ThemeProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import type { ColorEvent } from '@blocksuite/affine-shared/utils';
|
||||
import type { GfxToolsFullOptionValue } from '@blocksuite/block-std/gfx';
|
||||
import { SignalWatcher } from '@blocksuite/global/utils';
|
||||
import { computed } from '@preact/signals-core';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import type { ColorEvent } from '../../panel/color-panel.js';
|
||||
import type { LineWidthEvent } from '../../panel/line-width-panel.js';
|
||||
import { EdgelessToolbarToolMixin } from '../mixins/tool.mixin.js';
|
||||
|
||||
|
||||
@@ -8,13 +8,13 @@ import {
|
||||
EditPropsStore,
|
||||
ThemeProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import type { ColorEvent } from '@blocksuite/affine-shared/utils';
|
||||
import type { GfxToolsFullOptionValue } from '@blocksuite/block-std/gfx';
|
||||
import { SignalWatcher } from '@blocksuite/global/utils';
|
||||
import { computed } from '@preact/signals-core';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import type { ColorEvent } from '../../panel/color-panel.js';
|
||||
import type { LineWidthEvent } from '../../panel/line-width-panel.js';
|
||||
import { EdgelessToolbarToolMixin } from '../mixins/tool.mixin.js';
|
||||
|
||||
|
||||
@@ -14,13 +14,13 @@ import {
|
||||
EditPropsStore,
|
||||
ThemeProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import type { ColorEvent } from '@blocksuite/affine-shared/utils';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
||||
import { computed, effect, type Signal, signal } from '@preact/signals-core';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import type { EdgelessRootBlockComponent } from '../../../edgeless-root-block.js';
|
||||
import type { ColorEvent } from '../../panel/color-panel.js';
|
||||
import { ShapeComponentConfig } from './shape-menu-config.js';
|
||||
|
||||
export class EdgelessShapeMenu extends SignalWatcher(
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { DefaultTheme } from '@blocksuite/affine-model';
|
||||
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||
import type { ColorEvent } from '@blocksuite/affine-shared/utils';
|
||||
import type { GfxToolsFullOptionValue } from '@blocksuite/block-std/gfx';
|
||||
import { computed } from '@preact/signals-core';
|
||||
import { css, html, LitElement, nothing } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import type { ColorEvent } from '../../panel/color-panel.js';
|
||||
import { EdgelessToolbarToolMixin } from '../mixins/tool.mixin.js';
|
||||
|
||||
export class EdgelessTextMenu extends EdgelessToolbarToolMixin(LitElement) {
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
|
||||
import type {
|
||||
EdgelessColorPickerButton,
|
||||
PickColorEvent,
|
||||
} from '@blocksuite/affine-components/color-picker';
|
||||
import {
|
||||
packColor,
|
||||
packColorsWithColorScheme,
|
||||
} from '@blocksuite/affine-components/color-picker';
|
||||
import type {
|
||||
BrushElementModel,
|
||||
BrushProps,
|
||||
@@ -9,18 +17,12 @@ import {
|
||||
LineWidth,
|
||||
resolveColor,
|
||||
} from '@blocksuite/affine-model';
|
||||
import type { ColorEvent } from '@blocksuite/affine-shared/utils';
|
||||
import { countBy, maxBy, WithDisposable } from '@blocksuite/global/utils';
|
||||
import { html, LitElement, nothing } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
import { when } from 'lit/directives/when.js';
|
||||
|
||||
import type { EdgelessColorPickerButton } from '../../edgeless/components/color-picker/button.js';
|
||||
import type { PickColorEvent } from '../../edgeless/components/color-picker/types.js';
|
||||
import {
|
||||
packColor,
|
||||
packColorsWithColorScheme,
|
||||
} from '../../edgeless/components/color-picker/utils.js';
|
||||
import type { ColorEvent } from '../../edgeless/components/panel/color-panel.js';
|
||||
import type { LineWidthEvent } from '../../edgeless/components/panel/line-width-panel.js';
|
||||
import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js';
|
||||
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
|
||||
import type {
|
||||
EdgelessColorPickerButton,
|
||||
PickColorEvent,
|
||||
} from '@blocksuite/affine-components/color-picker';
|
||||
import {
|
||||
packColor,
|
||||
packColorsWithColorScheme,
|
||||
} from '@blocksuite/affine-components/color-picker';
|
||||
import {
|
||||
AddTextIcon,
|
||||
ConnectorCWithArrowIcon,
|
||||
@@ -34,6 +42,7 @@ import {
|
||||
resolveColor,
|
||||
StrokeStyle,
|
||||
} from '@blocksuite/affine-model';
|
||||
import type { ColorEvent } from '@blocksuite/affine-shared/utils';
|
||||
import { countBy, maxBy, WithDisposable } from '@blocksuite/global/utils';
|
||||
import { html, LitElement, nothing, type TemplateResult } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
@@ -43,13 +52,6 @@ import { repeat } from 'lit/directives/repeat.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { when } from 'lit/directives/when.js';
|
||||
|
||||
import type { EdgelessColorPickerButton } from '../../edgeless/components/color-picker/button.js';
|
||||
import type { PickColorEvent } from '../../edgeless/components/color-picker/types.js';
|
||||
import {
|
||||
packColor,
|
||||
packColorsWithColorScheme,
|
||||
} from '../../edgeless/components/color-picker/utils.js';
|
||||
import type { ColorEvent } from '../../edgeless/components/panel/color-panel.js';
|
||||
import {
|
||||
type LineStyleEvent,
|
||||
LineStylesPanel,
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
|
||||
import type {
|
||||
EdgelessColorPickerButton,
|
||||
PickColorEvent,
|
||||
} from '@blocksuite/affine-components/color-picker';
|
||||
import {
|
||||
packColor,
|
||||
packColorsWithColorScheme,
|
||||
} from '@blocksuite/affine-components/color-picker';
|
||||
import {
|
||||
NoteIcon,
|
||||
RenameIcon,
|
||||
@@ -14,6 +22,7 @@ import {
|
||||
NoteDisplayMode,
|
||||
resolveColor,
|
||||
} from '@blocksuite/affine-model';
|
||||
import type { ColorEvent } from '@blocksuite/affine-shared/utils';
|
||||
import { matchFlavours } from '@blocksuite/affine-shared/utils';
|
||||
import { GfxExtensionIdentifier } from '@blocksuite/block-std/gfx';
|
||||
import {
|
||||
@@ -28,13 +37,6 @@ import { property, query } from 'lit/decorators.js';
|
||||
import { join } from 'lit/directives/join.js';
|
||||
import { when } from 'lit/directives/when.js';
|
||||
|
||||
import type { EdgelessColorPickerButton } from '../../edgeless/components/color-picker/button.js';
|
||||
import type { PickColorEvent } from '../../edgeless/components/color-picker/types.js';
|
||||
import {
|
||||
packColor,
|
||||
packColorsWithColorScheme,
|
||||
} from '../../edgeless/components/color-picker/utils.js';
|
||||
import type { ColorEvent } from '../../edgeless/components/panel/color-panel.js';
|
||||
import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js';
|
||||
import type { EdgelessFrameManager } from '../../edgeless/frame-manager.js';
|
||||
import { mountFrameTitleEditor } from '../../edgeless/utils/text.js';
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
|
||||
import type {
|
||||
EdgelessColorPickerButton,
|
||||
PickColorEvent,
|
||||
} from '@blocksuite/affine-components/color-picker';
|
||||
import {
|
||||
packColor,
|
||||
packColorsWithColorScheme,
|
||||
} from '@blocksuite/affine-components/color-picker';
|
||||
import {
|
||||
ExpandIcon,
|
||||
LineStyleIcon,
|
||||
@@ -35,14 +43,6 @@ import { join } from 'lit/directives/join.js';
|
||||
import { createRef, type Ref, ref } from 'lit/directives/ref.js';
|
||||
import { when } from 'lit/directives/when.js';
|
||||
|
||||
import type {
|
||||
EdgelessColorPickerButton,
|
||||
PickColorEvent,
|
||||
} from '../../edgeless/components/color-picker/index.js';
|
||||
import {
|
||||
packColor,
|
||||
packColorsWithColorScheme,
|
||||
} from '../../edgeless/components/color-picker/utils.js';
|
||||
import {
|
||||
type LineStyleEvent,
|
||||
LineStylesPanel,
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
|
||||
import type {
|
||||
EdgelessColorPickerButton,
|
||||
PickColorEvent,
|
||||
} from '@blocksuite/affine-components/color-picker';
|
||||
import {
|
||||
packColor,
|
||||
packColorsWithColorScheme,
|
||||
} from '@blocksuite/affine-components/color-picker';
|
||||
import {
|
||||
AddTextIcon,
|
||||
ChangeShapeIcon,
|
||||
@@ -26,6 +34,7 @@ import {
|
||||
ShapeStyle,
|
||||
StrokeStyle,
|
||||
} from '@blocksuite/affine-model';
|
||||
import type { ColorEvent } from '@blocksuite/affine-shared/utils';
|
||||
import { countBy, maxBy, WithDisposable } from '@blocksuite/global/utils';
|
||||
import { css, html, LitElement, nothing, type TemplateResult } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
@@ -36,13 +45,6 @@ import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { when } from 'lit/directives/when.js';
|
||||
import isEqual from 'lodash.isequal';
|
||||
|
||||
import type { EdgelessColorPickerButton } from '../../edgeless/components/color-picker/button.js';
|
||||
import type { PickColorEvent } from '../../edgeless/components/color-picker/types.js';
|
||||
import {
|
||||
packColor,
|
||||
packColorsWithColorScheme,
|
||||
} from '../../edgeless/components/color-picker/utils.js';
|
||||
import type { ColorEvent } from '../../edgeless/components/panel/color-panel.js';
|
||||
import {
|
||||
type LineStyleEvent,
|
||||
LineStylesPanel,
|
||||
|
||||
@@ -4,6 +4,14 @@ import {
|
||||
normalizeShapeBound,
|
||||
TextUtils,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import type {
|
||||
EdgelessColorPickerButton,
|
||||
PickColorEvent,
|
||||
} from '@blocksuite/affine-components/color-picker';
|
||||
import {
|
||||
packColor,
|
||||
packColorsWithColorScheme,
|
||||
} from '@blocksuite/affine-components/color-picker';
|
||||
import {
|
||||
SmallArrowDownIcon,
|
||||
TextAlignCenterIcon,
|
||||
@@ -25,6 +33,7 @@ import {
|
||||
TextElementModel,
|
||||
type TextStyleProps,
|
||||
} from '@blocksuite/affine-model';
|
||||
import type { ColorEvent } from '@blocksuite/affine-shared/utils';
|
||||
import {
|
||||
Bound,
|
||||
countBy,
|
||||
@@ -37,15 +46,6 @@ import { choose } from 'lit/directives/choose.js';
|
||||
import { join } from 'lit/directives/join.js';
|
||||
import { when } from 'lit/directives/when.js';
|
||||
|
||||
import type {
|
||||
EdgelessColorPickerButton,
|
||||
PickColorEvent,
|
||||
} from '../../edgeless/components/color-picker/index.js';
|
||||
import {
|
||||
packColor,
|
||||
packColorsWithColorScheme,
|
||||
} from '../../edgeless/components/color-picker/utils.js';
|
||||
import type { ColorEvent } from '../../edgeless/components/panel/color-panel.js';
|
||||
import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js';
|
||||
|
||||
const FONT_SIZE_LIST = [
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { parseStringToRgba } from '@blocksuite/affine-components/color-picker';
|
||||
import {
|
||||
ColorScheme,
|
||||
FrameBlockModel,
|
||||
@@ -22,7 +23,6 @@ import { themeToVar } from '@toeverything/theme/v2';
|
||||
import { LitElement } from 'lit';
|
||||
import { property, state } from 'lit/decorators.js';
|
||||
|
||||
import { parseStringToRgba } from '../../edgeless/components/color-picker/utils.js';
|
||||
import type { EdgelessRootService } from '../../edgeless/index.js';
|
||||
import { frameTitleStyle, frameTitleStyleVars } from './styles.js';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user