@formulaxjs/editor 0.1.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/README.md CHANGED
@@ -1,45 +1,34 @@
1
1
  # @formulaxjs/editor
2
2
 
3
- Browser editing foundation for FormulaX.
3
+ Public editor entry and integration helpers for FormulaX.
4
4
 
5
- `@formulaxjs/editor` adapts `@formulaxjs/core` state to the DOM. It provides the interactive editor shell, HTML rendering helpers, modal wiring, and browser-side styles used by richer host integrations.
5
+ `@formulaxjs/editor` provides the public FormulaX editor entry backed by the Kity compatibility runtime, along with shared formula markup helpers and modal wiring used by richer host integrations.
6
6
 
7
7
  > Status: experimental. Public APIs may change before the first stable release.
8
8
 
9
9
  ## Install
10
10
 
11
11
  ```bash
12
- pnpm add @formulaxjs/editor @formulaxjs/core @formulaxjs/renderer @formulaxjs/kity-runtime
12
+ pnpm add @formulaxjs/editor
13
13
  ```
14
14
 
15
15
  ## Highlights
16
16
 
17
- - `FormulaEditor` for mounting an interactive FormulaX editor into a DOM node
18
- - `renderInteractiveHtml` for HTML rendering of FormulaX state
19
- - `mountFormulaXKityEditor` for modal-based Kity editing flows
20
- - `formulaXModalStyles` and `editorStyles` for default browser styling
17
+ - `FormulaXEditor` as the public runtime-backed editor entry
18
+ - `mountFormulaXEditor` for modal-based Kity editing flows
19
+ - `formulaXModalStyles` for shared modal styling
20
+ - Formula node helpers for host-editor markup integration
21
21
 
22
22
  ## Example
23
23
 
24
24
  ```ts
25
- import { FormulaEditor } from '@formulaxjs/editor';
25
+ import { FormulaXEditor } from '@formulaxjs/editor';
26
26
 
27
- const root = document.getElementById('editor');
28
-
29
- if (!root) {
30
- throw new Error('Missing #editor');
31
- }
32
-
33
- const editor = new FormulaEditor({
34
- root,
35
- onChange(state) {
36
- console.log(state);
37
- },
27
+ new FormulaXEditor({
28
+ el: '#app',
38
29
  });
39
-
40
- console.log(editor.getState());
41
30
  ```
42
31
 
43
32
  ## Package role
44
33
 
45
- Use this package for browser-native FormulaX editing. If you are integrating FormulaX into TinyMCE, CKEditor 5, or Tiptap, prefer the dedicated adapter packages instead.
34
+ Use this package as the main application-facing FormulaX editor entry. If you are integrating FormulaX into TinyMCE, CKEditor 5, or Tiptap, prefer the dedicated adapter packages instead.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,3 @@
1
+ @import url('./base.css');
2
+ @import url('./ui.css');
3
+ @import url('./scrollbar.css');
package/dist/index.cjs CHANGED
@@ -23,376 +23,27 @@ __export(index_exports, {
23
23
  DEFAULT_FORMULA_ATTRIBUTE: () => DEFAULT_FORMULA_ATTRIBUTE,
24
24
  DEFAULT_FORMULA_CLASS: () => DEFAULT_FORMULA_CLASS,
25
25
  FORMULA_FLAG_ATTRIBUTE: () => FORMULA_FLAG_ATTRIBUTE,
26
- FormulaEditor: () => FormulaEditor,
26
+ FormulaXEditor: () => import_kity_runtime2.FormulaXEditor,
27
27
  createFormulaElement: () => createFormulaElement,
28
28
  createFormulaMarkup: () => createFormulaMarkup,
29
- editorStyles: () => editorStyles,
29
+ createKityEditor: () => import_kity_runtime2.createKityEditor,
30
30
  ensureFormulaXModalStyles: () => ensureFormulaXModalStyles,
31
+ ensureKityRuntime: () => import_kity_runtime2.ensureKityRuntime,
31
32
  escapeAttribute: () => escapeAttribute,
32
33
  escapeHtml: () => escapeHtml,
33
34
  findFormulaElement: () => findFormulaElement,
34
35
  formulaXModalStyles: () => formulaXModalStyles,
35
36
  getFormulaLatexFromElement: () => getFormulaLatexFromElement,
36
37
  isFormulaElement: () => isFormulaElement,
37
- mountFormulaXKityEditor: () => mountFormulaXKityEditor,
38
- renderInteractiveHtml: () => renderInteractiveHtml,
38
+ mountFormulaXEditor: () => mountFormulaXEditor,
39
+ mountKityEditor: () => import_kity_runtime2.mountKityEditor,
39
40
  replaceFormulaElement: () => replaceFormulaElement,
40
- serializeSvgForInsertion: () => serializeSvgForInsertion,
41
- t: () => t,
42
- translations: () => translations
41
+ serializeSvgForInsertion: () => serializeSvgForInsertion
43
42
  });
