Compare commits

...

2 Commits

Author SHA1 Message Date
fd4caf0b4a add logo and icons 2024-04-26 18:38:32 +08:00
18bbc4922a v0.2.0
new: ElexisJS icons and logo
change: README.md
update: $Input.type() will convert input into Number/Check/File Input with several methods
update: $.set() [methodKey] parameter will change to [handle] function, handle() will be called when [value] parameter is $State.
add: $State .toJSON()
2024-04-26 18:12:14 +08:00
35 changed files with 0 additions and 1741 deletions

242
$index.ts
View File

@ -1,242 +0,0 @@
import { $State, $StateArgument, $StateOption } from "./index";
import { $Node } from "./lib/node/$Node"
import { $Document } from "./lib/node/$Document"
import { $Anchor } from "./lib/node/$Anchor";
import { $Button } from "./lib/node/$Button";
import { $Form } from "./lib/node/$Form";
import { $Input } from "./lib/node/$Input";
import { $Container } from "./lib/node/$Container";
import { $Element } from "./lib/node/$Element";
import { $Label } from "./lib/node/$Label";
import { $Image } from "./lib/node/$Image";
import { $Canvas } from "./lib/node/$Canvas";
import { $Dialog } from "./lib/node/$Dialog";
import { $View } from "./lib/node/$View";
import { $Select } from "./lib/node/$Select";
import { $Option } from "./lib/node/$Option";
import { $OptGroup } from "./lib/node/$OptGroup";
import { $Textarea } from "./lib/node/$Textarea";
import { $Util } from "./lib/$Util";
import { $HTMLElement } from "./lib/node/$HTMLElement";
import { $Async } from "./lib/node/$Async";
export type $ = typeof $;
export function $<E extends $Element = $Element>(query: `::${string}`): E[];
export function $<E extends $Element = $Element>(query: `:${string}`): E | null;
export function $(element: null): null;
export function $<K extends keyof $.TagNameTypeMap>(resolver: K): $.TagNameTypeMap[K];
export function $<K extends string>(resolver: K): $Container;
export function $<H extends HTMLElement>(htmlElement: H): $.$HTMLElementMap<H>;
export function $<H extends Element>(element: H): $Element;
export function $<N extends $Node>(node: N): N;
export function $<H extends EventTarget>(element: H): $Element;
export function $(element: null | HTMLElement | EventTarget): $Element | null;
export function $(element: undefined): undefined;
export function $(resolver: any) {
if (typeof resolver === 'undefined') return resolver;
if (resolver === null) return resolver;
if (resolver instanceof $Node) return resolver;
if (typeof resolver === 'string') {
if (resolver.startsWith('::')) return Array.from(document.querySelectorAll(resolver.replace(/^::/, ''))).map(dom => $(dom));
else if (resolver.startsWith(':')) return $(document.querySelector(resolver.replace(/^:/, '')));
else if (resolver in $.TagNameElementMap) {
const instance = $.TagNameElementMap[resolver as keyof $.TagNameElementMap]
if (instance === $HTMLElement) return new $HTMLElement(resolver);
if (instance === $Container) return new $Container(resolver);
//@ts-expect-error
return new instance();
} else return new $Container(resolver);
}
if (resolver instanceof Node) {
if (resolver.$) return resolver.$;
else return $Util.from(resolver);
}
throw `$: NOT SUPPORT TARGET ELEMENT TYPE ('${resolver}')`
}
export namespace $ {
export let anchorHandler: null | (($a: $Anchor, e: Event) => void) = null;
export let anchorPreventDefault: boolean = false;
export const TagNameElementMap = {
'document': $Document,
'body': $Container,
'a': $Anchor,
'p': $Container,
'pre': $Container,
'code': $Container,
'blockquote': $Container,
'strong': $Container,
'h1': $Container,
'h2': $Container,
'h3': $Container,
'h4': $Container,
'h5': $Container,
'h6': $Container,
'div': $Container,
'ol': $Container,
'ul': $Container,
'dl': $Container,
'li': $Container,
'input': $Input,
'label': $Label,
'button': $Button,
'form': $Form,
'img': $Image,
'dialog': $Dialog,
'canvas': $Canvas,
'view': $View,
'select': $Select,
'option': $Option,
'optgroup': $OptGroup,
'textarea': $Textarea,
'async': $Async,
}
export type TagNameElementMapType = typeof TagNameElementMap;
export interface TagNameElementMap extends TagNameElementMapType {}
export type TagNameTypeMap = {
[key in keyof $.TagNameElementMap]: InstanceType<$.TagNameElementMap[key]>;
};
export type ContainerTypeTagName = Exclude<keyof TagNameTypeMap, 'input'>;
export type SelfTypeTagName = 'input';
export type $HTMLElementMap<H extends HTMLElement> =
H extends HTMLLabelElement ? $Label
: H extends HTMLInputElement ? $Input
: H extends HTMLAnchorElement ? $Anchor
: H extends HTMLButtonElement ? $Button
: H extends HTMLFormElement ? $Form
: H extends HTMLImageElement ? $Image
: H extends HTMLFormElement ? $Form
: H extends HTMLCanvasElement ? $Canvas
: H extends HTMLDialogElement ? $Dialog
: H extends HTMLSelectElement ? $Select
: H extends HTMLOptionElement ? $Option
: H extends HTMLOptGroupElement ? $OptGroup
: H extends HTMLTextAreaElement ? $Textarea
: $Container<H>;
/**
* A helper for fluent method design. Return the `instance` object when arguments length not equal 0. Otherwise, return the `value`.
* @param instance The object to return when arguments length not equal 0.
* @param args The method `arguments`.
* @param value The value to return when arguments length equal 0.
* @param action The action to execute when arguments length not equal 0.
* @returns
*/
export function fluent<T, A, V>(instance: T, args: IArguments, value: () => V, action: (...args: any[]) => void) {
if (!args.length) return value();
action();
return instance;
}
export function orArrayResolve<T>(multable: OrArray<T>) {
if (multable instanceof Array) return multable;
else return [multable];
}
export function mixin(target: any, constructors: OrArray<any>) { return $Util.mixin(target, constructors) }
/**
* A helper for undefined able value and $State.set() which apply value to target.
* @param object Target object.
* @param key The key of target object.
* @param value Value of target property or parameter of method(Using Tuple to apply parameter).
* @param methodKey Variant key name when apply value on $State.set()
* @returns
*/
export function set<O, K extends keyof O>(
object: O,
key: K,
value: O[K] extends (...args: any) => any
? (undefined | $StateArgument<Parameters<O[K]>>)
: (undefined | $StateArgument<O[K]>),
methodKey?: string) {
if (value === undefined) return;
if (value instanceof $State && object instanceof Node) {
value.use(object.$, methodKey ?? key as any);
if (object[key] instanceof Function) (object[key] as Function)(value)
else object[key] = value.value;
return;
}
if (object[key] instanceof Function) (object[key] as Function)(value);
else object[key] = value as any;
}
export function state<T>(value: T, options?: $StateOption<T>) {
return new $State<T>(value, options)
}
export async function resize(object: Blob, size: number): Promise<string> {
return new Promise(resolve => {
const reader = new FileReader();
reader.onload = (e) => {
const $img = $('img');
$img.once('load', e => {
const $canvas = $('canvas');
const context = $canvas.getContext('2d');
const ratio = $img.height() / $img.width();
const [w, h] = [
ratio > 1 ? size / ratio : size,
ratio > 1 ? size : size * ratio,
]
$canvas.height(h).width(w);
context?.drawImage($img.dom, 0, 0, w, h);
resolve($canvas.toDataURL(object.type))
})
if (!e.target) throw "$.resize(): e.target is null";
$img.src(e.target.result as string);
}
reader.readAsDataURL(object);
})
}
export function rem(amount: number = 1) {
return parseInt(getComputedStyle(document.documentElement).fontSize) * amount
}
export function html(html: string) {
const body = new DOMParser().parseFromString(html, 'text/html').body;
return Array.from(body.children).map(child => $(child))
}
/**Build multiple element in once. */
export function builder<F extends BuildNodeFunction, R extends ReturnType<F>>(bulder: F, params: [...Parameters<F>][], callback?: BuilderSelfFunction<R>): R[]
export function builder<F extends BuildNodeFunction, R extends ReturnType<F>>(bulder: [F, ...Parameters<F>], size: number, callback?: BuilderSelfFunction<R>): R[]
export function builder<F extends BuildNodeFunction, R extends ReturnType<F>>(bulder: [F, ...Parameters<F>], options: ($Node | string | BuilderSelfFunction<R>)[]): R[]
export function builder<K extends $.SelfTypeTagName>(tagname: K, size: number, callback?: BuilderSelfFunction<$.TagNameTypeMap[K]>): $.TagNameTypeMap[K][]
export function builder<K extends $.SelfTypeTagName>(tagname: K, callback: BuilderSelfFunction<$.TagNameTypeMap[K]>[]): $.TagNameTypeMap[K][]
export function builder<K extends $.ContainerTypeTagName>(tagname: K, size: number, callback?: BuilderSelfFunction<$.TagNameTypeMap[K]>): $.TagNameTypeMap[K][]
export function builder<K extends $.ContainerTypeTagName>(tagname: K, options: ($Node | string | BuilderSelfFunction<$.TagNameTypeMap[K]>)[]): $.TagNameTypeMap[K][]
export function builder(tagname: any, resolver: any, callback?: BuilderSelfFunction<any>) {
if (typeof resolver === 'number') {
return Array(resolver).fill('').map(v => {
const ele = isTuppleBuilder(tagname) ? tagname[0](...tagname.slice(1) as []) : $(tagname);
if (callback) callback(ele);
return ele
});
}
else {
const eleArray = [];
for (const item of resolver) {
const ele = tagname instanceof Function ? tagname(...item) // tagname is function, item is params
: isTuppleBuilder(tagname) ? tagname[0](...tagname.slice(1) as [])
: $(tagname);
if (item instanceof Function) { item(ele) }
else if (item instanceof $Node || typeof item === 'string') { ele.content(item) }
eleArray.push(ele);
}
return eleArray;
}
function isTuppleBuilder(target: any): target is [BuildNodeFunction, ...any] {
if (target instanceof Array && target[0] instanceof Function) return true;
else return false;
}
}
export function registerTagName(string: string, node: {new(...args: undefined[]): $Node}) {
Object.assign($.TagNameElementMap, {[string]: node});
return $.TagNameElementMap;
}
}
type BuildNodeFunction = (...args: any[]) => $Node;
type BuilderSelfFunction<K extends $Node> = (self: K) => void;
globalThis.$ = $;

