@octane-xplat/lexical 0.0.1 → 0.10.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,211 @@
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(8);
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 __octaneUniversalPlan0 = universalPlan("nativescript", {
15
+ "kind": "slot",
16
+ "slot": 0
17
+ });
18
+ var supported$1 = supported;
19
+ var LexicalEditor = defineUniversalComponent("nativescript", function LexicalEditor(props) {
20
+ const inner = useRef(null, _h$0);
21
+ const [exposedHandle, setHandle] = useState(null, _h$1);
22
+ useImperativeHandle(props.ref, () => exposedHandle, [exposedHandle], _h$2);
23
+ const jsonReady = useRef(false, _h$3);
24
+ const lastJSON = useRef(void 0, _h$4);
25
+ const bindHost = useCallback((h) => {
26
+ inner.current = h;
27
+ setHandle((previous) => previous?.native === h?.native ? previous : h ? {
28
+ getHTML: () => h.getHTML(),
29
+ setHTML: (html) => h.setHTML(html),
30
+ getJSON: () => jsonBridgeReady() ? htmlToJSON(h.getHTML()) : null,
31
+ setJSON: (doc) => {
32
+ const html = jsonBridgeReady() ? jsonToHTML(doc) : null;
33
+ if (html != null) {
34
+ lastJSON.current = JSON.stringify(doc);
35
+ h.setHTML(html);
36
+ }
37
+ },
38
+ apply: (format) => h.apply(format),
39
+ linkTo: (url, anchor) => h.linkTo(url, anchor),
40
+ removeLink: () => h.removeLink(),
41
+ isActive: (format) => h.isActive(format),
42
+ undo: () => h.undo(),
43
+ redo: () => h.redo(),
44
+ focus: () => h.focus(),
45
+ blur: () => h.blur(),
46
+ isFocused: () => h.isFocused(),
47
+ native: h.native
48
+ } : null);
49
+ }, [], _h$5);
50
+ useEffect(() => {
51
+ ensureJSONBridge().then((ok) => {
52
+ jsonReady.current = ok;
53
+ props.onJSONReady?.(ok);
54
+ });
55
+ }, [], _h$6);
56
+ useEffect(() => {
57
+ if (!props.json || !jsonBridgeReady() || !inner.current) return;
58
+ const stamp = JSON.stringify(props.json);
59
+ if (stamp === lastJSON.current) return;
60
+ const html = jsonToHTML(props.json);
61
+ if (html != null) {
62
+ lastJSON.current = stamp;
63
+ inner.current.setHTML(html);
64
+ }
65
+ }, [props.json], _h$7);
66
+ const emitJSON = () => {
67
+ if (!inner.current || !jsonBridgeReady()) return;
68
+ const doc = htmlToJSON(inner.current.getHTML());
69
+ if (doc) {
70
+ lastJSON.current = JSON.stringify(doc);
71
+ props.onJSONChange?.(doc);
72
+ }
73
+ };
74
+ return universalValue(__octaneUniversalPlan0, [universalComponent("nativescript", RichTextEditor, universalProps([
75
+ [
76
+ "set",
77
+ "id",
78
+ props.id
79
+ ],
80
+ [
81
+ "set",
82
+ "className",
83
+ props.className
84
+ ],
85
+ [
86
+ "set",
87
+ "style",
88
+ props.style
89
+ ],
90
+ [
91
+ "set",
92
+ "value",
93
+ props.value
94
+ ],
95
+ [
96
+ "set",
97
+ "placeholder",
98
+ props.placeholder
99
+ ],
100
+ [
101
+ "set",
102
+ "editable",
103
+ props.editable
104
+ ],
105
+ [
106
+ "set",
107
+ "autofocus",
108
+ props.autofocus
109
+ ],
110
+ [
111
+ "set",
112
+ "onReady",
113
+ props.onReady
114
+ ],
115
+ [
116
+ "set",
117
+ "onChange",
118
+ (html) => {
119
+ props.onChange?.(html);
120
+ emitJSON();
121
+ }
122
+ ],
123
+ [
124
+ "set",
125
+ "onSelectionChange",
126
+ props.onSelectionChange
127
+ ],
128
+ [
129
+ "set",
130
+ "onFocus",
131
+ props.onFocus
132
+ ],
133
+ [
134
+ "set",
135
+ "onBlur",
136
+ props.onBlur
137
+ ],
138
+ [
139
+ "set",
140
+ "ref",
141
+ bindHost
142
+ ],
143
+ [
144
+ "set",
145
+ "android",
146
+ props.android
147
+ ],
148
+ [
149
+ "set",
150
+ "ios",
151
+ props.ios
152
+ ],
153
+ [
154
+ "set",
155
+ "row",
156
+ props.row
157
+ ],
158
+ [
159
+ "set",
160
+ "col",
161
+ props.col
162
+ ],
163
+ [
164
+ "set",
165
+ "rowSpan",
166
+ props.rowSpan
167
+ ],
168
+ [
169
+ "set",
170
+ "colSpan",
171
+ props.colSpan
172
+ ],
173
+ [
174
+ "set",
175
+ "dock",
176
+ props.dock
177
+ ],
178
+ [
179
+ "set",
180
+ "left",
181
+ props.left
182
+ ],
183
+ [
184
+ "set",
185
+ "top",
186
+ props.top
187
+ ],
188
+ [
189
+ "set",
190
+ "flexGrow",
191
+ props.flexGrow
192
+ ],
193
+ [
194
+ "set",
195
+ "flexShrink",
196
+ props.flexShrink
197
+ ],
198
+ [
199
+ "set",
200
+ "alignSelf",
201
+ props.alignSelf
202
+ ],
203
+ [
204
+ "set",
205
+ "order",
206
+ props.order
207
+ ]
208
+ ]))]);
209
+ }, { "module": "@nativescript-community/octane" });
210
+ //#endregion
211
+ export { LexicalEditor, supported$1 as supported };
@@ -0,0 +1,8 @@
1
+ {
2
+ "version": 1,
3
+ "buildId": "5ca3a356-fe4b-4bf2-8331-5e237e22a4e8",
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 };