@editneo/react 0.1.1 → 0.1.2

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 (37) hide show
  1. package/README.md +81 -40
  2. package/dist/EditableBlock.d.ts.map +1 -1
  3. package/dist/EditableBlock.js +123 -15
  4. package/dist/EditableBlock.js.map +1 -1
  5. package/dist/NeoCanvas.d.ts.map +1 -1
  6. package/dist/NeoCanvas.js +33 -8
  7. package/dist/NeoCanvas.js.map +1 -1
  8. package/dist/NeoEditor.d.ts +12 -4
  9. package/dist/NeoEditor.d.ts.map +1 -1
  10. package/dist/NeoEditor.js +51 -15
  11. package/dist/NeoEditor.js.map +1 -1
  12. package/dist/blocks/ListBlock.d.ts.map +1 -1
  13. package/dist/blocks/ListBlock.js +17 -1
  14. package/dist/blocks/ListBlock.js.map +1 -1
  15. package/dist/components/Aeropeak.d.ts +3 -1
  16. package/dist/components/Aeropeak.d.ts.map +1 -1
  17. package/dist/components/Aeropeak.js +46 -24
  18. package/dist/components/Aeropeak.js.map +1 -1
  19. package/dist/components/CursorOverlay.d.ts +4 -1
  20. package/dist/components/CursorOverlay.d.ts.map +1 -1
  21. package/dist/components/CursorOverlay.js +113 -10
  22. package/dist/components/CursorOverlay.js.map +1 -1
  23. package/dist/components/PDFDropZone.d.ts.map +1 -1
  24. package/dist/components/PDFDropZone.js +23 -33
  25. package/dist/components/PDFDropZone.js.map +1 -1
  26. package/dist/components/SlashMenu.d.ts.map +1 -1
  27. package/dist/components/SlashMenu.js +50 -46
  28. package/dist/components/SlashMenu.js.map +1 -1
  29. package/dist/hooks.d.ts +25 -0
  30. package/dist/hooks.d.ts.map +1 -1
  31. package/dist/hooks.js +19 -6
  32. package/dist/hooks.js.map +1 -1
  33. package/dist/index.d.ts +9 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +13 -1
  36. package/dist/index.js.map +1 -1
  37. package/package.json +23 -5
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  The React component layer for EditNeo. This package provides `NeoEditor` (the root editor component), a set of ready-made block renderers, interactive UI components like the floating toolbar and slash-command menu, and hooks for reading and manipulating editor state.
4
4
 
5
- Rendering is virtualized with `@tanstack/react-virtual`, so documents with thousands of blocks remain responsive.
5
+ Each `NeoEditor` instance creates its own isolated store, so multiple editors on the same page work independently. Rendering is virtualized with `@tanstack/react-virtual`, so documents with thousands of blocks remain responsive.
6
6
 
7
7
  ## Installation
8
8
 
@@ -12,6 +12,13 @@ npm install @editneo/react @editneo/core
12
12
 
13
13
  React 18 or 19 is required as a peer dependency.
14
14
 
15
+ **Optional packages:**
16
+
17
+ ```bash
18
+ npm install @editneo/sync # For real-time collaboration & offline persistence
19
+ npm install @editneo/pdf # For PDF drag-and-drop import
20
+ ```
21
+
15
22
  ## Getting Started
16
23
 
17
24
  A minimal working editor:
@@ -30,7 +37,7 @@ The `id` prop is a unique identifier for the document. It is used to namespace I
30
37
 
31
38
  ### `<NeoEditor />`
32
39
 
33
- The root component. It sets up the editor context, initializes the optional `SyncManager`, applies the theme, and renders the virtualized block canvas.
40
+ The root component. It creates a per-instance editor store, sets up the optional `SyncManager`, applies the theme, and renders the virtualized block canvas.
34
41
 
35
42
  | Prop | Type | Default | Description |
36
43
  | ------------- | ------------------------------------------------- | -------- | --------------------------------------------------------------- |
@@ -42,6 +49,8 @@ The root component. It sets up the editor context, initializes the optional `Syn
42
49
  | `className` | `string` | — | CSS class for the outer wrapper |
43
50
  | `children` | `ReactNode` | — | Toolbar, menus, or other UI to render inside the editor context |
44
51
 
52
+ > **Note:** `@editneo/sync` is lazy-loaded via dynamic `import()` and only instantiated when `syncConfig` or `offline` is set. If the package isn't installed, the editor works fine without it.
53
+
45
54
  **Usage with collaboration:**
46
55
 
47
56
  ```tsx
