@dcoder-x/plugin-shared 0.1.11 → 0.1.12

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.
@@ -19,13 +19,32 @@ class ComponentExtractor {
19
19
  }
20
20
  async extract() {
21
21
  const elements = [];
22
+ // Build a direct filePath → route.path index so that files which are
23
+ // themselves route entries get their own route, not whichever layout or
24
+ // parent route happened to import them first.
25
+ const fileRouteIndex = new Map();
26
+ for (const route of this.routes) {
27
+ const relFwd = route.filePath.replace(/\\/g, '/');
28
+ fileRouteIndex.set(relFwd, route.path);
29
+ // Also index by just the basename-less relative path variations
30
+ fileRouteIndex.set(route.filePath, route.path);
31
+ }
32
+ const seen = new Set(); // deduplicate across routes
22
33
  for (const route of this.routes) {
23
34
  const moduleFiles = this.getModuleFilesForRoute(route.filePath);
24
35
  for (const filePath of moduleFiles) {
36
+ if (seen.has(filePath))
37
+ continue;
38
+ seen.add(filePath);
25
39
  const source = this.readSource(filePath);
26
40
  if (!source)
27
41
  continue;
28
- elements.push(...this.extractFromSource(source, filePath, route.path));
42
+ // Prefer the route this file is directly registered as; fall back to
43
+ // the route whose module graph we're walking (the layout/parent route).
44
+ const assignedRoute = fileRouteIndex.get(filePath) ??
45
+ fileRouteIndex.get(filePath.replace(/\\/g, '/')) ??
46
+ route.path;
47
+ elements.push(...this.extractFromSource(source, filePath, assignedRoute));
29
48
  }
30
49
  }
31
50
  return elements;
@@ -38,12 +57,24 @@ class ComponentExtractor {
38
57
  const components = [];
39
58
  const contextResolver = new ComponentContextResolver_1.ComponentContextResolver();
40
59
  const graphExtractor = new InteractionGraphExtractor_1.InteractionGraphExtractor();
60
+ const fileRouteIndex = new Map();
61
+ for (const route of this.routes) {
62
+ fileRouteIndex.set(route.filePath, route.path);
63
+ fileRouteIndex.set(route.filePath.replace(/\\/g, '/'), route.path);
64
+ }
65
+ const seen = new Set();
41
66
  for (const route of this.routes) {
42
67
  const moduleFiles = this.getModuleFilesForRoute(route.filePath);
43
68
  for (const filePath of moduleFiles) {
69
+ if (seen.has(filePath))
70
+ continue;
71
+ seen.add(filePath);
44
72
  const source = this.readSource(filePath);
45
73
  if (!source)
46
74
  continue;
75
+ const assignedRoute = fileRouteIndex.get(filePath) ??
76
+ fileRouteIndex.get(filePath.replace(/\\/g, '/')) ??
77
+ route.path;
47
78
  let ast;
48
79
  try {
49
80
  ast = (0, parser_1.parse)(source, { sourceType: 'module', plugins: ['typescript', 'jsx'] });
@@ -61,7 +92,7 @@ class ComponentExtractor {
61
92
  components.push({
62
93
  name: componentName,
63
94
  filePath,
64
- route: route.path,
95
+ route: assignedRoute,
65
96
  stateVariables: context.stateVariables,
66
97
  interactions,
67
98
  });
@@ -79,7 +110,7 @@ class ComponentExtractor {
79
110
  components.push({
80
111
  name: componentName,
81
112
  filePath,
82
- route: route.path,
113
+ route: assignedRoute,
83
114
  stateVariables: context.stateVariables,
84
115
  interactions,
85
116
  });
@@ -416,8 +447,39 @@ function deriveLabel(tag, props, nearbyText) {
416
447
  return props['placeholder'];
417
448
  if (props['name'])
418
449
  return props['name'];
450
+ // Last resort: extract meaningful words from className for icon-only elements
451
+ // (e.g. password-toggle, close-modal buttons that contain only an SVG).
452
+ const className = props['className'] || props['class'] || '';
453
+ if (className) {
454
+ const meaningful = classNameToLabel(className);
455
+ if (meaningful)
456
+ return meaningful;
457
+ }
419
458
  return null;
420
459
  }
460
+ // Utility tokens that describe layout/spacing/color but not element purpose.
461
+ const TAILWIND_NOISE = new Set([
462
+ 'flex', 'grid', 'block', 'inline', 'hidden', 'relative', 'absolute', 'fixed', 'sticky',
463
+ 'items', 'justify', 'content', 'self', 'grow', 'shrink', 'order',
464
+ 'inset', 'top', 'right', 'bottom', 'left', 'z',
465
+ 'w', 'h', 'min', 'max', 'p', 'px', 'py', 'pt', 'pb', 'pl', 'pr', 'm', 'mx', 'my', 'mt', 'mb', 'ml', 'mr',
466
+ 'gap', 'space', 'divide', 'overflow', 'truncate', 'whitespace', 'break',
467
+ 'text', 'font', 'leading', 'tracking', 'uppercase', 'lowercase', 'capitalize', 'normal',
468
+ 'bg', 'border', 'rounded', 'shadow', 'ring', 'outline', 'opacity', 'transition', 'duration',
469
+ 'hover', 'focus', 'active', 'disabled', 'group', 'peer', 'placeholder',
470
+ 'sr', 'not', 'dark', 'sm', 'md', 'lg', 'xl', '2xl',
471
+ ]);
472
+ function classNameToLabel(className) {
473
+ const words = className
474
+ .split(/[\s\-_/]+/)
475
+ .map((w) => w.replace(/[^a-zA-Z]/g, '').toLowerCase())
476
+ .filter((w) => w.length > 2 && !TAILWIND_NOISE.has(w));
477
+ if (words.length === 0)
478
+ return null;
479
+ // Deduplicate, keep first 3 meaningful words
480
+ const unique = [...new Set(words)].slice(0, 3);
481
+ return unique.join(' ');
482
+ }
421
483
  function inferSemanticRole(tag, props, nearbyText) {
422
484
  const label = props['aria-label'] || props['placeholder'] || nearbyText[0] || '';
423
485
  return `${label} ${tag}`.trim().toLowerCase();
package/dist/types.d.ts CHANGED
@@ -96,6 +96,7 @@ export interface PolicySelectorEntry {
96
96
  tag: string;
97
97
  component: string;
98
98
  label?: string;
99
+ nearbyText?: string[];
99
100
  route: string;
100
101
  filePath: string;
101
102
  attributes: Array<{
@@ -142,6 +143,7 @@ export interface SelectorManifestEntry {
142
143
  component: string;
143
144
  tag: string;
144
145
  label?: string;
146
+ nearbyText?: string[];
145
147
  routes: string[];
146
148
  }
147
149
  export interface SelectorManifest {
@@ -83,6 +83,7 @@ class PackageBuilder {
83
83
  component: entry.component,
84
84
  tag: entry.tag,
85
85
  label: entry.label,
86
+ nearbyText: entry.nearbyText,
86
87
  routes: [entry.route],
87
88
  });
88
89
  }
@@ -180,6 +181,7 @@ class PackageBuilder {
180
181
  tag: element.tag,
181
182
  component,
182
183
  label: element.label,
184
+ nearbyText: element.nearbyText?.length ? element.nearbyText : undefined,
183
185
  route: element.route,
184
186
  filePath: projectRoot
185
187
  ? this.normalizePath(element.filePath, projectRoot)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcoder-x/plugin-shared",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",