{"_id":"@caikengren/tang-svg-editor","name":"@caikengren/tang-svg-editor","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@caikengren/tang-svg-editor","version":"0.1.0","description":"A host-agnostic multi-page SVG editor for React","keywords":["svg","svg-editor","editor","react","diagram","canvas","multi-page","annotation"],"license":"MIT","author":{"name":"fkcaikengren"},"repository":{"type":"git","url":"git+https://github.com/fkcaikengren/tang-svg-editor.git"},"homepage":"https://github.com/fkcaikengren/tang-svg-editor","bugs":{"url":"https://github.com/fkcaikengren/tang-svg-editor/issues"},"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","unpkg":"./dist/tang-svg-editor.browser.js","jsdelivr":"./dist/tang-svg-editor.browser.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./browser":{"types":"./dist/standalone.d.ts","import":"./dist/tang-svg-editor.browser.js"},"./style.css":{"types":"./dist/style.css.d.ts","default":"./dist/style.css"}},"sideEffects":["**/*.css"],"publishConfig":{"access":"public"},"engines":{"node":">=18"},"scripts":{"dev":"vite","build":"vite build && tsc -p tsconfig.build.json && vite build --config vite.browser.config.ts && node scripts/postbuild.mjs","typecheck":"tsc --noEmit","test":"vitest run","lint":"oxlint src tests vite.config.ts vite.browser.config.ts vitest.config.ts","pack":"pnpm build && pnpm pack","prepublishOnly":"pnpm build"},"dependencies":{"@floating-ui/react":"^0.27.19","clsx":"2.1.1","dompurify":"3.4.1","lucide-react":"1.11.0","nanoid":"5.1.9","radix-ui":"^1.4.3","react-colorful":"^5.7.0","tailwind-merge":"3.5.0","zustand":"^5.0.12"},"peerDependencies":{"react":">=18.2 <20","react-dom":">=18.2 <20"},"devDependencies":{"@tailwindcss/vite":"4.2.4","@testing-library/jest-dom":"6.9.1","@testing-library/react":"16.3.2","@types/dompurify":"3.0.5","@types/node":"25.6.0","@types/react":"19.2.14","@types/react-dom":"19.2.3","@vitejs/plugin-react":"6.0.1","jsdom":"29.1.0","oxlint":"1.34.0","react":"19.2.5","react-dom":"19.2.5","tailwindcss":"4.2.4","typescript":"6.0.3","vite":"8.0.10","vitest":"4.1.5"},"_id":"@caikengren/tang-svg-editor@0.1.0","gitHead":"ba5af2cf5cbd998cf0a07df4483e87ea81ec41dc","_nodeVersion":"22.13.1","_npmVersion":"10.9.2","dist":{"integrity":"sha512-15xed3mmdLLrs4/WFB9w9eanVCOaWm8tvVxgbiJS8PnqP4iGWeHvJRMSEwSl1C6jGreC96YBQza+15ZyN7HXlw==","shasum":"c4e4f4ee82e98ffb00d9b17e9f7c6e3b05d31aa7","tarball":"https://registry.npmjs.org/@caikengren/tang-svg-editor/-/tang-svg-editor-0.1.0.tgz","fileCount":138,"unpackedSize":2396513,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHx8pxA1s5j/evpwWYgdOx8Be974zMqnNHG81O9uV7lhAiAumTbiCp8TdCz3Cgby1/Sc95s7dvIi7WZcs/SO97JtEg=="}]},"_npmUser":{"name":"fkcaikengren","email":"fkcaikengren@163.com"},"directories":{},"maintainers":[{"name":"fkcaikengren","email":"fkcaikengren@163.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/tang-svg-editor_0.1.0_1785677656853_0.746153229126443"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-02T13:34:16.747Z","0.1.0":"2026-08-02T13:34:17.032Z","modified":"2026-08-02T13:34:17.255Z"},"maintainers":[{"name":"fkcaikengren","email":"fkcaikengren@163.com"}],"description":"A host-agnostic multi-page SVG editor for React","homepage":"https://github.com/fkcaikengren/tang-svg-editor","keywords":["svg","svg-editor","editor","react","diagram","canvas","multi-page","annotation"],"repository":{"type":"git","url":"git+https://github.com/fkcaikengren/tang-svg-editor.git"},"author":{"name":"fkcaikengren"},"bugs":{"url":"https://github.com/fkcaikengren/tang-svg-editor/issues"},"license":"MIT","readme":"# @caikengren/tang-svg-editor\n\n面向浏览器的多页 SVG 编辑器（React 组件）。它只负责 SVG 文档的编辑、标注、导入、撤销重做和显式保存；文件系统、持久化、路由和发布流程由宿主应用负责。\n\n## 特性\n\n- **多页 SVG 编辑**：页面新增、复制、删除、重排；`viewBox` 作为画布尺寸事实源。\n- **完整编辑能力**：撤销/重做、复制/剪切/粘贴/重复、全选、组合/取消组合、对齐、图层与页面操作。\n- **元素标注**：绑定到一个或多个有 ID 的 SVG 元素，随文档导出为 `data-edit-target` / `data-edit-annotation`。\n- **原子多 SVG 导入**：按自然文件名排序，任一文件无效时整批不提交。\n- **安全清洗**：导入、素材与 AI 结果统一经过 `sanitizeSvg` / `normalizeSvgForEditing`，移除脚本、事件属性与危险 URL。\n- **SVG/PNG 导出**：`exportPageSvg`、`exportPagePng` 与 `renderSvgToPngBlob`。\n- **宿主无关**：通过 `AssetAdapter` / `AIAdapter` / `ExportAdapter` 和 feature flags 对接宿主能力，不绑定具体平台。\n- **零构建接入**：`./browser` 入口支持 import map + 公共 CDN 直接使用。\n\n## 安装\n\n```bash\nnpm install @caikengren/tang-svg-editor\n# 或\npnpm add @caikengren/tang-svg-editor\n# 或\nyarn add @caikengren/tang-svg-editor\n```\n\n## React 接入\n\n```tsx\nimport { useState } from 'react'\nimport {\n  TangSvgEditor,\n  createBlankDocument,\n  type TangSvgDocument,\n} from '@caikengren/tang-svg-editor'\nimport '@caikengren/tang-svg-editor/style.css'\n\nexport function EditorPage() {\n  const [document, setDocument] = useState<TangSvgDocument>(() => createBlankDocument())\n\n  return (\n    <div style={{ height: '100vh' }}>\n      <TangSvgEditor\n        value={document}\n        onChange={(next, change) => {\n          setDocument(next)\n          console.log(change.source, change.operations)\n        }}\n        onSave={async (snapshot, intent) => {\n          await saveCompleteDocument(snapshot)\n          return {\n            status: 'saved',\n            savedRevision: intent.revision,\n            savedAt: Date.now(),\n            message: '保存成功',\n          }\n        }}\n      />\n    </div>\n  )\n}\n```\n\n容器必须有明确高度。`onChange` 在一次用户操作提交后触发一次，参数同时包含完整文档与精确的 `operations`；拖拽在释放时提交，不逐帧通知宿主。\n\n`onSave` 只由保存按钮、`Ctrl/Cmd+S` 或命令式 `save()` 触发。它收到全部页面的完整 SVG；编辑器会移除内部 `data-svg-editor-*` 属性，并保留 `data-edit-target` 与 `data-edit-annotation`。保存期间又发生修改时，较旧的保存响应不会清除新 dirty 状态。\n\n### 常用 props\n\n| 属性 | 作用 |\n| --- | --- |\n| `value` / `defaultValue` | 受控 / 非受控文档，二选一 |\n| `onChange(value, change)` | 每个可撤销 transaction 提交后回调完整快照 |\n| `onSave(value, intent)` | 保存按钮或快捷键触发的显式保存意图 |\n| `onBack()` / `onError(error)` | 可选宿主返回行为与标准化错误 |\n| `features` | 隐藏 assets / ai / import / export 内建能力 |\n| `headerActions` | 自定义 header 按钮 |\n| `readOnly` | 禁用写操作，保留预览、缩放和导出 |\n| `uiTheme` | `light`、`dark` 或 `system` |\n| `assets` / `ai` / `exporter` | 可选宿主能力适配器 |\n\n受控宿主原样回传最近一次 `onChange` 的对象时，编辑器保留本地历史；传入其他对象被视为外部替换，会清理选择并重置历史。\n\n## Header 按钮配置\n\n`tse-header` 默认按 `撤销 → 重做 → 保存 → 导入 SVG` 渲染，对应 `DEFAULT_EDITOR_HEADER_ACTIONS`（`['undo', 'redo', 'save', 'import']`）。传入 `headerActions` 会完整替换默认配置；内置动作可按 ID 复用，也可插入宿主按钮：\n\n```tsx\nimport {\n  DEFAULT_EDITOR_HEADER_ACTIONS,\n  TangSvgEditor,\n} from '@caikengren/tang-svg-editor'\n\nconst actions = [\n  ...DEFAULT_EDITOR_HEADER_ACTIONS.filter((action) => action !== 'import'),\n  {\n    id: 'exit-preview',\n    label: '退出预览',\n    onClick: stopPreview,\n  },\n] as const\n\n<TangSvgEditor headerActions={actions} />\n```\n\n自定义按钮支持 `ariaLabel`、`title`、`disabled`、`className`，以及 `default`、`primary`、`icon` 三种 `appearance`。自定义 `onClick` 可以返回 Promise；失败会通过 `onError` 以 `header-action-failed` 上报。保存和导入动作仍会根据 `onSave`、feature flags 与只读状态自动判断是否可用。\n\n## 标注与导入\n\n- 标注绑定到一个或多个有 ID 的 SVG 元素，说明上限为 10,000 字符。\n- 标注保存为 `data-edit-target=\"true\"` 和 `data-edit-annotation=\"...\"`。\n- “导入 SVG”支持多选，按包含数字的自然文件名顺序处理。\n- 同名文件替换现有页面；任一文件无效时整批不提交。\n- 可通过 feature flags 禁用页面新增、复制、删除和手动重排。\n\n## Browser ESM / CDN 接入\n\n`dist/tang-svg-editor.browser.js` 是浏览器原生 ESM 入口：它包含编辑器运行时依赖与 CSS，但把 React、ReactDOM 保留为外部依赖。通过 import map 保证宿主和编辑器共享同一份 React：\n\n```html\n<div id=\"editor-root\" style=\"height:100vh\"></div>\n<script type=\"importmap\">\n{\n  \"imports\": {\n    \"react\": \"https://esm.sh/react@19.2.5\",\n    \"react/jsx-runtime\": \"https://esm.sh/react@19.2.5/jsx-runtime\",\n    \"react-dom\": \"https://esm.sh/react-dom@19.2.5?external=react\",\n    \"react-dom/client\": \"https://esm.sh/react-dom@19.2.5/client?external=react\",\n    \"@caikengren/tang-svg-editor/browser\": \"https://cdn.jsdelivr.net/npm/@caikengren/tang-svg-editor@0.1.0/dist/tang-svg-editor.browser.js\"\n  }\n}\n</script>\n<script type=\"module\">\n  const { mount } = await import('@caikengren/tang-svg-editor/browser')\n  const editor = mount(document.getElementById('editor-root'), { value: documentData })\n</script>\n```\n\nnpm 发布后 jsDelivr/unpkg 会自动镜像文件。生产页面应固定完整版本号，不要引用 `@latest`。\n\n## Standalone API\n\n`./browser` 入口同时导出 `mount()`、`version`、`DEFAULT_EDITOR_HEADER_ACTIONS`、`createBlankDocument`、`normalizeSvgForEditing`、`sanitizeSvg`：\n\n```js\nimport { mount, version, createBlankDocument } from '@caikengren/tang-svg-editor/browser'\n```\n\n`mount()` 返回的 controller 提供 `getDocument`、`setDocument`、`save`、`focus`、`undo`、`redo`、`selectPage` 和 `destroy`。同一页面可以挂载多个实例；样式只注入一次。页面卸载时应调用 `destroy()`。\n\n## 快捷键\n\n- `Ctrl/Cmd+S`：保存\n- `Ctrl/Cmd+Z`：撤销\n- `Ctrl/Cmd+Shift+Z` 或 `Ctrl/Cmd+Y`：重做\n- `Ctrl/Cmd+C/X/V/D`：复制、剪切、粘贴、重复元素\n- `Ctrl/Cmd+A/G/Shift+G`：全选、组合、取消组合\n- `Delete/Backspace`：删除选择\n- `PageUp/PageDown`：切换页面\n\n## 兼容性\n\n- React `>=18.2 <20`（peer dependency，React 18.2 与 19.x 均已验证）。\n- 常规 npm 入口把 React 与 ReactDOM 作为 peer dependencies；Browser ESM 由 import map 提供 React。\n- 组件依赖 DOMParser、XMLSerializer、Canvas、Blob 与浏览器事件，**不支持服务端渲染**；Next.js 应放在 Client Component 中，或使用关闭 SSR 的动态导入。\n- 宿主必须导入 `style.css`，所有选择器与变量使用 `.tang-svg-editor`、`.tang-svg-editor-portal` 与 `--tse-*` 命名空间，不输出全局 reset。\n\n## 开发与发布\n\n```bash\n# 构建（产出 ESM/CJS + 类型声明 + 浏览器 ESM）\npnpm --filter @caikengren/tang-svg-editor build\n\n# 发布前门禁：tarball 内容、publint、Are The Types Wrong、consumer smoke\npnpm pack:check\npnpm consumer:check\n```\n\n### 发布（Changesets）\n\n```bash\npnpm changeset           # 1. 记录本次变更类型（patch / minor / major）\npnpm changeset version   # 2. 依据 changeset 提升版本号并生成 CHANGELOG（不自动提交）\npnpm release             # 3. package:check + consumer:check + changeset publish\n```\n\n`prepublishOnly` 会在 `changeset publish` 前自动执行 `pnpm build`，确保发布物是最新构建；发布前记得把第 2 步的版本号变更提交到 git。\n\n## License\n\n[MIT](LICENSE)\n","readmeFilename":"README.md","_rev":"1-e18997c97fdd3770bed0724663fd4219"}