44
43
  module.exports = __toCommonJS(index_exports);
45
44
 
46
- // src/dom-renderer.ts
47
- var joinPath = (path) => path.join(".");
48
- var renderInteractiveHtml = (doc, activePath) => `
49
- <div class="fx-editor-surface" data-role="surface">
50
- ${renderChildren(doc.body, [], activePath)}
51
- </div>
52
- `;
53
- var renderChildren = (nodes, basePath, activePath) => {
54
- const html = [];
55
- for (let index = 0; index <= nodes.length; index += 1) {
56
- const path = joinPath([...basePath, index]);
57
- const isActive = path === joinPath(activePath);
58
- html.push(
59
- `<button class="fx-slot${isActive ? " is-active" : ""}" data-path="${path}" type="button" title="${path}"></button>`
60
- );
61
- if (index < nodes.length) {
62
- html.push(renderNode(nodes[index], [...basePath, index], activePath));
63
- }
64
- }
65
- return html.join("");
66
- };
67
- var renderNode = (node, path, activePath) => {
68
- const pathValue = joinPath(path);
69
- const isActive = (p) => joinPath(p) === joinPath(activePath);
70
- switch (node.type) {
71
- case "text":
72
- return `<span class="fx-node fx-text" data-node-path="${pathValue}">${node.value}</span>`;
73
- case "group":
74
- return `<span class="fx-node fx-group" data-node-path="${pathValue}">${renderChildren(node.body, [...path, 0], activePath)}</span>`;
75
- case "frac":
76
- return `<span class="fx-node fx-frac" data-node-path="${pathValue}">
77
- <span class="fx-frac-num${isActive([...path, 0]) ? " is-active" : ""}" data-path="${joinPath([...path, 0])}">${renderChildren(node.numerator, [...path, 0], activePath)}</span>
78
- <span class="fx-frac-line"></span>
79
- <span class="fx-frac-den${isActive([...path, 1]) ? " is-active" : ""}" data-path="${joinPath([...path, 1])}">${renderChildren(node.denominator, [...path, 1], activePath)}</span>
80
- </span>`;
81
- case "supsub":
82
- return `<span class="fx-node fx-supsub" data-node-path="${pathValue}">
83
- <span class="fx-supsub-base">${renderChildren(node.base, [...path, 0], activePath)}</span>
84
- <span class="fx-supsub-stack">
85
- <span class="fx-sup${isActive([...path, 1]) ? " is-active" : ""}" data-path="${joinPath([...path, 1])}">${renderChildren(node.sup ?? [], [...path, 1], activePath)}</span>
86
- <span class="fx-sub${isActive([...path, 2]) ? " is-active" : ""}" data-path="${joinPath([...path, 2])}">${renderChildren(node.sub ?? [], [...path, 2], activePath)}</span>
87
- </span>
88
- </span>`;
89
- case "sqrt":
90
- return `<span class="fx-node fx-sqrt" data-node-path="${pathValue}">
91
- <span class="fx-sqrt-symbol">\u221A</span>
92
- <span class="fx-sqrt-body${isActive([...path, 0]) ? " is-active" : ""}" data-path="${joinPath([...path, 0])}">${renderChildren(node.value, [...path, 0], activePath)}</span>
93
- </span>`;
94
- case "fenced":
95
- return `<span class="fx-node fx-fenced" data-node-path="${pathValue}">
96
- <span class="fx-fence">${node.left}</span>
97
- <span class="fx-fenced-body${isActive([...path, 0]) ? " is-active" : ""}" data-path="${joinPath([...path, 0])}">${renderChildren(node.body, [...path, 0], activePath)}</span>
98
- <span class="fx-fence">${node.right}</span>
99
- </span>`;
100
- case "doc":
101
- return renderChildren(node.body, [0], activePath);
102
- }
103
- throw new Error(`Unsupported node type: ${String(node.type)}`);
104
- };
105
-
106
- // src/editor.ts
107
- var import_core = require("@formulaxjs/core");
108
-
109
- // src/styles.ts
110
- var editorStyles = `
111
- @keyframes fx-blink {
112
- 0%, 50% { opacity: 1; }
113
- 51%, 100% { opacity: 0; }
114
- }
115
- .fx-editor {
116
- border: 1px solid #d8d4c7;
117
- border-radius: 2px;
118
- background: #fffefb;
119
- box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.7);
120
- padding: 24px 28px;
121
- font-family: Cambria, 'Times New Roman', serif;
122
- font-size: 25px;
123
- line-height: 1.7;
124
- min-height: 120px;
125
- position: relative;
126
- }
127
- .fx-editor:focus-within {
128
- border-color: #7bbb59;
129
- box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.7), 0 0 0 3px rgba(123, 187, 89, 0.18);
130
- }
131
- .fx-editor-surface {
132
- display: flex;
133
- flex-wrap: wrap;
134
- align-items: baseline;
135
- gap: 3px;
136
- cursor: text;
137
- }
138
- .fx-node {
139
- position: relative;
140
- display: inline-flex;
141
- align-items: center;
142
- padding: 2px 3px;
143
- border-radius: 2px;
144
- transition: background 0.12s ease;
145
- }
146
- .fx-node:hover {
147
- background: rgba(122, 186, 89, 0.12);
148
- }
149
- .fx-text {
150
- color: #2b2925;
151
- }
152
- .fx-group {
153
- background: rgba(243, 241, 230, 0.82);
154
- border: 1px dashed #c8c0a8;
155
- }
156
- .fx-frac {
157
- display: inline-flex;
158
- flex-direction: column;
159
- align-items: center;
160
- vertical-align: middle;
161
- padding: 0 5px;
162
- }
163
- .fx-frac-num, .fx-frac-den {
164
- display: flex;
165
- flex-wrap: wrap;
166
- align-items: center;
167
- justify-content: center;
168
- padding: 3px 8px;
169
- min-width: 24px;
170
- min-height: 28px;
171
- border-radius: 2px;
172
- transition: all 0.15s ease;
173
- }
174
- .fx-frac-num {
175
- border-bottom: 2px solid #2e2c29;
176
- padding-bottom: 6px;
177
- }
178
- .fx-frac-den {
179
- border-top: 2px solid #2e2c29;
180
- padding-top: 6px;
181
- }
182
- .fx-frac-num:hover, .fx-frac-den:hover {
183
- background: rgba(122, 186, 89, 0.1);
184
- }
185
- .fx-frac-num.is-active, .fx-frac-den.is-active {
186
- background: rgba(122, 186, 89, 0.16);
187
- box-shadow: inset 0 0 0 1px rgba(83, 184, 86, 0.5);
188
- }
189
- .fx-supsub {
190
- display: inline-flex;
191
- align-items: baseline;
192
- gap: 0;
193
- }
194
- .fx-supsub-base {
195
- display: inline-flex;
196
- align-items: center;
197
- padding: 0 2px;
198
- }
199
- .fx-supsub-stack {
200
- display: inline-flex;
201
- flex-direction: column;
202
- align-items: flex-start;
203
- font-size: 0.65em;
204
- margin-left: 2px;
205
- }
206
- .fx-sup, .fx-sub {
207
- display: flex;
208
- align-items: center;
209
- min-width: 16px;
210
- min-height: 20px;
211
- padding: 0 4px;
212
- border-radius: 2px;
213
- transition: all 0.15s ease;
214
- }
215
- .fx-sup {
216
- vertical-align: super;
217
- }
218
- .fx-sub {
219
- vertical-align: sub;
220
- }
221
- .fx-sup:hover, .fx-sub:hover {
222
- background: rgba(122, 186, 89, 0.1);
223
- }
224
- .fx-sup.is-active, .fx-sub.is-active {
225
- background: rgba(122, 186, 89, 0.16);
226
- box-shadow: inset 0 0 0 1px rgba(83, 184, 86, 0.5);
227
- }
228
- .fx-sqrt {
229
- display: inline-flex;
230
- align-items: baseline;
231
- }
232
- .fx-sqrt-symbol {
233
- font-size: 1.1em;
234
- color: #2e2c29;
235
- margin-right: 2px;
236
- }
237
- .fx-sqrt-body {
238
- display: inline-flex;
239
- align-items: center;
240
- border-top: 2px solid #2e2c29;
241
- padding: 4px 6px 0;
242
- margin-left: 2px;
243
- }
244
- .fx-sqrt-body:hover {
245
- background: rgba(122, 186, 89, 0.1);
246
- }
247
- .fx-sqrt-body.is-active {
248
- background: rgba(122, 186, 89, 0.16);
249
- }
250
- .fx-fenced {
251
- display: inline-flex;
252
- align-items: center;
253
- background: rgba(243, 241, 230, 0.78);
254
- border-radius: 3px;
255
- padding: 0 4px;
256
- }
257
- .fx-fence {
258
- color: #59554d;
259
- font-size: 1.2em;
260
- padding: 0 2px;
261
- }
262
- .fx-fenced-body {
263
- display: inline-flex;
264
- align-items: center;
265
- padding: 0 6px;
266
- }
267
- .fx-fenced-body:hover {
268
- background: rgba(122, 186, 89, 0.1);
269
- border-radius: 2px;
270
- }
271
- .fx-fenced-body.is-active {
272
- background: rgba(122, 186, 89, 0.16);
273
- border-radius: 2px;
274
- }
275
- .fx-slot {
276
- width: 3px;
277
- height: 1.2em;
278
- border: none;
279
- background: #53b856;
280
- padding: 0;
281
- margin: 0 1px;
282
- cursor: text;
283
- border-radius: 1px;
284
- vertical-align: middle;
285
- }
286
- .fx-slot.is-active {
287
- animation: fx-blink 1s infinite;
288
- }
289
- .fx-slot:hover {
290
- background: #77c75b;
291
- }
292
- `;
293
-
294
- // src/editor.ts
295
- var FormulaEditor = class {
296
- state;
297
- root;
298
- onChange;
299
- locale;
300
- constructor(options) {
301
- this.root = options.root;
302
- this.state = options.initialState ?? (0, import_core.createEmptyState)();
303
- this.onChange = options.onChange;
304
- this.locale = options.locale ?? "en";
305
- this.root.classList.add("fx-editor");
306
- this.root.tabIndex = 0;
307
- this.ensureStyles();
308
- this.bindEvents();
309
- this.render();
310
- }
311
- getState() {
312
- return structuredClone(this.state);
313
- }
314
- getLocale() {
315
- return this.locale;
316
- }
317
- setState(state) {
318
- this.state = structuredClone(state);
319
- this.render();
320
- }
321
- dispatch(command) {
322
- this.state = (0, import_core.applyCommand)(this.state, command);
323
- this.render();
324
- this.onChange?.(this.getState());
325
- }
326
- ensureStyles() {
327
- if (document.getElementById("fx-editor-styles")) return;
328
- const style = document.createElement("style");
329
- style.id = "fx-editor-styles";
330
- style.textContent = editorStyles;
331
- document.head.appendChild(style);
332
- }
333
- bindEvents() {
334
- this.root.addEventListener("click", (event) => {
335
- const target = event.target;
336
- const pathValue = target.dataset.path;
337
- if (pathValue === void 0) return;
338
- const path = pathValue === "" ? [] : pathValue.split(".").map(Number);
339
- this.moveSelection(path);
340
- });
341
- this.root.addEventListener("keydown", (event) => {
342
- if (event.key === "Backspace") {
343
- event.preventDefault();
344
- this.dispatch((0, import_core.backspace)());
345
- return;
346
- }
347
- if (event.key === "/") {
348
- event.preventDefault();
349
- this.dispatch((0, import_core.insertFraction)());
350
- return;
351
- }
352
- if (event.key === "^") {
353
- event.preventDefault();
354
- this.dispatch((0, import_core.insertSuperscript)());
355
- return;
356
- }
357
- if (event.key === "_") {
358
- event.preventDefault();
359
- this.dispatch((0, import_core.insertSubscript)());
360
- return;
361
- }
362
- if (event.key === "r" && event.ctrlKey) {
363
- event.preventDefault();
364
- this.dispatch((0, import_core.insertSqrt)());
365
- return;
366
- }
367
- if (event.key === "(") {
368
- event.preventDefault();
369
- this.dispatch((0, import_core.insertFenced)("(", ")"));
370
- return;
371
- }
372
- if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
373
- event.preventDefault();
374
- this.dispatch((0, import_core.insertText)(event.key));
375
- }
376
- });
377
- }
378
- moveSelection(path) {
379
- this.state = {
380
- ...this.state,
381
- selection: {
382
- anchor: [...path],
383
- focus: [...path]
384
- }
385
- };
386
- this.render();
387
- this.onChange?.(this.getState());
388
- }
389
- render() {
390
- this.root.innerHTML = renderInteractiveHtml(this.state.doc, this.state.selection.focus);
391
- }
392
- };
393
-
394
45
  // src/formula-modal.ts
