svelte
Version:
Cybernetically enhanced web apps
473 lines (440 loc) • 8.33 kB
JavaScript
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',
'defer',
'disablepictureinpicture',
'disableremoteplayback'
];
/**
* 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',
novalidate: 'noValidate',
allowfullscreen: 'allowFullscreen',
disablepictureinpicture: 'disablePictureInPicture',
disableremoteplayback: 'disableRemotePlayback'
};
/**
* @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',
'noValidate',
'allowFullscreen',
'disablePictureInPicture',
'disableRemotePlayback'
];
/**
* @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',
'$props.id',
'$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');
}