3
.gitignore vendored
View File

@ -1,3 +0,0 @@
.npmignore
bun.lockb
node_modules

View File

@ -1,44 +0,0 @@
# ElexisJS
TypeScript First Web Framework, for Humans.
> ElexisJS is still in beta test now, some breaking changes might happen very often.
## What does ElexisJS bring to developer?
1. Write website with Native JavaScript syntax and full TypeScript development experiance, no more HTML or JSX.
2. For fluent method lovers.
3. Easy to import or create extensions to extend more functional.
## Installation
1. Install from npm
```
npm i elexis
```
2. Import to your project main entry js/ts file.
```ts
import 'elexis';
```
3. Use web packaging tools like [Vite](https://vitejs.dev/) to compile your project.
## How to Create Element
Using the simple $ function to create any element with node name.
```ts
$('a');
```
> This is not jQuery selector! It looks like same but it actually create `<a>` element, not selecting them.
## Fluent method
Create and modify element in one line.
```ts
$('h1').class('title').css({color: 'red'})
```
## Build your first "Hello, world!" ElexisJS project
Let's try this in your entry file:
```ts
$(document.body).content([
$('h1').class('title').content('Hello, world!')
])
```
## Extensions
1. [@elexis/router](https://github.com/elexisjs/router): Router for Single Page App.
2. [@elexis/layout](https://github.com/elexisjs/layout): Build waterfall/justified layout with automatic compute content size and position.

BIN
icon_dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
icon_light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -1,50 +0,0 @@
declare global {
var $: import('./$index').$;
interface Array<T> {
detype<F extends undefined | null, O>(...types: F[]): Array<Exclude<T, F>>
}
type OrMatrix<T> = T | OrMatrix<T>[];
type OrArray<T> = T | T[];
type OrPromise<T> = T | Promise<T>;
type Mutable<T> = {
-readonly [k in keyof T]: T[k];
};
type Types = 'string' | 'number' | 'boolean' | 'object' | 'symbol' | 'bigint' | 'function' | 'undefined'
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";
type ImageLoading = "eager" | "lazy";
type ConstructorType<T> = { new (...args: any[]): T }
interface Node {
$: import('./lib/node/$Node').$Node;
}
}
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
}) as Exclude<O, T>[];
}
export * from "./$index";
export * from "./lib/node/$Node";
export * from "./lib/node/$Anchor";
export * from "./lib/node/$Element";
export * from "./lib/$NodeManager";
export * from "./lib/node/$Text";
export * from "./lib/node/$Container";
export * from "./lib/node/$Button";
export * from "./lib/node/$Form";
export * from "./lib/$EventManager";
export * from "./lib/$State";
export * from "./lib/node/$View";
export * from "./lib/node/$Select";
export * from "./lib/node/$Option";
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";

View File

@ -1,65 +0,0 @@
export abstract class $EventMethod<EM> {
abstract events: $EventManager<EM>;
//@ts-expect-error
on<K extends keyof EM>(type: K, callback: (...args: EM[K]) => any) { this.events.on(type, callback); return this }
//@ts-expect-error
off<K extends keyof EM>(type: K, callback: (...args: EM[K]) => any) { this.events.off(type, callback); return this }
//@ts-expect-error
once<K extends keyof EM>(type: K, callback: (...args: EM[K]) => any) { this.events.once(type, callback); return this }
}
export class $EventManager<EM> {
private eventMap = new Map<string, $Event>();
register(...names: string[]) {
names.forEach(name => {
const event = new $Event(name);
this.eventMap.set(event.name, event);
})
return this;
}
delete(name: string) { this.eventMap.delete(name); return this }
//@ts-expect-error
fire<K extends keyof EM>(type: K, ...args: EM[K]) {
const event = this.get(type)
//@ts-expect-error
if (event instanceof $Event) event.fire(...args);
return this
}
//@ts-expect-error
on<K extends keyof EM>(type: K, callback: (...args: EM[K]) => any) {
this.get(type).add(callback);
return this
}
//@ts-expect-error
off<K extends keyof EM>(type: K, callback: (...args: EM[K]) => any) {
this.get(type).delete(callback);
return this
}
//@ts-expect-error
once<K extends keyof EM>(type: K, callback: (...args: EM[K]) => any) {
//@ts-expect-error
const onceFn = (...args: EM[K]) => {
this.get(type).delete(onceFn);
//@ts-expect-error
callback(...args);
}
this.get(type).add(onceFn);
return this;
}
get<K extends keyof EM>(type: K) {
//@ts-expect-error
const event = this.eventMap.get(type);
if (!event) throw new Error('EVENT NOT EXIST')
return event;
}
}
export class $Event {
name: string;
private callbackList = new Set<Function>()
constructor(name: string) {
this.name = name;
}
fire(...args: any[]) { this.callbackList.forEach(callback => callback(...args)) }
add(callback: Function) { this.callbackList.add(callback) }
delete(callback: Function) { this.callbackList.delete(callback) }
}

View File

@ -1,66 +0,0 @@
import { $Container } from "./node/$Container";
import { $Node } from "./node/$Node";
import { $Text } from "./node/$Text";
export class $NodeManager {
$container: $Container;
$elementList = 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);
(element as Mutable<$Node>).parent = this.$container;
}
}
remove(element: $Node) {
if (!this.$elementList.has(element)) return this;
this.$elementList.delete(element);
(element as Mutable<$Node>).parent = undefined;
return this;
}
removeAll(render = true) {
this.$elementList.forEach(ele => this.remove(ele));
if (render) this.render();
}
replace(target: $Node, replace: $Node) {
const array = this.array
array.splice(array.indexOf(target), 1, replace);
target.remove();
this.$elementList.clear();
array.forEach(node => this.$elementList.add(node));
return this;
}
render() {
const [domList, nodeList] = [this.array.map(node => node.dom), Array.from(this.dom.childNodes)];
const appendedNodeList: Node[] = []; // appended node list
// Rearrange
while (nodeList.length || domList.length) { // while nodeList or domList has item
const [node, dom] = [nodeList.at(0), domList.at(0)];
if (!dom) { if (node && !appendedNodeList.includes(node)) node.remove(); nodeList.shift()}
else if (!node) { if (!dom.$.__hidden) this.dom.append(dom); domList.shift();}
else if (dom !== node) {
if (!dom.$.__hidden) { this.dom.insertBefore(dom, node); appendedNodeList.push(dom) }
domList.shift();
}
else {
if (dom.$.__hidden) this.dom.removeChild(dom);
domList.shift(); nodeList.shift();
}
}
}
get array() {return [...this.$elementList.values()]};
get dom() {return this.$container.dom}
}

View File

