kmap-term-tree 0.9.21 → 1.0.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 (37) hide show
  1. package/demo/index.html +124 -10
  2. package/demo/kmap.html +64 -0
  3. package/dist/src/KmapTermTree.d.ts +37 -46
  4. package/dist/src/KmapTermTree.js +641 -216
  5. package/dist/src/KmapTermTree.js.map +1 -1
  6. package/dist/src/asciimath-parser.d.ts +17 -0
  7. package/dist/src/asciimath-parser.js +472 -0
  8. package/dist/src/asciimath-parser.js.map +1 -0
  9. package/dist/src/index.d.ts +2 -0
  10. package/dist/src/index.js +2 -0
  11. package/dist/src/index.js.map +1 -1
  12. package/dist/src/katex-css.d.ts +2 -0
  13. package/dist/src/katex-css.js +7 -0
  14. package/dist/src/katex-css.js.map +1 -0
  15. package/dist/src/model.d.ts +30 -0
  16. package/dist/src/model.js +26 -0
  17. package/dist/src/model.js.map +1 -0
  18. package/dist/src/parser.js.map +1 -1
  19. package/dist/src/tokenizer.js.map +1 -1
  20. package/dist/stories/index.stories.d.ts +20 -0
  21. package/dist/stories/index.stories.js +26 -2
  22. package/dist/stories/index.stories.js.map +1 -1
  23. package/dist/test/asciimath-parser.test.d.ts +1 -0
  24. package/dist/test/asciimath-parser.test.js +45 -0
  25. package/dist/test/asciimath-parser.test.js.map +1 -0
  26. package/dist/test/kmap-term-tree.test.js +45 -8
  27. package/dist/test/kmap-term-tree.test.js.map +1 -1
  28. package/dist/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +16 -50
  30. package/src/KmapTermTree.ts +675 -230
  31. package/src/asciimath-parser.ts +554 -0
  32. package/src/index.ts +2 -0
  33. package/src/katex-css.ts +7 -0
  34. package/src/model.ts +57 -0
  35. package/stories/index.stories.ts +35 -2
  36. package/test/asciimath-parser.test.ts +50 -0
  37. package/test/kmap-term-tree.test.ts +66 -7
@@ -1,273 +1,698 @@
1
1
  import { __decorate } from "tslib";
2
2
  import { css, html, LitElement, svg } from 'lit';
3
3
  import { property, state, query } from 'lit/decorators.js';
