xiaodao-painter 0.1.8 → 0.1.9

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.
Files changed (3) hide show
  1. package/README.ZH.md +362 -0
  2. package/README.md +259 -85
  3. package/package.json +17 -3
package/README.ZH.md ADDED
@@ -0,0 +1,362 @@
1
+ # 小刀画板工具 (Xiaodao Painter)
2
+
3
+ **中文** | [English](./README.md) | [演示](https://painter.xdz.me)
4
+
5
+ [![Downloads](https://img.shields.io/npm/d18m/xiaodao-painter)](https://www.npmjs.com/package/xiaodao-painter)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+ [![npm](https://img.shields.io/npm/v/xiaodao-painter)](https://www.npmjs.com/package/xiaodao-painter)
8
+ [![Vue 3](https://img.shields.io/badge/Vue-3.5+-42b883.svg)](https://vuejs.org/)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-6.0+-3178C6.svg)](https://www.typescriptlang.org/)
10
+ [![Vite](https://img.shields.io/badge/Vite-8.0+-646CFF.svg)](https://vitejs.dev/)
11
+
12
+ 一个自包含、可嵌入的 Vue 3 SVG 画板 / 绘图组件:把功能完整的画布(自由铅笔、基础图形、文本、选择与变换、平移缩放、撤销重做、内部剪贴板、颜色系统、明/暗主题、内置中英文国际化)打包进单个组件,并通过一个 `v-model` 驱动。
13
+
14
+ ![预览](./img/preview.png)
15
+
16
+ ---
17
+
18
+ ## 功能特性
19
+
20
+ ### 绘制与编辑
21
+
22
+ - **9 种工具**:选择、平移、铅笔、直线、矩形、圆、三角形、五角星、文本(另含独立的缩放工具)。
23
+ - **选择与变换**:单击选择;空白拖动框选;拖动已选图形移动;8 个手柄缩放(Shift = 对称 / 中心扩展);图层顺序(上移 / 下移)。
24
+ - **平移与缩放**:拖动平移画布;离散缩放档位(10%–1000%)以光标为锚点;触屏支持双指捏合缩放。
25
+ - **撤销 / 重做**:完整的笔画级撤销/重做栈。
26
+ - **内部剪贴板**:剪切、复制、粘贴(粘贴时偏移 +20px)。
27
+ - **颜色系统**:描边(前景)、填充(背景)、文本颜色三个色槽;可对选中图形逐个应用颜色,画布背景支持纯色或透明网格。
28
+ - **文本工具**:单击放置可编辑文本框;双击已有文本进行编辑。
29
+
30
+ ### 交互
31
+
32
+ - **指针与触屏**:统一的 `useDrawing` 交互层同时处理鼠标与触摸路径,包括双指捏合缩放、拖动绘制/选择。
33
+ - **缩放手柄**:图形 8 个、直线 2 个端点,拖动时实时预览。
34
+ - **修饰键**:`Ctrl` = 锁定 1:1 比例(闭合图形);`Shift` = 中心扩展 / 直线 45° 吸附。
35
+ - **快捷键**:Delete、Esc、Ctrl/⌘+Z / Ctrl/⌘+Y / Ctrl/⌘+X / Ctrl/⌘+C / Ctrl/� (V。
36
+ - **SVG 渲染**:每一条笔画都是真实的 SVG 元素(`<path>`、`<line>`、`<ellipse>`、`<rect>`、`<polygon>`、`foreignObject`),输出与分辨率无关、易于导出。
37
+
38
+ ### 视觉与主题
39
+
40
+ - **明 / 暗主题**:由 CSS 变量(`--wb-*`)驱动,切换主题只是换一个 class。
41
+ - **国际化**:内置中文(`zh-CN`)与英文(`en-US`),工具栏文案与缩放指示均走 `t()` 字典。
42
+ - **双向绑定**:通过 `v-model` 读写整份绘图数据。
43
+
44
+ ---
45
+
46
+ ## 安装
47
+
48
+ ```bash
49
+ # npm
50
+ npm install xiaodao-painter
51
+ # pnpm
52
+ pnpm add xiaodao-painter
53
+ # yarn
54
+ yarn add xiaodao-painter
55
+ ```
56
+
57
+ ### 对等依赖
58
+
59
+ - `vue` `^3.0.0`
60
+
61
+ 需要引入一次样式文件:
62
+
63
+ ```ts
64
+ import 'xiaodao-painter/style.css'
65
+ ```
66
+
67
+ ---
68
+
69
+ ## 快速开始
70
+
71
+ ```bash
72
+ # 克隆仓库
73
+ git clone https://github.com/xiaodaozhi/xiaodao-painter.git
74
+ cd xiaodao-painter
75
+
76
+ # 安装依赖
77
+ npm install
78
+
79
+ # 启动开发服务器(演示页)
80
+ npm run dev
81
+ ```
82
+
83
+ 在浏览器打开 `http://localhost:5173` 查看演示应用。
84
+
85
+ 要构建独立演示页,运行 `npm run build:demo` 并打开 `dist-demo/index.html`。
86
+
87
+ ---
88
+
89
+ ## 基础用法
90
+
91
+ ```vue
92
+ <template>
93
+ <Painter
94
+ v-model="data"
95
+ theme="light"
96
+ locale="zh-CN"
97
+ />
98
+ </template>
99
+
100
+ <script setup lang="ts">
101
+ import { ref, watch } from 'vue'
102
+ import { Painter } from 'xiaodao-painter'
103
+ import type { PainterData } from 'xiaodao-painter'
104
+ import 'xiaodao-painter/style.css'
105
+
106
+ const data = ref<PainterData>({
107
+ strokes: [],
108
+ canvasWidth: 800,
109
+ canvasHeight: 600,
110
+ })
111
+
112
+ watch(data, (val) => {
113
+ console.log('绘图数据变化:', val)
114
+ }, { deep: true })
115
+ </script>
116
+ ```
117
+
118
+ ### 指定尺寸与暗色主题
119
+
120
+ ```html
121
+ <Painter
122
+ v-model="data"
123
+ theme="dark"
124
+ locale="en-US"
125
+ :width="1200"
126
+ :height="800"
127
+ />
128
+ ```
129
+
130
+ ### 自定义画布背景
131
+
132
+ ```html
133
+ <Painter
134
+ v-model="data"
135
+ :modelValue="{
136
+ strokes: [],
137
+ canvasWidth: 800,
138
+ canvasHeight: 600,
139
+ canvasBackgroundColor: '#f5f5f5',
140
+ }"
141
+ />
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Props(属性)
147
+
148
+ | 属性 | 类型 | 默认值 | 说明 |
149
+ |------|------|---------|------|
150
+ | `modelValue` (v-model) | `PainterData` | `{ strokes: [], canvasWidth: 800, canvasHeight: 600 }` | 双向绑定的绘图数据,支持 `canvasBackgroundColor`。 |
151
+ | `theme` | `'light' \| 'dark'` | `'light'` | 界面主题。 |
152
+ | `locale` | `'zh-CN' \| 'en-US'` | `'zh-CN'` | 界面语言,传入其他值回退到 `zh-CN`。 |
153
+ | `width` | `string \| number` | `'100%'` | 组件宽度(CSS 值或 px 数值)。 |
154
+ | `height` | `string \| number` | `'100%'` | 组件高度(CSS 值或 px 数值)。 |
155
+
156
+ ---
157
+
158
+ ## 数据模型
159
+
160
+ 整份绘图由一个可序列化的 `PainterData` 对象描述,便于持久化、撤销重做与协作。
161
+
162
+ ```ts
163
+ interface PainterData {
164
+ strokes: Stroke[]
165
+ canvasWidth: number
166
+ canvasHeight: number
167
+ canvasBackgroundColor?: string // 任意 CSS 颜色或 'transparent'
168
+ }
169
+
170
+ type ToolType =
171
+ | 'select' | 'pan' | 'zoom'
172
+ | 'pencil' | 'line' | 'circle' | 'rect' | 'triangle' | 'star'
173
+ | 'text'
174
+
175
+ interface Point { x: number; y: number }
176
+
177
+ interface Stroke {
178
+ id: string // crypto.randomUUID()
179
+ type: ToolType
180
+ x: number // 包围盒左上角(提交时归一化)
181
+ y: number
182
+ width: number // 包围盒宽(绘制中可能为负)
183
+ height: number
184
+ points: Point[] // 铅笔路径点;非铅笔图形为空
185
+ strokeColor: string
186
+ fillColor: string // 铅笔与直线为 'transparent' / 'none'
187
+ strokeWidth: number
188
+ // 仅文本图形
189
+ text?: string
190
+ fontSize?: number
191
+ textAlign?: 'left' | 'center' | 'right'
192
+ textColor?: string
193
+ textAutoWidth?: boolean
194
+ }
195
+ ```
196
+
197
+ 坐标统一保存在**画布(SVG 用户)坐标系**中,与缩放和平移无关。组件在提交时会把负宽高的图形归一化,保证存储的包围盒始终为正。
198
+
199
+ ### 类型导出
200
+
201
+ ```ts
202
+ import type {
203
+ PainterData, // 主数据接口
204
+ Stroke, // 单条笔画 / 图形 / 文本
205
+ Point, // { x: number; y: number }
206
+ ToolType, // 所有工具标识的联合类型
207
+ CanvasConfig, // 画布尺寸 / 背景配置
208
+ } from 'xiaodao-painter'
209
+
210
+ import {
211
+ DEFAULT_CANVAS_WIDTH,
212
+ DEFAULT_CANVAS_HEIGHT,
213
+ STROKE_WIDTH,
214
+ DEFAULT_FONT_SIZE,
215
+ HIT_THRESHOLD,
216
+ } from 'xiaodao-painter'
217
+ ```
218
+
219
+ ---
220
+
221
+ ## 架构
222
+
223
+ ```src/
224
+ ├── index.ts # 公开入口:导出 Painter 与类型/常量
225
+ ├── App.vue # 演示根组件(设置页面标题、挂载组件)
226
+ ├── style.css # 全局主题 CSS 变量(明/暗)
227
+ └── components/painter/
228
+ ├── Painter.vue # 公开组件:props、v-model 同步、主题/语言注入
229
+ ├── Whiteboard.vue # SVG 画布、指针/触摸处理、缩放 UI、文本编辑
230
+ ├── Toolbar.vue # 工具按钮、颜色指示、画布尺寸弹窗
231
+ ├── ColorPalette.vue # 前景/填充/文本色槽的颜色选择器
232
+ ├── SelectionOverlay.vue # 包围框 + 8 个缩放手柄(直线仅 2 个)
233
+ ├── StrokeRenderer.vue # 把单个 Stroke 渲染为 SVG 元素
234
+ ├── composables/
235
+ │ ├── useDrawing.ts # 交互引擎(绘制/选择/移动/缩放/平移/文本)
236
+ │ └── useI18n.ts # locale、theme、根元素的 provide/inject
237
+ ├── stores/
238
+ │ ├── canvas.ts # 中心仓库:笔画、颜色、平移缩放、撤销、剪贴板
239
+ │ └── tools.ts # 当前工具状态
240
+ ├── utils/
241
+ │ ├── geometry.ts # 命中测试、星/三角形顶点、包围盒
242
+ │ ├── svg.ts # Stroke → SVG 元素转换
243
+ │ └── i18n.ts # zh-CN / en-US 字典 + t()
244
+ └── types/index.ts # TypeScript 类型与默认常量
245
+ ```
246
+
247
+ ### 设计原则
248
+
249
+ - **自包含**:单个 Vue 3 组件,除 Vue 外零运行时依赖,无需全局注册,无需 CSS 框架。
250
+ - **状态可序列化**:整份绘图是单个普通对象(`PainterData`),便于保存、加载与同步。
251
+ - **基于 SVG 渲染**:每条笔画都是真实 SVG 节点(`<path>`、`<line>`、`<ellipse>`、`<rect>`、`<polygon>`、`foreignObject`),输出与分辨率无关、易主题化、易导出。
252
+ - **可预测的双向绑定**:父组件拥有数据,组件回显并在变更时通过 `v-model` 上报;store 内的 `syncing` 标志与组件内的 `suppressing` 标志共同避免回环。
253
+ - **轻量交互**:手动处理指针与触摸事件并显式计算坐标变换(不使用 `getScreenCTM`),规避 CSS transform 下 iOS Safari 的不一致行为。
254
+ - **纯函数工具**:命中测试、SVG 转换、i18n 都放在 `utils/` 中作为无框架依赖的纯函数,易于复用与单测。
255
+ - **组合式 API**:业务逻辑抽离到 `composables/`(`useDrawing`、`useI18n`)与轻量 store 层(`canvas.ts`、`tools.ts`),保持单一职责。
256
+
257
+ > **单实例说明**:`canvas.ts` 与 `tools.ts` 是模块级响应式单例,同一页面多个 `<Painter>` 会共享状态。若要支持多实例,应将 store 改为基于 `provide`/`inject` 的组件级 store。
258
+
259
+ ---
260
+
261
+ ## 快捷键
262
+
263
+ | 按键 | 动作 |
264
+ |------|------|
265
+ | `Delete` / `Backspace` | 删除选中图形 |
266
+ | `Escape` | 取消选择 / 取消文本编辑 |
267
+ | `Ctrl/⌘ + Z` | 撤销 |
268
+ | `Ctrl/⌘ + Y` 或 `Ctrl/⌘ + Shift + Z` | 重做 |
269
+ | `Ctrl/⌘ + X` | 剪切 |
270
+ | `Ctrl/⌘ + C` | 复制 |
271
+ | `Ctrl/⌘ + V` | 粘贴(偏移 +20px) |
272
+
273
+ 绘制或缩放过程中,`Ctrl` 锁定 1:1,`Shift` 对闭合图形为中心扩展、对直线为 45° 吸附。
274
+
275
+ ---
276
+
277
+ ## 主题与国际化
278
+
279
+ ### 内置主题
280
+
281
+ 通过 `theme` 属性提供两套主题,颜色由 `style.css` 中的 CSS 变量(`--wb-*`)驱动,自动适配明/暗。切换主题时若当前前景色为默认值,会自动在 `#000000` 与 `#ffffff` 之间翻转。
282
+
283
+ 可在任意外层元素上覆盖 CSS 变量实现深度定制:
284
+
285
+ ```html
286
+ <div style="--wb-bg: #1a1a2e; --wb-text: #e0e0e0;">
287
+ <Painter v-model="data" theme="dark" />
288
+ </div>
289
+ ```
290
+
291
+ ### 国际化
292
+
293
+ 内置 `zh-CN` 与 `en-US` 文案。通过 `locale` 切换,未知取值回退到 `zh-CN`。字典位于 `src/components/painter/utils/i18n.ts`,可方便扩展更多语言。
294
+
295
+ ---
296
+
297
+ ## 开发
298
+
299
+ ```bash
300
+ npm run dev # 启动 Vite 开发服务器(演示页)
301
+ npm run build # 类型检查(vue-tsc)并构建库产物
302
+ npm run build:demo # 构建独立演示页到 ./dist-demo
303
+ npm run preview # 预览库的生产构建
304
+ npm run lint:check # 运行 ESLint
305
+ npm run typecheck # 仅类型检查
306
+ ```
307
+
308
+ ## 构建
309
+
310
+ 库构建通过 `vite.config.ts`(lib 模式)。`npm run build` 产出:
311
+
312
+ | 文件 | 说明 |
313
+ |------|------|
314
+ | `dist/xiaodao-painter.es.js` | ES 模块(供打包工具使用) |
315
+ | `dist/xiaodao-painter.umd.js` | UMD 包(供直接 `<script>` 引入) |
316
+ | `dist/xiaodao-painter.css` | 提取的样式表 |
317
+ | `dist/types/` | TypeScript 类型声明 |
318
+
319
+ `npm run build:demo` 通过 `vite.demo.config.ts` 构建独立演示站点到 `dist-demo/`(按 `dist` 同等规则 git 忽略)。
320
+
321
+ ---
322
+
323
+ ## 路线图
324
+
325
+ ### 近期
326
+
327
+ - [x] 铅笔、图形、文本绘制与选择变换
328
+ - [x] 平移缩放、撤销重做、内部剪贴板
329
+ - [x] 颜色系统、明/暗主题、国际化
330
+ - [ ] 导出 PNG / SVG / JSON
331
+ - [ ] 图形级对齐吸附与参考线
332
+
333
+ ### 中期
334
+
335
+ - [ ] 多页面 / 多图层支持
336
+ - [ ] 图片嵌入
337
+ - [ ] 云端保存 / 加载集成
338
+
339
+ ### 长期
340
+
341
+ - [ ] 协同编辑
342
+ - [ ] 自定义图形渲染插件系统
343
+
344
+ ---
345
+
346
+ ## 技术栈
347
+
348
+ | 层 | 技术 | 版本 |
349
+ |----|------|------|
350
+ | 框架 | Vue 3(Composition API + `<script setup>`) | ^3.5 |
351
+ | 构建 | Vite | ^8.0 |
352
+ | 语言 | TypeScript(strict) | ~6.0 |
353
+ | 渲染 | SVG 2D(DOM) | - |
354
+ | 类型检查 | vue-tsc | ^3.3 |
355
+ | 包管理器 | npm / pnpm | - |
356
+ | CSS | Scoped CSS + CSS 自定义属性 | - |
357
+
358
+ ---
359
+
360
+ ## 许可证
361
+
362
+ 本项目基于 MIT 许可证发布,详见 [LICENSE](LICENSE) 文件。
package/README.md CHANGED
@@ -1,49 +1,107 @@
1
- # xiaodao-painter
1
+ # Xiaodao Painter
2
2
 
3
- A self-contained, embeddable SVG whiteboard component for Vue 3. Built with TypeScript and Vite. Zero extra dependencies beyond Vue 3.
3
+ [中文](./README.ZH.md) | **English** | [Demo](https://painter.xdz.me)
4
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`.
5
+ [![Downloads](https://img.shields.io/npm/d18m/xiaodao-painter)](https://www.npmjs.com/package/xiaodao-painter)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+ [![npm](https://img.shields.io/npm/v/xiaodao-painter)](https://www.npmjs.com/package/xiaodao-painter)
8
+ [![Vue 3](https://img.shields.io/badge/Vue-3.5+-42b883.svg)](https://vuejs.org/)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-6.0+-3178C6.svg)](https://www.typescriptlang.org/)
10
+ [![Vite](https://img.shields.io/badge/Vite-8.0+-646CFF.svg)](https://vitejs.dev/)
11
+
12
+ A self-contained, embeddable **SVG drawing component** for Vue 3: a full-featured drawing surface (freehand pencil, shape primitives, text, selection & transform, pan & zoom, undo/redo, an internal clipboard, a color system, light/dark themes, and built-in i18n) packed into a single component, driven by one `v-model`.
6
13
 
7
14
  ![Preview](./img/preview.png)
8
15
 
16
+ ---
17
+
9
18
  ## Features
10
19
 
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`
20
+ ### Drawing & Editing
22
21
 
23
- ## Installation
22
+ - **9 tools**: Select, Pan, Pencil, Line, Rectangle, Circle, Triangle, Star, Text (plus a dedicated Zoom tool).
23
+ - **Selection & transform**: Click to select, box-select on empty space, drag to move, 8-handle resize (Shift = center-out / symmetric), layer reorder (bring to front / send to back).
24
+ - **Pan & zoom**: Grab-drag panning, discrete zoom steps (10%–1000%) anchored at the cursor; pinch zoom on touch devices.
25
+ - **Undo / redo**: Full stroke-level undo/redo stacks.
26
+ - **Internal clipboard**: Cut, copy, paste within the canvas (paste offset +20 px).
27
+ - **Color system**: Stroke (foreground), fill (background), and text-color slots; per-selection color application and canvas background (solid or transparent with grid).
28
+ - **Text tool**: Click to place an editable text box; double-click existing text to edit.
29
+
30
+ ### Interaction
31
+
32
+ - **Pointer & touch**: A single interaction layer (`useDrawing`) unifies mouse and touch paths, including pinch-to-zoom and drag-to-draw/select.
33
+ - **Resize handles**: 8 handles for shapes, 2 endpoints for lines; live preview while dragging.
34
+ - **Modifier keys**: `Ctrl` = constrain 1:1 ratio (shapes), `Shift` = center-out expansion / 45° line snap.
35
+ - **Keyboard shortcuts**: Delete, Escape, Ctrl/⌘+Z / Ctrl/⌘+Y / Ctrl/⌘+X / Ctrl/⌘+C / Ctrl/⌘+V.
36
+ - **SVG rendering**: Every stroke is a real SVG element (`<path>`, `<line>`, `<ellipse>`, `<rect>`, `<polygon>`, `foreignObject`), so the output is resolution-independent and easy to export.
37
+
38
+ ### Visual & Theming
24
39
 
25
- Install the package from npm:
40
+ - **Light / Dark themes**: Full color palette driven by CSS custom properties (`--wb-*`), switching swaps a single class.
41
+ - **Internationalization**: Chinese (`zh-CN`) and English (`en-US`) built in; toolbar labels and zoom indicator route through an `t()` dictionary.
42
+ - **Two-way binding**: Read and write the entire drawing via `v-model`.
43
+
44
+ ---
45
+
46
+ ## Installation
26
47
 
27
48
  ```bash
49
+ # npm
28
50
  npm install xiaodao-painter
51
+ # pnpm
52
+ pnpm add xiaodao-painter
53
+ # yarn
54
+ yarn add xiaodao-painter
29
55
  ```
30
56
 
31
- Or with pnpm / yarn:
57
+ ### Peer Dependencies
58
+
59
+ - `vue` `^3.0.0`
60
+
61
+ Import the stylesheet once:
62
+
63
+ ```ts
64
+ import 'xiaodao-painter/style.css'
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Quick Start
32
70
 
33
71
  ```bash
34
- pnpm add xiaodao-painter
35
- yarn add xiaodao-painter
72
+ # Clone the repository
73
+ git clone https://github.com/xiaodaozhi/xiaodao-painter.git
74
+ cd xiaodao-painter
75
+
76
+ # Install dependencies
77
+ npm install
78
+
79
+ # Start the dev server (demo page)
80
+ npm run dev
36
81
  ```
37
82
 
38
- ## Usage
83
+ Navigate to `http://localhost:5173` to see the demo application.
84
+
85
+ To build a standalone demo page, run `npm run build:demo` and open `dist-demo/index.html`.
86
+
87
+ ---
39
88
 
40
- Just import and use the `Painter` component. No additional plugins or global setup required — only Vue 3 is needed:
89
+ ## Basic Usage
41
90
 
42
91
  ```vue
92
+ <template>
93
+ <Painter
94
+ v-model="data"
95
+ theme="light"
96
+ locale="zh-CN"
97
+ />
98
+ </template>
99
+
43
100
  <script setup lang="ts">
44
101
  import { ref, watch } from 'vue'
45
102
  import { Painter } from 'xiaodao-painter'
46
103
  import type { PainterData } from 'xiaodao-painter'
104
+ import 'xiaodao-painter/style.css'
47
105
 
48
106
  const data = ref<PainterData>({
49
107
  strokes: [],
@@ -52,32 +110,24 @@ const data = ref<PainterData>({
52
110
  })
53
111
 
54
112
  watch(data, (val) => {
55
- console.log('Drawing data changed:', val)
113
+ console.log('Drawing changed:', val)
56
114
  }, { deep: true })
57
115
  </script>
58
-
59
- <template>
60
- <Painter
61
- v-model="data"
62
- theme="light"
63
- locale="zh-CN"
64
- />
65
- </template>
66
116
  ```
67
117
 
68
- With explicit dimensions and dark theme:
118
+ ### With explicit dimensions and dark theme
69
119
 
70
120
  ```html
71
121
  <Painter
72
122
  v-model="data"
73
123
  theme="dark"
74
124
  locale="en-US"
75
- width="1200"
76
- height="800"
125
+ :width="1200"
126
+ :height="800"
77
127
  />
78
128
  ```
79
129
 
80
- With a custom canvas background color:
130
+ ### With a custom canvas background
81
131
 
82
132
  ```html
83
133
  <Painter
@@ -91,44 +141,51 @@ With a custom canvas background color:
91
141
  />
92
142
  ```
93
143
 
144
+ ---
145
+
94
146
  ## Props
95
147
 
96
148
  | Prop | Type | Default | Description |
97
- | --- | --- | --- | --- |
98
- | `modelValue` | `PainterData` | `{ strokes: [], canvasWidth: 800, canvasHeight: 600 }` | Two-way bound drawing data (supports `canvasBackgroundColor`) |
99
- | `theme` | `'light' \| 'dark'` | `'light'` | UI theme |
100
- | `locale` | `string` | `'zh-CN'` | UI language (`'zh-CN'` or `'en-US'`) |
101
- | `width` | `string \| number` | `'100%'` | Component width (CSS value or px number) |
102
- | `height` | `string \| number` | `'100%'` | Component height (CSS value or px number) |
149
+ |------|------|---------|-------------|
150
+ | `modelValue` (v-model) | `PainterData` | `{ strokes: [], canvasWidth: 800, canvasHeight: 600 }` | Two-way bound drawing data. Supports `canvasBackgroundColor`. |
151
+ | `theme` | `'light' \| 'dark'` | `'light'` | UI theme. |
152
+ | `locale` | `'zh-CN' \| 'en-US'` | `'zh-CN'` | UI language. Any other value falls back to `zh-CN`. |
153
+ | `width` | `string \| number` | `'100%'` | Component width (CSS value or px number). |
154
+ | `height` | `string \| number` | `'100%'` | Component height (CSS value or px number). |
155
+
156
+ ---
103
157
 
104
158
  ## Data Model
105
159
 
160
+ The whole drawing is described by a single serializable `PainterData` object, which makes persistence, undo/redo, and collaboration straightforward.
161
+
106
162
  ```ts
107
163
  interface PainterData {
108
164
  strokes: Stroke[]
109
165
  canvasWidth: number
110
166
  canvasHeight: number
111
- canvasBackgroundColor?: string
167
+ canvasBackgroundColor?: string // any CSS color or 'transparent'
112
168
  }
113
169
 
114
- type ToolType = 'select' | 'pencil' | 'line' | 'circle' | 'rect' | 'triangle' | 'star' | 'text' | 'pan' | 'zoom'
170
+ type ToolType =
171
+ | 'select' | 'pan' | 'zoom'
172
+ | 'pencil' | 'line' | 'circle' | 'rect' | 'triangle' | 'star'
173
+ | 'text'
115
174
 
116
- interface Point {
117
- x: number
118
- y: number
119
- }
175
+ interface Point { x: number; y: number }
120
176
 
121
177
  interface Stroke {
122
- id: string
178
+ id: string // crypto.randomUUID()
123
179
  type: ToolType
124
- x: number
180
+ x: number // bounding-box top-left (normalized on commit)
125
181
  y: number
126
- width: number
182
+ width: number // bounding-box width (may be negative while drawing)
127
183
  height: number
128
- points: Point[]
184
+ points: Point[] // pencil path points; empty for non-pencil
129
185
  strokeColor: string
130
- fillColor: string
186
+ fillColor: string // 'transparent' / 'none' for pencil & line
131
187
  strokeWidth: number
188
+ // text-only fields
132
189
  text?: string
133
190
  fontSize?: number
134
191
  textAlign?: 'left' | 'center' | 'right'
@@ -137,52 +194,169 @@ interface Stroke {
137
194
  }
138
195
  ```
139
196
 
197
+ Coordinates are stored in **canvas (SVG user) coordinate space**, independent of zoom and pan. The component normalizes negative-width/height shapes on commit so the stored bounding box is always positive.
198
+
199
+ ### Type Exports
200
+
201
+ ```ts
202
+ import type {
203
+ PainterData, // main data interface
204
+ Stroke, // a single shape / text / pencil entry
205
+ Point, // { x: number; y: number }
206
+ ToolType, // union of all tool identifiers
207
+ CanvasConfig, // canvas size / background config
208
+ } from 'xiaodao-painter'
209
+
210
+ import {
211
+ DEFAULT_CANVAS_WIDTH,
212
+ DEFAULT_CANVAS_HEIGHT,
213
+ STROKE_WIDTH,
214
+ DEFAULT_FONT_SIZE,
215
+ HIT_THRESHOLD,
216
+ } from 'xiaodao-painter'
217
+ ```
218
+
219
+ ---
220
+
221
+ ## Architecture
222
+
223
+ ```src/
224
+ ├── index.ts # Public entry: exports Painter + types/constants
225
+ ├── App.vue # Demo root (sets page title, mounts the component)
226
+ ├── style.css # Global theme CSS variables (light/dark)
227
+ └── components/painter/
228
+ ├── Painter.vue # Public component: props, v-model sync, theme/locale provide
229
+ ├── Whiteboard.vue # SVG canvas, pointer/touch handling, zoom UI, text editing
230
+ ├── Toolbar.vue # Tool buttons, color indicators, canvas-size popover
231
+ ├── ColorPalette.vue # Color picker for foreground/fill/text slots
232
+ ├── SelectionOverlay.vue # Bounding box + 8 resize handles (line gets 2 handles)
233
+ ├── StrokeRenderer.vue # Renders one Stroke as an SVG element
234
+ ├── composables/
235
+ │ ├── useDrawing.ts # Interaction engine (draw/select/move/resize/pan/zoom/text)
236
+ │ └── useI18n.ts # provide/inject for locale, theme, root element
237
+ ├── stores/
238
+ │ ├── canvas.ts # Central store: strokes, colors, pan/zoom, undo, clipboard
239
+ │ └── tools.ts # Active tool state
240
+ ├── utils/
241
+ │ ├── geometry.ts # Hit testing, star/triangle vertices, bounding box
242
+ │ ├── svg.ts # Stroke → SVG element conversion
243
+ │ └── i18n.ts # zh-CN / en-US dictionaries + t()
244
+ └── types/index.ts # TypeScript types and default constants
245
+ ```
246
+
247
+ ### Design Principles
248
+
249
+ - **Self-contained**: A single Vue 3 component with zero runtime dependencies beyond Vue; no global registration, no CSS framework.
250
+ - **Serializable state**: The whole drawing is one plain `PainterData` object, so it can be saved, loaded, and synced trivially.
251
+ - **SVG-based rendering**: Every stroke is a real SVG node (`<path>`, `<line>`, `<ellipse>`, `<rect>`, `<polygon>`, `foreignObject`). Output is resolution-independent, easily themeable, and export-friendly.
252
+ - **Predictable two-way binding**: The parent owns the data; the component reflects it and emits changes back through `v-model`. A `syncing` flag in the store plus a `suppressing` flag in the component prevents feedback loops.
253
+ - **Framework-light interaction**: Pointer + touch are handled manually with an explicit coordinate transform (not `getScreenCTM`), avoiding unreliable behavior under CSS transforms on iOS Safari.
254
+ - **Pure helpers**: Hit-testing, SVG conversion, and i18n live in `utils/` as framework-free functions, easy to reuse or unit-test.
255
+ - **Composition API**: Business logic is extracted into `composables/` (`useDrawing`, `useI18n`) and a small store layer (`canvas.ts`, `tools.ts`), keeping single-responsibility modules.
256
+
257
+ > **Single-instance note:** `canvas.ts` and `tools.ts` are module-level reactive singletons. Multiple `<Painter>` components on one page currently share state. To support multiple instances, promote the stores to a `provide`/`inject` scoped store per component.
258
+
259
+ ---
260
+
140
261
  ## Keyboard Shortcuts
141
262
 
142
263
  | Key | Action |
143
- | --- | --- |
264
+ |-----|--------|
144
265
  | `Delete` / `Backspace` | Delete selected strokes |
145
- | `Escape` | Clear selection |
146
- | `Ctrl + Z` | Undo |
147
- | `Ctrl + Y` / `Ctrl + Shift + Z` | Redo |
148
- | `Ctrl + X` | Cut |
149
- | `Ctrl + C` | Copy |
150
- | `Ctrl + V` | Paste (offset +20 px) |
266
+ | `Escape` | Clear selection / cancel text edit |
267
+ | `Ctrl/⌘ + Z` | Undo |
268
+ | `Ctrl/⌘ + Y` or `Ctrl/⌘ + Shift + Z` | Redo |
269
+ | `Ctrl/⌘ + X` | Cut |
270
+ | `Ctrl/⌘ + C` | Copy |
271
+ | `Ctrl/⌘ + V` | Paste (offset +20 px) |
151
272
 
152
- ## Project Structure
273
+ While drawing or resizing, `Ctrl` constrains to 1:1, `Shift` gives center-out expansion (for closed shapes) or 45° line snapping (for lines).
153
274
 
275
+ ---
276
+
277
+ ## Theming & Internationalization
278
+
279
+ ### Built-in Themes
280
+
281
+ The component ships two themes via the `theme` prop. Colors are driven by CSS custom properties defined in `style.css` (`--wb-*`), so the component adapts to light/dark automatically. Switching theme also flips the default foreground color (`#000000` ⇄ `#ffffff`) when the current color is the theme default.
282
+
283
+ Override CSS variables on any wrapper element for full customization:
284
+
285
+ ```html
286
+ <div style="--wb-bg: #1a1a2e; --wb-text: #e0e0e0;">
287
+ <Painter v-model="data" theme="dark" />
288
+ </div>
154
289
  ```
155
- src/
156
- components/painter/
157
- Painter.vue # Public entry (props, v-model, theme sync)
158
- Whiteboard.vue # SVG canvas, keyboard shortcuts, pan/zoom centering
159
- Toolbar.vue # Left toolbar (tools, canvas settings, color indicators)
160
- ColorPalette.vue # Color picker popover
161
- SelectionOverlay.vue # Selection bounding box + resize handles
162
- StrokeRenderer.vue # Renders a single stroke as SVG
163
- composables/
164
- useDrawing.ts # Mouse interaction engine (draw, select, move, resize)
165
- useI18n.ts # Locale and theme provide/inject
166
- stores/
167
- canvas.ts # Main store (strokes, colors, pan/zoom, undo, clipboard)
168
- tools.ts # Active tool state
169
- utils/
170
- geometry.ts # Hit testing, star/triangle vertices, bounding box
171
- svg.ts # Stroke-to-SVG element conversion
172
- i18n.ts # zh-CN / en-US dictionaries
173
- types/
174
- index.ts # TypeScript types and default constants
175
- style.css # CSS custom properties (themes)
176
- ```
290
+
291
+ ### Internationalization
292
+
293
+ UI text is provided for `zh-CN` and `en-US`. Pass `locale` to switch; unknown values fall back to `zh-CN`. The dictionary lives in `src/components/painter/utils/i18n.ts` and can be extended with additional locales.
294
+
295
+ ---
177
296
 
178
297
  ## Development
179
298
 
180
299
  ```bash
181
- npm run dev # Start dev server
182
- npm run build # Type-check and build for production
183
- npm run preview # Preview production build
300
+ npm run dev # Start the Vite dev server (demo page)
301
+ npm run build # Type-check (vue-tsc) and build the library bundle
302
+ npm run build:demo # Build the standalone demo page into ./dist-demo
303
+ npm run preview # Preview the library production build
304
+ npm run lint:check # Run ESLint
305
+ npm run typecheck # Type-check only
184
306
  ```
185
307
 
308
+ ## Building
309
+
310
+ The library build uses `vite.config.ts` (lib mode). `npm run build` produces:
311
+
312
+ | File | Description |
313
+ |------|-------------|
314
+ | `dist/xiaodao-painter.es.js` | ES module (for bundlers) |
315
+ | `dist/xiaodao-painter.umd.js` | UMD bundle (for direct `<script>` usage) |
316
+ | `dist/xiaodao-painter.css` | Extracted stylesheet |
317
+ | `dist/types/` | TypeScript declaration files |
318
+
319
+ `npm run build:demo` builds a standalone demo site via `vite.demo.config.ts` into `dist-demo/` (git-ignored, same as `dist`).
320
+
321
+ ---
322
+
323
+ ## Roadmap
324
+
325
+ ### Near-term
326
+
327
+ - [x] Pencil, shape, text drawing with selection & transform
328
+ - [x] Pan & zoom, undo/redo, internal clipboard
329
+ - [x] Color system, light/dark themes, i18n
330
+ - [ ] Export to PNG / SVG / JSON
331
+ - [ ] Shape-level snapping & alignment guides
332
+
333
+ ### Mid-term
334
+
335
+ - [ ] Multi-page / multi-layer support
336
+ - [ ] Image embedding
337
+ - [ ] Cloud save / load integration
338
+
339
+ ### Long-term
340
+
341
+ - [ ] Collaborative editing
342
+ - [ ] Plugin system for custom shape renderers
343
+
344
+ ---
345
+
346
+ ## Tech Stack
347
+
348
+ | Layer | Technology | Version |
349
+ |-------|-----------|---------|
350
+ | Framework | Vue 3 (Composition API + `<script setup>`) | ^3.5 |
351
+ | Build | Vite | ^8.0 |
352
+ | Language | TypeScript (strict) | ~6.0 |
353
+ | Rendering | SVG 2D (DOM) | - |
354
+ | Type Checker | vue-tsc | ^3.3 |
355
+ | Package Manager | npm / pnpm | - |
356
+ | CSS | Scoped CSS + CSS Custom Properties | - |
357
+
358
+ ---
359
+
186
360
  ## License
187
361
 
188
- MIT
362
+ This project is licensed under the MIT License: see the [LICENSE](LICENSE) file for details.
package/package.json CHANGED
@@ -1,15 +1,29 @@
1
1
  {
2
2
  "name": "xiaodao-painter",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "A Vue 3 component for drawing on the canvas",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "homepage": "https://xiaodaozhi.com/xiaodao-painter.html",
8
8
  "keywords": [
9
9
  "vue",
10
+ "vue3",
11
+ "vue-component",
12
+ "whiteboard",
10
13
  "canvas",
11
- "drawing",
12
- "painter"
14
+ "draw",
15
+ "drawing-board",
16
+ "paint",
17
+ "sketch",
18
+ "annotation",
19
+ "手写板",
20
+ "白板",
21
+ "画布",
22
+ "画板",
23
+ "绘图",
24
+ "手绘",
25
+ "批注",
26
+ "电子白板"
13
27
  ],
14
28
  "files": [
15
29
  "dist"