v0.2.4
change: $NodeManager.$elementList rename to childList. update: $Container.insert() and $NodeManager.add() now can insert element with position. add: $Video element is avaliable. add: $Media element is avaliable. update: $State now can set with other $State object. change: The host of this repository is changed.
This commit is contained in:
parent
72f617df3c
commit
f614ecd5f5
15
$index.ts
15
$index.ts
@ -19,6 +19,7 @@ import { $Textarea } from "./lib/node/$Textarea";
|
||||
import { $Util } from "./lib/$Util";
|
||||
import { $HTMLElement } from "./lib/node/$HTMLElement";
|
||||
import { $Async } from "./lib/node/$Async";
|
||||
import { $Video } from "./lib/node/$Video";
|
||||
|
||||
export type $ = typeof $;
|
||||
export function $<E extends $Element = $Element>(query: `::${string}`): E[];
|
||||
@ -88,6 +89,7 @@ export namespace $ {
|
||||
'option': $Option,
|
||||
'optgroup': $OptGroup,
|
||||
'textarea': $Textarea,
|
||||
'video': $Video,
|
||||
'async': $Async,
|
||||
}
|
||||
export type TagNameElementMapType = typeof TagNameElementMap;
|
||||
@ -112,6 +114,7 @@ export namespace $ {
|
||||
: H extends HTMLOptionElement ? $Option
|
||||
: H extends HTMLOptGroupElement ? $OptGroup
|
||||
: H extends HTMLTextAreaElement ? $Textarea
|
||||
: H extends HTMLVideoElement ? $Video
|
||||
: $Container<H>;
|
||||
|
||||
/**
|
||||
@ -142,27 +145,27 @@ export namespace $ {
|
||||
* @param handle callback when param `value` is $State object.
|
||||
* @returns
|
||||
*/
|
||||
export function set<O extends Object, K extends keyof O, V>(
|
||||
export function set<O extends Object, K extends keyof O>(
|
||||
object: O,
|
||||
key: K,
|
||||
value: O[K] extends (...args: any) => any
|
||||
? (undefined | $StateArgument<Parameters<O[K]>>)
|
||||
? (undefined | [$StateArgument<Parameters<O[K]>>])
|
||||
: (undefined | $StateArgument<O[K]>),
|
||||
handle?: ($state: $State<O[K]>) => any) {
|
||||
if (value === undefined) return;
|
||||
if (value instanceof $State) {
|
||||
value.use(object, key);
|
||||
if (object[key] instanceof Function) (object[key] as Function)(value)
|
||||
if (object[key] instanceof Function) (object[key] as Function)(...value.value)
|
||||
else object[key] = value.value;
|
||||
if (handle) handle(value);
|
||||
return;
|
||||
}
|
||||
if (object[key] instanceof Function) (object[key] as Function)(value);
|
||||
if (object[key] instanceof Function) (object[key] as Function)(...value as any);
|
||||
else object[key] = value as any;
|
||||
}
|
||||
|
||||
export function state<T>(value: T, options?: $StateOption<T>) {
|
||||
return new $State<T>(value, options)
|
||||
export function state<T>(value: T, options?: $StateOption<T extends $State<infer K> ? K : T>) {
|
||||
return new $State<T>(value, options as $StateOption<T>) as T extends $State<infer K> ? $State<K> : $State<T>;
|
||||
}
|
||||
|
||||
export async function resize(object: Blob, size: number): Promise<string> {
|
||||
|
6
index.ts
6
index.ts
@ -26,7 +26,7 @@ declare global {
|
||||
Array.prototype.detype = function <T extends undefined | null, O>(this: O[], ...types: T[]) {
|
||||
return this.filter(item => {
|
||||
if (!types.length) return item !== undefined;
|
||||
else for (const type of types) if (typeof item !== typeof type) return false; else return true
|
||||
else for (const type of types) if (typeof item !== typeof type) return true; else return false;
|
||||
}) as Exclude<O, T>[];
|
||||
}
|
||||
export * from "./$index";
|
||||
@ -47,4 +47,6 @@ export * from "./lib/node/$OptGroup";
|
||||
export * from "./lib/node/$Textarea";
|
||||
export * from "./lib/node/$Image";
|
||||
export * from "./lib/node/$Async";
|
||||
export * from "./lib/node/$Document";
|
||||
export * from "./lib/node/$Document";
|
||||
export * from "./lib/node/$Media";
|
||||
export * from "./lib/node/$Video";
|
@ -3,32 +3,33 @@ import { $Node } from "./node/$Node";
|
||||
import { $Text } from "./node/$Text";
|
||||
|
||||
export class $NodeManager {
|
||||
$container: $Container;
|
||||
$elementList = new Set<$Node>
|
||||
readonly $container: $Container;
|
||||
readonly childList = new Set<$Node>
|
||||
constructor(container: $Container) {
|
||||
this.$container = container;
|
||||
}
|
||||
|
||||
add(element: $Node | string) {
|
||||
if (typeof element === 'string') {
|
||||
const text = new $Text(element);
|
||||
this.$elementList.add(text);
|
||||
(text as Mutable<$Node>).parent = this.$container;
|
||||
} else {
|
||||
this.$elementList.add(element);
|
||||
add(element: $Node, position = -1) {
|
||||
if (position === -1 || this.childList.size - 1 === position) {
|
||||
this.childList.add(element);
|
||||
(element as Mutable<$Node>).parent = this.$container;
|
||||
} else {
|
||||
const children = [...this.childList]
|
||||
children.splice(position, 0, element);
|
||||
this.childList.clear();
|
||||
children.forEach(child => this.childList.add(child));
|
||||
}
|
||||
}
|
||||
|
||||
remove(element: $Node) {
|
||||
if (!this.$elementList.has(element)) return this;
|
||||
this.$elementList.delete(element);
|
||||
if (!this.childList.has(element)) return this;
|
||||
this.childList.delete(element);
|
||||
(element as Mutable<$Node>).parent = undefined;
|
||||
return this;
|
||||
}
|
||||
|
||||
removeAll(render = true) {
|
||||
this.$elementList.forEach(ele => this.remove(ele));
|
||||
this.childList.forEach(ele => this.remove(ele));
|
||||
if (render) this.render();
|
||||
}
|
||||
|
||||
@ -36,8 +37,8 @@ export class $NodeManager {
|
||||
const array = this.array
|
||||
array.splice(array.indexOf(target), 1, replace);
|
||||
target.remove();
|
||||
this.$elementList.clear();
|
||||
array.forEach(node => this.$elementList.add(node));
|
||||
this.childList.clear();
|
||||
array.forEach(node => this.childList.add(node));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -60,7 +61,7 @@ export class $NodeManager {
|
||||
}
|
||||
}
|
||||
|
||||
get array() {return [...this.$elementList.values()]};
|
||||
get array() {return [...this.childList.values()]};
|
||||
|
||||
get dom() {return this.$container.dom}
|
||||
}
|
@ -2,28 +2,35 @@ export interface $StateOption<T> {
|
||||
format: (value: T) => string;
|
||||
}
|
||||
export class $State<T> {
|
||||
readonly value!: T;
|
||||
protected _value!: T | $State<T>;
|
||||
readonly attributes = new Map<Object, Set<string | number | symbol>>();
|
||||
readonly linkStates = new Set<$State<T>>;
|
||||
options: Partial<$StateOption<T>> = {}
|
||||
constructor(value: T, options?: $StateOption<T>) {
|
||||
this.set(value);
|
||||
if (options) this.options = options;
|
||||
}
|
||||
set(value: T) {
|
||||
(this as Mutable<$State<T>>).value = value;
|
||||
set(value: T | $State<T>) {
|
||||
this._value = value;
|
||||
if (value instanceof $State) value.linkStates.add(this as any);
|
||||
this.update();
|
||||
this.linkStates.forEach($state => $state.update());
|
||||
}
|
||||
|
||||
protected update() {
|
||||
// update element content for eatch attributes
|
||||
for (const [node, attrList] of this.attributes.entries()) {
|
||||
for (const attr of attrList) {
|
||||
//@ts-expect-error
|
||||
if (node[attr] instanceof Function) {
|
||||
//@ts-expect-error
|
||||
if (this.options.format) node[attr](this.options.format(value))
|
||||
if (this.options.format) node[attr](this.options.format(this.value))
|
||||
//@ts-expect-error
|
||||
else node[attr](value)
|
||||
else node[attr](this.value)
|
||||
}
|
||||
else if (attr in node) {
|
||||
//@ts-expect-error
|
||||
node[attr] = value
|
||||
node[attr] = this.value
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -56,6 +63,10 @@ export class $State<T> {
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
get value(): T {
|
||||
return this._value instanceof $State ? this._value.value as T : this._value;
|
||||
}
|
||||
};
|
||||
|
||||
export type $StateArgument<T> = T | $State<T> | undefined;
|
||||
export type $StateArgument<T> = $State<T> | undefined | (T extends (infer R)[] ? R : T);
|
@ -20,18 +20,22 @@ export class $Container<H extends HTMLElement = HTMLElement> extends $HTMLElemen
|
||||
this.insert(children);
|
||||
})}
|
||||
|
||||
private __position_cursor = 0;
|
||||
/**Insert element to this element */
|
||||
insert(children: $ContainerContentBuilder<this>): this { return $.fluent(this, arguments, () => this, () => {
|
||||
insert(children: $ContainerContentBuilder<this>, position = -1): this { return $.fluent(this, arguments, () => this, () => {
|
||||
if (children instanceof Function) children = children(this);
|
||||
children = $.orArrayResolve(children);
|
||||
this.__position_cursor = position < 0 ? this.children.array.length + position : position;
|
||||
for (const child of children) {
|
||||
if (child === undefined) continue;
|
||||
if (child instanceof Array) this.insert(child)
|
||||
if (child === undefined || child === null) continue;
|
||||
if (child instanceof Array) this.insert(child, this.__position_cursor);
|
||||
else if (typeof child === 'string') this.children.add(new $Text(child), position);
|
||||
else if (child instanceof $State) {
|
||||
const ele = new $Text(child.toString());
|
||||
child.use(ele, 'content');
|
||||
this.children.add(ele);
|
||||
} else this.children.add(child);
|
||||
this.children.add(ele, position);
|
||||
} else this.children.add(child, position);
|
||||
this.__position_cursor += 1;
|
||||
}
|
||||
this.children.render();
|
||||
})}
|
||||
@ -61,4 +65,4 @@ export class $Container<H extends HTMLElement = HTMLElement> extends $HTMLElemen
|
||||
}
|
||||
|
||||
export type $ContainerContentBuilder<P extends $Container> = OrMatrix<$ContainerContentType> | (($node: P) => OrMatrix<$ContainerContentType>)
|
||||
export type $ContainerContentType = $Node | string | undefined | $State<any>
|
||||
export type $ContainerContentType = $Node | string | undefined | $State<any> | null
|
106
lib/node/$Media.ts
Normal file
106
lib/node/$Media.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import { $State, $StateArgument } from "../$State";
|
||||
import { $Element, $ElementOptions } from "./$Element";
|
||||
|
||||
export interface $MediaOptions extends $ElementOptions {}
|
||||
export class $Media<H extends HTMLMediaElement> extends $Element<H> {
|
||||
constructor(tagname: string, options?: $MediaOptions) {
|
||||
super(tagname, options);
|
||||
}
|
||||
|
||||
autoplay(): boolean;
|
||||
autoplay(autoplay: $StateArgument<boolean>): this;
|
||||
autoplay(autoplay?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.autoplay, () => $.set<HTMLMediaElement, 'autoplay'>(this.dom, 'autoplay', autoplay))}
|
||||
|
||||
get buffered() { return this.dom.buffered }
|
||||
|
||||
controls(): boolean;
|
||||
controls(controls: $StateArgument<boolean>): this;
|
||||
controls(controls?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.controls, () => $.set<HTMLMediaElement, 'controls'>(this.dom, 'controls', controls))}
|
||||
|
||||
crossOrigin(): string | null;
|
||||
crossOrigin(crossOrigin: $StateArgument<string | null>): this;
|
||||
crossOrigin(crossOrigin?: $StateArgument<string | null>) { return $.fluent(this, arguments, () => this.dom.crossOrigin, () => $.set<HTMLMediaElement, 'crossOrigin'>(this.dom, 'crossOrigin', crossOrigin))}
|
||||
|
||||
get currentSrc() { return this.dom.currentSrc };
|
||||
|
||||
currentTime(): number;
|
||||
currentTime(currentTime: $StateArgument<number>): this;
|
||||
currentTime(currentTime?: $StateArgument<number>) { return $.fluent(this, arguments, () => this.dom.currentTime, () => $.set<HTMLMediaElement, 'currentTime'>(this.dom, 'currentTime', currentTime))}
|
||||
|
||||
defaultMuted(): boolean;
|
||||
defaultMuted(defaultMuted: $StateArgument<boolean>): this;
|
||||
defaultMuted(defaultMuted?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.defaultMuted, () => $.set<HTMLMediaElement, 'defaultMuted'>(this.dom, 'defaultMuted', defaultMuted))}
|
||||
|
||||
defaultPlaybackRate(): number;
|
||||
defaultPlaybackRate(defaultPlaybackRate: $StateArgument<number>): this;
|
||||
defaultPlaybackRate(defaultPlaybackRate?: $StateArgument<number>) { return $.fluent(this, arguments, () => this.dom.defaultPlaybackRate, () => $.set<HTMLMediaElement, 'defaultPlaybackRate'>(this.dom, 'defaultPlaybackRate', defaultPlaybackRate))}
|
||||
|
||||
disableRemotePlayback(): boolean;
|
||||
disableRemotePlayback(disableRemotePlayback: $StateArgument<boolean>): this;
|
||||
disableRemotePlayback(disableRemotePlayback?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.disableRemotePlayback, () => $.set<HTMLMediaElement, 'disableRemotePlayback'>(this.dom, 'disableRemotePlayback', disableRemotePlayback))}
|
||||
|
||||
get duration() { return this.dom.duration }
|
||||
get ended() { return this.dom.ended }
|
||||
get error() { return this.dom.error }
|
||||
|
||||
loop(): boolean;
|
||||
loop(loop: $StateArgument<boolean>): this;
|
||||
loop(loop?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.loop, () => $.set<HTMLMediaElement, 'loop'>(this.dom, 'loop', loop))}
|
||||
|
||||
mediaKeys(): MediaKeys | null;
|
||||
mediaKeys(mediaKeys: $StateArgument<[MediaKeys | null]>): this;
|
||||
mediaKeys(mediaKeys?: $StateArgument<[MediaKeys | null]>) { return $.fluent(this, arguments, () => this.dom.mediaKeys, () => $.set<HTMLMediaElement, 'setMediaKeys'>(this.dom, 'setMediaKeys', [mediaKeys]))}
|
||||
|
||||
muted(): boolean;
|
||||
muted(muted: $StateArgument<boolean>): this;
|
||||
muted(muted?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.muted, () => $.set<HTMLMediaElement, 'muted'>(this.dom, 'muted', muted))}
|
||||
|
||||
get networkState() { return this.dom.networkState }
|
||||
get paused() { return this.dom.paused }
|
||||
|
||||
playbackRate(): number;
|
||||
playbackRate(playbackRate: $StateArgument<number>): this;
|
||||
playbackRate(playbackRate?: $StateArgument<number>) { return $.fluent(this, arguments, () => this.dom.playbackRate, () => $.set<HTMLMediaElement, 'playbackRate'>(this.dom, 'playbackRate', playbackRate))}
|
||||
|
||||
get played() { return this.dom.played }
|
||||
|
||||
preload(): this['dom']['preload'];
|
||||
preload(preload: $StateArgument<this['dom']['preload']>): this;
|
||||
preload(preload?: $StateArgument<this['dom']['preload']>) { return $.fluent(this, arguments, () => this.dom.preload, () => $.set<HTMLMediaElement, 'preload'>(this.dom, 'preload', preload))}
|
||||
|
||||
preservesPitch(): boolean;
|
||||
preservesPitch(preservesPitch: $StateArgument<boolean>): this;
|
||||
preservesPitch(preservesPitch?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.preservesPitch, () => $.set<HTMLMediaElement, 'preservesPitch'>(this.dom, 'preservesPitch', preservesPitch))}
|
||||
|
||||
get readyState() { return this.dom.readyState }
|
||||
get remote() { return this.dom.remote }
|
||||
get seekable() { return this.dom.seekable }
|
||||
get seeking() { return this.dom.seeking }
|
||||
|
||||
sinkId(): string;
|
||||
sinkId(sinkId: $StateArgument<[string]>): this;
|
||||
sinkId(sinkId?: $StateArgument<[string]>) { return $.fluent(this, arguments, () => this.dom.sinkId, () => $.set<HTMLMediaElement, 'setSinkId'>(this.dom, 'setSinkId', [sinkId]))}
|
||||
|
||||
src(): string;
|
||||
src(src: $StateArgument<string>): this;
|
||||
src(src?: $StateArgument<string>) { return $.fluent(this, arguments, () => this.dom.src, () => $.set<HTMLMediaElement, 'src'>(this.dom, 'src', src))}
|
||||
|
||||
srcObject(): MediaProvider | null;
|
||||
srcObject(srcObject: $StateArgument<MediaProvider | null>): this;
|
||||
srcObject(srcObject?: $StateArgument<MediaProvider | null>) { return $.fluent(this, arguments, () => this.dom.srcObject, () => $.set<HTMLMediaElement, 'srcObject'>(this.dom, 'srcObject', srcObject))}
|
||||
|
||||
get textTracks() { return this.dom.textTracks }
|
||||
|
||||
volume(): number;
|
||||
volume(volume: $StateArgument<number>): this;
|
||||
volume(volume?: $StateArgument<number>) { return $.fluent(this, arguments, () => this.dom.volume, () => $.set<HTMLMediaElement, 'volume'>(this.dom, 'volume', volume))}
|
||||
|
||||
addTextTrack(kind: TextTrackKind, label?: string, language?: string) { return this.dom.addTextTrack(kind, label, language)}
|
||||
canPlayType(type: string) { return this.dom.canPlayType(type) }
|
||||
fastSeek(time: number) { this.dom.fastSeek(time); return this }
|
||||
load() { this.dom.load(); return this }
|
||||
pause() { this.dom.pause(); return this }
|
||||
async play() { await this.dom.play(); return this}
|
||||
|
||||
get isPlaying() { return this.currentTime() > 0 && !this.paused && !this.ended && this.readyState > 2 }
|
||||
}
|
37
lib/node/$Video.ts
Normal file
37
lib/node/$Video.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { $StateArgument } from "../$State";
|
||||
import { $Media, $MediaOptions } from "./$Media";
|
||||
|
||||
export interface $VideoOptions extends $MediaOptions {}
|
||||
export class $Video extends $Media<HTMLVideoElement> {
|
||||
constructor(options?: $VideoOptions) {
|
||||
super('video', options)
|
||||
}
|
||||
|
||||
disablePictureInPicture(): boolean;
|
||||
disablePictureInPicture(disablePictureInPicture: $StateArgument<boolean>): this;
|
||||
disablePictureInPicture(disablePictureInPicture?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.disablePictureInPicture, () => $.set(this.dom, 'disablePictureInPicture', disablePictureInPicture))}
|
||||
|
||||
height(): number;
|
||||
height(height: $StateArgument<number>): this;
|
||||
height(height?: $StateArgument<number>) { return $.fluent(this, arguments, () => this.dom.height, () => $.set(this.dom, 'height', height))}
|
||||
|
||||
width(): number;
|
||||
width(width: $StateArgument<number>): this;
|
||||
width(width?: $StateArgument<number>) { return $.fluent(this, arguments, () => this.dom.width, () => $.set(this.dom, 'width', width))}
|
||||
|
||||
playsInline(): boolean;
|
||||
playsInline(playsInline: $StateArgument<boolean>): this;
|
||||
playsInline(playsInline?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.playsInline, () => $.set(this.dom, 'playsInline', playsInline))}
|
||||
|
||||
poster(): string;
|
||||
poster(poster: $StateArgument<string>): this;
|
||||
poster(poster?: $StateArgument<string>) { return $.fluent(this, arguments, () => this.dom.poster, () => $.set(this.dom, 'poster', poster))}
|
||||
|
||||
get videoHeight() { return this.dom.videoHeight }
|
||||
get videoWidth() { return this.dom.videoWidth }
|
||||
|
||||
cancelVideoFrameCallback(handle: number) { this.dom.cancelVideoFrameCallback(handle); return this }
|
||||
getVideoPlaybackQuality() { return this.dom.getVideoPlaybackQuality() }
|
||||
requestPictureInPicture() { return this.dom.requestPictureInPicture() }
|
||||
requestVideoFrameCallback(callback: VideoFrameRequestCallback) { return this.dom.requestVideoFrameCallback(callback) }
|
||||
}
|
10
package.json
10
package.json
@ -1,21 +1,21 @@
|
||||
{
|
||||
"name": "elexis",
|
||||
"description": "Build Web in Native JavaScript Syntax",
|
||||
"version": "0.2.3",
|
||||
"version": "0.2.4",
|
||||
"author": {
|
||||
"name": "defaultkavy",
|
||||
"email": "defaultkavy@gmail.com",
|
||||
"url": "https://github.com/defaultkavy"
|
||||
"url": "https://git.defaultkavy.com/defaultkavy"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/defaultkavy/elexis.git"
|
||||
"url": "git+https://git.defaultkavy.com/defaultkavy/elexis.git"
|
||||
},
|
||||
"module": "index.ts",
|
||||
"bugs": {
|
||||
"url": "https://github.com/defaultkavy/elexis/issues"
|
||||
"url": "https://git.defaultkavy.com/defaultkavy/elexis/issues"
|
||||
},
|
||||
"homepage": "https://github.com/defaultkavy/elexis",
|
||||
"homepage": "https://git.defaultkavy.com/defaultkavy/elexis",
|
||||
"keywords": ["web", "front-end", "lib", "fluent", "framework"],
|
||||
"license": "ISC",
|
||||
"type": "module"
|
||||
|
Loading…
Reference in New Issue
Block a user