@editneo/react 0.1.0 → 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 +334 -0
  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 ADDED
@@ -0,0 +1,334 @@
1
+ # @editneo/react
2
+
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
+
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
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @editneo/react @editneo/core
11
+ ```
12
+
13
+ React 18 or 19 is required as a peer dependency.
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
+
22
+ ## Getting Started
23
+
24
+ A minimal working editor:
25
+
26
+ ```tsx
27
+ import { NeoEditor } from "@editneo/react";
28
+
29
+ function App() {
30
+ return <NeoEditor id="my-document" />;
31
+ }
32
+ ```
33
+
34
+ The `id` prop is a unique identifier for the document. It is used to namespace IndexedDB storage and sync rooms.
35
+
36
+ ## Components
37
+
38
+ ### `<NeoEditor />`
39
+
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.
41
+
42
+ | Prop | Type | Default | Description |
43
+ | ------------- | ------------------------------------------------- | -------- | --------------------------------------------------------------- |
44
+ | `id` | `string` | required | Unique document identifier |
45
+ | `offline` | `boolean` | `true` | Enable offline persistence via IndexedDB |
46
+ | `syncConfig` | `{ url: string; room: string }` | — | WebSocket server URL and room name for real-time collaboration |
47
+ | `theme` | `{ mode: 'light' \| 'dark'; [key: string]: any }` | — | Theme configuration |
48
+ | `renderBlock` | `(block, defaultRender) => ReactNode` | — | Intercept rendering for custom block types |
49
+ | `className` | `string` | — | CSS class for the outer wrapper |
50
+ | `children` | `ReactNode` | — | Toolbar, menus, or other UI to render inside the editor context |
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
+
54
+ **Usage with collaboration:**
55
+
56
+ ```tsx
57
+ <NeoEditor
58
+ id="shared-doc"
59
+ syncConfig={{
60
+ url: "wss://your-yjs-server.com",
61
+ room: "shared-doc",
62
+ }}
63
+ theme={{ mode: "dark" }}
64
+ >
65
+ <Aeropeak />
66
+ <SlashMenu />
67
+ <CursorOverlay />
68
+ </NeoEditor>
69
+ ```
70
+
71
+ **Custom block rendering:**
72
+
73
+ ```tsx
74
+ <NeoEditor
75
+ id="doc"
76
+ renderBlock={(block, defaultRender) => {
77
+ if (block.type === "spreadsheet") {
78
+ return <SpreadsheetEmbed block={block} />;
79
+ }
80
+ return defaultRender;
81
+ }}
82
+ />
83
+ ```
84
+
85
+ ### `<NeoCanvas />`
86
+
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.
88
+
89
+ ### `<BlockRenderer />`
90
+
91
+ Routes each block to the correct renderer based on its `type` field. Supports all built-in block types:
92
+
93
+ - **Text blocks:** paragraph, heading-1, heading-2, heading-3
94
+ - **Lists:** bullet-list, ordered-list, todo-list
95
+ - **Media:** image, video
96
+ - **Structural:** quote, callout, divider, code-block
97
+
98
+ If `renderBlock` is provided through the editor context, it is called first, giving you the chance to handle custom types before falling through to the defaults.
99
+
100
+ ### `<EditableBlock />`
101
+
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:
103
+
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
110
+
111
+ ---
112
+
113
+ ## Interactive Components
114
+
115
+ ### `<Aeropeak />` — Floating Toolbar
116
+
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.
118
+
119
+ | Prop | Type | Default | Description |
120
+ | ----------- | ----------------------------- | -------- | -------------------------------------------- |
121
+ | `children` | `ReactNode` | — | Custom toolbar content (replaces defaults) |
122
+ | `offset` | `number` | `10` | Vertical offset from the selection in pixels |
123
+ | `animation` | `'fade' \| 'scale' \| 'none'` | `'fade'` | Appearance animation |
124
+
125
+ ```tsx
126
+ // Default toolbar
127
+ <Aeropeak />
128
+
129
+ // Custom toolbar
130
+ <Aeropeak offset={12} animation="scale">
131
+ <AeroButton
132
+ icon={<strong>B</strong>}
133
+ label="Bold"
134
+ onClick={(editor) => editor.toggleMark?.('bold')}
135
+ />
136
+ <Separator />
137
+ <AeroButton
138
+ icon={<em>I</em>}
139
+ label="Italic"
140
+ onClick={(editor) => editor.toggleMark?.('italic')}
141
+ />
142
+ </Aeropeak>
143
+ ```
144
+
145
+ **Compound components:**
146
+
147
+ - `Aeropeak.Bold` / `Aeropeak.Italic` / `Aeropeak.Underline` / `Aeropeak.Strike` / `Aeropeak.Code` / `Aeropeak.Link` — prebuilt buttons
148
+ - `AeroButton` — a button that receives the editor instance when clicked
149
+ - `Separator` — a thin vertical divider between button groups
150
+
151
+ ### `<SlashMenu />` — Command Palette
152
+
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 `/`.
154
+
155
+ | Prop | Type | Default | Description |
156
+ | ---------------- | ------------------------------- | ------- | ------------------------------------------------------ |
157
+ | `customCommands` | `CommandItem[]` | `[]` | Additional commands to show alongside the defaults |
158
+ | `filter` | `(cmd: CommandItem) => boolean` | — | Filter function to hide certain commands |
159
+ | `menuComponent` | `React.ComponentType` | — | Completely replace the menu UI with a custom component |
160
+
161
+ **Built-in commands:** Paragraph, Heading 1-3, Bulleted List, Ordered List, To-do List, Quote, Code Block, Divider, Callout, Image.
162
+
163
+ New blocks are inserted immediately **after** the current block.
164
+
165
+ ```tsx
166
+ <SlashMenu
167
+ customCommands={[
168
+ {
169
+ key: "diagram",
170
+ label: "Diagram",
171
+ icon: <span>chart</span>,
172
+ execute: (editor) => editor.addBlock("image"),
173
+ },
174
+ ]}
175
+ />
176
+ ```
177
+
178
+ ### `<PDFDropZone />`
179
+
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.
181
+
182
+ | Prop | Type | Default | Description |
183
+ | --------------- | ------------------------------------------- | ------- | ------------------------------------------------------------------------------------ |
184
+ | `onDrop` | `(files: File[]) => void` | — | Callback when files are dropped. If provided, the default PDF extraction is skipped. |
185
+ | `renderOverlay` | `(props: { isOver: boolean }) => ReactNode` | — | Custom overlay content shown during drag-over |
186
+ | `children` | `ReactNode` | — | Content inside the drop zone |
187
+
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
+
190
+ ### `<CursorOverlay />`
191
+
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 |
197
+
198
+ ```tsx
199
+ <NeoEditor id="doc" syncConfig={{ url: "wss://...", room: "doc" }}>
200
+ <CursorOverlay />
201
+ </NeoEditor>
202
+ ```
203
+
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 |
219
+
220
+ ---
221
+
222
+ ## Hooks
223
+
224
+ ### `useEditor()`
225
+
226
+ The primary hook for interacting with the editor. Must be called inside a `<NeoEditor />`. Returns all store state and actions.
227
+
228
+ ```tsx
229
+ const {
230
+ blocks, // Record<string, NeoBlock>
231
+ rootBlocks, // string[]
232
+ selection, // { blockId, startOffset, endOffset }
233
+ addBlock, // (type, afterId?) => void
234
+ insertBlock, // alias for addBlock
235
+ insertFullBlock, // (block, afterId?) => void
236
+ updateBlock, // (id, partial) => void
237
+ deleteBlock, // (id) => void
238
+ moveBlock, // (id, afterId) => void
239
+ setBlockType, // (id, type) => void
240
+ toggleMark, // (mark) => void
241
+ setLink, // (url | null) => void
242
+ exportJSON, // () => { blocks, rootBlocks }
243
+ importJSON, // (data) => void
244
+ undo, // () => void
245
+ redo, // () => void
246
+ } = useEditor();
247
+ ```
248
+
249
+ Throws an error if called outside of `<NeoEditor />`.
250
+
251
+ ### `useSelection()`
252
+
253
+ A focused hook that subscribes only to the selection state, minimizing re-renders.
254
+
255
+ ```tsx
256
+ const selection = useSelection();
257
+ // { blockId: string | null, startOffset: number, endOffset: number }
258
+ ```
259
+
260
+ ### `useSyncStatus()`
261
+
262
+ Returns the current sync connection status.
263
+
264
+ ```tsx
265
+ const status = useSyncStatus();
266
+ // 'connected' or 'disconnected'
267
+ ```
268
+
269
+ ---
270
+
271
+ ## Theming
272
+
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:
274
+
275
+ ```css
276
+ :root {
277
+ --neo-font-family: "Inter", system-ui, sans-serif;
278
+ --neo-font-size-body: 16px;
279
+ --neo-code-font: "Fira Code", "Consolas", monospace;
280
+ --neo-accent-color: #3b82f6;
281
+ --neo-bg-canvas: #ffffff;
282
+ --neo-text-primary: #111827;
283
+ --neo-text-secondary: #6b7280;
284
+ --neo-selection-color: #b4d5fe;
285
+ --neo-border-color: #e5e7eb;
286
+ --neo-border-radius: 4px;
287
+ --neo-block-spacing: 4px;
288
+ --neo-content-width: 800px;
289
+ }
290
+ ```
291
+
292
+ Switch modes via the `theme` prop or via the `data-theme` attribute:
293
+
294
+ ```tsx
295
+ <NeoEditor id="doc" theme={{ mode: "dark" }} />
296
+ ```
297
+
298
+ ## Exports
299
+
300
+ Everything is exported from the package root:
301
+
302
+ ```typescript
303
+ // Components
304
+ export { NeoEditor, EditorContext } from "./NeoEditor";
305
+ export { NeoCanvas } from "./NeoCanvas";
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/*";
319
+
320
+ // Interactive UI
321
+ export {
322
+ PDFDropZone,
323
+ CursorOverlay,
324
+ Aeropeak,
325
+ SlashMenu,
326
+ } from "./components/*";
327
+
328
+ // Hooks
329
+ export { useEditor, useSelection, useSyncStatus } from "./hooks";
330
+ ```
331
+
332
+ ## License
333
+
334
+ MIT
@@ -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"}