diff --git a/site/content/docs/03-template-syntax.md b/site/content/docs/03-template-syntax.md index 170c303b8524..0610ed20a86b 100644 --- a/site/content/docs/03-template-syntax.md +++ b/site/content/docs/03-template-syntax.md @@ -1826,6 +1826,7 @@ The `` element provides a place to specify per-component compile * `accessors={false}` — the default * `namespace="..."` — the namespace where this component will be used, most commonly "svg"; use the "foreign" namespace to opt out of case-insensitive attribute names and HTML-specific warnings * `tag="..."` — the name to use when compiling this component as a custom element +* `elementInternals` - adds formAssociated static property for custom elements to participate in forms ```sv diff --git a/site/content/tutorial/16-special-elements/09-svelte-options/text.md b/site/content/tutorial/16-special-elements/09-svelte-options/text.md index 1a0105a09af8..55d77bd692d2 100644 --- a/site/content/tutorial/16-special-elements/09-svelte-options/text.md +++ b/site/content/tutorial/16-special-elements/09-svelte-options/text.md @@ -26,5 +26,6 @@ The options that can be set here are: * `accessors={false}` — the default * `namespace="..."` — the namespace where this component will be used, most commonly `"svg"` * `tag="..."` — the name to use when compiling this component as a custom element +* `elementInternals` - adds formAssociated static property for custom elements to participate in forms Consult the [API reference](/docs) for more information on these options. diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index e87cf6218af2..d0a96737e97f 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -45,6 +45,7 @@ interface ComponentOptions { immutable?: boolean; accessors?: boolean; preserveWhitespace?: boolean; + elementInternals?: boolean; } const regex_leading_directory_separator = /^[/\\]/; @@ -1570,6 +1571,17 @@ function process_component_options(component: Component, nodes) { break; } + case 'elementInternals': { + const value = get_value(attribute, compiler_errors.invalid_attribute_value(name)); + + if (typeof value !== 'boolean') { + return component.error(attribute, compiler_errors.invalid_attribute_value(name)); + } + + component_options.elementInternals = value; + break; + } + case 'namespace': { const ns = get_value(attribute, compiler_errors.invalid_namespace_attribute); diff --git a/src/compiler/compile/compiler_errors.ts b/src/compiler/compile/compiler_errors.ts index bad3911673fc..54d797a6248d 100644 --- a/src/compiler/compile/compiler_errors.ts +++ b/src/compiler/compile/compiler_errors.ts @@ -224,7 +224,7 @@ export default { }, invalid_options_attribute: { code: 'invalid-options-attribute', - message: " can only have static 'tag', 'namespace', 'accessors', 'immutable' and 'preserveWhitespace' attributes" + message: " can only have static 'tag', 'namespace', 'accessors', 'immutable', 'preserveWhitespace' and 'elementInternals' attributes" }, css_invalid_global: { code: 'css-invalid-global', diff --git a/src/compiler/compile/index.ts b/src/compiler/compile/index.ts index 7a18231cef9d..b87512bf479b 100644 --- a/src/compiler/compile/index.ts +++ b/src/compiler/compile/index.ts @@ -32,7 +32,8 @@ const valid_options = [ 'loopGuardTimeout', 'preserveComments', 'preserveWhitespace', - 'cssHash' + 'cssHash', + 'elementInternals' ]; const valid_css_values = [ diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 58b7a8317b29..d87eadfa0e61 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -553,6 +553,16 @@ export default function dom( } `[0] as ClassDeclaration; + if (component.component_options.elementInternals) { + declaration.body.body.push({ + type: 'PropertyDefinition', + static: true, + computed: false, + key: { type: 'Identifier', name: 'formAssociated' }, + value: x`true` + }); + } + if (props.length > 0) { declaration.body.body.push({ type: 'MethodDefinition', diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index 4ae53594bb91..56287dde6985 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -179,6 +179,7 @@ export interface CompileOptions { legacy?: boolean; customElement?: boolean; tag?: string; + elementInternals?: boolean; css?: 'injected' | 'external' | 'none' | boolean; loopGuardTimeout?: number; namespace?: string; diff --git a/src/runtime/internal/lifecycle.ts b/src/runtime/internal/lifecycle.ts index e75bbdc501f4..5305bac3640a 100644 --- a/src/runtime/internal/lifecycle.ts +++ b/src/runtime/internal/lifecycle.ts @@ -13,9 +13,9 @@ export function get_current_component() { /** * Schedules a callback to run immediately before the component is updated after any state change. - * + * * The first time the callback runs will be before the initial `onMount` - * + * * https://svelte.dev/docs#run-time-svelte-beforeupdate */ export function beforeUpdate(fn: () => any) { @@ -23,12 +23,12 @@ export function beforeUpdate(fn: () => any) { } /** - * The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM. - * It must be called during the component's initialisation (but doesn't need to live *inside* the component; + * The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM. + * It must be called during the component's initialisation (but doesn't need to live *inside* the component; * it can be called from an external module). - * + * * `onMount` does not run inside a [server-side component](/docs#run-time-server-side-component-api). - * + * * https://svelte.dev/docs#run-time-svelte-onmount */ export function onMount(fn: () => any) { @@ -37,19 +37,19 @@ export function onMount(fn: () => any) { /** * Schedules a callback to run immediately after the component has been updated. - * - * The first time the callback runs will be after the initial `onMount` + * + * The first time the callback runs will be after the initial `onMount` */ export function afterUpdate(fn: () => any) { get_current_component().$$.after_update.push(fn); } -/** +/** * Schedules a callback to run immediately before the component is unmounted. - * - * Out of `onMount`, `beforeUpdate`, `afterUpdate` and `onDestroy`, this is the + * + * Out of `onMount`, `beforeUpdate`, `afterUpdate` and `onDestroy`, this is the * only one that runs inside a server-side component. - * + * * https://svelte.dev/docs#run-time-svelte-ondestroy */ export function onDestroy(fn: () => any) { @@ -61,15 +61,15 @@ export interface DispatchOptions { } /** - * Creates an event dispatcher that can be used to dispatch [component events](/docs#template-syntax-component-directives-on-eventname). + * Creates an event dispatcher that can be used to dispatch [component events](/docs#template-syntax-component-directives-on-eventname). * Event dispatchers are functions that can take two arguments: `name` and `detail`. - * - * Component events created with `createEventDispatcher` create a - * [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent). + * + * Component events created with `createEventDispatcher` create a + * [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent). * These events do not [bubble](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture). - * The `detail` argument corresponds to the [CustomEvent.detail](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail) - * property and can contain any type of data. - * + * The `detail` argument corresponds to the [CustomEvent.detail](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail) + * property and can contain any type of data. + * * https://svelte.dev/docs#run-time-svelte-createeventdispatcher */ export function createEventDispatcher(): < @@ -99,12 +99,12 @@ export function createEventDispatcher(): < } /** - * Associates an arbitrary `context` object with the current component and the specified `key` - * and returns that object. The context is then available to children of the component + * Associates an arbitrary `context` object with the current component and the specified `key` + * and returns that object. The context is then available to children of the component * (including slotted content) with `getContext`. - * - * Like lifecycle functions, this must be called during component initialisation. - * + * + * Like lifecycle functions, this must be called during component initialisation. + * * https://svelte.dev/docs#run-time-svelte-setcontext */ export function setContext(key, context: T): T { @@ -113,9 +113,9 @@ export function setContext(key, context: T): T { } /** - * Retrieves the context that belongs to the closest parent component with the specified `key`. - * Must be called during component initialisation. - * + * Retrieves the context that belongs to the closest parent component with the specified `key`. + * Must be called during component initialisation. + * * https://svelte.dev/docs#run-time-svelte-getcontext */ export function getContext(key): T { @@ -123,10 +123,10 @@ export function getContext(key): T { } /** - * Retrieves the whole context map that belongs to the closest parent component. - * Must be called during component initialisation. Useful, for example, if you + * Retrieves the whole context map that belongs to the closest parent component. + * Must be called during component initialisation. Useful, for example, if you * programmatically create a component and want to pass the existing context to it. - * + * * https://svelte.dev/docs#run-time-svelte-getallcontexts */ export function getAllContexts = Map>(): T { @@ -134,13 +134,13 @@ export function getAllContexts = Map>(): T { } /** - * Checks whether a given `key` has been set in the context of a parent component. - * Must be called during component initialisation. - * + * Checks whether a given `key` has been set in the context of a parent component. + * Must be called during component initialisation. + * * https://svelte.dev/docs#run-time-svelte-hascontext */ export function hasContext(key): boolean { - return get_current_component().$$.context.has(key); + return get_current_component().$$.context.has(key); } // TODO figure out if we still want to support @@ -154,3 +154,23 @@ export function bubble(component, event) { callbacks.slice().forEach(fn => fn.call(this, event)); } } + +// Element internals not in TS3.x.x +declare global { + // eslint-disable-next-line + interface ElementInternals {} + interface HTMLElement { + attachInternals(): ElementInternals; + } +} + +/** + * Creates element internals for Web Components to participate in forms. + */ +export function createCustomElementInternals(): ElementInternals { + const component = get_current_component(); + if (!(component instanceof HTMLElement)) { + throw ReferenceError('NotHTMLElement'); + } + return component.attachInternals(); +}