@@ -55,13 +64,12 @@ The root component. It sets up the editor context, initializes the optional `Syn
55
64
  >
56
65
  <Aeropeak />
57
66
  <SlashMenu />
67
+ <CursorOverlay />
58
68
  </NeoEditor>
59
69
  ```
60
70
 
61
71
  **Custom block rendering:**
62
72
 
63
- If you have custom block types beyond the built-in ones, use `renderBlock` to intercept them:
64
-
65
73
  ```tsx
66
74
  <NeoEditor
67
75
  id="doc"
@@ -76,7 +84,7 @@ If you have custom block types beyond the built-in ones, use `renderBlock` to in
76
84
 
77
85
  ### `<NeoCanvas />`
78
86
 
79
- The virtualized document canvas. It renders only the blocks currently visible in the viewport, using `@tanstack/react-virtual` for smooth scrolling through large documents. You typically don't render this directly — `NeoEditor` includes it automatically.
87
+ The virtualized document canvas. It renders only the blocks currently visible in the viewport, using `@tanstack/react-virtual` for smooth scrolling. Block size estimates are type-aware (headings are taller than paragraphs, code blocks taller still). You typically don't render this directly — `NeoEditor` includes it automatically.
80
88
 
81
89
  ### `<BlockRenderer />`
82
90
 
@@ -91,11 +99,14 @@ If `renderBlock` is provided through the editor context, it is called first, giv
91
99
 
92
100
  ### `<EditableBlock />`
93
101
 
94
- Handles the content-editable rendering and input processing for a single block. It converts the block's `Span[]` content into styled inline elements (bold, italic, code, underline, strikethrough, links). It also handles:
102
+ Handles the content-editable rendering and input processing for a single block. It converts the block's `Span[]` content into styled inline elements (bold, italic, code, underline, strikethrough, links, colors, highlights). It also handles:
95
103
 
96
- - Enter key to split the block and create a new paragraph
97
- - Backspace at the start of a block to delete it
98
- - Input events to update the block's text content
104
+ - **Enter** split the block and create a new paragraph
105
+ - **Backspace** at the start delete the block
106
+ - **Ctrl+Z / Ctrl+Y** undo / redo
107
+ - **Shift+Enter** — soft line break (`<br>`)
108
+ - **Tab** — indent the block (increases indent level)
109
+ - Input events parsed from the DOM, preserving inline formatting
99
110
 
100
111
  ---
101
112
 
@@ -103,7 +114,7 @@ Handles the content-editable rendering and input processing for a single block.
103
114
 
104
115
  ### `<Aeropeak />` — Floating Toolbar
105
116
 
106
- A toolbar that appears above the user's text selection. By default it shows Bold, Italic, Strikethrough, and Link buttons. You can replace the default buttons with your own by passing children.
117
+ A toolbar that appears above the user's text selection. By default it shows Bold, Italic, Underline, Strikethrough, Code, and Link buttons. SSR-safe.
107
118
 
108
119
  | Prop | Type | Default | Description |
109
120
  | ----------- | ----------------------------- | -------- | -------------------------------------------- |
@@ -112,7 +123,7 @@ A toolbar that appears above the user's text selection. By default it shows Bold
112
123
  | `animation` | `'fade' \| 'scale' \| 'none'` | `'fade'` | Appearance animation |
113
124
 
114
125
  ```tsx
115
- // Default toolbar with Bold, Italic, Strike, Link
126
+ // Default toolbar
116
127
  <Aeropeak />
117
128
 
118
129
  // Custom toolbar
@@ -133,13 +144,13 @@ A toolbar that appears above the user's text selection. By default it shows Bold
133
144
 
134
145
  **Compound components:**
135
146
 
136
- - `Aeropeak.Bold` / `Aeropeak.Italic` / `Aeropeak.Strike` / `Aeropeak.Link` — prebuilt buttons
147
+ - `Aeropeak.Bold` / `Aeropeak.Italic` / `Aeropeak.Underline` / `Aeropeak.Strike` / `Aeropeak.Code` / `Aeropeak.Link` — prebuilt buttons
137
148
  - `AeroButton` — a button that receives the editor instance when clicked
138
149
  - `Separator` — a thin vertical divider between button groups
139
150
 
140
151
  ### `<SlashMenu />` — Command Palette
141
152
 
142
- Appears when the user types `/` in the editor. Lists available block types, supports keyboard navigation (arrow keys + enter), and filters results as the user continues typing.
153
+ Appears when the user types `/` in the editor. Lists available block types, supports keyboard navigation (arrow keys + Enter), and filters results as the user continues typing after `/`.
143
154
 
