@octane-xplat/tiptap 0.0.1 → 0.9.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 ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Alec Larson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,26 @@
1
- # @octane-xplat/tiptap
1
+ # `@octane-xplat/tiptap`
2
2
 
3
- Version 0.0.1 is a reservation stub so the automated release can publish this package name. It has no supported API. Use a later release for the implementation.
3
+ The unified rich-text facade: one `TiptapEditor` component across web and
4
+ native. Web renders a real tiptap `Editor` through `@octanejs/tiptap`'s
5
+ `EditorContent`; native renders the `@octane-xplat/richtext` leaf (WordPress
6
+ Aztec on Android, iOS stub) and layers tiptap document JSON on top through
7
+ the DOM-free ProseMirror slices (`@tiptap/pm`, `@tiptap/static-renderer`)
8
+ with a `zeed-dom` parse shim. No DOM-bound tiptap code (`EditorView`) ever
9
+ enters a native bundle — divergence lives at the file-suffix boundary.
10
+
11
+ ```ts
12
+ import { TiptapEditor, supported } from '@octane-xplat/tiptap'
13
+ ```
14
+
15
+ `value`/`onChange` exchange document HTML. `getJSON`/`setJSON` exchange
16
+ tiptap document JSON: synchronous on web, bridged on native after
17
+ `onJSONReady(true)` — the lazy bridge reports `false` (and `getJSON()`
18
+ returns `null`) on runtimes that cannot host the schema modules. On native,
19
+ HTML is the canonical interchange format; JSON output is a best-effort
20
+ mapping of Aztec's flat span list onto ProseMirror's tree.
21
+
22
+ The tiptap family pins to `3.28.0` to match `@octanejs/tiptap@0.0.51` —
23
+ extension `^` ranges float ahead of core and break (`getPreviousBlockSibling`),
24
+ so consumers should keep the workspace override. See
25
+ [`docs/rich-text.md`](../../docs/rich-text.md) for the shared contract,
26
+ format vocabulary, and known limits.
@@ -0,0 +1,3 @@
1
+ import { ensureJSONBridge, jsonBridgeReady } from "./index2.js";
2
+ import { TiptapEditor, supported } from "./index3.js";
3
+ export { TiptapEditor, ensureJSONBridge, jsonBridgeReady, supported };
@@ -0,0 +1,82 @@
1
+ //#region src/json-bridge.ts
2
+ var bridge;
3
+ /** zeed-dom's classList is a plain {contains,add,remove} object; tiptap's
4
+ * codeBlock parseHTML spreads it. Wrap the VElement getter so it hands back
5
+ * an iterable array that keeps the mutation methods. */
6
+ function patchClassList(VElement) {
7
+ const proto = VElement?.prototype;
8
+ if (!proto) throw new Error("VElement.prototype unavailable");
9
+ const desc = Object.getOwnPropertyDescriptor(proto, "classList");
10
+ if (!desc?.get) throw new Error("classList is not a prototype getter");
11
+ if (desc.get.__xplatPatched) return;
12
+ const patched = function() {
13
+ const cl = desc.get.call(this);
14
+ if (cl && typeof cl[Symbol.iterator] !== "function") {
15
+ const tokens = String(this.getAttribute("class") ?? "").split(/\s+/).filter(Boolean);
16
+ return Object.assign(tokens, cl);
17
+ }
18
+ return cl;
19
+ };
20
+ patched.__xplatPatched = true;
21
+ Object.defineProperty(proto, "classList", {
22
+ configurable: true,
23
+ get: patched,
24
+ set: desc.set
25
+ });
26
+ }
27
+ /** Install the minimal parse globals generateJSON's elementFromString
28
+ * reads. Only installs when the host lacks them, so a real DOM (web
29
+ * target) is never clobbered. */
30
+ function installDomShim(zeed) {
31
+ const g = globalThis;
32
+ if (g.window?.DOMParser) return;
33
+ class ZeedDOMParser {
34
+ parseFromString(html) {
35
+ const doc = zeed.createHTMLDocument();
36
+ doc.body.innerHTML = html;
37
+ return doc;
38
+ }
39
+ }
40
+ g.window = { DOMParser: ZeedDOMParser };
41
+ g.document = zeed.createHTMLDocument();
42
+ }
43
+ async function ensureJSONBridge() {
44
+ if (bridge !== void 0) return bridge !== null;
45
+ try {
46
+ const [core, sk, sr, zeed] = await Promise.all([
47
+ import("@tiptap/core"),
48
+ import("@tiptap/starter-kit"),
49
+ import("@tiptap/static-renderer"),
50
+ import("zeed-dom")
51
+ ]);
52
+ patchClassList(zeed.VElement);
53
+ installDomShim(zeed);
54
+ const StarterKit = sk.default ?? sk.StarterKit;
55
+ bridge = {
56
+ generateJSON: core.generateJSON,
57
+ renderToHTMLString: sr.renderToHTMLString,
58
+ extensions: [StarterKit]
59
+ };
60
+ return true;
61
+ } catch {
62
+ bridge = null;
63
+ return false;
64
+ }
65
+ }
66
+ /** HTML → doc JSON through tiptap's own parseHTML rules. Null when the
67
+ * bridge hasn't loaded (call ensureJSONBridge first / check `ready`). */
68
+ function htmlToJSON(html) {
69
+ return bridge?.generateJSON(html, bridge.extensions) ?? null;
70
+ }
71
+ /** Doc JSON → HTML via tiptap's own renderHTML rules (no DOM). */
72
+ function jsonToHTML(doc) {
73
+ return bridge?.renderToHTMLString({
74
+ extensions: bridge.extensions,
75
+ content: doc
76
+ }) ?? null;
77
+ }
78
+ function jsonBridgeReady() {
79
+ return bridge != null;
80
+ }
81
+ //#endregion
82
+ export { ensureJSONBridge, htmlToJSON, jsonBridgeReady, jsonToHTML };
@@ -0,0 +1,207 @@
1
+ import { ensureJSONBridge, htmlToJSON, jsonBridgeReady, jsonToHTML } from "./index2.js";
2
+ import { defineUniversalComponent, hookSlots, universalComponent, universalPlan, universalProps, universalValue, useCallback, useEffect, useRef } from "@nativescript-community/octane";
3
+ import { RichTextEditor, supported } from "@octane-xplat/richtext";
4
+ //#region src/TiptapEditor.tsrx
5
+ var _hs$ = /* @__PURE__ */ hookSlots(6);
6
+ var _h$0 = /* @__PURE__ */ Symbol(_hs$);
7
+ var _h$1 = /* @__PURE__ */ Symbol(_hs$ + 1);
8
+ var _h$2 = /* @__PURE__ */ Symbol(_hs$ + 2);
9
+ var _h$3 = /* @__PURE__ */ Symbol(_hs$ + 3);
10
+ var _h$4 = /* @__PURE__ */ Symbol(_hs$ + 4);
11
+ var _h$5 = /* @__PURE__ */ Symbol(_hs$ + 5);
12
+ var __octaneUniversalPlan0 = universalPlan("nativescript", {
13
+ "kind": "slot",
14
+ "slot": 0
15
+ });
16
+ var supported$1 = supported;
17
+ var TiptapEditor = defineUniversalComponent("nativescript", function TiptapEditor(props) {
18
+ const inner = useRef(null, _h$0);
19
+ const jsonReady = useRef(false, _h$1);
20
+ const lastJSON = useRef(void 0, _h$2);
21
+ const bindHost = useCallback((h) => {
22
+ inner.current = h;
23
+ props.bind?.({
24
+ getHTML: () => h.getHTML(),
25
+ setHTML: (html) => 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
+ });
45
+ }, [], _h$3);
46
+ useEffect(() => {
47
+ ensureJSONBridge().then((ok) => {
48
+ jsonReady.current = ok;
49
+ props.onJSONReady?.(ok);
50
+ });
51
+ }, [], _h$4);
52
+ useEffect(() => {
53
+ if (!props.json || !jsonBridgeReady() || !inner.current) return;
54
+ const stamp = JSON.stringify(props.json);
55
+ if (stamp === lastJSON.current) return;
56
+ const html = jsonToHTML(props.json);
57
+ if (html != null) {
58
+ lastJSON.current = stamp;
59
+ inner.current.setHTML(html);
60
+ }
61
+ }, [props.json], _h$5);
62
+ const emitJSON = () => {
63
+ if (!inner.current || !jsonBridgeReady()) return;
64
+ const doc = htmlToJSON(inner.current.getHTML());
65
+ if (doc) {
66
+ lastJSON.current = JSON.stringify(doc);
67
+ props.onJSONChange?.(doc);
68
+ }
69
+ };
70
+ return universalValue(__octaneUniversalPlan0, [universalComponent("nativescript", RichTextEditor, universalProps([
71
+ [
72
+ "set",
73
+ "id",
74
+ props.id
75
+ ],
76
+ [
77
+ "set",
78
+ "className",
79
+ props.className
80
+ ],
81
+ [
82
+ "set",
83
+ "style",
84
+ props.style
85
+ ],
86
+ [
87
+ "set",
88
+ "value",
89
+ props.value
90
+ ],
91
+ [
92
+ "set",
93
+ "placeholder",
94
+ props.placeholder
95
+ ],
96
+ [
97
+ "set",
98
+ "editable",
99
+ props.editable
100
+ ],
101
+ [
102
+ "set",
103
+ "autofocus",
104
+ props.autofocus
105
+ ],
106
+ [
107
+ "set",
108
+ "onReady",
109
+ props.onReady
110
+ ],
111
+ [
112
+ "set",
113
+ "onChange",
114
+ (html) => {
115
+ props.onChange?.(html);
116
+ emitJSON();
117
+ }
118
+ ],
119
+ [
120
+ "set",
121
+ "onSelectionChange",
122
+ props.onSelectionChange
123
+ ],
124
+ [
125
+ "set",
126
+ "onFocus",
127
+ props.onFocus
128
+ ],
129
+ [
130
+ "set",
131
+ "onBlur",
132
+ props.onBlur
133
+ ],
134
+ [
135
+ "set",
136
+ "bind",
137
+ bindHost
138
+ ],
139
+ [
140
+ "set",
141
+ "android",
142
+ props.android
143
+ ],
144
+ [
145
+ "set",
146
+ "ios",
147
+ props.ios
148
+ ],
149
+ [
150
+ "set",
151
+ "row",
152
+ props.row
153
+ ],
154
+ [
155
+ "set",
156
+ "col",
157
+ props.col
158
+ ],
159
+ [
160
+ "set",
161
+ "rowSpan",
162
+ props.rowSpan
163
+ ],
164
+ [
165
+ "set",
166
+ "colSpan",
167
+ props.colSpan
168
+ ],
169
+ [
170
+ "set",
171
+ "dock",
172
+ props.dock
173
+ ],
174
+ [
175
+ "set",
176
+ "left",
177
+ props.left
178
+ ],
179
+ [
180
+ "set",
181
+ "top",
182
+ props.top
183
+ ],
184
+ [
185
+ "set",
186
+ "flexGrow",
187
+ props.flexGrow
188
+ ],
189
+ [
190
+ "set",
191
+ "flexShrink",
192
+ props.flexShrink
193
+ ],
194
+ [
195
+ "set",
196
+ "alignSelf",
197
+ props.alignSelf
198
+ ],
199
+ [
200
+ "set",
201
+ "order",
202
+ props.order
203
+ ]
204
+ ]))]);
205
+ }, { "module": "@nativescript-community/octane" });
206
+ //#endregion
207
+ export { TiptapEditor, supported$1 as supported };
@@ -0,0 +1,8 @@
1
+ {
2
+ "version": 1,
3
+ "buildId": "02534c50-4097-48da-93e0-5e219c2d5b5c",
4
+ "mode": "production",
5
+ "capabilities": {
6
+ "independentHydration": false
7
+ }
8
+ }
@@ -0,0 +1,12 @@
1
+ import { TiptapEditor, supported } from "./index2.js";
2
+ //#region src/index.web.ts
3
+ /** Web needs no lazy bridge — tiptap is in the bundle already. Kept for
4
+ * call-site parity with the native entry. */
5
+ function ensureJSONBridge() {
6
+ return Promise.resolve(true);
7
+ }
8
+ function jsonBridgeReady() {
9
+ return true;
10
+ }
11
+ //#endregion
12
+ export { TiptapEditor, ensureJSONBridge, jsonBridgeReady, supported };
@@ -0,0 +1,182 @@
1
+ import { __s, bag3, clone, componentSlot, hookSlots, setStyle, template, useEffect, useRef, withSlot } from "octane";
2
+ import { updateFreshClassName } from "octane/internal/client";
3
+ import { EditorContent, useEditor } from "@octanejs/tiptap";
4
+ import StarterKit from "@tiptap/starter-kit";
5
+ //#region src/TiptapEditor.web.tsrx
6
+ var _t$0 = /* @__PURE__ */ template("<div></div>");
7
+ var _hs$ = /* @__PURE__ */ hookSlots(8);
8
+ var _h$3 = /* @__PURE__ */ Symbol(_hs$ + 3);
9
+ var supported = true;
10
+ var COMMAND = {
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
+ var IS_ACTIVE = {
30
+ bold: (e) => e.isActive("bold"),
31
+ italic: (e) => e.isActive("italic"),
32
+ underline: (e) => e.isActive("underline"),
33
+ strikethrough: (e) => e.isActive("strike"),
34
+ code: (e) => e.isActive("code"),
35
+ blockquote: (e) => e.isActive("blockquote"),
36
+ bulletList: (e) => e.isActive("bulletList"),
37
+ orderedList: (e) => e.isActive("orderedList"),
38
+ codeBlock: (e) => e.isActive("codeBlock"),
39
+ link: (e) => e.isActive("link")
40
+ };
41
+ var ACTIVE_OF = [
42
+ ["bold", "bold"],
43
+ ["italic", "italic"],
44
+ ["underline", "underline"],
45
+ ["strikethrough", "strike"],
46
+ ["code", "code"],
47
+ ["blockquote", "blockquote"],
48
+ ["bulletList", "bulletList"],
49
+ ["orderedList", "orderedList"],
50
+ ["codeBlock", "codeBlock"],
51
+ ["link", "link"],
52
+ [
53
+ "heading1",
54
+ "heading",
55
+ { level: 1 }
56
+ ],
57
+ [
58
+ "heading2",
59
+ "heading",
60
+ { level: 2 }
61
+ ],
62
+ [
63
+ "heading3",
64
+ "heading",
65
+ { level: 3 }
66
+ ],
67
+ [
68
+ "heading4",
69
+ "heading",
70
+ { level: 4 }
71
+ ],
72
+ [
73
+ "heading5",
74
+ "heading",
75
+ { level: 5 }
76
+ ],
77
+ [
78
+ "heading6",
79
+ "heading",
80
+ { level: 6 }
81
+ ],
82
+ ["paragraph", "paragraph"]
83
+ ];
84
+ function activeFormats(editor) {
85
+ const out = [];
86
+ for (const [name, kind, attrs] of ACTIVE_OF) if (editor.isActive(kind, attrs)) out.push(name);
87
+ return out;
88
+ }
89
+ var TiptapEditor = /* @__PURE__ */ __s(function TiptapEditor(props, __s, __extra) {
90
+ const lastEmitted = useRef(void 0, _hs$ + 0);
91
+ const lastJSON = useRef(void 0, _hs$ + 1);
92
+ const bound = useRef(void 0, _hs$ + 2);
93
+ const editor = withSlot(_h$3, useEditor, {
94
+ extensions: [StarterKit],
95
+ content: props.value ?? props.json ?? "",
96
+ editable: props.editable !== false,
97
+ autofocus: props.autofocus ?? false,
98
+ onCreate: () => props.onReady?.(),
99
+ onUpdate: ({ editor: e }) => {
100
+ const html = e.getHTML();
101
+ lastEmitted.current = html;
102
+ props.onChange?.(html);
103
+ const doc = e.getJSON();
104
+ lastJSON.current = JSON.stringify(doc);
105
+ props.onJSONChange?.(doc);
106
+ },
107
+ onFocus: () => props.onFocus?.(),
108
+ onBlur: () => props.onBlur?.(),
109
+ onSelectionUpdate: ({ editor: e }) => props.onSelectionChange?.({
110
+ start: e.state.selection.from,
111
+ end: e.state.selection.to,
112
+ active: activeFormats(e)
113
+ })
114
+ });
115
+ useEffect(() => {
116
+ if (!editor || props.value === void 0) return;
117
+ if (props.value === lastEmitted.current) return;
118
+ editor.commands.setContent(props.value);
119
+ }, [editor, props.value], _hs$ + 4);
120
+ useEffect(() => {
121
+ if (!editor || props.json === void 0) return;
122
+ if (JSON.stringify(props.json) === lastJSON.current) return;
123
+ editor.commands.setContent(props.json);
124
+ }, [editor, props.json], _hs$ + 5);
125
+ useEffect(() => {
126
+ if (!editor) return;
127
+ editor.setEditable(props.editable !== false);
128
+ }, [editor, props.editable], _hs$ + 6);
129
+ useEffect(() => {
130
+ if (!editor || bound.current?.native === editor) return;
131
+ const e = editor;
132
+ bound.current = {
133
+ getHTML: () => e.getHTML(),
134
+ setHTML: (html) => e.commands.setContent(html),
135
+ getJSON: () => e.getJSON(),
136
+ setJSON: (doc) => e.commands.setContent(doc),
137
+ apply: (format) => {
138
+ const run = COMMAND[format];
139
+ if (run) run(e.chain().focus()).run();
140
+ },
141
+ linkTo: (url, anchor) => {
142
+ if (anchor != null && anchor !== "") e.chain().focus().insertContent({
143
+ type: "text",
144
+ text: anchor,
145
+ marks: [{
146
+ type: "link",
147
+ attrs: { href: url }
148
+ }]
149
+ }).run();
150
+ else e.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
151
+ },
152
+ removeLink: () => e.chain().focus().unsetLink().run(),
153
+ isActive: (format) => IS_ACTIVE[format]?.(e) ?? false,
154
+ undo: () => e.commands.undo(),
155
+ redo: () => e.commands.redo(),
156
+ focus: () => e.commands.focus(),
157
+ blur: () => e.commands.blur(),
158
+ isFocused: () => e.isFocused,
159
+ native: e
160
+ };
161
+ props.bind?.(bound.current);
162
+ }, [editor], _hs$ + 7);
163
+ if (__s.block.pending && !__s.block.crossRenderUpdate) return;
164
+ let _b = __s.slots[0];
165
+ if (_b === void 0) {
166
+ let _m1;
167
+ const _root = clone(_t$0);
168
+ _m1 = _root;
169
+ _b = bag3(__s, _root, void 0, _m1, void 0);
170
+ }
171
+ _b.a = updateFreshClassName(["vx-richtext", props.className], _b.a, _b.b);
172
+ {
173
+ const _v = props.style;
174
+ if (_b.c !== _v) {
175
+ setStyle(_b.b, _v, _b.c);
176
+ _b.c = _v;
177
+ }
178
+ }
179
+ componentSlot(__s, 1, _b.b, EditorContent, { "editor": editor }, void 0, void 0, 2, void 0, void 0, "c:077f1c5f");
180
+ });
181
+ //#endregion
182
+ export { TiptapEditor, supported };
@@ -0,0 +1,8 @@
1
+ {
2
+ "version": 1,
3
+ "buildId": "877d10c5-3430-4ba4-9762-2bd655451b3d",
4
+ "mode": "production",
5
+ "capabilities": {
6
+ "independentHydration": false
7
+ }
8
+ }
package/package.json CHANGED
@@ -1,19 +1,92 @@
1
1
  {
2
2
  "name": "@octane-xplat/tiptap",
3
- "version": "0.0.1",
3
+ "version": "0.9.0",
4
4
  "description": "Unified rich-text editor facade for Octane xplat — @octanejs/tiptap on web, @octane-xplat/richtext (Aztec) + tiptap doc model on native",
5
+ "keywords": [
6
+ "cross-platform",
7
+ "editor",
8
+ "octane",
9
+ "prosemirror",
10
+ "rich-text",
11
+ "richtext",
12
+ "tiptap",
13
+ "xplat"
14
+ ],
15
+ "homepage": "https://github.com/octane-xplat/octane-xplat/tree/main/packages/tiptap",
5
16
  "license": "MIT",
6
17
  "repository": {
7
18
  "type": "git",
8
- "url": "https://github.com/octane-xplat/octane-xplat.git",
19
+ "url": "https://github.com/octane-xplat/octane-xplat",
9
20
  "directory": "packages/tiptap"
10
21
  },
11
- "homepage": "https://github.com/octane-xplat/octane-xplat/tree/main/packages/tiptap",
12
- "main": "./index.js",
13
- "types": "./index.d.ts",
14
22
  "files": [
15
- "README.md",
16
- "index.js",
17
- "index.d.ts"
18
- ]
19
- }
23
+ "dist",
24
+ "types",
25
+ "src",
26
+ "platforms"
27
+ ],
28
+ "type": "module",
29
+ "exports": {
30
+ ".": {
31
+ "macos": {
32
+ "types": "./types/index.d.ts",
33
+ "default": "./src/index.macos.ts"
34
+ },
35
+ "web": {
36
+ "types": "./types/index.d.ts",
37
+ "default": "./dist/web/index.js"
38
+ },
39
+ "native": {
40
+ "types": "./types/index.d.ts",
41
+ "default": "./dist/native/index.js"
42
+ },
43
+ "types": "./types/index.d.ts",
44
+ "default": "./dist/native/index.js"
45
+ }
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "dependencies": {
51
+ "@octane-xplat/richtext": "0.9.0",
52
+ "@octanejs/tiptap": "0.0.51",
53
+ "@tiptap/core": "3.28.0",
54
+ "@tiptap/pm": "3.28.0",
55
+ "@tiptap/starter-kit": "3.28.0",
56
+ "@tiptap/static-renderer": "3.28.0",
57
+ "zeed-dom": "^0.15.1"
58
+ },
59
+ "devDependencies": {
60
+ "@nativescript-community/octane": "0.2.4",
61
+ "@nativescript/core": "9.1.2",
62
+ "@octanejs/vite-plugin": "0.1.61",
63
+ "octane": "0.6.3",
64
+ "typescript": "5.9.3",
65
+ "vite": "8.3.0"
66
+ },
67
+ "peerDependencies": {
68
+ "@nativescript-community/octane": ">=0.2.1 <1",
69
+ "@nativescript/core": ">=9.1.0 <10",
70
+ "octane": ">=0.6.3 <1"
71
+ },
72
+ "peerDependenciesMeta": {
73
+ "@nativescript-community/octane": {
74
+ "optional": true
75
+ },
76
+ "@nativescript/core": {
77
+ "optional": true
78
+ },
79
+ "octane": {
80
+ "optional": true
81
+ }
82
+ },
83
+ "nativescript": {
84
+ "platforms": {
85
+ "android": "9.1.0",
86
+ "ios": "9.1.0"
87
+ }
88
+ },
89
+ "scripts": {
90
+ "build": "vite build && vite build --mode native && tsc -p tsconfig.types.json && node ../../scripts/check-native-dist.mjs"
91
+ }
92
+ }
@@ -0,0 +1,107 @@
1
+ /** @jsxImportSource @nativescript-community/octane */
2
+ import { 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 jsonReady = useRef(false)
16
+ const lastJSON = useRef<string | undefined>(undefined)
17
+
18
+ const bindHost = useCallback((h: RichTextEditorHandle) => {
19
+ inner.current = h
20
+
21
+ props.bind?.({
22
+ getHTML: () => h.getHTML(),
23
+ setHTML: (html: string) => h.setHTML(html),
24
+ getJSON: () => (jsonBridgeReady() ? htmlToJSON(h.getHTML()) : null),
25
+ setJSON: (doc) => {
26
+ const html = jsonBridgeReady() ? jsonToHTML(doc) : null
27
+ if (html != null) {
28
+ lastJSON.current = JSON.stringify(doc)
29
+ h.setHTML(html)
30
+ }
31
+ },
32
+ apply: (format) => h.apply(format),
33
+ linkTo: (url, anchor) => h.linkTo(url, anchor),
34
+ removeLink: () => h.removeLink(),
35
+ isActive: (format) => h.isActive(format),
36
+ undo: () => h.undo(),
37
+ redo: () => h.redo(),
38
+ focus: () => h.focus(),
39
+ blur: () => h.blur(),
40
+ isFocused: () => h.isFocused(),
41
+ native: h.native,
42
+ } satisfies TiptapEditorHandle)
43
+ }, [])
44
+
45
+ // The bridge loads once per process — dynamic imports of the DOM-free
46
+ // tiptap slices, plus the zeed-dom parse shim.
47
+ useEffect(() => {
48
+ ensureJSONBridge().then((ok) => {
49
+ jsonReady.current = ok
50
+ props.onJSONReady?.(ok)
51
+ })
52
+ }, [])
53
+
54
+ // `json` prop → editor content, when the bridge is up and the doc wasn't
55
+ // emitted by the editor itself.
56
+ useEffect(() => {
57
+ if (!props.json || !jsonBridgeReady() || !inner.current) return
58
+ const stamp = JSON.stringify(props.json)
59
+ if (stamp === lastJSON.current) return
60
+ const html = jsonToHTML(props.json)
61
+ if (html != null) {
62
+ lastJSON.current = stamp
63
+ inner.current.setHTML(html)
64
+ }
65
+ }, [props.json])
66
+
67
+ const emitJSON = () => {
68
+ if (!inner.current || !jsonBridgeReady()) return
69
+ const doc = htmlToJSON(inner.current.getHTML())
70
+ if (doc) {
71
+ lastJSON.current = JSON.stringify(doc)
72
+ props.onJSONChange?.(doc)
73
+ }
74
+ }
75
+
76
+ <RichTextEditor
77
+ id={props.id}
78
+ className={props.className}
79
+ style={props.style}
80
+ value={props.value}
81
+ placeholder={props.placeholder}
82
+ editable={props.editable}
83
+ autofocus={props.autofocus}
84
+ onReady={props.onReady}
85
+ onChange={(html) => {
86
+ props.onChange?.(html)
87
+ emitJSON()
88
+ }}
89
+ onSelectionChange={props.onSelectionChange}
90
+ onFocus={props.onFocus}
91
+ onBlur={props.onBlur}
92
+ bind={bindHost}
93
+ android={props.android}
94
+ ios={props.ios}
95
+ row={props.row}
96
+ col={props.col}
97
+ rowSpan={props.rowSpan}
98
+ colSpan={props.colSpan}
99
+ dock={props.dock}
100
+ left={props.left}
101
+ top={props.top}
102
+ flexGrow={props.flexGrow}
103
+ flexShrink={props.flexShrink}
104
+ alignSelf={props.alignSelf}
105
+ order={props.order}
106
+ />;
107
+ }
@@ -0,0 +1,159 @@
1
+ import { 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 IS_ACTIVE: Partial<Record<TiptapFormat, (editor: any) => boolean>> = {
31
+ bold: (e) => e.isActive('bold'),
32
+ italic: (e) => e.isActive('italic'),
33
+ underline: (e) => e.isActive('underline'),
34
+ strikethrough: (e) => e.isActive('strike'),
35
+ code: (e) => e.isActive('code'),
36
+ blockquote: (e) => e.isActive('blockquote'),
37
+ bulletList: (e) => e.isActive('bulletList'),
38
+ orderedList: (e) => e.isActive('orderedList'),
39
+ codeBlock: (e) => e.isActive('codeBlock'),
40
+ link: (e) => e.isActive('link'),
41
+ }
42
+
43
+ const ACTIVE_OF: [TiptapFormat, string, any?][] = [
44
+ ['bold', 'bold'],
45
+ ['italic', 'italic'],
46
+ ['underline', 'underline'],
47
+ ['strikethrough', 'strike'],
48
+ ['code', 'code'],
49
+ ['blockquote', 'blockquote'],
50
+ ['bulletList', 'bulletList'],
51
+ ['orderedList', 'orderedList'],
52
+ ['codeBlock', 'codeBlock'],
53
+ ['link', 'link'],
54
+ ['heading1', 'heading', { level: 1 }],
55
+ ['heading2', 'heading', { level: 2 }],
56
+ ['heading3', 'heading', { level: 3 }],
57
+ ['heading4', 'heading', { level: 4 }],
58
+ ['heading5', 'heading', { level: 5 }],
59
+ ['heading6', 'heading', { level: 6 }],
60
+ ['paragraph', 'paragraph'],
61
+ ]
62
+
63
+ function activeFormats(editor: any): TiptapFormat[] {
64
+ const out: TiptapFormat[] = []
65
+ for (const [name, kind, attrs] of ACTIVE_OF) {
66
+ if (editor.isActive(kind, attrs)) out.push(name)
67
+ }
68
+
69
+ return out
70
+ }
71
+
72
+ /** Unified editor facade, web half — a thin adapter over the published
73
+ * `@octanejs/tiptap` binding (useEditor + EditorContent + StarterKit). */
74
+ export function TiptapEditor(props: TiptapEditorProps) @{
75
+ const lastEmitted = useRef<string | undefined>(undefined)
76
+ const lastJSON = useRef<string | undefined>(undefined)
77
+ const bound = useRef<TiptapEditorHandle | undefined>(undefined)
78
+
79
+ const editor = useEditor({
80
+ extensions: [StarterKit],
81
+ content: (props.value ?? props.json ?? '') as any,
82
+ editable: props.editable !== false,
83
+ autofocus: props.autofocus ?? false,
84
+ onCreate: () => props.onReady?.(),
85
+ onUpdate: ({ editor: e }: any) => {
86
+ const html = e.getHTML()
87
+ lastEmitted.current = html
88
+ props.onChange?.(html)
89
+ const doc = e.getJSON()
90
+ lastJSON.current = JSON.stringify(doc)
91
+ props.onJSONChange?.(doc)
92
+ },
93
+ onFocus: () => props.onFocus?.(),
94
+ onBlur: () => props.onBlur?.(),
95
+ onSelectionUpdate: ({ editor: e }: any) =>
96
+ props.onSelectionChange?.({
97
+ start: e.state.selection.from,
98
+ end: e.state.selection.to,
99
+ active: activeFormats(e),
100
+ }),
101
+ })
102
+
103
+ // External `value` changes push setContent; editor-emitted HTML is skipped
104
+ // (the transaction already owns the doc).
105
+ useEffect(() => {
106
+ if (!editor || props.value === undefined) return
107
+ if (props.value === lastEmitted.current) return
108
+ editor.commands.setContent(props.value)
109
+ }, [editor, props.value])
110
+
111
+ useEffect(() => {
112
+ if (!editor || props.json === undefined) return
113
+ if (JSON.stringify(props.json) === lastJSON.current) return
114
+ editor.commands.setContent(props.json as any)
115
+ }, [editor, props.json])
116
+
117
+ useEffect(() => {
118
+ if (!editor) return
119
+ editor.setEditable(props.editable !== false)
120
+ }, [editor, props.editable])
121
+
122
+ // The handle rebinds whenever the editor instance appears — stable
123
+ // afterward (useEditor keeps the instance identity).
124
+ useEffect(() => {
125
+ if (!editor || bound.current?.native === editor) return
126
+ const e: any = editor
127
+ bound.current = {
128
+ getHTML: () => e.getHTML(),
129
+ setHTML: (html: string) => e.commands.setContent(html),
130
+ getJSON: () => e.getJSON(),
131
+ setJSON: (doc) => e.commands.setContent(doc),
132
+ apply: (format) => {
133
+ const run = COMMAND[format as WebFormat]
134
+ if (run) run(e.chain().focus()).run()
135
+ },
136
+ linkTo: (url, anchor) => {
137
+ if (anchor != null && anchor !== '') {
138
+ e.chain().focus().insertContent({ type: 'text', text: anchor, marks: [{ type: 'link', attrs: { href: url } }] }).run()
139
+ } else {
140
+ e.chain().focus().extendMarkRange('link').setLink({ href: url }).run()
141
+ }
142
+ },
143
+ removeLink: () => e.chain().focus().unsetLink().run(),
144
+ isActive: (format) => IS_ACTIVE[format]?.(e) ?? false,
145
+ undo: () => e.commands.undo(),
146
+ redo: () => e.commands.redo(),
147
+ focus: () => e.commands.focus(),
148
+ blur: () => e.commands.blur(),
149
+ isFocused: () => e.isFocused,
150
+ native: e,
151
+ }
152
+
153
+ props.bind?.(bound.current)
154
+ }, [editor])
155
+
156
+ <div className={['vx-richtext', props.className]} style={props.style}>
157
+ <EditorContent editor={editor} />
158
+ </div>
159
+ }
@@ -0,0 +1,19 @@
1
+ import type { TiptapEditorProps } from './types'
2
+
3
+ export const supported = false
4
+
5
+ /** AppKit host has no rich-text surface yet. */
6
+ export function TiptapEditor(_props: TiptapEditorProps): null {
7
+ return null
8
+ }
9
+
10
+ export type * from './types'
11
+
12
+ /** No editor, no bridge. */
13
+ export function ensureJSONBridge(): Promise<boolean> {
14
+ return Promise.resolve(false)
15
+ }
16
+
17
+ export function jsonBridgeReady(): boolean {
18
+ return false
19
+ }
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,96 @@
1
+ /** Format vocabulary shared by the web (tiptap StarterKit) and native
2
+ * (Aztec) backends. On web, starter-kit covers everything except
3
+ * taskList/highlight/subscript/superscript/align* — those no-op there and
4
+ * map to Aztec formats on Android. */
5
+ export type TiptapFormat =
6
+ | 'bold'
7
+ | 'italic'
8
+ | 'underline'
9
+ | 'strikethrough'
10
+ | 'code'
11
+ | 'blockquote'
12
+ | 'bulletList'
13
+ | 'orderedList'
14
+ | 'taskList'
15
+ | 'codeBlock'
16
+ | 'horizontalRule'
17
+ | 'highlight'
18
+ | 'subscript'
19
+ | 'superscript'
20
+ | 'paragraph'
21
+ | 'heading1'
22
+ | 'heading2'
23
+ | 'heading3'
24
+ | 'heading4'
25
+ | 'heading5'
26
+ | 'heading6'
27
+ | 'alignLeft'
28
+ | 'alignCenter'
29
+ | 'alignRight'
30
+ | 'link'
31
+
32
+ /** Tiptap document JSON (`{ type: 'doc', content: [...] }`). Opaque here —
33
+ * callers round-trip through tiptap's own schema on both platforms. */
34
+ export type TiptapJSON = { type: string; content?: unknown[] } & Record<string, unknown>
35
+
36
+ export interface TiptapEditorHandle {
37
+ getHTML(): string
38
+ /** Web: `commands.setContent` (undo-aware). Native: Aztec `fromHtml` —
39
+ * resets undo history. */
40
+ setHTML(html: string): void
41
+ /** Doc JSON, or null while the native schema modules are still loading /
42
+ * on a runtime that can't host them (`json` flag). Web is always ready. */
43
+ getJSON(): TiptapJSON | null
44
+ setJSON(doc: TiptapJSON): void
45
+ apply(format: TiptapFormat): void
46
+ linkTo(url: string, anchor?: string): void
47
+ removeLink(): void
48
+ isActive(format: TiptapFormat): boolean
49
+ undo(): void
50
+ redo(): void
51
+ focus(): void
52
+ blur(): void
53
+ isFocused(): boolean
54
+ /** The platform surface — a tiptap `Editor` on web, `AztecText` on
55
+ * Android. */
56
+ native: any
57
+ }
58
+
59
+ export interface TiptapEditorProps {
60
+ id?: string
61
+ className?: string
62
+ style?: any
63
+ /** Document HTML. Seed at mount; external changes re-push (web: via
64
+ * setContent, preserving the diff loop the editor emitted is skipped). */
65
+ value?: string
66
+ /** Document JSON. Takes precedence over `value` when both arrive. On
67
+ * native it waits for the schema modules (see `onJSONReady`). */
68
+ json?: TiptapJSON
69
+ placeholder?: string
70
+ editable?: boolean
71
+ autofocus?: boolean
72
+ onReady?: () => void
73
+ /** Fires once when the native JSON bridge settles — `true` if the
74
+ * DOM-free tiptap modules loaded, `false` on runtimes that can't host
75
+ * them (JSON calls then no-op / return null). Never fires on web. */
76
+ onJSONReady?: (ready: boolean) => void
77
+ onChange?: (html: string) => void
78
+ onJSONChange?: (doc: TiptapJSON) => void
79
+ onSelectionChange?: (event: { start: number; end: number; active: TiptapFormat[] }) => void
80
+ onFocus?: () => void
81
+ onBlur?: () => void
82
+ bind?: (handle: TiptapEditorHandle) => void
83
+ android?: Record<string, any>
84
+ ios?: Record<string, any>
85
+ row?: number | string
86
+ col?: number | string
87
+ rowSpan?: number
88
+ colSpan?: number
89
+ dock?: 'left' | 'top' | 'right' | 'bottom'
90
+ left?: number
91
+ top?: number
92
+ flexGrow?: number
93
+ flexShrink?: number
94
+ alignSelf?: 'auto' | 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline'
95
+ order?: number
96
+ }
@@ -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,75 @@
1
+ /** Format vocabulary shared by the web (tiptap StarterKit) and native
2
+ * (Aztec) backends. On web, starter-kit covers everything except
3
+ * taskList/highlight/subscript/superscript/align* — those no-op there and
4
+ * map to Aztec formats on Android. */
5
+ 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';
6
+ /** Tiptap document JSON (`{ type: 'doc', content: [...] }`). Opaque here —
7
+ * callers round-trip through tiptap's own schema on both platforms. */
8
+ export type TiptapJSON = {
9
+ type: string;
10
+ content?: unknown[];
11
+ } & Record<string, unknown>;
12
+ export interface TiptapEditorHandle {
13
+ getHTML(): string;
14
+ /** Web: `commands.setContent` (undo-aware). Native: Aztec `fromHtml` —
15
+ * resets undo history. */
16
+ setHTML(html: string): void;
17
+ /** Doc JSON, or null while the native schema modules are still loading /
18
+ * on a runtime that can't host them (`json` flag). Web is always ready. */
19
+ getJSON(): TiptapJSON | null;
20
+ setJSON(doc: TiptapJSON): void;
21
+ apply(format: TiptapFormat): void;
22
+ linkTo(url: string, anchor?: string): void;
23
+ removeLink(): void;
24
+ isActive(format: TiptapFormat): boolean;
25
+ undo(): void;
26
+ redo(): void;
27
+ focus(): void;
28
+ blur(): void;
29
+ isFocused(): boolean;
30
+ /** The platform surface — a tiptap `Editor` on web, `AztecText` on
31
+ * Android. */
32
+ native: any;
33
+ }
34
+ export interface TiptapEditorProps {
35
+ id?: string;
36
+ className?: string;
37
+ style?: any;
38
+ /** Document HTML. Seed at mount; external changes re-push (web: via
39
+ * setContent, preserving the diff loop the editor emitted is skipped). */
40
+ value?: string;
41
+ /** Document JSON. Takes precedence over `value` when both arrive. On
42
+ * native it waits for the schema modules (see `onJSONReady`). */
43
+ json?: TiptapJSON;
44
+ placeholder?: string;
45
+ editable?: boolean;
46
+ autofocus?: boolean;
47
+ onReady?: () => void;
48
+ /** Fires once when the native JSON bridge settles — `true` if the
49
+ * DOM-free tiptap modules loaded, `false` on runtimes that can't host
50
+ * them (JSON calls then no-op / return null). Never fires on web. */
51
+ onJSONReady?: (ready: boolean) => void;
52
+ onChange?: (html: string) => void;
53
+ onJSONChange?: (doc: TiptapJSON) => void;
54
+ onSelectionChange?: (event: {
55
+ start: number;
56
+ end: number;
57
+ active: TiptapFormat[];
58
+ }) => void;
59
+ onFocus?: () => void;
60
+ onBlur?: () => void;
61
+ bind?: (handle: TiptapEditorHandle) => void;
62
+ android?: Record<string, any>;
63
+ ios?: Record<string, any>;
64
+ row?: number | string;
65
+ col?: number | string;
66
+ rowSpan?: number;
67
+ colSpan?: number;
68
+ dock?: 'left' | 'top' | 'right' | 'bottom';
69
+ left?: number;
70
+ top?: number;
71
+ flexGrow?: number;
72
+ flexShrink?: number;
73
+ alignSelf?: 'auto' | 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline';
74
+ order?: number;
75
+ }
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 = {}