@openeditor/native 0.0.1

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 ADDED
@@ -0,0 +1,569 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { createDocument, findBlockSpecForNode, moveTopLevelBlock, textBlock, } from "@openeditor/core";
3
+ import { 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 { defaultBlockRegistry } from "@openeditor/schema";
9
+ const defaultNativeTheme = {
10
+ background: "#fafafa",
11
+ surface: "#ffffff",
12
+ surfaceMuted: "#f5f5f5",
13
+ text: "#171717",
14
+ textSoft: "#262626",
15
+ muted: "#737373",
16
+ placeholder: "#a3a3a3",
17
+ border: "#e5e5e5",
18
+ borderStrong: "#d4d4d4",
19
+ accent: "#f5f5f5",
20
+ accentText: "#171717",
21
+ accentStrong: "#171717",
22
+ primary: "#171717",
23
+ primaryText: "#ffffff",
24
+ codeBackground: "#f5f5f5",
25
+ codeText: "#171717",
26
+ debugBackground: "#171717",
27
+ debugText: "#d4d4d4",
28
+ debugHeading: "#ffffff",
29
+ debugBorder: "#404040",
30
+ debugButtonBackground: "#262626",
31
+ success: "#86efac",
32
+ shadow: "rgba(0, 0, 0, 0.12)",
33
+ backdrop: "rgba(0, 0, 0, 0.32)",
34
+ toolbar: {},
35
+ };
36
+ const openEditorNativeSchema = {
37
+ nodes: [
38
+ { name: "doc", content: "block+", role: "doc" },
39
+ { name: "paragraph", content: "inline*", group: "block", role: "textBlock", htmlTag: "p" },
40
+ { name: "heading", content: "inline*", group: "block", attrs: { level: { default: 2 } }, role: "textBlock" },
41
+ { name: "h1", content: "inline*", group: "block", role: "textBlock", htmlTag: "h1" },
42
+ { name: "h2", content: "inline*", group: "block", role: "textBlock", htmlTag: "h2" },
43
+ { name: "h3", content: "inline*", group: "block", role: "textBlock", htmlTag: "h3" },
44
+ { name: "h4", content: "inline*", group: "block", role: "textBlock", htmlTag: "h4" },
45
+ { name: "h5", content: "inline*", group: "block", role: "textBlock", htmlTag: "h5" },
46
+ { name: "h6", content: "inline*", group: "block", role: "textBlock", htmlTag: "h6" },
47
+ { name: "blockquote", content: "block+", group: "block", role: "block", htmlTag: "blockquote" },
48
+ { name: "bulletList", content: "listItem+", group: "block", role: "list", htmlTag: "ul" },
49
+ { name: "orderedList", content: "listItem+", group: "block", attrs: { start: { default: 1 } }, role: "list", htmlTag: "ol" },
50
+ { name: "taskList", content: "taskItem+", group: "block", role: "list", htmlTag: "ul" },
51
+ { name: "listItem", content: "paragraph block*", role: "listItem", htmlTag: "li" },
52
+ {
53
+ name: "taskItem",
54
+ content: "paragraph block*",
55
+ role: "listItem",
56
+ htmlTag: "li",
57
+ attrs: { checked: { default: false } },
58
+ },
59
+ { name: "codeBlock", content: "text*", group: "block", role: "textBlock", htmlTag: "pre" },
60
+ { name: "horizontalRule", content: "", group: "block", role: "block", htmlTag: "hr", isVoid: true },
61
+ {
62
+ name: "columns",
63
+ content: "column+",
64
+ group: "block",
65
+ role: "block",
66
+ htmlTag: "section",
67
+ attrs: { count: { default: 2 } },
68
+ },
69
+ {
70
+ name: "column",
71
+ content: "block+",
72
+ role: "block",
73
+ htmlTag: "div",
74
+ },
75
+ {
76
+ name: "image",
77
+ content: "",
78
+ group: "block",
79
+ attrs: {
80
+ src: {},
81
+ alt: { default: null },
82
+ title: { default: null },
83
+ width: { default: null },
84
+ height: { default: null },
85
+ },
86
+ role: "block",
87
+ htmlTag: "img",
88
+ isVoid: true,
89
+ },
90
+ { name: "hardBreak", content: "", group: "inline", role: "hardBreak", htmlTag: "br", isVoid: true },
91
+ { name: "text", content: "", group: "inline", role: "text" },
92
+ ],
93
+ marks: [
94
+ { name: "bold" },
95
+ { name: "italic" },
96
+ { name: "underline" },
97
+ { name: "strike" },
98
+ { name: "code" },
99
+ { name: "link", attrs: { href: {} } },
100
+ ],
101
+ };
102
+ const toNativeEditorTheme = (theme) => ({
103
+ backgroundColor: "transparent",
104
+ borderRadius: 0,
105
+ placeholderColor: theme.placeholder,
106
+ contentInsets: {
107
+ top: 16,
108
+ right: 16,
109
+ bottom: 16,
110
+ left: 16,
111
+ },
112
+ text: {
113
+ color: theme.text,
114
+ fontSize: 16,
115
+ lineHeight: 24,
116
+ },
117
+ paragraph: {
118
+ color: theme.textSoft,
119
+ fontSize: 16,
120
+ lineHeight: 24,
121
+ spacingAfter: 12,
122
+ },
123
+ headings: {
124
+ h1: { color: theme.text, fontSize: 28, fontWeight: "700", lineHeight: 34, spacingAfter: 14 },
125
+ h2: { color: theme.text, fontSize: 24, fontWeight: "700", lineHeight: 30, spacingAfter: 12 },
126
+ h3: { color: theme.text, fontSize: 20, fontWeight: "700", lineHeight: 26, spacingAfter: 10 },
127
+ h4: { color: theme.text, fontSize: 18, fontWeight: "700", lineHeight: 24, spacingAfter: 8 },
128
+ h5: { color: theme.text, fontSize: 16, fontWeight: "700", lineHeight: 22, spacingAfter: 8 },
129
+ h6: { color: theme.text, fontSize: 14, fontWeight: "700", lineHeight: 20, spacingAfter: 8 },
130
+ },
131
+ links: {
132
+ color: theme.accent,
133
+ underline: true,
134
+ },
135
+ list: {
136
+ markerColor: theme.muted,
137
+ itemSpacing: 6,
138
+ },
139
+ blockquote: {
140
+ borderColor: theme.accent,
141
+ borderWidth: 3,
142
+ text: {
143
+ color: theme.textSoft,
144
+ fontSize: 16,
145
+ lineHeight: 24,
146
+ },
147
+ },
148
+ codeBlock: {
149
+ backgroundColor: theme.codeBackground,
150
+ borderRadius: 8,
151
+ paddingHorizontal: 12,
152
+ paddingVertical: 8,
153
+ text: {
154
+ color: theme.codeText,
155
+ fontSize: 15,
156
+ lineHeight: 22,
157
+ },
158
+ },
159
+ horizontalRule: {
160
+ color: theme.border,
161
+ thickness: 1,
162
+ verticalMargin: 16,
163
+ },
164
+ toolbar: {
165
+ appearance: "native",
166
+ ...(typeof theme.toolbar.height === "number" ? { height: theme.toolbar.height } : {}),
167
+ buttonColor: theme.text,
168
+ buttonActiveColor: theme.text,
169
+ buttonActiveBackgroundColor: theme.accent,
170
+ buttonDisabledColor: theme.muted,
171
+ },
172
+ });
173
+ const loadNativeEditor = () => {
174
+ if (Platform.OS === "web") {
175
+ return null;
176
+ }
177
+ try {
178
+ return require("@openeditor/react-native-prose-editor").NativeRichTextEditor;
179
+ }
180
+ catch {
181
+ return null;
182
+ }
183
+ };
184
+ const dismissKeyboard = () => {
185
+ try {
186
+ const { KeyboardController } = require("react-native-keyboard-controller");
187
+ void KeyboardController.dismiss();
188
+ }
189
+ catch {
190
+ Keyboard.dismiss();
191
+ }
192
+ };
193
+ const contentFromNativeJson = (json) => fromNativeEditorDocument(json);
194
+ const normalizeForNativeEditor = (document) => sanitizeNativeEditorDocument(document);
195
+ const isSafeInputUrl = (value, allowDataImage = false) => {
196
+ const normalized = value.trim().toLowerCase();
197
+ if (!normalized) {
198
+ return false;
199
+ }
200
+ if (normalized.startsWith("javascript:") || normalized.startsWith("vbscript:")) {
201
+ return false;
202
+ }
203
+ if (normalized.startsWith("data:")) {
204
+ return allowDataImage && normalized.startsWith("data:image/");
205
+ }
206
+ return true;
207
+ };
208
+ const documentsMatch = (left, right) => JSON.stringify(left) === JSON.stringify(right);
209
+ const nativeSeedDocument = createDocument([
210
+ textBlock("heading", "OpenEditor Native", { level: 1 }),
211
+ textBlock("paragraph", "This native surface is intentionally limited to engine-owned blocks for now."),
212
+ {
213
+ type: "bulletList",
214
+ content: [
215
+ { type: "listItem", content: [textBlock("paragraph", "Paragraphs and headings")] },
216
+ { type: "listItem", content: [textBlock("paragraph", "Bullet and numbered lists")] },
217
+ { type: "listItem", content: [textBlock("paragraph", "Task lists, code blocks, columns, quotes, dividers, and images")] },
218
+ ],
219
+ },
220
+ {
221
+ type: "taskList",
222
+ content: [
223
+ { type: "taskItem", attrs: { checked: true }, content: [textBlock("paragraph", "Engine-owned structural list items")] },
224
+ { type: "taskItem", attrs: { checked: false }, content: [textBlock("paragraph", "Native task lists now live in the engine")] },
225
+ ],
226
+ },
227
+ {
228
+ type: "codeBlock",
229
+ content: [{ type: "text", text: "const native = 'owned by the engine';" }],
230
+ },
231
+ {
232
+ type: "blockquote",
233
+ content: [textBlock("paragraph", "Structural blocks return once the native engine owns them.")],
234
+ },
235
+ {
236
+ type: "columns",
237
+ attrs: { count: 2 },
238
+ content: [
239
+ { type: "column", content: [textBlock("paragraph", "Columns now flow through the native engine.")] },
240
+ { type: "column", content: [textBlock("paragraph", "Tables stay blocked until the native engine supports them cleanly.")] },
241
+ ],
242
+ },
243
+ { type: "horizontalRule" },
244
+ { type: "image", attrs: { src: "https://placehold.co/960x420/png", alt: "Placeholder" } },
245
+ ]);
246
+ const renderNativeViewerNode = (node, key, theme, renderers) => {
247
+ if (node.type === "text") {
248
+ return _jsx(Text, { style: { color: theme.text }, children: node.text ?? "" }, key);
249
+ }
250
+ const children = node.content?.map((child, index) => renderNativeViewerNode(child, `${key}-${index}`, theme, renderers)) ?? null;
251
+ const blockKey = findBlockSpecForNode(defaultBlockRegistry, node)?.name ?? node.type;
252
+ const customRenderer = renderers?.[blockKey];
253
+ if (customRenderer) {
254
+ return customRenderer({ node, children, theme });
255
+ }
256
+ switch (blockKey) {
257
+ case "paragraph":
258
+ return _jsx(View, { style: { marginBottom: 12 }, children: _jsx(Text, { style: { color: theme.textSoft }, children: children }) }, key);
259
+ case "heading": {
260
+ const level = typeof node.attrs?.level === "number" ? Math.min(Math.max(node.attrs.level, 1), 6) : 2;
261
+ const fontSize = [0, 28, 24, 20, 18, 16, 14][level];
262
+ return _jsx(Text, { style: { color: theme.text, fontSize, fontWeight: "700", marginBottom: 8 }, children: children }, key);
263
+ }
264
+ case "bulletList":
265
+ case "orderedList":
266
+ case "taskList":
267
+ return _jsx(View, { style: { marginBottom: 12 }, children: children }, key);
268
+ case "blockquote":
269
+ return _jsx(View, { style: { borderLeftColor: theme.accent, borderLeftWidth: 3, marginBottom: 12, paddingLeft: 12 }, children: children }, key);
270
+ case "codeBlock":
271
+ 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);
272
+ case "divider":
273
+ return _jsx(View, { style: { backgroundColor: theme.border, height: 1, marginVertical: 16 } }, key);
274
+ case "image":
275
+ 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));
276
+ case "columns":
277
+ return _jsx(View, { style: { flexDirection: "row", gap: 12, marginBottom: 12 }, children: children }, key);
278
+ case "table":
279
+ return _jsx(View, { style: { borderColor: theme.border, borderRadius: 8, borderWidth: 1, marginBottom: 12, overflow: "hidden" }, children: children }, key);
280
+ default:
281
+ if (node.type === "column") {
282
+ return _jsx(View, { style: { flex: 1 }, children: children }, key);
283
+ }
284
+ if (node.type === "tableRow") {
285
+ return _jsx(View, { style: { borderBottomColor: theme.border, borderBottomWidth: StyleSheet.hairlineWidth, flexDirection: "row" }, children: children }, key);
286
+ }
287
+ if (node.type === "tableHeader" || node.type === "tableCell") {
288
+ return _jsx(View, { style: { flex: 1, padding: 10 }, children: children }, key);
289
+ }
290
+ if (node.type === "listItem" || node.type === "taskItem") {
291
+ 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);
292
+ }
293
+ return _jsx(View, { children: children }, key);
294
+ }
295
+ };
296
+ export const OpenEditorNativeToolbar = ({ actions = [
297
+ { key: openEditorNativeToolbarActionKeys.undo, label: "Undo" },
298
+ { key: openEditorNativeToolbarActionKeys.redo, label: "Redo" },
299
+ { key: openEditorNativeToolbarActionKeys.paragraph, label: "Paragraph" },
300
+ { key: openEditorNativeToolbarActionKeys.bulletList, label: "Bullets" },
301
+ { key: openEditorNativeToolbarActionKeys.orderedList, label: "Numbers" },
302
+ ], onActionPress, theme, }) => {
303
+ const resolvedTheme = {
304
+ ...defaultNativeTheme,
305
+ ...theme,
306
+ toolbar: {
307
+ ...defaultNativeTheme.toolbar,
308
+ ...theme?.toolbar,
309
+ },
310
+ };
311
+ return (_jsx(View, { style: [styles.toolbar, { backgroundColor: resolvedTheme.surface, borderColor: resolvedTheme.border }], children: actions.map((action) => (_jsx(Pressable, { onPress: () => {
312
+ action.onPress?.(action.key);
313
+ onActionPress?.(action.key);
314
+ }, style: [styles.toolbarButton, { backgroundColor: resolvedTheme.accent, borderColor: resolvedTheme.borderStrong }], children: _jsx(Text, { style: [styles.toolbarButtonText, { color: resolvedTheme.accentText }], children: action.label }) }, action.key))) }));
315
+ };
316
+ export const OpenEditorNative = ({ document = nativeSeedDocument, editable = true, showToolbar = true, theme, toolbarPlacement = "keyboard", onChange, }) => {
317
+ const editorRef = useRef(null);
318
+ const [currentDocument, setCurrentDocument] = useState(() => normalizeForNativeEditor(document));
319
+ const [selection, setSelection] = useState();
320
+ const [linkRequest, setLinkRequest] = useState(null);
321
+ const [linkUrl, setLinkUrl] = useState("https://");
322
+ const [imageRequest, setImageRequest] = useState(null);
323
+ const [imageUrl, setImageUrl] = useState("https://placehold.co/960x420/png");
324
+ const [history, setHistory] = useState({
325
+ undoStack: [],
326
+ redoStack: [],
327
+ });
328
+ const nativeDocument = currentDocument;
329
+ const NativeEditor = useMemo(() => loadNativeEditor(), []);
330
+ const resolvedTheme = useMemo(() => ({
331
+ ...defaultNativeTheme,
332
+ ...theme,
333
+ toolbar: {
334
+ ...defaultNativeTheme.toolbar,
335
+ ...theme?.toolbar,
336
+ },
337
+ }), [theme]);
338
+ const nativeEditorTheme = useMemo(() => toNativeEditorTheme(resolvedTheme), [resolvedTheme]);
339
+ const toolbarItems = useMemo(() => createOpenEditorNativeToolbarItems({
340
+ canUndo: history.undoStack.length > 0,
341
+ canRedo: history.redoStack.length > 0,
342
+ }), [history.redoStack.length, history.undoStack.length]);
343
+ const syncDocument = (nextDocument, updateEditor = true) => {
344
+ const normalized = normalizeForNativeEditor(nextDocument);
345
+ if (!documentsMatch(normalized, nativeDocument)) {
346
+ setHistory((currentHistory) => ({
347
+ undoStack: [...currentHistory.undoStack.slice(-99), nativeDocument],
348
+ redoStack: [],
349
+ }));
350
+ }
351
+ setCurrentDocument(normalized);
352
+ onChange?.(normalized);
353
+ if (updateEditor) {
354
+ editorRef.current?.setContentJson(toNativeEditorDocument(normalized));
355
+ }
356
+ };
357
+ const moveSelectedBlock = (direction) => {
358
+ const selectedIndex = resolveSelectedTopLevelIndex(nativeDocument, selection);
359
+ const nextIndex = selectedIndex + direction;
360
+ if (nextIndex < 0 || nextIndex >= nativeDocument.content.length) {
361
+ return;
362
+ }
363
+ syncDocument(moveTopLevelBlock(nativeDocument, selectedIndex, nextIndex), true);
364
+ };
365
+ const handleToolbarAction = (key) => {
366
+ if (key === openEditorNativeToolbarActionKeys.dismissKeyboard) {
367
+ dismissKeyboard();
368
+ return;
369
+ }
370
+ if (key === openEditorNativeToolbarActionKeys.moveBlockUp) {
371
+ moveSelectedBlock(-1);
372
+ return;
373
+ }
374
+ if (key === openEditorNativeToolbarActionKeys.moveBlockDown) {
375
+ moveSelectedBlock(1);
376
+ return;
377
+ }
378
+ if (key === openEditorNativeToolbarActionKeys.undo) {
379
+ const previous = history.undoStack.at(-1);
380
+ if (!previous) {
381
+ return;
382
+ }
383
+ setHistory((currentHistory) => ({
384
+ undoStack: currentHistory.undoStack.slice(0, -1),
385
+ redoStack: [nativeDocument, ...currentHistory.redoStack].slice(0, 100),
386
+ }));
387
+ setCurrentDocument(previous);
388
+ onChange?.(previous);
389
+ editorRef.current?.setContentJson(toNativeEditorDocument(previous));
390
+ return;
391
+ }
392
+ if (key === openEditorNativeToolbarActionKeys.redo) {
393
+ const next = history.redoStack[0];
394
+ if (!next) {
395
+ return;
396
+ }
397
+ setHistory((currentHistory) => ({
398
+ undoStack: [...currentHistory.undoStack.slice(-99), nativeDocument],
399
+ redoStack: currentHistory.redoStack.slice(1),
400
+ }));
401
+ setCurrentDocument(next);
402
+ onChange?.(next);
403
+ editorRef.current?.setContentJson(toNativeEditorDocument(next));
404
+ return;
405
+ }
406
+ if (key === openEditorNativeToolbarActionKeys.bulletList) {
407
+ editorRef.current?.toggleList("bulletList");
408
+ return;
409
+ }
410
+ if (key === openEditorNativeToolbarActionKeys.orderedList) {
411
+ editorRef.current?.toggleList("orderedList");
412
+ return;
413
+ }
414
+ const actionDocument = defaultDocumentForToolbarAction(key);
415
+ if (actionDocument) {
416
+ editorRef.current?.insertContentJson(actionDocument);
417
+ }
418
+ };
419
+ const submitLinkDialog = () => {
420
+ const url = linkUrl.trim();
421
+ if (!linkRequest) {
422
+ return;
423
+ }
424
+ if (url && isSafeInputUrl(url)) {
425
+ linkRequest.setLink(url);
426
+ }
427
+ else {
428
+ linkRequest.unsetLink();
429
+ }
430
+ setLinkRequest(null);
431
+ };
432
+ const submitImageDialog = () => {
433
+ const url = imageUrl.trim();
434
+ if (imageRequest && isSafeInputUrl(url, imageRequest.allowBase64)) {
435
+ imageRequest.insertImage(url, { alt: "Placeholder" });
436
+ }
437
+ setImageRequest(null);
438
+ };
439
+ if (!NativeEditor) {
440
+ 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." })] }));
441
+ }
442
+ return (_jsxs(_Fragment, { children: [_jsx(NativeEditor, { ref: editorRef, initialJSON: toNativeEditorDocument(nativeDocument), schema: openEditorNativeSchema, editable: editable, placeholder: "Start writing...", showToolbar: showToolbar, toolbarPlacement: toolbarPlacement, toolbarItems: toolbarItems, onToolbarAction: handleToolbarAction, theme: nativeEditorTheme, autoDetectLinks: true, allowBase64Images: true, allowImageResizing: true, heightBehavior: "autoGrow", onRequestLink: (request) => {
443
+ setLinkUrl(request.href ?? "https://");
444
+ setLinkRequest(request);
445
+ }, onRequestImage: (request) => {
446
+ setImageUrl("https://placehold.co/960x420/png");
447
+ setImageRequest(request);
448
+ }, onSelectionChange: setSelection, onContentChangeJSON: (json) => {
449
+ syncDocument(contentFromNativeJson(json), false);
450
+ }, 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(UrlDialog, { title: "Image", value: imageUrl, visible: imageRequest !== null, placeholder: "https://", confirmLabel: "Insert", onChangeText: setImageUrl, onCancel: () => setImageRequest(null), onSubmit: submitImageDialog, theme: resolvedTheme })] }));
451
+ };
452
+ export const OpenEditorNativeViewer = ({ document = nativeSeedDocument, theme, renderers, }) => {
453
+ const resolvedTheme = {
454
+ ...defaultNativeTheme,
455
+ ...theme,
456
+ toolbar: {
457
+ ...defaultNativeTheme.toolbar,
458
+ ...theme?.toolbar,
459
+ },
460
+ };
461
+ const normalizedDocument = normalizeForNativeEditor(document);
462
+ 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)) }));
463
+ };
464
+ const CommandButton = ({ label, active = false, primary = false, theme, onPress, }) => (_jsx(Pressable, { accessibilityRole: "button", accessibilityState: { selected: active }, onPress: onPress, style: [
465
+ styles.commandButton,
466
+ { backgroundColor: theme.surface, borderColor: theme.border },
467
+ active && { backgroundColor: theme.accent, borderColor: theme.accent },
468
+ primary && { backgroundColor: theme.primary, borderColor: theme.primary },
469
+ ], children: _jsx(Text, { style: [styles.commandButtonText, { color: theme.text }, (active || primary) && { color: active ? theme.accentText : theme.primaryText }], children: label }) }));
470
+ 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 })] })] }) }) }));
471
+ const styles = StyleSheet.create({
472
+ viewer: {
473
+ borderRadius: 12,
474
+ borderWidth: 1,
475
+ gap: 4,
476
+ padding: 16,
477
+ },
478
+ nativeEditor: {
479
+ minHeight: 320,
480
+ },
481
+ toolbar: {
482
+ borderRadius: 10,
483
+ borderWidth: 1,
484
+ flexDirection: "row",
485
+ flexWrap: "wrap",
486
+ gap: 8,
487
+ padding: 8,
488
+ },
489
+ toolbarButton: {
490
+ borderRadius: 999,
491
+ borderWidth: 1,
492
+ minHeight: 34,
493
+ paddingHorizontal: 12,
494
+ paddingVertical: 8,
495
+ },
496
+ toolbarButtonText: {
497
+ fontSize: 13,
498
+ fontWeight: "700",
499
+ },
500
+ unavailable: {
501
+ backgroundColor: "#ffffff",
502
+ borderColor: "#e5e5e5",
503
+ borderRadius: 8,
504
+ borderCurve: "continuous",
505
+ borderWidth: 1,
506
+ margin: 16,
507
+ padding: 16,
508
+ },
509
+ unavailableTitle: {
510
+ fontSize: 16,
511
+ fontWeight: "700",
512
+ marginBottom: 4,
513
+ },
514
+ unavailableText: {
515
+ fontSize: 14,
516
+ lineHeight: 20,
517
+ },
518
+ commandButton: {
519
+ alignItems: "center",
520
+ backgroundColor: "#ffffff",
521
+ borderColor: "#e5e5e5",
522
+ borderRadius: 8,
523
+ borderWidth: 1,
524
+ justifyContent: "center",
525
+ minHeight: 38,
526
+ paddingHorizontal: 12,
527
+ },
528
+ commandButtonText: {
529
+ color: "#171717",
530
+ fontSize: 13,
531
+ fontWeight: "700",
532
+ },
533
+ dialogBackdrop: {
534
+ alignItems: "center",
535
+ backgroundColor: "rgba(0, 0, 0, 0.32)",
536
+ flex: 1,
537
+ justifyContent: "center",
538
+ padding: 24,
539
+ },
540
+ dialog: {
541
+ backgroundColor: "#ffffff",
542
+ borderRadius: 8,
543
+ gap: 12,
544
+ maxWidth: 420,
545
+ padding: 16,
546
+ width: "100%",
547
+ },
548
+ dialogTitle: {
549
+ color: "#171717",
550
+ fontSize: 17,
551
+ fontWeight: "700",
552
+ },
553
+ dialogInput: {
554
+ borderColor: "#e5e5e5",
555
+ borderRadius: 8,
556
+ borderWidth: 1,
557
+ color: "#171717",
558
+ fontSize: 15,
559
+ minHeight: 44,
560
+ paddingHorizontal: 12,
561
+ },
562
+ dialogActions: {
563
+ flexDirection: "row",
564
+ gap: 8,
565
+ justifyContent: "flex-end",
566
+ },
567
+ });
568
+ export { createNativeEditorDocument, fromNativeEditorDocument, sanitizeNativeEditorDocument, toNativeEditorDocument, } from "./document.js";
569
+ export { createOpenEditorNativeToolbarItems, defaultDocumentForToolbarAction, nativeToolbarParityCoverage, openEditorNativeToolbarActionKeys, openEditorNativeToolbarItems, } from "./toolbar.js";