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