@barocss/math-editor 0.4.0 → 0.5.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.
Files changed (49) hide show
  1. package/API-SESSION.md +10 -0
  2. package/API-WEB-COMPONENT.md +2 -0
  3. package/CHANGELOG.md +61 -0
  4. package/EDITING-SCENARIOS.md +174 -0
  5. package/IMPLEMENTATION.md +95 -0
  6. package/LATEX-GUIDE.md +22 -0
  7. package/LATEX-MODEL.md +12 -0
  8. package/LATEX-SCOPE.md +10 -1
  9. package/README.md +73 -19
  10. package/RELEASING.md +81 -10
  11. package/RENDERING-TESTS.md +79 -0
  12. package/ROADMAP.md +132 -5
  13. package/VALIDATION.md +331 -0
  14. package/dist/context-tools.d.ts +21 -0
  15. package/dist/context-tools.js +37 -0
  16. package/dist/dom/context-keyboard.d.ts +2 -0
  17. package/dist/dom/context-keyboard.js +22 -0
  18. package/dist/dom/menu-position.d.ts +3 -0
  19. package/dist/dom/menu-position.js +45 -1
  20. package/dist/dom.d.ts +2 -0
  21. package/dist/dom.js +187 -38
  22. package/dist/latex.js +11 -0
  23. package/dist/locales/en.js +13 -1
  24. package/dist/locales/en.json +13 -1
  25. package/dist/locales/ko.js +13 -1
  26. package/dist/locales/ko.json +13 -1
  27. package/dist/math-editor.d.ts +3 -1
  28. package/dist/math-editor.js +394 -280
  29. package/dist/math-layout.d.ts +12 -0
  30. package/dist/math-layout.js +59 -0
  31. package/dist/math-spacing.d.ts +13 -0
  32. package/dist/math-spacing.js +87 -0
  33. package/dist/model.d.ts +4 -0
  34. package/dist/model.js +49 -4
  35. package/dist/root-transform.d.ts +19 -0
  36. package/dist/root-transform.js +69 -0
  37. package/dist/suggestions.d.ts +9 -0
  38. package/dist/suggestions.js +53 -0
  39. package/dist/web-component.js +9 -1
  40. package/package.json +11 -3
  41. package/src/fonts/KaTeX_Math-Italic.woff2 +0 -0
  42. package/src/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  43. package/src/fonts/README.md +18 -0
  44. package/src/shapes/README.md +21 -0
  45. package/src/shapes/parenthesis-bottom.svg +1 -0
  46. package/src/shapes/parenthesis-top.svg +1 -0
  47. package/src/shapes/parenthesis.svg +1 -0
  48. package/src/shapes/radical.svg +1 -0
  49. package/src/style.css +695 -44
package/VALIDATION.md CHANGED
@@ -1,5 +1,105 @@
1
1
  # Validation report
2
2
 
