chore: improve constants (#12695)

* chore: consistently uppercase constants

* expose helpers

* use helpers

* tidy up

* why was that there

* fix

* tweak

* fix

* tidy up

* cheat, to preserve treeshakeability
pull/12696/head
Rich Harris 3 months ago committed by GitHub
parent f984307f9c
commit 79ef645151
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,6 +1,6 @@
/** @import { ObjectExpression } from 'estree' */
/** @import { SvelteOptionsRaw, Root, SvelteOptions } from '#compiler' */
import { namespace_mathml, namespace_svg } from '../../../../constants.js';
import { NAMESPACE_MATHML, NAMESPACE_SVG } from '../../../../constants.js';
import * as e from '../../../errors.js';
const regex_valid_tag_name = /^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/;
@ -155,9 +155,9 @@ export default function read_options(node) {
case 'namespace': {
const value = get_static_value(attribute);
if (value === namespace_svg) {
if (value === NAMESPACE_SVG) {
component_options.namespace = 'svg';
} else if (value === namespace_mathml) {
} else if (value === NAMESPACE_MATHML) {
component_options.namespace = 'mathml';
} else if (
value === 'html' ||

@ -7,13 +7,12 @@ import * as e from '../../errors.js';
import * as w from '../../warnings.js';
import { is_text_attribute } from '../../utils/ast.js';
import * as b from '../../utils/builders.js';
import { ReservedKeywords, Runes } from '../constants.js';
import { Scope, ScopeRoot, create_scopes, get_rune } from '../scope.js';
import check_graph_for_cycles from './utils/check_graph_for_cycles.js';
import { create_attribute } from '../nodes.js';
import { analyze_css } from './css/css-analyze.js';
import { prune } from './css/css-prune.js';
import { hash } from '../../../utils.js';
import { hash, is_rune } from '../../../utils.js';
import { warn_unused } from './css/css-warn.js';
import { extract_svelte_ignore } from '../../utils/extract_svelte_ignore.js';
import { ignore_map, ignore_stack, pop_ignore, push_ignore } from '../../state.js';
@ -203,6 +202,8 @@ function get_component_name(filename) {
return name[0].toUpperCase() + name.slice(1);
}
const RESERVED = ['$$props', '$$restProps', '$$slots'];
/**
* @param {Program} ast
* @param {ValidatedModuleCompileOptions} options
@ -212,7 +213,7 @@ export function analyze_module(ast, options) {
const { scope, scopes } = create_scopes(ast, new ScopeRoot(), false, null);
for (const [name, references] of scope.references) {
if (name[0] !== '$' || ReservedKeywords.includes(name)) continue;
if (name[0] !== '$' || RESERVED.includes(name)) continue;
if (name === '$' || name[1] === '$') {
e.global_reference_invalid(references[0].node, name);
}
@ -257,7 +258,7 @@ export function analyze_component(root, source, options) {
// create synthetic bindings for store subscriptions
for (const [name, references] of module.scope.references) {
if (name[0] !== '$' || ReservedKeywords.includes(name)) continue;
if (name[0] !== '$' || RESERVED.includes(name)) continue;
if (name === '$' || name[1] === '$') {
e.global_reference_invalid(references[0].node, name);
}
@ -269,7 +270,7 @@ export function analyze_component(root, source, options) {
// is referencing a rune and not a global store.
if (
options.runes === false ||
!Runes.includes(/** @type {any} */ (name)) ||
!is_rune(name) ||
(declaration !== null &&
// const state = $state(0) is valid
(get_rune(declaration.initial, instance.scope) === null ||
@ -307,7 +308,7 @@ export function analyze_component(root, source, options) {
if (options.runes !== false) {
if (declaration === null && /[a-z]/.test(store_name[0])) {
e.global_reference_invalid(references[0].node, name);
} else if (declaration !== null && Runes.includes(/** @type {any} */ (name))) {
} else if (declaration !== null && is_rune(name)) {
for (const { node, path } of references) {
if (path.at(-1)?.type === 'CallExpression') {
w.store_rune_conflict(node, store_name);
@ -339,9 +340,7 @@ export function analyze_component(root, source, options) {
const component_name = get_component_name(options.filename ?? 'Component');
const runes =
options.runes ??
Array.from(module.scope.references).some(([name]) => Runes.includes(/** @type {any} */ (name)));
const runes = options.runes ?? Array.from(module.scope.references.keys()).some(is_rune);
// TODO remove all the ?? stuff, we don't need it now that we're validating the config
/** @type {ComponentAnalysis} */

@ -1,8 +1,7 @@
/** @import { ArrowFunctionExpression, Expression, FunctionDeclaration, FunctionExpression } from 'estree' */
/** @import { Attribute, DelegatedEvent, RegularElement } from '#compiler' */
/** @import { Context } from '../types' */
import { DelegatedEvents } from '../../../../constants.js';
import { is_capture_event } from '../../../../utils.js';
import { is_capture_event, is_delegated } from '../../../../utils.js';
import {
get_attribute_chunks,
get_attribute_expression,
@ -60,7 +59,7 @@ export function Attribute(node, context) {
*/
function get_delegated_event(event_name, handler, context) {
// Handle delegated event handlers. Bail-out if not a delegated event.
if (!handler || !DelegatedEvents.includes(event_name)) {
if (!handler || !is_delegated(event_name)) {
return null;
}
@ -119,7 +118,7 @@ function get_delegated_event(event_name, handler, context) {
if (
element.type !== 'RegularElement' ||
element.metadata.has_spread ||
!DelegatedEvents.includes(event_name)
!is_delegated(event_name)
) {
return non_hoistable;
}

@ -9,8 +9,8 @@ import { validate_no_const_assignment } from './shared/utils.js';
import * as e from '../../../errors.js';
import * as w from '../../../warnings.js';
import { binding_properties } from '../../bindings.js';
import { ContentEditableBindings, SVGElements } from '../../constants.js';
import fuzzymatch from '../../1-parse/utils/fuzzymatch.js';
import { is_content_editable_binding, is_svg } from '../../../../utils.js';
/**
* @param {BindDirective} node
@ -197,7 +197,7 @@ export function BindDirective(node, context) {
}
}
if (node.name === 'offsetWidth' && SVGElements.includes(parent.name)) {
if (node.name === 'offsetWidth' && is_svg(parent.name)) {
e.bind_invalid_target(
node,
node.name,
@ -205,7 +205,7 @@ export function BindDirective(node, context) {
);
}
if (ContentEditableBindings.includes(node.name)) {
if (is_content_editable_binding(node.name)) {
const contenteditable = /** @type {Attribute} */ (
parent.attributes.find((a) => a.type === 'Attribute' && a.name === 'contenteditable')
);

@ -1,11 +1,10 @@
/** @import { Expression, Identifier } from 'estree' */
/** @import { SvelteNode } from '#compiler' */
/** @import { Context } from '../types' */
import is_reference from 'is-reference';
import { Runes } from '../../constants.js';
import { should_proxy_or_freeze } from '../../3-transform/client/utils.js';
import * as e from '../../../errors.js';
import * as w from '../../../warnings.js';
import { is_rune } from '../../../../utils.js';
/**
* @param {Identifier} node
@ -34,7 +33,7 @@ export function Identifier(node, context) {
if (context.state.analysis.runes) {
if (
Runes.includes(/** @type {Runes[number]} */ (node.name)) &&
is_rune(node.name) &&
context.state.scope.get(node.name) === null &&
context.state.scope.get(node.name.slice(1)) === null
) {
@ -49,7 +48,7 @@ export function Identifier(node, context) {
current = parent;
parent = /** @type {Expression} */ (context.path[--i]);
if (!Runes.includes(/** @type {Runes[number]} */ (name))) {
if (!is_rune(name)) {
if (name === '$effect.active') {
e.rune_renamed(parent, '$effect.active', '$effect.tracking');
}

@ -1,13 +1,12 @@
/** @import { RegularElement } from '#compiler' */
/** @import { Context } from '../types' */
import { is_void } from '../../../../utils.js';
import { is_mathml, is_svg, is_void } from '../../../../utils.js';
import {
is_tag_valid_with_ancestor,
is_tag_valid_with_parent
} from '../../../../html-tree-validation.js';
import * as e from '../../../errors.js';
import * as w from '../../../warnings.js';
import { MathMLElements, SVGElements } from '../../constants.js';
import { create_attribute } from '../../nodes.js';
import { regex_starts_with_newline } from '../../patterns.js';
import { check_element } from './shared/a11y.js';
@ -92,8 +91,8 @@ export function RegularElement(node, context) {
);
if (context.state.options.namespace !== 'foreign') {
if (SVGElements.includes(node.name)) node.metadata.svg = true;
else if (MathMLElements.includes(node.name)) node.metadata.mathml = true;
node.metadata.svg = is_svg(node.name);
node.metadata.mathml = is_mathml(node.name);
}
if (context.state.parent_element) {
@ -159,7 +158,7 @@ export function RegularElement(node, context) {
context.state.analysis.source[node.end - 2] === '/' &&
context.state.options.namespace !== 'foreign' &&
!is_void(node_name) &&
!SVGElements.includes(node_name)
!is_svg(node_name)
) {
w.element_invalid_self_closing_tag(node, node.name);
}

@ -1,6 +1,6 @@
/** @import { Attribute, SvelteElement, Text } from '#compiler' */
/** @import { Context } from '../types' */
import { namespace_mathml, namespace_svg } from '../../../../constants.js';
import { NAMESPACE_MATHML, NAMESPACE_SVG } from '../../../../constants.js';
import { is_text_attribute } from '../../../utils/ast.js';
import { check_element } from './shared/a11y.js';
import { validate_element } from './shared/element.js';
@ -23,8 +23,8 @@ export function SvelteElement(node, context) {
);
if (xmlns) {
node.metadata.svg = xmlns.value[0].data === namespace_svg;
node.metadata.mathml = xmlns.value[0].data === namespace_mathml;
node.metadata.svg = xmlns.value[0].data === NAMESPACE_SVG;
node.metadata.mathml = xmlns.value[0].data === NAMESPACE_MATHML;
} else {
let i = context.path.length;
while (i--) {

@ -14,9 +14,9 @@ import {
import * as w from '../../../../warnings.js';
import fuzzymatch from '../../../1-parse/utils/fuzzymatch.js';
import { is_event_attribute, is_text_attribute } from '../../../../utils/ast.js';
import { ContentEditableBindings } from '../../../constants.js';
import { walk } from 'zimmerframe';
import { list } from '../../../../utils/string.js';
import { is_content_editable_binding } from '../../../../../utils.js';
const aria_roles = roles_map.keys();
const abstract_roles = aria_roles.filter((role) => roles_map.get(role)?.abstract);
@ -720,10 +720,7 @@ export function check_element(node, state) {
has_contenteditable_attr = true;
}
}
} else if (
attribute.type === 'BindDirective' &&
ContentEditableBindings.includes(attribute.name)
) {
} else if (attribute.type === 'BindDirective' && is_content_editable_binding(attribute.name)) {
has_contenteditable_binding = true;
}
}

@ -4,13 +4,24 @@ import { get_attribute_expression, is_expression_attribute } from '../../../../u
import { regex_illegal_attribute_character } from '../../../patterns.js';
import * as e from '../../../../errors.js';
import * as w from '../../../../warnings.js';
import { EventModifiers } from '../../../constants.js';
import {
validate_attribute,
validate_attribute_name,
validate_slot_attribute
} from './attribute.js';
const EVENT_MODIFIERS = [
'preventDefault',
'stopPropagation',
'stopImmediatePropagation',
'capture',
'once',
'passive',
'nonpassive',
'self',
'trusted'
];
/**
* @param {import('#compiler').RegularElement | SvelteElement} node
* @param {Context} context
@ -122,8 +133,8 @@ export function validate_element(node, context) {
let has_passive_modifier = false;
let conflicting_passive_modifier = '';
for (const modifier of attribute.modifiers) {
if (!EventModifiers.includes(modifier)) {
const list = `${EventModifiers.slice(0, -1).join(', ')} or ${EventModifiers.at(-1)}`;
if (!EVENT_MODIFIERS.includes(modifier)) {
const list = `${EVENT_MODIFIERS.slice(0, -1).join(', ')} or ${EVENT_MODIFIERS.at(-1)}`;
e.event_handler_invalid_modifier(attribute, list);
}
if (modifier === 'passive') {

@ -3,8 +3,12 @@
/** @import { SourceLocation } from '#shared' */
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */
/** @import { Scope } from '../../../scope' */
import { DOMBooleanAttributes } from '../../../../../constants.js';
import { is_void } from '../../../../../utils.js';
import {
is_boolean_attribute,
is_dom_property,
is_load_error_element,
is_void
} from '../../../../../utils.js';
import { escape_html } from '../../../../../escaping.js';
import { dev, is_ignored, locator } from '../../../../state.js';
import {
@ -13,7 +17,6 @@ import {
is_text_attribute
} from '../../../../utils/ast.js';
import * as b from '../../../../utils/builders.js';
import { DOMProperties, LoadErrorElements } from '../../../constants.js';
import { is_custom_element_node } from '../../../nodes.js';
import { clean_nodes, determine_namespace_for_children } from '../../utils.js';
import { serialize_get_binding } from '../utils.js';
@ -130,7 +133,7 @@ export function RegularElement(node, context) {
attributes.push(attribute);
needs_input_reset = true;
needs_content_reset = true;
if (LoadErrorElements.includes(node.name)) {
if (is_load_error_element(node.name)) {
might_need_event_replaying = true;
}
} else if (attribute.type === 'ClassDirective') {
@ -155,7 +158,7 @@ export function RegularElement(node, context) {
) {
has_content_editable_binding = true;
}
} else if (attribute.type === 'UseDirective' && LoadErrorElements.includes(node.name)) {
} else if (attribute.type === 'UseDirective' && is_load_error_element(node.name)) {
might_need_event_replaying = true;
}
context.visit(attribute);
@ -211,7 +214,7 @@ export function RegularElement(node, context) {
if (is_event_attribute(attribute)) {
if (
(attribute.name === 'onload' || attribute.name === 'onerror') &&
LoadErrorElements.includes(node.name)
is_load_error_element(node.name)
) {
might_need_event_replaying = true;
}
@ -238,7 +241,7 @@ export function RegularElement(node, context) {
// to create the elements it needs.
context.state.template.push(
` ${attribute.name}${
DOMBooleanAttributes.includes(name) && literal_value === true
is_boolean_attribute(name) && literal_value === true
? ''
: `="${literal_value === true ? '' : escape_html(literal_value, true)}"`
}`
@ -599,7 +602,7 @@ function serialize_element_attribute_update_assignment(element, node_id, attribu
update = b.stmt(b.call('$.set_value', node_id, value));
} else if (name === 'checked') {
update = b.stmt(b.call('$.set_checked', node_id, value));
} else if (DOMProperties.includes(name)) {
} else if (is_dom_property(name)) {
update = b.stmt(b.assignment('=', b.member(node_id, b.id(name)), value));
} else {
const callee = name.startsWith('xlink') ? '$.set_xlink_attribute' : '$.set_attribute';

@ -1,11 +1,13 @@
/** @import { Expression, Identifier } from 'estree' */
/** @import { Attribute, ClassDirective, DelegatedEvent, ExpressionMetadata, ExpressionTag, Namespace, OnDirective, RegularElement, StyleDirective, SvelteElement, SvelteNode } from '#compiler' */
/** @import { ComponentContext } from '../../types' */
import { AttributeAliases } from '../../../../../../constants.js';
import { is_capture_event } from '../../../../../../utils.js';
import {
is_capture_event,
is_passive_event,
normalize_attribute
} from '../../../../../../utils.js';
import { get_attribute_expression } from '../../../../../utils/ast.js';
import * as b from '../../../../../utils/builders.js';
import { PassiveEvents } from '../../../../constants.js';
import { serialize_get_binding } from '../../utils.js';
import { serialize_event_handler, serialize_template_literal, serialize_update } from './utils.js';
@ -119,18 +121,15 @@ export function serialize_attribute_value(value, context) {
* @param {{ state: { metadata: { namespace: Namespace }}}} context
*/
export function get_attribute_name(element, attribute, context) {
let name = attribute.name;
if (
!element.metadata.svg &&
!element.metadata.mathml &&
context.state.metadata.namespace !== 'foreign'
) {
name = name.toLowerCase();
if (name in AttributeAliases) {
name = AttributeAliases[name];
}
return normalize_attribute(attribute.name);
}
return name;
return attribute.name;
}
/**
@ -236,7 +235,7 @@ export function serialize_event(node, metadata, context) {
} else if (node.modifiers.includes('nonpassive')) {
args.push(b.literal(false));
} else if (
PassiveEvents.includes(node.name) &&
is_passive_event(node.name) &&
/** @type {OnDirective} */ (node).type !== 'OnDirective'
) {
// For on:something events we don't apply passive behaviour to match Svelte 4.

@ -7,11 +7,6 @@ import {
is_text_attribute
} from '../../../../../utils/ast.js';
import { binding_properties } from '../../../../bindings.js';
import {
ContentEditableBindings,
LoadErrorElements,
WhitespaceInsensitiveAttributes
} from '../../../../constants.js';
import {
create_attribute,
create_expression_metadata,
@ -20,11 +15,17 @@ import {
import { regex_starts_with_newline } from '../../../../patterns.js';
import * as b from '../../../../../utils/builders.js';
import {
DOMBooleanAttributes,
ELEMENT_IS_NAMESPACED,
ELEMENT_PRESERVE_ATTRIBUTE_CASE
} from '../../../../../../constants.js';
import { serialize_attribute_value } from './utils.js';
import {
is_boolean_attribute,
is_content_editable_binding,
is_load_error_element
} from '../../../../../../utils.js';
const WHITESPACE_INSENSITIVE_ATTRIBUTES = ['class', 'style'];
/**
* Writes the output to the template output. Some elements may have attributes on them that require the
@ -77,7 +78,7 @@ export function serialize_element_attributes(node, context) {
} else if (is_event_attribute(attribute)) {
if (
(attribute.name === 'onload' || attribute.name === 'onerror') &&
LoadErrorElements.includes(node.name)
is_load_error_element(node.name)
) {
events_to_capture.add(attribute.name);
}
@ -108,7 +109,7 @@ export function serialize_element_attributes(node, context) {
const binding = binding_properties[attribute.name];
if (binding?.omit_in_ssr) continue;
if (ContentEditableBindings.includes(attribute.name)) {
if (is_content_editable_binding(attribute.name)) {
content = /** @type {Expression} */ (context.visit(attribute.expression));
} else if (attribute.name === 'value' && node.name === 'textarea') {
content = b.call(
@ -170,12 +171,12 @@ export function serialize_element_attributes(node, context) {
} else if (attribute.type === 'SpreadAttribute') {
attributes.push(attribute);
has_spread = true;
if (LoadErrorElements.includes(node.name)) {
if (is_load_error_element(node.name)) {
events_to_capture.add('onload');
events_to_capture.add('onerror');
}
} else if (attribute.type === 'UseDirective') {
if (LoadErrorElements.includes(node.name)) {
if (is_load_error_element(node.name)) {
events_to_capture.add('onload');
events_to_capture.add('onerror');
}
@ -227,14 +228,14 @@ export function serialize_element_attributes(node, context) {
serialize_attribute_value(
attribute.value,
context,
WhitespaceInsensitiveAttributes.includes(name)
WHITESPACE_INSENSITIVE_ATTRIBUTES.includes(name)
)
).value;
if (name !== 'class' || literal_value) {
context.state.template.push(
b.literal(
` ${attribute.name}${
DOMBooleanAttributes.includes(name) && literal_value === true
is_boolean_attribute(name) && literal_value === true
? ''
: `="${literal_value === true ? '' : String(literal_value)}"`
}`
@ -245,15 +246,14 @@ export function serialize_element_attributes(node, context) {
}
const name = get_attribute_name(node, attribute, context);
const is_boolean = DOMBooleanAttributes.includes(name);
const value = serialize_attribute_value(
attribute.value,
context,
WhitespaceInsensitiveAttributes.includes(name)
WHITESPACE_INSENSITIVE_ATTRIBUTES.includes(name)
);
context.state.template.push(
b.call('$.attr', b.literal(name), value, is_boolean && b.literal(is_boolean))
b.call('$.attr', b.literal(name), value, is_boolean_attribute(name) && b.true)
);
}
}
@ -344,7 +344,7 @@ function serialize_element_spread_attributes(
const value = serialize_attribute_value(
attribute.value,
context,
WhitespaceInsensitiveAttributes.includes(name)
WHITESPACE_INSENSITIVE_ATTRIBUTES.includes(name)
);
return b.prop('init', b.key(name), value);
}

@ -1,190 +0,0 @@
import { AttributeAliases, DOMBooleanAttributes } from '../../constants.js';
export const DOMProperties = [
...Object.values(AttributeAliases),
'value',
'inert',
'volume',
...DOMBooleanAttributes
];
export const PassiveEvents = ['wheel', 'touchstart', 'touchmove', 'touchend', 'touchcancel'];
export const Runes = /** @type {const} */ ([
'$state',
'$state.frozen',
'$state.snapshot',
'$state.is',
'$props',
'$bindable',
'$derived',
'$derived.by',
'$effect',
'$effect.pre',
'$effect.tracking',
'$effect.root',
'$inspect',
'$inspect().with',
'$host'
]);
/**
* Whitespace inside one of these elements will not result in
* a whitespace node being created in any circumstances. (This
* list is almost certainly very incomplete)
* TODO this is currently unused
*/
export const ElementsWithoutText = ['audio', 'datalist', 'dl', 'optgroup', 'select', 'video'];
export const ReservedKeywords = ['$$props', '$$restProps', '$$slots'];
/** Attributes where whitespace can be compacted */
export const WhitespaceInsensitiveAttributes = ['class', 'style'];
export const ContentEditableBindings = ['textContent', 'innerHTML', 'innerText'];
export const LoadErrorElements = [
'body',
'embed',
'iframe',
'img',
'link',
'object',
'script',
'style',
'track'
];
export const SVGElements = [
'altGlyph',
'altGlyphDef',
'altGlyphItem',
'animate',
'animateColor',
'animateMotion',
'animateTransform',
'circle',
'clipPath',
'color-profile',
'cursor',
'defs',
'desc',
'discard',
'ellipse',
'feBlend',
'feColorMatrix',
'feComponentTransfer',
'feComposite',
'feConvolveMatrix',
'feDiffuseLighting',
'feDisplacementMap',
'feDistantLight',
'feDropShadow',
'feFlood',
'feFuncA',
'feFuncB',
'feFuncG',
'feFuncR',
'feGaussianBlur',
'feImage',
'feMerge',
'feMergeNode',
'feMorphology',
'feOffset',
'fePointLight',
'feSpecularLighting',
'feSpotLight',
'feTile',
'feTurbulence',
'filter',
'font',
'font-face',
'font-face-format',
'font-face-name',
'font-face-src',
'font-face-uri',
'foreignObject',
'g',
'glyph',
'glyphRef',
'hatch',
'hatchpath',
'hkern',
'image',
'line',
'linearGradient',
'marker',
'mask',
'mesh',
'meshgradient',
'meshpatch',
'meshrow',
'metadata',
'missing-glyph',
'mpath',
'path',
'pattern',
'polygon',
'polyline',
'radialGradient',
'rect',
'set',
'solidcolor',
'stop',
'svg',
'switch',
'symbol',
'text',
'textPath',
'tref',
'tspan',
'unknown',
'use',
'view',
'vkern'
];
export const MathMLElements = [
'annotation',
'annotation-xml',
'maction',
'math',
'merror',
'mfrac',
'mi',
'mmultiscripts',
'mn',
'mo',
'mover',
'mpadded',
'mphantom',
'mprescripts',
'mroot',
'mrow',
'ms',
'mspace',
'msqrt',
'mstyle',
'msub',
'msubsup',
'msup',
'mtable',
'mtd',
'mtext',
'mtr',
'munder',
'munderover',
'semantics'
];
export const EventModifiers = [
'preventDefault',
'stopPropagation',
'stopImmediatePropagation',
'capture',
'once',
'passive',
'nonpassive',
'self',
'trusted'
];

@ -1,5 +1,5 @@
/** @import { ClassDeclaration, Expression, FunctionDeclaration, Identifier, ImportDeclaration, MemberExpression, Node, Pattern, VariableDeclarator } from 'estree' */
/** @import { Context, Visitor, Visitors } from 'zimmerframe' */
/** @import { Context, Visitor } from 'zimmerframe' */
/** @import { AnimateDirective, Binding, DeclarationKind, EachBlock, ElementLike, LetDirective, SvelteNode, TransitionDirective, UseDirective } from '#compiler' */
import is_reference from 'is-reference';
import { walk } from 'zimmerframe';
@ -12,8 +12,7 @@ import {
object,
unwrap_pattern
} from '../utils/ast.js';
import { Runes } from './constants.js';
import { is_reserved } from '../../utils.js';
import { is_reserved, is_rune } from '../../utils.js';
export class Scope {
/** @type {ScopeRoot} */
@ -752,7 +751,6 @@ export function set_scope(node, { next, state }) {
* Returns the name of the rune if the given expression is a `CallExpression` using a rune.
* @param {Node | EachBlock | null | undefined} node
* @param {Scope} scope
* @returns {Runes[number] | null}
*/
export function get_rune(node, scope) {
if (!node) return null;
@ -778,10 +776,10 @@ export function get_rune(node, scope) {
joined = n.name + joined;
if (!Runes.includes(/** @type {any} */ (joined))) return null;
if (!is_rune(joined)) return null;
const binding = scope.get(n.name);
if (binding !== null) return null; // rune name, but references a variable or store
return /** @type {typeof Runes[number] | null} */ (joined);
return joined;
}

@ -34,87 +34,8 @@ export const UNINITIALIZED = Symbol();
export const FILENAME = Symbol('filename');
export const HMR = Symbol('hmr');
/** List of elements that require raw contents and should not have SSR comments put in them */
export const RawTextElements = ['textarea', 'script', 'style', 'title'];
/** List of Element events that will be delegated */
export const DelegatedEvents = [
'beforeinput',
'click',
'change',
'dblclick',
'contextmenu',
'focusin',
'focusout',
'input',
'keydown',
'keyup',
'mousedown',
'mousemove',
'mouseout',
'mouseover',
'mouseup',
'pointerdown',
'pointermove',
'pointerout',
'pointerover',
'pointerup',
'touchend',
'touchmove',
'touchstart'
];
/** List of Element events that will be delegated and are passive */
export const PassiveDelegatedEvents = ['touchstart', 'touchmove', 'touchend'];
/**
* @type {Record<string, string>}
* List of attribute names that should be aliased to their property names
* because they behave differently between setting them as an attribute and
* setting them as a property.
*/
export const AttributeAliases = {
// no `class: 'className'` because we handle that separately
formnovalidate: 'formNoValidate',
ismap: 'isMap',
nomodule: 'noModule',
playsinline: 'playsInline',
readonly: 'readOnly'
};
/**
* Attributes that are boolean, i.e. they are present or not present.
*/
export const DOMBooleanAttributes = [
'allowfullscreen',
'async',
'autofocus',
'autoplay',
'checked',
'controls',
'default',
'disabled',
'formnovalidate',
'hidden',
'indeterminate',
'ismap',
'loop',
'multiple',
'muted',
'nomodule',
'novalidate',
'open',
'playsinline',
'readonly',
'required',
'reversed',
'seamless',
'selected',
'webkitdirectory'
];
export const namespace_svg = 'http://www.w3.org/2000/svg';
export const namespace_mathml = 'http://www.w3.org/1998/Math/MathML';
export const NAMESPACE_SVG = 'http://www.w3.org/2000/svg';
export const NAMESPACE_MATHML = 'http://www.w3.org/1998/Math/MathML';
// we use a list of ignorable runtime warnings because not every runtime warning
// can be ignored and we want to keep the validation for svelte-ignore in place
@ -126,3 +47,11 @@ export const IGNORABLE_RUNTIME_WARNINGS = /** @type {const} */ ([
'ownership_invalid_binding',
'ownership_invalid_mutation'
]);
/**
* Whitespace inside one of these elements will not result in
* a whitespace node being created in any circumstances. (This
* list is almost certainly very incomplete)
* TODO this is currently unused
*/
export const ELEMENTS_WITHOUT_TEXT = ['audio', 'datalist', 'dl', 'optgroup', 'select', 'video'];

@ -1,5 +1,5 @@
/** @import { Effect, EffectNodes, TemplateNode } from '#client' */
import { FILENAME, namespace_svg } from '../../../../constants.js';
import { FILENAME, NAMESPACE_SVG } from '../../../../constants.js';
import {
hydrate_next,
hydrate_node,
@ -68,7 +68,7 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
block(() => {
const next_tag = get_tag() || null;
var ns = get_namespace ? get_namespace() : is_svg || next_tag === 'svg' ? namespace_svg : null;
var ns = get_namespace ? get_namespace() : is_svg || next_tag === 'svg' ? NAMESPACE_SVG : null;
// Assumption: Noone changes the namespace but not the tag (what would that even mean?)
if (next_tag === tag) return;

@ -1,13 +1,13 @@
import { DEV } from 'esm-env';
import { hydrating } from '../hydration.js';
import { get_descriptors, get_prototype_of } from '../../../shared/utils.js';
import { AttributeAliases, DelegatedEvents, namespace_svg } from '../../../../constants.js';
import { NAMESPACE_SVG } from '../../../../constants.js';
import { create_event, delegate } from './events.js';
import { add_form_reset_listener, autofocus } from './misc.js';
import * as w from '../../warnings.js';
import { LOADING_ATTR_SYMBOL } from '../../constants.js';
import { queue_idle_task, queue_micro_task } from '../task.js';
import { is_capture_event } from '../../../../utils.js';
import { is_capture_event, is_delegated, normalize_attribute } from '../../../../utils.js';
/**
* The value/checked attribute in the template actually corresponds to the defaultValue property, so we need
@ -212,7 +212,7 @@ export function set_attributes(element, prev, next, lowercase_attributes, css_ha
const opts = {};
const event_handle_key = '$$' + key;
let event_name = key.slice(2);
var delegated = DelegatedEvents.includes(event_name);
var delegated = is_delegated(event_name);
if (is_capture_event(event_name)) {
event_name = event_name.slice(0, -7);
@ -268,8 +268,7 @@ export function set_attributes(element, prev, next, lowercase_attributes, css_ha
} else {
var name = key;
if (lowercase_attributes) {
name = name.toLowerCase();
name = AttributeAliases[name] || name;
name = normalize_attribute(name);
}
if (setters.includes(name)) {
@ -331,7 +330,7 @@ export function set_dynamic_element_attributes(node, prev, next, css_hash) {
/** @type {Element & ElementCSSInlineStyle} */ (node),
prev,
next,
node.namespaceURI !== namespace_svg,
node.namespaceURI !== NAMESPACE_SVG,
css_hash
);
}

@ -2,13 +2,8 @@
/** @import { Component, ComponentType, SvelteComponent } from '../../index.js' */
import { DEV } from 'esm-env';
import { clear_text_content, empty, init_operations } from './dom/operations.js';
import {
HYDRATION_END,
HYDRATION_ERROR,
HYDRATION_START,
PassiveDelegatedEvents
} from '../../constants.js';
import { flush_sync, push, pop, current_component_context, current_effect } from './runtime.js';
import { HYDRATION_END, HYDRATION_ERROR, HYDRATION_START } from '../../constants.js';
import { push, pop, current_component_context, current_effect } from './runtime.js';
import { effect_root, branch } from './reactivity/effects.js';
import {
hydrate_next,
@ -27,6 +22,7 @@ import { reset_head_anchor } from './dom/blocks/svelte-head.js';
import * as w from './warnings.js';
import * as e from './errors.js';
import { assign_nodes } from './dom/template.js';
import { is_passive_event } from '../../utils.js';
/**
* This is normally true block effects should run their intro transitions
@ -195,7 +191,7 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro
if (registered_events.has(event_name)) continue;
registered_events.add(event_name);
var passive = PassiveDelegatedEvents.includes(event_name);
var passive = is_passive_event(event_name);
// Add the event listener to both the container and the document.
// The container listener ensures we catch events from within in case

@ -6,8 +6,6 @@ import { is_promise, noop } from '../shared/utils.js';
import { subscribe_to_store } from '../../store/utils.js';
import {
UNINITIALIZED,
DOMBooleanAttributes,
RawTextElements,
ELEMENT_PRESERVE_ATTRIBUTE_CASE,
ELEMENT_IS_NAMESPACED
} from '../../constants.js';
@ -17,13 +15,16 @@ import { DEV } from 'esm-env';
import { current_component, pop, push } from './context.js';
import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js';
import { validate_store } from '../shared/validate.js';
import { is_void } from '../../utils.js';
import { is_boolean_attribute, is_void } from '../../utils.js';
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
// https://infra.spec.whatwg.org/#noncharacter
const INVALID_ATTR_NAME_CHAR_REGEX =
/[\s'">/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u;
/** List of elements that require raw contents and should not have SSR comments put in them */
const RAW_TEXT_ELEMENTS = ['textarea', 'script', 'style', 'title'];
/**
* @param {Payload} to_copy
* @returns {Payload}
@ -67,7 +68,7 @@ export function element(payload, tag, attributes_fn = noop, children_fn = noop)
if (!is_void(tag)) {
children_fn();
if (!RawTextElements.includes(tag)) {
if (!RAW_TEXT_ELEMENTS.includes(tag)) {
payload.out += EMPTY_COMMENT;
}
payload.out += `</${tag}>`;
@ -225,8 +226,7 @@ export function spread_attributes(attrs, classes, styles, flags = 0) {
name = name.toLowerCase();
}
const is_boolean = is_html && DOMBooleanAttributes.includes(name);
attr_str += attr(name, attrs[name], is_boolean);
attr_str += attr(name, attrs[name], is_html && is_boolean_attribute(name));
}
return attr_str;

@ -105,3 +105,314 @@ export function is_reserved(word) {
export function is_capture_event(name) {
return name.endsWith('capture') && name !== 'gotpointercapture' && name !== 'lostpointercapture';
}
/** List of Element events that will be delegated */
export const DELEGATED_EVENTS = [
'beforeinput',
'click',
'change',
'dblclick',
'contextmenu',
'focusin',
'focusout',
'input',
'keydown',
'keyup',
'mousedown',
'mousemove',
'mouseout',
'mouseover',
'mouseup',
'pointerdown',
'pointermove',
'pointerout',
'pointerover',
'pointerup',
'touchend',
'touchmove',
'touchstart'
];
/**
* Returns `true` if `event_name` is a delegated event
* @param {string} event_name
*/
export function is_delegated(event_name) {
return DELEGATED_EVENTS.includes(event_name);
}
/**
* Attributes that are boolean, i.e. they are present or not present.
*/
export const DOM_BOOLEAN_ATTRIBUTES = [
'allowfullscreen',
'async',
'autofocus',
'autoplay',
'checked',
'controls',
'default',
'disabled',
'formnovalidate',
'hidden',
'indeterminate',
'ismap',
'loop',
'multiple',
'muted',
'nomodule',
'novalidate',
'open',
'playsinline',
'readonly',
'required',
'reversed',
'seamless',
'selected',
'webkitdirectory'
];
/**
* Returns `true` if `name` is a boolean attribute
* @param {string} name
*/
export function is_boolean_attribute(name) {
return DOM_BOOLEAN_ATTRIBUTES.includes(name);
}
/**
* @type {Record<string, string>}
* List of attribute names that should be aliased to their property names
* because they behave differently between setting them as an attribute and
* setting them as a property.
*/
const ATTRIBUTE_ALIASES = {
// no `class: 'className'` because we handle that separately
formnovalidate: 'formNoValidate',
ismap: 'isMap',
nomodule: 'noModule',
playsinline: 'playsInline',
readonly: 'readOnly'
};
/**
* @param {string} name
*/
export function normalize_attribute(name) {
name = name.toLowerCase();
return ATTRIBUTE_ALIASES[name] ?? name;
}
const DOM_PROPERTIES = [
...DOM_BOOLEAN_ATTRIBUTES,
'formNoValidate',
'isMap',
'noModule',
'playsInline',
'readOnly',
'value',
'inert',
'volume'
];
/**
* @param {string} name
*/
export function is_dom_property(name) {
return DOM_PROPERTIES.includes(name);
}
const PASSIVE_EVENTS = ['wheel', 'touchstart', 'touchmove', 'touchend', 'touchcancel'];
/**
* Returns `true` if `name` is a passive event
* @param {string} name
*/
export function is_passive_event(name) {
return PASSIVE_EVENTS.includes(name);
}
const CONTENT_EDITABLE_BINDINGS = ['textContent', 'innerHTML', 'innerText'];
/** @param {string} name */
export function is_content_editable_binding(name) {
return CONTENT_EDITABLE_BINDINGS.includes(name);
}
const LOAD_ERROR_ELEMENTS = [
'body',
'embed',
'iframe',
'img',
'link',
'object',
'script',
'style',
'track'
];
/**
* Returns `true` if the element emits `load` and `error` events
* @param {string} name
*/
export function is_load_error_element(name) {
return LOAD_ERROR_ELEMENTS.includes(name);
}
const SVG_ELEMENTS = [
'altGlyph',
'altGlyphDef',
'altGlyphItem',
'animate',
'animateColor',
'animateMotion',
'animateTransform',
'circle',
'clipPath',
'color-profile',
'cursor',
'defs',
'desc',
'discard',
'ellipse',
'feBlend',
'feColorMatrix',
'feComponentTransfer',
'feComposite',
'feConvolveMatrix',
'feDiffuseLighting',
'feDisplacementMap',
'feDistantLight',
'feDropShadow',
'feFlood',
'feFuncA',
'feFuncB',
'feFuncG',
'feFuncR',
'feGaussianBlur',
'feImage',
'feMerge',
'feMergeNode',
'feMorphology',
'feOffset',
'fePointLight',
'feSpecularLighting',
'feSpotLight',
'feTile',
'feTurbulence',
'filter',
'font',
'font-face',
'font-face-format',
'font-face-name',
'font-face-src',
'font-face-uri',
'foreignObject',
'g',
'glyph',
'glyphRef',
'hatch',
'hatchpath',
'hkern',
'image',
'line',
'linearGradient',
'marker',
'mask',
'mesh',
'meshgradient',
'meshpatch',
'meshrow',
'metadata',
'missing-glyph',
'mpath',
'path',
'pattern',
'polygon',
'polyline',
'radialGradient',
'rect',
'set',
'solidcolor',
'stop',
'svg',
'switch',
'symbol',
'text',
'textPath',
'tref',
'tspan',
'unknown',
'use',
'view',
'vkern'
];
/** @param {string} name */
export function is_svg(name) {
return SVG_ELEMENTS.includes(name);
}
const MATHML_ELEMENTS = [
'annotation',
'annotation-xml',
'maction',
'math',
'merror',
'mfrac',
'mi',
'mmultiscripts',
'mn',
'mo',
'mover',
'mpadded',
'mphantom',
'mprescripts',
'mroot',
'mrow',
'ms',
'mspace',
'msqrt',
'mstyle',
'msub',
'msubsup',
'msup',
'mtable',
'mtd',
'mtext',
'mtr',
'munder',
'munderover',
'semantics'
];
/** @param {string} name */
export function is_mathml(name) {
return MATHML_ELEMENTS.includes(name);
}
const RUNES = /** @type {const} */ ([
'$state',
'$state.frozen',
'$state.snapshot',
'$state.is',
'$props',
'$bindable',
'$derived',
'$derived.by',
'$effect',
'$effect.pre',
'$effect.tracking',
'$effect.root',
'$inspect',
'$inspect().with',
'$host'
]);
/**
* @param {string} name
* @returns {name is RUNES[number]}
*/
export function is_rune(name) {
return RUNES.includes(/** @type {RUNES[number]} */ (name));
}

Loading…
Cancel
Save