@ -1,41 +0,0 @@
import { $Node } from "./node/$Node";
export interface $StateOption<T> {
format: (value: T) => string;
}
export class $State<T> {
readonly value: T;
readonly attributes = new Map<$Node, Set<string | number | symbol>>();
options: Partial<$StateOption<T>> = {}
constructor(value: T, options?: $StateOption<T>) {
this.value = value;
if (options) this.options = options;
}
set(value: T) {
(this as Mutable<$State<T>>).value = value;
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))
//@ts-expect-error
else node[attr](value)
}
}
}
}
toString(): string {
if (this.options.format) return this.options.format(this.value);
return `${this.value}`
}
use<T extends $Node, K extends keyof T>($node: T, attrName: K) {
const attrList = this.attributes.get($node)
if (attrList) attrList.add(attrName);
else this.attributes.set($node, new Set<string | number | symbol>().add(attrName))
}
};
export type $StateArgument<T> = T | $State<T | undefined>;

View File

@ -1,67 +0,0 @@
import { $State } from "./$State";
import { $Container } from "./node/$Container";
import { $Document } from "./node/$Document";
import { $Node } from "./node/$Node";
import { $SVGElement } from "./node/$SVGElement";
import { $Text } from "./node/$Text";
export namespace $Util {
export function fluent<T, A, V>(instance: T, args: IArguments, value: () => V, action: (...args: any[]) => void) {
if (!args.length) return value();
action();
return instance;
}
export function orArrayResolve<T>(multable: OrArray<T>) {
if (multable instanceof Array) return multable;
else return [multable];
}
export function mixin(target: any, constructors: OrArray<any>) {
orArrayResolve(constructors).forEach(constructor => {
Object.getOwnPropertyNames(constructor.prototype).forEach(name => {
if (name === 'constructor') return;
Object.defineProperty(
target.prototype,
name,
Object.getOwnPropertyDescriptor(constructor.prototype, name) || Object.create(null)
)
})
})
return target;
}
export function set<O, K extends keyof O>(object: O, key: K, value: any) {
if (value !== undefined) object[key] = value;
}
export function state<T>(value: T) {
return new $State<T>(value)
}
export function from(element: Node): $Node {
if (element.$) return element.$;
if (element.nodeName.toLowerCase() === 'body') return new $Container('body', {dom: element as HTMLBodyElement});
if (element.nodeName.toLowerCase() === '#document') return $Document.from(element as Document);
else if (element instanceof HTMLElement) {
const instance = $.TagNameElementMap[element.tagName.toLowerCase() as keyof typeof $.TagNameElementMap];
const $node = instance === $Container
? new instance(element.tagName, {dom: element})
//@ts-expect-error
: new instance({dom: element} as any);
if ($node instanceof $Container) for (const childnode of Array.from($node.dom.childNodes)) {
$node.children.add($(childnode as any));
}
return $node as $Node;
}
else if (element instanceof Text) {
const node = new $Text(element.textContent ?? '') as Mutable<$Node>;
node.dom = element;
return node as $Node;
}
else if (element instanceof SVGElement) {
if (element.tagName.toLowerCase() === 'svg') {return new $SVGElement('svg', {dom: element}) };
}
throw `$NODE.FROM: NOT SUPPORT TARGET ELEMENT TYPE (${element.nodeName})`
}
}

View File

@ -1,24 +0,0 @@
import { $Container, $ContainerOptions } from "./$Container";
export interface AnchorOptions extends $ContainerOptions {}
export class $Anchor extends $Container<HTMLAnchorElement> {
constructor(options?: AnchorOptions) {
super('a', options);
// Link Handler event
this.dom.addEventListener('click', e => {
if ($.anchorPreventDefault) e.preventDefault();
if ($.anchorHandler && !!this.href()) $.anchorHandler(this, e)
})
}
/**Set URL of anchor element. */
href(): string;
href(url: string | undefined): this;
href(url?: string | undefined) { return $.fluent(this, arguments, () => this.dom.href, () => {if (url) this.dom.href = url}) }
/**Link open with this window, new tab or other */
target(): $AnchorTarget | undefined;
target(target: $AnchorTarget | undefined): this;
target(target?: $AnchorTarget | undefined) { return $.fluent(this, arguments, () => (this.dom.target ?? undefined) as $AnchorTarget | undefined, () => {if (target) this.dom.target = target}) }
}
export type $AnchorTarget = '_blank' | '_self' | '_parent' | '_top';

View File

@ -1,22 +0,0 @@
import { $Container, $ContainerOptions } from "./$Container";
import { $Node } from "./$Node";
export interface $AsyncNodeOptions extends $ContainerOptions {}
export class $Async<N extends $Node = $Node> extends $Container {
#loaded: boolean = false;
constructor(options?: $AsyncNodeOptions) {
super('async', options)
}
await<T extends $Node = $Node>($node: Promise<T>) {
$node.then($node => this._loaded($node));
return this as $Async<T>
}
protected _loaded($node: $Node) {
this.#loaded = true;
this.replace($node)
this.dom.dispatchEvent(new Event('load'))
}
get loaded() { return this.#loaded }
}

View File

@ -1,39 +0,0 @@
import { $Container, $ContainerOptions } from "./$Container";
import { $State, $StateArgument } from "../$State";
export interface $ButtonOptions extends $ContainerOptions {}
export class $Button extends $Container<HTMLButtonElement> {
constructor(options?: $ButtonOptions) {
super('button', options);
}
disabled(): boolean;
disabled(disabled: $StateArgument<boolean>): this;
disabled(disabled?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.disabled, () => $.set(this.dom, 'disabled', disabled))}
type(): ButtonType;
type(type: ButtonType): this;
type(type?: ButtonType) { return $.fluent(this, arguments, () => this.dom.type as ButtonType, () => $.set(this.dom, 'type', type as any))}
checkValidity() { return this.dom.checkValidity() }
reportValidity() { return this.dom.reportValidity() }
formAction(): string;
formAction(action: string | undefined): this;
formAction(action?: string) { return $.fluent(this, arguments, () => this.dom.formAction, () => $.set(this.dom, 'formAction', action))}
formEnctype(): string;
formEnctype(enctype: string | undefined): this;
formEnctype(enctype?: string) { return $.fluent(this, arguments, () => this.dom.formEnctype, () => $.set(this.dom, 'formEnctype', enctype))}
formMethod(): string;
formMethod(method: string | undefined): this;
formMethod(method?: string) { return $.fluent(this, arguments, () => this.dom.formMethod, () => $.set(this.dom, 'formMethod', method))}
formNoValidate(): boolean;
formNoValidate(boolean: boolean | undefined): this;
formNoValidate(boolean?: boolean) { return $.fluent(this, arguments, () => this.dom.formNoValidate, () => $.set(this.dom, 'formNoValidate', boolean))}
formTarget(): string;
formTarget(target: string | undefined): this;
formTarget(target?: string) { return $.fluent(this, arguments, () => this.dom.formTarget, () => $.set(this.dom, 'formTarget', target))}
}

View File