3
+ ## Inline fence transformation suggestions — workspace, 2026-09-09
4
+
5
+ Core: **584 tests in 36 files passed**. Core type checking and the main site/docs
6
+ build passed. `fence-suggestions-check.js` passed in Chromium for rich React and
7
+ all nine host demos, including menu reopening, keyboard acceptance, cancellation,
8
+ retained caret, continued typing and separate Undo entries. Editor.js is a block
9
+ host; this run does not claim an inline placement for that adapter.
10
+
11
+ `fence-context-check.js` passed all eight bracket choices through the optional
12
+ footer in the same ten editor views. `fence-inline-options-check.js` passed in a
13
+ native inline iframe with toolbar and footer disabled, including nearest nested
14
+ fence targeting, ko/en switching, unchanged document on locale changes, retained
15
+ input focus/caret, continued typing and teardown.
16
+
17
+ Existing root conversion suggestions were rerun in React and Quill and passed.
18
+ `fence-rendering-check.js` verifies the exported source after changing a bracket
19
+ around a fraction. The editor and KaTeX screenshots were visually inspected:
20
+ `output/playwright/fence-transform-editor.png` and
21
+ `output/playwright/fence-transform-katex.png`. This uses the existing delimiter
22
+ renderer and does not claim pixel-identical layout.
23
+
24
+ See EDIT-029 through EDIT-032 for acceptance criteria. This is local Chromium
25
+ workspace evidence, not a release, real OS IME or cross-browser certification.
26
+
27
+ ## Contextual radical tools — workspace, 2026-09-09
28
+
29
+ Core: **570 tests passed in 35 files**. Type checking and the main documentation/demo build passed.
30
+
31
+ `context-tools-check.js` passed in local Chromium for rich React and all nine
32
+ host examples: Tiptap, ProseMirror, Lexical, Editor.js, TinyMCE, CKEditor, Quill,
33
+ Slate and Gutenberg. It covers query-independent discovery, retained tools after
34
+ suggestion dismissal, F6/Escape focus, conversion, selected index editing, a
35
+ non-square-index explanation, radicand retention, Undo, leaving a radical and
36
+ hiding tools during model range selection.
37
+
38
+ `context-tools-options-check.js` passed for nearest nested targeting, native DOM
39
+ inside an iframe, owner-document keyboard focus, dynamic `contextTools`, en/ko
40
+ labels, synthetic composition guards, outside blur, teardown, and dynamic Web
41
+ Component `context-tools` attributes. Synthetic events do not certify OS IME.
42
+
43
+ The footer was visually inspected separately from the suggestion popup. Evidence
44
+ is in `output/playwright/context-tools-footer.png` and the two browser fixtures.
45
+ EDIT-021 through EDIT-028 record acceptance criteria and remaining checks: native
46
+ Tab order, document equality across locale changes, individual framework-wrapper
47
+ option smoke tests, and broader browsers/platforms. The new footer currently
48
+ covers square/indexed roots. Other structure families remain roadmap work.
49
+ These are workspace-source checks; no package or site was published.
50
+
51
+ ## Fractional scripts and empty-slot deletion — workspace, 2026-09-09
52
+
53
+ Core: 568 tests passed in 35 files; type checking and formatting passed. The
54
+ main documentation/demo build passed. `empty-slot-deletion-check.js` passed
55
+ 16 real-key checks across React/native DOM: Delete and Backspace from empty
56
+ root, fraction, exponent and root-index slots, retained content, focus and Undo.
57
+ Grid cells and populated rows are protected by the added core tests.
58
+
59
+ `rendering-regression-check.js` passed 12 Chromium comparisons: three formulas,
60
+ two base sizes and two renderers. Fraction terms matched KaTeX font sizes;
61
+ vertical offset differences were at most 0.109em against a 0.25em threshold.
62
+ Horizontal gap, active-input size and focus assertions passed. Matching-size
63
+ editor/KaTeX screenshots were visually inspected for the indexed-root power
64
+ and simple fractional power. Full pixel identity is not claimed.
65
+
66
+ Local evidence: `output/playwright/math-rendering-regression-evidence.json`
67
+ records metrics and source SHA-256 values. Screenshot pairs use the paths in
68
+ [Rendering checks](RENDERING-TESTS.md). This is workspace-source evidence, not
69
+ packed-release, all-host, cross-browser or full notation certification.
70
+
71
+ See [Editing scenarios](EDITING-SCENARIOS.md) for stable scenario IDs, acceptance criteria, coverage gaps and per-run reporting.
72
+
73
+
74
+ ## Radical conversion suggestions — workspace, 2026-09-09
75
+
76
+ Core tests: 558 passed in 34 files; type checking passed. Ten added cases cover
77
+ nested radicands, retained node IDs, selected index `2`, safe reverse conversion,
78
+ non-square and structured indices, nearest-root targeting, stale targets, empty
79
+ slots, localization, literal text and Undo/Redo.
80
+
81
+ `root-transform-check.js` passed in Chromium for rich React and Quill's native
82
+ DOM field. It checks pointer and arrow/Enter acceptance, selected index editing,
83
+ radicand preservation, reverse conversion, and rejection of index `3` conversion.
84
+ The generated LaTeX and KaTeX preview match; editor/KaTeX radical screenshots
85
+ were inspected. This does not certify every browser or every host adapter.
86
+
87
+ ## Selection suggestion navigation — workspace, 2026-09-09
88
+
89
+ `apps/math-integrations/tests/selection-suggestions-check.js` passed in Chromium
90
+ for rich React and all nine host integrations. The check covers Shift+arrow
91
+ ranges, Up/Down navigation, the Alt+Down alias, Enter wrapping the selected
92
+ characters, retained input focus after wrapping, and native mouse-drag selection.
93
+ No page errors were reported. Core tests: 548 passed; core type check passed.
94
+ `selection-collapse-check.js` also passed for React horizontal caret recovery,
95
+ fraction boundaries and resumed typing. `tinymce-inline-check.js` passed for
96
+ inline and classic iframe hosts, including vertical caret recovery after Escape
97
+ dismisses the wrapping menu, Apply/Cancel, serialization and Undo/Redo.
98
+
99
+ A visible wrapping menu now owns vertical arrows. Left/Right still collapses
100
+ a model selection to its ordered edge. This replaces the Alt-only menu
101
+ navigation policy in the historical selection-collapse record below.
102
+
3
103
  ## Styling and themes — workspace, 2026-09-09
