mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-11 04:38:18 +00:00
Add force init lifecycle option
This commit is contained in:
@@ -48,6 +48,7 @@
|
||||
## JavaScript
|
||||
- JavaScript is behavior only; do not use JS to paper over CSS architecture issues.
|
||||
- Register JS components with `window.basecoat.register()` and make initialization idempotent with `data-*-initialized` flags.
|
||||
- Component init functions must return before attaching listeners or mutating DOM when the root already has its initialized flag.
|
||||
- Keep generated DOM minimal and aligned with documented HTML.
|
||||
- Prefer native browser APIs and events.
|
||||
- Keep scroll management scoped to component containers. Do not use `scrollIntoView()` when it can scroll the page unexpectedly.
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Added explicit `force: true` support to `window.basecoat.init()` and `window.basecoat.initAll()` for rehydrating restored DOM after framework or navigation cache restores.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Reinitialized Basecoat components after HTMX history restores in the docs site.
|
||||
|
||||
## [1.0.0] - 2026-06-27
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
@@ -92,6 +92,7 @@ These entrypoints are generated by `scripts/generate-css-entrypoints.js` and by
|
||||
|
||||
- `window.basecoat.init(name)` initializes uninitialized components for one registered component.
|
||||
- `window.basecoat.initAll()` initializes all uninitialized registered components.
|
||||
- `window.basecoat.init(name, { force: true })` and `window.basecoat.initAll({ force: true })` destroy and reinitialize matching components. Use this when a framework or navigation library restores previously initialized DOM from a cache. Force mode resets transient component state such as open menus, focus state, and active interactions.
|
||||
- `window.basecoat.refresh(element)` asks an initialized component to rescan dynamic children when it supports refresh.
|
||||
- Components use internal destroy hooks to clean up listeners when initialized roots are removed from the DOM.
|
||||
|
||||
|
||||
@@ -159,7 +159,33 @@ const styleUrls = {
|
||||
}
|
||||
};
|
||||
|
||||
const clearSerializedRuntimeState = (root) => {
|
||||
for (const element of root.querySelectorAll?.("*") || []) {
|
||||
delete element.dataset.basecoatComponent;
|
||||
for (const attribute of Array.from(element.attributes)) {
|
||||
if (/^data-[a-z0-9-]+-initialized$/.test(attribute.name)) {
|
||||
element.removeAttribute(attribute.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const cleanHistoryItem = (event) => {
|
||||
const item = event.detail?.item;
|
||||
if (typeof item?.content !== "string") return;
|
||||
|
||||
const template = document.createElement("template");
|
||||
template.innerHTML = item.content;
|
||||
clearSerializedRuntimeState(template.content);
|
||||
item.content = template.innerHTML;
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", syncSidebar);
|
||||
document.addEventListener("htmx:historyItemCreated", cleanHistoryItem);
|
||||
document.addEventListener("htmx:historyRestore", () => {
|
||||
window.basecoat?.initAll?.({ force: true });
|
||||
syncSidebar();
|
||||
});
|
||||
document.addEventListener("htmx:afterSettle", () => {
|
||||
window.basecoat?.initAll?.();
|
||||
syncSidebar();
|
||||
|
||||
@@ -243,6 +243,14 @@ Chart uses a separate helper and is not included in `basecoat-css/all`.
|
||||
|
||||
Each component page shows the minimal scripts or imports required for that component.
|
||||
|
||||
### JavaScript lifecycle
|
||||
|
||||
Basecoat initializes registered components on page load and when new DOM is inserted.
|
||||
|
||||
Use `window.basecoat.initAll()` after manually inserting HTML that contains Basecoat components. It only initializes components that are not already initialized.
|
||||
|
||||
Use `window.basecoat.initAll({ force: true })` when a framework or navigation library restores previously initialized DOM from a cache, such as an HTMX history restore. Force mode destroys existing component instances before reinitializing them, so it also resets transient state such as open menus, focus state, and active interactions.
|
||||
|
||||
## Compatibility
|
||||
|
||||
Basecoat 1.0 introduces new style packs and a smaller public API based on root classes plus semantic HTML and documented attributes. If you need pre-1.0 class aliases while migrating, load the compatibility stylesheet after your Basecoat stylesheet.
|
||||
|
||||
+26
-6
@@ -63,9 +63,24 @@
|
||||
node.querySelectorAll('[data-basecoat-component]').forEach(destroyComponent);
|
||||
};
|
||||
|
||||
const initAllComponents = () => {
|
||||
const uniqueElements = (elements) => Array.from(new Set(elements));
|
||||
|
||||
const getComponentElements = (componentName, selector, force = false) => {
|
||||
const elements = Array.from(document.querySelectorAll(selector));
|
||||
if (force) {
|
||||
elements.push(...document.querySelectorAll(`[data-basecoat-component="${componentName}"]`));
|
||||
}
|
||||
return uniqueElements(elements);
|
||||
};
|
||||
|
||||
const initAllComponents = (options = {}) => {
|
||||
const force = options.force === true;
|
||||
Object.entries(componentRegistry).forEach(([name, { selector }]) => {
|
||||
document.querySelectorAll(selector).forEach(element => initComponent(element, name));
|
||||
getComponentElements(name, selector, force).forEach((element) => {
|
||||
const wasComponent = element.dataset?.basecoatComponent === name;
|
||||
if (force) destroyComponent(element);
|
||||
if (wasComponent || element.matches(selector)) initComponent(element, name);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -111,18 +126,23 @@
|
||||
observer = null;
|
||||
};
|
||||
|
||||
const initRegisteredComponent = (componentName) => {
|
||||
const initRegisteredComponent = (componentName, options = {}) => {
|
||||
const component = componentRegistry[componentName];
|
||||
if (!component) {
|
||||
console.warn(`Component '${componentName}' not found in registry`);
|
||||
return;
|
||||
}
|
||||
|
||||
document.querySelectorAll(component.selector).forEach(element => initComponent(element, componentName));
|
||||
const force = options.force === true;
|
||||
getComponentElements(componentName, component.selector, force).forEach((element) => {
|
||||
const wasComponent = element.dataset?.basecoatComponent === componentName;
|
||||
if (force) destroyComponent(element);
|
||||
if (wasComponent || element.matches(component.selector)) initComponent(element, componentName);
|
||||
});
|
||||
};
|
||||
|
||||
const initAllRegisteredComponents = () => {
|
||||
initAllComponents();
|
||||
const initAllRegisteredComponents = (options = {}) => {
|
||||
initAllComponents(options);
|
||||
};
|
||||
|
||||
const setTheme = (mode) => {
|
||||
|
||||
Reference in New Issue
Block a user