@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 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
- return { type: "other" };
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
- return { type: "other" };
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
- if (!fs.existsSync(path10.join(root, "package.json")))
17946
- return { ok: false, steps: [`No package.json in ${root}`] };
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
- steps.push(`${PACKAGE} is already in package.json`);
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
- steps.push(`Running ${manager} ${args.join(" ")}`);
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
- return { ok: false, steps: [...steps, `${manager} could not install ${PACKAGE}`] };
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
- steps.push("No next.config or vite.config found. Add the plugin by hand: see the README");
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
- steps.push(`Could not read the config in ${name}. Add the plugin by hand: see the README`);
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
- steps.push(`${name} already uses the plugin`);
18043
+ step(`${name} already uses the plugin`);
17973
18044
  } else {
17974
18045
  fs.writeFileSync(file, wired);
17975
- steps.push(`Added the plugin to ${name}`);
18046
+ step(`Added the plugin to ${name}`);
17976
18047
  }
17977
- steps.push("Restart the dev server to see the components in Natural Design");
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
- for (const step of result.steps)
18015
- process.stdout.write(`${step}
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
- return { type: "other" };
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
- return { type: "other" };
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
- return { type: "other" };
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
- return { type: "other" };
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
  }
@@ -0,0 +1,2 @@
1
+ /** The decorator module (`export function decorator({ children })`), or null when the entry has no provider to take. */
2
+ export declare function providersModule(root: string): string | null;
@@ -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;
@@ -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
- return { type: "other" };
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
- return { type: "other" };
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
- const slot = (value) => value && typeof value === "object" && typeof value.__html === "string" ? React.createElement("span", { style: { display: "contents" }, dangerouslySetInnerHTML: { __html: value.__html } }) : value;
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 = ["// Generated by @naturaldesign/dev: the render host."];
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")).catch((error) => { document.documentElement.dataset.ndError = String(error && error.message || error); console.error(error); });', 'window.dispatchEvent(new CustomEvent("nd:host"));');
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
- return hostModule(root, globalStylesheets(root, options.styles), naturalConfigFile(root));
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
- index ??= indexComponents(root, { folders: options.indexFolders, packages: options.packages });
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.12",
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": {