4
104
 
5
105
  Release browser regression: all 385 existing Chromium cases passed across the
@@ -367,3 +467,234 @@ Core unit regression: **548 tests**. Shared integrations: **52 tests**. Core, in
367
467
  - Quill composition and typing regression fixtures pass, including read-only recovery, host history and matrix-cell entry.
368
468
 
369
469
  This is local Chromium evidence. OS IME, Safari/Firefox, installed WordPress and cross-application system clipboard checks were not repeated. No new mathematical grammar or numerical behavior is introduced. Release artifacts prepared before these changes require regeneration; nothing was published by this work.
470
+
471
+
472
+ ## EDIT-019 continuous editing — workspace, 2026-09-10
473
+
474
+ - Run: `2026-09-09T17-47-53-597Z`; source fingerprint remained stable.
475
+ - Evidence: [report](../../output/playwright/editing-scenarios/2026-09-09T17-47-53-597Z/REPORT.md), [checkpoints and source hashes](../../output/playwright/editing-scenarios/2026-09-09T17-47-53-597Z/report.json).
476
+ - Command: `pnpm --filter @barocss/math-editor test:editing`.
477
+ - Environment: Chromium 152 / macOS, English locale, workspace source.
478
+ - **React standalone block: PASS, 28 checkpoints. Quill in-place block: PASS, 29 checkpoints.**
479
+ - Real key input creates `fraction(indexedRoot(3, ab), 2) + matrix(1, 2; 3, 4)`;
480
+ an explicit tree assertion checks content and nesting. The last cell becomes
481
+ `456789` in a second uninterrupted typing chain.
482
+ - Both surfaces pass two five-step Undo/Redo chains, retained typing focus,
483
+ empty-root Delete/Undo/Redo and unchanged retained content. Each first history
484
+ step is compared with its recorded structural checkpoint.
485
+ - Quill draft operations leave host data unchanged. Apply is one host Undo step;
486
+ Redo restores it. Save, page reload, Restore and reopen preserve the Delta and
487
+ formula structure. The restored matrix remains editable; Cancel preserves the
488
+ saved document. React has no host persistence lifecycle, so that part is N/A.
489
+ - The runner fails on either scenario failure or a changed source fingerprint.
490
+ It stores the executed fixture, failure screenshots, diagnostics and final data.
491
+ - No runtime library changes were required. Earlier fixture failures exposed OS
492
+ `End` assumptions and selection of the wrong saved block; the final fixture
493
+ verifies caret offsets with arrows and reopens the uniquely identified formula.
494
+ - Not covered: other hosts, Quill inline mode, real OS IME/clipboard,
495
+ Firefox/WebKit, touch, accessibility and performance budgets. No release-wide
496
+ pass or new rendering pass is claimed.
497
+
498
+
499
+ ## EDIT-019 Quill inline extension — workspace, 2026-09-10
500
+
501
+ - Run: `2026-09-09T22-30-59-017Z`; source fingerprint remained stable.
502
+ - [Report](../../output/playwright/editing-scenarios/2026-09-09T22-30-59-017Z/REPORT.md) and
503
+ [checkpoints](../../output/playwright/editing-scenarios/2026-09-09T22-30-59-017Z/report.json).
504
+ - **React block: PASS (28 checkpoints); Quill block: PASS (29); Quill inline: PASS (30).**
505
+ - The unchanged continuous editing chain runs in all three targets. Renderer and
506
+ mode are recorded separately, including artifact names and the failure summary.
507
+ - Quill inline commits with Enter from the outer boundary. No extra formula line
508
+ or host paragraph appears; prose is unchanged by math editing.
509
+ - Apply/host Undo/Redo, Save/reload/Restore, re-edit and Cancel preserve the
510
+ inline formula. A later Right-arrow exit returns focus to Quill; typing `q`
511
+ inserts exactly one character immediately after that formula. Host Undo
512
+ restores the committed Delta, including formula data.
513
+ - Runtime library code was unchanged. The runner and scenario documentation were
514
+ extended; no npm publication or new rendering audit was required.
515
+ - Other hosts, standalone inline adapters, OS IME/clipboard, Firefox/WebKit,
516
+ touch, accessibility and performance remain outside this run.
517
+
518
+
519
+ ## EDIT-019 Tiptap and ProseMirror — workspace, 2026-09-10
520
+
521
+ - Run: `2026-09-09T22-39-51-097Z`, stable source fingerprint.
522
+ - [Report](../../output/playwright/editing-scenarios/2026-09-09T22-39-51-097Z/REPORT.md) and
523
+ [checkpoints and hashes](../../output/playwright/editing-scenarios/2026-09-09T22-39-51-097Z/report.json).
524
+ - **7 targets PASS / 205 checkpoints:** React block (28), Quill block/inline
525
+ (29/30), Tiptap block/inline (29/30), ProseMirror block/inline (29/30).
526
+ - Chromium 152 on macOS, English UI, workspace source. The extended runner hashes
527
+ both newly covered plugin sources and package metadata.
528
+ - Each target passes the existing continuous formula chain. Integrated hosts
529
+ additionally check Apply/host Undo/Redo, Save/reload/Restore, re-edit/Cancel,
530
+ retained formula data and unchanged prose/container structure. Inline targets
531
+ check Enter commit, right-boundary exit, exact prose position and host Undo.
532
+ - Initial integrated run `2026-09-09T22-36-38-459Z`: six PASS, Tiptap inline FAIL.
533
+ A quick prose edit after Restore shared the restored document's history event;
534
+ Undo removed both. The sample now brackets `setContent` with `closeHistory`
535
+ transactions, keeping the replacement and subsequent typing separate.
536
+ - Corrected run `2026-09-09T22-38-26-544Z`: seven PASS. The final run above repeats
537
+ all seven targets with the final runner documentation and source hashes.
538
+ - Integration-demo TypeScript and production build pass. Existing large-chunk
539
+ build warnings remain. Test source formatting passes. Core/plugin runtime
540
+ code was unchanged; the fix belongs to the Tiptap demo's restoration callback.
541
+ - Remaining: Lexical, Editor.js, TinyMCE, CKEditor, Slate and Gutenberg; native OS
542
+ IME/clipboard, other browser engines, touch, accessibility and performance.
543
+
544
+
545
+ ## EDIT-019 Lexical — workspace, 2026-09-10
546
+
547
+ - Run: `2026-09-09T23-08-15-353Z`, stable source fingerprint.
548
+ - [Report](../../output/playwright/editing-scenarios/2026-09-09T23-08-15-353Z/REPORT.md) and
549
+ [checkpoints and hashes](../../output/playwright/editing-scenarios/2026-09-09T23-08-15-353Z/report.json).
550
+ - **9 targets PASS / 264 checkpoints:** React block (28), Quill block/inline
551
+ (29/30), Tiptap block/inline (29/30), ProseMirror block/inline (29/30),
552
+ Lexical block/inline (29/30).
553
+ - Chromium 152 on macOS, English UI, workspace source. Fingerprints now include
554
+ the Lexical plugin source and package metadata.
555
+ - Lexical passes the full nested editing and two history chains, Apply as one
556
+ host Undo event, Redo, Save/reload/Restore, re-edit and Cancel. Inline also
557
+ passes Enter commit without a new paragraph, right-boundary exit, focus return,
558
+ typing immediately after the formula and host Undo.
559
+ - Host checks read Lexical serialized state and retain container boundaries and
560
+ formula atoms. Drafts must not change saved host state. Restoration must match
561
+ the complete saved JSON; math rendering alone is not the persistence check.
562
+ - The initial run `2026-09-09T23-06-35-494Z` reached Save/Restore in both Lexical
563
+ modes but used an incorrect locator for reopening. Lexical uses a mounted
564
+ `.bme-lexical-node` widget; `data-barocss-math` belongs to its HTML export.
565
+ The fixture now locates its live widget. No product code change was needed.
566
+ - Runner formatting passes. This change only extends tests and documentation;
567
+ no new build, package publication or rendering audit was required.
568
+ - Next: Editor.js, TinyMCE, CKEditor, Slate and Gutenberg. Native OS IME/clipboard,
569
+ other browser engines, touch, accessibility and performance remain outside this run.
570
+
571
+
572
+ ## EDIT-019 Editor.js — workspace, 2026-09-10
573
+
574
+ - Run: `2026-09-09T23-25-26-845Z`, stable source fingerprint.
575
+ - [Report](../../output/playwright/editing-scenarios/2026-09-09T23-25-26-845Z/REPORT.md) and
576
+ [checkpoints and hashes](../../output/playwright/editing-scenarios/2026-09-09T23-25-26-845Z/report.json).
577
+ - **10 targets PASS / 293 checkpoints:** React block (28), Quill/Tiptap/
578
+ ProseMirror/Lexical block and inline (29/30 each), Editor.js block (29).
579
+ - Chromium 152 on macOS, English UI, workspace source. Fingerprints include
580
+ Editor.js plugin source and package metadata.
581
+ - Editor.js passes nested fraction/root/matrix editing, suggestion selection,
582
+ two uninterrupted formula Undo/Redo chains, empty-root deletion/restoration,
583
+ Apply, Save/reload/Restore, re-edit and Cancel. Draft checks confirm saved host
584
+ data stays unchanged until Apply; prose blocks and block order remain intact.
585
+ - Async `save()` comparisons exclude only the generated `time` field. Block IDs,
586
+ saved model data, paragraph data and version remain in persistence assertions.
587
+ - **Editor.js host Undo/Redo is not covered:** the demo has no document history
588
+ integration. The runner records this limitation in `hostHistory`; it retains
589
+ the mandatory host Undo/Redo assertions for other configured hosts. Editor.js
590
+ inline mode is outside the block tool's contract.
591
+ - Test-source formatting passes. Only the runner, fixture and documentation
592
+ changed; no runtime fix, build, publication or new rendering audit was needed.
593
+ - Next: TinyMCE, CKEditor, Slate and Gutenberg. Native OS IME/clipboard, other
594
+ browser engines, touch, accessibility and performance remain separate work.
595
+
596
+
597
+ ## EDIT-019 TinyMCE — workspace, 2026-09-10
598
+
599
+ - Run: `2026-09-09T23-39-57-339Z`, stable source fingerprint.
600
+ - [Report](../../output/playwright/editing-scenarios/2026-09-09T23-39-57-339Z/REPORT.md) and
601
+ [checkpoints and hashes](../../output/playwright/editing-scenarios/2026-09-09T23-39-57-339Z/report.json).
602
+ - **12 targets PASS / 352 checkpoints:** React block (28), Quill/Tiptap/
603
+ ProseMirror/Lexical/TinyMCE block and inline (29/30 each), Editor.js block (29).
604
+ - Chromium 152 on macOS, English UI, workspace source. TinyMCE 8.9.0 uses
605
+ `inline: true` for its host. Both math modes are covered; classic iframe mode
606
+ is not covered by this run. Fingerprints include TinyMCE source and metadata.
607
+ - TinyMCE passes the sustained fraction/root/matrix chain, formula history,
608
+ draft isolation, one host Undo/Redo for Apply, Save/reload/Restore, re-edit and
609
+ Cancel. Inline also passes Enter commit, right-boundary exit, focus return,
610
+ typing immediately after the formula and host Undo.
611
+ - The fixture applies and reopens an empty atom before the chain because TinyMCE
612
+ inserts on Apply. This exposed a real bug: the empty preview renderer returned
613
+ no visible content, leaving no click target. The adapter now shows the existing
614
+ localized empty-formula message and skips the renderer for empty LaTeX.
615
+ Serialized content stays empty; the test checks the label does not leak into HTML.
616
+ - HTML comparisons retain the complete serialized document. A detached parser
617
+ reads prose and element boundaries for caret checks. Live locators exclude
618
+ TinyMCE's `.mce-offscreen-selection` clone inside the editable body.
619
+ - Diagnostic run `2026-09-09T23-38-20-112Z` confirmed that a reported prose Undo
620
+ failure was in the fixture: saved and actual HTML were identical, but building
621
+ the expected prose result had mutated the saved units array. The fixture now
622
+ copies that array. No timing wait or product history change is used to pass it.
623
+ - TinyMCE package TypeScript, two unit tests, build and changed-source formatting
624
+ pass. Package-local build also exposed a metafile path check using the wrong
625
+ working directory. The build now sets esbuild's working directory to the repo
626
+ root; the normal package build command succeeds with its dependency checks intact.
627
+ - Patch changeset: `.changeset/tiny-math-empty-preview.md`. No package was published.
628
+ - Remaining: CKEditor, Slate, Gutenberg, TinyMCE iframe mode, native OS
629
+ IME/clipboard and other browser/input environments. Editor.js document history
630
+ remains unverified because its demo has no host history integration.
631
+
632
+
633
+ ## EDIT-019 CKEditor — workspace, 2026-09-10
634
+
635
+ - Run: `2026-09-09T23-44-59-263Z`, stable source fingerprint.
636
+ - [Report](../../output/playwright/editing-scenarios/2026-09-09T23-44-59-263Z/REPORT.md) and
637
+ [checkpoints and hashes](../../output/playwright/editing-scenarios/2026-09-09T23-44-59-263Z/report.json).
638
+ - **14 targets PASS / 411 checkpoints:** React block (28), Quill/Tiptap/
639
+ ProseMirror/Lexical/TinyMCE/CKEditor block and inline (29/30 each),
640
+ Editor.js block (29).
641
+ - Chromium 152 on macOS, English UI, workspace source. CKEditor 48.5.0 uses the
642
+ local ClassicEditor demo. Fingerprints include CKEditor plugin source and
643
+ package metadata. This run does not cover other CKEditor builds or collaboration.
644
+ - CKEditor passes the sustained fraction/root/matrix chain, keyboard suggestion
645
+ selection, two formula Undo/Redo chains, empty-root deletion/restoration,
646
+ unchanged host data during drafts, Apply as one host Undo event and host Redo.
647
+ - Save/reload/Restore retains the complete serialized HTML and editable math
648
+ structure. Re-edit and Cancel leave that HTML unchanged. Inline also passes
649
+ Enter commit without another paragraph, Right-arrow exit, focus return,
650
+ prose input immediately after the formula and host Undo.
651
+ - The fixture reuses the detached HTML parser for prose/container checks and
652
+ copies parsed units when constructing expectations. Full saved-state equality
653
+ remains required for history and persistence checks.
654
+ - Test-source formatting passes. Only tests and documentation changed in this
655
+ extension; no runtime fix, additional build or package publication was needed.
656
+ - Next: Slate and Gutenberg. TinyMCE iframe hosts, other CKEditor builds,
657
+ native OS IME/clipboard and other browser/input environments remain separate
658
+ targets. Editor.js document history is still outside its demo's configuration.
659
+
660
+
661
+ ## EDIT-019 all integration demos — workspace, 2026-09-10
662
+
663
+ - Run: `2026-09-09T23-55-26-302Z`, stable source fingerprint.
664
+ - [Report](../../output/playwright/editing-scenarios/2026-09-09T23-55-26-302Z/REPORT.md) and
665
+ [checkpoints and hashes](../../output/playwright/editing-scenarios/2026-09-09T23-55-26-302Z/report.json).
666
+ - **17 targets PASS / 499 checkpoints**, covering all nine integration demos
667
+ plus standalone React block. Chromium 152 on macOS, English UI, workspace source.
668
+
669
+ | Editor | Tested math modes | Checkpoints | Host Undo/Redo |
670
+ | --- | --- | ---: | --- |
671
+ | React standalone | Block | 28 | Not applicable |
672
+ | Quill | Block + inline | 59 | PASS |
673
+ | Tiptap | Block + inline | 59 | PASS |
674
+ | ProseMirror | Block + inline | 59 | PASS |
675
+ | Lexical | Block + inline | 59 | PASS |
676
+ | Editor.js | Block | 29 | Not configured; unverified |
677
+ | TinyMCE | Block + inline, inline host | 59 | PASS |
678
+ | CKEditor | Block + inline, ClassicEditor | 59 | PASS |
679
+ | Slate | Block + inline | 59 | PASS |
680
+ | Gutenberg | Block, standalone provider | 29 | PASS |
681
+
682
+ - Slate passes the sustained nested editing chain, two formula Undo/Redo chains,
683
+ Apply as one host history event, Save/reload/Restore, re-edit and Cancel.
684
+ Inline also passes Enter commit, Right-arrow exit, host focus, exact prose
685
+ insertion position and host Undo. Comparisons retain the saved descendant
686
+ array and formula objects; prose checks also preserve paragraph boundaries.
687
+ - Gutenberg passes the same block chain and provider host Undo/Redo. Full saved
688
+ HTML equality includes WordPress comments and encoded math data. A separate
689
+ detached DOM comparison checks prose and block order; comments are not prose.
690
+ - Both new hosts keep saved document data unchanged while editing a draft.
691
+ Restore reproduces the full saved state and reopens the same editable model.
692
+ - Fingerprints now include Slate and Gutenberg source/package metadata, as well
693
+ as the previously covered hosts, core, shared helpers, demos and test runner.
694
+ - Formatting passes. This extension changed only tests and documentation; no
695
+ runtime fix, new build or publication was needed.
696
+ - This run covers local demo configurations. It does not replace the separate
697
+ WordPress admin/plugin ZIP checks. TinyMCE iframe, other CKEditor builds,
698
+ collaboration, native OS IME/clipboard, Firefox/WebKit, touch, accessibility
699
+ and performance remain separate targets. Editor.js/Gutenberg are block tools;
700
+ no inline mode is claimed for them.
@@ -0,0 +1,21 @@
1
+ import { type MathState, type MathStructure } from './model.js';
2
+ /** The closest supported wrapper owns the footer, even when roots and fences are nested. */
3
+ export declare function structureEditingContext(state: MathState): {
4
+ fence: MathStructure;
5
+ root?: undefined;
6
+ } | {
7
+ root: {
8
+ id: string;
9
+ kind: "root" | "indexedRoot";
10
+ target: "root" | "indexedRoot";
11
+ canTransform: boolean;
12
+ indexCaret: {
13
+ id: string;
14
+ start: number;
15
+ end: number;
16
+ } | undefined;
17
+ } | undefined;
18
+ fence?: undefined;
19
+ } | undefined;
20
+ /** Reject an action if focus has moved to a different wrapper since it was displayed. */
21
+ export declare function changeContextFence(state: MathState, id: string, kind: string): MathState;
@@ -0,0 +1,37 @@
1
+ import { documentRows, isLiteralText, } from './model.js';
2
+ import { fences } from './fences.js';
3
+ import { rootEditingContext } from './root-transform.js';
4
+ import { setMathPresentation } from './presentation.js';
5
+ /** The closest supported wrapper owns the footer, even when roots and fences are nested. */
6
+ export function structureEditingContext(state) {
7
+ if (isLiteralText(state.document, state.caret.id))
8
+ return;
9
+ const visit = (row, parent) => {
10
+ for (const node of row.children) {
11
+ if (node.type === 'text') {
12
+ if (node.id === state.caret.id)
13
+ return parent;
14
+ }
15
+ else {
16
+ const target = fences(node) || ['root', 'indexedRoot'].includes(node.type) ? node : parent;
17
+ for (const slot of node.slots) {
18
+ const found = visit(slot, target);
19
+ if (found)
20
+ return found;
21
+ }
22
+ }
23
+ }
24
+ };
25
+ const node = documentRows(state.document)
26
+ .map((row) => visit(row))
27
+ .find(Boolean);
28
+ if (!node)
29
+ return;
30
+ return fences(node) ? { fence: node } : { root: rootEditingContext(state) };
31
+ }
32
+ /** Reject an action if focus has moved to a different wrapper since it was displayed. */
33
+ export function changeContextFence(state, id, kind) {
34
+ return structureEditingContext(state)?.fence?.id === id
35
+ ? setMathPresentation(state, id, { fence: kind })
36
+ : state;
37
+ }
@@ -0,0 +1,2 @@
1
+ /** Keep focus in the same document (including iframe hosts). Arrow keys only browse; Enter applies. */
2
+ export declare function focusContextAction(host: HTMLElement, key: string): boolean;
@@ -0,0 +1,22 @@
1
+ /** Keep focus in the same document (including iframe hosts). Arrow keys only browse; Enter applies. */
2
+ export function focusContextAction(host, key) {
3
+ const buttons = Array.from(host.querySelectorAll('button:not(:disabled)'));
4
+ if (!buttons.length)
5
+ return false;
6
+ const index = buttons.indexOf(host.ownerDocument.activeElement);
7
+ let next;
8
+ if (key === 'F6')
9
+ next = buttons.find((button) => button.getAttribute('aria-pressed') === 'true') ?? buttons[0];
10
+ else if (key === 'ArrowRight')
11
+ next = buttons[(index + 1) % buttons.length];
12
+ else if (key === 'ArrowLeft')
13
+ next = buttons[(index - 1 + buttons.length) % buttons.length];
14
+ else if (key === 'Home')
15
+ next = buttons[0];
16
+ else if (key === 'End')
17
+ next = buttons[buttons.length - 1];
18
+ if (!next)
19
+ return false;
20
+ next.focus({ preventScroll: true });
21
+ return true;
22
+ }
@@ -4,6 +4,9 @@ interface MenuRect {
4
4
  right: number;
5
5
  bottom: number;
6
6
  }
