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!')
|
$('h1').class('amazing-title').css({color: 'red'}).content('Fluuuuuuuuuuuuent!')
|
||||||
```
|
```
|
||||||
|
|
||||||
## Router? I got you.
|
## Single Page App with Router
|
||||||
```ts
|
```ts
|
||||||
const router = new Router('/')
|
const router = new Router('/')
|
||||||
// example.com
|
// example.com
|
||||||
@ -36,10 +36,8 @@ const router = new Router('/')
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
.listen() // start resolve pathname and listen state change
|
.listen() // start resolve pathname and listen state change
|
||||||
```
|
|
||||||
|
|
||||||
## Single Page App
|
// prevent jump to other page from <a> link
|
||||||
```ts
|
|
||||||
$.anchorPreventDefault = true;
|
$.anchorPreventDefault = true;
|
||||||
$.anchorHandler = (url) => { router.open(url) }
|
$.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 Autocapitalize = 'none' | 'off' | 'sentences' | 'on' | 'words' | 'characters';
|
||||||
type SelectionDirection = "forward" | "backward" | "none";
|
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 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 ButtonType = "submit" | "reset" | "button" | "menu";
|
||||||
type TextDirection = 'ltr' | 'rtl' | 'auto' | '';
|
type TextDirection = 'ltr' | 'rtl' | 'auto' | '';
|
||||||
type ImageDecoding = "async" | "sync" | "auto";
|
type ImageDecoding = "async" | "sync" | "auto";
|
||||||
|
@ -16,7 +16,7 @@ export class $Container<H extends HTMLElement = HTMLElement> extends $Element<H>
|
|||||||
* @example Element.content([$('div')])
|
* @example Element.content([$('div')])
|
||||||
* Element.content('Hello World')*/
|
* Element.content('Hello World')*/
|
||||||
content(children: $ContainerContentBuilder<this>): this { return $.fluent(this, arguments, () => this, () => {
|
content(children: $ContainerContentBuilder<this>): this { return $.fluent(this, arguments, () => this, () => {
|
||||||
this.children.removeAll();
|
this.children.removeAll(false);
|
||||||
this.insert(children);
|
this.insert(children);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
@ -105,11 +105,17 @@ export class $Element<H extends HTMLElement = HTMLElement> extends $Node<H> {
|
|||||||
hidden(hidden?: boolean): this;
|
hidden(hidden?: boolean): this;
|
||||||
hidden(hidden?: boolean) { return $.fluent(this, arguments, () => this.dom.hidden, () => $.set(this.dom, 'hidden', hidden))}
|
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; }
|
click() { this.dom.click(); return this; }
|
||||||
attachInternals() { return this.dom.attachInternals(); }
|
attachInternals() { return this.dom.attachInternals(); }
|
||||||
hidePopover() { this.dom.hidePopover(); return this; }
|
hidePopover() { this.dom.hidePopover(); return this; }
|
||||||
showPopover() { this.dom.showPopover(); return this; }
|
showPopover() { this.dom.showPopover(); return this; }
|
||||||
togglePopover() { this.dom.togglePopover(); 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) {
|
animate(keyframes: Keyframe[] | PropertyIndexedKeyframes | null, options?: number | KeyframeAnimationOptions, callback?: (animation: Animation) => void) {
|
||||||
const animation = this.dom.animate(keyframes, options);
|
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 offsetParent() { return $(this.dom.offsetParent) }
|
||||||
get offsetTop() { return this.dom.offsetTop }
|
get offsetTop() { return this.dom.offsetTop }
|
||||||
get offsetWidth() { return this.dom.offsetWidth }
|
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): this;
|
||||||
type(type?: InputType) { return $.fluent(this, arguments, () => this.dom.type, () => $.set(this.dom, 'type', type))}
|
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 | null;
|
||||||
valueAsDate(date: Date | null): this;
|
valueAsDate(date: Date | null): this;
|
||||||
valueAsDate(date?: Date | null) { return $.fluent(this, arguments, () => this.dom.valueAsDate, () => $.set(this.dom, 'valueAsDate', date))}
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAll() {
|
removeAll(render = true) {
|
||||||
this.elementList.forEach(ele => this.remove(ele))
|
this.elementList.forEach(ele => this.remove(ele));
|
||||||
|
if (render) this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
replace(target: $Node, replace: $Node) {
|
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 {
|
export interface RouteRecordEventMap {
|
||||||
'open': [path: string, record: RouteRecord];
|
'open': [{path: string, record: RouteRecord}];
|
||||||
'load': [path: string, record: RouteRecord];
|
'load': [{path: string, record: RouteRecord}];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RouteRequest<Path extends PathResolverFn | string> {
|
export interface RouteRequest<Path extends PathResolverFn | string> {
|
||||||
|
@ -34,7 +34,7 @@ export class Router {
|
|||||||
addEventListener('popstate', this.popstate)
|
addEventListener('popstate', this.popstate)
|
||||||
$.routers.add(this);
|
$.routers.add(this);
|
||||||
this.resolvePath();
|
this.resolvePath();
|
||||||
this.events.fire('pathchange', location.href, 'Forward');
|
this.events.fire('pathchange', {path: location.href, navigation: 'Forward'});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ export class Router {
|
|||||||
const routeData: RouteData = { index: this.index, data: {} };
|
const routeData: RouteData = { index: this.index, data: {} };
|
||||||
history.pushState(routeData, '', path);
|
history.pushState(routeData, '', path);
|
||||||
$.routers.forEach(router => router.resolvePath())
|
$.routers.forEach(router => router.resolvePath())
|
||||||
this.events.fire('pathchange', path, 'Forward');
|
this.events.fire('pathchange', {path, navigation: 'Forward'});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ export class Router {
|
|||||||
replace(path: string) {
|
replace(path: string) {
|
||||||
history.replaceState({index: this.index}, '', path)
|
history.replaceState({index: this.index}, '', path)
|
||||||
$.routers.forEach(router => router.resolvePath(path));
|
$.routers.forEach(router => router.resolvePath(path));
|
||||||
this.events.fire('pathchange', path, 'Forward');
|
this.events.fire('pathchange', {path, navigation: 'Forward'});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,19 +73,19 @@ export class Router {
|
|||||||
else if (history.state.index < this.index) { }
|
else if (history.state.index < this.index) { }
|
||||||
this.index = history.state.index;
|
this.index = history.state.index;
|
||||||
this.resolvePath();
|
this.resolvePath();
|
||||||
this.events.fire('pathchange', location.pathname, 'Forward');
|
this.events.fire('pathchange', {path: location.pathname, navigation: 'Forward'});
|
||||||
}).bind(this)
|
}).bind(this)
|
||||||
|
|
||||||
private resolvePath(path = location.pathname) {
|
private resolvePath(path = location.pathname) {
|
||||||
if (!path.startsWith(this.basePath)) return;
|
if (!path.startsWith(this.basePath)) return;
|
||||||
path = path.replace(this.basePath, '/').replace('//', '/')
|
path = path.replace(this.basePath, '/').replace('//', '/');
|
||||||
let found = false;
|
let found = false;
|
||||||
const openCached = (pathId: string) => {
|
const openCached = (pathId: string) => {
|
||||||
const record = this.recordMap.get(pathId);
|
const record = this.recordMap.get(pathId);
|
||||||
if (record) {
|
if (record) {
|
||||||
found = true;
|
found = true;
|
||||||
if (record.content && !this.view.contains(record.content)) this.view.content(record.content);
|
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 true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -96,15 +96,15 @@ export class Router {
|
|||||||
params: data,
|
params: data,
|
||||||
record: record,
|
record: record,
|
||||||
loaded: () => {
|
loaded: () => {
|
||||||
record.events.fire('load', pathId, record);
|
record.events.fire('load', {path: pathId, record});
|
||||||
this.events.fire('load', pathId);
|
this.events.fire('load', {path: pathId});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (typeof content === 'string') content = new $Text(content);
|
if (typeof content === 'string') content = new $Text(content);
|
||||||
(record as Mutable<RouteRecord>).content = content;
|
(record as Mutable<RouteRecord>).content = content;
|
||||||
this.recordMap.set(pathId, record);
|
this.recordMap.set(pathId, record);
|
||||||
this.view.content(content);
|
this.view.content(content);
|
||||||
record.events.fire('open', path, record);
|
record.events.fire('open', {path, record});
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
for (const [pathResolver, route] of this.routeMap.entries()) {
|
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 {
|
interface RouterEventMap {
|
||||||
pathchange: [path: string, navigation: 'Back' | 'Forward'];
|
pathchange: [{path: string, navigation: 'Back' | 'Forward'}];
|
||||||
notfound: [path: string];
|
notfound: [{path: string, preventDefault: () => void}];
|
||||||
load: [path: string];
|
load: [{path: string}];
|
||||||
}
|
}
|
||||||
|
|
||||||
type RouteData = {
|
type RouteData = {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "fluentx",
|
"name": "fluentx",
|
||||||
"description": "Fast, fluent, simple web builder",
|
"description": "Fast, fluent, simple web builder",
|
||||||
"version": "0.0.2",
|
"version": "0.0.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
"author": {
|
"author": {
|
||||||
|
Loading…
Reference in New Issue
Block a user