@owomark/react 0.1.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.
- package/README.md +363 -0
- package/dist/index.css +32 -0
- package/dist/index.d.ts +249 -0
- package/dist/index.js +908 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
# OwoMark
|
|
2
|
+
|
|
3
|
+
A self-contained Markdown editor built on a single-layer `contenteditable` surface.
|
|
4
|
+
|
|
5
|
+
Official package: `@owomark/react`. The flat name `owomark-react` is reserved only as a compatibility redirect and should not be used for new installs.
|
|
6
|
+
|
|
7
|
+
Four packages:
|
|
8
|
+
|
|
9
|
+
| Package | Role |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `@owomark/core` | Framework-agnostic editor engine (document model, input handling, commands, shared state) |
|
|
12
|
+
| `@owomark/view` | View engine + preview rendering (DOM events, active block proxy, DOM patching, render cache) |
|
|
13
|
+
| `@owomark/react` | React components (`OwoMarkEditor`, `OwoMarkPreview`) and shared state hooks |
|
|
14
|
+
| (theme built into `@owomark/view`) | Light / dark CSS theme presets and token definitions |
|
|
15
|
+
|
|
16
|
+
## Quick Start (React)
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import { OwoMarkEditor } from '@owomark/react';
|
|
20
|
+
import '@owomark/view/style.css';
|
|
21
|
+
|
|
22
|
+
function App() {
|
|
23
|
+
const [md, setMd] = useState('# Hello\n\nStart typing...');
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<OwoMarkEditor
|
|
27
|
+
value={md}
|
|
28
|
+
onChange={setMd}
|
|
29
|
+
config={{ theme: 'light', enableSideAnnotation: true }}
|
|
30
|
+
placeholder="Write something..."
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Props
|
|
37
|
+
|
|
38
|
+
| Prop | Type | Default | Description |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| `value` | `string` | — | Controlled markdown value |
|
|
41
|
+
| `defaultValue` | `string` | — | Uncontrolled initial value |
|
|
42
|
+
| `onChange` | `(markdown: string) => void` | — | Called on content change |
|
|
43
|
+
| `onSelectionChange` | `(selection: OwoMarkSelection) => void` | — | Cursor/selection updates |
|
|
44
|
+
| `onCompositionStateChange` | `(active: boolean) => void` | — | IME composition state |
|
|
45
|
+
| `onScroll` | `React.UIEventHandler<HTMLDivElement>` | — | Forwards scroll events from the root editor container |
|
|
46
|
+
| `readOnly` | `boolean` | `false` | Disable editing |
|
|
47
|
+
| `placeholder` | `string` | — | Empty-state placeholder text |
|
|
48
|
+
| `theme` | `'light' \| 'dark' \| string` | `'light'` | Theme preset or custom class |
|
|
49
|
+
| `themeClassName` | `string` | — | Additional theme class |
|
|
50
|
+
| `className` | `string` | — | Extra CSS class on the root element |
|
|
51
|
+
| `commandsRef` | `Ref<OwoMarkCommands>` | — | Imperative command handle |
|
|
52
|
+
| `coreRef` | `Ref<OwoMarkCore \| null>` | — | Direct access to the underlying OwoMarkCore instance |
|
|
53
|
+
| `controller` | `OwoMarkSharedStateController` | — | Auto-connects editor to shared state (recommended for split editor) |
|
|
54
|
+
| `indentMode` | `'auto' \| '2' \| '4'` | `'auto'` | Tab indent width |
|
|
55
|
+
| `config` | `OwoMarkEditorConfig` | — | Unified editor config (`indentMode`, `enableSideAnnotation`, `enableMath`, `theme`) |
|
|
56
|
+
| `ariaLabel` | `string` | — | Accessibility label |
|
|
57
|
+
|
|
58
|
+
When `config` is provided, it becomes the unified configuration surface for editor behavior:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
<OwoMarkEditor
|
|
62
|
+
value={md}
|
|
63
|
+
onChange={setMd}
|
|
64
|
+
config={{
|
|
65
|
+
theme: 'light',
|
|
66
|
+
indentMode: '2',
|
|
67
|
+
enableSideAnnotation: true,
|
|
68
|
+
enableMath: true,
|
|
69
|
+
}}
|
|
70
|
+
/>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Imperative Commands
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
const cmds = useRef<OwoMarkCommands>(null);
|
|
77
|
+
|
|
78
|
+
<OwoMarkEditor commandsRef={cmds} ... />
|
|
79
|
+
|
|
80
|
+
// Later:
|
|
81
|
+
cmds.current.toggleBold();
|
|
82
|
+
cmds.current.toggleItalic();
|
|
83
|
+
cmds.current.insertLink('https://example.com');
|
|
84
|
+
cmds.current.insertCodeFence('ts');
|
|
85
|
+
cmds.current.undo();
|
|
86
|
+
cmds.current.redo();
|
|
87
|
+
cmds.current.getMarkdown();
|
|
88
|
+
cmds.current.setMarkdown('# New content');
|
|
89
|
+
cmds.current.replaceMarkdown('# New content', { anchor: 13, focus: 13 });
|
|
90
|
+
cmds.current.focus();
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Use `replaceMarkdown()` when the host needs to replace the document and also control the resulting selection in one atomic step, such as toolbar-driven inserts.
|
|
94
|
+
|
|
95
|
+
## Shared State + Preview
|
|
96
|
+
|
|
97
|
+
For a split editor with synchronized incremental preview:
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import {
|
|
101
|
+
OwoMarkEditor,
|
|
102
|
+
OwoMarkPreview,
|
|
103
|
+
useOwoMarkSharedState,
|
|
104
|
+
} from '@owomark/react';
|
|
105
|
+
import type { PreviewBlock, PreviewRenderContext } from '@owomark/react';
|
|
106
|
+
|
|
107
|
+
function SplitEditor() {
|
|
108
|
+
const controller = useOwoMarkSharedState({ initialMarkdown: '# Hello' });
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<div style={{ display: 'flex' }}>
|
|
112
|
+
<OwoMarkEditor
|
|
113
|
+
controller={controller}
|
|
114
|
+
theme="light"
|
|
115
|
+
placeholder="Write something..."
|
|
116
|
+
/>
|
|
117
|
+
<OwoMarkPreview
|
|
118
|
+
state={controller}
|
|
119
|
+
themeKey="vitesse-light"
|
|
120
|
+
className="preview-panel"
|
|
121
|
+
/>
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The `controller` prop on `OwoMarkEditor` automatically connects the editor to the shared state controller. The editor drives all state updates (content, selection, composition) directly — no manual `onChange` → `setMarkdown` bridging needed.
|
|
128
|
+
|
|
129
|
+
For hosts that still need `onChange` callbacks (e.g. for local draft persistence), they work alongside `controller`:
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
<OwoMarkEditor
|
|
133
|
+
controller={controller}
|
|
134
|
+
onChange={(md) => saveDraft(md)}
|
|
135
|
+
/>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Custom Block Renderer
|
|
139
|
+
|
|
140
|
+
Provide a host-side rendering function for full Markdown fidelity (GFM, math, syntax highlighting):
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
async function renderBlock(
|
|
144
|
+
block: PreviewBlock,
|
|
145
|
+
ctx: PreviewRenderContext,
|
|
146
|
+
): Promise<string> {
|
|
147
|
+
const result = await myUnifiedPipeline.process(block.raw);
|
|
148
|
+
return result.toString();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
<OwoMarkPreview state={controller} renderBlock={renderBlock} themeKey="vitesse-light" />
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Side Annotation
|
|
155
|
+
|
|
156
|
+
OwoMark supports right-side annotations for preview rendering and editor workflows. The feature is enabled by default and can be turned off with `config.enableSideAnnotation = false`.
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
<OwoMarkEditor
|
|
160
|
+
value={md}
|
|
161
|
+
onChange={setMd}
|
|
162
|
+
config={{ enableSideAnnotation: false }}
|
|
163
|
+
/>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
With the flag disabled:
|
|
167
|
+
|
|
168
|
+
- side-annotation syntax is kept as plain Markdown text
|
|
169
|
+
- preview does not render side annotation layout
|
|
170
|
+
- the `/side` slash command is hidden
|
|
171
|
+
- pressing Enter after an annotated block does not auto-insert `(>+)`
|
|
172
|
+
|
|
173
|
+
### Syntax Quick Reference
|
|
174
|
+
|
|
175
|
+
| Use case | Syntax |
|
|
176
|
+
|---|---|
|
|
177
|
+
| Single block annotation | `Text (>} note)` |
|
|
178
|
+
| Multi-block continuation (2-3 blocks) | `A (>} group)` + `B (>+)` |
|
|
179
|
+
| Container annotation (4+ blocks) | `:::side` + `(>} note)` + blocks + `:::` |
|
|
180
|
+
| Long-form note reference | `:::side [>id] ... :::` + `[>id]: {type=}} ...` |
|
|
181
|
+
|
|
182
|
+
### Type Table
|
|
183
|
+
|
|
184
|
+
| Symbol | Meaning | Example |
|
|
185
|
+
|---|---|---|
|
|
186
|
+
| `:` | Plain note | `(>: note)` |
|
|
187
|
+
| `}` | Right brace | `(>} grouped note)` |
|
|
188
|
+
| `{` | Left brace | `(>{ expanded note)` |
|
|
189
|
+
| `]` | Right bracket | `(>] range)` |
|
|
190
|
+
| `[` | Left bracket | `(>[ range)` |
|
|
191
|
+
| `|` | Vertical line | `(>| comment)` |
|
|
192
|
+
| `-` | Dash | `(>- remark)` |
|
|
193
|
+
| `->` | Thin arrow | `(>-> conclusion)` |
|
|
194
|
+
| `=>` | Fat arrow | `(>=> therefore)` |
|
|
195
|
+
| `~>` | Wave arrow | `(>~> related)` |
|
|
196
|
+
| `!` | Warning | `(>! caution)` |
|
|
197
|
+
| `?` | Question | `(>? todo)` |
|
|
198
|
+
|
|
199
|
+
### Examples
|
|
200
|
+
|
|
201
|
+
Single block:
|
|
202
|
+
|
|
203
|
+
```md
|
|
204
|
+
白菜 (>} 都是蔬菜)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Continuation chain:
|
|
208
|
+
|
|
209
|
+
```md
|
|
210
|
+
香蕉 (>} 都是水果)
|
|
211
|
+
菠萝 (>+)
|
|
212
|
+
苹果 (>+)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Container form:
|
|
216
|
+
|
|
217
|
+
```md
|
|
218
|
+
:::side
|
|
219
|
+
(>! 高危操作)
|
|
220
|
+
|
|
221
|
+
删除数据库前必须先备份。
|
|
222
|
+
确认备份可恢复后再执行破坏性操作。
|
|
223
|
+
:::
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Reference form:
|
|
227
|
+
|
|
228
|
+
```md
|
|
229
|
+
:::side [>cook-tip]
|
|
230
|
+
炒菜时油温很重要。
|
|
231
|
+
热锅凉油是基本功。
|
|
232
|
+
:::
|
|
233
|
+
|
|
234
|
+
[>cook-tip]: {type=}}
|
|
235
|
+
烹饪小贴士:油温过高会产生油烟,
|
|
236
|
+
日常烹饪建议控制在合适范围内。
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Notes
|
|
240
|
+
|
|
241
|
+
- Side annotations are right-side only; there is no left-column layout mode.
|
|
242
|
+
- `(>...)` must appear at the end of the block to be recognized.
|
|
243
|
+
- `(>+)` is a continuation marker only. It must follow a previous annotated sibling block.
|
|
244
|
+
- On narrow viewports, side annotations fall back to inline callout blocks under the anchor content.
|
|
245
|
+
|
|
246
|
+
### Shared State Hooks
|
|
247
|
+
|
|
248
|
+
| Hook | Description |
|
|
249
|
+
|------|-------------|
|
|
250
|
+
| `useOwoMarkSharedState(options?)` | Creates a persistent `OwoMarkSharedStateController` (survives re-renders) |
|
|
251
|
+
| `useSharedStateSnapshot(controller)` | Subscribes via `useSyncExternalStore`, returns current `OwoMarkSharedState` |
|
|
252
|
+
|
|
253
|
+
### `<OwoMarkPreview>` Props
|
|
254
|
+
|
|
255
|
+
| Prop | Type | Description |
|
|
256
|
+
|------|------|-------------|
|
|
257
|
+
| `state` | `OwoMarkSharedStateStore` | Shared state store (e.g. `controller` from `useOwoMarkSharedState`) |
|
|
258
|
+
| `themeKey` | `string` | Theme identifier for rendering and cache keying |
|
|
259
|
+
| `className` | `string` | CSS class for the preview root |
|
|
260
|
+
| `renderBlock` | `(block, context) => Promise<string>` | Custom per-block Markdown renderer |
|
|
261
|
+
| `registry` | `PreviewRendererRegistry` | Custom renderer registry (e.g. Mermaid) |
|
|
262
|
+
| `viewportFirst` | `boolean` | Prioritize rendering visible blocks first |
|
|
263
|
+
| `onContentUpdate` | `() => void` | Called after every DOM mutation — including idle backfill and deferred renders (for scroll sync) |
|
|
264
|
+
| `ariaLabel` | `string` | Accessibility label |
|
|
265
|
+
|
|
266
|
+
## CSS Setup
|
|
267
|
+
|
|
268
|
+
### Minimal (use package presets)
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
import '@owomark/view/style.css'; // includes mdx-components.css + owomark.css + side-annotation.css + slash-menu.css + light.css + dark.css
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Selective imports
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
import '@owomark/view/owomark.css'; // base editor styles (required)
|
|
278
|
+
import '@owomark/view/light.css'; // light preset tokens
|
|
279
|
+
// import '@owomark/view/dark.css'; // dark preset tokens
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Third-party theme layering
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
import '@owomark/view/style.css';
|
|
286
|
+
import 'owomark-theme-acme/style.css';
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
<OwoMarkEditor
|
|
291
|
+
value={md}
|
|
292
|
+
onChange={setMd}
|
|
293
|
+
theme="owo-theme-acme"
|
|
294
|
+
/>
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
The theme package should only override `--owo-*` tokens and optional visual details. It should not duplicate `@owomark/view` structural CSS. See the `@owomark/view` README for the authoring contract of `owomark-theme-xxx`.
|
|
298
|
+
|
|
299
|
+
## Host Theme Mapping
|
|
300
|
+
|
|
301
|
+
OwoMark uses `--owo-*` CSS custom properties for all visual styles. To integrate with your design system, map your variables to OwoMark tokens on a wrapper element:
|
|
302
|
+
|
|
303
|
+
```css
|
|
304
|
+
.my-editor-wrapper {
|
|
305
|
+
/* Surface */
|
|
306
|
+
--owo-editor-bg: var(--app-surface);
|
|
307
|
+
--owo-editor-text: var(--app-text-primary);
|
|
308
|
+
--owo-editor-heading: var(--app-text-strong);
|
|
309
|
+
|
|
310
|
+
/* Interaction */
|
|
311
|
+
--owo-editor-caret: var(--app-text-strong);
|
|
312
|
+
--owo-editor-link: var(--app-brand);
|
|
313
|
+
|
|
314
|
+
/* Border */
|
|
315
|
+
--owo-editor-border: var(--app-border);
|
|
316
|
+
|
|
317
|
+
/* Override default chrome if embedding */
|
|
318
|
+
& .owo-editor-root {
|
|
319
|
+
border: none;
|
|
320
|
+
padding: 0;
|
|
321
|
+
background: transparent;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
See `docs/specs/owomark-theme-tokens.md` for the full token list.
|
|
327
|
+
|
|
328
|
+
## Standalone Usage (no React)
|
|
329
|
+
|
|
330
|
+
Use `@owomark/core` + `@owomark/view` for a framework-free DOM editor:
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
import { createOwoMarkCore } from '@owomark/core';
|
|
334
|
+
import { createOwoMarkView } from '@owomark/view';
|
|
335
|
+
|
|
336
|
+
const core = createOwoMarkCore({ initialMarkdown: '# Hello' });
|
|
337
|
+
const view = createOwoMarkView(core, document.getElementById('editor')!);
|
|
338
|
+
|
|
339
|
+
core.onChange((markdown) => console.log(markdown));
|
|
340
|
+
|
|
341
|
+
// Cleanup
|
|
342
|
+
view.destroy();
|
|
343
|
+
core.destroy();
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Or use `createOwoMarkCore()` directly for pure logic without DOM:
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
import { createOwoMarkCore } from '@owomark/core';
|
|
350
|
+
|
|
351
|
+
const core = createOwoMarkCore({ initialMarkdown: '# Hello' });
|
|
352
|
+
console.log(core.getMarkdown());
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## Building
|
|
356
|
+
|
|
357
|
+
From the monorepo root:
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
npm run build:packages
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
This builds `@owomark/core` -> `@owomark/view` -> `@owomark/react` in dependency order using tsup (ESM + DTS).
|
package/dist/index.css
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* src/MdxSkeleton.css */
|
|
2
|
+
.owo-mdx-skeleton {
|
|
3
|
+
background:
|
|
4
|
+
linear-gradient(
|
|
5
|
+
90deg,
|
|
6
|
+
var(--owo-skeleton-base, #e5e7eb) 25%,
|
|
7
|
+
var(--owo-skeleton-shine, #f3f4f6) 50%,
|
|
8
|
+
var(--owo-skeleton-base, #e5e7eb) 75%);
|
|
9
|
+
background-size: 200% 100%;
|
|
10
|
+
animation: owo-mdx-skeleton-shimmer 1.5s ease-in-out infinite;
|
|
11
|
+
border-radius: 4px;
|
|
12
|
+
}
|
|
13
|
+
@keyframes owo-mdx-skeleton-shimmer {
|
|
14
|
+
0% {
|
|
15
|
+
background-position: 200% 0;
|
|
16
|
+
}
|
|
17
|
+
100% {
|
|
18
|
+
background-position: -200% 0;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
.owo-mdx-skeleton-block {
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
gap: 8px;
|
|
25
|
+
padding: 12px 0;
|
|
26
|
+
}
|
|
27
|
+
.owo-mdx-skeleton-line {
|
|
28
|
+
height: 14px;
|
|
29
|
+
}
|
|
30
|
+
.owo-mdx-skeleton-line:last-child {
|
|
31
|
+
width: 60%;
|
|
32
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { Ref, MutableRefObject, CSSProperties, ReactNode, ComponentType } from 'react';
|
|
3
|
+
import { IndentMode, OwoMarkSelection, OwoMarkCommands, OwoMarkCore, OwoMarkSharedStateController, OwoMarkSharedStateStore, PreviewBlock, CreateSharedStateOptions, OwoMarkSharedState, OwoMarkDocument, SlashState, BlockContext, BlockNode, VisibleRange, VirtualRow, Decorator } from '@owomark/core';
|
|
4
|
+
export { BlockContext, BlockContextType, BlockInsertType, CommandDefinition, OwoMarkCommands, OwoMarkCore, OwoMarkEditorInstance, OwoMarkSelection, OwoMarkSharedState, OwoMarkSharedStateController, OwoMarkSharedStateStore, PreviewBlock, PreviewBlockKind, SlashState } from '@owomark/core';
|
|
5
|
+
import { OwoMarkProcessorOptions, OwoMarkThemeName, PreviewStrategy, PreviewRendererRegistry, PreviewRenderContext } from '@owomark/view';
|
|
6
|
+
export { OwoMarkThemeName, PreviewRenderContext, PreviewRenderResult, PreviewRendererDefinition, PreviewRendererRegistry, THEME_DARK_CLASS, THEME_LIGHT_CLASS, getThemeClassName } from '@owomark/view';
|
|
7
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
8
|
+
|
|
9
|
+
type OwoMarkEditorConfig = {
|
|
10
|
+
/** Indent mode: 'auto' | '2' | '4'. Default: 'auto'. */
|
|
11
|
+
indentMode?: IndentMode;
|
|
12
|
+
/** Enable side annotation syntax. Default: true. */
|
|
13
|
+
enableSideAnnotation?: boolean;
|
|
14
|
+
/** Enable math (KaTeX) rendering. Default: true. */
|
|
15
|
+
enableMath?: boolean;
|
|
16
|
+
/** Enable fenced markdown sandbox rendering. Default: true. */
|
|
17
|
+
enableMarkdownSandbox?: boolean;
|
|
18
|
+
/** Minimum backtick count required for markdown sandbox fences. Default: 4. */
|
|
19
|
+
markdownSandbox?: {
|
|
20
|
+
outerFenceTicks?: number;
|
|
21
|
+
};
|
|
22
|
+
/** Color theme. Default: 'light'. */
|
|
23
|
+
theme?: 'light' | 'dark';
|
|
24
|
+
};
|
|
25
|
+
declare const DEFAULT_EDITOR_CONFIG: Required<OwoMarkEditorConfig>;
|
|
26
|
+
declare function resolveEditorConfig(config?: OwoMarkEditorConfig): Required<OwoMarkEditorConfig>;
|
|
27
|
+
declare function deriveProcessorOptions(config: Required<OwoMarkEditorConfig>): Partial<OwoMarkProcessorOptions>;
|
|
28
|
+
|
|
29
|
+
type OwoMarkEditorProps = {
|
|
30
|
+
value?: string;
|
|
31
|
+
defaultValue?: string;
|
|
32
|
+
onChange?: (markdown: string) => void;
|
|
33
|
+
onSelectionChange?: (selection: OwoMarkSelection) => void;
|
|
34
|
+
onCompositionStateChange?: (active: boolean) => void;
|
|
35
|
+
onScroll?: React.UIEventHandler<HTMLDivElement>;
|
|
36
|
+
readOnly?: boolean;
|
|
37
|
+
placeholder?: string;
|
|
38
|
+
className?: string;
|
|
39
|
+
theme?: OwoMarkThemeName;
|
|
40
|
+
themeClassName?: string;
|
|
41
|
+
commandsRef?: Ref<OwoMarkCommands>;
|
|
42
|
+
/**
|
|
43
|
+
* Ref to the underlying OwoMarkCore instance.
|
|
44
|
+
*/
|
|
45
|
+
coreRef?: Ref<OwoMarkCore | null>;
|
|
46
|
+
/**
|
|
47
|
+
* When provided, the editor automatically connects to this
|
|
48
|
+
* shared state controller.
|
|
49
|
+
*/
|
|
50
|
+
controller?: OwoMarkSharedStateController;
|
|
51
|
+
indentMode?: IndentMode;
|
|
52
|
+
ariaLabel?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Unified editor configuration. When provided, `indentMode` and `theme`
|
|
55
|
+
* props are ignored in favour of the config values.
|
|
56
|
+
*/
|
|
57
|
+
config?: OwoMarkEditorConfig;
|
|
58
|
+
};
|
|
59
|
+
declare const OwoMarkEditor: react.ForwardRefExoticComponent<OwoMarkEditorProps & react.RefAttributes<HTMLDivElement>>;
|
|
60
|
+
|
|
61
|
+
type OwoMarkPreviewProps = {
|
|
62
|
+
state: OwoMarkSharedStateStore;
|
|
63
|
+
className?: string;
|
|
64
|
+
strategy?: PreviewStrategy;
|
|
65
|
+
themeKey?: string;
|
|
66
|
+
registry?: PreviewRendererRegistry;
|
|
67
|
+
viewportFirst?: boolean;
|
|
68
|
+
ariaLabel?: string;
|
|
69
|
+
/**
|
|
70
|
+
* External block renderer. When provided, the preview engine uses this
|
|
71
|
+
* to render each block instead of the built-in lightweight renderer.
|
|
72
|
+
* Typically backed by the host's unified/remark/rehype/Shiki pipeline.
|
|
73
|
+
*
|
|
74
|
+
* Engine is recreated when this prop transitions between defined/undefined.
|
|
75
|
+
* Identity changes of the function itself do NOT cause engine recreation —
|
|
76
|
+
* the latest reference is always used via a stable ref.
|
|
77
|
+
*/
|
|
78
|
+
renderBlock?: (block: PreviewBlock, context: PreviewRenderContext) => Promise<string>;
|
|
79
|
+
/** Called after each successful DOM update. Use to trigger scroll sync. */
|
|
80
|
+
onContentUpdate?: () => void;
|
|
81
|
+
};
|
|
82
|
+
declare const OwoMarkPreview: (props: OwoMarkPreviewProps) => react_jsx_runtime.JSX.Element;
|
|
83
|
+
|
|
84
|
+
type UseOwoMarkSharedStateOptions = CreateSharedStateOptions;
|
|
85
|
+
/**
|
|
86
|
+
* React hook that creates and manages a OwoMarkSharedStateController.
|
|
87
|
+
*
|
|
88
|
+
* The controller is created once and persists across re-renders.
|
|
89
|
+
* It can be connected to a OwoMarkEditor and consumed by OwoMarkPreview.
|
|
90
|
+
*/
|
|
91
|
+
declare function useOwoMarkSharedState(options?: UseOwoMarkSharedStateOptions): OwoMarkSharedStateController;
|
|
92
|
+
/**
|
|
93
|
+
* Subscribe to shared state changes for React rendering.
|
|
94
|
+
* Returns the current OwoMarkSharedState, triggering re-renders on changes.
|
|
95
|
+
*/
|
|
96
|
+
declare function useSharedStateSnapshot(controller: OwoMarkSharedStateController): OwoMarkSharedState;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* React hook that creates and manages an OwoMarkCore instance
|
|
100
|
+
* with DOM event binding.
|
|
101
|
+
*
|
|
102
|
+
* No syncFromDOM for normal input — Core handles all inputTypes.
|
|
103
|
+
* Composition sync is the only controlled exception.
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
type UseOwoMarkCoreOptions = {
|
|
107
|
+
initialMarkdown: string;
|
|
108
|
+
readOnly?: boolean;
|
|
109
|
+
onCompositionStateChange?: (active: boolean) => void;
|
|
110
|
+
};
|
|
111
|
+
type UseOwoMarkCoreReturn = {
|
|
112
|
+
core: OwoMarkCore;
|
|
113
|
+
doc: OwoMarkDocument;
|
|
114
|
+
slashState: SlashState;
|
|
115
|
+
containerRef: MutableRefObject<HTMLDivElement | null>;
|
|
116
|
+
};
|
|
117
|
+
declare function useOwoMarkCore(options: UseOwoMarkCoreOptions): UseOwoMarkCoreReturn;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* React hook that subscribes to block context changes from OwoMarkCore.
|
|
121
|
+
* Returns the current BlockContext (block type, depth, heading level, etc.)
|
|
122
|
+
* at the cursor position.
|
|
123
|
+
*
|
|
124
|
+
* This replaces the host-maintained identifyBlockAtCursor() pattern.
|
|
125
|
+
*/
|
|
126
|
+
|
|
127
|
+
declare function useBlockContext(core: OwoMarkCore): BlockContext;
|
|
128
|
+
|
|
129
|
+
type UseVirtualListOptions = {
|
|
130
|
+
blocks: readonly BlockNode[];
|
|
131
|
+
/** The scrollable container element. */
|
|
132
|
+
containerRef: React.RefObject<HTMLElement | null>;
|
|
133
|
+
/** Number of extra blocks to render above/below viewport. Default: 5 */
|
|
134
|
+
overscan?: number;
|
|
135
|
+
/** Estimated characters per line for height estimation. Default: 80 */
|
|
136
|
+
charsPerLine?: number;
|
|
137
|
+
};
|
|
138
|
+
type UseVirtualListResult = {
|
|
139
|
+
/** Current visible range (inclusive indices). */
|
|
140
|
+
visibleRange: VisibleRange;
|
|
141
|
+
/** All virtual rows with heights. */
|
|
142
|
+
rows: VirtualRow[];
|
|
143
|
+
/** Total scrollable height. */
|
|
144
|
+
totalHeight: number;
|
|
145
|
+
/** Update a block's measured height. */
|
|
146
|
+
updateBlockHeight: (blockId: string, height: number) => void;
|
|
147
|
+
/** Check if a block index is in the visible range. */
|
|
148
|
+
isBlockVisible: (blockIndex: number) => boolean;
|
|
149
|
+
/** Force re-measure all heights. */
|
|
150
|
+
invalidateAllHeights: () => void;
|
|
151
|
+
};
|
|
152
|
+
declare function useVirtualList(options: UseVirtualListOptions): UseVirtualListResult;
|
|
153
|
+
|
|
154
|
+
type EditorBlockProps = {
|
|
155
|
+
block: BlockNode;
|
|
156
|
+
blockIndex: number;
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Block layer: logical line unit.
|
|
160
|
+
* Re-renders only when block identity (id) or depth changes.
|
|
161
|
+
* Decorators handle finer-grained updates.
|
|
162
|
+
*/
|
|
163
|
+
declare const EditorBlock: react.NamedExoticComponent<EditorBlockProps>;
|
|
164
|
+
|
|
165
|
+
type EditorDecoratorProps = {
|
|
166
|
+
decorator: Decorator;
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Decorator layer: applies visual styling.
|
|
170
|
+
* Re-renders only when decorator type or leaf content changes.
|
|
171
|
+
*/
|
|
172
|
+
declare const EditorDecorator: react.NamedExoticComponent<EditorDecoratorProps>;
|
|
173
|
+
|
|
174
|
+
type EditorLeafProps = {
|
|
175
|
+
text: string;
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* Leaf layer: carries the actual text content.
|
|
179
|
+
* Re-renders only when text changes.
|
|
180
|
+
*/
|
|
181
|
+
declare const EditorLeaf: react.NamedExoticComponent<EditorLeafProps>;
|
|
182
|
+
|
|
183
|
+
type SlashMenuProps = {
|
|
184
|
+
state: SlashState;
|
|
185
|
+
onSelect: (index: number) => void;
|
|
186
|
+
onDismiss: () => void;
|
|
187
|
+
};
|
|
188
|
+
declare const SlashMenu: react.NamedExoticComponent<SlashMenuProps>;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Lightweight caret positioning utility for slash menu.
|
|
192
|
+
* Uses Range.getBoundingClientRect() + viewport flip.
|
|
193
|
+
* ~30 LOC — no external dependency.
|
|
194
|
+
*/
|
|
195
|
+
type CaretRect = {
|
|
196
|
+
top: number;
|
|
197
|
+
left: number;
|
|
198
|
+
bottom: number;
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* Get the caret's screen coordinates from the current selection.
|
|
202
|
+
* Returns null if no selection or range is available.
|
|
203
|
+
*/
|
|
204
|
+
declare function getCaretRect(): CaretRect | null;
|
|
205
|
+
/**
|
|
206
|
+
* Compute menu position given caret rect and menu dimensions.
|
|
207
|
+
* Places menu below caret by default; flips above if it would overflow viewport.
|
|
208
|
+
*/
|
|
209
|
+
declare function computeMenuPosition(caret: CaretRect, menuHeight: number, menuWidth: number): {
|
|
210
|
+
top: number;
|
|
211
|
+
left: number;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
type MdxSkeletonProps = {
|
|
215
|
+
/** Height in pixels for the skeleton placeholder. */
|
|
216
|
+
height?: number;
|
|
217
|
+
/** Number of shimmer lines to show. Default: 3. */
|
|
218
|
+
lines?: number;
|
|
219
|
+
className?: string;
|
|
220
|
+
style?: CSSProperties;
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* Lightweight skeleton loading indicator for MDX component placeholders.
|
|
224
|
+
* Pure CSS animation — no external UI library dependency.
|
|
225
|
+
*/
|
|
226
|
+
declare function MdxSkeleton({ height, lines, className, style }: MdxSkeletonProps): react_jsx_runtime.JSX.Element;
|
|
227
|
+
|
|
228
|
+
type MdxComponentShellProps = {
|
|
229
|
+
/** Component display name, used as cache key for height estimation. */
|
|
230
|
+
name: string;
|
|
231
|
+
children: ReactNode;
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
* Wraps an MDX component to provide:
|
|
235
|
+
* 1. Skeleton placeholder with estimated height before first paint
|
|
236
|
+
* 2. Real-height measurement after mount via ResizeObserver
|
|
237
|
+
* 3. Height caching so subsequent renders of the same component type
|
|
238
|
+
* use a better estimate, reducing layout shift.
|
|
239
|
+
*/
|
|
240
|
+
declare function MdxComponentShell({ name, children }: MdxComponentShellProps): react_jsx_runtime.JSX.Element;
|
|
241
|
+
/**
|
|
242
|
+
* Create a wrapped version of an MDX component that renders inside
|
|
243
|
+
* MdxComponentShell for skeleton loading + height management.
|
|
244
|
+
*/
|
|
245
|
+
declare function wrapWithShell<P extends Record<string, any>>(name: string, Component: ComponentType<P>): ComponentType<P>;
|
|
246
|
+
/** Clear the height cache (useful for testing or theme changes). */
|
|
247
|
+
declare function clearComponentHeightCache(): void;
|
|
248
|
+
|
|
249
|
+
export { type CaretRect, DEFAULT_EDITOR_CONFIG, EditorBlock, type EditorBlockProps, EditorDecorator, type EditorDecoratorProps, EditorLeaf, type EditorLeafProps, MdxComponentShell, type MdxComponentShellProps, MdxSkeleton, type MdxSkeletonProps, OwoMarkEditor, type OwoMarkEditorConfig, type OwoMarkEditorProps, OwoMarkPreview, type OwoMarkPreviewProps, SlashMenu, type SlashMenuProps, type UseOwoMarkCoreOptions, type UseOwoMarkCoreReturn, type UseOwoMarkSharedStateOptions, type UseVirtualListOptions, type UseVirtualListResult, clearComponentHeightCache, computeMenuPosition, deriveProcessorOptions, getCaretRect, resolveEditorConfig, useBlockContext, useOwoMarkCore, useOwoMarkSharedState, useSharedStateSnapshot, useVirtualList, wrapWithShell };
|