@mathzetta/inlinemath 0.0.0-stage → 0.1.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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +282 -2
  3. package/dist/cas.external.js +2 -0
  4. package/dist/cas.js +2 -0
  5. package/dist/chunks/calculate-BX9lJ7Ff.js +2 -0
  6. package/dist/chunks/draw-Cm4exbg7.js +2 -0
  7. package/dist/chunks/html-CvuSyhzk.js +2 -0
  8. package/dist/chunks/html-DWtfytjA.js +2 -0
  9. package/dist/chunks/lite-Bj_L6w2g.js +2 -0
  10. package/dist/fonts/GUST-FONT-LICENSE.txt +30 -0
  11. package/dist/fonts/fonts.css +17 -0
  12. package/dist/fonts/lmroman12-italic.woff2 +0 -0
  13. package/dist/fonts/lmroman12-regular.woff2 +0 -0
  14. package/dist/inlinemath-cas.external.global.min.js +2 -0
  15. package/dist/inlinemath-cas.global.min.js +2 -0
  16. package/dist/inlinemath-lite-calc.global.min.js +2 -0
  17. package/dist/inlinemath-lite.external.global.min.js +2 -0
  18. package/dist/inlinemath-lite.global.min.js +2 -0
  19. package/dist/inlinemath.css +127 -0
  20. package/dist/inlinemath.external.global.min.js +2 -0
  21. package/dist/inlinemath.external.js +2 -0
  22. package/dist/inlinemath.global.min.js +2 -0
  23. package/dist/inlinemath.js +2 -0
  24. package/dist/lite-calc.js +2 -0
  25. package/dist/lite.external.js +2 -0
  26. package/dist/lite.js +2 -0
  27. package/dist/markdown.external.js +2 -0
  28. package/dist/markdown.js +2 -0
  29. package/fonts/GUST-FONT-LICENSE.txt +30 -0
  30. package/fonts/fonts.css +17 -0
  31. package/fonts/lmroman12-italic.woff2 +0 -0
  32. package/fonts/lmroman12-regular.woff2 +0 -0
  33. package/inlinemath.css +127 -0
  34. package/package.json +95 -4
  35. package/src/calculator.js +6 -0
  36. package/src/cas.d.ts +3 -0
  37. package/src/cas.js +139 -0
  38. package/src/draw.js +72 -0
  39. package/src/glyphs.js +38 -0
  40. package/src/html.js +32 -0
  41. package/src/index.d.ts +3 -0
  42. package/src/index.js +17 -0
  43. package/src/layout.js +605 -0
  44. package/src/lite-calc.d.ts +3 -0
  45. package/src/lite-calc.js +15 -0
  46. package/src/lite.d.ts +3 -0
  47. package/src/lite.js +18 -0
  48. package/src/markdown.d.ts +3 -0
  49. package/src/markdown.js +361 -0
  50. package/src/mathzetta.js +17 -0
  51. package/src/select.js +108 -0
  52. package/src/types.d.ts +133 -0
  53. package/src/view.js +162 -0
