diff --git a/.changeset/dirty-sloths-move.md b/.changeset/dirty-sloths-move.md new file mode 100644 index 0000000000..07436a0b2e --- /dev/null +++ b/.changeset/dirty-sloths-move.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: consistently set value to blank string when value attribute is undefined diff --git a/.changeset/large-islands-cover.md b/.changeset/large-islands-cover.md new file mode 100644 index 0000000000..969d2fc254 --- /dev/null +++ b/.changeset/large-islands-cover.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: optimise || expressions in template diff --git a/.changeset/strange-hairs-trade.md b/.changeset/strange-hairs-trade.md new file mode 100644 index 0000000000..192e5ef3d1 --- /dev/null +++ b/.changeset/strange-hairs-trade.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: expand boolean attribute support diff --git a/.changeset/two-doors-exercise.md b/.changeset/two-doors-exercise.md new file mode 100644 index 0000000000..b41e582ae7 --- /dev/null +++ b/.changeset/two-doors-exercise.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: add check for `is` attribute to correctly detect custom elements diff --git a/.github/workflows/docs-preview-create-request.yml b/.github/workflows/docs-preview-create-request.yml deleted file mode 100644 index f57766dc36..0000000000 --- a/.github/workflows/docs-preview-create-request.yml +++ /dev/null @@ -1,26 +0,0 @@ -# https://github.com/sveltejs/svelte.dev/blob/main/apps/svelte.dev/scripts/sync-docs/README.md -name: Docs preview create request - -on: - pull_request_target: - branches: - - main - -jobs: - dispatch: - runs-on: ubuntu-latest - steps: - - name: Repository Dispatch - uses: peter-evans/repository-dispatch@v3 - with: - token: ${{ secrets.SYNC_REQUEST_TOKEN }} - repository: sveltejs/svelte.dev - event-type: docs-preview-create - client-payload: |- - { - "package": "svelte", - "repo": "${{ github.repository }}", - "owner": "${{ github.event.pull_request.head.repo.owner.login }}", - "branch": "${{ github.event.pull_request.head.ref }}", - "pr": ${{ github.event.pull_request.number }} - } diff --git a/.github/workflows/docs-preview-delete-request.yml b/.github/workflows/docs-preview-delete-request.yml deleted file mode 100644 index 4eb0e996a6..0000000000 --- a/.github/workflows/docs-preview-delete-request.yml +++ /dev/null @@ -1,27 +0,0 @@ -# https://github.com/sveltejs/svelte.dev/blob/main/apps/svelte.dev/scripts/sync-docs/README.md -name: Docs preview delete request - -on: - pull_request_target: - branches: - - main - types: [closed] - -jobs: - dispatch: - runs-on: ubuntu-latest - steps: - - name: Repository Dispatch - uses: peter-evans/repository-dispatch@v3 - with: - token: ${{ secrets.SYNC_REQUEST_TOKEN }} - repository: sveltejs/svelte.dev - event-type: docs-preview-delete - client-payload: |- - { - "package": "svelte", - "repo": "${{ github.repository }}", - "owner": "${{ github.event.pull_request.head.repo.owner.login }}", - "branch": "${{ github.event.pull_request.head.ref }}", - "pr": ${{ github.event.pull_request.number }} - } diff --git a/.github/workflows/sync-request.yml b/.github/workflows/sync-request.yml deleted file mode 100644 index de2ce77692..0000000000 --- a/.github/workflows/sync-request.yml +++ /dev/null @@ -1,22 +0,0 @@ -# https://github.com/sveltejs/svelte.dev/blob/main/apps/svelte.dev/scripts/sync-docs/README.md -name: Sync request - -on: - push: - branches: - - main - -jobs: - dispatch: - runs-on: ubuntu-latest - steps: - - name: Repository Dispatch - uses: peter-evans/repository-dispatch@v3 - with: - token: ${{ secrets.SYNC_REQUEST_TOKEN }} - repository: sveltejs/svelte.dev - event-type: sync-request - client-payload: |- - { - "package": "svelte" - } 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 7c22f3c7bc..2e61ca8abe 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 @@ -227,7 +227,7 @@ export function RegularElement(node, context) { node_id, attributes_id, (node.metadata.svg || node.metadata.mathml || is_custom_element_node(node)) && b.true, - node.name.includes('-') && b.true, + is_custom_element_node(node) && b.true, context.state ); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js index 0b4ac87342..5bc3041ca4 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js @@ -4,6 +4,7 @@ import { cannot_be_set_statically } from '../../../../../../utils.js'; import { is_event_attribute, is_text_attribute } from '../../../../../utils/ast.js'; import * as b from '../../../../../utils/builders.js'; +import { is_custom_element_node } from '../../../../nodes.js'; import { build_template_chunk } from './utils.js'; /** @@ -128,7 +129,7 @@ export function process_children(nodes, initial, is_element, { visit, state }) { function is_static_element(node, state) { if (node.type !== 'RegularElement') return false; if (node.fragment.metadata.dynamic) return false; - if (node.name.includes('-')) return false; // we're setting all attributes on custom elements through properties + if (is_custom_element_node(node)) return false; // we're setting all attributes on custom elements through properties for (const attribute of node.attributes) { if (attribute.type !== 'Attribute') { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js index 077ced10c2..8746143408 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js @@ -126,22 +126,22 @@ export function build_template_chunk( // extra work in the template_effect (instead we do the work in set_text). return { value, has_state }; } else { - let expression = value; - // only add nullish coallescence if it hasn't been added already - if (value.type === 'LogicalExpression' && value.operator === '??') { - const { right } = value; - // `undefined` isn't a Literal (due to pre-ES5 shenanigans), so the only nullish literal is `null` - // however, you _can_ make a variable called `undefined` in a Svelte component, so we can't just treat it the same way - if (right.type !== 'Literal') { - expression = b.logical('??', value, b.literal('')); - } else if (right.value === null) { - // if they do something weird like `stuff ?? null`, replace `null` with empty string - value.right = b.literal(''); + // add `?? ''` where necessary (TODO optimise more cases) + if ( + value.type === 'LogicalExpression' && + value.right.type === 'Literal' && + (value.operator === '??' || value.operator === '||') + ) { + // `foo ?? null` -=> `foo ?? ''` + // otherwise leave the expression untouched + if (value.right.value === null) { + value = { ...value, right: b.literal('') }; } } else { - expression = b.logical('??', value, b.literal('')); + value = b.logical('??', value, b.literal('')); } - expressions.push(expression); + + expressions.push(value); } quasi = b.quasi('', i + 1 === values.length); diff --git a/packages/svelte/src/compiler/phases/nodes.js b/packages/svelte/src/compiler/phases/nodes.js index b1e13e5cff..e92d2d0893 100644 --- a/packages/svelte/src/compiler/phases/nodes.js +++ b/packages/svelte/src/compiler/phases/nodes.js @@ -23,10 +23,14 @@ export function is_element_node(node) { /** * @param {AST.RegularElement | AST.SvelteElement} node - * @returns {node is AST.RegularElement} + * @returns {boolean} */ export function is_custom_element_node(node) { - return node.type === 'RegularElement' && node.name.includes('-'); + return ( + node.type === 'RegularElement' && + (node.name.includes('-') || + node.attributes.some((attr) => attr.type === 'Attribute' && attr.name === 'is')) + ); } /** diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index a2fffe8696..eab27e6c02 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -68,14 +68,14 @@ export function set_value(element, value) { // treat null and undefined the same for the initial value value ?? undefined) || // @ts-expect-error - // `progress` elements always need their value set when its `0` + // `progress` elements always need their value set when it's `0` (element.value === value && (value !== 0 || element.nodeName !== 'PROGRESS')) ) { return; } // @ts-expect-error - element.value = value; + element.value = value ?? ''; } /** diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js index b16c0551f1..e8e1bc224c 100644 --- a/packages/svelte/src/utils.js +++ b/packages/svelte/src/utils.js @@ -170,7 +170,10 @@ const DOM_BOOLEAN_ATTRIBUTES = [ 'reversed', 'seamless', 'selected', - 'webkitdirectory' + 'webkitdirectory', + 'defer', + 'disablepictureinpicture', + 'disableremoteplayback' ]; /** @@ -197,7 +200,10 @@ const ATTRIBUTE_ALIASES = { defaultvalue: 'defaultValue', defaultchecked: 'defaultChecked', srcobject: 'srcObject', - novalidate: 'noValidate' + novalidate: 'noValidate', + allowfullscreen: 'allowFullscreen', + disablepictureinpicture: 'disablePictureInPicture', + disableremoteplayback: 'disableRemotePlayback' }; /** @@ -219,7 +225,11 @@ const DOM_PROPERTIES = [ 'volume', 'defaultValue', 'defaultChecked', - 'srcObject' + 'srcObject', + 'noValidate', + 'allowFullscreen', + 'disablePictureInPicture', + 'disableRemotePlayback' ]; /** diff --git a/packages/svelte/tests/runtime-runes/samples/attribute-boolean-case-insensitivity/_config.js b/packages/svelte/tests/runtime-runes/samples/attribute-boolean-case-insensitivity/_config.js new file mode 100644 index 0000000000..d0b8a421b3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/attribute-boolean-case-insensitivity/_config.js @@ -0,0 +1,60 @@ +import { test } from '../../test'; + +export default test({ + // JSDOM lacks support for some of these attributes, so we'll skip it for now. + // + // See: + // - `async`: https://github.com/jsdom/jsdom/issues/1564 + // - `nomodule`: https://github.com/jsdom/jsdom/issues/2475 + // - `autofocus`: https://github.com/jsdom/jsdom/issues/3041 + // - `inert`: https://github.com/jsdom/jsdom/issues/3605 + // - etc...: https://github.com/jestjs/jest/issues/139#issuecomment-592673550 + skip_mode: ['client'], + + html: ` + +
+ + + +