@octane-xplat/tiptap 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.
@@ -0,0 +1,109 @@
1
+ /** @jsxImportSource @nativescript-community/octane */
2
+ import { useState, useImperativeHandle, useCallback, useEffect, useRef } from 'octane'
3
+ import { RichTextEditor, supported as leafSupported } from '@octane-xplat/richtext'
4
+ import type { RichTextEditorHandle } from '@octane-xplat/richtext'
5
+ import type { TiptapEditorHandle, TiptapEditorProps } from './types'
6
+ import { ensureJSONBridge, htmlToJSON, jsonBridgeReady, jsonToHTML } from './json-bridge'
7
+
8
+ export const supported = leafSupported
9
+
10
+ /** Unified editor facade, native half. Rendering/editing run through the
11
+ * Aztec leaf; the tiptap document model (schema, generateJSON,
12
+ * renderToHTMLString) handles JSON interchange on top of it. */
13
+ export function TiptapEditor(props: TiptapEditorProps) @{
14
+ const inner = useRef<RichTextEditorHandle | null>(null)
15
+ const [exposedHandle, setHandle] = useState<TiptapEditorHandle | null>(null)
16
+ useImperativeHandle<TiptapEditorHandle | null>(props.ref, () => exposedHandle, [exposedHandle])
17
+ const jsonReady = useRef(false)
18
+ const lastJSON = useRef<string | undefined>(undefined)
19
+
20
+ const bindHost = useCallback((h: RichTextEditorHandle | null) => {
21
+ inner.current = h
22
+
23
+ setHandle((previous) => previous?.native === h?.native ? previous : h ? ({
24
+ getHTML: () => h.getHTML(),
25
+ setHTML: (html: string) => h.setHTML(html),
26
+ getJSON: () => (jsonBridgeReady() ? htmlToJSON(h.getHTML()) : null),
27
+ setJSON: (doc) => {
28
+ const html = jsonBridgeReady() ? jsonToHTML(doc) : null
29
+ if (html != null) {
30
+ lastJSON.current = JSON.stringify(doc)
31
+ h.setHTML(html)
32
+ }
33
+ },
34
+ apply: (format) => h.apply(format),
35
+ linkTo: (url, anchor) => h.linkTo(url, anchor),
36
+ removeLink: () => h.removeLink(),
37
+ isActive: (format) => h.isActive(format),
38
+ undo: () => h.undo(),
39
+ redo: () => h.redo(),
40
+ focus: () => h.focus(),
41
+ blur: () => h.blur(),
42
+ isFocused: () => h.isFocused(),
43
+ native: h.native,
44
+ } satisfies TiptapEditorHandle) : null)
45
+ }, [])
46
+
47
+ // The bridge loads once per process — dynamic imports of the DOM-free
48
+ // tiptap slices, plus the zeed-dom parse shim.
49
+ useEffect(() => {
50
+ ensureJSONBridge().then((ok) => {
51
+ jsonReady.current = ok
52
+ props.onJSONReady?.(ok)
53
+ })
54
+ }, [])
55
+
56
+ // `json` prop → editor content, when the bridge is up and the doc wasn't
57
+ // emitted by the editor itself.
58
+ useEffect(() => {
59
+ if (!props.json || !jsonBridgeReady() || !inner.current) return
60
+ const stamp = JSON.stringify(props.json)
61
+ if (stamp === lastJSON.current) return
62
+ const html = jsonToHTML(props.json)
63
+ if (html != null) {
64
+ lastJSON.current = stamp
65
+ inner.current.setHTML(html)
66
+ }
67
+ }, [props.json])
68
+
69
+ const emitJSON = () => {
70
+ if (!inner.current || !jsonBridgeReady()) return
71
+ const doc = htmlToJSON(inner.current.getHTML())
72
+ if (doc) {
73
+ lastJSON.current = JSON.stringify(doc)
74
+ props.onJSONChange?.(doc)
75
+ }
76
+ }
77
+
78
+ <RichTextEditor
79
+ id={props.id}
80
+ className={props.className}
81
+ style={props.style}
82
+ value={props.value}
83
+ placeholder={props.placeholder}
84
+ editable={props.editable}
85
+ autofocus={props.autofocus}
86
+ onReady={props.onReady}
87
+ onChange={(html) => {
88
+ props.onChange?.(html)
89
+ emitJSON()
90
+ }}
91
+ onSelectionChange={props.onSelectionChange}
92
+ onFocus={props.onFocus}
93
+ onBlur={props.onBlur}
94
+ ref={bindHost}
95
+ android={props.android}
96
+ ios={props.ios}
97
+ row={props.row}
98
+ col={props.col}
99
+ rowSpan={props.rowSpan}
100
+ colSpan={props.colSpan}
101
+ dock={props.dock}
102
+ left={props.left}
103
+ top={props.top}
104
+ flexGrow={props.flexGrow}
105
+ flexShrink={props.flexShrink}
106
+ alignSelf={props.alignSelf}
107
+ order={props.order}
108
+ />;
109
+ }
@@ -0,0 +1,155 @@
1
+ import { useImperativeHandle, useEffect, useRef } from 'octane'
2
+ import { EditorContent, useEditor } from '@octanejs/tiptap'
3
+ import StarterKit from '@tiptap/starter-kit'
4
+ import type { TiptapEditorHandle, TiptapEditorProps, TiptapFormat } from './types'
5
+
6
+ export const supported = true
7
+
8
+ type WebFormat = Exclude<TiptapFormat, 'link' | 'taskList' | 'highlight' | 'subscript' | 'superscript' | 'alignLeft' | 'alignCenter' | 'alignRight'>
9
+
10
+ const COMMAND: Record<WebFormat, (chain: any) => any> = {
11
+ bold: (c) => c.toggleBold(),
12
+ italic: (c) => c.toggleItalic(),
13
+ underline: (c) => c.toggleUnderline(),
14
+ strikethrough: (c) => c.toggleStrike(),
15
+ code: (c) => c.toggleCode(),
16
+ blockquote: (c) => c.toggleBlockquote(),
17
+ bulletList: (c) => c.toggleBulletList(),
18
+ orderedList: (c) => c.toggleOrderedList(),
19
+ codeBlock: (c) => c.toggleCodeBlock(),
20
+ horizontalRule: (c) => c.setHorizontalRule(),
21
+ paragraph: (c) => c.setParagraph(),
22
+ heading1: (c) => c.toggleHeading({ level: 1 }),
23
+ heading2: (c) => c.toggleHeading({ level: 2 }),
24
+ heading3: (c) => c.toggleHeading({ level: 3 }),
25
+ heading4: (c) => c.toggleHeading({ level: 4 }),
26
+ heading5: (c) => c.toggleHeading({ level: 5 }),
27
+ heading6: (c) => c.toggleHeading({ level: 6 }),
28
+ }
29
+
30
+ const ACTIVE_OF: [TiptapFormat, string, any?][] = [
31
+ ['bold', 'bold'],
32
+ ['italic', 'italic'],
33
+ ['underline', 'underline'],
34
+ ['strikethrough', 'strike'],
35
+ ['code', 'code'],
36
+ ['blockquote', 'blockquote'],
37
+ ['bulletList', 'bulletList'],
38
+ ['orderedList', 'orderedList'],
39
+ ['codeBlock', 'codeBlock'],
40
+ ['link', 'link'],
41
+ ['heading1', 'heading', { level: 1 }],
42
+ ['heading2', 'heading', { level: 2 }],
43
+ ['heading3', 'heading', { level: 3 }],
44
+ ['heading4', 'heading', { level: 4 }],
45
+ ['heading5', 'heading', { level: 5 }],
46
+ ['heading6', 'heading', { level: 6 }],
47
+ ['paragraph', 'paragraph'],
48
+ ]
49
+
50
+ function activeFormats(editor: any): TiptapFormat[] {
51
+ const out: TiptapFormat[] = []
52
+ for (const [name, kind, attrs] of ACTIVE_OF) {
53
+ if (editor.isActive(kind, attrs)) out.push(name)
54
+ }
55
+
56
+ return out
57
+ }
58
+
59
+ /** Unified editor facade, web half — a thin adapter over the published
60
+ * `@octanejs/tiptap` binding (useEditor + EditorContent + StarterKit). */
61
+ export function TiptapEditor(props: TiptapEditorProps) @{
62
+ const lastEmitted = useRef<string | undefined>(undefined)
63
+ const lastJSON = useRef<string | undefined>(undefined)
64
+ const lastHTMLInput = useRef(JSON.stringify([props.json !== undefined, props.value]))
65
+ const lastJSONInput = useRef(JSON.stringify(props.json))
66
+ const bound = useRef<TiptapEditorHandle | undefined>(undefined)
67
+
68
+ const editor = useEditor({
69
+ extensions: [StarterKit],
70
+ content: (props.json ?? props.value ?? '') as any,
71
+ editable: props.editable !== false,
72
+ autofocus: props.autofocus ?? false,
73
+ onCreate: () => props.onReady?.(),
74
+ onUpdate: ({ editor: e }: any) => {
75
+ const html = e.getHTML()
76
+ lastEmitted.current = html
77
+ props.onChange?.(html)
78
+ const doc = e.getJSON()
79
+ lastJSON.current = JSON.stringify(doc)
80
+ props.onJSONChange?.(doc)
81
+ },
82
+ onFocus: () => props.onFocus?.(),
83
+ onBlur: () => props.onBlur?.(),
84
+ onSelectionUpdate: ({ editor: e }: any) =>
85
+ props.onSelectionChange?.({
86
+ start: e.state.selection.from,
87
+ end: e.state.selection.to,
88
+ active: activeFormats(e),
89
+ }),
90
+ })
91
+
92
+ // External `value` changes push setContent; editor-emitted HTML is skipped
93
+ // (the transaction already owns the doc).
94
+ useEffect(() => {
95
+ if (!editor) return
96
+ const stamp = JSON.stringify([props.json !== undefined, props.value])
97
+ if (stamp === lastHTMLInput.current) return
98
+ lastHTMLInput.current = stamp
99
+ if (props.value === undefined || props.json !== undefined) return
100
+ if (props.value === lastEmitted.current) return
101
+ editor.commands.setContent(props.value)
102
+ }, [editor, props.value, props.json])
103
+
104
+ useEffect(() => {
105
+ if (!editor) return
106
+ const stamp = JSON.stringify(props.json)
107
+ if (stamp === lastJSONInput.current) return
108
+ lastJSONInput.current = stamp
109
+ if (props.json === undefined || stamp === lastJSON.current) return
110
+ editor.commands.setContent(props.json as any)
111
+ }, [editor, props.json])
112
+
113
+ useEffect(() => {
114
+ if (!editor) return
115
+ editor.setEditable(props.editable !== false)
116
+ }, [editor, props.editable])
117
+
118
+ // The handle rebinds whenever the editor instance appears — stable
119
+ // afterward (useEditor keeps the instance identity).
120
+ useImperativeHandle<TiptapEditorHandle | null>(props.ref, () => {
121
+ if (!editor) return null
122
+ const e: any = editor
123
+ bound.current = {
124
+ getHTML: () => e.getHTML(),
125
+ setHTML: (html: string) => e.commands.setContent(html),
126
+ getJSON: () => e.getJSON(),
127
+ setJSON: (doc) => e.commands.setContent(doc),
128
+ apply: (format) => {
129
+ const run = COMMAND[format as WebFormat]
130
+ if (run) run(e.chain().focus()).run()
131
+ },
132
+ linkTo: (url, anchor) => {
133
+ if (anchor != null && anchor !== '') {
134
+ e.chain().focus().insertContent({ type: 'text', text: anchor, marks: [{ type: 'link', attrs: { href: url } }] }).run()
135
+ } else {
136
+ e.chain().focus().extendMarkRange('link').setLink({ href: url }).run()
137
+ }
138
+ },
139
+ removeLink: () => e.chain().focus().unsetLink().run(),
140
+ isActive: (format) => activeFormats(e).includes(format),
141
+ undo: () => e.commands.undo(),
142
+ redo: () => e.commands.redo(),
143
+ focus: () => e.commands.focus(),
144
+ blur: () => e.commands.blur(),
145
+ isFocused: () => e.isFocused,
146
+ native: e,
147
+ }
148
+
149
+ return bound.current
150
+ }, [editor])
151
+
152
+ <div className={['vx-richtext', props.className]} style={props.style}>
153
+ <EditorContent editor={editor} />
154
+ </div>
155
+ }
@@ -0,0 +1,5 @@
1
+ import { createRoot } from 'octane'
2
+ import { TiptapEditor } from './TiptapEditor.web.tsrx'
3
+ import { installEditorDocument } from '@octane-xplat/richtext/browser-transport'
4
+ const root = createRoot(document.getElementById('root')!)
5
+ installEditorDocument((props) => root.render(<TiptapEditor {...props} />))
@@ -0,0 +1,13 @@
1
+ export { TiptapEditor } 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
@@ -0,0 +1,3 @@
1
+ export { TiptapEditor, supported } from './TiptapEditor.tsrx'
2
+ export { ensureJSONBridge, jsonBridgeReady } from './json-bridge'
3
+ export type * from './types'
@@ -0,0 +1,12 @@
1
+ export { TiptapEditor, supported } from './TiptapEditor.web.tsrx'
2
+ export type * from './types'
3
+
4
+ /** Web needs no lazy bridge — tiptap 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
+ }
@@ -0,0 +1,124 @@
1
+ /** Lazy tiptap document-model bridge for native runtimes. The DOM-free
2
+ * slices (@tiptap/core schema + generateJSON, @tiptap/static-renderer,
3
+ * zeed-dom as the parser host) load on demand — on a runtime that can't
4
+ * host them the bridge settles to unavailable and callers get null/no-op.
5
+ *
6
+ * Import behavior was proven by the demos probe (see packages/demos
7
+ * TiptapProbe): all four modules load cleanly on both NS engines; the
8
+ * failure mode left is a future version or bundler regression, which the
9
+ * catch below degrades instead of crashing. */
10
+
11
+ import type { TiptapJSON } from './types'
12
+
13
+ interface Bridge {
14
+ generateJSON: (html: string, extensions: any[]) => any
15
+ renderToHTMLString: (options: { extensions: any[]; content: any }) => string
16
+ extensions: any[]
17
+ }
18
+
19
+ let bridge: Bridge | null | undefined
20
+
21
+ /** zeed-dom's classList is a plain {contains,add,remove} object; tiptap's
22
+ * codeBlock parseHTML spreads it. Wrap the VElement getter so it hands back
23
+ * an iterable array that keeps the mutation methods. */
24
+ function patchClassList(VElement: any): void {
25
+ const proto = VElement?.prototype
26
+ if (!proto) {
27
+ throw new Error('VElement.prototype unavailable')
28
+ }
29
+
30
+ const desc = Object.getOwnPropertyDescriptor(proto, 'classList')
31
+ if (!desc?.get) {
32
+ throw new Error('classList is not a prototype getter')
33
+ }
34
+
35
+ if ((desc.get as any).__xplatPatched) {
36
+ return
37
+ }
38
+
39
+ const patched = function (this: any) {
40
+ const cl = desc.get!.call(this)
41
+ if (cl && typeof cl[Symbol.iterator] !== 'function') {
42
+ const tokens = String(this.getAttribute('class') ?? '')
43
+ .split(/\s+/)
44
+ .filter(Boolean)
45
+
46
+ return Object.assign(tokens, cl)
47
+ }
48
+
49
+ return cl
50
+ }
51
+
52
+ ;(patched as any).__xplatPatched = true
53
+ Object.defineProperty(proto, 'classList', {
54
+ configurable: true,
55
+ get: patched,
56
+ set: desc.set,
57
+ })
58
+ }
59
+
60
+ /** Install the minimal parse globals generateJSON's elementFromString
61
+ * reads. Only installs when the host lacks them, so a real DOM (web
62
+ * target) is never clobbered. */
63
+ function installDomShim(zeed: any): void {
64
+ const g = globalThis as any
65
+ if (g.window?.DOMParser) {
66
+ return
67
+ }
68
+
69
+ class ZeedDOMParser {
70
+ parseFromString(html: string) {
71
+ const doc = zeed.createHTMLDocument()
72
+ doc.body.innerHTML = html
73
+ return doc
74
+ }
75
+ }
76
+
77
+ g.window = { DOMParser: ZeedDOMParser }
78
+ g.document = zeed.createHTMLDocument()
79
+ }
80
+
81
+ export async function ensureJSONBridge(): Promise<boolean> {
82
+ if (bridge !== undefined) {
83
+ return bridge !== null
84
+ }
85
+
86
+ try {
87
+ const [core, sk, sr, zeed] = await Promise.all([
88
+ import('@tiptap/core'),
89
+ import('@tiptap/starter-kit'),
90
+ import('@tiptap/static-renderer'),
91
+ import('zeed-dom'),
92
+ ])
93
+
94
+ patchClassList(zeed.VElement)
95
+ installDomShim(zeed)
96
+ const StarterKit = (sk as any).default ?? (sk as any).StarterKit
97
+
98
+ bridge = {
99
+ generateJSON: core.generateJSON,
100
+ renderToHTMLString: sr.renderToHTMLString,
101
+ extensions: [StarterKit],
102
+ }
103
+
104
+ return true
105
+ } catch {
106
+ bridge = null
107
+ return false
108
+ }
109
+ }
110
+
111
+ /** HTML → doc JSON through tiptap's own parseHTML rules. Null when the
112
+ * bridge hasn't loaded (call ensureJSONBridge first / check `ready`). */
113
+ export function htmlToJSON(html: string): TiptapJSON | null {
114
+ return (bridge?.generateJSON(html, bridge.extensions) as TiptapJSON | undefined) ?? null
115
+ }
116
+
117
+ /** Doc JSON → HTML via tiptap's own renderHTML rules (no DOM). */
118
+ export function jsonToHTML(doc: TiptapJSON): string | null {
119
+ return bridge?.renderToHTMLString({ extensions: bridge.extensions, content: doc }) ?? null
120
+ }
121
+
122
+ export function jsonBridgeReady(): boolean {
123
+ return bridge != null
124
+ }
package/src/types.ts ADDED
@@ -0,0 +1,102 @@
1
+ import type { Octane } from 'octane/jsx-runtime'
2
+
3
+ /** Format vocabulary shared by the web (tiptap StarterKit) and native
4
+ * (Aztec) backends. On web, starter-kit covers everything except
5
+ * taskList/highlight/subscript/superscript/align* — those no-op there and
6
+ * map to Aztec formats on Android. */
7
+ export type TiptapFormat =
8
+ | 'bold'
9
+ | 'italic'
10
+ | 'underline'
11
+ | 'strikethrough'
12
+ | 'code'
13
+ | 'blockquote'
14
+ | 'bulletList'
15
+ | 'orderedList'
16
+ | 'taskList'
17
+ | 'codeBlock'
18
+ | 'horizontalRule'
19
+ | 'highlight'
20
+ | 'subscript'
21
+ | 'superscript'
22
+ | 'paragraph'
23
+ | 'heading1'
24
+ | 'heading2'
25
+ | 'heading3'
26
+ | 'heading4'
27
+ | 'heading5'
28
+ | 'heading6'
29
+ | 'alignLeft'
30
+ | 'alignCenter'
31
+ | 'alignRight'
32
+ | 'link'
33
+
34
+ /** Tiptap document JSON (`{ type: 'doc', content: [...] }`). Opaque here —
35
+ * callers round-trip through tiptap's own schema on both platforms. */
36
+ export type TiptapJSON = { type: string; content?: unknown[] } & Record<string, unknown>
37
+
38
+ export interface TiptapEditorHandle {
39
+ /** AppKit: returns the latest WebKit snapshot; commands are asynchronous. */
40
+ getHTML(): string
41
+ /** Web: `commands.setContent` (undo-aware). Native: Aztec `fromHtml` —
42
+ * resets undo history. */
43
+ setHTML(html: string): void
44
+ /** Doc JSON, or null while the native schema modules are still loading /
45
+ * on a runtime that can't host them (`json` flag). Web is always ready. */
46
+ /** AppKit: latest live engine JSON snapshot; null before onReady. */
47
+ getJSON(): TiptapJSON | null
48
+ setJSON(doc: TiptapJSON): void
49
+ apply(format: TiptapFormat): void
50
+ linkTo(url: string, anchor?: string): void
51
+ removeLink(): void
52
+ isActive(format: TiptapFormat): boolean
53
+ undo(): void
54
+ redo(): void
55
+ focus(): void
56
+ blur(): void
57
+ isFocused(): boolean
58
+ /** The platform surface — a tiptap `Editor` on web, `AztecText` on
59
+ * Android. */
60
+ /** AppKit: the XplatEditorHost transport, not the browser engine instance. */
61
+ native: any
62
+ }
63
+
64
+ export interface TiptapEditorProps {
65
+ id?: string
66
+ className?: string
67
+ style?: any
68
+ /** Document HTML. Seed at mount; external changes re-push (web: via
69
+ * setContent, preserving the diff loop the editor emitted is skipped). */
70
+ value?: string
71
+ /** Document JSON. Takes precedence over `value` when both arrive. On
72
+ * native it waits for the schema modules (see `onJSONReady`). */
73
+ json?: TiptapJSON
74
+ placeholder?: string
75
+ editable?: boolean
76
+ autofocus?: boolean
77
+ onReady?: () => void
78
+ /** Fires once when the native JSON bridge settles — `true` if the
79
+ * DOM-free tiptap modules loaded, `false` on runtimes that can't host
80
+ * them (JSON calls then no-op / return null). Never fires on web. */
81
+ /** AppKit: fires true with mounted engine readiness; no headless conversion bridge. */
82
+ onJSONReady?: (ready: boolean) => void
83
+ onChange?: (html: string) => void
84
+ onJSONChange?: (doc: TiptapJSON) => void
85
+ onSelectionChange?: (event: { start: number; end: number; active: TiptapFormat[] }) => void
86
+ onFocus?: () => void
87
+ onBlur?: () => void
88
+ ref?: Octane.Ref<TiptapEditorHandle>
89
+ android?: Record<string, any>
90
+ ios?: Record<string, any>
91
+ row?: number | string
92
+ col?: number | string
93
+ rowSpan?: number
94
+ colSpan?: number
95
+ dock?: 'left' | 'top' | 'right' | 'bottom'
96
+ left?: number
97
+ top?: number
98
+ flexGrow?: number
99
+ flexShrink?: number
100
+ alignSelf?: 'auto' | 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline'
101
+ order?: number
102
+ }
@@ -0,0 +1,8 @@
1
+ import type { UniversalComponent } from 'octane/universal'
2
+ import type { TiptapEditorProps, TiptapJSON } from './types'
3
+
4
+ export declare const TiptapEditor: UniversalComponent<TiptapEditorProps>
5
+ export declare const supported: boolean
6
+ export declare function ensureJSONBridge(): Promise<boolean>
7
+ export declare function jsonBridgeReady(): boolean
8
+ export type * from './types'
@@ -0,0 +1,7 @@
1
+ import type { UniversalComponent } from 'octane/universal'
2
+ import type { TiptapEditorProps } from '../src/types'
3
+ export declare const TiptapEditor: UniversalComponent<TiptapEditorProps>
4
+ export declare const supported: boolean
5
+ export type * from '../src/types'
6
+ export declare function ensureJSONBridge(): Promise<boolean>
7
+ export declare function jsonBridgeReady(): boolean
@@ -0,0 +1,80 @@
1
+ import type { Octane } from 'octane/jsx-runtime';
2
+ /** Format vocabulary shared by the web (tiptap StarterKit) and native
3
+ * (Aztec) backends. On web, starter-kit covers everything except
4
+ * taskList/highlight/subscript/superscript/align* — those no-op there and
5
+ * map to Aztec formats on Android. */
6
+ export type TiptapFormat = 'bold' | 'italic' | 'underline' | 'strikethrough' | 'code' | 'blockquote' | 'bulletList' | 'orderedList' | 'taskList' | 'codeBlock' | 'horizontalRule' | 'highlight' | 'subscript' | 'superscript' | 'paragraph' | 'heading1' | 'heading2' | 'heading3' | 'heading4' | 'heading5' | 'heading6' | 'alignLeft' | 'alignCenter' | 'alignRight' | 'link';
7
+ /** Tiptap document JSON (`{ type: 'doc', content: [...] }`). Opaque here —
8
+ * callers round-trip through tiptap's own schema on both platforms. */
9
+ export type TiptapJSON = {
10
+ type: string;
11
+ content?: unknown[];
12
+ } & Record<string, unknown>;
13
+ export interface TiptapEditorHandle {
14
+ /** AppKit: returns the latest WebKit snapshot; commands are asynchronous. */
15
+ getHTML(): string;
16
+ /** Web: `commands.setContent` (undo-aware). Native: Aztec `fromHtml` —
17
+ * resets undo history. */
18
+ setHTML(html: string): void;
19
+ /** Doc JSON, or null while the native schema modules are still loading /
20
+ * on a runtime that can't host them (`json` flag). Web is always ready. */
21
+ /** AppKit: latest live engine JSON snapshot; null before onReady. */
22
+ getJSON(): TiptapJSON | null;
23
+ setJSON(doc: TiptapJSON): void;
24
+ apply(format: TiptapFormat): void;
25
+ linkTo(url: string, anchor?: string): void;
26
+ removeLink(): void;
27
+ isActive(format: TiptapFormat): boolean;
28
+ undo(): void;
29
+ redo(): void;
30
+ focus(): void;
31
+ blur(): void;
32
+ isFocused(): boolean;
33
+ /** The platform surface — a tiptap `Editor` on web, `AztecText` on
34
+ * Android. */
35
+ /** AppKit: the XplatEditorHost transport, not the browser engine instance. */
36
+ native: any;
37
+ }
38
+ export interface TiptapEditorProps {
39
+ id?: string;
40
+ className?: string;
41
+ style?: any;
42
+ /** Document HTML. Seed at mount; external changes re-push (web: via
43
+ * setContent, preserving the diff loop the editor emitted is skipped). */
44
+ value?: string;
45
+ /** Document JSON. Takes precedence over `value` when both arrive. On
46
+ * native it waits for the schema modules (see `onJSONReady`). */
47
+ json?: TiptapJSON;
48
+ placeholder?: string;
49
+ editable?: boolean;
50
+ autofocus?: boolean;
51
+ onReady?: () => void;
52
+ /** Fires once when the native JSON bridge settles — `true` if the
53
+ * DOM-free tiptap modules loaded, `false` on runtimes that can't host
54
+ * them (JSON calls then no-op / return null). Never fires on web. */
55
+ /** AppKit: fires true with mounted engine readiness; no headless conversion bridge. */
56
+ onJSONReady?: (ready: boolean) => void;
57
+ onChange?: (html: string) => void;
58
+ onJSONChange?: (doc: TiptapJSON) => void;
59
+ onSelectionChange?: (event: {
60
+ start: number;
61
+ end: number;
62
+ active: TiptapFormat[];
63
+ }) => void;
64
+ onFocus?: () => void;
65
+ onBlur?: () => void;
66
+ ref?: Octane.Ref<TiptapEditorHandle>;
67
+ android?: Record<string, any>;
68
+ ios?: Record<string, any>;
69
+ row?: number | string;
70
+ col?: number | string;
71
+ rowSpan?: number;
72
+ colSpan?: number;
73
+ dock?: 'left' | 'top' | 'right' | 'bottom';
74
+ left?: number;
75
+ top?: number;
76
+ flexGrow?: number;
77
+ flexShrink?: number;
78
+ alignSelf?: 'auto' | 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline';
79
+ order?: number;
80
+ }
package/index.d.ts DELETED
@@ -1 +0,0 @@
1
- export {}
package/index.js DELETED
@@ -1,2 +0,0 @@
1
- // Reservation stub; implementation will arrive in a later release.
2
- module.exports = {}