@fraczak/k 6.4.0 → 6.6.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.
@@ -0,0 +1,599 @@
1
+ import { isProduct, isVariant } from "../Value.mjs";
2
+ import { propertyListToFilter, valueToK } from "../codecs/runtime/show-value.mjs";
3
+
4
+ function escapeHtml(str) {
5
+ return String(str)
6
+ .replace(/&/g, "&")
7
+ .replace(/</g, "&lt;")
8
+ .replace(/>/g, "&gt;")
9
+ .replace(/"/g, "&quot;")
10
+ .replace(/'/g, "&#039;");
11
+ }
12
+
13
+ function previewProductFields(val, maxItems = 3) {
14
+ if (!isProduct(val)) return "";
15
+ const keys = Object.keys(val.product);
16
+ if (keys.length === 0) return "{}";
17
+ const previewKeys = keys.slice(0, maxItems).map((k) => `.${k}`);
18
+ if (keys.length > maxItems) previewKeys.push("...");
19
+ return previewKeys.join(", ");
20
+ }
21
+
22
+ function previewValue(val, maxItems = 4) {
23
+ if (val === null || val === undefined) return String(val);
24
+ if (isProduct(val)) {
25
+ const keys = Object.keys(val.product);
26
+ if (keys.length === 0) return "{}";
27
+ const previewKeys = keys.slice(0, maxItems).map((k) => `.${k}`);
28
+ if (keys.length > maxItems) previewKeys.push("...");
29
+ return `{ ${previewKeys.join(", ")} }`;
30
+ }
31
+ if (isVariant(val)) {
32
+ const tags = [];
33
+ let curr = val;
34
+ while (isVariant(curr) && tags.length < maxItems) {
35
+ tags.push(curr.tag);
36
+ curr = curr.value;
37
+ }
38
+ if (isVariant(curr)) tags.push("...");
39
+ return `| ${tags.join(" | ")}`;
40
+ }
41
+ return String(val);
42
+ }
43
+
44
+ export function renderValueNode(val, currentDepth = 1, depthLimit = 3) {
45
+ if (val === null || val === undefined) {
46
+ const el = document.createElement("span");
47
+ el.className = "k-tree-leaf k-val-nil";
48
+ el.textContent = String(val);
49
+ return el;
50
+ }
51
+
52
+ if (isProduct(val)) {
53
+ const keys = Object.keys(val.product);
54
+ if (keys.length === 0) {
55
+ const el = document.createElement("span");
56
+ el.className = "k-tree-leaf k-val-empty";
57
+ el.textContent = "{}";
58
+ return el;
59
+ }
60
+
61
+ const details = document.createElement("details");
62
+ details.className = "k-tree-node k-tree-product";
63
+ const isOpen = currentDepth <= depthLimit;
64
+ details.open = isOpen;
65
+
66
+ const summary = document.createElement("summary");
67
+ summary.className = "k-tree-summary";
68
+
69
+ const arrow = document.createElement("span");
70
+ arrow.className = "k-tree-arrow";
71
+ arrow.textContent = "▶";
72
+ summary.appendChild(arrow);
73
+
74
+ const openBrace = document.createElement("span");
75
+ openBrace.className = "k-val-brace";
76
+ openBrace.textContent = "{";
77
+ summary.appendChild(openBrace);
78
+
79
+ const badge = document.createElement("span");
80
+ badge.className = "k-tree-badge";
81
+ badge.textContent = `${keys.length} ${keys.length === 1 ? "field" : "fields"}`;
82
+ summary.appendChild(badge);
83
+
84
+ const preview = document.createElement("span");
85
+ preview.className = "k-val-preview";
86
+ preview.textContent = previewProductFields(val, 3);
87
+ summary.appendChild(preview);
88
+
89
+ const closeBrace = document.createElement("span");
90
+ closeBrace.className = "k-val-brace";
91
+ closeBrace.textContent = "}";
92
+ summary.appendChild(closeBrace);
93
+
94
+ details.appendChild(summary);
95
+
96
+ const childrenContainer = document.createElement("div");
97
+ childrenContainer.className = "k-tree-children";
98
+ details.appendChild(childrenContainer);
99
+
100
+ let populated = false;
101
+ const populateChildren = () => {
102
+ if (populated) return;
103
+ populated = true;
104
+ for (const k of keys) {
105
+ const row = document.createElement("div");
106
+ row.className = "k-tree-row";
107
+
108
+ const keySpan = document.createElement("span");
109
+ keySpan.className = "k-tree-field-key";
110
+ keySpan.textContent = `.${k}:`;
111
+ row.appendChild(keySpan);
112
+
113
+ const childContainer = document.createElement("div");
114
+ childContainer.className = "k-tree-child-val";
115
+ childContainer.appendChild(
116
+ renderValueNode(val.product[k], isOpen ? currentDepth + 1 : 1, depthLimit)
117
+ );
118
+ row.appendChild(childContainer);
119
+
120
+ childrenContainer.appendChild(row);
121
+ }
122
+ };
123
+
124
+ if (isOpen) {
125
+ populateChildren();
126
+ } else {
127
+ details.addEventListener("toggle", () => {
128
+ if (details.open) {
129
+ populateChildren();
130
+ }
131
+ });
132
+ }
133
+
134
+ return details;
135
+ }
136
+
137
+ if (isVariant(val)) {
138
+ const isTerminal = isProduct(val.value) && Object.keys(val.value.product).length === 0;
139
+ if (isTerminal) {
140
+ const el = document.createElement("span");
141
+ el.className = "k-tree-leaf k-tree-variant-leaf";
142
+ el.innerHTML = `<span class="k-val-pipe">|</span> <span class="k-val-tag">${escapeHtml(val.tag)}</span>`;
143
+ return el;
144
+ }
145
+
146
+ const details = document.createElement("details");
147
+ details.className = "k-tree-node k-tree-variant";
148
+ const isOpen = currentDepth <= depthLimit;
149
+ details.open = isOpen;
150
+
151
+ const summary = document.createElement("summary");
152
+ summary.className = "k-tree-summary";
153
+
154
+ const arrow = document.createElement("span");
155
+ arrow.className = "k-tree-arrow";
156
+ arrow.textContent = "▶";
157
+ summary.appendChild(arrow);
158
+
159
+ const pipeSpan = document.createElement("span");
160
+ pipeSpan.className = "k-val-pipe";
161
+ pipeSpan.textContent = "|";
162
+ summary.appendChild(pipeSpan);
163
+
164
+ const tagSpan = document.createElement("span");
165
+ tagSpan.className = "k-val-tag";
166
+ tagSpan.textContent = val.tag;
167
+ summary.appendChild(tagSpan);
168
+
169
+ if (isVariant(val.value)) {
170
+ let chainCount = 0;
171
+ let curr = val.value;
172
+ while (isVariant(curr)) {
173
+ chainCount++;
174
+ curr = curr.value;
175
+ }
176
+ const badge = document.createElement("span");
177
+ badge.className = "k-tree-badge";
178
+ badge.textContent = `+${chainCount} tags`;
179
+ summary.appendChild(badge);
180
+ }
181
+
182
+ const preview = document.createElement("span");
183
+ preview.className = "k-val-preview";
184
+ preview.textContent = previewValue(val.value, 3);
185
+ summary.appendChild(preview);
186
+
187
+ details.appendChild(summary);
188
+
189
+ const childrenContainer = document.createElement("div");
190
+ childrenContainer.className = "k-tree-children";
191
+ details.appendChild(childrenContainer);
192
+
193
+ let populated = false;
194
+ const populateChild = () => {
195
+ if (populated) return;
196
+ populated = true;
197
+ const row = document.createElement("div");
198
+ row.className = "k-tree-row";
199
+ const childContainer = document.createElement("div");
200
+ childContainer.className = "k-tree-child-val";
201
+ childContainer.appendChild(
202
+ renderValueNode(val.value, isOpen ? currentDepth + 1 : 1, depthLimit)
203
+ );
204
+ row.appendChild(childContainer);
205
+ childrenContainer.appendChild(row);
206
+ };
207
+
208
+ if (isOpen) {
209
+ populateChild();
210
+ } else {
211
+ details.addEventListener("toggle", () => {
212
+ if (details.open) {
213
+ populateChild();
214
+ }
215
+ });
216
+ }
217
+
218
+ return details;
219
+ }
220
+
221
+ const el = document.createElement("span");
222
+ el.className = "k-tree-leaf k-val-primitive";
223
+ el.textContent = String(val);
224
+ return el;
225
+ }
226
+
227
+ export function renderPatternTree(propertyList, depthLimit = 3) {
228
+ if (!propertyList || propertyList.length === 0) {
229
+ const el = document.createElement("span");
230
+ el.className = "k-tree-leaf k-pat-empty";
231
+ el.textContent = "(...)";
232
+ return el;
233
+ }
234
+
235
+ const refCount = new Array(propertyList.length).fill(0);
236
+ for (const [, edges] of propertyList) {
237
+ for (const [, target] of edges) {
238
+ if (target >= 0 && target < propertyList.length) {
239
+ refCount[target]++;
240
+ }
241
+ }
242
+ }
243
+ refCount[0]++;
244
+
245
+ const varNames = new Map();
246
+ let varCounter = 0;
247
+ for (let i = 0; i < propertyList.length; i++) {
248
+ if (refCount[i] > 1) {
249
+ varNames.set(i, `X${varCounter++}`);
250
+ }
251
+ }
252
+
253
+ const definedNodes = new Set();
254
+
255
+ function renderNode(nodeId, currentDepth, maxDepth, ancestors = new Set()) {
256
+ if (nodeId == null || nodeId < 0 || nodeId >= propertyList.length) {
257
+ const el = document.createElement("span");
258
+ el.className = "k-tree-leaf k-pat-empty";
259
+ el.textContent = "(...)";
260
+ return el;
261
+ }
262
+
263
+ const hasVar = varNames.has(nodeId);
264
+ const varName = hasVar ? varNames.get(nodeId) : null;
265
+
266
+ // Cycle detection
267
+ if (ancestors.has(nodeId)) {
268
+ const el = document.createElement("span");
269
+ el.className = "k-tree-leaf k-pat-cycle";
270
+ el.innerHTML = `<span class="k-pat-var">${escapeHtml(varName || `X${nodeId}`)}</span> <span class="k-tree-badge k-badge-cycle">recursive</span>`;
271
+ return el;
272
+ }
273
+
274
+ // Already defined on an earlier branch
275
+ if (hasVar && definedNodes.has(nodeId)) {
276
+ const el = document.createElement("span");
277
+ el.className = "k-tree-leaf k-pat-ref";
278
+ el.innerHTML = `<span class="k-pat-var">${escapeHtml(varName)}</span>`;
279
+ return el;
280
+ }
281
+
282
+ if (hasVar) {
283
+ definedNodes.add(nodeId);
284
+ }
285
+
286
+ const newAncestors = new Set(ancestors);
287
+ newAncestors.add(nodeId);
288
+
289
+ const [kind, edges] = propertyList[nodeId];
290
+
291
+ if (kind === "any") {
292
+ const el = document.createElement("span");
293
+ el.className = "k-tree-leaf k-pat-any";
294
+ el.textContent = hasVar ? varName : "(...)";
295
+ return el;
296
+ }
297
+
298
+ const isProductPat = kind.endsWith("product");
299
+ const isOpenPat = kind.startsWith("open-");
300
+ const openChar = isProductPat ? "{" : "<";
301
+ const closeChar = isProductPat ? "}" : ">";
302
+ const itemLabel = isProductPat ? "field" : "variant";
303
+
304
+ if (edges.length === 0 && !isOpenPat) {
305
+ const el = document.createElement("span");
306
+ el.className = "k-tree-leaf k-pat-empty";
307
+ const varSuffix = hasVar ? `=${varName}` : "";
308
+ el.innerHTML = `<span class="k-pat-brace">${openChar}${closeChar}</span>${varSuffix ? `<span class="k-pat-vardef">${varSuffix}</span>` : ""}`;
309
+ return el;
310
+ }
311
+
312
+ const details = document.createElement("details");
313
+ details.className = `k-tree-node ${isProductPat ? "k-pat-product" : "k-pat-union"}`;
314
+ const isOpen = currentDepth <= maxDepth;
315
+ details.open = isOpen;
316
+
317
+ const summary = document.createElement("summary");
318
+ summary.className = "k-tree-summary";
319
+
320
+ const arrow = document.createElement("span");
321
+ arrow.className = "k-tree-arrow";
322
+ arrow.textContent = "▶";
323
+ summary.appendChild(arrow);
324
+
325
+ const openSpan = document.createElement("span");
326
+ openSpan.className = isProductPat ? "k-pat-brace" : "k-pat-union";
327
+ openSpan.textContent = openChar;
328
+ summary.appendChild(openSpan);
329
+
330
+ const badge = document.createElement("span");
331
+ badge.className = "k-tree-badge";
332
+ badge.textContent = `${edges.length} ${edges.length === 1 ? itemLabel : itemLabel + "s"}${isOpenPat ? ", ..." : ""}`;
333
+ summary.appendChild(badge);
334
+
335
+ if (hasVar) {
336
+ const varDefSpan = document.createElement("span");
337
+ varDefSpan.className = "k-pat-vardef";
338
+ varDefSpan.textContent = `=${varName}`;
339
+ summary.appendChild(varDefSpan);
340
+ }
341
+
342
+ const closeSpan = document.createElement("span");
343
+ closeSpan.className = isProductPat ? "k-pat-brace" : "k-pat-union";
344
+ closeSpan.textContent = closeChar;
345
+ summary.appendChild(closeSpan);
346
+
347
+ details.appendChild(summary);
348
+
349
+ const childrenContainer = document.createElement("div");
350
+ childrenContainer.className = "k-tree-children";
351
+ details.appendChild(childrenContainer);
352
+
353
+ let populated = false;
354
+ const populateChildren = () => {
355
+ if (populated) return;
356
+ populated = true;
357
+ for (const [label, target] of edges) {
358
+ const row = document.createElement("div");
359
+ row.className = "k-tree-row";
360
+
361
+ const labelSpan = document.createElement("span");
362
+ if (isProductPat) {
363
+ labelSpan.className = "k-pat-field-key";
364
+ labelSpan.textContent = `.${label}:`;
365
+ } else {
366
+ labelSpan.className = "k-pat-tag";
367
+ labelSpan.innerHTML = `<span class="k-val-pipe">|</span> ${escapeHtml(label)}`;
368
+ }
369
+ row.appendChild(labelSpan);
370
+
371
+ const childContainer = document.createElement("div");
372
+ childContainer.className = "k-tree-child-pat";
373
+ childContainer.appendChild(
374
+ renderNode(target, isOpen ? currentDepth + 1 : 1, maxDepth, newAncestors)
375
+ );
376
+ row.appendChild(childContainer);
377
+
378
+ childrenContainer.appendChild(row);
379
+ }
380
+
381
+ if (isOpenPat) {
382
+ const row = document.createElement("div");
383
+ row.className = "k-tree-row";
384
+ const ellipsis = document.createElement("span");
385
+ ellipsis.className = "k-pat-ellipsis";
386
+ ellipsis.textContent = "...";
387
+ row.appendChild(ellipsis);
388
+ childrenContainer.appendChild(row);
389
+ }
390
+ };
391
+
392
+ if (isOpen) {
393
+ populateChildren();
394
+ } else {
395
+ details.addEventListener("toggle", () => {
396
+ if (details.open) {
397
+ populateChildren();
398
+ }
399
+ });
400
+ }
401
+
402
+ return details;
403
+ }
404
+
405
+ return renderNode(0, 1, depthLimit);
406
+ }
407
+
408
+ export function createOutputEntryElement({
409
+ value,
410
+ rawText,
411
+ codecOutputs = [],
412
+ timingText = null
413
+ }) {
414
+ const container = document.createElement("div");
415
+ container.className = "entry-line line-output k-entry-container";
416
+
417
+ // Toolbar
418
+ const toolbar = document.createElement("div");
419
+ toolbar.className = "k-output-toolbar";
420
+
421
+ // Mode buttons group
422
+ const modeGroup = document.createElement("div");
423
+ modeGroup.className = "k-mode-group";
424
+
425
+ const treeBtn = document.createElement("button");
426
+ treeBtn.type = "button";
427
+ treeBtn.className = "k-btn-mode active";
428
+ treeBtn.innerHTML = `<span class="k-mode-icon">🌲</span> Tree`;
429
+ treeBtn.title = "View structured Value & Pattern trees";
430
+
431
+ const rawBtn = document.createElement("button");
432
+ rawBtn.type = "button";
433
+ rawBtn.className = "k-btn-mode";
434
+ rawBtn.innerHTML = `<span class="k-mode-icon">📄</span> Raw`;
435
+ rawBtn.title = "View raw K expression & pattern text";
436
+
437
+ modeGroup.appendChild(treeBtn);
438
+ modeGroup.appendChild(rawBtn);
439
+ toolbar.appendChild(modeGroup);
440
+
441
+ // Tree Action buttons group (Expand / Collapse / Copy)
442
+ const actionGroup = document.createElement("div");
443
+ actionGroup.className = "k-action-group";
444
+
445
+ const expandAllBtn = document.createElement("button");
446
+ expandAllBtn.type = "button";
447
+ expandAllBtn.className = "k-btn-action";
448
+ expandAllBtn.innerHTML = `⊞ Expand`;
449
+ expandAllBtn.title = "Expand tree nodes";
450
+
451
+ const collapseAllBtn = document.createElement("button");
452
+ collapseAllBtn.type = "button";
453
+ collapseAllBtn.className = "k-btn-action";
454
+ collapseAllBtn.innerHTML = `⊟ Collapse`;
455
+ collapseAllBtn.title = "Reset trees to top 3 levels";
456
+
457
+ const copyBtn = document.createElement("button");
458
+ copyBtn.type = "button";
459
+ copyBtn.className = "k-btn-action";
460
+ copyBtn.innerHTML = `📋 Copy`;
461
+ copyBtn.title = "Copy raw output to clipboard";
462
+ copyBtn.onclick = (e) => {
463
+ e.stopPropagation();
464
+ navigator.clipboard.writeText(rawText);
465
+ copyBtn.innerHTML = `✓ Copied`;
466
+ setTimeout(() => {
467
+ copyBtn.innerHTML = `📋 Copy`;
468
+ }, 1500);
469
+ };
470
+
471
+ actionGroup.appendChild(expandAllBtn);
472
+ actionGroup.appendChild(collapseAllBtn);
473
+ actionGroup.appendChild(copyBtn);
474
+ toolbar.appendChild(actionGroup);
475
+
476
+ container.appendChild(toolbar);
477
+
478
+ // 1. Tree View Wrapper
479
+ const treeWrapper = document.createElement("div");
480
+ treeWrapper.className = "k-tree-view";
481
+
482
+ // Value Section
483
+ const valSection = document.createElement("details");
484
+ valSection.className = "k-tree-section k-tree-value-section";
485
+ valSection.open = true;
486
+
487
+ const valSummary = document.createElement("summary");
488
+ valSummary.className = "k-tree-section-summary";
489
+ valSummary.innerHTML = `<span class="k-tree-arrow">▶</span> <span class="k-section-title">🌲 Value</span> <span class="k-section-preview">${escapeHtml(valueToK(value))}</span>`;
490
+ valSection.appendChild(valSummary);
491
+
492
+ const valBody = document.createElement("div");
493
+ valBody.className = "k-tree-section-body";
494
+ valBody.appendChild(renderValueNode(value, 1, 3));
495
+ valSection.appendChild(valBody);
496
+ treeWrapper.appendChild(valSection);
497
+
498
+ // Pattern Section (if pattern present)
499
+ let patBody = null;
500
+ if (value && value.pattern) {
501
+ const patSection = document.createElement("details");
502
+ patSection.className = "k-tree-section k-tree-pat-section";
503
+ patSection.open = true;
504
+
505
+ const patSummary = document.createElement("summary");
506
+ patSummary.className = "k-tree-section-summary";
507
+ const patPreview = propertyListToFilter(value.pattern);
508
+ patSummary.innerHTML = `<span class="k-tree-arrow">▶</span> <span class="k-section-title">🌿 Pattern</span> <span class="k-section-preview">?${escapeHtml(patPreview)}</span>`;
509
+ patSection.appendChild(patSummary);
510
+
511
+ patBody = document.createElement("div");
512
+ patBody.className = "k-tree-section-body";
513
+ patBody.appendChild(renderPatternTree(value.pattern, 3));
514
+ patSection.appendChild(patBody);
515
+ treeWrapper.appendChild(patSection);
516
+ }
517
+
518
+ // Codecs Section (if any codec outputs)
519
+ if (codecOutputs.length > 0) {
520
+ const codecsBox = document.createElement("div");
521
+ codecsBox.className = "k-codecs-box";
522
+ for (const c of codecOutputs) {
523
+ const cRow = document.createElement("div");
524
+ cRow.className = "k-codec-row";
525
+ cRow.textContent = c;
526
+ codecsBox.appendChild(cRow);
527
+ }
528
+ treeWrapper.appendChild(codecsBox);
529
+ }
530
+
531
+ // Timing (if any)
532
+ if (timingText) {
533
+ const timingRow = document.createElement("div");
534
+ timingRow.className = "k-timing-row";
535
+ timingRow.textContent = timingText;
536
+ treeWrapper.appendChild(timingRow);
537
+ }
538
+
539
+ container.appendChild(treeWrapper);
540
+
541
+ // 2. Raw Text View Wrapper
542
+ const rawWrapper = document.createElement("div");
543
+ rawWrapper.className = "k-raw-view";
544
+ rawWrapper.style.display = "none";
545
+
546
+ const rawPre = document.createElement("pre");
547
+ rawPre.className = "k-raw-pre";
548
+ rawPre.textContent = rawText;
549
+ rawWrapper.appendChild(rawPre);
550
+
551
+ container.appendChild(rawWrapper);
552
+
553
+ // Mode switching logic
554
+ treeBtn.onclick = () => {
555
+ treeBtn.classList.add("active");
556
+ rawBtn.classList.remove("active");
557
+ treeWrapper.style.display = "";
558
+ rawWrapper.style.display = "none";
559
+ expandAllBtn.style.display = "";
560
+ collapseAllBtn.style.display = "";
561
+ };
562
+
563
+ rawBtn.onclick = () => {
564
+ rawBtn.classList.add("active");
565
+ treeBtn.classList.remove("active");
566
+ treeWrapper.style.display = "none";
567
+ rawWrapper.style.display = "";
568
+ expandAllBtn.style.display = "none";
569
+ collapseAllBtn.style.display = "none";
570
+ };
571
+
572
+ // Expand All / Collapse All logic
573
+ expandAllBtn.onclick = () => {
574
+ const allDetails = treeWrapper.querySelectorAll("details");
575
+ allDetails.forEach((d) => {
576
+ d.open = true;
577
+ d.dispatchEvent(new Event("toggle"));
578
+ });
579
+ // Repeat once to expand lazily populated children
580
+ setTimeout(() => {
581
+ const moreDetails = treeWrapper.querySelectorAll("details:not([open])");
582
+ moreDetails.forEach((d) => {
583
+ d.open = true;
584
+ d.dispatchEvent(new Event("toggle"));
585
+ });
586
+ }, 10);
587
+ };
588
+
589
+ collapseAllBtn.onclick = () => {
590
+ valBody.innerHTML = "";
591
+ valBody.appendChild(renderValueNode(value, 1, 3));
592
+ if (value && value.pattern && patBody) {
593
+ patBody.innerHTML = "";
594
+ patBody.appendChild(renderPatternTree(value.pattern, 3));
595
+ }
596
+ };
597
+
598
+ return container;
599
+ }