added dismount lifecycle
This commit is contained in:
36
src/lifecycle.ts
Normal file
36
src/lifecycle.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
const CLEANUP_KEY = Symbol('cleanup_func');
|
||||
|
||||
export function onDismount(el: Node, fn: () => void) {
|
||||
const element = el as any;
|
||||
if (!element[CLEANUP_KEY]) {
|
||||
element[CLEANUP_KEY] = [];
|
||||
}
|
||||
|
||||
element[CLEANUP_KEY].push(fn);
|
||||
}
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach(mutation => {
|
||||
mutation.removedNodes.forEach(node => {
|
||||
deepCleanup(node);
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
function deepCleanup(node: Node) {
|
||||
const el = node as any;
|
||||
|
||||
if (el[CLEANUP_KEY]) {
|
||||
el[CLEANUP_KEY].forEach((fn: () => void) => fn());
|
||||
el[CLEANUP_KEY] = null;
|
||||
}
|
||||
|
||||
if (node.hasChildNodes()) {
|
||||
node.childNodes.forEach(deepCleanup)
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,11 @@ export interface RouteContext {
|
||||
query: QueryParams
|
||||
}
|
||||
|
||||
type NextFunction = () => void;
|
||||
type RouteHandler = (ctx: RouteContext) => void;
|
||||
type RouteGuard = (ctx: RouteContext, next: NextFunction) => void;
|
||||
export type NextFunction = () => void;
|
||||
export type RouteHandler = (ctx: RouteContext) => HTMLElement;
|
||||
export type RouteGuard = (ctx: RouteContext, next: NextFunction) => void;
|
||||
|
||||
export type LayoutHandler = () => { view: HTMLElement, slot: HTMLElement }
|
||||
|
||||
interface Route {
|
||||
path: string;
|
||||
@@ -16,6 +18,7 @@ interface Route {
|
||||
keys: string[],
|
||||
handler: RouteHandler;
|
||||
guards: RouteGuard[];
|
||||
layout?: LayoutHandler
|
||||
}
|
||||
|
||||
export class Router {
|
||||
@@ -24,6 +27,9 @@ export class Router {
|
||||
private rootElement: HTMLElement | null = null;
|
||||
private isTransitioning = false;
|
||||
|
||||
private currentLayout: LayoutHandler | null = null;
|
||||
private currentSlot: HTMLElement | null = null;
|
||||
|
||||
private static instance: Router | null = null;
|
||||
|
||||
constructor(rootId: string) {
|
||||
@@ -41,7 +47,7 @@ export class Router {
|
||||
}
|
||||
}
|
||||
|
||||
public addRoute(path: string, handler: RouteHandler, guards: RouteGuard[] = []) {
|
||||
public addRoute(path: string, handler: RouteHandler, guards: RouteGuard[] = [], layout?: LayoutHandler) {
|
||||
const keys: string[] = [];
|
||||
const regexpPath = path.replace(/:(\w+)/g, (_, key) => {
|
||||
keys.push(key);
|
||||
@@ -51,7 +57,7 @@ export class Router {
|
||||
this.routes.push({
|
||||
path,
|
||||
regex: new RegExp(`^${regexpPath}$`),
|
||||
keys, handler, guards
|
||||
keys, handler, guards, layout
|
||||
})
|
||||
}
|
||||
|
||||
@@ -76,9 +82,33 @@ export class Router {
|
||||
const ctx: RouteContext = { params, query };
|
||||
|
||||
this.runGuards(route.guards, ctx, () => {
|
||||
const pageElement = route.handler(ctx);
|
||||
|
||||
const layoutChanged = route.layout !== this.currentLayout;
|
||||
const transitionTarget = layoutChanged ? this.rootElement : this.currentSlot
|
||||
|
||||
|
||||
this.performTransition(() => {
|
||||
route.handler(ctx);
|
||||
})
|
||||
if (layoutChanged) {
|
||||
if (route.layout) {
|
||||
const { view, slot } = route.layout();
|
||||
this.rootElement!.innerHTML = '';
|
||||
this.rootElement!.appendChild(view);
|
||||
this.currentSlot = slot;
|
||||
this.currentLayout = route.layout;
|
||||
} else {
|
||||
this.rootElement!.innerHTML = '';
|
||||
this.currentSlot = this.rootElement;
|
||||
this.currentLayout = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.currentSlot) {
|
||||
this.currentSlot.innerHTML = ''
|
||||
this.currentSlot.appendChild(pageElement);
|
||||
}
|
||||
|
||||
}, transitionTarget)
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -100,14 +130,15 @@ export class Router {
|
||||
next();
|
||||
}
|
||||
|
||||
private performTransition(updateDomFn: () => void) {
|
||||
if (!this.rootElement) {
|
||||
private performTransition(updateDomFn: () => void, targetElement: HTMLElement | null) {
|
||||
const el = targetElement || this.rootElement;
|
||||
|
||||
if (!el) {
|
||||
updateDomFn();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isTransitioning = true;
|
||||
|
||||
this.rootElement.classList.add('fading');
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
Reference in New Issue
Block a user