@ -1,29 +0,0 @@
import { $Container, $ContainerOptions } from "./$Container";
export interface $CanvasOptions extends $ContainerOptions {}
export class $Canvas extends $Container<HTMLCanvasElement> {
constructor(options?: $CanvasOptions) {
super('canvas', options);
}
height(): number;
height(height?: number): this;
height(height?: number) { return $.fluent(this, arguments, () => this.dom.height, () => { $.set(this.dom, 'height', height)}) }
width(): number;
width(width?: number): this;
width(width?: number) { return $.fluent(this, arguments, () => this.dom.width, () => { $.set(this.dom, 'width', width)}) }
captureStream(frameRequestRate?: number) { return this.dom.captureStream(frameRequestRate) }
getContext(contextId: "2d", options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D | null;
getContext(contextId: "bitmaprenderer", options?: ImageBitmapRenderingContextSettings): ImageBitmapRenderingContext | null;
getContext(contextId: "webgl", options?: WebGLContextAttributes): WebGLRenderingContext | null;
getContext(contextId: "webgl2", options?: WebGLContextAttributes): WebGL2RenderingContext | null;
getContext(contextId: string, options?: any): RenderingContext | null { return this.dom.getContext(contextId); }
toBlob(callback: BlobCallback, type?: string, quality?: any) { this.dom.toBlob(callback, type, quality); return this;}
toDataURL(type?: string, quality?: any) { return this.dom.toDataURL(type, quality) }
transferControlToOffscreen() { return this.dom.transferControlToOffscreen() }
}

View File

@ -1,64 +0,0 @@
import { $Element, $ElementOptions } from "./$Element";
import { $NodeManager } from "../$NodeManager";
import { $Node } from "./$Node";
import { $State, $StateArgument } from "../$State";
import { $Text } from "./$Text";
import { $HTMLElement, $HTMLElementOptions } from "./$HTMLElement";
export interface $ContainerOptions extends $HTMLElementOptions {}
export class $Container<H extends HTMLElement = HTMLElement> extends $HTMLElement<H> {
readonly children: $NodeManager = new $NodeManager(this);
constructor(tagname: string, options?: $ContainerOptions) {
super(tagname, options)
}
/**Replace element to this element.
* @example Element.content([$('div')])
* Element.content('Hello World')*/
content(children: $ContainerContentBuilder<this>): this { return $.fluent(this, arguments, () => this, () => {
this.children.removeAll(false);
this.insert(children);
})}
/**Insert element to this element */
insert(children: $ContainerContentBuilder<this>): this { return $.fluent(this, arguments, () => this, () => {
if (children instanceof Function) children = children(this);
children = $.orArrayResolve(children);
for (const child of children) {
if (child === undefined) continue;
if (child instanceof Array) this.insert(child)
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.render();
})}
/**Remove all children elemetn from this element */
clear() {
this.children.removeAll();
return this;
}
//**Query selector one of child element */
$<E extends $Element>(query: string): E | null { return $(this.dom.querySelector(query)) as E | null }
//**Query selector of child elements */
$all<E extends $Element>(query: string): E[] { return Array.from(this.dom.querySelectorAll(query)).map($dom => $($dom) as E) }
get scrollHeight() { return this.dom.scrollHeight }
get scrollWidth() { return this.dom.scrollWidth }
scrollTop(): number;
scrollTop(scrollTop: $StateArgument<number> | undefined): this
scrollTop(scrollTop?: $StateArgument<number> | undefined) { return $.fluent(this, arguments, () => this.dom.scrollTop, () => $.set(this.dom, 'scrollTop', scrollTop as any))}
scrollLeft(): number;
scrollLeft(scrollLeft: $StateArgument<number> | undefined): this
scrollLeft(scrollLeft?: $StateArgument<number> | undefined) { return $.fluent(this, arguments, () => this.dom.scrollLeft, () => $.set(this.dom, 'scrollLeft', scrollLeft as any))}
}
export type $ContainerContentBuilder<P extends $Container> = OrMatrix<$ContainerContentType> | (($node: P) => OrMatrix<$ContainerContentType>)
export type $ContainerContentType = $Node | string | undefined | $State<any>

View File

@ -1,19 +0,0 @@
import { $Container, $ContainerOptions } from "./$Container";
export interface $DialogOptions extends $ContainerOptions {}
export class $Dialog extends $Container<HTMLDialogElement> {
constructor(options?: $DialogOptions) {
super('dialog', options);
}
open(): boolean;
open(open?: boolean): this;
open(open?: boolean) { return $.fluent(this, arguments, () => this.dom.open, () => $.set(this.dom, 'open', open)) }
returnValue(): string;
returnValue(returnValue?: string): this;
returnValue(returnValue?: string) { return $.fluent(this, arguments, () => this.dom.returnValue, () => $.set(this.dom, 'returnValue', returnValue)) }
close() { this.dom.close(); return this; }
show() { this.dom.show(); return this; }
showModal() { this.dom.showModal(); return this; }
}

View File

@ -1,40 +0,0 @@
import { $Element, $DOMRect } from "./$Element";
import { $Node } from "./$Node";
export class $Document extends $Node {
dom: Node;
constructor(document: Document) {
super()
this.dom = document;
this.dom.$ = this;
}
domRect(target?: $Element | $DOMRect) {
const this_rect: $DOMRect = {
bottom: innerHeight,
height: innerHeight,
left: 0,
right: innerWidth,
top: 0,
width: innerWidth,
x: 0,
y: 0
};
if (!target) return this_rect;
const target_rect = target instanceof $Element ? target.dom.getBoundingClientRect() : target;
const rect: $DOMRect = {
...this_rect,
top: this_rect.top - target_rect.top,
left: this_rect.left - target_rect.left,
right: this_rect.right - target_rect.left,
bottom: this_rect.bottom - target_rect.top,
x: this_rect.x - target_rect.x,
y: this_rect.y - target_rect.y,
}
return rect;
}
static from(document: Document) {
if (document.$ instanceof $Document) return document.$
else return new $Document(document);
}
}

View File

@ -1,113 +0,0 @@
import { $Node } from "./$Node";
export interface $ElementOptions {
id?: string;
class?: string[];
dom?: HTMLElement | SVGElement;
}
export class $Element<H extends HTMLElement | SVGElement = HTMLElement> extends $Node<H> {
readonly dom: H;
private static_classes = new Set<string>();
constructor(tagname: string, options?: $ElementOptions) {
super();
this.dom = this.createDom(tagname, options) as H;
this.dom.$ = this;
this.setOptions(options);
}
private createDom(tagname: string, options?: $ElementOptions) {
if (options?.dom) return options.dom;
if (tagname === 'svg') return document.createElementNS("http://www.w3.org/2000/svg", "svg");
return document.createElement(tagname);
}
setOptions(options: $ElementOptions | undefined) {
this.id(options?.id)
if (options && options.class) this.class(...options.class)
return this;
}
/**Replace id of element. @example Element.id('customId');*/
id(): string;
id(name: string | undefined): this;
id(name?: string | undefined): this | string {return $.fluent(this, arguments, () => this.dom.id, () => $.set(this.dom, 'id', name as any))}
/**Replace list of class name to element. @example Element.class('name1', 'name2') */
class(): DOMTokenList;
class(...name: (string | undefined)[]): this;
class(...name: (string | undefined)[]): this | DOMTokenList {return $.fluent(this, arguments, () => this.dom.classList, () => {this.dom.classList.forEach(n => this.static_classes.has(n) ?? this.dom.classList.remove(n)); this.dom.classList.add(...name.detype())})}
/**Add class name to dom. */
addClass(...name: (string | undefined)[]): this {return $.fluent(this, arguments, () => this, () => {this.dom.classList.add(...name.detype())})}
/**Remove class name from dom */
removeClass(...name: (string | undefined)[]): this {return $.fluent(this, arguments, () => this, () => {this.dom.classList.remove(...name.detype())})}
staticClass(): Set<string>;
staticClass(...name: (string | undefined)[]): this;
staticClass(...name: (string | undefined)[]) {return $.fluent(this, arguments, () => this.static_classes, () => {this.removeClass(...this.static_classes); this.static_classes.clear(); this.addStaticClass(...name);})}
addStaticClass(...name: (string | undefined)[]) {return $.fluent(this, arguments, () => this, () => {name.detype().forEach(n => this.static_classes.add(n)); this.addClass(...name)})}
removeStaticClass(...name: (string | undefined)[]) {return $.fluent(this, arguments, () => this, () => {name.detype().forEach(n => this.static_classes.delete(n)); this.removeClass(...name)})}
/**Modify css of element. */
css(): CSSStyleDeclaration
css(style: Partial<CSSStyleDeclaration>): this;
css(style?: Partial<CSSStyleDeclaration>) { return $.fluent(this, arguments, () => this.dom.style, () => {Object.assign(this.dom.style, style)})}
/**
* Get or set attribute from this element.
* @param qualifiedName Attribute name
* @param value Attribute value. Set `null` will remove attribute.
*/
attribute(qualifiedName: string | undefined): string | null;
attribute(qualifiedName: string | undefined, value?: string | number | boolean | null): this;
attribute(qualifiedName: string | undefined, value?: string | number | boolean | null): this | string | null {
if (!arguments.length) return null;
if (arguments.length === 1) {
if (qualifiedName === undefined) return null;
return this.dom.getAttribute(qualifiedName);
}
if (arguments.length === 2) {
if (!qualifiedName) return this;
if (value === null) this.dom.removeAttribute(qualifiedName);
else if (value !== undefined) this.dom.setAttribute(qualifiedName, `${value}`);
return this;
}
return this;
}
tabIndex(): number;
tabIndex(tabIndex: number): this;
tabIndex(tabIndex?: number) { return $.fluent(this, arguments, () => this.dom.tabIndex, () => $.set(this.dom, 'tabIndex', tabIndex as any))}
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);
if (callback) callback(animation);
return this;
}
getAnimations(options?: GetAnimationsOptions) { return this.dom.getAnimations(options) }
get dataset() { return this.dom.dataset }
domRect(target?: $Element | $DOMRect) {
const this_rect = this.dom.getBoundingClientRect();
if (!target) return this_rect;
const target_rect = target instanceof $Element ? target.dom.getBoundingClientRect() : target;
const rect: $DOMRect = {
...this_rect,
top: this_rect.top - target_rect.top,
left: this_rect.left - target_rect.left,
right: this_rect.right - target_rect.left,
bottom: this_rect.bottom - target_rect.top,
x: this_rect.x - target_rect.x,
y: this_rect.y - target_rect.y,
}
return rect;
}
}
export type $DOMRect = Omit<DOMRect, 'toJSON'>;

View File

