@octane-xplat/lexical 0.0.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +85 -3
- package/dist/macos/browser.d.ts +1 -0
- package/dist/macos/browser.js +2 -0
- package/dist/native/index.js +3 -0
- package/dist/native/index2.js +232 -0
- package/dist/native/index3.js +211 -0
- package/dist/native/octane-client-build.json +8 -0
- package/dist/web/index.js +12 -0
- package/dist/web/index2.js +346 -0
- package/dist/web/octane-client-build.json +8 -0
- package/package.json +89 -3
- package/src/Editor.macos.tsx +9 -0
- package/src/LexicalEditor.tsrx +111 -0
- package/src/LexicalEditor.web.tsrx +406 -0
- package/src/browser-entry.web.tsx +5 -0
- package/src/index.macos.ts +13 -0
- package/src/index.ts +3 -0
- package/src/index.web.ts +12 -0
- package/src/json-bridge.ts +335 -0
- package/src/types.ts +105 -0
- package/types/index.d.ts +8 -0
- package/types/index.macos.d.ts +7 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { useState, useImperativeHandle, useEffect, useRef } from 'octane'
|
|
2
|
+
import {
|
|
3
|
+
AutoFocusPlugin,
|
|
4
|
+
ContentEditable,
|
|
5
|
+
EditorRefPlugin,
|
|
6
|
+
HistoryPlugin,
|
|
7
|
+
LexicalComposer,
|
|
8
|
+
LexicalErrorBoundary,
|
|
9
|
+
LinkPlugin,
|
|
10
|
+
ListPlugin,
|
|
11
|
+
OnChangePlugin,
|
|
12
|
+
RichTextPlugin,
|
|
13
|
+
} from '@octanejs/lexical'
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
$createParagraphNode,
|
|
17
|
+
$createTextNode,
|
|
18
|
+
$findMatchingParent,
|
|
19
|
+
$getRoot,
|
|
20
|
+
$getSelection,
|
|
21
|
+
$isNodeSelection,
|
|
22
|
+
$isRangeSelection,
|
|
23
|
+
FORMAT_ELEMENT_COMMAND,
|
|
24
|
+
FORMAT_TEXT_COMMAND,
|
|
25
|
+
REDO_COMMAND,
|
|
26
|
+
UNDO_COMMAND,
|
|
27
|
+
} from 'lexical'
|
|
28
|
+
|
|
29
|
+
import { $createHeadingNode, $createQuoteNode, $isHeadingNode, $isQuoteNode, HeadingNode, QuoteNode } from '@lexical/rich-text'
|
|
30
|
+
import {
|
|
31
|
+
$isListItemNode,
|
|
32
|
+
$isListNode,
|
|
33
|
+
INSERT_CHECK_LIST_COMMAND,
|
|
34
|
+
INSERT_ORDERED_LIST_COMMAND,
|
|
35
|
+
INSERT_UNORDERED_LIST_COMMAND,
|
|
36
|
+
ListItemNode,
|
|
37
|
+
ListNode,
|
|
38
|
+
} from '@lexical/list'
|
|
39
|
+
|
|
40
|
+
import { $createLinkNode, $isLinkNode, AutoLinkNode, LinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link'
|
|
41
|
+
import { $createCodeNode, $isCodeNode, CodeNode } from '@lexical/code'
|
|
42
|
+
import { HorizontalRuleNode, INSERT_HORIZONTAL_RULE_COMMAND, $isHorizontalRuleNode } from '@lexical/extension'
|
|
43
|
+
import { $setBlocksType } from '@lexical/selection'
|
|
44
|
+
import { $generateHtmlFromNodes, $generateNodesFromDOM } from '@lexical/html'
|
|
45
|
+
import type { LexicalEditorHandle, LexicalEditorProps, LexicalFormat } from './types'
|
|
46
|
+
|
|
47
|
+
export const supported = true
|
|
48
|
+
|
|
49
|
+
/** The facade's fixed node set — Aztec's capability surface plus
|
|
50
|
+
* AutoLinkNode (parsing safety for docs produced by fuller editors). Apps
|
|
51
|
+
* needing custom nodes or plugins import @octanejs/lexical directly. */
|
|
52
|
+
const NODES = [
|
|
53
|
+
HeadingNode,
|
|
54
|
+
QuoteNode,
|
|
55
|
+
ListNode,
|
|
56
|
+
ListItemNode,
|
|
57
|
+
LinkNode,
|
|
58
|
+
AutoLinkNode,
|
|
59
|
+
CodeNode,
|
|
60
|
+
HorizontalRuleNode,
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
const HEADING_TAG: Partial<Record<LexicalFormat, 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'>> = {
|
|
64
|
+
heading1: 'h1',
|
|
65
|
+
heading2: 'h2',
|
|
66
|
+
heading3: 'h3',
|
|
67
|
+
heading4: 'h4',
|
|
68
|
+
heading5: 'h5',
|
|
69
|
+
heading6: 'h6',
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const TEXT_FORMAT: Partial<Record<LexicalFormat, string>> = {
|
|
73
|
+
bold: 'bold',
|
|
74
|
+
italic: 'italic',
|
|
75
|
+
underline: 'underline',
|
|
76
|
+
strikethrough: 'strikethrough',
|
|
77
|
+
code: 'code',
|
|
78
|
+
highlight: 'highlight',
|
|
79
|
+
subscript: 'subscript',
|
|
80
|
+
superscript: 'superscript',
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const ELEMENT_FORMAT: Partial<Record<LexicalFormat, string>> = {
|
|
84
|
+
alignLeft: 'left',
|
|
85
|
+
alignCenter: 'center',
|
|
86
|
+
alignRight: 'right',
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Toggle a block type at the selection — reverts to paragraph when the
|
|
90
|
+
* selection already sits in that block type. */
|
|
91
|
+
function setBlock(editor: any, is: (n: any) => boolean, create: () => any, match?: (n: any) => boolean): void {
|
|
92
|
+
editor.update(() => {
|
|
93
|
+
const sel = $getSelection()
|
|
94
|
+
if (!$isRangeSelection(sel)) return
|
|
95
|
+
const block = $findMatchingParent(sel.anchor.getNode(), is)
|
|
96
|
+
const active = block != null && (match ? match(block) : true)
|
|
97
|
+
$setBlocksType(sel, () => (active ? $createParagraphNode() : create()))
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function applyFormat(editor: any, format: LexicalFormat): void {
|
|
102
|
+
const text = TEXT_FORMAT[format]
|
|
103
|
+
if (text) {
|
|
104
|
+
editor.dispatchCommand(FORMAT_TEXT_COMMAND, text)
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const el = ELEMENT_FORMAT[format]
|
|
109
|
+
if (el) {
|
|
110
|
+
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, el)
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const tag = HEADING_TAG[format]
|
|
115
|
+
if (tag) {
|
|
116
|
+
setBlock(editor, $isHeadingNode, () => $createHeadingNode(tag), (n) => n.getTag() === tag)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
switch (format) {
|
|
121
|
+
case 'paragraph':
|
|
122
|
+
setBlock(editor, () => true, () => $createParagraphNode(), () => false)
|
|
123
|
+
break
|
|
124
|
+
case 'blockquote':
|
|
125
|
+
setBlock(editor, $isQuoteNode, () => $createQuoteNode())
|
|
126
|
+
break
|
|
127
|
+
case 'codeBlock':
|
|
128
|
+
setBlock(editor, $isCodeNode, () => $createCodeNode())
|
|
129
|
+
break
|
|
130
|
+
case 'bulletList':
|
|
131
|
+
editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined)
|
|
132
|
+
break
|
|
133
|
+
case 'orderedList':
|
|
134
|
+
editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined)
|
|
135
|
+
break
|
|
136
|
+
case 'taskList':
|
|
137
|
+
editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined)
|
|
138
|
+
break
|
|
139
|
+
case 'horizontalRule':
|
|
140
|
+
editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined)
|
|
141
|
+
break
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isActive(editor: any, format: LexicalFormat): boolean {
|
|
146
|
+
return editor.read(() => {
|
|
147
|
+
const sel = $getSelection()
|
|
148
|
+
if (sel == null) return false
|
|
149
|
+
|
|
150
|
+
const text = TEXT_FORMAT[format]
|
|
151
|
+
if (text) {
|
|
152
|
+
return $isRangeSelection(sel) && sel.hasFormat(text as any)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const el = ELEMENT_FORMAT[format]
|
|
156
|
+
if (el) {
|
|
157
|
+
const nodes = sel.getNodes()
|
|
158
|
+
return nodes.some((n: any) => {
|
|
159
|
+
const block = $findMatchingParent(n, (p: any) => p.getFormatType != null)
|
|
160
|
+
return block != null && (block as any).getFormatType() === el
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const tag = HEADING_TAG[format]
|
|
165
|
+
if (tag) {
|
|
166
|
+
return sel.getNodes().some((n: any) => {
|
|
167
|
+
const h = $findMatchingParent(n, $isHeadingNode)
|
|
168
|
+
return h?.getTag() === tag
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
switch (format) {
|
|
173
|
+
case 'blockquote':
|
|
174
|
+
return sel.getNodes().some((n: any) => $findMatchingParent(n, $isQuoteNode) != null)
|
|
175
|
+
case 'codeBlock':
|
|
176
|
+
return sel.getNodes().some((n: any) => $findMatchingParent(n, $isCodeNode) != null)
|
|
177
|
+
case 'bulletList':
|
|
178
|
+
case 'orderedList':
|
|
179
|
+
case 'taskList': {
|
|
180
|
+
const want = format === 'bulletList' ? 'bullet' : format === 'orderedList' ? 'number' : 'check'
|
|
181
|
+
return sel.getNodes().some((n: any) => {
|
|
182
|
+
const l = $findMatchingParent(n, $isListNode)
|
|
183
|
+
return l?.getListType() === want
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
case 'link':
|
|
187
|
+
return sel.getNodes().some(
|
|
188
|
+
(n: any) => $isLinkNode(n) || $isLinkNode($findMatchingParent(n, $isLinkNode)),
|
|
189
|
+
)
|
|
190
|
+
case 'horizontalRule':
|
|
191
|
+
return $isNodeSelection(sel) && sel.getNodes().some($isHorizontalRuleNode)
|
|
192
|
+
case 'paragraph':
|
|
193
|
+
return sel.getNodes().some((n: any) =>
|
|
194
|
+
$findMatchingParent(n, (p: any) => p.getType?.() === 'paragraph'),
|
|
195
|
+
)
|
|
196
|
+
default:
|
|
197
|
+
return false
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Plain-text offset of a selection point — DFS accumulating text size.
|
|
203
|
+
* Block separators aren't counted; positions are best-effort parity with
|
|
204
|
+
* the native flat-offset contract, not ProseMirror-style coordinates. */
|
|
205
|
+
function flatOffset(root: any, key: string, offset: number): number {
|
|
206
|
+
const contains = (node: any): boolean =>
|
|
207
|
+
node.getKey() === key || (node.getChildren?.() ?? []).some(contains)
|
|
208
|
+
|
|
209
|
+
let acc = 0
|
|
210
|
+
const visit = (node: any): boolean => {
|
|
211
|
+
if (node.getKey() === key) {
|
|
212
|
+
acc += offset
|
|
213
|
+
return true
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
for (const c of node.getChildren?.() ?? []) {
|
|
217
|
+
if (contains(c)) {
|
|
218
|
+
if (visit(c)) return true
|
|
219
|
+
} else {
|
|
220
|
+
acc += c.getTextContentSize?.() ?? 0
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return false
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
visit(root)
|
|
228
|
+
return acc
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function activeFormats(editor: any): LexicalFormat[] {
|
|
232
|
+
const out: LexicalFormat[] = []
|
|
233
|
+
for (const f of Object.keys(TEXT_FORMAT) as LexicalFormat[]) {
|
|
234
|
+
if (isActive(editor, f)) out.push(f)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
for (const f of ['paragraph', 'heading1', 'heading2', 'heading3', 'heading4', 'heading5', 'heading6', 'blockquote', 'bulletList', 'orderedList', 'taskList', 'codeBlock', 'link'] as LexicalFormat[]) {
|
|
238
|
+
if (isActive(editor, f)) out.push(f)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return out
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Unified editor facade, web half — a fixed-plugin LexicalComposer over
|
|
245
|
+
* the published `@octanejs/lexical` binding. The composition is sealed on
|
|
246
|
+
* purpose: the facade's contract is the native-compatible vocabulary, and
|
|
247
|
+
* apps that need arbitrary plugins use the upstream package directly. */
|
|
248
|
+
export function LexicalEditor(props: LexicalEditorProps) @{
|
|
249
|
+
const editorRef = useRef<any>(null)
|
|
250
|
+
const lastEmitted = useRef<string | undefined>(undefined)
|
|
251
|
+
const lastJSON = useRef<string | undefined>(undefined)
|
|
252
|
+
const lastHTMLInput = useRef(JSON.stringify([props.json !== undefined, props.value]))
|
|
253
|
+
const lastJSONInput = useRef(JSON.stringify(props.json))
|
|
254
|
+
const bound = useRef<LexicalEditorHandle | undefined>(undefined)
|
|
255
|
+
const [exposedHandle, setHandle] = useState<LexicalEditorHandle | null>(null)
|
|
256
|
+
useImperativeHandle<LexicalEditorHandle | null>(props.ref, () => exposedHandle, [exposedHandle])
|
|
257
|
+
|
|
258
|
+
const initialConfig = {
|
|
259
|
+
namespace: 'octane-xplat/lexical',
|
|
260
|
+
nodes: NODES,
|
|
261
|
+
editable: props.editable !== false,
|
|
262
|
+
editorState: props.json != null
|
|
263
|
+
? JSON.stringify(props.json)
|
|
264
|
+
: props.value != null
|
|
265
|
+
? (editor: any) => {
|
|
266
|
+
const dom = new DOMParser().parseFromString(props.value!, 'text/html')
|
|
267
|
+
const nodes = $generateNodesFromDOM(editor, dom)
|
|
268
|
+
$getRoot().append(...nodes)
|
|
269
|
+
}
|
|
270
|
+
: undefined,
|
|
271
|
+
onError: (e: Error) => {
|
|
272
|
+
throw e
|
|
273
|
+
},
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const attach = (editor: any) => {
|
|
277
|
+
if (!editor || bound.current?.native === editor) return
|
|
278
|
+
editorRef.current = editor
|
|
279
|
+
bound.current = {
|
|
280
|
+
getHTML: () => editor.read(() => $generateHtmlFromNodes(editor)),
|
|
281
|
+
setHTML: (html: string) =>
|
|
282
|
+
editor.update(() => {
|
|
283
|
+
const dom = new DOMParser().parseFromString(html, 'text/html')
|
|
284
|
+
const nodes = $generateNodesFromDOM(editor, dom)
|
|
285
|
+
$getRoot().clear().append(...nodes)
|
|
286
|
+
}),
|
|
287
|
+
getJSON: () => editor.getEditorState().toJSON(),
|
|
288
|
+
setJSON: (doc) =>
|
|
289
|
+
editor.setEditorState(editor.parseEditorState(JSON.stringify(doc))),
|
|
290
|
+
apply: (format) => {
|
|
291
|
+
editor.focus()
|
|
292
|
+
applyFormat(editor, format)
|
|
293
|
+
},
|
|
294
|
+
linkTo: (url, anchor) => {
|
|
295
|
+
if (anchor != null && anchor !== '') {
|
|
296
|
+
editor.update(() => {
|
|
297
|
+
const link = $createLinkNode(url)
|
|
298
|
+
link.append($createTextNode(anchor))
|
|
299
|
+
const sel = $getSelection()
|
|
300
|
+
if ($isRangeSelection(sel)) sel.insertNodes([link])
|
|
301
|
+
})
|
|
302
|
+
} else {
|
|
303
|
+
editor.dispatchCommand(TOGGLE_LINK_COMMAND, url)
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
removeLink: () => editor.dispatchCommand(TOGGLE_LINK_COMMAND, null),
|
|
307
|
+
isActive: (format) => isActive(editor, format),
|
|
308
|
+
undo: () => editor.dispatchCommand(UNDO_COMMAND, undefined),
|
|
309
|
+
redo: () => editor.dispatchCommand(REDO_COMMAND, undefined),
|
|
310
|
+
focus: () => editor.focus(),
|
|
311
|
+
blur: () => editor.blur(),
|
|
312
|
+
isFocused: () => {
|
|
313
|
+
const root = editor.getRootElement()
|
|
314
|
+
return root != null && root === document.activeElement
|
|
315
|
+
},
|
|
316
|
+
native: editor,
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
setHandle(bound.current!)
|
|
320
|
+
props.onReady?.()
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// External `value`/`json` changes re-push; editor-emitted docs are skipped
|
|
324
|
+
// (the update already owns the doc).
|
|
325
|
+
useEffect(() => {
|
|
326
|
+
const e = editorRef.current
|
|
327
|
+
if (!e) return
|
|
328
|
+
const stamp = JSON.stringify([props.json !== undefined, props.value])
|
|
329
|
+
if (stamp === lastHTMLInput.current) return
|
|
330
|
+
lastHTMLInput.current = stamp
|
|
331
|
+
if (props.json !== undefined || props.value === undefined || props.value === lastEmitted.current) return
|
|
332
|
+
bound.current?.setHTML(props.value)
|
|
333
|
+
}, [exposedHandle, props.value, props.json])
|
|
334
|
+
|
|
335
|
+
useEffect(() => {
|
|
336
|
+
const e = editorRef.current
|
|
337
|
+
if (!e) return
|
|
338
|
+
const stamp = JSON.stringify(props.json)
|
|
339
|
+
if (stamp === lastJSONInput.current) return
|
|
340
|
+
lastJSONInput.current = stamp
|
|
341
|
+
if (props.json === undefined || stamp === lastJSON.current) return
|
|
342
|
+
bound.current?.setJSON(props.json)
|
|
343
|
+
}, [exposedHandle, props.json])
|
|
344
|
+
|
|
345
|
+
useEffect(() => {
|
|
346
|
+
editorRef.current?.setEditable(props.editable !== false)
|
|
347
|
+
}, [exposedHandle, props.editable])
|
|
348
|
+
|
|
349
|
+
const emitChange = (state: any, editor: any) => {
|
|
350
|
+
const html = state.read(() => $generateHtmlFromNodes(editor))
|
|
351
|
+
lastEmitted.current = html
|
|
352
|
+
props.onChange?.(html)
|
|
353
|
+
const doc = state.toJSON()
|
|
354
|
+
lastJSON.current = JSON.stringify(doc)
|
|
355
|
+
props.onJSONChange?.(doc)
|
|
356
|
+
state.read(() => {
|
|
357
|
+
const sel = $getSelection()
|
|
358
|
+
if ($isRangeSelection(sel) && props.onSelectionChange) {
|
|
359
|
+
const root = $getRoot()
|
|
360
|
+
props.onSelectionChange({
|
|
361
|
+
start: flatOffset(root, sel.anchor.key, sel.anchor.offset),
|
|
362
|
+
end: flatOffset(root, sel.focus.key, sel.focus.offset),
|
|
363
|
+
active: activeFormats(editor),
|
|
364
|
+
})
|
|
365
|
+
}
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
<div className={props.className} style={{ position: 'relative', ...props.style }}>
|
|
370
|
+
<LexicalComposer initialConfig={initialConfig}>
|
|
371
|
+
<RichTextPlugin
|
|
372
|
+
contentEditable={
|
|
373
|
+
<ContentEditable
|
|
374
|
+
id={props.id}
|
|
375
|
+
aria-placeholder={props.placeholder ?? ''}
|
|
376
|
+
placeholder={null}
|
|
377
|
+
className="vx-lexical-input"
|
|
378
|
+
style={{ outline: 'none' }}
|
|
379
|
+
/>
|
|
380
|
+
}
|
|
381
|
+
placeholder={
|
|
382
|
+
props.placeholder ? (
|
|
383
|
+
<div
|
|
384
|
+
className="vx-lexical-placeholder"
|
|
385
|
+
style={{
|
|
386
|
+
position: 'absolute',
|
|
387
|
+
inset: '8px auto auto 10px',
|
|
388
|
+
pointerEvents: 'none',
|
|
389
|
+
opacity: 0.5,
|
|
390
|
+
}}
|
|
391
|
+
>
|
|
392
|
+
{props.placeholder}
|
|
393
|
+
</div>
|
|
394
|
+
) : null
|
|
395
|
+
}
|
|
396
|
+
ErrorBoundary={LexicalErrorBoundary}
|
|
397
|
+
/>
|
|
398
|
+
<HistoryPlugin />
|
|
399
|
+
<ListPlugin />
|
|
400
|
+
<LinkPlugin />
|
|
401
|
+
{props.autofocus ? <AutoFocusPlugin /> : null}
|
|
402
|
+
<OnChangePlugin onChange={emitChange} />
|
|
403
|
+
<EditorRefPlugin editorRef={attach} />
|
|
404
|
+
</LexicalComposer>
|
|
405
|
+
</div>
|
|
406
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { createRoot } from 'octane'
|
|
2
|
+
import { LexicalEditor } from './LexicalEditor.web.tsrx'
|
|
3
|
+
import { installEditorDocument } from '@octane-xplat/richtext/browser-transport'
|
|
4
|
+
const root = createRoot(document.getElementById('root')!)
|
|
5
|
+
installEditorDocument((props) => root.render(<LexicalEditor {...props} />))
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { LexicalEditor } from './Editor.macos'
|
|
2
|
+
/** The AppKit/WKWebView backend is implemented. */
|
|
3
|
+
export const supported = true
|
|
4
|
+
export type * from './types'
|
|
5
|
+
|
|
6
|
+
/** The local bundle includes its JSON model; wait for onReady before reading a document. */
|
|
7
|
+
export async function ensureJSONBridge(): Promise<boolean> {
|
|
8
|
+
return true
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function jsonBridgeReady(): boolean {
|
|
12
|
+
return true
|
|
13
|
+
}
|
package/src/index.ts
ADDED
package/src/index.web.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { LexicalEditor, supported } from './LexicalEditor.web.tsrx'
|
|
2
|
+
export type * from './types'
|
|
3
|
+
|
|
4
|
+
/** Web needs no lazy bridge — lexical is in the bundle already. Kept for
|
|
5
|
+
* call-site parity with the native entry. */
|
|
6
|
+
export function ensureJSONBridge(): Promise<boolean> {
|
|
7
|
+
return Promise.resolve(true)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function jsonBridgeReady(): boolean {
|
|
11
|
+
return true
|
|
12
|
+
}
|