@codecanvas-ide/engine 0.2.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 +4 -1
- package/dist/codecanvas-engine.js +299 -227
- package/dist/index.js +762 -270
- package/dist/index.js.map +4 -4
- package/dist/types/canvas.d.ts +31 -0
- package/dist/types/highlight.d.ts +6 -0
- package/dist/types/html.d.ts +2 -0
- package/dist/types/layoutRules.d.ts +1 -1
- package/dist/types/parse.d.ts +6 -1
- package/dist/types/react.d.ts +30 -0
- package/dist/types/styles.d.ts +1 -1
- package/dist/types/syntax.d.ts +3 -0
- package/dist/types/types.d.ts +15 -2
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,101 @@
|
|
|
1
1
|
// src/parse.ts
|
|
2
|
+
import ts3 from "typescript";
|
|
3
|
+
|
|
4
|
+
// src/react.ts
|
|
5
|
+
import ts2 from "typescript";
|
|
6
|
+
|
|
7
|
+
// src/syntax.ts
|
|
2
8
|
import ts from "typescript";
|
|
9
|
+
function unwrap(e) {
|
|
10
|
+
while (ts.isParenthesizedExpression(e) || ts.isAsExpression(e) || ts.isSatisfiesExpression(e) || ts.isTypeAssertionExpression(e)) e = e.expression;
|
|
11
|
+
return e;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/react.ts
|
|
15
|
+
var REACT_MODULE = /^(react|react-dom|react-native)(\/|$)/;
|
|
16
|
+
function fileLanguageOf(path, sf) {
|
|
17
|
+
const script = /\.[cm]?jsx?$/.test(path) ? "js" : "ts";
|
|
18
|
+
const hasJsxExtension = /\.[jt]sx$/.test(path);
|
|
19
|
+
const mayHaveJsx = script === "js" && /<[A-Za-z>]/.test(sf.text);
|
|
20
|
+
const hasJsx = hasJsxExtension || mayHaveJsx && containsJsx(sf);
|
|
21
|
+
const isReact = hasJsx || importsReact(sf);
|
|
22
|
+
return { script, hasJsx, isReact, label: languageLabel(script, hasJsx, isReact) };
|
|
23
|
+
}
|
|
24
|
+
function languageLabel(script, hasJsx, isReact) {
|
|
25
|
+
if (hasJsx) return script === "js" ? "React JSX" : "React TSX";
|
|
26
|
+
if (isReact) return script === "js" ? "React JS" : "React TS";
|
|
27
|
+
return script === "js" ? "JS" : "TS";
|
|
28
|
+
}
|
|
29
|
+
function isJsx(node) {
|
|
30
|
+
return ts2.isJsxElement(node) || ts2.isJsxSelfClosingElement(node) || ts2.isJsxFragment(node);
|
|
31
|
+
}
|
|
32
|
+
function containsJsx(node) {
|
|
33
|
+
return isJsx(node) || !!ts2.forEachChild(node, (child) => containsJsx(child) || void 0);
|
|
34
|
+
}
|
|
35
|
+
function importsReact(sf) {
|
|
36
|
+
return sf.statements.some((statement) => {
|
|
37
|
+
const isImportOrReExport = ts2.isImportDeclaration(statement) || ts2.isExportDeclaration(statement);
|
|
38
|
+
if (isImportOrReExport) return !!statement.moduleSpecifier && ts2.isStringLiteral(statement.moduleSpecifier) && REACT_MODULE.test(statement.moduleSpecifier.text);
|
|
39
|
+
if (ts2.isImportEqualsDeclaration(statement)) return ts2.isExternalModuleReference(statement.moduleReference) && isReactModuleName(statement.moduleReference.expression);
|
|
40
|
+
if (ts2.isVariableStatement(statement)) return statement.declarationList.declarations.some((declaration) => !!declaration.initializer && isRequireOfReact(declaration.initializer));
|
|
41
|
+
return false;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function isRequireOfReact(expression) {
|
|
45
|
+
if (ts2.isPropertyAccessExpression(expression)) return isRequireOfReact(expression.expression);
|
|
46
|
+
const isRequireCall = ts2.isCallExpression(expression) && ts2.isIdentifier(expression.expression) && expression.expression.text === "require";
|
|
47
|
+
return isRequireCall && expression.arguments.length === 1 && isReactModuleName(expression.arguments[0]);
|
|
48
|
+
}
|
|
49
|
+
function isReactModuleName(expression) {
|
|
50
|
+
return ts2.isStringLiteral(expression) && REACT_MODULE.test(expression.text);
|
|
51
|
+
}
|
|
52
|
+
var COMPONENT_WRAPPERS = /* @__PURE__ */ new Set(["memo", "forwardRef", "React.memo", "React.forwardRef"]);
|
|
53
|
+
var COMPONENT_BASE_CLASSES = /* @__PURE__ */ new Set(["Component", "PureComponent", "React.Component", "React.PureComponent"]);
|
|
54
|
+
var isComponentName = (name) => /^[A-Z]/.test(name);
|
|
55
|
+
function returnsJsx(fn) {
|
|
56
|
+
if (!fn.body) return false;
|
|
57
|
+
if (!ts2.isBlock(fn.body)) return isJsxValue(fn.body);
|
|
58
|
+
const hasJsxReturn = (node) => {
|
|
59
|
+
if (ts2.isReturnStatement(node)) return !!node.expression && isJsxValue(node.expression);
|
|
60
|
+
if (ts2.isFunctionLike(node) || ts2.isClassLike(node)) return false;
|
|
61
|
+
return !!ts2.forEachChild(node, (child) => hasJsxReturn(child) || void 0);
|
|
62
|
+
};
|
|
63
|
+
return hasJsxReturn(fn.body);
|
|
64
|
+
}
|
|
65
|
+
function isJsxValue(expression) {
|
|
66
|
+
const value = unwrap(expression);
|
|
67
|
+
if (isJsx(value)) return true;
|
|
68
|
+
if (ts2.isConditionalExpression(value)) return isJsxValue(value.whenTrue) || isJsxValue(value.whenFalse);
|
|
69
|
+
if (ts2.isBinaryExpression(value)) return isJsxValue(value.left) || isJsxValue(value.right);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
function componentFunctionOf(initializer) {
|
|
73
|
+
const value = unwrap(initializer);
|
|
74
|
+
if (ts2.isArrowFunction(value) || ts2.isFunctionExpression(value)) return returnsJsx(value) ? value : void 0;
|
|
75
|
+
const isWrapperCall = ts2.isCallExpression(value) && value.arguments.length > 0 && COMPONENT_WRAPPERS.has(value.expression.getText());
|
|
76
|
+
if (!isWrapperCall) return void 0;
|
|
77
|
+
const wrapped = unwrap(value.arguments[0]);
|
|
78
|
+
if (ts2.isArrowFunction(wrapped) || ts2.isFunctionExpression(wrapped)) return wrapped;
|
|
79
|
+
return componentFunctionOf(wrapped);
|
|
80
|
+
}
|
|
81
|
+
function isComponentClass(cls) {
|
|
82
|
+
const extendsClause = cls.heritageClauses?.find((clause) => clause.token === ts2.SyntaxKind.ExtendsKeyword);
|
|
83
|
+
const baseClass = extendsClause?.types[0]?.expression.getText();
|
|
84
|
+
return !!baseClass && COMPONENT_BASE_CLASSES.has(baseClass);
|
|
85
|
+
}
|
|
86
|
+
var STATE_HOOKS = /* @__PURE__ */ new Set(["useState", "useReducer", "React.useState", "React.useReducer"]);
|
|
87
|
+
function stateHookOf(declaration) {
|
|
88
|
+
const call = declaration.initializer && unwrap(declaration.initializer);
|
|
89
|
+
const isStateHookCall = !!call && ts2.isCallExpression(call) && STATE_HOOKS.has(call.expression.getText());
|
|
90
|
+
if (!isStateHookCall) return void 0;
|
|
91
|
+
if (ts2.isIdentifier(declaration.name)) return { call, value: declaration };
|
|
92
|
+
if (!ts2.isArrayBindingPattern(declaration.name)) return void 0;
|
|
93
|
+
const [value, setter] = declaration.name.elements.map((element) => ts2.isBindingElement(element) && ts2.isIdentifier(element.name) ? element : void 0);
|
|
94
|
+
return value || setter ? { call, value, setter } : void 0;
|
|
95
|
+
}
|
|
96
|
+
var isHookName = (name) => /^use[A-Z0-9]/.test(name);
|
|
97
|
+
|
|
98
|
+
// src/parse.ts
|
|
3
99
|
var PREVIEW_LINES = 6;
|
|
4
100
|
var CONTAINER_PREVIEW_LINES = 3;
|
|
5
101
|
var PREVIEW_WIDTH = 64;
|
|
@@ -15,11 +111,12 @@ function parseStructure(path, text, prev) {
|
|
|
15
111
|
let sf;
|
|
16
112
|
if (old && !old.reused) {
|
|
17
113
|
old.reused = true;
|
|
18
|
-
sf =
|
|
114
|
+
sf = ts3.updateSourceFile(old.sf, text, changeRange(prev.text, text));
|
|
19
115
|
} else {
|
|
20
|
-
sf =
|
|
116
|
+
sf = ts3.createSourceFile(path, text, ts3.ScriptTarget.Latest, true, kind);
|
|
21
117
|
}
|
|
22
|
-
const
|
|
118
|
+
const language = fileLanguageOf(path, sf);
|
|
119
|
+
const b = new Builder(sf, text, language.isReact);
|
|
23
120
|
b.collectStatements(sf.statements, null, 0);
|
|
24
121
|
const syntax = sf.parseDiagnostics ?? [];
|
|
25
122
|
const model = {
|
|
@@ -31,7 +128,8 @@ function parseStructure(path, text, prev) {
|
|
|
31
128
|
syntaxErrors: syntax.length,
|
|
32
129
|
firstErrorLine: -1,
|
|
33
130
|
diagnostics: [],
|
|
34
|
-
indentUnit: detectIndentUnit(text)
|
|
131
|
+
indentUnit: detectIndentUnit(text),
|
|
132
|
+
language
|
|
35
133
|
};
|
|
36
134
|
model.diagnostics = syntax.map((d) => toCodeDiagnostic(model, sf, d, "syntax"));
|
|
37
135
|
model.firstErrorLine = model.diagnostics[0]?.line ?? -1;
|
|
@@ -54,9 +152,9 @@ function toCodeDiagnostic(model, sf, d, source) {
|
|
|
54
152
|
start,
|
|
55
153
|
length: d.length ?? 0,
|
|
56
154
|
line: sf.getLineAndCharacterOfPosition(Math.min(start, sf.text.length)).line + 1,
|
|
57
|
-
message:
|
|
155
|
+
message: ts3.flattenDiagnosticMessageText(d.messageText, "\n"),
|
|
58
156
|
source,
|
|
59
|
-
severity: d.category ===
|
|
157
|
+
severity: d.category === ts3.DiagnosticCategory.Error ? "error" : "warning",
|
|
60
158
|
blockId: blockAt(model, start)?.id ?? null
|
|
61
159
|
};
|
|
62
160
|
}
|
|
@@ -77,29 +175,29 @@ function changeRange(a, b) {
|
|
|
77
175
|
while (s < n && a.charCodeAt(s) === b.charCodeAt(s)) s++;
|
|
78
176
|
let e = 0;
|
|
79
177
|
while (e < n - s && a.charCodeAt(a.length - 1 - e) === b.charCodeAt(b.length - 1 - e)) e++;
|
|
80
|
-
return
|
|
178
|
+
return ts3.createTextChangeRange(ts3.createTextSpan(s, a.length - e - s), b.length - e - s);
|
|
81
179
|
}
|
|
82
180
|
function commonJsExportedFunction(s) {
|
|
83
|
-
if (!
|
|
181
|
+
if (!ts3.isExpressionStatement(s) || !ts3.isBinaryExpression(s.expression) || s.expression.operatorToken.kind !== ts3.SyntaxKind.EqualsToken) return null;
|
|
84
182
|
const { left, right } = s.expression;
|
|
85
183
|
const fn = unwrap(right);
|
|
86
|
-
const isFunction =
|
|
87
|
-
if (!isFunction || !
|
|
184
|
+
const isFunction = ts3.isFunctionExpression(fn) || ts3.isArrowFunction(fn);
|
|
185
|
+
if (!isFunction || !ts3.isPropertyAccessExpression(left) || !ts3.isIdentifier(left.name)) return null;
|
|
88
186
|
const target = left.expression.getText();
|
|
89
187
|
const isExportsObject = target === "exports" || target === "module.exports";
|
|
90
188
|
return isExportsObject ? { assignment: s.expression, exportedName: left.name, fn } : null;
|
|
91
189
|
}
|
|
92
190
|
function scriptKindOf(path) {
|
|
93
|
-
if (/\.[cm]?jsx?$/.test(path)) return path.endsWith("x") ?
|
|
94
|
-
return path.endsWith("x") ?
|
|
191
|
+
if (/\.[cm]?jsx?$/.test(path)) return path.endsWith("x") ? ts3.ScriptKind.JSX : ts3.ScriptKind.JS;
|
|
192
|
+
return path.endsWith("x") ? ts3.ScriptKind.TSX : ts3.ScriptKind.TS;
|
|
95
193
|
}
|
|
96
194
|
function createProgram(sf) {
|
|
97
195
|
const options = {
|
|
98
196
|
noLib: true,
|
|
99
197
|
noResolve: true,
|
|
100
198
|
allowJs: true,
|
|
101
|
-
target:
|
|
102
|
-
jsx:
|
|
199
|
+
target: ts3.ScriptTarget.Latest,
|
|
200
|
+
jsx: ts3.JsxEmit.Preserve,
|
|
103
201
|
types: []
|
|
104
202
|
};
|
|
105
203
|
const host = {
|
|
@@ -115,7 +213,7 @@ function createProgram(sf) {
|
|
|
115
213
|
readFile: () => void 0,
|
|
116
214
|
getDirectories: () => []
|
|
117
215
|
};
|
|
118
|
-
return
|
|
216
|
+
return ts3.createProgram({ rootNames: [sf.fileName], options, host });
|
|
119
217
|
}
|
|
120
218
|
function detectIndentUnit(text) {
|
|
121
219
|
const m = /\n(\t+|[ ]{2,8})\S/.exec(text);
|
|
@@ -123,12 +221,14 @@ function detectIndentUnit(text) {
|
|
|
123
221
|
return m[1].startsWith(" ") ? " " : m[1].length >= 4 && m[1].length % 4 === 0 ? " " : " ";
|
|
124
222
|
}
|
|
125
223
|
var Builder = class {
|
|
126
|
-
constructor(sf, text) {
|
|
224
|
+
constructor(sf, text, isReact) {
|
|
127
225
|
this.sf = sf;
|
|
128
226
|
this.text = text;
|
|
227
|
+
this.isReact = isReact;
|
|
129
228
|
}
|
|
130
229
|
sf;
|
|
131
230
|
text;
|
|
231
|
+
isReact;
|
|
132
232
|
roots = [];
|
|
133
233
|
byId = /* @__PURE__ */ new Map();
|
|
134
234
|
/**
|
|
@@ -140,6 +240,8 @@ var Builder = class {
|
|
|
140
240
|
rangeToBlock = /* @__PURE__ */ new Map();
|
|
141
241
|
/** Every name a usage could resolve to. Other identifiers skip the (slow) checker. */
|
|
142
242
|
names = /* @__PURE__ */ new Set();
|
|
243
|
+
/** Setters of React state (`setCount`), keyed like `declToBlock`: using one writes the state. */
|
|
244
|
+
stateSetters = /* @__PURE__ */ new Set();
|
|
143
245
|
// ---------- structure ----------
|
|
144
246
|
collectStatements(stmts, parent, depth, declarationsOnly = false) {
|
|
145
247
|
let group = [];
|
|
@@ -168,21 +270,23 @@ var Builder = class {
|
|
|
168
270
|
};
|
|
169
271
|
for (let i = 0; i < stmts.length; i++) {
|
|
170
272
|
const s = stmts[i];
|
|
171
|
-
if (
|
|
273
|
+
if (ts3.isFunctionDeclaration(s)) {
|
|
172
274
|
flushGroup();
|
|
173
275
|
const overloads = [s];
|
|
174
276
|
while (!overloads[overloads.length - 1].body) {
|
|
175
277
|
const next = stmts[i + 1];
|
|
176
|
-
if (next &&
|
|
278
|
+
if (next && ts3.isFunctionDeclaration(next) && next.name?.text === s.name?.text) {
|
|
177
279
|
overloads.push(next);
|
|
178
280
|
i++;
|
|
179
281
|
} else break;
|
|
180
282
|
}
|
|
181
|
-
|
|
182
|
-
|
|
283
|
+
const name = s.name?.text ?? "default";
|
|
284
|
+
const isComponent = this.isReact && isComponentName(name) && returnsJsx(overloads[overloads.length - 1]);
|
|
285
|
+
this.addFunctionLike(overloads, name, parent, depth, { kind: isComponent ? "component" : "function" });
|
|
286
|
+
} else if (ts3.isClassDeclaration(s)) {
|
|
183
287
|
flushGroup();
|
|
184
288
|
this.addClass(s, s, s.name?.text ?? "default", parent, depth);
|
|
185
|
-
} else if (
|
|
289
|
+
} else if (ts3.isVariableStatement(s)) {
|
|
186
290
|
flushGroup();
|
|
187
291
|
this.addVariableStatement(s, parent, depth, declarationsOnly);
|
|
188
292
|
} else if (commonJsExportedFunction(s)) {
|
|
@@ -192,14 +296,14 @@ var Builder = class {
|
|
|
192
296
|
this.addFunctionLike([fn], exportedName.text, parent, depth, { rangeNodes: [s], declNodes, extraTags: ["export"], nameNode: exportedName });
|
|
193
297
|
} else if (declarationsOnly) {
|
|
194
298
|
continue;
|
|
195
|
-
} else if (
|
|
299
|
+
} else if (ts3.isImportDeclaration(s) || ts3.isImportEqualsDeclaration(s)) {
|
|
196
300
|
flushGroup();
|
|
197
301
|
this.addImport(s, parent, depth);
|
|
198
|
-
} else if (
|
|
302
|
+
} else if (ts3.isInterfaceDeclaration(s) || ts3.isTypeAliasDeclaration(s) || ts3.isEnumDeclaration(s)) {
|
|
199
303
|
flushGroup();
|
|
200
|
-
const keyword =
|
|
304
|
+
const keyword = ts3.isInterfaceDeclaration(s) ? "interface" : ts3.isEnumDeclaration(s) ? "enum" : "type";
|
|
201
305
|
const typeParams = "typeParameters" in s && s.typeParameters ? `<${s.typeParameters.map((t) => t.getText()).join(", ")}>` : "";
|
|
202
|
-
const bodyStart =
|
|
306
|
+
const bodyStart = ts3.isTypeAliasDeclaration(s) ? s.type.getStart(this.sf) : this.text.indexOf("{", s.name.end);
|
|
203
307
|
this.add(
|
|
204
308
|
{
|
|
205
309
|
kind: "type",
|
|
@@ -217,7 +321,7 @@ var Builder = class {
|
|
|
217
321
|
parent,
|
|
218
322
|
depth
|
|
219
323
|
);
|
|
220
|
-
} else if (
|
|
324
|
+
} else if (ts3.isModuleDeclaration(s) && s.body && ts3.isModuleBlock(s.body)) {
|
|
221
325
|
flushGroup();
|
|
222
326
|
const body = s.body;
|
|
223
327
|
this.add(
|
|
@@ -247,13 +351,13 @@ var Builder = class {
|
|
|
247
351
|
addImport(s, parent, depth) {
|
|
248
352
|
let spec;
|
|
249
353
|
let names = [];
|
|
250
|
-
if (
|
|
354
|
+
if (ts3.isImportDeclaration(s)) {
|
|
251
355
|
spec = s.moduleSpecifier.text;
|
|
252
356
|
const clause = s.importClause;
|
|
253
357
|
if (clause?.name) names.push(clause.name.text);
|
|
254
358
|
const nb = clause?.namedBindings;
|
|
255
|
-
if (nb &&
|
|
256
|
-
if (nb &&
|
|
359
|
+
if (nb && ts3.isNamespaceImport(nb)) names.push("* as " + nb.name.text);
|
|
360
|
+
if (nb && ts3.isNamedImports(nb)) names.push(...nb.elements.map((e) => e.name.text));
|
|
257
361
|
if (!clause) names = ["(side effect)"];
|
|
258
362
|
} else {
|
|
259
363
|
spec = s.name.text;
|
|
@@ -267,7 +371,7 @@ var Builder = class {
|
|
|
267
371
|
key: "import:" + spec,
|
|
268
372
|
name: spec,
|
|
269
373
|
detail: "",
|
|
270
|
-
tags:
|
|
374
|
+
tags: ts3.isImportDeclaration(s) && s.importClause?.isTypeOnly ? ["type"] : [],
|
|
271
375
|
rangeNodes: [s],
|
|
272
376
|
declNodes: [s],
|
|
273
377
|
previewRange: null,
|
|
@@ -286,11 +390,11 @@ var Builder = class {
|
|
|
286
390
|
let previewRange = null;
|
|
287
391
|
let insert = null;
|
|
288
392
|
let insertAt = -1;
|
|
289
|
-
if (body &&
|
|
393
|
+
if (body && ts3.isBlock(body)) {
|
|
290
394
|
previewRange = [body.getStart(this.sf) + 1, body.end - 1];
|
|
291
395
|
insert = "body";
|
|
292
396
|
const last = body.statements[body.statements.length - 1];
|
|
293
|
-
insertAt = last &&
|
|
397
|
+
insertAt = last && ts3.isReturnStatement(last) ? this.lineStartOf(last.getStart(this.sf)) : body.end - 1;
|
|
294
398
|
} else if (body) {
|
|
295
399
|
previewRange = [body.getStart(this.sf), body.end];
|
|
296
400
|
}
|
|
@@ -298,11 +402,12 @@ var Builder = class {
|
|
|
298
402
|
const ret = sig.type ? ": " + sig.type.getText() : "";
|
|
299
403
|
const detail = squash(`${typeParams}(${sig.parameters.map((p) => p.getText()).join(", ")})${ret}`);
|
|
300
404
|
const tags = [...opts.extraTags ?? [], ...modifierTags(nodes[0])];
|
|
405
|
+
if (this.isReact && isHookName(name)) tags.push("hook");
|
|
301
406
|
if (nodes.length > 1) tags.push(`${nodes.length - 1} overload${nodes.length > 2 ? "s" : ""}`);
|
|
302
407
|
if (impl.asteriskToken) tags.push("generator");
|
|
303
408
|
this.add(
|
|
304
409
|
{
|
|
305
|
-
kind: "function",
|
|
410
|
+
kind: opts.kind ?? "function",
|
|
306
411
|
key: opts.key ?? name,
|
|
307
412
|
name,
|
|
308
413
|
detail,
|
|
@@ -313,7 +418,7 @@ var Builder = class {
|
|
|
313
418
|
previewRange,
|
|
314
419
|
insert,
|
|
315
420
|
insertAt,
|
|
316
|
-
children: body &&
|
|
421
|
+
children: body && ts3.isBlock(body) ? (blk) => this.collectStatements(body.statements, blk, depth + 1, true) : void 0
|
|
317
422
|
},
|
|
318
423
|
parent,
|
|
319
424
|
depth
|
|
@@ -325,7 +430,7 @@ var Builder = class {
|
|
|
325
430
|
const typeParams = cls.typeParameters?.length ? `<${cls.typeParameters.map((t) => t.getText()).join(", ")}>` : "";
|
|
326
431
|
this.add(
|
|
327
432
|
{
|
|
328
|
-
kind: "class",
|
|
433
|
+
kind: this.isReact && isComponentClass(cls) ? "component" : "class",
|
|
329
434
|
key: name,
|
|
330
435
|
name,
|
|
331
436
|
detail: squash(typeParams + (heritage ? " " + heritage : "")),
|
|
@@ -345,26 +450,26 @@ var Builder = class {
|
|
|
345
450
|
collectMembers(members, parent, depth) {
|
|
346
451
|
for (let i = 0; i < members.length; i++) {
|
|
347
452
|
const m = members[i];
|
|
348
|
-
if (
|
|
349
|
-
const name =
|
|
453
|
+
if (ts3.isMethodDeclaration(m) || ts3.isConstructorDeclaration(m)) {
|
|
454
|
+
const name = ts3.isConstructorDeclaration(m) ? "constructor" : m.name.getText();
|
|
350
455
|
const overloads = [m];
|
|
351
456
|
while (!overloads[overloads.length - 1].body) {
|
|
352
457
|
const next = members[i + 1];
|
|
353
|
-
const nextName = next && (
|
|
458
|
+
const nextName = next && (ts3.isConstructorDeclaration(next) ? "constructor" : ts3.isMethodDeclaration(next) ? next.name.getText() : null);
|
|
354
459
|
if (nextName === name) {
|
|
355
460
|
overloads.push(next);
|
|
356
461
|
i++;
|
|
357
462
|
} else break;
|
|
358
463
|
}
|
|
359
464
|
this.addFunctionLike(overloads, name, parent, depth);
|
|
360
|
-
} else if (
|
|
465
|
+
} else if (ts3.isGetAccessor(m) || ts3.isSetAccessor(m)) {
|
|
361
466
|
const name = m.name.getText();
|
|
362
|
-
const word =
|
|
467
|
+
const word = ts3.isGetAccessor(m) ? "get" : "set";
|
|
363
468
|
this.addFunctionLike([m], name, parent, depth, { key: `${word} ${name}`, extraTags: [word] });
|
|
364
|
-
} else if (
|
|
469
|
+
} else if (ts3.isPropertyDeclaration(m)) {
|
|
365
470
|
const name = m.name.getText();
|
|
366
471
|
const init = m.initializer && unwrap(m.initializer);
|
|
367
|
-
if (init && (
|
|
472
|
+
if (init && (ts3.isArrowFunction(init) || ts3.isFunctionExpression(init))) {
|
|
368
473
|
this.addFunctionLike([init], name, parent, depth, { rangeNodes: [m], declNodes: [m], extraTags: modifierTags(m), nameNode: m.name });
|
|
369
474
|
} else {
|
|
370
475
|
this.addVariable(m, m, name, m.type ? ": " + m.type.getText() : "", modifierTags(m), parent, depth, m.initializer);
|
|
@@ -374,25 +479,61 @@ var Builder = class {
|
|
|
374
479
|
}
|
|
375
480
|
addVariableStatement(s, parent, depth, functionsOnly) {
|
|
376
481
|
const decls = s.declarationList.declarations;
|
|
377
|
-
const keyword = s.declarationList.flags &
|
|
482
|
+
const keyword = s.declarationList.flags & ts3.NodeFlags.Const ? "const" : s.declarationList.flags & ts3.NodeFlags.Let ? "let" : "var";
|
|
378
483
|
for (const d of decls) {
|
|
379
484
|
const rangeNode = decls.length === 1 ? s : d;
|
|
380
485
|
const name = d.name.getText();
|
|
381
486
|
const tags = [...modifierTags(s), keyword];
|
|
487
|
+
const stateHook = this.isReact ? stateHookOf(d) : void 0;
|
|
488
|
+
if (stateHook) {
|
|
489
|
+
this.addState(stateHook, rangeNode, tags, parent, depth);
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
382
492
|
const init = d.initializer && unwrap(d.initializer);
|
|
383
|
-
|
|
493
|
+
const componentFunction = this.isReact && init && isComponentName(name) ? componentFunctionOf(init) : void 0;
|
|
494
|
+
if (componentFunction) {
|
|
495
|
+
this.addFunctionLike([componentFunction], name, parent, depth, { kind: "component", rangeNodes: [rangeNode], declNodes: [d], extraTags: tags, nameNode: d.name });
|
|
496
|
+
} else if (init && (ts3.isArrowFunction(init) || ts3.isFunctionExpression(init))) {
|
|
384
497
|
this.addFunctionLike([init], name, parent, depth, { rangeNodes: [rangeNode], declNodes: [d], extraTags: tags, nameNode: d.name });
|
|
385
|
-
} else if (init &&
|
|
498
|
+
} else if (init && ts3.isClassExpression(init)) {
|
|
386
499
|
this.addClass(init, rangeNode, name, parent, depth, [d, init], tags);
|
|
387
500
|
} else if (!functionsOnly) {
|
|
388
501
|
this.addVariable(d, rangeNode, name, d.type ? ": " + d.type.getText() : "", tags, parent, depth, d.initializer);
|
|
389
502
|
}
|
|
390
503
|
}
|
|
391
504
|
}
|
|
505
|
+
/** `const [count, setCount] = useState(0)`: a state block named `count`; using `setCount` writes it. */
|
|
506
|
+
addState(hook, rangeNode, tags, parent, depth) {
|
|
507
|
+
const named = hook.value ?? hook.setter;
|
|
508
|
+
const setterName = hook.setter?.name.getText();
|
|
509
|
+
const [firstArg, ...otherArgs] = hook.call.arguments;
|
|
510
|
+
const args = firstArg ? truncate(squash(firstArg.getText()), 24) + (otherArgs.length ? ", \u2026" : "") : "";
|
|
511
|
+
if (hook.setter && setterName) {
|
|
512
|
+
this.stateSetters.add(nodeKey(hook.setter));
|
|
513
|
+
this.names.add(setterName);
|
|
514
|
+
}
|
|
515
|
+
this.add(
|
|
516
|
+
{
|
|
517
|
+
kind: "state",
|
|
518
|
+
key: named.name.getText(),
|
|
519
|
+
name: named.name.getText(),
|
|
520
|
+
detail: `${hook.call.expression.getText()}(${args})`,
|
|
521
|
+
tags: setterName && hook.value ? [...tags, setterName] : tags,
|
|
522
|
+
rangeNodes: [rangeNode],
|
|
523
|
+
declNodes: [hook.value, hook.setter].filter((declaration) => !!declaration),
|
|
524
|
+
nameNode: named.name,
|
|
525
|
+
previewRange: [hook.call.getStart(this.sf), hook.call.end],
|
|
526
|
+
insert: null,
|
|
527
|
+
insertAt: -1
|
|
528
|
+
},
|
|
529
|
+
parent,
|
|
530
|
+
depth
|
|
531
|
+
);
|
|
532
|
+
}
|
|
392
533
|
addVariable(decl, rangeNode, name, type, tags, parent, depth, initializer) {
|
|
393
534
|
const init = initializer && unwrap(initializer);
|
|
394
|
-
const objectChildren = init &&
|
|
395
|
-
(p) =>
|
|
535
|
+
const objectChildren = init && ts3.isObjectLiteralExpression(init) ? init.properties.filter(
|
|
536
|
+
(p) => ts3.isMethodDeclaration(p) || ts3.isPropertyAssignment(p) && (ts3.isArrowFunction(unwrap(p.initializer)) || ts3.isFunctionExpression(unwrap(p.initializer)))
|
|
396
537
|
) : [];
|
|
397
538
|
this.add(
|
|
398
539
|
{
|
|
@@ -410,7 +551,7 @@ var Builder = class {
|
|
|
410
551
|
children: objectChildren.length ? (blk) => {
|
|
411
552
|
for (const p of objectChildren) {
|
|
412
553
|
const pname = p.name.getText();
|
|
413
|
-
if (
|
|
554
|
+
if (ts3.isMethodDeclaration(p)) this.addFunctionLike([p], pname, blk, depth + 1);
|
|
414
555
|
else {
|
|
415
556
|
const fn = unwrap(p.initializer);
|
|
416
557
|
this.addFunctionLike([fn], pname, blk, depth + 1, { rangeNodes: [p], declNodes: [p], nameNode: p.name });
|
|
@@ -472,7 +613,7 @@ var Builder = class {
|
|
|
472
613
|
/** Include doc comments directly above the node (no blank line in between). */
|
|
473
614
|
startWithComments(node) {
|
|
474
615
|
let start = node.getStart(this.sf);
|
|
475
|
-
const comments =
|
|
616
|
+
const comments = ts3.getLeadingCommentRanges(this.text, node.pos) ?? [];
|
|
476
617
|
for (let i = comments.length - 1; i >= 0; i--) {
|
|
477
618
|
const gap = this.text.slice(comments[i].end, start);
|
|
478
619
|
if (/\S/.test(gap) || (gap.match(/\n/g)?.length ?? 0) > 1) break;
|
|
@@ -500,8 +641,8 @@ var Builder = class {
|
|
|
500
641
|
const visit = (node, current) => {
|
|
501
642
|
const blk = this.rangeToBlock.get(nodeKey(node));
|
|
502
643
|
if (blk) current = blk;
|
|
503
|
-
if (
|
|
504
|
-
|
|
644
|
+
if (ts3.isIdentifier(node) && current) this.recordUsage(node, current, checker, byPair, otherFiles);
|
|
645
|
+
ts3.forEachChild(node, (c) => visit(c, current));
|
|
505
646
|
};
|
|
506
647
|
visit(sf, null);
|
|
507
648
|
return [...byPair.values()];
|
|
@@ -510,29 +651,30 @@ var Builder = class {
|
|
|
510
651
|
const isKnownName = this.names.has(id.text) || !!otherFiles?.projectNames.has(id.text);
|
|
511
652
|
if (!isKnownName) return;
|
|
512
653
|
const parent = id.parent;
|
|
513
|
-
const isDeclarationName = !!parent && parent.name === id && !
|
|
654
|
+
const isDeclarationName = !!parent && parent.name === id && !ts3.isPropertyAccessExpression(parent) && !ts3.isShorthandPropertyAssignment(parent);
|
|
514
655
|
if (isDeclarationName) return;
|
|
515
656
|
let sym;
|
|
516
657
|
try {
|
|
517
|
-
sym = parent &&
|
|
658
|
+
sym = parent && ts3.isShorthandPropertyAssignment(parent) ? checker.getShorthandAssignmentValueSymbol(parent) : checker.getSymbolAtLocation(id);
|
|
518
659
|
} catch {
|
|
519
660
|
return;
|
|
520
661
|
}
|
|
521
662
|
if (!sym?.declarations) return;
|
|
522
|
-
|
|
663
|
+
let kind = usageKind(id);
|
|
523
664
|
let to;
|
|
524
665
|
let importBlockId;
|
|
525
666
|
for (const d of sym.declarations) {
|
|
526
667
|
to = this.declToBlock.get(nodeKey(d));
|
|
668
|
+
if (this.stateSetters.has(nodeKey(d))) kind = "write";
|
|
527
669
|
if (!to) {
|
|
528
670
|
let n = d;
|
|
529
|
-
while (n && !
|
|
530
|
-
if (n && !
|
|
671
|
+
while (n && !ts3.isImportDeclaration(n) && !ts3.isImportEqualsDeclaration(n) && !ts3.isSourceFile(n)) n = n.parent;
|
|
672
|
+
if (n && !ts3.isSourceFile(n)) importBlockId = to = this.declToBlock.get(nodeKey(n));
|
|
531
673
|
}
|
|
532
674
|
if (to) break;
|
|
533
675
|
}
|
|
534
676
|
if (otherFiles) {
|
|
535
|
-
const isImportedName = !!importBlockId && (sym.flags &
|
|
677
|
+
const isImportedName = !!importBlockId && (sym.flags & ts3.SymbolFlags.Alias) !== 0;
|
|
536
678
|
const target = isImportedName ? safeAliasedSymbol(checker, sym) : to ? void 0 : sym;
|
|
537
679
|
const declaredElsewhere = target?.declarations?.find((d) => d.getSourceFile() !== id.getSourceFile());
|
|
538
680
|
const external = declaredElsewhere && otherFiles.blockInOtherFile(declaredElsewhere);
|
|
@@ -549,6 +691,7 @@ function addUsage(out, usage) {
|
|
|
549
691
|
if (!existing) return void out.set(pairKey, usage);
|
|
550
692
|
existing.count++;
|
|
551
693
|
if (usage.kind === "call") existing.kind = "call";
|
|
694
|
+
else if (usage.kind === "write" && existing.kind === "read") existing.kind = "write";
|
|
552
695
|
}
|
|
553
696
|
function safeAliasedSymbol(checker, symbol) {
|
|
554
697
|
try {
|
|
@@ -561,23 +704,26 @@ function nodeKey(node) {
|
|
|
561
704
|
return `${node.kind}:${node.pos}:${node.end}`;
|
|
562
705
|
}
|
|
563
706
|
function usageKind(id) {
|
|
707
|
+
if (isJsxTagName(id)) return "render";
|
|
564
708
|
let n = id;
|
|
565
|
-
if (
|
|
709
|
+
if (ts3.isPropertyAccessExpression(n.parent) && n.parent.name === n) n = n.parent;
|
|
566
710
|
const p = n.parent;
|
|
567
|
-
if ((
|
|
568
|
-
if (
|
|
569
|
-
if (
|
|
570
|
-
if (
|
|
571
|
-
if ((
|
|
572
|
-
if (
|
|
711
|
+
if ((ts3.isCallExpression(p) || ts3.isNewExpression(p)) && p.expression === n) return "call";
|
|
712
|
+
if (ts3.isTaggedTemplateExpression(p) && p.tag === n) return "call";
|
|
713
|
+
if (ts3.isJsxOpeningLikeElement(p) && p.tagName === n) return "render";
|
|
714
|
+
if (ts3.isBinaryExpression(p) && p.left === n && p.operatorToken.kind >= ts3.SyntaxKind.FirstAssignment && p.operatorToken.kind <= ts3.SyntaxKind.LastAssignment) return "write";
|
|
715
|
+
if ((ts3.isPrefixUnaryExpression(p) || ts3.isPostfixUnaryExpression(p)) && (p.operator === ts3.SyntaxKind.PlusPlusToken || p.operator === ts3.SyntaxKind.MinusMinusToken)) return "write";
|
|
716
|
+
if (ts3.isTypeReferenceNode(p) || ts3.isExpressionWithTypeArguments(p) || ts3.isTypeQueryNode(p) || ts3.isQualifiedName(p)) return "type";
|
|
573
717
|
return "read";
|
|
574
718
|
}
|
|
575
|
-
function
|
|
576
|
-
|
|
577
|
-
|
|
719
|
+
function isJsxTagName(id) {
|
|
720
|
+
let tag = id;
|
|
721
|
+
while (ts3.isPropertyAccessExpression(tag.parent) && tag.parent.expression === tag) tag = tag.parent;
|
|
722
|
+
const element = tag.parent;
|
|
723
|
+
return (ts3.isJsxOpeningLikeElement(element) || ts3.isJsxClosingElement(element)) && element.tagName === tag;
|
|
578
724
|
}
|
|
579
725
|
function modifierTags(n) {
|
|
580
|
-
const mods =
|
|
726
|
+
const mods = ts3.canHaveModifiers(n) ? ts3.getModifiers(n) ?? [] : [];
|
|
581
727
|
return mods.map((m) => m.getText());
|
|
582
728
|
}
|
|
583
729
|
function squash(s) {
|
|
@@ -625,13 +771,13 @@ function makePreview(text, range, children, max) {
|
|
|
625
771
|
}
|
|
626
772
|
|
|
627
773
|
// src/project.ts
|
|
628
|
-
import
|
|
774
|
+
import ts5 from "typescript";
|
|
629
775
|
|
|
630
776
|
// src/workspace.ts
|
|
631
|
-
import
|
|
777
|
+
import ts4 from "typescript";
|
|
632
778
|
function parseConfig(config, projectPaths) {
|
|
633
779
|
const configFileName = "/" + config.path;
|
|
634
|
-
const json =
|
|
780
|
+
const json = ts4.parseConfigFileTextToJson(configFileName, config.text);
|
|
635
781
|
if (json.error || !json.config) return null;
|
|
636
782
|
const readConfig = (name) => name === configFileName ? config.text : config.extendedFiles?.[name.slice(1)];
|
|
637
783
|
const host = {
|
|
@@ -641,7 +787,7 @@ function parseConfig(config, projectPaths) {
|
|
|
641
787
|
readFile: readConfig
|
|
642
788
|
};
|
|
643
789
|
const folder = config.path.slice(0, config.path.lastIndexOf("/") + 1);
|
|
644
|
-
const parsed =
|
|
790
|
+
const parsed = ts4.parseJsonConfigFileContent(json.config, host, "/" + folder, void 0, configFileName);
|
|
645
791
|
const included = new Set(parsed.fileNames.map((name) => name.slice(1)));
|
|
646
792
|
return { path: config.path, folder, options: parsed.options, includes: (projectPath) => included.has(projectPath) };
|
|
647
793
|
}
|
|
@@ -743,7 +889,7 @@ var Project = class {
|
|
|
743
889
|
/** Language services by config path, created when a file of that config is first resolved. */
|
|
744
890
|
services = /* @__PURE__ */ new Map();
|
|
745
891
|
/** Shared by the services, so a file several programs include is parsed once. */
|
|
746
|
-
documentRegistry =
|
|
892
|
+
documentRegistry = ts5.createDocumentRegistry(true, "/");
|
|
747
893
|
/** Changes when files are added or removed, or the settings change: where imports point may change then. */
|
|
748
894
|
fileSetVersion = 0;
|
|
749
895
|
/** Load the standard library later, e.g. once it has been downloaded. Type errors show from then on. */
|
|
@@ -777,11 +923,11 @@ var Project = class {
|
|
|
777
923
|
compilerOptions(configOptions, packagePaths) {
|
|
778
924
|
const paths = { ...packagePaths, ...configOptions.paths };
|
|
779
925
|
return {
|
|
780
|
-
target:
|
|
781
|
-
module:
|
|
782
|
-
moduleResolution:
|
|
926
|
+
target: ts5.ScriptTarget.ES2022,
|
|
927
|
+
module: ts5.ModuleKind.ESNext,
|
|
928
|
+
moduleResolution: ts5.ModuleResolutionKind.Bundler,
|
|
783
929
|
strict: true,
|
|
784
|
-
jsx:
|
|
930
|
+
jsx: ts5.JsxEmit.Preserve,
|
|
785
931
|
skipLibCheck: true,
|
|
786
932
|
allowImportingTsExtensions: true,
|
|
787
933
|
...configOptions,
|
|
@@ -827,7 +973,7 @@ var Project = class {
|
|
|
827
973
|
serviceOfConfig(configPath) {
|
|
828
974
|
let service = this.services.get(configPath);
|
|
829
975
|
if (!service) {
|
|
830
|
-
service =
|
|
976
|
+
service = ts5.createLanguageService(this.createHost(configPath), this.documentRegistry);
|
|
831
977
|
this.services.set(configPath, service);
|
|
832
978
|
}
|
|
833
979
|
return service;
|
|
@@ -996,7 +1142,7 @@ var Project = class {
|
|
|
996
1142
|
const reExportedPaths = /* @__PURE__ */ new Set();
|
|
997
1143
|
const host = this.moduleResolutionHost();
|
|
998
1144
|
const resolve = (specifier) => {
|
|
999
|
-
const result =
|
|
1145
|
+
const result = ts5.resolveModuleName(specifier, toFileName(path), this.optionsOfFile(path), host).resolvedModule;
|
|
1000
1146
|
return result ? fromFileName(result.resolvedFileName) : null;
|
|
1001
1147
|
};
|
|
1002
1148
|
for (const block of file.model.roots) {
|
|
@@ -1050,7 +1196,7 @@ var Project = class {
|
|
|
1050
1196
|
getScriptSnapshot: (name) => {
|
|
1051
1197
|
if (name.startsWith(LIB_DIR)) {
|
|
1052
1198
|
const text = this.libFiles[name.slice(LIB_DIR.length)];
|
|
1053
|
-
return text === void 0 ? void 0 :
|
|
1199
|
+
return text === void 0 ? void 0 : ts5.ScriptSnapshot.fromString(text);
|
|
1054
1200
|
}
|
|
1055
1201
|
const path = fromFileName(name);
|
|
1056
1202
|
const file = path ? this.files.get(path) : void 0;
|
|
@@ -1064,7 +1210,7 @@ var Project = class {
|
|
|
1064
1210
|
const containingPath = fromFileName(containingFile);
|
|
1065
1211
|
const options = this.optionsOfFile(containingPath);
|
|
1066
1212
|
const host = this.moduleResolutionHost();
|
|
1067
|
-
return moduleLiterals.map((literal) =>
|
|
1213
|
+
return moduleLiterals.map((literal) => ts5.resolveModuleName(literal.text, containingFile, options, host));
|
|
1068
1214
|
},
|
|
1069
1215
|
getDefaultLibFileName: () => LIB_DIR + "lib.d.ts",
|
|
1070
1216
|
fileExists: (name) => this.fileExists(name),
|
|
@@ -1100,7 +1246,7 @@ function changedRegion(before, after) {
|
|
|
1100
1246
|
while (start < shorter && before.charCodeAt(start) === after.charCodeAt(start)) start++;
|
|
1101
1247
|
let fromEnd = 0;
|
|
1102
1248
|
while (fromEnd < shorter - start && before.charCodeAt(before.length - 1 - fromEnd) === after.charCodeAt(after.length - 1 - fromEnd)) fromEnd++;
|
|
1103
|
-
return
|
|
1249
|
+
return ts5.createTextChangeRange(ts5.createTextSpan(start, before.length - fromEnd - start), after.length - fromEnd - start);
|
|
1104
1250
|
}
|
|
1105
1251
|
function toFileName(path) {
|
|
1106
1252
|
return "/" + path;
|
|
@@ -1124,10 +1270,10 @@ function namesVisibleFrom(model, block) {
|
|
|
1124
1270
|
return names;
|
|
1125
1271
|
}
|
|
1126
1272
|
function isValidIdentifier(name) {
|
|
1127
|
-
const scanner =
|
|
1273
|
+
const scanner = ts5.createScanner(ts5.ScriptTarget.Latest, true, ts5.LanguageVariant.Standard, name);
|
|
1128
1274
|
const token = scanner.scan();
|
|
1129
1275
|
const isWholeText = scanner.getTokenEnd() === name.length && name.length > 0;
|
|
1130
|
-
const isNameToken = token ===
|
|
1276
|
+
const isNameToken = token === ts5.SyntaxKind.Identifier || token > ts5.SyntaxKind.LastReservedWord && token <= ts5.SyntaxKind.LastKeyword;
|
|
1131
1277
|
return isWholeText && isNameToken;
|
|
1132
1278
|
}
|
|
1133
1279
|
|
|
@@ -1257,7 +1403,7 @@ function connectProjectWorker(port) {
|
|
|
1257
1403
|
}
|
|
1258
1404
|
|
|
1259
1405
|
// src/edits.ts
|
|
1260
|
-
import
|
|
1406
|
+
import ts6 from "typescript";
|
|
1261
1407
|
function applyEdit(text, e) {
|
|
1262
1408
|
return text.slice(0, e.start) + e.text + text.slice(e.end);
|
|
1263
1409
|
}
|
|
@@ -1384,13 +1530,13 @@ function toTopLevel(code) {
|
|
|
1384
1530
|
function renameDeclaration(code, inClass, name) {
|
|
1385
1531
|
const prefix = inClass ? "class __cc__ {\n" : "";
|
|
1386
1532
|
const src = prefix + code + (inClass ? "\n}" : "");
|
|
1387
|
-
const sf =
|
|
1533
|
+
const sf = ts6.createSourceFile("snippet.ts", src, ts6.ScriptTarget.Latest, true);
|
|
1388
1534
|
let target;
|
|
1389
1535
|
const first = sf.statements[0];
|
|
1390
|
-
if (inClass && first &&
|
|
1391
|
-
else if (first && (
|
|
1536
|
+
if (inClass && first && ts6.isClassDeclaration(first)) target = first.members[0]?.name;
|
|
1537
|
+
else if (first && (ts6.isFunctionDeclaration(first) || ts6.isClassDeclaration(first) || ts6.isInterfaceDeclaration(first) || ts6.isTypeAliasDeclaration(first) || ts6.isEnumDeclaration(first)))
|
|
1392
1538
|
target = first.name;
|
|
1393
|
-
else if (first &&
|
|
1539
|
+
else if (first && ts6.isVariableStatement(first)) target = first.declarationList.declarations[0]?.name;
|
|
1394
1540
|
if (!target) return code;
|
|
1395
1541
|
const s = target.getStart(sf) - prefix.length;
|
|
1396
1542
|
return code.slice(0, s) + name + code.slice(target.end - prefix.length);
|
|
@@ -1402,26 +1548,26 @@ function must(model, id) {
|
|
|
1402
1548
|
}
|
|
1403
1549
|
function clipFromText(code) {
|
|
1404
1550
|
const text = code.replace(/\s+$/, "");
|
|
1405
|
-
const sf =
|
|
1551
|
+
const sf = ts6.createSourceFile("clip.ts", text, ts6.ScriptTarget.Latest, true);
|
|
1406
1552
|
const first = sf.statements[0];
|
|
1407
1553
|
const diags = sf.parseDiagnostics ?? [];
|
|
1408
1554
|
if (first && !diags.length) {
|
|
1409
1555
|
let name;
|
|
1410
1556
|
let kind = "statement";
|
|
1411
|
-
if (
|
|
1412
|
-
else if (
|
|
1413
|
-
else if (
|
|
1414
|
-
else if (
|
|
1557
|
+
if (ts6.isFunctionDeclaration(first)) name = first.name?.text, kind = "function";
|
|
1558
|
+
else if (ts6.isClassDeclaration(first)) name = first.name?.text, kind = "class";
|
|
1559
|
+
else if (ts6.isVariableStatement(first)) name = first.declarationList.declarations[0]?.name.getText(), kind = "variable";
|
|
1560
|
+
else if (ts6.isInterfaceDeclaration(first) || ts6.isTypeAliasDeclaration(first) || ts6.isEnumDeclaration(first)) name = first.name.text, kind = "type";
|
|
1415
1561
|
if (name) return { code: text, name, kind, fromClass: false };
|
|
1416
1562
|
}
|
|
1417
|
-
const wrapped =
|
|
1563
|
+
const wrapped = ts6.createSourceFile("clip.ts", `class __cc__ {
|
|
1418
1564
|
${text}
|
|
1419
|
-
}`,
|
|
1565
|
+
}`, ts6.ScriptTarget.Latest, true);
|
|
1420
1566
|
const cls = wrapped.statements[0];
|
|
1421
1567
|
const wd = wrapped.parseDiagnostics ?? [];
|
|
1422
|
-
if (cls &&
|
|
1568
|
+
if (cls && ts6.isClassDeclaration(cls) && !wd.length && cls.members[0]?.name) {
|
|
1423
1569
|
const m = cls.members[0];
|
|
1424
|
-
return { code: text, name: m.name.getText(wrapped), kind:
|
|
1570
|
+
return { code: text, name: m.name.getText(wrapped), kind: ts6.isPropertyDeclaration(m) ? "variable" : "function", fromClass: true };
|
|
1425
1571
|
}
|
|
1426
1572
|
return { code: text, name: "", kind: "statement", fromClass: false };
|
|
1427
1573
|
}
|
|
@@ -1579,7 +1725,7 @@ function resolveConflict(text, hunk, choice) {
|
|
|
1579
1725
|
var BAND_ORDER = ["data", "types", "main", "bottom"];
|
|
1580
1726
|
var ZONE_RULES = [
|
|
1581
1727
|
{ zone: "left", description: "Imports on the left", matches: (block) => block.kind === "import" },
|
|
1582
|
-
{ zone: "data", description: "Constants
|
|
1728
|
+
{ zone: "data", description: "Constants, other data and React state at the top", matches: (block) => block.kind === "variable" || block.kind === "state" },
|
|
1583
1729
|
{ zone: "types", description: "Then interfaces, types and enums", matches: (block) => block.kind === "type" },
|
|
1584
1730
|
{ zone: "bottom", description: "Code that runs when the file loads, at the bottom", matches: (block) => block.kind === "statement" },
|
|
1585
1731
|
{ zone: "main", description: "Functions, classes and the rest in between", matches: () => true }
|
|
@@ -1669,7 +1815,7 @@ var PLACEMENT_RULES = [
|
|
|
1669
1815
|
];
|
|
1670
1816
|
var zoneOf = (block) => ZONE_RULES.find((rule) => rule.matches(block)).zone;
|
|
1671
1817
|
var NEWSPAPER_DEPTH = 3;
|
|
1672
|
-
var isFunctionLike = (block) => block.kind === "function";
|
|
1818
|
+
var isFunctionLike = (block) => block.kind === "function" || block.kind === "component";
|
|
1673
1819
|
function accessedName(block, accessor) {
|
|
1674
1820
|
if (block.kind !== "function") return void 0;
|
|
1675
1821
|
if (block.tags.includes(accessor)) return block.name;
|
|
@@ -1823,6 +1969,7 @@ function orderByRelatedness(units, factsOf) {
|
|
|
1823
1969
|
function siblingFacts(blocks, text) {
|
|
1824
1970
|
const idsByName = /* @__PURE__ */ new Map();
|
|
1825
1971
|
for (const block of blocks) idsByName.set(block.name, [...idsByName.get(block.name) ?? [], block.id]);
|
|
1972
|
+
const componentIds = new Set(blocks.filter((block) => block.kind === "component").map((block) => block.id));
|
|
1826
1973
|
const facts = /* @__PURE__ */ new Map();
|
|
1827
1974
|
for (const block of blocks) {
|
|
1828
1975
|
const code = codeWithoutChildren(text, [block.start, block.end], block.children);
|
|
@@ -1832,9 +1979,11 @@ function siblingFacts(blocks, text) {
|
|
|
1832
1979
|
const ids = idsByName.get(match[0])?.filter((id) => id !== block.id);
|
|
1833
1980
|
if (!ids?.length) continue;
|
|
1834
1981
|
const isCall = /^\s*\(/.test(code.slice(match.index + match[0].length, match.index + match[0].length + 40));
|
|
1982
|
+
const isJsxTag = /<\/?$/.test(code.slice(Math.max(0, match.index - 2), match.index));
|
|
1835
1983
|
for (const id of ids) {
|
|
1836
1984
|
uses.add(id);
|
|
1837
|
-
|
|
1985
|
+
const isRender = isJsxTag && componentIds.has(id);
|
|
1986
|
+
if ((isCall || isRender) && !calls.includes(id)) calls.push(id);
|
|
1838
1987
|
}
|
|
1839
1988
|
}
|
|
1840
1989
|
facts.set(block.id, { calls, uses });
|
|
@@ -1875,13 +2024,13 @@ function contains(arrangement, unit) {
|
|
|
1875
2024
|
|
|
1876
2025
|
// src/layout.ts
|
|
1877
2026
|
var M = {
|
|
1878
|
-
headerH:
|
|
1879
|
-
headerCharW: 7.
|
|
1880
|
-
//
|
|
1881
|
-
previewLineH:
|
|
1882
|
-
previewCharW: 6.
|
|
1883
|
-
//
|
|
1884
|
-
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,
|
|
1885
2034
|
pad: 14,
|
|
1886
2035
|
// inner padding of containers
|
|
1887
2036
|
gap: 28,
|
|
@@ -2149,98 +2298,222 @@ function setHTML(element, html) {
|
|
|
2149
2298
|
const htmlPolicy = trustedHtmlPolicy();
|
|
2150
2299
|
element.innerHTML = htmlPolicy ? htmlPolicy.createHTML(html) : html;
|
|
2151
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
|
+
}
|
|
2152
2380
|
|
|
2153
2381
|
// src/styles.ts
|
|
2154
2382
|
var CSS = (
|
|
2155
2383
|
/* css */
|
|
2156
2384
|
`
|
|
2157
2385
|
.cc-root {
|
|
2158
|
-
--cc-bg: #
|
|
2159
|
-
--cc-dot:
|
|
2160
|
-
--cc-frame-bg: rgba(255,255,255,.
|
|
2161
|
-
--cc-frame-border:
|
|
2162
|
-
--cc-
|
|
2163
|
-
--cc-
|
|
2164
|
-
--cc-
|
|
2165
|
-
--cc-
|
|
2166
|
-
--cc-
|
|
2167
|
-
--cc-
|
|
2168
|
-
--cc-
|
|
2169
|
-
--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;
|
|
2170
2416
|
|
|
2171
|
-
--k-import: #
|
|
2172
|
-
--k-
|
|
2173
|
-
--k-function: #
|
|
2174
|
-
--k-
|
|
2175
|
-
--k-type: #
|
|
2176
|
-
--k-
|
|
2177
|
-
--k-
|
|
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;
|
|
2178
2426
|
|
|
2179
2427
|
position: relative; overflow: hidden; width: 100%; height: 100%;
|
|
2180
2428
|
background-color: var(--cc-bg);
|
|
2181
|
-
background-image: radial-gradient(var(--cc-dot)
|
|
2429
|
+
background-image: radial-gradient(circle, var(--cc-dot) 1px, transparent 1.4px);
|
|
2182
2430
|
color: var(--cc-text);
|
|
2183
|
-
font: 13px/1.3
|
|
2431
|
+
font: 13px/1.3 var(--cc-ui-font);
|
|
2184
2432
|
touch-action: none; user-select: none; -webkit-user-select: none; outline: none;
|
|
2185
2433
|
-webkit-tap-highlight-color: transparent;
|
|
2186
2434
|
}
|
|
2187
2435
|
.cc-root[data-theme="light"] {
|
|
2188
|
-
--cc-bg: #
|
|
2189
|
-
--cc-frame-bg: rgba(255,255,255,.
|
|
2190
|
-
--cc-
|
|
2191
|
-
--cc-
|
|
2192
|
-
--cc-
|
|
2193
|
-
--
|
|
2194
|
-
--
|
|
2195
|
-
--
|
|
2196
|
-
--k-
|
|
2197
|
-
--k-
|
|
2198
|
-
--k-namespace: #6f5ce6; --k-namespace-head: #6f5ce6; --k-namespace-bg: rgba(111,92,230,.07);
|
|
2199
|
-
--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;
|
|
2200
2446
|
}
|
|
2201
2447
|
.cc-world { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
|
|
2202
2448
|
.cc-root.cc-moving .cc-world { will-change: transform; }
|
|
2203
|
-
.cc-
|
|
2204
|
-
.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; }
|
|
2205
2460
|
.cc-empty { position: absolute; font-size: 15px; color: var(--cc-muted); white-space: nowrap; }
|
|
2206
2461
|
|
|
2207
2462
|
.cc-block {
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
.cc-
|
|
2214
|
-
.cc-block.k-
|
|
2215
|
-
.cc-block.k-
|
|
2216
|
-
.cc-block.k-
|
|
2217
|
-
.cc-block.k-
|
|
2218
|
-
.cc-block.k-
|
|
2219
|
-
.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); }
|
|
2220
2478
|
|
|
2221
2479
|
.cc-head {
|
|
2222
|
-
display: flex; align-items: center; gap:
|
|
2223
|
-
|
|
2224
|
-
font:
|
|
2225
|
-
}
|
|
2226
|
-
.cc-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
.cc-
|
|
2230
|
-
|
|
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; }
|
|
2231
2497
|
.cc-fold {
|
|
2232
2498
|
flex: none; margin-left: auto; width: 22px; height: 22px; border: 0; border-radius: 6px; padding: 0;
|
|
2233
|
-
background:
|
|
2499
|
+
background: transparent; color: var(--cc-faint); font-size: 12px; line-height: 22px; cursor: pointer;
|
|
2234
2500
|
}
|
|
2235
|
-
.cc-fold:hover { background:
|
|
2501
|
+
.cc-fold:hover { background: var(--cc-raised); color: var(--cc-strong); }
|
|
2236
2502
|
.cc-prev {
|
|
2237
|
-
margin: 0; padding:
|
|
2238
|
-
font:
|
|
2239
|
-
}
|
|
2240
|
-
.cc-
|
|
2241
|
-
.cc-
|
|
2242
|
-
.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; }
|
|
2243
2515
|
.cc-mini > i.k-class { border-color: var(--k-class); } .cc-mini > i.k-function { border-color: var(--k-function); }
|
|
2516
|
+
.cc-mini > i.k-component { border-color: var(--k-component); } .cc-mini > i.k-state { border-color: var(--k-state); }
|
|
2244
2517
|
.cc-mini > i.k-variable { border-color: var(--k-variable); } .cc-mini > i.k-type { border-color: var(--k-type); }
|
|
2245
2518
|
|
|
2246
2519
|
.cc-err {
|
|
@@ -2248,9 +2521,10 @@ var CSS = (
|
|
|
2248
2521
|
background: var(--cc-danger); color: #fff; font: 700 11px/18px ui-sans-serif, system-ui, sans-serif; text-align: center;
|
|
2249
2522
|
}
|
|
2250
2523
|
.cc-block.cc-has-error { border-color: var(--cc-danger); }
|
|
2524
|
+
.cc-err { color: #1a0d0b; }
|
|
2251
2525
|
.cc-root.lod-2 .cc-err { min-width: 34px; height: 34px; border-radius: 17px; font-size: 22px; line-height: 34px; }
|
|
2252
2526
|
.cc-rename {
|
|
2253
|
-
font: inherit; font-weight:
|
|
2527
|
+
font: inherit; font-weight: 600; color: var(--cc-strong); background: var(--cc-raised); border: 1px solid var(--cc-ink);
|
|
2254
2528
|
border-radius: 4px; padding: 0 4px; height: 22px; outline: none; user-select: text; -webkit-user-select: text; max-width: 100%;
|
|
2255
2529
|
}
|
|
2256
2530
|
.cc-rename.invalid { border-color: var(--cc-danger); }
|
|
@@ -2263,10 +2537,7 @@ var CSS = (
|
|
|
2263
2537
|
font: 900 13px/18px ui-sans-serif, system-ui, sans-serif; text-align: center;
|
|
2264
2538
|
}
|
|
2265
2539
|
.cc-root.lod-2 .cc-conflict-chip { min-width: 34px; height: 34px; border-radius: 17px; font-size: 24px; line-height: 34px; }
|
|
2266
|
-
.cc-block.cc-conflict-incoming {
|
|
2267
|
-
--k: #f0883e; --kh: #8a4a17; --kbg: rgba(240, 136, 62, .08);
|
|
2268
|
-
border-style: dashed; pointer-events: none; opacity: .9;
|
|
2269
|
-
}
|
|
2540
|
+
.cc-block.cc-conflict-incoming { --k: #f0883e; border: 1px dashed #f0883e; pointer-events: none; opacity: .9; }
|
|
2270
2541
|
.cc-diff-chip.incoming { background: #f0883e; }
|
|
2271
2542
|
.cc-banner.cc-banner-conflicts { border-color: #f0883e; }
|
|
2272
2543
|
|
|
@@ -2281,10 +2552,9 @@ var CSS = (
|
|
|
2281
2552
|
.cc-diff-chip.removed { background: var(--cc-danger); }
|
|
2282
2553
|
.cc-root.lod-2 .cc-diff-chip { min-width: 34px; height: 34px; border-radius: 8px; font-size: 24px; line-height: 34px; }
|
|
2283
2554
|
.cc-block.cc-diff-removed {
|
|
2284
|
-
--k: var(--cc-danger);
|
|
2285
|
-
|
|
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;
|
|
2286
2557
|
}
|
|
2287
|
-
.cc-root[data-theme="light"] .cc-block.cc-diff-removed { --kh: #d64545; --kbg: rgba(214, 69, 69, .08); }
|
|
2288
2558
|
.cc-prev.cc-diff-prev { opacity: .95; }
|
|
2289
2559
|
.cc-diff-prev > span { display: block; margin: 0 -12px; padding: 0 12px; }
|
|
2290
2560
|
.cc-diff-prev .cc-line-same { opacity: .5; }
|
|
@@ -2292,23 +2562,24 @@ var CSS = (
|
|
|
2292
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); }
|
|
2293
2563
|
.cc-diff-summary { padding: 0 8px; font-size: 12px; color: var(--cc-muted); white-space: nowrap; }
|
|
2294
2564
|
|
|
2295
|
-
.cc-block.cc-sel { box-shadow: 0 0 0 2px var(--cc-
|
|
2296
|
-
.cc-block.cc-editing { box-shadow: 0 0 0 2px var(--cc-
|
|
2297
|
-
.cc-block.cc-dragging { box-shadow: var(--cc-shadow); opacity: .92; z-index: 9999 !important; }
|
|
2298
|
-
.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; }
|
|
2299
2569
|
|
|
2300
2570
|
.cc-edges { position: absolute; left: 0; top: 0; overflow: visible; pointer-events: none; }
|
|
2301
|
-
.cc-edge { fill: none; stroke-width: 1.5; opacity: .
|
|
2302
|
-
.cc-edge.u-type { stroke-dasharray:
|
|
2303
|
-
.cc-root.has-hl .cc-edge { opacity: .
|
|
2304
|
-
.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; }
|
|
2305
2575
|
|
|
2306
2576
|
/* level of detail when zoomed out */
|
|
2307
|
-
.cc-root.lod-1 .cc-
|
|
2577
|
+
.cc-root.lod-1 .cc-tag { visibility: hidden; }
|
|
2308
2578
|
.cc-root.lod-2 .cc-prev, .cc-root.lod-2 .cc-detail, .cc-root.lod-2 .cc-mini { visibility: hidden; }
|
|
2309
2579
|
.cc-root.lod-2 .cc-head { font-size: 26px; }
|
|
2310
|
-
.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; }
|
|
2311
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; }
|
|
2312
2583
|
|
|
2313
2584
|
/* overlays (screen space) */
|
|
2314
2585
|
.cc-banner {
|
|
@@ -2316,37 +2587,66 @@ var CSS = (
|
|
|
2316
2587
|
padding: 8px 12px; border-radius: 10px; background: var(--cc-panel); border: 1px solid var(--cc-danger); box-shadow: var(--cc-shadow);
|
|
2317
2588
|
font-size: 13px; max-width: calc(100% - 32px);
|
|
2318
2589
|
}
|
|
2319
|
-
.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; }
|
|
2320
2591
|
.cc-toast {
|
|
2321
2592
|
position: absolute; left: 50%; bottom: 76px; transform: translateX(-50%); z-index: 30; padding: 8px 14px; border-radius: 10px;
|
|
2322
2593
|
background: var(--cc-panel); border: 1px solid var(--cc-panel-border); box-shadow: var(--cc-shadow); font-size: 13px;
|
|
2323
2594
|
transition: opacity .2s; pointer-events: none; max-width: calc(100% - 32px);
|
|
2324
2595
|
}
|
|
2325
2596
|
.cc-bar {
|
|
2326
|
-
position: absolute; left: 50%; bottom:
|
|
2327
|
-
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);
|
|
2328
2599
|
width: max-content; max-width: calc(100% - 24px); box-sizing: border-box; flex-wrap: wrap; justify-content: center;
|
|
2329
2600
|
}
|
|
2330
|
-
.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; }
|
|
2331
|
-
.cc-bar .cc-chip i { width: 10px; height: 10px; border-radius: 3px; background: var(--k); flex: none; }
|
|
2332
2601
|
.cc-btn {
|
|
2333
|
-
|
|
2334
|
-
cursor: pointer; white-space: nowrap;
|
|
2335
|
-
}
|
|
2336
|
-
.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); }
|
|
2337
2610
|
.cc-btn.danger { color: var(--cc-danger); }
|
|
2338
|
-
.cc-btn.primary { background: var(--cc-accent); color:
|
|
2339
|
-
.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; } }
|
|
2340
2640
|
.cc-refs {
|
|
2341
2641
|
position: absolute; left: 50%; bottom: 64px; transform: translateX(-50%); z-index: 21; width: min(360px, calc(100% - 24px)); max-height: 45%;
|
|
2342
2642
|
overflow: auto; border-radius: 12px; background: var(--cc-panel); border: 1px solid var(--cc-panel-border); box-shadow: var(--cc-shadow); padding: 6px;
|
|
2343
2643
|
}
|
|
2344
|
-
.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); }
|
|
2345
2645
|
.cc-refs button {
|
|
2346
2646
|
display: flex; width: 100%; align-items: center; gap: 8px; border: 0; background: transparent; color: var(--cc-text); text-align: left;
|
|
2347
2647
|
padding: 6px 8px; border-radius: 8px; cursor: pointer; font: 12px/18px var(--cc-font, "JetBrains Mono", ui-monospace, Menlo, monospace);
|
|
2348
2648
|
}
|
|
2349
|
-
.cc-refs button:hover { background:
|
|
2649
|
+
.cc-refs button:hover { background: var(--cc-raised); }
|
|
2350
2650
|
.cc-refs button i { width: 10px; height: 10px; border-radius: 3px; background: var(--k); flex: none; }
|
|
2351
2651
|
.cc-refs button span:last-child { margin-left: auto; color: var(--cc-muted); }
|
|
2352
2652
|
.cc-refs button:disabled { cursor: default; opacity: .7; }
|
|
@@ -2385,11 +2685,24 @@ var KIND_LABEL = {
|
|
|
2385
2685
|
import: "import",
|
|
2386
2686
|
class: "class",
|
|
2387
2687
|
function: "fn",
|
|
2688
|
+
component: "component",
|
|
2689
|
+
state: "state",
|
|
2388
2690
|
variable: "var",
|
|
2389
2691
|
type: "type",
|
|
2390
2692
|
namespace: "ns",
|
|
2391
2693
|
statement: "code"
|
|
2392
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
|
+
};
|
|
2393
2706
|
var DIFF_CHIPS = {
|
|
2394
2707
|
added: '<span class="cc-diff-chip added" title="Added">+</span>',
|
|
2395
2708
|
changed: '<span class="cc-diff-chip changed" title="Changed">~</span>',
|
|
@@ -2398,6 +2711,9 @@ var DIFF_CHIPS = {
|
|
|
2398
2711
|
containsChanges: "",
|
|
2399
2712
|
unchanged: ""
|
|
2400
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>';
|
|
2401
2717
|
var LABEL_TAGS = /* @__PURE__ */ new Set(["const", "let", "var", "interface", "type", "enum", "namespace", "get", "set"]);
|
|
2402
2718
|
var MIN_SCALE = 0.04;
|
|
2403
2719
|
var MAX_SCALE = 4;
|
|
@@ -2414,18 +2730,28 @@ var CodeCanvas = class {
|
|
|
2414
2730
|
this.blocksEl = el("div", "cc-blocks");
|
|
2415
2731
|
this.svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
2416
2732
|
this.svg.classList.add("cc-edges");
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
).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>`);
|
|
2420
2735
|
this.world.append(this.frameEl, this.blocksEl, this.svg);
|
|
2421
2736
|
this.banner = el("div", "cc-banner");
|
|
2422
2737
|
this.banner.hidden = true;
|
|
2423
2738
|
this.toastEl = el("div", "cc-toast");
|
|
2424
2739
|
this.toastEl.style.opacity = "0";
|
|
2425
|
-
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");
|
|
2426
2747
|
this.refsEl = el("div", "cc-refs");
|
|
2427
2748
|
this.refsEl.hidden = true;
|
|
2428
|
-
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
|
+
}
|
|
2429
2755
|
container.appendChild(root);
|
|
2430
2756
|
this.bindEvents();
|
|
2431
2757
|
this.ro = new ResizeObserver(() => this.fittedToView ? this.fit(false) : this.queueCamera());
|
|
@@ -2444,7 +2770,14 @@ var CodeCanvas = class {
|
|
|
2444
2770
|
frameEl;
|
|
2445
2771
|
blocksEl;
|
|
2446
2772
|
svg;
|
|
2773
|
+
/** The dock at the bottom. */
|
|
2447
2774
|
bar;
|
|
2775
|
+
selectionBar;
|
|
2776
|
+
menuEl;
|
|
2777
|
+
minimapEl = null;
|
|
2778
|
+
minimapScale = 1;
|
|
2779
|
+
zoomLabel;
|
|
2780
|
+
tool = "select";
|
|
2448
2781
|
refsEl;
|
|
2449
2782
|
banner;
|
|
2450
2783
|
toastEl;
|
|
@@ -2806,7 +3139,9 @@ var CodeCanvas = class {
|
|
|
2806
3139
|
if (!g || !view) return;
|
|
2807
3140
|
const f = g.frame;
|
|
2808
3141
|
Object.assign(this.frameEl.style, { transform: `translate(${f.x}px,${f.y}px)`, width: f.w + "px", height: f.h + "px" });
|
|
2809
|
-
|
|
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>`}`);
|
|
2810
3145
|
const seen = /* @__PURE__ */ new Set();
|
|
2811
3146
|
const diff = this.currentDiff();
|
|
2812
3147
|
for (const geo of g.order) {
|
|
@@ -2857,6 +3192,7 @@ var CodeCanvas = class {
|
|
|
2857
3192
|
this.renderEdges();
|
|
2858
3193
|
this.updateHighlight();
|
|
2859
3194
|
this.updateErrorMarkers();
|
|
3195
|
+
this.renderMinimap();
|
|
2860
3196
|
}
|
|
2861
3197
|
/** Blocks only the incoming side of a conflict has, drawn dashed where they would go. */
|
|
2862
3198
|
renderIncomingBlocks() {
|
|
@@ -2937,16 +3273,18 @@ var CodeCanvas = class {
|
|
|
2937
3273
|
}
|
|
2938
3274
|
blockHTML(b, geo, mini, change, changedLines = "", isConflicted = false) {
|
|
2939
3275
|
const label = blockLabel(b);
|
|
2940
|
-
const
|
|
3276
|
+
const chips = b.tags.filter((t) => t !== label && t !== "export" && (b.kind === "import" || !LABEL_TAGS.has(t)));
|
|
2941
3277
|
const exp = b.tags.includes("export") ? '<span class="cc-exp" title="exported"></span>' : "";
|
|
2942
3278
|
const isRemoved = change === "removed" || change === "incoming";
|
|
2943
3279
|
const fold = b.children.length && !isRemoved ? `<button class="cc-fold" title="${geo.collapsed ? "Expand" : "Collapse"}">${geo.collapsed ? "\u25B8" : "\u25BE"}</button>` : "";
|
|
2944
|
-
const
|
|
2945
|
-
<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>` : "";
|
|
2946
3283
|
const diffChip = (change ? DIFF_CHIPS[change] : "") + (isConflicted ? '<span class="cc-conflict-chip" title="Merge conflict">!</span>' : "");
|
|
2947
|
-
|
|
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(
|
|
2948
3286
|
b.detail
|
|
2949
|
-
)}</span>${
|
|
3287
|
+
)}</span>${chipsHTML}${fold}</div>${prev}${mini ? `<div class="cc-mini">${mini}</div>` : ""}`;
|
|
2950
3288
|
}
|
|
2951
3289
|
/** Faint miniature of a collapsed block's nested blocks. */
|
|
2952
3290
|
miniMap(geo) {
|
|
@@ -2991,34 +3329,41 @@ var CodeCanvas = class {
|
|
|
2991
3329
|
this.svg.appendChild(p);
|
|
2992
3330
|
e = { key, from: w.from.id, to: w.to.id, el: p };
|
|
2993
3331
|
}
|
|
2994
|
-
e.el.setAttribute("class", `cc-edge u-${w.usage}`);
|
|
2995
|
-
e.el.
|
|
2996
|
-
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;
|
|
2997
3335
|
e.el.setAttribute("d", edgePath(w.from, w.to));
|
|
2998
3336
|
next.push(e);
|
|
2999
3337
|
}
|
|
3000
3338
|
for (const e of old.values()) e.el.remove();
|
|
3001
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")}`;
|
|
3002
3342
|
}
|
|
3003
3343
|
updateHighlight() {
|
|
3004
3344
|
const h = this.selected ?? this.hovered;
|
|
3005
3345
|
const inH = (id) => !!h && (id === h || id.startsWith(h + "/"));
|
|
3346
|
+
const isAroundSelection = (id) => !!this.selected && (inH(id) || this.selected.startsWith(id + "/"));
|
|
3006
3347
|
const linked = /* @__PURE__ */ new Set();
|
|
3007
3348
|
for (const e of this.edges) {
|
|
3008
3349
|
const on = inH(e.from) || inH(e.to);
|
|
3009
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})`);
|
|
3010
3352
|
if (on) {
|
|
3011
3353
|
linked.add(e.from);
|
|
3012
3354
|
linked.add(e.to);
|
|
3013
3355
|
}
|
|
3014
3356
|
}
|
|
3015
3357
|
this.root.classList.toggle("has-hl", !!h);
|
|
3358
|
+
this.root.classList.toggle("has-sel", !!this.selected);
|
|
3016
3359
|
for (const [id, { el: e }] of this.els) {
|
|
3017
3360
|
e.classList.toggle("cc-sel", id === this.selected);
|
|
3018
3361
|
e.classList.toggle("cc-linked", linked.has(id) && !inH(id));
|
|
3362
|
+
e.classList.toggle("cc-related", isAroundSelection(id) || linked.has(id));
|
|
3019
3363
|
e.classList.toggle("cc-editing", !!this.editing && id === this.editing.id);
|
|
3020
3364
|
}
|
|
3021
3365
|
if (this.refsOpen) this.renderRefs();
|
|
3366
|
+
this.placeSelectionBar();
|
|
3022
3367
|
}
|
|
3023
3368
|
// ---------------------------------------------------------------- camera
|
|
3024
3369
|
queueCamera() {
|
|
@@ -3032,7 +3377,7 @@ var CodeCanvas = class {
|
|
|
3032
3377
|
applyCamera() {
|
|
3033
3378
|
const { x, y, s } = this.cam;
|
|
3034
3379
|
this.world.style.transform = `translate(${x}px,${y}px) scale(${s})`;
|
|
3035
|
-
const grid =
|
|
3380
|
+
const grid = 22 * s;
|
|
3036
3381
|
this.root.style.backgroundSize = s < 0.25 ? "0 0" : `${grid}px ${grid}px`;
|
|
3037
3382
|
this.root.style.backgroundPosition = `${x}px ${y}px`;
|
|
3038
3383
|
const lod = s < 0.3 ? 2 : s < 0.55 ? 1 : 0;
|
|
@@ -3044,6 +3389,9 @@ var CodeCanvas = class {
|
|
|
3044
3389
|
this.root.classList.add("cc-moving");
|
|
3045
3390
|
clearTimeout(this.movingTimer);
|
|
3046
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();
|
|
3047
3395
|
this.opts.onCameraChange?.(s);
|
|
3048
3396
|
}
|
|
3049
3397
|
setCamera(c) {
|
|
@@ -3117,6 +3465,19 @@ var CodeCanvas = class {
|
|
|
3117
3465
|
on(r, "pointercancel", (e) => this.onUp(e, true));
|
|
3118
3466
|
on(r, "wheel", (e) => this.onWheel(e), { passive: false });
|
|
3119
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
|
+
});
|
|
3120
3481
|
on(r, "pointerover", (e) => {
|
|
3121
3482
|
if (e.pointerType !== "mouse" || this.gesture) return;
|
|
3122
3483
|
const id = e.target.closest?.(".cc-block")?.getAttribute("data-id") ?? null;
|
|
@@ -3138,9 +3499,11 @@ var CodeCanvas = class {
|
|
|
3138
3499
|
on(doc, "paste", (e) => this.onClipboard(e, "paste"));
|
|
3139
3500
|
}
|
|
3140
3501
|
isOverlay(t) {
|
|
3141
|
-
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");
|
|
3142
3503
|
}
|
|
3143
3504
|
onDown(e) {
|
|
3505
|
+
if (!e.target?.closest?.(".cc-menu")) this.closeMenu();
|
|
3506
|
+
if (e.target?.closest?.(".cc-minimap")) return this.moveToMinimapPoint(e);
|
|
3144
3507
|
if (this.isOverlay(e.target)) return;
|
|
3145
3508
|
if (e.pointerType === "mouse" && e.button !== 0 && e.button !== 1) return;
|
|
3146
3509
|
this.root.focus({ preventScroll: true });
|
|
@@ -3154,7 +3517,7 @@ var CodeCanvas = class {
|
|
|
3154
3517
|
}
|
|
3155
3518
|
if (this.pointers.size > 2) return;
|
|
3156
3519
|
const p = this.local(e);
|
|
3157
|
-
if (e.button === 1) {
|
|
3520
|
+
if (e.button === 1 || this.tool === "pan") {
|
|
3158
3521
|
this.gesture = { t: "pan", id: e.pointerId, sx: p.x, sy: p.y, cam: { ...this.cam } };
|
|
3159
3522
|
return;
|
|
3160
3523
|
}
|
|
@@ -3653,61 +4016,186 @@ var CodeCanvas = class {
|
|
|
3653
4016
|
}
|
|
3654
4017
|
// ---------------------------------------------------------------- toolbar & references
|
|
3655
4018
|
renderBar() {
|
|
3656
|
-
|
|
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;
|
|
3657
4055
|
bar.replaceChildren();
|
|
3658
|
-
bar.hidden = !!this.editing;
|
|
3659
4056
|
const b = this.selected ? this.view?.blocks.get(this.selected) : null;
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
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 {
|
|
3665
4078
|
const errors = this.diagnosticsInside(b.id);
|
|
3666
|
-
if (this.conflicts) {
|
|
3667
|
-
const isConflicted = this.conflicts.hunksByBlock.has(b.id);
|
|
3668
|
-
const firstHunk = isConflicted ? this.conflicts.hunks[this.conflicts.hunksByBlock.get(b.id)[0]] : void 0;
|
|
3669
|
-
const keep = (label, choice, side) => {
|
|
3670
|
-
const keepButton = button(label, () => this.keepConflictSide(b.id, choice), choice === "current" ? "primary" : void 0);
|
|
3671
|
-
if (side) keepButton.title = side;
|
|
3672
|
-
return keepButton;
|
|
3673
|
-
};
|
|
3674
|
-
bar.append(
|
|
3675
|
-
chip,
|
|
3676
|
-
el("div", "cc-sep"),
|
|
3677
|
-
...firstHunk ? [keep("Keep current", "current", firstHunk.currentLabel), keep("Keep incoming", "incoming", firstHunk.incomingLabel), keep("Keep both", "both"), el("div", "cc-sep")] : [],
|
|
3678
|
-
button("Edit", () => this.openEditor(b.id)),
|
|
3679
|
-
button("Copy", () => this.copy(b.id))
|
|
3680
|
-
);
|
|
3681
|
-
return this.renderRefs();
|
|
3682
|
-
}
|
|
3683
4079
|
bar.append(
|
|
3684
|
-
|
|
4080
|
+
refsToggle,
|
|
3685
4081
|
el("div", "cc-sep"),
|
|
3686
4082
|
...errors.length ? [button(plural(errors.length, "error"), () => this.toggleRefs(), "danger")] : [],
|
|
3687
|
-
button("
|
|
4083
|
+
...this.opts.onOpenCode ? [button("Open code", () => this.opts.onOpenCode(b))] : [],
|
|
4084
|
+
...this.opts.onAsk ? [button("Ask", () => this.opts.onAsk(b))] : [],
|
|
3688
4085
|
...isRenamable(b) ? [button("Rename", () => this.startRename(b.id))] : [],
|
|
3689
|
-
button(
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
...b.insert ? [button("+ Inside", () => this.createFunctionAtCenter())] : [],
|
|
3693
|
-
button("Delete", () => this.deleteBlock(b.id), "danger")
|
|
3694
|
-
);
|
|
3695
|
-
} else {
|
|
3696
|
-
const firstErrorBlock = this.diagnostics.find((d) => d.blockId)?.blockId;
|
|
3697
|
-
if (firstErrorBlock) bar.append(button(plural(this.diagnostics.length, "error"), () => this.showErrorsOf(firstErrorBlock), "danger"));
|
|
3698
|
-
const diff = this.currentDiff();
|
|
3699
|
-
if (diff) bar.append(changesSummary(diff), el("div", "cc-sep"));
|
|
3700
|
-
bar.append(button("+ Function", () => this.createFunctionAtCenter()));
|
|
3701
|
-
if (this.clip) bar.append(button("Paste", () => (this.lastPointer = null, this.paste())));
|
|
3702
|
-
bar.append(
|
|
3703
|
-
this.layoutBeforeArrange ? button("Undo arrange", () => this.undoArrange()) : button("Arrange", () => this.arrange()),
|
|
3704
|
-
el("div", "cc-sep"),
|
|
3705
|
-
button("Fit", () => this.fit()),
|
|
3706
|
-
button("\u2212", () => this.zoomBy(0.8)),
|
|
3707
|
-
button("+", () => this.zoomBy(1.25))
|
|
4086
|
+
button("Delete", () => this.deleteBlock(b.id), "danger"),
|
|
4087
|
+
this.moreButton(b),
|
|
4088
|
+
close
|
|
3708
4089
|
);
|
|
3709
4090
|
}
|
|
3710
|
-
this.
|
|
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);
|
|
4193
|
+
}
|
|
4194
|
+
languageLabelParts() {
|
|
4195
|
+
if (!this.view) return [];
|
|
4196
|
+
const label = el("span", "cc-language");
|
|
4197
|
+
label.textContent = this.view.language.label;
|
|
4198
|
+
return [el("div", "cc-sep"), label];
|
|
3711
4199
|
}
|
|
3712
4200
|
toggleRefs() {
|
|
3713
4201
|
this.refsOpen = !this.refsOpen;
|
|
@@ -3748,7 +4236,7 @@ var CodeCanvas = class {
|
|
|
3748
4236
|
btn.style.setProperty("--k", `var(--k-${block?.kind ?? "statement"})`);
|
|
3749
4237
|
const name = block && model ? displayName(model, block) : blockId;
|
|
3750
4238
|
const label = isOtherFile ? `<span class="cc-file">${esc(fileName(path))} \u203A</span> ${esc(name)}` : esc(name);
|
|
3751
|
-
setHTML(btn, `<i></i><span>${label}</span><span>${usage.kind}${usage.count > 1 ? " \xD7" + usage.count : ""}</span>`);
|
|
4239
|
+
setHTML(btn, `<i></i><span>${label}</span><span>${usage.kind === "render" ? "renders" : usage.kind}${usage.count > 1 ? " \xD7" + usage.count : ""}</span>`);
|
|
3752
4240
|
btn.onclick = () => isOtherFile ? this.opts.onOpenBlock?.(path, blockId) : this.reveal(blockId);
|
|
3753
4241
|
btn.disabled = isOtherFile && !this.opts.onOpenBlock;
|
|
3754
4242
|
return btn;
|
|
@@ -3863,6 +4351,13 @@ function el(tag, cls) {
|
|
|
3863
4351
|
if (cls) e.className = cls;
|
|
3864
4352
|
return e;
|
|
3865
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
|
+
}
|
|
3866
4361
|
function button(label, onClick, variant) {
|
|
3867
4362
|
const b = keepsCanvasFocus(document.createElement("button"));
|
|
3868
4363
|
b.className = "cc-btn" + (variant ? " " + variant : "");
|
|
@@ -3877,9 +4372,6 @@ function keepsCanvasFocus(element) {
|
|
|
3877
4372
|
element.addEventListener("pointerdown", (e) => e.preventDefault());
|
|
3878
4373
|
return element;
|
|
3879
4374
|
}
|
|
3880
|
-
function esc(s) {
|
|
3881
|
-
return s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
3882
|
-
}
|
|
3883
4375
|
function clamp2(v, lo, hi) {
|
|
3884
4376
|
return Math.min(hi, Math.max(lo, v));
|
|
3885
4377
|
}
|