@naturaldesign/dev 0.0.12 → 0.0.13
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/dist/cli.mjs +120 -20
- package/dist/index.mjs +68 -5
- package/dist/loader.cjs +32 -4
- package/dist/types/component-index/babel-props.d.ts +2 -1
- package/dist/types/component-index/scan.d.ts +6 -0
- package/dist/types/component-index/types.d.ts +5 -0
- package/dist/types/providers.d.ts +2 -0
- package/dist/types/registry.d.ts +3 -1
- package/dist/types/styles.d.ts +4 -0
- package/dist/vite.mjs +283 -15
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -15114,8 +15114,20 @@ function classifyType(type, scope, depth = 0) {
|
|
|
15114
15114
|
return classifyType(local.typeAnnotation, scope, depth + 1);
|
|
15115
15115
|
if (lastSegment(name) === "PropType")
|
|
15116
15116
|
return classifyType(typeArguments(type)[0], scope, depth + 1);
|
|
15117
|
-
|
|
15118
|
-
|
|
15117
|
+
if (/^(ReadonlyArray|Array|Set)$/.test(lastSegment(name)))
|
|
15118
|
+
return { type: "other", shape: "array" };
|
|
15119
|
+
if (/^(Function|Dispatch|SetStateAction|MouseEventHandler|ChangeEventHandler|EventHandler)$/.test(lastSegment(name)) || /Handler$/.test(lastSegment(name)))
|
|
15120
|
+
return { type: "other", shape: "function" };
|
|
15121
|
+
return { type: "other", shape: "object" };
|
|
15122
|
+
}
|
|
15123
|
+
case "TSFunctionType":
|
|
15124
|
+
return { type: "other", shape: "function" };
|
|
15125
|
+
case "TSArrayType":
|
|
15126
|
+
case "TSTupleType":
|
|
15127
|
+
return { type: "other", shape: "array" };
|
|
15128
|
+
case "TSTypeLiteral":
|
|
15129
|
+
case "TSMappedType":
|
|
15130
|
+
return { type: "other", shape: "object" };
|
|
15119
15131
|
case "TSUnionType": {
|
|
15120
15132
|
const kinds = type.types.filter((member) => member.type !== "TSUndefinedKeyword" && member.type !== "TSNullKeyword" && member.type !== "TSVoidKeyword").map((member) => classifyType(member, scope, depth + 1));
|
|
15121
15133
|
return mergeKinds(kinds);
|
|
@@ -15159,7 +15171,7 @@ function membersToProps(members, scope, optionalAll = false) {
|
|
|
15159
15171
|
const kind = name === "children" ? { type: "slot" } : classifyType(member.typeAnnotation?.typeAnnotation, scope);
|
|
15160
15172
|
out.push({ name, ...kind, optional: optionalAll || !!member.optional });
|
|
15161
15173
|
} else if (member.type === "TSMethodSignature") {
|
|
15162
|
-
out.push({ name, type: "other", optional: optionalAll || !!member.optional });
|
|
15174
|
+
out.push({ name, type: "other", shape: "function", optional: optionalAll || !!member.optional });
|
|
15163
15175
|
}
|
|
15164
15176
|
}
|
|
15165
15177
|
return out;
|
|
@@ -15546,6 +15558,21 @@ function patternDefaults2(ts, fn) {
|
|
|
15546
15558
|
return out;
|
|
15547
15559
|
}
|
|
15548
15560
|
var SLOT_TEXT = /\b(ReactNode|ReactElement|ReactChild|ReactPortal|JSX\.Element)\b/;
|
|
15561
|
+
function shapeOf(ts, checker, parts) {
|
|
15562
|
+
if (parts.length && parts.every((part) => part.getCallSignatures().length > 0))
|
|
15563
|
+
return "function";
|
|
15564
|
+
const isArray = (part) => {
|
|
15565
|
+
const check = checker;
|
|
15566
|
+
if (check.isArrayType?.(part) || check.isTupleType?.(part))
|
|
15567
|
+
return true;
|
|
15568
|
+
return /^(Array|ReadonlyArray)$/.test(part.getSymbol()?.getName() ?? "");
|
|
15569
|
+
};
|
|
15570
|
+
if (parts.length && parts.every(isArray))
|
|
15571
|
+
return "array";
|
|
15572
|
+
if (parts.length && parts.every((part) => part.flags & ts.TypeFlags.Object))
|
|
15573
|
+
return "object";
|
|
15574
|
+
return;
|
|
15575
|
+
}
|
|
15549
15576
|
function classify(ts, checker, name, type, declared) {
|
|
15550
15577
|
if (name === "children" || SLOT_TEXT.test(declared) || SLOT_TEXT.test(checker.typeToString(type)))
|
|
15551
15578
|
return { type: "slot" };
|
|
@@ -15560,7 +15587,8 @@ function classify(ts, checker, name, type, declared) {
|
|
|
15560
15587
|
return { type: "number" };
|
|
15561
15588
|
if (parts.every((part) => part.flags & ts.TypeFlags.StringLike))
|
|
15562
15589
|
return { type: "text" };
|
|
15563
|
-
|
|
15590
|
+
const shape = shapeOf(ts, checker, parts);
|
|
15591
|
+
return shape ? { type: "other", shape } : { type: "other" };
|
|
15564
15592
|
}
|
|
15565
15593
|
function propsOf(ts, checker, propsType, at, fn, isOwn) {
|
|
15566
15594
|
const defaults = patternDefaults2(ts, fn);
|
|
@@ -15888,11 +15916,46 @@ function indexFiles(root, files, options = {}) {
|
|
|
15888
15916
|
}
|
|
15889
15917
|
return out;
|
|
15890
15918
|
}
|
|
15919
|
+
function withoutDuplicates(entries) {
|
|
15920
|
+
const kept = new Map;
|
|
15921
|
+
for (const entry2 of entries) {
|
|
15922
|
+
const key = `${entry2.file}#${entry2.name}`;
|
|
15923
|
+
const seen = kept.get(key);
|
|
15924
|
+
if (!seen || seen.exportName === "default" && entry2.exportName !== "default")
|
|
15925
|
+
kept.set(key, entry2);
|
|
15926
|
+
}
|
|
15927
|
+
return entries.filter((entry2) => kept.get(`${entry2.file}#${entry2.name}`) === entry2);
|
|
15928
|
+
}
|
|
15929
|
+
function words(name) {
|
|
15930
|
+
return name.match(/[A-Z][a-z0-9]*|[a-z0-9]+/g) ?? [name];
|
|
15931
|
+
}
|
|
15932
|
+
function markParts(entries) {
|
|
15933
|
+
const byFile = new Map;
|
|
15934
|
+
for (const entry2 of entries)
|
|
15935
|
+
byFile.set(entry2.file, [...byFile.get(entry2.file) ?? [], entry2]);
|
|
15936
|
+
for (const [file, inFile] of byFile) {
|
|
15937
|
+
const names = [...new Set(inFile.map((entry2) => entry2.name))];
|
|
15938
|
+
if (names.length < 2)
|
|
15939
|
+
continue;
|
|
15940
|
+
const stem = path3.basename(file).replace(/\.[^.]+$/, "");
|
|
15941
|
+
const base = (stem === "index" ? path3.basename(path3.dirname(file)) : stem).split(/[^A-Za-z0-9]+/).filter(Boolean).map((word) => word[0].toUpperCase() + word.slice(1)).join("");
|
|
15942
|
+
const root = names.find((name) => name === base) ?? names[0];
|
|
15943
|
+
const head = words(root)[0];
|
|
15944
|
+
for (const entry2 of inFile) {
|
|
15945
|
+
if (entry2.name === root)
|
|
15946
|
+
continue;
|
|
15947
|
+
const first = words(entry2.name)[0];
|
|
15948
|
+
if (first.startsWith(head) || head.startsWith(first))
|
|
15949
|
+
entry2.part = true;
|
|
15950
|
+
}
|
|
15951
|
+
}
|
|
15952
|
+
return entries;
|
|
15953
|
+
}
|
|
15891
15954
|
function indexComponents(projectRoot, options = {}) {
|
|
15892
15955
|
const root = path3.resolve(projectRoot);
|
|
15893
15956
|
const files = componentFiles(root, options.folders);
|
|
15894
15957
|
const byFile = indexFiles(root, files, options);
|
|
15895
|
-
const own = files.flatMap((file) => byFile.get(file) ?? []);
|
|
15958
|
+
const own = markParts(withoutDuplicates(files.flatMap((file) => byFile.get(file) ?? [])));
|
|
15896
15959
|
const ts = options.packages?.length && options.typescript !== false ? tsIndexerFor(root) : null;
|
|
15897
15960
|
const fromPackages = ts ? options.packages.flatMap((name) => {
|
|
15898
15961
|
try {
|
|
@@ -17942,22 +18005,30 @@ ${indent}`);
|
|
|
17942
18005
|
function init(rootArg, options = {}) {
|
|
17943
18006
|
const root = path10.resolve(rootArg);
|
|
17944
18007
|
const steps = [];
|
|
17945
|
-
|
|
17946
|
-
|
|
18008
|
+
const step = (line) => {
|
|
18009
|
+
steps.push(line);
|
|
18010
|
+
options.onStep?.(line);
|
|
18011
|
+
};
|
|
18012
|
+
if (!fs.existsSync(path10.join(root, "package.json"))) {
|
|
18013
|
+
step(`No package.json in ${root}`);
|
|
18014
|
+
return { ok: false, steps };
|
|
18015
|
+
}
|
|
17947
18016
|
const manager = packageManager(root);
|
|
17948
18017
|
if (listsPackage(root)) {
|
|
17949
|
-
|
|
18018
|
+
step(`${PACKAGE} is already in package.json`);
|
|
17950
18019
|
} else if (options.install !== false) {
|
|
17951
18020
|
const args = manager === "npm" ? ["install", "--save-dev", PACKAGE] : ["add", "-D", PACKAGE];
|
|
17952
|
-
|
|
18021
|
+
step(`Running ${manager} ${args.join(" ")}`);
|
|
17953
18022
|
const run = spawnSync(manager, args, { cwd: root, stdio: ["ignore", "inherit", "inherit"] });
|
|
17954
|
-
if (run.status !== 0)
|
|
17955
|
-
|
|
18023
|
+
if (run.status !== 0) {
|
|
18024
|
+
step(`${manager} could not install ${PACKAGE}`);
|
|
18025
|
+
return { ok: false, steps };
|
|
18026
|
+
}
|
|
17956
18027
|
}
|
|
17957
18028
|
const next = firstExisting(root, NEXT_CONFIGS);
|
|
17958
18029
|
const vite = next ? null : firstExisting(root, VITE_CONFIGS);
|
|
17959
18030
|
if (!next && !vite) {
|
|
17960
|
-
|
|
18031
|
+
step("No next.config or vite.config found. Add the plugin by hand: see the README");
|
|
17961
18032
|
return { ok: false, steps };
|
|
17962
18033
|
}
|
|
17963
18034
|
const name = next ?? vite;
|
|
@@ -17965,19 +18036,48 @@ function init(rootArg, options = {}) {
|
|
|
17965
18036
|
const source = fs.readFileSync(file, "utf8");
|
|
17966
18037
|
const wired = next ? wireNext(source) : wireVite(source, /\.cjs$/.test(name) || name.endsWith(".js") && /module\.exports\s*=/.test(source));
|
|
17967
18038
|
if (wired === null) {
|
|
17968
|
-
|
|
18039
|
+
step(`Could not read the config in ${name}. Add the plugin by hand: see the README`);
|
|
17969
18040
|
return { ok: false, steps };
|
|
17970
18041
|
}
|
|
17971
18042
|
if (wired === source) {
|
|
17972
|
-
|
|
18043
|
+
step(`${name} already uses the plugin`);
|
|
17973
18044
|
} else {
|
|
17974
18045
|
fs.writeFileSync(file, wired);
|
|
17975
|
-
|
|
18046
|
+
step(`Added the plugin to ${name}`);
|
|
17976
18047
|
}
|
|
17977
|
-
|
|
18048
|
+
step("Restart the dev server to see the components in Natural Design");
|
|
17978
18049
|
return { ok: true, steps };
|
|
17979
18050
|
}
|
|
17980
18051
|
|
|
18052
|
+
// src/logo.ts
|
|
18053
|
+
var LOGO = `
|
|
18054
|
+
⣤⣶⣦⡄ ⣀⣀ ⣴⣶⡆
|
|
18055
|
+
⢠⣴⣶⣤⠛⢿⠿⢣⣴⣶⣤ ⣿⣿ ⣿⣿⡇
|
|
18056
|
+
⠘⠻⠿⠛⣤⣶⣦⡜⠻⠿⠟ ⣶⣶⣠⣶⣿⣿⣷⣆ ⣠⣶⣿⠿⢿⣷⣦⡀⢰⣾⣿⣿⣶⣶ ⣶⣶ ⣶⣶ ⣶⣶⣠⣶⣿⡆⢀⣴⣾⡿⠿⣿⣶⣄ ⣿⣿⡇
|
|
18057
|
+
⢠⣴⣶⣤⠛⢿⠟⢣⣴⣶⣤ ⣿⣿⡏ ⠈⣿⣿⡆ ⠛⠛ ⣹⣿⡇ ⣿⣿ ⣿⣿ ⣿⣿ ⣿⣿⡟⠉⠁ ⠚⠛⠃ ⢈⣿⣿ ⣿⣿⡇
|
|
18058
|
+
⣤⣶⣦⡜⠿⡿⠟⣤⣶⣦⡜⠻⡿⠟⣤⣶⣦⡄ ⣿⣿ ⣿⣿⡇⢀⣤⣶⣾⠿⠿⢻⣿⡇ ⣿⣿ ⣿⣿ ⣿⣿ ⣿⣿ ⣠⣶⣶⠿⠿⠛⣿⣿ ⣿⣿⡇
|
|
18059
|
+
⠛⢿⠟⢣⣴⣶⣤⠛⢿⠿⢣⣴⣶⣤⠛⢿⠿⠃ ⣿⣿ ⣿⣿⡇⢸⣿⣏ ⣼⣿⡇ ⣿⣿⡀ ⣿⣿⣄ ⣰⣿⣿ ⣿⣿ ⠰⣿⣿⡁ ⣠⣿⣿ ⣿⣿⡇
|
|
18060
|
+
⣤⣶⣦⡜⠿⡿⠟ ⣰⡄⠘⠻⡿⠟⣤⣶⣦⡄ ⠿⠿ ⠿⠿⠇⠈⠻⢿⣿⡾⠟⠹⠿⠧ ⠻⠿⠿⠿ ⠙⠿⣿⣿⠿⠋⠿⠿ ⠿⠿ ⠙⠿⣿⣶⠿⠋⠿⠿⠄ ⠿⠿⠇
|
|
18061
|
+
⠻⢿⠿⢣⣴⣶⣤⠘⠿⠿⢠⣴⣶⣤⠛⢿⠿⠃ ⣿⣿⡆ ⣿⣿⡆
|
|
18062
|
+
⠘⠿⡿⠟⣀ ⢀⡜⠿⡿⠟ ⣀⣀⡀ ⣿⣿⡇ ⣀⣀⣀ ⣀⣀⣀⡀ ⣉⣉ ⢀⣀⣀ ⣀⡀ ⢀⣀⡀ ⣀⣀⣀
|
|
18063
|
+
⣿⣿⣿⡇ ⢀⣴⣿⡿⠿⠿⣷⣿⣿⡇ ⢀⣴⣿⠿⠛⠿⣿⣦⡀ ⣴⣿⠟⠛⠻⣿⣷⡀ ⣿⣿ ⢠⣾⣿⠿⠿⢿⣾⣿⣿ ⢸⣿⣷⡿⠿⠿⣿⣷⡄
|
|
18064
|
+
⣿⣿⣿⡇ ⣼⣿⡏ ⠈⣿⣿⡇ ⣿⣿⣃⣀⣀⣀⣸⣿⣷ ⣿⣿⣦⣤⣀⣈⠉⠁ ⣿⣿ ⣿⣿⠃ ⢹⣿⣿ ⢸⣿⡏ ⢹⣿⡇
|
|
18065
|
+
⣿⣿⣿⡇ ⣿⣿⡇ ⣿⣿⡇ ⣿⣿⡛⠛⠛⠛⠛⠛⠛ ⠈⠙⠛⠻⠿⣿⣿⣆ ⣿⣿ ⣿⣿⡀ ⣸⣿⣿ ⢸⣿⡇ ⢸⣿⡇
|
|
18066
|
+
⣿⣿⣿⡇ ⠸⣿⣿⣄⣀⣠⣼⣿⣿⡇ ⠹⣿⣷⣄⣀⣀⣴⣶⡖ ⠸⣿⣷⣄⣀⣀⣸⣿⡿ ⣿⣿ ⠙⢿⣿⣶⣤⣶⢿⣿⣿ ⢸⣿⡇ ⢸⣿⡇
|
|
18067
|
+
⠿⣿⡿⠇ ⠈⠛⠿⠿⠟⠁⠛⠛⠃ ⠈⠛⠻⠿⠿⠛⠉ ⠈⠛⠻⠿⠿⠟⠋ ⠛⠛ ⣀⣀⡉⠉⠉⠁⢸⣿⡟ ⠘⠛⠃ ⠘⠛⠃
|
|
18068
|
+
⠙⢿⣷⣶⣶⣶⣿⠟⠁
|
|
18069
|
+
⠉⠉⠁
|
|
18070
|
+
`.slice(1, -1);
|
|
18071
|
+
var LOGO_WIDTH = 77;
|
|
18072
|
+
function printLogo(stream = process.stdout) {
|
|
18073
|
+
if (!stream.isTTY || (stream.columns ?? 0) < LOGO_WIDTH + 2 || process.env.CI)
|
|
18074
|
+
return;
|
|
18075
|
+
stream.write(`
|
|
18076
|
+
${LOGO}
|
|
18077
|
+
|
|
18078
|
+
`);
|
|
18079
|
+
}
|
|
18080
|
+
|
|
17981
18081
|
// src/cli.ts
|
|
17982
18082
|
function parseArgs(argv) {
|
|
17983
18083
|
const positional = [];
|
|
@@ -18010,10 +18110,10 @@ async function main(argv) {
|
|
|
18010
18110
|
const [command, ...rest] = argv;
|
|
18011
18111
|
const { positional, flags } = parseArgs(rest);
|
|
18012
18112
|
if (command === "init") {
|
|
18013
|
-
const result = init(positional[0] ?? ".", { install: !flags.has("no-install") }
|
|
18014
|
-
|
|
18015
|
-
|
|
18016
|
-
|
|
18113
|
+
const result = init(positional[0] ?? ".", { install: !flags.has("no-install"), onStep: (line) => process.stdout.write(`${line}
|
|
18114
|
+
`) });
|
|
18115
|
+
if (result.ok)
|
|
18116
|
+
printLogo();
|
|
18017
18117
|
return result.ok ? 0 : 1;
|
|
18018
18118
|
}
|
|
18019
18119
|
console.log = console.info = (...args) => console.error(...args);
|
package/dist/index.mjs
CHANGED
|
@@ -16349,8 +16349,20 @@ function classifyType(type, scope, depth = 0) {
|
|
|
16349
16349
|
return classifyType(local.typeAnnotation, scope, depth + 1);
|
|
16350
16350
|
if (lastSegment(name) === "PropType")
|
|
16351
16351
|
return classifyType(typeArguments(type)[0], scope, depth + 1);
|
|
16352
|
-
|
|
16353
|
-
|
|
16352
|
+
if (/^(ReadonlyArray|Array|Set)$/.test(lastSegment(name)))
|
|
16353
|
+
return { type: "other", shape: "array" };
|
|
16354
|
+
if (/^(Function|Dispatch|SetStateAction|MouseEventHandler|ChangeEventHandler|EventHandler)$/.test(lastSegment(name)) || /Handler$/.test(lastSegment(name)))
|
|
16355
|
+
return { type: "other", shape: "function" };
|
|
16356
|
+
return { type: "other", shape: "object" };
|
|
16357
|
+
}
|
|
16358
|
+
case "TSFunctionType":
|
|
16359
|
+
return { type: "other", shape: "function" };
|
|
16360
|
+
case "TSArrayType":
|
|
16361
|
+
case "TSTupleType":
|
|
16362
|
+
return { type: "other", shape: "array" };
|
|
16363
|
+
case "TSTypeLiteral":
|
|
16364
|
+
case "TSMappedType":
|
|
16365
|
+
return { type: "other", shape: "object" };
|
|
16354
16366
|
case "TSUnionType": {
|
|
16355
16367
|
const kinds = type.types.filter((member) => member.type !== "TSUndefinedKeyword" && member.type !== "TSNullKeyword" && member.type !== "TSVoidKeyword").map((member) => classifyType(member, scope, depth + 1));
|
|
16356
16368
|
return mergeKinds(kinds);
|
|
@@ -16394,7 +16406,7 @@ function membersToProps(members, scope, optionalAll = false) {
|
|
|
16394
16406
|
const kind = name === "children" ? { type: "slot" } : classifyType(member.typeAnnotation?.typeAnnotation, scope);
|
|
16395
16407
|
out.push({ name, ...kind, optional: optionalAll || !!member.optional });
|
|
16396
16408
|
} else if (member.type === "TSMethodSignature") {
|
|
16397
|
-
out.push({ name, type: "other", optional: optionalAll || !!member.optional });
|
|
16409
|
+
out.push({ name, type: "other", shape: "function", optional: optionalAll || !!member.optional });
|
|
16398
16410
|
}
|
|
16399
16411
|
}
|
|
16400
16412
|
return out;
|
|
@@ -16781,6 +16793,21 @@ function patternDefaults2(ts, fn) {
|
|
|
16781
16793
|
return out;
|
|
16782
16794
|
}
|
|
16783
16795
|
var SLOT_TEXT = /\b(ReactNode|ReactElement|ReactChild|ReactPortal|JSX\.Element)\b/;
|
|
16796
|
+
function shapeOf(ts, checker, parts) {
|
|
16797
|
+
if (parts.length && parts.every((part) => part.getCallSignatures().length > 0))
|
|
16798
|
+
return "function";
|
|
16799
|
+
const isArray = (part) => {
|
|
16800
|
+
const check = checker;
|
|
16801
|
+
if (check.isArrayType?.(part) || check.isTupleType?.(part))
|
|
16802
|
+
return true;
|
|
16803
|
+
return /^(Array|ReadonlyArray)$/.test(part.getSymbol()?.getName() ?? "");
|
|
16804
|
+
};
|
|
16805
|
+
if (parts.length && parts.every(isArray))
|
|
16806
|
+
return "array";
|
|
16807
|
+
if (parts.length && parts.every((part) => part.flags & ts.TypeFlags.Object))
|
|
16808
|
+
return "object";
|
|
16809
|
+
return;
|
|
16810
|
+
}
|
|
16784
16811
|
function classify(ts, checker, name, type, declared) {
|
|
16785
16812
|
if (name === "children" || SLOT_TEXT.test(declared) || SLOT_TEXT.test(checker.typeToString(type)))
|
|
16786
16813
|
return { type: "slot" };
|
|
@@ -16795,7 +16822,8 @@ function classify(ts, checker, name, type, declared) {
|
|
|
16795
16822
|
return { type: "number" };
|
|
16796
16823
|
if (parts.every((part) => part.flags & ts.TypeFlags.StringLike))
|
|
16797
16824
|
return { type: "text" };
|
|
16798
|
-
|
|
16825
|
+
const shape = shapeOf(ts, checker, parts);
|
|
16826
|
+
return shape ? { type: "other", shape } : { type: "other" };
|
|
16799
16827
|
}
|
|
16800
16828
|
function propsOf(ts, checker, propsType, at, fn, isOwn) {
|
|
16801
16829
|
const defaults = patternDefaults2(ts, fn);
|
|
@@ -17123,11 +17151,46 @@ function indexFiles(root, files, options = {}) {
|
|
|
17123
17151
|
}
|
|
17124
17152
|
return out;
|
|
17125
17153
|
}
|
|
17154
|
+
function withoutDuplicates(entries) {
|
|
17155
|
+
const kept = new Map;
|
|
17156
|
+
for (const entry2 of entries) {
|
|
17157
|
+
const key = `${entry2.file}#${entry2.name}`;
|
|
17158
|
+
const seen = kept.get(key);
|
|
17159
|
+
if (!seen || seen.exportName === "default" && entry2.exportName !== "default")
|
|
17160
|
+
kept.set(key, entry2);
|
|
17161
|
+
}
|
|
17162
|
+
return entries.filter((entry2) => kept.get(`${entry2.file}#${entry2.name}`) === entry2);
|
|
17163
|
+
}
|
|
17164
|
+
function words(name) {
|
|
17165
|
+
return name.match(/[A-Z][a-z0-9]*|[a-z0-9]+/g) ?? [name];
|
|
17166
|
+
}
|
|
17167
|
+
function markParts(entries) {
|
|
17168
|
+
const byFile = new Map;
|
|
17169
|
+
for (const entry2 of entries)
|
|
17170
|
+
byFile.set(entry2.file, [...byFile.get(entry2.file) ?? [], entry2]);
|
|
17171
|
+
for (const [file, inFile] of byFile) {
|
|
17172
|
+
const names = [...new Set(inFile.map((entry2) => entry2.name))];
|
|
17173
|
+
if (names.length < 2)
|
|
17174
|
+
continue;
|
|
17175
|
+
const stem = path3.basename(file).replace(/\.[^.]+$/, "");
|
|
17176
|
+
const base = (stem === "index" ? path3.basename(path3.dirname(file)) : stem).split(/[^A-Za-z0-9]+/).filter(Boolean).map((word) => word[0].toUpperCase() + word.slice(1)).join("");
|
|
17177
|
+
const root = names.find((name) => name === base) ?? names[0];
|
|
17178
|
+
const head = words(root)[0];
|
|
17179
|
+
for (const entry2 of inFile) {
|
|
17180
|
+
if (entry2.name === root)
|
|
17181
|
+
continue;
|
|
17182
|
+
const first = words(entry2.name)[0];
|
|
17183
|
+
if (first.startsWith(head) || head.startsWith(first))
|
|
17184
|
+
entry2.part = true;
|
|
17185
|
+
}
|
|
17186
|
+
}
|
|
17187
|
+
return entries;
|
|
17188
|
+
}
|
|
17126
17189
|
function indexComponents(projectRoot, options = {}) {
|
|
17127
17190
|
const root = path3.resolve(projectRoot);
|
|
17128
17191
|
const files = componentFiles(root, options.folders);
|
|
17129
17192
|
const byFile = indexFiles(root, files, options);
|
|
17130
|
-
const own = files.flatMap((file) => byFile.get(file) ?? []);
|
|
17193
|
+
const own = markParts(withoutDuplicates(files.flatMap((file) => byFile.get(file) ?? [])));
|
|
17131
17194
|
const ts = options.packages?.length && options.typescript !== false ? tsIndexerFor(root) : null;
|
|
17132
17195
|
const fromPackages = ts ? options.packages.flatMap((name) => {
|
|
17133
17196
|
try {
|
package/dist/loader.cjs
CHANGED
|
@@ -15145,8 +15145,20 @@ function classifyType(type, scope, depth = 0) {
|
|
|
15145
15145
|
return classifyType(local.typeAnnotation, scope, depth + 1);
|
|
15146
15146
|
if (lastSegment(name) === "PropType")
|
|
15147
15147
|
return classifyType(typeArguments(type)[0], scope, depth + 1);
|
|
15148
|
-
|
|
15149
|
-
|
|
15148
|
+
if (/^(ReadonlyArray|Array|Set)$/.test(lastSegment(name)))
|
|
15149
|
+
return { type: "other", shape: "array" };
|
|
15150
|
+
if (/^(Function|Dispatch|SetStateAction|MouseEventHandler|ChangeEventHandler|EventHandler)$/.test(lastSegment(name)) || /Handler$/.test(lastSegment(name)))
|
|
15151
|
+
return { type: "other", shape: "function" };
|
|
15152
|
+
return { type: "other", shape: "object" };
|
|
15153
|
+
}
|
|
15154
|
+
case "TSFunctionType":
|
|
15155
|
+
return { type: "other", shape: "function" };
|
|
15156
|
+
case "TSArrayType":
|
|
15157
|
+
case "TSTupleType":
|
|
15158
|
+
return { type: "other", shape: "array" };
|
|
15159
|
+
case "TSTypeLiteral":
|
|
15160
|
+
case "TSMappedType":
|
|
15161
|
+
return { type: "other", shape: "object" };
|
|
15150
15162
|
case "TSUnionType": {
|
|
15151
15163
|
const kinds = type.types.filter((member) => member.type !== "TSUndefinedKeyword" && member.type !== "TSNullKeyword" && member.type !== "TSVoidKeyword").map((member) => classifyType(member, scope, depth + 1));
|
|
15152
15164
|
return mergeKinds(kinds);
|
|
@@ -15190,7 +15202,7 @@ function membersToProps(members, scope, optionalAll = false) {
|
|
|
15190
15202
|
const kind = name === "children" ? { type: "slot" } : classifyType(member.typeAnnotation?.typeAnnotation, scope);
|
|
15191
15203
|
out.push({ name, ...kind, optional: optionalAll || !!member.optional });
|
|
15192
15204
|
} else if (member.type === "TSMethodSignature") {
|
|
15193
|
-
out.push({ name, type: "other", optional: optionalAll || !!member.optional });
|
|
15205
|
+
out.push({ name, type: "other", shape: "function", optional: optionalAll || !!member.optional });
|
|
15194
15206
|
}
|
|
15195
15207
|
}
|
|
15196
15208
|
return out;
|
|
@@ -15521,6 +15533,21 @@ function patternDefaults2(ts, fn) {
|
|
|
15521
15533
|
return out;
|
|
15522
15534
|
}
|
|
15523
15535
|
var SLOT_TEXT = /\b(ReactNode|ReactElement|ReactChild|ReactPortal|JSX\.Element)\b/;
|
|
15536
|
+
function shapeOf(ts, checker, parts) {
|
|
15537
|
+
if (parts.length && parts.every((part) => part.getCallSignatures().length > 0))
|
|
15538
|
+
return "function";
|
|
15539
|
+
const isArray = (part) => {
|
|
15540
|
+
const check = checker;
|
|
15541
|
+
if (check.isArrayType?.(part) || check.isTupleType?.(part))
|
|
15542
|
+
return true;
|
|
15543
|
+
return /^(Array|ReadonlyArray)$/.test(part.getSymbol()?.getName() ?? "");
|
|
15544
|
+
};
|
|
15545
|
+
if (parts.length && parts.every(isArray))
|
|
15546
|
+
return "array";
|
|
15547
|
+
if (parts.length && parts.every((part) => part.flags & ts.TypeFlags.Object))
|
|
15548
|
+
return "object";
|
|
15549
|
+
return;
|
|
15550
|
+
}
|
|
15524
15551
|
function classify(ts, checker, name, type, declared) {
|
|
15525
15552
|
if (name === "children" || SLOT_TEXT.test(declared) || SLOT_TEXT.test(checker.typeToString(type)))
|
|
15526
15553
|
return { type: "slot" };
|
|
@@ -15535,7 +15562,8 @@ function classify(ts, checker, name, type, declared) {
|
|
|
15535
15562
|
return { type: "number" };
|
|
15536
15563
|
if (parts.every((part) => part.flags & ts.TypeFlags.StringLike))
|
|
15537
15564
|
return { type: "text" };
|
|
15538
|
-
|
|
15565
|
+
const shape = shapeOf(ts, checker, parts);
|
|
15566
|
+
return shape ? { type: "other", shape } : { type: "other" };
|
|
15539
15567
|
}
|
|
15540
15568
|
function propsOf(ts, checker, propsType, at, fn, isOwn) {
|
|
15541
15569
|
const defaults = patternDefaults2(ts, fn);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Node } from '../ast';
|
|
2
|
-
import type { PropDefault, PropEntry, PropKind } from './types';
|
|
2
|
+
import type { PropDefault, PropEntry, PropKind, PropShape } from './types';
|
|
3
3
|
export type TypeScope = Map<string, Node>;
|
|
4
4
|
/**
|
|
5
5
|
* Top-level interfaces and type aliases of a script, exported or not; and (keyed `value:<name>`)
|
|
@@ -11,6 +11,7 @@ export declare function typeArguments(node: Node | null | undefined): Node[];
|
|
|
11
11
|
interface Kind {
|
|
12
12
|
type: PropKind;
|
|
13
13
|
options?: string[];
|
|
14
|
+
shape?: PropShape;
|
|
14
15
|
}
|
|
15
16
|
/** Classifies one prop's type node. */
|
|
16
17
|
export declare function classifyType(type: Node | null | undefined, scope: TypeScope, depth?: number): Kind;
|
|
@@ -15,5 +15,11 @@ export declare function inComponentFolder(root: string, file: string, folders?:
|
|
|
15
15
|
export declare function componentFiles(root: string, folders?: string[]): string[];
|
|
16
16
|
/** Indexes the given files (absolute). React files share one TypeScript program. */
|
|
17
17
|
export declare function indexFiles(root: string, files: string[], options?: IndexOptions): Map<string, ComponentEntry[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Marks the pieces of compound components. In a file exporting several (shadcn's
|
|
20
|
+
* `alert-dialog.tsx`: AlertDialog, AlertDialogTitle, …), the root is the export named after the
|
|
21
|
+
* file, else the first; the others that share its first word are its parts (TabItem of Tabs too).
|
|
22
|
+
*/
|
|
23
|
+
export declare function markParts(entries: ComponentEntry[]): ComponentEntry[];
|
|
18
24
|
/** The project's components: one entry per exported component in the component folders, then the listed packages'. */
|
|
19
25
|
export declare function indexComponents(projectRoot: string, options?: IndexOptions): ComponentEntry[];
|
|
@@ -10,7 +10,10 @@ export interface PropEntry {
|
|
|
10
10
|
optional: boolean;
|
|
11
11
|
/** A static default from the source (destructuring default, withDefaults, `export let x = …`). */
|
|
12
12
|
default?: PropDefault;
|
|
13
|
+
/** For an `other` prop, the kind of value it takes, so a render can pass a harmless stand-in (a no-op, `[]`, `{}`). */
|
|
14
|
+
shape?: PropShape;
|
|
13
15
|
}
|
|
16
|
+
export type PropShape = 'function' | 'array' | 'object';
|
|
14
17
|
export interface ComponentEntry {
|
|
15
18
|
name: string;
|
|
16
19
|
/** Path relative to the project root, `/`-separated. */
|
|
@@ -21,4 +24,6 @@ export interface ComponentEntry {
|
|
|
21
24
|
props: PropEntry[];
|
|
22
25
|
/** Set for a component an installed npm package exports: `file` is then the package name (read-only). */
|
|
23
26
|
package?: string;
|
|
27
|
+
/** A piece of another component in its file (`AlertDialogTitle` of `AlertDialog`): it works only inside it, so it is not listed on its own. */
|
|
28
|
+
part?: boolean;
|
|
24
29
|
}
|
package/dist/types/registry.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ComponentEntry, Framework } from './component-index/types';
|
|
2
2
|
export declare const REGISTRY_ID = "virtual:natural-design/registry";
|
|
3
3
|
export declare const HOST_ID = "virtual:natural-design/host";
|
|
4
|
+
/** The app's providers read from its entry (providers.ts), when it has no natural.config. */
|
|
5
|
+
export declare const PROVIDERS_ID = "virtual:natural-design/providers";
|
|
4
6
|
export declare const HOST_PATH = "/__natural/host";
|
|
5
7
|
export declare const INDEX_PATH = "/__natural/components.json";
|
|
6
8
|
/** A URL Vite serves for a project file: root-relative inside the root, `/@fs/` outside it. */
|
|
@@ -10,6 +12,6 @@ export declare function naturalConfigFile(root: string): string | null;
|
|
|
10
12
|
/** The registry module's code. `frameworks` lists those whose runtime the project can resolve. */
|
|
11
13
|
export declare function registryModule(root: string, components: ComponentEntry[], frameworks: Set<Framework>, packages?: string[]): string;
|
|
12
14
|
/** The host page's module: global styles, the registry, the decorator, and `?component=Name&props={…}` for a direct render. */
|
|
13
|
-
export declare function hostModule(root: string, stylesheets: string[], configFile: string | null): string;
|
|
15
|
+
export declare function hostModule(root: string, stylesheets: string[], configFile: string | null, appProviders?: boolean): string;
|
|
14
16
|
/** The bare host document, before Vite's transformIndexHtml adds its client and the React refresh preamble. */
|
|
15
17
|
export declare function hostHtml(root: string, base: string): string;
|
package/dist/types/styles.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
declare function resolveSpecifier(root: string, from: string, specifier: string): string | null;
|
|
2
|
+
/** The app's entry modules: index.html's module scripts, then the usual entry files that exist. */
|
|
3
|
+
export declare function appEntries(root: string): string[];
|
|
4
|
+
export { resolveSpecifier };
|
|
1
5
|
/** Absolute paths of the global stylesheets, in import order. `explicit` (root-relative) wins when given. */
|
|
2
6
|
export declare function globalStylesheets(root: string, explicit?: string[]): string[];
|
|
3
7
|
/** `<html …>` and `<body …>` attributes of the project's index.html (theme classes, fonts), for the host page. */
|
package/dist/vite.mjs
CHANGED
|
@@ -15110,8 +15110,20 @@ function classifyType(type, scope, depth = 0) {
|
|
|
15110
15110
|
return classifyType(local.typeAnnotation, scope, depth + 1);
|
|
15111
15111
|
if (lastSegment(name) === "PropType")
|
|
15112
15112
|
return classifyType(typeArguments(type)[0], scope, depth + 1);
|
|
15113
|
-
|
|
15114
|
-
|
|
15113
|
+
if (/^(ReadonlyArray|Array|Set)$/.test(lastSegment(name)))
|
|
15114
|
+
return { type: "other", shape: "array" };
|
|
15115
|
+
if (/^(Function|Dispatch|SetStateAction|MouseEventHandler|ChangeEventHandler|EventHandler)$/.test(lastSegment(name)) || /Handler$/.test(lastSegment(name)))
|
|
15116
|
+
return { type: "other", shape: "function" };
|
|
15117
|
+
return { type: "other", shape: "object" };
|
|
15118
|
+
}
|
|
15119
|
+
case "TSFunctionType":
|
|
15120
|
+
return { type: "other", shape: "function" };
|
|
15121
|
+
case "TSArrayType":
|
|
15122
|
+
case "TSTupleType":
|
|
15123
|
+
return { type: "other", shape: "array" };
|
|
15124
|
+
case "TSTypeLiteral":
|
|
15125
|
+
case "TSMappedType":
|
|
15126
|
+
return { type: "other", shape: "object" };
|
|
15115
15127
|
case "TSUnionType": {
|
|
15116
15128
|
const kinds = type.types.filter((member) => member.type !== "TSUndefinedKeyword" && member.type !== "TSNullKeyword" && member.type !== "TSVoidKeyword").map((member) => classifyType(member, scope, depth + 1));
|
|
15117
15129
|
return mergeKinds(kinds);
|
|
@@ -15155,7 +15167,7 @@ function membersToProps(members, scope, optionalAll = false) {
|
|
|
15155
15167
|
const kind = name === "children" ? { type: "slot" } : classifyType(member.typeAnnotation?.typeAnnotation, scope);
|
|
15156
15168
|
out.push({ name, ...kind, optional: optionalAll || !!member.optional });
|
|
15157
15169
|
} else if (member.type === "TSMethodSignature") {
|
|
15158
|
-
out.push({ name, type: "other", optional: optionalAll || !!member.optional });
|
|
15170
|
+
out.push({ name, type: "other", shape: "function", optional: optionalAll || !!member.optional });
|
|
15159
15171
|
}
|
|
15160
15172
|
}
|
|
15161
15173
|
return out;
|
|
@@ -15534,6 +15546,21 @@ function patternDefaults2(ts, fn) {
|
|
|
15534
15546
|
return out;
|
|
15535
15547
|
}
|
|
15536
15548
|
var SLOT_TEXT = /\b(ReactNode|ReactElement|ReactChild|ReactPortal|JSX\.Element)\b/;
|
|
15549
|
+
function shapeOf(ts, checker, parts) {
|
|
15550
|
+
if (parts.length && parts.every((part) => part.getCallSignatures().length > 0))
|
|
15551
|
+
return "function";
|
|
15552
|
+
const isArray = (part) => {
|
|
15553
|
+
const check = checker;
|
|
15554
|
+
if (check.isArrayType?.(part) || check.isTupleType?.(part))
|
|
15555
|
+
return true;
|
|
15556
|
+
return /^(Array|ReadonlyArray)$/.test(part.getSymbol()?.getName() ?? "");
|
|
15557
|
+
};
|
|
15558
|
+
if (parts.length && parts.every(isArray))
|
|
15559
|
+
return "array";
|
|
15560
|
+
if (parts.length && parts.every((part) => part.flags & ts.TypeFlags.Object))
|
|
15561
|
+
return "object";
|
|
15562
|
+
return;
|
|
15563
|
+
}
|
|
15537
15564
|
function classify(ts, checker, name, type, declared) {
|
|
15538
15565
|
if (name === "children" || SLOT_TEXT.test(declared) || SLOT_TEXT.test(checker.typeToString(type)))
|
|
15539
15566
|
return { type: "slot" };
|
|
@@ -15548,7 +15575,8 @@ function classify(ts, checker, name, type, declared) {
|
|
|
15548
15575
|
return { type: "number" };
|
|
15549
15576
|
if (parts.every((part) => part.flags & ts.TypeFlags.StringLike))
|
|
15550
15577
|
return { type: "text" };
|
|
15551
|
-
|
|
15578
|
+
const shape = shapeOf(ts, checker, parts);
|
|
15579
|
+
return shape ? { type: "other", shape } : { type: "other" };
|
|
15552
15580
|
}
|
|
15553
15581
|
function propsOf(ts, checker, propsType, at, fn, isOwn) {
|
|
15554
15582
|
const defaults = patternDefaults2(ts, fn);
|
|
@@ -15880,11 +15908,46 @@ function indexFiles(root, files, options = {}) {
|
|
|
15880
15908
|
}
|
|
15881
15909
|
return out;
|
|
15882
15910
|
}
|
|
15911
|
+
function withoutDuplicates(entries) {
|
|
15912
|
+
const kept = new Map;
|
|
15913
|
+
for (const entry2 of entries) {
|
|
15914
|
+
const key = `${entry2.file}#${entry2.name}`;
|
|
15915
|
+
const seen = kept.get(key);
|
|
15916
|
+
if (!seen || seen.exportName === "default" && entry2.exportName !== "default")
|
|
15917
|
+
kept.set(key, entry2);
|
|
15918
|
+
}
|
|
15919
|
+
return entries.filter((entry2) => kept.get(`${entry2.file}#${entry2.name}`) === entry2);
|
|
15920
|
+
}
|
|
15921
|
+
function words(name) {
|
|
15922
|
+
return name.match(/[A-Z][a-z0-9]*|[a-z0-9]+/g) ?? [name];
|
|
15923
|
+
}
|
|
15924
|
+
function markParts(entries) {
|
|
15925
|
+
const byFile = new Map;
|
|
15926
|
+
for (const entry2 of entries)
|
|
15927
|
+
byFile.set(entry2.file, [...byFile.get(entry2.file) ?? [], entry2]);
|
|
15928
|
+
for (const [file, inFile] of byFile) {
|
|
15929
|
+
const names = [...new Set(inFile.map((entry2) => entry2.name))];
|
|
15930
|
+
if (names.length < 2)
|
|
15931
|
+
continue;
|
|
15932
|
+
const stem = path3.basename(file).replace(/\.[^.]+$/, "");
|
|
15933
|
+
const base = (stem === "index" ? path3.basename(path3.dirname(file)) : stem).split(/[^A-Za-z0-9]+/).filter(Boolean).map((word) => word[0].toUpperCase() + word.slice(1)).join("");
|
|
15934
|
+
const root = names.find((name) => name === base) ?? names[0];
|
|
15935
|
+
const head = words(root)[0];
|
|
15936
|
+
for (const entry2 of inFile) {
|
|
15937
|
+
if (entry2.name === root)
|
|
15938
|
+
continue;
|
|
15939
|
+
const first = words(entry2.name)[0];
|
|
15940
|
+
if (first.startsWith(head) || head.startsWith(first))
|
|
15941
|
+
entry2.part = true;
|
|
15942
|
+
}
|
|
15943
|
+
}
|
|
15944
|
+
return entries;
|
|
15945
|
+
}
|
|
15883
15946
|
function indexComponents(projectRoot, options = {}) {
|
|
15884
15947
|
const root = path3.resolve(projectRoot);
|
|
15885
15948
|
const files = componentFiles(root, options.folders);
|
|
15886
15949
|
const byFile = indexFiles(root, files, options);
|
|
15887
|
-
const own = files.flatMap((file) => byFile.get(file) ?? []);
|
|
15950
|
+
const own = markParts(withoutDuplicates(files.flatMap((file) => byFile.get(file) ?? [])));
|
|
15888
15951
|
const ts = options.packages?.length && options.typescript !== false ? tsIndexerFor(root) : null;
|
|
15889
15952
|
const fromPackages = ts ? options.packages.flatMap((name) => {
|
|
15890
15953
|
try {
|
|
@@ -15938,6 +16001,18 @@ function stylesImportedBy(root, entry2) {
|
|
|
15938
16001
|
}
|
|
15939
16002
|
return out;
|
|
15940
16003
|
}
|
|
16004
|
+
function appEntries(root) {
|
|
16005
|
+
const entries = [];
|
|
16006
|
+
const html = path4.join(root, "index.html");
|
|
16007
|
+
if (existsSync3(html)) {
|
|
16008
|
+
for (const script of readFileSync3(html, "utf8").matchAll(ENTRY_SCRIPT))
|
|
16009
|
+
if (script[1].startsWith("/"))
|
|
16010
|
+
entries.push(path4.join(root, script[1]));
|
|
16011
|
+
}
|
|
16012
|
+
for (const file of ENTRIES)
|
|
16013
|
+
entries.push(path4.join(root, file));
|
|
16014
|
+
return [...new Set(entries)].filter((entry2) => existsSync3(entry2));
|
|
16015
|
+
}
|
|
15941
16016
|
function globalStylesheets(root, explicit) {
|
|
15942
16017
|
if (explicit?.length)
|
|
15943
16018
|
return explicit.map((file) => path4.resolve(root, file.replace(/^\//, "")));
|
|
@@ -15980,6 +16055,7 @@ function documentAttributes(root) {
|
|
|
15980
16055
|
// src/registry.ts
|
|
15981
16056
|
var REGISTRY_ID = "virtual:natural-design/registry";
|
|
15982
16057
|
var HOST_ID = "virtual:natural-design/host";
|
|
16058
|
+
var PROVIDERS_ID = "virtual:natural-design/providers";
|
|
15983
16059
|
var HOST_PATH = "/__natural/host";
|
|
15984
16060
|
var INDEX_PATH = "/__natural/components.json";
|
|
15985
16061
|
function serveUrl(root, file) {
|
|
@@ -15997,7 +16073,8 @@ var RENDERERS = {
|
|
|
15997
16073
|
react: `async (component, props, element) => {
|
|
15998
16074
|
const pick = (m) => (m && m.default && typeof m.default === "object" && !m.createElement && !m.createRoot && !m.flushSync ? m.default : m);
|
|
15999
16075
|
const [React, Client, Dom] = (await Promise.all([import("react"), import("react-dom/client"), import("react-dom")])).map(pick);
|
|
16000
|
-
|
|
16076
|
+
// Slot markup becomes children; a stand-in for a required callback (props travel as JSON) becomes a no-op.
|
|
16077
|
+
const slot = (value) => value && typeof value === "object" && value.__nd === "function" ? () => {} : value && typeof value === "object" && typeof value.__html === "string" ? React.createElement("span", { style: { display: "contents" }, dangerouslySetInnerHTML: { __html: value.__html } }) : value;
|
|
16001
16078
|
const resolved = {};
|
|
16002
16079
|
for (const key of Object.keys(props)) resolved[key] = slot(props[key]);
|
|
16003
16080
|
let node = React.createElement(component, resolved);
|
|
@@ -16082,7 +16159,7 @@ nd.render = async (name, props, element) => {
|
|
|
16082
16159
|
if (!renderer) throw new Error(entry.framework + " components cannot be mounted in the browser");
|
|
16083
16160
|
nd.unmount(element);
|
|
16084
16161
|
mounted.set(element, await renderer(component, props || {}, element));
|
|
16085
|
-
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
16162
|
+
await Promise.race([new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))), new Promise((resolve) => setTimeout(resolve, 100))]);
|
|
16086
16163
|
return element;
|
|
16087
16164
|
};
|
|
16088
16165
|
nd.registry = true;
|
|
@@ -16090,8 +16167,18 @@ window.dispatchEvent(new CustomEvent("nd:registry"));
|
|
|
16090
16167
|
export default nd;
|
|
16091
16168
|
`;
|
|
16092
16169
|
}
|
|
16093
|
-
function hostModule(root, stylesheets, configFile) {
|
|
16094
|
-
const lines = [
|
|
16170
|
+
function hostModule(root, stylesheets, configFile, appProviders = false) {
|
|
16171
|
+
const lines = [
|
|
16172
|
+
"// Generated by @naturaldesign/dev: the render host.",
|
|
16173
|
+
"const ndFailed = (error) => {",
|
|
16174
|
+
' const message = String((error && error.message) || error || "Render failed");',
|
|
16175
|
+
" if (document.documentElement.dataset.ndError) return;",
|
|
16176
|
+
" document.documentElement.dataset.ndError = message;",
|
|
16177
|
+
' if (window.parent !== window) window.parent.postMessage({ type: "nd:host-error", component: new URLSearchParams(location.search).get("component"), message }, "*");',
|
|
16178
|
+
"};",
|
|
16179
|
+
'window.addEventListener("error", (event) => ndFailed(event.error || event.message));',
|
|
16180
|
+
'window.addEventListener("unhandledrejection", (event) => ndFailed(event.reason));'
|
|
16181
|
+
];
|
|
16095
16182
|
for (const sheet of stylesheets)
|
|
16096
16183
|
lines.push(`import ${JSON.stringify(serveUrl(root, sheet))};`);
|
|
16097
16184
|
lines.push(`import nd from ${JSON.stringify(REGISTRY_ID)};`);
|
|
@@ -16099,8 +16186,11 @@ function hostModule(root, stylesheets, configFile) {
|
|
|
16099
16186
|
lines.push(`import * as config from ${JSON.stringify(serveUrl(root, configFile))};`);
|
|
16100
16187
|
lines.push("const decorator = config.decorator || (config.default && config.default.decorator);");
|
|
16101
16188
|
lines.push("if (decorator) nd.decorator = decorator;");
|
|
16189
|
+
} else if (appProviders) {
|
|
16190
|
+
lines.push(`import { decorator } from ${JSON.stringify(PROVIDERS_ID)};`);
|
|
16191
|
+
lines.push("nd.decorator = decorator;");
|
|
16102
16192
|
}
|
|
16103
|
-
lines.push("nd.host = true;", "const params = new URLSearchParams(location.search);", 'const requested = params.get("component");', 'if (requested) nd.render(requested, JSON.parse(params.get("props") || "{}"), document.getElementById("nd-host-root")).
|
|
16193
|
+
lines.push("nd.host = true;", "const params = new URLSearchParams(location.search);", 'const requested = params.get("component");', 'const ndDone = () => setTimeout(() => { if (window.parent !== window && !document.documentElement.dataset.ndError) { const root = document.getElementById("nd-host-root"); window.parent.postMessage({ type: "nd:host-done", component: requested, empty: !root || (!root.children.length && !root.textContent.trim()) || root.getBoundingClientRect().height === 0 }, "*"); } }, 400);', 'if (requested) nd.render(requested, JSON.parse(params.get("props") || "{}"), document.getElementById("nd-host-root")).then(ndDone, (error) => { ndFailed(error); console.error(error); });', 'window.dispatchEvent(new CustomEvent("nd:host"));');
|
|
16104
16194
|
return lines.join(`
|
|
16105
16195
|
`) + `
|
|
16106
16196
|
`;
|
|
@@ -16123,6 +16213,173 @@ function hostHtml(root, base) {
|
|
|
16123
16213
|
`;
|
|
16124
16214
|
}
|
|
16125
16215
|
|
|
16216
|
+
// src/providers.ts
|
|
16217
|
+
import { readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
|
|
16218
|
+
import path6 from "node:path";
|
|
16219
|
+
var SKIPPED = new Set(["StrictMode", "Fragment", "Profiler", "Suspense"]);
|
|
16220
|
+
var EXTENSIONS = [".tsx", ".ts", ".jsx", ".js", ".mjs"];
|
|
16221
|
+
function elementName(name) {
|
|
16222
|
+
if (name.type === "JSXIdentifier")
|
|
16223
|
+
return { binding: String(name.name), member: [] };
|
|
16224
|
+
if (name.type === "JSXMemberExpression") {
|
|
16225
|
+
const inner = elementName(name.object);
|
|
16226
|
+
return inner ? { binding: inner.binding, member: [...inner.member, String(name.property.name)] } : null;
|
|
16227
|
+
}
|
|
16228
|
+
return null;
|
|
16229
|
+
}
|
|
16230
|
+
function elementChildren(element) {
|
|
16231
|
+
return (element.children ?? []).filter((child) => child.type === "JSXElement");
|
|
16232
|
+
}
|
|
16233
|
+
function renderedTree(program) {
|
|
16234
|
+
let found = null;
|
|
16235
|
+
walk(program, (node) => {
|
|
16236
|
+
if (found)
|
|
16237
|
+
return false;
|
|
16238
|
+
if (node.type !== "CallExpression")
|
|
16239
|
+
return;
|
|
16240
|
+
const callee = node.callee;
|
|
16241
|
+
const args = node.arguments ?? [];
|
|
16242
|
+
const isRender = callee.type === "MemberExpression" && callee.property.name === "render";
|
|
16243
|
+
const isHydrate = callee.type === "Identifier" && callee.name === "hydrateRoot" || callee.type === "MemberExpression" && callee.property.name === "hydrateRoot";
|
|
16244
|
+
const tree = isRender ? args[0] : isHydrate ? args[1] : null;
|
|
16245
|
+
if (tree?.type === "JSXElement")
|
|
16246
|
+
found = tree;
|
|
16247
|
+
});
|
|
16248
|
+
return found;
|
|
16249
|
+
}
|
|
16250
|
+
function wrapperChain(tree) {
|
|
16251
|
+
const chain = [];
|
|
16252
|
+
let current = tree;
|
|
16253
|
+
while (current) {
|
|
16254
|
+
const children = elementChildren(current);
|
|
16255
|
+
if (!children.length)
|
|
16256
|
+
break;
|
|
16257
|
+
chain.push(current);
|
|
16258
|
+
current = children.find((child) => elementChildren(child).length > 0) ?? null;
|
|
16259
|
+
}
|
|
16260
|
+
return chain;
|
|
16261
|
+
}
|
|
16262
|
+
function importsOf(program) {
|
|
16263
|
+
const imports = new Map;
|
|
16264
|
+
for (const statement of program.body ?? []) {
|
|
16265
|
+
if (statement.type !== "ImportDeclaration" || statement.importKind === "type")
|
|
16266
|
+
continue;
|
|
16267
|
+
const source = String(statement.source.value);
|
|
16268
|
+
for (const specifier of statement.specifiers ?? []) {
|
|
16269
|
+
const local = String(specifier.local.name);
|
|
16270
|
+
if (specifier.type === "ImportDefaultSpecifier")
|
|
16271
|
+
imports.set(local, { source, imported: "default" });
|
|
16272
|
+
else if (specifier.type === "ImportNamespaceSpecifier")
|
|
16273
|
+
imports.set(local, { source, imported: "*" });
|
|
16274
|
+
else
|
|
16275
|
+
imports.set(local, { source, imported: String(specifier.imported.name ?? specifier.imported.value) });
|
|
16276
|
+
}
|
|
16277
|
+
}
|
|
16278
|
+
return imports;
|
|
16279
|
+
}
|
|
16280
|
+
function propValue(value, imports, used) {
|
|
16281
|
+
if (!value)
|
|
16282
|
+
return "true";
|
|
16283
|
+
if (value.type === "StringLiteral")
|
|
16284
|
+
return JSON.stringify(value.value);
|
|
16285
|
+
if (value.type !== "JSXExpressionContainer")
|
|
16286
|
+
return null;
|
|
16287
|
+
const expression = value.expression;
|
|
16288
|
+
if (expression.type === "StringLiteral" || expression.type === "NumericLiteral" || expression.type === "BooleanLiteral")
|
|
16289
|
+
return JSON.stringify(expression.value);
|
|
16290
|
+
if (expression.type === "NullLiteral")
|
|
16291
|
+
return "null";
|
|
16292
|
+
if (expression.type === "Identifier" && imports.has(String(expression.name))) {
|
|
16293
|
+
used.add(String(expression.name));
|
|
16294
|
+
return String(expression.name);
|
|
16295
|
+
}
|
|
16296
|
+
return null;
|
|
16297
|
+
}
|
|
16298
|
+
function servedSource(root, entry2, source) {
|
|
16299
|
+
if (!source.startsWith("."))
|
|
16300
|
+
return source;
|
|
16301
|
+
const file = resolveSpecifier(root, entry2, source);
|
|
16302
|
+
if (!file)
|
|
16303
|
+
return source;
|
|
16304
|
+
const isFile = (candidate) => {
|
|
16305
|
+
try {
|
|
16306
|
+
return statSync3(candidate).isFile();
|
|
16307
|
+
} catch {
|
|
16308
|
+
return false;
|
|
16309
|
+
}
|
|
16310
|
+
};
|
|
16311
|
+
const candidates = [file, ...EXTENSIONS.map((extension) => file + extension), ...EXTENSIONS.map((extension) => path6.join(file, `index${extension}`))];
|
|
16312
|
+
return serveUrl(root, candidates.find(isFile) ?? file);
|
|
16313
|
+
}
|
|
16314
|
+
function providersModule(root) {
|
|
16315
|
+
for (const entry2 of appEntries(root)) {
|
|
16316
|
+
if (!/\.(tsx|jsx|ts|js)$/.test(entry2))
|
|
16317
|
+
continue;
|
|
16318
|
+
let code;
|
|
16319
|
+
try {
|
|
16320
|
+
code = readFileSync4(entry2, "utf8");
|
|
16321
|
+
} catch {
|
|
16322
|
+
continue;
|
|
16323
|
+
}
|
|
16324
|
+
const file = parseModule(code);
|
|
16325
|
+
if (!file)
|
|
16326
|
+
continue;
|
|
16327
|
+
const program = programOf(file);
|
|
16328
|
+
const tree = renderedTree(program);
|
|
16329
|
+
if (!tree)
|
|
16330
|
+
continue;
|
|
16331
|
+
const imports = importsOf(program);
|
|
16332
|
+
const used = new Set;
|
|
16333
|
+
const wrappers = [];
|
|
16334
|
+
for (const element of wrapperChain(tree)) {
|
|
16335
|
+
const name = elementName(element.openingElement.name);
|
|
16336
|
+
if (!name)
|
|
16337
|
+
continue;
|
|
16338
|
+
if (SKIPPED.has(name.member.at(-1) ?? name.binding) || name.binding === "Fragment")
|
|
16339
|
+
continue;
|
|
16340
|
+
if (!imports.has(name.binding))
|
|
16341
|
+
continue;
|
|
16342
|
+
const props = [];
|
|
16343
|
+
let rebuildable = true;
|
|
16344
|
+
for (const attribute of element.openingElement.attributes ?? []) {
|
|
16345
|
+
if (attribute.type !== "JSXAttribute") {
|
|
16346
|
+
rebuildable = false;
|
|
16347
|
+
break;
|
|
16348
|
+
}
|
|
16349
|
+
const value = propValue(attribute.value, imports, used);
|
|
16350
|
+
if (value === null) {
|
|
16351
|
+
rebuildable = false;
|
|
16352
|
+
break;
|
|
16353
|
+
}
|
|
16354
|
+
props.push({ name: String(attribute.name.name), value });
|
|
16355
|
+
}
|
|
16356
|
+
if (!rebuildable)
|
|
16357
|
+
continue;
|
|
16358
|
+
used.add(name.binding);
|
|
16359
|
+
wrappers.push({ binding: name.binding, member: name.member, props });
|
|
16360
|
+
}
|
|
16361
|
+
if (!wrappers.length)
|
|
16362
|
+
return null;
|
|
16363
|
+
const lines = [`// Generated by @naturaldesign/dev: the app's providers, read from ${path6.relative(root, entry2)}.`, 'import * as __ndReact from "react";'];
|
|
16364
|
+
for (const binding of used) {
|
|
16365
|
+
const source = JSON.stringify(servedSource(root, entry2, imports.get(binding).source));
|
|
16366
|
+
const { imported } = imports.get(binding);
|
|
16367
|
+
lines.push(imported === "default" ? `import ${binding} from ${source};` : imported === "*" ? `import * as ${binding} from ${source};` : `import { ${imported === binding ? binding : `${imported} as ${binding}`} } from ${source};`);
|
|
16368
|
+
}
|
|
16369
|
+
let body = "children";
|
|
16370
|
+
for (const wrapper of [...wrappers].reverse()) {
|
|
16371
|
+
const type = [wrapper.binding, ...wrapper.member].join(".");
|
|
16372
|
+
const props = wrapper.props.length ? `{ ${wrapper.props.map((prop) => `${JSON.stringify(prop.name)}: ${prop.value}`).join(", ")} }` : "null";
|
|
16373
|
+
body = `__ndReact.createElement(${type}, ${props}, ${body})`;
|
|
16374
|
+
}
|
|
16375
|
+
lines.push(`export function decorator({ children }) {`, ` return ${body};`, `}`);
|
|
16376
|
+
return lines.join(`
|
|
16377
|
+
`) + `
|
|
16378
|
+
`;
|
|
16379
|
+
}
|
|
16380
|
+
return null;
|
|
16381
|
+
}
|
|
16382
|
+
|
|
16126
16383
|
// ../../node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs
|
|
16127
16384
|
var comma = 44;
|
|
16128
16385
|
var semicolon = 59;
|
|
@@ -17383,17 +17640,22 @@ function naturalDesign(options = {}) {
|
|
|
17383
17640
|
resolveId(id) {
|
|
17384
17641
|
if (!registryOn)
|
|
17385
17642
|
return null;
|
|
17386
|
-
if (id === REGISTRY_ID || id === HOST_ID)
|
|
17643
|
+
if (id === REGISTRY_ID || id === HOST_ID || id === PROVIDERS_ID)
|
|
17387
17644
|
return RESOLVED(id);
|
|
17388
17645
|
return null;
|
|
17389
17646
|
},
|
|
17390
17647
|
load(id) {
|
|
17391
17648
|
if (id === RESOLVED(REGISTRY_ID)) {
|
|
17392
|
-
registryEntries ??= indexComponents(root, { folders: options.indexFolders, typescript: false });
|
|
17649
|
+
registryEntries ??= index ?? indexComponents(root, { folders: options.indexFolders, typescript: false });
|
|
17393
17650
|
return registryModule(root, registryEntries, frameworks(), options.packages ?? []);
|
|
17394
17651
|
}
|
|
17395
|
-
if (id === RESOLVED(HOST_ID))
|
|
17396
|
-
|
|
17652
|
+
if (id === RESOLVED(HOST_ID)) {
|
|
17653
|
+
const config = naturalConfigFile(root);
|
|
17654
|
+
return hostModule(root, globalStylesheets(root, options.styles), config, !config && providersModule(root) !== null);
|
|
17655
|
+
}
|
|
17656
|
+
if (id === RESOLVED(PROVIDERS_ID))
|
|
17657
|
+
return providersModule(root) ?? `export function decorator({ children }) { return children }
|
|
17658
|
+
`;
|
|
17397
17659
|
return null;
|
|
17398
17660
|
},
|
|
17399
17661
|
transformIndexHtml() {
|
|
@@ -17419,7 +17681,13 @@ function naturalDesign(options = {}) {
|
|
|
17419
17681
|
const url = (req.url ?? "").split("?")[0];
|
|
17420
17682
|
if (options.index !== false && url.endsWith(INDEX_PATH)) {
|
|
17421
17683
|
try {
|
|
17422
|
-
|
|
17684
|
+
if (!index) {
|
|
17685
|
+
index = indexComponents(root, { folders: options.indexFolders, packages: options.packages });
|
|
17686
|
+
registryEntries = null;
|
|
17687
|
+
const module = server.moduleGraph.getModuleById(RESOLVED(REGISTRY_ID));
|
|
17688
|
+
if (module)
|
|
17689
|
+
server.moduleGraph.invalidateModule(module);
|
|
17690
|
+
}
|
|
17423
17691
|
return send(res, 200, "application/json", JSON.stringify({ root, components: index }));
|
|
17424
17692
|
} catch (error) {
|
|
17425
17693
|
return send(res, 500, "application/json", JSON.stringify({ root, components: [], error: String(error?.message ?? error) }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturaldesign/dev",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Dev-only plugin for Natural Design: stamps JSX with its source, indexes the project's components and renders them, so Natural Design can show and edit a project's real components. Run `npx @naturaldesign/dev init` in a Next or Vite project.",
|
|
6
6
|
"bin": {
|