jsx > Javascript Test > normalizeLayerNames 1`] = `
+"/**
+ * Usage:
+ *
+ *
+ *
+ */
+class MyNormalizedLayerNamesComponent extends HTMLElement {
+ get _root() {
+ return this.shadowRoot || this;
+ }
+
+ constructor() {
+ super();
+ const self = this;
+
+ this.state = {};
+ if (!this.props) {
+ this.props = {};
+ }
+
+ // used to keep track of all nodes created by show/for
+ this.nodesToDestroy = [];
+ // batch updates
+ this.pendingUpdate = false;
+
+ if (undefined) {
+ this.attachShadow({ mode: \\"open\\" });
+ }
+ }
+
+ destroyAnyNodes() {
+ // destroy current view template refs before rendering again
+ this.nodesToDestroy.forEach((el) => el.remove());
+ this.nodesToDestroy = [];
+ }
+
+ connectedCallback() {
+ this._root.innerHTML = \`
+
+ Emoji
+ Dashes
+ CamelCase
+ Special chars
+ Special chars with dashes
+ Single Number
+
+ Multiple Numbers
+
+
+ Chars with numbers at end
+
+
+ Chars with numbers at start
+
+
+ Numnbers separated by dash
+
+ Emoji
+
+ Number
+
+
+ \`;
+ this.pendingUpdate = true;
+
+ this.render();
+ this.onMount();
+ this.pendingUpdate = false;
+ this.update();
+ }
+
+ onMount() {}
+
+ onUpdate() {}
+
+ update() {
+ if (this.pendingUpdate === true) {
+ return;
+ }
+ this.pendingUpdate = true;
+ this.render();
+ this.onUpdate();
+ this.pendingUpdate = false;
+ }
+
+ render() {
+ // re-rendering needs to ensure that all nodes generated by for/show are refreshed
+ this.destroyAnyNodes();
+ this.updateBindings();
+ }
+
+ updateBindings() {}
+
+ // Helper to render content
+ renderTextNode(el, text) {
+ const textNode = document.createTextNode(text);
+ if (el?.scope) {
+ textNode.scope = el.scope;
+ }
+ if (el?.context) {
+ textNode.context = el.context;
+ }
+ el.after(textNode);
+ this.nodesToDestroy.push(el.nextSibling);
+ }
+}
+
+customElements.define(
+ \\"my-normalized-layer-names-component\\",
+ MyNormalizedLayerNamesComponent
+);
+"
+`;
+
exports[`webcomponent > jsx > Javascript Test > onEvent 1`] = `
"/**
* Usage:
@@ -29196,6 +29330,144 @@ customElements.define(\\"nested-styles\\", NestedStyles);
"
`;
+exports[`webcomponent > jsx > Typescript Test > normalizeLayerNames 1`] = `
+"export interface MyNormalizedLayerNamesComponentProps {
+ id: string;
+}
+
+/**
+ * Usage:
+ *
+ *
+ *
+ */
+class MyNormalizedLayerNamesComponent extends HTMLElement {
+ get _root() {
+ return this.shadowRoot || this;
+ }
+
+ constructor() {
+ super();
+ const self = this;
+
+ this.state = {};
+ if (!this.props) {
+ this.props = {};
+ }
+
+ // used to keep track of all nodes created by show/for
+ this.nodesToDestroy = [];
+ // batch updates
+ this.pendingUpdate = false;
+
+ if (undefined) {
+ this.attachShadow({ mode: \\"open\\" });
+ }
+ }
+
+ destroyAnyNodes() {
+ // destroy current view template refs before rendering again
+ this.nodesToDestroy.forEach((el) => el.remove());
+ this.nodesToDestroy = [];
+ }
+
+ connectedCallback() {
+ this._root.innerHTML = \`
+
+ Emoji
+ Dashes
+ CamelCase
+ Special chars
+ Special chars with dashes
+ Single Number
+
+ Multiple Numbers
+
+
+ Chars with numbers at end
+
+
+ Chars with numbers at start
+
+
+ Numnbers separated by dash
+
+ Emoji
+
+ Number
+
+
+ \`;
+ this.pendingUpdate = true;
+
+ this.render();
+ this.onMount();
+ this.pendingUpdate = false;
+ this.update();
+ }
+
+ onMount() {}
+
+ onUpdate() {}
+
+ update() {
+ if (this.pendingUpdate === true) {
+ return;
+ }
+ this.pendingUpdate = true;
+ this.render();
+ this.onUpdate();
+ this.pendingUpdate = false;
+ }
+
+ render() {
+ // re-rendering needs to ensure that all nodes generated by for/show are refreshed
+ this.destroyAnyNodes();
+ this.updateBindings();
+ }
+
+ updateBindings() {}
+
+ // Helper to render content
+ renderTextNode(el, text) {
+ const textNode = document.createTextNode(text);
+ if (el?.scope) {
+ textNode.scope = el.scope;
+ }
+ if (el?.context) {
+ textNode.context = el.context;
+ }
+ el.after(textNode);
+ this.nodesToDestroy.push(el.nextSibling);
+ }
+}
+
+customElements.define(
+ \\"my-normalized-layer-names-component\\",
+ MyNormalizedLayerNamesComponent
+);
+"
+`;
+
exports[`webcomponent > jsx > Typescript Test > onEvent 1`] = `
"/**
* Usage:
diff --git a/packages/core/src/__tests__/data/normalize-layer-names.raw.tsx b/packages/core/src/__tests__/data/normalize-layer-names.raw.tsx
new file mode 100644
index 0000000000..ab6db6cdaf
--- /dev/null
+++ b/packages/core/src/__tests__/data/normalize-layer-names.raw.tsx
@@ -0,0 +1,30 @@
+export default function MyNormalizedLayerNamesComponent(props: { id: string }) {
+ return (
+
+ Emoji
+ Dashes
+ CamelCase
+ Special chars
+ Special chars with dashes
+
+ Single Number
+
+
+ Multiple Numbers
+
+
+ Chars with numbers at end
+
+
+ Chars with numbers at start
+
+
+ Numnbers separated by dash
+
+ Emoji
+
+ Number
+
+
+ );
+}
diff --git a/packages/core/src/__tests__/test-generator.ts b/packages/core/src/__tests__/test-generator.ts
index 44222fe885..998400bd29 100644
--- a/packages/core/src/__tests__/test-generator.ts
+++ b/packages/core/src/__tests__/test-generator.ts
@@ -40,6 +40,7 @@ const basicRefAttributePassing = getRawFile('./data/ref/basic-ref-attribute-pass
const basicRefAttributePassingCustomRef = getRawFile(
'./data/ref/basic-ref-attribute-passing-custom-ref.raw.tsx',
);
+const normalizeLayerNames = getRawFile('./data/normalize-layer-names.raw.tsx');
const basicForwardRef = getRawFile('./data/ref/basic-forwardRef.raw.tsx');
const basicForwardRefMetadata = getRawFile('./data/ref/basic-forwardRef-metadata.raw.tsx');
const basicRefPrevious = getRawFile('./data/ref/basic-ref-usePrevious.raw.tsx');
@@ -251,6 +252,7 @@ const BASIC_TESTS: Tests = {
'use-style': useStyle,
'use-style-and-css': useStyleAndCss,
layerName,
+ normalizeLayerNames,
styleClassAndCss,
stylePropClassAndCss,
'use-style-outside-component': useStyleOutsideComponent,
diff --git a/packages/core/src/helpers/styles/collect-css.ts b/packages/core/src/helpers/styles/collect-css.ts
index 0378839840..c1a7a6fcb0 100644
--- a/packages/core/src/helpers/styles/collect-css.ts
+++ b/packages/core/src/helpers/styles/collect-css.ts
@@ -29,6 +29,27 @@ const updateClassForNode = (item: MitosisNode, className: string) => {
}
};
+export function normalizeName(name: string | undefined): string {
+ if (!name || name.trim() === '' || name.match(/^[^a-zA-Z0-9]*$/)) {
+ return '';
+ }
+
+ // Clean the name first
+ const cleaned = name.replace(/[^a-zA-Z0-9\-_]/g, '');
+
+ // If pure numeric or only contains numbers and dashes
+ if (cleaned.match(/^[0-9-]+$/)) {
+ // Extract just the numbers and format as css{number}
+ const numbers = cleaned.replace(/[^0-9]/g, '');
+ return `css${numbers}`;
+ }
+
+ // Remove leading numbers and dashes for other cases
+ const normalized = cleaned.replace(/^[0-9-]+(?=[a-zA-Z])/, '');
+
+ return normalized || '';
+}
+
const collectStyles = (
json: MitosisComponent,
options: CollectStyleOptions = {},
@@ -44,15 +65,13 @@ const collectStyles = (
const value = parseCssObject(item.bindings.css?.code as string);
delete item.bindings.css;
- // Clean the name by keeping only alphanumeric characters, underscores, and dashes
- const cleanedName = item.properties.$name?.replace(/[^a-zA-Z0-9_-]/g, '');
- // Remove leading numbers or dashes
- const normalizedName = cleanedName?.replace(/^[0-9-]+/, '');
+ const normalizedName = normalizeName(item.properties.$name);
+
const componentName = normalizedName
? dashCase(normalizedName)
: /^h\d$/.test(item.name || '') // don't dashcase h1 into h-1
? item.name
- : dashCase(item.name || 'div');
+ : dashCase(normalizeName(item.name) || 'div');
const classNameWPrefix = `${componentName}${options.prefix ? `-${options.prefix}` : ''}`;
diff --git a/packages/core/src/helpers/styles/collect-styled-components.ts b/packages/core/src/helpers/styles/collect-styled-components.ts
index f735f214b5..5cc42d57e9 100644
--- a/packages/core/src/helpers/styles/collect-styled-components.ts
+++ b/packages/core/src/helpers/styles/collect-styled-components.ts
@@ -5,6 +5,7 @@ import { MitosisComponent } from '../../types/mitosis-component';
import { capitalize } from '../capitalize';
import { isMitosisNode } from '../is-mitosis-node';
import { isUpperCase } from '../is-upper-case';
+import { normalizeName } from './collect-css';
import {
getNestedSelectors,
getStylesOnly,
@@ -26,14 +27,14 @@ export const collectStyledComponents = (json: MitosisComponent): string => {
delete item.bindings.css;
const normalizedNameProperty = item.properties.$name
- ? capitalize(camelCase(item.properties.$name.replace(/[^a-z]/gi, '')))
+ ? capitalize(camelCase(normalizeName(item.properties.$name)))
: null;
const componentName = normalizedNameProperty
? normalizedNameProperty
: /^h\d$/.test(item.name || '')
? item.name
- : capitalize(camelCase(item.name || 'div'));
+ : capitalize(camelCase(normalizeName(item.name) || 'div'));
const index = (componentIndexes[componentName] =
(componentIndexes[componentName] || 0) + 1);