@trackunit/react-components 3.12.3-alpha-4632fb37afe.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs.js +104 -959
- package/index.esm.js +102 -952
- package/migrations/assets/react-router-components/package.json +23 -0
- package/migrations/utils/move-named-imports.js +409 -0
- package/migrations/utils/package-json-utils.js +73 -0
- package/migrations/v4-0-0/move-to-router-components.js +71 -0
- package/migrations.json +5 -0
- package/package.json +6 -7
- package/src/components/ExternalLink/ExternalLink.d.ts +2 -2
- package/src/index.d.ts +0 -4
- package/translation.cjs.js +0 -1
- package/translation.esm.js +0 -1
- package/translation10.cjs.js +0 -1
- package/translation10.esm.js +0 -1
- package/translation11.cjs.js +0 -1
- package/translation11.esm.js +0 -1
- package/translation12.cjs.js +0 -1
- package/translation12.esm.js +0 -1
- package/translation13.cjs.js +0 -1
- package/translation13.esm.js +0 -1
- package/translation14.cjs.js +0 -1
- package/translation14.esm.js +0 -1
- package/translation15.cjs.js +0 -1
- package/translation15.esm.js +0 -1
- package/translation16.cjs.js +0 -1
- package/translation16.esm.js +0 -1
- package/translation17.cjs.js +0 -1
- package/translation17.esm.js +0 -1
- package/translation2.cjs.js +0 -1
- package/translation2.esm.js +0 -1
- package/translation3.cjs.js +0 -1
- package/translation3.esm.js +0 -1
- package/translation4.cjs.js +0 -1
- package/translation4.esm.js +0 -1
- package/translation5.cjs.js +0 -1
- package/translation5.esm.js +0 -1
- package/translation6.cjs.js +0 -1
- package/translation6.esm.js +0 -1
- package/translation7.cjs.js +0 -1
- package/translation7.esm.js +0 -1
- package/translation8.cjs.js +0 -1
- package/translation8.esm.js +0 -1
- package/translation9.cjs.js +0 -1
- package/translation9.esm.js +0 -1
- package/src/components/Breadcrumb/Breadcrumb.d.ts +0 -39
- package/src/components/Breadcrumb/Breadcrumb.variant.d.ts +0 -45
- package/src/components/Breadcrumb/BreadcrumbContainer.d.ts +0 -8
- package/src/components/Breadcrumb/BreadcrumbForLargeScreen.d.ts +0 -6
- package/src/components/Breadcrumb/BreadcrumbForMediumScreen.d.ts +0 -6
- package/src/components/Breadcrumb/BreadcrumbForSmallScreen.d.ts +0 -6
- package/src/components/Breadcrumb/BreadcrumbItem.d.ts +0 -6
- package/src/components/Breadcrumb/utils/types.d.ts +0 -28
- package/src/components/Breadcrumb/utils/useBreadcrumbItemsToRender.d.ts +0 -6
- package/src/hooks/persistence/hashFragment.d.ts +0 -70
- package/src/hooks/persistence/readPrimitiveSearchParam.d.ts +0 -8
- package/src/hooks/persistence/urlIdentity.d.ts +0 -64
- package/src/hooks/persistence/usePersistedState.d.ts +0 -117
- package/src/hooks/persistence/useSearchParamSync.d.ts +0 -17
- package/src/hooks/persistence/useUrlParamSync.d.ts +0 -71
- package/src/hooks/useCursorUrlSync.d.ts +0 -23
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@trackunit/react-router-components",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"repository": "https://github.com/Trackunit/manager",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=24.x"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@trackunit/css-class-variance-utilities": "2.6.7",
|
|
11
|
+
"@trackunit/i18n-library-translation": "2.12.6",
|
|
12
|
+
"@trackunit/react-components": "4.0.0",
|
|
13
|
+
"@trackunit/react-drawer": "2.14.7",
|
|
14
|
+
"@trackunit/shared-utils": "1.22.7",
|
|
15
|
+
"dequal": "^2.0.3",
|
|
16
|
+
"zod": "^3.25.76"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@tanstack/react-router": "^1.114.29",
|
|
20
|
+
"react": "^19.0.0"
|
|
21
|
+
},
|
|
22
|
+
"migrations": "./migrations.json"
|
|
23
|
+
}
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.logManualReview = exports.moveNamedImports = void 0;
|
|
37
|
+
const devkit_1 = require("@nx/devkit");
|
|
38
|
+
const ts = __importStar(require("typescript"));
|
|
39
|
+
const jsx_utils_1 = require("./jsx-utils");
|
|
40
|
+
const MOCK_OBJECTS = new Set(["vi", "jest"]);
|
|
41
|
+
const MOCK_METHODS = new Set([
|
|
42
|
+
"mock",
|
|
43
|
+
"doMock",
|
|
44
|
+
"unmock",
|
|
45
|
+
"doUnmock",
|
|
46
|
+
"importActual",
|
|
47
|
+
"importMock",
|
|
48
|
+
"requireActual",
|
|
49
|
+
"requireMock",
|
|
50
|
+
]);
|
|
51
|
+
const toRenameMap = (names) => Array.isArray(names) ? new Map(names.map(name => [name, name])) : new Map(Object.entries(names));
|
|
52
|
+
const parseSource = (filePath, content) => ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, filePath.endsWith(".ts") ? ts.ScriptKind.TS : ts.ScriptKind.TSX);
|
|
53
|
+
const isModule = (node, moduleName) => node !== undefined &&
|
|
54
|
+
(ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) &&
|
|
55
|
+
node.text === moduleName;
|
|
56
|
+
/** Renders `moduleName` with the quote style of an existing specifier. */
|
|
57
|
+
const quoteLike = (specifier, moduleName) => {
|
|
58
|
+
const quote = specifier.getText().startsWith("'") ? "'" : '"';
|
|
59
|
+
return `${quote}${moduleName}${quote}`;
|
|
60
|
+
};
|
|
61
|
+
const renderSpecifier = ({ imported, local, isTypeOnly }, withTypePrefix) => `${withTypePrefix && isTypeOnly ? "type " : ""}${imported === local ? imported : `${imported} as ${local}`}`;
|
|
62
|
+
const statementTerminator = (statement) => (statement.getText().endsWith(";") ? ";" : "");
|
|
63
|
+
const lineOf = (sourceFile, node) => sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
64
|
+
/** `true` when `node` is a property-like key (`a.X`, `{ X: 1 }`, `<C X="" />`) rather than a reference to a binding. */
|
|
65
|
+
const isPropertyKey = (node) => {
|
|
66
|
+
const parent = node.parent;
|
|
67
|
+
if (ts.isPropertyAccessExpression(parent) || ts.isQualifiedName(parent)) {
|
|
68
|
+
return ts.isPropertyAccessExpression(parent) ? parent.name === node : parent.right === node;
|
|
69
|
+
}
|
|
70
|
+
if (ts.isBindingElement(parent))
|
|
71
|
+
return parent.propertyName === node;
|
|
72
|
+
if (ts.isPropertyAssignment(parent) ||
|
|
73
|
+
ts.isPropertyDeclaration(parent) ||
|
|
74
|
+
ts.isPropertySignature(parent) ||
|
|
75
|
+
ts.isMethodDeclaration(parent) ||
|
|
76
|
+
ts.isMethodSignature(parent) ||
|
|
77
|
+
ts.isGetAccessorDeclaration(parent) ||
|
|
78
|
+
ts.isSetAccessorDeclaration(parent) ||
|
|
79
|
+
ts.isEnumMember(parent) ||
|
|
80
|
+
ts.isJsxAttribute(parent)) {
|
|
81
|
+
return parent.name === node;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
};
|
|
85
|
+
/** `true` when `node` declares a new binding (variable, parameter, function, class, type...). */
|
|
86
|
+
const isBindingDeclaration = (node) => {
|
|
87
|
+
const parent = node.parent;
|
|
88
|
+
if (ts.isBindingElement(parent))
|
|
89
|
+
return parent.name === node;
|
|
90
|
+
if (ts.isVariableDeclaration(parent) ||
|
|
91
|
+
ts.isParameter(parent) ||
|
|
92
|
+
ts.isFunctionDeclaration(parent) ||
|
|
93
|
+
ts.isFunctionExpression(parent) ||
|
|
94
|
+
ts.isClassDeclaration(parent) ||
|
|
95
|
+
ts.isClassExpression(parent) ||
|
|
96
|
+
ts.isInterfaceDeclaration(parent) ||
|
|
97
|
+
ts.isTypeAliasDeclaration(parent) ||
|
|
98
|
+
ts.isEnumDeclaration(parent) ||
|
|
99
|
+
ts.isTypeParameterDeclaration(parent) ||
|
|
100
|
+
ts.isImportSpecifier(parent) ||
|
|
101
|
+
ts.isImportClause(parent) ||
|
|
102
|
+
ts.isNamespaceImport(parent) ||
|
|
103
|
+
ts.isImportEqualsDeclaration(parent)) {
|
|
104
|
+
return parent.name === node;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
};
|
|
108
|
+
/** Import and re-export statements are rewritten as a whole, so their identifiers are never edited one by one. */
|
|
109
|
+
const isInsideModuleStatement = (node) => ts.findAncestor(node, ancestor => ts.isImportDeclaration(ancestor) || (ts.isExportDeclaration(ancestor) && ancestor.moduleSpecifier !== undefined)) !== undefined;
|
|
110
|
+
const collectIdentifiers = (sourceFile) => {
|
|
111
|
+
const identifiers = [];
|
|
112
|
+
const visit = (node) => {
|
|
113
|
+
if (ts.isIdentifier(node))
|
|
114
|
+
identifiers.push(node);
|
|
115
|
+
ts.forEachChild(node, visit);
|
|
116
|
+
};
|
|
117
|
+
visit(sourceFile);
|
|
118
|
+
return identifiers;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Edits that rename every reference to the local binding `oldName` to
|
|
122
|
+
* `newName`, or `null` when that can't be done safely (the new name is
|
|
123
|
+
* already used in the file, or `oldName` is redeclared and might be shadowed).
|
|
124
|
+
*/
|
|
125
|
+
const renameReferenceEdits = (sourceFile, identifiers, oldName, newName) => {
|
|
126
|
+
if (identifiers.some(id => id.text === newName))
|
|
127
|
+
return null;
|
|
128
|
+
const outside = identifiers.filter(id => id.text === oldName && !isInsideModuleStatement(id));
|
|
129
|
+
if (outside.some(isBindingDeclaration))
|
|
130
|
+
return null;
|
|
131
|
+
const edits = [];
|
|
132
|
+
for (const id of outside) {
|
|
133
|
+
if (isPropertyKey(id))
|
|
134
|
+
continue;
|
|
135
|
+
const start = id.getStart(sourceFile);
|
|
136
|
+
const end = id.getEnd();
|
|
137
|
+
const parent = id.parent;
|
|
138
|
+
if (ts.isShorthandPropertyAssignment(parent)) {
|
|
139
|
+
edits.push({ start, end, text: `${oldName}: ${newName}` });
|
|
140
|
+
}
|
|
141
|
+
else if (ts.isExportSpecifier(parent)) {
|
|
142
|
+
if (parent.propertyName === undefined)
|
|
143
|
+
edits.push({ start, end, text: `${newName} as ${oldName}` });
|
|
144
|
+
else if (parent.propertyName === id)
|
|
145
|
+
edits.push({ start, end, text: newName });
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
edits.push({ start, end, text: newName });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return edits;
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Usages of `fromModule` the codemod can't rewrite statically. They are only
|
|
155
|
+
* reported when the file references a moved name at all, so e.g. a
|
|
156
|
+
* `vi.mock(fromModule, ...)` in a spec that never touches a moved name is not
|
|
157
|
+
* noise. `export *` is always reported: the moved names silently drop out of it.
|
|
158
|
+
*/
|
|
159
|
+
const findManualReviewItems = (sourceFile, identifiers, fromModule, toModule, renames) => {
|
|
160
|
+
const items = [];
|
|
161
|
+
const file = sourceFile.fileName;
|
|
162
|
+
const mentionsMovedName = identifiers.some(id => renames.has(id.text)) || sourceFile.text.split(/["'`]/).some(chunk => renames.has(chunk));
|
|
163
|
+
const movedList = [...renames.keys()].join(", ");
|
|
164
|
+
const hint = `moved names (${movedList}) now come from "${toModule}"`;
|
|
165
|
+
const report = (node, what) => {
|
|
166
|
+
items.push({ file, line: lineOf(sourceFile, node), reason: `${what} of "${fromModule}"; ${hint}` });
|
|
167
|
+
};
|
|
168
|
+
const visit = (node) => {
|
|
169
|
+
if (ts.isImportDeclaration(node) && isModule(node.moduleSpecifier, fromModule)) {
|
|
170
|
+
const bindings = node.importClause?.namedBindings;
|
|
171
|
+
if (bindings !== undefined && ts.isNamespaceImport(bindings) && mentionsMovedName) {
|
|
172
|
+
report(node, `namespace import \`* as ${bindings.name.text}\``);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else if (ts.isExportDeclaration(node) && isModule(node.moduleSpecifier, fromModule)) {
|
|
176
|
+
if (node.exportClause === undefined)
|
|
177
|
+
report(node, "`export *`");
|
|
178
|
+
else if (ts.isNamespaceExport(node.exportClause))
|
|
179
|
+
report(node, "`export * as`");
|
|
180
|
+
}
|
|
181
|
+
else if (ts.isImportEqualsDeclaration(node) && mentionsMovedName) {
|
|
182
|
+
const reference = node.moduleReference;
|
|
183
|
+
if (ts.isExternalModuleReference(reference) && isModule(reference.expression, fromModule)) {
|
|
184
|
+
report(node, "`import = require()`");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else if (ts.isImportTypeNode(node) && mentionsMovedName) {
|
|
188
|
+
const argument = node.argument;
|
|
189
|
+
if (ts.isLiteralTypeNode(argument) && isModule(argument.literal, fromModule))
|
|
190
|
+
report(node, "`import()` type");
|
|
191
|
+
}
|
|
192
|
+
else if (ts.isCallExpression(node) && mentionsMovedName && isModule(node.arguments[0], fromModule)) {
|
|
193
|
+
const callee = node.expression;
|
|
194
|
+
if (callee.kind === ts.SyntaxKind.ImportKeyword) {
|
|
195
|
+
report(node, "dynamic import()");
|
|
196
|
+
}
|
|
197
|
+
else if (ts.isIdentifier(callee) && callee.text === "require") {
|
|
198
|
+
report(node, "require()");
|
|
199
|
+
}
|
|
200
|
+
else if (ts.isPropertyAccessExpression(callee) &&
|
|
201
|
+
ts.isIdentifier(callee.expression) &&
|
|
202
|
+
MOCK_OBJECTS.has(callee.expression.text) &&
|
|
203
|
+
MOCK_METHODS.has(callee.name.text)) {
|
|
204
|
+
report(node, `${callee.expression.text}.${callee.name.text}()`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
ts.forEachChild(node, visit);
|
|
208
|
+
};
|
|
209
|
+
visit(sourceFile);
|
|
210
|
+
return items;
|
|
211
|
+
};
|
|
212
|
+
const findTargetImport = (sourceFile, toModule, typeOnly) => {
|
|
213
|
+
for (const statement of sourceFile.statements) {
|
|
214
|
+
if (!ts.isImportDeclaration(statement) || !isModule(statement.moduleSpecifier, toModule))
|
|
215
|
+
continue;
|
|
216
|
+
const clause = statement.importClause;
|
|
217
|
+
if (clause === undefined || clause.isTypeOnly !== typeOnly)
|
|
218
|
+
continue;
|
|
219
|
+
const bindings = clause.namedBindings;
|
|
220
|
+
if (bindings === undefined || !ts.isNamedImports(bindings))
|
|
221
|
+
continue;
|
|
222
|
+
return { statement, elements: bindings.elements, additions: [] };
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
};
|
|
226
|
+
const specifierKey = ({ imported, local }) => `${imported} as ${local}`;
|
|
227
|
+
const specifierOf = (element, parentTypeOnly) => ({
|
|
228
|
+
imported: (element.propertyName ?? element.name).text,
|
|
229
|
+
local: element.name.text,
|
|
230
|
+
isTypeOnly: parentTypeOnly || element.isTypeOnly,
|
|
231
|
+
});
|
|
232
|
+
const rewriteFile = (filePath, content, { fromModule, toModule }, renames) => {
|
|
233
|
+
const sourceFile = parseSource(filePath, content);
|
|
234
|
+
const identifiers = collectIdentifiers(sourceFile);
|
|
235
|
+
const manualReview = findManualReviewItems(sourceFile, identifiers, fromModule, toModule, renames);
|
|
236
|
+
const edits = [];
|
|
237
|
+
const valueTarget = findTargetImport(sourceFile, toModule, false);
|
|
238
|
+
const typeTarget = findTargetImport(sourceFile, toModule, true);
|
|
239
|
+
const targetFor = (spec) => {
|
|
240
|
+
if (!spec.isTypeOnly)
|
|
241
|
+
return valueTarget;
|
|
242
|
+
return typeTarget ?? valueTarget;
|
|
243
|
+
};
|
|
244
|
+
const replaceStatement = (statement, kept, added) => {
|
|
245
|
+
const parts = [...(kept === null ? [] : [kept]), ...added];
|
|
246
|
+
const start = statement.getStart(sourceFile);
|
|
247
|
+
let end = statement.getEnd();
|
|
248
|
+
if (parts.length === 0) {
|
|
249
|
+
if (content.startsWith("\r\n", end))
|
|
250
|
+
end += 2;
|
|
251
|
+
else if (content.startsWith("\n", end))
|
|
252
|
+
end += 1;
|
|
253
|
+
const leading = content.slice(statement.getFullStart(), start);
|
|
254
|
+
if (leading.includes("eslint-disable")) {
|
|
255
|
+
manualReview.push({
|
|
256
|
+
file: filePath,
|
|
257
|
+
line: lineOf(sourceFile, statement),
|
|
258
|
+
reason: `removed import of "${fromModule}" had an eslint-disable comment, which now applies to the next line`,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
edits.push({ start, end, text: parts.join("\n") });
|
|
263
|
+
};
|
|
264
|
+
const createdImports = new Map();
|
|
265
|
+
const deferredImports = [];
|
|
266
|
+
for (const statement of sourceFile.statements) {
|
|
267
|
+
if (ts.isImportDeclaration(statement) && isModule(statement.moduleSpecifier, fromModule)) {
|
|
268
|
+
const clause = statement.importClause;
|
|
269
|
+
const bindings = clause?.namedBindings;
|
|
270
|
+
if (clause === undefined || bindings === undefined || !ts.isNamedImports(bindings))
|
|
271
|
+
continue;
|
|
272
|
+
const moved = bindings.elements.filter(element => renames.has((element.propertyName ?? element.name).text));
|
|
273
|
+
if (moved.length === 0)
|
|
274
|
+
continue;
|
|
275
|
+
const kept = bindings.elements.filter(element => !moved.includes(element));
|
|
276
|
+
const createdKind = clause.isTypeOnly;
|
|
277
|
+
const addCreated = (spec) => {
|
|
278
|
+
const created = createdImports.get(createdKind) ?? { anchor: statement, specs: [] };
|
|
279
|
+
createdImports.set(createdKind, created);
|
|
280
|
+
if (!created.specs.some(existing => specifierKey(existing) === specifierKey(spec)))
|
|
281
|
+
created.specs.push(spec);
|
|
282
|
+
};
|
|
283
|
+
for (const element of moved) {
|
|
284
|
+
const original = specifierOf(element, clause.isTypeOnly);
|
|
285
|
+
const newName = renames.get(original.imported) ?? original.imported;
|
|
286
|
+
let local = original.local;
|
|
287
|
+
if (newName !== original.imported && original.local === original.imported) {
|
|
288
|
+
const renameEdits = renameReferenceEdits(sourceFile, identifiers, original.local, newName);
|
|
289
|
+
if (renameEdits !== null) {
|
|
290
|
+
edits.push(...renameEdits);
|
|
291
|
+
local = newName;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const spec = { imported: newName, local, isTypeOnly: original.isTypeOnly };
|
|
295
|
+
const target = targetFor(spec);
|
|
296
|
+
if (target === null) {
|
|
297
|
+
addCreated(spec);
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const existing = new Set([
|
|
301
|
+
...target.elements.map(e => specifierKey(specifierOf(e, false))),
|
|
302
|
+
...target.additions.map(specifierKey),
|
|
303
|
+
]);
|
|
304
|
+
if (!existing.has(specifierKey(spec)))
|
|
305
|
+
target.additions.push(spec);
|
|
306
|
+
}
|
|
307
|
+
const terminator = statementTerminator(statement);
|
|
308
|
+
const defaultName = clause.name?.text;
|
|
309
|
+
const keptText = kept.length === 0 && defaultName === undefined
|
|
310
|
+
? null
|
|
311
|
+
: `import ${clause.isTypeOnly ? "type " : ""}${[
|
|
312
|
+
...(defaultName === undefined ? [] : [defaultName]),
|
|
313
|
+
...(kept.length === 0 ? [] : [`{ ${kept.map(e => e.getText(sourceFile)).join(", ")} }`]),
|
|
314
|
+
].join(", ")} from ${statement.moduleSpecifier.getText(sourceFile)}${terminator}`;
|
|
315
|
+
deferredImports.push({ statement, keptText });
|
|
316
|
+
}
|
|
317
|
+
else if (ts.isExportDeclaration(statement) &&
|
|
318
|
+
isModule(statement.moduleSpecifier, fromModule) &&
|
|
319
|
+
statement.moduleSpecifier !== undefined &&
|
|
320
|
+
statement.exportClause !== undefined &&
|
|
321
|
+
ts.isNamedExports(statement.exportClause)) {
|
|
322
|
+
const elements = statement.exportClause.elements;
|
|
323
|
+
const moved = elements.filter(element => renames.has((element.propertyName ?? element.name).text));
|
|
324
|
+
if (moved.length === 0)
|
|
325
|
+
continue;
|
|
326
|
+
const kept = elements.filter(element => !moved.includes(element));
|
|
327
|
+
const typePrefix = statement.isTypeOnly ? "type " : "";
|
|
328
|
+
const terminator = statementTerminator(statement);
|
|
329
|
+
const specs = moved.map(element => {
|
|
330
|
+
const imported = (element.propertyName ?? element.name).text;
|
|
331
|
+
return renderSpecifier({ imported: renames.get(imported) ?? imported, local: element.name.text, isTypeOnly: element.isTypeOnly }, true);
|
|
332
|
+
});
|
|
333
|
+
replaceStatement(statement, kept.length === 0
|
|
334
|
+
? null
|
|
335
|
+
: `export ${typePrefix}{ ${kept.map(e => e.getText(sourceFile)).join(", ")} } from ${statement.moduleSpecifier.getText(sourceFile)}${terminator}`, [
|
|
336
|
+
`export ${typePrefix}{ ${specs.join(", ")} } from ${quoteLike(statement.moduleSpecifier, toModule)}${terminator}`,
|
|
337
|
+
]);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
for (const { statement, keptText } of deferredImports) {
|
|
341
|
+
const createdText = [...createdImports.entries()]
|
|
342
|
+
.filter(([, created]) => created.anchor === statement)
|
|
343
|
+
.map(([typeOnly, created]) => `import ${typeOnly ? "type " : ""}{ ${created.specs
|
|
344
|
+
.map(spec => renderSpecifier(spec, !typeOnly))
|
|
345
|
+
.join(", ")} } from ${quoteLike(statement.moduleSpecifier, toModule)}${statementTerminator(statement)}`);
|
|
346
|
+
replaceStatement(statement, keptText, createdText);
|
|
347
|
+
}
|
|
348
|
+
for (const target of [valueTarget, typeTarget]) {
|
|
349
|
+
if (target === null || target.additions.length === 0)
|
|
350
|
+
continue;
|
|
351
|
+
const typeOnly = target.statement.importClause?.isTypeOnly === true;
|
|
352
|
+
const bindings = target.statement.importClause?.namedBindings;
|
|
353
|
+
if (bindings === undefined)
|
|
354
|
+
continue;
|
|
355
|
+
const merged = [
|
|
356
|
+
...target.elements.map(e => e.getText(sourceFile)),
|
|
357
|
+
...target.additions.map(spec => renderSpecifier(spec, !typeOnly)),
|
|
358
|
+
];
|
|
359
|
+
edits.push({ start: bindings.getStart(sourceFile), end: bindings.getEnd(), text: `{ ${merged.join(", ")} }` });
|
|
360
|
+
}
|
|
361
|
+
if (edits.length === 0)
|
|
362
|
+
return { content, manualReview };
|
|
363
|
+
edits.sort((a, b) => b.start - a.start);
|
|
364
|
+
let updated = content;
|
|
365
|
+
for (const { start, end, text } of edits) {
|
|
366
|
+
updated = updated.slice(0, start) + text + updated.slice(end);
|
|
367
|
+
}
|
|
368
|
+
return { content: updated, manualReview };
|
|
369
|
+
};
|
|
370
|
+
/**
|
|
371
|
+
* Moves named imports and named re-exports of `names` from `fromModule` to
|
|
372
|
+
* `toModule` across every `.ts`/`.tsx`/`.jsx` file in the tree.
|
|
373
|
+
*
|
|
374
|
+
* - Mixed imports are split; names that didn't move stay on `fromModule`.
|
|
375
|
+
* - Aliases (`as`), `import type` and inline `type` modifiers are preserved.
|
|
376
|
+
* - Moved names are merged into an existing named import of `toModule`, or
|
|
377
|
+
* else into one new import per kind (value / `import type`).
|
|
378
|
+
* - With a rename record, an unaliased import has its references renamed too;
|
|
379
|
+
* when that isn't safe (name clash or shadowing) it is imported as
|
|
380
|
+
* `{ NewName as OldName }` instead. Re-exports keep their public name.
|
|
381
|
+
* - Namespace imports, `export *`, dynamic `import()`, `require()` and
|
|
382
|
+
* `vi`/`jest` mocks of `fromModule` are left untouched and returned in
|
|
383
|
+
* `manualReview`, never skipped silently.
|
|
384
|
+
*
|
|
385
|
+
* Synchronous and format-free, so it can run inside a `run-migrations`
|
|
386
|
+
* migration; callers format changed files themselves (e.g. `formatFiles`).
|
|
387
|
+
*/
|
|
388
|
+
const moveNamedImports = (tree, options) => {
|
|
389
|
+
const renames = toRenameMap(options.names);
|
|
390
|
+
const changedFiles = [];
|
|
391
|
+
const manualReview = [];
|
|
392
|
+
(0, jsx_utils_1.visitTsFiles)(tree, options.fromModule, (filePath, content) => {
|
|
393
|
+
const result = rewriteFile(filePath, content, options, renames);
|
|
394
|
+
manualReview.push(...result.manualReview);
|
|
395
|
+
if (result.content !== content)
|
|
396
|
+
changedFiles.push(filePath);
|
|
397
|
+
return result.content;
|
|
398
|
+
});
|
|
399
|
+
return { changedFiles, manualReview };
|
|
400
|
+
};
|
|
401
|
+
exports.moveNamedImports = moveNamedImports;
|
|
402
|
+
/** Logs each manual-review item as a warning, prefixed with the migration name. */
|
|
403
|
+
const logManualReview = (migrationName, items) => {
|
|
404
|
+
for (const { file, line, reason } of items) {
|
|
405
|
+
devkit_1.logger.warn(` ${migrationName}: manual review needed at ${file}:${line} - ${reason}`);
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
exports.logManualReview = logManualReview;
|
|
409
|
+
//# sourceMappingURL=move-named-imports.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readCompanionVersion = exports.findPackageJsonsDeclaring = exports.ensureDependency = void 0;
|
|
4
|
+
const devkit_1 = require("@nx/devkit");
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
/** Root package name of the manager monorepo, where `@trackunit/*` versions are owned by CI. */
|
|
7
|
+
const MANAGER_WORKSPACE_PACKAGE_NAME = "@trackunit/manager";
|
|
8
|
+
/**
|
|
9
|
+
* Adds `packageName@range` to a package.json unless it is already declared
|
|
10
|
+
* in `dependencies` or `devDependencies` (an existing entry is never
|
|
11
|
+
* changed). Does nothing in the manager monorepo itself. Doesn't install;
|
|
12
|
+
* the consumer has to re-install afterwards.
|
|
13
|
+
*/
|
|
14
|
+
const ensureDependency = (tree, options) => {
|
|
15
|
+
const { packageName, range, besidePackage, packageJsonPath = "package.json" } = options;
|
|
16
|
+
if (!tree.exists(packageJsonPath))
|
|
17
|
+
return "no-package-json";
|
|
18
|
+
const packageJson = (0, devkit_1.readJson)(tree, packageJsonPath);
|
|
19
|
+
if (packageJson.name === MANAGER_WORKSPACE_PACKAGE_NAME)
|
|
20
|
+
return "manager-workspace";
|
|
21
|
+
if (packageJson.dependencies?.[packageName] !== undefined ||
|
|
22
|
+
packageJson.devDependencies?.[packageName] !== undefined) {
|
|
23
|
+
return "already-present";
|
|
24
|
+
}
|
|
25
|
+
const asDevDependency = besidePackage !== undefined &&
|
|
26
|
+
packageJson.dependencies?.[besidePackage] === undefined &&
|
|
27
|
+
packageJson.devDependencies?.[besidePackage] !== undefined;
|
|
28
|
+
const entry = { [packageName]: range };
|
|
29
|
+
(0, devkit_1.addDependenciesToPackageJson)(tree, asDevDependency ? {} : entry, asDevDependency ? entry : {}, packageJsonPath, true);
|
|
30
|
+
return "added";
|
|
31
|
+
};
|
|
32
|
+
exports.ensureDependency = ensureDependency;
|
|
33
|
+
/**
|
|
34
|
+
* Every package.json except the root one (and anything under
|
|
35
|
+
* `node_modules`) that declares `packageName` as a dependency, devDependency
|
|
36
|
+
* or peerDependency.
|
|
37
|
+
*/
|
|
38
|
+
const findPackageJsonsDeclaring = (tree, packageName) => {
|
|
39
|
+
const found = [];
|
|
40
|
+
(0, devkit_1.visitNotIgnoredFiles)(tree, "/", filePath => {
|
|
41
|
+
if (filePath === "package.json" || !filePath.endsWith("/package.json"))
|
|
42
|
+
return;
|
|
43
|
+
if (filePath.startsWith("node_modules/") || filePath.includes("/node_modules/"))
|
|
44
|
+
return;
|
|
45
|
+
const packageJson = (0, devkit_1.readJson)(tree, filePath);
|
|
46
|
+
if (packageJson.dependencies?.[packageName] !== undefined ||
|
|
47
|
+
packageJson.devDependencies?.[packageName] !== undefined ||
|
|
48
|
+
packageJson.peerDependencies?.[packageName] !== undefined) {
|
|
49
|
+
found.push(filePath);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return found;
|
|
53
|
+
};
|
|
54
|
+
exports.findPackageJsonsDeclaring = findPackageJsonsDeclaring;
|
|
55
|
+
/**
|
|
56
|
+
* Version of a companion package, read from the first of `candidatePaths`
|
|
57
|
+
* (absolute file-system paths, not tree paths) that is the package.json of
|
|
58
|
+
* `packageName`. Throws when none is, so a migration fails and stays pending
|
|
59
|
+
* instead of writing a wrong range.
|
|
60
|
+
*/
|
|
61
|
+
const readCompanionVersion = (packageName, candidatePaths) => {
|
|
62
|
+
for (const candidatePath of candidatePaths) {
|
|
63
|
+
if (!(0, node_fs_1.existsSync)(candidatePath))
|
|
64
|
+
continue;
|
|
65
|
+
const packageJson = JSON.parse((0, node_fs_1.readFileSync)(candidatePath, "utf-8"));
|
|
66
|
+
if (packageJson.name === packageName && typeof packageJson.version === "string" && packageJson.version !== "") {
|
|
67
|
+
return packageJson.version;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`Could not find the version of ${packageName}. Looked in: ${candidatePaths.join(", ")}`);
|
|
71
|
+
};
|
|
72
|
+
exports.readCompanionVersion = readCompanionVersion;
|
|
73
|
+
//# sourceMappingURL=package-json-utils.js.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.moveToRouterComponents = exports.resolveRouterComponentsRange = exports.ROUTER_COMPONENTS_MOVED_NAMES = void 0;
|
|
4
|
+
const devkit_1 = require("@nx/devkit");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const jsx_utils_1 = require("../utils/jsx-utils");
|
|
7
|
+
const move_named_imports_1 = require("../utils/move-named-imports");
|
|
8
|
+
const package_json_utils_1 = require("../utils/package-json-utils");
|
|
9
|
+
const MIGRATION_NAME = "move-to-router-components";
|
|
10
|
+
const FROM_MODULE = "@trackunit/react-components";
|
|
11
|
+
const TO_MODULE = "@trackunit/react-router-components";
|
|
12
|
+
/** Exports that left `@trackunit/react-components` for `@trackunit/react-router-components`. */
|
|
13
|
+
exports.ROUTER_COMPONENTS_MOVED_NAMES = [
|
|
14
|
+
"usePersistedState",
|
|
15
|
+
"useSearchParamSync",
|
|
16
|
+
"useCursorUrlSync",
|
|
17
|
+
"MAX_URL_LENGTH",
|
|
18
|
+
"UrlPersistenceLocation",
|
|
19
|
+
"Breadcrumb",
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* `^<version>` of `@trackunit/react-router-components`. The published
|
|
23
|
+
* package ships its package.json as a `build-migrations` asset; running from
|
|
24
|
+
* source falls back to the workspace copy.
|
|
25
|
+
*/
|
|
26
|
+
const resolveRouterComponentsRange = () => `^${(0, package_json_utils_1.readCompanionVersion)(TO_MODULE, [
|
|
27
|
+
(0, node_path_1.join)(__dirname, "../assets/react-router-components/package.json"),
|
|
28
|
+
(0, node_path_1.join)(__dirname, "../../../router-components/package.json"),
|
|
29
|
+
])}`;
|
|
30
|
+
exports.resolveRouterComponentsRange = resolveRouterComponentsRange;
|
|
31
|
+
/**
|
|
32
|
+
* Moves imports and re-exports of the router-coupled hooks, constants and components from
|
|
33
|
+
* `@trackunit/react-components` to `@trackunit/react-router-components`.
|
|
34
|
+
*
|
|
35
|
+
* When something was rewritten, `@trackunit/react-router-components` is added
|
|
36
|
+
* to the root package.json next to `@trackunit/react-components` (an
|
|
37
|
+
* existing entry is kept). Per-project package.json files are only reported.
|
|
38
|
+
* Throws before touching any file when the router-components version can't be
|
|
39
|
+
* resolved, so a failed run leaves nothing half-migrated.
|
|
40
|
+
*/
|
|
41
|
+
const moveToRouterComponents = (tree, resolveRange = exports.resolveRouterComponentsRange) => {
|
|
42
|
+
const range = resolveRange();
|
|
43
|
+
const { changedFiles, manualReview } = (0, move_named_imports_1.moveNamedImports)(tree, {
|
|
44
|
+
fromModule: FROM_MODULE,
|
|
45
|
+
toModule: TO_MODULE,
|
|
46
|
+
names: exports.ROUTER_COMPONENTS_MOVED_NAMES,
|
|
47
|
+
});
|
|
48
|
+
(0, move_named_imports_1.logManualReview)(MIGRATION_NAME, manualReview);
|
|
49
|
+
if (changedFiles.length > 0) {
|
|
50
|
+
const result = (0, package_json_utils_1.ensureDependency)(tree, {
|
|
51
|
+
packageName: TO_MODULE,
|
|
52
|
+
range,
|
|
53
|
+
besidePackage: FROM_MODULE,
|
|
54
|
+
});
|
|
55
|
+
if (result === "added") {
|
|
56
|
+
devkit_1.logger.info(` ${MIGRATION_NAME}: added ${TO_MODULE} to package.json; re-install dependencies (e.g. \`yarn\` or \`npm install\`) after running migrations`);
|
|
57
|
+
}
|
|
58
|
+
else if (result === "no-package-json") {
|
|
59
|
+
devkit_1.logger.warn(` ${MIGRATION_NAME}: no root package.json found; add ${TO_MODULE} as a dependency yourself`);
|
|
60
|
+
}
|
|
61
|
+
if (result !== "manager-workspace") {
|
|
62
|
+
for (const packageJsonPath of (0, package_json_utils_1.findPackageJsonsDeclaring)(tree, FROM_MODULE)) {
|
|
63
|
+
devkit_1.logger.warn(` ${MIGRATION_NAME}: ${packageJsonPath} declares ${FROM_MODULE}; add ${TO_MODULE} there too if that project uses the moved names`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
(0, jsx_utils_1.logSummary)(MIGRATION_NAME, changedFiles.length, manualReview.length);
|
|
68
|
+
};
|
|
69
|
+
exports.moveToRouterComponents = moveToRouterComponents;
|
|
70
|
+
exports.default = exports.moveToRouterComponents;
|
|
71
|
+
//# sourceMappingURL=move-to-router-components.js.map
|
package/migrations.json
CHANGED
|
@@ -49,6 +49,11 @@
|
|
|
49
49
|
"version": "3.0.0",
|
|
50
50
|
"description": "Replace the removed CopyableText with the Text + CopyButton pair (the displayed value stays plain text; only the button copies).",
|
|
51
51
|
"implementation": "./migrations/v3-0-0/copyabletext-to-text-copybutton"
|
|
52
|
+
},
|
|
53
|
+
"v4-0-0-move-to-router-components": {
|
|
54
|
+
"version": "4.0.0",
|
|
55
|
+
"description": "Move imports of usePersistedState, useSearchParamSync, useCursorUrlSync, MAX_URL_LENGTH, UrlPersistenceLocation and Breadcrumb from @trackunit/react-components to @trackunit/react-router-components, and add @trackunit/react-router-components to the root package.json. Re-install dependencies afterwards.",
|
|
56
|
+
"implementation": "./migrations/v4-0-0/move-to-router-components"
|
|
52
57
|
}
|
|
53
58
|
}
|
|
54
59
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-components",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"migrations": "./migrations.json",
|
|
@@ -14,16 +14,15 @@
|
|
|
14
14
|
"@floating-ui/react": "^0.26.25",
|
|
15
15
|
"string-ts": "^2.0.0",
|
|
16
16
|
"tailwind-merge": "^3.7.0",
|
|
17
|
-
"@trackunit/ui-design-tokens": "1.21.
|
|
18
|
-
"@trackunit/css-class-variance-utilities": "2.6.
|
|
19
|
-
"@trackunit/shared-utils": "1.22.
|
|
20
|
-
"@trackunit/ui-icons": "1.20.
|
|
17
|
+
"@trackunit/ui-design-tokens": "1.21.7",
|
|
18
|
+
"@trackunit/css-class-variance-utilities": "2.6.7",
|
|
19
|
+
"@trackunit/shared-utils": "1.22.7",
|
|
20
|
+
"@trackunit/ui-icons": "1.20.7",
|
|
21
21
|
"es-toolkit": "^1.52.0",
|
|
22
22
|
"@tanstack/react-virtual": "^3.14.13",
|
|
23
|
-
"dequal": "^2.0.3",
|
|
24
23
|
"fflate": "^0.8.3",
|
|
25
24
|
"zod": "^3.25.76",
|
|
26
|
-
"@trackunit/i18n-library-translation": "2.12.
|
|
25
|
+
"@trackunit/i18n-library-translation": "2.12.6"
|
|
27
26
|
},
|
|
28
27
|
"peerDependencies": {
|
|
29
28
|
"react": "^19.0.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MouseEventHandler, ReactElement, ReactNode } from "react";
|
|
2
2
|
import { CommonProps } from "../../common/CommonProps";
|
|
3
|
-
import type { Styleable } from "../../common/Styleable";
|
|
4
3
|
import { Refable } from "../../common/Refable";
|
|
4
|
+
import type { Styleable } from "../../common/Styleable";
|
|
5
5
|
export interface ExternalLinkProps extends CommonProps, Styleable, Refable<HTMLAnchorElement> {
|
|
6
6
|
/**
|
|
7
7
|
* The URL to which users are redirected
|
|
@@ -37,7 +37,7 @@ export interface ExternalLinkProps extends CommonProps, Styleable, Refable<HTMLA
|
|
|
37
37
|
* Use ExternalLink for any links that navigate to external resources outside the application (e.g., documentation, support pages, vendor sites).
|
|
38
38
|
*
|
|
39
39
|
* ### When not to use
|
|
40
|
-
* Do not use ExternalLink for in-app navigation. Use `
|
|
40
|
+
* Do not use ExternalLink for in-app navigation. Use `RouteLink` from `@trackunit/react-router-components` for in-app routes.
|
|
41
41
|
*
|
|
42
42
|
* @example Basic external link
|
|
43
43
|
* ```tsx
|
package/src/index.d.ts
CHANGED
|
@@ -10,7 +10,6 @@ export * from "./components/Alert/Alert";
|
|
|
10
10
|
export * from "./components/AssetIdentity/IdentityCard";
|
|
11
11
|
export * from "./components/AssetIdentity/IdentityCardSkeleton";
|
|
12
12
|
export * from "./components/Badge/Badge";
|
|
13
|
-
export * from "./components/Breadcrumb/Breadcrumb";
|
|
14
13
|
export * from "./components/buttons/Button/Button";
|
|
15
14
|
export * from "./components/buttons/IconButton/IconButton";
|
|
16
15
|
export * from "./components/buttons/shared/Button.variants";
|
|
@@ -126,8 +125,6 @@ export * from "./hooks/localStorage/useLocalStorageReducer";
|
|
|
126
125
|
export * from "./hooks/localStorage/useSessionStorage";
|
|
127
126
|
export * from "./hooks/localStorage/useSessionStorageReducer";
|
|
128
127
|
export * from "./hooks/noPagination";
|
|
129
|
-
export * from "./hooks/persistence/usePersistedState";
|
|
130
|
-
export * from "./hooks/persistence/useSearchParamSync";
|
|
131
128
|
export * from "./hooks/persistence/useStorageKey";
|
|
132
129
|
export * from "./hooks/persistence/useStripForeignIdentityHash";
|
|
133
130
|
export * from "./hooks/useBidirectionalScroll";
|
|
@@ -135,7 +132,6 @@ export * from "./hooks/useClickOutside";
|
|
|
135
132
|
export * from "./hooks/useContainerBreakpoints";
|
|
136
133
|
export * from "./hooks/useContinuousTimeout";
|
|
137
134
|
export * from "./hooks/useCopyToClipboard";
|
|
138
|
-
export * from "./hooks/useCursorUrlSync";
|
|
139
135
|
export * from "./hooks/useDebounce";
|
|
140
136
|
export * from "./hooks/useDevicePixelRatio";
|
|
141
137
|
export * from "./hooks/useElevatedReducer";
|