@pie-lib/math-input-mathlive 1.1.5-beta.9 → 2.0.1-next.20261107131445

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 (195) hide show
  1. package/README.md +84 -109
  2. package/dist/_virtual/_rolldown/runtime.js +11 -0
  3. package/dist/horizontal-keypad.d.ts +22 -0
  4. package/dist/horizontal-keypad.js +54 -0
  5. package/dist/index.d.ts +20 -0
  6. package/dist/index.js +12 -0
  7. package/dist/keypad/index.d.ts +40 -0
  8. package/dist/keypad/index.js +245 -0
  9. package/dist/keypad/keys-layout.d.ts +7 -0
  10. package/dist/keypad/keys-layout.js +5 -0
  11. package/dist/keys/basic-operators.d.ts +5 -0
  12. package/dist/keys/basic-operators.js +30 -0
  13. package/dist/keys/chars.d.ts +5 -0
  14. package/dist/keys/comparison.d.ts +4 -0
  15. package/dist/keys/comparison.js +32 -0
  16. package/dist/keys/constants.d.ts +4 -0
  17. package/dist/keys/constants.js +35 -0
  18. package/dist/keys/digits.d.ts +15 -0
  19. package/dist/keys/digits.js +34 -0
  20. package/dist/keys/edit.d.ts +6 -0
  21. package/dist/keys/edit.js +9 -0
  22. package/dist/keys/exponent.d.ts +4 -0
  23. package/dist/keys/exponent.js +28 -0
  24. package/dist/keys/fractions.d.ts +3 -0
  25. package/dist/keys/fractions.js +27 -0
  26. package/dist/keys/geometry.d.ts +23 -0
  27. package/dist/keys/geometry.js +127 -0
  28. package/dist/keys/grades.d.ts +9 -0
  29. package/dist/keys/grades.js +414 -0
  30. package/dist/keys/index.d.ts +6 -0
  31. package/dist/keys/index.js +50 -0
  32. package/dist/keys/log.d.ts +3 -0
  33. package/dist/keys/log.js +25 -0
  34. package/dist/keys/logic.d.ts +2 -0
  35. package/dist/keys/logic.js +13 -0
  36. package/dist/keys/matrices.d.ts +2 -0
  37. package/dist/keys/matrices.js +17 -0
  38. package/dist/keys/misc.d.ts +10 -0
  39. package/dist/keys/misc.js +60 -0
  40. package/dist/keys/navigation.d.ts +2 -0
  41. package/dist/keys/navigation.js +13 -0
  42. package/dist/keys/operators.d.ts +1 -0
  43. package/dist/keys/operators.js +11 -0
  44. package/dist/keys/statistics.d.ts +5 -0
  45. package/dist/keys/statistics.js +38 -0
  46. package/dist/keys/sub-sup.d.ts +2 -0
  47. package/dist/keys/sub-sup.js +17 -0
  48. package/dist/keys/trigonometry.d.ts +6 -0
  49. package/dist/keys/trigonometry.js +43 -0
  50. package/dist/keys/utils.d.ts +5 -0
  51. package/dist/keys/utils.js +24 -0
  52. package/dist/keys/vars.d.ts +3 -0
  53. package/dist/keys/vars.js +22 -0
  54. package/dist/latex-bridge.d.ts +197 -0
  55. package/dist/latex-bridge.js +210 -0
  56. package/dist/longdiv.d.ts +24 -0
  57. package/dist/longdiv.js +43 -0
  58. package/dist/math-input.d.ts +26 -0
  59. package/dist/math-input.js +74 -0
  60. package/dist/mathlive-instance.d.ts +221 -0
  61. package/dist/mathlive-instance.js +159 -0
  62. package/dist/mf/common-styles.d.ts +594 -0
  63. package/dist/mf/common-styles.js +80 -0
  64. package/dist/mf/field-label.d.ts +16 -0
  65. package/dist/mf/field-label.js +13 -0
  66. package/dist/mf/index.d.ts +4 -0
  67. package/dist/mf/index.js +21 -0
  68. package/dist/mf/input.d.ts +94 -0
  69. package/dist/mf/input.js +127 -0
  70. package/dist/mf/static.d.ts +143 -0
  71. package/dist/mf/static.js +230 -0
  72. package/dist/selection.d.ts +43 -0
  73. package/dist/selection.js +39 -0
  74. package/package.json +32 -18
  75. package/lib/__tests__/composites.test.js +0 -255
  76. package/lib/__tests__/drop-in-compat.test.js +0 -133
  77. package/lib/__tests__/embedded-fonts.test.js +0 -121
  78. package/lib/__tests__/index.test.js +0 -118
  79. package/lib/__tests__/input-imperative.test.js +0 -155
  80. package/lib/__tests__/keypad-label.test.js +0 -231
  81. package/lib/__tests__/keypad-render.test.js +0 -206
  82. package/lib/__tests__/latex-bridge.test.js +0 -386
  83. package/lib/__tests__/mathlive-instance.test.js +0 -419
  84. package/lib/__tests__/mount-race.test.js +0 -218
  85. package/lib/__tests__/static-prompts.test.js +0 -405
  86. package/lib/__tests__/tmp-markup.test.js +0 -26
  87. package/lib/horizontal-keypad.js +0 -101
  88. package/lib/horizontal-keypad.js.map +0 -1
  89. package/lib/index.js +0 -143
  90. package/lib/index.js.map +0 -1
  91. package/lib/keypad/index.js +0 -431
  92. package/lib/keypad/index.js.map +0 -1
  93. package/lib/keypad/keys-layout.js +0 -22
  94. package/lib/keypad/keys-layout.js.map +0 -1
  95. package/lib/keys/basic-operators.js +0 -33
  96. package/lib/keys/basic-operators.js.map +0 -1
  97. package/lib/keys/chars.js +0 -12
  98. package/lib/keys/chars.js.map +0 -1
  99. package/lib/keys/comparison.js +0 -39
  100. package/lib/keys/comparison.js.map +0 -1
  101. package/lib/keys/constants.js +0 -37
  102. package/lib/keys/constants.js.map +0 -1
  103. package/lib/keys/digits.js +0 -46
  104. package/lib/keys/digits.js.map +0 -1
  105. package/lib/keys/edit.js +0 -14
  106. package/lib/keys/edit.js.map +0 -1
  107. package/lib/keys/exponent.js +0 -30
  108. package/lib/keys/exponent.js.map +0 -1
  109. package/lib/keys/fractions.js +0 -29
  110. package/lib/keys/fractions.js.map +0 -1
  111. package/lib/keys/geometry.js +0 -143
  112. package/lib/keys/geometry.js.map +0 -1
  113. package/lib/keys/grades.js +0 -259
  114. package/lib/keys/grades.js.map +0 -1
  115. package/lib/keys/index.js +0 -35
  116. package/lib/keys/index.js.map +0 -1
  117. package/lib/keys/log.js +0 -27
  118. package/lib/keys/log.js.map +0 -1
  119. package/lib/keys/logic.js +0 -19
  120. package/lib/keys/logic.js.map +0 -1
  121. package/lib/keys/matrices.js +0 -19
  122. package/lib/keys/matrices.js.map +0 -1
  123. package/lib/keys/misc.js +0 -69
  124. package/lib/keys/misc.js.map +0 -1
  125. package/lib/keys/navigation.js +0 -20
  126. package/lib/keys/navigation.js.map +0 -1
  127. package/lib/keys/operators.js +0 -15
  128. package/lib/keys/operators.js.map +0 -1
  129. package/lib/keys/statistics.js +0 -40
  130. package/lib/keys/statistics.js.map +0 -1
  131. package/lib/keys/sub-sup.js +0 -19
  132. package/lib/keys/sub-sup.js.map +0 -1
  133. package/lib/keys/trigonometry.js +0 -45
  134. package/lib/keys/trigonometry.js.map +0 -1
  135. package/lib/keys/utils.js +0 -87
  136. package/lib/keys/utils.js.map +0 -1
  137. package/lib/keys/vars.js +0 -24
  138. package/lib/keys/vars.js.map +0 -1
  139. package/lib/latex-bridge.js +0 -508
  140. package/lib/latex-bridge.js.map +0 -1
  141. package/lib/math-input.js +0 -143
  142. package/lib/math-input.js.map +0 -1
  143. package/lib/mathlive-instance.js +0 -585
  144. package/lib/mathlive-instance.js.map +0 -1
  145. package/lib/mf/common-styles.js +0 -203
  146. package/lib/mf/common-styles.js.map +0 -1
  147. package/lib/mf/index.js +0 -84
  148. package/lib/mf/index.js.map +0 -1
  149. package/lib/mf/input.js +0 -364
  150. package/lib/mf/input.js.map +0 -1
  151. package/lib/mf/static.js +0 -500
  152. package/lib/mf/static.js.map +0 -1
  153. package/src/__tests__/composites.test.jsx +0 -220
  154. package/src/__tests__/drop-in-compat.test.jsx +0 -112
  155. package/src/__tests__/index.test.jsx +0 -109
  156. package/src/__tests__/input-imperative.test.jsx +0 -144
  157. package/src/__tests__/keypad-label.test.jsx +0 -283
  158. package/src/__tests__/keypad-render.test.jsx +0 -123
  159. package/src/__tests__/latex-bridge.test.js +0 -389
  160. package/src/__tests__/mathlive-instance.test.js +0 -418
  161. package/src/__tests__/mount-race.test.jsx +0 -142
  162. package/src/__tests__/static-prompts.test.jsx +0 -364
  163. package/src/horizontal-keypad.jsx +0 -73
  164. package/src/index.jsx +0 -63
  165. package/src/keypad/index.jsx +0 -386
  166. package/src/keypad/keys-layout.js +0 -16
  167. package/src/keys/basic-operators.js +0 -32
  168. package/src/keys/chars.js +0 -5
  169. package/src/keys/comparison.js +0 -28
  170. package/src/keys/constants.js +0 -35
  171. package/src/keys/digits.js +0 -40
  172. package/src/keys/edit.js +0 -3
  173. package/src/keys/exponent.js +0 -28
  174. package/src/keys/fractions.js +0 -26
  175. package/src/keys/geometry.js +0 -149
  176. package/src/keys/grades.js +0 -367
  177. package/src/keys/index.js +0 -20
  178. package/src/keys/log.js +0 -22
  179. package/src/keys/logic.js +0 -15
  180. package/src/keys/matrices.js +0 -15
  181. package/src/keys/misc.js +0 -71
  182. package/src/keys/navigation.js +0 -8
  183. package/src/keys/operators.js +0 -10
  184. package/src/keys/statistics.js +0 -38
  185. package/src/keys/sub-sup.js +0 -15
  186. package/src/keys/trigonometry.js +0 -15
  187. package/src/keys/utils.js +0 -66
  188. package/src/keys/vars.js +0 -19
  189. package/src/latex-bridge.js +0 -524
  190. package/src/math-input.jsx +0 -115
  191. package/src/mathlive-instance.js +0 -582
  192. package/src/mf/common-styles.js +0 -210
  193. package/src/mf/index.js +0 -30
  194. package/src/mf/input.jsx +0 -319
  195. package/src/mf/static.jsx +0 -479
