/**
* Charge system (bootstrap) for auto-discovery and initialization of Volt roots
*
* Handles declarative state initialization via data-volt-state and data-volt-computed
*/
import type { ChargedRoot, ChargeResult, Scope } from "$types/volt";
import { mount } from "./binder";
import { report } from "./error";
import { evaluate } from "./evaluator";
import { getComputedAttributes, isNil } from "./shared";
import { computed, signal } from "./signal";
import { registerStore } from "./store";
/**
* Discover and mount all Volt roots in the document.
*
* Parses data-volt-state for initial state and data-volt-computed for derived values.
* Also parses declarative global store from script[data-volt-store] elements.
*
* @param rootSelector - Selector for root elements (default: "[data-volt]")
* @returns ChargeResult containing mounted roots and cleanup function
*
* @example
* ```html
*
*
*
*
* ```
*
* ```ts
* const { cleanup } = charge();
* // Later: cleanup() to unmount all
* ```
*/
export function charge(rootSelector = "[data-volt]"): ChargeResult {
parseDeclarativeStore();
const elements = document.querySelectorAll(rootSelector);
const chargedRoots: ChargedRoot[] = [];
for (const element of elements) {
try {
const scope = createScopeFromElement(element);
const cleanup = mount(element, scope);
chargedRoots.push({ element, scope, cleanup });
} catch (error) {
report(error as Error, { source: "charge", element: element as HTMLElement });
}
}
return {
roots: chargedRoots,
cleanup: () => {
for (const root of chargedRoots) {
try {
root.cleanup();
} catch (error) {
report(error as Error, { source: "charge", element: root.element as HTMLElement });
}
}
},
};
}
/**
* Create a reactive scope from element's data-volt-state and data-volt-computed attributes
*/
function createScopeFromElement(el: Element): Scope {
const scope: Scope = {};
const stateAttr = (el as HTMLElement).dataset.voltState;
if (stateAttr) {
try {
const stateData = JSON.parse(stateAttr);
if (typeof stateData !== "object" || isNil(stateData) || Array.isArray(stateData)) {
report(new Error(`data-volt-state must be a JSON object, got ${typeof stateData}`), {
source: "charge",
level: "fatal",
element: el as HTMLElement,
directive: "data-volt-state",
expression: stateAttr,
});
} else {
for (const [key, value] of Object.entries(stateData)) {
scope[key] = signal(value);
}
}
} catch (error) {
report(error as Error, {
source: "charge",
level: "fatal",
element: el as HTMLElement,
directive: "data-volt-state",
expression: stateAttr,
});
}
}
const computedAttrs = getComputedAttributes(el);
for (const [name, expression] of computedAttrs) {
try {
scope[name] = computed(() => evaluate(expression, scope));
} catch (error) {
report(error as Error, {
source: "charge",
element: el as HTMLElement,
directive: `data-volt-computed:${name}`,
expression: expression,
});
}
}
return scope;
}
/**
* Parse and register global store from declarative script tags.
*
* Looks for: