@openeditor/native 0.0.32 → 0.0.34

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/dist/index.js CHANGED
@@ -1,910 +1,2 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { DEFAULT_CALLOUT_EMOJI, DEFAULT_PAGE_EMOJI, createDocument, findBlockSpecForNode, isOpenEditorBlockEnabled, moveTopLevelBlock, normalizeEmoji, } from "@openeditor/core";
3
- import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
4
- import { Image as NativeImage, Keyboard, Modal, Platform, Pressable, StyleSheet, Text, TextInput, View } from "react-native";
5
- import { fromNativeEditorDocument, sanitizeNativeEditorDocument, toNativeEditorDocument, } from "./document.js";
6
- import { resolveSelectedTopLevelIndex } from "./selection.js";
7
- import { createOpenEditorNativeToolbarItems, defaultDocumentForToolbarAction, openEditorNativeToolbarActionKeys, } from "./toolbar.js";
8
- import { normalizeNativeThemeColor } from "./theme.js";
9
- export { normalizeNativeThemeColor } from "./theme.js";
10
- import { OpenEditorNativeEmojiPicker } from "./emoji-picker.js";
11
- export { OpenEditorNativeEmojiPicker, } from "./emoji-picker.js";
12
- export { emojiForNativeSkinTone, getOpenEditorNativeEmojiSections, openEditorNativeEmojiSkinToneOptions, } from "./emoji-data.js";
13
- import { defaultBlockRegistry } from "@openeditor/extensions";
14
- const defaultLightNeutralSurface = "#eeeeee";
15
- const defaultLightStructuralLine = "#d4d4d4";
16
- const defaultNativeTheme = {
17
- background: "#fafafa",
18
- surface: "#ffffff",
19
- surfaceMuted: defaultLightNeutralSurface,
20
- blockSurface: defaultLightNeutralSurface,
21
- text: "#171717",
22
- textSoft: "#262626",
23
- muted: "#737373",
24
- placeholder: "#a3a3a3",
25
- border: defaultLightNeutralSurface,
26
- borderStrong: defaultLightStructuralLine,
27
- structuralLine: defaultLightStructuralLine,
28
- accent: defaultLightNeutralSurface,
29
- accentText: "#171717",
30
- accentStrong: "#171717",
31
- primary: "#171717",
32
- primaryText: "#ffffff",
33
- codeBackground: defaultLightNeutralSurface,
34
- codeText: "#171717",
35
- debugBackground: "#171717",
36
- debugText: "#d4d4d4",
37
- debugHeading: "#ffffff",
38
- debugBorder: "#404040",
39
- debugButtonBackground: "#262626",
40
- success: "#86efac",
41
- shadow: "rgba(0, 0, 0, 0.12)",
42
- backdrop: "rgba(0, 0, 0, 0.32)",
43
- toolbar: {},
44
- };
45
- const openEditorNativeSchema = {
46
- nodes: [
47
- { name: "doc", content: "block+", role: "doc" },
48
- { name: "paragraph", content: "inline*", group: "block", role: "textBlock", htmlTag: "p" },
49
- { name: "heading", content: "inline*", group: "block", attrs: { level: { default: 2 } }, role: "textBlock" },
50
- { name: "h1", content: "inline*", group: "block", role: "textBlock", htmlTag: "h1" },
51
- { name: "h2", content: "inline*", group: "block", role: "textBlock", htmlTag: "h2" },
52
- { name: "h3", content: "inline*", group: "block", role: "textBlock", htmlTag: "h3" },
53
- { name: "h4", content: "inline*", group: "block", role: "textBlock", htmlTag: "h4" },
54
- { name: "h5", content: "inline*", group: "block", role: "textBlock", htmlTag: "h5" },
55
- { name: "h6", content: "inline*", group: "block", role: "textBlock", htmlTag: "h6" },
56
- { name: "blockquote", content: "block+", group: "block", role: "block", htmlTag: "blockquote" },
57
- { name: "bulletList", content: "listItem+", group: "block", role: "list", htmlTag: "ul" },
58
- { name: "orderedList", content: "listItem+", group: "block", attrs: { start: { default: 1 } }, role: "list", htmlTag: "ol" },
59
- { name: "taskList", content: "taskItem+", group: "block", role: "list", htmlTag: "ul" },
60
- { name: "listItem", content: "paragraph block*", role: "listItem", htmlTag: "li" },
61
- {
62
- name: "taskItem",
63
- content: "paragraph block*",
64
- role: "listItem",
65
- htmlTag: "li",
66
- attrs: { checked: { default: false } },
67
- },
68
- { name: "toggleList", content: "toggleListItem+", group: "block", role: "list", htmlTag: "ul" },
69
- {
70
- name: "toggleListItem",
71
- content: "paragraph block*",
72
- role: "listItem",
73
- htmlTag: "li",
74
- attrs: { open: { default: true } },
75
- },
76
- {
77
- name: "callout",
78
- content: "block+",
79
- group: "block",
80
- role: "block",
81
- htmlTag: "aside",
82
- attrs: { emoji: { default: DEFAULT_CALLOUT_EMOJI } },
83
- },
84
- {
85
- name: "page",
86
- content: "inline*",
87
- group: "block",
88
- role: "textBlock",
89
- htmlTag: "p",
90
- attrs: {
91
- pageId: { default: null },
92
- icon: { default: DEFAULT_PAGE_EMOJI },
93
- href: { default: null },
94
- },
95
- },
96
- {
97
- name: "attachment",
98
- content: "",
99
- group: "block",
100
- role: "block",
101
- htmlTag: "div",
102
- isVoid: true,
103
- attrs: {
104
- attachmentId: { default: null },
105
- name: { default: "" },
106
- mimeType: { default: null },
107
- size: { default: null },
108
- url: { default: null },
109
- },
110
- },
111
- { name: "codeBlock", content: "text*", group: "block", role: "textBlock", htmlTag: "pre" },
112
- { name: "horizontalRule", content: "", group: "block", role: "block", htmlTag: "hr", isVoid: true },
113
- {
114
- name: "columns",
115
- content: "column+",
116
- group: "block",
117
- role: "block",
118
- layoutRole: "columns",
119
- },
120
- {
121
- name: "column",
122
- content: "block+",
123
- role: "block",
124
- layoutRole: "column",
125
- },
126
- {
127
- name: "table",
128
- content: "tableRow+",
129
- group: "block",
130
- role: "block",
131
- layoutRole: "table",
132
- htmlTag: "table",
133
- },
134
- {
135
- name: "tableRow",
136
- content: "(tableCell | tableHeader)+",
137
- role: "block",
138
- layoutRole: "tableRow",
139
- htmlTag: "tr",
140
- },
141
- {
142
- name: "tableCell",
143
- content: "block+",
144
- role: "block",
145
- layoutRole: "tableCell",
146
- htmlTag: "td",
147
- attrs: {
148
- colspan: { default: 1 },
149
- rowspan: { default: 1 },
150
- colwidth: { default: null },
151
- align: { default: null },
152
- },
153
- },
154
- {
155
- name: "tableHeader",
156
- content: "block+",
157
- role: "block",
158
- layoutRole: "tableHeader",
159
- htmlTag: "th",
160
- attrs: {
161
- colspan: { default: 1 },
162
- rowspan: { default: 1 },
163
- colwidth: { default: null },
164
- align: { default: null },
165
- },
166
- },
167
- {
168
- name: "image",
169
- content: "",
170
- group: "block",
171
- attrs: {
172
- src: {},
173
- alt: { default: null },
174
- title: { default: null },
175
- width: { default: null },
176
- height: { default: null },
177
- },
178
- role: "block",
179
- htmlTag: "img",
180
- isVoid: true,
181
- },
182
- { name: "hardBreak", content: "", group: "inline", role: "hardBreak", htmlTag: "br", isVoid: true },
183
- { name: "text", content: "", group: "inline", role: "text" },
184
- ],
185
- marks: [
186
- { name: "bold" },
187
- { name: "italic" },
188
- { name: "underline" },
189
- { name: "strike" },
190
- { name: "code" },
191
- { name: "link", attrs: { href: {} } },
192
- ],
193
- };
194
- const toNativeEditorTheme = (theme) => ({
195
- backgroundColor: normalizeNativeThemeColor(theme.background),
196
- blockSurfaceColor: normalizeNativeThemeColor(theme.blockSurface),
197
- structuralLineColor: normalizeNativeThemeColor(theme.structuralLine),
198
- accentStrongColor: normalizeNativeThemeColor(theme.accentStrong),
199
- accentStrongTextColor: normalizeNativeThemeColor(theme.primaryText),
200
- borderRadius: 0,
201
- placeholderColor: normalizeNativeThemeColor(theme.placeholder),
202
- contentInsets: {
203
- top: 16,
204
- right: 16,
205
- bottom: 16,
206
- left: 16,
207
- },
208
- text: {
209
- color: normalizeNativeThemeColor(theme.text),
210
- fontSize: 16,
211
- lineHeight: 24,
212
- },
213
- paragraph: {
214
- color: normalizeNativeThemeColor(theme.textSoft),
215
- fontSize: 16,
216
- lineHeight: 24,
217
- spacingAfter: 12,
218
- },
219
- headings: {
220
- h1: { color: normalizeNativeThemeColor(theme.text), fontSize: 28, fontWeight: "700", lineHeight: 34, spacingAfter: 14 },
221
- h2: { color: normalizeNativeThemeColor(theme.text), fontSize: 24, fontWeight: "700", lineHeight: 30, spacingAfter: 12 },
222
- h3: { color: normalizeNativeThemeColor(theme.text), fontSize: 20, fontWeight: "700", lineHeight: 26, spacingAfter: 10 },
223
- h4: { color: normalizeNativeThemeColor(theme.text), fontSize: 18, fontWeight: "700", lineHeight: 24, spacingAfter: 8 },
224
- h5: { color: normalizeNativeThemeColor(theme.text), fontSize: 16, fontWeight: "700", lineHeight: 22, spacingAfter: 8 },
225
- h6: { color: normalizeNativeThemeColor(theme.text), fontSize: 14, fontWeight: "700", lineHeight: 20, spacingAfter: 8 },
226
- },
227
- links: {
228
- color: normalizeNativeThemeColor(theme.accentStrong),
229
- underline: true,
230
- },
231
- list: {
232
- markerColor: normalizeNativeThemeColor(theme.muted),
233
- itemSpacing: 6,
234
- },
235
- blockquote: {
236
- borderColor: normalizeNativeThemeColor(theme.structuralLine),
237
- borderWidth: 3,
238
- text: {
239
- color: normalizeNativeThemeColor(theme.textSoft),
240
- fontSize: 16,
241
- lineHeight: 24,
242
- },
243
- },
244
- codeBlock: {
245
- backgroundColor: normalizeNativeThemeColor(theme.codeBackground),
246
- borderRadius: 8,
247
- paddingHorizontal: 12,
248
- paddingVertical: 8,
249
- text: {
250
- color: normalizeNativeThemeColor(theme.codeText),
251
- fontSize: 15,
252
- lineHeight: 22,
253
- },
254
- },
255
- table: {
256
- cellBackgroundColor: normalizeNativeThemeColor(theme.blockSurface),
257
- headerBackgroundColor: normalizeNativeThemeColor(theme.blockSurface),
258
- borderColor: normalizeNativeThemeColor(theme.structuralLine),
259
- selectionBackgroundColor: normalizeNativeThemeColor(theme.accent),
260
- selectionBorderColor: normalizeNativeThemeColor(theme.accentStrong),
261
- },
262
- horizontalRule: {
263
- color: normalizeNativeThemeColor(theme.structuralLine),
264
- thickness: 1,
265
- verticalMargin: 16,
266
- },
267
- toolbar: {
268
- appearance: "native",
269
- ...(typeof theme.toolbar.height === "number" ? { height: theme.toolbar.height } : {}),
270
- buttonColor: normalizeNativeThemeColor(theme.text),
271
- buttonActiveColor: normalizeNativeThemeColor(theme.text),
272
- buttonActiveBackgroundColor: normalizeNativeThemeColor(theme.accent),
273
- buttonDisabledColor: normalizeNativeThemeColor(theme.muted),
274
- },
275
- });
276
- const loadNativeEditor = () => {
277
- if (Platform.OS === "web") {
278
- return null;
279
- }
280
- try {
281
- return require("@openeditor/react-native-prose-editor").NativeRichTextEditor;
282
- }
283
- catch {
284
- return null;
285
- }
286
- };
287
- const dismissKeyboard = () => {
288
- try {
289
- const { KeyboardController } = require("react-native-keyboard-controller");
290
- void KeyboardController.dismiss();
291
- }
292
- catch {
293
- Keyboard.dismiss();
294
- }
295
- };
296
- const contentFromNativeJson = (json) => fromNativeEditorDocument(json);
297
- const normalizeForNativeEditor = (document) => sanitizeNativeEditorDocument(document);
298
- const isSafeInputUrl = (value, allowDataImage = false) => {
299
- const normalized = value.trim().toLowerCase();
300
- if (!normalized) {
301
- return false;
302
- }
303
- if (normalized.startsWith("javascript:") || normalized.startsWith("vbscript:")) {
304
- return false;
305
- }
306
- if (normalized.startsWith("data:")) {
307
- return allowDataImage && normalized.startsWith("data:image/");
308
- }
309
- return true;
310
- };
311
- const documentsMatch = (left, right) => JSON.stringify(left) === JSON.stringify(right);
312
- const attachmentTypeLabel = (mimeType, name) => {
313
- const subtype = mimeType?.split("/").at(-1)?.split("+")[0]?.trim();
314
- const extension = name.includes(".") ? name.split(".").at(-1)?.trim() : undefined;
315
- return (subtype || extension || "FILE").slice(0, 4).toUpperCase();
316
- };
317
- const formatAttachmentSize = (size) => {
318
- if (size === null || !Number.isFinite(size) || size < 0)
319
- return null;
320
- if (size < 1024)
321
- return `${Math.round(size)} B`;
322
- if (size < 1024 * 1024)
323
- return `${(size / 1024).toFixed(size < 10 * 1024 ? 1 : 0)} KB`;
324
- return `${(size / (1024 * 1024)).toFixed(size < 10 * 1024 * 1024 ? 1 : 0)} MB`;
325
- };
326
- const NativeToggleListItem = ({ children, initiallyOpen, theme, }) => {
327
- const [open, setOpen] = useState(initiallyOpen);
328
- const childArray = Array.isArray(children) ? children : [children];
329
- return (_jsxs(View, { style: styles.viewerListRow, children: [_jsx(Pressable, { accessibilityLabel: open ? "Collapse toggle item" : "Expand toggle item", accessibilityRole: "button", hitSlop: 8, onPress: () => setOpen((current) => !current), style: styles.viewerListControl, children: _jsx(View, { style: [
330
- styles.viewerToggleCaret,
331
- { borderLeftColor: theme.muted },
332
- open ? styles.viewerToggleCaretOpen : null,
333
- ] }) }), _jsx(View, { style: styles.viewerListContent, children: open ? childArray : childArray.slice(0, 1) })] }));
334
- };
335
- const OpenEditorNativePageViewer = ({ cachedPage, pageRuntime, theme, }) => {
336
- const [page, setPage] = useState(cachedPage);
337
- useEffect(() => {
338
- setPage(cachedPage);
339
- if (!pageRuntime?.resolvePage)
340
- return;
341
- let active = true;
342
- void pageRuntime.resolvePage(cachedPage.pageId).then((resolved) => {
343
- if (active && resolved)
344
- setPage(resolved);
345
- }).catch(() => undefined);
346
- return () => {
347
- active = false;
348
- };
349
- }, [cachedPage.href, cachedPage.icon, cachedPage.pageId, cachedPage.title, pageRuntime]);
350
- const content = _jsxs(_Fragment, { children: [_jsx(Text, { style: [styles.viewerPageIcon, { color: theme.text }], children: normalizeEmoji(page.icon, DEFAULT_PAGE_EMOJI) }), _jsx(Text, { style: [styles.viewerPageTitle, { color: theme.text, textDecorationColor: theme.muted }], children: page.title }), _jsx(Text, { style: { color: theme.muted }, children: "\u2192" })] });
351
- return pageRuntime?.openPage ? (_jsx(Pressable, { accessibilityLabel: `Open ${page.title}`, accessibilityRole: "button", onPress: () => void pageRuntime.openPage?.(page), style: { alignItems: "center", borderRadius: 8, flexDirection: "row", gap: 8, marginBottom: 8, padding: 8 }, children: content })) : (_jsx(View, { style: { alignItems: "center", flexDirection: "row", gap: 8, marginBottom: 8, padding: 8 }, children: content }));
352
- };
353
- const renderNativeViewerNode = (node, key, theme, renderers, pageRuntime, attachmentRuntime) => {
354
- if (node.type === "text") {
355
- return _jsx(Text, { style: { color: theme.text }, children: node.text ?? "" }, key);
356
- }
357
- const children = node.content?.map((child, index) => renderNativeViewerNode(child, `${key}-${index}`, theme, renderers, pageRuntime, attachmentRuntime)) ?? null;
358
- const blockKey = findBlockSpecForNode(defaultBlockRegistry, node)?.name ?? node.type;
359
- const customRenderer = renderers?.[blockKey];
360
- if (customRenderer) {
361
- return customRenderer({ node, children, theme });
362
- }
363
- switch (blockKey) {
364
- case "paragraph":
365
- return _jsx(View, { style: { marginBottom: 12 }, children: _jsx(Text, { style: { color: theme.textSoft }, children: children }) }, key);
366
- case "heading": {
367
- const level = typeof node.attrs?.level === "number" ? Math.min(Math.max(node.attrs.level, 1), 6) : 2;
368
- const fontSize = [0, 28, 24, 20, 18, 16, 14][level];
369
- return _jsx(Text, { style: { color: theme.text, fontSize, fontWeight: "700", marginBottom: 8 }, children: children }, key);
370
- }
371
- case "bulletList":
372
- case "orderedList":
373
- case "taskList":
374
- case "toggleList":
375
- return _jsx(View, { style: { marginBottom: 12 }, children: children }, key);
376
- case "blockquote":
377
- return _jsx(View, { style: { borderLeftColor: theme.structuralLine, borderLeftWidth: 3, marginBottom: 12, paddingLeft: 12 }, children: children }, key);
378
- case "codeBlock":
379
- return _jsx(View, { style: { backgroundColor: theme.codeBackground, borderRadius: 8, marginBottom: 12, padding: 12 }, children: _jsx(Text, { style: { color: theme.codeText }, children: node.content?.map((child) => child.text ?? "").join("") }) }, key);
380
- case "callout": {
381
- const emoji = normalizeEmoji(node.attrs?.emoji, DEFAULT_CALLOUT_EMOJI);
382
- return _jsxs(View, { style: { backgroundColor: theme.blockSurface, borderRadius: 12, flexDirection: "row", gap: 10, marginBottom: 12, padding: 14 }, children: [_jsx(Text, { style: { fontSize: 20 }, children: emoji }), _jsx(View, { style: { flex: 1 }, children: children })] }, key);
383
- }
384
- case "divider":
385
- return _jsx(View, { style: { backgroundColor: theme.structuralLine, height: 1, marginVertical: 16 } }, key);
386
- case "image":
387
- return (_jsx(NativeImage, { source: { uri: typeof node.attrs?.src === "string" ? node.attrs.src : "" }, accessibilityLabel: typeof node.attrs?.alt === "string" ? node.attrs.alt : undefined, style: { borderRadius: 8, height: 220, marginBottom: 12, width: "100%" }, resizeMode: "cover" }, key));
388
- case "columns":
389
- return _jsx(View, { style: { flexDirection: "row", gap: 12, marginBottom: 12 }, children: children }, key);
390
- case "table":
391
- return _jsx(View, { style: { borderColor: theme.structuralLine, borderRadius: 8, borderWidth: 1, marginBottom: 12, overflow: "hidden" }, children: children }, key);
392
- case "page": {
393
- const pageId = typeof node.attrs?.pageId === "string" ? node.attrs.pageId : "";
394
- const title = node.content?.map((child) => child.text ?? "").join("") || "Untitled";
395
- const icon = normalizeEmoji(node.attrs?.icon, DEFAULT_PAGE_EMOJI);
396
- return _jsx(OpenEditorNativePageViewer, { cachedPage: { pageId, title, icon, href: typeof node.attrs?.href === "string" ? node.attrs.href : null }, pageRuntime: pageId ? pageRuntime : undefined, theme: theme }, key);
397
- }
398
- case "attachment": {
399
- const snapshot = {
400
- attachmentId: typeof node.attrs?.attachmentId === "string" ? node.attrs.attachmentId : null,
401
- name: typeof node.attrs?.name === "string" ? node.attrs.name : "Attachment",
402
- mimeType: typeof node.attrs?.mimeType === "string" ? node.attrs.mimeType : null,
403
- size: typeof node.attrs?.size === "number" ? node.attrs.size : null,
404
- url: typeof node.attrs?.url === "string" ? node.attrs.url : null,
405
- };
406
- const size = formatAttachmentSize(snapshot.size);
407
- const type = attachmentTypeLabel(snapshot.mimeType, snapshot.name);
408
- const content = _jsxs(_Fragment, { children: [_jsx(View, { style: [styles.viewerAttachmentIcon, { backgroundColor: theme.blockSurface }], children: _jsx(Text, { style: [styles.viewerAttachmentType, { color: theme.muted }], children: type }) }), _jsxs(View, { style: { flex: 1 }, children: [_jsx(Text, { numberOfLines: 1, style: { color: theme.text, fontWeight: "600" }, children: snapshot.name }), _jsx(Text, { style: { color: theme.muted, fontSize: 12, marginTop: 3 }, children: [size, type].filter(Boolean).join(" · ") })] }), _jsx(Text, { style: { color: theme.muted }, children: "\u2192" })] });
409
- return attachmentRuntime?.openAttachment ? _jsx(Pressable, { accessibilityLabel: `Open ${snapshot.name}`, accessibilityRole: "button", onPress: () => void attachmentRuntime.openAttachment?.(snapshot), style: [styles.viewerAttachment, { backgroundColor: theme.surface, borderColor: theme.structuralLine }], children: content }, key) : _jsx(View, { style: [styles.viewerAttachment, { backgroundColor: theme.surface, borderColor: theme.structuralLine }], children: content }, key);
410
- }
411
- default:
412
- if (node.type === "column") {
413
- return _jsx(View, { style: { flex: 1 }, children: children }, key);
414
- }
415
- if (node.type === "tableRow") {
416
- return _jsx(View, { style: { borderBottomColor: theme.structuralLine, borderBottomWidth: StyleSheet.hairlineWidth, flexDirection: "row" }, children: children }, key);
417
- }
418
- if (node.type === "tableHeader" || node.type === "tableCell") {
419
- return _jsx(View, { style: { backgroundColor: theme.blockSurface, flex: 1, padding: 10 }, children: children }, key);
420
- }
421
- if (node.type === "taskItem") {
422
- const checked = node.attrs?.checked === true;
423
- return _jsxs(View, { style: styles.viewerListRow, children: [_jsx(View, { style: [styles.viewerTaskCheckbox, { backgroundColor: checked ? theme.accentStrong : theme.blockSurface }], children: checked ? _jsx(Text, { style: [styles.viewerTaskCheck, { color: theme.primaryText }], children: "\u2713" }) : null }), _jsx(View, { style: styles.viewerListContent, children: children })] }, key);
424
- }
425
- if (node.type === "toggleListItem") {
426
- return _jsx(NativeToggleListItem, { initiallyOpen: node.attrs?.open !== false, theme: theme, children: children }, key);
427
- }
428
- if (node.type === "listItem") {
429
- return _jsxs(View, { style: { flexDirection: "row", gap: 8, marginBottom: 6 }, children: [_jsx(Text, { style: { color: theme.muted }, children: "\u2022" }), _jsx(View, { style: { flex: 1 }, children: children })] }, key);
430
- }
431
- return _jsx(View, { children: children }, key);
432
- }
433
- };
434
- export const OpenEditorNativeToolbar = ({ actions = [
435
- { key: openEditorNativeToolbarActionKeys.undo, label: "Undo" },
436
- { key: openEditorNativeToolbarActionKeys.redo, label: "Redo" },
437
- { key: openEditorNativeToolbarActionKeys.paragraph, label: "Paragraph" },
438
- { key: openEditorNativeToolbarActionKeys.bulletList, label: "Bullets" },
439
- { key: openEditorNativeToolbarActionKeys.orderedList, label: "Numbers" },
440
- ], onActionPress, theme, }) => {
441
- const resolvedTheme = {
442
- ...defaultNativeTheme,
443
- ...theme,
444
- toolbar: {
445
- ...defaultNativeTheme.toolbar,
446
- ...theme?.toolbar,
447
- },
448
- };
449
- return (_jsx(View, { style: [styles.toolbar, { backgroundColor: resolvedTheme.surface, borderColor: resolvedTheme.border }], children: actions.map((action) => (_jsx(Pressable, { onPress: () => {
450
- action.onPress?.(action.key);
451
- onActionPress?.(action.key);
452
- }, style: [styles.toolbarButton, { backgroundColor: resolvedTheme.accent, borderColor: resolvedTheme.borderStrong }], children: _jsx(Text, { style: [styles.toolbarButtonText, { color: resolvedTheme.accentText }], children: action.label }) }, action.key))) }));
453
- };
454
- /** Canonical native page-title and page-icon editor for opened page surfaces. */
455
- export const OpenEditorNativePageHeader = ({ page, runtime, theme, onPageChange }) => {
456
- const [title, setTitle] = useState(page.title);
457
- const [icon, setIcon] = useState(normalizeEmoji(page.icon, DEFAULT_PAGE_EMOJI));
458
- const [showEmojiPicker, setShowEmojiPicker] = useState(false);
459
- const [error, setError] = useState(null);
460
- const resolvedTheme = { ...defaultNativeTheme, ...theme };
461
- useEffect(() => setTitle(page.title), [page.pageId, page.title]);
462
- useEffect(() => setIcon(normalizeEmoji(page.icon, DEFAULT_PAGE_EMOJI)), [page.icon, page.pageId]);
463
- const updatePage = async (update) => {
464
- setError(null);
465
- try {
466
- const resolved = await runtime.updatePage(page.pageId, update);
467
- const next = resolved ?? { ...page, ...update };
468
- setTitle(next.title);
469
- setIcon(normalizeEmoji(next.icon, DEFAULT_PAGE_EMOJI));
470
- onPageChange?.(next);
471
- }
472
- catch (cause) {
473
- setError(cause instanceof Error ? cause.message : "Could not update the page.");
474
- }
475
- };
476
- return _jsxs(View, { style: styles.pageHeader, children: [_jsx(Pressable, { accessibilityLabel: "Change page icon", accessibilityRole: "button", onPress: () => setShowEmojiPicker(true), children: _jsx(Text, { style: styles.pageHeaderIcon, children: icon }) }), _jsx(TextInput, { accessibilityLabel: "Page title", multiline: true, onBlur: () => {
477
- const nextTitle = title.trim() || "Untitled";
478
- setTitle(nextTitle);
479
- if (nextTitle !== page.title)
480
- void updatePage({ title: nextTitle });
481
- }, onChangeText: setTitle, placeholder: "Untitled", placeholderTextColor: resolvedTheme.muted, style: [styles.pageHeaderTitle, { color: resolvedTheme.text }], value: title }), error ? _jsx(Text, { accessibilityRole: "alert", style: styles.pageHeaderError, children: error }) : null, _jsx(OpenEditorNativeEmojiPicker, { onEmojiSelect: (nextIcon) => {
482
- setIcon(nextIcon);
483
- void updatePage({ icon: nextIcon });
484
- }, onRequestClose: () => setShowEmojiPicker(false), theme: { surfaceMuted: resolvedTheme.surfaceMuted, text: resolvedTheme.text, muted: resolvedTheme.muted }, visible: showEmojiPicker })] });
485
- };
486
- export const OpenEditorNative = forwardRef(function OpenEditorNative({ initialDocument = createDocument(), editable = true, placeholder = "Start writing...", showToolbar = true, theme, toolbarPlacement = "keyboard", onChange, pageRuntime, attachmentRuntime, enabledBlocks, }, forwardedRef) {
487
- const editorRef = useRef(null);
488
- const [currentDocument, setCurrentDocument] = useState(() => normalizeForNativeEditor(initialDocument));
489
- const currentDocumentRef = useRef(currentDocument);
490
- currentDocumentRef.current = currentDocument;
491
- const onChangeRef = useRef(onChange);
492
- onChangeRef.current = onChange;
493
- const [selection, setSelection] = useState();
494
- const [linkRequest, setLinkRequest] = useState(null);
495
- const [linkUrl, setLinkUrl] = useState("https://");
496
- const [imageRequest, setImageRequest] = useState(null);
497
- const [emojiRequest, setEmojiRequest] = useState(null);
498
- const [imageUrl, setImageUrl] = useState("https://placehold.co/960x420/png");
499
- const [attachmentUpload, setAttachmentUpload] = useState(null);
500
- const attachmentAbortRef = useRef(null);
501
- const [history, setHistory] = useState({ canUndo: false, canRedo: false });
502
- const nativeDocument = currentDocument;
503
- const NativeEditor = useMemo(() => loadNativeEditor(), []);
504
- const resolvedTheme = useMemo(() => ({
505
- ...defaultNativeTheme,
506
- ...theme,
507
- toolbar: {
508
- ...defaultNativeTheme.toolbar,
509
- ...theme?.toolbar,
510
- },
511
- }), [theme]);
512
- const nativeEditorTheme = useMemo(() => toNativeEditorTheme(resolvedTheme), [resolvedTheme]);
513
- const authorableBlocks = useMemo(() => [...defaultBlockRegistry.values()]
514
- .filter((block) => block.support?.native !== "unsupported")
515
- .map((block) => block.name)
516
- .filter((blockName) => isOpenEditorBlockEnabled(blockName, { enabledBlocks }))
517
- .filter((blockName) => blockName !== "page" || Boolean(pageRuntime?.createPage))
518
- .filter((blockName) => blockName !== "attachment" || Boolean(attachmentRuntime?.selectAttachment && attachmentRuntime.uploadAttachment)), [attachmentRuntime, enabledBlocks, pageRuntime]);
519
- const isBlockEnabled = (blockName) => authorableBlocks.includes(/^heading[1-6]$/.test(blockName) ? "heading" : blockName);
520
- const toolbarItems = useMemo(() => createOpenEditorNativeToolbarItems({
521
- canUndo: history.canUndo,
522
- canRedo: history.canRedo,
523
- enabledBlocks: authorableBlocks,
524
- }), [authorableBlocks, history.canRedo, history.canUndo]);
525
- const syncDocument = (nextDocument, updateEditor = true) => {
526
- const normalized = normalizeForNativeEditor(nextDocument);
527
- if (documentsMatch(normalized, currentDocumentRef.current))
528
- return;
529
- currentDocumentRef.current = normalized;
530
- setCurrentDocument(normalized);
531
- onChangeRef.current?.(normalized);
532
- if (updateEditor) {
533
- editorRef.current?.setContentJson(toNativeEditorDocument(normalized));
534
- }
535
- };
536
- useImperativeHandle(forwardedRef, () => ({
537
- getContent: () => currentDocumentRef.current,
538
- setContent: (nextDocument) => syncDocument(nextDocument, true),
539
- undo: () => editorRef.current?.undo(),
540
- redo: () => editorRef.current?.redo(),
541
- }));
542
- const moveSelectedBlock = (direction) => {
543
- const selectedIndex = resolveSelectedTopLevelIndex(nativeDocument, selection);
544
- const nextIndex = selectedIndex + direction;
545
- if (nextIndex < 0 || nextIndex >= nativeDocument.content.length) {
546
- return;
547
- }
548
- syncDocument(moveTopLevelBlock(nativeDocument, selectedIndex, nextIndex), true);
549
- };
550
- const uploadAttachment = async () => {
551
- if (!attachmentRuntime?.selectAttachment || !attachmentRuntime.uploadAttachment)
552
- return;
553
- attachmentAbortRef.current?.abort();
554
- const controller = new AbortController();
555
- attachmentAbortRef.current = controller;
556
- try {
557
- const input = await attachmentRuntime.selectAttachment({ signal: controller.signal });
558
- if (!input || controller.signal.aborted)
559
- return;
560
- setAttachmentUpload({ input, progress: 0, error: null });
561
- const validation = await attachmentRuntime.validateAttachment?.(input);
562
- if (validation && !validation.accepted)
563
- throw new Error(validation.message);
564
- const snapshot = await attachmentRuntime.uploadAttachment(input, { signal: controller.signal, onProgress: (progress) => setAttachmentUpload((current) => current ? { ...current, progress } : current) });
565
- if (controller.signal.aborted)
566
- return;
567
- editorRef.current?.insertContentJson({ type: "doc", content: [{ type: "attachment", attrs: snapshot }] });
568
- setAttachmentUpload(null);
569
- }
570
- catch (cause) {
571
- if (!controller.signal.aborted)
572
- setAttachmentUpload((current) => current ? { ...current, error: cause instanceof Error ? cause.message : "Upload failed." } : current);
573
- }
574
- };
575
- const handleToolbarAction = (key) => {
576
- if (key === openEditorNativeToolbarActionKeys.dismissKeyboard) {
577
- dismissKeyboard();
578
- return;
579
- }
580
- if (key === openEditorNativeToolbarActionKeys.moveBlockUp) {
581
- moveSelectedBlock(-1);
582
- return;
583
- }
584
- if (key === openEditorNativeToolbarActionKeys.moveBlockDown) {
585
- moveSelectedBlock(1);
586
- return;
587
- }
588
- if (key === openEditorNativeToolbarActionKeys.attachment) {
589
- void uploadAttachment();
590
- return;
591
- }
592
- if (key === openEditorNativeToolbarActionKeys.undo) {
593
- editorRef.current?.undo();
594
- return;
595
- }
596
- if (key === openEditorNativeToolbarActionKeys.redo) {
597
- editorRef.current?.redo();
598
- return;
599
- }
600
- if (key === openEditorNativeToolbarActionKeys.bulletList) {
601
- editorRef.current?.toggleList("bulletList");
602
- return;
603
- }
604
- if (key === openEditorNativeToolbarActionKeys.orderedList) {
605
- editorRef.current?.toggleList("orderedList");
606
- return;
607
- }
608
- if (key === openEditorNativeToolbarActionKeys.columns) {
609
- editorRef.current?.insertColumns();
610
- return;
611
- }
612
- if (key === openEditorNativeToolbarActionKeys.table) {
613
- editorRef.current?.insertTable();
614
- return;
615
- }
616
- if (!isBlockEnabled(key))
617
- return;
618
- if (key === openEditorNativeToolbarActionKeys.page && pageRuntime?.createPage) {
619
- void pageRuntime.createPage({ title: "Untitled", icon: DEFAULT_PAGE_EMOJI }).then((page) => {
620
- editorRef.current?.insertContentJson({
621
- type: "doc",
622
- content: [{
623
- type: "page",
624
- attrs: { pageId: page.pageId, icon: page.icon ?? DEFAULT_PAGE_EMOJI, href: page.href ?? null },
625
- content: [{ type: "text", text: page.title || "Untitled" }],
626
- }],
627
- });
628
- }).catch(() => undefined);
629
- return;
630
- }
631
- const actionDocument = defaultDocumentForToolbarAction(key);
632
- if (actionDocument) {
633
- editorRef.current?.insertContentJson(actionDocument);
634
- }
635
- };
636
- const submitLinkDialog = () => {
637
- const url = linkUrl.trim();
638
- if (!linkRequest) {
639
- return;
640
- }
641
- if (url && isSafeInputUrl(url)) {
642
- linkRequest.setLink(url);
643
- }
644
- else {
645
- linkRequest.unsetLink();
646
- }
647
- setLinkRequest(null);
648
- };
649
- const submitImageDialog = () => {
650
- const url = imageUrl.trim();
651
- if (imageRequest && isSafeInputUrl(url, imageRequest.allowBase64)) {
652
- imageRequest.insertImage(url, { alt: "Placeholder" });
653
- }
654
- setImageRequest(null);
655
- };
656
- if (!NativeEditor) {
657
- return (_jsxs(View, { style: [styles.unavailable, { backgroundColor: resolvedTheme.surface, borderColor: resolvedTheme.border }], children: [_jsx(Text, { style: [styles.unavailableTitle, { color: resolvedTheme.text }], children: "Native editor unavailable" }), _jsx(Text, { style: [styles.unavailableText, { color: resolvedTheme.muted }], children: "OpenEditorNative requires the @openeditor/react-native-prose-editor native module in a development build." })] }));
658
- }
659
- return (_jsxs(_Fragment, { children: [_jsx(NativeEditor, { ref: editorRef, initialJSON: toNativeEditorDocument(nativeDocument), schema: openEditorNativeSchema, editable: editable, placeholder: placeholder, showToolbar: showToolbar, toolbarPlacement: toolbarPlacement, toolbarItems: toolbarItems, onToolbarAction: handleToolbarAction, theme: nativeEditorTheme, autoDetectLinks: true, allowBase64Images: true, allowImageResizing: true, heightBehavior: "autoGrow", onRequestLink: (request) => {
660
- setLinkUrl(request.href ?? "https://");
661
- setLinkRequest(request);
662
- }, onRequestImage: (request) => {
663
- setImageUrl("https://placehold.co/960x420/png");
664
- setImageRequest(request);
665
- }, onOpenPage: (page) => {
666
- if (!pageRuntime?.openPage)
667
- return;
668
- if (!pageRuntime.resolvePage) {
669
- void pageRuntime.openPage(page);
670
- return;
671
- }
672
- void pageRuntime.resolvePage(page.pageId)
673
- .then((resolved) => pageRuntime.openPage?.(resolved ?? page))
674
- .catch(() => pageRuntime.openPage?.(page));
675
- }, onRequestEmoji: (request) => {
676
- if (request.nodeType === "page" && !pageRuntime)
677
- return;
678
- setEmojiRequest(request);
679
- }, onOpenAttachment: (attachment) => void attachmentRuntime?.openAttachment?.(attachment), onSelectionChange: setSelection, onHistoryStateChange: setHistory, onContentChangeJSON: (json) => {
680
- syncDocument(contentFromNativeJson(json), false);
681
- }, style: styles.nativeEditor }), _jsx(UrlDialog, { title: "Link", value: linkUrl, visible: linkRequest !== null, placeholder: "https://", confirmLabel: linkUrl.trim() ? "Apply" : "Remove", onChangeText: setLinkUrl, onCancel: () => setLinkRequest(null), onSubmit: submitLinkDialog, theme: resolvedTheme }), _jsx(Modal, { animationType: "fade", onRequestClose: () => { attachmentAbortRef.current?.abort(); setAttachmentUpload(null); }, transparent: true, visible: attachmentUpload !== null, children: _jsx(View, { style: [styles.dialogBackdrop, { backgroundColor: resolvedTheme.backdrop }], children: _jsxs(View, { accessibilityViewIsModal: true, style: [styles.dialog, { backgroundColor: resolvedTheme.surface, borderColor: resolvedTheme.border }], children: [_jsx(Text, { style: [styles.unavailableTitle, { color: resolvedTheme.text }], children: attachmentUpload?.input.name }), _jsx(Text, { style: [styles.unavailableText, { color: attachmentUpload?.error ? "#b42318" : resolvedTheme.muted }], children: attachmentUpload?.error ?? `Uploading ${Math.round((attachmentUpload?.progress ?? 0) * 100)}%` }), _jsxs(View, { style: styles.dialogActions, children: [attachmentUpload?.error ? _jsx(CommandButton, { label: "Retry", onPress: () => void uploadAttachment(), theme: resolvedTheme }) : null, _jsx(CommandButton, { label: "Cancel", onPress: () => { attachmentAbortRef.current?.abort(); setAttachmentUpload(null); }, theme: resolvedTheme })] })] }) }) }), _jsx(UrlDialog, { title: "Image", value: imageUrl, visible: imageRequest !== null, placeholder: "https://", confirmLabel: "Insert", onChangeText: setImageUrl, onCancel: () => setImageRequest(null), onSubmit: submitImageDialog, theme: resolvedTheme }), _jsx(OpenEditorNativeEmojiPicker, { onEmojiSelect: (emoji) => {
682
- if (!emojiRequest)
683
- return;
684
- const attribute = emojiRequest.nodeType === "page" ? "icon" : "emoji";
685
- editorRef.current?.updateNodeAttrs(emojiRequest.docPos, {
686
- ...emojiRequest.attrs,
687
- [attribute]: emoji,
688
- });
689
- if (emojiRequest.nodeType === "page" && typeof emojiRequest.attrs.pageId === "string") {
690
- void pageRuntime?.updatePage?.(emojiRequest.attrs.pageId, { icon: emoji });
691
- }
692
- setEmojiRequest(null);
693
- }, onRequestClose: () => setEmojiRequest(null), theme: {
694
- surfaceMuted: resolvedTheme.surfaceMuted,
695
- text: resolvedTheme.text,
696
- muted: resolvedTheme.muted,
697
- }, visible: emojiRequest !== null })] }));
698
- });
699
- export const OpenEditorNativeViewer = ({ document = createDocument(), theme, renderers, pageRuntime, attachmentRuntime, }) => {
700
- const resolvedTheme = {
701
- ...defaultNativeTheme,
702
- ...theme,
703
- toolbar: {
704
- ...defaultNativeTheme.toolbar,
705
- ...theme?.toolbar,
706
- },
707
- };
708
- const normalizedDocument = normalizeForNativeEditor(document);
709
- return (_jsx(View, { style: [styles.viewer, { backgroundColor: resolvedTheme.surface, borderColor: resolvedTheme.border }], children: normalizedDocument.content.map((node, index) => renderNativeViewerNode(node, `native-viewer-${index}`, resolvedTheme, renderers, pageRuntime, attachmentRuntime)) }));
710
- };
711
- const CommandButton = ({ label, active = false, primary = false, theme, onPress, }) => (_jsx(Pressable, { accessibilityRole: "button", accessibilityState: { selected: active }, onPress: onPress, style: [
712
- styles.commandButton,
713
- { backgroundColor: theme.surface, borderColor: theme.border },
714
- active && { backgroundColor: theme.accent, borderColor: theme.accent },
715
- primary && { backgroundColor: theme.primary, borderColor: theme.primary },
716
- ], children: _jsx(Text, { style: [styles.commandButtonText, { color: theme.text }, (active || primary) && { color: active ? theme.accentText : theme.primaryText }], children: label }) }));
717
- const UrlDialog = ({ title, value, visible, placeholder, confirmLabel, onChangeText, onCancel, onSubmit, theme, }) => (_jsx(Modal, { visible: visible, transparent: true, animationType: "fade", onRequestClose: onCancel, children: _jsx(View, { style: [styles.dialogBackdrop, { backgroundColor: theme.backdrop }], children: _jsxs(View, { style: [styles.dialog, { backgroundColor: theme.surface }], children: [_jsx(Text, { style: [styles.dialogTitle, { color: theme.text }], children: title }), _jsx(TextInput, { autoCapitalize: "none", autoCorrect: false, keyboardType: "url", onChangeText: onChangeText, placeholder: placeholder, placeholderTextColor: theme.placeholder, style: [styles.dialogInput, { borderColor: theme.border, color: theme.text }], value: value }), _jsxs(View, { style: styles.dialogActions, children: [_jsx(CommandButton, { label: "Cancel", theme: theme, onPress: onCancel }), _jsx(CommandButton, { label: confirmLabel, primary: true, theme: theme, onPress: onSubmit })] })] }) }) }));
718
- const styles = StyleSheet.create({
719
- viewer: {
720
- borderRadius: 12,
721
- borderWidth: 1,
722
- gap: 4,
723
- padding: 16,
724
- },
725
- viewerListRow: {
726
- alignItems: "flex-start",
727
- flexDirection: "row",
728
- gap: 8,
729
- marginBottom: 6,
730
- },
731
- viewerListControl: {
732
- alignItems: "center",
733
- height: 24,
734
- justifyContent: "center",
735
- width: 20,
736
- },
737
- viewerListContent: {
738
- flex: 1,
739
- },
740
- viewerToggleCaret: {
741
- borderBottomColor: "transparent",
742
- borderBottomWidth: 4,
743
- borderLeftWidth: 6,
744
- borderTopColor: "transparent",
745
- borderTopWidth: 4,
746
- height: 0,
747
- transform: [{ rotate: "0deg" }],
748
- width: 0,
749
- },
750
- viewerToggleCaretOpen: {
751
- transform: [{ rotate: "90deg" }],
752
- },
753
- viewerTaskCheckbox: {
754
- alignItems: "center",
755
- borderRadius: 4,
756
- height: 20,
757
- justifyContent: "center",
758
- marginTop: 2,
759
- width: 20,
760
- },
761
- viewerTaskCheck: {
762
- fontSize: 13,
763
- fontWeight: "800",
764
- lineHeight: 16,
765
- },
766
- viewerPageIcon: {
767
- fontSize: 18,
768
- lineHeight: 24,
769
- textAlign: "center",
770
- width: 24,
771
- },
772
- viewerPageTitle: {
773
- flex: 1,
774
- fontWeight: "600",
775
- lineHeight: 24,
776
- textDecorationLine: "underline",
777
- },
778
- viewerAttachment: {
779
- alignItems: "center",
780
- borderRadius: 12,
781
- borderWidth: 1,
782
- flexDirection: "row",
783
- gap: 12,
784
- marginBottom: 8,
785
- minHeight: 68,
786
- paddingHorizontal: 12,
787
- paddingVertical: 10,
788
- },
789
- viewerAttachmentIcon: {
790
- alignItems: "center",
791
- borderRadius: 9,
792
- height: 48,
793
- justifyContent: "center",
794
- width: 42,
795
- },
796
- viewerAttachmentType: {
797
- fontSize: 9,
798
- fontWeight: "800",
799
- letterSpacing: 0.4,
800
- },
801
- nativeEditor: {
802
- minHeight: 320,
803
- },
804
- toolbar: {
805
- borderRadius: 10,
806
- borderWidth: 1,
807
- flexDirection: "row",
808
- flexWrap: "wrap",
809
- gap: 8,
810
- padding: 8,
811
- },
812
- toolbarButton: {
813
- borderRadius: 999,
814
- borderWidth: 1,
815
- minHeight: 34,
816
- paddingHorizontal: 12,
817
- paddingVertical: 8,
818
- },
819
- toolbarButtonText: {
820
- fontSize: 13,
821
- fontWeight: "700",
822
- },
823
- unavailable: {
824
- backgroundColor: "#ffffff",
825
- borderColor: "#e5e5e5",
826
- borderRadius: 8,
827
- borderCurve: "continuous",
828
- borderWidth: 1,
829
- margin: 16,
830
- padding: 16,
831
- },
832
- unavailableTitle: {
833
- fontSize: 16,
834
- fontWeight: "700",
835
- marginBottom: 4,
836
- },
837
- unavailableText: {
838
- fontSize: 14,
839
- lineHeight: 20,
840
- },
841
- commandButton: {
842
- alignItems: "center",
843
- backgroundColor: "#ffffff",
844
- borderColor: "#e5e5e5",
845
- borderRadius: 8,
846
- borderWidth: 1,
847
- justifyContent: "center",
848
- minHeight: 38,
849
- paddingHorizontal: 12,
850
- },
851
- commandButtonText: {
852
- color: "#171717",
853
- fontSize: 13,
854
- fontWeight: "700",
855
- },
856
- pageHeader: {
857
- gap: 8,
858
- paddingBottom: 16,
859
- },
860
- pageHeaderIcon: {
861
- fontSize: 40,
862
- lineHeight: 48,
863
- },
864
- pageHeaderTitle: {
865
- fontSize: 30,
866
- fontWeight: "700",
867
- lineHeight: 36,
868
- padding: 0,
869
- },
870
- pageHeaderError: {
871
- color: "#dc2626",
872
- fontSize: 12,
873
- },
874
- dialogBackdrop: {
875
- alignItems: "center",
876
- backgroundColor: "rgba(0, 0, 0, 0.32)",
877
- flex: 1,
878
- justifyContent: "center",
879
- padding: 24,
880
- },
881
- dialog: {
882
- backgroundColor: "#ffffff",
883
- borderRadius: 8,
884
- gap: 12,
885
- maxWidth: 420,
886
- padding: 16,
887
- width: "100%",
888
- },
889
- dialogTitle: {
890
- color: "#171717",
891
- fontSize: 17,
892
- fontWeight: "700",
893
- },
894
- dialogInput: {
895
- borderColor: "#e5e5e5",
896
- borderRadius: 8,
897
- borderWidth: 1,
898
- color: "#171717",
899
- fontSize: 15,
900
- minHeight: 44,
901
- paddingHorizontal: 12,
902
- },
903
- dialogActions: {
904
- flexDirection: "row",
905
- gap: 8,
906
- justifyContent: "flex-end",
907
- },
908
- });
909
- export { createNativeEditorDocument, fromNativeEditorDocument, sanitizeNativeEditorDocument, toNativeEditorDocument, } from "./document.js";
910
- export { createOpenEditorNativeToolbarItems, defaultDocumentForToolbarAction, nativeToolbarParityCoverage, openEditorNativeToolbarActionKeys, openEditorNativeToolbarItems, } from "./toolbar.js";
1
+ export { OpenEditorNative, } from "./native-editor.js";
2
+ export { OpenEditorNativeToolbar, defaultOpenEditorNativeToolbarItems, openEditorNativeBlockPickerItems, } from "./toolbar-host.js";