@pixerate/editor 0.20.0 → 0.22.0

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 (70) hide show
  1. package/README.md +24 -13
  2. package/dist/agent/index.cjs +8 -8
  3. package/dist/agent/index.d.cts +2 -2
  4. package/dist/agent/index.d.ts +2 -2
  5. package/dist/agent/index.js +2 -2
  6. package/dist/canvas/index.cjs +1269 -13
  7. package/dist/canvas/index.cjs.map +1 -1
  8. package/dist/canvas/index.d.cts +432 -6
  9. package/dist/canvas/index.d.ts +432 -6
  10. package/dist/canvas/index.js +1264 -8
  11. package/dist/canvas/index.js.map +1 -1
  12. package/dist/{chunk-U6NDQFOK.cjs → chunk-6M3BCTBD.cjs} +427 -138
  13. package/dist/chunk-6M3BCTBD.cjs.map +1 -0
  14. package/dist/{chunk-TVRC4KVT.cjs → chunk-AA4ZEVCC.cjs} +845 -90
  15. package/dist/chunk-AA4ZEVCC.cjs.map +1 -0
  16. package/dist/{chunk-LA7PEGU3.cjs → chunk-CL37JGFW.cjs} +4 -4
  17. package/dist/{chunk-LA7PEGU3.cjs.map → chunk-CL37JGFW.cjs.map} +1 -1
  18. package/dist/{chunk-A7DKIA72.js → chunk-DENSCKFC.js} +642 -69
  19. package/dist/chunk-DENSCKFC.js.map +1 -0
  20. package/dist/{chunk-4QPLEUL2.cjs → chunk-GVSMRPHB.cjs} +1046 -54
  21. package/dist/chunk-GVSMRPHB.cjs.map +1 -0
  22. package/dist/{chunk-KYRZDWM4.js → chunk-JJDLPEWL.js} +2 -2
  23. package/dist/{chunk-3TYMNULT.js → chunk-MJZBNWG7.js} +807 -52
  24. package/dist/chunk-MJZBNWG7.js.map +1 -0
  25. package/dist/{chunk-CYFJCKC4.js → chunk-QGHGJ56O.js} +997 -5
  26. package/dist/chunk-QGHGJ56O.js.map +1 -0
  27. package/dist/{chunk-GVW4P7BG.js → chunk-RU52I7QO.js} +305 -16
  28. package/dist/chunk-RU52I7QO.js.map +1 -0
  29. package/dist/{chunk-JYRIOFEN.cjs → chunk-SAZRENEZ.cjs} +647 -74
  30. package/dist/chunk-SAZRENEZ.cjs.map +1 -0
  31. package/dist/{controller-OUdxPY-X.d.ts → controller-B7GIybc4.d.cts} +139 -7
  32. package/dist/{controller-OUdxPY-X.d.cts → controller-B7GIybc4.d.ts} +139 -7
  33. package/dist/extensions/index.cjs +14 -4
  34. package/dist/extensions/index.cjs.map +1 -1
  35. package/dist/extensions/index.d.cts +3 -3
  36. package/dist/extensions/index.d.ts +3 -3
  37. package/dist/extensions/index.js +13 -3
  38. package/dist/{index-k3mFbPx_.d.cts → index-CCr_z9kF.d.cts} +3 -3
  39. package/dist/{index-Ng1pG7JE.d.ts → index-Ck6ajwXh.d.ts} +3 -3
  40. package/dist/index.cjs +250 -16
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.cts +294 -8
  43. package/dist/index.d.ts +294 -8
  44. package/dist/index.js +241 -7
  45. package/dist/index.js.map +1 -1
  46. package/dist/{registry-iAHbqkYt.d.cts → registry-GJI4gToU.d.cts} +15 -4
  47. package/dist/{registry-iAHbqkYt.d.ts → registry-GJI4gToU.d.ts} +15 -4
  48. package/dist/serializers/index.cjs +3 -3
  49. package/dist/serializers/index.d.cts +1 -1
  50. package/dist/serializers/index.d.ts +1 -1
  51. package/dist/serializers/index.js +2 -2
  52. package/dist/spreadsheet/index.cjs +7 -3
  53. package/dist/spreadsheet/index.cjs.map +1 -1
  54. package/dist/spreadsheet/index.d.cts +33 -4
  55. package/dist/spreadsheet/index.d.ts +33 -4
  56. package/dist/spreadsheet/index.js +6 -2
  57. package/dist/{track-changes-G8JcWB8b.d.cts → track-changes-BSF58RK0.d.cts} +92 -9
  58. package/dist/{track-changes-G8JcWB8b.d.ts → track-changes-BSF58RK0.d.ts} +92 -9
  59. package/dist/{types-DMe0vFB1.d.cts → types-CJ4U-HHI.d.cts} +1 -1
  60. package/dist/{types-DMe0vFB1.d.ts → types-CJ4U-HHI.d.ts} +1 -1
  61. package/package.json +11 -4
  62. package/dist/chunk-3TYMNULT.js.map +0 -1
  63. package/dist/chunk-4QPLEUL2.cjs.map +0 -1
  64. package/dist/chunk-A7DKIA72.js.map +0 -1
  65. package/dist/chunk-CYFJCKC4.js.map +0 -1
  66. package/dist/chunk-GVW4P7BG.js.map +0 -1
  67. package/dist/chunk-JYRIOFEN.cjs.map +0 -1
  68. package/dist/chunk-TVRC4KVT.cjs.map +0 -1
  69. package/dist/chunk-U6NDQFOK.cjs.map +0 -1
  70. /package/dist/{chunk-KYRZDWM4.js.map → chunk-JJDLPEWL.js.map} +0 -0
