@formulaxjs/editor 0.1.1 → 0.3.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/dist/index.cjs CHANGED
@@ -20,450 +20,178 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- DEFAULT_FORMULA_ATTRIBUTE: () => DEFAULT_FORMULA_ATTRIBUTE,
24
- DEFAULT_FORMULA_CLASS: () => DEFAULT_FORMULA_CLASS,
25
- FORMULA_FLAG_ATTRIBUTE: () => FORMULA_FLAG_ATTRIBUTE,
26
- FormulaEditor: () => FormulaEditor,
27
- createFormulaElement: () => createFormulaElement,
28
- createFormulaMarkup: () => createFormulaMarkup,
29
- editorStyles: () => editorStyles,
23
+ clearFormulaXPerfMarks: () => clearFormulaXPerfMarks,
30
24
  ensureFormulaXModalStyles: () => ensureFormulaXModalStyles,
31
- escapeAttribute: () => escapeAttribute,
32
- escapeHtml: () => escapeHtml,
33
- findFormulaElement: () => findFormulaElement,
34
25
  formulaXModalStyles: () => formulaXModalStyles,
35
- getFormulaLatexFromElement: () => getFormulaLatexFromElement,
36
- isFormulaElement: () => isFormulaElement,
37
- mountFormulaXKityEditor: () => mountFormulaXKityEditor,
38
- renderInteractiveHtml: () => renderInteractiveHtml,
39
- replaceFormulaElement: () => replaceFormulaElement,
40
- serializeSvgForInsertion: () => serializeSvgForInsertion,
41
- t: () => t,
42
- translations: () => translations
26
+ markFormulaXPerf: () => markFormulaXPerf,
27
+ measureFormulaXPerf: () => measureFormulaXPerf,
28
+ mountFormulaXEditor: () => mountFormulaXEditor,
29
+ preloadFormulaXEditor: () => preloadFormulaXEditor,
30
+ recordFormulaXPerfPoint: () => recordFormulaXPerfPoint,
31
+ renderFormulaXEditorLoadingState: () => renderFormulaXEditorLoadingState,
32
+ scheduleFormulaXEditorPreload: () => scheduleFormulaXEditorPreload,
33
+ waitForFormulaXAnimationFrame: () => waitForFormulaXAnimationFrame
43
34
  });
44
35
  module.exports = __toCommonJS(index_exports);
45
36
 
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
37
+ // src/formula-modal.ts
107
38
  var import_core = require("@formulaxjs/core");
39
+ var import_kity_runtime2 = require("@formulaxjs/kity-runtime");
40
+ var import_renderer = require("@formulaxjs/renderer");
41
+ var import_renderer_kity = require("@formulaxjs/renderer-kity");
108
42
 
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;
43
+ // src/perf.ts
44
+ var import_kity_runtime = require("@formulaxjs/kity-runtime");
45
+ function getPerfHost() {
46
+ return globalThis;
47
+ }
48
+ function getPerfState() {
49
+ const host = getPerfHost();
50
+ host.__FORMULAX_PERF_STATE__ ??= {
51
+ reportedMeasureCount: 0,
52
+ reportScheduled: false
53
+ };
54
+ return host.__FORMULAX_PERF_STATE__;
274
55
  }
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;
56
+ function hasPerfSupport() {
57
+ return typeof performance !== "undefined" && typeof performance.mark === "function" && typeof performance.measure === "function" && typeof performance.getEntriesByType === "function";
285
58
  }