395
- var import_core2 = require("@formulaxjs/core");
46
+ var import_core = require("@formulaxjs/core");
396
47
  var import_kity_runtime = require("@formulaxjs/kity-runtime");
397
48
 
398
49
  // src/formula-node.ts
@@ -722,7 +373,7 @@ function ensureFormulaXModalStyles(doc = document) {
722
373
  style.textContent = formulaXModalStyles;
723
374
  doc.head.appendChild(style);
724
375
  }
725
- function mountFormulaXKityEditor(root, options = {}) {
376
+ function mountFormulaXEditor(root, options = {}) {
726
377
  let destroyed = false;
727
378
  let latestLatex = options.initialLatex ?? "";
728
379
  let handle = null;
@@ -775,11 +426,11 @@ function mountFormulaXKityEditor(root, options = {}) {
775
426
  const latex = await getCurrentLatex();
776
427
  try {
777
428
  return {
778
- ...(0, import_core2.createEmptyState)(),
779
- doc: (0, import_core2.parseLatex)(latex)
429
+ ...(0, import_core.createEmptyState)(),
430
+ doc: (0, import_core.parseLatex)(latex)
780
431
  };
781
432
  } catch {
782
- return (0, import_core2.createEmptyState)();
433
+ return (0, import_core.createEmptyState)();
783
434
  }
784
435
  },
785
436
  async getRenderHtml() {
@@ -1132,103 +783,28 @@ function mergeInlineStyles2(...values) {
1132
783
  return values.flatMap((value) => value?.split(";") ?? []).map((value) => value.trim()).filter(Boolean).join("; ");
1133
784
  }
1134
785
 
1135
- // src/i18n.ts
1136
- var translations = {
1137
- en: {
1138
- equation: "Equation",
1139
- structures: "Structures",
1140
- symbols: "Symbols",
1141
- matrices: "Matrices",
1142
- templates: "Templates",
1143
- insert: "Insert",
1144
- greek: "Greek",
1145
- operators: "Operators",
1146
- relations: "Relations",
1147
- fraction: "Fraction",
1148
- superscript: "Superscript",
1149
- subscript: "Subscript",
1150
- squareRoot: "Square Root",
1151
- parentheses: "Parentheses",
1152
- plusMinus: "Plus Minus",
1153
- multiply: "Multiply",
1154
- divide: "Divide",
1155
- dot: "Dot",
1156
- union: "Union",
1157
- intersect: "Intersection",
1158
- lessOrEqual: "Less or Equal",
1159
- greaterOrEqual: "Greater or Equal",
1160
- notEqual: "Not Equal",
1161
- approximate: "Approximate",
1162
- infinity: "Infinity",
1163
- arrow: "Arrow",
1164
- limit: "Limit",
1165
- sine: "Sine",
1166
- logarithm: "Logarithm",
1167
- matrix: "Matrix",
1168
- summation: "Summation",
1169
- integral: "Integral",
1170
- placeholder: "WPS-inspired ribbon layout. Some tiles are placeholders for future SDK features."
1171
- },
1172
- zh: {
1173
- equation: "\u516C\u5F0F",
1174
- structures: "\u7ED3\u6784",
1175
- symbols: "\u7B26\u53F7",
1176
- matrices: "\u77E9\u9635",
1177
- templates: "\u6A21\u677F",
1178
- insert: "\u63D2\u5165",
1179
- greek: "\u5E0C\u814A\u5B57\u6BCD",
1180
- operators: "\u8FD0\u7B97\u7B26",
1181
- relations: "\u5173\u7CFB",
1182
- fraction: "\u5206\u6570",
1183
- superscript: "\u4E0A\u6807",
1184
- subscript: "\u4E0B\u6807",
1185
- squareRoot: "\u5E73\u65B9\u6839",
1186
- parentheses: "\u62EC\u53F7",
1187
- plusMinus: "\u52A0\u51CF",
1188
- multiply: "\u4E58",
1189
- divide: "\u9664",
1190
- dot: "\u70B9\u4E58",
1191
- union: "\u5E76\u96C6",
1192
- intersect: "\u4EA4\u96C6",
1193
- lessOrEqual: "\u5C0F\u4E8E\u7B49\u4E8E",
1194
- greaterOrEqual: "\u5927\u4E8E\u7B49\u4E8E",
1195
- notEqual: "\u4E0D\u7B49\u4E8E",
1196
- approximate: "\u7EA6\u7B49\u4E8E",
1197
- infinity: "\u65E0\u7A77",
1198
- arrow: "\u7BAD\u5934",
1199
- limit: "\u6781\u9650",
1200
- sine: "\u6B63\u5F26",
1201
- logarithm: "\u5BF9\u6570",
1202
- matrix: "\u77E9\u9635",
1203
- summation: "\u6C42\u548C",
1204
- integral: "\u79EF\u5206",
1205
- placeholder: "WPS \u98CE\u683C\u7684\u5DE5\u5177\u680F\u5E03\u5C40\u3002\u90E8\u5206\u6309\u94AE\u4ECD\u662F\u672A\u6765 SDK \u529F\u80FD\u7684\u5360\u4F4D\u9879\u3002"
1206
- }
1207
- };
1208
- function t(locale, key) {
1209
- return translations[locale][key] ?? translations.en[key];
1210
- }
786
+ // src/index.ts
787
+ var import_kity_runtime2 = require("@formulaxjs/kity-runtime");
1211
788
  // Annotate the CommonJS export names for ESM import in node:
1212
789
  0 && (module.exports = {
1213
790
  DEFAULT_FORMULA_ATTRIBUTE,
1214
791
  DEFAULT_FORMULA_CLASS,
1215
792
  FORMULA_FLAG_ATTRIBUTE,
1216
- FormulaEditor,
793
+ FormulaXEditor,
1217
794
  createFormulaElement,
1218
795
  createFormulaMarkup,
1219
- editorStyles,
796
+ createKityEditor,
1220
797
  ensureFormulaXModalStyles,
798
+ ensureKityRuntime,
1221
799
  escapeAttribute,
1222
800
  escapeHtml,
1223
801
  findFormulaElement,
1224
802
  formulaXModalStyles,
1225
803
  getFormulaLatexFromElement,
1226
804
  isFormulaElement,
1227
- mountFormulaXKityEditor,
1228
- renderInteractiveHtml,
805
+ mountFormulaXEditor,
806
+ mountKityEditor,
1229
807
  replaceFormulaElement,
1230
- serializeSvgForInsertion,
1231
- t,
1232
- translations
808
+ serializeSvgForInsertion
1233
809
  });
1234
810
  //# sourceMappingURL=index.cjs.map