summaryrefslogtreecommitdiff
path: root/node_modules/svelte/src/runtime/internal/Component.js
diff options
context:
space:
mode:
authorrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
committerrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
commitbce557cc2dc767628bed6aac87301a1be7c5431b (patch)
treeb51a051228d01fe3306cd7626d4a96768aadb944 /node_modules/svelte/src/runtime/internal/Component.js
init commit
Diffstat (limited to 'node_modules/svelte/src/runtime/internal/Component.js')
-rw-r--r--node_modules/svelte/src/runtime/internal/Component.js524
1 files changed, 524 insertions, 0 deletions
diff --git a/node_modules/svelte/src/runtime/internal/Component.js b/node_modules/svelte/src/runtime/internal/Component.js
new file mode 100644
index 0000000..bc2c83d
--- /dev/null
+++ b/node_modules/svelte/src/runtime/internal/Component.js
@@ -0,0 +1,524 @@
+import {
+ add_render_callback,
+ flush,
+ flush_render_callbacks,
+ schedule_update,
+ dirty_components
+} from './scheduler.js';
+import { current_component, set_current_component } from './lifecycle.js';
+import { blank_object, is_empty, is_function, run, run_all, noop } from './utils.js';
+import {
+ children,
+ detach,
+ start_hydrating,
+ end_hydrating,
+ get_custom_elements_slots,
+ insert,
+ element,
+ attr
+} from './dom.js';
+import { transition_in } from './transitions.js';
+
+/** @returns {void} */
+export function bind(component, name, callback) {
+ 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();
+}
+
+/** @returns {void} */
+export function claim_component(block, 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);
+}
+
+/** @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 = [];
+ }
+}
+
+/** @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;
+}
+
+// TODO: Document the other params
+/**
+ * @param {SvelteComponent} component
+ * @param {import('./public.js').ComponentConstructorOptions} options
+ *
+ * @param {import('./utils.js')['not_equal']} not_equal Used to compare props and state values.
+ * @param {(target: Element | ShadowRoot) => void} [append_styles] Function that appends styles to the DOM when the component is first initialised.
+ * This will be the `add_css` function from the compiled component.
+ *
+ * @returns {void}
+ */
+export function init(
+ component,
+ options,
+ instance,
+ create_fragment,
+ not_equal,
+ props,
+ append_styles = null,
+ dirty = [-1]
+) {
+ const parent_component = current_component;
+ set_current_component(component);
+ /** @type {import('./private.js').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();
+ // TODO: what is the correct type here?
+ // @ts-expect-error
+ const nodes = children(options.target);
+ $$.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 {
+ /** The Svelte component constructor */
+ $$ctor;
+ /** Slots */
+ $$s;
+ /** The Svelte component instance */
+ $$c;
+ /** Whether or not the custom element is connected */
+ $$cn = false;
+ /** Component props data */
+ $$d = {};
+ /** `true` if currently in the process of reflecting component props back to attributes */
+ $$r = false;
+ /** @type {Record<string, CustomElementPropDefinition>} Props definition (name, reflected, type etc) */
+ $$p_d = {};
+ /** @type {Record<string, Function[]>} Event listeners */
+ $$l = {};
+ /** @type {Map<Function, Function>} Event listener unsubscribe functions */
+ $$l_u = new Map();
+
+ constructor($$componentCtor, $$slots, use_shadow_dom) {
+ super();
+ this.$$ctor = $$componentCtor;
+ this.$$s = $$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.$$l[type] = this.$$l[type] || [];
+ this.$$l[type].push(listener);
+ if (this.$$c) {
+ const unsub = this.$$c.$on(type, listener);
+ this.$$l_u.set(listener, unsub);
+ }
+ super.addEventListener(type, listener, options);
+ }
+
+ removeEventListener(type, listener, options) {
+ super.removeEventListener(type, listener, options);
+ if (this.$$c) {
+ const unsub = this.$$l_u.get(listener);
+ if (unsub) {
+ unsub();
+ this.$$l_u.delete(listener);
+ }
+ }
+ if (this.$$l[type]) {
+ const idx = this.$$l[type].indexOf(listener);
+ if (idx >= 0) {
+ this.$$l[type].splice(idx, 1);
+ }
+ }
+ }
+
+ async connectedCallback() {
+ this.$$cn = true;
+ if (!this.$$c) {
+ // We wait one tick to let possible child slot elements be created/mounted
+ await Promise.resolve();
+ if (!this.$$cn || this.$$c) {
+ return;
+ }
+ function create_slot(name) {
+ return () => {
+ let node;
+ const obj = {
+ c: function create() {
+ node = element('slot');
+ if (name !== 'default') {
+ attr(node, 'name', name);
+ }
+ },
+ /**
+ * @param {HTMLElement} target
+ * @param {HTMLElement} [anchor]
+ */
+ 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.$$s) {
+ 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.$$g_p(attribute.name);
+ if (!(name in this.$$d)) {
+ this.$$d[name] = get_custom_element_value(name, attribute.value, this.$$p_d, 'toProp');
+ }
+ }
+ // Port over props that were set programmatically before ce was initialized
+ for (const key in this.$$p_d) {
+ if (!(key in this.$$d) && this[key] !== undefined) {
+ this.$$d[key] = this[key]; // don't transform, these were set through JavaScript
+ delete this[key]; // remove the property that shadows the getter/setter
+ }
+ }
+ this.$$c = new this.$$ctor({
+ target: this.shadowRoot || this,
+ props: {
+ ...this.$$d,
+ $$slots,
+ $$scope: {
+ ctx: []
+ }
+ }
+ });
+
+ // Reflect component props as attributes
+ const reflect_attributes = () => {
+ this.$$r = true;
+ for (const key in this.$$p_d) {
+ this.$$d[key] = this.$$c.$$.ctx[this.$$c.$$.props[key]];
+ if (this.$$p_d[key].reflect) {
+ const attribute_value = get_custom_element_value(
+ key,
+ this.$$d[key],
+ this.$$p_d,
+ 'toAttribute'
+ );
+ if (attribute_value == null) {
+ this.removeAttribute(this.$$p_d[key].attribute || key);
+ } else {
+ this.setAttribute(this.$$p_d[key].attribute || key, attribute_value);
+ }
+ }
+ }
+ this.$$r = false;
+ };
+ this.$$c.$$.after_update.push(reflect_attributes);
+ reflect_attributes(); // once initially because after_update is added too late for first render
+
+ for (const type in this.$$l) {
+ for (const listener of this.$$l[type]) {
+ const unsub = this.$$c.$on(type, listener);
+ this.$$l_u.set(listener, unsub);
+ }
+ }
+ this.$$l = {};
+ }
+ }
+
+ // 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.$$r) return;
+ attr = this.$$g_p(attr);
+ this.$$d[attr] = get_custom_element_value(attr, newValue, this.$$p_d, 'toProp');
+ this.$$c?.$set({ [attr]: this.$$d[attr] });
+ }
+
+ disconnectedCallback() {
+ this.$$cn = false;
+ // In a microtask, because this could be a move within the DOM
+ Promise.resolve().then(() => {
+ if (!this.$$cn && this.$$c) {
+ this.$$c.$destroy();
+ this.$$c = undefined;
+ }
+ });
+ }
+
+ $$g_p(attribute_name) {
+ return (
+ Object.keys(this.$$p_d).find(
+ (key) =>
+ this.$$p_d[key].attribute === attribute_name ||
+ (!this.$$p_d[key].attribute && key.toLowerCase() === attribute_name)
+ ) || attribute_name
+ );
+ }
+ };
+}
+
+/**
+ * @param {string} prop
+ * @param {any} value
+ * @param {Record<string, CustomElementPropDefinition>} props_definition
+ * @param {'toAttribute' | 'toProp'} [transform]
+ */
+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;
+ }
+ }
+}
+
+/**
+ * @internal
+ *
+ * Turn a Svelte component into a custom element.
+ * @param {import('./public.js').ComponentType} Component A Svelte component constructor
+ * @param {Record<string, CustomElementPropDefinition>} props_definition The props to observe
+ * @param {string[]} slots The slots to create
+ * @param {string[]} accessors Other accessors besides the ones for props the component has
+ * @param {boolean} use_shadow_dom Whether to use shadow DOM
+ * @param {(ce: new () => HTMLElement) => new () => HTMLElement} [extend]
+ */
+export function create_custom_element(
+ Component,
+ props_definition,
+ slots,
+ accessors,
+ use_shadow_dom,
+ extend
+) {
+ let Class = class extends SvelteElement {
+ constructor() {
+ super(Component, slots, use_shadow_dom);
+ this.$$p_d = 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.$$c && prop in this.$$c ? this.$$c[prop] : this.$$d[prop];
+ },
+ set(value) {
+ value = get_custom_element_value(prop, value, props_definition);
+ this.$$d[prop] = value;
+ this.$$c?.$set({ [prop]: value });
+ }
+ });
+ });
+ accessors.forEach((accessor) => {
+ Object.defineProperty(Class.prototype, accessor, {
+ get() {
+ return this.$$c?.[accessor];
+ }
+ });
+ });
+ if (extend) {
+ // @ts-expect-error - assigning here is fine
+ Class = extend(Class);
+ }
+ Component.element = /** @type {any} */ (Class);
+ return Class;
+}
+
+/**
+ * Base class for Svelte components. Used when dev=false.
+ *
+ * @template {Record<string, any>} [Props=any]
+ * @template {Record<string, any>} [Events=any]
+ */
+export class SvelteComponent {
+ /**
+ * ### PRIVATE API
+ *
+ * Do not use, may change at any time
+ *
+ * @type {any}
+ */
+ $$ = undefined;
+ /**
+ * ### PRIVATE API
+ *
+ * Do not use, may change at any time
+ *
+ * @type {any}
+ */
+ $$set = undefined;
+
+ /** @returns {void} */
+ $destroy() {
+ destroy_component(this, 1);
+ this.$destroy = noop;
+ }
+
+ /**
+ * @template {Extract<keyof Events, string>} K
+ * @param {K} type
+ * @param {((e: Events[K]) => void) | null | undefined} callback
+ * @returns {() => void}
+ */
+ $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);
+ };
+ }
+
+ /**
+ * @param {Partial<Props>} props
+ * @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]
+ */