mathjson-tree-builder 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.
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/dist/data-model/classify.d.ts +8 -0
- package/dist/data-model/path.d.ts +57 -0
- package/dist/data-model/symbols.d.ts +17 -0
- package/dist/element.d.ts +65 -0
- package/dist/index.d.ts +13 -0
- package/dist/mathjson-tree-builder.js +3720 -0
- package/dist/mathjson-tree-builder.js.map +1 -0
- package/dist/registry/constructs.generated.d.ts +11 -0
- package/dist/registry/curated-args.d.ts +10 -0
- package/dist/registry/display-names.d.ts +10 -0
- package/dist/registry/notation-shape.d.ts +11 -0
- package/dist/registry/notation.d.ts +29 -0
- package/dist/registry/registry.d.ts +54 -0
- package/dist/styles.d.ts +1 -0
- package/dist/types.d.ts +77 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mārtiņš Mednis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# mathjson-tree-builder
|
|
2
|
+
|
|
3
|
+
**mathjson-tree-builder** is the front-end counterpart to
|
|
4
|
+
[mathjson-solver](https://github.com/LongenesisLtd/mathjson-solver): build
|
|
5
|
+
[MathJSON](https://mathlive.io/math-json/) formulas and data-processing
|
|
6
|
+
expressions visually in the browser, evaluate them in your Python backend.
|
|
7
|
+
|
|
8
|
+
Hand-writing MathJSON's array syntax isn't something you can ask a
|
|
9
|
+
non-technical user to do — this framework-agnostic **Custom Element** gives
|
|
10
|
+
them point-and-click building instead, with named external values (form
|
|
11
|
+
fields, survey questions, ...) droppable into any slot.
|
|
12
|
+
|
|
13
|
+
**[Live demo](https://mrtmednis.gitlab.io/mathjson-tree-builder/)**
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
## Install & use
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install mathjson-tree-builder
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<script type="module">
|
|
25
|
+
import "mathjson-tree-builder";
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<mathjson-tree-builder id="builder"></mathjson-tree-builder>
|
|
29
|
+
|
|
30
|
+
<script type="module">
|
|
31
|
+
const el = document.getElementById("builder");
|
|
32
|
+
|
|
33
|
+
el.value = ["Divide", "weight", ["Power", "height", 2]]; // BMI
|
|
34
|
+
|
|
35
|
+
el.symbols = [
|
|
36
|
+
{ id: "weight", label: "Weight (kg)", type: "number" },
|
|
37
|
+
{ id: "height", label: "Height (m)", type: "number" },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
el.addEventListener("change", (e) => {
|
|
41
|
+
console.log(e.detail.value); // the current MathJSON expression,
|
|
42
|
+
// ready to hand to mathjson-solver
|
|
43
|
+
});
|
|
44
|
+
</script>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Works identically in plain HTML, React, Vue, Angular, Svelte — it's a native
|
|
48
|
+
Custom Element with no framework dependency baked into the published bundle
|
|
49
|
+
(Lit is used to *build* it, not required to *use* it).
|
|
50
|
+
|
|
51
|
+
## What it does
|
|
52
|
+
|
|
53
|
+
- Every one of the ~338 constructs mathjson-solver implements is buildable,
|
|
54
|
+
via a generic `Name(arg1, arg2, ...)` fallback with add/remove-argument and
|
|
55
|
+
wrap-in-parent-construct affordances on every node.
|
|
56
|
+
- The common ones get real notation instead of the generic form: arithmetic
|
|
57
|
+
and comparisons as infix (`a + b`, `age > 18`), `Power`/`Sqrt` as
|
|
58
|
+
superscript/radical, `Sum`/`Product` as Σ/Π, set/list membership as
|
|
59
|
+
`age in (1, 2, 3)`.
|
|
60
|
+
- `Constants` bindings let a piece of a formula be named and collapsed, so a
|
|
61
|
+
large formula reads as a handful of names instead of one long expression.
|
|
62
|
+
- `If` supports both of mathjson-solver's shapes — a single condition, or an
|
|
63
|
+
unlimited elseif chain — and any comparison/membership/boolean operator
|
|
64
|
+
can be swapped for another without rebuilding the condition from scratch.
|
|
65
|
+
- A raw-JSON escape hatch for pasting in an expression built elsewhere, or
|
|
66
|
+
anything the tree view doesn't gracefully cover.
|
|
67
|
+
|
|
68
|
+
See [`docs/API.md`](https://gitlab.com/mrtmednis/mathjson-tree-builder/-/blob/main/docs/API.md) for the full public API and a detailed
|
|
69
|
+
rendering-model reference.
|
|
70
|
+
|
|
71
|
+
## Known limitations
|
|
72
|
+
|
|
73
|
+
Two things worth knowing before embedding it: the per-slot menu doesn't yet
|
|
74
|
+
use the browser's Popover API, so a host container with
|
|
75
|
+
`overflow: hidden`/`auto`/`scroll` will clip it; and moving focus away from
|
|
76
|
+
an active node without a click can leave its menu visible slightly longer
|
|
77
|
+
than ideal. See [`docs/API.md`](https://gitlab.com/mrtmednis/mathjson-tree-builder/-/blob/main/docs/API.md) for the full details.
|
|
78
|
+
|
|
79
|
+
Not what you're after if you need a real math-notation input — CortexJS's
|
|
80
|
+
[`mathfield`](https://cortexjs.io/mathfield/) is a mature LaTeX editor and
|
|
81
|
+
solves a different problem than this does; see
|
|
82
|
+
[`CONTRIBUTING.md`](https://gitlab.com/mrtmednis/mathjson-tree-builder/-/blob/main/CONTRIBUTING.md)
|
|
83
|
+
for how the two compare.
|
|
84
|
+
|
|
85
|
+
## Contributing
|
|
86
|
+
|
|
87
|
+
See [`CONTRIBUTING.md`](https://gitlab.com/mrtmednis/mathjson-tree-builder/-/blob/main/CONTRIBUTING.md) for development setup and how the
|
|
88
|
+
construct registry is generated, and
|
|
89
|
+
[`CHANGELOG.md`](https://gitlab.com/mrtmednis/mathjson-tree-builder/-/blob/main/CHANGELOG.md)
|
|
90
|
+
for what's shipped in each release.
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
MIT
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classify a node's current "kind" for rendering/editing purposes. This is
|
|
3
|
+
* the one place that decides literal-vs-symbol-vs-function for a value —
|
|
4
|
+
* see `resolveSymbol` for why a string is ambiguous on its own.
|
|
5
|
+
*/
|
|
6
|
+
import type { MathJsonExpression, SymbolReference } from "../types.js";
|
|
7
|
+
export type NodeKind = "number" | "boolean" | "text" | "symbol" | "function";
|
|
8
|
+
export declare function classifyValue(value: MathJsonExpression, symbols: readonly SymbolReference[]): NodeKind;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic, path-based tree mutation.
|
|
3
|
+
*
|
|
4
|
+
* This is the *only* place that knows how to read/write a node given its
|
|
5
|
+
* path from the root. Node rendering code calls these, and only these —
|
|
6
|
+
* there is deliberately no per-construct-name mutation logic anywhere else
|
|
7
|
+
* (contrast with prior art, which re-derived "where in
|
|
8
|
+
* the parent array does this go" via a giant if/else keyed on construct
|
|
9
|
+
* name).
|
|
10
|
+
*/
|
|
11
|
+
import { type MathJsonExpression, type Path } from "../types.js";
|
|
12
|
+
/** Read the node at `path`, relative to `root`. `path: []` returns `root`. */
|
|
13
|
+
export declare function getAtPath(root: MathJsonExpression, path: Path): MathJsonExpression;
|
|
14
|
+
/**
|
|
15
|
+
* Return a new root with the node at `path` replaced by `value`. Does not
|
|
16
|
+
* mutate `root`. `path: []` replaces the whole root — wrapping the root in
|
|
17
|
+
* a new parent construct (the "add parent node" operation) is therefore
|
|
18
|
+
* the exact same call as wrapping any other node, just with an empty path;
|
|
19
|
+
* callers reassign `el.value` to the result either way.
|
|
20
|
+
*
|
|
21
|
+
* Uses shallow copy-on-the-path-only structural sharing: only the arrays
|
|
22
|
+
* along `path` are cloned, every sibling subtree is reused as-is.
|
|
23
|
+
*/
|
|
24
|
+
export declare function setAtPath(root: MathJsonExpression, path: Path, value: MathJsonExpression): MathJsonExpression;
|
|
25
|
+
/**
|
|
26
|
+
* Append a new argument to the compound expression at `path`.
|
|
27
|
+
* `node` must be `[constructName, ...args]`; the new value is added last.
|
|
28
|
+
*/
|
|
29
|
+
export declare function withArgAdded(node: MathJsonExpression[], value: MathJsonExpression): MathJsonExpression[];
|
|
30
|
+
/**
|
|
31
|
+
* Remove the argument at `argIndex` (an index into the compound array
|
|
32
|
+
* itself, so `1` is the first argument — index `0` is the construct name
|
|
33
|
+
* and is never a valid `argIndex`).
|
|
34
|
+
*/
|
|
35
|
+
export declare function withArgRemoved(node: MathJsonExpression[], argIndex: number): MathJsonExpression[];
|
|
36
|
+
/**
|
|
37
|
+
* Wrap the node currently at `path` in a new parent construct `X`, so it
|
|
38
|
+
* becomes `X`'s first argument: `V` -> `[X, V, ...extraArgs]`. This is the
|
|
39
|
+
* one and only implementation of the "add parent node" operation — it
|
|
40
|
+
* works identically whether `path` addresses the root (`[]`) or any nested
|
|
41
|
+
* node, because it's built directly on `getAtPath`/`setAtPath`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function wrapAtPath(root: MathJsonExpression, path: Path, constructName: string, extraArgs?: MathJsonExpression[]): MathJsonExpression;
|
|
44
|
+
/**
|
|
45
|
+
* Swap the construct name of the compound node at `path`, keeping its
|
|
46
|
+
* arguments unchanged: `[Greater, a, b]` -> `[LessEqual, a, b]`. Meant for
|
|
47
|
+
* switching among arity-compatible siblings (the curated comparison/infix
|
|
48
|
+
* groups in `registry/notation.ts`'s `swapTargets`) — this doesn't check
|
|
49
|
+
* compatibility itself, it's a pure rename of index 0.
|
|
50
|
+
*
|
|
51
|
+
* `reverseFirstTwoArgs` additionally swaps the first two arguments —
|
|
52
|
+
* needed for the one pair in that group with a different calling
|
|
53
|
+
* convention (`Contains`'s collection-first order vs. everything else's
|
|
54
|
+
* value-first order; see `registry/notation.ts`'s `swapNeedsArgReversal`),
|
|
55
|
+
* where keeping the args as-is would silently reverse the meaning.
|
|
56
|
+
*/
|
|
57
|
+
export declare function swapOperator(root: MathJsonExpression, path: Path, newName: string, reverseFirstTwoArgs?: boolean): MathJsonExpression;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Symbol reference resolution.
|
|
3
|
+
*
|
|
4
|
+
* A MathJSON string leaf is ambiguous on its own — it's either a literal
|
|
5
|
+
* string *value* or a bare symbol reference. This editor doesn't decide
|
|
6
|
+
* that itself; it just checks the string against the host-supplied
|
|
7
|
+
* `symbols` list and renders accordingly (a matching id renders as a
|
|
8
|
+
* labeled chip, a non-matching string renders as a plain literal input).
|
|
9
|
+
*/
|
|
10
|
+
import type { SymbolReference } from "../types.js";
|
|
11
|
+
export declare function resolveSymbol(symbols: readonly SymbolReference[], value: string): SymbolReference | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Filter/sort `symbols` for a given search term and optional expected
|
|
14
|
+
* `type` hint (a soft hint, not a hard validation rule — matching
|
|
15
|
+
* entries sort first, non-matching entries are still included).
|
|
16
|
+
*/
|
|
17
|
+
export declare function searchSymbols(symbols: readonly SymbolReference[], query: string, expectedType?: string): SymbolReference[];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<mathjson-tree-builder>` — the custom element.
|
|
3
|
+
*
|
|
4
|
+
* Node rendering is implemented as a recursive private template method
|
|
5
|
+
* (`#renderSlot`) rather than as separate nested custom elements — one
|
|
6
|
+
* shadow root for the whole widget, one component recursively renders
|
|
7
|
+
* itself per node at the template level. This keeps the "every mutation
|
|
8
|
+
* goes through one path-based function" contract simple: every
|
|
9
|
+
* rendering call closes directly over its own `path` and calls `#commit`,
|
|
10
|
+
* with no event-bubbling-across-shadow-boundaries plumbing needed.
|
|
11
|
+
*
|
|
12
|
+
* Rendering is also click-to-edit: an unfocused node renders as plain
|
|
13
|
+
* content (no inputs, no "⋯" menu) — only the currently *active* node
|
|
14
|
+
* (the one that has focus, per `activePath`) shows its editable control
|
|
15
|
+
* and its menu. See the `activePath` state and `#onFocusIn` below.
|
|
16
|
+
*/
|
|
17
|
+
import { LitElement, type PropertyValues, type TemplateResult } from "lit";
|
|
18
|
+
import { type Evaluator, type MathJsonExpression, type SymbolReference } from "./types.js";
|
|
19
|
+
export declare class MathJsonTreeBuilderElement extends LitElement {
|
|
20
|
+
#private;
|
|
21
|
+
static styles: import("lit").CSSResult;
|
|
22
|
+
value: MathJsonExpression;
|
|
23
|
+
symbols: SymbolReference[];
|
|
24
|
+
blacklist: string[];
|
|
25
|
+
evaluate?: Evaluator;
|
|
26
|
+
/**
|
|
27
|
+
* Operand count above which `And`/`Or` switch from the compact inline
|
|
28
|
+
* `a ∧ b ∧ c` form to a vertical stacked list (one operand per line) —
|
|
29
|
+
* the inline form wraps mid-row once it runs out of width, which reads
|
|
30
|
+
* fine for a couple of conditions but ragged for a long chain. A real
|
|
31
|
+
* HTML attribute (not `attribute: false` like the object-typed
|
|
32
|
+
* properties above) since it's a plain number a host page can set
|
|
33
|
+
* declaratively: `<mathjson-tree-builder inline-to-stacked-threshold="5">`.
|
|
34
|
+
*/
|
|
35
|
+
inlineToStackedThreshold: number;
|
|
36
|
+
/**
|
|
37
|
+
* The path (as a `pathKey` string) of the currently focused/active node,
|
|
38
|
+
* or `null` when nothing in this widget has focus. Drives both which
|
|
39
|
+
* node renders its editable control instead of plain text, and which
|
|
40
|
+
* node's "⋯" menu is shown — see the class doc comment.
|
|
41
|
+
*/
|
|
42
|
+
private activePath;
|
|
43
|
+
/** Raw-JSON escape hatch: true while showing the textarea instead of the tree. */
|
|
44
|
+
private jsonMode;
|
|
45
|
+
/** The textarea's current (possibly invalid, uncommitted) content while in JSON mode. */
|
|
46
|
+
private jsonDraft;
|
|
47
|
+
/** Parse/shape error for `jsonDraft`, or `null` when it's valid and already committed. */
|
|
48
|
+
private jsonError;
|
|
49
|
+
/**
|
|
50
|
+
* Path keys of explicitly expanded `Constants` bindings — purely
|
|
51
|
+
* a view concern, never touches `value`. Absent = collapsed, so a big
|
|
52
|
+
* formula reads as just its constant names until you open one — the
|
|
53
|
+
* point of naming a piece of it in the first place.
|
|
54
|
+
*/
|
|
55
|
+
private expandedConstants;
|
|
56
|
+
connectedCallback(): void;
|
|
57
|
+
disconnectedCallback(): void;
|
|
58
|
+
protected updated(changed: PropertyValues): void;
|
|
59
|
+
protected render(): TemplateResult;
|
|
60
|
+
}
|
|
61
|
+
declare global {
|
|
62
|
+
interface HTMLElementTagNameMap {
|
|
63
|
+
"mathjson-tree-builder": MathJsonTreeBuilderElement;
|
|
64
|
+
}
|
|
65
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entry point. Importing this module registers
|
|
3
|
+
* `<mathjson-tree-builder>` via `customElements.define` (mirroring
|
|
4
|
+
* MathfieldElement's own integration pattern) as a side effect of
|
|
5
|
+
* importing `./element.js`.
|
|
6
|
+
*/
|
|
7
|
+
export { MathJsonTreeBuilderElement, } from "./element.js";
|
|
8
|
+
export type { ArgSpec, ConstructDescriptor, EvalResult, Evaluator, MathJsonExpression, Path, SymbolReference, } from "./types.js";
|
|
9
|
+
export { isCompound, isValidMathJson } from "./types.js";
|
|
10
|
+
export { getAtPath, setAtPath, swapOperator, withArgAdded, withArgRemoved, wrapAtPath, } from "./data-model/path.js";
|
|
11
|
+
export { resolveSymbol, searchSymbols } from "./data-model/symbols.js";
|
|
12
|
+
export { allConstructs, argBounds, categoryLabel, defaultArgsFor, displayName, getConstruct, getNotation, isComparisonLike, operatorGlyph, searchConstructs, swapNeedsArgReversal, swapTargets, CATEGORY_LABELS, } from "./registry/registry.js";
|
|
13
|
+
export type { NotationSpec } from "./registry/notation.js";
|