package/README.md CHANGED
@@ -1,144 +1,119 @@
1
1
  # @pie-lib/math-input-mathlive
2
2
 
3
- MathLive-backed math input. A drop-in alternative to `@pie-lib/math-input`, which
4
- is built on the `@pie-framework/mathquill` fork.
5
-
6
- Findings that motivated this package: [`../math-input/SPIKE-mathlive.md`](../math-input/SPIKE-mathlive.md).
3
+ MathLive-backed math input: a drop-in alternative to `@pie-lib/math-input`, which is built on the
4
+ `@pie-framework/mathquill` fork. `@pie-lib/math-toolbar`, `@pie-lib/editable-html-tip-tap`,
5
+ `@pie-element/math-inline` and `@pie-element/math-templated` use it.
7
6
 
8
7
  ## Migrating
9
8
 
10
- The export surface mirrors `@pie-lib/math-input`, so in most cases only the
11
- import changes:
9
+ The export surface mirrors `@pie-lib/math-input`, so most call sites change only the import:
12
10
 
13
11
  ```diff
14
12
  -import { mq, keys, keysForGrade, HorizontalKeypad, registerEmbed, applyStaticMath } from '@pie-lib/math-input';
15
13
  +import { mq, keys, keysForGrade, HorizontalKeypad, registerEmbed, applyStaticMath } from '@pie-lib/math-input-mathlive';
16
14
  ```
