diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-template/template.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-template/template.js index f546ce4962..e181419a9e 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-template/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-template/template.js @@ -137,7 +137,7 @@ function objectify(item) { attributes.properties.push( b.prop( 'init', - b.key(fix_attribute_casing(key)), + b.key(item.is_html ? fix_attribute_casing(key) : key), value === undefined ? b.void0 : b.literal(value) ) ); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js index 468eadca87..5a74f5a115 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js @@ -235,7 +235,7 @@ export function RegularElement(node, context) { continue; } - const name = get_attribute_name(node, attribute); + const name = get_attribute_name(node, attribute, !!context.state.analysis.custom_renderer); if ( !is_custom_element && diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js index 58ed88855f..d2d14d7da1 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js @@ -132,9 +132,10 @@ export function build_attribute_value(value, context, memoize = (value) => value /** * @param {AST.RegularElement | AST.SvelteElement} element * @param {AST.Attribute} attribute + * @param {boolean} [custom_renderer] */ -export function get_attribute_name(element, attribute) { - if (!element.metadata.svg && !element.metadata.mathml) { +export function get_attribute_name(element, attribute, custom_renderer) { + if (!custom_renderer && !element.metadata.svg && !element.metadata.mathml) { return normalize_attribute(attribute.name); } diff --git a/packages/svelte/src/internal/client/dom/operations.js b/packages/svelte/src/internal/client/dom/operations.js index b5acb544bf..4539d12af5 100644 --- a/packages/svelte/src/internal/client/dom/operations.js +++ b/packages/svelte/src/internal/client/dom/operations.js @@ -579,7 +579,7 @@ export function set_element_checked(element, checked) { */ export function set_element_default_value(element, value) { if (renderer) { - renderer.setAttribute(element, 'defaultvalue', value); + renderer.setAttribute(element, 'defaultValue', value); return; } // @ts-expect-error @@ -599,9 +599,9 @@ export function set_element_default_value(element, value) { export function set_element_default_checked(element, checked) { if (renderer) { if (checked) { - renderer.setAttribute(element, 'defaultchecked', ''); + renderer.setAttribute(element, 'defaultChecked', ''); } else { - renderer.removeAttribute(element, 'defaultchecked'); + renderer.removeAttribute(element, 'defaultChecked'); } return; } diff --git a/packages/svelte/tests/custom-renderers/samples/attribute-casing/_config.js b/packages/svelte/tests/custom-renderers/samples/attribute-casing/_config.js new file mode 100644 index 0000000000..a44fc4ae1f --- /dev/null +++ b/packages/svelte/tests/custom-renderers/samples/attribute-casing/_config.js @@ -0,0 +1,37 @@ +import { test } from '../../test'; + +export default test({ + test({ assert, target }) { + const elements = target.children.filter((/** @type {any} */ n) => n.type === 'element'); + + assert.equal(elements.length, 4); + + // Static camelCase attribute should preserve casing + const div1 = elements[0]; + assert.equal(div1.name, 'div'); + assert.equal(div1.attributes['dataColor'], 'red'); + // Should NOT have a lowercased version + assert.equal(div1.attributes['datacolor'], undefined); + + // Dynamic camelCase attribute should preserve casing + const div2 = elements[1]; + assert.equal(div2.name, 'div'); + assert.equal(div2.attributes['viewBox'], '0 0 100 100'); + // Should NOT have a lowercased version + assert.equal(div2.attributes['viewbox'], undefined); + + // Static tabIndex should preserve casing + const span = elements[2]; + assert.equal(span.name, 'span'); + assert.equal(span.attributes['tabIndex'], '0'); + // Should NOT have a lowercased version + assert.equal(span.attributes['tabindex'], undefined); + + // Spread camelCase attributes should preserve casing + const p = elements[3]; + assert.equal(p.name, 'p'); + assert.equal(p.attributes['dataValue'], 'spread'); + // Should NOT have a lowercased version + assert.equal(p.attributes['datavalue'], undefined); + } +}); diff --git a/packages/svelte/tests/custom-renderers/samples/attribute-casing/main.svelte b/packages/svelte/tests/custom-renderers/samples/attribute-casing/main.svelte new file mode 100644 index 0000000000..ef6e7f2907 --- /dev/null +++ b/packages/svelte/tests/custom-renderers/samples/attribute-casing/main.svelte @@ -0,0 +1,9 @@ + + +
static camelCase
+
dynamic camelCase
+static tabIndex +

spread camelCase

diff --git a/packages/svelte/tests/custom-renderers/samples/special-attributes/_config.js b/packages/svelte/tests/custom-renderers/samples/special-attributes/_config.js index 34ff2fe38c..4b839e5ad2 100644 --- a/packages/svelte/tests/custom-renderers/samples/special-attributes/_config.js +++ b/packages/svelte/tests/custom-renderers/samples/special-attributes/_config.js @@ -27,16 +27,16 @@ export default test({ assert.equal(input_checked.attributes['type'], 'checkbox'); assert.equal(input_checked.attributes['checked'], ''); - // Input 3: value="fixed" defaultvalue="default_val" + // Input 3: value="fixed" defaultValue="default_val" const input_default_value = inputs[2]; assert.equal(input_default_value.attributes['value'], 'fixed'); - assert.equal(input_default_value.attributes['defaultvalue'], 'default_val'); + assert.equal(input_default_value.attributes['defaultValue'], 'default_val'); - // Input 4: type="checkbox" checked="" defaultchecked="" + // Input 4: type="checkbox" checked="" defaultChecked="" const input_default_checked = inputs[3]; assert.equal(input_default_checked.attributes['type'], 'checkbox'); assert.equal(input_default_checked.attributes['checked'], ''); - assert.equal(input_default_checked.attributes['defaultchecked'], ''); + assert.equal(input_default_checked.attributes['defaultChecked'], ''); // Input 5: spread attributes const input_spread = inputs[4]; @@ -56,12 +56,12 @@ export default test({ // Input 2: checked should be removed assert.equal(input_checked.attributes['checked'], undefined); - // Input 3: defaultvalue="new_default", value still "fixed" + // Input 3: defaultValue="new_default", value still "fixed" assert.equal(input_default_value.attributes['value'], 'fixed'); - assert.equal(input_default_value.attributes['defaultvalue'], 'new_default'); + assert.equal(input_default_value.attributes['defaultValue'], 'new_default'); - // Input 4: defaultchecked should be removed - assert.equal(input_default_checked.attributes['defaultchecked'], undefined); + // Input 4: defaultChecked should be removed + assert.equal(input_default_checked.attributes['defaultChecked'], undefined); // Input 5: spread attributes should update value to "world" assert.equal(input_spread.attributes['value'], 'world');