144
155
  | Prop | Type | Default | Description |
145
156
  | ---------------- | ------------------------------- | ------- | ------------------------------------------------------ |
@@ -149,6 +160,8 @@ Appears when the user types `/` in the editor. Lists available block types, supp
149
160
 
150
161
  **Built-in commands:** Paragraph, Heading 1-3, Bulleted List, Ordered List, To-do List, Quote, Code Block, Divider, Callout, Image.
151
162
 
163
+ New blocks are inserted immediately **after** the current block.
164
+
152
165
  ```tsx
153
166
  <SlashMenu
154
167
  customCommands={[
@@ -162,20 +175,9 @@ Appears when the user types `/` in the editor. Lists available block types, supp
162
175
  />
163
176
  ```
164
177
 
165
- **`CommandItem` interface:**
166
-
167
- ```typescript
168
- interface CommandItem {
169
- key: string; // Unique key
170
- label: string; // Display label
171
- icon?: ReactNode; // Icon element
172
- execute: (editor: any) => void; // Action to perform
173
- }
174
- ```
175
-
176
178
  ### `<PDFDropZone />`
177
179
 
178
- A wrapper component that detects PDF file drops and runs client-side extraction to convert the PDF into editor blocks.
180
+ A wrapper component that detects PDF file drops, runs client-side extraction via `@editneo/pdf`, and inserts the extracted blocks into the editor. Shows a processing indicator while extraction is in progress.
179
181
 
180
182
  | Prop | Type | Default | Description |
181
183
  | --------------- | ------------------------------------------- | ------- | ------------------------------------------------------------------------------------ |
@@ -183,13 +185,15 @@ A wrapper component that detects PDF file drops and runs client-side extraction
183
185
  | `renderOverlay` | `(props: { isOver: boolean }) => ReactNode` | — | Custom overlay content shown during drag-over |
184
186
  | `children` | `ReactNode` | — | Content inside the drop zone |
185
187
 
186
- ```tsx
187
- <PDFDropZone>{/* Your editor content goes inside */}</PDFDropZone>
188
- ```
188
+ > **Note:** `@editneo/pdf` is lazy-loaded via dynamic `import()`. If the package isn't installed, the drop zone logs a warning and does nothing.
189
189
 
190
190
  ### `<CursorOverlay />`
191
191
 
192
- Displays colored cursors and labels for remote collaborators when using real-time sync. Each user's cursor position is tracked through Yjs awareness.
192
+ Displays colored cursors and name labels for remote collaborators. Cursor positions are calculated using `Range.getBoundingClientRect()` for pixel-accurate placement, and updated automatically via `MutationObserver` when the DOM changes.
193
+
194
+ | Prop | Type | Default | Description |
195
+ | ------------- | -------------------------------------- | ------- | ---------------------------------------- |
196
+ | `renderLabel` | `(user: { name, color }) => ReactNode` | — | Custom label renderer for remote cursors |
193
197
 
194
198
  ```tsx
195
199
  <NeoEditor id="doc" syncConfig={{ url: "wss://...", room: "doc" }}>
@@ -197,7 +201,21 @@ Displays colored cursors and labels for remote collaborators when using real-tim
197
201
  </NeoEditor>
198
202
  ```
199
203
 
200
- Cursor colors are assigned automatically based on the user's awareness client ID.
204
+ ---
205
+
206
+ ## Block Components
207
+
208
+ All block components are exported and can be used standalone if needed:
209
+
210
+ | Component | Block Type | Description |
211
+ | -------------- | ------------------- | -------------------------------------------------- |
212
+ | `HeadingBlock` | heading-1/2/3 | Renders `<h1>`, `<h2>`, or `<h3>` |
213
+ | `ListBlock` | bullet/ordered/todo | Supports numbered lists and interactive checkboxes |
214
+ | `CodeBlock` | code-block | Monospace code with language support |
215
+ | `QuoteBlock` | quote | Bordered blockquote |
216
+ | `CalloutBlock` | callout | Highlighted callout box |
217
+ | `DividerBlock` | divider | Horizontal rule |
218
+ | `MediaBlock` | image, video | Image/video with src, alt, width, height props |
201
219
 
202
220
  ---
203
221
 
@@ -205,7 +223,7 @@ Cursor colors are assigned automatically based on the user's awareness client ID
205
223
 
206
224
  ### `useEditor()`
207
225
 
