@mathzetta/inlinemath 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +326 -2
  3. package/dist/cas.external.js +2 -0
  4. package/dist/cas.js +2 -0
  5. package/dist/chunks/calculate-Cz87Lt0N.js +2 -0
  6. package/dist/chunks/draw-Bev17BJJ.js +2 -0
  7. package/dist/chunks/edit-C65xXzo8.js +2 -0
  8. package/dist/chunks/edit-M68aRA5o.js +2 -0
  9. package/dist/chunks/html-BimAqn7x.js +2 -0
  10. package/dist/chunks/html-CupluJSh.js +2 -0
  11. package/dist/chunks/lite-3Fb-OHbt.js +2 -0
  12. package/dist/fonts/GUST-FONT-LICENSE.txt +30 -0
  13. package/dist/fonts/fonts.css +17 -0
  14. package/dist/fonts/lmroman12-italic.woff2 +0 -0
  15. package/dist/fonts/lmroman12-regular.woff2 +0 -0
  16. package/dist/inlinemath-cas.external.global.min.js +2 -0
  17. package/dist/inlinemath-cas.global.min.js +2 -0
  18. package/dist/inlinemath-lite-calc.global.min.js +2 -0
  19. package/dist/inlinemath-lite.external.global.min.js +2 -0
  20. package/dist/inlinemath-lite.global.min.js +2 -0
  21. package/dist/inlinemath.css +158 -0
  22. package/dist/inlinemath.external.global.min.js +2 -0
  23. package/dist/inlinemath.external.js +2 -0
  24. package/dist/inlinemath.global.min.js +2 -0
  25. package/dist/inlinemath.js +2 -0
  26. package/dist/lite-calc.js +2 -0
  27. package/dist/lite.external.js +2 -0
  28. package/dist/lite.js +2 -0
  29. package/dist/markdown.external.js +2 -0
  30. package/dist/markdown.js +2 -0
  31. package/fonts/GUST-FONT-LICENSE.txt +30 -0
  32. package/fonts/fonts.css +17 -0
  33. package/fonts/lmroman12-italic.woff2 +0 -0
  34. package/fonts/lmroman12-regular.woff2 +0 -0
  35. package/inlinemath.css +158 -0
  36. package/package.json +95 -4
  37. package/src/calculator.js +6 -0
  38. package/src/cas.d.ts +3 -0
  39. package/src/cas.js +149 -0
  40. package/src/draw.js +72 -0
  41. package/src/edit.js +96 -0
  42. package/src/glyphs.js +40 -0
  43. package/src/html.js +32 -0
  44. package/src/index.d.ts +3 -0
  45. package/src/index.js +17 -0
  46. package/src/layout.js +649 -0
  47. package/src/lite-calc.d.ts +3 -0
  48. package/src/lite-calc.js +15 -0
  49. package/src/lite.d.ts +3 -0
  50. package/src/lite.js +18 -0
  51. package/src/markdown.d.ts +3 -0
  52. package/src/markdown.js +676 -0
  53. package/src/mathzetta.js +18 -0
  54. package/src/select.js +108 -0
  55. package/src/types.d.ts +204 -0
  56. package/src/variables.js +228 -0
  57. package/src/view.js +382 -0
