@openeditor/native 0.0.24 → 0.0.26
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 +25 -0
- package/dist/emoji-button.d.ts +8 -0
- package/dist/emoji-button.js +14 -0
- package/dist/emoji-data.d.ts +44 -0
- package/dist/emoji-data.js +50 -0
- package/dist/emoji-picker.d.ts +12 -0
- package/dist/emoji-picker.js +67 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +159 -61
- package/dist/toolbar.d.ts +1 -1
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -8,8 +8,13 @@ Pass `attachmentRuntime` to use the system picker and host storage lifecycle. Th
|
|
|
8
8
|
|
|
9
9
|
- `OpenEditorNative` as the primary editing surface
|
|
10
10
|
- `OpenEditorNativeViewer` for read-only rendering
|
|
11
|
+
- `OpenEditorNativeEmojiPicker` for standalone native emoji selection
|
|
11
12
|
- native document bridge helpers where renderer adaptation is required
|
|
12
13
|
|
|
14
|
+
Callout and Page emoji are editable in `OpenEditorNative`: pressing the emoji
|
|
15
|
+
opens the native picker and commits the selected Unicode value through editor
|
|
16
|
+
history. Inline emoji continue to use the device keyboard.
|
|
17
|
+
|
|
13
18
|
## Required peers
|
|
14
19
|
|
|
15
20
|
- `@openeditor/react-native-prose-editor`
|
|
@@ -33,6 +38,26 @@ export function Example() {
|
|
|
33
38
|
}
|
|
34
39
|
```
|
|
35
40
|
|
|
41
|
+
## Standalone emoji picker
|
|
42
|
+
|
|
43
|
+
The editor wires this automatically for callouts and pages. Hosts can also use
|
|
44
|
+
the same picker for page headers or other product UI:
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { OpenEditorNativeEmojiPicker } from "@openeditor/native";
|
|
48
|
+
|
|
49
|
+
<OpenEditorNativeEmojiPicker
|
|
50
|
+
visible={pickerOpen}
|
|
51
|
+
onEmojiSelect={setPageIcon}
|
|
52
|
+
onRequestClose={() => setPickerOpen(false)}
|
|
53
|
+
/>;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The native picker shares OpenEditor’s Emojibase dataset with the Frimousse web
|
|
57
|
+
picker, while using a platform-native medium bottom sheet and native controls
|
|
58
|
+
for scrolling, search, and touch handling. Long-press an emoji that supports
|
|
59
|
+
skin tones to choose a variant from the platform-native context menu.
|
|
60
|
+
|
|
36
61
|
## Internal notes
|
|
37
62
|
|
|
38
63
|
- The native prose editor engine remains an implementation detail behind this package.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type OpenEditorNativeEmoji } from "./emoji-data.js";
|
|
2
|
+
type EmojiButtonProps = {
|
|
3
|
+
item: OpenEditorNativeEmoji;
|
|
4
|
+
size: number;
|
|
5
|
+
onSelect: (emoji: string) => void;
|
|
6
|
+
};
|
|
7
|
+
export declare const EmojiButton: (props: EmojiButtonProps) => import("react").JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { MenuView } from "@expo/ui/community/menu";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { Pressable, Text } from "react-native";
|
|
5
|
+
import { emojiForNativeSkinTone, openEditorNativeEmojiSkinToneOptions, } from "./emoji-data.js";
|
|
6
|
+
const EmojiTrigger = ({ item, size, onSelect }) => (_jsx(Pressable, { accessibilityLabel: item.emoji, accessibilityRole: "button", onPress: () => onSelect(item.emoji), style: { alignItems: "center", height: 44, justifyContent: "center", width: size }, children: _jsx(Text, { style: { fontSize: 27 }, children: item.emoji }) }));
|
|
7
|
+
const ToneEmojiButton = ({ item, size, onSelect }) => {
|
|
8
|
+
const actions = useMemo(() => openEditorNativeEmojiSkinToneOptions.map((option) => {
|
|
9
|
+
const emoji = emojiForNativeSkinTone(item, option.tone);
|
|
10
|
+
return { id: emoji, title: `${emoji} ${option.label}` };
|
|
11
|
+
}), [item]);
|
|
12
|
+
return (_jsx(MenuView, { actions: actions, onPressAction: ({ nativeEvent }) => onSelect(nativeEvent.event), shouldOpenOnLongPress: true, style: { height: 44, width: size }, children: _jsx(EmojiTrigger, { item: item, onSelect: onSelect, size: size }) }));
|
|
13
|
+
};
|
|
14
|
+
export const EmojiButton = (props) => (props.item.skins?.length ? _jsx(ToneEmojiButton, { ...props }) : _jsx(EmojiTrigger, { ...props }));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type OpenEditorNativeEmojiSkinTone = 0 | 1 | 2 | 3 | 4 | 5;
|
|
2
|
+
export type OpenEditorNativeEmoji = {
|
|
3
|
+
emoji: string;
|
|
4
|
+
label: string;
|
|
5
|
+
search: string;
|
|
6
|
+
group: number;
|
|
7
|
+
order: number;
|
|
8
|
+
skins?: readonly {
|
|
9
|
+
emoji: string;
|
|
10
|
+
tone?: number;
|
|
11
|
+
}[];
|
|
12
|
+
};
|
|
13
|
+
export type OpenEditorNativeEmojiSection = {
|
|
14
|
+
key: string;
|
|
15
|
+
title: string;
|
|
16
|
+
data: readonly OpenEditorNativeEmoji[];
|
|
17
|
+
};
|
|
18
|
+
export declare const openEditorNativeEmojiSkinToneOptions: readonly [{
|
|
19
|
+
readonly tone: 0;
|
|
20
|
+
readonly emoji: "✋";
|
|
21
|
+
readonly label: "Default skin tone";
|
|
22
|
+
}, {
|
|
23
|
+
readonly tone: 1;
|
|
24
|
+
readonly emoji: "✋🏻";
|
|
25
|
+
readonly label: "Light skin tone";
|
|
26
|
+
}, {
|
|
27
|
+
readonly tone: 2;
|
|
28
|
+
readonly emoji: "✋🏼";
|
|
29
|
+
readonly label: "Medium-light skin tone";
|
|
30
|
+
}, {
|
|
31
|
+
readonly tone: 3;
|
|
32
|
+
readonly emoji: "✋🏽";
|
|
33
|
+
readonly label: "Medium skin tone";
|
|
34
|
+
}, {
|
|
35
|
+
readonly tone: 4;
|
|
36
|
+
readonly emoji: "✋🏾";
|
|
37
|
+
readonly label: "Medium-dark skin tone";
|
|
38
|
+
}, {
|
|
39
|
+
readonly tone: 5;
|
|
40
|
+
readonly emoji: "✋🏿";
|
|
41
|
+
readonly label: "Dark skin tone";
|
|
42
|
+
}];
|
|
43
|
+
export declare const emojiForNativeSkinTone: (item: OpenEditorNativeEmoji, skinTone: OpenEditorNativeEmojiSkinTone) => string;
|
|
44
|
+
export declare const getOpenEditorNativeEmojiSections: (query?: string) => readonly OpenEditorNativeEmojiSection[];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import emojiDataJson from "emojibase-data/en/data.json";
|
|
2
|
+
import emojiMessagesJson from "emojibase-data/en/messages.json";
|
|
3
|
+
const normalizeSearch = (value) => value.toLocaleLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").trim();
|
|
4
|
+
const rawEmojiData = emojiDataJson;
|
|
5
|
+
const rawMessages = emojiMessagesJson;
|
|
6
|
+
const groupMessages = new Map(rawMessages.groups.map((group) => [group.order, { key: group.key, title: group.message }]));
|
|
7
|
+
const nativeEmoji = rawEmojiData
|
|
8
|
+
.filter((item) => typeof item.group === "number" && item.group !== 2)
|
|
9
|
+
.map((item) => ({
|
|
10
|
+
emoji: item.emoji,
|
|
11
|
+
label: item.label,
|
|
12
|
+
search: normalizeSearch([item.label, ...(item.tags ?? [])].join(" ")),
|
|
13
|
+
group: item.group,
|
|
14
|
+
order: item.order ?? Number.MAX_SAFE_INTEGER,
|
|
15
|
+
skins: item.skins,
|
|
16
|
+
}))
|
|
17
|
+
.sort((left, right) => left.order - right.order);
|
|
18
|
+
export const openEditorNativeEmojiSkinToneOptions = [
|
|
19
|
+
{ tone: 0, emoji: "✋", label: "Default skin tone" },
|
|
20
|
+
{ tone: 1, emoji: "✋🏻", label: "Light skin tone" },
|
|
21
|
+
{ tone: 2, emoji: "✋🏼", label: "Medium-light skin tone" },
|
|
22
|
+
{ tone: 3, emoji: "✋🏽", label: "Medium skin tone" },
|
|
23
|
+
{ tone: 4, emoji: "✋🏾", label: "Medium-dark skin tone" },
|
|
24
|
+
{ tone: 5, emoji: "✋🏿", label: "Dark skin tone" },
|
|
25
|
+
];
|
|
26
|
+
export const emojiForNativeSkinTone = (item, skinTone) => {
|
|
27
|
+
if (skinTone === 0)
|
|
28
|
+
return item.emoji;
|
|
29
|
+
return item.skins?.find((skin) => skin.tone === skinTone)?.emoji ?? item.emoji;
|
|
30
|
+
};
|
|
31
|
+
export const getOpenEditorNativeEmojiSections = (query = "") => {
|
|
32
|
+
const normalizedQuery = normalizeSearch(query);
|
|
33
|
+
const groups = new Map();
|
|
34
|
+
for (const item of nativeEmoji) {
|
|
35
|
+
if (normalizedQuery && !item.search.includes(normalizedQuery))
|
|
36
|
+
continue;
|
|
37
|
+
const group = groups.get(item.group);
|
|
38
|
+
if (group)
|
|
39
|
+
group.push(item);
|
|
40
|
+
else
|
|
41
|
+
groups.set(item.group, [item]);
|
|
42
|
+
}
|
|
43
|
+
return [...groups.entries()]
|
|
44
|
+
.sort(([left], [right]) => left - right)
|
|
45
|
+
.map(([group, data]) => ({
|
|
46
|
+
key: groupMessages.get(group)?.key ?? String(group),
|
|
47
|
+
title: groupMessages.get(group)?.title ?? "Emoji",
|
|
48
|
+
data,
|
|
49
|
+
}));
|
|
50
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type OpenEditorNativeEmojiPickerTheme = {
|
|
2
|
+
surfaceMuted?: string;
|
|
3
|
+
text?: string;
|
|
4
|
+
muted?: string;
|
|
5
|
+
};
|
|
6
|
+
export type OpenEditorNativeEmojiPickerProps = {
|
|
7
|
+
visible: boolean;
|
|
8
|
+
onEmojiSelect: (emoji: string) => void;
|
|
9
|
+
onRequestClose: () => void;
|
|
10
|
+
theme?: OpenEditorNativeEmojiPickerTheme;
|
|
11
|
+
};
|
|
12
|
+
export declare const OpenEditorNativeEmojiPicker: ({ visible, onEmojiSelect, onRequestClose, theme, }: OpenEditorNativeEmojiPickerProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { BottomSheet, RNHostView } from "@expo/ui";
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
import { ScrollView, Text, TextInput, useWindowDimensions, View, } from "react-native";
|
|
5
|
+
import { getOpenEditorNativeEmojiSections, } from "./emoji-data.js";
|
|
6
|
+
import { EmojiButton } from "./emoji-button.js";
|
|
7
|
+
const toRows = (items, columns = 8) => {
|
|
8
|
+
const rows = [];
|
|
9
|
+
for (let index = 0; index < items.length; index += columns) {
|
|
10
|
+
rows.push(items.slice(index, index + columns));
|
|
11
|
+
}
|
|
12
|
+
return rows;
|
|
13
|
+
};
|
|
14
|
+
const DEFAULT_THEME = {
|
|
15
|
+
surfaceMuted: "#eeeeee",
|
|
16
|
+
text: "#171717",
|
|
17
|
+
muted: "#737373",
|
|
18
|
+
};
|
|
19
|
+
export const OpenEditorNativeEmojiPicker = ({ visible, onEmojiSelect, onRequestClose, theme, }) => {
|
|
20
|
+
const { height: windowHeight, width: windowWidth } = useWindowDimensions();
|
|
21
|
+
const [query, setQuery] = useState("");
|
|
22
|
+
const resolvedTheme = { ...DEFAULT_THEME, ...theme };
|
|
23
|
+
const nativeScrollHeight = Math.max(240, Math.floor(windowHeight * 0.5) - 32);
|
|
24
|
+
const contentWidth = Math.max(240, Math.min(windowWidth - 32, 520));
|
|
25
|
+
const emojiButtonWidth = contentWidth / 8;
|
|
26
|
+
const sections = useMemo(() => getOpenEditorNativeEmojiSections(query).map((section) => ({
|
|
27
|
+
...section,
|
|
28
|
+
data: toRows(section.data),
|
|
29
|
+
})), [query]);
|
|
30
|
+
const close = () => {
|
|
31
|
+
setQuery("");
|
|
32
|
+
onRequestClose();
|
|
33
|
+
};
|
|
34
|
+
const selectEmoji = (selectedEmoji) => {
|
|
35
|
+
onEmojiSelect(selectedEmoji);
|
|
36
|
+
close();
|
|
37
|
+
};
|
|
38
|
+
return (_jsx(BottomSheet, { isPresented: visible, onDismiss: close, snapPoints: ["half"], children: _jsx(RNHostView, { matchContents: true, children: _jsxs(ScrollView, { contentContainerStyle: { paddingBottom: 32 }, keyboardShouldPersistTaps: "handled", style: { height: nativeScrollHeight, width: contentWidth }, children: [_jsx(TextInput, { autoCapitalize: "none", autoCorrect: false, onChangeText: setQuery, placeholder: "Search emoji\u2026", placeholderTextColor: resolvedTheme.muted, returnKeyType: "search", style: {
|
|
39
|
+
backgroundColor: resolvedTheme.surfaceMuted,
|
|
40
|
+
borderRadius: 10,
|
|
41
|
+
color: resolvedTheme.text,
|
|
42
|
+
fontSize: 16,
|
|
43
|
+
height: 44,
|
|
44
|
+
paddingHorizontal: 12,
|
|
45
|
+
width: contentWidth,
|
|
46
|
+
} }), sections.length === 0 ? (_jsx(Text, { style: {
|
|
47
|
+
color: resolvedTheme.muted,
|
|
48
|
+
fontSize: 15,
|
|
49
|
+
height: 160,
|
|
50
|
+
paddingTop: 48,
|
|
51
|
+
textAlign: "center",
|
|
52
|
+
width: contentWidth,
|
|
53
|
+
}, children: "No emoji found." })) : sections.map((section) => (_jsxs(View, { style: { width: contentWidth }, children: [_jsx(Text, { style: {
|
|
54
|
+
color: resolvedTheme.muted,
|
|
55
|
+
fontSize: 12,
|
|
56
|
+
fontWeight: "600",
|
|
57
|
+
height: 32,
|
|
58
|
+
paddingHorizontal: 8,
|
|
59
|
+
paddingVertical: 8,
|
|
60
|
+
width: contentWidth,
|
|
61
|
+
}, children: section.title }), section.data.map((row, rowIndex) => (_jsx(View, { style: {
|
|
62
|
+
alignItems: "center",
|
|
63
|
+
flexDirection: "row",
|
|
64
|
+
height: 44,
|
|
65
|
+
width: contentWidth,
|
|
66
|
+
}, children: row.map((item) => (_jsx(EmojiButton, { item: item, onSelect: selectEmoji, size: emojiButtonWidth }, item.emoji))) }, `${section.key}-${rowIndex}`)))] }, section.key)))] }) }) }));
|
|
67
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { type OpenEditorDocument, type OpenEditorAttachmentRuntime, type ProseMi
|
|
|
2
2
|
import { type ReactNode } from "react";
|
|
3
3
|
import type { NativeRichTextEditorToolbarPlacement } from "@openeditor/react-native-prose-editor";
|
|
4
4
|
export { normalizeNativeThemeColor } from "./theme.js";
|
|
5
|
+
export { OpenEditorNativeEmojiPicker, type OpenEditorNativeEmojiPickerProps, type OpenEditorNativeEmojiPickerTheme, } from "./emoji-picker.js";
|
|
6
|
+
export { emojiForNativeSkinTone, getOpenEditorNativeEmojiSections, openEditorNativeEmojiSkinToneOptions, type OpenEditorNativeEmoji, type OpenEditorNativeEmojiSection, type OpenEditorNativeEmojiSkinTone, } from "./emoji-data.js";
|
|
5
7
|
export type OpenEditorNativeProps = {
|
|
6
8
|
document?: OpenEditorDocument;
|
|
7
9
|
editable?: boolean;
|
|
@@ -44,12 +46,14 @@ export type OpenEditorNativeTheme = {
|
|
|
44
46
|
background: string;
|
|
45
47
|
surface: string;
|
|
46
48
|
surfaceMuted: string;
|
|
49
|
+
blockSurface?: string;
|
|
47
50
|
text: string;
|
|
48
51
|
textSoft?: string;
|
|
49
52
|
muted: string;
|
|
50
53
|
placeholder?: string;
|
|
51
54
|
border: string;
|
|
52
55
|
borderStrong?: string;
|
|
56
|
+
structuralLine?: string;
|
|
53
57
|
accent: string;
|
|
54
58
|
accentText: string;
|
|
55
59
|
accentStrong?: string;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
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, moveTopLevelBlock, normalizeEmoji,
|
|
2
|
+
import { DEFAULT_CALLOUT_EMOJI, DEFAULT_PAGE_EMOJI, createDocument, findBlockSpecForNode, moveTopLevelBlock, normalizeEmoji, } from "@openeditor/core";
|
|
3
3
|
import { useMemo, useRef, useState } from "react";
|
|
4
4
|
import { Image as NativeImage, Keyboard, Modal, Platform, Pressable, StyleSheet, Text, TextInput, View } from "react-native";
|
|
5
5
|
import { fromNativeEditorDocument, sanitizeNativeEditorDocument, toNativeEditorDocument, } from "./document.js";
|
|
@@ -7,23 +7,30 @@ import { resolveSelectedTopLevelIndex } from "./selection.js";
|
|
|
7
7
|
import { createOpenEditorNativeToolbarItems, defaultDocumentForToolbarAction, openEditorNativeToolbarActionKeys, } from "./toolbar.js";
|
|
8
8
|
import { normalizeNativeThemeColor } from "./theme.js";
|
|
9
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";
|
|
10
13
|
import { defaultBlockRegistry } from "@openeditor/extensions";
|
|
14
|
+
const defaultLightNeutralSurface = "#eeeeee";
|
|
15
|
+
const defaultLightStructuralLine = "#d4d4d4";
|
|
11
16
|
const defaultNativeTheme = {
|
|
12
17
|
background: "#fafafa",
|
|
13
18
|
surface: "#ffffff",
|
|
14
|
-
surfaceMuted:
|
|
19
|
+
surfaceMuted: defaultLightNeutralSurface,
|
|
20
|
+
blockSurface: defaultLightNeutralSurface,
|
|
15
21
|
text: "#171717",
|
|
16
22
|
textSoft: "#262626",
|
|
17
23
|
muted: "#737373",
|
|
18
24
|
placeholder: "#a3a3a3",
|
|
19
|
-
border:
|
|
20
|
-
borderStrong:
|
|
21
|
-
|
|
25
|
+
border: defaultLightNeutralSurface,
|
|
26
|
+
borderStrong: defaultLightStructuralLine,
|
|
27
|
+
structuralLine: defaultLightStructuralLine,
|
|
28
|
+
accent: defaultLightNeutralSurface,
|
|
22
29
|
accentText: "#171717",
|
|
23
30
|
accentStrong: "#171717",
|
|
24
31
|
primary: "#171717",
|
|
25
32
|
primaryText: "#ffffff",
|
|
26
|
-
codeBackground:
|
|
33
|
+
codeBackground: defaultLightNeutralSurface,
|
|
27
34
|
codeText: "#171717",
|
|
28
35
|
debugBackground: "#171717",
|
|
29
36
|
debugText: "#d4d4d4",
|
|
@@ -186,6 +193,10 @@ const openEditorNativeSchema = {
|
|
|
186
193
|
};
|
|
187
194
|
const toNativeEditorTheme = (theme) => ({
|
|
188
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),
|
|
189
200
|
borderRadius: 0,
|
|
190
201
|
placeholderColor: normalizeNativeThemeColor(theme.placeholder),
|
|
191
202
|
contentInsets: {
|
|
@@ -222,7 +233,7 @@ const toNativeEditorTheme = (theme) => ({
|
|
|
222
233
|
itemSpacing: 6,
|
|
223
234
|
},
|
|
224
235
|
blockquote: {
|
|
225
|
-
borderColor: normalizeNativeThemeColor(theme.
|
|
236
|
+
borderColor: normalizeNativeThemeColor(theme.structuralLine),
|
|
226
237
|
borderWidth: 3,
|
|
227
238
|
text: {
|
|
228
239
|
color: normalizeNativeThemeColor(theme.textSoft),
|
|
@@ -242,14 +253,14 @@ const toNativeEditorTheme = (theme) => ({
|
|
|
242
253
|
},
|
|
243
254
|
},
|
|
244
255
|
table: {
|
|
245
|
-
cellBackgroundColor: normalizeNativeThemeColor(theme.
|
|
246
|
-
headerBackgroundColor: normalizeNativeThemeColor(theme.
|
|
247
|
-
borderColor: normalizeNativeThemeColor(theme.
|
|
256
|
+
cellBackgroundColor: normalizeNativeThemeColor(theme.blockSurface),
|
|
257
|
+
headerBackgroundColor: normalizeNativeThemeColor(theme.blockSurface),
|
|
258
|
+
borderColor: normalizeNativeThemeColor(theme.structuralLine),
|
|
248
259
|
selectionBackgroundColor: normalizeNativeThemeColor(theme.accent),
|
|
249
260
|
selectionBorderColor: normalizeNativeThemeColor(theme.accentStrong),
|
|
250
261
|
},
|
|
251
262
|
horizontalRule: {
|
|
252
|
-
color: normalizeNativeThemeColor(theme.
|
|
263
|
+
color: normalizeNativeThemeColor(theme.structuralLine),
|
|
253
264
|
thickness: 1,
|
|
254
265
|
verticalMargin: 16,
|
|
255
266
|
},
|
|
@@ -298,42 +309,29 @@ const isSafeInputUrl = (value, allowDataImage = false) => {
|
|
|
298
309
|
return true;
|
|
299
310
|
};
|
|
300
311
|
const documentsMatch = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
type: "blockquote",
|
|
325
|
-
content: [textBlock("paragraph", "Structural blocks return once the native engine owns them.")],
|
|
326
|
-
},
|
|
327
|
-
{
|
|
328
|
-
type: "columns",
|
|
329
|
-
content: [
|
|
330
|
-
{ type: "column", content: [textBlock("paragraph", "Columns now flow through the native engine.")] },
|
|
331
|
-
{ type: "column", content: [textBlock("paragraph", "Tables stay blocked until the native engine supports them cleanly.")] },
|
|
332
|
-
],
|
|
333
|
-
},
|
|
334
|
-
{ type: "horizontalRule" },
|
|
335
|
-
{ type: "image", attrs: { src: "https://placehold.co/960x420/png", alt: "Placeholder" } },
|
|
336
|
-
]);
|
|
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
|
+
};
|
|
337
335
|
const renderNativeViewerNode = (node, key, theme, renderers, onOpenPage, attachmentRuntime) => {
|
|
338
336
|
if (node.type === "text") {
|
|
339
337
|
return _jsx(Text, { style: { color: theme.text }, children: node.text ?? "" }, key);
|
|
@@ -355,28 +353,29 @@ const renderNativeViewerNode = (node, key, theme, renderers, onOpenPage, attachm
|
|
|
355
353
|
case "bulletList":
|
|
356
354
|
case "orderedList":
|
|
357
355
|
case "taskList":
|
|
356
|
+
case "toggleList":
|
|
358
357
|
return _jsx(View, { style: { marginBottom: 12 }, children: children }, key);
|
|
359
358
|
case "blockquote":
|
|
360
|
-
return _jsx(View, { style: { borderLeftColor: theme.
|
|
359
|
+
return _jsx(View, { style: { borderLeftColor: theme.structuralLine, borderLeftWidth: 3, marginBottom: 12, paddingLeft: 12 }, children: children }, key);
|
|
361
360
|
case "codeBlock":
|
|
362
361
|
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);
|
|
363
362
|
case "callout": {
|
|
364
363
|
const emoji = normalizeEmoji(node.attrs?.emoji, DEFAULT_CALLOUT_EMOJI);
|
|
365
|
-
return _jsxs(View, { style: { backgroundColor: theme.
|
|
364
|
+
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);
|
|
366
365
|
}
|
|
367
366
|
case "divider":
|
|
368
|
-
return _jsx(View, { style: { backgroundColor: theme.
|
|
367
|
+
return _jsx(View, { style: { backgroundColor: theme.structuralLine, height: 1, marginVertical: 16 } }, key);
|
|
369
368
|
case "image":
|
|
370
369
|
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));
|
|
371
370
|
case "columns":
|
|
372
371
|
return _jsx(View, { style: { flexDirection: "row", gap: 12, marginBottom: 12 }, children: children }, key);
|
|
373
372
|
case "table":
|
|
374
|
-
return _jsx(View, { style: { borderColor: theme.
|
|
373
|
+
return _jsx(View, { style: { borderColor: theme.structuralLine, borderRadius: 8, borderWidth: 1, marginBottom: 12, overflow: "hidden" }, children: children }, key);
|
|
375
374
|
case "page": {
|
|
376
375
|
const pageId = typeof node.attrs?.pageId === "string" ? node.attrs.pageId : "";
|
|
377
376
|
const title = node.content?.map((child) => child.text ?? "").join("") || "Untitled";
|
|
378
377
|
const icon = normalizeEmoji(node.attrs?.icon, DEFAULT_PAGE_EMOJI);
|
|
379
|
-
const content = _jsxs(_Fragment, { children: [_jsx(Text, { style: {
|
|
378
|
+
const content = _jsxs(_Fragment, { children: [_jsx(Text, { style: [styles.viewerPageIcon, { color: theme.text }], children: icon }), _jsx(Text, { style: [styles.viewerPageTitle, { color: theme.text, textDecorationColor: theme.muted }], children: title }), _jsx(Text, { style: { color: theme.muted }, children: "\u2192" })] });
|
|
380
379
|
return onOpenPage && pageId ? (_jsx(Pressable, { accessibilityRole: "button", onPress: () => onOpenPage({ pageId, title, icon, href: typeof node.attrs?.href === "string" ? node.attrs.href : null }), style: { alignItems: "center", borderRadius: 8, flexDirection: "row", gap: 8, marginBottom: 8, padding: 8 }, children: content }, key)) : _jsx(View, { style: { alignItems: "center", flexDirection: "row", gap: 8, marginBottom: 8, padding: 8 }, children: content }, key);
|
|
381
380
|
}
|
|
382
381
|
case "attachment": {
|
|
@@ -387,20 +386,29 @@ const renderNativeViewerNode = (node, key, theme, renderers, onOpenPage, attachm
|
|
|
387
386
|
size: typeof node.attrs?.size === "number" ? node.attrs.size : null,
|
|
388
387
|
url: typeof node.attrs?.url === "string" ? node.attrs.url : null,
|
|
389
388
|
};
|
|
390
|
-
const
|
|
391
|
-
|
|
389
|
+
const size = formatAttachmentSize(snapshot.size);
|
|
390
|
+
const type = attachmentTypeLabel(snapshot.mimeType, snapshot.name);
|
|
391
|
+
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" })] });
|
|
392
|
+
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);
|
|
392
393
|
}
|
|
393
394
|
default:
|
|
394
395
|
if (node.type === "column") {
|
|
395
396
|
return _jsx(View, { style: { flex: 1 }, children: children }, key);
|
|
396
397
|
}
|
|
397
398
|
if (node.type === "tableRow") {
|
|
398
|
-
return _jsx(View, { style: { borderBottomColor: theme.
|
|
399
|
+
return _jsx(View, { style: { borderBottomColor: theme.structuralLine, borderBottomWidth: StyleSheet.hairlineWidth, flexDirection: "row" }, children: children }, key);
|
|
399
400
|
}
|
|
400
401
|
if (node.type === "tableHeader" || node.type === "tableCell") {
|
|
401
|
-
return _jsx(View, { style: { flex: 1, padding: 10 }, children: children }, key);
|
|
402
|
+
return _jsx(View, { style: { backgroundColor: theme.blockSurface, flex: 1, padding: 10 }, children: children }, key);
|
|
403
|
+
}
|
|
404
|
+
if (node.type === "taskItem") {
|
|
405
|
+
const checked = node.attrs?.checked === true;
|
|
406
|
+
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);
|
|
402
407
|
}
|
|
403
|
-
if (node.type === "
|
|
408
|
+
if (node.type === "toggleListItem") {
|
|
409
|
+
return _jsx(NativeToggleListItem, { initiallyOpen: node.attrs?.open !== false, theme: theme, children: children }, key);
|
|
410
|
+
}
|
|
411
|
+
if (node.type === "listItem") {
|
|
404
412
|
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);
|
|
405
413
|
}
|
|
406
414
|
return _jsx(View, { children: children }, key);
|
|
@@ -426,13 +434,14 @@ export const OpenEditorNativeToolbar = ({ actions = [
|
|
|
426
434
|
onActionPress?.(action.key);
|
|
427
435
|
}, style: [styles.toolbarButton, { backgroundColor: resolvedTheme.accent, borderColor: resolvedTheme.borderStrong }], children: _jsx(Text, { style: [styles.toolbarButtonText, { color: resolvedTheme.accentText }], children: action.label }) }, action.key))) }));
|
|
428
436
|
};
|
|
429
|
-
export const OpenEditorNative = ({ document =
|
|
437
|
+
export const OpenEditorNative = ({ document = createDocument(), editable = true, placeholder = "Start writing...", showToolbar = true, theme, toolbarPlacement = "keyboard", onChange, onOpenPage, attachmentRuntime, }) => {
|
|
430
438
|
const editorRef = useRef(null);
|
|
431
439
|
const [currentDocument, setCurrentDocument] = useState(() => normalizeForNativeEditor(document));
|
|
432
440
|
const [selection, setSelection] = useState();
|
|
433
441
|
const [linkRequest, setLinkRequest] = useState(null);
|
|
434
442
|
const [linkUrl, setLinkUrl] = useState("https://");
|
|
435
443
|
const [imageRequest, setImageRequest] = useState(null);
|
|
444
|
+
const [emojiRequest, setEmojiRequest] = useState(null);
|
|
436
445
|
const [imageUrl, setImageUrl] = useState("https://placehold.co/960x420/png");
|
|
437
446
|
const [attachmentUpload, setAttachmentUpload] = useState(null);
|
|
438
447
|
const attachmentAbortRef = useRef(null);
|
|
@@ -597,11 +606,24 @@ export const OpenEditorNative = ({ document = nativeSeedDocument, editable = tru
|
|
|
597
606
|
}, onRequestImage: (request) => {
|
|
598
607
|
setImageUrl("https://placehold.co/960x420/png");
|
|
599
608
|
setImageRequest(request);
|
|
600
|
-
}, onOpenPage: onOpenPage, onOpenAttachment: (attachment) => void attachmentRuntime?.openAttachment?.(attachment), onSelectionChange: setSelection, onContentChangeJSON: (json) => {
|
|
609
|
+
}, onOpenPage: onOpenPage, onRequestEmoji: setEmojiRequest, onOpenAttachment: (attachment) => void attachmentRuntime?.openAttachment?.(attachment), onSelectionChange: setSelection, onContentChangeJSON: (json) => {
|
|
601
610
|
syncDocument(contentFromNativeJson(json), false);
|
|
602
|
-
}, 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 })
|
|
611
|
+
}, 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) => {
|
|
612
|
+
if (!emojiRequest)
|
|
613
|
+
return;
|
|
614
|
+
const attribute = emojiRequest.nodeType === "page" ? "icon" : "emoji";
|
|
615
|
+
editorRef.current?.updateNodeAttrs(emojiRequest.docPos, {
|
|
616
|
+
...emojiRequest.attrs,
|
|
617
|
+
[attribute]: emoji,
|
|
618
|
+
});
|
|
619
|
+
setEmojiRequest(null);
|
|
620
|
+
}, onRequestClose: () => setEmojiRequest(null), theme: {
|
|
621
|
+
surfaceMuted: resolvedTheme.surfaceMuted,
|
|
622
|
+
text: resolvedTheme.text,
|
|
623
|
+
muted: resolvedTheme.muted,
|
|
624
|
+
}, visible: emojiRequest !== null })] }));
|
|
603
625
|
};
|
|
604
|
-
export const OpenEditorNativeViewer = ({ document =
|
|
626
|
+
export const OpenEditorNativeViewer = ({ document = createDocument(), theme, renderers, onOpenPage, attachmentRuntime, }) => {
|
|
605
627
|
const resolvedTheme = {
|
|
606
628
|
...defaultNativeTheme,
|
|
607
629
|
...theme,
|
|
@@ -627,6 +649,82 @@ const styles = StyleSheet.create({
|
|
|
627
649
|
gap: 4,
|
|
628
650
|
padding: 16,
|
|
629
651
|
},
|
|
652
|
+
viewerListRow: {
|
|
653
|
+
alignItems: "flex-start",
|
|
654
|
+
flexDirection: "row",
|
|
655
|
+
gap: 8,
|
|
656
|
+
marginBottom: 6,
|
|
657
|
+
},
|
|
658
|
+
viewerListControl: {
|
|
659
|
+
alignItems: "center",
|
|
660
|
+
height: 24,
|
|
661
|
+
justifyContent: "center",
|
|
662
|
+
width: 20,
|
|
663
|
+
},
|
|
664
|
+
viewerListContent: {
|
|
665
|
+
flex: 1,
|
|
666
|
+
},
|
|
667
|
+
viewerToggleCaret: {
|
|
668
|
+
borderBottomColor: "transparent",
|
|
669
|
+
borderBottomWidth: 4,
|
|
670
|
+
borderLeftWidth: 6,
|
|
671
|
+
borderTopColor: "transparent",
|
|
672
|
+
borderTopWidth: 4,
|
|
673
|
+
height: 0,
|
|
674
|
+
transform: [{ rotate: "0deg" }],
|
|
675
|
+
width: 0,
|
|
676
|
+
},
|
|
677
|
+
viewerToggleCaretOpen: {
|
|
678
|
+
transform: [{ rotate: "90deg" }],
|
|
679
|
+
},
|
|
680
|
+
viewerTaskCheckbox: {
|
|
681
|
+
alignItems: "center",
|
|
682
|
+
borderRadius: 4,
|
|
683
|
+
height: 20,
|
|
684
|
+
justifyContent: "center",
|
|
685
|
+
marginTop: 2,
|
|
686
|
+
width: 20,
|
|
687
|
+
},
|
|
688
|
+
viewerTaskCheck: {
|
|
689
|
+
fontSize: 13,
|
|
690
|
+
fontWeight: "800",
|
|
691
|
+
lineHeight: 16,
|
|
692
|
+
},
|
|
693
|
+
viewerPageIcon: {
|
|
694
|
+
fontSize: 18,
|
|
695
|
+
lineHeight: 24,
|
|
696
|
+
textAlign: "center",
|
|
697
|
+
width: 24,
|
|
698
|
+
},
|
|
699
|
+
viewerPageTitle: {
|
|
700
|
+
flex: 1,
|
|
701
|
+
fontWeight: "600",
|
|
702
|
+
lineHeight: 24,
|
|
703
|
+
textDecorationLine: "underline",
|
|
704
|
+
},
|
|
705
|
+
viewerAttachment: {
|
|
706
|
+
alignItems: "center",
|
|
707
|
+
borderRadius: 12,
|
|
708
|
+
borderWidth: 1,
|
|
709
|
+
flexDirection: "row",
|
|
710
|
+
gap: 12,
|
|
711
|
+
marginBottom: 8,
|
|
712
|
+
minHeight: 68,
|
|
713
|
+
paddingHorizontal: 12,
|
|
714
|
+
paddingVertical: 10,
|
|
715
|
+
},
|
|
716
|
+
viewerAttachmentIcon: {
|
|
717
|
+
alignItems: "center",
|
|
718
|
+
borderRadius: 9,
|
|
719
|
+
height: 48,
|
|
720
|
+
justifyContent: "center",
|
|
721
|
+
width: 42,
|
|
722
|
+
},
|
|
723
|
+
viewerAttachmentType: {
|
|
724
|
+
fontSize: 9,
|
|
725
|
+
fontWeight: "800",
|
|
726
|
+
letterSpacing: 0.4,
|
|
727
|
+
},
|
|
630
728
|
nativeEditor: {
|
|
631
729
|
minHeight: 320,
|
|
632
730
|
},
|
package/dist/toolbar.d.ts
CHANGED
|
@@ -24,6 +24,6 @@ export declare const createOpenEditorNativeToolbarItems: ({ canUndo, canRedo, }?
|
|
|
24
24
|
export declare const openEditorNativeToolbarItems: readonly EditorToolbarItem[];
|
|
25
25
|
export declare const nativeToolbarParityCoverage: () => {
|
|
26
26
|
missingBlockControls: string[];
|
|
27
|
-
missingMarkControls: ("bold" | "
|
|
27
|
+
missingMarkControls: ("bold" | "link" | "strike" | "italic" | "underline" | "code")[];
|
|
28
28
|
};
|
|
29
29
|
export declare const defaultDocumentForToolbarAction: (key: string) => ProseMirrorDocument | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openeditor/native",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.26",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"repository": {
|
|
@@ -26,15 +26,18 @@
|
|
|
26
26
|
"./package.json": "./package.json"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
+
"@expo/ui": "~56.0.18",
|
|
29
30
|
"@expo/vector-icons": "15.0.3",
|
|
30
|
-
"@openeditor/core": "0.0.
|
|
31
|
-
"@openeditor/exporters": "0.0.
|
|
32
|
-
"@openeditor/extensions": "0.0.
|
|
31
|
+
"@openeditor/core": "0.0.26",
|
|
32
|
+
"@openeditor/exporters": "0.0.26",
|
|
33
|
+
"@openeditor/extensions": "0.0.26",
|
|
34
|
+
"emojibase": "17.0.0",
|
|
35
|
+
"emojibase-data": "16.0.2",
|
|
33
36
|
"react": "19.2.3",
|
|
34
37
|
"react-native": "0.85.3"
|
|
35
38
|
},
|
|
36
39
|
"peerDependencies": {
|
|
37
|
-
"@openeditor/react-native-prose-editor": "0.0.
|
|
40
|
+
"@openeditor/react-native-prose-editor": "0.0.26",
|
|
38
41
|
"react-native-keyboard-controller": ">=1.21",
|
|
39
42
|
"react": ">=19",
|
|
40
43
|
"react-native": ">=0.85"
|