208
- The primary hook for interacting with the editor. Must be called inside a `<NeoEditor />`. Returns all store state and actions, plus convenience aliases.
226
+ The primary hook for interacting with the editor. Must be called inside a `<NeoEditor />`. Returns all store state and actions.
209
227
 
210
228
  ```tsx
211
229
  const {
@@ -214,9 +232,15 @@ const {
214
232
  selection, // { blockId, startOffset, endOffset }
215
233
  addBlock, // (type, afterId?) => void
216
234
  insertBlock, // alias for addBlock
235
+ insertFullBlock, // (block, afterId?) => void
217
236
  updateBlock, // (id, partial) => void
218
237
  deleteBlock, // (id) => void
238
+ moveBlock, // (id, afterId) => void
239
+ setBlockType, // (id, type) => void
219
240
  toggleMark, // (mark) => void
241
+ setLink, // (url | null) => void
242
+ exportJSON, // () => { blocks, rootBlocks }
243
+ importJSON, // (data) => void
220
244
  undo, // () => void
221
245
  redo, // () => void
222
246
  } = useEditor();
@@ -226,7 +250,7 @@ Throws an error if called outside of `<NeoEditor />`.
226
250
 
227
251
  ### `useSelection()`
228
252
 
229
- A focused hook that subscribes only to the selection state, minimizing re-renders in components that don't care about the full document.
253
+ A focused hook that subscribes only to the selection state, minimizing re-renders.
230
254
 
231
255
  ```tsx
232
256
  const selection = useSelection();
@@ -246,28 +270,31 @@ const status = useSyncStatus();
246
270
 
247
271
  ## Theming
248
272
 
249
- `NeoEditor` applies a set of CSS variables to the document root. You can override them globally in your stylesheet:
273
+ `NeoEditor` scopes CSS variables to its root element, so multiple editors on the same page can have different themes without conflicts. Override any variable in your stylesheet:
250
274
 
251
275
  ```css
252
276
  :root {
253
277
  --neo-font-family: "Inter", system-ui, sans-serif;
254
278
  --neo-font-size-body: 16px;
279
+ --neo-code-font: "Fira Code", "Consolas", monospace;
255
280
  --neo-accent-color: #3b82f6;
256
281
  --neo-bg-canvas: #ffffff;
257
282
  --neo-text-primary: #111827;
283
+ --neo-text-secondary: #6b7280;
284
+ --neo-selection-color: #b4d5fe;
285
+ --neo-border-color: #e5e7eb;
258
286
  --neo-border-radius: 4px;
259
287
  --neo-block-spacing: 4px;
288
+ --neo-content-width: 800px;
260
289
  }
261
290
  ```
262
291
 
263
- Or switch between light and dark mode via the `theme` prop:
292
+ Switch modes via the `theme` prop or via the `data-theme` attribute:
264
293
 
265
294
  ```tsx
266
295
  <NeoEditor id="doc" theme={{ mode: "dark" }} />
267
296
  ```
268
297
 
269
- Dark mode sets `--neo-bg-canvas` to `#0f172a` and `--neo-text-primary` to `#f3f4f6`.
270
-
271
298
  ## Exports
272
299
 
273
300
  Everything is exported from the package root:
@@ -277,12 +304,26 @@ Everything is exported from the package root:
277
304
  export { NeoEditor, EditorContext } from "./NeoEditor";
278
305
  export { NeoCanvas } from "./NeoCanvas";
279
306
  export { EditableBlock } from "./EditableBlock";
307
+ export { BlockRenderer } from "./BlockRenderer";
308
+
309
+ // Block renderers
310
+ export {
311
+ HeadingBlock,
312
+ ListBlock,
313
+ MediaBlock,
314
+ CodeBlock,
315
+ QuoteBlock,
316
+ CalloutBlock,
317
+ DividerBlock,
318
+ } from "./blocks/*";
280
319
 
281
320
  // Interactive UI
282
- export { PDFDropZone } from "./components/PDFDropZone";
283
- export { CursorOverlay } from "./components/CursorOverlay";
284
- export { Aeropeak, AeroButton, Separator } from "./components/Aeropeak";
285
- export { SlashMenu } from "./components/SlashMenu";
321
+ export {
322
+ PDFDropZone,
323
+ CursorOverlay,
324
+ Aeropeak,
325
+ SlashMenu,
326
+ } from "./components/*";
286
327
 
287
328
  // Hooks
288
329
  export { useEditor, useSelection, useSyncStatus } from "./hooks";