@ -1,44 +0,0 @@
import { $Container, $ContainerOptions } from "./$Container";
export interface $FormOptions extends $ContainerOptions {}
export class $Form extends $Container<HTMLFormElement> {
constructor(options?: $FormOptions) {
super('form', options);
}
autocomplete(): AutoFill;
autocomplete(autocomplete: AutoFill | undefined): this;
autocomplete(autocomplete?: AutoFill) { return $.fluent(this, arguments, () => this.dom.autocomplete as AutoFill, () => $.set(this.dom, 'autocomplete', autocomplete as AutoFillBase))}
action(): string;
action(action: string | undefined): this;
action(action?: string) { return $.fluent(this, arguments, () => this.dom.formAction, () => $.set(this.dom, 'formAction', action))}
enctype(): string;
enctype(enctype: string | undefined): this;
enctype(enctype?: string) { return $.fluent(this, arguments, () => this.dom.formEnctype, () => $.set(this.dom, 'formEnctype', enctype))}
method(): string;
method(method: string | undefined): this;
method(method?: string) { return $.fluent(this, arguments, () => this.dom.formMethod, () => $.set(this.dom, 'formMethod', method))}
noValidate(): boolean;
noValidate(boolean: boolean | undefined): this;
noValidate(boolean?: boolean) { return $.fluent(this, arguments, () => this.dom.formNoValidate, () => $.set(this.dom, 'formNoValidate', boolean))}
acceptCharset(): string;
acceptCharset(acceptCharset: string | undefined): this;
acceptCharset(acceptCharset?: string) { return $.fluent(this, arguments, () => this.dom.acceptCharset, () => $.set(this.dom, 'acceptCharset', acceptCharset))}
target(): string;
target(target: string | undefined): this;
target(target?: string) { return $.fluent(this, arguments, () => this.dom.formTarget, () => $.set(this.dom, 'formTarget', target))}
requestSubmit() { this.dom.requestSubmit(); return this }
reset(): this { this.dom.reset(); return this }
submit() { this.dom.submit(); return this }
checkValidity() { return this.dom.checkValidity() }
reportValidity() { return this.dom.reportValidity() }
get length() { return this.dom.length }
get elements() { return Array.from(this.dom.elements).map(ele => $(ele)) }
}

View File

@ -1,65 +0,0 @@
import { $Element, $ElementOptions } from "./$Element";
export interface $HTMLElementOptions extends $ElementOptions {}
export class $HTMLElement<H extends HTMLElement = HTMLElement> extends $Element<H> {
constructor(tagname: string, options?: $HTMLElementOptions) {
super(tagname, options)
}
autocapitalize(): Autocapitalize;
autocapitalize(autocapitalize?: Autocapitalize): this;
autocapitalize(autocapitalize?: Autocapitalize) { return $.fluent(this, arguments, () => this.dom.autocapitalize, () => $.set(this.dom, 'autocapitalize', autocapitalize as any))}
innerText(): string;
innerText(text?: string): this;
innerText(text?: string) { return $.fluent(this, arguments, () => this.dom.innerText, () => $.set(this.dom, 'innerText', text as any))}
title(): string;
title(title?: string): this;
title(title?: string) { return $.fluent(this, arguments, () => this.dom.title, () => $.set(this.dom, 'title', title as any))}
dir(): TextDirection;
dir(dir?: TextDirection): this;
dir(dir?: TextDirection) { return $.fluent(this, arguments, () => this.dom.dir, () => $.set(this.dom, 'dir', dir as any))}
translate(): boolean;
translate(translate?: boolean): this;
translate(translate?: boolean) { return $.fluent(this, arguments, () => this.dom.translate, () => $.set(this.dom, 'translate', translate as any))}
popover(): string | null;
popover(popover?: string | null): this;
popover(popover?: string | null) { return $.fluent(this, arguments, () => this.dom.popover, () => $.set(this.dom, 'popover', popover as any))}
spellcheck(): boolean;
spellcheck(spellcheck?: boolean): this;
spellcheck(spellcheck?: boolean) { return $.fluent(this, arguments, () => this.dom.spellcheck, () => $.set(this.dom, 'spellcheck', spellcheck as any))}
inert(): boolean;
inert(inert?: boolean): this;
inert(inert?: boolean) { return $.fluent(this, arguments, () => this.dom.inert, () => $.set(this.dom, 'inert', inert as any))}
lang(): string;
lang(lang?: string): this;
lang(lang?: string) { return $.fluent(this, arguments, () => this.dom.lang, () => $.set(this.dom, 'lang', lang as any))}
draggable(): boolean;
draggable(draggable?: boolean): this;
draggable(draggable?: boolean) { return $.fluent(this, arguments, () => this.dom.draggable, () => $.set(this.dom, 'draggable', draggable as any))}
hidden(): boolean;
hidden(hidden?: boolean): this;
hidden(hidden?: boolean) { return $.fluent(this, arguments, () => this.dom.hidden, () => $.set(this.dom, 'hidden', hidden as any))}
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; }
get accessKeyLabel() { return this.dom.accessKeyLabel }
get offsetHeight() { return this.dom.offsetHeight }
get offsetLeft() { return this.dom.offsetLeft }
get offsetParent() { return $(this.dom.offsetParent) }
get offsetTop() { return this.dom.offsetTop }
get offsetWidth() { return this.dom.offsetWidth }
}

View File

@ -1,118 +0,0 @@
import { $HTMLElement, $HTMLElementOptions } from "./$HTMLElement";
import { $State } from "../$State";
export interface $ImageOptions extends $HTMLElementOptions {}
export class $Image extends $HTMLElement<HTMLImageElement> {
constructor(options?: $ImageOptions) {
super('img', options);
}
async load(src: Promise<string> | string): Promise<$Image> {
return new Promise(resolve => {
const $img = this.once('load', () => {
resolve($img)
})
if (typeof src === 'string') $img.src(src);
else src.then(src => $img.src(src));
})
}
static resize(src: string | File, size: number | [number, number]): Promise<string> {
return new Promise(resolve => {
const img = new Image();
img.addEventListener('load', () => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
if (!context) throw '$Image.resize: context undefined';
const ratio = img.width / img.height;
const [landscape, portrait, square] = [ratio > 1, ratio < 1, ratio === 1];
const w = size instanceof Array ? size[0] : portrait ? size : size * ratio;
const h = size instanceof Array ? size[1] : landscape ? size : size / ratio;
canvas.height = h; canvas.width = w;
context.drawImage(img, 0, 0, w, h);
resolve(canvas.toDataURL());
}, {once: true})
if (src instanceof File) {
const fr = new FileReader()
fr.addEventListener('load', () => img.src = fr.result as string)
fr.readAsDataURL(src);
} else img.src = src;
})
}
/**HTMLImageElement base property */
alt(): string;
alt(alt: string): this;
alt(alt?: string) { return $.fluent(this, arguments, () => this.dom.alt, () => $.set(this.dom, 'alt', alt))}
/**HTMLImageElement base property */
crossOrigin(): string | null;
crossOrigin(crossOrigin: string | null): this;
crossOrigin(crossOrigin?: string | null) { return $.fluent(this, arguments, () => this.dom.crossOrigin, () => $.set(this.dom, 'crossOrigin', crossOrigin))}
/**HTMLImageElement base property */
decoding(): ImageDecoding;
decoding(decoding: ImageDecoding): this;
decoding(decoding?: ImageDecoding) { return $.fluent(this, arguments, () => this.dom.decoding, () => $.set(this.dom, 'decoding', decoding))}
/**HTMLImageElement base property */
height(): number;
height(height: number): this;
height(height?: number) { return $.fluent(this, arguments, () => this.dom.height, () => $.set(this.dom, 'height', height))}
/**HTMLImageElement base property */
isMap(): boolean;
isMap(isMap: boolean): this;
isMap(isMap?: boolean) { return $.fluent(this, arguments, () => this.dom.isMap, () => $.set(this.dom, 'isMap', isMap))}
/**HTMLImageElement base property */
loading(): ImageLoading;
loading(loading: ImageLoading): this;
loading(loading?: ImageLoading) { return $.fluent(this, arguments, () => this.dom.loading, () => $.set(this.dom, 'loading', loading))}
/**HTMLImageElement base property */
referrerPolicy(): string;
referrerPolicy(referrerPolicy: string): this;
referrerPolicy(referrerPolicy?: string) { return $.fluent(this, arguments, () => this.dom.referrerPolicy, () => $.set(this.dom, 'referrerPolicy', referrerPolicy))}
/**HTMLImageElement base property */
sizes(): string;
sizes(sizes: string): this;
sizes(sizes?: string) { return $.fluent(this, arguments, () => this.dom.sizes, () => $.set(this.dom, 'sizes', sizes))}
/**HTMLImageElement base property */
src(): string;
src(src?: string | $State<string | undefined>): this;
src(src?: string | $State<string | undefined>) { return $.fluent(this, arguments, () => this.dom.src, () => $.set(this.dom, 'src', src))}
/**HTMLImageElement base property */
srcset(): string;
srcset(srcset: string): this;
srcset(srcset?: string) { return $.fluent(this, arguments, () => this.dom.srcset, () => $.set(this.dom, 'srcset', srcset))}
/**HTMLImageElement base property */
useMap(): string;
useMap(useMap: string): this;
useMap(useMap?: string) { return $.fluent(this, arguments, () => this.dom.useMap, () => $.set(this.dom, 'useMap', useMap))}
/**HTMLImageElement base property */
width(): number;
width(width: number): this;
width(width?: number) { return $.fluent(this, arguments, () => this.dom.width, () => $.set(this.dom, 'width', width))}
/**HTMLImageElement base method */
decode() { return this.dom.decode() }
/**HTMLImageElement base property */
get complete() { return this.dom.complete }
/**HTMLImageElement base property */
get currentSrc() { return this.dom.currentSrc }
/**HTMLImageElement base property */
get naturalHeight() { return this.dom.naturalHeight }
/**HTMLImageElement base property */
get naturalWidth() { return this.dom.naturalWidth }
/**HTMLImageElement base property */
get x() { return this.dom.x }
/**HTMLImageElement base property */
get y() { return this.dom.y }
}