package/src/view.js ADDED
@@ -0,0 +1,162 @@
1
+ /**
2
+ * A live expression on the page: draw it, point at parts of it, react to
3
+ * clicks, step it to the next expression with the unchanged parts gliding
4
+ * into place.
5
+ *
6
+ * const view = render(element, 'x^2 + 2x');
7
+ * view.highlight('2x'); // or an id, path, key, test
8
+ * view.color('x^2', 'crimson');
9
+ * view.on('click', ({ node }) => …);
10
+ * view.set('2x + 2', { animate: true }); // next step
11
+ * view.segments() // [{ text, id }] spoken pieces
12
+ */
13
+
14
+ import { toSpeechSegments } from './mathzetta.js';
15
+ import { treeOf, layoutOptions } from './html.js';
16
+ import { layout } from './layout.js';
17
+ import { select, signature, pathOf } from './select.js';
18
+
19
+ export class View {
20
+ /**
21
+ * @param {Element} element where to draw (its contents are replaced)
22
+ * @param {string|string[]|object} input
23
+ * @param {object} [options] as for toHTML
24
+ */
25
+ constructor(element, input, options = {}) {
26
+ this.element = element;
27
+ this.options = options;
28
+ this.p = options.prefix || 'mv';
29
+ this.handlers = [];
30
+ this.set(input);
31
+ }
32
+
33
+ /**
34
+ * Show a new expression. With { animate: true }, parts that appear in
35
+ * both move from their old place to their new one, and new parts fade in.
36
+ */
37
+ set(input, { animate = false, duration = 600, easing = 'ease-in-out' } = {}) {
38
+ const before = animate && this.tree ? this.snapshot() : null;
39
+ this.tree = treeOf(input, this.options);
40
+ this.element.innerHTML = layout(this.tree, layoutOptions(this.tree, this.options)).html;
41
+ this.root = this.element.firstElementChild;
42
+ if (this.handlers.length) this.root.classList.add(`${this.p}-clickable`);
43
+ if (before) this.morph(before, { duration, easing });
44
+ return this;
45
+ }
46
+
47
+ /** The nodes a target picks out (see select.js for the ways to write one). */
48
+ nodes(target) {
49
+ return select(this.tree, target);
50
+ }
51
+
52
+ /** The elements drawing those nodes. */
53
+ elements(target) {
54
+ return this.nodes(target).map((n) => this.elementOf(n)).filter(Boolean);
55
+ }
56
+
57
+ elementOf(node) {
58
+ return node.id === undefined ? null : this.root.querySelector(`[data-node="${node.id}"]`);
59
+ }
60
+
61
+ /** Add a class (mv-hl by default) to the target's elements. */
62
+ highlight(target, className = `${this.p}-hl`) {
63
+ this.elements(target).forEach((e) => e.classList.add(className));
64
+ return this;
65
+ }
66
+
67
+ /** Remove a highlight class, from the target or everywhere. */
68
+ unhighlight(target = null, className = `${this.p}-hl`) {
69
+ const list = target === null ? this.root.querySelectorAll(`.${className}`) : this.elements(target);
70
+ list.forEach((e) => e.classList.remove(className));
71
+ return this;
72
+ }
73
+
74
+ /** Colour parts; pass null to clear. */
75
+ color(target, colour) {
76
+ this.elements(target).forEach((e) => { e.style.color = colour || ''; });
77
+ return this;
78
+ }
79
+
80
+ /**
81
+ * React to events on parts of the expression. `fn` gets
82
+ * { node, id, path, element, event } for the innermost node under the pointer.
83
+ */
84
+ on(type, fn) {
85
+ const listener = (event) => {
86
+ const element = event.target.closest?.('[data-node]');
87
+ if (!element || !this.root.contains(element)) return;
88
+ const id = Number(element.dataset.node);
89
+ const node = select(this.tree, id)[0];
90
+ fn({ node, id, path: pathOf(this.tree, id), element, event });
91
+ };
92
+ this.element.addEventListener(type, listener);
93
+ this.handlers.push([type, listener]);
94
+ this.root.classList.add(`${this.p}-clickable`);
95
+ return () => this.element.removeEventListener(type, listener);
96
+ }
97
+
98
+ /** The expression read aloud in pieces, each tagged with the node it describes. */
99
+ segments(options = {}) {
100
+ return toSpeechSegments(this.tree, options);
101
+ }
102
+
103
+ /** Light up the node a spoken piece is about (for karaoke-style narration). */
104
+ showSegment(segment, className = `${this.p}-hl`) {
105
+ this.unhighlight(null, className);
106
+ if (segment && segment.id !== undefined) this.highlight(segment.id, className);
107
+ return this;
108
+ }
109
+
110
+ // ---- animation between steps --------------------------------------------
111
+
112
+ /** Where each drawn part is now, grouped by what it looks like. */
113
+ snapshot() {
114
+ const bySignature = new Map();
115
+ const visit = (n) => {
116
+ const e = this.elementOf(n);
117
+ if (e) {
118
+ const sig = signature(n);
119
+ if (!bySignature.has(sig)) bySignature.set(sig, []);
120
+ bySignature.get(sig).push(e.getBoundingClientRect());
121
+ }
122
+ if (n.type === 'op') n.args.forEach(visit);
123
+ };
124
+ visit(this.tree);
125
+ return bySignature;
126
+ }
127
+
128
+ /**
129
+ * Move each part of the new expression that was also in the old one from
130
+ * its old place (largest matching parts first), and fade in the rest.
131
+ */
132
+ morph(before, { duration, easing }) {
133
+ if (typeof Element === 'undefined' || !Element.prototype.animate) return;
134
+ const visit = (n) => {
135
+ const e = this.elementOf(n);
136
+ const old = e && before.get(signature(n));
137
+ if (old && old.length) {
138
+ const from = old.shift();
139
+ const to = e.getBoundingClientRect();
140
+ const dx = from.left - to.left;
141
+ const dy = from.top - to.top;
142
+ const sx = to.width ? from.width / to.width : 1;
143
+ const sy = to.height ? from.height / to.height : 1;
144
+ if (Math.abs(dx) + Math.abs(dy) > 0.5 || Math.abs(sx - 1) + Math.abs(sy - 1) > 0.01) {
145
+ e.animate([
146
+ { transformOrigin: '0 0', transform: `translate(${dx}px, ${dy}px) scale(${sx}, ${sy})` },
147
+ { transformOrigin: '0 0', transform: 'none' },
148
+ ], { duration, easing });
149
+ }
150
+ return;
151
+ }
152
+ if (n.type === 'op') n.args.forEach(visit);
153
+ else if (e) e.animate([{ opacity: 0 }, { opacity: 0 }, { opacity: 1 }], { duration, easing });
154
+ };
155
+ visit(this.tree);
156
+ }
157
+ }
158
+
159
+ /** Draw an expression into an element and return a View for it. */
160
+ export function render(element, input, options = {}) {
161
+ return new View(element, input, options);
162
+ }