@codecanvas-ide/engine 0.3.0 → 0.4.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/README.md +2 -1
- package/dist/codecanvas-engine.js +298 -234
- package/dist/index.js +502 -177
- package/dist/index.js.map +4 -4
- package/dist/types/canvas.d.ts +30 -0
- package/dist/types/highlight.d.ts +6 -0
- package/dist/types/html.d.ts +2 -0
- package/dist/types/styles.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2024,13 +2024,13 @@ function contains(arrangement, unit) {
|
|
|
2024
2024
|
|
|
2025
2025
|
// src/layout.ts
|
|
2026
2026
|
var M = {
|
|
2027
|
-
headerH:
|
|
2028
|
-
headerCharW: 7.
|
|
2029
|
-
//
|
|
2030
|
-
previewLineH:
|
|
2031
|
-
previewCharW: 6.
|
|
2032
|
-
//
|
|
2033
|
-
previewPad:
|
|
2027
|
+
headerH: 34,
|
|
2028
|
+
headerCharW: 7.6,
|
|
2029
|
+
// 12.5px monospace
|
|
2030
|
+
previewLineH: 19,
|
|
2031
|
+
previewCharW: 6.95,
|
|
2032
|
+
// 11.5px monospace
|
|
2033
|
+
previewPad: 16,
|
|
2034
2034
|
pad: 14,
|
|
2035
2035
|
// inner padding of containers
|
|
2036
2036
|
gap: 28,
|
|
@@ -2298,103 +2298,220 @@ function setHTML(element, html) {
|
|
|
2298
2298
|
const htmlPolicy = trustedHtmlPolicy();
|
|
2299
2299
|
element.innerHTML = htmlPolicy ? htmlPolicy.createHTML(html) : html;
|
|
2300
2300
|
}
|
|
2301
|
+
function esc(s) {
|
|
2302
|
+
return s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
// src/highlight.ts
|
|
2306
|
+
var KEYWORDS = /* @__PURE__ */ new Set([
|
|
2307
|
+
"import",
|
|
2308
|
+
"from",
|
|
2309
|
+
"export",
|
|
2310
|
+
"default",
|
|
2311
|
+
"const",
|
|
2312
|
+
"let",
|
|
2313
|
+
"var",
|
|
2314
|
+
"function",
|
|
2315
|
+
"return",
|
|
2316
|
+
"async",
|
|
2317
|
+
"await",
|
|
2318
|
+
"if",
|
|
2319
|
+
"else",
|
|
2320
|
+
"for",
|
|
2321
|
+
"while",
|
|
2322
|
+
"do",
|
|
2323
|
+
"switch",
|
|
2324
|
+
"case",
|
|
2325
|
+
"break",
|
|
2326
|
+
"continue",
|
|
2327
|
+
"new",
|
|
2328
|
+
"class",
|
|
2329
|
+
"extends",
|
|
2330
|
+
"implements",
|
|
2331
|
+
"interface",
|
|
2332
|
+
"type",
|
|
2333
|
+
"enum",
|
|
2334
|
+
"namespace",
|
|
2335
|
+
"this",
|
|
2336
|
+
"super",
|
|
2337
|
+
"throw",
|
|
2338
|
+
"try",
|
|
2339
|
+
"catch",
|
|
2340
|
+
"finally",
|
|
2341
|
+
"typeof",
|
|
2342
|
+
"instanceof",
|
|
2343
|
+
"in",
|
|
2344
|
+
"of",
|
|
2345
|
+
"yield",
|
|
2346
|
+
"void",
|
|
2347
|
+
"delete",
|
|
2348
|
+
"null",
|
|
2349
|
+
"undefined",
|
|
2350
|
+
"true",
|
|
2351
|
+
"false",
|
|
2352
|
+
"static",
|
|
2353
|
+
"public",
|
|
2354
|
+
"private",
|
|
2355
|
+
"protected",
|
|
2356
|
+
"readonly",
|
|
2357
|
+
"as",
|
|
2358
|
+
"satisfies",
|
|
2359
|
+
"get",
|
|
2360
|
+
"set"
|
|
2361
|
+
]);
|
|
2362
|
+
var TOKEN = /(\/\*.*?\*\/|\/\/.*$|'(?:\\.|[^'\\])*'?|"(?:\\.|[^"\\])*"?|`(?:\\.|[^`\\])*`?|[A-Za-z_$][\w$]*|\d[\w.]*|\s+|.)/g;
|
|
2363
|
+
function highlightLine(line) {
|
|
2364
|
+
let html = "";
|
|
2365
|
+
for (const [token] of line.matchAll(TOKEN)) {
|
|
2366
|
+
const syntaxClass = classOf(token);
|
|
2367
|
+
html += syntaxClass ? `<span class="${syntaxClass}">${esc(token)}</span>` : esc(token);
|
|
2368
|
+
}
|
|
2369
|
+
return html;
|
|
2370
|
+
}
|
|
2371
|
+
function classOf(token) {
|
|
2372
|
+
if (token.startsWith("//") || token.startsWith("/*")) return "s-com";
|
|
2373
|
+
if (/^['"`]/.test(token)) return "s-str";
|
|
2374
|
+
if (/^\d/.test(token)) return "s-con";
|
|
2375
|
+
if (KEYWORDS.has(token)) return "s-kw";
|
|
2376
|
+
if (/^[A-Z][A-Z0-9_]{2,}$/.test(token)) return "s-con";
|
|
2377
|
+
if (/^[A-Z][a-z]\w*$/.test(token)) return "s-type";
|
|
2378
|
+
return void 0;
|
|
2379
|
+
}
|
|
2301
2380
|
|
|
2302
2381
|
// src/styles.ts
|
|
2303
2382
|
var CSS = (
|
|
2304
2383
|
/* css */
|
|
2305
2384
|
`
|
|
2306
2385
|
.cc-root {
|
|
2307
|
-
--cc-bg: #
|
|
2308
|
-
--cc-dot:
|
|
2309
|
-
--cc-frame-bg: rgba(255,255,255,.
|
|
2310
|
-
--cc-frame-border:
|
|
2311
|
-
--cc-
|
|
2312
|
-
--cc-
|
|
2313
|
-
--cc-
|
|
2314
|
-
--cc-
|
|
2315
|
-
--cc-
|
|
2316
|
-
--cc-
|
|
2317
|
-
--cc-
|
|
2318
|
-
--cc-
|
|
2386
|
+
--cc-bg: #0c0d0f;
|
|
2387
|
+
--cc-dot: #25272c;
|
|
2388
|
+
--cc-frame-bg: rgba(255,255,255,.015);
|
|
2389
|
+
--cc-frame-border: #2e3036;
|
|
2390
|
+
--cc-node-bg: #16171a;
|
|
2391
|
+
--cc-node-border: #2b2d33;
|
|
2392
|
+
--cc-node-head-border: #24262b;
|
|
2393
|
+
--cc-text: #e6e7ea;
|
|
2394
|
+
--cc-strong: #f2f2f3;
|
|
2395
|
+
--cc-muted: #a9adb6;
|
|
2396
|
+
--cc-faint: #8b8f98;
|
|
2397
|
+
--cc-faintest: #6e727b;
|
|
2398
|
+
--cc-code: #c9ccd2;
|
|
2399
|
+
--cc-panel: #16171a;
|
|
2400
|
+
--cc-surface2: #1c1d21;
|
|
2401
|
+
--cc-raised: #2a2c31;
|
|
2402
|
+
--cc-panel-border: #26282d;
|
|
2403
|
+
--cc-accent: #e8c27a;
|
|
2404
|
+
--cc-ink: #e8c27a;
|
|
2405
|
+
--cc-on-accent: #1a1408;
|
|
2406
|
+
--cc-danger: #f2877d;
|
|
2407
|
+
--cc-shadow: 0 10px 30px rgba(0,0,0,.35);
|
|
2408
|
+
--cc-shadow-sm: 0 1px 0 rgba(0,0,0,.2);
|
|
2409
|
+
--cc-shadow-lg: 0 24px 60px rgba(0,0,0,.55);
|
|
2410
|
+
--cc-syn-kw: #c3a6f0;
|
|
2411
|
+
--cc-syn-str: #9fd3ec;
|
|
2412
|
+
--cc-syn-type: #b8c4ff;
|
|
2413
|
+
--cc-syn-com: #7d828c;
|
|
2414
|
+
--cc-syn-con: #7cc4e8;
|
|
2415
|
+
--cc-ui-font: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
2319
2416
|
|
|
2320
|
-
--k-import: #
|
|
2321
|
-
--k-
|
|
2322
|
-
--k-function: #
|
|
2323
|
-
--k-
|
|
2324
|
-
--k-
|
|
2325
|
-
--k-
|
|
2326
|
-
--k-
|
|
2327
|
-
--k-namespace: #
|
|
2328
|
-
--k-statement: #
|
|
2417
|
+
--k-import: #b4b9c2;
|
|
2418
|
+
--k-variable: #7cc4e8;
|
|
2419
|
+
--k-function: #e8c27a;
|
|
2420
|
+
--k-class: #8fd0a5;
|
|
2421
|
+
--k-type: #b8b0f0;
|
|
2422
|
+
--k-component: #5fd1b5;
|
|
2423
|
+
--k-state: #f0a36a;
|
|
2424
|
+
--k-namespace: #9aa6bd;
|
|
2425
|
+
--k-statement: #8b8f98;
|
|
2329
2426
|
|
|
2330
2427
|
position: relative; overflow: hidden; width: 100%; height: 100%;
|
|
2331
2428
|
background-color: var(--cc-bg);
|
|
2332
|
-
background-image: radial-gradient(var(--cc-dot)
|
|
2429
|
+
background-image: radial-gradient(circle, var(--cc-dot) 1px, transparent 1.4px);
|
|
2333
2430
|
color: var(--cc-text);
|
|
2334
|
-
font: 13px/1.3
|
|
2431
|
+
font: 13px/1.3 var(--cc-ui-font);
|
|
2335
2432
|
touch-action: none; user-select: none; -webkit-user-select: none; outline: none;
|
|
2336
2433
|
-webkit-tap-highlight-color: transparent;
|
|
2337
2434
|
}
|
|
2338
2435
|
.cc-root[data-theme="light"] {
|
|
2339
|
-
--cc-bg: #
|
|
2340
|
-
--cc-frame-bg: rgba(255,255,255,.
|
|
2341
|
-
--cc-
|
|
2342
|
-
--cc-
|
|
2343
|
-
--cc-
|
|
2344
|
-
--
|
|
2345
|
-
--
|
|
2346
|
-
--
|
|
2347
|
-
--k-
|
|
2348
|
-
--k-
|
|
2349
|
-
--k-variable: #2f6fe0; --k-variable-head: #2f6fe0; --k-variable-bg: rgba(47,111,224,.07);
|
|
2350
|
-
--k-type: #b58a0c; --k-type-head: #a87f08; --k-type-bg: rgba(181,138,12,.08);
|
|
2351
|
-
--k-namespace: #6f5ce6; --k-namespace-head: #6f5ce6; --k-namespace-bg: rgba(111,92,230,.07);
|
|
2352
|
-
--k-statement: #64708a; --k-statement-head: #64708a; --k-statement-bg: rgba(100,112,138,.07);
|
|
2436
|
+
--cc-bg: #f3f3f1; --cc-dot: #d4d4cf;
|
|
2437
|
+
--cc-frame-bg: rgba(255,255,255,.5); --cc-frame-border: #cfcfc9;
|
|
2438
|
+
--cc-node-bg: #ffffff; --cc-node-border: #dadad4; --cc-node-head-border: #edede9;
|
|
2439
|
+
--cc-text: #1f2023; --cc-strong: #0f1012; --cc-muted: #4a4d55; --cc-faint: #5f626a; --cc-faintest: #8a8d94; --cc-code: #2e3036;
|
|
2440
|
+
--cc-panel: #ffffff; --cc-surface2: #f5f5f3; --cc-raised: #ececE8; --cc-panel-border: #e1e1dc;
|
|
2441
|
+
--cc-ink: #8a5d0e; --cc-danger: #b4362b;
|
|
2442
|
+
--cc-shadow: 0 8px 24px rgba(20,20,10,.08); --cc-shadow-sm: 0 1px 2px rgba(20,20,10,.06); --cc-shadow-lg: 0 24px 60px rgba(20,20,10,.18);
|
|
2443
|
+
--cc-syn-kw: #7a3fc0; --cc-syn-str: #1d6a92; --cc-syn-type: #3b4fb0; --cc-syn-com: #6f737b; --cc-syn-con: #1d6a92;
|
|
2444
|
+
--k-import: #555a63; --k-variable: #1d6a92; --k-function: #8a5d0e; --k-class: #2f7a4a; --k-type: #5a4bb0;
|
|
2445
|
+
--k-component: #11756a; --k-state: #a8521a; --k-namespace: #4f5b73; --k-statement: #5f626a;
|
|
2353
2446
|
}
|
|
2354
2447
|
.cc-world { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
|
|
2355
2448
|
.cc-root.cc-moving .cc-world { will-change: transform; }
|
|
2356
|
-
.cc-
|
|
2357
|
-
.cc-
|
|
2449
|
+
.cc-root.cc-tool-pan, .cc-root.cc-tool-pan .cc-block { cursor: grab; }
|
|
2450
|
+
.cc-mono { font-family: var(--cc-font, "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace); }
|
|
2451
|
+
.cc-frame { position: absolute; border: 1px dashed var(--cc-frame-border); background: var(--cc-frame-bg); border-radius: 18px; }
|
|
2452
|
+
.cc-frame-title {
|
|
2453
|
+
position: absolute; left: 20px; top: 14px; display: flex; align-items: center; gap: 8px; white-space: nowrap;
|
|
2454
|
+
font: 12px/18px var(--cc-font, "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
|
2455
|
+
}
|
|
2456
|
+
.cc-frame-folder { color: var(--cc-faint); }
|
|
2457
|
+
.cc-frame-file { color: var(--cc-text); font-weight: 500; }
|
|
2458
|
+
.cc-frame-count { padding: 2px 6px; border-radius: 5px; background: var(--cc-surface2); color: var(--cc-faint); font-size: 10.5px; line-height: 14px; }
|
|
2459
|
+
.cc-frame-count:empty { display: none; }
|
|
2358
2460
|
.cc-empty { position: absolute; font-size: 15px; color: var(--cc-muted); white-space: nowrap; }
|
|
2359
2461
|
|
|
2360
2462
|
.cc-block {
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
.cc-
|
|
2367
|
-
.cc-block.k-
|
|
2368
|
-
.cc-block.k-
|
|
2369
|
-
.cc-block.k-
|
|
2370
|
-
.cc-block.k-
|
|
2371
|
-
.cc-block.k-
|
|
2372
|
-
.cc-block.k-
|
|
2373
|
-
.cc-block.k-
|
|
2374
|
-
.cc-block.k-
|
|
2463
|
+
--kglyph: color-mix(in srgb, var(--k) 16%, transparent);
|
|
2464
|
+
position: absolute; left: 0; top: 0; box-sizing: border-box; border-radius: 12px;
|
|
2465
|
+
border: 1px solid var(--cc-node-border); background: var(--cc-node-bg); overflow: hidden; contain: layout style;
|
|
2466
|
+
box-shadow: var(--cc-shadow-sm); cursor: default; transition: opacity 160ms ease, box-shadow 160ms ease;
|
|
2467
|
+
}
|
|
2468
|
+
.cc-root[data-theme="light"] .cc-block { --kglyph: color-mix(in srgb, var(--k) 10%, transparent); }
|
|
2469
|
+
.cc-block.k-import { --k: var(--k-import); }
|
|
2470
|
+
.cc-block.k-class { --k: var(--k-class); }
|
|
2471
|
+
.cc-block.k-function { --k: var(--k-function); }
|
|
2472
|
+
.cc-block.k-component { --k: var(--k-component); }
|
|
2473
|
+
.cc-block.k-state { --k: var(--k-state); }
|
|
2474
|
+
.cc-block.k-variable { --k: var(--k-variable); }
|
|
2475
|
+
.cc-block.k-type { --k: var(--k-type); }
|
|
2476
|
+
.cc-block.k-namespace { --k: var(--k-namespace); }
|
|
2477
|
+
.cc-block.k-statement { --k: var(--k-statement); }
|
|
2375
2478
|
|
|
2376
2479
|
.cc-head {
|
|
2377
|
-
display: flex; align-items: center; gap:
|
|
2378
|
-
|
|
2379
|
-
font:
|
|
2380
|
-
}
|
|
2381
|
-
.cc-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
.cc-
|
|
2385
|
-
|
|
2480
|
+
display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; box-sizing: border-box;
|
|
2481
|
+
border-bottom: 1px solid var(--cc-node-head-border); color: var(--cc-strong); white-space: nowrap; overflow: hidden;
|
|
2482
|
+
font: 12.5px/34px var(--cc-font, "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
|
2483
|
+
}
|
|
2484
|
+
.cc-glyph {
|
|
2485
|
+
flex: none; width: 18px; min-width: 18px; height: 18px; padding: 0 2px; box-sizing: border-box; border-radius: 5px;
|
|
2486
|
+
background: var(--kglyph); color: var(--k); display: flex; align-items: center; justify-content: center;
|
|
2487
|
+
font: 600 10.5px/18px var(--cc-font, "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace); letter-spacing: -.04em;
|
|
2488
|
+
}
|
|
2489
|
+
.k-component > .cc-head .cc-glyph, .k-namespace > .cc-head .cc-glyph { width: auto; }
|
|
2490
|
+
.cc-name { font-weight: 600; flex: none; max-width: 70%; overflow: hidden; text-overflow: ellipsis; }
|
|
2491
|
+
.cc-detail { font-size: 11.5px; color: var(--cc-faint); overflow: hidden; text-overflow: ellipsis; min-width: 0; flex: 1 1 auto; }
|
|
2492
|
+
.cc-tag {
|
|
2493
|
+
flex: none; padding: 0 7px; border-radius: 6px; background: var(--kglyph); color: var(--k);
|
|
2494
|
+
font: 500 10.5px/18px var(--cc-ui-font);
|
|
2495
|
+
}
|
|
2496
|
+
.cc-exp { width: 5px; height: 5px; border-radius: 50%; background: var(--cc-faint); flex: none; }
|
|
2386
2497
|
.cc-fold {
|
|
2387
2498
|
flex: none; margin-left: auto; width: 22px; height: 22px; border: 0; border-radius: 6px; padding: 0;
|
|
2388
|
-
background:
|
|
2499
|
+
background: transparent; color: var(--cc-faint); font-size: 12px; line-height: 22px; cursor: pointer;
|
|
2389
2500
|
}
|
|
2390
|
-
.cc-fold:hover { background:
|
|
2501
|
+
.cc-fold:hover { background: var(--cc-raised); color: var(--cc-strong); }
|
|
2391
2502
|
.cc-prev {
|
|
2392
|
-
margin: 0; padding:
|
|
2393
|
-
font:
|
|
2394
|
-
}
|
|
2395
|
-
.cc-
|
|
2396
|
-
.cc-
|
|
2397
|
-
.cc-
|
|
2503
|
+
margin: 0; padding: 7px 12px 9px; white-space: pre; overflow: hidden; color: var(--cc-code);
|
|
2504
|
+
font: 11.5px/19px var(--cc-font, "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
|
2505
|
+
}
|
|
2506
|
+
.cc-prev .s-kw { color: var(--cc-syn-kw); }
|
|
2507
|
+
.cc-prev .s-str { color: var(--cc-syn-str); }
|
|
2508
|
+
.cc-prev .s-type { color: var(--cc-syn-type); }
|
|
2509
|
+
.cc-prev .s-com { color: var(--cc-syn-com); }
|
|
2510
|
+
.cc-prev .s-con { color: var(--cc-syn-con); }
|
|
2511
|
+
.k-import > .cc-prev { color: var(--cc-muted); }
|
|
2512
|
+
.cc-more { color: var(--cc-faintest); font-style: italic; }
|
|
2513
|
+
.cc-mini { position: relative; margin: 6px 12px 0; height: 34px; opacity: .55; }
|
|
2514
|
+
.cc-mini > i { position: absolute; border-radius: 2px; border: 1px solid var(--cc-faint); background: transparent; }
|
|
2398
2515
|
.cc-mini > i.k-class { border-color: var(--k-class); } .cc-mini > i.k-function { border-color: var(--k-function); }
|
|
2399
2516
|
.cc-mini > i.k-component { border-color: var(--k-component); } .cc-mini > i.k-state { border-color: var(--k-state); }
|
|
2400
2517
|
.cc-mini > i.k-variable { border-color: var(--k-variable); } .cc-mini > i.k-type { border-color: var(--k-type); }
|
|
@@ -2404,9 +2521,10 @@ var CSS = (
|
|
|
2404
2521
|
background: var(--cc-danger); color: #fff; font: 700 11px/18px ui-sans-serif, system-ui, sans-serif; text-align: center;
|
|
2405
2522
|
}
|
|
2406
2523
|
.cc-block.cc-has-error { border-color: var(--cc-danger); }
|
|
2524
|
+
.cc-err { color: #1a0d0b; }
|
|
2407
2525
|
.cc-root.lod-2 .cc-err { min-width: 34px; height: 34px; border-radius: 17px; font-size: 22px; line-height: 34px; }
|
|
2408
2526
|
.cc-rename {
|
|
2409
|
-
font: inherit; font-weight:
|
|
2527
|
+
font: inherit; font-weight: 600; color: var(--cc-strong); background: var(--cc-raised); border: 1px solid var(--cc-ink);
|
|
2410
2528
|
border-radius: 4px; padding: 0 4px; height: 22px; outline: none; user-select: text; -webkit-user-select: text; max-width: 100%;
|
|
2411
2529
|
}
|
|
2412
2530
|
.cc-rename.invalid { border-color: var(--cc-danger); }
|
|
@@ -2419,10 +2537,7 @@ var CSS = (
|
|
|
2419
2537
|
font: 900 13px/18px ui-sans-serif, system-ui, sans-serif; text-align: center;
|
|
2420
2538
|
}
|
|
2421
2539
|
.cc-root.lod-2 .cc-conflict-chip { min-width: 34px; height: 34px; border-radius: 17px; font-size: 24px; line-height: 34px; }
|
|
2422
|
-
.cc-block.cc-conflict-incoming {
|
|
2423
|
-
--k: #f0883e; --kh: #8a4a17; --kbg: rgba(240, 136, 62, .08);
|
|
2424
|
-
border-style: dashed; pointer-events: none; opacity: .9;
|
|
2425
|
-
}
|
|
2540
|
+
.cc-block.cc-conflict-incoming { --k: #f0883e; border: 1px dashed #f0883e; pointer-events: none; opacity: .9; }
|
|
2426
2541
|
.cc-diff-chip.incoming { background: #f0883e; }
|
|
2427
2542
|
.cc-banner.cc-banner-conflicts { border-color: #f0883e; }
|
|
2428
2543
|
|
|
@@ -2437,10 +2552,9 @@ var CSS = (
|
|
|
2437
2552
|
.cc-diff-chip.removed { background: var(--cc-danger); }
|
|
2438
2553
|
.cc-root.lod-2 .cc-diff-chip { min-width: 34px; height: 34px; border-radius: 8px; font-size: 24px; line-height: 34px; }
|
|
2439
2554
|
.cc-block.cc-diff-removed {
|
|
2440
|
-
--k: var(--cc-danger);
|
|
2441
|
-
|
|
2555
|
+
--k: var(--cc-danger); border: 1px dashed var(--cc-danger); background: color-mix(in srgb, var(--cc-danger) 8%, var(--cc-node-bg));
|
|
2556
|
+
pointer-events: none; opacity: .9;
|
|
2442
2557
|
}
|
|
2443
|
-
.cc-root[data-theme="light"] .cc-block.cc-diff-removed { --kh: #d64545; --kbg: rgba(214, 69, 69, .08); }
|
|
2444
2558
|
.cc-prev.cc-diff-prev { opacity: .95; }
|
|
2445
2559
|
.cc-diff-prev > span { display: block; margin: 0 -12px; padding: 0 12px; }
|
|
2446
2560
|
.cc-diff-prev .cc-line-same { opacity: .5; }
|
|
@@ -2448,23 +2562,24 @@ var CSS = (
|
|
|
2448
2562
|
.cc-diff-prev .cc-line-removed { background: rgba(248, 81, 73, .26); text-decoration: line-through; text-decoration-color: rgba(248, 81, 73, .7); }
|
|
2449
2563
|
.cc-diff-summary { padding: 0 8px; font-size: 12px; color: var(--cc-muted); white-space: nowrap; }
|
|
2450
2564
|
|
|
2451
|
-
.cc-block.cc-sel { box-shadow: 0 0 0 2px var(--cc-
|
|
2452
|
-
.cc-block.cc-editing { box-shadow: 0 0 0 2px var(--cc-
|
|
2453
|
-
.cc-block.cc-dragging { box-shadow: var(--cc-shadow); opacity: .92; z-index: 9999 !important; }
|
|
2454
|
-
.cc-root.has-
|
|
2565
|
+
.cc-block.cc-sel { box-shadow: 0 0 0 2px var(--cc-ink), var(--cc-shadow-lg); }
|
|
2566
|
+
.cc-block.cc-editing { box-shadow: 0 0 0 2px var(--cc-ink), var(--cc-shadow-lg); }
|
|
2567
|
+
.cc-block.cc-dragging { box-shadow: var(--cc-shadow-lg); opacity: .92; z-index: 9999 !important; }
|
|
2568
|
+
.cc-root.has-sel .cc-block:not(.cc-related) { opacity: .38; }
|
|
2455
2569
|
|
|
2456
2570
|
.cc-edges { position: absolute; left: 0; top: 0; overflow: visible; pointer-events: none; }
|
|
2457
|
-
.cc-edge { fill: none; stroke-width: 1.5; opacity: .
|
|
2458
|
-
.cc-edge.u-type { stroke-dasharray:
|
|
2459
|
-
.cc-root.has-hl .cc-edge { opacity: .
|
|
2460
|
-
.cc-root.has-hl .cc-edge.hl { opacity: .95; stroke-width: 2
|
|
2571
|
+
.cc-edge { fill: none; stroke: var(--edge); stroke-width: 1.5; opacity: .5; }
|
|
2572
|
+
.cc-edge.to-import, .cc-edge.u-type { stroke-dasharray: 3 4; }
|
|
2573
|
+
.cc-root.has-hl .cc-edge { opacity: .18; }
|
|
2574
|
+
.cc-root.has-hl .cc-edge.hl { stroke: var(--cc-ink); opacity: .95; stroke-width: 2; }
|
|
2461
2575
|
|
|
2462
2576
|
/* level of detail when zoomed out */
|
|
2463
|
-
.cc-root.lod-1 .cc-
|
|
2577
|
+
.cc-root.lod-1 .cc-tag { visibility: hidden; }
|
|
2464
2578
|
.cc-root.lod-2 .cc-prev, .cc-root.lod-2 .cc-detail, .cc-root.lod-2 .cc-mini { visibility: hidden; }
|
|
2465
2579
|
.cc-root.lod-2 .cc-head { font-size: 26px; }
|
|
2466
|
-
.cc-root.lod-2 .cc-
|
|
2580
|
+
.cc-root.lod-2 .cc-glyph, .cc-root.lod-2 .cc-fold, .cc-root.lod-2 .cc-tag { display: none; }
|
|
2467
2581
|
.cc-root.lod-2 .cc-frame-title { font-size: 40px; line-height: 44px; top: -8px; }
|
|
2582
|
+
.cc-root.lod-2 .cc-frame-count { font-size: 28px; line-height: 36px; padding: 2px 12px; border-radius: 10px; }
|
|
2468
2583
|
|
|
2469
2584
|
/* overlays (screen space) */
|
|
2470
2585
|
.cc-banner {
|
|
@@ -2472,38 +2587,66 @@ var CSS = (
|
|
|
2472
2587
|
padding: 8px 12px; border-radius: 10px; background: var(--cc-panel); border: 1px solid var(--cc-danger); box-shadow: var(--cc-shadow);
|
|
2473
2588
|
font-size: 13px; max-width: calc(100% - 32px);
|
|
2474
2589
|
}
|
|
2475
|
-
.cc-banner[hidden], .cc-bar[hidden], .cc-refs[hidden] { display: none; }
|
|
2590
|
+
.cc-banner[hidden], .cc-bar[hidden], .cc-refs[hidden], .cc-selbar[hidden], .cc-menu[hidden] { display: none; }
|
|
2476
2591
|
.cc-toast {
|
|
2477
2592
|
position: absolute; left: 50%; bottom: 76px; transform: translateX(-50%); z-index: 30; padding: 8px 14px; border-radius: 10px;
|
|
2478
2593
|
background: var(--cc-panel); border: 1px solid var(--cc-panel-border); box-shadow: var(--cc-shadow); font-size: 13px;
|
|
2479
2594
|
transition: opacity .2s; pointer-events: none; max-width: calc(100% - 32px);
|
|
2480
2595
|
}
|
|
2481
2596
|
.cc-bar {
|
|
2482
|
-
position: absolute; left: 50%; bottom:
|
|
2483
|
-
padding:
|
|
2597
|
+
position: absolute; left: 50%; bottom: 12px; transform: translateX(-50%); z-index: 20; display: flex; align-items: center; gap: 2px;
|
|
2598
|
+
padding: 4px; border-radius: 12px; background: var(--cc-panel); border: 1px solid var(--cc-panel-border); box-shadow: var(--cc-shadow);
|
|
2484
2599
|
width: max-content; max-width: calc(100% - 24px); box-sizing: border-box; flex-wrap: wrap; justify-content: center;
|
|
2485
2600
|
}
|
|
2486
|
-
.cc-bar .cc-chip { padding: 0 10px 0 6px; font: 600 12px/30px var(--cc-font, "JetBrains Mono", ui-monospace, Menlo, monospace); white-space: nowrap; display: flex; align-items: center; gap: 6px; max-width: 220px; overflow: hidden; text-overflow: ellipsis; }
|
|
2487
|
-
.cc-bar .cc-chip i { width: 10px; height: 10px; border-radius: 3px; background: var(--k); flex: none; }
|
|
2488
2601
|
.cc-btn {
|
|
2489
|
-
|
|
2490
|
-
cursor: pointer; white-space: nowrap;
|
|
2491
|
-
}
|
|
2492
|
-
.cc-btn:hover { background:
|
|
2602
|
+
display: inline-flex; align-items: center; justify-content: center; border: 0; background: transparent; color: var(--cc-muted);
|
|
2603
|
+
font: 400 13px/32px var(--cc-ui-font); padding: 0 12px; border-radius: 8px; cursor: pointer; white-space: nowrap; height: 32px; box-sizing: border-box;
|
|
2604
|
+
}
|
|
2605
|
+
.cc-btn:hover { background: var(--cc-raised); color: var(--cc-strong); }
|
|
2606
|
+
.cc-btn:focus-visible { outline: 2px solid var(--cc-ink); outline-offset: -2px; }
|
|
2607
|
+
.cc-btn.strong { color: var(--cc-strong); font-weight: 500; }
|
|
2608
|
+
.cc-btn.square { padding: 0; min-width: 30px; }
|
|
2609
|
+
.cc-btn.on { background: var(--cc-raised); color: var(--cc-strong); }
|
|
2493
2610
|
.cc-btn.danger { color: var(--cc-danger); }
|
|
2494
|
-
.cc-btn.primary { background: var(--cc-accent); color:
|
|
2495
|
-
.cc-
|
|
2496
|
-
.cc-
|
|
2611
|
+
.cc-btn.primary { background: var(--cc-accent); color: var(--cc-on-accent); font-weight: 600; }
|
|
2612
|
+
.cc-btn.primary:hover { filter: brightness(1.06); }
|
|
2613
|
+
.cc-zoom { min-width: 44px; text-align: center; font: 12px/32px var(--cc-font, "JetBrains Mono", ui-monospace, Menlo, monospace); color: var(--cc-muted); }
|
|
2614
|
+
.cc-language { padding: 0 8px; font: 500 11px/32px var(--cc-font, "JetBrains Mono", ui-monospace, Menlo, monospace); color: var(--cc-faint); white-space: nowrap; }
|
|
2615
|
+
.cc-sep { width: 1px; height: 20px; background: var(--cc-panel-border); margin: 0 4px; flex: none; }
|
|
2616
|
+
.cc-selbar {
|
|
2617
|
+
position: absolute; z-index: 19; display: flex; align-items: center; gap: 2px; padding: 3px; border-radius: 10px; white-space: nowrap;
|
|
2618
|
+
background: var(--cc-panel); border: 1px solid var(--cc-panel-border); box-shadow: var(--cc-shadow);
|
|
2619
|
+
}
|
|
2620
|
+
.cc-selbar .cc-btn { height: 26px; line-height: 26px; padding: 0 9px; border-radius: 7px; font-size: 12px; color: var(--cc-strong); }
|
|
2621
|
+
.cc-selbar .cc-btn.square { padding: 0; min-width: 26px; color: var(--cc-faint); }
|
|
2622
|
+
.cc-selbar .cc-btn.meta { color: var(--cc-faint); font-size: 11.5px; padding: 0 8px; }
|
|
2623
|
+
.cc-selbar .cc-btn.danger { color: var(--cc-danger); }
|
|
2624
|
+
.cc-selbar .cc-btn.primary { color: var(--cc-on-accent); }
|
|
2625
|
+
.cc-selbar .cc-sep { height: 16px; margin: 0 2px; }
|
|
2626
|
+
.cc-menu {
|
|
2627
|
+
position: absolute; z-index: 40; display: flex; flex-direction: column; min-width: 168px; padding: 4px; border-radius: 10px;
|
|
2628
|
+
background: var(--cc-panel); border: 1px solid var(--cc-panel-border); box-shadow: var(--cc-shadow-lg);
|
|
2629
|
+
}
|
|
2630
|
+
.cc-menu .cc-btn { justify-content: flex-start; height: 30px; line-height: 30px; padding: 0 10px; border-radius: 7px; font-size: 12.5px; color: var(--cc-text); }
|
|
2631
|
+
.cc-menu .cc-btn.danger { color: var(--cc-danger); }
|
|
2632
|
+
.cc-minimap {
|
|
2633
|
+
position: absolute; right: var(--cc-minimap-right, 12px); bottom: var(--cc-minimap-bottom, 12px); z-index: 18; width: 148px; height: 92px;
|
|
2634
|
+
box-sizing: border-box; border-radius: 10px; overflow: hidden; background: var(--cc-panel); border: 1px solid var(--cc-panel-border); cursor: pointer;
|
|
2635
|
+
}
|
|
2636
|
+
.cc-minimap-blocks { position: absolute; left: 10px; top: 13px; width: 128px; height: 66px; }
|
|
2637
|
+
.cc-minimap-blocks > i { position: absolute; border-radius: 2px; background: var(--k); opacity: .55; }
|
|
2638
|
+
.cc-minimap-view { position: absolute; left: 10px; top: 13px; box-sizing: border-box; border: 1.5px solid var(--cc-ink); border-radius: 6px; margin: 13px 0 0 10px; pointer-events: none; }
|
|
2639
|
+
@media (max-width: 640px) { .cc-minimap { display: none; } }
|
|
2497
2640
|
.cc-refs {
|
|
2498
2641
|
position: absolute; left: 50%; bottom: 64px; transform: translateX(-50%); z-index: 21; width: min(360px, calc(100% - 24px)); max-height: 45%;
|
|
2499
2642
|
overflow: auto; border-radius: 12px; background: var(--cc-panel); border: 1px solid var(--cc-panel-border); box-shadow: var(--cc-shadow); padding: 6px;
|
|
2500
2643
|
}
|
|
2501
|
-
.cc-refs h4 { margin: 8px 8px 4px; font
|
|
2644
|
+
.cc-refs h4 { margin: 8px 8px 4px; font: 600 11px/16px var(--cc-ui-font); text-transform: uppercase; letter-spacing: .06em; color: var(--cc-faint); }
|
|
2502
2645
|
.cc-refs button {
|
|
2503
2646
|
display: flex; width: 100%; align-items: center; gap: 8px; border: 0; background: transparent; color: var(--cc-text); text-align: left;
|
|
2504
2647
|
padding: 6px 8px; border-radius: 8px; cursor: pointer; font: 12px/18px var(--cc-font, "JetBrains Mono", ui-monospace, Menlo, monospace);
|
|
2505
2648
|
}
|
|
2506
|
-
.cc-refs button:hover { background:
|
|
2649
|
+
.cc-refs button:hover { background: var(--cc-raised); }
|
|
2507
2650
|
.cc-refs button i { width: 10px; height: 10px; border-radius: 3px; background: var(--k); flex: none; }
|
|
2508
2651
|
.cc-refs button span:last-child { margin-left: auto; color: var(--cc-muted); }
|
|
2509
2652
|
.cc-refs button:disabled { cursor: default; opacity: .7; }
|
|
@@ -2549,6 +2692,17 @@ var KIND_LABEL = {
|
|
|
2549
2692
|
namespace: "ns",
|
|
2550
2693
|
statement: "code"
|
|
2551
2694
|
};
|
|
2695
|
+
var KIND_GLYPH = {
|
|
2696
|
+
import: "\u2193",
|
|
2697
|
+
class: "C",
|
|
2698
|
+
function: "\u0192",
|
|
2699
|
+
component: "\u27E8\u27E9",
|
|
2700
|
+
state: "\u25C6",
|
|
2701
|
+
variable: "=",
|
|
2702
|
+
type: "T",
|
|
2703
|
+
namespace: "{}",
|
|
2704
|
+
statement: "\u25B8"
|
|
2705
|
+
};
|
|
2552
2706
|
var DIFF_CHIPS = {
|
|
2553
2707
|
added: '<span class="cc-diff-chip added" title="Added">+</span>',
|
|
2554
2708
|
changed: '<span class="cc-diff-chip changed" title="Changed">~</span>',
|
|
@@ -2557,6 +2711,9 @@ var DIFF_CHIPS = {
|
|
|
2557
2711
|
containsChanges: "",
|
|
2558
2712
|
unchanged: ""
|
|
2559
2713
|
};
|
|
2714
|
+
var MINIMAP = { width: 128, height: 66 };
|
|
2715
|
+
var ICON_SELECT = '<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true"><path d="M2.5 1.5l9 4.5-4 1.2-1.4 4.3z"/></svg>';
|
|
2716
|
+
var ICON_PAN = '<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true"><path d="M7 1.5v11M1.5 7h11M7 1.5l-2 2M7 1.5l2 2M7 12.5l-2-2M7 12.5l2-2M1.5 7l2-2M1.5 7l2 2M12.5 7l-2-2M12.5 7l-2 2"/></svg>';
|
|
2560
2717
|
var LABEL_TAGS = /* @__PURE__ */ new Set(["const", "let", "var", "interface", "type", "enum", "namespace", "get", "set"]);
|
|
2561
2718
|
var MIN_SCALE = 0.04;
|
|
2562
2719
|
var MAX_SCALE = 4;
|
|
@@ -2573,18 +2730,28 @@ var CodeCanvas = class {
|
|
|
2573
2730
|
this.blocksEl = el("div", "cc-blocks");
|
|
2574
2731
|
this.svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
2575
2732
|
this.svg.classList.add("cc-edges");
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
).join("")}</defs>`);
|
|
2733
|
+
const dotMarker = (id, color) => `<marker id="${id}" viewBox="0 0 8 8" refX="4" refY="4" markerWidth="6" markerHeight="6" markerUnits="userSpaceOnUse"><circle cx="4" cy="4" r="3" fill="${color}"/></marker>`;
|
|
2734
|
+
setHTML(this.svg, `<defs>${Object.keys(KIND_GLYPH).map((k) => dotMarker(`cc-dot-${k}`, `var(--k-${k})`)).join("")}${dotMarker("cc-dot-hl", "var(--cc-ink)")}</defs>`);
|
|
2579
2735
|
this.world.append(this.frameEl, this.blocksEl, this.svg);
|
|
2580
2736
|
this.banner = el("div", "cc-banner");
|
|
2581
2737
|
this.banner.hidden = true;
|
|
2582
2738
|
this.toastEl = el("div", "cc-toast");
|
|
2583
2739
|
this.toastEl.style.opacity = "0";
|
|
2584
|
-
this.bar = el("div", "cc-bar");
|
|
2740
|
+
this.bar = el("div", "cc-bar cc-dock");
|
|
2741
|
+
this.selectionBar = el("div", "cc-selbar");
|
|
2742
|
+
this.selectionBar.hidden = true;
|
|
2743
|
+
this.menuEl = el("div", "cc-menu");
|
|
2744
|
+
this.menuEl.setAttribute("role", "menu");
|
|
2745
|
+
this.menuEl.hidden = true;
|
|
2746
|
+
this.zoomLabel = el("span", "cc-zoom");
|
|
2585
2747
|
this.refsEl = el("div", "cc-refs");
|
|
2586
2748
|
this.refsEl.hidden = true;
|
|
2587
|
-
root.append(this.world, this.banner, this.refsEl, this.bar, this.toastEl);
|
|
2749
|
+
root.append(this.world, this.banner, this.refsEl, this.bar, this.selectionBar, this.menuEl, this.toastEl);
|
|
2750
|
+
if (opts.minimap ?? true) {
|
|
2751
|
+
this.minimapEl = el("div", "cc-minimap");
|
|
2752
|
+
this.minimapEl.setAttribute("aria-hidden", "true");
|
|
2753
|
+
root.append(this.minimapEl);
|
|
2754
|
+
}
|
|
2588
2755
|
container.appendChild(root);
|
|
2589
2756
|
this.bindEvents();
|
|
2590
2757
|
this.ro = new ResizeObserver(() => this.fittedToView ? this.fit(false) : this.queueCamera());
|
|
@@ -2603,7 +2770,14 @@ var CodeCanvas = class {
|
|
|
2603
2770
|
frameEl;
|
|
2604
2771
|
blocksEl;
|
|
2605
2772
|
svg;
|
|
2773
|
+
/** The dock at the bottom. */
|
|
2606
2774
|
bar;
|
|
2775
|
+
selectionBar;
|
|
2776
|
+
menuEl;
|
|
2777
|
+
minimapEl = null;
|
|
2778
|
+
minimapScale = 1;
|
|
2779
|
+
zoomLabel;
|
|
2780
|
+
tool = "select";
|
|
2607
2781
|
refsEl;
|
|
2608
2782
|
banner;
|
|
2609
2783
|
toastEl;
|
|
@@ -2965,7 +3139,9 @@ var CodeCanvas = class {
|
|
|
2965
3139
|
if (!g || !view) return;
|
|
2966
3140
|
const f = g.frame;
|
|
2967
3141
|
Object.assign(this.frameEl.style, { transform: `translate(${f.x}px,${f.y}px)`, width: f.w + "px", height: f.h + "px" });
|
|
2968
|
-
|
|
3142
|
+
const folder = this.path.includes("/") ? this.path.slice(0, this.path.lastIndexOf("/")) + " /" : "";
|
|
3143
|
+
const fileName2 = this.path.slice(this.path.lastIndexOf("/") + 1);
|
|
3144
|
+
setHTML(this.frameEl, `<div class="cc-frame-title">${folder ? `<span class="cc-frame-folder">${esc(folder)}</span>` : ""}<span class="cc-frame-file">${esc(fileName2)}</span><span class="cc-frame-count"></span></div>${view.roots.length ? "" : `<div class="cc-empty" style="left:${M.filePad}px;top:${M.filePad + M.fileHeader}px">Empty file. Double-click (or double-tap) to create a function.</div>`}`);
|
|
2969
3145
|
const seen = /* @__PURE__ */ new Set();
|
|
2970
3146
|
const diff = this.currentDiff();
|
|
2971
3147
|
for (const geo of g.order) {
|
|
@@ -3016,6 +3192,7 @@ var CodeCanvas = class {
|
|
|
3016
3192
|
this.renderEdges();
|
|
3017
3193
|
this.updateHighlight();
|
|
3018
3194
|
this.updateErrorMarkers();
|
|
3195
|
+
this.renderMinimap();
|
|
3019
3196
|
}
|
|
3020
3197
|
/** Blocks only the incoming side of a conflict has, drawn dashed where they would go. */
|
|
3021
3198
|
renderIncomingBlocks() {
|
|
@@ -3096,16 +3273,18 @@ var CodeCanvas = class {
|
|
|
3096
3273
|
}
|
|
3097
3274
|
blockHTML(b, geo, mini, change, changedLines = "", isConflicted = false) {
|
|
3098
3275
|
const label = blockLabel(b);
|
|
3099
|
-
const
|
|
3276
|
+
const chips = b.tags.filter((t) => t !== label && t !== "export" && (b.kind === "import" || !LABEL_TAGS.has(t)));
|
|
3100
3277
|
const exp = b.tags.includes("export") ? '<span class="cc-exp" title="exported"></span>' : "";
|
|
3101
3278
|
const isRemoved = change === "removed" || change === "incoming";
|
|
3102
3279
|
const fold = b.children.length && !isRemoved ? `<button class="cc-fold" title="${geo.collapsed ? "Expand" : "Collapse"}">${geo.collapsed ? "\u25B8" : "\u25BE"}</button>` : "";
|
|
3103
|
-
const
|
|
3104
|
-
<span class="cc-more">\u2026 ${b.previewMore} more line${b.previewMore > 1 ? "s" : ""}</span>` : ""
|
|
3280
|
+
const more = b.previewMore ? `
|
|
3281
|
+
<span class="cc-more">\u2026 ${b.previewMore} more line${b.previewMore > 1 ? "s" : ""}</span>` : "";
|
|
3282
|
+
const prev = changedLines ? `<pre class="cc-prev cc-diff-prev">${changedLines}</pre>` : b.preview.length ? `<pre class="cc-prev">${b.preview.map(highlightLine).join("\n")}${more}</pre>` : "";
|
|
3105
3283
|
const diffChip = (change ? DIFF_CHIPS[change] : "") + (isConflicted ? '<span class="cc-conflict-chip" title="Merge conflict">!</span>' : "");
|
|
3106
|
-
|
|
3284
|
+
const chipsHTML = chips.map((chip) => `<span class="cc-tag">${esc(chip)}</span>`).join("");
|
|
3285
|
+
return `<div class="cc-head"><span class="cc-glyph" title="${esc(label)}">${KIND_GLYPH[b.kind]}</span><span class="cc-name">${esc(b.name)}</span>${exp}${diffChip}<span class="cc-detail">${esc(
|
|
3107
3286
|
b.detail
|
|
3108
|
-
)}</span>${
|
|
3287
|
+
)}</span>${chipsHTML}${fold}</div>${prev}${mini ? `<div class="cc-mini">${mini}</div>` : ""}`;
|
|
3109
3288
|
}
|
|
3110
3289
|
/** Faint miniature of a collapsed block's nested blocks. */
|
|
3111
3290
|
miniMap(geo) {
|
|
@@ -3150,34 +3329,41 @@ var CodeCanvas = class {
|
|
|
3150
3329
|
this.svg.appendChild(p);
|
|
3151
3330
|
e = { key, from: w.from.id, to: w.to.id, el: p };
|
|
3152
3331
|
}
|
|
3153
|
-
e.el.setAttribute("class", `cc-edge u-${w.usage}`);
|
|
3154
|
-
e.el.
|
|
3155
|
-
e.el.
|
|
3332
|
+
e.el.setAttribute("class", `cc-edge u-${w.usage}${w.kind === "import" ? " to-import" : ""}`);
|
|
3333
|
+
e.el.style.setProperty("--edge", `var(--k-${w.kind})`);
|
|
3334
|
+
e.el.dataset.kind = w.kind;
|
|
3156
3335
|
e.el.setAttribute("d", edgePath(w.from, w.to));
|
|
3157
3336
|
next.push(e);
|
|
3158
3337
|
}
|
|
3159
3338
|
for (const e of old.values()) e.el.remove();
|
|
3160
3339
|
this.edges = next;
|
|
3340
|
+
const counter = this.frameEl.querySelector(".cc-frame-count");
|
|
3341
|
+
if (counter) counter.textContent = `${plural(view.blocks.size, "node")} \xB7 ${plural(next.length, "link")}`;
|
|
3161
3342
|
}
|
|
3162
3343
|
updateHighlight() {
|
|
3163
3344
|
const h = this.selected ?? this.hovered;
|
|
3164
3345
|
const inH = (id) => !!h && (id === h || id.startsWith(h + "/"));
|
|
3346
|
+
const isAroundSelection = (id) => !!this.selected && (inH(id) || this.selected.startsWith(id + "/"));
|
|
3165
3347
|
const linked = /* @__PURE__ */ new Set();
|
|
3166
3348
|
for (const e of this.edges) {
|
|
3167
3349
|
const on = inH(e.from) || inH(e.to);
|
|
3168
3350
|
e.el.classList.toggle("hl", on);
|
|
3351
|
+
e.el.setAttribute("marker-end", on ? "url(#cc-dot-hl)" : `url(#cc-dot-${e.el.dataset.kind})`);
|
|
3169
3352
|
if (on) {
|
|
3170
3353
|
linked.add(e.from);
|
|
3171
3354
|
linked.add(e.to);
|
|
3172
3355
|
}
|
|
3173
3356
|
}
|
|
3174
3357
|
this.root.classList.toggle("has-hl", !!h);
|
|
3358
|
+
this.root.classList.toggle("has-sel", !!this.selected);
|
|
3175
3359
|
for (const [id, { el: e }] of this.els) {
|
|
3176
3360
|
e.classList.toggle("cc-sel", id === this.selected);
|
|
3177
3361
|
e.classList.toggle("cc-linked", linked.has(id) && !inH(id));
|
|
3362
|
+
e.classList.toggle("cc-related", isAroundSelection(id) || linked.has(id));
|
|
3178
3363
|
e.classList.toggle("cc-editing", !!this.editing && id === this.editing.id);
|
|
3179
3364
|
}
|
|
3180
3365
|
if (this.refsOpen) this.renderRefs();
|
|
3366
|
+
this.placeSelectionBar();
|
|
3181
3367
|
}
|
|
3182
3368
|
// ---------------------------------------------------------------- camera
|
|
3183
3369
|
queueCamera() {
|
|
@@ -3191,7 +3377,7 @@ var CodeCanvas = class {
|
|
|
3191
3377
|
applyCamera() {
|
|
3192
3378
|
const { x, y, s } = this.cam;
|
|
3193
3379
|
this.world.style.transform = `translate(${x}px,${y}px) scale(${s})`;
|
|
3194
|
-
const grid =
|
|
3380
|
+
const grid = 22 * s;
|
|
3195
3381
|
this.root.style.backgroundSize = s < 0.25 ? "0 0" : `${grid}px ${grid}px`;
|
|
3196
3382
|
this.root.style.backgroundPosition = `${x}px ${y}px`;
|
|
3197
3383
|
const lod = s < 0.3 ? 2 : s < 0.55 ? 1 : 0;
|
|
@@ -3203,6 +3389,9 @@ var CodeCanvas = class {
|
|
|
3203
3389
|
this.root.classList.add("cc-moving");
|
|
3204
3390
|
clearTimeout(this.movingTimer);
|
|
3205
3391
|
this.movingTimer = window.setTimeout(() => this.root.classList.remove("cc-moving"), 160);
|
|
3392
|
+
this.zoomLabel.textContent = Math.round(s * 100) + "%";
|
|
3393
|
+
this.placeSelectionBar();
|
|
3394
|
+
this.placeMinimapView();
|
|
3206
3395
|
this.opts.onCameraChange?.(s);
|
|
3207
3396
|
}
|
|
3208
3397
|
setCamera(c) {
|
|
@@ -3276,6 +3465,19 @@ var CodeCanvas = class {
|
|
|
3276
3465
|
on(r, "pointercancel", (e) => this.onUp(e, true));
|
|
3277
3466
|
on(r, "wheel", (e) => this.onWheel(e), { passive: false });
|
|
3278
3467
|
on(r, "keydown", (e) => this.onKey(e));
|
|
3468
|
+
on(r, "contextmenu", (e) => {
|
|
3469
|
+
const id = e.target.closest?.(".cc-block")?.getAttribute("data-id");
|
|
3470
|
+
if (!id || this.isOverlay(e.target) || this.stale) return;
|
|
3471
|
+
e.preventDefault();
|
|
3472
|
+
this.openBlockMenu(id, this.local(e));
|
|
3473
|
+
});
|
|
3474
|
+
on(this.menuEl, "keydown", (e) => {
|
|
3475
|
+
if (e.key === "Escape") {
|
|
3476
|
+
e.stopPropagation();
|
|
3477
|
+
this.closeMenu();
|
|
3478
|
+
this.root.focus({ preventScroll: true });
|
|
3479
|
+
}
|
|
3480
|
+
});
|
|
3279
3481
|
on(r, "pointerover", (e) => {
|
|
3280
3482
|
if (e.pointerType !== "mouse" || this.gesture) return;
|
|
3281
3483
|
const id = e.target.closest?.(".cc-block")?.getAttribute("data-id") ?? null;
|
|
@@ -3297,9 +3499,11 @@ var CodeCanvas = class {
|
|
|
3297
3499
|
on(doc, "paste", (e) => this.onClipboard(e, "paste"));
|
|
3298
3500
|
}
|
|
3299
3501
|
isOverlay(t) {
|
|
3300
|
-
return !!t?.closest?.(".cc-editor, .cc-bar, .cc-refs, .cc-banner, .cc-rename");
|
|
3502
|
+
return !!t?.closest?.(".cc-editor, .cc-bar, .cc-selbar, .cc-menu, .cc-minimap, .cc-refs, .cc-banner, .cc-rename");
|
|
3301
3503
|
}
|
|
3302
3504
|
onDown(e) {
|
|
3505
|
+
if (!e.target?.closest?.(".cc-menu")) this.closeMenu();
|
|
3506
|
+
if (e.target?.closest?.(".cc-minimap")) return this.moveToMinimapPoint(e);
|
|
3303
3507
|
if (this.isOverlay(e.target)) return;
|
|
3304
3508
|
if (e.pointerType === "mouse" && e.button !== 0 && e.button !== 1) return;
|
|
3305
3509
|
this.root.focus({ preventScroll: true });
|
|
@@ -3313,7 +3517,7 @@ var CodeCanvas = class {
|
|
|
3313
3517
|
}
|
|
3314
3518
|
if (this.pointers.size > 2) return;
|
|
3315
3519
|
const p = this.local(e);
|
|
3316
|
-
if (e.button === 1) {
|
|
3520
|
+
if (e.button === 1 || this.tool === "pan") {
|
|
3317
3521
|
this.gesture = { t: "pan", id: e.pointerId, sx: p.x, sy: p.y, cam: { ...this.cam } };
|
|
3318
3522
|
return;
|
|
3319
3523
|
}
|
|
@@ -3812,63 +4016,180 @@ var CodeCanvas = class {
|
|
|
3812
4016
|
}
|
|
3813
4017
|
// ---------------------------------------------------------------- toolbar & references
|
|
3814
4018
|
renderBar() {
|
|
3815
|
-
|
|
4019
|
+
this.renderDock();
|
|
4020
|
+
this.renderSelectionBar();
|
|
4021
|
+
this.renderRefs();
|
|
4022
|
+
}
|
|
4023
|
+
/** The dock at the bottom: tools, file actions, zoom and the language. */
|
|
4024
|
+
renderDock() {
|
|
4025
|
+
const dock = this.bar;
|
|
4026
|
+
dock.replaceChildren();
|
|
4027
|
+
dock.hidden = !!this.editing;
|
|
4028
|
+
const toolButton = (tool, label, icon) => {
|
|
4029
|
+
const toolBtn = iconButton(label, icon, () => this.setTool(tool));
|
|
4030
|
+
toolBtn.classList.toggle("on", this.tool === tool);
|
|
4031
|
+
toolBtn.setAttribute("aria-pressed", String(this.tool === tool));
|
|
4032
|
+
return toolBtn;
|
|
4033
|
+
};
|
|
4034
|
+
dock.append(toolButton("select", "Select", ICON_SELECT), toolButton("pan", "Pan", ICON_PAN), el("div", "cc-sep"));
|
|
4035
|
+
const firstErrorBlock = this.diagnostics.find((d) => d.blockId)?.blockId;
|
|
4036
|
+
if (firstErrorBlock) dock.append(button(plural(this.diagnostics.length, "error"), () => this.showErrorsOf(firstErrorBlock), "danger"));
|
|
4037
|
+
const diff = this.currentDiff();
|
|
4038
|
+
if (diff) dock.append(changesSummary(diff), el("div", "cc-sep"));
|
|
4039
|
+
dock.append(button("+ Function", () => this.createFunctionAtCenter(), "strong"));
|
|
4040
|
+
if (this.clip) dock.append(button("Paste", () => (this.lastPointer = null, this.paste())));
|
|
4041
|
+
dock.append(
|
|
4042
|
+
this.layoutBeforeArrange ? button("Undo arrange", () => this.undoArrange()) : button("Arrange", () => this.arrange()),
|
|
4043
|
+
button("Fit", () => this.fit()),
|
|
4044
|
+
el("div", "cc-sep"),
|
|
4045
|
+
button("\u2212", () => this.zoomBy(0.8), "square"),
|
|
4046
|
+
this.zoomLabel,
|
|
4047
|
+
button("+", () => this.zoomBy(1.25), "square"),
|
|
4048
|
+
...this.languageLabelParts()
|
|
4049
|
+
);
|
|
4050
|
+
this.zoomLabel.title = "Zoom";
|
|
4051
|
+
}
|
|
4052
|
+
/** The bar above the selected block: what it uses and what uses it, and actions on it. */
|
|
4053
|
+
renderSelectionBar() {
|
|
4054
|
+
const bar = this.selectionBar;
|
|
3816
4055
|
bar.replaceChildren();
|
|
3817
|
-
bar.hidden = !!this.editing;
|
|
3818
4056
|
const b = this.selected ? this.view?.blocks.get(this.selected) : null;
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
4057
|
+
bar.hidden = !b || !!this.editing;
|
|
4058
|
+
if (!b) return;
|
|
4059
|
+
const refs = this.refsFor(b.id);
|
|
4060
|
+
const usedBy = `used by ${refs.incoming.length}`;
|
|
4061
|
+
const refsToggle = button(refs.outgoing.length ? `${plural(refs.outgoing.length, "dep")} \xB7 ${usedBy}` : usedBy, () => this.toggleRefs(), "meta");
|
|
4062
|
+
refsToggle.classList.add("cc-refs-toggle");
|
|
4063
|
+
refsToggle.title = "References";
|
|
4064
|
+
refsToggle.setAttribute("aria-expanded", String(this.refsOpen));
|
|
4065
|
+
const close = button("\xD7", () => this.select(null), "square");
|
|
4066
|
+
close.setAttribute("aria-label", "Clear selection");
|
|
4067
|
+
if (this.conflicts) {
|
|
4068
|
+
const isConflicted = this.conflicts.hunksByBlock.has(b.id);
|
|
4069
|
+
const firstHunk = isConflicted ? this.conflicts.hunks[this.conflicts.hunksByBlock.get(b.id)[0]] : void 0;
|
|
4070
|
+
const keep = (label, choice, side) => {
|
|
4071
|
+
const keepButton = button(label, () => this.keepConflictSide(b.id, choice), choice === "current" ? "primary" : void 0);
|
|
4072
|
+
if (side) keepButton.title = side;
|
|
4073
|
+
return keepButton;
|
|
4074
|
+
};
|
|
4075
|
+
const keepButtons = firstHunk ? [keep("Keep current", "current", firstHunk.currentLabel), keep("Keep incoming", "incoming", firstHunk.incomingLabel), keep("Keep both", "both"), el("div", "cc-sep")] : [];
|
|
4076
|
+
bar.append(refsToggle, el("div", "cc-sep"), ...keepButtons, button("Edit", () => this.openEditor(b.id)), this.moreButton(b), close);
|
|
4077
|
+
} else {
|
|
3824
4078
|
const errors = this.diagnosticsInside(b.id);
|
|
3825
|
-
if (this.conflicts) {
|
|
3826
|
-
const isConflicted = this.conflicts.hunksByBlock.has(b.id);
|
|
3827
|
-
const firstHunk = isConflicted ? this.conflicts.hunks[this.conflicts.hunksByBlock.get(b.id)[0]] : void 0;
|
|
3828
|
-
const keep = (label, choice, side) => {
|
|
3829
|
-
const keepButton = button(label, () => this.keepConflictSide(b.id, choice), choice === "current" ? "primary" : void 0);
|
|
3830
|
-
if (side) keepButton.title = side;
|
|
3831
|
-
return keepButton;
|
|
3832
|
-
};
|
|
3833
|
-
bar.append(
|
|
3834
|
-
chip,
|
|
3835
|
-
el("div", "cc-sep"),
|
|
3836
|
-
...firstHunk ? [keep("Keep current", "current", firstHunk.currentLabel), keep("Keep incoming", "incoming", firstHunk.incomingLabel), keep("Keep both", "both"), el("div", "cc-sep")] : [],
|
|
3837
|
-
button("Edit", () => this.openEditor(b.id)),
|
|
3838
|
-
button("Copy", () => this.copy(b.id)),
|
|
3839
|
-
...this.languageLabelParts()
|
|
3840
|
-
);
|
|
3841
|
-
return this.renderRefs();
|
|
3842
|
-
}
|
|
3843
4079
|
bar.append(
|
|
3844
|
-
|
|
4080
|
+
refsToggle,
|
|
3845
4081
|
el("div", "cc-sep"),
|
|
3846
4082
|
...errors.length ? [button(plural(errors.length, "error"), () => this.toggleRefs(), "danger")] : [],
|
|
3847
|
-
button("
|
|
4083
|
+
...this.opts.onOpenCode ? [button("Open code", () => this.opts.onOpenCode(b))] : [],
|
|
4084
|
+
...this.opts.onAsk ? [button("Ask", () => this.opts.onAsk(b))] : [],
|
|
3848
4085
|
...isRenamable(b) ? [button("Rename", () => this.startRename(b.id))] : [],
|
|
3849
|
-
button(
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
...b.insert ? [button("+ Inside", () => this.createFunctionAtCenter())] : [],
|
|
3853
|
-
button("Delete", () => this.deleteBlock(b.id), "danger")
|
|
3854
|
-
);
|
|
3855
|
-
} else {
|
|
3856
|
-
const firstErrorBlock = this.diagnostics.find((d) => d.blockId)?.blockId;
|
|
3857
|
-
if (firstErrorBlock) bar.append(button(plural(this.diagnostics.length, "error"), () => this.showErrorsOf(firstErrorBlock), "danger"));
|
|
3858
|
-
const diff = this.currentDiff();
|
|
3859
|
-
if (diff) bar.append(changesSummary(diff), el("div", "cc-sep"));
|
|
3860
|
-
bar.append(button("+ Function", () => this.createFunctionAtCenter()));
|
|
3861
|
-
if (this.clip) bar.append(button("Paste", () => (this.lastPointer = null, this.paste())));
|
|
3862
|
-
bar.append(
|
|
3863
|
-
this.layoutBeforeArrange ? button("Undo arrange", () => this.undoArrange()) : button("Arrange", () => this.arrange()),
|
|
3864
|
-
el("div", "cc-sep"),
|
|
3865
|
-
button("Fit", () => this.fit()),
|
|
3866
|
-
button("\u2212", () => this.zoomBy(0.8)),
|
|
3867
|
-
button("+", () => this.zoomBy(1.25))
|
|
4086
|
+
button("Delete", () => this.deleteBlock(b.id), "danger"),
|
|
4087
|
+
this.moreButton(b),
|
|
4088
|
+
close
|
|
3868
4089
|
);
|
|
3869
4090
|
}
|
|
3870
|
-
|
|
3871
|
-
|
|
4091
|
+
this.placeSelectionBar();
|
|
4092
|
+
}
|
|
4093
|
+
moreButton(b) {
|
|
4094
|
+
const more = button("\u22EF", () => {
|
|
4095
|
+
const box = more.getBoundingClientRect();
|
|
4096
|
+
const rootBox = this.root.getBoundingClientRect();
|
|
4097
|
+
this.openBlockMenu(b.id, { x: box.left - rootBox.left, y: box.bottom - rootBox.top + 4 });
|
|
4098
|
+
}, "square");
|
|
4099
|
+
more.setAttribute("aria-label", "More actions");
|
|
4100
|
+
more.setAttribute("aria-haspopup", "menu");
|
|
4101
|
+
return more;
|
|
4102
|
+
}
|
|
4103
|
+
/** The block's actions as a menu, from ⋯ or a right-click. */
|
|
4104
|
+
openBlockMenu(id, at) {
|
|
4105
|
+
const b = this.view?.blocks.get(id);
|
|
4106
|
+
if (!b) return;
|
|
4107
|
+
if (this.selected !== id) this.select(id);
|
|
4108
|
+
const item = (label, action, variant) => button(label, () => (this.closeMenu(), action()), variant);
|
|
4109
|
+
const items = [
|
|
4110
|
+
item("Edit", () => this.openEditor(id)),
|
|
4111
|
+
...this.opts.onOpenCode ? [item("Open code", () => this.opts.onOpenCode(b))] : [],
|
|
4112
|
+
...this.opts.onAsk ? [item("Ask", () => this.opts.onAsk(b))] : [],
|
|
4113
|
+
...isRenamable(b) && !this.conflicts ? [item("Rename", () => this.startRename(id))] : [],
|
|
4114
|
+
item("References", () => (this.refsOpen = true, this.renderBar())),
|
|
4115
|
+
item("Copy", () => this.copy(id)),
|
|
4116
|
+
...b.kind !== "import" && b.kind !== "statement" && !this.conflicts ? [item("Duplicate", () => this.duplicate(id))] : [],
|
|
4117
|
+
...b.insert && !this.conflicts ? [item("+ Inside", () => this.createFunctionAtCenter())] : [],
|
|
4118
|
+
...!this.conflicts ? [item("Delete", () => this.deleteBlock(id), "danger")] : []
|
|
4119
|
+
];
|
|
4120
|
+
const menu = this.menuEl;
|
|
4121
|
+
menu.replaceChildren(...items);
|
|
4122
|
+
menu.hidden = false;
|
|
4123
|
+
const left = clamp2(at.x, 8, Math.max(8, this.root.clientWidth - menu.offsetWidth - 8));
|
|
4124
|
+
const top = clamp2(at.y, 8, Math.max(8, this.root.clientHeight - menu.offsetHeight - 8));
|
|
4125
|
+
Object.assign(menu.style, { left: left + "px", top: top + "px" });
|
|
4126
|
+
items[0]?.focus({ preventScroll: true });
|
|
4127
|
+
}
|
|
4128
|
+
closeMenu() {
|
|
4129
|
+
this.menuEl.hidden = true;
|
|
4130
|
+
}
|
|
4131
|
+
/** Keep the selection bar right above the selected block (below it when there is no room). */
|
|
4132
|
+
placeSelectionBar() {
|
|
4133
|
+
const bar = this.selectionBar;
|
|
4134
|
+
const geo = this.selected ? this.geom?.geo.get(this.selected) : void 0;
|
|
4135
|
+
if (bar.hidden || !geo || geo.hidden) {
|
|
4136
|
+
bar.style.visibility = "hidden";
|
|
4137
|
+
return;
|
|
4138
|
+
}
|
|
4139
|
+
bar.style.visibility = "";
|
|
4140
|
+
const { x, y, s } = this.cam;
|
|
4141
|
+
const blockTop = geo.ay * s + y;
|
|
4142
|
+
const blockBottom = (geo.ay + geo.h) * s + y;
|
|
4143
|
+
const barHeight = bar.offsetHeight || 34;
|
|
4144
|
+
const fitsAbove = blockTop - barHeight - 8 >= 8;
|
|
4145
|
+
const left = clamp2(geo.ax * s + x, 8, Math.max(8, this.root.clientWidth - bar.offsetWidth - 8));
|
|
4146
|
+
bar.style.left = left + "px";
|
|
4147
|
+
bar.style.top = (fitsAbove ? blockTop - barHeight - 8 : blockBottom + 8) + "px";
|
|
4148
|
+
}
|
|
4149
|
+
setTool(tool) {
|
|
4150
|
+
this.tool = tool;
|
|
4151
|
+
this.root.classList.toggle("cc-tool-pan", tool === "pan");
|
|
4152
|
+
this.renderDock();
|
|
4153
|
+
}
|
|
4154
|
+
/** The whole file in small, in a corner; the visible part outlined. A click moves there. */
|
|
4155
|
+
renderMinimap() {
|
|
4156
|
+
const g = this.geom;
|
|
4157
|
+
const minimap = this.minimapEl;
|
|
4158
|
+
if (!minimap || !g) return;
|
|
4159
|
+
const f = g.frame;
|
|
4160
|
+
const scale = Math.min(MINIMAP.width / f.w, MINIMAP.height / f.h);
|
|
4161
|
+
this.minimapScale = scale;
|
|
4162
|
+
const bars = g.order.filter((geo) => !geo.hidden && geo.block.depth <= 1).map(
|
|
4163
|
+
(geo) => `<i style="--k:var(--k-${geo.block.kind});left:${((geo.ax - f.x) * scale).toFixed(1)}px;top:${((geo.ay - f.y) * scale).toFixed(1)}px;width:${Math.max(2, geo.w * scale).toFixed(1)}px;height:${Math.max(
|
|
4164
|
+
2,
|
|
4165
|
+
geo.h * scale
|
|
4166
|
+
).toFixed(1)}px"></i>`
|
|
4167
|
+
).join("");
|
|
4168
|
+
setHTML(minimap, `<div class="cc-minimap-blocks">${bars}</div><span class="cc-minimap-view"></span>`);
|
|
4169
|
+
this.placeMinimapView();
|
|
4170
|
+
}
|
|
4171
|
+
placeMinimapView() {
|
|
4172
|
+
const g = this.geom;
|
|
4173
|
+
const view = this.minimapEl?.querySelector(".cc-minimap-view");
|
|
4174
|
+
if (!g || !view) return;
|
|
4175
|
+
const { x, y, s } = this.cam;
|
|
4176
|
+
const f = g.frame;
|
|
4177
|
+
const scale = this.minimapScale;
|
|
4178
|
+
const left = clamp2((-x / s - f.x) * scale, -2, MINIMAP.width);
|
|
4179
|
+
const top = clamp2((-y / s - f.y) * scale, -2, MINIMAP.height);
|
|
4180
|
+
const right = clamp2(((this.root.clientWidth - x) / s - f.x) * scale, left, MINIMAP.width + 2);
|
|
4181
|
+
const bottom = clamp2(((this.root.clientHeight - y) / s - f.y) * scale, top, MINIMAP.height + 2);
|
|
4182
|
+
Object.assign(view.style, { left: left + "px", top: top + "px", width: right - left + "px", height: bottom - top + "px" });
|
|
4183
|
+
}
|
|
4184
|
+
moveToMinimapPoint(e) {
|
|
4185
|
+
const g = this.geom;
|
|
4186
|
+
const inner = this.minimapEl?.querySelector(".cc-minimap-blocks");
|
|
4187
|
+
if (!g || !inner) return;
|
|
4188
|
+
const box = inner.getBoundingClientRect();
|
|
4189
|
+
const worldX = g.frame.x + (e.clientX - box.left) / this.minimapScale;
|
|
4190
|
+
const worldY = g.frame.y + (e.clientY - box.top) / this.minimapScale;
|
|
4191
|
+
const s = this.cam.s;
|
|
4192
|
+
this.moveCamera({ x: this.root.clientWidth / 2 - worldX * s, y: this.root.clientHeight / 2 - worldY * s, s }, true);
|
|
3872
4193
|
}
|
|
3873
4194
|
languageLabelParts() {
|
|
3874
4195
|
if (!this.view) return [];
|
|
@@ -4030,6 +4351,13 @@ function el(tag, cls) {
|
|
|
4030
4351
|
if (cls) e.className = cls;
|
|
4031
4352
|
return e;
|
|
4032
4353
|
}
|
|
4354
|
+
function iconButton(label, icon, onClick) {
|
|
4355
|
+
const iconBtn = button("", onClick, "square");
|
|
4356
|
+
setHTML(iconBtn, icon);
|
|
4357
|
+
iconBtn.setAttribute("aria-label", label);
|
|
4358
|
+
iconBtn.title = label;
|
|
4359
|
+
return iconBtn;
|
|
4360
|
+
}
|
|
4033
4361
|
function button(label, onClick, variant) {
|
|
4034
4362
|
const b = keepsCanvasFocus(document.createElement("button"));
|
|
4035
4363
|
b.className = "cc-btn" + (variant ? " " + variant : "");
|
|
@@ -4044,9 +4372,6 @@ function keepsCanvasFocus(element) {
|
|
|
4044
4372
|
element.addEventListener("pointerdown", (e) => e.preventDefault());
|
|
4045
4373
|
return element;
|
|
4046
4374
|
}
|
|
4047
|
-
function esc(s) {
|
|
4048
|
-
return s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
4049
|
-
}
|
|
4050
4375
|
function clamp2(v, lo, hi) {
|
|
4051
4376
|
return Math.min(hi, Math.max(lo, v));
|
|
4052
4377
|
}
|