View File

@ -1,187 +0,0 @@
import { $Element, $ElementOptions } from "./$Element";
import { $State, $StateArgument } from "../$State";
export interface $InputOptions extends $ElementOptions {}
export class $Input extends $Element<HTMLInputElement> {
constructor(options?: $InputOptions) {
super('input', options);
}
accept(): string[]
accept(...filetype: string[]): this
accept(...filetype: string[]) { return $.fluent(this, arguments, () => this.dom.accept.split(','), () => this.dom.accept = filetype.toString() )}
capture(): string;
capture(capture: string): this;
capture(capture?: string) { return $.fluent(this, arguments, () => this.dom.capture, () => $.set(this.dom, 'capture', capture))}
alt(): string;
alt(alt: string): this;
alt(alt?: string) { return $.fluent(this, arguments, () => this.dom.alt, () => $.set(this.dom, 'alt', alt))}
height(): number;
height(height: number): this;
height(height?: number) { return $.fluent(this, arguments, () => this.dom.height, () => $.set(this.dom, 'height', height))}
width(): number;
width(wdith: number): this;
width(width?: number) { return $.fluent(this, arguments, () => this.dom.width, () => $.set(this.dom, 'width', width))}
checked(): boolean;
checked(boolean: boolean): this;
checked(boolean?: boolean) { return $.fluent(this, arguments, () => this.dom.checked, () => $.set(this.dom, 'checked', boolean))}
max(): number;
max(max: number): this;
max(max?: number) { return $.fluent(this, arguments, () => this.dom.max === '' ? null : parseInt(this.dom.min), () => $.set(this.dom, 'max', max?.toString()))}
min(): number;
min(min: number): this;
min(min?: number) { return $.fluent(this, arguments, () => this.dom.min === '' ? null : parseInt(this.dom.min), () => $.set(this.dom, 'min', min?.toString()))}
maxLength(): number;
maxLength(maxLength: number): this;
maxLength(maxLength?: number) { return $.fluent(this, arguments, () => this.dom.maxLength, () => $.set(this.dom, 'maxLength', maxLength))}
minLength(): number;
minLength(minLength: number): this;
minLength(minLength?: number) { return $.fluent(this, arguments, () => this.dom.minLength, () => $.set(this.dom, 'minLength', minLength))}
autocomplete(): AutoFill;
autocomplete(autocomplete: AutoFill): this;
autocomplete(autocomplete?: AutoFill) { return $.fluent(this, arguments, () => this.dom.autocomplete, () => $.set(this.dom, 'autocomplete', autocomplete))}
defaultValue(): string;
defaultValue(defaultValue: string): this;
defaultValue(defaultValue?: string) { return $.fluent(this, arguments, () => this.dom.defaultValue, () => $.set(this.dom, 'defaultValue', defaultValue))}
defaultChecked(): boolean;
defaultChecked(defaultChecked: boolean): this;
defaultChecked(defaultChecked?: boolean) { return $.fluent(this, arguments, () => this.dom.defaultChecked, () => $.set(this.dom, 'defaultChecked', defaultChecked))}
dirName(): string;
dirName(dirName: string): this;
dirName(dirName?: string) { return $.fluent(this, arguments, () => this.dom.dirName, () => $.set(this.dom, 'dirName', dirName))}
disabled(): boolean;
disabled(disabled: boolean): this;
disabled(disabled?: boolean) { return $.fluent(this, arguments, () => this.dom.disabled, () => $.set(this.dom, 'disabled', disabled))}
multiple(): boolean;
multiple(multiple: boolean): this;
multiple(multiple?: boolean) { return $.fluent(this, arguments, () => this.dom.multiple, () => $.set(this.dom, 'multiple', multiple))}
pattern(): string;
pattern(pattern: string): this;
pattern(pattern?: string) { return $.fluent(this, arguments, () => this.dom.pattern, () => $.set(this.dom, 'pattern', pattern))}
placeholder(): string;
placeholder(placeholder?: string): this;
placeholder(placeholder?: string) { return $.fluent(this, arguments, () => this.dom.placeholder, () => $.set(this.dom, 'placeholder', placeholder))}
readOnly(): boolean;
readOnly(readOnly: boolean): this;
readOnly(readOnly?: boolean) { return $.fluent(this, arguments, () => this.dom.readOnly, () => $.set(this.dom, 'readOnly', readOnly))}
required(): boolean;
required(required: boolean): this;
required(required?: boolean) { return $.fluent(this, arguments, () => this.dom.required, () => $.set(this.dom, 'required', required))}
selectionDirection(): SelectionDirection | null;
selectionDirection(selectionDirection: SelectionDirection | null): this;
selectionDirection(selectionDirection?: SelectionDirection | null) { return $.fluent(this, arguments, () => this.dom.selectionDirection, () => $.set(this.dom, 'selectionDirection', selectionDirection))}
selectionEnd(): number | null;
selectionEnd(selectionEnd: number | null): this;
selectionEnd(selectionEnd?: number | null) { return $.fluent(this, arguments, () => this.dom.selectionEnd, () => $.set(this.dom, 'selectionEnd', selectionEnd))}
selectionStart(): number | null;
selectionStart(selectionStart: number | null): this;
selectionStart(selectionStart?: number | null) { return $.fluent(this, arguments, () => this.dom.selectionStart, () => $.set(this.dom, 'selectionStart', selectionStart))}
size(): number;
size(size: number): this;
size(size?: number) { return $.fluent(this, arguments, () => this.dom.size, () => $.set(this.dom, 'size', size))}
src(): string;
src(src: string): this;
src(src?: string) { return $.fluent(this, arguments, () => this.dom.src, () => $.set(this.dom, 'src', src))}
step(): number;
step(step: number): this;
step(step?: number) { return $.fluent(this, arguments, () => Number(this.dom.step), () => $.set(this.dom, 'step', step?.toString()))}
type(): InputType;
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))}
valueAsNumber(): number;
valueAsNumber(number: number): this;
valueAsNumber(number?: number) { return $.fluent(this, arguments, () => this.dom.valueAsNumber, () => $.set(this.dom, 'valueAsNumber', number))}
webkitdirectory(): boolean;
webkitdirectory(boolean: boolean): this;
webkitdirectory(boolean?: boolean) { return $.fluent(this, arguments, () => this.dom.webkitdirectory, () => $.set(this.dom, 'webkitdirectory', boolean))}
select() { this.dom.select(); return this }
setCustomValidity(error: string) { this.dom.setCustomValidity(error); return this }
setRangeText(replacement: string): this;
setRangeText(replacement: string, start: number, end: number, selectionMode?: SelectionMode): this;
setRangeText(replacement: string, start?: number, end?: number, selectionMode?: SelectionMode) {
if (typeof start === 'number' && typeof end === 'number') this.dom.setRangeText(replacement, start, end, selectionMode)
this.dom.setRangeText(replacement);
return this
}
setSelectionRange(start: number | null, end: number | null, direction?: SelectionDirection) { this.dom.setSelectionRange(start, end, direction); return this }
showPicker() { this.dom.showPicker(); return this }
stepDown() { this.dom.stepDown(); return this }
stepUp() { this.dom.stepUp(); return this }
checkValidity() { return this.dom.checkValidity() }
reportValidity() { return this.dom.reportValidity() }
get files() { return this.dom.files }
get webkitEntries() { return this.dom.webkitEntries }
formAction(): string;
formAction(action: string | undefined): this;
formAction(action?: string) { return $.fluent(this, arguments, () => this.dom.formAction, () => $.set(this.dom, 'formAction', action))}
formEnctype(): string;
formEnctype(enctype: string | undefined): this;
formEnctype(enctype?: string) { return $.fluent(this, arguments, () => this.dom.formEnctype, () => $.set(this.dom, 'formEnctype', enctype))}
formMethod(): string;
formMethod(method: string | undefined): this;
formMethod(method?: string) { return $.fluent(this, arguments, () => this.dom.formMethod, () => $.set(this.dom, 'formMethod', method))}
formNoValidate(): boolean;
formNoValidate(boolean: boolean | undefined): this;
formNoValidate(boolean?: boolean) { return $.fluent(this, arguments, () => this.dom.formNoValidate, () => $.set(this.dom, 'formNoValidate', boolean))}
formTarget(): string;
formTarget(target: string | undefined): this;
formTarget(target?: string) { return $.fluent(this, arguments, () => this.dom.formTarget, () => $.set(this.dom, 'formTarget', target))}
name(): string;
name(name?: $StateArgument<string> | undefined): this;
name(name?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.name, () => $.set(this.dom, 'name', name))}
value(): string;
value(value: $StateArgument<string> | undefined): this;
value(value?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.value, () => $.set(this.dom, 'value', value))}
get form() { return this.dom.form ? $(this.dom.form) : null }
get labels() { return Array.from(this.dom.labels ?? []).map(label => $(label)) }
get validationMessage() { return this.dom.validationMessage }
get validity() { return this.dom.validity }
get willValidate() { return this.dom.willValidate }
}