17
15
 
18
- The field wrappers are exported as `mf`, with `mq` kept as an alias so existing
19
- `mq.Input` / `mq.Static` call sites keep working.
16
+ The field wrappers are exported as `mf`, with `mq` kept as an alias so `mq.Input` / `mq.Static` call
17
+ sites keep working. Both take `language`, which names the field "Enter answer" in the item language,
18
+ and `ariaDescribedBy`, the id of an element that describes it.
20
19
 
21
20
  ### Behaviour differences
22
21
 
23
- | | `@pie-lib/math-input` | this package |
24
- | --------------------------- | -------------------------------- | ----------------------------------------------------------------------------- |
25
- | `mq.Input` | MathQuill `MathField` | `<math-field>` |
26
- | `mq.Static` (display) | one `MQ.StaticMath` per instance | `convertLatexToMarkup`, no live instance |
27
- | `mq.Static` (answer blocks) | `innerFields` | prompts API (`getPrompts`/`getPromptValue`) |
28
- | `updateSpans()` | patched MathQuill DOM | **no-op** (kept for compatibility) |
29
- | `registerEmbed` | MathQuill embed plugin | macro + `\htmlData` shim, see below |
30
- | Loading | synchronous | **async** - `await loadMathLive()` before `applyStaticMath` / `latexToMarkup` |
22
+ | | `@pie-lib/math-input` | this package |
23
+ | --------------------------- | -------------------------------- | --------------------------------------------------------------------- |
24
+ | `mq.Input` | MathQuill `MathField` | `<math-field>` |
25
+ | `mq.Static` (display) | one `MQ.StaticMath` per instance | `convertLatexToMarkup`, no live instance |
26
+ | `mq.Static` (answer blocks) | `innerFields` | prompts API, through handles with `latex`, `write`, `cmd`, `keystroke`, `clear`, `focus` and `el` |
27
+ | Whole static latex | `mathField.latex()` | `latex()` on the `mq.Static` instance |
28
+ | `updateSpans()` | patched MathQuill DOM | **no-op** (kept for compatibility) |
29
+ | `registerEmbed` | MathQuill embed plugin | macro + `\htmlData` shim, see below |
30
+ | Loading | synchronous | **async**: fields and static math fill in once MathLive has loaded |
31
+
32
+ MathLive's `input` event fires asynchronously, so `onChange` / `onSubFieldChange` run on the next
33
+ task after an edit.
31
34
 
32
35
  ### Content model is preserved
33
36
 
34
- Stored latex keeps its MathQuill-era form, so items authored with either
35
- implementation stay readable and `@pie-lib/math-rendering` needs no change.
36
- Translation happens at the package boundary:
37
+ Stored latex keeps its MathQuill-era form, so items authored with either implementation stay
38
+ readable and MathJax rendering needs no change. Translation happens at the package boundary:
37
39
 
38
40
  | stored | in MathLive |
39
41
  | --------------------------- | ----------------------- |
40
42
  | `\embed{newLine}[]` | `\displaylines{… \\ …}` |
41
43
  | `\MathQuillMathField[id]{}` | `\placeholder[id]{}` |
44
+ | `\embed{answerBlock}[id]` | `\answerBlock{id}` |
45
+ | `$` | `\$` |
46
+
47
+ `\answerBlock{id}` is a built-in macro: a box labelled "Response", tagged `data-response-area`, that
48
+ serialises back unchanged and deletes as one atom. To show a submitted answer in the same box, build
49
+ the latex with `answerBlockLatex(id, answer)`, which tags it `data-answer-block` and its label
50
+ `data-answer-block-index`. This replaces rendering an empty embed and calling `applyStaticMath` on
51
+ a span inside it, which has no MathLive equivalent.
52
+
53
+ A line break inside a group, such as an answer field holding several lines, becomes a one-column
54
+ `array`, because `\displaylines` rows only work at the top level. `mq.Static`'s `latex()` rebuilds
55
+ the stored latex from its `latex` prop with the current answers filled in, so everything outside
56
+ the answers comes back exactly as stored.
42
57
 
43
58
  `toMathLive()` / `fromMathLive()` are exported if you need the conversion directly.
44
59
 
45
60
  ### `registerEmbed`
46
61
 
47
- Same signature, different mechanism - MathLive has no embed plugin system, so an
48
- embed becomes a macro that expands to real LaTeX, tagged with
49
- `\htmlData{embed=<name>}` so CSS can target `[data-embed="<name>"]` the way it
50
- used to target the embed's `htmlString`.
62
+ Same signature, different mechanism. MathLive has no embed plugin system, so an embed becomes a
63
+ macro that expands to real LaTeX, tagged with `\htmlData{embed=<name>}` so CSS can target
64
+ `[data-embed="<name>"]`. `newLine` and `answerBlock` are handled natively, and registering them
65
+ changes nothing. Any other name falls back to a `\placeholder{}` unless the factory returns
66
+ `mathliveLatex`:
51
67
 
52
68
  ```js
53
- registerEmbed('answerBlock', () => ({
54
- latex: () => '\\embed{answerBlock}[]',
55
- mathliveLatex: '\\placeholder{}', // optional: control the expansion
69
+ registerEmbed('myEmbed', () => ({
70
+ latex: () => '\\embed{myEmbed}[]',
71
+ mathliveLatex: '\\boxed{?}',
56
72
  }));
57
73
  ```