@@ -1 +1 @@
1
- {"version":3,"file":"EditableBlock.d.ts","sourceRoot":"","sources":["../src/EditableBlock.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAkB,MAAM,eAAe,CAAC;AAEzD,UAAU,kBAAkB;IAC1B,KAAK,EAAE,QAAQ,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAqEtD,CAAC"}
1
+ {"version":3,"file":"EditableBlock.d.ts","sourceRoot":"","sources":["../src/EditableBlock.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAwC,MAAM,OAAO,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAQ,MAAM,eAAe,CAAC;AAI/C,UAAU,kBAAkB;IAC1B,KAAK,EAAE,QAAQ,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AA8ED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAmGtD,CAAC"}
@@ -1,41 +1,149 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useRef, useEffect } from 'react';
3
- import { useEditorStore } from '@editneo/core';
2
+ import { useRef, useContext, useEffect } from 'react';
3
+ import { useStore } from 'zustand';
4
+ import { EditorContext } from './NeoEditor';
5
+ /**
6
+ * Parse the contentEditable DOM back into Span[] preserving inline formatting.
7
+ * Walks the child nodes and reads computed/element styles to reconstruct
8
+ * bold, italic, underline, strikethrough, code, link, color, and highlight.
9
+ */
10
+ function parseContentEditableToSpans(el) {
11
+ const spans = [];
12
+ function walk(node, inherited) {
13
+ var _a, _b;
14
+ if (node.nodeType === Node.TEXT_NODE) {
15
+ const text = node.textContent || '';
16
+ if (text.length > 0) {
17
+ spans.push({ text, ...inherited });
18
+ }
19
+ return;
20
+ }
21
+ if (node.nodeType !== Node.ELEMENT_NODE)
22
+ return;
23
+ const elem = node;
24
+ const tag = elem.tagName.toLowerCase();
25
+ // Build formatting from the element
26
+ const fmt = { ...inherited };
27
+ if (tag === 'b' || tag === 'strong')
28
+ fmt.bold = true;
29
+ if (tag === 'i' || tag === 'em')
30
+ fmt.italic = true;
31
+ if (tag === 'u')
32
+ fmt.underline = true;
33
+ if (tag === 's' || tag === 'del' || tag === 'strike')
34
+ fmt.strike = true;
35
+ if (tag === 'code')
36
+ fmt.code = true;
37
+ if (tag === 'a')
38
+ fmt.link = elem.getAttribute('href') || undefined;
39
+ // Check inline styles
40
+ const style = elem.style;
41
+ if (style.fontWeight === 'bold' || parseInt(style.fontWeight) >= 700)
42
+ fmt.bold = true;
43
+ if (style.fontStyle === 'italic')
44
+ fmt.italic = true;
45
+ if ((_a = style.textDecoration) === null || _a === void 0 ? void 0 : _a.includes('underline'))
46
+ fmt.underline = true;
47
+ if ((_b = style.textDecoration) === null || _b === void 0 ? void 0 : _b.includes('line-through'))
48
+ fmt.strike = true;
49
+ if (style.color)
50
+ fmt.color = style.color;
51
+ if (style.backgroundColor && style.backgroundColor !== 'transparent')
52
+ fmt.highlight = style.backgroundColor;
53
+ for (const child of Array.from(elem.childNodes)) {
54
+ walk(child, fmt);
55
+ }
56
+ }
57
+ for (const child of Array.from(el.childNodes)) {
58
+ walk(child, {});
59
+ }
60
+ // Merge adjacent spans with identical formatting
61
+ const merged = [];
62
+ for (const span of spans) {
63
+ const prev = merged[merged.length - 1];
64
+ if (prev && spansHaveSameFormat(prev, span)) {
65
+ prev.text += span.text;
66
+ }
67
+ else {
68
+ merged.push({ ...span });
69
+ }
70
+ }
71
+ return merged;
72
+ }
73
+ function spansHaveSameFormat(a, b) {
74
+ return (!!a.bold === !!b.bold &&
75
+ !!a.italic === !!b.italic &&
76
+ !!a.underline === !!b.underline &&
77
+ !!a.strike === !!b.strike &&
78
+ !!a.code === !!b.code &&
79
+ (a.color || '') === (b.color || '') &&
80
+ (a.highlight || '') === (b.highlight || '') &&
81
+ (a.link || '') === (b.link || ''));
82
+ }
4
83
  export const EditableBlock = ({ block, autoFocus }) => {
5
84
  const contentRef = useRef(null);
6
- const updateBlock = useEditorStore((state) => state.updateBlock);
7
- const addBlock = useEditorStore((state) => state.addBlock);
8
- const deleteBlock = useEditorStore((state) => state.deleteBlock);
85
+ const context = useContext(EditorContext);
86
+ if (!context) {
87
+ throw new Error('EditableBlock must be used within a NeoEditor');
88
+ }
89
+ const updateBlock = useStore(context.store, (state) => state.updateBlock);
90
+ const addBlock = useStore(context.store, (state) => state.addBlock);
91
+ const deleteBlock = useStore(context.store, (state) => state.deleteBlock);
92
+ const undo = useStore(context.store, (state) => state.undo);
93
+ const redo = useStore(context.store, (state) => state.redo);
9
94
  useEffect(() => {
10
95
  if (autoFocus && contentRef.current) {
11
96
  contentRef.current.focus();
12
97
  }
13
98
  }, [autoFocus]);
99
+ /** (#9) Parse DOM back into spans preserving formatting */
14
100
  const handleInput = (e) => {
15
- const text = e.currentTarget.innerText;
16
- // Simple text update for now, Span parsing to be added later
17
- updateBlock(block.id, { content: [{ text }] });
101
+ const el = contentRef.current;
102
+ if (!el)
103
+ return;
104
+ const newSpans = parseContentEditableToSpans(el);
105
+ updateBlock(block.id, { content: newSpans });
18
106
  };
107
+ /** (#23) Additional keyboard shortcuts */
19
108
  const handleKeyDown = (e) => {
20
109
  var _a;
21
- if (e.key === 'Enter') {
110
+ // Ctrl+Z / Cmd+Z = undo
111
+ if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
112
+ e.preventDefault();
113
+ undo();
114
+ return;
115
+ }
116
+ // Ctrl+Shift+Z / Ctrl+Y = redo
117
+ if ((e.ctrlKey || e.metaKey) && (e.key === 'y' || (e.key === 'z' && e.shiftKey))) {
118
+ e.preventDefault();
119
+ redo();
120
+ return;
121
+ }
122
+ if (e.key === 'Enter' && !e.shiftKey) {
22
123
  e.preventDefault();
23
124
  addBlock('paragraph', block.id);
24
125
  }
126
+ else if (e.key === 'Enter' && e.shiftKey) {
127
+ // Shift+Enter: let browser insert <br> (soft break) — don't prevent default
128
+ }
25
129
  else if (e.key === 'Backspace' && ((_a = contentRef.current) === null || _a === void 0 ? void 0 : _a.innerText) === '') {
26
130
  e.preventDefault();
27
131
  deleteBlock(block.id);
28
132
  }
29
- else if (e.key === '/') {
30
- // Slash menu trigger logic (placeholder)
133
+ else if (e.key === 'Tab') {
134
+ e.preventDefault();
135
+ // Insert 2 spaces for now — real indent to be added
136
+ document.execCommand('insertText', false, ' ');
31
137
  }
32
138
  };
33
139
  return (_jsx("div", { ref: contentRef, contentEditable: true, suppressContentEditableWarning: true, onInput: handleInput, onKeyDown: handleKeyDown, style: {
34
140
  minHeight: '24px',
35
141
  outline: 'none',
36
142
  padding: '4px 0',
37
- // Basic styling based on type
38
- fontSize: block.type === 'heading-1' ? '2em' : '1em',
143
+ fontSize: block.type === 'heading-1' ? '2em'
144
+ : block.type === 'heading-2' ? '1.5em'
145
+ : block.type === 'heading-3' ? '1.25em'
146
+ : '1em',
39
147
  fontWeight: block.type.startsWith('heading') ? 'bold' : 'normal',
40
148
  }, children: block.content.map((span, i) => {
41
149
  let style = {};
@@ -53,10 +161,10 @@ export const EditableBlock = ({ block, autoFocus }) => {
53
161
  style.backgroundColor = span.highlight;
54
162
  const content = _jsx("span", { style: style, children: span.text }, i);
55
163
  if (span.code) {
56
- return _jsx("code", { style: { fontFamily: 'monospace', backgroundColor: '#eee', padding: '2px 4px', borderRadius: '3px' }, children: content }, i);
164
+ return _jsx("code", { style: { fontFamily: 'var(--neo-code-font, monospace)', backgroundColor: '#eee', padding: '2px 4px', borderRadius: '3px' }, children: content }, i);
57
165
  }
58
166
  if (span.link) {
59
- return _jsx("a", { href: span.link, style: { color: 'blue', textDecoration: 'underline' }, children: content }, i);
167
+ return _jsx("a", { href: span.link, style: { color: 'var(--neo-accent-color, #3b82f6)', textDecoration: 'underline' }, children: content }, i);
60
168
  }
61
169
  return content;
62
170
  }) }));
@@ -1 +1 @@
1
- {"version":3,"file":"EditableBlock.js","sourceRoot":"","sources":["../src/EditableBlock.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"EditableBlock.js","sourceRoot":"","sources":["../src/EditableBlock.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"NeoCanvas.d.ts","sourceRoot":"","sources":["../src/NeoCanvas.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAiB,MAAM,OAAO,CAAC;AAKtC,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAsD7B,CAAC"}
1
+ {"version":3,"file":"NeoCanvas.d.ts","sourceRoot":"","sources":["../src/NeoCanvas.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6B,MAAM,OAAO,CAAC;AAMlD,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAkF7B,CAAC"}
package/dist/NeoCanvas.js CHANGED
@@ -1,26 +1,51 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useRef } from 'react';
2
+ import { useRef, useContext } from 'react';
3
3
  import { useVirtualizer } from '@tanstack/react-virtual';
4
- import { useEditorStore } from '@editneo/core';
5
- import { EditableBlock } from './EditableBlock';
4
+ import { useStore } from 'zustand';
5
+ import { EditorContext } from './NeoEditor';
6
+ import { BlockRenderer } from './BlockRenderer';
6
7
  export const NeoCanvas = () => {
7
- const rootBlocks = useEditorStore((state) => state.rootBlocks);
8
- const blocks = useEditorStore((state) => state.blocks);
8
+ const context = useContext(EditorContext);
9
+ if (!context) {
10
+ throw new Error('NeoCanvas must be used within a NeoEditor');
11
+ }
12
+ const rootBlocks = useStore(context.store, (state) => state.rootBlocks);
13
+ const blocks = useStore(context.store, (state) => state.blocks);
9
14
  const parentRef = useRef(null);
15
+ /** (#26) Type-aware size estimates for better virtualizer performance */
16
+ const estimateSize = (index) => {
17
+ const blockId = rootBlocks[index];
18
+ const block = blocks[blockId];
19
+ if (!block)
20
+ return 35;
21
+ switch (block.type) {
22
+ case 'heading-1': return 60;
23
+ case 'heading-2': return 48;
24
+ case 'heading-3': return 40;
25
+ case 'code-block': return 120;
26
+ case 'image':
27
+ case 'video': return 200;
28
+ case 'divider': return 24;
29
+ default: return 35;
30
+ }
31
+ };
10
32
  const rowVirtualizer = useVirtualizer({
11
33
  count: rootBlocks.length,
12
34
  getScrollElement: () => parentRef.current,
13
- estimateSize: () => 35, // Estimate row height
35
+ estimateSize,
14
36
  overscan: 5,
15
37
  });
16
38
  return (_jsx("div", { ref: parentRef, style: {
17
- height: '100vh',
39
+ height: '100%', /* (#24) was 100vh, causing double scrollbar */
18
40
  width: '100%',
19
41
  overflow: 'auto',
20
42
  }, children: _jsx("div", { style: {
21
43
  height: `${rowVirtualizer.getTotalSize()}px`,
22
44
  width: '100%',
23
45
  position: 'relative',
46
+ maxWidth: 'var(--neo-content-width, 800px)',
47
+ margin: '0 auto',
48
+ padding: '0 1rem',
24
49
  }, children: rowVirtualizer.getVirtualItems().map((virtualRow) => {
25
50
  const blockId = rootBlocks[virtualRow.index];
26
51
  const block = blocks[blockId];
@@ -32,7 +57,7 @@ export const NeoCanvas = () => {
32
57
  left: 0,
33
58
  width: '100%',
34
59
  transform: `translateY(${virtualRow.start}px)`,
35
- }, children: _jsx(EditableBlock, { block: block }) }, virtualRow.key));
60
+ }, children: _jsx(BlockRenderer, { block: block }) }, virtualRow.key));
36
61
  }) }) }));
