@wix/zero-config-implementation 1.16.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,9 @@
1
+ export declare function isGlobalSemanticClass(className: string): boolean;
2
+ /**
3
+ * Picks the best semantic class from a list of class names.
4
+ * Prefers a class without a BEM modifier (e.g. `card__header`) over one with
5
+ * a modifier (e.g. `card__header--active`), since the base class is more
6
+ * stable as a selector or element name. Falls back to the first semantic class
7
+ * found if all candidates have modifiers.
8
+ */
9
+ export declare function findPreferredSemanticClass(classNames: string[]): string | undefined;
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "registry": "https://registry.npmjs.org/",
5
5
  "access": "public"
6
6
  },
7
- "version": "1.16.0",
7
+ "version": "1.18.0",
8
8
  "description": "Core library for extracting component manifests from JS and CSS files",
9
9
  "type": "module",
10
10
  "main": "dist/index.js",
@@ -75,5 +75,5 @@
75
75
  ]
76
76
  }
77
77
  },
78
- "falconPackageHash": "5cc87a0628792bffcdfe10736b0f9dad0328860a5d97028ab43a1aa7"
78
+ "falconPackageHash": "28d3025e283d998b34b77ef31c51e4195289ad39631c5054675ce0b6"
79
79
  }
@@ -16,6 +16,7 @@ import type {
16
16
  ElementItem,
17
17
  } from '../schema'
18
18
  import { ELEMENTS } from '../schema'
19
+ import { findPreferredSemanticClass } from '../utils/css-class'
19
20
  import { buildDataItem } from './data-item-builder'
20
21
  import { formatDisplayName } from './utils'
21
22
 
@@ -44,7 +45,8 @@ function buildEditorElement(component: ComponentInfoWithCss): EditorElement {
44
45
 
45
46
  function buildSelector(rootElement?: ExtractedElement): string {
46
47
  if (rootElement?.attributes.class) {
47
- return `.${rootElement.attributes.class.split(' ')[0]}`
48
+ const semanticClass = findPreferredSemanticClass(rootElement.attributes.class.split(' '))
49
+ if (semanticClass) return `.${semanticClass}`
48
50
  }
49
51
  return rootElement?.tag ?? ''
50
52
  }
@@ -8,6 +8,7 @@
8
8
  import { pascalCase } from 'case-anything'
9
9
  import { type DefaultTreeAdapterMap, parseFragment } from 'parse5'
10
10
  import { TRACE_ATTR } from '../../../../component-renderer'
11
+ import { findPreferredSemanticClass } from '../../../../utils/css-class'
11
12
  import { PRESETS_WRAPPER_CLASS_NAME } from '../../utils/mock-generator'
12
13
  import type { CssPropertiesData } from '../css-properties'
13
14
  import { addTextProperties } from '../css-properties'
@@ -169,6 +170,12 @@ function getElementNamePart(element: Element, getElementById: (id: string) => El
169
170
  }
170
171
  }
171
172
 
173
+ const classAttr = getAttribute(element, 'class')
174
+ if (classAttr) {
175
+ const semanticClass = findPreferredSemanticClass(classAttr.split(' '))
176
+ if (semanticClass) return pascalCase(semanticClass)
177
+ }
178
+
172
179
  return normalizeTagName(element.tagName)
173
180
  }
174
181
 
@@ -0,0 +1,48 @@
1
+ /**
2
+ * A lowercase kebab-case word: starts with a letter, followed by lowercase
3
+ * letters, digits, or hyphens. e.g. `menu`, `item-label`, `foo2`
4
+ */
5
+ const KEBAB_WORD = '[a-z][a-z0-9-]*'
6
+
7
+ /**
8
+ * An optional BEM element suffix: `__` followed by a kebab word.
9
+ * e.g. `__item-label` in `menu__item-label`
10
+ */
11
+ const OPTIONAL_BEM_ELEMENT = `(__${KEBAB_WORD})?`
12
+
13
+ /**
14
+ * A single BEM modifier suffix: `--` followed by a kebab word.
15
+ * e.g. `--primary` in `button--primary`
16
+ */
17
+ const BEM_MODIFIER = `--${KEBAB_WORD}`
18
+
19
+ /**
20
+ * A semantic CSS class name following BEM conventions: a kebab-case block,
21
+ * optionally followed by a `__element` and/or one or more `--modifier` parts.
22
+ * All parts must be lowercase — this rejects CSS Module hash suffixes which
23
+ * contain uppercase letters (e.g. `root__SvhJ-`) or leading underscores
24
+ * (e.g. `_root_x7k9p2`).
25
+ */
26
+ const SEMANTIC_CLASS_PATTERN = new RegExp(`^${KEBAB_WORD}${OPTIONAL_BEM_ELEMENT}(${BEM_MODIFIER})*$`)
27
+
28
+ /**
29
+ * Matches a class that has at least one BEM modifier (`--`).
30
+ * Used to deprioritize modifier classes in favour of the base class.
31
+ */
32
+ const HAS_BEM_MODIFIER = new RegExp(BEM_MODIFIER)
33
+
34
+ export function isGlobalSemanticClass(className: string): boolean {
35
+ return SEMANTIC_CLASS_PATTERN.test(className)
36
+ }
37
+
38
+ /**
39
+ * Picks the best semantic class from a list of class names.
40
+ * Prefers a class without a BEM modifier (e.g. `card__header`) over one with
41
+ * a modifier (e.g. `card__header--active`), since the base class is more
42
+ * stable as a selector or element name. Falls back to the first semantic class
43
+ * found if all candidates have modifiers.
44
+ */
45
+ export function findPreferredSemanticClass(classNames: string[]): string | undefined {
46
+ const semanticClasses = classNames.filter(isGlobalSemanticClass)
47
+ return semanticClasses.find((className) => !HAS_BEM_MODIFIER.test(className)) ?? semanticClasses[0]
48
+ }