58
74
 
59
- `newLine` and `answerBlock` are handled natively. Any other name falls back to a
60
- `\placeholder{}` and logs a warning.
61
-
62
- **Limitation:** MathQuill's `htmlString` could inject an arbitrary DOM subtree.
63
- `\htmlData` only decorates MathLive's own output, so embeds that mounted a custom
64
- widget do not port and need a redesign (`mf.getElementInfo()` exposes per-element
65
- `bounds`/`id`/`data` if you need to overlay something).
75
+ **Limitation:** MathQuill's `htmlString` could inject an arbitrary DOM subtree. `\htmlData` only
76
+ decorates MathLive's own output, so an embed that mounted a custom widget needs a redesign.
66
77
 
67
78
  ## Notes
68
79
 
69
- - **Browser only.** MathLive registers custom elements; `loadMathLive()` returns
70
- `undefined` during SSR and every consumer degrades gracefully. Import the
71
- package client-side (see `packages/demo/pages/math-input-mathlive.js`).
72
- - **Bundle.** The editor is ~227KB gzip plus ~296KB of KaTeX fonts, so it is
73
- loaded lazily via dynamic `import()`. Render-only consumers can use
74
- `mathlive/ssr` (~111KB gzip) through `latexToMarkup`.
75
- - **Stylesheets: import both, once, in the host app.**
76
-
77
- ```js
78
- import 'mathlive/fonts.css'; // KaTeX @font-face rules
79
- import 'mathlive/static.css'; // REQUIRED for convertLatexToMarkup output
80
- ```
81
-
82
- `static.css` is not optional. Keypad labels and `mf.Static` in display mode are
83
- rendered with `convertLatexToMarkup`, whose output relies on
84
- `ML__vlist` / `ML__strut` / `ML__base` / `ML__sqrt-sign` / `ML__pstrut` and on
85
- `svg { position: absolute; width: 100% }`. Without the stylesheet, static math
86
- renders misaligned and stretchy accents (`\overrightarrow`,
87
- `\overleftrightarrow`, `\overarc`) expand without bound - a keypad button can
88
- end up stretching across the viewport. The keypad defends against the worst of
89
- this (`overflow: hidden` plus an svg rule on the label), but correct layout
90
- still needs the real stylesheet.
91
-
92
- Live `<math-field>` elements style themselves via shadow DOM and are unaffected.
93
-
94
- On fonts specifically, `fonts.css` is the recommended route - no copied files,
95
- no CDN:
96
-
97
- The stylesheet's relative `url(fonts/*.woff2)` paths are resolved by your
98
- bundler, so the fonts are emitted with the build and content-hashed. It also
99
- sets `--ML__static-fonts`, which makes MathLive skip font fetching entirely.
100
-
101
- This matters because MathLive's default `fontsDirectory` is the relative
102
- `'./fonts/'`, which it resolves against its own script URL - empty under a
103
- bundler, so `loadFonts` throws
104
- `Failed to construct 'URL': Invalid base URL`. Importing the stylesheet avoids
105
- that code path completely.
106
-
107
- _Bundler caveat:_ a `url-loader`/`file-loader` rule matching `woff2` will
108
- corrupt these fonts (it emits a JS module under a `.woff2` name). Exclude
109
- mathlive from such rules - see `packages/demo/next.config.js`.
110
-
111
- **Fonts are required, not cosmetic.** Some symbols are composed from Private
112
- Use Area glyphs that exist only in the KaTeX fonts: `\neq` is U+E020 (a
113
- negation slash) overlapped on `=`, and `\nsim` / `\ncong` are the same. With
114
- no fonts the browser substitutes an arbitrary glyph for U+E020, so those keys
115
- render as garbage rather than in a plainer face.
116
-
117
- **Alternative** - serve the fonts yourself (a static path, or a CDN):
118
-
119
- ```js
120
- import { configureFonts } from '@pie-lib/math-input-mathlive';
121
-
122
- configureFonts('/mathlive-fonts'); // before loadMathLive()
123
- configureFonts('https://unpkg.com/mathlive@0.110.0/fonts'); // note: /fonts, /dist/fonts 404s
124
- configureFonts(null); // opt out; \neq and friends will render incorrectly
125
- ```
126
-
127
- Paths are resolved to absolute URLs against `document.baseURI`.
128
-
129
- If you neither import the stylesheet nor call `configureFonts`, the package
130
- falls back to a **version-pinned CDN** - `https://unpkg.com/mathlive@<engine
131
- version>/fonts` - so those glyph-dependent symbols still render. Serve the
132
- fonts yourself to avoid the third-party request.
133
-
134
- - **Custom macros.** `PIE_MACROS` covers the pie-specific commands the MathQuill
135
- fork understood (`\parallelogram`, `\longdiv`, `\napprox`, …) and is kept in
136
- sync with the MathJax macros in `@pie-lib/math-rendering`.
137
-
138
- ## Development
139
-
140
- ```bash
141
- nvm use --delete-prefix v22 # mathlive needs node >= 18
142
- ./node_modules/.bin/jest packages/math-input-mathlive/src
143
- ./node_modules/.bin/next dev packages/demo # then /math-input-mathlive
144
- ```
80
+ - **Browser only.** MathLive registers custom elements. `loadMathLive()` returns `undefined` during
81
+ SSR, and under Node it resolves MathLive's render-only build, which has no `MathfieldElement`: fields
82
+ then stay empty while static math still renders.
83
+ - **Loading.** MathLive is loaded with a dynamic `import()` marked `webpackMode: "eager"`, so a webpack
84
+ host keeps it in its main bundle rather than a chunk that may not be served.
85
+ - **Several bundles on a page.** Each IIFE bundle carries its own MathLive, and only the first to
86
+ load registers `math-field`. Fields are created with `document.createElement('math-field')`, so
87
+ they use whichever copy registered it; constructing another copy's class throws "Illegal
88
+ constructor".
89
+ - **Focus after a click.** MathLive moves keyboard focus into a field 60ms after a click, so a key
90
+ typed sooner is lost. Tests should wait for the field to be focused before typing.
91
+ - **Field semantics.** A field is a group named "Enter answer" in the item language, around
92
+ MathLive's keyboard input, a textbox in its shadow root that gets the same name. MathLive names
93
+ that textbox with the spoken math once it has focus. Keypad keys are named by their buttons, so
94
+ a LaTeX key's drawing is hidden from assistive technology.
95
+ - **Answer blocks.** Static keeps the caret in the first answer block until the field has focus.
96
+ Otherwise MathLive, finding no block after the caret when focus arrives, moves focus on to the
97
+ next element, and Tab skips the field. Tab into a field with answer blocks puts the caret in the
98
+ first one. Static
99
+ reports the block holding the caret through `setInput` and `onSubFieldFocus` whenever the caret
100
+ moves to another block, since the blocks share one field and that fires no new `focusin`.
101
+ - **Text alternative.** Static math in display mode has no accessible text of its own, so its markup
102
+ is `aria-hidden` and MathML is rendered beside it, visually hidden, as MathLive's `<math-span>`
103
+ does.
104
+ - **Fonts.** Some symbols (`\neq`, `\nsim`, `\ncong`) are built from Private Use Area glyphs that exist
105
+ only in the KaTeX fonts. With no `configureFonts()` call the fonts come from a version-pinned CDN
106
+ (`https://unpkg.com/mathlive@<version>/fonts`); call `configureFonts('/path/to/fonts')` before the
107
+ first load to serve them yourself, or `configureFonts(null)` to opt out.
108
+ - **Core stylesheet.** `convertLatexToMarkup` output (keypad labels, display-mode static math) needs
109
+ MathLive's core styles; the package has MathLive inject them on load.
110
+ - **Custom macros.** `PIE_MACROS` covers the pie-specific commands the MathQuill fork understood
111
+ (`\parallelogram`, `\longdiv`, `\napprox`, …), kept in sync with the MathJax macros.
112
+
113
+ ## Testing
114
+
115
+ happy-dom cannot run MathLive's editor. Tests that need a mounted field mock `mathlive` with a
116
+ stand-in `MathfieldElement` (see `test/field-label.test.tsx`, and
117
+ `packages/elements-react/math-inline/src/delivery/__tests__/fake-mathlive.ts` for one with prompts).
118
+ A package that mocks `mathlive` must list it as a devDependency so the mock resolves to the module
119
+ this package imports.
@@ -0,0 +1,11 @@
1
+ //#region \0rolldown/runtime.js
2
+ var e = Object.defineProperty, t = (t, n) => {
3
+ let r = {};
4
+ for (var i in t) e(r, i, {
5
+ get: t[i],
6
+ enumerable: !0
7
+ });
8
+ return n || e(r, Symbol.toStringTag, { value: "Module" }), r;
9
+ };
10
+ //#endregion
11
+ export { t as __exportAll };
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ export default class HorizontalKeypad extends React.Component {
4
+ static propTypes: {
5
+ className: PropTypes.Requireable<string>;
6
+ controlledKeypadMode: PropTypes.Requireable<boolean>;
7
+ mode: PropTypes.Requireable<NonNullable<string | number | null | undefined>>;
8
+ layoutForKeyPad: PropTypes.Requireable<object>;
9
+ onClick: PropTypes.Validator<(...args: any[]) => any>;
10
+ onFocus: PropTypes.Requireable<(...args: any[]) => any>;
11
+ noDecimal: PropTypes.Requireable<boolean>;
12
+ additionalKeys: PropTypes.Requireable<any[]>;
13
+ setKeypadInteraction: PropTypes.Requireable<(...args: any[]) => any>;
14
+ };
15
+ static defaultProps: {
16
+ mode: string;
17
+ noDecimal: boolean;
18
+ additionalKeys: never[];
19
+ };
20
+ keypadPress: (data: any) => void;
21
+ render(): React.JSX.Element;
22
+ }
@@ -0,0 +1,54 @@
1
+ import { extendKeySet as e } from "./keys/utils.js";
2
+ import { keysForGrade as t, normalizeAdditionalKeys as n } from "./keys/grades.js";
3
+ import { KeyPad as r } from "./keypad/index.js";
4
+ import i from "react";
5
+ import a from "prop-types";
6
+ import { jsx as o } from "react/jsx-runtime";
7
+ //#region src/horizontal-keypad.tsx
8
+ var s = (e) => {
9
+ if (e.command) return {
10
+ value: e.command,
11
+ type: "command"
12
+ };
13
+ if (e.write) return { value: e.write };
14
+ if (e.keystroke) return {
15
+ type: "cursor",
16
+ value: e.keystroke
17
+ };
18
+ }, c = class extends i.Component {
19
+ static propTypes = {
20
+ className: a.string,
21
+ controlledKeypadMode: a.bool,
22
+ mode: a.oneOfType([a.string, a.number]),
23
+ layoutForKeyPad: a.object,
24
+ onClick: a.func.isRequired,
25
+ onFocus: a.func,
26
+ noDecimal: a.bool,
27
+ additionalKeys: a.array,
28
+ setKeypadInteraction: a.func
29
+ };
30
+ static defaultProps = {
31
+ mode: "scientific",
32
+ noDecimal: !1,
33
+ additionalKeys: []
34
+ };
35
+ keypadPress = (e) => {
36
+ this.props.onClick(s(e));
37
+ };
38
+ render() {
39
+ let { mode: i, onFocus: a, controlledKeypadMode: s, noDecimal: c, className: l, additionalKeys: u, layoutForKeyPad: d, setKeypadInteraction: f } = this.props, p = n(u);
40
+ return /* @__PURE__ */ o(r, {
41
+ className: l,
42
+ controlledKeypadMode: s,
43
+ onFocus: a,
44
+ noDecimal: c,
45
+ layoutForKeyPad: d,
46
+ additionalKeys: e(t(i), p),
47
+ onPress: this.keypadPress,
48
+ mode: i,
49
+ setKeypadInteraction: f
50
+ });
51
+ }
52
+ };
53
+ //#endregion
54
+ export { c as default };
@@ -0,0 +1,20 @@
1
+ import { keysForGrade } from './keys/grades.js';
2
+ import * as keys from './keys/index.js';
3
+ import MathInput from './math-input.js';
4
+ import HorizontalKeypad from './horizontal-keypad.js';
5
+ import KeyPad from './keypad/index.js';
6
+ import * as mf from './mf/index.js';
7
+ import { registerEmbed, applyStaticMath, latexToMarkup, loadMathLive, configureFonts, getMacros, PIE_MACROS } from './mathlive-instance.js';
8
+ import { toMathLive, fromMathLive, fieldIds, keyToAction, answerBlockLatex, NEWLINE_EMBED } from './latex-bridge.js';
9
+ declare const addBrackets: (s: any) => any;
10
+ declare const removeBrackets: (s: any) => any;
11
+ /**
12
+ * `updateSpans` is intentionally a no-op.
13
+ *
14
+ * In @pie-lib/math-input it patched MathQuill's rendered DOM by walking
15
+ * `span[mathquill-command-id]` to resize the parallel glyph and tag primes.
16
+ * MathLive emits none of those attributes and does not need the fixups, but the
17
+ * export is kept so callers do not break.
18
+ */
19
+ declare const updateSpans: () => void;
20
+ export { keysForGrade, addBrackets, removeBrackets, keys, MathInput, HorizontalKeypad, KeyPad, updateSpans, mf, mf as mq, registerEmbed, applyStaticMath, latexToMarkup, loadMathLive, configureFonts, getMacros, PIE_MACROS, toMathLive, fromMathLive, fieldIds, keyToAction, answerBlockLatex, NEWLINE_EMBED, };
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ import { keysForGrade as e } from "./keys/grades.js";
2
+ import { keys_exports as t } from "./keys/index.js";
3
+ import { NEWLINE_EMBED as n, answerBlockLatex as r, fieldIds as i, fromMathLive as a, keyToAction as o, toMathLive as s } from "./latex-bridge.js";
4
+ import { PIE_MACROS as c, applyStaticMath as l, configureFonts as u, getMacros as d, latexToMarkup as f, loadMathLive as p, registerEmbed as m } from "./mathlive-instance.js";
5
+ import { mf_exports as h } from "./mf/index.js";
6
+ import { KeyPad as g } from "./keypad/index.js";
7
+ import { MathInput as _ } from "./math-input.js";
8
+ import v from "./horizontal-keypad.js";
9
+ //#region src/index.tsx
10
+ var y = (e) => e.indexOf("\\(") === 0 ? e : `\\(${e}`, b = (e) => e.indexOf("\\)") === e.length - 2 ? e : `${e}\\)`, x = (e) => e.indexOf("\\(") === 0 ? e.substring(2) : e, S = (e) => e.indexOf("\\)") === e.length - 2 ? e.substring(0, e.length - 2) : e, C = (e) => b(y(e)), w = (e) => S(x(e)), T = () => {};
11
+ //#endregion
12
+ export { v as HorizontalKeypad, g as KeyPad, _ as MathInput, n as NEWLINE_EMBED, c as PIE_MACROS, C as addBrackets, r as answerBlockLatex, l as applyStaticMath, u as configureFonts, i as fieldIds, a as fromMathLive, d as getMacros, o as keyToAction, t as keys, e as keysForGrade, f as latexToMarkup, p as loadMathLive, h as mf, h as mq, m as registerEmbed, w as removeBrackets, s as toMathLive, T as updateSpans };
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ export declare class LatexLabel extends React.Component {
4
+ static propTypes: {
5
+ latex: PropTypes.Validator<string>;
6
+ decorative: PropTypes.Requireable<boolean>;
7
+ };
8
+ constructor(props: any);
9
+ componentDidMount(): Promise<void>;
10
+ componentDidUpdate(prevProps: any, prevState: any): void;
11
+ componentWillUnmount(): void;
12
+ render(): React.JSX.Element;
13
+ }
14
+ export declare class KeyPad extends React.Component {
15
+ static propTypes: {
16
+ className: PropTypes.Requireable<string>;
17
+ controlledKeypadMode: PropTypes.Requireable<boolean>;
18
+ baseSet: PropTypes.Requireable<any[]>;
19
+ additionalKeys: PropTypes.Requireable<any[]>;
20
+ layoutForKeyPad: PropTypes.Requireable<object>;
21
+ onPress: PropTypes.Validator<(...args: any[]) => any>;
22
+ onFocus: PropTypes.Requireable<(...args: any[]) => any>;
23
+ noDecimal: PropTypes.Requireable<boolean>;
24
+ setKeypadInteraction: PropTypes.Requireable<(...args: any[]) => any>;
25
+ mode: PropTypes.Requireable<NonNullable<string | number | null | undefined>>;
26
+ };
27
+ static defaultProps: {
28
+ baseSet: any[][];
29
+ noDecimal: boolean;
30
+ };
31
+ constructor(props: any);
32
+ componentDidMount(): void;
33
+ componentWillUnmount(): void;
34
+ handleKeypadInteraction: () => void;
35
+ buttonClick: (key: any) => void;
36
+ flowKeys: (base: any, extras: any) => unknown[];
37
+ keyIsNotAllowed: (key: any) => boolean;
38
+ render(): React.JSX.Element;
39
+ }
40
+ export default KeyPad;
@@ -0,0 +1,245 @@
1
+ import { baseSet as e } from "../keys/index.js";
2
+ import { commonKeyboardStyles as t, rootIndexStyles as n } from "../mf/common-styles.js";
3
+ import { fixLongDivisionWhenReady as r } from "../longdiv.js";
4
+ import { latexToMarkup as i, loadMathLive as a } from "../mathlive-instance.js";
5
+ import { sortKeys as o } from "./keys-layout.js";
6
+ import { flatten as s } from "@pie-element/shared-lodash";
7
+ import c from "react";
8
+ import l from "prop-types";
9
+ import { styled as u } from "@mui/material/styles";
10
+ import d from "debug";
11
+ import { jsx as f } from "react/jsx-runtime";
12
+ import p from "@mui/material/Button";
13
+ import m from "@mui/material/IconButton";
14
+ import { color as h } from "@pie-lib/render-ui";
15
+ //#region src/keypad/index.tsx
16
+ var g = d("pie-lib:math-input-mathlive:keypad"), _ = () => [h.text(), h.keypadInk()], v = () => ({ "& .MuiTouchRipple-root": { color: `rgb(from ${h.text()} min(r, 255 - r) min(g, 255 - g) min(b, 255 - b))` } }), y = () => ({ "&.Mui-focusVisible": {
17
+ outline: `2px solid ${h.focusOutline()}`,
18
+ outlineOffset: "-2px",
19
+ boxShadow: `inset 0 0 0 4px ${h.v("pie")("background", h.defaults.WHITE)}`
20
+ } }), b = ["\\parallel"], x = "skewX(-15deg)", S = "0.03em currentColor", C = 1.25, w = u("span", { shouldForwardProp: (e) => e !== "slanted" })(({ slanted: e }) => ({
21
+ ...e && {
22
+ transform: `${x} scaleY(${C})`,
23
+ "& *": { WebkitTextStroke: S }
24
+ },
25
+ pointerEvents: "none",
26
+ textTransform: "none",
27
+ color: _(),
28
+ display: "inline-block",
29
+ textAlign: "center",
30
+ lineHeight: 1,
31
+ maxWidth: "100%",
32
+ overflow: "hidden",
33
+ "& .ML__placeholder": { backgroundColor: h.keypadEmptyPlaceholder ? h.keypadEmptyPlaceholder() : void 0 },
34
+ "& [data-pie-empty]": { opacity: .5 },
35
+ "&:has(.ML__mfrac)": { paddingTop: "0.25em" },
36
+ "& .ML__mfrac:has([data-pie-empty]) .ML__frac-line": {
37
+ position: "relative",
38
+ top: "0.14em"
39
+ },
40
+ "& .ML__mfrac:has([data-pie-empty]):not(:has(.ML__center ~ .ML__center [data-pie-empty])) .ML__frac-line": { top: "0.13em" },
41
+ "& .ML__mfrac:has([data-pie-empty]):not(:only-child) .ML__frac-line": { top: "0.1em" },
42
+ ...n
43
+ })), T = class e extends c.Component {
44
+ static propTypes = {
45
+ latex: l.string.isRequired,
46
+ decorative: l.bool
47
+ };
48
+ constructor(e) {
49
+ super(e), this.state = { markup: i(e.latex) }, this.labelRef = c.createRef();
50
+ }
51
+ async componentDidMount() {
52
+ if (this.state.markup) {
53
+ r(this.labelRef.current);
54
+ return;
55
+ }
56
+ if (await a(), this.unmounted) return;
57
+ let t = i(this.props.latex);
58
+ t ? this.setState({ markup: t }) : e.warned || (e.warned = !0, g("MathLive unavailable - keypad labels are falling back to raw latex"));
59
+ }
60
+ componentDidUpdate(e, t) {
61
+ e.latex !== this.props.latex && this.setState({ markup: i(this.props.latex) }), this.state.markup !== (t && t.markup) && r(this.labelRef.current);
62
+ }
63
+ componentWillUnmount() {
64
+ this.unmounted = !0;
65
+ }
66
+ render() {
67
+ let { markup: e } = this.state, t = this.props.decorative ? "true" : void 0;
68
+ return e ? /* @__PURE__ */ f(w, {
69
+ ref: this.labelRef,
70
+ "aria-hidden": t,
71
+ slanted: b.includes(this.props.latex),
72
+ dangerouslySetInnerHTML: { __html: e }
73
+ }) : /* @__PURE__ */ f(w, {
74
+ "aria-hidden": t,
75
+ slanted: b.includes(this.props.latex),
76
+ children: this.props.latex
77
+ });
78
+ }
79
+ }, E = (e) => e ? {
80
+ gridTemplateColumns: `repeat(${e.columns}, minmax(min-content, 150px))`,
81
+ gridTemplateRows: `repeat(${e.rows}, minmax(40px, 60px))`,
82
+ gridAutoFlow: "initial"
83
+ } : {}, D = u("div")(() => ({
84
+ ...t,
85
+ width: "100%",
86
+ display: "grid",
87
+ gridTemplateRows: "repeat(5, minmax(40px, 60px))",
88
+ gridRowGap: "0px",
89
+ gridColumnGap: "0px",
90
+ gridAutoFlow: "column",
91
+ "&.character": {
92
+ textTransform: "initial !important",
93
+ gridTemplateRows: "repeat(5, minmax(40px, 50px)) !important"
94
+ },
95
+ "&.language": {
96
+ gridTemplateRows: "repeat(4, minmax(40px, 50px)) !important",
97
+ "& *": { fontFamily: "Roboto, Helvetica, Arial, sans-serif !important" }
98
+ }
99
+ })), O = (e) => ({
100
+ backgroundColor: e === "operators" ? h.keypadButtonOperator() : h.keypadButton(),
101
+ "&:hover": { backgroundColor: e === "operators" ? h.keypadButtonOperatorHover() : h.keypadButtonHover() }
102
+ }), k = u(p)(({ category: e, isDelete: t, isComma: n, isDot: r }) => ({
103
+ minWidth: "auto",
104
+ textTransform: "none",
105
+ fontSize: n || r ? "200% !important" : "140% !important",
106
+ lineHeight: n || r ? "100%" : "normal",
107
+ color: _(),
108
+ ...O(e),
109
+ borderRadius: 0,
110
+ ...t && { fontFamily: "Roboto, Helvetica, Arial, sans-serif !important" },
111
+ ...v(),
112
+ ...y()
113
+ })), A = u(p)(({ category: e }) => ({
114
+ textTransform: "none",
115
+ padding: 0,
116
+ margin: 0,
117
+ fontSize: "110% !important",
118
+ minWidth: "auto",
119
+ borderRadius: 0,
120
+ ...O(e),
121
+ ...v(),
122
+ ...y()
123
+ })), j = u(m)(({ category: e }) => ({
124
+ minWidth: "auto",
125
+ ...O(e),
126
+ borderRadius: 0,
127
+ "& .icon": { height: "30px" }
128
+ })), M = class extends c.Component {
129
+ static propTypes = {
130
+ className: l.string,
131
+ controlledKeypadMode: l.bool,
132
+ baseSet: l.array,
133
+ additionalKeys: l.array,
134
+ layoutForKeyPad: l.object,
135
+ onPress: l.func.isRequired,
136
+ onFocus: l.func,
137
+ noDecimal: l.bool,
138
+ setKeypadInteraction: l.func,
139
+ mode: l.oneOfType([l.string, l.number])
140
+ };
141
+ static defaultProps = {
142
+ baseSet: e,
143
+ noDecimal: !1
144
+ };
145
+ constructor(e) {
146
+ super(e), this.keypadRef = c.createRef();
147
+ }
148
+ componentDidMount() {
149
+ a();
150
+ let e = this.keypadRef?.current, t = e?.closest(".main-container"), n = e?.closest(".pie-toolbar");
151
+ if (this.props.controlledKeypadMode && t && n) {
152
+ let e = t.getBoundingClientRect(), r = n.getBoundingClientRect(), i = e.top + e.height - (r.top + r.height);
153
+ if (i < 0) {
154
+ let n = e.height + e.top - i;
155
+ t.style.height = `${n}px`;
156
+ }
157
+ }
158
+ e && (e.addEventListener("touchstart", this.handleKeypadInteraction, !0), e.addEventListener("mousedown", this.handleKeypadInteraction, !0));
159
+ }
160
+ componentWillUnmount() {
161
+ let e = this.keypadRef?.current;
162
+ if (this.props.controlledKeypadMode && e) {
163
+ let t = e.closest(".main-container");
164
+ t && (t.style.height = "unset");
165
+ }
166
+ e && (e.removeEventListener("touchstart", this.handleKeypadInteraction, !0), e.removeEventListener("mousedown", this.handleKeypadInteraction, !0));
167
+ }
168
+ handleKeypadInteraction = () => {
169
+ this.props.setKeypadInteraction && this.props.setKeypadInteraction(!0);
170
+ };
171
+ buttonClick = (e) => {
172
+ g("[buttonClick]", e), this.props.onPress(e);
173
+ };
174
+ flowKeys = (e, t) => s([...o(e), ...o(t)]);
175
+ keyIsNotAllowed = (e) => {
176
+ let { noDecimal: t } = this.props;
177
+ return !!((e.write === "." && e.label === "." || e.write === "," && e.label === ",") && t);
178
+ };
179
+ render() {
180
+ let { className: e, baseSet: t, additionalKeys: n, layoutForKeyPad: r, onFocus: i, mode: a } = this.props, o = [
181
+ "non-negative-integers",
182
+ "integers",
183
+ "decimals",
184
+ "fractions",
185
+ "item-authoring",
186
+ "language"
187
+ ].includes(a), s = o ? this.flowKeys([], n || []) : this.flowKeys(t, n || []), c = s.length % 5 ? 1 : 0, l = {
188
+ gridTemplateColumns: `repeat(${Math.floor(s.length / 5) + c}, minmax(min-content, 150px))`,
189
+ ...E(r)
190
+ };
191
+ return /* @__PURE__ */ f(D, {
192
+ ref: this.keypadRef,
193
+ className: [e, a].filter(Boolean).join(" "),
194
+ style: l,
195
+ onFocus: i,
196
+ children: s.map((e, t) => {
197
+ if (!e) return /* @__PURE__ */ f("span", {}, `empty-${t}`);
198
+ if (!(e.label || e.latex || e.icon || e.command || e.keystroke || e.write || e.type || e.actions || e.extraProps)) return /* @__PURE__ */ f(k, {
199
+ disabled: !0,
200
+ "aria-hidden": "true",
201
+ tabIndex: -1,
202
+ category: o ? void 0 : e.category
203
+ }, `placeholder-${t}`);
204
+ let n = `${e.label || e.latex || e.command}-${t}`, r = o ? void 0 : e.category, i = {
205
+ ...e.actions || {},
206
+ ...e.extraProps || {}
207
+ }, a = this.buttonClick.bind(this, e), s = this.keyIsNotAllowed(e), c = e.ariaLabel ? e.ariaLabel : e.name || e.label;
208
+ if (e.latex) return /* @__PURE__ */ f(A, {
209
+ onClick: a,
210
+ disabled: s,
211
+ category: r,
212
+ "aria-label": c,
213
+ ...i,
214
+ children: /* @__PURE__ */ f(T, {
215
+ latex: e.latex,
216
+ decorative: !!c
217
+ })
218
+ }, n);
219
+ if (e.label) return /* @__PURE__ */ f(k, {
220
+ onClick: a,
221
+ disabled: s,
222
+ category: r,
223
+ isDelete: e.label === "⌫",
224
+ isComma: e.label === ",",
225
+ isDot: e.label === ".",
226
+ "aria-label": c,
227
+ ...i,
228
+ children: e.label
229
+ }, n);
230
+ let l = e.icon ? e.icon : "div";
231
+ return /* @__PURE__ */ f(j, {
232
+ tabIndex: "-1",
233
+ onClick: a,
234
+ disabled: s,
235
+ category: r,
236
+ size: "large",
237
+ ...i,
238
+ children: /* @__PURE__ */ f(l, { className: "icon" })
239
+ }, n);
240
+ })
241
+ });
242
+ }
243
+ };
244
+ //#endregion
245
+ export { M as KeyPad, M as default, T as LatexLabel };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Sort additional keys.
3
+ *
4
+ * Expects an array of rows.
5
+ * @param {} keys
6
+ */
7
+ export declare const sortKeys: (keys: any) => unknown[][];