@octane-xplat/richtext 0.13.0 → 0.15.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/README.md CHANGED
@@ -52,9 +52,9 @@ export function Formatting() {
52
52
  }}
53
53
  onSelectionChange={({ active }) => console.log(active)}
54
54
  />
55
- <Button onPress={() => editor.current?.apply('bold')}>Bold</Button>
55
+ <Button clickAction={() => editor.current?.apply('bold')}>Bold</Button>
56
56
  <Button
57
- onPress={() => {
57
+ clickAction={() => {
58
58
  const handle = editor.current
59
59
  if (!handle) return
60
60
  handle.setHTML('<p>New note</p>')
@@ -10,8 +10,7 @@ export default {
10
10
  {
11
11
  name: 'AztecEditor-iOS',
12
12
  libs: ['Aztec'],
13
- repositoryURL:
14
- 'https://github.com/wordpress-mobile/AztecEditor-iOS',
13
+ repositoryURL: 'https://github.com/wordpress-mobile/AztecEditor-iOS',
15
14
  // '#' prefix selects a revision requirement in the generated
16
15
  // pbxproj — develop @ 44aeac6 (podspec version 1.20.0, first
17
16
  // with SPM support). MPL-2.0; see platforms/ios/UPSTREAM.md.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octane-xplat/richtext",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "Native rich-text editing leaf for Octane xplat — Android Aztec and AppKit WKWebView StarterKit",
5
5
  "keywords": [
6
6
  "android",
@@ -20,11 +20,14 @@
20
20
  "directory": "packages/richtext"
21
21
  },
22
22
  "files": [
23
- "dist",
24
23
  "types",
25
24
  "src",
26
25
  "platforms",
27
- "nativescript.config.ts"
26
+ "nativescript.config.ts",
27
+ "dist/macos/browser.js",
28
+ "dist/macos/browser.d.ts",
29
+ "!src/**/*.test.*",
30
+ "!src/**/*.spec.*"
28
31
  ],
29
32
  "type": "module",
30
33
  "exports": {
@@ -35,14 +38,14 @@
35
38
  },
36
39
  "web": {
37
40
  "types": "./types/index.d.ts",
38
- "default": "./dist/web/index.js"
41
+ "default": "./src/index.web.ts"
39
42
  },
40
43
  "native": {
41
44
  "types": "./types/index.d.ts",
42
- "default": "./dist/native/index.js"
45
+ "default": "./src/index.ts"
43
46
  },
44
47
  "types": "./types/index.d.ts",
45
- "default": "./dist/native/index.js"
48
+ "default": "./src/index.ts"
46
49
  },
47
50
  "./macos-host": {
48
51
  "types": "./types/macos-host.d.ts",
@@ -57,15 +60,16 @@
57
60
  "access": "public"
58
61
  },
59
62
  "devDependencies": {
60
- "@nativescript-community/octane": "0.2.4",
63
+ "@nativescript-community/octane": "0.2.5",
61
64
  "@nativescript/core": "9.1.3",
62
- "@octane-xplat/macos-renderer": "0.13.0",
65
+ "@octane-xplat/macos-renderer": "0.15.0",
63
66
  "@octanejs/vite-plugin": "0.1.61",
64
67
  "@tiptap/core": "3.28.0",
65
68
  "@tiptap/starter-kit": "3.28.0",
66
69
  "octane": "0.11.0",
67
70
  "typescript": "5.9.3",
68
- "vite": "8.3.0"
71
+ "vite": "8.3.0",
72
+ "vitest": "4.1.11"
69
73
  },
70
74
  "peerDependencies": {
71
75
  "@nativescript-community/octane": ">=0.2.1 <1",
@@ -97,9 +101,11 @@
97
101
  }
98
102
  },
99
103
  "scripts": {
100
- "build": "vite build && vite build --mode native && tsc -p tsconfig.types.json && node ../../scripts/check-native-dist.mjs && node build-browser.mjs",
101
104
  "test": "node --test test/*.test.mjs",
102
105
  "test:macos": "node test/verify-wk.mjs",
103
- "test:packed": "node test/packed-consumer.mjs"
106
+ "test:native": "vitest run --config vitest.native.config.mts",
107
+ "test:packed": "node test/packed-consumer.mjs",
108
+ "typegen": "tsc -p tsconfig.types.json",
109
+ "build:assets": "node build-browser.mjs"
104
110
  }
105
111
  }
@@ -1,5 +1,5 @@
1
1
  /** @jsxImportSource @nativescript-community/octane */
2
- import { useImperativeHandle, useCallback, useEffect, useRef } from 'octane'
2
+ import { useImperativeHandle, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'octane'
3
3
  import { View } from '@nativescript/core'
4
4
  import { registerElement } from '@nativescript-community/octane'
5
5
  import type { Attributes } from '@nativescript-community/octane/intrinsics'
@@ -93,13 +93,35 @@ export function RichTextEditor(props: RichTextEditorProps) @{
93
93
  const applyingValue = useRef(false)
94
94
  const keyListener = useRef<any>(null)
95
95
  const emitTimer = useRef<any>(null)
96
+ const loadedOff = useRef<(() => void) | null>(null)
97
+ // The AztecText widget — created by the host view's `_setupUI`, which is
98
+ // deferred while the tree above isn't loaded yet. `null` until `loaded`
99
+ // delivers it; the ref and the init effect below key off this.
100
+ const [nativeView, setNativeView] = useState<any>(null)
96
101
 
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
- useImperativeHandle(props.ref, () => handle(editor()), []);
102
+ // The handle fires only once the native editor exists — before that a
103
+ // consumer gets null, not a stub that throws on every method.
104
+ useImperativeHandle<RichTextEditorHandle | null>(props.ref, () => (nativeView ? handle : null), [nativeView]);
100
105
 
101
106
  const bindHost = useCallback((v: any) => {
107
+ loadedOff.current?.()
108
+ loadedOff.current = null
102
109
  host.current = v
110
+ if (!v) return
111
+ if (v.android) {
112
+ setNativeView(v.android)
113
+ return
114
+ }
115
+
116
+ setNativeView(null)
117
+ // `.android` arrives with `loaded`; subscribe until it does.
118
+ const onLoaded = () => {
119
+ const e = editor()
120
+ if (e) setNativeView(e)
121
+ }
122
+
123
+ v.on('loaded', onLoaded)
124
+ loadedOff.current = () => v.off('loaded', onLoaded)
103
125
  }, [])
104
126
 
105
127
  const editor = () => host.current?.android
@@ -142,9 +164,13 @@ export function RichTextEditor(props: RichTextEditorProps) @{
142
164
  }, 150)
143
165
  }
144
166
 
145
- const handle = (e: any): RichTextEditorHandle => ({
146
- getHTML: () => e.toPlainHtml(false),
167
+ // Methods resolve the editor at call time so a host-view swap can't
168
+ // strand them on a dead widget.
169
+ const handle: RichTextEditorHandle = {
170
+ getHTML: () => editor()?.toPlainHtml(false) ?? '',
147
171
  setHTML: (html: string) => {
172
+ const e = editor()
173
+ if (!e) return
148
174
  applyingValue.current = true
149
175
  try {
150
176
  e.fromHtml(html, true)
@@ -156,18 +182,26 @@ export function RichTextEditor(props: RichTextEditorProps) @{
156
182
  apply: (format: RichTextFormat) => {
157
183
  if (format === 'link') throw new RangeError('Use linkTo(url, anchor) to apply a link')
158
184
  if (!FORMATS[format]) throw new RangeError(`[richtext] Unsupported format: ${format}`)
185
+ const e = editor()
186
+ if (!e) return
159
187
  e.toggleFormatting(formatOf(format))
160
188
  emitChange()
161
189
  },
162
190
  linkTo: (url: string, anchor?: string) => {
191
+ const e = editor()
192
+ if (!e) return
163
193
  e.link(url, anchor ?? url, false)
164
194
  emitChange()
165
195
  },
166
196
  removeLink: () => {
197
+ const e = editor()
198
+ if (!e) return
167
199
  e.removeLink()
168
200
  emitChange()
169
201
  },
170
202
  isActive: (format: RichTextFormat) => {
203
+ const e = editor()
204
+ if (!e) return false
171
205
  const applied = e.getAppliedStyles(e.getSelectionStart(), e.getSelectionEnd())
172
206
  const count = applied?.size?.() ?? 0
173
207
  const target = formatOf(format)
@@ -177,9 +211,11 @@ export function RichTextEditor(props: RichTextEditorProps) @{
177
211
 
178
212
  return false
179
213
  },
180
- undo: () => e.undo(),
181
- redo: () => e.redo(),
214
+ undo: () => editor()?.undo(),
215
+ redo: () => editor()?.redo(),
182
216
  split: () => {
217
+ const e = editor()
218
+ if (!e) return false
183
219
  const selStart = e.getSelectionStart()
184
220
  const selEnd = e.getSelectionEnd()
185
221
  if (selStart < 0 || selEnd < 0) return false
@@ -194,6 +230,8 @@ export function RichTextEditor(props: RichTextEditorProps) @{
194
230
  return true
195
231
  },
196
232
  join: () => {
233
+ const e = editor()
234
+ if (!e) return false
197
235
  const start = e.getSelectionStart()
198
236
  if (start <= 0 || e.getSelectionEnd() !== start) return false
199
237
  const text = e.getText()
@@ -205,55 +243,74 @@ export function RichTextEditor(props: RichTextEditorProps) @{
205
243
  return true
206
244
  },
207
245
  indent: () => {
208
- if (!e.isIndentAvailable()) return false
246
+ const e = editor()
247
+ if (!e || !e.isIndentAvailable()) return false
209
248
  e.indent()
210
249
  emitChange()
211
250
  return true
212
251
  },
213
252
  outdent: () => {
214
- if (!e.isOutdentAvailable()) return false
253
+ const e = editor()
254
+ if (!e || !e.isOutdentAvailable()) return false
215
255
  e.outdent()
216
256
  emitChange()
217
257
  return true
218
258
  },
219
- canIndent: () => Boolean(e.isIndentAvailable()),
220
- canOutdent: () => Boolean(e.isOutdentAvailable()),
259
+ canIndent: () => Boolean(editor()?.isIndentAvailable()),
260
+ canOutdent: () => Boolean(editor()?.isOutdentAvailable()),
221
261
  focus: () => {
262
+ const e = editor()
263
+ if (!e) return
222
264
  e.requestFocus()
223
265
  showKeyboard(e)
224
266
  },
225
- blur: () => e.clearFocus(),
226
- isFocused: () => e.isFocused(),
227
- native: e,
228
- })
267
+ blur: () => editor()?.clearFocus(),
268
+ isFocused: () => editor()?.isFocused() ?? false,
269
+ get native() {
270
+ return editor()
271
+ },
272
+ }
229
273
 
230
- useEffect(() => {
231
- const e = editor()
274
+ // Runs once the native editor exists — after `loaded` on a deferred
275
+ // mount, or right at mount on an already-loaded tree — and again if the
276
+ // platform view is ever recreated. Layout phase keeps the ref publish →
277
+ // value seed → onReady order inside one commit. Listeners guard on
278
+ // `alive` so teardown (or a Kotlin setter that won't take null) can't
279
+ // call back into a dead component.
280
+ useLayoutEffect(() => {
281
+ const e = nativeView
232
282
  if (!e || !aztec) return
283
+ let alive = true
233
284
 
234
285
  e.setOnSelectionChangedListener(
235
286
  new aztec.AztecText.OnSelectionChangedListener({
236
- onSelectionChanged: (start: number, end: number) =>
237
- props.onSelectionChange?.({
238
- start,
239
- end,
240
- active: activeFormats(e),
241
- }),
287
+ onSelectionChanged: (start: number, end: number) => {
288
+ if (alive) {
289
+ props.onSelectionChange?.({
290
+ start,
291
+ end,
292
+ active: activeFormats(e),
293
+ })
294
+ }
295
+ },
242
296
  }),
243
297
  )
244
298
 
245
299
  const watcher = new androidText.TextWatcher({
246
300
  beforeTextChanged: () => {},
247
301
  onTextChanged: () => {},
248
- afterTextChanged: () => emitChange(),
302
+ afterTextChanged: () => {
303
+ if (alive) emitChange()
304
+ },
249
305
  })
250
306
 
251
307
  e.addTextChangedListener(watcher)
252
308
 
253
309
  e.setOnFocusChangeListener(
254
310
  new androidView.View.OnFocusChangeListener({
255
- onFocusChange: (_v: any, focused: boolean) =>
256
- focused ? props.onFocus?.() : props.onBlur?.(),
311
+ onFocusChange: (_v: any, focused: boolean) => {
312
+ if (alive) (focused ? props.onFocus?.() : props.onBlur?.())
313
+ },
257
314
  }),
258
315
  )
259
316
 
@@ -278,11 +335,13 @@ export function RichTextEditor(props: RichTextEditorProps) @{
278
335
  props.onReady?.()
279
336
 
280
337
  return () => {
338
+ alive = false
281
339
  clearTimeout(emitTimer.current)
282
- e.removeTextChangedListener(watcher)
283
- e.setOnFocusChangeListener(null)
340
+ e.removeTextChangedListener?.(watcher)
341
+ e.setOnFocusChangeListener?.(null)
342
+ keyListener.current = null
284
343
  }
285
- }, [])
344
+ }, [nativeView])
286
345
 
287
346
  // `value` lands like a controlled prop, minus the loop: text the editor
288
347
  // emitted through onChange is never pushed back; any other change is an
@@ -0,0 +1,7 @@
1
+ // The WKWebView payload is bundled by build-browser.mjs into dist/macos/
2
+ // before publish; it has no compile-time source. The wildcard keeps the
3
+ // import resolvable in typecheck/typegen programs.
4
+ declare module '*/dist/macos/browser.js' {
5
+ const browser: string
6
+ export default browser
7
+ }
package/types/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
- import type { UniversalComponent } from 'octane/universal'
2
1
  import type { RichTextEditorProps } from './types'
3
2
 
4
- export declare const RichTextEditor: UniversalComponent<RichTextEditorProps>
3
+ export declare function RichTextEditor(props: RichTextEditorProps): unknown
5
4
  export declare const supported: boolean
6
5
  export type * from './types'
@@ -1,5 +1,4 @@
1
- import type { UniversalComponent } from 'octane/universal'
2
- import type { RichTextEditorProps } from '../src/types'
3
- export declare const RichTextEditor: UniversalComponent<RichTextEditorProps>
1
+ import type { RichTextEditorProps } from './types.js'
2
+ export declare function RichTextEditor(props: RichTextEditorProps): unknown
4
3
  export declare const supported: boolean
5
- export type * from '../src/types'
4
+ export type * from './types.js'
@@ -1,11 +1,13 @@
1
- import type { UniversalComponent } from 'octane/universal'
2
- import type { RichTextEditorProps } from '../src/types'
1
+ import type { RichTextEditorProps } from './types.js'
3
2
  /** Internal shared editor host. */
4
- export declare const NativeEditor: UniversalComponent<
5
- RichTextEditorProps & {
3
+ export declare function NativeEditor(
4
+ props: RichTextEditorProps & {
6
5
  browser: string
6
+ // Lexical/Tiptap forward their own handle refs — the host just passes
7
+ // the value through, so it accepts any ref shape.
8
+ ref?: any
7
9
  json?: any
8
10
  onJSONReady?: (ok: boolean) => void
9
11
  onJSONChange?: (doc: any) => void
10
- }
11
- >
12
+ },
13
+ ): unknown
@@ -1,281 +0,0 @@
1
- import { defineUniversalComponent, hookSlots, registerElement, universalPlan, universalValue, useCallback, useEffect, useImperativeHandle, useRef } from "@nativescript-community/octane";
2
- import { View } from "@nativescript/core";
3
- //#region src/RichTextEditor.android.tsrx
4
- var _hs$ = /* @__PURE__ */ hookSlots(11);
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 _h$10 = /* @__PURE__ */ Symbol(_hs$ + 10);
16
- var __octaneUniversalPlan0 = universalPlan("nativescript", {
17
- "kind": "host",
18
- "type": "xplatrichtext",
19
- "bindings": [
20
- ["ref", 0],
21
- ["id", 1],
22
- ["className", 2],
23
- ["style", 3],
24
- ["row", 4],
25
- ["col", 5],
26
- ["rowSpan", 6],
27
- ["colSpan", 7],
28
- ["dock", 8],
29
- ["left", 9],
30
- ["top", 10],
31
- ["flexGrow", 11],
32
- ["flexShrink", 12],
33
- ["alignSelf", 13],
34
- ["order", 14]
35
- ]
36
- });
37
- var aztec = globalThis.org?.wordpress?.aztec;
38
- var androidText = globalThis.android?.text;
39
- var androidView = globalThis.android?.view;
40
- var FORMATS = {
41
- bold: () => aztec.AztecTextFormat.FORMAT_BOLD,
42
- italic: () => aztec.AztecTextFormat.FORMAT_ITALIC,
43
- underline: () => aztec.AztecTextFormat.FORMAT_UNDERLINE,
44
- strikethrough: () => aztec.AztecTextFormat.FORMAT_STRIKETHROUGH,
45
- code: () => aztec.AztecTextFormat.FORMAT_CODE,
46
- blockquote: () => aztec.AztecTextFormat.FORMAT_QUOTE,
47
- bulletList: () => aztec.AztecTextFormat.FORMAT_UNORDERED_LIST,
48
- orderedList: () => aztec.AztecTextFormat.FORMAT_ORDERED_LIST,
49
- taskList: () => aztec.AztecTextFormat.FORMAT_TASK_LIST,
50
- codeBlock: () => aztec.AztecTextFormat.FORMAT_PREFORMAT,
51
- horizontalRule: () => aztec.AztecTextFormat.FORMAT_HORIZONTAL_RULE,
52
- highlight: () => aztec.AztecTextFormat.FORMAT_MARK,
53
- subscript: () => aztec.AztecTextFormat.FORMAT_SUBSCRIPT,
54
- superscript: () => aztec.AztecTextFormat.FORMAT_SUPERSCRIPT,
55
- paragraph: () => aztec.AztecTextFormat.FORMAT_PARAGRAPH,
56
- heading1: () => aztec.AztecTextFormat.FORMAT_HEADING_1,
57
- heading2: () => aztec.AztecTextFormat.FORMAT_HEADING_2,
58
- heading3: () => aztec.AztecTextFormat.FORMAT_HEADING_3,
59
- heading4: () => aztec.AztecTextFormat.FORMAT_HEADING_4,
60
- heading5: () => aztec.AztecTextFormat.FORMAT_HEADING_5,
61
- heading6: () => aztec.AztecTextFormat.FORMAT_HEADING_6,
62
- alignLeft: () => aztec.AztecTextFormat.FORMAT_ALIGN_LEFT,
63
- alignCenter: () => aztec.AztecTextFormat.FORMAT_ALIGN_CENTER,
64
- alignRight: () => aztec.AztecTextFormat.FORMAT_ALIGN_RIGHT
65
- };
66
- var AZTEC_OF = Object.entries(FORMATS);
67
- var formatOf = (format) => format === "link" ? aztec.AztecTextFormat.FORMAT_LINK : FORMATS[format]();
68
- var AztecEditor = class extends View {
69
- createNativeView() {
70
- return new aztec.AztecText(this._context);
71
- }
72
- };
73
- registerElement("xplatrichtext", AztecEditor);
74
- var supported = true;
75
- var activeFormats = (editor) => {
76
- const styles = editor.getAppliedStyles(editor.getSelectionStart(), editor.getSelectionEnd());
77
- const count = styles?.size?.() ?? 0;
78
- const active = /* @__PURE__ */ new Set();
79
- for (let i = 0; i < count; i++) active.add(styles.get(i));
80
- const out = [];
81
- for (const [name, get] of AZTEC_OF) if (active.has(get())) out.push(name);
82
- if (active.has(aztec.AztecTextFormat.FORMAT_LINK)) out.push("link");
83
- return out;
84
- };
85
- var RichTextEditor = defineUniversalComponent("nativescript", function RichTextEditor(props) {
86
- const host = useRef(null, _h$0);
87
- const lastEmitted = useRef(void 0, _h$1);
88
- const applyingValue = useRef(false, _h$2);
89
- const keyListener = useRef(null, _h$3);
90
- const emitTimer = useRef(null, _h$4);
91
- useImperativeHandle(props.ref, () => handle(editor()), [], _h$5);
92
- const bindHost = useCallback((v) => {
93
- host.current = v;
94
- }, [], _h$6);
95
- const editor = () => host.current?.android;
96
- const setEditable = (next) => {
97
- const e = editor();
98
- if (!e) return;
99
- if (keyListener.current == null) keyListener.current = e.getKeyListener();
100
- if (next) {
101
- e.setKeyListener(keyListener.current);
102
- e.setFocusable(true);
103
- e.setCursorVisible(true);
104
- } else {
105
- e.setKeyListener(null);
106
- e.setFocusable(false);
107
- e.setCursorVisible(false);
108
- }
109
- };
110
- const showKeyboard = (e) => {
111
- try {
112
- e.getContext().getSystemService(globalThis.android.content.Context.INPUT_METHOD_SERVICE)?.showSoftInput(e, androidView.inputmethod.InputMethodManager.SHOW_IMPLICIT);
113
- } catch {}
114
- };
115
- const emitChange = () => {
116
- if (!editor() || applyingValue.current) return;
117
- clearTimeout(emitTimer.current);
118
- emitTimer.current = setTimeout(() => {
119
- const current = editor();
120
- if (!current || applyingValue.current) return;
121
- const html = current.toPlainHtml(false);
122
- lastEmitted.current = html;
123
- props.onChange?.(html);
124
- }, 150);
125
- };
126
- const handle = (e) => ({
127
- getHTML: () => e.toPlainHtml(false),
128
- setHTML: (html) => {
129
- applyingValue.current = true;
130
- try {
131
- e.fromHtml(html, true);
132
- lastEmitted.current = html;
133
- } finally {
134
- applyingValue.current = false;
135
- }
136
- },
137
- apply: (format) => {
138
- if (format === "link") throw new RangeError("Use linkTo(url, anchor) to apply a link");
139
- if (!FORMATS[format]) throw new RangeError(`[richtext] Unsupported format: ${format}`);
140
- e.toggleFormatting(formatOf(format));
141
- emitChange();
142
- },
143
- linkTo: (url, anchor) => {
144
- e.link(url, anchor ?? url, false);
145
- emitChange();
146
- },
147
- removeLink: () => {
148
- e.removeLink();
149
- emitChange();
150
- },
151
- isActive: (format) => {
152
- const applied = e.getAppliedStyles(e.getSelectionStart(), e.getSelectionEnd());
153
- const count = applied?.size?.() ?? 0;
154
- const target = formatOf(format);
155
- for (let i = 0; i < count; i++) if (applied.get(i) === target) return true;
156
- return false;
157
- },
158
- undo: () => e.undo(),
159
- redo: () => e.redo(),
160
- split: () => {
161
- const selStart = e.getSelectionStart();
162
- const selEnd = e.getSelectionEnd();
163
- if (selStart < 0 || selEnd < 0) return false;
164
- const start = Math.min(selStart, selEnd);
165
- const end = Math.max(selStart, selEnd);
166
- const text = e.getText();
167
- if (end > String(text).length) return false;
168
- if (end > start) text.delete(start, end);
169
- text.insert(start, "\n");
170
- e.setSelection(start + 1);
171
- emitChange();
172
- return true;
173
- },
174
- join: () => {
175
- const start = e.getSelectionStart();
176
- if (start <= 0 || e.getSelectionEnd() !== start) return false;
177
- const text = e.getText();
178
- const s = String(text);
179
- if (start > s.length || s.charAt(start - 1) !== "\n") return false;
180
- text.delete(start - 1, start);
181
- e.setSelection(start - 1);
182
- emitChange();
183
- return true;
184
- },
185
- indent: () => {
186
- if (!e.isIndentAvailable()) return false;
187
- e.indent();
188
- emitChange();
189
- return true;
190
- },
191
- outdent: () => {
192
- if (!e.isOutdentAvailable()) return false;
193
- e.outdent();
194
- emitChange();
195
- return true;
196
- },
197
- canIndent: () => Boolean(e.isIndentAvailable()),
198
- canOutdent: () => Boolean(e.isOutdentAvailable()),
199
- focus: () => {
200
- e.requestFocus();
201
- showKeyboard(e);
202
- },
203
- blur: () => e.clearFocus(),
204
- isFocused: () => e.isFocused(),
205
- native: e
206
- });
207
- useEffect(() => {
208
- const e = editor();
209
- if (!e || !aztec) return;
210
- e.setOnSelectionChangedListener(new aztec.AztecText.OnSelectionChangedListener({ onSelectionChanged: (start, end) => props.onSelectionChange?.({
211
- start,
212
- end,
213
- active: activeFormats(e)
214
- }) }));
215
- const watcher = new androidText.TextWatcher({
216
- beforeTextChanged: () => {},
217
- onTextChanged: () => {},
218
- afterTextChanged: () => emitChange()
219
- });
220
- e.addTextChangedListener(watcher);
221
- e.setOnFocusChangeListener(new androidView.View.OnFocusChangeListener({ onFocusChange: (_v, focused) => focused ? props.onFocus?.() : props.onBlur?.() }));
222
- if (props.placeholder !== void 0) e.setHint(props.placeholder);
223
- setEditable(props.editable !== false);
224
- if (props.value !== void 0) {
225
- applyingValue.current = true;
226
- try {
227
- e.fromHtml(props.value, true);
228
- } finally {
229
- applyingValue.current = false;
230
- }
231
- }
232
- if (props.autofocus) {
233
- e.requestFocus();
234
- showKeyboard(e);
235
- }
236
- if (props.android) Object.assign(e, props.android);
237
- props.onReady?.();
238
- return () => {
239
- clearTimeout(emitTimer.current);
240
- e.removeTextChangedListener(watcher);
241
- e.setOnFocusChangeListener(null);
242
- };
243
- }, [], _h$7);
244
- useEffect(() => {
245
- const e = editor();
246
- if (!e || props.value === void 0) return;
247
- if (props.value === lastEmitted.current) return;
248
- applyingValue.current = true;
249
- try {
250
- e.fromHtml(props.value, true);
251
- } finally {
252
- applyingValue.current = false;
253
- }
254
- }, [props.value], _h$8);
255
- useEffect(() => {
256
- setEditable(props.editable !== false);
257
- }, [props.editable], _h$9);
258
- useEffect(() => {
259
- const e = editor();
260
- if (e && props.placeholder !== void 0) e.setHint(props.placeholder);
261
- }, [props.placeholder], _h$10);
262
- return universalValue(__octaneUniversalPlan0, [
263
- bindHost,
264
- props.id,
265
- props.className,
266
- props.style,
267
- props.row,
268
- props.col,
269
- props.rowSpan,
270
- props.colSpan,
271
- props.dock,
272
- props.left,
273
- props.top,
274
- props.flexGrow,
275
- props.flexShrink,
276
- props.alignSelf,
277
- props.order
278
- ]);
279
- }, { "module": "@nativescript-community/octane" });
280
- //#endregion
281
- export { RichTextEditor, supported };
@@ -1,199 +0,0 @@
1
- import { defineUniversalComponent, hookSlots, registerElement, universalPlan, universalValue, useCallback, useEffect, useImperativeHandle, useRef } from "@nativescript-community/octane";
2
- import { View } from "@nativescript/core";
3
- //#region src/RichTextEditor.ios.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 UNSUPPORTED = /* @__PURE__ */ new Set([
37
- "taskList",
38
- "alignLeft",
39
- "alignCenter",
40
- "alignRight"
41
- ]);
42
- var AztecEditor = class extends View {
43
- createNativeView() {
44
- return new XplatAztecEditorView();
45
- }
46
- };
47
- registerElement("xplatrichtext", AztecEditor);
48
- var supported = true;
49
- var RichTextEditor = defineUniversalComponent("nativescript", function RichTextEditor(props) {
50
- const host = useRef(null, _h$0);
51
- const lastEmitted = useRef(void 0, _h$1);
52
- const applyingValue = useRef(false, _h$2);
53
- const emitTimer = useRef(null, _h$3);
54
- useImperativeHandle(props.ref, () => handle(editor()), [], _h$4);
55
- const bindHost = useCallback((v) => {
56
- host.current = v;
57
- }, [], _h$5);
58
- const editor = () => host.current?.ios;
59
- const emitChange = () => {
60
- if (!editor() || applyingValue.current) return;
61
- clearTimeout(emitTimer.current);
62
- emitTimer.current = setTimeout(() => {
63
- const current = editor();
64
- if (!current || applyingValue.current) return;
65
- const html = current.getHTML();
66
- lastEmitted.current = html;
67
- props.onChange?.(html);
68
- }, 150);
69
- };
70
- const handle = (e) => ({
71
- getHTML: () => e.getHTML(),
72
- setHTML: (html) => {
73
- applyingValue.current = true;
74
- try {
75
- e.setHTML(html);
76
- lastEmitted.current = html;
77
- } finally {
78
- applyingValue.current = false;
79
- }
80
- },
81
- apply: (format) => {
82
- if (format === "link") throw new RangeError("Use linkTo(url, anchor) to apply a link");
83
- if (UNSUPPORTED.has(format) || !e.applyFormat(format)) throw new RangeError(`[richtext] apply('${format}') is not supported by the iOS Aztec surface`);
84
- emitChange();
85
- },
86
- linkTo: (url, anchor) => {
87
- e.linkTo(url, anchor ?? null);
88
- emitChange();
89
- },
90
- removeLink: () => {
91
- e.removeLink();
92
- emitChange();
93
- },
94
- isActive: (format) => !UNSUPPORTED.has(format) && Boolean(e.isFormatActive(format)),
95
- undo: () => e.undo(),
96
- redo: () => e.redo(),
97
- split: () => {
98
- const ok = Boolean(e.split());
99
- if (ok) emitChange();
100
- return ok;
101
- },
102
- join: () => {
103
- const ok = Boolean(e.join());
104
- if (ok) emitChange();
105
- return ok;
106
- },
107
- indent: () => {
108
- const ok = Boolean(e.indent());
109
- if (ok) emitChange();
110
- return ok;
111
- },
112
- outdent: () => {
113
- const ok = Boolean(e.outdent());
114
- if (ok) emitChange();
115
- return ok;
116
- },
117
- canIndent: () => Boolean(e.canIndent()),
118
- canOutdent: () => Boolean(e.canOutdent()),
119
- focus: () => e.focus(),
120
- blur: () => e.blur(),
121
- isFocused: () => e.focused(),
122
- native: e
123
- });
124
- useEffect(() => {
125
- const e = editor();
126
- if (!e) return;
127
- e.onTextChange = () => emitChange();
128
- e.onSelectionChange = (start, end, active) => {
129
- const formats = [];
130
- const count = active?.count ?? 0;
131
- for (let i = 0; i < count; i++) formats.push(active.objectAtIndex(i));
132
- props.onSelectionChange?.({
133
- start,
134
- end,
135
- active: formats
136
- });
137
- };
138
- e.onFocus = () => props.onFocus?.();
139
- e.onBlur = () => props.onBlur?.();
140
- if (props.placeholder !== void 0) e.placeholder = props.placeholder;
141
- e.editable = props.editable !== false;
142
- if (props.value !== void 0) {
143
- applyingValue.current = true;
144
- try {
145
- e.setHTML(props.value);
146
- } finally {
147
- applyingValue.current = false;
148
- }
149
- }
150
- if (props.autofocus) e.focus();
151
- if (props.ios) Object.assign(e, props.ios);
152
- props.onReady?.();
153
- return () => {
154
- clearTimeout(emitTimer.current);
155
- e.onTextChange = null;
156
- e.onSelectionChange = null;
157
- e.onFocus = null;
158
- e.onBlur = null;
159
- };
160
- }, [], _h$6);
161
- useEffect(() => {
162
- const e = editor();
163
- if (!e || props.value === void 0) return;
164
- if (props.value === lastEmitted.current) return;
165
- applyingValue.current = true;
166
- try {
167
- e.setHTML(props.value);
168
- } finally {
169
- applyingValue.current = false;
170
- }
171
- }, [props.value], _h$7);
172
- useEffect(() => {
173
- const e = editor();
174
- if (e) e.editable = props.editable !== false;
175
- }, [props.editable], _h$8);
176
- useEffect(() => {
177
- const e = editor();
178
- if (e && props.placeholder !== void 0) e.placeholder = props.placeholder;
179
- }, [props.placeholder], _h$9);
180
- return universalValue(__octaneUniversalPlan0, [
181
- bindHost,
182
- props.id,
183
- props.className,
184
- props.style,
185
- props.row,
186
- props.col,
187
- props.rowSpan,
188
- props.colSpan,
189
- props.dock,
190
- props.left,
191
- props.top,
192
- props.flexGrow,
193
- props.flexShrink,
194
- props.alignSelf,
195
- props.order
196
- ]);
197
- }, { "module": "@nativescript-community/octane" });
198
- //#endregion
199
- export { RichTextEditor, supported };
@@ -1,22 +0,0 @@
1
- import { defineUniversalComponent, universalPlan, universalValue } from "@nativescript-community/octane";
2
- //#region src/RichTextEditor.windows.tsrx
3
- var __octaneUniversalPlan0 = universalPlan("nativescript", {
4
- "kind": "host",
5
- "type": "label",
6
- "props": { "text": "Rich text editing is not supported on Windows 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 };
@@ -1,2 +0,0 @@
1
- import { RichTextEditor, supported } from "./RichTextEditor";
2
- export { RichTextEditor, supported };
@@ -1,8 +0,0 @@
1
- {
2
- "version": 1,
3
- "buildId": "67a88bd2-9978-455b-bc4e-8cb8a8cbd5dc",
4
- "mode": "production",
5
- "capabilities": {
6
- "independentHydration": false
7
- }
8
- }
package/dist/web/index.js DELETED
@@ -1,9 +0,0 @@
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 };
@@ -1,8 +0,0 @@
1
- {
2
- "version": 1,
3
- "buildId": "54086534-84b0-4656-b815-0a070fe92a4c",
4
- "mode": "production",
5
- "capabilities": {
6
- "independentHydration": false
7
- }
8
- }