xiaodao-painter 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +201 -0
- package/dist/types/components/painter/ColorPalette.vue.d.ts +3 -0
- package/dist/types/components/painter/Painter.vue.d.ts +22 -0
- package/dist/types/components/painter/SelectionOverlay.vue.d.ts +8 -0
- package/dist/types/components/painter/StrokeRenderer.vue.d.ts +8 -0
- package/dist/types/components/painter/Toolbar.vue.d.ts +3 -0
- package/dist/types/components/painter/Whiteboard.vue.d.ts +3 -0
- package/dist/types/components/painter/composables/useDrawing.d.ts +23 -0
- package/dist/types/components/painter/composables/useI18n.d.ts +13 -0
- package/dist/types/components/painter/stores/canvas.d.ts +238 -0
- package/dist/types/components/painter/stores/tools.d.ts +6 -0
- package/dist/types/components/painter/types/index.d.ts +37 -0
- package/dist/types/components/painter/utils/geometry.d.ts +13 -0
- package/dist/types/components/painter/utils/i18n.d.ts +2 -0
- package/dist/types/components/painter/utils/svg.d.ts +7 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/xiaodao-painter.css +2 -0
- package/dist/xiaodao-painter.es.js +2522 -0
- package/dist/xiaodao-painter.umd.js +1 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Leeqwind
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# xiaodao-painter
|
|
2
|
+
|
|
3
|
+
A self-contained, embeddable SVG whiteboard component for Vue 3. Built with TypeScript, Pinia, and Vite.
|
|
4
|
+
|
|
5
|
+
Supports drawing tools, selection, resize, pan/zoom, undo/redo, clipboard, layer ordering, color palettes, i18n, and light/dark themes — all with two-way data binding via `v-model`.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **9 tools** — Select, Pan, Zoom, Pencil, Line, Circle, Rectangle, Triangle, Star
|
|
12
|
+
- **Selection & manipulation** — click to select, box-select, drag-move, 8-handle resize (Shift = symmetric), layer reorder (bring to front / send to back)
|
|
13
|
+
- **Pan & zoom** — grab-drag panning, discrete zoom steps (10% – 1000%) anchored at cursor position
|
|
14
|
+
- **Undo / redo** — full stroke-level undo/redo stacks
|
|
15
|
+
- **Internal clipboard** — cut, copy, paste (within the whiteboard, offset +20 px)
|
|
16
|
+
- **Color system** — stroke (foreground) and fill (background) color slots, per-stroke color application to selections, canvas background color with transparent grid support
|
|
17
|
+
- **Themes** — light and dark, with auto-switching foreground color on theme change
|
|
18
|
+
- **Internationalization** — Chinese (zh-CN) and English (en-US) built in
|
|
19
|
+
- **Keyboard shortcuts** — Delete, Escape, Ctrl+Z / Ctrl+Y / Ctrl+X / Ctrl+C / Ctrl+V
|
|
20
|
+
- **Modifier keys** — Ctrl = constrain 1:1 ratio, Shift = center-out expansion / 45° line snap
|
|
21
|
+
- **Two-way binding** — parent reads/writes full drawing data via `v-model`
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
Install the package from npm:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install xiaodao-painter
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or with pnpm / yarn:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pnpm add xiaodao-painter
|
|
35
|
+
yarn add xiaodao-painter
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
First, make sure [Pinia](https://pinia.vuejs.org/) is registered in your Vue app (xiaodao-painter depends on Pinia internally):
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
// main.ts
|
|
44
|
+
import { createApp } from 'vue'
|
|
45
|
+
import { createPinia } from 'pinia'
|
|
46
|
+
import App from './App.vue'
|
|
47
|
+
|
|
48
|
+
const app = createApp(App)
|
|
49
|
+
app.use(createPinia())
|
|
50
|
+
app.mount('#app')
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Then import and use the `Painter` component:
|
|
54
|
+
|
|
55
|
+
```vue
|
|
56
|
+
<script setup lang="ts">
|
|
57
|
+
import { ref, watch } from 'vue'
|
|
58
|
+
import { Painter } from 'xiaodao-painter'
|
|
59
|
+
import type { PainterData } from 'xiaodao-painter'
|
|
60
|
+
|
|
61
|
+
const data = ref<PainterData>({
|
|
62
|
+
strokes: [],
|
|
63
|
+
canvasWidth: 800,
|
|
64
|
+
canvasHeight: 600,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
watch(data, (val) => {
|
|
68
|
+
console.log('Drawing data changed:', val)
|
|
69
|
+
}, { deep: true })
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<template>
|
|
73
|
+
<Painter
|
|
74
|
+
v-model="data"
|
|
75
|
+
theme="light"
|
|
76
|
+
locale="zh-CN"
|
|
77
|
+
/>
|
|
78
|
+
</template>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
With explicit dimensions and dark theme:
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<Painter
|
|
85
|
+
v-model="data"
|
|
86
|
+
theme="dark"
|
|
87
|
+
locale="en-US"
|
|
88
|
+
width="1200"
|
|
89
|
+
height="800"
|
|
90
|
+
/>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
With a custom canvas background color:
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<Painter
|
|
97
|
+
v-model="data"
|
|
98
|
+
:modelValue="{
|
|
99
|
+
strokes: [],
|
|
100
|
+
canvasWidth: 800,
|
|
101
|
+
canvasHeight: 600,
|
|
102
|
+
canvasBackgroundColor: '#f5f5f5',
|
|
103
|
+
}"
|
|
104
|
+
/>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Props
|
|
108
|
+
|
|
109
|
+
| Prop | Type | Default | Description |
|
|
110
|
+
| --- | --- | --- | --- |
|
|
111
|
+
| `modelValue` | `PainterData` | `{ strokes: [], canvasWidth: 800, canvasHeight: 600 }` | Two-way bound drawing data (supports `canvasBackgroundColor`) |
|
|
112
|
+
| `theme` | `'light' \| 'dark'` | `'light'` | UI theme |
|
|
113
|
+
| `locale` | `string` | `'zh-CN'` | UI language (`'zh-CN'` or `'en-US'`) |
|
|
114
|
+
| `width` | `string \| number` | `'100%'` | Component width (CSS value or px number) |
|
|
115
|
+
| `height` | `string \| number` | `'100%'` | Component height (CSS value or px number) |
|
|
116
|
+
|
|
117
|
+
## Data Model
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
interface PainterData {
|
|
121
|
+
strokes: Stroke[]
|
|
122
|
+
canvasWidth: number
|
|
123
|
+
canvasHeight: number
|
|
124
|
+
canvasBackgroundColor?: string
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
type ToolType = 'select' | 'pencil' | 'line' | 'circle' | 'rect' | 'triangle' | 'star' | 'text' | 'pan' | 'zoom'
|
|
128
|
+
|
|
129
|
+
interface Point {
|
|
130
|
+
x: number
|
|
131
|
+
y: number
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
interface Stroke {
|
|
135
|
+
id: string
|
|
136
|
+
type: ToolType
|
|
137
|
+
x: number
|
|
138
|
+
y: number
|
|
139
|
+
width: number
|
|
140
|
+
height: number
|
|
141
|
+
points: Point[]
|
|
142
|
+
strokeColor: string
|
|
143
|
+
fillColor: string
|
|
144
|
+
strokeWidth: number
|
|
145
|
+
text?: string
|
|
146
|
+
fontSize?: number
|
|
147
|
+
textAlign?: 'left' | 'center' | 'right'
|
|
148
|
+
textColor?: string
|
|
149
|
+
textAutoWidth?: boolean
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Keyboard Shortcuts
|
|
154
|
+
|
|
155
|
+
| Key | Action |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| `Delete` / `Backspace` | Delete selected strokes |
|
|
158
|
+
| `Escape` | Clear selection |
|
|
159
|
+
| `Ctrl + Z` | Undo |
|
|
160
|
+
| `Ctrl + Y` / `Ctrl + Shift + Z` | Redo |
|
|
161
|
+
| `Ctrl + X` | Cut |
|
|
162
|
+
| `Ctrl + C` | Copy |
|
|
163
|
+
| `Ctrl + V` | Paste (offset +20 px) |
|
|
164
|
+
|
|
165
|
+
## Project Structure
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
src/
|
|
169
|
+
components/painter/
|
|
170
|
+
Painter.vue # Public entry (props, v-model, theme sync)
|
|
171
|
+
Whiteboard.vue # SVG canvas, keyboard shortcuts, pan/zoom centering
|
|
172
|
+
Toolbar.vue # Left toolbar (tools, canvas settings, color indicators)
|
|
173
|
+
ColorPalette.vue # Color picker popover
|
|
174
|
+
SelectionOverlay.vue # Selection bounding box + resize handles
|
|
175
|
+
StrokeRenderer.vue # Renders a single stroke as SVG
|
|
176
|
+
composables/
|
|
177
|
+
useDrawing.ts # Mouse interaction engine (draw, select, move, resize)
|
|
178
|
+
useI18n.ts # Locale and theme provide/inject
|
|
179
|
+
stores/
|
|
180
|
+
canvas.ts # Main store (strokes, colors, pan/zoom, undo, clipboard)
|
|
181
|
+
tools.ts # Active tool state
|
|
182
|
+
utils/
|
|
183
|
+
geometry.ts # Hit testing, star/triangle vertices, bounding box
|
|
184
|
+
svg.ts # Stroke-to-SVG element conversion
|
|
185
|
+
i18n.ts # zh-CN / en-US dictionaries
|
|
186
|
+
types/
|
|
187
|
+
index.ts # TypeScript types and default constants
|
|
188
|
+
style.css # CSS custom properties (themes)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Development
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
npm run dev # Start dev server
|
|
195
|
+
npm run build # Type-check and build for production
|
|
196
|
+
npm run preview # Preview production build
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
MIT
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PainterData } from './types/index.ts';
|
|
2
|
+
import './style.css';
|
|
3
|
+
type __VLS_Props = {
|
|
4
|
+
theme?: 'light' | 'dark';
|
|
5
|
+
locale?: string;
|
|
6
|
+
modelValue?: PainterData;
|
|
7
|
+
width?: string | number;
|
|
8
|
+
height?: string | number;
|
|
9
|
+
};
|
|
10
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
11
|
+
"update:modelValue": (data: PainterData) => any;
|
|
12
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
13
|
+
"onUpdate:modelValue"?: ((data: PainterData) => any) | undefined;
|
|
14
|
+
}>, {
|
|
15
|
+
height: string | number;
|
|
16
|
+
width: string | number;
|
|
17
|
+
theme: "light" | "dark";
|
|
18
|
+
locale: string;
|
|
19
|
+
modelValue: PainterData;
|
|
20
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
21
|
+
declare const _default: typeof __VLS_export;
|
|
22
|
+
export default _default;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Stroke } from './types';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
stroke: Stroke;
|
|
4
|
+
startResize: (strokeId: string, handle: string, cursor: string, event: MouseEvent | TouchEvent) => void;
|
|
5
|
+
};
|
|
6
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Stroke } from './types';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
stroke: Stroke;
|
|
4
|
+
isSelected: boolean;
|
|
5
|
+
};
|
|
6
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Ref } from 'vue';
|
|
2
|
+
import type { Point, Stroke } from '../types';
|
|
3
|
+
export declare function useDrawing(whiteboardRef: Ref<HTMLElement | null>): {
|
|
4
|
+
isDrawing: Ref<boolean, boolean>;
|
|
5
|
+
previewStroke: import("vue").ComputedRef<Stroke | null>;
|
|
6
|
+
onMouseDown: (event: MouseEvent) => void;
|
|
7
|
+
onMouseMove: (event: MouseEvent) => void;
|
|
8
|
+
onMouseUp: (event: MouseEvent) => void;
|
|
9
|
+
onTouchStart: (event: TouchEvent) => void;
|
|
10
|
+
onTouchMove: (event: TouchEvent) => void;
|
|
11
|
+
onTouchEnd: (event: TouchEvent) => void;
|
|
12
|
+
getSVGPoint: (event: MouseEvent) => Point;
|
|
13
|
+
updateModifiers: (e: KeyboardEvent) => void;
|
|
14
|
+
clearModifiers: () => void;
|
|
15
|
+
isResizing: Ref<boolean, boolean>;
|
|
16
|
+
resizeCursor: Ref<string, string>;
|
|
17
|
+
startResize: (strokeId: string, handle: string, cursor: string, event: MouseEvent | TouchEvent) => void;
|
|
18
|
+
commitTextEdit: (strokeId: string, text: string) => void;
|
|
19
|
+
cancelTextEdit: (strokeId: string) => void;
|
|
20
|
+
startEditText: (strokeId: string) => void;
|
|
21
|
+
saveCurrentTextEdit: () => void;
|
|
22
|
+
setTextContentGetter: (getter: () => string | null) => void;
|
|
23
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Locale } from '../utils/i18n';
|
|
2
|
+
export declare const I18N_KEY: string;
|
|
3
|
+
export declare const THEME_KEY: string;
|
|
4
|
+
export declare function provideLocale(locale: Locale): void;
|
|
5
|
+
export declare function useI18n(): {
|
|
6
|
+
locale: Locale;
|
|
7
|
+
t: (key: string) => string;
|
|
8
|
+
};
|
|
9
|
+
export declare function provideTheme(theme: 'light' | 'dark'): void;
|
|
10
|
+
export declare function useTheme(): 'light' | 'dark';
|
|
11
|
+
export declare const ROOT_EL_KEY: unique symbol;
|
|
12
|
+
export declare function provideRootEl(el: Readonly<import('vue').Ref<HTMLElement | null>>): void;
|
|
13
|
+
export declare function useRootEl(): import('vue').Ref<HTMLElement | null> | null;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import type { Stroke } from '../types';
|
|
2
|
+
export declare const useCanvasStore: import("pinia").SetupStoreDefinition<"canvas", {
|
|
3
|
+
strokes: import("vue").Ref<{
|
|
4
|
+
id: string;
|
|
5
|
+
type: import("../types").ToolType;
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
points: {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
}[];
|
|
14
|
+
strokeColor: string;
|
|
15
|
+
fillColor: string;
|
|
16
|
+
strokeWidth: number;
|
|
17
|
+
text?: string | undefined;
|
|
18
|
+
fontSize?: number | undefined;
|
|
19
|
+
textAlign?: "left" | "center" | "right" | undefined;
|
|
20
|
+
textColor?: string | undefined;
|
|
21
|
+
textAutoWidth?: boolean | undefined;
|
|
22
|
+
}[], Stroke[] | {
|
|
23
|
+
id: string;
|
|
24
|
+
type: import("../types").ToolType;
|
|
25
|
+
x: number;
|
|
26
|
+
y: number;
|
|
27
|
+
width: number;
|
|
28
|
+
height: number;
|
|
29
|
+
points: {
|
|
30
|
+
x: number;
|
|
31
|
+
y: number;
|
|
32
|
+
}[];
|
|
33
|
+
strokeColor: string;
|
|
34
|
+
fillColor: string;
|
|
35
|
+
strokeWidth: number;
|
|
36
|
+
text?: string | undefined;
|
|
37
|
+
fontSize?: number | undefined;
|
|
38
|
+
textAlign?: "left" | "center" | "right" | undefined;
|
|
39
|
+
textColor?: string | undefined;
|
|
40
|
+
textAutoWidth?: boolean | undefined;
|
|
41
|
+
}[]>;
|
|
42
|
+
selectedStrokeIds: import("vue").Ref<Set<string> & Omit<Set<string>, keyof Set<any>>, Set<string> | (Set<string> & Omit<Set<string>, keyof Set<any>>)>;
|
|
43
|
+
selectedStrokeId: import("vue").ComputedRef<string | null>;
|
|
44
|
+
selectedStroke: import("vue").ComputedRef<{
|
|
45
|
+
id: string;
|
|
46
|
+
type: import("../types").ToolType;
|
|
47
|
+
x: number;
|
|
48
|
+
y: number;
|
|
49
|
+
width: number;
|
|
50
|
+
height: number;
|
|
51
|
+
points: {
|
|
52
|
+
x: number;
|
|
53
|
+
y: number;
|
|
54
|
+
}[];
|
|
55
|
+
strokeColor: string;
|
|
56
|
+
fillColor: string;
|
|
57
|
+
strokeWidth: number;
|
|
58
|
+
text?: string | undefined;
|
|
59
|
+
fontSize?: number | undefined;
|
|
60
|
+
textAlign?: "left" | "center" | "right" | undefined;
|
|
61
|
+
textColor?: string | undefined;
|
|
62
|
+
textAutoWidth?: boolean | undefined;
|
|
63
|
+
} | null>;
|
|
64
|
+
selectedStrokes: import("vue").ComputedRef<{
|
|
65
|
+
id: string;
|
|
66
|
+
type: import("../types").ToolType;
|
|
67
|
+
x: number;
|
|
68
|
+
y: number;
|
|
69
|
+
width: number;
|
|
70
|
+
height: number;
|
|
71
|
+
points: {
|
|
72
|
+
x: number;
|
|
73
|
+
y: number;
|
|
74
|
+
}[];
|
|
75
|
+
strokeColor: string;
|
|
76
|
+
fillColor: string;
|
|
77
|
+
strokeWidth: number;
|
|
78
|
+
text?: string | undefined;
|
|
79
|
+
fontSize?: number | undefined;
|
|
80
|
+
textAlign?: "left" | "center" | "right" | undefined;
|
|
81
|
+
textColor?: string | undefined;
|
|
82
|
+
textAutoWidth?: boolean | undefined;
|
|
83
|
+
}[]>;
|
|
84
|
+
selectedStrokeColor: import("vue").ComputedRef<string | null>;
|
|
85
|
+
selectedFillColor: import("vue").ComputedRef<string | null>;
|
|
86
|
+
selectedTextColor: import("vue").ComputedRef<string | null>;
|
|
87
|
+
foregroundColor: import("vue").Ref<string, string>;
|
|
88
|
+
backgroundColor: import("vue").Ref<string, string>;
|
|
89
|
+
activeColorSlot: import("vue").Ref<"background" | "textColor" | "foreground", "background" | "textColor" | "foreground">;
|
|
90
|
+
showColorPalette: import("vue").Ref<boolean, boolean>;
|
|
91
|
+
isSelecting: import("vue").Ref<boolean, boolean>;
|
|
92
|
+
selectionBox: import("vue").Ref<{
|
|
93
|
+
x: number;
|
|
94
|
+
y: number;
|
|
95
|
+
width: number;
|
|
96
|
+
height: number;
|
|
97
|
+
} | null, {
|
|
98
|
+
x: number;
|
|
99
|
+
y: number;
|
|
100
|
+
width: number;
|
|
101
|
+
height: number;
|
|
102
|
+
} | {
|
|
103
|
+
x: number;
|
|
104
|
+
y: number;
|
|
105
|
+
width: number;
|
|
106
|
+
height: number;
|
|
107
|
+
} | null>;
|
|
108
|
+
toggleColorPalette: () => void;
|
|
109
|
+
hideColorPalette: () => void;
|
|
110
|
+
addStroke: (stroke: Stroke) => void;
|
|
111
|
+
updateStroke: (id: string, changes: Partial<Stroke>) => void;
|
|
112
|
+
selectStroke: (id: string | null) => void;
|
|
113
|
+
selectStrokes: (ids: string[]) => void;
|
|
114
|
+
clearSelection: () => void;
|
|
115
|
+
isSelected: (id: string) => boolean;
|
|
116
|
+
deleteSelectedStrokes: () => void;
|
|
117
|
+
setColorSlot: (slot: "foreground" | "background" | "textColor") => void;
|
|
118
|
+
setColor: (color: string) => void;
|
|
119
|
+
buildStroke: (type: Stroke["type"], x: number, y: number, width: number, height: number, points: Stroke["points"]) => Stroke;
|
|
120
|
+
strokeWidth: import("vue").Ref<number, number>;
|
|
121
|
+
setStrokeWidth: (w: number) => void;
|
|
122
|
+
canvasBackgroundColor: import("vue").Ref<string, string>;
|
|
123
|
+
setCanvasBackgroundColor: (color: string) => void;
|
|
124
|
+
canUndo: import("vue").ComputedRef<boolean>;
|
|
125
|
+
canRedo: import("vue").ComputedRef<boolean>;
|
|
126
|
+
undoStack: import("vue").Ref<{
|
|
127
|
+
id: string;
|
|
128
|
+
type: import("../types").ToolType;
|
|
129
|
+
x: number;
|
|
130
|
+
y: number;
|
|
131
|
+
width: number;
|
|
132
|
+
height: number;
|
|
133
|
+
points: {
|
|
134
|
+
x: number;
|
|
135
|
+
y: number;
|
|
136
|
+
}[];
|
|
137
|
+
strokeColor: string;
|
|
138
|
+
fillColor: string;
|
|
139
|
+
strokeWidth: number;
|
|
140
|
+
text?: string | undefined;
|
|
141
|
+
fontSize?: number | undefined;
|
|
142
|
+
textAlign?: "left" | "center" | "right" | undefined;
|
|
143
|
+
textColor?: string | undefined;
|
|
144
|
+
textAutoWidth?: boolean | undefined;
|
|
145
|
+
}[][], Stroke[][] | {
|
|
146
|
+
id: string;
|
|
147
|
+
type: import("../types").ToolType;
|
|
148
|
+
x: number;
|
|
149
|
+
y: number;
|
|
150
|
+
width: number;
|
|
151
|
+
height: number;
|
|
152
|
+
points: {
|
|
153
|
+
x: number;
|
|
154
|
+
y: number;
|
|
155
|
+
}[];
|
|
156
|
+
strokeColor: string;
|
|
157
|
+
fillColor: string;
|
|
158
|
+
strokeWidth: number;
|
|
159
|
+
text?: string | undefined;
|
|
160
|
+
fontSize?: number | undefined;
|
|
161
|
+
textAlign?: "left" | "center" | "right" | undefined;
|
|
162
|
+
textColor?: string | undefined;
|
|
163
|
+
textAutoWidth?: boolean | undefined;
|
|
164
|
+
}[][]>;
|
|
165
|
+
pushUndo: () => void;
|
|
166
|
+
undo: () => void;
|
|
167
|
+
redo: () => void;
|
|
168
|
+
moveSelectedUp: () => void;
|
|
169
|
+
moveSelectedDown: () => void;
|
|
170
|
+
syncFromParent: (data: Stroke[]) => void;
|
|
171
|
+
getStrokeDisplayBounds: (stroke: Stroke) => {
|
|
172
|
+
x: number;
|
|
173
|
+
y: number;
|
|
174
|
+
width: number;
|
|
175
|
+
height: number;
|
|
176
|
+
};
|
|
177
|
+
clipboard: import("vue").Ref<{
|
|
178
|
+
id: string;
|
|
179
|
+
type: import("../types").ToolType;
|
|
180
|
+
x: number;
|
|
181
|
+
y: number;
|
|
182
|
+
width: number;
|
|
183
|
+
height: number;
|
|
184
|
+
points: {
|
|
185
|
+
x: number;
|
|
186
|
+
y: number;
|
|
187
|
+
}[];
|
|
188
|
+
strokeColor: string;
|
|
189
|
+
fillColor: string;
|
|
190
|
+
strokeWidth: number;
|
|
191
|
+
text?: string | undefined;
|
|
192
|
+
fontSize?: number | undefined;
|
|
193
|
+
textAlign?: "left" | "center" | "right" | undefined;
|
|
194
|
+
textColor?: string | undefined;
|
|
195
|
+
textAutoWidth?: boolean | undefined;
|
|
196
|
+
}[], Stroke[] | {
|
|
197
|
+
id: string;
|
|
198
|
+
type: import("../types").ToolType;
|
|
199
|
+
x: number;
|
|
200
|
+
y: number;
|
|
201
|
+
width: number;
|
|
202
|
+
height: number;
|
|
203
|
+
points: {
|
|
204
|
+
x: number;
|
|
205
|
+
y: number;
|
|
206
|
+
}[];
|
|
207
|
+
strokeColor: string;
|
|
208
|
+
fillColor: string;
|
|
209
|
+
strokeWidth: number;
|
|
210
|
+
text?: string | undefined;
|
|
211
|
+
fontSize?: number | undefined;
|
|
212
|
+
textAlign?: "left" | "center" | "right" | undefined;
|
|
213
|
+
textColor?: string | undefined;
|
|
214
|
+
textAutoWidth?: boolean | undefined;
|
|
215
|
+
}[]>;
|
|
216
|
+
cutSelectedStrokes: () => void;
|
|
217
|
+
copySelectedStrokes: () => void;
|
|
218
|
+
pasteStrokes: () => void;
|
|
219
|
+
canvasWidth: import("vue").Ref<number, number>;
|
|
220
|
+
canvasHeight: import("vue").Ref<number, number>;
|
|
221
|
+
panX: import("vue").Ref<number, number>;
|
|
222
|
+
panY: import("vue").Ref<number, number>;
|
|
223
|
+
zoomLevel: import("vue").Ref<number, number>;
|
|
224
|
+
showZoomIndicator: import("vue").Ref<boolean, boolean>;
|
|
225
|
+
viewBox: import("vue").ComputedRef<string>;
|
|
226
|
+
canvasTransform: import("vue").ComputedRef<string>;
|
|
227
|
+
setPan: (x: number, y: number) => void;
|
|
228
|
+
setCanvasSize: (w: number, h: number) => void;
|
|
229
|
+
zoomAt: (screenX: number, screenY: number, factor: number) => void;
|
|
230
|
+
continuousZoomAt: (screenX: number, screenY: number, newZoom: number) => void;
|
|
231
|
+
getZoomPercent: () => number;
|
|
232
|
+
dataVersion: import("vue").Ref<number, number>;
|
|
233
|
+
theme: import("vue").Ref<"dark" | "light", "dark" | "light">;
|
|
234
|
+
textColor: import("vue").Ref<string, string>;
|
|
235
|
+
editingTextId: import("vue").Ref<string | null, string | null>;
|
|
236
|
+
hoveredTextId: import("vue").Ref<string | null, string | null>;
|
|
237
|
+
reset: () => void;
|
|
238
|
+
}>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type ToolType = 'select' | 'pencil' | 'line' | 'circle' | 'rect' | 'triangle' | 'star' | 'text' | 'pan' | 'zoom';
|
|
2
|
+
export interface Point {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
}
|
|
6
|
+
export interface Stroke {
|
|
7
|
+
id: string;
|
|
8
|
+
type: ToolType;
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
points: Point[];
|
|
14
|
+
strokeColor: string;
|
|
15
|
+
fillColor: string;
|
|
16
|
+
strokeWidth: number;
|
|
17
|
+
text?: string;
|
|
18
|
+
fontSize?: number;
|
|
19
|
+
textAlign?: 'left' | 'center' | 'right';
|
|
20
|
+
textColor?: string;
|
|
21
|
+
textAutoWidth?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface CanvasConfig {
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
}
|
|
27
|
+
export interface PainterData {
|
|
28
|
+
strokes: Stroke[];
|
|
29
|
+
canvasWidth: number;
|
|
30
|
+
canvasHeight: number;
|
|
31
|
+
canvasBackgroundColor?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare const DEFAULT_CANVAS_WIDTH = 800;
|
|
34
|
+
export declare const DEFAULT_CANVAS_HEIGHT = 600;
|
|
35
|
+
export declare const STROKE_WIDTH = 3;
|
|
36
|
+
export declare const DEFAULT_FONT_SIZE = 16;
|
|
37
|
+
export declare const HIT_THRESHOLD = 8;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Point, Stroke } from '../types';
|
|
2
|
+
export declare function starVertices(cx: number, cy: number, rx: number, ry: number, flipY?: boolean, innerRatio?: number): Point[];
|
|
3
|
+
export declare function triangleVertices(x: number, y: number, w: number, h: number): Point[];
|
|
4
|
+
export declare function pointToSegmentDistance(p: Point, a: Point, b: Point): number;
|
|
5
|
+
export declare function pointInPolygon(p: Point, polygon: Point[]): boolean;
|
|
6
|
+
export declare function pointInEllipse(p: Point, cx: number, cy: number, rx: number, ry: number): boolean;
|
|
7
|
+
export declare function computeBoundingBox(points: Point[]): {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
};
|
|
13
|
+
export declare function hitTestStroke(p: Point, stroke: Stroke): boolean;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Stroke, Point } from '../types';
|
|
2
|
+
export declare function pencilToPathD(points: Point[]): string;
|
|
3
|
+
export interface SvgAttrs {
|
|
4
|
+
tag: string;
|
|
5
|
+
attrs: Record<string, string | number>;
|
|
6
|
+
}
|
|
7
|
+
export declare function strokeToSvg(stroke: Stroke): SvgAttrs;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import Painter from './components/painter/Painter.vue';
|
|
2
|
+
import type { PainterData, Stroke, Point, ToolType, CanvasConfig } from './components/painter/types';
|
|
3
|
+
import { DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT, STROKE_WIDTH, DEFAULT_FONT_SIZE, HIT_THRESHOLD } from './components/painter/types';
|
|
4
|
+
import './components/painter/style.css';
|
|
5
|
+
export { Painter, Painter as default, DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT, STROKE_WIDTH, DEFAULT_FONT_SIZE, HIT_THRESHOLD, };
|
|
6
|
+
export type { PainterData, Stroke, Point, ToolType, CanvasConfig, };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
.toolbar[data-v-9532ade7]{background:var(--wb-surface-toolbar);border-right:1px solid var(--wb-border);flex-direction:column;flex-shrink:0;align-items:center;gap:2px;width:52px;padding:8px 6px;display:flex}.tool-group[data-v-9532ade7]{flex-direction:column;gap:2px;display:flex}.tools-scroll[data-v-9532ade7]{scrollbar-width:none;-ms-overflow-style:none;flex-direction:column;flex-shrink:1;gap:2px;min-height:0;display:flex;overflow:hidden auto}.tools-scroll[data-v-9532ade7]::-webkit-scrollbar{display:none}.scroll-btn[data-v-9532ade7]{border-radius:var(--wb-radius-sm);width:40px;height:22px;color:var(--wb-text-secondary);cursor:pointer;transition:all var(--wb-transition);background:0 0;border:none;flex-shrink:0;justify-content:center;align-items:center;padding:0;display:flex}.scroll-btn[data-v-9532ade7]:hover:not(:disabled){background:var(--wb-surface-hover);color:var(--wb-text)}.scroll-btn[data-v-9532ade7]:disabled{opacity:.25;cursor:default}.scroll-icon[data-v-9532ade7]{pointer-events:none;width:14px;height:14px}.tool-btn[data-v-9532ade7]{border-radius:var(--wb-radius-sm);width:40px;height:40px;color:var(--wb-text-secondary);cursor:pointer;transition:all var(--wb-transition);background:0 0;border:none;justify-content:center;align-items:center;padding:0;display:flex}.tool-btn[data-v-9532ade7]:hover{background:var(--wb-surface-hover);color:var(--wb-text)}.tool-btn.active[data-v-9532ade7]{background:var(--wb-accent);color:#fff}.tool-icon[data-v-9532ade7]{pointer-events:none;width:22px;height:22px}.spacer[data-v-9532ade7]{flex:1}.canvas-size-wrapper[data-v-9532ade7]{margin-bottom:6px;position:relative}.canvas-size-backdrop[data-v-9532ade7]{z-index:9998;position:fixed;inset:0}.canvas-size-popover[data-v-9532ade7]{background:var(--wb-surface-toolbar);border:1px solid var(--wb-border);z-index:9999;border-radius:8px;min-width:180px;padding:12px 14px;box-shadow:0 2px 8px #0000001a}.popover-title[data-v-9532ade7]{color:var(--wb-text);margin-bottom:10px;font-size:13px;font-weight:600}.popover-field[data-v-9532ade7]{align-items:center;gap:6px;margin-bottom:8px;display:flex}.popover-label[data-v-9532ade7]{color:var(--wb-text-secondary);flex-shrink:0;width:32px;font-size:12px}.popover-input[data-v-9532ade7]{border:1px solid var(--wb-border);background:var(--wb-surface);width:70px;height:28px;color:var(--wb-text);text-align:right;border-radius:4px;outline:none;flex:1;padding:0 6px;font-size:13px}.popover-input[data-v-9532ade7]:focus{border-color:var(--wb-accent)}.popover-unit[data-v-9532ade7]{color:var(--wb-text-secondary);flex-shrink:0;width:20px;font-size:11px}.bg-color-row[data-v-9532ade7]{align-items:center;gap:4px;display:flex}.bg-radio-btn[data-v-9532ade7]{border:1px solid var(--wb-border);background:var(--wb-surface);width:28px;height:28px;color:var(--wb-text-secondary);cursor:pointer;transition:all var(--wb-transition);border-radius:4px;justify-content:center;align-items:center;padding:0;display:flex;position:relative}.bg-radio-btn[data-v-9532ade7]:hover{border-color:var(--wb-text-secondary)}.bg-radio-btn.active[data-v-9532ade7]{border-color:var(--wb-accent);box-shadow:0 0 0 1px var(--wb-accent)}.bg-radio-btn--transparent[data-v-9532ade7]{background:repeating-conic-gradient(#ccc 0% 25%,#fff 0% 50%) 50%/8px 8px}.bg-color-fill[data-v-9532ade7]{border:1px solid var(--wb-border);border-radius:2px;width:16px;height:16px;display:block}.hidden-color-input[data-v-9532ade7]{opacity:0;pointer-events:none;width:0;height:0;position:absolute}.popover-actions[data-v-9532ade7]{justify-content:flex-end;gap:6px;margin-top:10px;display:flex}.popover-btn[data-v-9532ade7]{border:1px solid var(--wb-border);background:var(--wb-surface);color:var(--wb-text);cursor:pointer;transition:all var(--wb-transition);border-radius:4px;padding:4px 12px;font-size:12px}.popover-btn[data-v-9532ade7]:hover{background:var(--wb-surface-hover)}.popover-btn.confirm[data-v-9532ade7]{background:var(--wb-accent);color:#fff;border-color:var(--wb-accent)}.popover-btn.confirm[data-v-9532ade7]:hover{opacity:.9}.color-section[data-v-9532ade7]{flex-direction:column;align-items:center;gap:6px;padding:0;display:flex}.color-indicator[data-v-9532ade7]{cursor:pointer;width:32px;height:32px;transition:all var(--wb-transition);border:2px solid #0000;border-radius:8px;padding:2px;box-shadow:0 2px 8px #0000001a}.color-indicator[data-v-9532ade7]:hover{background:var(--wb-surface-hover);border-color:var(--wb-border)}.color-indicator.active[data-v-9532ade7]{border-color:var(--wb-accent);background:var(--wb-accent-light)}.color-indicator.disabled[data-v-9532ade7]{opacity:.35;cursor:default;pointer-events:none}.color-swatch[data-v-9532ade7]{border:1px solid var(--wb-border);border-radius:6px;justify-content:center;align-items:center;width:100%;height:100%;display:flex;position:relative}.bg[data-v-9532ade7]{border-style:dashed;border-radius:6px}.mixed[data-v-9532ade7],.transparent[data-v-9532ade7]{background:repeating-conic-gradient(#ccc 0% 25%,#fff 0% 50%) 50%/8px 8px!important}.mixed-icon[data-v-9532ade7]{width:16px;height:16px;color:var(--wb-text-secondary);pointer-events:none}.text-color-swatch[data-v-9532ade7]{justify-content:center;align-items:center;display:flex;position:relative}.text-color-icon[data-v-9532ade7]{pointer-events:none;width:18px;height:18px;transition:color .15s}.selected[data-v-d3706fa4]{filter:drop-shadow(0 0 3px #0078d799)}.whiteboard[data-v-975e0b8b]{background:var(--wb-canvas-surround);flex:1;position:relative;overflow:hidden}.canvas-wrapper[data-v-975e0b8b]{will-change:transform;margin:auto;position:absolute}.canvas[data-v-975e0b8b]{display:block}.canvas-toolbar[data-v-975e0b8b]{z-index:50;flex-direction:column;gap:2px;display:flex;position:absolute;top:8px;right:8px}.canvas-tb-btn[data-v-975e0b8b]{border:1px solid var(--wb-border);background:var(--wb-surface);width:28px;height:28px;color:var(--wb-text);cursor:pointer;transition:all var(--wb-transition);opacity:.85;border-radius:4px;justify-content:center;align-items:center;padding:0;display:flex}.canvas-tb-btn[data-v-975e0b8b]:hover:not(:disabled){background:var(--wb-surface-hover);opacity:1}.canvas-tb-btn[data-v-975e0b8b]:disabled{opacity:.35;cursor:default}.canvas-tb-btn--danger[data-v-975e0b8b]:hover:not(:disabled){color:#ef4444;border-color:#ef4444}.tb-separator[data-v-975e0b8b]{background:var(--wb-border);height:1px;margin:2px 4px}.zoom-control[data-v-975e0b8b]{color:#fff;z-index:100;background:#000000b3;border-radius:6px;align-items:center;gap:4px;padding:4px 6px;font-family:system-ui,sans-serif;font-size:13px;display:flex;position:absolute;bottom:16px;right:16px}.zoom-btn[data-v-975e0b8b]{color:#fff;cursor:pointer;background:#ffffff26;border:none;border-radius:4px;flex-shrink:0;justify-content:center;align-items:center;width:24px;height:24px;padding:0;transition:background .15s;display:flex}.zoom-btn[data-v-975e0b8b]:hover{background:#ffffff4d}.zoom-percent[data-v-975e0b8b]{text-align:center;-webkit-user-select:none;user-select:none;min-width:40px}.text-format-control[data-v-975e0b8b]{color:#fff;z-index:100;background:#000000b3;border-radius:6px;align-items:center;gap:2px;padding:4px 6px;font-family:system-ui,sans-serif;font-size:13px;display:flex;position:absolute;bottom:16px;right:16px}.font-size-btn[data-v-975e0b8b]{color:#fff;cursor:pointer;background:#ffffff26;border:none;border-radius:4px;flex-shrink:0;justify-content:center;align-items:center;width:24px;height:24px;padding:0;transition:background .15s;display:flex}.font-size-btn[data-v-975e0b8b]:hover{background:#ffffff4d}.font-size-value[data-v-975e0b8b]{text-align:center;-webkit-user-select:none;user-select:none;min-width:28px;font-size:12px}.text-align-separator[data-v-975e0b8b]{background:#ffffff4d;width:1px;height:16px;margin:0 4px}.text-align-btn[data-v-975e0b8b]{color:#fff;cursor:pointer;background:#ffffff26;border:none;border-radius:4px;flex-shrink:0;justify-content:center;align-items:center;width:28px;height:24px;padding:0;transition:background .15s;display:flex}.text-align-btn[data-v-975e0b8b]:hover{background:#ffffff4d}.text-align-btn.active[data-v-975e0b8b]{background:#6366f199}.overlay[data-v-a8b8bc9c]{z-index:100;position:fixed;inset:0}.color-palette[data-v-a8b8bc9c]{background:var(--wb-surface);border:1px solid var(--wb-border);z-index:101;border-radius:8px;flex-direction:column;gap:14px;min-width:300px;padding:16px;display:flex;box-shadow:0 1px 6px #0000001a}.palette-header[data-v-a8b8bc9c]{border-bottom:1px solid var(--wb-border);justify-content:space-between;align-items:center;padding-bottom:12px;display:flex}.palette-title[data-v-a8b8bc9c]{color:var(--wb-text);font-size:14px;font-weight:600}.current-color[data-v-a8b8bc9c]{align-items:center;display:flex}.current-swatch[data-v-a8b8bc9c]{border:2px solid var(--wb-border);border-radius:50%;width:28px;height:28px}.current-swatch.fill[data-v-a8b8bc9c]{border-radius:6px}.current-transparent[data-v-a8b8bc9c]{justify-content:center;align-items:center;display:flex;background:repeating-conic-gradient(#ccc 0% 25%,#fff 0% 50%) 50%/8px 8px!important}.preset-grid[data-v-a8b8bc9c]{grid-template-columns:repeat(8,1fr);gap:6px;display:grid}.color-swatch[data-v-a8b8bc9c]{cursor:pointer;width:30px;height:30px;transition:all var(--wb-transition);border:2px solid #0000;border-radius:50%;outline:none;padding:0}.color-swatch[data-v-a8b8bc9c]:hover{border-color:var(--wb-text);box-shadow:var(--wb-shadow-md);z-index:1;transform:scale(1.15)}.color-swatch[data-v-a8b8bc9c]:active{transform:scale(.95)}.transparent-row[data-v-a8b8bc9c]{justify-content:center;display:flex}.transparent-swatch[data-v-a8b8bc9c]{background:repeating-conic-gradient(#ccc 0% 25%,#fff 0% 50%) 50%/8px 8px;justify-content:center;align-items:center;display:flex}.palette-footer[data-v-a8b8bc9c]{border-top:1px solid var(--wb-border);padding-top:10px}.custom-color[data-v-a8b8bc9c]{align-items:center;gap:10px;display:flex}.color-picker[data-v-a8b8bc9c]{border:2px solid var(--wb-border);border-radius:var(--wb-radius-sm);cursor:pointer;background:0 0;width:36px;height:36px;padding:2px}.color-picker[data-v-a8b8bc9c]::-webkit-color-swatch-wrapper{padding:0}.color-picker[data-v-a8b8bc9c]::-webkit-color-swatch{border:none;border-radius:4px}.custom-label[data-v-a8b8bc9c]{color:var(--wb-text-secondary);font-size:13px;font-weight:500}:root,.wb-theme-light{--wb-bg:#f0f2f5;--wb-surface:#fff;--wb-surface-hover:#f5f6f8;--wb-surface-toolbar:#f8f9fb;--wb-text:#1a1a2e;--wb-text-secondary:#6b7280;--wb-border:#e5e7eb;--wb-accent:#6366f1;--wb-accent-light:#eef2ff;--wb-canvas-bg:#fff;--wb-canvas-surround:#e0e0e0;--wb-grid-a:#fff;--wb-grid-b:#f0f0f0;--wb-shadow-sm:0 1px 2px #0000000d;--wb-shadow-md:0 4px 12px #00000014;--wb-shadow-lg:0 8px 24px #0000001f;--wb-radius-sm:8px;--wb-radius-md:12px;--wb-radius-lg:16px;--wb-transition:.2s cubic-bezier(.4, 0, .2, 1)}.wb-theme-dark{--wb-bg:#1a1a2e;--wb-surface:#252540;--wb-surface-hover:#2e2e4a;--wb-surface-toolbar:#202038;--wb-text:#e2e2f0;--wb-text-secondary:#a0a0b8;--wb-border:#3a3a55;--wb-accent:#818cf8;--wb-accent-light:#818cf826;--wb-canvas-bg:#1e1e30;--wb-canvas-surround:#12121f;--wb-grid-a:#2a2a3e;--wb-grid-b:#1e1e30;--wb-shadow-sm:0 1px 2px #0000004d;--wb-shadow-md:0 4px 12px #0006;--wb-shadow-lg:0 8px 24px #00000080}.wb-root[data-v-40d42bb5]{width:100%;height:100%;display:flex;position:relative;overflow:hidden}
|
|
2
|
+
/*$vite$:1*/
|