diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 05169a7bc2..83f2ae6dc2 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -33,6 +33,10 @@ "types": "./types/index.d.ts", "default": "./src/animate/index.js" }, + "./attachments": { + "types": "./types/index.d.ts", + "default": "./src/attachments/index.js" + }, "./compiler": { "types": "./types/index.d.ts", "require": "./compiler/index.js", diff --git a/packages/svelte/scripts/generate-types.js b/packages/svelte/scripts/generate-types.js index d44afe8205..1f718e9b5b 100644 --- a/packages/svelte/scripts/generate-types.js +++ b/packages/svelte/scripts/generate-types.js @@ -30,6 +30,7 @@ await createBundle({ [pkg.name]: `${dir}/src/index.d.ts`, [`${pkg.name}/action`]: `${dir}/src/action/public.d.ts`, [`${pkg.name}/animate`]: `${dir}/src/animate/public.d.ts`, + [`${pkg.name}/attachments`]: `${dir}/src/attachments/public.d.ts`, [`${pkg.name}/compiler`]: `${dir}/src/compiler/public.d.ts`, [`${pkg.name}/easing`]: `${dir}/src/easing/index.js`, [`${pkg.name}/legacy`]: `${dir}/src/legacy/legacy-client.js`, diff --git a/packages/svelte/src/attachments/index.js b/packages/svelte/src/attachments/index.js new file mode 100644 index 0000000000..6a66024c86 --- /dev/null +++ b/packages/svelte/src/attachments/index.js @@ -0,0 +1,4 @@ +export { + create_attachment_key as createAttachmentKey, + is_attachment_key as isAttachmentKey +} from '../internal/client/dom/elements/attachments.js'; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js index 75cd1a4466..2b9c47475b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js @@ -257,7 +257,7 @@ export function build_component(node, component_name, context, anchor = context. push_prop( b.prop( 'get', - b.call('Symbol', b.literal('@attach')), + b.call('$.create_attachment_key'), /** @type {Expression} */ (context.visit(attribute.expression)), true ) diff --git a/packages/svelte/src/internal/client/dom/elements/attachments.js b/packages/svelte/src/internal/client/dom/elements/attachments.js index c7299c3716..2bfa84d305 100644 --- a/packages/svelte/src/internal/client/dom/elements/attachments.js +++ b/packages/svelte/src/internal/client/dom/elements/attachments.js @@ -1,5 +1,24 @@ import { effect } from '../../reactivity/effects.js'; +const key = `@attach-${Math.random().toString(36).slice(2)}`; +const name = `Symbol(${key})`; + +// TODO this feels a bit belt-and-braces to me, tbh — are we sure we need it? +/** + * Creates a `Symbol` that Svelte recognises as an attachment key + */ +export function create_attachment_key() { + return Symbol(key); +} + +/** + * Returns `true` if the symbol was created with `createAttachmentKey` + * @param {string | symbol} key + */ +export function is_attachment_key(key) { + return typeof key === 'symbol' && key.toString() === name; +} + /** * @param {Element} node * @param {() => (node: Element) => void} get_fn diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 5cc1286662..2f3e301502 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -13,7 +13,7 @@ import { set_active_effect, set_active_reaction } from '../../runtime.js'; -import { attach } from './attachments.js'; +import { attach, is_attachment_key } from './attachments.js'; /** * The value/checked attribute in the template actually corresponds to the defaultValue property, so we need @@ -242,8 +242,8 @@ export function set_custom_element_data(node, prop, value) { /** * Spreads attributes onto a DOM element, taking into account the currently set attributes * @param {Element & ElementCSSInlineStyle} element - * @param {Record | undefined} prev - * @param {Record} next New attributes - this function mutates this object + * @param {Record | undefined} prev + * @param {Record} next New attributes - this function mutates this object * @param {string} [css_hash] * @param {boolean} [preserve_attribute_case] * @param {boolean} [is_custom_element] @@ -409,7 +409,9 @@ export function set_attributes( } for (let symbol of Object.getOwnPropertySymbols(next)) { - attach(element, () => next[symbol]); + if (is_attachment_key(symbol)) { + attach(element, () => next[symbol]); + } } return current; diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 05135dc764..95cf6a9958 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -27,7 +27,7 @@ export { element } from './dom/blocks/svelte-element.js'; export { head } from './dom/blocks/svelte-head.js'; export { append_styles } from './dom/css.js'; export { action } from './dom/elements/actions.js'; -export { attach } from './dom/elements/attachments.js'; +export { attach, create_attachment_key, is_attachment_key } from './dom/elements/attachments.js'; export { remove_input_defaults, set_attribute, diff --git a/packages/svelte/tests/runtime-runes/samples/attachment-spread/main.svelte b/packages/svelte/tests/runtime-runes/samples/attachment-spread/main.svelte index 804c9cfede..dbd8c47ada 100644 --- a/packages/svelte/tests/runtime-runes/samples/attachment-spread/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/attachment-spread/main.svelte @@ -1,6 +1,8 @@