package/src/edit.js ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Editing a part of a drawn expression in place: a text box opens over the part, holding the
3
+ * part as text; Enter puts what was typed back into the expression, Escape (or leaving it with
4
+ * something that doesn't read) changes nothing. Used by View.editable() and by the Markdown
5
+ * pages' textbox variables.
6
+ *
7
+ * The part is written as text by MathZetta's toText, which the caller passes in (import it
8
+ * from 'mathzetta/text'): Inline Math's core imports only 'mathzetta', 'mathzetta/speech' and
9
+ * 'mathzetta/calculate', so a buildless page's import map stays the same.
10
+ */
11
+
12
+ import { bare } from './variables.js';
13
+
14
+ /** A copy of `tree` (ids dropped) with the node at `path` replaced by `part`. */
15
+ export function replaceAt(tree, path, part) {
16
+ const visit = (n, k) => (k === path.length
17
+ ? part
18
+ : { ...n, args: n.args.map((a, j) => (j === path[k] ? visit(a, k + 1) : a)) });
19
+ return bare(visit(tree, 0));
20
+ }
21
+
22
+ /**
23
+ * Open a text box over `element` holding `text`.
24
+ * @param {Element} element the drawn part
25
+ * @param {string} text what the box starts with
26
+ * @param {object} how
27
+ * @param {(text: string) => object} how.parse the typed text → a tree (throws if it doesn't read)
28
+ * @param {(tree: object, text: string) => void} how.commit called with the new part
29
+ * @param {() => void} [how.cancel] called when nothing changed
30
+ * @param {string} [how.label] the box's accessible name
31
+ * @returns {HTMLInputElement|null}
32
+ */
33
+ export function openEditor(element, text, { parse, commit, cancel, label = 'Edit' }) {
34
+ const doc = element.ownerDocument;
35
+ if (!doc || !doc.body) return null;
36
+ const view = doc.defaultView;
37
+ const r = element.getBoundingClientRect();
38
+ const input = doc.createElement('input');
39
+ input.type = 'text';
40
+ input.className = 'mv-edit';
41
+ input.value = text;
42
+ input.spellcheck = false;
43
+ input.autocomplete = 'off';
44
+ input.setAttribute('aria-label', label);
45
+ const fit = () => { input.style.width = `${Math.max(4, input.value.length + 2)}ch`; };
46
+ const font = view.getComputedStyle(element);
47
+ Object.assign(input.style, {
48
+ position: 'absolute',
49
+ left: `${r.left + view.scrollX}px`,
50
+ top: `${r.top + view.scrollY + r.height / 2}px`,
51
+ minWidth: `${Math.max(r.width, 32)}px`,
52
+ fontSize: font.fontSize,
53
+ });
54
+ fit();
55
+ doc.body.appendChild(input);
56
+ view.getSelection?.()?.removeAllRanges(); // the double-click selected a word of the page
57
+ input.focus();
58
+ input.select();
59
+
60
+ let done = false;
61
+ const close = () => {
62
+ done = true;
63
+ input.remove();
64
+ };
65
+ const finish = (keep) => {
66
+ if (done) return true;
67
+ if (!keep || input.value === text) {
68
+ close();
69
+ if (cancel) cancel();
70
+ return true;
71
+ }
72
+ let tree;
73
+ try {
74
+ tree = parse(input.value);
75
+ } catch (error) {
76
+ input.setAttribute('aria-invalid', 'true');
77
+ input.title = error.message;
78
+ return false;
79
+ }
80
+ close();
81
+ commit(tree, input.value);
82
+ return true;
83
+ };
84
+ input.addEventListener('keydown', (event) => {
85
+ event.stopPropagation(); // the view's arrow keys are not for the box
86
+ if (event.key === 'Enter') { event.preventDefault(); finish(true); }
87
+ if (event.key === 'Escape') { event.preventDefault(); finish(false); }
88
+ });
89
+ input.addEventListener('input', () => {
90
+ fit();
91
+ input.removeAttribute('aria-invalid');
92
+ input.removeAttribute('title');
93
+ });
94
+ input.addEventListener('blur', () => { if (!finish(true)) finish(false); });
95
+ return input;
96
+ }
package/src/glyphs.js ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Shapes drawn as inline SVG: parentheses, braces, the radical sign and the
3
+ * big operators. Each is drawn in a 0..1 box with preserveAspectRatio="none"
4
+ * and sized by CSS, so the same path serves every height.
5
+ *
6
+ * Filled shapes (parens, braces) keep their thickness because their width
7
+ * stays fixed while their height stretches. Stroked shapes (√, ∫) use
8
+ * non-scaling strokes so the line weight never changes with size.
9
+ *
10
+ * To restyle a shape, change its path here; nothing else depends on it.
11
+ */
12
+
13
+ export const PATHS = {
14
+ // Crescents: outer curve down, inner curve back up.
15
+ '(': { fill: 'M0.92,0 C0.1,0.2 0.1,0.8 0.92,1 L1,0.975 C0.48,0.75 0.48,0.25 1,0.025 Z' },
16
+ ')': { fill: 'M0.08,0 C0.9,0.2 0.9,0.8 0.08,1 L0,0.975 C0.52,0.75 0.52,0.25 0,0.025 Z' },
17
+ '{': { fill: 'M1,0 C0.45,0 0.5,0.05 0.5,0.2 L0.5,0.38 C0.5,0.46 0.35,0.49 0.05,0.5 C0.35,0.51 0.5,0.54 0.5,0.62 L0.5,0.8 C0.5,0.95 0.45,1 1,1 L1,0.985 C0.68,0.985 0.64,0.95 0.64,0.8 L0.64,0.62 C0.64,0.53 0.55,0.505 0.3,0.5 C0.55,0.495 0.64,0.47 0.64,0.38 L0.64,0.2 C0.64,0.05 0.68,0.015 1,0.015 Z' },
18
+ '}': { fill: 'M0,0 C0.55,0 0.5,0.05 0.5,0.2 L0.5,0.38 C0.5,0.46 0.65,0.49 0.95,0.5 C0.65,0.51 0.5,0.54 0.5,0.62 L0.5,0.8 C0.5,0.95 0.55,1 0,1 L0,0.985 C0.32,0.985 0.36,0.95 0.36,0.8 L0.36,0.62 C0.36,0.53 0.45,0.505 0.7,0.5 C0.45,0.495 0.36,0.47 0.36,0.38 L0.36,0.2 C0.36,0.05 0.32,0.015 0,0.015 Z' },
19
+ // Radical: short tick, long down-stroke, long up-stroke to the top right.
20
+ 'sqrt': { stroke: 'M0,0.62 L0.18,0.55 L0.45,0.97 L0.98,0.015' },
21
+ // \cancel: one stroke, bottom left to top right, stretched over what it crosses out.
22
+ 'cancel': { stroke: 'M0,1 L1,0' },
23
+ // Integral: an S slanted to the right with small hooks at each end.
24
+ 'int': { stroke: 'M0.95,0.09 C0.9,0.01 0.72,0 0.66,0.12 C0.6,0.25 0.42,0.75 0.34,0.88 C0.28,0.99 0.1,0.99 0.05,0.91' },
25
+ 'sum': { fill: 'M0.04,0 L0.94,0 L0.98,0.24 L0.94,0.24 C0.9,0.1 0.8,0.07 0.62,0.07 L0.24,0.07 L0.58,0.5 L0.2,0.93 L0.64,0.93 C0.82,0.93 0.92,0.9 0.95,0.76 L0.99,0.76 L0.94,1 L0.02,1 L0.47,0.47 Z' },
26
+ 'prod': { fill: 'M0,0 L1,0 L1,0.04 L0.88,0.05 L0.88,0.95 L1,0.96 L1,1 L0.62,1 L0.62,0.96 L0.74,0.95 L0.74,0.06 L0.26,0.06 L0.26,0.95 L0.38,0.96 L0.38,1 L0,1 L0,0.96 L0.12,0.95 L0.12,0.05 L0,0.04 Z' },
27
+ };
28
+
29
+ /**
30
+ * An SVG element for a shape. `style` sizes and places it (width, height,
31
+ * vertical-align or absolute position).
32
+ */
33
+ export function svg(name, cls, style = '') {
34
+ const shape = PATHS[name];
35
+ const path = shape.fill
36
+ ? `<path d="${shape.fill}" fill="currentColor"/>`
37
+ : `<path d="${shape.stroke}" fill="none" stroke="currentColor" vector-effect="non-scaling-stroke"/>`;
38
+ const attr = style ? ` style="${style}"` : '';
39
+ return `<svg class="${cls}" viewBox="0 0 1 1" preserveAspectRatio="none" aria-hidden="true"${attr}>${path}</svg>`;
40
+ }
package/src/html.js ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Input → tree → HTML string. No DOM needed, so this runs in Node too. The full build: `{{...}}`
3
+ * blocks are calculated (draw.js is the same without the calculator).
4
+ */
5
+
6
+ import { calculateBlock } from './calculator.js';
7
+ import * as draw from './draw.js';
8
+
9
+ export { layoutOptions } from './draw.js';
10
+
11
+ /**
12
+ * Anything MathZetta reads → a tree with node ids. `{{...}}` blocks in text
13
+ * are calculated first: '3+4={{3+4}}' draws 3 + 4 = 7.
14
+ * @param {string|string[]|object} input expression text, standard postfix, or a tree
15
+ * @param {object} [options] { scope } variable values for `{{...}}` blocks;
16
+ * { blocks } another handler for them (casBlocks() from '@mathzetta/inlinemath/cas' simplifies, solves …)
17
+ */
18
+ export function treeOf(input, options = {}) {
19
+ return draw.treeOf(input, { ...options, blocks: options.blocks ?? calculateBlock });
20
+ }
21
+
22
+ /**
23
+ * @param {string|string[]|object} input
24
+ * @param {object} [options] layout options (see DEFAULTS in layout.js) plus:
25
+ * @param {object|Array} [options.colors] { 'x^2': 'red', 3: '#06c' } or [[target, colour], …]
26
+ * @param {*} [options.highlight] target(s) to mark with the mv-hl class
27
+ * @param {object} [options.scope] variable values for `{{...}}` blocks
28
+ * @returns {string}
29
+ */
30
+ export function toHTML(input, options = {}) {
31
+ return draw.toHTML(input, { ...options, blocks: options.blocks ?? calculateBlock });
32
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ // '@mathzetta/inlinemath' — drawing, the live view, selection. See types.d.ts.
2
+ export type { Input, Node, Target, LayoutOptions, DrawOptions, ViewOptions, ClickInfo } from './types.js';
3
+ export { toHTML, treeOf, layout, DEFAULTS, View, render, shouldAnimate, select, signature, nodeAt, pathOf, numberTree, PATHS } from './types.js';
package/src/index.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Inline Math — draws calculus-level maths as HTML and CSS, from anything
3
+ * MathZetta can read, with every part addressable.
4
+ *
5
+ * import { toHTML, render } from '@mathzetta/inlinemath';
6
+ *
7
+ * toHTML('\\int_0^1 x^2 dx') // a string, works in Node
8
+ * const view = render(div, 'd/dx sin(x^2)') // live: highlight, click, animate
9
+ *
10
+ * Needs inlinemath.css on the page (and optionally fonts/fonts.css).
11
+ */
12
+
13
+ export { toHTML, treeOf } from './html.js';
14
+ export { layout, DEFAULTS } from './layout.js';
15
+ export { View, render, shouldAnimate } from './view.js';
16
+ export { select, signature, nodeAt, pathOf, numberTree } from './select.js';
17
+ export { PATHS } from './glyphs.js';