refactor(editor): extract color picker component (#9456)

This commit is contained in:
Saul-Mirone
2024-12-31 07:23:37 +00:00
parent 353eaf7fbe
commit 597b631918
29 changed files with 106 additions and 85 deletions

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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]/,
};

View File

@@ -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;
}
}

View File

@@ -1,3 +0,0 @@
export * from './button.js';
export * from './color-picker.js';
export * from './types.js';

View File

@@ -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;
}
`;

View File

@@ -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 };

View File

@@ -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 };
};

View File

@@ -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" />`

View File

@@ -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) {

View File

@@ -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';

View File

@@ -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';

View File

@@ -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(

View File

@@ -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) {

View File

@@ -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';

View File

@@ -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,

View File

@@ -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';

View File

@@ -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,

View File

@@ -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,

View File

@@ -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 = [

View File

@@ -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';