37
62
  };
38
63
  //# sourceMappingURL=NeoCanvas.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"NeoCanvas.js","sourceRoot":"","sources":["../src/NeoCanvas.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,MAAM,CAAC,MAAM,SAAS,GAAa,GAAG,EAAE;IACtC,MAAM,UAAU,GAAG,cAAc,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAG,cAAc,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACvD,MAAM,SAAS,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAE/C,MAAM,cAAc,GAAG,cAAc,CAAC;QACpC,KAAK,EAAE,UAAU,CAAC,MAAM;QACxB,gBAAgB,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO;QACzC,YAAY,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,sBAAsB;QAC9C,QAAQ,EAAE,CAAC;KACZ,CAAC,CAAC;IAEH,OAAO,CACL,cACE,GAAG,EAAE,SAAS,EACd,KAAK,EAAE;YACL,MAAM,EAAE,OAAO;YACf,KAAK,EAAE,MAAM;YACb,QAAQ,EAAE,MAAM;SACjB,YAED,cACE,KAAK,EAAE;gBACL,MAAM,EAAE,GAAG,cAAc,CAAC,YAAY,EAAE,IAAI;gBAC5C,KAAK,EAAE,MAAM;gBACb,QAAQ,EAAE,UAAU;aACrB,YAEA,cAAc,CAAC,eAAe,EAAE,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE;gBACnD,MAAM,OAAO,GAAG,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;gBAC7C,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;gBAE9B,IAAI,CAAC,KAAK;oBAAE,OAAO,IAAI,CAAC;gBAExB,OAAO,CACL,4BAEc,UAAU,CAAC,KAAK,EAC5B,GAAG,EAAE,cAAc,CAAC,cAAc,EAClC,KAAK,EAAE;wBACL,QAAQ,EAAE,UAAU;wBACpB,GAAG,EAAE,CAAC;wBACN,IAAI,EAAE,CAAC;wBACP,KAAK,EAAE,MAAM;wBACb,SAAS,EAAE,cAAc,UAAU,CAAC,KAAK,KAAK;qBAC/C,YAED,KAAC,aAAa,IAAC,KAAK,EAAE,KAAK,GAAI,IAX1B,UAAU,CAAC,GAAG,CAYf,CACP,CAAC;YACJ,CAAC,CAAC,GACE,GACF,CACP,CAAC;AACJ,CAAC,CAAC"}