286
- .fx-slot.is-active {
287
- animation: fx-blink 1s infinite;
59
+ function isPerfDebugEnabled() {
60
+ return getPerfHost().__FORMULAX_PERF__ === true;
288
61
  }
289
- .fx-slot:hover {
290
- background: #77c75b;
62
+ function schedulePerfReport() {
63
+ if (!hasPerfSupport() || !isPerfDebugEnabled()) {
64
+ return;
65
+ }
66
+ const state = getPerfState();
67
+ if (state.reportScheduled) {
68
+ return;
69
+ }
70
+ state.reportScheduled = true;
71
+ queueMicrotask(() => {
72
+ state.reportScheduled = false;
73
+ const entries = performance.getEntriesByType("measure").filter((entry) => entry.name.startsWith("fx:")).sort((left, right) => left.startTime - right.startTime);
74
+ const nextEntries = entries.slice(state.reportedMeasureCount);
75
+ state.reportedMeasureCount = entries.length;
76
+ if (!nextEntries.length) {
77
+ return;
78
+ }
79
+ console.table(nextEntries.map((entry) => ({
80
+ name: entry.name,
81
+ duration: Number(entry.duration.toFixed(2)),
82
+ startTime: Number(entry.startTime.toFixed(2))
83
+ })));
84
+ });
291
85
  }
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();
86
+ function markFormulaXPerf(name) {
87
+ if (!hasPerfSupport()) {
88
+ return null;
310
89
  }
311
- getState() {
312
- return structuredClone(this.state);
90
+ const markName = `${name}::${Date.now()}::${Math.random().toString(36).slice(2, 8)}`;
91
+ performance.mark(markName);
92
+ return markName;
93
+ }
94
+ function measureFormulaXPerf(name, startMark, endMark) {
95
+ if (!hasPerfSupport() || !startMark) {
96
+ return null;
313
97
  }
314
- getLocale() {
315
- return this.locale;
98
+ const resolvedEndMark = endMark ?? markFormulaXPerf(`${name}:end`);
99
+ if (!resolvedEndMark) {
100
+ return null;
316
101
  }
317
- setState(state) {
318
- this.state = structuredClone(state);
319
- this.render();
102
+ performance.measure(name, startMark, resolvedEndMark);
103
+ schedulePerfReport();
104
+ return resolvedEndMark;
105
+ }
106
+ function recordFormulaXPerfPoint(name) {
107
+ const markName = markFormulaXPerf(name);
108
+ if (!markName) {
109
+ return;
320
110
  }
321
- dispatch(command) {
322
- this.state = (0, import_core.applyCommand)(this.state, command);
323
- this.render();
324
- this.onChange?.(this.getState());
111
+ measureFormulaXPerf(name, markName, markName);
112
+ clearFormulaXPerfMarks(markName);
113
+ }
114
+ function clearFormulaXPerfMarks(...marks) {
115
+ if (!hasPerfSupport()) {
116
+ return;
325
117
  }
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);
118
+ for (const mark of marks) {
119
+ if (!mark) {
120
+ continue;
121
+ }
122
+ performance.clearMarks(mark);
332
123
  }
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
- });
124
+ }
125
+ async function preloadFormulaXEditor() {
126
+ await (0, import_kity_runtime.ensureKityRuntime)();
127
+ }
128
+ function scheduleFormulaXEditorPreload(mode, target) {
129
+ if (mode === false || typeof window === "undefined") {
130
+ return () => void 0;
377
131
  }
378
- moveSelection(path) {
379
- this.state = {
380
- ...this.state,
381
- selection: {
382
- anchor: [...path],
383
- focus: [...path]
132
+ let disposed = false;
133
+ let triggered = false;
134
+ const cleanupCallbacks = [];
135
+ const trigger = () => {
136
+ if (disposed || triggered) {
137
+ return;
138
+ }
139
+ triggered = true;
140
+ while (cleanupCallbacks.length) {
141
+ cleanupCallbacks.pop()?.();
142
+ }
143
+ void preloadFormulaXEditor();
144
+ };
145
+ if (mode === "idle") {
146
+ const host = getPerfHost();
147
+ if (typeof host.requestIdleCallback === "function") {
148
+ const handle = host.requestIdleCallback(() => {
149
+ trigger();
150
+ });
151
+ cleanupCallbacks.push(() => {
152
+ host.cancelIdleCallback?.(handle);
153
+ });
154
+ } else {
155
+ const handle = window.setTimeout(() => {
156
+ trigger();
157
+ }, 1);
158
+ cleanupCallbacks.push(() => {
159
+ window.clearTimeout(handle);
160
+ });
161
+ }
162
+ return () => {
163
+ disposed = true;
164
+ while (cleanupCallbacks.length) {
165
+ cleanupCallbacks.pop()?.();
384
166
  }
385
167
  };
386
- this.render();
387
- this.onChange?.(this.getState());
388
168
  }
389
- render() {
390
- this.root.innerHTML = renderInteractiveHtml(this.state.doc, this.state.selection.focus);
169
+ if (target && "addEventListener" in target && "removeEventListener" in target) {
170
+ const eventTarget = target;
171
+ const onActivate = () => {
172
+ trigger();
173
+ };
174
+ eventTarget.addEventListener("pointerenter", onActivate, { once: true, passive: true });
175
+ eventTarget.addEventListener("focusin", onActivate, { once: true });
176
+ cleanupCallbacks.push(() => {
177
+ eventTarget.removeEventListener("pointerenter", onActivate);
178
+ eventTarget.removeEventListener("focusin", onActivate);
179
+ });
391
180
  }
392
- };
393
-
394
- // src/formula-modal.ts
395
- var import_core2 = require("@formulaxjs/core");
396
- var import_kity_runtime = require("@formulaxjs/kity-runtime");
397
-
398
- // src/formula-node.ts
399
- var DEFAULT_FORMULA_ATTRIBUTE = "data-formulax-latex";
400
- var FORMULA_FLAG_ATTRIBUTE = "data-formulax";
401
- var DEFAULT_FORMULA_CLASS = "formulax-math";
402
- function escapeAttribute(value) {
403
- return value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
404
- }
405
- function escapeHtml(value) {
406
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
407
- }
408
- function createFormulaMarkup(latex, options = {}) {
409
- const attributeName = options.attributeName ?? DEFAULT_FORMULA_ATTRIBUTE;
410
- const className = options.className ?? DEFAULT_FORMULA_CLASS;
411
- const displayClass = options.displayMode ? `${className} ${className}--block` : className;
412
- const safeLatex = escapeAttribute(latex);
413
- const cursorStyle = options.cursorStyle?.trim() || "pointer";
414
- const extraAttributes = {
415
- ...options.extraAttributes ?? {},
416
- style: mergeInlineStyles(
417
- typeof options.extraAttributes?.style === "string" ? options.extraAttributes.style : "",
418
- cursorStyle ? `cursor: ${cursorStyle}` : ""
419
- )
181
+ return () => {
182
+ disposed = true;
183
+ while (cleanupCallbacks.length) {
184
+ cleanupCallbacks.pop()?.();
185
+ }
420
186
  };
421
- const serializedAttributes = Object.entries(extraAttributes).filter(([, value]) => value !== null && value !== void 0 && value !== false).map(([key, value]) => value === true ? key : `${key}="${escapeAttribute(String(value))}"`);
422
- return [
423
- "<span",
424
- ` class="${escapeAttribute(displayClass)}"`,
425
- ` ${FORMULA_FLAG_ATTRIBUTE}="true"`,
426
- ` ${attributeName}="${safeLatex}"`,
427
- ` data-latex="${safeLatex}"`,
428
- ' contenteditable="false"',
429
- ' role="button"',
430
- ' tabindex="0"',
431
- serializedAttributes.length ? ` ${serializedAttributes.join(" ")}` : "",
432
- ">",
433
- options.renderHtml ?? `<span class="${escapeAttribute(className)}__render">${escapeHtml(latex || "\\square")}</span>`,
434
- "</span>"
435
- ].join("");
436
- }
437
- function mergeInlineStyles(existingStyle, nextStyle) {
438
- const existing = existingStyle.trim().replace(/;+\s*$/, "");
439
- const next = nextStyle.trim().replace(/;+\s*$/, "");
440
- if (!existing) return next;
441
- if (!next) return existing;
442
- return `${existing}; ${next}`;
443
- }
444
- function createFormulaElement(ownerDocument, latex, options = {}) {
445
- const wrapper = ownerDocument.createElement("span");
446
- wrapper.innerHTML = createFormulaMarkup(latex, options);
447
- return wrapper.firstElementChild;
448
- }
449
- function replaceFormulaElement(target, latex, options = {}) {
450
- const next = createFormulaElement(target.ownerDocument ?? document, latex, options);
451
- if (!next) return null;
452
- target.replaceWith(next);
453
- return next;
454
- }
455
- function getFormulaLatexFromElement(element, attributeName = DEFAULT_FORMULA_ATTRIBUTE) {
456
- return element.getAttribute(attributeName) ?? element.getAttribute("data-latex") ?? "";
457
187
  }
458
- function isFormulaElement(node) {
459
- if (!node || typeof node !== "object") return false;
460
- const element = node;
461
- return typeof element.getAttribute === "function" && element.getAttribute(FORMULA_FLAG_ATTRIBUTE) === "true";
462
- }
463
- function findFormulaElement(node) {
464
- if (!node) return null;
465
- const element = node.nodeType === 1 ? node : node.parentElement;
466
- return element?.closest?.(`[${FORMULA_FLAG_ATTRIBUTE}="true"]`);
188
+ function waitForFormulaXAnimationFrame() {
189
+ if (typeof window === "undefined" || typeof window.requestAnimationFrame !== "function") {
190
+ return Promise.resolve();
191
+ }
192
+ return new Promise((resolve) => {
193
+ window.requestAnimationFrame(() => resolve());
194
+ });
467
195
  }
468
196
 
469
197
  // src/formula-modal.ts
@@ -604,8 +332,7 @@ var formulaXModalStyles = `
604
332
  .fx-formula-kity-host .kf-editor svg text,
605
333
  .fx-formula-kity-host .kf-editor-ui-area-item-text,
606
334
  .fx-formula-kity-host .kf-editor-ui-box-item-text,
607
- .fx-formula-kity-host .kf-editor-ui-box-item-val,
608
- .formulax-math__render {
335
+ .fx-formula-kity-host .kf-editor-ui-box-item-val {
609
336
  font-family: "KF AMS MAIN", "Cambria Math", "Latin Modern Math", "Times New Roman", serif !important;
610
337
  }
611
338
 
@@ -680,60 +407,35 @@ var formulaXModalStyles = `
680
407
  background: #2563eb;
681
408
  color: #fff;
682
409
  }
683
-
684
- .formulax-math {
685
- display: inline-flex;
686
- align-items: center;
687
- vertical-align: middle;
688
- line-height: 1;
689
- padding: 0 2px;
690
- margin: 0 1px;
691
- border-radius: 3px;
692
- background: transparent;
693
- cursor: pointer;
694
- user-select: none;
695
- }
696
-
697
- .formulax-math:hover {
698
- outline: 1px solid rgba(37, 99, 235, 0.35);
699
- background: rgba(37, 99, 235, 0.06);
700
- }
701
-
702
- .formulax-math__svg {
703
- display: inline-block;
704
- flex: 0 0 auto;
705
- max-width: 100%;
706
- vertical-align: -0.35em;
707
- pointer-events: none;
708
- }
709
-
710
- .formulax-math__image {
711
- display: inline-block;
712
- max-width: 100%;
713
- height: auto;
714
- vertical-align: middle;
715
- pointer-events: none;
716
- }
717
410
  `;
718
411
  function ensureFormulaXModalStyles(doc = document) {
412
+ (0, import_renderer.ensureFormulaXBaseStyles)(doc);
719
413
  if (doc.getElementById(STYLE_ID)) return;
720
414
  const style = doc.createElement("style");
721
415
  style.id = STYLE_ID;
722
416
  style.textContent = formulaXModalStyles;
723
417
  doc.head.appendChild(style);
724
418
  }
725
- function mountFormulaXKityEditor(root, options = {}) {
726
- let destroyed = false;
727
- let latestLatex = options.initialLatex ?? "";
728
- let handle = null;
729
- const initialLatex = latestLatex.trim() ? latestLatex : EMPTY_FORMULA_PLACEHOLDER;
419
+ function renderFormulaXEditorLoadingState(root) {
730
420
  root.classList.add("fx-formula-kity-host");
731
421
  root.innerHTML = `
732
422
  <div class="fx-formula-editor-loading" role="status" aria-live="polite">
733
423
  Loading FormulaX editor...
734
424
  </div>
735
425
  `;
736
- const readyPromise = (0, import_kity_runtime.mountKityEditor)(root, {
426
+ }
427
+ function mountFormulaXEditor(root, options = {}) {
428
+ recordFormulaXPerfPoint("fx:formula-editor:mount:start");
429
+ const mountStart = markFormulaXPerf("fx:formula-editor:mount:start:scope");
430
+ let destroyed = false;
431
+ let latestLatex = options.initialLatex ?? "";
432
+ let handle = null;
433
+ const initialLatex = latestLatex.trim() ? latestLatex : EMPTY_FORMULA_PLACEHOLDER;
434
+ renderFormulaXEditorLoadingState(root);
435
+ const loadingVisibleMark = markFormulaXPerf("fx:formula-editor:loading-visible");
436
+ measureFormulaXPerf("fx:formula-editor:loading-visible", mountStart, loadingVisibleMark);
437
+ clearFormulaXPerfMarks(loadingVisibleMark);
438
+ const readyPromise = (0, import_kity_runtime2.mountKityEditor)(root, {
737
439
  initialLatex,
738
440
  height: options.height ?? "100%",
739
441
  autofocus: options.autofocus ?? true,
@@ -746,6 +448,9 @@ function mountFormulaXKityEditor(root, options = {}) {
746
448
  nextHandle.destroy();
747
449
  throw new Error("FormulaX editor mount cancelled");
748
450
  }
451
+ const readyMark = markFormulaXPerf("fx:kity-editor:ready");
452
+ measureFormulaXPerf("fx:kity-editor:ready", mountStart, readyMark);
453
+ clearFormulaXPerfMarks(readyMark);
749
454
  handle = nextHandle;
750
455
  return nextHandle;
751
456
  }).catch((error) => {
@@ -754,11 +459,13 @@ function mountFormulaXKityEditor(root, options = {}) {
754
459
  root.innerHTML = `
755
460
  <div class="fx-formula-editor-error">
756
461
  Failed to load FormulaX editor.
757
- <pre>${escapeHtml(error instanceof Error ? error.message : String(error))}</pre>
462
+ <pre>${(0, import_renderer.escapeHtml)(error instanceof Error ? error.message : String(error))}</pre>
758
463
  </div>
759
464
  `;
760
465
  }
761
466
  throw error;
467
+ }).finally(() => {
468
+ clearFormulaXPerfMarks(mountStart);
762
469
  });
763
470
  const getCurrentLatex = async () => {
764
471
  const readyHandle = handle ?? await readyPromise;
@@ -775,17 +482,17 @@ function mountFormulaXKityEditor(root, options = {}) {
775
482
  const latex = await getCurrentLatex();
776
483
  try {
777
484
  return {
778
- ...(0, import_core2.createEmptyState)(),
779
- doc: (0, import_core2.parseLatex)(latex)
485
+ ...(0, import_core.createEmptyState)(),
486
+ doc: (0, import_core.parseLatex)(latex)
780
487
  };
781
488
  } catch {
782
- return (0, import_core2.createEmptyState)();
489
+ return (0, import_core.createEmptyState)();
783
490
  }
784
491
  },
785
492
  async getRenderHtml() {
786
493
  await readyPromise;
787
- await waitForFormulaSvgLayout(root);
788
- return renderCurrentFormulaAsSvgHtml(root);
494
+ await (0, import_renderer_kity.waitForKityFormulaSvgLayout)(root);
495
+ return (0, import_renderer_kity.serializeKityFormulaFromRoot)(root);
789
496
  },
790
497
  destroy() {
791
498
  if (destroyed) return;
@@ -835,400 +542,18 @@ async function tryReadLatexFromKityHandle(handle) {
835
542
  }
836
543
  return null;
837
544
  }
838
- function renderCurrentFormulaAsSvgHtml(root) {
839
- const svg = findFormulaSvg(root);
840
- if (!svg) {
841
- return "";
842
- }
843
- return serializeSvgForInsertion(svg);
844
- }
845
- async function waitForFormulaSvgLayout(root) {
846
- const doc = root.ownerDocument ?? document;
847
- const view = doc.defaultView ?? window;
848
- await waitForDocumentFonts(doc);
849
- let previous = readRenderedFormulaBox(root);
850
- for (let attempt = 0; attempt < 4; attempt += 1) {
851
- await waitForAnimationFrame(view);
852
- const current = readRenderedFormulaBox(root);
853
- if (previous && current && areSvgBoxesClose(previous, current)) {
854
- return;
855
- }
856
- previous = current;
857
- }
858
- }
859
- function findFormulaSvg(root) {
860
- return root.querySelector(
861
- ".kf-editor-edit-area svg, .kf-editor-canvas-container svg, svg"
862
- );
863
- }
864
- function readRenderedFormulaBox(root) {
865
- const svg = findFormulaSvg(root);
866
- if (!svg) {
867
- return null;
868
- }
869
- return getInlineSvgContent(svg)?.box ?? readSvgBox(svg);
870
- }
871
- function areSvgBoxesClose(left, right) {
872
- return Math.abs(left.x - right.x) < 0.01 && Math.abs(left.y - right.y) < 0.01 && Math.abs(left.width - right.width) < 0.01 && Math.abs(left.height - right.height) < 0.01;
873
- }
874
- async function waitForDocumentFonts(doc) {
875
- if (!doc.fonts?.ready) {
876
- return;
877
- }
878
- try {
879
- await doc.fonts.ready;
880
- } catch {
881
- }
882
- }
883
- function waitForAnimationFrame(view) {
884
- return new Promise((resolve) => {
885
- view.requestAnimationFrame(() => resolve());
886
- });
887
- }
888
- function serializeSvgForInsertion(svg) {
889
- const content = getInlineSvgContent(svg);
890
- const inlineViewport = content ? createInlineSvgViewport(content.box) : null;
891
- const clone = content && inlineViewport ? createInlineSvgClone(svg, content, inlineViewport) : svg.cloneNode(true);
892
- uniquifySvgIds(clone);
893
- sizeSvgForInlineDisplay(clone, svg, inlineViewport);
894
- clone.removeAttribute("id");
895
- clone.removeAttribute("xmlns");
896
- clone.removeAttribute("xmlns:xlink");
897
- clone.setAttribute("class", mergeClassNames(clone.getAttribute("class"), "formulax-math__svg"));
898
- clone.setAttribute("focusable", "false");
899
- clone.setAttribute("aria-hidden", "true");
900
- clone.setAttribute("preserveAspectRatio", clone.getAttribute("preserveAspectRatio") || "xMinYMin meet");
901
- return new XMLSerializer().serializeToString(clone);
902
- }
903
- function getInlineSvgContent(svg) {
904
- const candidates = [
905
- '[data-root="true"] > g[data-type="kf-editor-exp-content-box"]',
906
- 'g[data-type="kf-editor-exp-content-box"]',
907
- 'g[data-type="kf-container"]',
908
- "svg > g, g"
909
- ];
910
- for (const selector of candidates) {
911
- const content = svg.querySelector(selector);
912
- const rootSpace = content ? readSvgBoxInRootSpace(content) : null;
913
- if (content && rootSpace) {
914
- return {
915
- root: content,
916
- box: rootSpace.box,
917
- matrix: rootSpace.matrix
918
- };
919
- }
920
- }
921
- return null;
922
- }
923
- function readSvgBoxInRootSpace(element) {
924
- const box = readSvgBox(element);
925
- const matrix = getSvgRootSpaceMatrix(element);
926
- if (!box || !matrix) {
927
- return null;
928
- }
929
- const points = [
930
- { x: box.x, y: box.y },
931
- { x: box.x + box.width, y: box.y },
932
- { x: box.x, y: box.y + box.height },
933
- { x: box.x + box.width, y: box.y + box.height }
934
- ].map((point) => ({
935
- x: matrix.a * point.x + matrix.c * point.y + matrix.e,
936
- y: matrix.b * point.x + matrix.d * point.y + matrix.f
937
- }));
938
- const xs = points.map((point) => point.x);
939
- const ys = points.map((point) => point.y);
940
- const x = Math.min(...xs);
941
- const y = Math.min(...ys);
942
- const width = Math.max(...xs) - x;
943
- const height = Math.max(...ys) - y;
944
- if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
945
- return null;
946
- }
947
- return {
948
- box: { x, y, width, height },
949
- matrix
950
- };
951
- }
952
- function getSvgRootSpaceMatrix(element) {
953
- const elementMatrix = typeof element.getCTM === "function" ? element.getCTM() : null;
954
- const rootMatrix = typeof element.ownerSVGElement?.getCTM === "function" ? element.ownerSVGElement.getCTM() : null;
955
- if (!elementMatrix) {
956
- return null;
957
- }
958
- return rootMatrix ? multiplySvgMatrices(invertSvgMatrix(rootMatrix), elementMatrix) : toSvgMatrixLike(elementMatrix);
959
- }
960
- function invertSvgMatrix(matrix) {
961
- const determinant = matrix.a * matrix.d - matrix.b * matrix.c;
962
- if (!Number.isFinite(determinant) || determinant === 0) {
963
- return {
964
- a: 1,
965
- b: 0,
966
- c: 0,
967
- d: 1,
968
- e: 0,
969
- f: 0
970
- };
971
- }
972
- return {
973
- a: matrix.d / determinant,
974
- b: -matrix.b / determinant,
975
- c: -matrix.c / determinant,
976
- d: matrix.a / determinant,
977
- e: (matrix.c * matrix.f - matrix.d * matrix.e) / determinant,
978
- f: (matrix.b * matrix.e - matrix.a * matrix.f) / determinant
979
- };
980
- }
981
- function multiplySvgMatrices(left, right) {
982
- return {
983
- a: left.a * right.a + left.c * right.b,
984
- b: left.b * right.a + left.d * right.b,
985
- c: left.a * right.c + left.c * right.d,
986
- d: left.b * right.c + left.d * right.d,
987
- e: left.a * right.e + left.c * right.f + left.e,
988
- f: left.b * right.e + left.d * right.f + left.f
989
- };
990
- }
991
- function toSvgMatrixLike(matrix) {
992
- return {
993
- a: matrix.a,
994
- b: matrix.b,
995
- c: matrix.c,
996
- d: matrix.d,
997
- e: matrix.e,
998
- f: matrix.f
999
- };
1000
- }
1001
- function readSvgBox(element) {
1002
- if (typeof element.getBBox !== "function") {
1003
- return null;
1004
- }
1005
- try {
1006
- const box = element.getBBox();
1007
- if (!Number.isFinite(box.width) || !Number.isFinite(box.height) || box.width <= 0 || box.height <= 0) {
1008
- return null;
1009
- }
1010
- return {
1011
- x: box.x,
1012
- y: box.y,
1013
- width: box.width,
1014
- height: box.height
1015
- };
1016
- } catch {
1017
- return null;
1018
- }
1019
- }
1020
- function createInlineSvgViewport(contentBox) {
1021
- const edgePadding = Math.max(0.5, Math.min(contentBox.width, contentBox.height) * 6e-3);
1022
- const inset = edgePadding / 2;
1023
- return {
1024
- x: contentBox.x - inset,
1025
- y: contentBox.y - inset,
1026
- width: contentBox.width + edgePadding,
1027
- height: contentBox.height + edgePadding
1028
- };
1029
- }
1030
- function createInlineSvgClone(source, content, viewport) {
1031
- const clone = source.cloneNode(false);
1032
- const ownerDocument = source.ownerDocument;
1033
- copySvgRootAttributes(source, clone);
1034
- clone.setAttribute(
1035
- "viewBox",
1036
- `0 0 ${roundLength(viewport.width)} ${roundLength(viewport.height)}`
1037
- );
1038
- Array.from(source.children).forEach((child) => {
1039
- if (child.tagName.toLowerCase() === "defs") {
1040
- clone.appendChild(child.cloneNode(true));
1041
- }
1042
- });
1043
- const wrapper = ownerDocument.createElementNS("http://www.w3.org/2000/svg", "g");
1044
- wrapper.setAttribute(
1045
- "transform",
1046
- `translate(${roundLength(-viewport.x)} ${roundLength(-viewport.y)})`
1047
- );
1048
- const flattened = ownerDocument.createElementNS("http://www.w3.org/2000/svg", "g");
1049
- flattened.setAttribute(
1050
- "transform",
1051
- `matrix(${roundLength(content.matrix.a)} ${roundLength(content.matrix.b)} ${roundLength(content.matrix.c)} ${roundLength(content.matrix.d)} ${roundLength(content.matrix.e)} ${roundLength(content.matrix.f)})`
1052
- );
1053
- flattened.appendChild(content.root.cloneNode(true));
1054
- wrapper.appendChild(flattened);
1055
- clone.appendChild(wrapper);
1056
- return clone;
1057
- }
1058
- function copySvgRootAttributes(source, target) {
1059
- const excluded = /* @__PURE__ */ new Set([
1060
- "id",
1061
- "width",
1062
- "height",
1063
- "viewBox",
1064
- "class",
1065
- "focusable",
1066
- "aria-hidden",
1067
- "xmlns",
1068
- "xmlns:xlink"
1069
- ]);
1070
- Array.from(source.attributes).forEach((attribute) => {
1071
- if (excluded.has(attribute.name)) return;
1072
- target.setAttribute(attribute.name, attribute.value);
1073
- });
1074
- }
1075
- function sizeSvgForInlineDisplay(clone, source, viewport) {
1076
- const viewBox = clone.viewBox?.baseVal;
1077
- const rect = source.getBoundingClientRect();
1078
- const width = viewport?.width || viewBox?.width || rect.width || Number(clone.getAttribute("width")) || 1;
1079
- const height = viewport?.height || viewBox?.height || rect.height || Number(clone.getAttribute("height")) || 1;
1080
- const ratio = Math.max(0.1, width / Math.max(1, height));
1081
- const inlineHeightEm = 0.875;
1082
- const inlineWidthEm = Math.min(40, Math.max(0.75, ratio * inlineHeightEm));
1083
- clone.setAttribute("width", roundLength(width));
1084
- clone.setAttribute("height", roundLength(height));
1085
- clone.setAttribute(
1086
- "style",
1087
- mergeInlineStyles2(
1088
- clone.getAttribute("style"),
1089
- `width:${roundLength(inlineWidthEm)}em`,
1090
- `height:${inlineHeightEm}em`
1091
- )
1092
- );
1093
- }
1094
- function roundLength(value) {
1095
- return String(Math.round(value * 1e3) / 1e3);
1096
- }
1097
- function uniquifySvgIds(svg) {
1098
- const idMap = /* @__PURE__ */ new Map();
1099
- const prefix = `fx-${randomIdPrefix()}-`;
1100
- const elementsWithId = svg.querySelectorAll("[id]");
1101
- elementsWithId.forEach((element) => {
1102
- const id = element.getAttribute("id");
1103
- if (!id) return;
1104
- const nextId = `${prefix}${id}`;
1105
- idMap.set(id, nextId);
1106
- element.setAttribute("id", nextId);
1107
- });
1108
- if (!idMap.size) return;
1109
- svg.querySelectorAll("*").forEach((element) => {
1110
- Array.from(element.attributes).forEach((attribute) => {
1111
- const nextValue = rewriteSvgReferences(attribute.value, idMap);
1112
- if (nextValue !== attribute.value) {
1113
- element.setAttribute(attribute.name, nextValue);
1114
- }
1115
- });
1116
- });
1117
- }
1118
- function randomIdPrefix() {
1119
- return Math.random().toString(36).slice(2, 5).padEnd(3, "0");
1120
- }
1121
- function rewriteSvgReferences(value, idMap) {
1122
- let nextValue = value;
1123
- idMap.forEach((nextId, id) => {
1124
- nextValue = nextValue.replaceAll(`#${id}`, `#${nextId}`).replaceAll(`url(${id})`, `url(${nextId})`).replaceAll(`url(#${id})`, `url(#${nextId})`);
1125
- });
1126
- return nextValue;
1127
- }
1128
- function mergeClassNames(...values) {
1129
- return values.flatMap((value) => value?.split(/\s+/) ?? []).filter(Boolean).filter((value, index, list) => list.indexOf(value) === index).join(" ");
1130
- }
1131
- function mergeInlineStyles2(...values) {
1132
- return values.flatMap((value) => value?.split(";") ?? []).map((value) => value.trim()).filter(Boolean).join("; ");
1133
- }
1134
-
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
- }
1211
545
  // Annotate the CommonJS export names for ESM import in node:
1212
546
  0 && (module.exports = {
1213
- DEFAULT_FORMULA_ATTRIBUTE,
1214
- DEFAULT_FORMULA_CLASS,
1215
- FORMULA_FLAG_ATTRIBUTE,
1216
- FormulaEditor,
1217
- createFormulaElement,
1218
- createFormulaMarkup,
1219
- editorStyles,
547
+ clearFormulaXPerfMarks,
1220
548
  ensureFormulaXModalStyles,
1221
- escapeAttribute,
1222
- escapeHtml,
1223
- findFormulaElement,
1224
549
  formulaXModalStyles,
1225
- getFormulaLatexFromElement,
1226
- isFormulaElement,
1227
- mountFormulaXKityEditor,
1228
- renderInteractiveHtml,
1229
- replaceFormulaElement,
1230
- serializeSvgForInsertion,
1231
- t,
1232
- translations
550
+ markFormulaXPerf,
551
+ measureFormulaXPerf,
552
+ mountFormulaXEditor,
553
+ preloadFormulaXEditor,
554
+ recordFormulaXPerfPoint,
555
+ renderFormulaXEditorLoadingState,
556
+ scheduleFormulaXEditorPreload,
557
+ waitForFormulaXAnimationFrame
1233
558
  });
1234
559
  //# sourceMappingURL=index.cjs.map