mirror of https://github.com/sveltejs/svelte
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
461 lines
7.8 KiB
461 lines
7.8 KiB
const regex_return_characters = /\r/g;
|
|
|
|
/**
|
|
* @param {string} str
|
|
* @returns {string}
|
|
*/
|
|
export function hash(str) {
|
|
str = str.replace(regex_return_characters, '');
|
|
let hash = 5381;
|
|
let i = str.length;
|
|
|
|
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
|
|
return (hash >>> 0).toString(36);
|
|
}
|
|
|
|
const VOID_ELEMENT_NAMES = [
|
|
'area',
|
|
'base',
|
|
'br',
|
|
'col',
|
|
'command',
|
|
'embed',
|
|
'hr',
|
|
'img',
|
|
'input',
|
|
'keygen',
|
|
'link',
|
|
'meta',
|
|
'param',
|
|
'source',
|
|
'track',
|
|
'wbr'
|
|
];
|
|
|
|
/**
|
|
* Returns `true` if `name` is of a void element
|
|
* @param {string} name
|
|
*/
|
|
export function is_void(name) {
|
|
return VOID_ELEMENT_NAMES.includes(name) || name.toLowerCase() === '!doctype';
|
|
}
|
|
|
|
const RESERVED_WORDS = [
|
|
'arguments',
|
|
'await',
|
|
'break',
|
|
'case',
|
|
'catch',
|
|
'class',
|
|
'const',
|
|
'continue',
|
|
'debugger',
|
|
'default',
|
|
'delete',
|
|
'do',
|
|
'else',
|
|
'enum',
|
|
'eval',
|
|
'export',
|
|
'extends',
|
|
'false',
|
|
'finally',
|
|
'for',
|
|
'function',
|
|
'if',
|
|
'implements',
|
|
'import',
|
|
'in',
|
|
'instanceof',
|
|
'interface',
|
|
'let',
|
|
'new',
|
|
'null',
|
|
'package',
|
|
'private',
|
|
'protected',
|
|
'public',
|
|
'return',
|
|
'static',
|
|
'super',
|
|
'switch',
|
|
'this',
|
|
'throw',
|
|
'true',
|
|
'try',
|
|
'typeof',
|
|
'var',
|
|
'void',
|
|
'while',
|
|
'with',
|
|
'yield'
|
|
];
|
|
|
|
/**
|
|
* Returns `true` if `word` is a reserved JavaScript keyword
|
|
* @param {string} word
|
|
*/
|
|
export function is_reserved(word) {
|
|
return RESERVED_WORDS.includes(word);
|
|
}
|
|
|
|
/**
|
|
* @param {string} name
|
|
*/
|
|
export function is_capture_event(name) {
|
|
return name.endsWith('capture') && name !== 'gotpointercapture' && name !== 'lostpointercapture';
|
|
}
|
|
|
|
/** List of Element events that will be delegated */
|
|
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.
|
|
*/
|
|
const DOM_BOOLEAN_ATTRIBUTES = [
|
|
'allowfullscreen',
|
|
'async',
|
|
'autofocus',
|
|
'autoplay',
|
|
'checked',
|
|
'controls',
|
|
'default',
|
|
'disabled',
|
|
'formnovalidate',
|
|
'hidden',
|
|
'indeterminate',
|
|
'inert',
|
|
'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',
|
|
defaultvalue: 'defaultValue',
|
|
defaultchecked: 'defaultChecked',
|
|
srcobject: 'srcObject'
|
|
};
|
|
|
|
/**
|
|
* @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',
|
|
'volume',
|
|
'defaultValue',
|
|
'defaultChecked',
|
|
'srcObject'
|
|
];
|
|
|
|
/**
|
|
* @param {string} name
|
|
*/
|
|
export function is_dom_property(name) {
|
|
return DOM_PROPERTIES.includes(name);
|
|
}
|
|
|
|
const NON_STATIC_PROPERTIES = ['autofocus', 'muted', 'defaultValue', 'defaultChecked'];
|
|
|
|
/**
|
|
* Returns `true` if the given attribute cannot be set through the template
|
|
* string, i.e. needs some kind of JavaScript handling to work.
|
|
* @param {string} name
|
|
*/
|
|
export function cannot_be_set_statically(name) {
|
|
return NON_STATIC_PROPERTIES.includes(name);
|
|
}
|
|
|
|
/**
|
|
* Subset of delegated events which should be passive by default.
|
|
* These two are already passive via browser defaults on window, document and body.
|
|
* But since
|
|
* - we're delegating them
|
|
* - they happen often
|
|
* - they apply to mobile which is generally less performant
|
|
* we're marking them as passive by default for other elements, too.
|
|
*/
|
|
const PASSIVE_EVENTS = ['touchstart', 'touchmove'];
|
|
|
|
/**
|
|
* 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.raw',
|
|
'$state.snapshot',
|
|
'$props',
|
|
'$bindable',
|
|
'$derived',
|
|
'$derived.by',
|
|
'$effect',
|
|
'$effect.pre',
|
|
'$effect.tracking',
|
|
'$effect.root',
|
|
'$inspect',
|
|
'$inspect().with',
|
|
'$inspect.trace',
|
|
'$host'
|
|
]);
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @returns {name is RUNES[number]}
|
|
*/
|
|
export function is_rune(name) {
|
|
return RUNES.includes(/** @type {RUNES[number]} */ (name));
|
|
}
|
|
|
|
/** List of elements that require raw contents and should not have SSR comments put in them */
|
|
const RAW_TEXT_ELEMENTS = /** @type {const} */ (['textarea', 'script', 'style', 'title']);
|
|
|
|
/** @param {string} name */
|
|
export function is_raw_text_element(name) {
|
|
return RAW_TEXT_ELEMENTS.includes(/** @type {RAW_TEXT_ELEMENTS[number]} */ (name));
|
|
}
|
|
|
|
/**
|
|
* Prevent devtools trying to make `location` a clickable link by inserting a zero-width space
|
|
* @param {string | undefined} location
|
|
*/
|
|
export function sanitize_location(location) {
|
|
return location?.replace(/\//g, '/\u200b');
|
|
}
|