@openeditor/native 0.0.34 → 0.0.35
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 +46 -5
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/native-editor.d.ts +15 -10
- package/dist/native-editor.js +70 -27
- package/dist/native-layout.d.ts +20 -0
- package/dist/native-layout.js +15 -0
- package/dist/native-provider.d.ts +9 -0
- package/dist/native-provider.js +9 -0
- package/dist/theme.d.ts +32 -0
- package/dist/theme.js +58 -0
- package/dist/toolbar-host.d.ts +4 -1
- package/dist/toolbar-host.js +97 -92
- package/dist/toolbar-items.d.ts +24 -3
- package/dist/toolbar-items.js +51 -46
- package/dist/toolbar-surface.d.ts +17 -0
- package/dist/toolbar-surface.js +33 -0
- package/package.json +16 -5
- package/plugin.cjs +79 -0
package/README.md
CHANGED
|
@@ -9,9 +9,10 @@ lightweight state/revision events; document JSON only crosses it when the host
|
|
|
9
9
|
explicitly calls `getDocument()` or `flushDocument()`.
|
|
10
10
|
|
|
11
11
|
On iOS, the host hides WKWebView's default previous/next/Done accessory bar so
|
|
12
|
-
the OpenEditor toolbar is the only bar above the software keyboard. The
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
the OpenEditor toolbar is the only bar above the software keyboard. The SDK
|
|
13
|
+
sticks that toolbar to the keyboard, reserves its document inset, and enables
|
|
14
|
+
interactive keyboard dismissal while the editor scrolls. Consumers can opt
|
|
15
|
+
back into the system bar with
|
|
15
16
|
`webViewProps={{ hideKeyboardAccessoryView: false }}`.
|
|
16
17
|
|
|
17
18
|
The `theme` prop accepts the same OpenEditor theme-token contract used by the
|
|
@@ -19,8 +20,36 @@ web React surface.
|
|
|
19
20
|
|
|
20
21
|
## Host integration
|
|
21
22
|
|
|
22
|
-
Install the
|
|
23
|
-
|
|
23
|
+
Install the native peer dependencies:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
pnpm add expo-glass-effect react-native-keyboard-controller react-native-reanimated react-native-svg react-native-webview
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
For Expo, install the SDK plugin so iOS WebViews receive interactive keyboard
|
|
30
|
+
dismissal:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"expo": {
|
|
35
|
+
"plugins": ["@openeditor/native/plugin"]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Rebuild the native application after adding the plugin or a native dependency.
|
|
41
|
+
Mount the SDK provider once at the app root:
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { OpenEditorNativeProvider } from "@openeditor/native";
|
|
45
|
+
|
|
46
|
+
export default function RootLayout() {
|
|
47
|
+
return <OpenEditorNativeProvider>{/* app routes */}</OpenEditorNativeProvider>;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The self-contained offline runtime is packaged with the component, so Metro
|
|
52
|
+
asset customization is not required:
|
|
24
53
|
|
|
25
54
|
```tsx
|
|
26
55
|
const editor = useRef<OpenEditorNativeController>(null);
|
|
@@ -46,6 +75,18 @@ const editor = useRef<OpenEditorNativeController>(null);
|
|
|
46
75
|
/>
|
|
47
76
|
```
|
|
48
77
|
|
|
78
|
+
The default toolbar is a keyboard-sticky, inset pill that meets the keyboard
|
|
79
|
+
edge without a synthetic spacer. It uses native iOS glass and a theme-aware
|
|
80
|
+
material fallback elsewhere. A custom `renderToolbar` returns only the toolbar
|
|
81
|
+
content; the SDK still owns its glass surface and positioning, so do not add
|
|
82
|
+
another `KeyboardStickyView` or outer background. Use `toolbarContentInset`
|
|
83
|
+
when the custom toolbar needs more than the default 76-point document clearance,
|
|
84
|
+
and `toolbarOffset` only when the keyboard edge itself needs a different gap.
|
|
85
|
+
|
|
86
|
+
The editor host must reach the screen's bottom edge. Do not wrap the editor in
|
|
87
|
+
a bottom-padding `SafeAreaView`; the keyboard owns that edge while editing, and
|
|
88
|
+
reserving the home-indicator inset there creates a visible toolbar gap.
|
|
89
|
+
|
|
49
90
|
The block picker derives its available page, image, and attachment commands
|
|
50
91
|
from the supplied effect handlers. If the application cannot upload media to a
|
|
51
92
|
durable URL, omit the corresponding picker effect rather than returning a
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { OpenEditorNative, type OpenEditorNativeContentInsets, type OpenEditorNativeProps, type OpenEditorNativeTheme, } from "./native-editor.js";
|
|
2
|
+
export { OpenEditorNativeProvider, type OpenEditorNativeProviderProps, } from "./native-provider.js";
|
|
2
3
|
export { type OpenEditorNativeController, type OpenEditorNativeControllerEvent, type OpenEditorNativeEffectHandlers, } from "./controller.js";
|
|
3
4
|
export { OpenEditorNativeToolbar, defaultOpenEditorNativeToolbarItems, openEditorNativeBlockPickerItems, type OpenEditorNativeToolbarItem, type OpenEditorNativeToolbarProps, } from "./toolbar-host.js";
|
|
5
|
+
export { resolveOpenEditorNativeTheme, type OpenEditorNativeColorScheme, type ResolvedOpenEditorNativeTheme, } from "./theme.js";
|
|
4
6
|
export type { OpenEditorRuntimeCommand, OpenEditorRuntimeState } from "@openeditor/embedded-runtime";
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { OpenEditorNative, } from "./native-editor.js";
|
|
2
|
+
export { OpenEditorNativeProvider, } from "./native-provider.js";
|
|
2
3
|
export { OpenEditorNativeToolbar, defaultOpenEditorNativeToolbarItems, openEditorNativeBlockPickerItems, } from "./toolbar-host.js";
|
|
4
|
+
export { resolveOpenEditorNativeTheme, } from "./theme.js";
|
package/dist/native-editor.d.ts
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
import type { OpenEditorDocument } from "@openeditor/core";
|
|
1
|
+
import type { OpenEditorDocument, OpenEditorPageSnapshot } from "@openeditor/core";
|
|
2
|
+
import type { GlassColorScheme } from "expo-glass-effect";
|
|
2
3
|
import { type OpenEditorRuntimeState, type OpenEditorNativeEffectName, type OpenEditorTheme } from "@openeditor/embedded-runtime";
|
|
3
4
|
import { type ReactNode, type RefAttributes } from "react";
|
|
4
5
|
import { type StyleProp, type ViewStyle } from "react-native";
|
|
5
6
|
import { type WebViewProps } from "react-native-webview";
|
|
6
7
|
import { type OpenEditorNativeController, type OpenEditorNativeEffectHandlers } from "./controller.js";
|
|
7
8
|
import { type OpenEditorNativeToolbarItem } from "./toolbar-host.js";
|
|
9
|
+
import { type OpenEditorNativeContentInsets } from "./native-layout.js";
|
|
10
|
+
export type { OpenEditorNativeContentInsets } from "./native-layout.js";
|
|
8
11
|
export type OpenEditorNativeTheme = Partial<OpenEditorTheme>;
|
|
9
|
-
export type OpenEditorNativeContentInsets = {
|
|
10
|
-
top: number;
|
|
11
|
-
right: number;
|
|
12
|
-
bottom: number;
|
|
13
|
-
left: number;
|
|
14
|
-
};
|
|
15
12
|
type EdgeInsets = OpenEditorNativeContentInsets;
|
|
16
13
|
type OwnedWebViewProps = "source" | "onMessage" | "style" | "scrollEnabled" | "nestedScrollEnabled";
|
|
17
14
|
export type OpenEditorNativeProps = {
|
|
18
15
|
initialDocument: OpenEditorDocument;
|
|
16
|
+
/** Page metadata rendered inside the shared, scroll-owned document surface. */
|
|
17
|
+
page?: OpenEditorPageSnapshot;
|
|
19
18
|
editable?: boolean;
|
|
20
19
|
placeholder?: string;
|
|
21
20
|
theme?: OpenEditorNativeTheme;
|
|
@@ -24,7 +23,9 @@ export type OpenEditorNativeProps = {
|
|
|
24
23
|
toolbarItems?: readonly OpenEditorNativeToolbarItem[];
|
|
25
24
|
renderToolbar?: (context: {
|
|
26
25
|
availableNativeEffects: readonly OpenEditorNativeEffectName[];
|
|
26
|
+
blockHandlesVisible: boolean;
|
|
27
27
|
controller: OpenEditorNativeController;
|
|
28
|
+
setBlockHandlesVisible: (visible: boolean) => void;
|
|
28
29
|
state: OpenEditorRuntimeState;
|
|
29
30
|
}) => ReactNode;
|
|
30
31
|
nativeEffects?: OpenEditorNativeEffectHandlers;
|
|
@@ -34,10 +35,14 @@ export type OpenEditorNativeProps = {
|
|
|
34
35
|
}) => void;
|
|
35
36
|
onReady?: (controller: OpenEditorNativeController) => void;
|
|
36
37
|
onError?: (error: Error) => void;
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
toolbarContentInset?: number;
|
|
39
|
+
toolbarOffset?: {
|
|
40
|
+
closed?: number;
|
|
41
|
+
opened?: number;
|
|
42
|
+
};
|
|
43
|
+
toolbarColorScheme?: GlassColorScheme;
|
|
44
|
+
toolbarContainerStyle?: StyleProp<ViewStyle>;
|
|
39
45
|
style?: StyleProp<ViewStyle>;
|
|
40
46
|
webViewProps?: Omit<WebViewProps, OwnedWebViewProps>;
|
|
41
47
|
};
|
|
42
48
|
export declare const OpenEditorNative: import("react").ForwardRefExoticComponent<OpenEditorNativeProps & RefAttributes<OpenEditorNativeController>>;
|
|
43
|
-
export {};
|
package/dist/native-editor.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { getOpenEditorThemeEntries, openEditorThemeCssName, openEditorThemeTokenNames, } from "@openeditor/embedded-runtime";
|
|
3
2
|
import { openEditorEmbeddedSurfaceHtml } from "@openeditor/embedded-surface/html";
|
|
4
3
|
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from "react";
|
|
5
|
-
import { Keyboard,
|
|
4
|
+
import { Keyboard, Platform, StyleSheet, useColorScheme, View, } from "react-native";
|
|
5
|
+
import { KeyboardStickyView } from "react-native-keyboard-controller";
|
|
6
6
|
import { WebView, } from "react-native-webview";
|
|
7
7
|
import { emptyOpenEditorNativeState, isOpenEditorNativeLifecycleCancellation, OpenEditorNativeBridge, } from "./controller.js";
|
|
8
8
|
import { OpenEditorNativeToolbar, } from "./toolbar-host.js";
|
|
9
|
-
|
|
9
|
+
import { OpenEditorNativeToolbarSurface } from "./toolbar-surface.js";
|
|
10
|
+
import { OPENEDITOR_NATIVE_TOOLBAR_OFFSET, OPENEDITOR_NATIVE_TOOLBAR_CONTENT_INSET, OPENEDITOR_NATIVE_TOOLBAR_HEIGHT, OPENEDITOR_NATIVE_TOOLBAR_RADIUS, resolveOpenEditorNativeContentInsets, } from "./native-layout.js";
|
|
11
|
+
import { resolveOpenEditorNativeTheme } from "./theme.js";
|
|
12
|
+
const DEFAULT_INSETS = { top: 0, right: 16, bottom: 0, left: 16 };
|
|
10
13
|
const HostWebView = WebView;
|
|
11
|
-
export const OpenEditorNative = forwardRef(function OpenEditorNative({ initialDocument, editable = true, placeholder = "Start writing…", theme, contentInsets =
|
|
14
|
+
export const OpenEditorNative = forwardRef(function OpenEditorNative({ initialDocument, page, editable = true, placeholder = "Start writing…", theme, contentInsets = DEFAULT_INSETS, showToolbar = true, toolbarItems, renderToolbar, nativeEffects, onDocumentChanged, onReady, onError, toolbarContentInset = OPENEDITOR_NATIVE_TOOLBAR_CONTENT_INSET, toolbarOffset = OPENEDITOR_NATIVE_TOOLBAR_OFFSET, toolbarColorScheme, toolbarContainerStyle, style, webViewProps, }, forwardedRef) {
|
|
15
|
+
const systemColorScheme = useColorScheme();
|
|
16
|
+
const resolvedColorScheme = systemColorScheme === "dark" ? "dark" : "light";
|
|
17
|
+
const resolvedTheme = useMemo(() => resolveOpenEditorNativeTheme(theme, resolvedColorScheme), [resolvedColorScheme, theme]);
|
|
18
|
+
const resolvedToolbarColorScheme = toolbarColorScheme ?? resolvedColorScheme;
|
|
12
19
|
const webViewRef = useRef(null);
|
|
13
20
|
const sessionIdRef = useRef(`native_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`);
|
|
14
21
|
const callbacksRef = useRef({
|
|
@@ -19,9 +26,11 @@ export const OpenEditorNative = forwardRef(function OpenEditorNative({ initialDo
|
|
|
19
26
|
});
|
|
20
27
|
callbacksRef.current = { nativeEffects, onDocumentChanged, onError, onReady };
|
|
21
28
|
const initialDocumentRef = useRef(initialDocument);
|
|
22
|
-
const initialConfigRef = useRef({ editable, placeholder, theme });
|
|
29
|
+
const initialConfigRef = useRef({ editable, placeholder, theme: resolvedTheme });
|
|
23
30
|
const initializedRef = useRef(false);
|
|
31
|
+
const bridgeLifetimeRef = useRef(0);
|
|
24
32
|
const [editorState, setEditorState] = useState(emptyOpenEditorNativeState);
|
|
33
|
+
const [blockHandlesVisible, setBlockHandlesVisible] = useState(false);
|
|
25
34
|
const [keyboardVisible, setKeyboardVisible] = useState(() => Keyboard.isVisible?.() ?? false);
|
|
26
35
|
const [bridge] = useState(() => new OpenEditorNativeBridge({
|
|
27
36
|
sessionId: sessionIdRef.current,
|
|
@@ -37,22 +46,30 @@ export const OpenEditorNative = forwardRef(function OpenEditorNative({ initialDo
|
|
|
37
46
|
baseUrl: "https://openeditor.local/",
|
|
38
47
|
}), []);
|
|
39
48
|
useImperativeHandle(forwardedRef, () => bridge, [bridge]);
|
|
40
|
-
const resolvedInsets = {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
};
|
|
49
|
+
const resolvedInsets = resolveOpenEditorNativeContentInsets({
|
|
50
|
+
contentInsets,
|
|
51
|
+
editable,
|
|
52
|
+
showToolbar,
|
|
53
|
+
toolbarContentInset,
|
|
54
|
+
});
|
|
46
55
|
const availableNativeEffects = Object.keys(nativeEffects ?? {});
|
|
47
56
|
const hostConfig = JSON.stringify({
|
|
48
57
|
availableNativeEffects,
|
|
58
|
+
blockHandles: editable && blockHandlesVisible,
|
|
49
59
|
contentInsets: resolvedInsets,
|
|
60
|
+
page,
|
|
50
61
|
placeholder,
|
|
51
62
|
sessionId: sessionIdRef.current,
|
|
52
|
-
theme,
|
|
63
|
+
theme: resolvedTheme,
|
|
53
64
|
});
|
|
54
65
|
const injectedConfig = `window.__OPENEDITOR_NATIVE_HOST__=${hostConfig};true;`;
|
|
55
|
-
const
|
|
66
|
+
const hostConfigUpdateScript = `window.dispatchEvent(new CustomEvent("openeditor:host-config",{detail:${JSON.stringify({
|
|
67
|
+
blockHandles: editable && blockHandlesVisible,
|
|
68
|
+
contentInsets: resolvedInsets,
|
|
69
|
+
page,
|
|
70
|
+
placeholder,
|
|
71
|
+
theme: resolvedTheme,
|
|
72
|
+
})}}));true;`;
|
|
56
73
|
const initializeRuntime = useCallback(() => {
|
|
57
74
|
if (!bridge.runtimeReady || initializedRef.current)
|
|
58
75
|
return;
|
|
@@ -79,13 +96,11 @@ export const OpenEditorNative = forwardRef(function OpenEditorNative({ initialDo
|
|
|
79
96
|
});
|
|
80
97
|
}, [bridge]);
|
|
81
98
|
useEffect(() => {
|
|
82
|
-
webViewRef.current?.injectJavaScript(
|
|
83
|
-
}, [
|
|
99
|
+
webViewRef.current?.injectJavaScript(hostConfigUpdateScript);
|
|
100
|
+
}, [hostConfigUpdateScript]);
|
|
84
101
|
useEffect(() => {
|
|
85
|
-
const showSubscription = Keyboard.addListener("keyboardDidShow", () => setKeyboardVisible(true));
|
|
86
|
-
const hideSubscription = Keyboard.addListener("keyboardDidHide", () =>
|
|
87
|
-
setKeyboardVisible(false);
|
|
88
|
-
});
|
|
102
|
+
const showSubscription = Keyboard.addListener(Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow", () => setKeyboardVisible(true));
|
|
103
|
+
const hideSubscription = Keyboard.addListener(Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide", () => setKeyboardVisible(false));
|
|
89
104
|
return () => {
|
|
90
105
|
showSubscription.remove();
|
|
91
106
|
hideSubscription.remove();
|
|
@@ -114,11 +129,17 @@ export const OpenEditorNative = forwardRef(function OpenEditorNative({ initialDo
|
|
|
114
129
|
callbacksRef.current.onError?.(new Error(`${event.code}: ${event.message}`));
|
|
115
130
|
}
|
|
116
131
|
});
|
|
132
|
+
return unsubscribe;
|
|
133
|
+
}, [bridge, initializeRuntime]);
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
const lifetime = ++bridgeLifetimeRef.current;
|
|
117
136
|
return () => {
|
|
118
|
-
|
|
119
|
-
|
|
137
|
+
queueMicrotask(() => {
|
|
138
|
+
if (bridgeLifetimeRef.current === lifetime)
|
|
139
|
+
bridge.dispose();
|
|
140
|
+
});
|
|
120
141
|
};
|
|
121
|
-
}, [bridge
|
|
142
|
+
}, [bridge]);
|
|
122
143
|
useEffect(() => {
|
|
123
144
|
let active = true;
|
|
124
145
|
void bridge
|
|
@@ -140,11 +161,18 @@ export const OpenEditorNative = forwardRef(function OpenEditorNative({ initialDo
|
|
|
140
161
|
}
|
|
141
162
|
initializeRuntime();
|
|
142
163
|
};
|
|
164
|
+
const toolbarEnabled = showToolbar && editable;
|
|
143
165
|
const shouldRenderToolbar = showToolbar && editable && editorState.focused && keyboardVisible;
|
|
144
|
-
const toolbar = renderToolbar && shouldRenderToolbar ? (renderToolbar({
|
|
145
|
-
|
|
166
|
+
const toolbar = renderToolbar && shouldRenderToolbar ? (renderToolbar({
|
|
167
|
+
availableNativeEffects,
|
|
168
|
+
blockHandlesVisible,
|
|
169
|
+
controller: bridge,
|
|
170
|
+
setBlockHandlesVisible,
|
|
171
|
+
state: editorState,
|
|
172
|
+
})) : !renderToolbar && toolbarEnabled ? (_jsx(OpenEditorNativeToolbar, { availableNativeEffects: availableNativeEffects, blockHandlesVisible: blockHandlesVisible, controller: bridge, items: toolbarItems, onBlockHandlesVisibleChange: setBlockHandlesVisible, onCommandError: (error) => callbacksRef.current.onError?.(error), state: editorState, style: styles.toolbarContent, theme: resolvedTheme, visible: shouldRenderToolbar })) : null;
|
|
173
|
+
return (_jsxs(View, { style: [
|
|
146
174
|
styles.host,
|
|
147
|
-
{ backgroundColor:
|
|
175
|
+
{ backgroundColor: resolvedTheme.surface },
|
|
148
176
|
style,
|
|
149
177
|
], children: [_jsx(View, { style: styles.webViewContainer, children: _jsx(HostWebView, { ...webViewProps, allowsBackForwardNavigationGestures: false, bounces: webViewProps?.bounces ?? true, injectedJavaScriptBeforeContentLoaded: injectedConfig, javaScriptEnabled: true, hideKeyboardAccessoryView: webViewProps?.hideKeyboardAccessoryView ?? true, nestedScrollEnabled: true, onLoadStart: (event) => {
|
|
150
178
|
initializedRef.current = false;
|
|
@@ -160,12 +188,27 @@ export const OpenEditorNative = forwardRef(function OpenEditorNative({ initialDo
|
|
|
160
188
|
], ref: webViewRef, scrollEnabled: true, source: runtimeSource, style: [
|
|
161
189
|
styles.webView,
|
|
162
190
|
{
|
|
163
|
-
backgroundColor:
|
|
191
|
+
backgroundColor: resolvedTheme.surface,
|
|
164
192
|
},
|
|
165
|
-
] }) }), toolbar] }));
|
|
193
|
+
] }) }), toolbarEnabled ? (_jsx(KeyboardStickyView, { offset: toolbarOffset, pointerEvents: "box-none", style: [styles.toolbarContainer, toolbarContainerStyle], children: _jsx(OpenEditorNativeToolbarSurface, { active: shouldRenderToolbar, colorScheme: resolvedToolbarColorScheme, theme: resolvedTheme, children: toolbar }) })) : null] }));
|
|
166
194
|
});
|
|
167
195
|
const styles = StyleSheet.create({
|
|
168
196
|
host: { flex: 1, minHeight: 0 },
|
|
169
197
|
webViewContainer: { flex: 1, minHeight: 0, overflow: "hidden" },
|
|
170
198
|
webView: { flex: 1 },
|
|
199
|
+
toolbarContainer: {
|
|
200
|
+
bottom: 0,
|
|
201
|
+
borderRadius: OPENEDITOR_NATIVE_TOOLBAR_RADIUS,
|
|
202
|
+
height: OPENEDITOR_NATIVE_TOOLBAR_HEIGHT,
|
|
203
|
+
left: 12,
|
|
204
|
+
overflow: "hidden",
|
|
205
|
+
position: "absolute",
|
|
206
|
+
right: 12,
|
|
207
|
+
zIndex: 20,
|
|
208
|
+
},
|
|
209
|
+
toolbarContent: {
|
|
210
|
+
backgroundColor: "transparent",
|
|
211
|
+
borderWidth: 0,
|
|
212
|
+
minHeight: OPENEDITOR_NATIVE_TOOLBAR_HEIGHT,
|
|
213
|
+
},
|
|
171
214
|
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type OpenEditorNativeContentInsets = {
|
|
2
|
+
top: number;
|
|
3
|
+
right: number;
|
|
4
|
+
bottom: number;
|
|
5
|
+
left: number;
|
|
6
|
+
};
|
|
7
|
+
export declare const OPENEDITOR_NATIVE_TOOLBAR_HEIGHT = 46;
|
|
8
|
+
export declare const OPENEDITOR_NATIVE_TOOLBAR_RADIUS: number;
|
|
9
|
+
export declare const OPENEDITOR_NATIVE_TOOLBAR_CONTENT_INSET = 70;
|
|
10
|
+
export declare const OPENEDITOR_NATIVE_TOOLBAR_OFFSET: {
|
|
11
|
+
readonly closed: 0;
|
|
12
|
+
/** Keep the intentional breathing room above the software keyboard. */
|
|
13
|
+
readonly opened: -8;
|
|
14
|
+
};
|
|
15
|
+
export declare const resolveOpenEditorNativeContentInsets: ({ contentInsets, editable, showToolbar, toolbarContentInset, }: {
|
|
16
|
+
contentInsets: Partial<OpenEditorNativeContentInsets>;
|
|
17
|
+
editable: boolean;
|
|
18
|
+
showToolbar: boolean;
|
|
19
|
+
toolbarContentInset: number;
|
|
20
|
+
}) => OpenEditorNativeContentInsets;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const OPENEDITOR_NATIVE_TOOLBAR_HEIGHT = 46;
|
|
2
|
+
export const OPENEDITOR_NATIVE_TOOLBAR_RADIUS = OPENEDITOR_NATIVE_TOOLBAR_HEIGHT / 2;
|
|
3
|
+
export const OPENEDITOR_NATIVE_TOOLBAR_CONTENT_INSET = 70;
|
|
4
|
+
export const OPENEDITOR_NATIVE_TOOLBAR_OFFSET = {
|
|
5
|
+
closed: 0,
|
|
6
|
+
/** Keep the intentional breathing room above the software keyboard. */
|
|
7
|
+
opened: -8,
|
|
8
|
+
};
|
|
9
|
+
export const resolveOpenEditorNativeContentInsets = ({ contentInsets, editable, showToolbar, toolbarContentInset, }) => ({
|
|
10
|
+
top: contentInsets.top ?? 0,
|
|
11
|
+
right: contentInsets.right ?? 0,
|
|
12
|
+
bottom: (contentInsets.bottom ?? 0) +
|
|
13
|
+
(showToolbar && editable ? toolbarContentInset : 0),
|
|
14
|
+
left: contentInsets.left ?? 0,
|
|
15
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
export type OpenEditorNativeProviderProps = {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Owns the native keyboard animation context used by OpenEditor's toolbar.
|
|
7
|
+
* Mount once above every OpenEditorNative instance, normally at the app root.
|
|
8
|
+
*/
|
|
9
|
+
export declare function OpenEditorNativeProvider({ children }: OpenEditorNativeProviderProps): ReactElement;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { KeyboardProvider } from "react-native-keyboard-controller";
|
|
3
|
+
/**
|
|
4
|
+
* Owns the native keyboard animation context used by OpenEditor's toolbar.
|
|
5
|
+
* Mount once above every OpenEditorNative instance, normally at the app root.
|
|
6
|
+
*/
|
|
7
|
+
export function OpenEditorNativeProvider({ children }) {
|
|
8
|
+
return _jsx(KeyboardProvider, { children: children });
|
|
9
|
+
}
|
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { OpenEditorTheme } from "@openeditor/embedded-runtime";
|
|
2
|
+
export type OpenEditorNativeColorScheme = "light" | "dark";
|
|
3
|
+
declare const LIGHT_THEME: {
|
|
4
|
+
surface: string;
|
|
5
|
+
surfaceRaised: string;
|
|
6
|
+
surfaceMuted: string;
|
|
7
|
+
interactionHover: string;
|
|
8
|
+
interactionSelected: string;
|
|
9
|
+
blockSurface: string;
|
|
10
|
+
text: string;
|
|
11
|
+
textSoft: string;
|
|
12
|
+
heading: string;
|
|
13
|
+
muted: string;
|
|
14
|
+
placeholder: string;
|
|
15
|
+
border: string;
|
|
16
|
+
borderStrong: string;
|
|
17
|
+
structuralLine: string;
|
|
18
|
+
accent: string;
|
|
19
|
+
accentText: string;
|
|
20
|
+
accentStrong: string;
|
|
21
|
+
buttonBackground: string;
|
|
22
|
+
buttonText: string;
|
|
23
|
+
codeBackground: string;
|
|
24
|
+
codeText: string;
|
|
25
|
+
link: string;
|
|
26
|
+
linkHover: string;
|
|
27
|
+
shadow: string;
|
|
28
|
+
fontSans: string;
|
|
29
|
+
};
|
|
30
|
+
export type ResolvedOpenEditorNativeTheme = typeof LIGHT_THEME;
|
|
31
|
+
export declare const resolveOpenEditorNativeTheme: (theme: Partial<OpenEditorTheme> | undefined, colorScheme: OpenEditorNativeColorScheme) => ResolvedOpenEditorNativeTheme;
|
|
32
|
+
export {};
|
package/dist/theme.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const LIGHT_THEME = {
|
|
2
|
+
surface: "#ffffff",
|
|
3
|
+
surfaceRaised: "#ffffff",
|
|
4
|
+
surfaceMuted: "#f5f5f5",
|
|
5
|
+
interactionHover: "#f5f5f5",
|
|
6
|
+
interactionSelected: "#e5e5e5",
|
|
7
|
+
blockSurface: "#fafafa",
|
|
8
|
+
text: "#171717",
|
|
9
|
+
textSoft: "#404040",
|
|
10
|
+
heading: "#0a0a0a",
|
|
11
|
+
muted: "#737373",
|
|
12
|
+
placeholder: "#a3a3a3",
|
|
13
|
+
border: "#e5e5e5",
|
|
14
|
+
borderStrong: "#d4d4d4",
|
|
15
|
+
structuralLine: "#e5e5e5",
|
|
16
|
+
accent: "#171717",
|
|
17
|
+
accentText: "#ffffff",
|
|
18
|
+
accentStrong: "#0a0a0a",
|
|
19
|
+
buttonBackground: "#ffffff",
|
|
20
|
+
buttonText: "#171717",
|
|
21
|
+
codeBackground: "#f5f5f5",
|
|
22
|
+
codeText: "#262626",
|
|
23
|
+
link: "#1d4ed8",
|
|
24
|
+
linkHover: "#1e40af",
|
|
25
|
+
shadow: "rgba(0,0,0,0.18)",
|
|
26
|
+
fontSans: '-apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif',
|
|
27
|
+
};
|
|
28
|
+
const DARK_THEME = {
|
|
29
|
+
surface: "#111111",
|
|
30
|
+
surfaceRaised: "#1c1c1e",
|
|
31
|
+
surfaceMuted: "#27272a",
|
|
32
|
+
interactionHover: "#333336",
|
|
33
|
+
interactionSelected: "#3a3a3c",
|
|
34
|
+
blockSurface: "#1c1c1e",
|
|
35
|
+
text: "#f5f5f5",
|
|
36
|
+
textSoft: "#d4d4d4",
|
|
37
|
+
heading: "#fafafa",
|
|
38
|
+
muted: "#a3a3a3",
|
|
39
|
+
placeholder: "#737373",
|
|
40
|
+
border: "#333333",
|
|
41
|
+
borderStrong: "#525252",
|
|
42
|
+
structuralLine: "#333333",
|
|
43
|
+
accent: "#f5f5f5",
|
|
44
|
+
accentText: "#111111",
|
|
45
|
+
accentStrong: "#ffffff",
|
|
46
|
+
buttonBackground: "#27272a",
|
|
47
|
+
buttonText: "#f5f5f5",
|
|
48
|
+
codeBackground: "#1f1f1f",
|
|
49
|
+
codeText: "#e5e5e5",
|
|
50
|
+
link: "#60a5fa",
|
|
51
|
+
linkHover: "#93c5fd",
|
|
52
|
+
shadow: "rgba(0,0,0,0.60)",
|
|
53
|
+
fontSans: '-apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif',
|
|
54
|
+
};
|
|
55
|
+
export const resolveOpenEditorNativeTheme = (theme, colorScheme) => ({
|
|
56
|
+
...(colorScheme === "dark" ? DARK_THEME : LIGHT_THEME),
|
|
57
|
+
...theme,
|
|
58
|
+
});
|
package/dist/toolbar-host.d.ts
CHANGED
|
@@ -13,5 +13,8 @@ export type OpenEditorNativeToolbarProps = {
|
|
|
13
13
|
theme?: OpenEditorNativeTheme;
|
|
14
14
|
style?: StyleProp<ViewStyle>;
|
|
15
15
|
onCommandError?: (error: Error) => void;
|
|
16
|
+
visible?: boolean;
|
|
17
|
+
blockHandlesVisible?: boolean;
|
|
18
|
+
onBlockHandlesVisibleChange?: (visible: boolean) => void;
|
|
16
19
|
};
|
|
17
|
-
export declare const OpenEditorNativeToolbar: import("react").MemoExoticComponent<({ availableNativeEffects, controller, state, items, theme, style, onCommandError, }: OpenEditorNativeToolbarProps) => import("react").JSX.Element>;
|
|
20
|
+
export declare const OpenEditorNativeToolbar: import("react").MemoExoticComponent<({ availableNativeEffects, controller, state, items, theme, style, onCommandError, visible, blockHandlesVisible, onBlockHandlesVisibleChange, }: OpenEditorNativeToolbarProps) => import("react").JSX.Element>;
|
package/dist/toolbar-host.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { BottomSheet as UniversalBottomSheet, Button as NativeButton, Column as NativeColumn, ListItem as NativeListItem, RNHostView as NativeRNHostView, Row as NativeRow, ScrollView as NativeScrollView, Spacer as NativeSpacer, Text as NativeText, } from "@expo/ui";
|
|
3
|
+
import { BottomSheet as SwiftUIBottomSheet, Group as SwiftUIGroup, Host as SwiftUIHost, } from "@expo/ui/swift-ui";
|
|
4
|
+
import { accessibilityLabel as nativeAccessibilityLabel, frame as nativeFrame, glassEffect, padding as nativePadding, presentationDetents, presentationDragIndicator, } from "@expo/ui/swift-ui/modifiers";
|
|
5
|
+
import { openEditorIcons } from "@openeditor/icons";
|
|
6
|
+
import { HugeiconsIcon } from "@hugeicons/react-native";
|
|
2
7
|
import { memo, useRef, useState } from "react";
|
|
3
|
-
import { Keyboard, Modal, Pressable, ScrollView, StyleSheet, Text, TextInput, View, } from "react-native";
|
|
8
|
+
import { Keyboard, Modal, Platform, Pressable, ScrollView, StyleSheet, Text, TextInput, View, } from "react-native";
|
|
4
9
|
import { defaultOpenEditorNativeToolbarItems, openEditorNativeBlockPickerItems, openEditorNativeTableToolbarItems, } from "./toolbar-items.js";
|
|
5
10
|
export { defaultOpenEditorNativeToolbarItems, openEditorNativeBlockPickerItems, } from "./toolbar-items.js";
|
|
6
11
|
const isItemActive = (item, state) => (item.activeWhen?.block !== undefined &&
|
|
@@ -8,11 +13,33 @@ const isItemActive = (item, state) => (item.activeWhen?.block !== undefined &&
|
|
|
8
13
|
(item.activeWhen?.headingLevel === undefined ||
|
|
9
14
|
state.headingLevel === item.activeWhen.headingLevel)) ||
|
|
10
15
|
(item.activeWhen?.mark !== undefined &&
|
|
11
|
-
state.activeMarks.includes(item.activeWhen.mark))
|
|
16
|
+
state.activeMarks.includes(item.activeWhen.mark)) ||
|
|
17
|
+
(item.activeWhen?.table !== undefined &&
|
|
18
|
+
state.table?.[item.activeWhen.table] === true);
|
|
12
19
|
const isItemDisabled = (item, state) => !state.editable ||
|
|
13
20
|
(item.disabledWhen === "cannotUndo" && !state.canUndo) ||
|
|
14
|
-
(item.disabledWhen === "cannotRedo" && !state.canRedo)
|
|
15
|
-
|
|
21
|
+
(item.disabledWhen === "cannotRedo" && !state.canRedo) ||
|
|
22
|
+
(item.disabledWhen === "cannotMergeCells" && !state.table?.canMergeCells);
|
|
23
|
+
const TOOLBAR_BUTTON_SIZE = 32;
|
|
24
|
+
const IOS_BLOCK_PICKER_PRESENTATION_MODIFIERS = [
|
|
25
|
+
nativeFrame({ alignment: "topLeading", maxWidth: Infinity }),
|
|
26
|
+
nativePadding({ leading: 16, top: 16, trailing: 16 }),
|
|
27
|
+
presentationDragIndicator("visible"),
|
|
28
|
+
presentationDetents(["medium", "large"]),
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* Expo's universal wrapper maps `onDismiss` to the binding-change event on
|
|
32
|
+
* iOS, losing SwiftUI's actual post-animation dismissal callback. Editor
|
|
33
|
+
* commands need that real lifecycle boundary so focus-owning native effects do
|
|
34
|
+
* not race the outgoing sheet.
|
|
35
|
+
*/
|
|
36
|
+
const NativeBlockPickerSheet = ({ children, isPresented, onDismiss, onIsPresentedChange, }) => {
|
|
37
|
+
if (Platform.OS !== "ios") {
|
|
38
|
+
return (_jsx(UniversalBottomSheet, { isPresented: isPresented, onDismiss: onDismiss, showDragIndicator: true, snapPoints: ["half", "full"], testID: "openeditor-block-picker", children: children }));
|
|
39
|
+
}
|
|
40
|
+
return (_jsx(SwiftUIHost, { pointerEvents: "none", style: { position: "absolute" }, children: _jsx(SwiftUIBottomSheet, { isPresented: isPresented, onDismiss: onDismiss, onIsPresentedChange: onIsPresentedChange, testID: "openeditor-block-picker", children: _jsx(SwiftUIGroup, { modifiers: IOS_BLOCK_PICKER_PRESENTATION_MODIFIERS, children: children }) }) }));
|
|
41
|
+
};
|
|
42
|
+
export const OpenEditorNativeToolbar = memo(function OpenEditorNativeToolbar({ availableNativeEffects = [], controller, state, items = defaultOpenEditorNativeToolbarItems, theme, style, onCommandError, visible = true, blockHandlesVisible = false, onBlockHandlesVisibleChange, }) {
|
|
16
43
|
const [blockPickerOpen, setBlockPickerOpen] = useState(false);
|
|
17
44
|
const [linkEditorOpen, setLinkEditorOpen] = useState(false);
|
|
18
45
|
const [linkValue, setLinkValue] = useState("");
|
|
@@ -42,34 +69,35 @@ export const OpenEditorNativeToolbar = memo(function OpenEditorNativeToolbar({ a
|
|
|
42
69
|
return availableNativeEffects.includes("createPage");
|
|
43
70
|
return true;
|
|
44
71
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
72
|
+
const blockPickerGroups = blockPickerItems.reduce((groups, item) => {
|
|
73
|
+
const current = groups.at(-1);
|
|
74
|
+
if (current?.label === item.group)
|
|
75
|
+
current.items.push(item);
|
|
76
|
+
else
|
|
77
|
+
groups.push({ label: item.group, items: [item] });
|
|
78
|
+
return groups;
|
|
79
|
+
}, []);
|
|
80
|
+
const visibleToolbarItems = state.activeNodes.includes("table")
|
|
81
|
+
? [
|
|
82
|
+
...defaultOpenEditorNativeToolbarItems.filter((item) => item.action === "toggleBlockHandles"),
|
|
83
|
+
...openEditorNativeTableToolbarItems.filter((item) => item.key === "table-split-cell"
|
|
84
|
+
? state.table?.canSplitCell
|
|
85
|
+
: item.key !== "table-merge-cells" || !state.table?.canSplitCell),
|
|
86
|
+
]
|
|
87
|
+
: items;
|
|
88
|
+
return (_jsxs(_Fragment, { children: [visible ? (_jsx(View, { style: [styles.root, style], children: _jsx(ScrollView, { contentContainerStyle: styles.content, horizontal: true, keyboardShouldPersistTaps: "always", showsHorizontalScrollIndicator: false, children: visibleToolbarItems.map((item) => {
|
|
89
|
+
const active = item.action === "toggleBlockHandles"
|
|
90
|
+
? blockHandlesVisible
|
|
91
|
+
: isItemActive(item, state);
|
|
51
92
|
const disabled = isItemDisabled(item, state);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
93
|
+
const accessibilityLabel = item.action === "toggleBlockHandles" && blockHandlesVisible
|
|
94
|
+
? "Hide block handles"
|
|
95
|
+
: item.label;
|
|
96
|
+
return (_jsx(Pressable, { accessibilityLabel: accessibilityLabel, accessibilityRole: "button", accessibilityState: { disabled, selected: active }, disabled: disabled, hitSlop: 6, onPress: () => {
|
|
97
|
+
if (item.action === "toggleBlockHandles") {
|
|
98
|
+
onBlockHandlesVisibleChange?.(!blockHandlesVisible);
|
|
56
99
|
return;
|
|
57
100
|
}
|
|
58
|
-
runCommand(controller.command(item.command));
|
|
59
|
-
}, style: ({ pressed }) => [
|
|
60
|
-
styles.button,
|
|
61
|
-
{ borderColor: colors.border },
|
|
62
|
-
pressed && styles.pressed,
|
|
63
|
-
disabled && styles.disabled,
|
|
64
|
-
], children: _jsx(Text, { style: [
|
|
65
|
-
styles.label,
|
|
66
|
-
{ color: disabled ? colors.muted : colors.text },
|
|
67
|
-
], children: item.label }) }, item.key));
|
|
68
|
-
})
|
|
69
|
-
: items.map((item) => {
|
|
70
|
-
const active = isItemActive(item, state);
|
|
71
|
-
const disabled = isItemDisabled(item, state);
|
|
72
|
-
return (_jsx(Pressable, { accessibilityLabel: item.label, accessibilityRole: "button", accessibilityState: { disabled, selected: active }, disabled: disabled, onPress: () => {
|
|
73
101
|
if (item.action === "openBlockPicker") {
|
|
74
102
|
pickerSelectionRef.current =
|
|
75
103
|
state.selection.type === "text"
|
|
@@ -94,43 +122,51 @@ export const OpenEditorNativeToolbar = memo(function OpenEditorNativeToolbar({ a
|
|
|
94
122
|
runCommand(controller.command(item.command));
|
|
95
123
|
}, style: ({ pressed }) => [
|
|
96
124
|
styles.button,
|
|
97
|
-
{ borderColor: active ? colors.accent : colors.border },
|
|
98
125
|
active && { backgroundColor: colors.accent },
|
|
99
126
|
pressed && styles.pressed,
|
|
100
127
|
disabled && styles.disabled,
|
|
101
|
-
], children: _jsx(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
? colors.muted
|
|
108
|
-
: colors.text,
|
|
109
|
-
},
|
|
110
|
-
], children: item.label }) }, item.key));
|
|
111
|
-
}) }), _jsx(Modal, { animationType: "slide", onDismiss: () => {
|
|
128
|
+
], children: _jsx(HugeiconsIcon, { accessible: false, color: active
|
|
129
|
+
? colors.accentText
|
|
130
|
+
: disabled
|
|
131
|
+
? colors.muted
|
|
132
|
+
: colors.text, icon: openEditorIcons[item.icon], size: 20, strokeWidth: 1.8 }) }, item.key));
|
|
133
|
+
}) }) })) : null, _jsx(NativeBlockPickerSheet, { isPresented: blockPickerOpen, onDismiss: () => {
|
|
112
134
|
const block = pendingBlockRef.current;
|
|
113
135
|
pendingBlockRef.current = null;
|
|
114
136
|
pickerSelectionRef.current = undefined;
|
|
115
|
-
if (block)
|
|
116
|
-
runCommand(controller.command(block));
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
137
|
+
if (block) {
|
|
138
|
+
runCommand(controller.command(block).then(() => controller.focus()));
|
|
139
|
+
}
|
|
140
|
+
}, onIsPresentedChange: setBlockPickerOpen, children: _jsxs(NativeColumn, { alignment: "start", spacing: 0, style: { height: "100%", width: "100%" }, children: [_jsxs(NativeRow, { alignment: "center", spacing: 8, style: { paddingBottom: 10, width: "100%" }, children: [_jsx(NativeButton, { modifiers: Platform.OS === "ios"
|
|
141
|
+
? [
|
|
142
|
+
nativeAccessibilityLabel("Cancel"),
|
|
143
|
+
glassEffect({
|
|
144
|
+
glass: { interactive: true, variant: "clear" },
|
|
145
|
+
shape: "circle",
|
|
146
|
+
}),
|
|
147
|
+
]
|
|
148
|
+
: undefined, onPress: () => setBlockPickerOpen(false), style: { borderRadius: 18, height: 36, width: 36 }, testID: "close-block-picker", variant: "text", children: _jsx(NativeRNHostView, { matchContents: true, children: _jsx(View, { style: { height: 18, width: 18 }, children: _jsx(HugeiconsIcon, { accessible: false, color: colors.text, icon: openEditorIcons.close, size: 18, strokeWidth: 1.8 }) }) }) }), _jsx(NativeSpacer, { flexible: true }), _jsx(NativeText, { testID: "block-picker-title", textStyle: { fontSize: 17, fontWeight: "600" }, children: "Insert block" }), _jsx(NativeSpacer, { flexible: true }), _jsx(NativeButton, { modifiers: Platform.OS === "ios"
|
|
149
|
+
? [
|
|
150
|
+
nativeAccessibilityLabel("Done"),
|
|
151
|
+
glassEffect({
|
|
152
|
+
glass: { interactive: true, variant: "clear" },
|
|
153
|
+
shape: "circle",
|
|
154
|
+
}),
|
|
155
|
+
]
|
|
156
|
+
: undefined, onPress: () => setBlockPickerOpen(false), style: { borderRadius: 18, height: 36, width: 36 }, testID: "done-block-picker", variant: "text", children: _jsx(NativeRNHostView, { matchContents: true, children: _jsx(View, { style: { height: 18, width: 18 }, children: _jsx(HugeiconsIcon, { accessible: false, color: colors.text, icon: openEditorIcons.check, size: 18, strokeWidth: 1.8 }) }) }) })] }), _jsx(NativeScrollView, { showsIndicators: true, style: { height: "100%", width: "100%" }, children: _jsx(NativeColumn, { alignment: "start", spacing: 20, style: { width: "100%" }, children: blockPickerGroups.map((group) => (_jsxs(NativeColumn, { alignment: "start", spacing: 4, style: { width: "100%" }, children: [_jsx(NativeText, { textStyle: {
|
|
157
|
+
color: colors.muted,
|
|
158
|
+
fontSize: 12,
|
|
159
|
+
fontWeight: "600",
|
|
160
|
+
}, children: group.label }), group.items.map((item) => (_jsx(NativeListItem, { leading: (_jsx(HugeiconsIcon, { accessible: false, color: colors.text, icon: openEditorIcons[item.icon], size: 20, strokeWidth: 1.8 })), modifiers: Platform.OS === "ios"
|
|
161
|
+
? [nativePadding({ vertical: 10 })]
|
|
162
|
+
: undefined, onPress: () => {
|
|
122
163
|
pendingBlockRef.current = {
|
|
123
164
|
type: "insertBlock",
|
|
124
165
|
block: item.key,
|
|
125
166
|
selection: pickerSelectionRef.current,
|
|
126
167
|
};
|
|
127
168
|
setBlockPickerOpen(false);
|
|
128
|
-
},
|
|
129
|
-
styles.blockPickerItem,
|
|
130
|
-
{ borderBottomColor: colors.border },
|
|
131
|
-
pressed && styles.pressed,
|
|
132
|
-
], children: _jsx(Text, { style: [styles.blockPickerLabel, { color: colors.text }], children: item.label }) })] }, item.key));
|
|
133
|
-
}) })] }) }), _jsx(Modal, { animationType: "fade", onRequestClose: () => setLinkEditorOpen(false), presentationStyle: "formSheet", visible: linkEditorOpen, children: _jsxs(View, { style: [styles.linkSheet, { backgroundColor: colors.background }], children: [_jsx(Text, { accessibilityRole: "header", style: [styles.sheetTitle, { color: colors.text }], children: "Edit link" }), _jsx(TextInput, { accessibilityLabel: "Link URL", autoCapitalize: "none", autoCorrect: false, onChangeText: setLinkValue, placeholder: "https://example.com", placeholderTextColor: colors.muted, style: [
|
|
169
|
+
}, testID: `insert-${item.key}`, trailing: (_jsx(HugeiconsIcon, { accessible: false, color: colors.muted, icon: openEditorIcons.chevronRight, size: 16, strokeWidth: 1.8 })), children: _jsx(NativeText, { children: item.label }, `${item.key}-label`) }, item.key)))] }, group.label))) }) })] }) }), _jsx(Modal, { animationType: "fade", onRequestClose: () => setLinkEditorOpen(false), presentationStyle: "formSheet", visible: linkEditorOpen, children: _jsxs(View, { style: [styles.linkSheet, { backgroundColor: colors.background }], children: [_jsx(Text, { accessibilityRole: "header", style: [styles.sheetTitle, { color: colors.text }], children: "Edit link" }), _jsx(TextInput, { accessibilityLabel: "Link URL", autoCapitalize: "none", autoCorrect: false, onChangeText: setLinkValue, placeholder: "https://example.com", placeholderTextColor: colors.muted, style: [
|
|
134
170
|
styles.linkInput,
|
|
135
171
|
{ borderColor: colors.border, color: colors.text },
|
|
136
172
|
], value: linkValue }), _jsxs(View, { style: styles.linkActions, children: [_jsx(Pressable, { accessibilityLabel: "Cancel link editing", accessibilityRole: "button", onPress: () => setLinkEditorOpen(false), style: styles.linkAction, children: _jsx(Text, { style: { color: colors.text }, children: "Cancel" }) }), _jsx(Pressable, { accessibilityLabel: "Remove", accessibilityRole: "button", onPress: () => {
|
|
@@ -144,55 +180,24 @@ export const OpenEditorNativeToolbar = memo(function OpenEditorNativeToolbar({ a
|
|
|
144
180
|
});
|
|
145
181
|
const styles = StyleSheet.create({
|
|
146
182
|
root: {
|
|
147
|
-
|
|
148
|
-
minHeight: 52,
|
|
183
|
+
minHeight: 46,
|
|
149
184
|
},
|
|
150
185
|
content: {
|
|
151
186
|
alignItems: "center",
|
|
152
|
-
gap:
|
|
153
|
-
paddingHorizontal:
|
|
187
|
+
gap: 6,
|
|
188
|
+
paddingHorizontal: 8,
|
|
154
189
|
paddingVertical: 7,
|
|
155
190
|
},
|
|
156
191
|
button: {
|
|
157
192
|
alignItems: "center",
|
|
158
|
-
borderRadius:
|
|
159
|
-
|
|
193
|
+
borderRadius: TOOLBAR_BUTTON_SIZE / 2,
|
|
194
|
+
height: TOOLBAR_BUTTON_SIZE,
|
|
160
195
|
justifyContent: "center",
|
|
161
|
-
|
|
162
|
-
minWidth: 40,
|
|
163
|
-
paddingHorizontal: 10,
|
|
164
|
-
},
|
|
165
|
-
label: {
|
|
166
|
-
fontSize: 13,
|
|
167
|
-
fontWeight: "600",
|
|
196
|
+
width: TOOLBAR_BUTTON_SIZE,
|
|
168
197
|
},
|
|
169
198
|
pressed: { opacity: 0.64 },
|
|
170
199
|
disabled: { opacity: 0.42 },
|
|
171
|
-
sheet: { flex: 1 },
|
|
172
|
-
sheetHeader: {
|
|
173
|
-
alignItems: "center",
|
|
174
|
-
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
175
|
-
flexDirection: "row",
|
|
176
|
-
justifyContent: "space-between",
|
|
177
|
-
minHeight: 56,
|
|
178
|
-
paddingHorizontal: 20,
|
|
179
|
-
},
|
|
180
200
|
sheetTitle: { fontSize: 18, fontWeight: "700" },
|
|
181
|
-
sheetClose: { fontSize: 16, fontWeight: "600" },
|
|
182
|
-
sheetContent: { paddingBottom: 32, paddingHorizontal: 20 },
|
|
183
|
-
groupLabel: {
|
|
184
|
-
fontSize: 12,
|
|
185
|
-
fontWeight: "700",
|
|
186
|
-
paddingBottom: 5,
|
|
187
|
-
paddingTop: 20,
|
|
188
|
-
textTransform: "uppercase",
|
|
189
|
-
},
|
|
190
|
-
blockPickerItem: {
|
|
191
|
-
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
192
|
-
minHeight: 48,
|
|
193
|
-
justifyContent: "center",
|
|
194
|
-
},
|
|
195
|
-
blockPickerLabel: { fontSize: 16, fontWeight: "500" },
|
|
196
201
|
linkSheet: { flex: 1, gap: 20, justifyContent: "center", padding: 24 },
|
|
197
202
|
linkInput: {
|
|
198
203
|
borderRadius: 10,
|
package/dist/toolbar-items.d.ts
CHANGED
|
@@ -1,88 +1,109 @@
|
|
|
1
1
|
import type { OpenEditorRuntimeCommand } from "@openeditor/embedded-runtime";
|
|
2
|
+
import type { OpenEditorIconName } from "@openeditor/icons";
|
|
2
3
|
export type OpenEditorNativeToolbarItem = {
|
|
3
4
|
key: string;
|
|
4
5
|
label: string;
|
|
6
|
+
icon: OpenEditorIconName;
|
|
5
7
|
command?: OpenEditorRuntimeCommand;
|
|
6
|
-
action?: "openBlockPicker" | "openLinkEditor" | "dismissKeyboard";
|
|
8
|
+
action?: "openBlockPicker" | "openLinkEditor" | "toggleBlockHandles" | "dismissKeyboard";
|
|
7
9
|
activeWhen?: {
|
|
8
10
|
block?: string;
|
|
9
11
|
headingLevel?: number;
|
|
10
12
|
mark?: string;
|
|
13
|
+
table?: "headerRow" | "headerColumn";
|
|
11
14
|
};
|
|
12
|
-
disabledWhen?: "cannotUndo" | "cannotRedo";
|
|
15
|
+
disabledWhen?: "cannotUndo" | "cannotRedo" | "cannotMergeCells";
|
|
13
16
|
};
|
|
14
17
|
export declare const defaultOpenEditorNativeToolbarItems: readonly OpenEditorNativeToolbarItem[];
|
|
15
18
|
export declare const openEditorNativeBlockPickerItems: readonly [{
|
|
16
19
|
readonly key: "paragraph";
|
|
17
|
-
readonly label: "
|
|
20
|
+
readonly label: "Text";
|
|
18
21
|
readonly group: "Text";
|
|
22
|
+
readonly icon: "text";
|
|
19
23
|
}, {
|
|
20
24
|
readonly key: "heading1";
|
|
21
25
|
readonly label: "Heading 1";
|
|
22
26
|
readonly group: "Text";
|
|
27
|
+
readonly icon: "heading1";
|
|
23
28
|
}, {
|
|
24
29
|
readonly key: "heading2";
|
|
25
30
|
readonly label: "Heading 2";
|
|
26
31
|
readonly group: "Text";
|
|
32
|
+
readonly icon: "heading2";
|
|
27
33
|
}, {
|
|
28
34
|
readonly key: "heading3";
|
|
29
35
|
readonly label: "Heading 3";
|
|
30
36
|
readonly group: "Text";
|
|
37
|
+
readonly icon: "heading3";
|
|
31
38
|
}, {
|
|
32
39
|
readonly key: "bulletList";
|
|
33
40
|
readonly label: "Bullet list";
|
|
34
41
|
readonly group: "Text";
|
|
42
|
+
readonly icon: "bulletList";
|
|
35
43
|
}, {
|
|
36
44
|
readonly key: "orderedList";
|
|
37
45
|
readonly label: "Numbered list";
|
|
38
46
|
readonly group: "Text";
|
|
47
|
+
readonly icon: "orderedList";
|
|
39
48
|
}, {
|
|
40
49
|
readonly key: "taskList";
|
|
41
50
|
readonly label: "Task list";
|
|
42
51
|
readonly group: "Text";
|
|
52
|
+
readonly icon: "taskList";
|
|
43
53
|
}, {
|
|
44
54
|
readonly key: "toggleList";
|
|
45
55
|
readonly label: "Toggle list";
|
|
46
56
|
readonly group: "Text";
|
|
57
|
+
readonly icon: "toggleList";
|
|
47
58
|
}, {
|
|
48
59
|
readonly key: "blockquote";
|
|
49
60
|
readonly label: "Quote";
|
|
50
61
|
readonly group: "Text";
|
|
62
|
+
readonly icon: "blockquote";
|
|
51
63
|
}, {
|
|
52
64
|
readonly key: "codeBlock";
|
|
53
65
|
readonly label: "Code block";
|
|
54
66
|
readonly group: "Text";
|
|
67
|
+
readonly icon: "codeBlock";
|
|
55
68
|
}, {
|
|
56
69
|
readonly key: "divider";
|
|
57
70
|
readonly label: "Divider";
|
|
58
71
|
readonly group: "Structure";
|
|
72
|
+
readonly icon: "divider";
|
|
59
73
|
}, {
|
|
60
74
|
readonly key: "columns";
|
|
61
75
|
readonly label: "Columns";
|
|
62
76
|
readonly group: "Layout";
|
|
77
|
+
readonly icon: "columns";
|
|
63
78
|
}, {
|
|
64
79
|
readonly key: "table";
|
|
65
80
|
readonly label: "Table";
|
|
66
81
|
readonly group: "Layout";
|
|
82
|
+
readonly icon: "table";
|
|
67
83
|
}, {
|
|
68
84
|
readonly key: "callout";
|
|
69
85
|
readonly label: "Callout";
|
|
70
86
|
readonly group: "Embed";
|
|
87
|
+
readonly icon: "callout";
|
|
71
88
|
}, {
|
|
72
89
|
readonly key: "diagram";
|
|
73
90
|
readonly label: "Diagram";
|
|
74
91
|
readonly group: "Embed";
|
|
92
|
+
readonly icon: "diagram";
|
|
75
93
|
}, {
|
|
76
94
|
readonly key: "page";
|
|
77
95
|
readonly label: "Page";
|
|
78
96
|
readonly group: "Embed";
|
|
97
|
+
readonly icon: "page";
|
|
79
98
|
}, {
|
|
80
99
|
readonly key: "image";
|
|
81
100
|
readonly label: "Image";
|
|
82
101
|
readonly group: "Media";
|
|
102
|
+
readonly icon: "image";
|
|
83
103
|
}, {
|
|
84
104
|
readonly key: "attachment";
|
|
85
105
|
readonly label: "File";
|
|
86
106
|
readonly group: "Media";
|
|
107
|
+
readonly icon: "attachment";
|
|
87
108
|
}];
|
|
88
109
|
export declare const openEditorNativeTableToolbarItems: readonly OpenEditorNativeToolbarItem[];
|
package/dist/toolbar-items.js
CHANGED
|
@@ -1,52 +1,57 @@
|
|
|
1
1
|
export const defaultOpenEditorNativeToolbarItems = [
|
|
2
|
-
{ key: "insert", label: "
|
|
3
|
-
{ key: "
|
|
4
|
-
{ key: "
|
|
5
|
-
{ key: "heading-
|
|
6
|
-
{ key: "
|
|
7
|
-
{ key: "
|
|
8
|
-
{ key: "
|
|
9
|
-
{ key: "
|
|
10
|
-
{ key: "
|
|
11
|
-
{ key: "
|
|
12
|
-
{ key: "
|
|
13
|
-
{ key: "
|
|
14
|
-
{ key: "
|
|
15
|
-
{ key: "
|
|
16
|
-
{ key: "
|
|
17
|
-
{ key: "
|
|
18
|
-
{ key: "
|
|
19
|
-
{ key: "
|
|
2
|
+
{ key: "insert", label: "Insert block", icon: "addBlock", action: "openBlockPicker" },
|
|
3
|
+
{ key: "handles", label: "Show block handles", icon: "dragHandle", action: "toggleBlockHandles" },
|
|
4
|
+
{ key: "paragraph", label: "Text", icon: "text", command: { type: "setParagraph" }, activeWhen: { block: "paragraph" } },
|
|
5
|
+
{ key: "heading-1", label: "Heading 1", icon: "heading1", command: { type: "toggleHeading", level: 1 }, activeWhen: { block: "heading", headingLevel: 1 } },
|
|
6
|
+
{ key: "heading-2", label: "Heading 2", icon: "heading2", command: { type: "toggleHeading", level: 2 }, activeWhen: { block: "heading", headingLevel: 2 } },
|
|
7
|
+
{ key: "bold", label: "Bold", icon: "bold", command: { type: "toggleMark", mark: "bold" }, activeWhen: { mark: "bold" } },
|
|
8
|
+
{ key: "italic", label: "Italic", icon: "italic", command: { type: "toggleMark", mark: "italic" }, activeWhen: { mark: "italic" } },
|
|
9
|
+
{ key: "underline", label: "Underline", icon: "underline", command: { type: "toggleMark", mark: "underline" }, activeWhen: { mark: "underline" } },
|
|
10
|
+
{ key: "strike", label: "Strikethrough", icon: "strike", command: { type: "toggleMark", mark: "strike" }, activeWhen: { mark: "strike" } },
|
|
11
|
+
{ key: "code", label: "Inline code", icon: "code", command: { type: "toggleMark", mark: "code" }, activeWhen: { mark: "code" } },
|
|
12
|
+
{ key: "link", label: "Link", icon: "link", action: "openLinkEditor", activeWhen: { mark: "link" } },
|
|
13
|
+
{ key: "bullet-list", label: "Bullet list", icon: "bulletList", command: { type: "toggleList", list: "bullet" }, activeWhen: { block: "bulletList" } },
|
|
14
|
+
{ key: "ordered-list", label: "Numbered list", icon: "orderedList", command: { type: "toggleList", list: "ordered" }, activeWhen: { block: "orderedList" } },
|
|
15
|
+
{ key: "task-list", label: "Task list", icon: "taskList", command: { type: "toggleList", list: "task" }, activeWhen: { block: "taskList" } },
|
|
16
|
+
{ key: "quote", label: "Quote", icon: "blockquote", command: { type: "toggleBlockquote" }, activeWhen: { block: "blockquote" } },
|
|
17
|
+
{ key: "code-block", label: "Code block", icon: "codeBlock", command: { type: "toggleCodeBlock" }, activeWhen: { block: "codeBlock" } },
|
|
18
|
+
{ key: "undo", label: "Undo", icon: "undo", command: { type: "undo" }, disabledWhen: "cannotUndo" },
|
|
19
|
+
{ key: "redo", label: "Redo", icon: "redo", command: { type: "redo" }, disabledWhen: "cannotRedo" },
|
|
20
|
+
{ key: "keyboard", label: "Dismiss keyboard", icon: "keyboard", action: "dismissKeyboard" },
|
|
20
21
|
];
|
|
21
22
|
export const openEditorNativeBlockPickerItems = [
|
|
22
|
-
{ key: "paragraph", label: "
|
|
23
|
-
{ key: "heading1", label: "Heading 1", group: "Text" },
|
|
24
|
-
{ key: "heading2", label: "Heading 2", group: "Text" },
|
|
25
|
-
{ key: "heading3", label: "Heading 3", group: "Text" },
|
|
26
|
-
{ key: "bulletList", label: "Bullet list", group: "Text" },
|
|
27
|
-
{ key: "orderedList", label: "Numbered list", group: "Text" },
|
|
28
|
-
{ key: "taskList", label: "Task list", group: "Text" },
|
|
29
|
-
{ key: "toggleList", label: "Toggle list", group: "Text" },
|
|
30
|
-
{ key: "blockquote", label: "Quote", group: "Text" },
|
|
31
|
-
{ key: "codeBlock", label: "Code block", group: "Text" },
|
|
32
|
-
{ key: "divider", label: "Divider", group: "Structure" },
|
|
33
|
-
{ key: "columns", label: "Columns", group: "Layout" },
|
|
34
|
-
{ key: "table", label: "Table", group: "Layout" },
|
|
35
|
-
{ key: "callout", label: "Callout", group: "Embed" },
|
|
36
|
-
{ key: "diagram", label: "Diagram", group: "Embed" },
|
|
37
|
-
{ key: "page", label: "Page", group: "Embed" },
|
|
38
|
-
{ key: "image", label: "Image", group: "Media" },
|
|
39
|
-
{ key: "attachment", label: "File", group: "Media" },
|
|
23
|
+
{ key: "paragraph", label: "Text", group: "Text", icon: "text" },
|
|
24
|
+
{ key: "heading1", label: "Heading 1", group: "Text", icon: "heading1" },
|
|
25
|
+
{ key: "heading2", label: "Heading 2", group: "Text", icon: "heading2" },
|
|
26
|
+
{ key: "heading3", label: "Heading 3", group: "Text", icon: "heading3" },
|
|
27
|
+
{ key: "bulletList", label: "Bullet list", group: "Text", icon: "bulletList" },
|
|
28
|
+
{ key: "orderedList", label: "Numbered list", group: "Text", icon: "orderedList" },
|
|
29
|
+
{ key: "taskList", label: "Task list", group: "Text", icon: "taskList" },
|
|
30
|
+
{ key: "toggleList", label: "Toggle list", group: "Text", icon: "toggleList" },
|
|
31
|
+
{ key: "blockquote", label: "Quote", group: "Text", icon: "blockquote" },
|
|
32
|
+
{ key: "codeBlock", label: "Code block", group: "Text", icon: "codeBlock" },
|
|
33
|
+
{ key: "divider", label: "Divider", group: "Structure", icon: "divider" },
|
|
34
|
+
{ key: "columns", label: "Columns", group: "Layout", icon: "columns" },
|
|
35
|
+
{ key: "table", label: "Table", group: "Layout", icon: "table" },
|
|
36
|
+
{ key: "callout", label: "Callout", group: "Embed", icon: "callout" },
|
|
37
|
+
{ key: "diagram", label: "Diagram", group: "Embed", icon: "diagram" },
|
|
38
|
+
{ key: "page", label: "Page", group: "Embed", icon: "page" },
|
|
39
|
+
{ key: "image", label: "Image", group: "Media", icon: "image" },
|
|
40
|
+
{ key: "attachment", label: "File", group: "Media", icon: "attachment" },
|
|
40
41
|
];
|
|
41
42
|
export const openEditorNativeTableToolbarItems = [
|
|
42
|
-
{ key: "table-row-before", label: "
|
|
43
|
-
{ key: "table-row-after", label: "
|
|
44
|
-
{ key: "table-delete-row", label: "
|
|
45
|
-
{ key: "table-column-before", label: "
|
|
46
|
-
{ key: "table-column-after", label: "
|
|
47
|
-
{ key: "table-delete-column", label: "
|
|
48
|
-
{ key: "table-
|
|
49
|
-
{ key: "table-
|
|
50
|
-
{ key: "table-
|
|
51
|
-
{ key: "table-
|
|
43
|
+
{ key: "table-row-before", label: "Insert row above", icon: "tableRowInsertBefore", command: { type: "addTableRow", after: false } },
|
|
44
|
+
{ key: "table-row-after", label: "Insert row below", icon: "tableRowInsertAfter", command: { type: "addTableRow" } },
|
|
45
|
+
{ key: "table-delete-row", label: "Delete row", icon: "tableRowDelete", command: { type: "deleteTableRow" } },
|
|
46
|
+
{ key: "table-column-before", label: "Insert column left", icon: "tableColumnInsertBefore", command: { type: "addTableColumn", after: false } },
|
|
47
|
+
{ key: "table-column-after", label: "Insert column right", icon: "tableColumnInsertAfter", command: { type: "addTableColumn" } },
|
|
48
|
+
{ key: "table-delete-column", label: "Delete column", icon: "tableColumnDelete", command: { type: "deleteTableColumn" } },
|
|
49
|
+
{ key: "table-header-row", label: "Toggle header row", icon: "tableHeaderRow", command: { type: "toggleTableHeaderRow" }, activeWhen: { table: "headerRow" } },
|
|
50
|
+
{ key: "table-header-column", label: "Toggle header column", icon: "tableHeaderColumn", command: { type: "toggleTableHeaderColumn" }, activeWhen: { table: "headerColumn" } },
|
|
51
|
+
{ key: "table-merge-cells", label: "Merge selected cells", icon: "mergeCells", command: { type: "mergeTableCells" }, disabledWhen: "cannotMergeCells" },
|
|
52
|
+
{ key: "table-split-cell", label: "Split cell", icon: "splitCells", command: { type: "splitTableCell" } },
|
|
53
|
+
{ key: "table-delete", label: "Delete table", icon: "delete", command: { type: "deleteTable" } },
|
|
54
|
+
{ key: "table-undo", label: "Undo", icon: "undo", command: { type: "undo" }, disabledWhen: "cannotUndo" },
|
|
55
|
+
{ key: "table-redo", label: "Redo", icon: "redo", command: { type: "redo" }, disabledWhen: "cannotRedo" },
|
|
56
|
+
{ key: "table-keyboard", label: "Dismiss keyboard", icon: "keyboard", action: "dismissKeyboard" },
|
|
52
57
|
];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type GlassColorScheme } from "expo-glass-effect";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { type StyleProp, type ViewStyle } from "react-native";
|
|
4
|
+
import type { OpenEditorNativeTheme } from "./native-editor.js";
|
|
5
|
+
export type OpenEditorNativeToolbarSurfaceProps = {
|
|
6
|
+
active?: boolean;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
colorScheme?: GlassColorScheme;
|
|
9
|
+
style?: StyleProp<ViewStyle>;
|
|
10
|
+
theme?: OpenEditorNativeTheme;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* The platform material surrounding OpenEditor's keyboard toolbar.
|
|
14
|
+
* iOS uses the system glass implementation when available; other runtimes
|
|
15
|
+
* receive the same geometry with a theme-aware opaque fallback.
|
|
16
|
+
*/
|
|
17
|
+
export declare function OpenEditorNativeToolbarSurface({ active, children, colorScheme, style, theme, }: OpenEditorNativeToolbarSurfaceProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { GlassView, isGlassEffectAPIAvailable, } from "expo-glass-effect";
|
|
3
|
+
import { Platform, StyleSheet, View, } from "react-native";
|
|
4
|
+
import { OPENEDITOR_NATIVE_TOOLBAR_HEIGHT, OPENEDITOR_NATIVE_TOOLBAR_RADIUS, } from "./native-layout.js";
|
|
5
|
+
/**
|
|
6
|
+
* The platform material surrounding OpenEditor's keyboard toolbar.
|
|
7
|
+
* iOS uses the system glass implementation when available; other runtimes
|
|
8
|
+
* receive the same geometry with a theme-aware opaque fallback.
|
|
9
|
+
*/
|
|
10
|
+
export function OpenEditorNativeToolbarSurface({ active = true, children, colorScheme = "auto", style, theme, }) {
|
|
11
|
+
if (Platform.OS === "ios" && isGlassEffectAPIAvailable()) {
|
|
12
|
+
return (_jsx(GlassView, { colorScheme: colorScheme, glassEffectStyle: active ? "regular" : "none", isInteractive: true, style: [styles.surface, style], children: children }));
|
|
13
|
+
}
|
|
14
|
+
return (_jsx(View, { style: [
|
|
15
|
+
styles.surface,
|
|
16
|
+
styles.fallback,
|
|
17
|
+
{
|
|
18
|
+
backgroundColor: theme?.surfaceRaised ?? theme?.surfaceMuted ?? theme?.surface ?? "#ffffff",
|
|
19
|
+
borderColor: theme?.border ?? "#e5e7eb",
|
|
20
|
+
},
|
|
21
|
+
style,
|
|
22
|
+
], children: children }));
|
|
23
|
+
}
|
|
24
|
+
const styles = StyleSheet.create({
|
|
25
|
+
surface: {
|
|
26
|
+
borderRadius: OPENEDITOR_NATIVE_TOOLBAR_RADIUS,
|
|
27
|
+
height: OPENEDITOR_NATIVE_TOOLBAR_HEIGHT,
|
|
28
|
+
overflow: "hidden",
|
|
29
|
+
},
|
|
30
|
+
fallback: {
|
|
31
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
32
|
+
},
|
|
33
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openeditor/native",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.35",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"repository": {
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"access": "public"
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
|
-
"dist"
|
|
18
|
+
"dist",
|
|
19
|
+
"plugin.cjs"
|
|
19
20
|
],
|
|
20
21
|
"exports": {
|
|
21
22
|
".": {
|
|
@@ -23,16 +24,26 @@
|
|
|
23
24
|
"types": "./dist/index.d.ts",
|
|
24
25
|
"import": "./dist/index.js"
|
|
25
26
|
},
|
|
27
|
+
"./plugin": "./plugin.cjs",
|
|
26
28
|
"./package.json": "./package.json"
|
|
27
29
|
},
|
|
28
30
|
"dependencies": {
|
|
29
|
-
"@
|
|
30
|
-
"@
|
|
31
|
-
"@
|
|
31
|
+
"@expo/config-plugins": "57.0.6",
|
|
32
|
+
"@expo/ui": "~57.0.7",
|
|
33
|
+
"@hugeicons/react-native": "^1.0.15",
|
|
34
|
+
"@openeditor/core": "0.0.35",
|
|
35
|
+
"@openeditor/embedded-runtime": "0.0.35",
|
|
36
|
+
"@openeditor/embedded-surface": "0.0.35",
|
|
37
|
+
"@openeditor/icons": "0.0.35"
|
|
32
38
|
},
|
|
33
39
|
"peerDependencies": {
|
|
40
|
+
"expo": ">=57",
|
|
34
41
|
"react": ">=19",
|
|
35
42
|
"react-native": ">=0.85",
|
|
43
|
+
"expo-glass-effect": ">=57",
|
|
44
|
+
"react-native-keyboard-controller": ">=1.21",
|
|
45
|
+
"react-native-reanimated": ">=3",
|
|
46
|
+
"react-native-svg": ">=15",
|
|
36
47
|
"react-native-webview": ">=13"
|
|
37
48
|
}
|
|
38
49
|
}
|
package/plugin.cjs
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const { withAppDelegate } = require("@expo/config-plugins");
|
|
2
|
+
|
|
3
|
+
const WEBKIT_IMPORT = "import WebKit\n";
|
|
4
|
+
const OBSERVER_PROPERTY =
|
|
5
|
+
" private var openEditorKeyboardWillShowObserver: NSObjectProtocol?\n";
|
|
6
|
+
const OBSERVER_SETUP = ` openEditorKeyboardWillShowObserver = NotificationCenter.default.addObserver(
|
|
7
|
+
forName: UIResponder.keyboardWillShowNotification,
|
|
8
|
+
object: nil,
|
|
9
|
+
queue: .main
|
|
10
|
+
) { [weak self] _ in
|
|
11
|
+
guard let rootView = self?.window?.rootViewController?.view else { return }
|
|
12
|
+
self?.enableOpenEditorInteractiveKeyboardDismissal(in: rootView)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
`;
|
|
16
|
+
const DISMISSAL_METHOD = ` deinit {
|
|
17
|
+
if let openEditorKeyboardWillShowObserver {
|
|
18
|
+
NotificationCenter.default.removeObserver(openEditorKeyboardWillShowObserver)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private func enableOpenEditorInteractiveKeyboardDismissal(in view: UIView) {
|
|
23
|
+
if let webView = view as? WKWebView {
|
|
24
|
+
webView.scrollView.keyboardDismissMode = .interactive
|
|
25
|
+
}
|
|
26
|
+
view.subviews.forEach { enableOpenEditorInteractiveKeyboardDismissal(in: $0) }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
const applyOpenEditorKeyboardDismissal = (contents) => {
|
|
32
|
+
for (const anchor of [
|
|
33
|
+
"import React\n",
|
|
34
|
+
"class AppDelegate: ExpoAppDelegate {\n",
|
|
35
|
+
" return super.application(application, didFinishLaunchingWithOptions: launchOptions)\n",
|
|
36
|
+
" // Linking API\n",
|
|
37
|
+
]) {
|
|
38
|
+
if (!contents.includes(anchor)) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`OpenEditor's Expo plugin could not find ${JSON.stringify(anchor)} in AppDelegate.swift.`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let next = contents;
|
|
46
|
+
if (!next.includes("import WebKit")) {
|
|
47
|
+
next = next.replace("import React\n", `import React\n${WEBKIT_IMPORT}`);
|
|
48
|
+
}
|
|
49
|
+
if (!next.includes("openEditorKeyboardWillShowObserver")) {
|
|
50
|
+
next = next.replace(
|
|
51
|
+
"class AppDelegate: ExpoAppDelegate {\n",
|
|
52
|
+
`class AppDelegate: ExpoAppDelegate {\n${OBSERVER_PROPERTY}`,
|
|
53
|
+
);
|
|
54
|
+
next = next.replace(
|
|
55
|
+
" return super.application(application, didFinishLaunchingWithOptions: launchOptions)\n",
|
|
56
|
+
`${OBSERVER_SETUP} return super.application(application, didFinishLaunchingWithOptions: launchOptions)\n`,
|
|
57
|
+
);
|
|
58
|
+
next = next.replace(
|
|
59
|
+
" // Linking API\n",
|
|
60
|
+
`${DISMISSAL_METHOD} // Linking API\n`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return next;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const withOpenEditorNative = (config) =>
|
|
67
|
+
withAppDelegate(config, (mod) => {
|
|
68
|
+
if (mod.modResults.language !== "swift") {
|
|
69
|
+
throw new Error("OpenEditor's Expo plugin requires a Swift AppDelegate.");
|
|
70
|
+
}
|
|
71
|
+
mod.modResults.contents = applyOpenEditorKeyboardDismissal(
|
|
72
|
+
mod.modResults.contents,
|
|
73
|
+
);
|
|
74
|
+
return mod;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
module.exports = withOpenEditorNative;
|
|
78
|
+
module.exports.applyOpenEditorKeyboardDismissal =
|
|
79
|
+
applyOpenEditorKeyboardDismissal;
|