View File

@ -1,14 +0,0 @@
import { $Container, $ContainerOptions } from "./$Container";
export interface $LabelOptions extends $ContainerOptions {}
export class $Label extends $Container<HTMLLabelElement> {
constructor(options?: $LabelOptions) {
super('label', options);
}
for(): string;
for(name?: string): this;
for(name?: string) { return $.fluent(this, arguments, () => this.dom.htmlFor, () => { $.set(this.dom, 'htmlFor', name, 'for')}) }
get form() { return this.dom.form }
get control() { return this.dom.control }
}

View File

@ -1,68 +0,0 @@
import { $, $Element, $State, $Text } from "../../index";
import { $Container } from "./$Container";
export abstract class $Node<N extends Node = Node> {
abstract readonly dom: N;
readonly __hidden: boolean = false;
private domEvents: {[key: string]: Map<Function, Function>} = {};
readonly parent?: $Container;
on<K extends keyof HTMLElementEventMap>(type: K, callback: (event: HTMLElementEventMap[K], $node: this) => any, options?: AddEventListenerOptions | boolean) {
if (!this.domEvents[type]) this.domEvents[type] = new Map()
const middleCallback = (e: Event) => callback(e as HTMLElementEventMap[K], this);
this.domEvents[type].set(callback, middleCallback)
this.dom.addEventListener(type, middleCallback, options)
return this;
}
off<K extends keyof HTMLElementEventMap>(type: K, callback: (event: HTMLElementEventMap[K], $node: this) => any, options?: AddEventListenerOptions | boolean) {
const middleCallback = this.domEvents[type]?.get(callback);
if (middleCallback) this.dom.removeEventListener(type, middleCallback as EventListener, options)
return this;
}
once<K extends keyof HTMLElementEventMap>(type: K, callback: (event: HTMLElementEventMap[K], $node: this) => any, options?: AddEventListenerOptions | boolean) {
const onceFn = (event: Event) => {
this.dom.removeEventListener(type, onceFn, options)
callback(event as HTMLElementEventMap[K], this);
};
this.dom.addEventListener(type, onceFn, options)
return this;
}
hide(): boolean;
hide(hide?: boolean | $State<boolean>, render?: boolean): this;
hide(hide?: boolean | $State<boolean>, render = true) { return $.fluent(this, arguments, () => this.__hidden, () => {
if (hide === undefined) return;
if (hide instanceof $State) { (this as Mutable<$Node>).__hidden = hide.value; hide.use(this, 'hide')}
else (this as Mutable<$Node>).__hidden = hide;
if (render) this.parent?.children.render();
return this;
})}
/**Remove this element from parent */
remove() {
this.parent?.children.remove(this).render();
return this;
}
/**Replace $Node with this element */
replace($node: $Node) {
this.parent?.children.replace(this, $node).render();
return this;
}
contains(target: $Node | EventTarget | Node | null): boolean {
if (!target) return false;
if (target instanceof $Node) return this.dom.contains(target.dom);
else if (target instanceof EventTarget) return this.dom.contains($(target).dom)
else return this.dom.contains(target)
}
self(callback: ($node: this) => void) { callback(this); return this; }
inDOM() { return document.contains(this.dom); }
isElement(): this is $Element {
if (this instanceof $Element) return true;
else return false;
}
}

View File

@ -1,17 +0,0 @@
import { $Container, $ContainerOptions } from "./$Container";
import { $State, $StateArgument } from "../$State";
export interface $OptGroupOptions extends $ContainerOptions {}
export class $OptGroup extends $Container<HTMLOptGroupElement> {
constructor(options?: $OptGroupOptions) {
super('optgroup', options);
}
disabled(): boolean;
disabled(disabled: $StateArgument<boolean> | undefined): this;
disabled(disabled?: $StateArgument<boolean> | undefined) { return $.fluent(this, arguments, () => this.dom.disabled, () => $.set(this.dom, 'disabled', disabled))}
label(): string;
label(label: $StateArgument<string> | undefined): this;
label(label?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.label, () => $.set(this.dom, 'label', label))}
}

View File

@ -1,37 +0,0 @@
import { $Container, $ContainerOptions } from "./$Container";
import { $State, $StateArgument } from "../$State";
export interface $OptionOptions extends $ContainerOptions {}
export class $Option extends $Container<HTMLOptionElement> {
constructor(options?: $OptionOptions) {
super('option', options);
}
defaultSelected(): boolean;
defaultSelected(defaultSelected: $StateArgument<boolean> | undefined): this;
defaultSelected(defaultSelected?: $StateArgument<boolean> | undefined) { return $.fluent(this, arguments, () => this.dom.defaultSelected, () => $.set(this.dom, 'defaultSelected', defaultSelected))}
disabled(): boolean;
disabled(disabled: $StateArgument<boolean> | undefined): this;
disabled(disabled?: $StateArgument<boolean> | undefined) { return $.fluent(this, arguments, () => this.dom.disabled, () => $.set(this.dom, 'disabled', disabled))}
label(): string;
label(label: $StateArgument<string> | undefined): this;
label(label?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.label, () => $.set(this.dom, 'label', label))}
selected(): boolean;
selected(selected: $StateArgument<boolean> | undefined): this;
selected(selected?: $StateArgument<boolean> | undefined) { return $.fluent(this, arguments, () => this.dom.selected, () => $.set(this.dom, 'selected', selected))}
text(): string;
text(text: $StateArgument<string> | undefined): this;
text(text?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.text, () => $.set(this.dom, 'text', text))}
value(): string;
value(value: $StateArgument<string> | undefined): this;
value(value?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.value, () => $.set(this.dom, 'value', value))}
get form() { return this.dom.form ? $(this.dom.form) : null }
get index() { return this.dom.index }
}

View File

@ -1,8 +0,0 @@
import { $Element, $ElementOptions } from "./$Element"
export interface $SVGOptions extends $ElementOptions {}
export class $SVGElement<S extends SVGElement> extends $Element<S> {
constructor(tagname: string, options?: $SVGOptions) {
super(tagname, options);
}
}

View File

