@assure-one/design-system 1.38.0 → 1.40.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 +63 -28
- package/codemods/README.md +76 -25
- package/codemods/lib/entry-map.mjs +315 -0
- package/codemods/lib/registry.mjs +28 -0
- package/codemods/lib/runner.mjs +8 -3
- package/codemods/run.mjs +27 -5
- package/codemods/transforms/cm-01-entry-imports.mjs +202 -0
- package/codemods/transforms/cm-15-dom-selectors.mjs +16 -9
- package/dist/brand-scope-BOHId6BY.d.ts +58 -0
- package/dist/brand-theme-ZYM867Jz.d.ts +59 -0
- package/dist/chunk-2MKLYYIY.js +3754 -0
- package/dist/chunk-2MKLYYIY.js.map +1 -0
- package/dist/chunk-6S2R2VWN.js +5527 -0
- package/dist/chunk-6S2R2VWN.js.map +1 -0
- package/dist/chunk-7U3ADR2Q.js +4336 -0
- package/dist/chunk-7U3ADR2Q.js.map +1 -0
- package/dist/chunk-CPOGSNXS.js +389 -0
- package/dist/chunk-CPOGSNXS.js.map +1 -0
- package/dist/chunk-EUHGZA6Q.js +313 -0
- package/dist/chunk-EUHGZA6Q.js.map +1 -0
- package/dist/chunk-KQBYTTAN.js +1829 -0
- package/dist/chunk-KQBYTTAN.js.map +1 -0
- package/dist/chunk-X76J4QSJ.js +164 -0
- package/dist/chunk-X76J4QSJ.js.map +1 -0
- package/dist/css/components.css +1 -1
- package/dist/css/legacy-aliases.css +2 -0
- package/dist/css/tailwind.css +2 -0
- package/dist/css/tokens.css +8 -6
- package/dist/data-table-view-ep8al7P9.d.ts +1132 -0
- package/dist/design-system-provider-CfD2g2lY.d.ts +142 -0
- package/dist/icons/index.d.ts +441 -3
- package/dist/icons/index.js +2 -1835
- package/dist/icons/index.js.map +1 -1
- package/dist/index.d.ts +404 -3847
- package/dist/index.js +8728 -24213
- package/dist/index.js.map +1 -1
- package/dist/legacy/index.d.ts +1464 -0
- package/dist/legacy/index.js +7 -0
- package/dist/legacy/index.js.map +1 -0
- package/dist/next/index.d.ts +2 -1
- package/dist/reference-BkDbrPIB.d.ts +310 -0
- package/dist/styles.css +2 -1
- package/dist/suite/index.d.ts +989 -0
- package/dist/suite/index.js +8 -0
- package/dist/suite/index.js.map +1 -0
- package/dist/{system-CyAVbHhM.d.ts → system-DfJIVBlJ.d.ts} +1 -310
- package/dist/testing/index.cjs +3 -2
- package/dist/testing/index.js +3 -2
- package/dist/tokens/index.d.ts +3 -61
- package/dist/tokens/index.js +3 -856
- package/dist/tokens/index.js.map +1 -1
- package/dist/{design-system-provider-B5MbvbRc.d.ts → types-Coqw8iaS.d.ts} +36 -142
- package/dist/vocab-DornLJbW.d.ts +76 -0
- package/docs/components.md +54 -52
- package/docs/components.registry.json +22 -8
- package/docs/for-ai-agents.md +12 -6
- package/package.json +17 -4
- package/public/brand/assure-audit-mark.svg +6 -0
- package/public/brand/assure-audit.svg +8 -0
- package/public/brand/assure-books-mark.svg +6 -0
- package/public/brand/assure-books.svg +8 -0
- package/public/brand/assure-pro-mark.svg +6 -0
- package/public/brand/assure-pro.svg +8 -0
- package/public/brand/assure-tax-mark.svg +6 -0
- package/public/brand/assure-tax.svg +8 -0
- package/public/brand/mycpe-teams-mark.svg +36 -0
- package/public/brand/mycpe-teams.svg +66 -0
- package/dist/index-vztxMSfl.d.ts +0 -512
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* codemods listed here can be run by id; the sequence is the plan's.
|
|
4
4
|
*/
|
|
5
5
|
export const CODEMODS = [
|
|
6
|
+
{ id: "CM-01", module: "../transforms/cm-01-entry-imports.mjs" },
|
|
6
7
|
{ id: "CM-05", module: "../transforms/cm-05-button-icon-slots.mjs" },
|
|
7
8
|
{ id: "CM-13", module: "../transforms/cm-13-tooltip-delay.mjs" },
|
|
8
9
|
{ id: "CM-07", module: "../transforms/cm-07-input-size.mjs" },
|
|
@@ -21,6 +22,33 @@ export const CODEMODS = [
|
|
|
21
22
|
{ id: "CM-20", module: "../transforms/cm-20-select-sentinels.mjs" },
|
|
22
23
|
];
|
|
23
24
|
|
|
25
|
+
/**
|
|
26
|
+
* `upgrade --to <version>`: the codemods a 1.x project runs before it installs
|
|
27
|
+
* that release, by the registry contracts the release removes or flips (plan
|
|
28
|
+
* §21.2). Applied in program order; transforming ones already in the ledger
|
|
29
|
+
* are skipped, report-only ones run every time.
|
|
30
|
+
*
|
|
31
|
+
* 1.40 (Wave 9, D11) — CM-01 root suite/legacy/accidental exports leave the
|
|
32
|
+
* root (C-ROOT-SUITE, C-ROOT-ACCIDENTAL) · CM-04 `variant="accent"` removed
|
|
33
|
+
* (C-BTN-ACCENT) · CM-19 Progress auto-colour off by default (C-PROGRESS) ·
|
|
34
|
+
* CM-12 Button `type="button"` default (C-BTN-TYPE) · CM-15 the 1.40 DOM
|
|
35
|
+
* freezes are lifted (C-DOM-*, C-TOAST-ARIALABEL) · CM-16 `styles.css`
|
|
36
|
+
* deprecated (C-CSS-STYLES). CM-02 is not in it: D5 keeps today's size scale.
|
|
37
|
+
*/
|
|
38
|
+
export const UPGRADE_TARGETS = {
|
|
39
|
+
"1.40": ["CM-01", "CM-04", "CM-19", "CM-12", "CM-15", "CM-16"],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Older names of a target: the 1.40 bundle was `--to 2` while Wave 9 was planned as 2.0. */
|
|
43
|
+
const UPGRADE_ALIASES = { 2: "1.40" };
|
|
44
|
+
|
|
45
|
+
/** The bundle for `--to 1.40`, `1.40.0`, `v1.40` (or the alias `--to 2`, `2.0`, `v2`…), in program order, or `null`. */
|
|
46
|
+
export function upgradeBundle(to) {
|
|
47
|
+
const [major, minor] = String(to).trim().replace(/^v/i, "").split(".");
|
|
48
|
+
const ids = UPGRADE_TARGETS[UPGRADE_ALIASES[major] ?? `${major}.${Number(minor)}`];
|
|
49
|
+
return ids ? CODEMODS.filter((c) => ids.includes(c.id)).map((c) => c.id) : null;
|
|
50
|
+
}
|
|
51
|
+
|
|
24
52
|
export const findCodemod = (id) =>
|
|
25
53
|
CODEMODS.find((c) => c.id.toLowerCase() === String(id).toLowerCase()) ?? null;
|
|
26
54
|
|
package/codemods/lib/runner.mjs
CHANGED
|
@@ -170,14 +170,19 @@ const countBy = (items, key) => {
|
|
|
170
170
|
return Object.fromEntries([...map].sort(([a], [b]) => (a < b ? -1 : 1)));
|
|
171
171
|
};
|
|
172
172
|
|
|
173
|
-
/**
|
|
174
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Codemods a project has not applied yet, in program order. With `only` (an
|
|
175
|
+
* `upgrade --to` bundle) the list is limited to those ids and keeps their
|
|
176
|
+
* report-only codemods, which never enter the ledger.
|
|
177
|
+
*/
|
|
178
|
+
export async function pendingCodemods(root, { only = null } = {}) {
|
|
175
179
|
const { CODEMODS } = await import("./registry.mjs");
|
|
176
180
|
const ids = appliedIds(readLedger(root));
|
|
177
181
|
const out = [];
|
|
178
182
|
for (const { id } of CODEMODS) {
|
|
183
|
+
if (only && !only.includes(id)) continue;
|
|
179
184
|
const mod = await loadCodemod(id);
|
|
180
185
|
out.push({ ...mod.meta, applied: ids.has(id) });
|
|
181
186
|
}
|
|
182
|
-
return out.filter((m) => m.class
|
|
187
|
+
return out.filter((m) => (m.class === "X" ? Boolean(only) : !m.applied));
|
|
183
188
|
}
|
package/codemods/run.mjs
CHANGED
|
@@ -7,9 +7,12 @@
|
|
|
7
7
|
* node node_modules/@assure-one/design-system/codemods/run.mjs list
|
|
8
8
|
* node node_modules/@assure-one/design-system/codemods/run.mjs CM-15 src --dry
|
|
9
9
|
* node node_modules/@assure-one/design-system/codemods/run.mjs upgrade --dry
|
|
10
|
+
* node node_modules/@assure-one/design-system/codemods/run.mjs upgrade --to 1.40 --dry
|
|
10
11
|
*
|
|
11
12
|
* Options
|
|
12
13
|
* --dry report only; write neither files nor the ledger
|
|
14
|
+
* --to <version> upgrade: only the codemods that release needs (`--to 1.40`;
|
|
15
|
+
* `--to 2` is an alias)
|
|
13
16
|
* --root <dir> project root (default: nearest package.json above cwd)
|
|
14
17
|
* --report <prefix> write <prefix>.json and <prefix>.md
|
|
15
18
|
* --json <file> write the JSON report
|
|
@@ -27,11 +30,12 @@ import { dirname, resolve } from "node:path";
|
|
|
27
30
|
import { findProjectRoot } from "./lib/files.mjs";
|
|
28
31
|
import { installedDsVersion } from "./lib/environment.mjs";
|
|
29
32
|
import { renderMarkdown, renderSummary } from "./lib/report.mjs";
|
|
30
|
-
import {
|
|
33
|
+
import { readLedger } from "./lib/ledger.mjs";
|
|
34
|
+
import { CODEMODS, UPGRADE_TARGETS, loadAll, upgradeBundle } from "./lib/registry.mjs";
|
|
31
35
|
import { Refused, pendingCodemods, runCodemod } from "./lib/runner.mjs";
|
|
32
36
|
|
|
33
37
|
const FLAGS = new Set(["--dry", "--print", "--quiet", "--fail-on-findings", "--help", "-h"]);
|
|
34
|
-
const VALUES = new Set(["--root", "--report", "--json", "--md"]);
|
|
38
|
+
const VALUES = new Set(["--root", "--report", "--json", "--md", "--to"]);
|
|
35
39
|
|
|
36
40
|
function parseArgs(argv) {
|
|
37
41
|
const options = {};
|
|
@@ -55,7 +59,7 @@ class UsageError extends Error {}
|
|
|
55
59
|
const usage = () => `Usage:
|
|
56
60
|
run.mjs list
|
|
57
61
|
run.mjs <CM-ID> [paths…] [--dry] [--root dir] [--report prefix] [--print] [--fail-on-findings]
|
|
58
|
-
run.mjs upgrade [paths…] [--dry]
|
|
62
|
+
run.mjs upgrade [paths…] [--to <version>] [--dry]
|
|
59
63
|
|
|
60
64
|
Codemods: ${CODEMODS.map((c) => c.id).join(", ")} (design system ${installedDsVersion()})
|
|
61
65
|
`;
|
|
@@ -112,7 +116,21 @@ async function main(argv) {
|
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
if (command === "upgrade") {
|
|
115
|
-
const
|
|
119
|
+
const only = options.to ? upgradeBundle(options.to) : null;
|
|
120
|
+
if (options.to && !only) {
|
|
121
|
+
throw new UsageError(
|
|
122
|
+
`--to ${options.to}: no upgrade bundle (known: ${Object.keys(UPGRADE_TARGETS).join(", ")}; 2 is an alias of 1.40)`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
const pending = await pendingCodemods(root, { only });
|
|
126
|
+
if (only) {
|
|
127
|
+
const recorded = new Set(readLedger(root).applied.map((e) => e.id));
|
|
128
|
+
const skipped = only.filter((id) => recorded.has(id));
|
|
129
|
+
say(
|
|
130
|
+
`Upgrade to ${options.to}: ${pending.map((m) => m.id).join(", ")}` +
|
|
131
|
+
(skipped.length ? `; already in the ledger: ${skipped.join(", ")}` : ""),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
116
134
|
if (!pending.length) {
|
|
117
135
|
say(`Nothing to apply in ${root}; the ledger is up to date.`);
|
|
118
136
|
return 0;
|
|
@@ -125,8 +143,12 @@ async function main(argv) {
|
|
|
125
143
|
dry: Boolean(options.dry),
|
|
126
144
|
});
|
|
127
145
|
say(renderSummary(report));
|
|
146
|
+
for (const warning of report.warnings) say(`! ${warning}`);
|
|
128
147
|
if (options.print) process.stdout.write(renderMarkdown(report));
|
|
129
|
-
|
|
148
|
+
const prefix = options.report && pending.length > 1 ? `${options.report}-${meta.id}` : null;
|
|
149
|
+
for (const path of writeReport(report, prefix ? { report: prefix } : options))
|
|
150
|
+
say(`Report: ${path}`);
|
|
151
|
+
if (report.ledger.recorded) say(`Recorded ${meta.id} in ${root}/${report.ledger.file}`);
|
|
130
152
|
if (options["fail-on-findings"] && report.summary.registered) code = 1;
|
|
131
153
|
}
|
|
132
154
|
return code;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CM-01 — root imports → `/suite` and `/legacy` (class R; plan §29 seq 1,
|
|
3
|
+
* registry `C-ROOT-SUITE`, lands with W8-02).
|
|
4
|
+
*
|
|
5
|
+
* W8-02 gave the shared Assure patterns their own entry and put the frozen and
|
|
6
|
+
* product-owned modules behind `/legacy`. Both are re-export barrels over the
|
|
7
|
+
* very same module instances the root exports, so moving an import is a pure
|
|
8
|
+
* path change: no duplicated context, no behaviour difference, nothing to test
|
|
9
|
+
* afterwards. The root keeps the names through 1.x and drops them in 2.0.
|
|
10
|
+
*
|
|
11
|
+
* ## What it rewrites
|
|
12
|
+
*
|
|
13
|
+
* ```tsx
|
|
14
|
+
* import { Button, AgreementViewer, TimeLogger } from "@assure-one/design-system";
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* becomes
|
|
18
|
+
*
|
|
19
|
+
* ```tsx
|
|
20
|
+
* import { Button } from "@assure-one/design-system";
|
|
21
|
+
* import { AgreementViewer } from "@assure-one/design-system/suite";
|
|
22
|
+
* import { TimeLogger } from "@assure-one/design-system/legacy";
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* A declaration whose names all move keeps its shape and only its module
|
|
26
|
+
* specifier changes. `import type` and per-specifier `type` prefixes, aliases
|
|
27
|
+
* (`X as Y`), quote style and a default import are preserved; the default
|
|
28
|
+
* import always stays on the root.
|
|
29
|
+
*
|
|
30
|
+
* Which entry owns which name comes from `codemods/lib/entry-map.mjs`, which
|
|
31
|
+
* `pnpm entry-map` generates from `architecture/entry-map.json` — so the
|
|
32
|
+
* installed copy of the codemod always knows the map of its own version.
|
|
33
|
+
*
|
|
34
|
+
* ## What it reports
|
|
35
|
+
*
|
|
36
|
+
* | rule | action | severity | why |
|
|
37
|
+
* | ------------------ | ------- | -------- | --------------------------------------------------------------------------------------- |
|
|
38
|
+
* | `moved-to-suite` | applied | low | shared Assure patterns now import from `/suite` |
|
|
39
|
+
* | `moved-to-legacy` | applied | low | frozen modules with no known consumer now import from `/legacy` |
|
|
40
|
+
* | `product-owned` | applied | medium | the import moved to `/legacy`, but the module is leaving for a product repository |
|
|
41
|
+
* | `namespace-import` | review | high | `import * as DS` cannot be split; the named members have to be imported one by one first |
|
|
42
|
+
*
|
|
43
|
+
* Imports already on `/suite` or `/legacy` are not touched (idempotency), and
|
|
44
|
+
* neither is a name the root still owns. Test files are skipped.
|
|
45
|
+
*/
|
|
46
|
+
import { ENTRY_BY_SYMBOL, ENTRY_SPECIFIER, PRODUCT_TARGET } from "../lib/entry-map.mjs";
|
|
47
|
+
import { applyEdits } from "../lib/jsx-edit.mjs";
|
|
48
|
+
import { analyseForms } from "../lib/forms.mjs";
|
|
49
|
+
|
|
50
|
+
export const meta = {
|
|
51
|
+
id: "CM-01",
|
|
52
|
+
title: "Root imports of suite, legacy and product-owned names → /suite and /legacy",
|
|
53
|
+
class: "R",
|
|
54
|
+
oneShot: false,
|
|
55
|
+
requires: { codemods: [], dsVersion: null },
|
|
56
|
+
parses: ["code"],
|
|
57
|
+
includeTests: false,
|
|
58
|
+
usesTypeScript: true,
|
|
59
|
+
usesPostcss: false,
|
|
60
|
+
registryIds: ["C-ROOT-SUITE"],
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const RULES = {
|
|
64
|
+
"moved-to-suite": { action: "applied", severity: "low" },
|
|
65
|
+
"moved-to-legacy": { action: "applied", severity: "low" },
|
|
66
|
+
"product-owned": { action: "applied", severity: "medium" },
|
|
67
|
+
"namespace-import": { action: "review", severity: "high" },
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const ROOT = "@assure-one/design-system";
|
|
71
|
+
|
|
72
|
+
/** The entry that owns an imported name, or `null` when the root keeps it. */
|
|
73
|
+
export const entryOf = (name) => ENTRY_BY_SYMBOL[name] ?? null;
|
|
74
|
+
|
|
75
|
+
const ruleFor = (name, entry) =>
|
|
76
|
+
PRODUCT_TARGET[name] ? "product-owned" : entry === "suite" ? "moved-to-suite" : "moved-to-legacy";
|
|
77
|
+
|
|
78
|
+
/** One `import …` statement built from the specifiers of the original one. */
|
|
79
|
+
function declaration({ typeOnly, defaultName, specifiers, quote, semi }, specifier) {
|
|
80
|
+
const clause = [defaultName, specifiers.length ? `{ ${specifiers.join(", ")} }` : null].filter(
|
|
81
|
+
Boolean,
|
|
82
|
+
);
|
|
83
|
+
return `import ${typeOnly ? "type " : ""}${clause.join(", ")} from ${quote}${specifier}${quote}${semi}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function transform(file, { ts }) {
|
|
87
|
+
const facts = analyseForms(ts, file.source, file.rel);
|
|
88
|
+
const sf = facts.sf;
|
|
89
|
+
const findings = [];
|
|
90
|
+
const edits = [];
|
|
91
|
+
|
|
92
|
+
for (const stmt of sf.statements) {
|
|
93
|
+
if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier)) continue;
|
|
94
|
+
if (stmt.moduleSpecifier.text !== ROOT) continue;
|
|
95
|
+
const clause = stmt.importClause;
|
|
96
|
+
if (!clause) continue;
|
|
97
|
+
|
|
98
|
+
const line = sf.getLineAndCharacterOfPosition(stmt.getStart(sf)).line + 1;
|
|
99
|
+
const base = { line, registryId: "C-ROOT-SUITE", component: null, gate: null };
|
|
100
|
+
|
|
101
|
+
// `import * as DS` — the members are reached through the namespace, so the
|
|
102
|
+
// import cannot be split by name. Only worth reporting when the file
|
|
103
|
+
// actually reads a name that moved.
|
|
104
|
+
if (clause.namedBindings && ts.isNamespaceImport(clause.namedBindings)) {
|
|
105
|
+
const ns = clause.namedBindings.name.text;
|
|
106
|
+
const used = new Set();
|
|
107
|
+
const visit = (node) => {
|
|
108
|
+
if (
|
|
109
|
+
ts.isPropertyAccessExpression(node) &&
|
|
110
|
+
ts.isIdentifier(node.expression) &&
|
|
111
|
+
node.expression.text === ns &&
|
|
112
|
+
entryOf(node.name.text)
|
|
113
|
+
) {
|
|
114
|
+
used.add(node.name.text);
|
|
115
|
+
}
|
|
116
|
+
if (ts.isQualifiedName(node) && ts.isIdentifier(node.left) && node.left.text === ns) {
|
|
117
|
+
if (entryOf(node.right.text)) used.add(node.right.text);
|
|
118
|
+
}
|
|
119
|
+
ts.forEachChild(node, visit);
|
|
120
|
+
};
|
|
121
|
+
ts.forEachChild(sf, visit);
|
|
122
|
+
if (used.size) {
|
|
123
|
+
findings.push({
|
|
124
|
+
...base,
|
|
125
|
+
rule: "namespace-import",
|
|
126
|
+
...RULES["namespace-import"],
|
|
127
|
+
match: `import * as ${ns}`,
|
|
128
|
+
detail: { namespace: ns, symbols: [...used].sort() },
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const named = clause.namedBindings;
|
|
135
|
+
if (!named || !ts.isNamedImports(named)) continue;
|
|
136
|
+
|
|
137
|
+
const buckets = { root: [], suite: [], legacy: [] };
|
|
138
|
+
const movedNames = { suite: [], legacy: [] };
|
|
139
|
+
for (const element of named.elements) {
|
|
140
|
+
const imported = (element.propertyName ?? element.name).text;
|
|
141
|
+
const entry = entryOf(imported);
|
|
142
|
+
const bucket = entry ?? "root";
|
|
143
|
+
buckets[bucket].push(element.getText(sf));
|
|
144
|
+
if (entry) movedNames[entry].push(imported);
|
|
145
|
+
}
|
|
146
|
+
if (!movedNames.suite.length && !movedNames.legacy.length) continue;
|
|
147
|
+
|
|
148
|
+
const text = stmt.getText(sf);
|
|
149
|
+
const shape = {
|
|
150
|
+
typeOnly: clause.isTypeOnly,
|
|
151
|
+
defaultName: clause.name ? clause.name.text : null,
|
|
152
|
+
quote: stmt.moduleSpecifier.getText(sf)[0],
|
|
153
|
+
semi: text.endsWith(";") ? ";" : "",
|
|
154
|
+
};
|
|
155
|
+
const rows = [];
|
|
156
|
+
if (buckets.root.length || shape.defaultName) {
|
|
157
|
+
rows.push(declaration({ ...shape, specifiers: buckets.root }, ROOT));
|
|
158
|
+
}
|
|
159
|
+
for (const entry of ["suite", "legacy"]) {
|
|
160
|
+
if (!buckets[entry].length) continue;
|
|
161
|
+
rows.push(
|
|
162
|
+
declaration(
|
|
163
|
+
{ ...shape, defaultName: null, specifiers: buckets[entry] },
|
|
164
|
+
ENTRY_SPECIFIER[entry],
|
|
165
|
+
),
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
edits.push({ pos: stmt.getStart(sf), end: stmt.getEnd(), text: rows.join("\n") });
|
|
169
|
+
|
|
170
|
+
for (const entry of ["suite", "legacy"]) {
|
|
171
|
+
const byRule = new Map();
|
|
172
|
+
for (const name of movedNames[entry]) {
|
|
173
|
+
const rule = ruleFor(name, entry);
|
|
174
|
+
if (!byRule.has(rule)) byRule.set(rule, []);
|
|
175
|
+
byRule.get(rule).push(name);
|
|
176
|
+
}
|
|
177
|
+
for (const [rule, names] of byRule) {
|
|
178
|
+
findings.push({
|
|
179
|
+
...base,
|
|
180
|
+
rule,
|
|
181
|
+
...RULES[rule],
|
|
182
|
+
match: `{ ${names.sort().join(", ")} }`,
|
|
183
|
+
detail: {
|
|
184
|
+
from: ROOT,
|
|
185
|
+
to: ENTRY_SPECIFIER[entry],
|
|
186
|
+
symbols: names,
|
|
187
|
+
...(rule === "product-owned"
|
|
188
|
+
? { owners: [...new Set(names.map((n) => PRODUCT_TARGET[n]))].sort() }
|
|
189
|
+
: {}),
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
output: edits.length ? applyEdits(file.source, edits) : file.source,
|
|
198
|
+
findings,
|
|
199
|
+
notTransformed: [],
|
|
200
|
+
parseErrors: facts.parseErrors,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
@@ -17,8 +17,9 @@
|
|
|
17
17
|
*
|
|
18
18
|
* Findings without a registry id are reported as `unregistered`: they are
|
|
19
19
|
* DOM coupling the registry does not track yet. Radix `data-state`-style
|
|
20
|
-
* attributes, ARIA roles
|
|
21
|
-
* (P-DATA-STATE, P-ROLES,
|
|
20
|
+
* attributes, ARIA roles, cmdk attributes and the `dl/dt/dd` of
|
|
21
|
+
* MetadataGrid/DataItem are permanent contracts (P-DATA-STATE, P-ROLES,
|
|
22
|
+
* C-CMDK-ATTR, P-NATIVE-TABLE) and are not reported.
|
|
22
23
|
*
|
|
23
24
|
* Never modifies files.
|
|
24
25
|
*/
|
|
@@ -185,16 +186,19 @@ const TAG_RULES = [
|
|
|
185
186
|
{ id: "C-DOM-12", components: /^ProgressBar$/, test: has(/role/) },
|
|
186
187
|
{ id: "C-DOM-13", components: /^(?:DataTableHeader|DataTableHead)$/, test: has(/button/) },
|
|
187
188
|
{ id: "C-DOM-15", components: /^DocumentRequestCard/, test: any },
|
|
188
|
-
{
|
|
189
|
-
id: "C-DOM-16",
|
|
190
|
-
components: /^(?:MetadataGrid|DataItem)$/,
|
|
191
|
-
test: has(/(?:^|[_>\s])(?:dt|dd|span)\b/),
|
|
192
|
-
},
|
|
189
|
+
{ id: "C-DOM-16", components: /^(?:MetadataGrid|DataItem)$/, test: has(/(?:^|[_>\s])span\b/) },
|
|
193
190
|
];
|
|
194
191
|
|
|
195
192
|
const ruleFor = (component, reach) =>
|
|
196
193
|
TAG_RULES.find((r) => r.components.test(component) && r.test(reach)) ?? null;
|
|
197
194
|
|
|
195
|
+
// `dl/dt/dd` inside MetadataGrid/DataItem are permanent (P-NATIVE-TABLE;
|
|
196
|
+
// only the `span` part of C-DOM-16 is frozen), so selectors on them are not
|
|
197
|
+
// reported.
|
|
198
|
+
const isPermanent = (component, reach) =>
|
|
199
|
+
/^(?:MetadataGrid|DataItem)$/.test(component) &&
|
|
200
|
+
/(?:^|[_>\s])(?:dl|dt|dd)\b/.test(reach.selector);
|
|
201
|
+
|
|
198
202
|
/**
|
|
199
203
|
* Whether an unmapped selector on a *wrapper* element is worth reporting: it
|
|
200
204
|
* must name an element, `*`, or a `data-`/`role` attribute. A wrapper selector
|
|
@@ -310,6 +314,7 @@ function codeFindings(file, ts) {
|
|
|
310
314
|
const reach = domReach(token);
|
|
311
315
|
if (!reach) continue;
|
|
312
316
|
const rule = ruleFor(usage.component, reach) ?? ruleFor(usage.base, reach);
|
|
317
|
+
if (!rule && isPermanent(usage.base, reach)) continue;
|
|
313
318
|
add({
|
|
314
319
|
line,
|
|
315
320
|
registryId: rule?.id ?? null,
|
|
@@ -332,6 +337,7 @@ function codeFindings(file, ts) {
|
|
|
332
337
|
.map((c) => ({ c, rule: ruleFor(c, reach) }))
|
|
333
338
|
.filter((x) => x.rule && x.rule.test !== any);
|
|
334
339
|
const hit = rules[0];
|
|
340
|
+
if (!hit && el.dsDescendants.some((c) => isPermanent(c, reach))) continue;
|
|
335
341
|
if (!hit && !namesStructure(reach.selector)) continue;
|
|
336
342
|
add({
|
|
337
343
|
line,
|
|
@@ -381,12 +387,13 @@ function codeFindings(file, ts) {
|
|
|
381
387
|
});
|
|
382
388
|
}
|
|
383
389
|
|
|
384
|
-
// 4. MetadataGrid / DataItem wrappers style
|
|
390
|
+
// 4. MetadataGrid / DataItem wrappers style their `span` from anywhere in
|
|
391
|
+
// the file (`dt/dd` are permanent, see `isPermanent`).
|
|
385
392
|
if (used.has("MetadataGrid") || used.has("DataItem")) {
|
|
386
393
|
const reported = new Set(findings.map((f) => `${f.line}${f.match}`));
|
|
387
394
|
for (const { token, line } of facts.fileTokens) {
|
|
388
395
|
const reach = domReach(token);
|
|
389
|
-
if (!reach || !/(?:^|[_>\s])
|
|
396
|
+
if (!reach || !/(?:^|[_>\s])span\b/.test(reach.selector)) continue;
|
|
390
397
|
if (reported.has(`${line}${token}`)) continue;
|
|
391
398
|
add({
|
|
392
399
|
line,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as React$1 from 'react';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Brand scope — ambient product theming.
|
|
6
|
+
*
|
|
7
|
+
* The `brand` color family (bg-brand, hover:bg-brand-hover, bg-brand-bg,
|
|
8
|
+
* border-brand-line, text-fg-on-brand, plus the accent/focus/primary
|
|
9
|
+
* interaction tokens) follows the nearest `[data-brand]` scope and defaults to
|
|
10
|
+
* Pro. Authors type the STATE only; the product comes from the scope. Set it
|
|
11
|
+
* once high in the tree — ideally on the same root as the `.dark` class
|
|
12
|
+
* (`<html>`/`<body>`) — so Radix-portalled surfaces inherit it through the DOM.
|
|
13
|
+
*
|
|
14
|
+
* <html data-brand="tax"> // whole shell renders teal
|
|
15
|
+
* <div {...brandScope("audit")}> // a subtree renders blue
|
|
16
|
+
*
|
|
17
|
+
* For the rarer case of multiple scopes on one page WITH portalled content
|
|
18
|
+
* (a Dialog/Popover that escapes its scoped subtree to document.body), wrap the
|
|
19
|
+
* region in <BrandScope> / <BrandScopeProvider> and have the portalled
|
|
20
|
+
* `*Content` re-stamp `data-brand={useBrandScope()}` on its own element.
|
|
21
|
+
*/
|
|
22
|
+
type BrandProduct = "pro" | "audit" | "books" | "tax";
|
|
23
|
+
/**
|
|
24
|
+
* `data-brand` attribute object to spread on any element (mirrors
|
|
25
|
+
* `serviceToneStyle`). This is the primary API for opting a subtree into a
|
|
26
|
+
* product without naming the product in a utility class.
|
|
27
|
+
*/
|
|
28
|
+
declare function brandScope(product: BrandProduct): {
|
|
29
|
+
"data-brand": BrandProduct;
|
|
30
|
+
};
|
|
31
|
+
/** Every product, in suite order — for switchers and story matrices. */
|
|
32
|
+
declare const BRAND_PRODUCTS: BrandProduct[];
|
|
33
|
+
/** Human label for a product, for switchers and headings. */
|
|
34
|
+
declare const brandLabel: Record<BrandProduct, string>;
|
|
35
|
+
/**
|
|
36
|
+
* Provides the active product to descendants so portalled surfaces can
|
|
37
|
+
* re-stamp `data-brand` on themselves via {@link useBrandScope}. Purely a
|
|
38
|
+
* structural carrier — the visual theming still comes from the `[data-brand]`
|
|
39
|
+
* attribute in the DOM, not from this context.
|
|
40
|
+
*/
|
|
41
|
+
declare function BrandScopeProvider({ product, children, }: {
|
|
42
|
+
product: BrandProduct;
|
|
43
|
+
children: React.ReactNode;
|
|
44
|
+
}): react_jsx_runtime.JSX.Element;
|
|
45
|
+
/** Read the ambient product from the nearest provider (defaults to Pro). */
|
|
46
|
+
declare function useBrandScope(): BrandProduct;
|
|
47
|
+
interface BrandScopeProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
48
|
+
/** The product this subtree renders as. */
|
|
49
|
+
product: BrandProduct;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Convenience wrapper that stamps `data-brand` on a `<div>` AND provides the
|
|
53
|
+
* product via context, so a portalled descendant can re-stamp itself. Use
|
|
54
|
+
* `brandScope()` directly when you want the attribute on your own element.
|
|
55
|
+
*/
|
|
56
|
+
declare const BrandScope: React$1.ForwardRefExoticComponent<BrandScopeProps & React$1.RefAttributes<HTMLDivElement>>;
|
|
57
|
+
|
|
58
|
+
export { BRAND_PRODUCTS as B, type BrandProduct as a, brandLabel as b, BrandScope as c, type BrandScopeProps as d, BrandScopeProvider as e, brandScope as f, useBrandScope as u };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The seed a host supplies per tenant.
|
|
3
|
+
*
|
|
4
|
+
* @experimental Gated by decision D7.
|
|
5
|
+
*/
|
|
6
|
+
interface BrandThemeSeed {
|
|
7
|
+
/**
|
|
8
|
+
* The brand fill, as `#rgb` or `#rrggbb`. Drives the `action.brand` family,
|
|
9
|
+
* the focus ring and the brand gradient.
|
|
10
|
+
*/
|
|
11
|
+
brand: string;
|
|
12
|
+
/**
|
|
13
|
+
* An optional second hue for the `action.accent` family, as `#rgb` or
|
|
14
|
+
* `#rrggbb`. `undefined` or `null` means "the same as `brand`", which is what
|
|
15
|
+
* the built-in `[data-ds-brand]` scopes do.
|
|
16
|
+
*/
|
|
17
|
+
accent?: string | null | undefined;
|
|
18
|
+
}
|
|
19
|
+
/** Every custom property `createBrandTheme()` sets, in output order. */
|
|
20
|
+
declare const BRAND_THEME_PROPERTIES: readonly ["--ds-color-action-brand-bg", "--ds-color-action-brand-bg-hover", "--ds-color-action-brand-bg-active", "--ds-color-action-brand-bg-disabled", "--ds-color-action-brand-subtle-bg", "--ds-color-action-brand-subtle-bg-hover", "--ds-color-action-brand-subtle-bg-active", "--ds-color-action-brand-border", "--ds-color-action-brand-ring", "--ds-color-action-brand-fg", "--ds-color-action-accent-bg", "--ds-color-action-accent-bg-strong", "--ds-color-action-accent-bg-hover", "--ds-color-action-accent-bg-active", "--ds-color-action-accent-subtle-bg", "--ds-color-action-accent-ring", "--ds-color-action-accent-fg", "--ds-color-border-focus", "--ds-focus-ring-color", "--ds-focus-ring-border", "--ds-gradient-brand", "--ds-compat-color-brand-soft", "--ds-compat-color-brand-soft-foreground"];
|
|
21
|
+
/** One of the custom properties `createBrandTheme()` sets. */
|
|
22
|
+
type BrandThemeProperty = (typeof BRAND_THEME_PROPERTIES)[number];
|
|
23
|
+
/**
|
|
24
|
+
* The style object `createBrandTheme()` returns: every property of
|
|
25
|
+
* {@link BRAND_THEME_PROPERTIES} with a CSS value.
|
|
26
|
+
*
|
|
27
|
+
* React types `style` as `CSSProperties`, which has no index signature for
|
|
28
|
+
* custom properties, so spread it or cast it at the call site:
|
|
29
|
+
* `style={{ ...createBrandTheme(seed) }}`.
|
|
30
|
+
*
|
|
31
|
+
* @experimental Gated by decision D7.
|
|
32
|
+
*/
|
|
33
|
+
type BrandTheme = {
|
|
34
|
+
readonly [P in BrandThemeProperty]: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Turn a tenant's colours into the design system's brand and accent tokens.
|
|
38
|
+
*
|
|
39
|
+
* ```tsx
|
|
40
|
+
* const theme = createBrandTheme({ brand: firm.primaryColor, accent: firm.accentColor });
|
|
41
|
+
* return <div style={{ ...theme }}>{children}</div>;
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* Apply it to the element that scopes the brand — `<body>` for a whole-tenant
|
|
45
|
+
* theme, a wrapper for a preview. The properties are the same ones a built-in
|
|
46
|
+
* `[data-ds-brand]` scope declares, so anything reading them (every design
|
|
47
|
+
* system component, and any application CSS reading `--ds-*`) follows.
|
|
48
|
+
*
|
|
49
|
+
* @param seed - `brand`, and optionally `accent` (decision D7).
|
|
50
|
+
* @returns A frozen object of custom property names to CSS values.
|
|
51
|
+
* @throws TypeError when the seed is not an object, carries an unknown key, or
|
|
52
|
+
* a colour is missing or not `#rgb`/`#rrggbb`.
|
|
53
|
+
*
|
|
54
|
+
* @experimental Gated by decision D7. The property set may grow, and the seed
|
|
55
|
+
* shape may change, until D7 is resolved.
|
|
56
|
+
*/
|
|
57
|
+
declare function createBrandTheme(seed: BrandThemeSeed): BrandTheme;
|
|
58
|
+
|
|
59
|
+
export { BRAND_THEME_PROPERTIES as B, type BrandTheme as a, type BrandThemeProperty as b, type BrandThemeSeed as c, createBrandTheme as d };
|