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
|
query: QueryParams
|
||||||
}
|
}
|
||||||
|
|
||||||
type NextFunction = () => void;
|
export type NextFunction = () => void;
|
||||||
type RouteHandler = (ctx: RouteContext) => void;
|
export type RouteHandler = (ctx: RouteContext) => HTMLElement;
|
||||||
type RouteGuard = (ctx: RouteContext, next: NextFunction) => void;
|
export type RouteGuard = (ctx: RouteContext, next: NextFunction) => void;
|
||||||
|
|
||||||
|
export type LayoutHandler = () => { view: HTMLElement, slot: HTMLElement }
|
||||||
|
|
||||||
interface Route {
|
interface Route {
|
||||||
path: string;
|
path: string;
|
||||||
@@ -16,6 +18,7 @@ interface Route {
|
|||||||
keys: string[],
|
keys: string[],
|
||||||
handler: RouteHandler;
|
handler: RouteHandler;
|
||||||
guards: RouteGuard[];
|
guards: RouteGuard[];
|
||||||
|
layout?: LayoutHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Router {
|
export class Router {
|
||||||
@@ -24,6 +27,9 @@ export class Router {
|
|||||||
private rootElement: HTMLElement | null = null;
|
private rootElement: HTMLElement | null = null;
|
||||||
private isTransitioning = false;
|
private isTransitioning = false;
|
||||||
|
|
||||||
|
private currentLayout: LayoutHandler | null = null;
|
||||||
|
private currentSlot: HTMLElement | null = null;
|
||||||
|
|
||||||
private static instance: Router | null = null;
|
private static instance: Router | null = null;
|
||||||
|
|
||||||
constructor(rootId: string) {
|
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 keys: string[] = [];
|
||||||
const regexpPath = path.replace(/:(\w+)/g, (_, key) => {
|
const regexpPath = path.replace(/:(\w+)/g, (_, key) => {
|
||||||
keys.push(key);
|
keys.push(key);
|
||||||
@@ -51,7 +57,7 @@ export class Router {
|
|||||||
this.routes.push({
|
this.routes.push({
|
||||||
path,
|
path,
|
||||||
regex: new RegExp(`^${regexpPath}$`),
|
regex: new RegExp(`^${regexpPath}$`),
|
||||||
keys, handler, guards
|
keys, handler, guards, layout
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,9 +82,33 @@ export class Router {
|
|||||||
const ctx: RouteContext = { params, query };
|
const ctx: RouteContext = { params, query };
|
||||||
|
|
||||||
this.runGuards(route.guards, ctx, () => {
|
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(() => {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
@@ -100,14 +130,15 @@ export class Router {
|
|||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
private performTransition(updateDomFn: () => void) {
|
private performTransition(updateDomFn: () => void, targetElement: HTMLElement | null) {
|
||||||
if (!this.rootElement) {
|
const el = targetElement || this.rootElement;
|
||||||
|
|
||||||
|
if (!el) {
|
||||||
updateDomFn();
|
updateDomFn();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isTransitioning = true;
|
this.isTransitioning = true;
|
||||||
|
|
||||||
this.rootElement.classList.add('fading');
|
this.rootElement.classList.add('fading');
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user