package/README.md CHANGED
@@ -6,6 +6,8 @@ Framework bindings: [`@pixerate/editor-react`](https://www.npmjs.com/package/@pi
6
6
 
7
7
  ```bash
8
8
  npm install @pixerate/editor
9
+ # only if you use @pixerate/editor/canvas (or the agent canvas planner):
10
+ npm install @dagrejs/dagre
9
11
  ```
10
12
 
11
13
  ## Prompt grammar
@@ -106,18 +108,20 @@ editor.commands.resolveComment(id); // also deleteComment, setActiveComment, foc
106
108
  store.subscribe((snapshot) => save(store.toJSON()));
107
109
 
108
110
  editor.commands.toggleTrackChanges();
109
- getTrackedChanges(editor); // [{ id, type: "insertion" | "deletion", author, from, to, text, … }]
111
+ getTrackedChanges(editor); // [{ id, type: "insertion" | "deletion" | "format" | "block", author, from, to, text, description, … }]
110
112
  editor.commands.acceptChange(id); // rejectChange, acceptAllChanges, rejectAllChanges: one undo step each
111
113
  ```
112
114
 
113
115
  - **Comments** are `comment` marks holding a thread id; thread content lives in the headless `CommentStore` (`subscribe` / `getSnapshot`, cached snapshots, `toJSON` / `load`). Threads whose text is deleted are flagged `orphaned` and re-attach on undo.
114
- - **Track changes** marks typed and pasted text with an `insertion` mark and keeps deleted text with a `deletion` mark (struck through). Deleting your own insertion really deletes it. IME composition is tracked once it ends. Not tracked: formatting-only changes, block-structure changes (splitting/joining paragraphs, lists) and edits inside code blocks. Set the `TRACK_CHANGES_SKIP_META` transaction meta to apply a change untracked (e.g. `setContent`).
115
- - **Serializers** render tracked content as if all changes were accepted: `getEditorText`, `htmlToPlainText` and `getEditorMarkdown` drop deleted text by default. Pass `{ trackedChanges: "original" }` (as if rejected) or `"all"` (both). `resolveTrackedChanges(doc, view)` returns a resolved document. TipTap's own `editor.getText()` still includes deleted text.
116
- - Both extensions are also available as `createRichTextPreset({ comments: true, trackChanges: true })`. Mark styles use Tailwind classes; override them via `commentClass` / `activeClass` and `insertionHTMLAttributes` / `deletionHTMLAttributes`.
116
+ - **Track changes** marks typed and pasted text with an `insertion` mark and keeps deleted text with a `deletion` mark (struck through). Deleting your own insertion really deletes it. IME composition is tracked once it ends. Insertions and deletions inside code blocks are tracked too (`trackCodeBlocks: false` turns that off). Set the `TRACK_CHANGES_SKIP_META` transaction meta to apply a change untracked (e.g. `setContent`).
117
+ - **Tracked formatting** (`trackFormatting`, on by default): adding or removing marks (bold, italic, links, highlight, font size, …) on existing text records a `formatChange` mark holding the author, id and the `before` / `after` formatting, shown as a dotted underline. Descriptions read `Formatted: Bold`, `Removed formatting: Italic` or `Changed formatting: Font size`. Accept keeps the new formatting; reject restores the old. Adjacent or repeated formatting by the same author merges into one change, changing it back drops the record, and formatting a tracked insertion stays part of the insertion. Review marks, comments and bookmarks are never formatting (add more with `ignoredMarks`).
118
+ - **Tracked block changes** (`trackBlocks`, on by default): changing a textblock's type or attributes (paragraph ↔ heading, heading level, code block, alignment) or its wrappers (bulleted/numbered/task lists, indenting, outdenting, quotes) stores the previous state in a `blockChange` node attribute on `blockTypes` (paragraph, heading, code block), shown with a margin bar. Descriptions read `Changed Paragraph to Heading 2`, `Wrapped in Bulleted list`, `Changed alignment to Center`. Reject restores the type, attributes and nesting. Paragraph splits and joins are not tracked (a split-off block does not inherit the record). `describeFormatChange` and `describeBlockChange` build the descriptions.
119
+ - **Serializers** render tracked content as if all changes were accepted: `getEditorText`, `htmlToPlainText` and `getEditorMarkdown` drop deleted text and use the new formatting and block types by default. Pass `{ trackedChanges: "original" }` (as if rejected) or `"all"` (both). `resolveTrackedChanges(doc, view)` returns a resolved document. TipTap's own `editor.getText()` still includes deleted text.
120
+ - Both extensions are also available as `createRichTextPreset({ comments: true, trackChanges: true })`. Mark styles use Tailwind classes; override them via `commentClass` / `activeClass` and `insertionHTMLAttributes` / `deletionHTMLAttributes` / `formatChangeHTMLAttributes` / `blockChangeClass`.
117
121
 
118
122
  ## File import and rich clipboard
119
123
 
120
- An import adapter registry maps file types (MIME types and extensions) to converters. Built-in `document` adapters cover markdown (`.md`, `.markdown`, `.mdown`, `.mkdn`, `.mkd`, `.Rmd`, `.md.txt`), HTML (sanitized with the same allowlist as markdown), plain text (`.txt`) and RTF (best-effort text extraction). The longest matching extension wins, then exact MIME types, then `priority`, then the most recent registration.
124
+ An import adapter registry maps file types (MIME types and extensions) to converters. Built-in `document` adapters cover markdown (`.md`, `.markdown`, `.mdown`, `.mkdn`, `.mkd`, `.Rmd`, `.md.txt`), HTML (sanitized with the same allowlist as markdown), plain text (`.txt`), RTF (best-effort text extraction) and DOCX (`.docx`, `.docm`, `.dotx`). The longest matching extension wins, then exact MIME types, then `priority`, then the most recent registration.
121
125
 
122
126
  ```ts
123
127
  import { importFile, registerImportAdapter, getImportAccept } from "@pixerate/editor";
@@ -125,20 +129,27 @@ import { importFile, registerImportAdapter, getImportAccept } from "@pixerate/ed
125
129
  const { adapterId, result } = await importFile(file); // result: { html, text }
126
130
  input.accept = getImportAccept("document");
127
131
 
128
- // DOCX, PDF, zipped exports or CSV → spreadsheet are pluggable:
132
+ // Other formats are pluggable:
129
133
  registerImportAdapter({
130
- id: "docx",
131
- label: "Word",
132
- mimeTypes: ["application/vnd.openxmlformats-officedocument.wordprocessingml.document"],
133
- extensions: [".docx"],
134
+ id: "odt",
135
+ label: "OpenDocument text",
136
+ mimeTypes: ["application/vnd.oasis.opendocument.text"],
137
+ extensions: [".odt"],
134
138
  target: "document",
135
139
  async import(_file, ctx) {
136
- const html = await myDocxToHtml(await ctx.readArrayBuffer());
140
+ const html = await myOdtToHtml(await ctx.readArrayBuffer());
137
141
  return { html, text: htmlToPlainText(html) };
138
142
  },
139
143
  });
140
144
  ```
141
145
 
146
+ ### DOCX, PDF and zipped exports (no extra dependencies)
147
+
148
+ - **DOCX** (built in): `docxImportAdapter` reads `word/document.xml` (located via `_rels/.rels`), styles, numbering, relationships and footnotes / endnotes with the platform `DOMParser`, and maps headings (`Heading1`–`6`, `Title`, outline levels), bold / italic / underline / strike / super- and subscript, hyperlinks (including `HYPERLINK` fields), nested bulleted / numbered lists, tables (`colspan` / `rowspan`, header rows), line breaks, tabs and embedded PNG / JPEG / GIF / WebP images (as data URLs; dropped by the editor when the image extension is off). Output goes through `sanitizeImportedHtml`. **Tracked changes are imported as if accepted** (`w:ins` kept, `w:del` dropped); `createDocxImportAdapter({ trackedChanges: "original" })` imports them as if rejected. Text boxes, shapes, charts, equations, comments and layout are not imported. Without a global `DOMParser` (plain Node) it throws `ImportError("unsupported_environment")`; pass one with `createDocxImportAdapter({ DOMParser })`. Other options: `images`, `maxImageSize` (10 MiB), `zip` limits.
149
+ - **PDF** (opt-in, bring your own pdf.js): `registerImportAdapter(createPdfImportAdapter(pdfjs))` with any object shaped like `pdfjs-dist` (`getDocument({ data }).promise`, `page.getTextContent()`). Text items are grouped into lines by baseline and into paragraphs by larger-than-usual vertical gaps or blank lines; options `paragraphGap`, `maxPages`, `documentParams`. Text only. `pdfTextItemsToParagraphs` is exported for custom adapters.
150
+ - **Zipped exports** (opt-in): `registerImportAdapter(zipImportAdapter)` imports a `.zip` of markdown / HTML / text / DOCX files (Notion, Confluence, Obsidian exports) with the registry's other adapters — index / README files first, then natural path order — with a heading per file (Notion ids stripped from titles). The result adds `files` and `skipped` (unsupported types, nested archives, failures). Not registered by default because `.zip` is a generic container: claiming every dropped archive would steal drops from other handlers. `createZipImportAdapter({ headings, headingLevel, maxFiles, registry, zip })` customizes it.
151
+ - **ZIP reader**: `readZip(bytes, { maxEntries, maxTotalSize, unsafePaths })` parses the central directory (ZIP64 included) and lazily extracts stored / deflate entries via `DecompressionStream("deflate-raw")` (browsers, Node 18+), verifying CRC-32. It rejects encrypted entries, `..` / absolute names and archives over the entry or uncompressed-size caps (default 10 000 entries / 256 MiB, enforced on inflated bytes) with typed `ZipError`s. DOCX reads use 100 MiB / 5 000 entries.
152
+
142
153
  `FileImport` (or `createRichTextPreset({ fileImport: true })`) inserts dropped or pasted files at the drop position, and `FileImportController` / `pickImportFiles` back file-picker buttons. `RichClipboard` (or `richClipboard: true`) also writes copied content as `application/x.pixerate.document+json` (ProseMirror slice JSON plus a schema signature) and prefers it on paste when the schemas match, falling back to HTML / plain text. Both are opt-in, and `EditorController` keeps them on plain text in `plainTextMode`.
143
154
 
144
155
  ## Agent tool registry
@@ -187,10 +198,10 @@ Each module is also available as a subpath import:
187
198
  | `@pixerate/editor/history` | `HistoryManager`: a command-pattern undo/redo stack |
188
199
  | `@pixerate/editor/spreadsheet` | `SpreadsheetController`, formula parser/evaluator and dependency graph, formula function registry (`registerFormulaFunction`, `listFormulaFunctions`, `searchFormulaFunctions`, `getFormulaSignature`) and autocomplete helpers (`getFormulaHints`, `applyFormulaSuggestion`) |
189
200
  | `@pixerate/editor/image-editor` | `ImageEditorController` and Canvas 2D renderer: crop, adjustments, annotations, depth masks and AI hooks. Needs a DOM/canvas environment. |
190
- | `@pixerate/editor/canvas` | Node-graph layout (dagre), placement, trajectories, displacement, clipboard and streaming layout helpers |
201
+ | `@pixerate/editor/canvas` | Node-graph layout (needs the optional `@dagrejs/dagre` peer), placement, trajectories, displacement, clipboard and streaming layout helpers |
191
202
  | `@pixerate/editor/agent` | MCP tool schemas, a planner for agent-driven canvas edits, and the dynamic `ToolRegistry` with editor/spreadsheet tool providers |
192
203
 
193
- The root entry (`@pixerate/editor`) re-exports everything, including TipTap. In backends, import from `@pixerate/editor/grammar` to keep the dependency graph small.
204
+ The root entry (`@pixerate/editor`) re-exports everything except canvas, agent and image editor, including TipTap. Import from the narrowest subpath you need: `grammar`, `history`, `spreadsheet` and `image-editor` have no third-party imports, so a spreadsheet-only app bundles no TipTap, markdown or dagre code. The package is marked `sideEffects: false`, so unused root exports are also tree-shaken.
194
205
 
195
206
  ## License
196
207
 
@@ -2,12 +2,12 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkTVRC4KVTcjs = require('../chunk-TVRC4KVT.cjs');
5
+ var _chunkAA4ZEVCCcjs = require('../chunk-AA4ZEVCC.cjs');
6
6
 
7
7
 
8
8
 
9
9
 
10
- var _chunkJYRIOFENcjs = require('../chunk-JYRIOFEN.cjs');
10
+ var _chunkSAZRENEZcjs = require('../chunk-SAZRENEZ.cjs');
11
11
 
12
12
 
13
13
 
@@ -762,7 +762,7 @@ function createToolRegistry(options) {
762
762
  var toEditor = (target) => "commands" in target ? target : target.editor;
763
763
  function replaceWithText(editor, from, to, text) {
764
764
  const { state, view } = editor;
765
- const tr = /[\r\n]/.test(text) ? state.tr.replace(from, to, _chunkTVRC4KVTcjs.plainTextToSlice.call(void 0, text, state.schema)) : text ? state.tr.insertText(text, from, to) : state.tr.delete(from, to);
765
+ const tr = /[\r\n]/.test(text) ? state.tr.replace(from, to, _chunkAA4ZEVCCcjs.plainTextToSlice.call(void 0, text, state.schema)) : text ? state.tr.insertText(text, from, to) : state.tr.delete(from, to);
766
766
  view.dispatch(tr.scrollIntoView());
767
767
  }
768
768
  function selectionInfo(editor) {
@@ -788,7 +788,7 @@ function createRichTextToolProvider(target, options) {
788
788
  annotations: { readOnlyHint: true },
789
789
  handler: ({ format = "text" }) => {
790
790
  const ed = editor();
791
- const content = format === "html" ? ed.getHTML() : format === "markdown" ? _chunkTVRC4KVTcjs.getEditorMarkdown.call(void 0, ed) : _chunkTVRC4KVTcjs.getEditorText.call(void 0, ed);
791
+ const content = format === "html" ? ed.getHTML() : format === "markdown" ? _chunkAA4ZEVCCcjs.getEditorMarkdown.call(void 0, ed) : _chunkAA4ZEVCCcjs.getEditorText.call(void 0, ed);
792
792
  return { format, content, selection: selectionInfo(ed), size: ed.state.doc.content.size };
793
793
  }
794
794
  };
@@ -855,7 +855,7 @@ var CELL_REF_REGEX = /^\$?([A-Za-z]{1,3})\$?(\d{1,7})$/;
855
855
  function parseCell(ref) {
856
856
  const match = CELL_REF_REGEX.exec(ref.trim());
857
857
  if (!match) throw new Error(`Invalid cell reference "${ref}". Use A1 notation, e.g. "B3".`);
858
- return { col: _chunkJYRIOFENcjs.colNameToIndex.call(void 0, match[1].toUpperCase()), row: parseInt(match[2], 10) - 1 };
858
+ return { col: _chunkSAZRENEZcjs.colNameToIndex.call(void 0, match[1].toUpperCase()), row: parseInt(match[2], 10) - 1 };
859
859
  }
860
860
  function createSpreadsheetToolProvider(controller, options) {
861
861
  const lookup = (row, col) => {
@@ -896,14 +896,14 @@ function createSpreadsheetToolProvider(controller, options) {
896
896
  const cell = controller.getCell(found.rowDef.id, found.colDef.id);
897
897
  valueRow.push(_nullishCoalesce(cell.error, () => ( cell.value)));
898
898
  rawRow.push(cell.raw);
899
- const error = _nullishCoalesce(cell.error, () => ( (_chunkJYRIOFENcjs.isFormulaError.call(void 0, cell.value) ? cell.value : void 0)));
900
- if (error) errors.push({ cell: `${_chunkJYRIOFENcjs.indexToColName.call(void 0, c)}${r + 1}`, error });
899
+ const error = _nullishCoalesce(cell.error, () => ( (_chunkSAZRENEZcjs.isFormulaError.call(void 0, cell.value) ? cell.value : void 0)));
900
+ if (error) errors.push({ cell: `${_chunkSAZRENEZcjs.indexToColName.call(void 0, c)}${r + 1}`, error });
901
901
  }
902
902
  values.push(valueRow);
903
903
  raw.push(rawRow);
904
904
  }
905
905
  return {
906
- range: values.length && values[0].length ? `${_chunkJYRIOFENcjs.indexToColName.call(void 0, c0)}${r0 + 1}:${_chunkJYRIOFENcjs.indexToColName.call(void 0, c1)}${r1 + 1}` : null,
906
+ range: values.length && values[0].length ? `${_chunkSAZRENEZcjs.indexToColName.call(void 0, c0)}${r0 + 1}:${_chunkSAZRENEZcjs.indexToColName.call(void 0, c1)}${r1 + 1}` : null,
907
907
  values,
908
908
  raw,
909
909
  errors,
@@ -1,6 +1,6 @@
1
- import { C as CanvasNode, a as CanvasEdge, X as XYPosition, S as Side } from '../types-DMe0vFB1.cjs';
1
+ import { C as CanvasNode, a as CanvasEdge, X as XYPosition, S as Side } from '../types-CJ4U-HHI.cjs';
2
2
  import { Editor } from '@tiptap/core';
3
- import { W as SpreadsheetController } from '../controller-OUdxPY-X.cjs';
3
+ import { S as SpreadsheetController } from '../controller-B7GIybc4.cjs';
4
4
 
5
5
  /**
6
6
  * Agent presence state for visual multiplayer/agent choreography.
@@ -1,6 +1,6 @@
1
- import { C as CanvasNode, a as CanvasEdge, X as XYPosition, S as Side } from '../types-DMe0vFB1.js';
1
+ import { C as CanvasNode, a as CanvasEdge, X as XYPosition, S as Side } from '../types-CJ4U-HHI.js';
2
2
  import { Editor } from '@tiptap/core';
3
- import { W as SpreadsheetController } from '../controller-OUdxPY-X.js';
3
+ import { S as SpreadsheetController } from '../controller-B7GIybc4.js';
4
4
 
5
5
  /**
6
6
  * Agent presence state for visual multiplayer/agent choreography.
@@ -2,12 +2,12 @@ import {
2
2
  getEditorMarkdown,
3
3
  getEditorText,
4
4
  plainTextToSlice
5
- } from "../chunk-3TYMNULT.js";
5
+ } from "../chunk-MJZBNWG7.js";
6
6
  import {
7
7
  colNameToIndex,
8
8
  indexToColName,
9
9
  isFormulaError
10
- } from "../chunk-A7DKIA72.js";
10
+ } from "../chunk-DENSCKFC.js";
11
11
  import {
12
12
  ensureLayout,
13
13
  getLayoutedNodes,