version 0.0.3
con: Route/Router event callback function use object to pass params change: $NodeManager.removeAll() with render params add: type InputMode update: README
This commit is contained in:
parent
1489b1dba5
commit
0b3ca308d6
@ -22,7 +22,7 @@ $('a')
|
||||
$('h1').class('amazing-title').css({color: 'red'}).content('Fluuuuuuuuuuuuent!')
|
||||
```
|
||||
|
||||
## Router? I got you.
|
||||
## Single Page App with Router
|
||||
```ts
|
||||
const router = new Router('/')
|
||||
// example.com
|
||||
@ -36,10 +36,8 @@ const router = new Router('/')
|
||||
}))
|
||||
|
||||
.listen() // start resolve pathname and listen state change
|
||||
```
|
||||
|
||||
## Single Page App
|
||||
```ts
|
||||
// prevent jump to other page from <a> link
|
||||
$.anchorPreventDefault = true;
|
||||
$.anchorHandler = (url) => { router.open(url) }
|
||||
|
||||
|
1
index.ts
1
index.ts
@ -13,6 +13,7 @@ declare global {
|
||||
type Autocapitalize = 'none' | 'off' | 'sentences' | 'on' | 'words' | 'characters';
|
||||
type SelectionDirection = "forward" | "backward" | "none";
|
||||
type InputType = "button" | "checkbox" | "color" | "date" | "datetime-local" | "email" | "file" | "hidden" | "image" | "month" | "number" | "password" | "radio" | "range" | "reset" | "search" | "submit" | "tel" | "text" | "time" | "url" | "week";
|
||||
type InputMode = "" | "none" | "text" | "decimal" | "numeric" | "tel" | "search" | "email" | "url";
|
||||
type ButtonType = "submit" | "reset" | "button" | "menu";
|
||||
type TextDirection = 'ltr' | 'rtl' | 'auto' | '';
|
||||
type ImageDecoding = "async" | "sync" | "auto";
|
||||
|
@ -16,7 +16,7 @@ export class $Container<H extends HTMLElement = HTMLElement> extends $Element<H>
|
||||
* @example Element.content([$('div')])
|
||||
* Element.content('Hello World')*/
|
||||
content(children: $ContainerContentBuilder<this>): this { return $.fluent(this, arguments, () => this, () => {
|
||||
this.children.removeAll();
|
||||
this.children.removeAll(false);
|
||||
this.insert(children);
|
||||
})}
|
||||
|
||||
|
@ -105,11 +105,17 @@ export class $Element<H extends HTMLElement = HTMLElement> extends $Node<H> {
|
||||
hidden(hidden?: boolean): this;
|
||||
hidden(hidden?: boolean) { return $.fluent(this, arguments, () => this.dom.hidden, () => $.set(this.dom, 'hidden', hidden))}
|
||||
|
||||
tabIndex(): number;
|
||||
tabIndex(tabIndex: number): this;
|
||||
tabIndex(tabIndex?: number) { return $.fluent(this, arguments, () => this.dom.tabIndex, () => $.set(this.dom, 'tabIndex', tabIndex))}
|
||||
|
||||
click() { this.dom.click(); return this; }
|
||||
attachInternals() { return this.dom.attachInternals(); }
|
||||
hidePopover() { this.dom.hidePopover(); return this; }
|
||||
showPopover() { this.dom.showPopover(); return this; }
|
||||
togglePopover() { this.dom.togglePopover(); return this; }
|
||||
focus() { this.dom.focus(); return this; }
|
||||
blur() { this.dom.blur(); return this; }
|
||||
|
||||
animate(keyframes: Keyframe[] | PropertyIndexedKeyframes | null, options?: number | KeyframeAnimationOptions, callback?: (animation: Animation) => void) {
|
||||
const animation = this.dom.animate(keyframes, options);
|
||||
@ -125,4 +131,5 @@ export class $Element<H extends HTMLElement = HTMLElement> extends $Node<H> {
|
||||
get offsetParent() { return $(this.dom.offsetParent) }
|
||||
get offsetTop() { return this.dom.offsetTop }
|
||||
get offsetWidth() { return this.dom.offsetWidth }
|
||||
get dataset() { return this.dom.dataset }
|
||||
}
|
@ -118,6 +118,10 @@ export class $Input extends $Element<HTMLInputElement> {
|
||||
type(type: InputType): this;
|
||||
type(type?: InputType) { return $.fluent(this, arguments, () => this.dom.type, () => $.set(this.dom, 'type', type))}
|
||||
|
||||
inputMode(): InputMode;
|
||||
inputMode(mode: InputMode): this;
|
||||
inputMode(mode?: InputMode) { return $.fluent(this, arguments, () => this.dom.inputMode as InputMode, () => $.set(this.dom, 'inputMode', mode))}
|
||||
|
||||
valueAsDate(): Date | null;
|
||||
valueAsDate(date: Date | null): this;
|
||||
valueAsDate(date?: Date | null) { return $.fluent(this, arguments, () => this.dom.valueAsDate, () => $.set(this.dom, 'valueAsDate', date))}
|
||||
|
@ -29,8 +29,9 @@ export class $NodeManager {
|
||||
return this;
|
||||
}
|
||||
|
||||
removeAll() {
|
||||
this.elementList.forEach(ele => this.remove(ele))
|
||||
removeAll(render = true) {
|
||||
this.elementList.forEach(ele => this.remove(ele));
|
||||
if (render) this.render();
|
||||
}
|
||||
|
||||
replace(target: $Node, replace: $Node) {
|
||||
|
79
lib/Router/README.md
Normal file
79
lib/Router/README.md
Normal file
@ -0,0 +1,79 @@
|
||||
# fluentX/router
|
||||
|
||||
## Usage
|
||||
```ts
|
||||
import { $, Router, Route } from 'fluentX';
|
||||
|
||||
// create new Router with base path '/',
|
||||
// also create a custom view Element for router container
|
||||
const router = new Router('/', $('view'));
|
||||
|
||||
// append router view element
|
||||
const $app = $('app').content([
|
||||
router.$view
|
||||
])
|
||||
|
||||
const home_page_route = new Route('/', () => {
|
||||
// which this callback function return will be appended
|
||||
// into router view element when path match
|
||||
return $('h1').content('This is a Homepage!')
|
||||
})
|
||||
|
||||
// Add home_page_route into router
|
||||
router.addRoute(home_page_route);
|
||||
// Router starting listen location path change
|
||||
router.listen();
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
### Router - load
|
||||
```ts
|
||||
const gallery_route = new Route('/gallery', ({loaded}) => {
|
||||
|
||||
async function $ImageList() {
|
||||
// fetch images and return elements
|
||||
|
||||
// after all image loaded, using loaded function.
|
||||
// this will trigger load event on anccestor router
|
||||
loaded();
|
||||
}
|
||||
return $('div').content([
|
||||
$ImageList()
|
||||
])
|
||||
})
|
||||
|
||||
router.on('load', () => {...})
|
||||
```
|
||||
|
||||
### RouteRecord - open
|
||||
```ts
|
||||
const viewer_route = new Route('/viewer', ({record}) => {
|
||||
const page_open_count$ = $.state(0);
|
||||
|
||||
// this event will be fire everytime this page opened
|
||||
record.on('open', () => {
|
||||
page_open_count$.set( page_open_count$.value + 1 );
|
||||
})
|
||||
|
||||
return $('div').content(page_open_count$)
|
||||
})
|
||||
```
|
||||
|
||||
### Router - notfound
|
||||
```ts
|
||||
// Route will remove all child of view when path is not exist.
|
||||
// Using preventDefault function to prevent this action.
|
||||
router.on('notfound', ({preventDefault}) => {
|
||||
preventDefault(); // prevent remove all child of view
|
||||
... // do something
|
||||
})
|
||||
```
|
||||
|
||||
### Router - pathchange
|
||||
```ts
|
||||
// This event fired on location change happened
|
||||
router.on('pathchange', () => {
|
||||
... // do something
|
||||
})
|
||||
```
|
@ -35,8 +35,8 @@ export class RouteRecord {
|
||||
}
|
||||
|
||||
export interface RouteRecordEventMap {
|
||||
'open': [path: string, record: RouteRecord];
|
||||
'load': [path: string, record: RouteRecord];
|
||||
'open': [{path: string, record: RouteRecord}];
|
||||
'load': [{path: string, record: RouteRecord}];
|
||||
}
|
||||
|
||||
export interface RouteRequest<Path extends PathResolverFn | string> {
|
||||
|
@ -34,7 +34,7 @@ export class Router {
|
||||
addEventListener('popstate', this.popstate)
|
||||
$.routers.add(this);
|
||||
this.resolvePath();
|
||||
this.events.fire('pathchange', location.href, 'Forward');
|
||||
this.events.fire('pathchange', {path: location.href, navigation: 'Forward'});
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ export class Router {
|
||||
const routeData: RouteData = { index: this.index, data: {} };
|
||||
history.pushState(routeData, '', path);
|
||||
$.routers.forEach(router => router.resolvePath())
|
||||
this.events.fire('pathchange', path, 'Forward');
|
||||
this.events.fire('pathchange', {path, navigation: 'Forward'});
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ export class Router {
|
||||
replace(path: string) {
|
||||
history.replaceState({index: this.index}, '', path)
|
||||
$.routers.forEach(router => router.resolvePath(path));
|
||||
this.events.fire('pathchange', path, 'Forward');
|
||||
this.events.fire('pathchange', {path, navigation: 'Forward'});
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -73,19 +73,19 @@ export class Router {
|
||||
else if (history.state.index < this.index) { }
|
||||
this.index = history.state.index;
|
||||
this.resolvePath();
|
||||
this.events.fire('pathchange', location.pathname, 'Forward');
|
||||
this.events.fire('pathchange', {path: location.pathname, navigation: 'Forward'});
|
||||
}).bind(this)
|
||||
|
||||
private resolvePath(path = location.pathname) {
|
||||
if (!path.startsWith(this.basePath)) return;
|
||||
path = path.replace(this.basePath, '/').replace('//', '/')
|
||||
path = path.replace(this.basePath, '/').replace('//', '/');
|
||||
let found = false;
|
||||
const openCached = (pathId: string) => {
|
||||
const record = this.recordMap.get(pathId);
|
||||
if (record) {
|
||||
found = true;
|
||||
if (record.content && !this.view.contains(record.content)) this.view.content(record.content);
|
||||
record.events.fire('open', path, record);
|
||||
record.events.fire('open', {path, record});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -96,15 +96,15 @@ export class Router {
|
||||
params: data,
|
||||
record: record,
|
||||
loaded: () => {
|
||||
record.events.fire('load', pathId, record);
|
||||
this.events.fire('load', pathId);
|
||||
record.events.fire('load', {path: pathId, record});
|
||||
this.events.fire('load', {path: pathId});
|
||||
}
|
||||
});
|
||||
if (typeof content === 'string') content = new $Text(content);
|
||||
(record as Mutable<RouteRecord>).content = content;
|
||||
this.recordMap.set(pathId, record);
|
||||
this.view.content(content);
|
||||
record.events.fire('open', path, record);
|
||||
record.events.fire('open', {path, record});
|
||||
found = true;
|
||||
}
|
||||
for (const [pathResolver, route] of this.routeMap.entries()) {
|
||||
@ -142,13 +142,18 @@ export class Router {
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) this.events.fire('notfound', path);
|
||||
if (!found) {
|
||||
let preventDefaultState = false;
|
||||
const preventDefault = () => preventDefaultState = true;
|
||||
this.events.fire('notfound', {path, preventDefault});
|
||||
if (!preventDefaultState) this.view.children.removeAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
interface RouterEventMap {
|
||||
pathchange: [path: string, navigation: 'Back' | 'Forward'];
|
||||
notfound: [path: string];
|
||||
load: [path: string];
|
||||
pathchange: [{path: string, navigation: 'Back' | 'Forward'}];
|
||||
notfound: [{path: string, preventDefault: () => void}];
|
||||
load: [{path: string}];
|
||||
}
|
||||
|
||||
type RouteData = {
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "fluentx",
|
||||
"description": "Fast, fluent, simple web builder",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"type": "module",
|
||||
"module": "index.ts",
|
||||
"author": {
|
||||
|
Loading…
Reference in New Issue
Block a user