1
+ {"version":3,"file":"NeoCanvas.js","sourceRoot":"","sources":["../src/NeoCanvas.tsx"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { SyncManager } from '@editneo/sync';
2
+ import { EditorStoreInstance, EditorStore } from '@editneo/core';
3
3
  import './styles.css';
4
4
  export interface NeoEditorProps {
5
5
  id: string;
@@ -16,10 +16,18 @@ export interface NeoEditorProps {
16
16
  };
17
17
  children?: React.ReactNode;
18
18
  }
19
- export declare const EditorContext: React.Context<{
19
+ interface EditorContextValue {
20
20
  editorId: string;
21
- syncManager: SyncManager | null;
21
+ store: EditorStoreInstance;
22
+ syncManager: any | null;
22
23
  renderBlock?: (block: any, defaultRender: any) => React.ReactNode;
23
- } | null>;
24
+ }
25
+ export declare const EditorContext: React.Context<EditorContextValue | null>;
26
+ /**
27
+ * Hook to access the current editor's Zustand store via context.
28
+ * Supports selectors for fine-grained re-render control.
29
+ */
30
+ export declare function useEditorStoreContext<T>(selector: (state: EditorStore) => T): T;
24
31
  export declare const NeoEditor: React.FC<NeoEditorProps>;
32
+ export {};
25
33
  //# sourceMappingURL=NeoEditor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NeoEditor.d.ts","sourceRoot":"","sources":["../src/NeoEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsD,MAAM,OAAO,CAAC;AAE3E,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,cAAc,CAAC;AAEtB,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE;QACX,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,KAAK,KAAK,CAAC,SAAS,CAAC;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;QAEvB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;IACF,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,eAAO,MAAM,aAAa;cACd,MAAM;iBACH,WAAW,GAAG,IAAI;kBACjB,CAAC,KAAK,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,KAAK,KAAK,CAAC,SAAS;SACpD,CAAC;AAEhB,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CA4D9C,CAAC"}
1
+ {"version":3,"file":"NeoEditor.d.ts","sourceRoot":"","sources":["../src/NeoEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0E,MAAM,OAAO,CAAC;AAC/F,OAAO,EAAqB,mBAAmB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAGpF,OAAO,cAAc,CAAC;AAEtB,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE;QACX,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,KAAK,KAAK,CAAC,SAAS,CAAC;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;QACvB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;IACF,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,mBAAmB,CAAC;IAC3B,WAAW,EAAE,GAAG,GAAG,IAAI,CAAC;IACxB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,KAAK,KAAK,CAAC,SAAS,CAAC;CACnE;AAED,eAAO,MAAM,aAAa,0CAAiD,CAAC;AAE5E;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,CAAC,GAAG,CAAC,CAM/E;AAED,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CAsF9C,CAAC"}