Lint and format src/runtime/internal/Component.js

pull/8569/head
S. Elliott Johnson 3 years ago
parent 78da97493b
commit 2c591835fe

@ -1,262 +1,293 @@
import { add_render_callback, flush, flush_render_callbacks, schedule_update, dirty_components } from './scheduler'; import {
add_render_callback,
flush,
flush_render_callbacks,
schedule_update,
dirty_components
} from './scheduler';
import { current_component, set_current_component } from './lifecycle'; import { current_component, set_current_component } from './lifecycle';
import { blank_object, is_empty, is_function, run, run_all, noop } from './utils'; import { blank_object, is_empty, is_function, run, run_all, noop } from './utils';
import { children, detach, start_hydrating, end_hydrating, get_custom_elements_slots, insert } from './dom'; import {
children,
detach,
start_hydrating,
end_hydrating,
get_custom_elements_slots,
insert
} from './dom';
import { transition_in } from './transitions'; import { transition_in } from './transitions';
/** @returns {void} */ /** @returns {void} */
export function bind(component, name, callback) { export function bind(component, name, callback) {
const index = component.$$.props[name]; const index = component.$$.props[name];
if (index !== undefined) { if (index !== undefined) {
component.$$.bound[index] = callback; component.$$.bound[index] = callback;
callback(component.$$.ctx[index]); callback(component.$$.ctx[index]);
} }
} }
/** @returns {void} */ /** @returns {void} */
export function create_component(block) { export function create_component(block) {
block && block.c(); block && block.c();
} }
/** @returns {void} */ /** @returns {void} */
export function claim_component(block, parent_nodes) { export function claim_component(block, parent_nodes) {
block && block.l(parent_nodes); block && block.l(parent_nodes);
} }
/** @returns {void} */ /** @returns {void} */
export function mount_component(component, target, anchor) { export function mount_component(component, target, anchor) {
const { fragment, after_update } = component.$$; const { fragment, after_update } = component.$$;
fragment && fragment.m(target, anchor); fragment && fragment.m(target, anchor);
// onMount happens before the initial afterUpdate // onMount happens before the initial afterUpdate
add_render_callback(() => { add_render_callback(() => {
const new_on_destroy = component.$$.on_mount.map(run).filter(is_function); const new_on_destroy = component.$$.on_mount.map(run).filter(is_function);
// if the component was destroyed immediately // if the component was destroyed immediately
// it will update the `$$.on_destroy` reference to `null`. // it will update the `$$.on_destroy` reference to `null`.
// the destructured on_destroy may still reference to the old array // the destructured on_destroy may still reference to the old array
if (component.$$.on_destroy) { if (component.$$.on_destroy) {
component.$$.on_destroy.push(...new_on_destroy); component.$$.on_destroy.push(...new_on_destroy);
} } else {
else { // Edge case - component was destroyed immediately,
// Edge case - component was destroyed immediately, // most likely as a result of a binding initialising
// most likely as a result of a binding initialising run_all(new_on_destroy);
run_all(new_on_destroy); }
} component.$$.on_mount = [];
component.$$.on_mount = []; });
}); after_update.forEach(add_render_callback);
after_update.forEach(add_render_callback);
} }
/** @returns {void} */ /** @returns {void} */
export function destroy_component(component, detaching) { export function destroy_component(component, detaching) {
const $$ = component.$$; const $$ = component.$$;
if ($$.fragment !== null) { if ($$.fragment !== null) {
flush_render_callbacks($$.after_update); flush_render_callbacks($$.after_update);
run_all($$.on_destroy); run_all($$.on_destroy);
$$.fragment && $$.fragment.d(detaching); $$.fragment && $$.fragment.d(detaching);
// TODO null out other refs, including component.$$ (but need to // TODO null out other refs, including component.$$ (but need to
// preserve final state?) // preserve final state?)
$$.on_destroy = $$.fragment = null; $$.on_destroy = $$.fragment = null;
$$.ctx = []; $$.ctx = [];
} }
} }
/** @returns {void} */ /** @returns {void} */
function make_dirty(component, i) { function make_dirty(component, i) {
if (component.$$.dirty[0] === -1) { if (component.$$.dirty[0] === -1) {
dirty_components.push(component); dirty_components.push(component);
schedule_update(); schedule_update();
component.$$.dirty.fill(0); component.$$.dirty.fill(0);
} }
component.$$.dirty[(i / 31) | 0] |= 1 << i % 31; component.$$.dirty[(i / 31) | 0] |= 1 << i % 31;
} }
/** @returns {void} */ /** @returns {void} */
export function init(component, options, instance, create_fragment, not_equal, props, append_styles, dirty = [-1]) { export function init(
const parent_component = current_component; component,
set_current_component(component); options,
/** @type {T$$} */ instance,
const $$ = (component.$$ = { create_fragment,
fragment: null, not_equal,
ctx: [], props,
// state append_styles,
props, dirty = [-1]
update: noop, ) {
not_equal, const parent_component = current_component;
bound: blank_object(), set_current_component(component);
// lifecycle /** @type {T$$} */
on_mount: [], const $$ = (component.$$ = {
on_destroy: [], fragment: null,
on_disconnect: [], ctx: [],
before_update: [], // state
after_update: [], props,
context: new Map(options.context || (parent_component ? parent_component.$$.context : [])), update: noop,
// everything else not_equal,
callbacks: blank_object(), bound: blank_object(),
dirty, // lifecycle
skip_bound: false, on_mount: [],
root: options.target || parent_component.$$.root on_destroy: [],
}); on_disconnect: [],
append_styles && append_styles($$.root); before_update: [],
let ready = false; after_update: [],
$$.ctx = instance context: new Map(options.context || (parent_component ? parent_component.$$.context : [])),
? instance(component, options.props || {}, (i, ret, ...rest) => { // everything else
const value = rest.length ? rest[0] : ret; callbacks: blank_object(),
if ($$.ctx && not_equal($$.ctx[i], ($$.ctx[i] = value))) { dirty,
if (!$$.skip_bound && $$.bound[i]) skip_bound: false,
$$.bound[i](value); root: options.target || parent_component.$$.root
if (ready) });
make_dirty(component, i); append_styles && append_styles($$.root);
} let ready = false;
return ret; $$.ctx = instance
}) ? instance(component, options.props || {}, (i, ret, ...rest) => {
: []; const value = rest.length ? rest[0] : ret;
$$.update(); if ($$.ctx && not_equal($$.ctx[i], ($$.ctx[i] = value))) {
ready = true; if (!$$.skip_bound && $$.bound[i]) $$.bound[i](value);
run_all($$.before_update); if (ready) make_dirty(component, i);
// `false` as a special case of no DOM component }
$$.fragment = create_fragment ? create_fragment($$.ctx) : false; return ret;
if (options.target) { })
if (options.hydrate) { : [];
start_hydrating(); $$.update();
const nodes = children(options.target); ready = true;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion run_all($$.before_update);
$$.fragment && $$.fragment.l(nodes); // `false` as a special case of no DOM component
nodes.forEach(detach); $$.fragment = create_fragment ? create_fragment($$.ctx) : false;
} if (options.target) {
else { if (options.hydrate) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion start_hydrating();
$$.fragment && $$.fragment.c(); const nodes = children(options.target);
} // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (options.intro) $$.fragment && $$.fragment.l(nodes);
transition_in(component.$$.fragment); nodes.forEach(detach);
mount_component(component, options.target, options.anchor); } else {
end_hydrating(); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
flush(); $$.fragment && $$.fragment.c();
} }
set_current_component(parent_component); if (options.intro) transition_in(component.$$.fragment);
mount_component(component, options.target, options.anchor);
end_hydrating();
flush();
}
set_current_component(parent_component);
} }
export let SvelteElement; export let SvelteElement;
if (typeof HTMLElement === 'function') { if (typeof HTMLElement === 'function') {
SvelteElement = class extends HTMLElement { SvelteElement = class extends HTMLElement {
$$componentCtor; $$componentCtor;
$$slots; $$slots;
$$component; $$component;
$$connected = false; $$connected = false;
$$data = {}; $$data = {};
$$reflecting = false; $$reflecting = false;
$$props_definition = {}; $$props_definition = {};
$$listeners = {}; $$listeners = {};
$$listener_unsubscribe_fns = new Map(); $$listener_unsubscribe_fns = new Map();
constructor($$componentCtor, $$slots, use_shadow_dom) { constructor($$componentCtor, $$slots, use_shadow_dom) {
super(); super();
this.$$componentCtor = $$componentCtor; this.$$componentCtor = $$componentCtor;
this.$$slots = $$slots; this.$$slots = $$slots;
if (use_shadow_dom) { if (use_shadow_dom) {
this.attachShadow({ mode: 'open' }); this.attachShadow({ mode: 'open' });
} }
} }
addEventListener(type, listener, options) { addEventListener(type, listener, options) {
// We can't determine upfront if the event is a custom event or not, so we have to // We can't determine upfront if the event is a custom event or not, so we have to
// listen to both. If someone uses a custom event with the same name as a regular // listen to both. If someone uses a custom event with the same name as a regular
// browser event, this fires twice - we can't avoid that. // browser event, this fires twice - we can't avoid that.
this.$$listeners[type] = this.$$listeners[type] || []; this.$$listeners[type] = this.$$listeners[type] || [];
this.$$listeners[type].push(listener); this.$$listeners[type].push(listener);
if (this.$$component) { if (this.$$component) {
const unsub = this.$$component.$on(type, listener); const unsub = this.$$component.$on(type, listener);
this.$$listener_unsubscribe_fns.set(listener, unsub); this.$$listener_unsubscribe_fns.set(listener, unsub);
} }
super.addEventListener(type, listener, options); super.addEventListener(type, listener, options);
} }
removeEventListener(type, listener, options) { removeEventListener(type, listener, options) {
super.removeEventListener(type, listener, options); super.removeEventListener(type, listener, options);
if (this.$$component) { if (this.$$component) {
const unsub = this.$$listener_unsubscribe_fns.get(listener); const unsub = this.$$listener_unsubscribe_fns.get(listener);
if (unsub) { if (unsub) {
unsub(); unsub();
this.$$listener_unsubscribe_fns.delete(listener); this.$$listener_unsubscribe_fns.delete(listener);
} }
} }
} }
async connectedCallback() { async connectedCallback() {
this.$$connected = true; this.$$connected = true;
if (!this.$$component) { if (!this.$$component) {
// We wait one tick to let possible child slot elements be created/mounted // We wait one tick to let possible child slot elements be created/mounted
await Promise.resolve(); await Promise.resolve();
if (!this.$$connected) { if (!this.$$connected) {
return; return;
} }
function create_slot(name) { function create_slot(name) {
return () => { return () => {
let node; let node;
const obj = { const obj = {
c: function create() { c: function create() {
node = document.createElement('slot'); node = document.createElement('slot');
if (name !== 'default') { if (name !== 'default') {
node.setAttribute('name', name); node.setAttribute('name', name);
} }
}, },
m: function mount(target, anchor) { m: function mount(target, anchor) {
insert(target, node, anchor); insert(target, node, anchor);
}, },
d: function destroy(detaching) { d: function destroy(detaching) {
if (detaching) { if (detaching) {
detach(node); detach(node);
} }
} }
}; };
return obj; return obj;
}; };
} }
const $$slots = {}; const $$slots = {};
const existing_slots = get_custom_elements_slots(this); const existing_slots = get_custom_elements_slots(this);
for (const name of this.$$slots) { for (const name of this.$$slots) {
if (name in existing_slots) { if (name in existing_slots) {
$$slots[name] = [create_slot(name)]; $$slots[name] = [create_slot(name)];
} }
} }
for (const attribute of this.attributes) { for (const attribute of this.attributes) {
// this.$$data takes precedence over this.attributes // this.$$data takes precedence over this.attributes
const name = this.$$get_prop_name(attribute.name); const name = this.$$get_prop_name(attribute.name);
if (!(name in this.$$data)) { if (!(name in this.$$data)) {
this.$$data[name] = get_custom_element_value(name, attribute.value, this.$$props_definition, 'toProp'); this.$$data[name] = get_custom_element_value(
} name,
} attribute.value,
this.$$component = new this.$$componentCtor({ this.$$props_definition,
target: this.shadowRoot || this, 'toProp'
props: { );
...this.$$data, }
$$slots, }
$$scope: { this.$$component = new this.$$componentCtor({
ctx: [] target: this.shadowRoot || this,
} props: {
} ...this.$$data,
}); $$slots,
for (const type in this.$$listeners) { $$scope: {
for (const listener of this.$$listeners[type]) { ctx: []
const unsub = this.$$component.$on(type, listener); }
this.$$listener_unsubscribe_fns.set(listener, unsub); }
} });
} for (const type in this.$$listeners) {
this.$$listeners = {}; for (const listener of this.$$listeners[type]) {
} const unsub = this.$$component.$on(type, listener);
} this.$$listener_unsubscribe_fns.set(listener, unsub);
// We don't need this when working within Svelte code, but for compatibility of people using this outside of Svelte }
// and setting attributes through setAttribute etc, this is helpful }
attributeChangedCallback(attr, _oldValue, newValue) { this.$$listeners = {};
if (this.$$reflecting) }
return; }
attr = this.$$get_prop_name(attr); // We don't need this when working within Svelte code, but for compatibility of people using this outside of Svelte
this.$$data[attr] = get_custom_element_value(attr, newValue, this.$$props_definition, 'toProp'); // and setting attributes through setAttribute etc, this is helpful
this.$$component.$set({ [attr]: this.$$data[attr] }); attributeChangedCallback(attr, _oldValue, newValue) {
} if (this.$$reflecting) return;
disconnectedCallback() { attr = this.$$get_prop_name(attr);
this.$$connected = false; this.$$data[attr] = get_custom_element_value(
// In a microtask, because this could be a move within the DOM attr,
Promise.resolve().then(() => { newValue,
if (!this.$$connected) { this.$$props_definition,
this.$$component.$destroy(); 'toProp'
this.$$component = undefined; );
} this.$$component.$set({ [attr]: this.$$data[attr] });
}); }
} disconnectedCallback() {
$$get_prop_name(attribute_name) { this.$$connected = false;
return (Object.keys(this.$$props_definition).find((key) => this.$$props_definition[key].attribute === attribute_name || // In a microtask, because this could be a move within the DOM
(!this.$$props_definition[key].attribute && key.toLowerCase() === attribute_name)) || attribute_name); Promise.resolve().then(() => {
} if (!this.$$connected) {
}; this.$$component.$destroy();
this.$$component = undefined;
}
});
}
$$get_prop_name(attribute_name) {
return (
Object.keys(this.$$props_definition).find(
(key) =>
this.$$props_definition[key].attribute === attribute_name ||
(!this.$$props_definition[key].attribute && key.toLowerCase() === attribute_name)
) || attribute_name
);
}
};
} }
/** @param {string} prop /** @param {string} prop
* @param {any} value * @param {any} value
@ -265,37 +296,35 @@ if (typeof HTMLElement === 'function') {
* @returns {any} * @returns {any}
*/ */
function get_custom_element_value(prop, value, props_definition, transform) { function get_custom_element_value(prop, value, props_definition, transform) {
const type = props_definition[prop]?.type; const type = props_definition[prop]?.type;
value = type === 'Boolean' && typeof value !== 'boolean' ? value != null : value; value = type === 'Boolean' && typeof value !== 'boolean' ? value != null : value;
if (!transform || !props_definition[prop]) { if (!transform || !props_definition[prop]) {
return value; return value;
} } else if (transform === 'toAttribute') {
else if (transform === 'toAttribute') { switch (type) {
switch (type) { case 'Object':
case 'Object': case 'Array':
case 'Array': return value == null ? null : JSON.stringify(value);
return value == null ? null : JSON.stringify(value); case 'Boolean':
case 'Boolean': return value ? '' : null;
return value ? '' : null; case 'Number':
case 'Number': return value == null ? null : value;
return value == null ? null : value; default:
default: return value;
return value; }
} } else {
} switch (type) {
else { case 'Object':
switch (type) { case 'Array':
case 'Object': return value && JSON.parse(value);
case 'Array': case 'Boolean':
return value && JSON.parse(value); return value; // conversion already handled above
case 'Boolean': case 'Number':
return value; // conversion already handled above return value != null ? +value : value;
case 'Number': default:
return value != null ? +value : value; return value;
default: }
return value; }
}
}
} }
/** /**
* @internal * @internal
@ -308,90 +337,98 @@ function get_custom_element_value(prop, value, props_definition, transform) {
* @param {boolean} use_shadow_dom Whether to use shadow DOM * @param {boolean} use_shadow_dom Whether to use shadow DOM
* @returns {Class<Class>} A custom element class * @returns {Class<Class>} A custom element class
*/ */
export function create_custom_element(Component, props_definition, slots, accessors, use_shadow_dom) { export function create_custom_element(
const Class = class extends SvelteElement { Component,
constructor() { props_definition,
super(Component, slots, use_shadow_dom); slots,
this.$$props_definition = props_definition; accessors,
} use_shadow_dom
static get observedAttributes() { ) {
return Object.keys(props_definition).map((key) => (props_definition[key].attribute || key).toLowerCase()); const Class = class extends SvelteElement {
} constructor() {
}; super(Component, slots, use_shadow_dom);
Object.keys(props_definition).forEach((prop) => { this.$$props_definition = props_definition;
Object.defineProperty(Class.prototype, prop, { }
get() { static get observedAttributes() {
return this.$$component && prop in this.$$component return Object.keys(props_definition).map((key) =>
? this.$$component[prop] (props_definition[key].attribute || key).toLowerCase()
: this.$$data[prop]; );
}, }
set(value) { };
value = get_custom_element_value(prop, value, props_definition); Object.keys(props_definition).forEach((prop) => {
this.$$data[prop] = value; Object.defineProperty(Class.prototype, prop, {
this.$$component?.$set({ [prop]: value }); get() {
if (props_definition[prop].reflect) { return this.$$component && prop in this.$$component
this.$$reflecting = true; ? this.$$component[prop]
const attribute_value = get_custom_element_value(prop, value, props_definition, 'toAttribute'); : this.$$data[prop];
if (attribute_value == null) { },
this.removeAttribute(prop); set(value) {
} value = get_custom_element_value(prop, value, props_definition);
else { this.$$data[prop] = value;
this.setAttribute(props_definition[prop].attribute || prop, attribute_value); this.$$component?.$set({ [prop]: value });
} if (props_definition[prop].reflect) {
this.$$reflecting = false; this.$$reflecting = true;
} const attribute_value = get_custom_element_value(
} prop,
}); value,
}); props_definition,
accessors.forEach((accessor) => { 'toAttribute'
Object.defineProperty(Class.prototype, accessor, { );
get() { if (attribute_value == null) {
return this.$$component?.[accessor]; this.removeAttribute(prop);
} } else {
}); this.setAttribute(props_definition[prop].attribute || prop, attribute_value);
}); }
Component.element = Class; this.$$reflecting = false;
return Class; }
}
});
});
accessors.forEach((accessor) => {
Object.defineProperty(Class.prototype, accessor, {
get() {
return this.$$component?.[accessor];
}
});
});
Component.element = Class;
return Class;
} }
/** /**
* Base class for Svelte components. Used when dev=false. * Base class for Svelte components. Used when dev=false.
*/ */
export class SvelteComponent { export class SvelteComponent {
/** */ /** */
$$ = undefined; $$ = undefined;
/** */ /** */
$$set = undefined; $$set = undefined;
/** @returns {void} */ /** @returns {void} */
$destroy() { $destroy() {
destroy_component(this, 1); destroy_component(this, 1);
this.$destroy = noop; this.$destroy = noop;
} }
/** @returns {any} */ /** @returns {any} */
$on(type, callback) { $on(type, callback) {
if (!is_function(callback)) { if (!is_function(callback)) {
return noop; return noop;
} }
const callbacks = this.$$.callbacks[type] || (this.$$.callbacks[type] = []); const callbacks = this.$$.callbacks[type] || (this.$$.callbacks[type] = []);
callbacks.push(callback); callbacks.push(callback);
return () => { return () => {
const index = callbacks.indexOf(callback); const index = callbacks.indexOf(callback);
if (index !== -1) if (index !== -1) callbacks.splice(index, 1);
callbacks.splice(index, 1); };
}; }
} /** @returns {void} */
/** @returns {void} */ $set($$props) {
$set($$props) { if (this.$$set && !is_empty($$props)) {
if (this.$$set && !is_empty($$props)) { this.$$.skip_bound = true;
this.$$.skip_bound = true; this.$$set($$props);
this.$$set($$props); this.$$.skip_bound = false;
this.$$.skip_bound = false; }
} }
}
} }
/** @typedef {Object} CustomElementPropDefinition /** @typedef {Object} CustomElementPropDefinition
* @property {string} [attribute] * @property {string} [attribute]
* @property {boolean} [reflect] * @property {boolean} [reflect]

Loading…
Cancel
Save