@octane-xplat/richtext 0.0.0-stage → 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 +21 -0
- package/README.md +21 -2
- package/dist/native/RichTextEditor.android.js +234 -0
- package/dist/native/RichTextEditor.ios.js +22 -0
- package/dist/native/index.js +2 -0
- package/dist/native/octane-client-build.json +8 -0
- package/dist/web/index.js +9 -0
- package/dist/web/octane-client-build.json +8 -0
- package/package.json +80 -3
- package/platforms/android/include.gradle +7 -0
- package/src/RichTextEditor.android.tsrx +281 -0
- package/src/RichTextEditor.ios.tsrx +15 -0
- package/src/index.macos.ts +10 -0
- package/src/index.ts +5 -0
- package/src/index.web.ts +11 -0
- package/src/types.ts +97 -0
- package/types/index.d.ts +6 -0
- package/types/types.d.ts +68 -0
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,22 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@octane-xplat/richtext`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The native rich-text editing leaf: WordPress Aztec's `AztecText` on Android —
|
|
4
|
+
a `Spannable`-backed `EditText`, not a WebView. iOS mounts an unsupported
|
|
5
|
+
placeholder until the Aztec-iOS Swift facade lands; web, macOS, and Windows
|
|
6
|
+
return `supported: false` (the `@octane-xplat/tiptap` facade covers web).
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { RichTextEditor, supported } from '@octane-xplat/richtext'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Content in/out is document HTML via Aztec `fromHtml`/`toPlainHtml`. The
|
|
13
|
+
`bind` callback returns an imperative handle (`getHTML`/`setHTML`,
|
|
14
|
+
`apply(format)`, `linkTo`/`removeLink`, `isActive`, `undo`/`redo`,
|
|
15
|
+
`focus`/`blur`, `native`) once the editor exists, and `onSelectionChange`
|
|
16
|
+
reports the active `RichTextFormat` set at the caret.
|
|
17
|
+
|
|
18
|
+
The Android Aztec dependency (`org.wordpress:aztec:v2.1.7`) arrives through
|
|
19
|
+
the leaf's `platforms/android/include.gradle` — no app-level gradle work.
|
|
20
|
+
See the framework guide for the full contract and the demo:
|
|
21
|
+
[`docs/rich-text.md`](../../docs/rich-text.md) and
|
|
22
|
+
[`packages/demos/src/RichTextEditorDemo.tsrx`](../demos/src/RichTextEditorDemo.tsrx).
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { defineUniversalComponent, hookSlots, registerElement, universalPlan, universalValue, useCallback, useEffect, useRef } from "@nativescript-community/octane";
|
|
2
|
+
import { View } from "@nativescript/core";
|
|
3
|
+
//#region src/RichTextEditor.android.tsrx
|
|
4
|
+
var _hs$ = /* @__PURE__ */ hookSlots(10);
|
|
5
|
+
var _h$0 = /* @__PURE__ */ Symbol(_hs$);
|
|
6
|
+
var _h$1 = /* @__PURE__ */ Symbol(_hs$ + 1);
|
|
7
|
+
var _h$2 = /* @__PURE__ */ Symbol(_hs$ + 2);
|
|
8
|
+
var _h$3 = /* @__PURE__ */ Symbol(_hs$ + 3);
|
|
9
|
+
var _h$4 = /* @__PURE__ */ Symbol(_hs$ + 4);
|
|
10
|
+
var _h$5 = /* @__PURE__ */ Symbol(_hs$ + 5);
|
|
11
|
+
var _h$6 = /* @__PURE__ */ Symbol(_hs$ + 6);
|
|
12
|
+
var _h$7 = /* @__PURE__ */ Symbol(_hs$ + 7);
|
|
13
|
+
var _h$8 = /* @__PURE__ */ Symbol(_hs$ + 8);
|
|
14
|
+
var _h$9 = /* @__PURE__ */ Symbol(_hs$ + 9);
|
|
15
|
+
var __octaneUniversalPlan0 = universalPlan("nativescript", {
|
|
16
|
+
"kind": "host",
|
|
17
|
+
"type": "xplatrichtext",
|
|
18
|
+
"bindings": [
|
|
19
|
+
["ref", 0],
|
|
20
|
+
["id", 1],
|
|
21
|
+
["className", 2],
|
|
22
|
+
["style", 3],
|
|
23
|
+
["row", 4],
|
|
24
|
+
["col", 5],
|
|
25
|
+
["rowSpan", 6],
|
|
26
|
+
["colSpan", 7],
|
|
27
|
+
["dock", 8],
|
|
28
|
+
["left", 9],
|
|
29
|
+
["top", 10],
|
|
30
|
+
["flexGrow", 11],
|
|
31
|
+
["flexShrink", 12],
|
|
32
|
+
["alignSelf", 13],
|
|
33
|
+
["order", 14]
|
|
34
|
+
]
|
|
35
|
+
});
|
|
36
|
+
var aztec = globalThis.org?.wordpress?.aztec;
|
|
37
|
+
var androidText = globalThis.android?.text;
|
|
38
|
+
var androidView = globalThis.android?.view;
|
|
39
|
+
var FORMATS = {
|
|
40
|
+
bold: () => aztec.AztecTextFormat.FORMAT_BOLD,
|
|
41
|
+
italic: () => aztec.AztecTextFormat.FORMAT_ITALIC,
|
|
42
|
+
underline: () => aztec.AztecTextFormat.FORMAT_UNDERLINE,
|
|
43
|
+
strikethrough: () => aztec.AztecTextFormat.FORMAT_STRIKETHROUGH,
|
|
44
|
+
code: () => aztec.AztecTextFormat.FORMAT_CODE,
|
|
45
|
+
blockquote: () => aztec.AztecTextFormat.FORMAT_QUOTE,
|
|
46
|
+
bulletList: () => aztec.AztecTextFormat.FORMAT_UNORDERED_LIST,
|
|
47
|
+
orderedList: () => aztec.AztecTextFormat.FORMAT_ORDERED_LIST,
|
|
48
|
+
taskList: () => aztec.AztecTextFormat.FORMAT_TASK_LIST,
|
|
49
|
+
codeBlock: () => aztec.AztecTextFormat.FORMAT_PREFORMAT,
|
|
50
|
+
horizontalRule: () => aztec.AztecTextFormat.FORMAT_HORIZONTAL_RULE,
|
|
51
|
+
highlight: () => aztec.AztecTextFormat.FORMAT_MARK,
|
|
52
|
+
subscript: () => aztec.AztecTextFormat.FORMAT_SUBSCRIPT,
|
|
53
|
+
superscript: () => aztec.AztecTextFormat.FORMAT_SUPERSCRIPT,
|
|
54
|
+
paragraph: () => aztec.AztecTextFormat.FORMAT_PARAGRAPH,
|
|
55
|
+
heading1: () => aztec.AztecTextFormat.FORMAT_HEADING_1,
|
|
56
|
+
heading2: () => aztec.AztecTextFormat.FORMAT_HEADING_2,
|
|
57
|
+
heading3: () => aztec.AztecTextFormat.FORMAT_HEADING_3,
|
|
58
|
+
heading4: () => aztec.AztecTextFormat.FORMAT_HEADING_4,
|
|
59
|
+
heading5: () => aztec.AztecTextFormat.FORMAT_HEADING_5,
|
|
60
|
+
heading6: () => aztec.AztecTextFormat.FORMAT_HEADING_6,
|
|
61
|
+
alignLeft: () => aztec.AztecTextFormat.FORMAT_ALIGN_LEFT,
|
|
62
|
+
alignCenter: () => aztec.AztecTextFormat.FORMAT_ALIGN_CENTER,
|
|
63
|
+
alignRight: () => aztec.AztecTextFormat.FORMAT_ALIGN_RIGHT
|
|
64
|
+
};
|
|
65
|
+
var AZTEC_OF = Object.entries(FORMATS);
|
|
66
|
+
var formatOf = (format) => format === "link" ? aztec.AztecTextFormat.FORMAT_LINK : FORMATS[format]();
|
|
67
|
+
var AztecEditor = class extends View {
|
|
68
|
+
createNativeView() {
|
|
69
|
+
return new aztec.AztecText(this._context);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
registerElement("xplatrichtext", AztecEditor);
|
|
73
|
+
var supported = true;
|
|
74
|
+
var activeFormats = (editor) => {
|
|
75
|
+
const styles = editor.getAppliedStyles(editor.getSelectionStart(), editor.getSelectionEnd());
|
|
76
|
+
const count = styles?.size?.() ?? 0;
|
|
77
|
+
const active = /* @__PURE__ */ new Set();
|
|
78
|
+
for (let i = 0; i < count; i++) active.add(styles.get(i));
|
|
79
|
+
const out = [];
|
|
80
|
+
for (const [name, get] of AZTEC_OF) if (active.has(get())) out.push(name);
|
|
81
|
+
if (active.has(aztec.AztecTextFormat.FORMAT_LINK)) out.push("link");
|
|
82
|
+
return out;
|
|
83
|
+
};
|
|
84
|
+
var RichTextEditor = defineUniversalComponent("nativescript", function RichTextEditor(props) {
|
|
85
|
+
const host = useRef(null, _h$0);
|
|
86
|
+
const lastEmitted = useRef(void 0, _h$1);
|
|
87
|
+
const applyingValue = useRef(false, _h$2);
|
|
88
|
+
const keyListener = useRef(null, _h$3);
|
|
89
|
+
const emitTimer = useRef(null, _h$4);
|
|
90
|
+
const bindHost = useCallback((v) => {
|
|
91
|
+
host.current = v;
|
|
92
|
+
}, [], _h$5);
|
|
93
|
+
const editor = () => host.current?.android;
|
|
94
|
+
const setEditable = (next) => {
|
|
95
|
+
const e = editor();
|
|
96
|
+
if (!e) return;
|
|
97
|
+
if (keyListener.current == null) keyListener.current = e.getKeyListener();
|
|
98
|
+
if (next) {
|
|
99
|
+
e.setKeyListener(keyListener.current);
|
|
100
|
+
e.setFocusable(true);
|
|
101
|
+
e.setCursorVisible(true);
|
|
102
|
+
} else {
|
|
103
|
+
e.setKeyListener(null);
|
|
104
|
+
e.setFocusable(false);
|
|
105
|
+
e.setCursorVisible(false);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
const showKeyboard = (e) => {
|
|
109
|
+
try {
|
|
110
|
+
e.getContext().getSystemService(globalThis.android.content.Context.INPUT_METHOD_SERVICE)?.showSoftInput(e, androidView.inputmethod.InputMethodManager.SHOW_IMPLICIT);
|
|
111
|
+
} catch {}
|
|
112
|
+
};
|
|
113
|
+
const emitChange = () => {
|
|
114
|
+
if (!editor() || applyingValue.current) return;
|
|
115
|
+
clearTimeout(emitTimer.current);
|
|
116
|
+
emitTimer.current = setTimeout(() => {
|
|
117
|
+
const current = editor();
|
|
118
|
+
if (!current || applyingValue.current) return;
|
|
119
|
+
const html = current.toPlainHtml(false);
|
|
120
|
+
lastEmitted.current = html;
|
|
121
|
+
props.onChange?.(html);
|
|
122
|
+
}, 150);
|
|
123
|
+
};
|
|
124
|
+
const handle = (e) => ({
|
|
125
|
+
getHTML: () => e.toPlainHtml(false),
|
|
126
|
+
setHTML: (html) => {
|
|
127
|
+
applyingValue.current = true;
|
|
128
|
+
try {
|
|
129
|
+
e.fromHtml(html, true);
|
|
130
|
+
lastEmitted.current = html;
|
|
131
|
+
} finally {
|
|
132
|
+
applyingValue.current = false;
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
apply: (format) => {
|
|
136
|
+
if (format === "link") return;
|
|
137
|
+
e.toggleFormatting(formatOf(format));
|
|
138
|
+
emitChange();
|
|
139
|
+
},
|
|
140
|
+
linkTo: (url, anchor) => {
|
|
141
|
+
e.link(url, anchor ?? url, false);
|
|
142
|
+
emitChange();
|
|
143
|
+
},
|
|
144
|
+
removeLink: () => {
|
|
145
|
+
e.removeLink();
|
|
146
|
+
emitChange();
|
|
147
|
+
},
|
|
148
|
+
isActive: (format) => {
|
|
149
|
+
const applied = e.getAppliedStyles(e.getSelectionStart(), e.getSelectionEnd());
|
|
150
|
+
const count = applied?.size?.() ?? 0;
|
|
151
|
+
const target = formatOf(format);
|
|
152
|
+
for (let i = 0; i < count; i++) if (applied.get(i) === target) return true;
|
|
153
|
+
return false;
|
|
154
|
+
},
|
|
155
|
+
undo: () => e.undo(),
|
|
156
|
+
redo: () => e.redo(),
|
|
157
|
+
focus: () => {
|
|
158
|
+
e.requestFocus();
|
|
159
|
+
showKeyboard(e);
|
|
160
|
+
},
|
|
161
|
+
blur: () => e.clearFocus(),
|
|
162
|
+
isFocused: () => e.isFocused(),
|
|
163
|
+
native: e
|
|
164
|
+
});
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
const e = editor();
|
|
167
|
+
if (!e || !aztec) return;
|
|
168
|
+
e.setOnSelectionChangedListener(new aztec.AztecText.OnSelectionChangedListener({ onSelectionChanged: (start, end) => props.onSelectionChange?.({
|
|
169
|
+
start,
|
|
170
|
+
end,
|
|
171
|
+
active: activeFormats(e)
|
|
172
|
+
}) }));
|
|
173
|
+
e.addTextChangedListener(new androidText.TextWatcher({
|
|
174
|
+
beforeTextChanged: () => {},
|
|
175
|
+
onTextChanged: () => {},
|
|
176
|
+
afterTextChanged: () => emitChange()
|
|
177
|
+
}));
|
|
178
|
+
e.setOnFocusChangeListener(new androidView.View.OnFocusChangeListener({ onFocusChange: (_v, focused) => focused ? props.onFocus?.() : props.onBlur?.() }));
|
|
179
|
+
if (props.placeholder !== void 0) e.setHint(props.placeholder);
|
|
180
|
+
setEditable(props.editable !== false);
|
|
181
|
+
if (props.value !== void 0) {
|
|
182
|
+
applyingValue.current = true;
|
|
183
|
+
try {
|
|
184
|
+
e.fromHtml(props.value, true);
|
|
185
|
+
} finally {
|
|
186
|
+
applyingValue.current = false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (props.autofocus) {
|
|
190
|
+
e.requestFocus();
|
|
191
|
+
showKeyboard(e);
|
|
192
|
+
}
|
|
193
|
+
if (props.android) Object.assign(e, props.android);
|
|
194
|
+
props.bind?.(handle(e));
|
|
195
|
+
props.onReady?.();
|
|
196
|
+
}, [], _h$6);
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
const e = editor();
|
|
199
|
+
if (!e || props.value === void 0) return;
|
|
200
|
+
if (props.value === lastEmitted.current) return;
|
|
201
|
+
applyingValue.current = true;
|
|
202
|
+
try {
|
|
203
|
+
e.fromHtml(props.value, true);
|
|
204
|
+
} finally {
|
|
205
|
+
applyingValue.current = false;
|
|
206
|
+
}
|
|
207
|
+
}, [props.value], _h$7);
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
setEditable(props.editable !== false);
|
|
210
|
+
}, [props.editable], _h$8);
|
|
211
|
+
useEffect(() => {
|
|
212
|
+
const e = editor();
|
|
213
|
+
if (e && props.placeholder !== void 0) e.setHint(props.placeholder);
|
|
214
|
+
}, [props.placeholder], _h$9);
|
|
215
|
+
return universalValue(__octaneUniversalPlan0, [
|
|
216
|
+
bindHost,
|
|
217
|
+
props.id,
|
|
218
|
+
props.className,
|
|
219
|
+
props.style,
|
|
220
|
+
props.row,
|
|
221
|
+
props.col,
|
|
222
|
+
props.rowSpan,
|
|
223
|
+
props.colSpan,
|
|
224
|
+
props.dock,
|
|
225
|
+
props.left,
|
|
226
|
+
props.top,
|
|
227
|
+
props.flexGrow,
|
|
228
|
+
props.flexShrink,
|
|
229
|
+
props.alignSelf,
|
|
230
|
+
props.order
|
|
231
|
+
]);
|
|
232
|
+
}, { "module": "@nativescript-community/octane" });
|
|
233
|
+
//#endregion
|
|
234
|
+
export { RichTextEditor, supported };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineUniversalComponent, universalPlan, universalValue } from "@nativescript-community/octane";
|
|
2
|
+
//#region src/RichTextEditor.ios.tsrx
|
|
3
|
+
var __octaneUniversalPlan0 = universalPlan("nativescript", {
|
|
4
|
+
"kind": "host",
|
|
5
|
+
"type": "label",
|
|
6
|
+
"props": { "text": "Rich text editing is not supported on iOS yet." },
|
|
7
|
+
"bindings": [
|
|
8
|
+
["id", 0],
|
|
9
|
+
["className", 1],
|
|
10
|
+
["style", 2]
|
|
11
|
+
]
|
|
12
|
+
});
|
|
13
|
+
var supported = false;
|
|
14
|
+
var RichTextEditor = defineUniversalComponent("nativescript", function RichTextEditor(props) {
|
|
15
|
+
return universalValue(__octaneUniversalPlan0, [
|
|
16
|
+
props.id,
|
|
17
|
+
props.className,
|
|
18
|
+
props.style
|
|
19
|
+
]);
|
|
20
|
+
}, { "module": "@nativescript-community/octane" });
|
|
21
|
+
//#endregion
|
|
22
|
+
export { RichTextEditor, supported };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/index.web.ts
|
|
2
|
+
var supported = false;
|
|
3
|
+
/** Web routes through @octane-xplat/tiptap — this leaf only exists for the
|
|
4
|
+
* native (Aztec) surface. */
|
|
5
|
+
function RichTextEditor(_props) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
//#endregion
|
|
9
|
+
export { RichTextEditor, supported };
|
package/package.json
CHANGED
|
@@ -1,6 +1,83 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octane-xplat/richtext",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Native rich-text editing leaf for Octane xplat — WordPress Aztec on Android (iOS stubbed, deferred)",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"android",
|
|
7
|
+
"aztec",
|
|
8
|
+
"cross-platform",
|
|
9
|
+
"editor",
|
|
10
|
+
"octane",
|
|
11
|
+
"rich-text",
|
|
12
|
+
"richtext",
|
|
13
|
+
"xplat"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/octane-xplat/octane-xplat/tree/main/packages/richtext",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/octane-xplat/octane-xplat",
|
|
20
|
+
"directory": "packages/richtext"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
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
|
+
"devDependencies": {
|
|
51
|
+
"@nativescript-community/octane": "0.2.4",
|
|
52
|
+
"@nativescript/core": "9.1.2",
|
|
53
|
+
"@octanejs/vite-plugin": "0.1.61",
|
|
54
|
+
"octane": "0.6.3",
|
|
55
|
+
"typescript": "5.9.3",
|
|
56
|
+
"vite": "8.3.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@nativescript-community/octane": ">=0.2.1 <1",
|
|
60
|
+
"@nativescript/core": ">=9.1.0 <10",
|
|
61
|
+
"octane": ">=0.6.3 <1"
|
|
62
|
+
},
|
|
63
|
+
"peerDependenciesMeta": {
|
|
64
|
+
"@nativescript-community/octane": {
|
|
65
|
+
"optional": true
|
|
66
|
+
},
|
|
67
|
+
"@nativescript/core": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"octane": {
|
|
71
|
+
"optional": true
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"nativescript": {
|
|
75
|
+
"platforms": {
|
|
76
|
+
"android": "9.1.0",
|
|
77
|
+
"ios": "9.1.0"
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"scripts": {
|
|
81
|
+
"build": "vite build && vite build --mode native && tsc -p tsconfig.types.json && node ../../scripts/check-native-dist.mjs"
|
|
82
|
+
}
|
|
6
83
|
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/** @jsxImportSource @nativescript-community/octane */
|
|
2
|
+
import { useCallback, useEffect, useRef } from 'octane'
|
|
3
|
+
import { View } from '@nativescript/core'
|
|
4
|
+
import { registerElement } from '@nativescript-community/octane'
|
|
5
|
+
import type { Attributes } from '@nativescript-community/octane/intrinsics'
|
|
6
|
+
import type {
|
|
7
|
+
RichTextEditorHandle,
|
|
8
|
+
RichTextEditorProps,
|
|
9
|
+
RichTextFormat,
|
|
10
|
+
} from './types'
|
|
11
|
+
|
|
12
|
+
/** `org.wordpress.aztec` arrives through the leaf's aar — there are no
|
|
13
|
+
* generated typings for it, so every touch goes through `as any` at one
|
|
14
|
+
* boundary instead of `declare const org` per call site. */
|
|
15
|
+
const aztec = (globalThis as any).org?.wordpress?.aztec
|
|
16
|
+
const androidText = (globalThis as any).android?.text
|
|
17
|
+
const androidView = (globalThis as any).android?.view
|
|
18
|
+
|
|
19
|
+
const FORMATS: Record<Exclude<RichTextFormat, 'link'>, () => any> = {
|
|
20
|
+
bold: () => aztec.AztecTextFormat.FORMAT_BOLD,
|
|
21
|
+
italic: () => aztec.AztecTextFormat.FORMAT_ITALIC,
|
|
22
|
+
underline: () => aztec.AztecTextFormat.FORMAT_UNDERLINE,
|
|
23
|
+
strikethrough: () => aztec.AztecTextFormat.FORMAT_STRIKETHROUGH,
|
|
24
|
+
code: () => aztec.AztecTextFormat.FORMAT_CODE,
|
|
25
|
+
blockquote: () => aztec.AztecTextFormat.FORMAT_QUOTE,
|
|
26
|
+
bulletList: () => aztec.AztecTextFormat.FORMAT_UNORDERED_LIST,
|
|
27
|
+
orderedList: () => aztec.AztecTextFormat.FORMAT_ORDERED_LIST,
|
|
28
|
+
taskList: () => aztec.AztecTextFormat.FORMAT_TASK_LIST,
|
|
29
|
+
codeBlock: () => aztec.AztecTextFormat.FORMAT_PREFORMAT,
|
|
30
|
+
horizontalRule: () => aztec.AztecTextFormat.FORMAT_HORIZONTAL_RULE,
|
|
31
|
+
highlight: () => aztec.AztecTextFormat.FORMAT_MARK,
|
|
32
|
+
subscript: () => aztec.AztecTextFormat.FORMAT_SUBSCRIPT,
|
|
33
|
+
superscript: () => aztec.AztecTextFormat.FORMAT_SUPERSCRIPT,
|
|
34
|
+
paragraph: () => aztec.AztecTextFormat.FORMAT_PARAGRAPH,
|
|
35
|
+
heading1: () => aztec.AztecTextFormat.FORMAT_HEADING_1,
|
|
36
|
+
heading2: () => aztec.AztecTextFormat.FORMAT_HEADING_2,
|
|
37
|
+
heading3: () => aztec.AztecTextFormat.FORMAT_HEADING_3,
|
|
38
|
+
heading4: () => aztec.AztecTextFormat.FORMAT_HEADING_4,
|
|
39
|
+
heading5: () => aztec.AztecTextFormat.FORMAT_HEADING_5,
|
|
40
|
+
heading6: () => aztec.AztecTextFormat.FORMAT_HEADING_6,
|
|
41
|
+
alignLeft: () => aztec.AztecTextFormat.FORMAT_ALIGN_LEFT,
|
|
42
|
+
alignCenter: () => aztec.AztecTextFormat.FORMAT_ALIGN_CENTER,
|
|
43
|
+
alignRight: () => aztec.AztecTextFormat.FORMAT_ALIGN_RIGHT,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const AZTEC_OF: [RichTextFormat, () => any][] = Object.entries(FORMATS) as any
|
|
47
|
+
|
|
48
|
+
const formatOf = (format: RichTextFormat) =>
|
|
49
|
+
format === 'link' ? aztec.AztecTextFormat.FORMAT_LINK : FORMATS[format]()
|
|
50
|
+
|
|
51
|
+
class AztecEditor extends View {
|
|
52
|
+
createNativeView() {
|
|
53
|
+
return new aztec.AztecText(this._context)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
declare module '@nativescript-community/octane/intrinsics' {
|
|
58
|
+
interface NativeScriptElements {
|
|
59
|
+
xplatrichtext: Attributes<typeof AztecEditor>
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
registerElement('xplatrichtext', AztecEditor)
|
|
64
|
+
|
|
65
|
+
export const supported = true
|
|
66
|
+
|
|
67
|
+
/** Active formats at the selection, mapped back to facade spellings. */
|
|
68
|
+
const activeFormats = (editor: any): RichTextFormat[] => {
|
|
69
|
+
const styles = editor.getAppliedStyles(
|
|
70
|
+
editor.getSelectionStart(),
|
|
71
|
+
editor.getSelectionEnd(),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const count = styles?.size?.() ?? 0
|
|
75
|
+
const active = new Set<any>()
|
|
76
|
+
for (let i = 0; i < count; i++) active.add(styles.get(i))
|
|
77
|
+
|
|
78
|
+
const out: RichTextFormat[] = []
|
|
79
|
+
for (const [name, get] of AZTEC_OF) {
|
|
80
|
+
if (active.has(get())) out.push(name)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (active.has(aztec.AztecTextFormat.FORMAT_LINK)) out.push('link')
|
|
84
|
+
return out
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** WordPress Aztec surface on Android — a true Spannable-backed editor, not
|
|
88
|
+
* a WebView. Content in/out is HTML (`fromHtml`/`toPlainHtml`); JSON
|
|
89
|
+
* interchange lives one layer up in @octane-xplat/tiptap. */
|
|
90
|
+
export function RichTextEditor(props: RichTextEditorProps) @{
|
|
91
|
+
const host = useRef<any>(null)
|
|
92
|
+
const lastEmitted = useRef<string | undefined>(undefined)
|
|
93
|
+
const applyingValue = useRef(false)
|
|
94
|
+
const keyListener = useRef<any>(null)
|
|
95
|
+
const emitTimer = useRef<any>(null)
|
|
96
|
+
|
|
97
|
+
// Stable identity — an inline closure re-fires ref(null)/ref(v) per
|
|
98
|
+
// render, which would bounce host.current around the effects below.
|
|
99
|
+
const bindHost = useCallback((v: any) => {
|
|
100
|
+
host.current = v
|
|
101
|
+
}, [])
|
|
102
|
+
|
|
103
|
+
const editor = () => host.current?.android
|
|
104
|
+
|
|
105
|
+
const setEditable = (next: boolean) => {
|
|
106
|
+
const e = editor()
|
|
107
|
+
if (!e) return
|
|
108
|
+
if (keyListener.current == null) keyListener.current = e.getKeyListener()
|
|
109
|
+
if (next) {
|
|
110
|
+
e.setKeyListener(keyListener.current)
|
|
111
|
+
e.setFocusable(true)
|
|
112
|
+
e.setCursorVisible(true)
|
|
113
|
+
} else {
|
|
114
|
+
e.setKeyListener(null)
|
|
115
|
+
e.setFocusable(false)
|
|
116
|
+
e.setCursorVisible(false)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const showKeyboard = (e: any) => {
|
|
121
|
+
try {
|
|
122
|
+
const imm = e.getContext().getSystemService(
|
|
123
|
+
(globalThis as any).android.content.Context.INPUT_METHOD_SERVICE,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
imm?.showSoftInput(e, androidView.inputmethod.InputMethodManager.SHOW_IMPLICIT)
|
|
127
|
+
} catch {}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const emitChange = () => {
|
|
131
|
+
const e = editor()
|
|
132
|
+
if (!e || applyingValue.current) return
|
|
133
|
+
clearTimeout(emitTimer.current)
|
|
134
|
+
emitTimer.current = setTimeout(() => {
|
|
135
|
+
const current = editor()
|
|
136
|
+
if (!current || applyingValue.current) return
|
|
137
|
+
const html = current.toPlainHtml(false)
|
|
138
|
+
lastEmitted.current = html
|
|
139
|
+
props.onChange?.(html)
|
|
140
|
+
}, 150)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const handle = (e: any): RichTextEditorHandle => ({
|
|
144
|
+
getHTML: () => e.toPlainHtml(false),
|
|
145
|
+
setHTML: (html: string) => {
|
|
146
|
+
applyingValue.current = true
|
|
147
|
+
try {
|
|
148
|
+
e.fromHtml(html, true)
|
|
149
|
+
lastEmitted.current = html
|
|
150
|
+
} finally {
|
|
151
|
+
applyingValue.current = false
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
apply: (format: RichTextFormat) => {
|
|
155
|
+
if (format === 'link') return
|
|
156
|
+
e.toggleFormatting(formatOf(format))
|
|
157
|
+
emitChange()
|
|
158
|
+
},
|
|
159
|
+
linkTo: (url: string, anchor?: string) => {
|
|
160
|
+
e.link(url, anchor ?? url, false)
|
|
161
|
+
emitChange()
|
|
162
|
+
},
|
|
163
|
+
removeLink: () => {
|
|
164
|
+
e.removeLink()
|
|
165
|
+
emitChange()
|
|
166
|
+
},
|
|
167
|
+
isActive: (format: RichTextFormat) => {
|
|
168
|
+
const applied = e.getAppliedStyles(e.getSelectionStart(), e.getSelectionEnd())
|
|
169
|
+
const count = applied?.size?.() ?? 0
|
|
170
|
+
const target = formatOf(format)
|
|
171
|
+
for (let i = 0; i < count; i++) {
|
|
172
|
+
if (applied.get(i) === target) return true
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return false
|
|
176
|
+
},
|
|
177
|
+
undo: () => e.undo(),
|
|
178
|
+
redo: () => e.redo(),
|
|
179
|
+
focus: () => {
|
|
180
|
+
e.requestFocus()
|
|
181
|
+
showKeyboard(e)
|
|
182
|
+
},
|
|
183
|
+
blur: () => e.clearFocus(),
|
|
184
|
+
isFocused: () => e.isFocused(),
|
|
185
|
+
native: e,
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
const e = editor()
|
|
190
|
+
if (!e || !aztec) return
|
|
191
|
+
|
|
192
|
+
e.setOnSelectionChangedListener(
|
|
193
|
+
new aztec.AztecText.OnSelectionChangedListener({
|
|
194
|
+
onSelectionChanged: (start: number, end: number) =>
|
|
195
|
+
props.onSelectionChange?.({
|
|
196
|
+
start,
|
|
197
|
+
end,
|
|
198
|
+
active: activeFormats(e),
|
|
199
|
+
}),
|
|
200
|
+
}),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
e.addTextChangedListener(
|
|
204
|
+
new androidText.TextWatcher({
|
|
205
|
+
beforeTextChanged: () => {},
|
|
206
|
+
onTextChanged: () => {},
|
|
207
|
+
afterTextChanged: () => emitChange(),
|
|
208
|
+
}),
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
e.setOnFocusChangeListener(
|
|
212
|
+
new androidView.View.OnFocusChangeListener({
|
|
213
|
+
onFocusChange: (_v: any, focused: boolean) =>
|
|
214
|
+
focused ? props.onFocus?.() : props.onBlur?.(),
|
|
215
|
+
}),
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
if (props.placeholder !== undefined) e.setHint(props.placeholder)
|
|
219
|
+
setEditable(props.editable !== false)
|
|
220
|
+
|
|
221
|
+
if (props.value !== undefined) {
|
|
222
|
+
applyingValue.current = true
|
|
223
|
+
try {
|
|
224
|
+
e.fromHtml(props.value, true)
|
|
225
|
+
} finally {
|
|
226
|
+
applyingValue.current = false
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (props.autofocus) {
|
|
231
|
+
e.requestFocus()
|
|
232
|
+
showKeyboard(e)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (props.android) Object.assign(e, props.android)
|
|
236
|
+
props.bind?.(handle(e))
|
|
237
|
+
props.onReady?.()
|
|
238
|
+
}, [])
|
|
239
|
+
|
|
240
|
+
// `value` lands like a controlled prop, minus the loop: text the editor
|
|
241
|
+
// emitted through onChange is never pushed back; any other change is an
|
|
242
|
+
// external reset (and resets Aztec's undo stack, like PM setContent).
|
|
243
|
+
useEffect(() => {
|
|
244
|
+
const e = editor()
|
|
245
|
+
if (!e || props.value === undefined) return
|
|
246
|
+
if (props.value === lastEmitted.current) return
|
|
247
|
+
applyingValue.current = true
|
|
248
|
+
try {
|
|
249
|
+
e.fromHtml(props.value, true)
|
|
250
|
+
} finally {
|
|
251
|
+
applyingValue.current = false
|
|
252
|
+
}
|
|
253
|
+
}, [props.value])
|
|
254
|
+
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
setEditable(props.editable !== false)
|
|
257
|
+
}, [props.editable])
|
|
258
|
+
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
const e = editor()
|
|
261
|
+
if (e && props.placeholder !== undefined) e.setHint(props.placeholder)
|
|
262
|
+
}, [props.placeholder])
|
|
263
|
+
|
|
264
|
+
<xplatrichtext
|
|
265
|
+
ref={bindHost}
|
|
266
|
+
id={props.id}
|
|
267
|
+
className={props.className}
|
|
268
|
+
style={props.style}
|
|
269
|
+
row={props.row}
|
|
270
|
+
col={props.col}
|
|
271
|
+
rowSpan={props.rowSpan}
|
|
272
|
+
colSpan={props.colSpan}
|
|
273
|
+
dock={props.dock}
|
|
274
|
+
left={props.left}
|
|
275
|
+
top={props.top}
|
|
276
|
+
flexGrow={props.flexGrow}
|
|
277
|
+
flexShrink={props.flexShrink}
|
|
278
|
+
alignSelf={props.alignSelf}
|
|
279
|
+
order={props.order}
|
|
280
|
+
/>;
|
|
281
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** @jsxImportSource @nativescript-community/octane */
|
|
2
|
+
import type { RichTextEditorProps } from './types'
|
|
3
|
+
|
|
4
|
+
export const supported = false
|
|
5
|
+
|
|
6
|
+
/** iOS bring-up is deferred — the Swift facade over Aztec-iOS lands in a
|
|
7
|
+
* follow-up. Renders the placeholder so a shared screen still mounts. */
|
|
8
|
+
export function RichTextEditor(props: RichTextEditorProps) @{
|
|
9
|
+
<label
|
|
10
|
+
id={props.id}
|
|
11
|
+
className={props.className}
|
|
12
|
+
style={props.style}
|
|
13
|
+
text="Rich text editing is not supported on iOS yet."
|
|
14
|
+
/>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RichTextEditorProps } from './types'
|
|
2
|
+
|
|
3
|
+
export const supported = false
|
|
4
|
+
|
|
5
|
+
/** The macOS editor is deferred; the tiptap facade covers desktop needs. */
|
|
6
|
+
export function RichTextEditor(_props: RichTextEditorProps): null {
|
|
7
|
+
return null
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type * from './types'
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Keep this relative import extensionless so the NativeScript resolver picks
|
|
2
|
+
// the Android (Aztec) or iOS (stub until the Swift facade lands) component
|
|
3
|
+
// for the current build target.
|
|
4
|
+
export { RichTextEditor, supported } from './RichTextEditor'
|
|
5
|
+
export type * from './types'
|
package/src/index.web.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RichTextEditorProps } from './types'
|
|
2
|
+
|
|
3
|
+
export const supported = false
|
|
4
|
+
|
|
5
|
+
/** Web routes through @octane-xplat/tiptap — this leaf only exists for the
|
|
6
|
+
* native (Aztec) surface. */
|
|
7
|
+
export function RichTextEditor(_props: RichTextEditorProps): null {
|
|
8
|
+
return null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type * from './types'
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/** Supported on Android today (WordPress Aztec); iOS is stubbed until the
|
|
2
|
+
* Swift-facade bring-up. Web/macOS/Windows route through the tiptap facade
|
|
3
|
+
* instead of this leaf. */
|
|
4
|
+
export type RichTextFormat =
|
|
5
|
+
| 'bold'
|
|
6
|
+
| 'italic'
|
|
7
|
+
| 'underline'
|
|
8
|
+
| 'strikethrough'
|
|
9
|
+
| 'code'
|
|
10
|
+
| 'blockquote'
|
|
11
|
+
| 'bulletList'
|
|
12
|
+
| 'orderedList'
|
|
13
|
+
| 'taskList'
|
|
14
|
+
| 'codeBlock'
|
|
15
|
+
| 'horizontalRule'
|
|
16
|
+
| 'highlight'
|
|
17
|
+
| 'subscript'
|
|
18
|
+
| 'superscript'
|
|
19
|
+
| 'paragraph'
|
|
20
|
+
| 'heading1'
|
|
21
|
+
| 'heading2'
|
|
22
|
+
| 'heading3'
|
|
23
|
+
| 'heading4'
|
|
24
|
+
| 'heading5'
|
|
25
|
+
| 'heading6'
|
|
26
|
+
| 'alignLeft'
|
|
27
|
+
| 'alignCenter'
|
|
28
|
+
| 'alignRight'
|
|
29
|
+
| 'link'
|
|
30
|
+
|
|
31
|
+
export interface RichTextEditorHandle {
|
|
32
|
+
/** Serialized document HTML (Aztec's "plain" HTML — no contenteditable
|
|
33
|
+
* markup). */
|
|
34
|
+
getHTML(): string
|
|
35
|
+
/** Replace the document; resets Aztec's undo history. */
|
|
36
|
+
setHTML(html: string): void
|
|
37
|
+
/** Toggle a format at the current selection. `link` needs
|
|
38
|
+
* `linkTo(url, anchor)` instead. */
|
|
39
|
+
apply(format: RichTextFormat): void
|
|
40
|
+
/** Insert or re-target a link over the current selection. */
|
|
41
|
+
linkTo(url: string, anchor?: string): void
|
|
42
|
+
removeLink(): void
|
|
43
|
+
/** Formats active at the selection — powers toolbar highlight state. */
|
|
44
|
+
isActive(format: RichTextFormat): boolean
|
|
45
|
+
/** Android: Aztec's history batches keyboard input only — format
|
|
46
|
+
* toggles and programmatic edits do not register. */
|
|
47
|
+
undo(): void
|
|
48
|
+
redo(): void
|
|
49
|
+
focus(): void
|
|
50
|
+
blur(): void
|
|
51
|
+
isFocused(): boolean
|
|
52
|
+
/** The platform surface — `AztecText` on Android. */
|
|
53
|
+
native: any
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface RichTextSelectionChange {
|
|
57
|
+
start: number
|
|
58
|
+
end: number
|
|
59
|
+
/** Formats active at the caret/range, in facade spellings. */
|
|
60
|
+
active: RichTextFormat[]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface RichTextEditorProps {
|
|
64
|
+
id?: string
|
|
65
|
+
className?: string
|
|
66
|
+
style?: any
|
|
67
|
+
/** Initial document HTML. Re-applied only when the string changes
|
|
68
|
+
* externally — text the editor emitted back through `onChange` is never
|
|
69
|
+
* re-pushed, so typing doesn't reparse the document. */
|
|
70
|
+
value?: string
|
|
71
|
+
placeholder?: string
|
|
72
|
+
editable?: boolean
|
|
73
|
+
autofocus?: boolean
|
|
74
|
+
onReady?: () => void
|
|
75
|
+
onChange?: (html: string) => void
|
|
76
|
+
onSelectionChange?: (event: RichTextSelectionChange) => void
|
|
77
|
+
onFocus?: () => void
|
|
78
|
+
onBlur?: () => void
|
|
79
|
+
/** Imperative handle — fires once when the native editor exists. */
|
|
80
|
+
bind?: (handle: RichTextEditorHandle) => void
|
|
81
|
+
/** Per-platform escape bag — properties are assigned onto the native
|
|
82
|
+
* `AztecText` after the leaf's own props. */
|
|
83
|
+
android?: Record<string, any>
|
|
84
|
+
ios?: Record<string, any>
|
|
85
|
+
// NativeScript layout-child attributes the driver reads on this element.
|
|
86
|
+
row?: number | string
|
|
87
|
+
col?: number | string
|
|
88
|
+
rowSpan?: number
|
|
89
|
+
colSpan?: number
|
|
90
|
+
dock?: 'left' | 'top' | 'right' | 'bottom'
|
|
91
|
+
left?: number
|
|
92
|
+
top?: number
|
|
93
|
+
flexGrow?: number
|
|
94
|
+
flexShrink?: number
|
|
95
|
+
alignSelf?: 'auto' | 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline'
|
|
96
|
+
order?: number
|
|
97
|
+
}
|
package/types/index.d.ts
ADDED
package/types/types.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** Supported on Android today (WordPress Aztec); iOS is stubbed until the
|
|
2
|
+
* Swift-facade bring-up. Web/macOS/Windows route through the tiptap facade
|
|
3
|
+
* instead of this leaf. */
|
|
4
|
+
export type RichTextFormat = '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';
|
|
5
|
+
export interface RichTextEditorHandle {
|
|
6
|
+
/** Serialized document HTML (Aztec's "plain" HTML — no contenteditable
|
|
7
|
+
* markup). */
|
|
8
|
+
getHTML(): string;
|
|
9
|
+
/** Replace the document; resets Aztec's undo history. */
|
|
10
|
+
setHTML(html: string): void;
|
|
11
|
+
/** Toggle a format at the current selection. `link` needs
|
|
12
|
+
* `linkTo(url, anchor)` instead. */
|
|
13
|
+
apply(format: RichTextFormat): void;
|
|
14
|
+
/** Insert or re-target a link over the current selection. */
|
|
15
|
+
linkTo(url: string, anchor?: string): void;
|
|
16
|
+
removeLink(): void;
|
|
17
|
+
/** Formats active at the selection — powers toolbar highlight state. */
|
|
18
|
+
isActive(format: RichTextFormat): boolean;
|
|
19
|
+
/** Android: Aztec's history batches keyboard input only — format
|
|
20
|
+
* toggles and programmatic edits do not register. */
|
|
21
|
+
undo(): void;
|
|
22
|
+
redo(): void;
|
|
23
|
+
focus(): void;
|
|
24
|
+
blur(): void;
|
|
25
|
+
isFocused(): boolean;
|
|
26
|
+
/** The platform surface — `AztecText` on Android. */
|
|
27
|
+
native: any;
|
|
28
|
+
}
|
|
29
|
+
export interface RichTextSelectionChange {
|
|
30
|
+
start: number;
|
|
31
|
+
end: number;
|
|
32
|
+
/** Formats active at the caret/range, in facade spellings. */
|
|
33
|
+
active: RichTextFormat[];
|
|
34
|
+
}
|
|
35
|
+
export interface RichTextEditorProps {
|
|
36
|
+
id?: string;
|
|
37
|
+
className?: string;
|
|
38
|
+
style?: any;
|
|
39
|
+
/** Initial document HTML. Re-applied only when the string changes
|
|
40
|
+
* externally — text the editor emitted back through `onChange` is never
|
|
41
|
+
* re-pushed, so typing doesn't reparse the document. */
|
|
42
|
+
value?: string;
|
|
43
|
+
placeholder?: string;
|
|
44
|
+
editable?: boolean;
|
|
45
|
+
autofocus?: boolean;
|
|
46
|
+
onReady?: () => void;
|
|
47
|
+
onChange?: (html: string) => void;
|
|
48
|
+
onSelectionChange?: (event: RichTextSelectionChange) => void;
|
|
49
|
+
onFocus?: () => void;
|
|
50
|
+
onBlur?: () => void;
|
|
51
|
+
/** Imperative handle — fires once when the native editor exists. */
|
|
52
|
+
bind?: (handle: RichTextEditorHandle) => void;
|
|
53
|
+
/** Per-platform escape bag — properties are assigned onto the native
|
|
54
|
+
* `AztecText` after the leaf's own props. */
|
|
55
|
+
android?: Record<string, any>;
|
|
56
|
+
ios?: Record<string, any>;
|
|
57
|
+
row?: number | string;
|
|
58
|
+
col?: number | string;
|
|
59
|
+
rowSpan?: number;
|
|
60
|
+
colSpan?: number;
|
|
61
|
+
dock?: 'left' | 'top' | 'right' | 'bottom';
|
|
62
|
+
left?: number;
|
|
63
|
+
top?: number;
|
|
64
|
+
flexGrow?: number;
|
|
65
|
+
flexShrink?: number;
|
|
66
|
+
alignSelf?: 'auto' | 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline';
|
|
67
|
+
order?: number;
|
|
68
|
+
}
|