@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.
- package/LICENSE +21 -0
- package/README.md +326 -2
- package/dist/cas.external.js +2 -0
- package/dist/cas.js +2 -0
- package/dist/chunks/calculate-Cz87Lt0N.js +2 -0
- package/dist/chunks/draw-Bev17BJJ.js +2 -0
- package/dist/chunks/edit-C65xXzo8.js +2 -0
- package/dist/chunks/edit-M68aRA5o.js +2 -0
- package/dist/chunks/html-BimAqn7x.js +2 -0
- package/dist/chunks/html-CupluJSh.js +2 -0
- package/dist/chunks/lite-3Fb-OHbt.js +2 -0
- package/dist/fonts/GUST-FONT-LICENSE.txt +30 -0
- package/dist/fonts/fonts.css +17 -0
- package/dist/fonts/lmroman12-italic.woff2 +0 -0
- package/dist/fonts/lmroman12-regular.woff2 +0 -0
- package/dist/inlinemath-cas.external.global.min.js +2 -0
- package/dist/inlinemath-cas.global.min.js +2 -0
- package/dist/inlinemath-lite-calc.global.min.js +2 -0
- package/dist/inlinemath-lite.external.global.min.js +2 -0
- package/dist/inlinemath-lite.global.min.js +2 -0
- package/dist/inlinemath.css +158 -0
- package/dist/inlinemath.external.global.min.js +2 -0
- package/dist/inlinemath.external.js +2 -0
- package/dist/inlinemath.global.min.js +2 -0
- package/dist/inlinemath.js +2 -0
- package/dist/lite-calc.js +2 -0
- package/dist/lite.external.js +2 -0
- package/dist/lite.js +2 -0
- package/dist/markdown.external.js +2 -0
- package/dist/markdown.js +2 -0
- package/fonts/GUST-FONT-LICENSE.txt +30 -0
- package/fonts/fonts.css +17 -0
- package/fonts/lmroman12-italic.woff2 +0 -0
- package/fonts/lmroman12-regular.woff2 +0 -0
- package/inlinemath.css +158 -0
- package/package.json +95 -4
- package/src/calculator.js +6 -0
- package/src/cas.d.ts +3 -0
- package/src/cas.js +149 -0
- package/src/draw.js +72 -0
- package/src/edit.js +96 -0
- package/src/glyphs.js +40 -0
- package/src/html.js +32 -0
- package/src/index.d.ts +3 -0
- package/src/index.js +17 -0
- package/src/layout.js +649 -0
- package/src/lite-calc.d.ts +3 -0
- package/src/lite-calc.js +15 -0
- package/src/lite.d.ts +3 -0
- package/src/lite.js +18 -0
- package/src/markdown.d.ts +3 -0
- package/src/markdown.js +676 -0
- package/src/mathzetta.js +18 -0
- package/src/select.js +108 -0
- package/src/types.d.ts +204 -0
- package/src/variables.js +228 -0
- 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';
|