7
+ /** Follow layout and visual viewport changes, including ancestor transforms that
8
+ * do not produce window resize or element ResizeObserver notifications. */
9
+ export declare function followMathMenuPosition(anchor: HTMLElement, position: () => void, ignoreScrollWithin?: HTMLElement): () => void;
7
10
  /** Choose a scrollable vertical gap without covering host controls in the same column. */
8
11
  export declare function mathMenuPlacement(bounds: MenuRect, anchor: MenuRect, width: number, desiredHeight: number, preferredHeight: number, avoid?: readonly MenuRect[]): {
9
12
  left: number;
@@ -1,3 +1,41 @@
1
+ /** Follow layout and visual viewport changes, including ancestor transforms that
2
+ * do not produce window resize or element ResizeObserver notifications. */
3
+ export function followMathMenuPosition(anchor, position, ignoreScrollWithin) {
4
+ const win = anchor.ownerDocument.defaultView;
5
+ let frame = 0;
6
+ const schedule = () => {
7
+ if (!frame)
8
+ frame = win.requestAnimationFrame(() => {
9
+ frame = 0;
10
+ position();
11
+ });
12
+ };
13
+ const scroll = (event) => {
14
+ if (event.target &&
15
+ 'nodeType' in event.target &&
16
+ ignoreScrollWithin?.contains(event.target))
17
+ return;
18
+ schedule();
19
+ };
20
+ const changes = new MutationObserver(schedule);
21
+ for (let element = anchor; element; element = element.parentElement)
22
+ changes.observe(element, { attributes: true, attributeFilter: ['style', 'class'] });
23
+ const resize = typeof ResizeObserver === 'undefined' ? undefined : new ResizeObserver(schedule);
24
+ resize?.observe(anchor);
25
+ win.addEventListener('resize', schedule);
26
+ win.addEventListener('scroll', scroll, true);
27
+ win.visualViewport?.addEventListener('resize', schedule);
28
+ win.visualViewport?.addEventListener('scroll', schedule);
29
+ return () => {
30
+ changes.disconnect();
31
+ resize?.disconnect();
32
+ win.cancelAnimationFrame(frame);
33
+ win.removeEventListener('resize', schedule);
34
+ win.removeEventListener('scroll', scroll, true);
35
+ win.visualViewport?.removeEventListener('resize', schedule);
36
+ win.visualViewport?.removeEventListener('scroll', schedule);
37
+ };
38
+ }
1
39
  /** Fixed menus escape ordinary scrolling text areas. Transforms and containment
2
40
  * establish a local containing block, so overflow at that block and above still clips. */
3
41
  function establishesFixedContainingBlock(style) {
@@ -68,7 +106,13 @@ export function mathMenuPlacement(bounds, anchor, width, desiredHeight, preferre
68
106
  /** Position a fixed portal in viewport coordinates, including transformed modal hosts. */
69
107
  export function positionMathMenu(menu, anchor, maxHeight, avoidElements = []) {
70
108
  const win = menu.ownerDocument.defaultView;
71
- const bounds = { left: 8, top: 8, right: win.innerWidth - 8, bottom: win.innerHeight - 8 };
109
+ const viewport = win.visualViewport;
110
+ const bounds = {
111
+ left: (viewport?.offsetLeft ?? 0) + 8,
112
+ top: (viewport?.offsetTop ?? 0) + 8,
113
+ right: (viewport?.offsetLeft ?? 0) + (viewport?.width ?? win.innerWidth) - 8,
114
+ bottom: (viewport?.offsetTop ?? 0) + (viewport?.height ?? win.innerHeight) - 8,
115
+ };
72
116
  let contained = false;
73
117
  for (let parent = menu.parentElement; parent; parent = parent.parentElement) {
74
118
  const style = win.getComputedStyle(parent);
package/dist/dom.d.ts CHANGED
@@ -17,6 +17,8 @@ export interface DOMMathEditorOptions {
17
17
  mode?: MathEditorMode;
18
18
  toolbar?: boolean | readonly StructureKind[];
19
19
  toolbarMaxItems?: number;
20
+ /** Show contextual structure actions below the editing surface. Defaults to true. */
21
+ contextTools?: boolean;
20
22
  showLineNumbers?: boolean;
21
23
  onChange?: (document: MathDocument, latex: string) => void;
22
24
  /** Final DOM/caret notification, including focus and selection-only updates. */