@ -1,57 +0,0 @@
import { $Container, $ContainerOptions } from "./$Container";
import { $OptGroup } from "./$OptGroup";
import { $Option } from "./$Option";
import { $State, $StateArgument } from "../$State";
export interface $SelectOptions extends $ContainerOptions {}
export class $Select extends $Container<HTMLSelectElement> {
constructor(options?: $SelectOptions) {
super('select')
}
add(option: $SelectContentType | OrMatrix<$SelectContentType>) {
this.insert(option);
return this;
}
item(index: number) { return $(this.dom.item(index)) }
namedItem(name: string) { return $(this.dom.namedItem(name)) }
disabled(): boolean;
disabled(disabled: $StateArgument<boolean> | undefined): this;
disabled(disabled?: $StateArgument<boolean> | undefined) { return $.fluent(this, arguments, () => this.dom.disabled, () => $.set(this.dom, 'disabled', disabled))}
multiple(): boolean;
multiple(multiple: $StateArgument<boolean> | undefined): this;
multiple(multiple?: $StateArgument<boolean> | undefined) { return $.fluent(this, arguments, () => this.dom.multiple, () => $.set(this.dom, 'multiple', multiple))}
required(): boolean;
required(required: boolean): this;
required(required?: boolean) { return $.fluent(this, arguments, () => this.dom.required, () => $.set(this.dom, 'required', required))}
autocomplete(): AutoFill;
autocomplete(autocomplete: AutoFill): this;
autocomplete(autocomplete?: AutoFill) { return $.fluent(this, arguments, () => this.dom.autocomplete, () => $.set(this.dom, 'autocomplete', autocomplete))}
get length() { return this.dom.length }
get size() { return this.dom.size }
get options() { return Array.from(this.dom.options).map($option => $($option)) }
get selectedIndex() { return this.dom.selectedIndex }
get selectedOptions() { return Array.from(this.dom.selectedOptions).map($option => $($option)) }
name(): string;
name(name?: $StateArgument<string> | undefined): this;
name(name?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.name, () => $.set(this.dom, 'name', name))}
value(): string;
value(value?: $StateArgument<string> | undefined): this;
value(value?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.value, () => $.set(this.dom, 'value', value))}
get form() { return this.dom.form ? $(this.dom.form) : null }
get labels() { return Array.from(this.dom.labels ?? []).map(label => $(label)) }
get validationMessage() { return this.dom.validationMessage }
get validity() { return this.dom.validity }
get willValidate() { return this.dom.willValidate }
}
export type $SelectContentType = $Option | $OptGroup | undefined;

View File

@ -1,14 +0,0 @@
import { $Node } from "./$Node";
export class $Text extends $Node<Text> {
dom: Text;
constructor(data: string) {
super();
this.dom = new Text(data);
this.dom.$ = this;
}
content(): string;
content(text: string): this;
content(text?: string) { return $.fluent(this, arguments, () => this.dom.textContent, () => $.set(this.dom, 'textContent', text))}
}

View File

@ -1,100 +0,0 @@
import { $Container, $ContainerOptions } from "./$Container";
import { $StateArgument } from "../$State";
export interface $TextareaOptions extends $ContainerOptions {}
export class $Textarea extends $Container<HTMLTextAreaElement> {
constructor(options?: $TextareaOptions) {
super('textarea', options);
}
cols(): number;
cols(cols: number): this;
cols(cols?: number) { return $.fluent(this, arguments, () => this.dom.cols, () => $.set(this.dom, 'cols', cols))}
name(): string;
name(name?: $StateArgument<string> | undefined): this;
name(name?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.name, () => $.set(this.dom, 'name', name))}
wrap(): string;
wrap(wrap?: $StateArgument<string> | undefined): this;
wrap(wrap?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.wrap, () => $.set(this.dom, 'wrap', wrap))}
value(): string;
value(value?: $StateArgument<string> | undefined): this;
value(value?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.value, () => $.set(this.dom, 'value', value))}
maxLength(): number;
maxLength(maxLength: number): this;
maxLength(maxLength?: number) { return $.fluent(this, arguments, () => this.dom.maxLength, () => $.set(this.dom, 'maxLength', maxLength))}
minLength(): number;
minLength(minLength: number): this;
minLength(minLength?: number) { return $.fluent(this, arguments, () => this.dom.minLength, () => $.set(this.dom, 'minLength', minLength))}
autocomplete(): AutoFill;
autocomplete(autocomplete: AutoFill): this;
autocomplete(autocomplete?: AutoFill) { return $.fluent(this, arguments, () => this.dom.autocomplete, () => $.set(this.dom, 'autocomplete', autocomplete))}
defaultValue(): string;
defaultValue(defaultValue: string): this;
defaultValue(defaultValue?: string) { return $.fluent(this, arguments, () => this.dom.defaultValue, () => $.set(this.dom, 'defaultValue', defaultValue))}
dirName(): string;
dirName(dirName: string): this;
dirName(dirName?: string) { return $.fluent(this, arguments, () => this.dom.dirName, () => $.set(this.dom, 'dirName', dirName))}
disabled(): boolean;
disabled(disabled: boolean): this;
disabled(disabled?: boolean) { return $.fluent(this, arguments, () => this.dom.disabled, () => $.set(this.dom, 'disabled', disabled))}
placeholder(): string;
placeholder(placeholder?: string): this;
placeholder(placeholder?: string) { return $.fluent(this, arguments, () => this.dom.placeholder, () => $.set(this.dom, 'placeholder', placeholder))}
readOnly(): boolean;
readOnly(readOnly: boolean): this;
readOnly(readOnly?: boolean) { return $.fluent(this, arguments, () => this.dom.readOnly, () => $.set(this.dom, 'readOnly', readOnly))}
required(): boolean;
required(required: boolean): this;
required(required?: boolean) { return $.fluent(this, arguments, () => this.dom.required, () => $.set(this.dom, 'required', required))}
selectionDirection(): SelectionDirection;
selectionDirection(selectionDirection: SelectionDirection): this;
selectionDirection(selectionDirection?: SelectionDirection) { return $.fluent(this, arguments, () => this.dom.selectionDirection, () => $.set(this.dom, 'selectionDirection', selectionDirection))}
selectionEnd(): number;
selectionEnd(selectionEnd: number): this;
selectionEnd(selectionEnd?: number) { return $.fluent(this, arguments, () => this.dom.selectionEnd, () => $.set(this.dom, 'selectionEnd', selectionEnd))}
selectionStart(): number;
selectionStart(selectionStart: number): this;
selectionStart(selectionStart?: number) { return $.fluent(this, arguments, () => this.dom.selectionStart, () => $.set(this.dom, 'selectionStart', selectionStart))}
type(): InputType;
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))}
select() { this.dom.select(); return this }
setCustomValidity(error: string) { this.dom.setCustomValidity(error); return this }
setRangeText(replacement: string): this;
setRangeText(replacement: string, start: number, end: number, selectionMode?: SelectionMode): this;
setRangeText(replacement: string, start?: number, end?: number, selectionMode?: SelectionMode) {
if (typeof start === 'number' && typeof end === 'number') this.dom.setRangeText(replacement, start, end, selectionMode)
this.dom.setRangeText(replacement);
return this
}
setSelectionRange(start: number | null, end: number | null, direction?: SelectionDirection) { this.dom.setSelectionRange(start, end, direction); return this }
checkValidity() { return this.dom.checkValidity() }
reportValidity() { return this.dom.reportValidity() }
get validationMessage() { return this.dom.validationMessage }
get validity() { return this.dom.validity }
get form() { return this.dom.form ? $(this.dom.form) : null }
get labels() { return Array.from(this.dom.labels ?? []).map(label => $(label)) }
}

View File

@ -1,41 +0,0 @@
import { $Container, $ContainerOptions } from "./$Container";
import { $EventManager } from "../$EventManager";
import { $Node } from "./$Node";
export interface $ViewOptions extends $ContainerOptions {}
export class $View extends $Container {
protected view_cache = new Map<string, $Node>();
event = new $EventManager<$ViewEventMap>().register('switch')
content_id: string | null = null;
constructor(options?: $ViewOptions) {
super('view', options);
}
setView(id: string, $node: $Node) {
this.view_cache.set(id, $node);
return this;
}
deleteView(id: string) {
this.view_cache.delete(id);
return this;
}
deleteAllView() {
this.view_cache.clear();
return this;
}
switchView(id: string) {
const target_content = this.view_cache.get(id);
if (target_content === undefined) return this;
this.content(target_content);
this.content_id = id;
this.event.fire('switch', id);
return this;
}
}
export interface $ViewEventMap {
'switch': [id: string]
}

BIN
logo_dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
logo_light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -1,22 +0,0 @@
{
"name": "elexis",
"description": "Web library design for JS/TS lover.",
"version": "0.1.0",
"author": {
"name": "defaultkavy",
"email": "defaultkavy@gmail.com",
"url": "https://github.com/defaultkavy"
},
"repository": {
"type": "git",
"url": "git+https://github.com/defaultkavy/elexis.git"
},
"module": "index.ts",
"bugs": {
"url": "https://github.com/defaultkavy/elexis/issues"
},
"homepage": "https://github.com/defaultkavy/elexis",
"keywords": ["web", "front-end", "lib", "fluent", "framework"],
"license": "ISC",
"type": "module"
}

View File

@ -1,21 +0,0 @@
{
"compilerOptions": {
"lib": ["dom", "ES2022"],
"module": "esnext",
"target": "esnext",
"moduleResolution": "bundler",
"moduleDetection": "force",
"allowImportingTsExtensions": true,
"noEmit": true,
"composite": true,
"strict": true,
"downlevelIteration": true,
"skipLibCheck": true,
"jsx": "react-jsx",
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"allowJs": true,
"experimentalDecorators": true
},
}