4
- import { ifDefined } from 'lit/directives/if-defined.js';
5
- import { Parser } from "./parser.js";
6
- import { Token } from "./tokenizer.js";
4
+ import katex from 'katex';
5
+ import { katexStyles } from './katex-css.js';
6
+ import { parseAsciiMath, astToAnnotatedLatex } from './asciimath-parser.js';
7
7
  export class KmapTermTree extends LitElement {
8
8
  constructor() {
9
9
  super(...arguments);
10
- this.parser = new Parser();
11
- this.tension = 0;
12
- }
13
- // @ts-ignore
14
- updateSlotted({ target }) {
15
- // @ts-ignore
16
- this.term = target.assignedNodes().map((n) => n.textContent).join('');
10
+ this.tension = 0.5;
11
+ this.showLabels = false;
12
+ this.showDetails = true;
13
+ this.flatNodes = [];
14
+ this.edges = [];
15
+ this.activeSubtreeIds = new Set();
16
+ this.treeHeight = 100;
17
+ this.lastObservedWidth = 0;
18
+ this.animationTimers = [];
19
+ this.onAnimationOrTransitionEnd = () => {
20
+ this.triggerLayoutUpdate();
21
+ };
17
22
  }
18
- update(_changedProperties) {
19
- if (_changedProperties.has("term")) {
20
- if (this.term !== undefined) {
21
- this.tokens = Token.tokenize(this.term);
22
- this.termNode = this.parser.parse(this.tokens);
23
- let map = {};
24
- this.maxDepths = this.calcDepths(this.termNode, map);
25
- let connections = [];
26
- this.termNode.breadthFirst((n) => {
27
- if (n.leftChildNode !== undefined)
28
- connections.push({ from: n.token.id, to: n.leftChildNode.token.id });
29
- if (n.rightChildNode !== undefined)
30
- connections.push({ from: n.token.id, to: n.rightChildNode.token.id });
31
- });
32
- this.depths = map;
33
- this.connections = connections;
23
+ connectedCallback() {
24
+ super.connectedCallback();
25
+ this.resizeObserver = new ResizeObserver(entries => {
26
+ for (const entry of entries) {
27
+ const width = entry.contentRect.width;
28
+ if (Math.abs(width - this.lastObservedWidth) > 0.5) {
29
+ this.lastObservedWidth = width;
30
+ this.triggerLayoutUpdate();
31
+ }
34
32
  }
33
+ });
34
+ window.addEventListener('animationend', this.onAnimationOrTransitionEnd);
35
+ window.addEventListener('transitionend', this.onAnimationOrTransitionEnd);
36
+ if (typeof document !== 'undefined' && document.fonts) {
37
+ document.fonts.ready.then(() => {
38
+ if (this.isConnected) {
39
+ this.recalculateLayout();
40
+ }
41
+ });
35
42
  }
36
- super.update(_changedProperties);
37
- }
38
- calcDepths(node, map) {
39
- let depth = 0;
40
- if (node.leftChildNode)
41
- depth = Math.max(depth, this.calcDepths(node.leftChildNode, map) + 1);
42
- if (node.rightChildNode)
43
- depth = Math.max(depth, this.calcDepths(node.rightChildNode, map) + 1);
44
- map[node.token.id] = depth;
45
- return depth;
46
43
  }
47
- render() {
48
- return html `
49
- ${this.connections ? html `
50
- <kmap-term-tree-edges .tension="${this.tension}"></kmap-term-tree-edges>
51
- ` : ''}
52
- ${!this.tokens ? '' : html `
53
- <div class="tokens" id="tokens">
54
- ${this.tokens.map((t) => html `
55
- <div class="token" id="${t.id}">${KmapTermTree._prettify(t.value)}</div>
56
- `)}
57
- </div>
58
- <div class="notokens">
59
- ${this.tokens.map((t) => html `
60
- <div>${KmapTermTree._prettify(t.value)}</div>
61
- `)}
62
- </div>
63
- `}
64
- ${this.tokens && this.depths ? html `
65
- <div class="nodes">
66
- ${this.tokens.map((t) => html `
67
- <div class="node" id="n${t.id}" depth="${ifDefined(this.depths ? this.depths[t.id] : undefined)}">${KmapTermTree._prettify(t.value)}</div>
68
- `)}
69
- </div>
70
- ` : ''}
71
- <div hidden>
72
- <slot @slotchange=${this.updateSlotted}></slot>
73
- </div>
74
- `;
44
+ disconnectedCallback() {
45
+ var _a;
46
+ if (this.resizeFrameId) {
47
+ cancelAnimationFrame(this.resizeFrameId);
48
+ }
49
+ this.clearAnimationTimers();
50
+ window.removeEventListener('animationend', this.onAnimationOrTransitionEnd);
51
+ window.removeEventListener('transitionend', this.onAnimationOrTransitionEnd);
52
+ (_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
53
+ super.disconnectedCallback();
75
54
  }
76
- async updated(_changedProperties) {
77
- if (_changedProperties.has("connections")) {
78
- await this.updateComplete;
79
- if (this.tokensElement && this.maxDepths) {
80
- let tokensHeight = this.tokensElement.offsetHeight;
81
- //console.log(this.maxDepths)
82
- //console.log(tokensHeight + "px")
83
- this.style.setProperty("--kmap-term-tree-max-depth", "" + this.maxDepths);
84
- this.style.setProperty("--kmap-term-tree-vertical-distance", tokensHeight + "px");
85
- this.style.height = "calc(3px + var(--kmap-term-tree-vertical-distance) * (var(--kmap-term-tree-max-depth)*1.5 + 1))";
86
- //console.log(this.style.height);
55
+ triggerLayoutUpdate() {
56
+ if (this.resizeFrameId) {
57
+ cancelAnimationFrame(this.resizeFrameId);
58
+ }
59
+ this.resizeFrameId = requestAnimationFrame(() => {
60
+ if (this.isConnected) {
61
+ this.recalculateLayout();
87
62
  }
88
- let edges = [];
89
- if (this.edgesElement && this.connections) {
90
- for (const connection of this.connections) {
91
- let edge = this._connect(connection);
92
- if (edge)
93
- edges.push(edge);
63
+ });
64
+ }
65
+ watchAncestorAnimations() {
66
+ let curr = this;
67
+ const animations = [];
68
+ while (curr) {
69
+ if (typeof curr.getAnimations === 'function') {
70
+ for (const anim of curr.getAnimations()) {
71
+ if (anim.playState === 'running' || anim.pending) {
72
+ animations.push(anim);
73
+ }
94
74
  }
95
- this.edgesElement.edges = edges;
96
75
  }
76
+ const root = curr.getRootNode();
77
+ if (root && 'host' in root) {
78
+ curr = root.host;
79
+ }
80
+ else {
81
+ curr = curr.parentElement;
82
+ }
83
+ }
84
+ if (animations.length > 0) {
85
+ Promise.all(animations.map(a => a.finished.catch(() => { }))).then(() => {
86
+ if (this.isConnected) {
87
+ this.recalculateLayout();
88
+ }
89
+ });
97
90
  }
98
91
  }
99
- _connect(connection) {
100
- let from = this.shadowRoot.getElementById("n" + connection.from);
101
- let to = this.shadowRoot.getElementById("n" + connection.to);
102
- if (!from || !to)
103
- return;
104
- let frompos = this._findAbsolutePosition(from);
105
- let topos = this._findAbsolutePosition(to);
106
- return { from: frompos, to: topos };
92
+ scheduleAnimationCheck() {
93
+ this.clearAnimationTimers();
94
+ this.watchAncestorAnimations();
95
+ const t1 = window.setTimeout(() => {
96
+ if (!this.isConnected)
97
+ return;
98
+ this.watchAncestorAnimations();
99
+ this.recalculateLayout();
100
+ }, 50);
101
+ const t2 = window.setTimeout(() => {
102
+ if (!this.isConnected)
103
+ return;
104
+ this.recalculateLayout();
105
+ }, 320);
106
+ const t3 = window.setTimeout(() => {
107
+ if (!this.isConnected)
108
+ return;
109
+ this.recalculateLayout();
110
+ }, 450);
111
+ this.animationTimers.push(t1, t2, t3);
107
112
  }
108
- _findAbsolutePosition(element) {
109
- var x = 0;
110
- var y = 0;
111
- var el = element;
112
- for (; el !== this; el = el.offsetParent) {
113
- x += el.offsetLeft;
114
- y += el.offsetTop;
113
+ clearAnimationTimers() {
114
+ for (const t of this.animationTimers) {
115
+ clearTimeout(t);
115
116
  }
116
- return {
117
- x: x,
118
- y: y,
119
- width: element.offsetWidth,
120
- height: element.offsetHeight,
121
- };
117
+ this.animationTimers = [];
122
118
  }
123
- static _prettify(value) {
124
- switch (value) {
125
- case '*':
126
- return "·";
127
- case '-':
128
- return "−"; // math minus
129
- default:
130
- return value;
119
+ // Support slotted content: <kmap-term-tree>2a+3b</kmap-term-tree>
120
+ updateSlotted(event) {
121
+ const slot = event.target;
122
+ const text = slot
123
+ .assignedNodes()
124
+ .map(n => n.textContent)
125
+ .join('')
126
+ .trim();
127
+ if (text && text !== this.term) {
128
+ this.term = text;
131
129
  }
132
130
  }
133
- }
134
- // language=CSS
135
- KmapTermTree.styles = css `
136
- :host {
137
- display: block;
138
- color: var(--kmap-term-tree-text-color, #000);
139
- position: relative;
140
- }
141
- .tokens, .nodes, kmap-term-tree-edges {
142
- position: absolute;
143
- }
144
- .tokens, .nodes {
145
- white-space: nowrap;
146
- }
147
- .notokens {
148
- white-space: nowrap;
149
- height: 0px;
131
+ willUpdate(changedProperties) {
132
+ if (changedProperties.has('term') && this.term !== undefined) {
133
+ this.parseInput(this.term);
134
+ }
150
135
  }
151
- .notokens div {
152
- display: inline-block;
136
+ parseInput(termStr) {
137
+ try {
138
+ this.error = undefined;
139
+ const cleanTerm = termStr.trim();
140
+ if (!cleanTerm) {
141
+ this.ast = undefined;
142
+ this.flatNodes = [];
143
+ this.edges = [];
144
+ return;
145
+ }
146
+ this.ast = parseAsciiMath(cleanTerm);
147
+ this.activeNodeId = this.ast.id;
148
+ this.activeSubtreeIds = this.collectSubtreeIds(this.ast);
149
+ }
150
+ catch (e) {
151
+ this.error = e.message || 'Fehler beim Parsen des Terms';
152
+ this.ast = undefined;
153
+ this.flatNodes = [];
154
+ this.edges = [];
155
+ }
153
156
  }
154
- kmap-term-tree-edges {
155
- width: 100%;
156
- height: 100%;
157
+ async updated(changedProperties) {
158
+ if (changedProperties.has('term') || changedProperties.has('ast')) {
159
+ this.renderKaTeX();
160
+ await this.updateComplete;
161
+ this.recalculateLayout();
162
+ if (this.treeCanvasElement && this.resizeObserver) {
163
+ this.resizeObserver.observe(this.treeCanvasElement);
164
+ }
165
+ this.scheduleAnimationCheck();
166
+ }
157
167
  }
158
- .token {
159
- display: inline-block;
168
+ renderKaTeX() {
169
+ if (!this.formulaElement || !this.ast)
170
+ return;
171
+ try {
172
+ const latex = astToAnnotatedLatex(this.ast);
173
+ katex.render(latex, this.formulaElement, {
174
+ trust: true,
175
+ strict: false,
176
+ throwOnError: false,
177
+ });
178
+ }
179
+ catch (err) {
180
+ this.error = err.message;
181
+ }
160
182
  }
161
- .token, .node {
162
- display: inline-block;
163
- position: relative;
164
- background-color: var(--kmap-term-tree-background-color, white);
183
+ recalculateLayout() {
184
+ if (!this.ast ||
185
+ !this.formulaElement ||
186
+ !this.treeCanvasElement ||
187
+ !this.shadowRoot)
188
+ return;
189
+ const canvasRect = this.treeCanvasElement.getBoundingClientRect();
190
+ const offsetWidth = this.treeCanvasElement.offsetWidth;
191
+ const offsetHeight = this.treeCanvasElement.offsetHeight;
192
+ if (canvasRect.width === 0 || offsetWidth === 0)
193
+ return;
194
+ this.lastObservedWidth = offsetWidth;
195
+ const scaleX = canvasRect.width / offsetWidth;
196
+ const scaleY = offsetHeight > 0 ? canvasRect.height / offsetHeight : scaleX;
197
+ if (scaleX < 0.01 || scaleY < 0.01)
198
+ return;
199
+ const flatNodes = [];
200
+ const nodeMap = new Map();
201
+ // 1. Collect all nodes and measure leaf positions from KaTeX DOM elements
202
+ const collectAndPositionLeaves = (node) => {
203
+ nodeMap.set(node.id, node);
204
+ if (node.children.length === 0) {
205
+ // Leaf node: measure rendered KaTeX span
206
+ const el = this.shadowRoot.getElementById(node.id);
207
+ if (el) {
208
+ const rect = el.getBoundingClientRect();
209
+ node.x = (rect.left - canvasRect.left + rect.width / 2) / scaleX;
210
+ node.y = (rect.bottom - canvasRect.top) / scaleY;
211
+ node.width = rect.width / scaleX;
212
+ node.height = rect.height / scaleY;
213
+ }
214
+ else {
215
+ node.x = offsetWidth / 2;
216
+ node.y = 20;
217
+ }
218
+ }
219
+ else {
220
+ flatNodes.push(node);
221
+ for (const child of node.children) {
222
+ collectAndPositionLeaves(child);
223
+ }
224
+ }
225
+ };
226
+ collectAndPositionLeaves(this.ast);
227
+ // 2. Position inner nodes bottom-up based on children coordinates
228
+ const formulaRect = this.formulaElement.getBoundingClientRect();
229
+ const formulaBottomRel = (formulaRect.bottom - canvasRect.top) / scaleY;
230
+ const initialGap = 34; // distance from formula bottom to level 1 operators
231
+ const rowHeight = 40; // distance between tree levels
232
+ const positionInnerNodes = (node) => {
233
+ var _a, _b, _c;
234
+ for (const child of node.children) {
235
+ if (child.children.length > 0) {
236
+ positionInnerNodes(child);
237
+ }
238
+ }
239
+ if (node.children.length > 0) {
240
+ if (node.type === 'function' || node.isUnary) {
241
+ // Center under the full function / unary expression: e.g. sin(arg) or -arg
242
+ const el = this.shadowRoot.getElementById(node.id);
243
+ if (el) {
244
+ const rect = el.getBoundingClientRect();
245
+ node.x = (rect.left - canvasRect.left + rect.width / 2) / scaleX;
246
+ }
247
+ else {
248
+ node.x = (_a = node.children[0].x) !== null && _a !== void 0 ? _a : 0;
249
+ }
250
+ }
251
+ else {
252
+ const first = node.children[0];
253
+ const last = node.children[node.children.length - 1];
254
+ node.x = (((_b = first.x) !== null && _b !== void 0 ? _b : 0) + ((_c = last.x) !== null && _c !== void 0 ? _c : 0)) / 2;
255
+ }
256
+ node.y = formulaBottomRel + initialGap + (node.depth - 1) * rowHeight;
257
+ }
258
+ };
259
+ positionInnerNodes(this.ast);
260
+ // 3. Build edges between parent nodes and their children
261
+ const edges = [];
262
+ for (const node of flatNodes) {
263
+ if (node.x === undefined || node.y === undefined)
264
+ continue;
265
+ for (const child of node.children) {
266
+ if (child.x === undefined || child.y === undefined)
267
+ continue;
268
+ edges.push({
269
+ fromNodeId: child.id,
270
+ toNodeId: node.id,
271
+ fromX: child.x,
272
+ fromY: child.y + (child.depth > 0 ? 14 : 2),
273
+ toX: node.x,
274
+ toY: node.y - 14,
275
+ });
276
+ }
277
+ }
278
+ const maxDepth = this.ast.depth;
279
+ this.treeHeight = Math.max(60, formulaBottomRel + initialGap + (maxDepth - 1) * rowHeight + 24);
280
+ this.flatNodes = flatNodes;
281
+ this.edges = edges;
282
+ this.updateHighlightRect(this.activeNodeId);
283
+ this.requestUpdate();
165
284
  }
166
- .node {
167
- display: inline-block;
168
- position: relative;
169
- border: 1px solid var(--kmap-term-tree-border-color, #005b9f);
170
- padding: 0px 4px;
171
- border-radius: 1em;
172
- margin: -1px -5px;
285
+ collectSubtreeIds(node) {
286
+ const ids = new Set();
287
+ const traverse = (n) => {
288
+ ids.add(n.id);
289
+ for (const c of n.children)
290
+ traverse(c);
291
+ };
292
+ traverse(node);
293
+ return ids;
173
294
  }
174
- .node[depth="0"], .node:not([depth]) {
175
- visibility: hidden;
295
+ onNodeHover(node, enter) {
296
+ if (!enter) {
297
+ // Revert highlight to root node
298
+ if (this.ast) {
299
+ this.setActiveNode(this.ast);
300
+ }
301
+ return;
302
+ }
303
+ this.setActiveNode(node);
176
304
  }
177
- .node[depth="1"] {
178
- top: calc(var(--kmap-term-tree-vertical-distance, 1.5em) * 1.5);
305
+ setActiveNode(node) {
306
+ this.activeNodeId = node.id;
307
+ this.activeSubtreeIds = this.collectSubtreeIds(node);
308
+ this.updateHighlightRect(node.id);
309
+ this.requestUpdate();
179
310
  }
180
- .node[depth="2"] {
181
- top: calc(var(--kmap-term-tree-vertical-distance, 1.5em) * 3);
311
+ findNode(root, id) {
312
+ if (!root)
313
+ return undefined;
314
+ if (root.id === id)
315
+ return root;
316
+ for (const child of root.children) {
317
+ const found = this.findNode(child, id);
318
+ if (found)
319
+ return found;
320
+ }
321
+ return undefined;
182
322
  }
183
- .node[depth="3"] {
184
- top: calc(var(--kmap-term-tree-vertical-distance, 1.5em) * 4.5);
323
+ getNodeVisualBounds(node) {
324
+ if (!this.shadowRoot)
325
+ return null;
326
+ let minTop = Infinity;
327
+ let maxBottom = -Infinity;
328
+ let minLeft = Infinity;
329
+ let maxRight = -Infinity;
330
+ const collectBounds = (n) => {
331
+ const el = this.shadowRoot.getElementById(n.id);
332
+ if (el) {
333
+ const r = el.getBoundingClientRect();
334
+ if (r.width > 0 && r.height > 0) {
335
+ minTop = Math.min(minTop, r.top);
336
+ maxBottom = Math.max(maxBottom, r.bottom);
337
+ minLeft = Math.min(minLeft, r.left);
338
+ maxRight = Math.max(maxRight, r.right);
339
+ }
340
+ }
341
+ for (const child of n.children) {
342
+ collectBounds(child);
343
+ }
344
+ };
345
+ collectBounds(node);
346
+ if (minTop === Infinity)
347
+ return null;
348
+ return { top: minTop, bottom: maxBottom, left: minLeft, right: maxRight };
185
349
  }
186
- .node[depth="4"] {
187
- top: calc(var(--kmap-term-tree-vertical-distance, 1.5em) * 6);
350
+ updateHighlightRect(nodeId) {
351
+ if (!nodeId || !this.shadowRoot || !this.treeCanvasElement || !this.ast) {
352
+ this.highlightRect = undefined;
353
+ return;
354
+ }
355
+ const node = this.findNode(this.ast, nodeId);
356
+ if (!node) {
357
+ this.highlightRect = undefined;
358
+ return;
359
+ }
360
+ const bounds = this.getNodeVisualBounds(node);
361
+ if (!bounds) {
362
+ this.highlightRect = undefined;
363
+ return;
364
+ }
365
+ const canvasRect = this.treeCanvasElement.getBoundingClientRect();
366
+ const offsetWidth = this.treeCanvasElement.offsetWidth;
367
+ const offsetHeight = this.treeCanvasElement.offsetHeight;
368
+ if (canvasRect.width === 0 || offsetWidth === 0) {
369
+ this.highlightRect = undefined;
370
+ return;
371
+ }
372
+ const scaleX = canvasRect.width / offsetWidth;
373
+ const scaleY = offsetHeight > 0 ? canvasRect.height / offsetHeight : scaleX;
374
+ if (scaleX < 0.01 || scaleY < 0.01) {
375
+ this.highlightRect = undefined;
376
+ return;
377
+ }
378
+ const paddingX = 4;
379
+ const paddingY = 4;
380
+ this.highlightRect = {
381
+ left: (bounds.left - canvasRect.left) / scaleX - paddingX,
382
+ top: (bounds.top - canvasRect.top) / scaleY - paddingY,
383
+ width: (bounds.right - bounds.left) / scaleX + paddingX * 2,
384
+ height: (bounds.bottom - bounds.top) / scaleY + paddingY * 2,
385
+ };
188
386
  }
189
- .node[depth="5"] {
190
- top: calc(var(--kmap-term-tree-vertical-distance, 1.5em) * 7.5);
387
+ renderPath(edge) {
388
+ const delta = (edge.toY - edge.fromY) * this.tension;
389
+ const hx1 = edge.fromX;
390
+ const hy1 = edge.fromY + delta;
391
+ const hx2 = edge.toX;
392
+ const hy2 = edge.toY - delta;
393
+ return `M ${edge.fromX} ${edge.fromY} C ${hx1} ${hy1} ${hx2} ${hy2} ${edge.toX} ${edge.toY}`;
191
394
  }
192
- .node[depth="6"] {
193
- top: calc(var(--kmap-term-tree-vertical-distance, 1.5em) * 9);
395
+ render() {
396
+ var _a;
397
+ const activeNode = this.flatNodes.find(n => n.id === this.activeNodeId) || this.ast;
398
+ const isRoot = (activeNode === null || activeNode === void 0 ? void 0 : activeNode.id) === ((_a = this.ast) === null || _a === void 0 ? void 0 : _a.id);
399
+ return html `
400
+ <div class="container">
401
+ <!-- Canvas area with formula, edges, and operator nodes sharing the same coordinate space -->
402
+ <div class="tree-canvas" style="height: ${this.treeHeight}px">
403
+ ${this.highlightRect
404
+ ? html `
405
+ <div
406
+ class="highlight-overlay"
407
+ style="left: ${this.highlightRect.left}px; top: ${this
408
+ .highlightRect.top}px; width: ${this.highlightRect
409
+ .width}px; height: ${this.highlightRect.height}px;"
410
+ ></div>
411
+ `
412
+ : ''}
413
+
414
+ <div class="formula-wrapper">
415
+ <div id="formula"></div>
416
+ </div>
417
+
418
+ ${this.error
419
+ ? html `<div class="error-msg">${this.error}</div>`
420
+ : html `
421
+ <svg class="edges-svg">
422
+ ${this.edges.map(edge => svg `
423
+ <path
424
+ class="edge-path ${this.activeSubtreeIds.has(edge.fromNodeId) &&
425
+ this.activeSubtreeIds.has(edge.toNodeId)
426
+ ? 'highlighted'
427
+ : ''}"
428
+ d="${this.renderPath(edge)}"
429
+ />
430
+ `)}
431
+ </svg>
432
+
433
+ ${this.flatNodes.map(node => html `
434
+ <div
435
+ class="tree-node ${this.activeNodeId === node.id
436
+ ? 'active'
437
+ : ''}"
438
+ style="left: ${node.x}px; top: ${node.y}px;"
439
+ @mouseenter=${() => this.onNodeHover(node, true)}
440
+ @mouseleave=${() => this.onNodeHover(node, false)}
441
+ @click=${() => this.setActiveNode(node)}
442
+ >
443
+ <div class="node-badge" title="${node.label}">
444
+ ${node.symbol}
445
+ </div>
446
+ ${this.showLabels
447
+ ? html `<span class="node-label">${node.label}</span>`
448
+ : ''}
449
+ </div>
450
+ `)}
451
+ `}
452
+ </div>
453
+
454
+ <!-- Didactic info box -->
455
+ ${this.showDetails && activeNode
456
+ ? html `
457
+ <div class="info-bar">
458
+ <span>${isRoot ? 'Hauptrechenart:' : 'Teilterm:'}</span>
459
+ <span class="info-badge">${activeNode.label}</span>
460
+ <span style="color: #64748b">
461
+ ${activeNode.children.length === 2
462
+ ? `(verknüpft 2 Teilterme mit ${activeNode.symbol})`
463
+ : activeNode.isUnary
464
+ ? `(wirkt auf ${activeNode.children[0].label || 'Ausdruck'})`
465
+ : ''}
466
+ </span>
467
+ </div>
468
+ `
469
+ : ''}
470
+
471
+ <div hidden>
472
+ <slot @slotchange=${this.updateSlotted}></slot>
473
+ </div>
474
+ </div>
475
+ `;
194
476
  }
195
- `;
477
+ }
478
+ KmapTermTree.styles = [
479
+ katexStyles,
480
+ css `
481
+ :host {
482
+ display: block;
483
+ position: relative;
484
+ font-family: var(
485
+ --kmap-term-tree-font-family,
486
+ system-ui,
487
+ -apple-system,
488
+ sans-serif
489
+ );
490
+ color: var(--kmap-term-tree-text-color, #1f2937);
491
+ user-select: none;
492
+ width: 100%;
493
+ }
494
+
495
+ .container {
496
+ position: relative;
497
+ display: flex;
498
+ flex-direction: column;
499
+ align-items: center;
500
+ width: 100%;
501
+ box-sizing: border-box;
502
+ }
503
+
504
+ .tree-canvas {
505
+ position: relative;
506
+ width: 100%;
507
+ min-height: 80px;
508
+ }
509
+
510
+ .formula-wrapper {
511
+ position: relative;
512
+ z-index: 2;
513
+ display: flex;
514
+ justify-content: center;
515
+ align-items: center;
516
+ padding-top: 4px;
517
+ font-size: var(--kmap-term-tree-formula-font-size, 1.35rem);
518
+ }
519
+
520
+ .highlight-overlay {
521
+ position: absolute;
522
+ z-index: 1;
523
+ pointer-events: none;
524
+ background-color: var(
525
+ --kmap-term-tree-highlight-color,
526
+ rgba(251, 191, 36, 0.28)
527
+ );
528
+ border: 2px solid
529
+ var(--kmap-term-tree-highlight-outline, rgba(245, 158, 11, 0.75));
530
+ border-radius: 6px;
531
+ box-sizing: border-box;
532
+ transition: left 0.12s ease-out, top 0.12s ease-out,
533
+ width 0.12s ease-out, height 0.12s ease-out;
534
+ }
535
+
536
+ .edges-svg {
537
+ position: absolute;
538
+ top: 0;
539
+ left: 0;
540
+ width: 100%;
541
+ height: 100%;
542
+ pointer-events: none;
543
+ z-index: 1;
544
+ overflow: visible;
545
+ }
546
+
547
+ .edge-path {
548
+ fill: none;
549
+ stroke: var(--kmap-term-tree-edge-color, #f59e0b);
550
+ stroke-width: 2.2;
551
+ stroke-linecap: round;
552
+ opacity: 0.85;
553
+ transition: stroke 0.2s ease, stroke-width 0.2s ease, opacity 0.2s ease;
554
+ }
555
+
556
+ .edge-path.highlighted {
557
+ stroke: var(--kmap-term-tree-highlight-edge-color, #d97706);
558
+ stroke-width: 3.2;
559
+ opacity: 1;
560
+ }
561
+
562
+ .tree-node {
563
+ position: absolute;
564
+ z-index: 2;
565
+ transform: translate(-50%, -50%);
566
+ display: inline-flex;
567
+ flex-direction: column;
568
+ align-items: center;
569
+ cursor: pointer;
570
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
571
+ }
572
+
573
+ .tree-node:hover,
574
+ .tree-node.active {
575
+ transform: translate(-50%, -50%) scale(1.15);
576
+ }
577
+
578
+ .node-badge {
579
+ display: flex;
580
+ align-items: center;
581
+ justify-content: center;
582
+ width: 28px;
583
+ height: 28px;
584
+ border-radius: 50%;
585
+ background: var(--kmap-term-tree-background-color, #ffffff);
586
+ border: 2px solid var(--kmap-term-tree-border-color, #0284c7);
587
+ color: var(--kmap-term-tree-node-text-color, #0284c7);
588
+ font-weight: 700;
589
+ font-size: 1rem;
590
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
591
+ box-sizing: border-box;
592
+ transition: all 0.2s ease;
593
+ }
594
+
595
+ .tree-node:hover .node-badge,
596
+ .tree-node.active .node-badge {
597
+ background: var(--kmap-term-tree-active-node-bg, #0284c7);
598
+ color: #ffffff;
599
+ border-color: var(--kmap-term-tree-active-node-border, #0284c7);
600
+ box-shadow: 0 4px 8px rgba(2, 132, 199, 0.35);
601
+ }
602
+
603
+ .node-label {
604
+ margin-top: 2px;
605
+ font-size: 0.7rem;
606
+ font-weight: 500;
607
+ color: #64748b;
608
+ white-space: nowrap;
609
+ background: rgba(255, 255, 255, 0.95);
610
+ padding: 1px 4px;
611
+ border-radius: 4px;
612
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
613
+ }
614
+
615
+ /* KaTeX Highlight */
616
+ .highlight-term {
617
+ background-color: var(
618
+ --kmap-term-tree-highlight-color,
619
+ rgba(251, 191, 36, 0.35)
620
+ ) !important;
621
+ border-radius: 4px;
622
+ outline: 2px solid
623
+ var(--kmap-term-tree-highlight-outline, rgba(245, 158, 11, 0.7));
624
+ transition: background-color 0.2s ease, outline 0.2s ease;
625
+ }
626
+
627
+ .info-bar {
628
+ margin-top: 12px;
629
+ padding: 6px 14px;
630
+ background: #f8fafc;
631
+ border: 1px solid #e2e8f0;
632
+ border-radius: 8px;
633
+ font-size: 0.88rem;
634
+ display: flex;
635
+ align-items: center;
636
+ gap: 8px;
637
+ color: #334155;
638
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
639
+ }
640
+
641
+ .info-badge {
642
+ font-weight: 700;
643
+ color: #0284c7;
644
+ background: #e0f2fe;
645
+ padding: 2px 8px;
646
+ border-radius: 4px;
647
+ }
648
+
649
+ .error-msg {
650
+ color: #dc2626;
651
+ padding: 8px;
652
+ font-size: 0.9rem;
653
+ }
654
+ `,
655
+ ];
196
656
  __decorate([
197
657
  property({ type: String })
198
658
  ], KmapTermTree.prototype, "term", void 0);
199
659
  __decorate([
200
660
  property({ type: Number })
201
661
  ], KmapTermTree.prototype, "tension", void 0);
662
+ __decorate([
663
+ property({ type: Boolean })
664
+ ], KmapTermTree.prototype, "showLabels", void 0);
665
+ __decorate([
666
+ property({ type: Boolean })
667
+ ], KmapTermTree.prototype, "showDetails", void 0);
202
668
  __decorate([
203
669
  state()
204
- ], KmapTermTree.prototype, "tokens", void 0);
670
+ ], KmapTermTree.prototype, "ast", void 0);
205
671
  __decorate([
206
672
  state()
207
- ], KmapTermTree.prototype, "nodes", void 0);
673
+ ], KmapTermTree.prototype, "error", void 0);
208
674
  __decorate([
209
675
  state()
210
- ], KmapTermTree.prototype, "termNode", void 0);
676
+ ], KmapTermTree.prototype, "flatNodes", void 0);
211
677
  __decorate([
212
678
  state()
213
- ], KmapTermTree.prototype, "depths", void 0);
679
+ ], KmapTermTree.prototype, "edges", void 0);
214
680
  __decorate([
215
681
  state()
216
- ], KmapTermTree.prototype, "maxDepths", void 0);
682
+ ], KmapTermTree.prototype, "activeNodeId", void 0);
217
683
  __decorate([
218
684
  state()
219
- ], KmapTermTree.prototype, "connections", void 0);
685
+ ], KmapTermTree.prototype, "activeSubtreeIds", void 0);
220
686
  __decorate([
221
- query("kmap-term-tree-edges")
222
- ], KmapTermTree.prototype, "edgesElement", void 0);
687
+ state()
688
+ ], KmapTermTree.prototype, "treeHeight", void 0);
223
689
  __decorate([
224
- query("#tokens")
225
- ], KmapTermTree.prototype, "tokensElement", void 0);
226
- export class KmapTermTreeEdge extends LitElement {
227
- constructor() {
228
- super(...arguments);
229
- this.tension = .5;
230
- this.edges = [];
231
- }
232
- render() {
233
- const parent = this.offsetParent;
234
- //language=SVG
235
- return this.edges === undefined ? '' : svg `
236
- <svg style="position:absolute;left:0px;top:0px" width="${parent.clientWidth}" height="${parent.clientHeight}">
237
- ${this.edges.map(edge => svg `
238
- <path d="${this._path(edge)}" fill="none" stroke-width="2" stroke-opacity=".9"/>
239
- `)}
240
- </svg>
241
- `;
242
- }
243
- _path(edge) {
244
- let fromx = edge.from.x + edge.from.width / 2;
245
- let fromy = edge.from.y + edge.from.height / 2;
246
- let tox = edge.to.x + edge.to.width / 2;
247
- let toy = edge.to.y + edge.to.height / 2;
248
- var delta = (toy - fromy) * this.tension;
249
- var hx1 = fromx;
250
- var hy1 = fromy + delta;
251
- var hx2 = tox;
252
- var hy2 = toy - delta;
253
- return "M " + fromx + " " + fromy +
254
- " C " + hx1 + " " + hy1
255
- + " " + hx2 + " " + hy2
256
- + " " + tox + " " + toy;
257
- }
258
- }
259
- KmapTermTreeEdge.styles = css `
260
- :host {
261
- display: block;
262
- stroke: var(--kmap-term-tree-edge-color, #fbc02d);
263
- position: relative;
264
- }
265
- `;
690
+ state()
691
+ ], KmapTermTree.prototype, "highlightRect", void 0);
266
692
  __decorate([
267
- property({ type: Number })
268
- ], KmapTermTreeEdge.prototype, "tension", void 0);
693
+ query('#formula')
694
+ ], KmapTermTree.prototype, "formulaElement", void 0);
269
695
  __decorate([
270
- property({ type: Array })
271
- ], KmapTermTreeEdge.prototype, "edges", void 0);
272
- window.customElements.define('kmap-term-tree-edges', KmapTermTreeEdge);
696
+ query('.tree-canvas')
697
+ ], KmapTermTree.prototype, "treeCanvasElement", void 0);
273
698
  //# sourceMappingURL=KmapTermTree.js.map