@dvcol/neo-svelte
Version:
Neomorphic ui library for svelte 5
127 lines (126 loc) • 3.73 kB
JavaScript
import { getContext, setContext } from 'svelte';
import { SvelteMap } from 'svelte/reactivity';
import { NeoErrorMissingTabId } from '../utils/error.utils.js';
import { Logger } from '../utils/logger.utils.js';
export class NeoTabContext {
#tabs = new SvelteMap();
#panes = new SvelteMap();
#onChange;
#onClose;
#active = $state();
#previous = $state();
#position = $state({});
#options = $state({});
get active() {
return this.#active;
}
get value() {
return this.getValue(this.active);
}
get previous() {
return this.getValue(this.#previous);
}
get position() {
return this.#position;
}
get state() {
return {
...this.#options,
active: this.active,
value: this.value,
};
}
constructor({ onChange, onClose } = {}) {
this.#onChange = onChange;
this.#onClose = onClose;
}
getValue(tabId) {
if (!tabId)
return;
return this.#tabs.get(tabId);
}
getPane(tabId) {
if (!tabId)
return;
return this.#panes.get(tabId);
}
onOption(options) {
Object.assign(this.#options, options);
if (!options.slide)
delete this.#position.oldTab;
}
#getPosition(tabId) {
if (!tabId)
return;
const _ref = this.getValue(tabId)?.ref;
const parent = _ref?.parentElement?.getBoundingClientRect();
const rect = _ref?.getBoundingClientRect();
if (!parent || !rect)
return;
return {
id: tabId,
top: rect.top - parent.top,
left: rect.left - parent.left,
width: rect.width,
height: rect.height,
};
}
onPosition(_ref = this.value?.ref) {
const _new = {
oldTab: this.#getPosition(this.position?.newTab?.id),
};
if (this.active) {
_new.newTab = this.#getPosition(this.active);
}
this.#position = _new;
return this.position;
}
onChange(tabId, emit = true) {
if (tabId === this.#active) {
if (this.#active && this.state?.toggle)
this.onChange();
return;
}
const current = this.value;
this.#previous = this.active;
this.#active = tabId;
this.onPosition();
if (emit)
this.#onChange?.(this.active, this.value, current);
}
onClose(tabId) {
this.#onClose?.(tabId, this.value);
}
register(tabId, value) {
if (!tabId)
throw new NeoErrorMissingTabId();
if (this.#tabs.has(tabId)) {
return Logger.warn(`Tab ID '${String(tabId)}' already exists. Tab registration ignored.`, { existing: this.getValue(tabId), ignored: value });
}
this.#tabs.set(tabId, { ...value, index: this.#tabs.size });
}
remove(tabId) {
this.#tabs.delete(tabId);
if (this.#active === tabId)
this.onChange();
}
registerPane(tabId, panelId) {
if (this.#panes.has(tabId)) {
return Logger.warn(`Tab ID '${String(tabId)}' already exists. Pane registration ignored.`, {
existing: this.#panes.get(tabId),
ignored: panelId,
});
}
this.#panes.set(tabId, panelId);
}
removePane(tabId) {
this.#panes.delete(tabId);
}
}
const NeoTabsContextSymbol = Symbol('NeoTabsContext');
export function getTabContext() {
return getContext(NeoTabsContextSymbol);
}
export function setTabContext(callback) {
return setContext(NeoTabsContextSymbol, new NeoTabContext(callback));
}