@canvist/react-native 0.0.0 → 0.2.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/CanvistReactNative.podspec +31 -0
- package/android/build.gradle +50 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/kotlin/io/github/ifiokjr/canvist/reactnative/CanvistEditorEvent.kt +32 -0
- package/android/src/main/kotlin/io/github/ifiokjr/canvist/reactnative/CanvistEditorViewManager.kt +145 -0
- package/android/src/main/kotlin/io/github/ifiokjr/canvist/reactnative/CanvistEditorsModule.kt +81 -0
- package/android/src/main/kotlin/io/github/ifiokjr/canvist/reactnative/CanvistReactNativePackage.kt +33 -0
- package/dist/CanvistEditorViewNativeComponent.d.ts +52 -0
- package/dist/NativeCanvistEditors.d.ts +31 -0
- package/dist/bridge.d.ts +18 -0
- package/dist/editor.d.ts +91 -0
- package/dist/mod.d.ts +27 -0
- package/ios/CanvistEditorComponentView.h +10 -0
- package/ios/CanvistEditorComponentView.mm +132 -0
- package/ios/CanvistEditorsModule.h +10 -0
- package/ios/CanvistEditorsModule.mm +101 -0
- package/ios/CanvistSwift.h +3 -0
- package/package.json +94 -1
- package/readme.md +105 -0
- package/src/CanvistEditorViewNativeComponent.ts +63 -0
- package/src/NativeCanvistEditors.ts +33 -0
- package/src/bridge.ts +64 -0
- package/src/editor.tsx +268 -0
- package/src/mod.ts +61 -0
- package/CHANGELOG.md +0 -3
- package/LICENSE +0 -24
- package/README.md +0 -3
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "CanvistReactNative"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = { type: package["license"] }
|
|
11
|
+
s.author = { "Ifiok Jr." => "support@canvist.dev" }
|
|
12
|
+
s.source = { git: "https://github.com/canvist/canvist.git", tag: "v#{s.version}" }
|
|
13
|
+
|
|
14
|
+
s.platforms = { ios: "17.0" }
|
|
15
|
+
s.source_files = "ios/**/*.{h,mm}"
|
|
16
|
+
s.private_header_files = "ios/**/*.h"
|
|
17
|
+
|
|
18
|
+
# The native editor and its hosting (`CanvistHostView`, `CanvistHostCalls`):
|
|
19
|
+
# `platforms/apple/Canvist.podspec` in the canvist repository, which the
|
|
20
|
+
# app's Podfile points at.
|
|
21
|
+
s.dependency "Canvist", s.version.to_s
|
|
22
|
+
# Set before `install_modules_dependencies`, which adds its own settings to
|
|
23
|
+
# these: no Intel simulator slice, like `Canvist`, and `Canvist-Swift.h`,
|
|
24
|
+
# which a static Swift pod does not put on its dependents' search paths.
|
|
25
|
+
s.pod_target_xcconfig = {
|
|
26
|
+
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" => "x86_64",
|
|
27
|
+
"HEADER_SEARCH_PATHS" => '"$(PODS_CONFIGURATION_BUILD_DIR)/Canvist/Swift Compatibility Header"',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
install_modules_dependencies(s)
|
|
31
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// The React Native glue for canvist's Android editor (`io.github.ifiokjr:canvist-android`).
|
|
2
|
+
import groovy.json.JsonSlurper
|
|
3
|
+
|
|
4
|
+
buildscript {
|
|
5
|
+
ext.getExtOrDefault = { name, fallback ->
|
|
6
|
+
rootProject.ext.has(name) ? rootProject.ext.get(name) : fallback
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
repositories {
|
|
10
|
+
google()
|
|
11
|
+
mavenCentral()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
dependencies {
|
|
15
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
16
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion', '2.2.0')}"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
apply plugin: "com.android.library"
|
|
21
|
+
if (project.extensions.findByName("kotlin") == null) {
|
|
22
|
+
apply plugin: "kotlin-android"
|
|
23
|
+
}
|
|
24
|
+
apply plugin: "com.facebook.react"
|
|
25
|
+
|
|
26
|
+
// The editor is released in step with this package, so they share a version.
|
|
27
|
+
def canvistVersion = new JsonSlurper().parse(file("../package.json")).version
|
|
28
|
+
|
|
29
|
+
android {
|
|
30
|
+
namespace "io.github.ifiokjr.canvist.reactnative"
|
|
31
|
+
compileSdkVersion getExtOrDefault("compileSdkVersion", 36)
|
|
32
|
+
|
|
33
|
+
defaultConfig {
|
|
34
|
+
minSdkVersion getExtOrDefault("minSdkVersion", 24)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
compileOptions {
|
|
38
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
39
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
sourceSets {
|
|
43
|
+
main.java.srcDirs += "src/main/kotlin"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
dependencies {
|
|
48
|
+
implementation "com.facebook.react:react-android"
|
|
49
|
+
implementation "io.github.ifiokjr:canvist-android:${canvistVersion}"
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
package io.github.ifiokjr.canvist.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.WritableMap
|
|
4
|
+
import com.facebook.react.uimanager.events.Event
|
|
5
|
+
|
|
6
|
+
/** One of the view's events, with its payload. */
|
|
7
|
+
internal class CanvistEditorEvent(
|
|
8
|
+
surfaceId: Int,
|
|
9
|
+
viewId: Int,
|
|
10
|
+
private val name: String,
|
|
11
|
+
private val payload: WritableMap,
|
|
12
|
+
) : Event<CanvistEditorEvent>(surfaceId, viewId) {
|
|
13
|
+
override fun getEventName() = name
|
|
14
|
+
|
|
15
|
+
override fun getEventData() = payload
|
|
16
|
+
|
|
17
|
+
companion object {
|
|
18
|
+
const val READY = "topEditorReady"
|
|
19
|
+
const val CHANGE = "topEditorChange"
|
|
20
|
+
const val FOCUS = "topEditorFocus"
|
|
21
|
+
const val ERROR = "topEditorError"
|
|
22
|
+
|
|
23
|
+
/** The JavaScript prop each event calls. */
|
|
24
|
+
val registrations =
|
|
25
|
+
mapOf(
|
|
26
|
+
READY to "onEditorReady",
|
|
27
|
+
CHANGE to "onEditorChange",
|
|
28
|
+
FOCUS to "onEditorFocus",
|
|
29
|
+
ERROR to "onEditorError",
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
}
|
package/android/src/main/kotlin/io/github/ifiokjr/canvist/reactnative/CanvistEditorViewManager.kt
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
package io.github.ifiokjr.canvist.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.ReactContext
|
|
5
|
+
import com.facebook.react.bridge.ReadableArray
|
|
6
|
+
import com.facebook.react.bridge.WritableMap
|
|
7
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
8
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
9
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
10
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
11
|
+
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
12
|
+
import com.facebook.react.viewmanagers.CanvistEditorViewManagerDelegate
|
|
13
|
+
import com.facebook.react.viewmanagers.CanvistEditorViewManagerInterface
|
|
14
|
+
import io.github.ifiokjr.canvist.CanvistHostView
|
|
15
|
+
import io.github.ifiokjr.canvist.EditorChange
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The Fabric view manager for `CanvistEditorView`. The editor starts once a
|
|
19
|
+
* view's first props have all arrived; later changes to `extensions` and
|
|
20
|
+
* `content` have no effect, as the JavaScript API documents.
|
|
21
|
+
*/
|
|
22
|
+
@ReactModule(name = CanvistEditorViewManager.NAME)
|
|
23
|
+
class CanvistEditorViewManager :
|
|
24
|
+
SimpleViewManager<CanvistHostView>(),
|
|
25
|
+
CanvistEditorViewManagerInterface<CanvistHostView> {
|
|
26
|
+
private val delegate = CanvistEditorViewManagerDelegate(this)
|
|
27
|
+
|
|
28
|
+
/** Props of views whose editors have not started yet. */
|
|
29
|
+
private val pending = HashMap<CanvistHostView, StartProps>()
|
|
30
|
+
|
|
31
|
+
override fun getDelegate(): ViewManagerDelegate<CanvistHostView> = delegate
|
|
32
|
+
|
|
33
|
+
override fun getName() = NAME
|
|
34
|
+
|
|
35
|
+
override fun createViewInstance(context: ThemedReactContext): CanvistHostView = CanvistHostView(context).apply { listener = Events }
|
|
36
|
+
|
|
37
|
+
override fun setEditorId(
|
|
38
|
+
view: CanvistHostView,
|
|
39
|
+
value: String?,
|
|
40
|
+
) {
|
|
41
|
+
pending.getOrPut(view, ::StartProps).editorId = value
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
override fun setExtensions(
|
|
45
|
+
view: CanvistHostView,
|
|
46
|
+
value: ReadableArray?,
|
|
47
|
+
) {
|
|
48
|
+
val names = value?.toArrayList()?.filterIsInstance<String>() ?: emptyList()
|
|
49
|
+
pending.getOrPut(view, ::StartProps).extensions = names
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override fun setContentFormat(
|
|
53
|
+
view: CanvistHostView,
|
|
54
|
+
value: String?,
|
|
55
|
+
) {
|
|
56
|
+
pending.getOrPut(view, ::StartProps).format = value
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
override fun setContent(
|
|
60
|
+
view: CanvistHostView,
|
|
61
|
+
value: String?,
|
|
62
|
+
) {
|
|
63
|
+
pending.getOrPut(view, ::StartProps).content = value
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override fun setFontSize(
|
|
67
|
+
view: CanvistHostView,
|
|
68
|
+
value: Float,
|
|
69
|
+
) {
|
|
70
|
+
view.fontSize = value
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
override fun onAfterUpdateTransaction(view: CanvistHostView) {
|
|
74
|
+
super.onAfterUpdateTransaction(view)
|
|
75
|
+
val props = pending.remove(view) ?: return
|
|
76
|
+
val editorId = props.editorId?.takeIf { it.isNotEmpty() } ?: return
|
|
77
|
+
view.start(editorId, props.extensions, props.format?.ifEmpty { null }, props.content?.ifEmpty { null })
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
override fun onDropViewInstance(view: CanvistHostView) {
|
|
81
|
+
pending.remove(view)
|
|
82
|
+
view.stop()
|
|
83
|
+
super.onDropViewInstance(view)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
override fun focus(view: CanvistHostView) = view.focusEditor()
|
|
87
|
+
|
|
88
|
+
override fun blur(view: CanvistHostView) = view.blurEditor()
|
|
89
|
+
|
|
90
|
+
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> =
|
|
91
|
+
CanvistEditorEvent.registrations.mapValues { (_, prop) -> mapOf("registrationName" to prop) }
|
|
92
|
+
|
|
93
|
+
private class StartProps {
|
|
94
|
+
var editorId: String? = null
|
|
95
|
+
var extensions: List<String> = emptyList()
|
|
96
|
+
var format: String? = null
|
|
97
|
+
var content: String? = null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Sends each host's reports to JavaScript as the view's events. */
|
|
101
|
+
private object Events : CanvistHostView.Listener {
|
|
102
|
+
override fun onReady(
|
|
103
|
+
view: CanvistHostView,
|
|
104
|
+
editorId: String,
|
|
105
|
+
) = dispatch(view, CanvistEditorEvent.READY) { putString("editorId", editorId) }
|
|
106
|
+
|
|
107
|
+
override fun onChange(
|
|
108
|
+
view: CanvistHostView,
|
|
109
|
+
change: EditorChange,
|
|
110
|
+
) = dispatch(view, CanvistEditorEvent.CHANGE) {
|
|
111
|
+
putBoolean("document", change.document)
|
|
112
|
+
putBoolean("selection", change.selection)
|
|
113
|
+
putBoolean("composition", change.composition)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
override fun onFocusChange(
|
|
117
|
+
view: CanvistHostView,
|
|
118
|
+
focused: Boolean,
|
|
119
|
+
) = dispatch(view, CanvistEditorEvent.FOCUS) { putBoolean("focused", focused) }
|
|
120
|
+
|
|
121
|
+
override fun onError(
|
|
122
|
+
view: CanvistHostView,
|
|
123
|
+
code: String,
|
|
124
|
+
message: String,
|
|
125
|
+
) = dispatch(view, CanvistEditorEvent.ERROR) {
|
|
126
|
+
putString("code", code)
|
|
127
|
+
putString("message", message)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private fun dispatch(
|
|
131
|
+
view: CanvistHostView,
|
|
132
|
+
name: String,
|
|
133
|
+
payload: WritableMap.() -> Unit,
|
|
134
|
+
) {
|
|
135
|
+
val context = view.context as? ReactContext ?: return
|
|
136
|
+
val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id) ?: return
|
|
137
|
+
val data = Arguments.createMap().apply(payload)
|
|
138
|
+
dispatcher.dispatchEvent(CanvistEditorEvent(UIManagerHelper.getSurfaceId(view), view.id, name, data))
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
companion object {
|
|
143
|
+
const val NAME = "CanvistEditorView"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
package io.github.ifiokjr.canvist.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
4
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
5
|
+
import io.github.ifiokjr.canvist.Command
|
|
6
|
+
import io.github.ifiokjr.canvist.HostedEditors
|
|
7
|
+
import io.github.ifiokjr.canvist.docFormat
|
|
8
|
+
import io.github.ifiokjr.canvist.selectRangeJson
|
|
9
|
+
import io.github.ifiokjr.canvist.selectionJson
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The `CanvistEditors` TurboModule: synchronous calls, on the JavaScript
|
|
13
|
+
* thread, into the editors [HostedEditors] finds by `editorId`. A failure
|
|
14
|
+
* throws with `[canvist:<code>] <reason>` as its message, which React Native
|
|
15
|
+
* rethrows in JavaScript.
|
|
16
|
+
*/
|
|
17
|
+
@ReactModule(name = CanvistEditorsModule.NAME)
|
|
18
|
+
class CanvistEditorsModule(
|
|
19
|
+
context: ReactApplicationContext,
|
|
20
|
+
) : NativeCanvistEditorsSpec(context) {
|
|
21
|
+
override fun getName() = NAME
|
|
22
|
+
|
|
23
|
+
override fun run(
|
|
24
|
+
editorId: String,
|
|
25
|
+
name: String,
|
|
26
|
+
args: String,
|
|
27
|
+
): Boolean = HostedEditors.call(editorId) { it.run(Command(name, args)) }
|
|
28
|
+
|
|
29
|
+
override fun can(
|
|
30
|
+
editorId: String,
|
|
31
|
+
name: String,
|
|
32
|
+
args: String,
|
|
33
|
+
): Boolean = HostedEditors.call(editorId) { it.can(Command(name, args)) }
|
|
34
|
+
|
|
35
|
+
override fun query(
|
|
36
|
+
editorId: String,
|
|
37
|
+
name: String,
|
|
38
|
+
args: String,
|
|
39
|
+
): String = HostedEditors.call(editorId) { it.query(name, args) }
|
|
40
|
+
|
|
41
|
+
override fun load(
|
|
42
|
+
editorId: String,
|
|
43
|
+
format: String,
|
|
44
|
+
content: String,
|
|
45
|
+
): Boolean =
|
|
46
|
+
HostedEditors.call(editorId) {
|
|
47
|
+
it.load(docFormat(format), content)
|
|
48
|
+
true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override fun exportDocument(
|
|
52
|
+
editorId: String,
|
|
53
|
+
format: String,
|
|
54
|
+
): String = HostedEditors.call(editorId) { it.export(docFormat(format)) }
|
|
55
|
+
|
|
56
|
+
override fun selection(editorId: String): String = HostedEditors.call(editorId) { it.selectionJson() }
|
|
57
|
+
|
|
58
|
+
override fun select(
|
|
59
|
+
editorId: String,
|
|
60
|
+
range: String,
|
|
61
|
+
): Boolean = HostedEditors.call(editorId) { it.selectRangeJson(range) }
|
|
62
|
+
|
|
63
|
+
override fun selectAll(editorId: String): Boolean = HostedEditors.call(editorId) { it.selectAll() }
|
|
64
|
+
|
|
65
|
+
override fun insertText(
|
|
66
|
+
editorId: String,
|
|
67
|
+
text: String,
|
|
68
|
+
): Boolean = HostedEditors.call(editorId) { it.insertText(text) }
|
|
69
|
+
|
|
70
|
+
override fun undo(editorId: String): Boolean = HostedEditors.call(editorId) { it.undo() }
|
|
71
|
+
|
|
72
|
+
override fun redo(editorId: String): Boolean = HostedEditors.call(editorId) { it.redo() }
|
|
73
|
+
|
|
74
|
+
override fun canUndo(editorId: String): Boolean = HostedEditors.call(editorId) { it.canUndo() }
|
|
75
|
+
|
|
76
|
+
override fun canRedo(editorId: String): Boolean = HostedEditors.call(editorId) { it.canRedo() }
|
|
77
|
+
|
|
78
|
+
companion object {
|
|
79
|
+
const val NAME = "CanvistEditors"
|
|
80
|
+
}
|
|
81
|
+
}
|
package/android/src/main/kotlin/io/github/ifiokjr/canvist/reactnative/CanvistReactNativePackage.kt
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package io.github.ifiokjr.canvist.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
import com.facebook.react.uimanager.ViewManager
|
|
9
|
+
|
|
10
|
+
/** canvist's editor view and the module that calls into its editors. */
|
|
11
|
+
class CanvistReactNativePackage : BaseReactPackage() {
|
|
12
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> = listOf(CanvistEditorViewManager())
|
|
13
|
+
|
|
14
|
+
override fun getModule(
|
|
15
|
+
name: String,
|
|
16
|
+
reactContext: ReactApplicationContext,
|
|
17
|
+
): NativeModule? = if (name == CanvistEditorsModule.NAME) CanvistEditorsModule(reactContext) else null
|
|
18
|
+
|
|
19
|
+
override fun getReactModuleInfoProvider() =
|
|
20
|
+
ReactModuleInfoProvider {
|
|
21
|
+
mapOf(
|
|
22
|
+
CanvistEditorsModule.NAME to
|
|
23
|
+
ReactModuleInfo(
|
|
24
|
+
name = CanvistEditorsModule.NAME,
|
|
25
|
+
className = CanvistEditorsModule.NAME,
|
|
26
|
+
canOverrideExistingModule = false,
|
|
27
|
+
needsEagerInit = false,
|
|
28
|
+
isCxxModule = false,
|
|
29
|
+
isTurboModule = true,
|
|
30
|
+
),
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Fabric component spec for the native editor view: React Native's
|
|
3
|
+
* codegen reads this file to generate the iOS and Android glue, so it keeps
|
|
4
|
+
* to the shapes codegen understands.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import type * as React from "react";
|
|
9
|
+
import type { CodegenTypes, HostComponent, ViewProps } from "react-native";
|
|
10
|
+
/** What changed, after every change to the document or selection. */
|
|
11
|
+
export type EditorChangeEvent = Readonly<{
|
|
12
|
+
document: boolean;
|
|
13
|
+
selection: boolean;
|
|
14
|
+
composition: boolean;
|
|
15
|
+
}>;
|
|
16
|
+
/** The view gained or lost focus. */
|
|
17
|
+
export type EditorFocusEvent = Readonly<{
|
|
18
|
+
focused: boolean;
|
|
19
|
+
}>;
|
|
20
|
+
/** The view could not create or load its editor. */
|
|
21
|
+
export type EditorErrorEvent = Readonly<{
|
|
22
|
+
code: string;
|
|
23
|
+
message: string;
|
|
24
|
+
}>;
|
|
25
|
+
/** The view is ready for calls through the `CanvistEditors` module. */
|
|
26
|
+
export type EditorReadyEvent = Readonly<{
|
|
27
|
+
editorId: string;
|
|
28
|
+
}>;
|
|
29
|
+
export interface NativeProps extends ViewProps {
|
|
30
|
+
/** The key the `CanvistEditors` module finds this view's editor by. */
|
|
31
|
+
editorId: string;
|
|
32
|
+
/** The extensions to register, by name. Read when the view is created. */
|
|
33
|
+
extensions: ReadonlyArray<string>;
|
|
34
|
+
/** The format of `content`. Read when the view is created. */
|
|
35
|
+
contentFormat?: string;
|
|
36
|
+
/** The document to start with. Read when the view is created. */
|
|
37
|
+
content?: string;
|
|
38
|
+
/** The body text size before the user's text size setting; 0 is the platform's default. */
|
|
39
|
+
fontSize?: CodegenTypes.WithDefault<CodegenTypes.Float, 0>;
|
|
40
|
+
onEditorReady?: CodegenTypes.DirectEventHandler<EditorReadyEvent>;
|
|
41
|
+
onEditorChange?: CodegenTypes.DirectEventHandler<EditorChangeEvent>;
|
|
42
|
+
onEditorFocus?: CodegenTypes.DirectEventHandler<EditorFocusEvent>;
|
|
43
|
+
onEditorError?: CodegenTypes.DirectEventHandler<EditorErrorEvent>;
|
|
44
|
+
}
|
|
45
|
+
export type CanvistEditorViewType = HostComponent<NativeProps>;
|
|
46
|
+
interface NativeCommands {
|
|
47
|
+
focus: (viewRef: React.ElementRef<CanvistEditorViewType>) => void;
|
|
48
|
+
blur: (viewRef: React.ElementRef<CanvistEditorViewType>) => void;
|
|
49
|
+
}
|
|
50
|
+
export declare const Commands: NativeCommands;
|
|
51
|
+
declare const _default: HostComponent<NativeProps>;
|
|
52
|
+
export default _default;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TurboModule spec for calls into a view's editor. Every method is
|
|
3
|
+
* synchronous on the JavaScript thread, so commands and queries answer at
|
|
4
|
+
* once, as they do on the web. Methods return a value even when there is
|
|
5
|
+
* nothing to say, because React Native runs `void` methods asynchronously.
|
|
6
|
+
*
|
|
7
|
+
* Failures throw with `[canvist:<code>] <reason>` in the message.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import type { TurboModule } from "react-native";
|
|
12
|
+
export interface Spec extends TurboModule {
|
|
13
|
+
run(editorId: string, name: string, args: string): boolean;
|
|
14
|
+
can(editorId: string, name: string, args: string): boolean;
|
|
15
|
+
query(editorId: string, name: string, args: string): string;
|
|
16
|
+
/** Returns `true`. */
|
|
17
|
+
load(editorId: string, format: string, content: string): boolean;
|
|
18
|
+
exportDocument(editorId: string, format: string): string;
|
|
19
|
+
selection(editorId: string): string;
|
|
20
|
+
/** Returns whether the selection moved. */
|
|
21
|
+
select(editorId: string, range: string): boolean;
|
|
22
|
+
/** Returns whether the selection moved. */
|
|
23
|
+
selectAll(editorId: string): boolean;
|
|
24
|
+
insertText(editorId: string, text: string): boolean;
|
|
25
|
+
undo(editorId: string): boolean;
|
|
26
|
+
redo(editorId: string): boolean;
|
|
27
|
+
canUndo(editorId: string): boolean;
|
|
28
|
+
canRedo(editorId: string): boolean;
|
|
29
|
+
}
|
|
30
|
+
declare const _default: Spec;
|
|
31
|
+
export default _default;
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bridge from the typed editor to one native view: synchronous calls
|
|
3
|
+
* through the `CanvistEditors` TurboModule, and focus through the view's
|
|
4
|
+
* commands.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import type { NativeBridge } from "@canvist/headless/native";
|
|
9
|
+
import type * as React from "react";
|
|
10
|
+
import { type CanvistEditorViewType } from "./CanvistEditorViewNativeComponent.ts";
|
|
11
|
+
import type { Spec } from "./NativeCanvistEditors.ts";
|
|
12
|
+
/** The mounted native view, as React refers to it. */
|
|
13
|
+
export type EditorViewInstance = React.ElementRef<CanvistEditorViewType>;
|
|
14
|
+
/**
|
|
15
|
+
* A bridge to the editor `editorId` names through `module`, focusing the
|
|
16
|
+
* view `view` returns. Focus calls do nothing once the view is gone.
|
|
17
|
+
*/
|
|
18
|
+
export declare function nativeBridge(module: Spec, editorId: string, view: () => EditorViewInstance | null): NativeBridge;
|
package/dist/editor.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The editor component and hooks.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import type { CanvistError, Content, StarterKit } from "@canvist/headless";
|
|
7
|
+
import { NativeEditor } from "@canvist/headless/native";
|
|
8
|
+
import { type ReactElement, type Ref } from "react";
|
|
9
|
+
import type { ViewProps } from "react-native";
|
|
10
|
+
import { type EditorViewInstance } from "./bridge.ts";
|
|
11
|
+
import { type NativeProps } from "./CanvistEditorViewNativeComponent.ts";
|
|
12
|
+
/** The extensions the native editors compile in: TipTap's StarterKit, or some of it. */
|
|
13
|
+
export type NativeExtensions = readonly StarterKit[number][];
|
|
14
|
+
/**
|
|
15
|
+
* A native editor whose commands, queries, and documents are typed from
|
|
16
|
+
* the extensions `E`, with the same API as `@canvist/web`'s editor.
|
|
17
|
+
*/
|
|
18
|
+
export type Editor<E extends NativeExtensions> = NativeEditor<E>;
|
|
19
|
+
/** Options for {@link useCanvistEditor} and {@link CanvistEditor}. */
|
|
20
|
+
export interface EditorOptions<E extends NativeExtensions> {
|
|
21
|
+
/** The extensions to register: every StarterKit extension by default. Read once. */
|
|
22
|
+
readonly extensions?: E;
|
|
23
|
+
/** The document to start with: one empty paragraph by default. Read once. */
|
|
24
|
+
readonly content?: Content<E>;
|
|
25
|
+
/** The body text size before the user's text size setting; the platform's body size by default. */
|
|
26
|
+
readonly fontSize?: number;
|
|
27
|
+
/** Called when the view could not create its editor or load `content`. */
|
|
28
|
+
readonly onError?: (error: CanvistError) => void;
|
|
29
|
+
}
|
|
30
|
+
/** Props for {@link CanvistView}: what {@link useCanvistEditor} returns, plus the view's own. */
|
|
31
|
+
export interface CanvistViewProps extends NativeProps {
|
|
32
|
+
readonly ref?: Ref<EditorViewInstance>;
|
|
33
|
+
}
|
|
34
|
+
/** What {@link useCanvistEditor} returns. */
|
|
35
|
+
export interface CanvistEditorHandle<E extends NativeExtensions> {
|
|
36
|
+
/** The editor, once the view has created it; `null` before that. */
|
|
37
|
+
readonly editor: Editor<E> | null;
|
|
38
|
+
/** Spread onto a {@link CanvistView}, alongside its style and accessibility props. */
|
|
39
|
+
readonly props: CanvistViewProps;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The native editor view. Render it with the props {@link useCanvistEditor}
|
|
43
|
+
* returns; give it a size, as it scrolls its own document.
|
|
44
|
+
*/
|
|
45
|
+
export declare const CanvistView: import("react-native").HostComponent<NativeProps>;
|
|
46
|
+
/**
|
|
47
|
+
* Create an editor in the {@link CanvistView} you spread `props` onto.
|
|
48
|
+
* `extensions` and `content` are read when the view is created; later
|
|
49
|
+
* changes to them have no effect.
|
|
50
|
+
*
|
|
51
|
+
* ```tsx
|
|
52
|
+
* function Notes() {
|
|
53
|
+
* const { editor, props } = useCanvistEditor({ extensions: [bold, heading] });
|
|
54
|
+
*
|
|
55
|
+
* return (
|
|
56
|
+
* <>
|
|
57
|
+
* <Button title="Bold" onPress={() => editor?.commands.toggleBold()} />
|
|
58
|
+
* <CanvistView {...props} style={{ flex: 1 }} />
|
|
59
|
+
* </>
|
|
60
|
+
* );
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare function useCanvistEditor<const E extends NativeExtensions = StarterKit>(options?: EditorOptions<E>): CanvistEditorHandle<E>;
|
|
65
|
+
/**
|
|
66
|
+
* Read `select(editor)` and re-render whenever it changes: after any edit,
|
|
67
|
+
* selection change, or change of focus. `select` must return a primitive
|
|
68
|
+
* or a value that stays the same object while nothing changed, as with
|
|
69
|
+
* `useSyncExternalStore`. Until the editor exists, the value is `fallback`.
|
|
70
|
+
*/
|
|
71
|
+
export declare function useEditorState<E extends NativeExtensions, T>(editor: Editor<E> | null, select: (editor: Editor<E>) => T, fallback: T): T;
|
|
72
|
+
/** Props for {@link CanvistEditor}: the editor's options and the view's. */
|
|
73
|
+
export interface CanvistEditorProps<E extends NativeExtensions> extends EditorOptions<E>, ViewProps {
|
|
74
|
+
/** Called once the editor is ready. */
|
|
75
|
+
readonly onReady?: (editor: Editor<E>) => void;
|
|
76
|
+
/** Called after every change to the document, the selection, or focus. */
|
|
77
|
+
readonly onUpdate?: (editor: Editor<E>) => void;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A native canvist editor: the iOS or Android `CanvistView`. Give it a
|
|
81
|
+
* size, as it scrolls its own document.
|
|
82
|
+
*
|
|
83
|
+
* ```tsx
|
|
84
|
+
* <CanvistEditor
|
|
85
|
+
* content={{ format: "markdown", input: "# Notes" }}
|
|
86
|
+
* onUpdate={(editor) => save(editor.export("markdown"))}
|
|
87
|
+
* style={{ flex: 1 }}
|
|
88
|
+
* />
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export declare function CanvistEditor<const E extends NativeExtensions = StarterKit>(props: CanvistEditorProps<E>): ReactElement;
|
package/dist/mod.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canvist rich-text editor for React Native.
|
|
3
|
+
*
|
|
4
|
+
* `CanvistEditor` shows the native iOS or Android editor: canvist's Rust
|
|
5
|
+
* engine, painted natively and edited through the platform's own text
|
|
6
|
+
* input, not a WebView. Commands, queries, and documents are typed from the
|
|
7
|
+
* extensions you register, with the same API as `@canvist/web`: calling a
|
|
8
|
+
* command you did not register is a type error. Calls are synchronous.
|
|
9
|
+
*
|
|
10
|
+
* ```tsx
|
|
11
|
+
* import { CanvistEditor, bold, heading } from "@canvist/react-native";
|
|
12
|
+
*
|
|
13
|
+
* <CanvistEditor
|
|
14
|
+
* extensions={[bold, heading]}
|
|
15
|
+
* content={{ format: "markdown", input: "# Notes" }}
|
|
16
|
+
* onReady={(editor) => editor.commands.toggleHeading({ level: 2 })}
|
|
17
|
+
* style={{ flex: 1 }}
|
|
18
|
+
* />
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @module
|
|
22
|
+
*/
|
|
23
|
+
export type { CanvistError, CommandName, CommandParams, Commands, Content, DocJson, ErrorCode, ExtensionDescriptor, Extensions, Format, Position, Queries, QueryName, QueryParams, QueryResult, Range, Registered, StarterKit, } from "@canvist/headless";
|
|
24
|
+
export * from "@canvist/headless/extensions";
|
|
25
|
+
export { isCanvistError } from "@canvist/headless/native";
|
|
26
|
+
export type { EditorChangeEvent, EditorErrorEvent, EditorFocusEvent, } from "./CanvistEditorViewNativeComponent.ts";
|
|
27
|
+
export { CanvistEditor, type CanvistEditorHandle, type CanvistEditorProps, CanvistView, type CanvistViewProps, type Editor, type EditorOptions, type NativeExtensions, useCanvistEditor, useEditorState, } from "./editor.tsx";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#import <React/RCTViewComponentView.h>
|
|
2
|
+
|
|
3
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
4
|
+
|
|
5
|
+
/// The Fabric component view for `CanvistEditorView`: hosts a `CanvistView`
|
|
6
|
+
/// and turns its changes into events.
|
|
7
|
+
@interface CanvistEditorComponentView : RCTViewComponentView
|
|
8
|
+
@end
|
|
9
|
+
|
|
10
|
+
NS_ASSUME_NONNULL_END
|