@octane-xplat/lexical 0.0.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ import { ensureJSONBridge, jsonBridgeReady } from "./index2.js";
2
+ import { LexicalEditor, supported } from "./index3.js";
3
+ export { LexicalEditor, ensureJSONBridge, jsonBridgeReady, supported };
@@ -0,0 +1,232 @@
1
+ //#region src/json-bridge.ts
2
+ var bridge;
3
+ var kebab = (k) => k.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase());
4
+ function chainDesc(proto, key) {
5
+ let o = proto;
6
+ while (o && o !== Object.prototype) {
7
+ const d = Object.getOwnPropertyDescriptor(o, key);
8
+ if (d) return d;
9
+ o = Object.getPrototypeOf(o);
10
+ }
11
+ }
12
+ function cssText(el) {
13
+ return el.getAttribute("style") ?? "";
14
+ }
15
+ function writeStyleProp(el, key, value) {
16
+ const rules = cssText(el).split(";").map((s) => s.trim()).filter(Boolean).filter((r) => !r.startsWith(kebab(key) + ":"));
17
+ if (value !== "" && value != null) rules.push(`${kebab(key)}: ${value}`);
18
+ el.setAttribute("style", rules.join("; "));
19
+ }
20
+ /** zeed-dom is a vdom, not a DOM — lexical's walkers and exportDOM need four
21
+ * categories of patches beyond the classList/DOMParser install the tiptap
22
+ * bridge carries:
23
+ *
24
+ * 1. `parentElement` (absent pre-0.18 zeed-dom; guarded either way).
25
+ * 2. `firstChild`/`lastChild`/sibling accessors return `undefined` where the
26
+ * DOM spec says `null`, and the getters live on an ancestor proto —
27
+ * chainDesc finds them so the coercion wraps the real getter.
28
+ * 3. `style`: DOM reads of unset props return `''`, and lexical's exportDOM
29
+ * writes via property assignment and `setProperty` — neither serializes
30
+ * in zeed. The proxy defaults missing props to `''` and writes through
31
+ * to the element's `style` attribute.
32
+ * 4. Attribute-valued property assignments (`element.href = …`) must land
33
+ * as attributes — LinkNode.exportDOM sets `anchor.href`, dropping the
34
+ * link target entirely without this.
35
+ *
36
+ * Plus the `Node` constants global (lexical's helpers read
37
+ * `Node.TEXT_NODE`). */
38
+ function installLexicalDomShim(zeed) {
39
+ const bootDoc = zeed.createHTMLDocument();
40
+ bootDoc.body.innerHTML = "<p>x</p>";
41
+ const elProto = Object.getPrototypeOf(bootDoc.body.childNodes[0]);
42
+ const textProto = Object.getPrototypeOf(bootDoc.body.childNodes[0].childNodes[0]);
43
+ for (const p of [elProto, textProto]) {
44
+ if (!chainDesc(p, "parentElement")) Object.defineProperty(p, "parentElement", {
45
+ configurable: true,
46
+ get() {
47
+ const par = this.parentNode;
48
+ return par && par.nodeType === 1 ? par : null;
49
+ }
50
+ });
51
+ for (const k of [
52
+ "firstChild",
53
+ "lastChild",
54
+ "nextSibling",
55
+ "previousSibling"
56
+ ]) {
57
+ const d = chainDesc(p, k);
58
+ Object.defineProperty(p, k, {
59
+ configurable: true,
60
+ get() {
61
+ return (d?.get ? d.get.call(this) : void 0) ?? null;
62
+ },
63
+ set: d?.set
64
+ });
65
+ }
66
+ }
67
+ const styleCache = /* @__PURE__ */ new WeakMap();
68
+ Object.defineProperty(elProto, "style", {
69
+ configurable: true,
70
+ get() {
71
+ let proxy = styleCache.get(this);
72
+ if (!proxy) {
73
+ const el = this;
74
+ proxy = new Proxy({}, {
75
+ get: (_t, k) => {
76
+ if (k === "setProperty") return (prop, value) => writeStyleProp(el, prop, value);
77
+ if (k === "removeProperty") return (prop) => {
78
+ writeStyleProp(el, prop, "");
79
+ return "";
80
+ };
81
+ if (k === "cssText") return cssText(el);
82
+ if (k === "getPropertyValue") return (prop) => {
83
+ const raw = cssText(el).split(";").map((s) => s.trim()).find((r) => r.startsWith(prop + ":"));
84
+ return raw ? raw.slice(raw.indexOf(":") + 1).trim() : "";
85
+ };
86
+ if (typeof k === "string") {
87
+ const raw = cssText(el).split(";").map((s) => s.trim()).find((r) => r.startsWith(kebab(k) + ":"));
88
+ return raw ? raw.slice(raw.indexOf(":") + 1).trim() : "";
89
+ }
90
+ },
91
+ set: (_t, k, v) => {
92
+ if (typeof k === "string") writeStyleProp(el, k, v);
93
+ return true;
94
+ }
95
+ });
96
+ styleCache.set(this, proxy);
97
+ }
98
+ return proxy;
99
+ }
100
+ });
101
+ for (const attr of [
102
+ "href",
103
+ "target",
104
+ "rel",
105
+ "title"
106
+ ]) {
107
+ const d = chainDesc(elProto, attr);
108
+ if (!d?.get && !d?.set) Object.defineProperty(elProto, attr, {
109
+ configurable: true,
110
+ get() {
111
+ return this.getAttribute(attr) ?? "";
112
+ },
113
+ set(v) {
114
+ this.setAttribute(attr, v);
115
+ }
116
+ });
117
+ }
118
+ const clDesc = chainDesc(elProto, "classList");
119
+ if (clDesc?.get) Object.defineProperty(elProto, "classList", {
120
+ configurable: true,
121
+ get() {
122
+ const cl = clDesc.get.call(this);
123
+ if (cl && typeof cl[Symbol.iterator] !== "function") {
124
+ const tokens = String(this.getAttribute("class") ?? "").split(/\s+/).filter(Boolean);
125
+ return Object.assign(tokens, cl);
126
+ }
127
+ return cl;
128
+ },
129
+ set: clDesc.set
130
+ });
131
+ const g = globalThis;
132
+ if (!g.window?.DOMParser) {
133
+ class ZeedDOMParser {
134
+ parseFromString(html) {
135
+ const doc = zeed.createHTMLDocument();
136
+ doc.body.innerHTML = html;
137
+ return doc;
138
+ }
139
+ }
140
+ g.window = { DOMParser: ZeedDOMParser };
141
+ g.document = zeed.createHTMLDocument();
142
+ }
143
+ g.Node = g.Node ?? {
144
+ ELEMENT_NODE: 1,
145
+ ATTRIBUTE_NODE: 2,
146
+ TEXT_NODE: 3,
147
+ CDATA_SECTION_NODE: 4,
148
+ PROCESSING_INSTRUCTION_NODE: 7,
149
+ COMMENT_NODE: 8,
150
+ DOCUMENT_NODE: 9,
151
+ DOCUMENT_TYPE_NODE: 10,
152
+ DOCUMENT_FRAGMENT_NODE: 11
153
+ };
154
+ }
155
+ async function ensureJSONBridge() {
156
+ if (bridge !== void 0) return bridge !== null;
157
+ try {
158
+ const [lexical, html, rt, list, link, code, ext, zeed] = await Promise.all([
159
+ import("lexical"),
160
+ import("@lexical/html"),
161
+ import("@lexical/rich-text"),
162
+ import("@lexical/list"),
163
+ import("@lexical/link"),
164
+ import("@lexical/code"),
165
+ import("@lexical/extension"),
166
+ import("zeed-dom")
167
+ ]);
168
+ installLexicalDomShim(zeed);
169
+ bridge = {
170
+ editor: lexical.createEditor({
171
+ namespace: "octane-xplat/lexical",
172
+ nodes: [
173
+ rt.HeadingNode,
174
+ rt.QuoteNode,
175
+ list.ListNode,
176
+ list.ListItemNode,
177
+ link.LinkNode,
178
+ code.CodeNode,
179
+ ext.HorizontalRuleNode
180
+ ],
181
+ onError: () => void 0
182
+ }),
183
+ lexical,
184
+ html,
185
+ zeed
186
+ };
187
+ return true;
188
+ } catch {
189
+ bridge = null;
190
+ return false;
191
+ }
192
+ }
193
+ function jsonBridgeReady() {
194
+ return bridge != null;
195
+ }
196
+ function parseHTML(htmlText) {
197
+ const doc = bridge.zeed.createHTMLDocument();
198
+ doc.body.innerHTML = htmlText;
199
+ return doc;
200
+ }
201
+ /** HTML → serialized editor state through lexical's own DOM import rules.
202
+ * Null when the bridge hasn't loaded (call ensureJSONBridge first / check
203
+ * `jsonBridgeReady`). */
204
+ function htmlToJSON(htmlText) {
205
+ if (!bridge) return null;
206
+ try {
207
+ bridge.editor.update(() => {
208
+ const nodes = bridge.html.$generateNodesFromDOM(bridge.editor, parseHTML(htmlText));
209
+ bridge.lexical.$getRoot().clear().append(...nodes);
210
+ }, { discrete: true });
211
+ return bridge.editor.getEditorState().toJSON();
212
+ } catch {
213
+ return null;
214
+ }
215
+ }
216
+ /** Serialized editor state → HTML via lexical's own exportDOM rules (no
217
+ * browser DOM — the zeed-dom shim stands in). */
218
+ function jsonToHTML(doc) {
219
+ if (!bridge) return null;
220
+ try {
221
+ bridge.editor.setEditorState(bridge.editor.parseEditorState(JSON.stringify(doc)));
222
+ let out = "";
223
+ bridge.editor.read(() => {
224
+ out = bridge.html.$generateHtmlFromNodes(bridge.editor);
225
+ });
226
+ return out;
227
+ } catch {
228
+ return null;
229
+ }
230
+ }
231
+ //#endregion
232
+ export { ensureJSONBridge, htmlToJSON, jsonBridgeReady, jsonToHTML };
@@ -0,0 +1,228 @@
1
+ import { ensureJSONBridge, htmlToJSON, jsonBridgeReady, jsonToHTML } from "./index2.js";
2
+ import { defineUniversalComponent, hookSlots, universalComponent, universalPlan, universalProps, universalValue, useCallback, useEffect, useImperativeHandle, useRef, useState } from "@nativescript-community/octane";
3
+ import { RichTextEditor, supported } from "@octane-xplat/richtext";
4
+ //#region src/LexicalEditor.tsrx
5
+ var _hs$ = /* @__PURE__ */ hookSlots(9);
6
+ var _h$0 = /* @__PURE__ */ Symbol(_hs$);
7
+ var _h$1 = /* @__PURE__ */ Symbol(_hs$ + 1);
8
+ var _h$2 = /* @__PURE__ */ Symbol(_hs$ + 2);
9
+ var _h$3 = /* @__PURE__ */ Symbol(_hs$ + 3);
10
+ var _h$4 = /* @__PURE__ */ Symbol(_hs$ + 4);
11
+ var _h$5 = /* @__PURE__ */ Symbol(_hs$ + 5);
12
+ var _h$6 = /* @__PURE__ */ Symbol(_hs$ + 6);
13
+ var _h$7 = /* @__PURE__ */ Symbol(_hs$ + 7);
14
+ var _h$8 = /* @__PURE__ */ Symbol(_hs$ + 8);
15
+ var __octaneUniversalPlan0 = universalPlan("nativescript", {
16
+ "kind": "slot",
17
+ "slot": 0
18
+ });
19
+ var supported$1 = supported;
20
+ var LexicalEditor = defineUniversalComponent("nativescript", function LexicalEditor(props) {
21
+ const inner = useRef(null, _h$0);
22
+ const [exposedHandle, setHandle] = useState(null, _h$1);
23
+ useImperativeHandle(props.ref, () => exposedHandle, [exposedHandle], _h$2);
24
+ const lastJSON = useRef(void 0, _h$3);
25
+ const appliedHost = useRef(null, _h$4);
26
+ const desiredDoc = useRef(void 0, _h$5);
27
+ const applyDoc = () => {
28
+ const host = inner.current;
29
+ const doc = desiredDoc.current;
30
+ if (!host || doc === void 0 || !jsonBridgeReady()) return;
31
+ const stamp = JSON.stringify(doc);
32
+ if (stamp === lastJSON.current && appliedHost.current === host) return;
33
+ const html = jsonToHTML(doc);
34
+ if (html != null) {
35
+ lastJSON.current = stamp;
36
+ appliedHost.current = host;
37
+ host.setHTML(html);
38
+ }
39
+ };
40
+ const bindHost = useCallback((h) => {
41
+ inner.current = h;
42
+ if (h) queueMicrotask(applyDoc);
43
+ setHandle((previous) => previous?.native === h?.native ? previous : h ? {
44
+ getHTML: () => h.getHTML(),
45
+ setHTML: (html) => h.setHTML(html),
46
+ getJSON: () => jsonBridgeReady() ? htmlToJSON(h.getHTML()) : null,
47
+ setJSON: (doc) => {
48
+ desiredDoc.current = doc;
49
+ applyDoc();
50
+ },
51
+ apply: (format) => h.apply(format),
52
+ linkTo: (url, anchor) => h.linkTo(url, anchor),
53
+ removeLink: () => h.removeLink(),
54
+ isActive: (format) => h.isActive(format),
55
+ undo: () => h.undo(),
56
+ redo: () => h.redo(),
57
+ focus: () => h.focus(),
58
+ blur: () => h.blur(),
59
+ isFocused: () => h.isFocused(),
60
+ native: h.native
61
+ } : null);
62
+ }, [], _h$6);
63
+ useEffect(() => {
64
+ let alive = true;
65
+ ensureJSONBridge().then((ok) => {
66
+ if (!alive) return;
67
+ props.onJSONReady?.(ok);
68
+ applyDoc();
69
+ });
70
+ return () => {
71
+ alive = false;
72
+ };
73
+ }, [], _h$7);
74
+ useEffect(() => {
75
+ if (!props.json) return;
76
+ desiredDoc.current = props.json;
77
+ applyDoc();
78
+ }, [props.json], _h$8);
79
+ const emitJSON = () => {
80
+ if (!inner.current || !jsonBridgeReady()) return;
81
+ const doc = htmlToJSON(inner.current.getHTML());
82
+ if (doc) {
83
+ lastJSON.current = JSON.stringify(doc);
84
+ appliedHost.current = inner.current;
85
+ props.onJSONChange?.(doc);
86
+ }
87
+ };
88
+ return universalValue(__octaneUniversalPlan0, [universalComponent("nativescript", RichTextEditor, universalProps([
89
+ [
90
+ "set",
91
+ "id",
92
+ props.id
93
+ ],
94
+ [
95
+ "set",
96
+ "className",
97
+ props.className
98
+ ],
99
+ [
100
+ "set",
101
+ "style",
102
+ props.style
103
+ ],
104
+ [
105
+ "set",
106
+ "value",
107
+ props.value
108
+ ],
109
+ [
110
+ "set",
111
+ "placeholder",
112
+ props.placeholder
113
+ ],
114
+ [
115
+ "set",
116
+ "editable",
117
+ props.editable
118
+ ],
119
+ [
120
+ "set",
121
+ "autofocus",
122
+ props.autofocus
123
+ ],
124
+ [
125
+ "set",
126
+ "onReady",
127
+ () => {
128
+ applyDoc();
129
+ props.onReady?.();
130
+ }
131
+ ],
132
+ [
133
+ "set",
134
+ "onChange",
135
+ (html) => {
136
+ props.onChange?.(html);
137
+ emitJSON();
138
+ }
139
+ ],
140
+ [
141
+ "set",
142
+ "onSelectionChange",
143
+ props.onSelectionChange
144
+ ],
145
+ [
146
+ "set",
147
+ "onFocus",
148
+ props.onFocus
149
+ ],
150
+ [
151
+ "set",
152
+ "onBlur",
153
+ props.onBlur
154
+ ],
155
+ [
156
+ "set",
157
+ "ref",
158
+ bindHost
159
+ ],
160
+ [
161
+ "set",
162
+ "android",
163
+ props.android
164
+ ],
165
+ [
166
+ "set",
167
+ "ios",
168
+ props.ios
169
+ ],
170
+ [
171
+ "set",
172
+ "row",
173
+ props.row
174
+ ],
175
+ [
176
+ "set",
177
+ "col",
178
+ props.col
179
+ ],
180
+ [
181
+ "set",
182
+ "rowSpan",
183
+ props.rowSpan
184
+ ],
185
+ [
186
+ "set",
187
+ "colSpan",
188
+ props.colSpan
189
+ ],
190
+ [
191
+ "set",
192
+ "dock",
193
+ props.dock
194
+ ],
195
+ [
196
+ "set",
197
+ "left",
198
+ props.left
199
+ ],
200
+ [
201
+ "set",
202
+ "top",
203
+ props.top
204
+ ],
205
+ [
206
+ "set",
207
+ "flexGrow",
208
+ props.flexGrow
209
+ ],
210
+ [
211
+ "set",
212
+ "flexShrink",
213
+ props.flexShrink
214
+ ],
215
+ [
216
+ "set",
217
+ "alignSelf",
218
+ props.alignSelf
219
+ ],
220
+ [
221
+ "set",
222
+ "order",
223
+ props.order
224
+ ]
225
+ ]))]);
226
+ }, { "module": "@nativescript-community/octane" });
227
+ //#endregion
228
+ export { LexicalEditor, supported$1 as supported };
@@ -0,0 +1,8 @@
1
+ {
2
+ "version": 1,
3
+ "buildId": "0e9b3b59-08fe-4f74-9dd5-4ebc5fe298b3",
4
+ "mode": "production",
5
+ "capabilities": {
6
+ "independentHydration": false
7
+ }
8
+ }
@@ -0,0 +1,12 @@
1
+ import { LexicalEditor, supported } from "./index2.js";
2
+ //#region src/index.web.ts
3
+ /** Web needs no lazy bridge — lexical is in the bundle already. Kept for
4
+ * call-site parity with the native entry. */
5
+ function ensureJSONBridge() {
6
+ return Promise.resolve(true);
7
+ }
8
+ function jsonBridgeReady() {
9
+ return true;
10
+ }
11
+ //#endregion
12
+ export { LexicalEditor, ensureJSONBridge, jsonBridgeReady, supported };