add logo and icons
243
$index.ts
@ -1,243 +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 extends Object, K extends keyof O, V>(
|
|
||||||
object: O,
|
|
||||||
key: K,
|
|
||||||
value: O[K] extends (...args: any) => any
|
|
||||||
? (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)
|
|
||||||
else object[key] = value.value;
|
|
||||||
if (handle) handle(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.$ = $;
|
|
2
.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
bun.lockb
|
|
||||||
node_modules
|
|
@ -1,2 +0,0 @@
|
|||||||
.gitignore
|
|
||||||
assets
|
|
90
README.md
@ -1,90 +0,0 @@
|
|||||||
<picture style="display: flex; justify-content: center">
|
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/defaultkavy/elexis/assets/logo_light.png">
|
|
||||||
<source media="(prefers-color-scheme: light)" srcset="https://github.com/defaultkavy/elexis/assets/logo_dark.png">
|
|
||||||
<img alt="Elexis Logo">
|
|
||||||
</picture>
|
|
||||||
<p style="text-align: center">Build Web in Native JavaScript Syntax</p>
|
|
||||||
|
|
||||||
> 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 code in your entry file:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
$(document.body).content([
|
|
||||||
$('h1').class('title').content('Hello, world!')
|
|
||||||
])
|
|
||||||
```
|
|
||||||
|
|
||||||
In the first line, we create a `$Container` to using Elexis API on `document.body` element. Then we see a `content` method after the container object, this method mean the following elements will be the content of container.
|
|
||||||
|
|
||||||
We can pass an array into `content` method. In this array, we put a new `<h1>` element which have a class name "title" and text content "Hello, world!".
|
|
||||||
|
|
||||||
Run the code, we will get this body structure in DOM:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<body>
|
|
||||||
<h1 class="title">Hello, world!</h1>
|
|
||||||
</body>
|
|
||||||
```
|
|
||||||
|
|
||||||
So far, we just simply do a hello world project that you can type less in HTML way, and these is not the point of ElexisJS. Let's figure out what ElexisJS will boost development speed in the following examples.
|
|
||||||
|
|
||||||
## Using `$State` to sync view and data changes
|
|
||||||
|
|
||||||
This line will create a `$State` value, usually we will put `$` sign behind variable name to mean this is a `$State` variable.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const number$ = $.state(42);
|
|
||||||
```
|
|
||||||
|
|
||||||
This `$State` value has been set a number `42`, which will become a number type `$State`. We can simply put this state value into any display content!
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const value$ = $.state(42);
|
|
||||||
|
|
||||||
$(document.body).content([
|
|
||||||
$('input').type('number').value(value$),
|
|
||||||
$('p').content(['User input value: ', value$])
|
|
||||||
])
|
|
||||||
```
|
|
||||||
|
|
||||||
You will see the `<input>` element is fill with number `42`, and also `<p>` element will display `'User input value: 42'`. Now try to change input value in browser, the value text in `<p>` element will be synced by your input!
|
|
||||||
|
|
||||||
Using `set` method to set value of `$State`, all displayed content of `value$` will be synced.
|
|
||||||
```ts
|
|
||||||
value$.set(0)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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.
|
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
50
index.ts
@ -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";
|
|
@ -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) }
|
|
||||||
}
|
|
@ -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}
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
export interface $StateOption<T> {
|
|
||||||
format: (value: T) => string;
|
|
||||||
}
|
|
||||||
export class $State<T> {
|
|
||||||
readonly value: T;
|
|
||||||
readonly attributes = new Map<Object, 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) {
|
|
||||||
console.debug(node, attr)
|
|
||||||
//@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)
|
|
||||||
}
|
|
||||||
else if (attr in node) {
|
|
||||||
//@ts-expect-error
|
|
||||||
node[attr] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toString(): string {
|
|
||||||
if (this.options.format) return this.options.format(this.value);
|
|
||||||
if (this.value instanceof Object) return JSON.stringify(this.toJSON());
|
|
||||||
return `${this.value}`
|
|
||||||
}
|
|
||||||
|
|
||||||
use<O extends Object, K extends keyof O>(object: O, attrName: K) {
|
|
||||||
const attrList = this.attributes.get(object)
|
|
||||||
if (attrList) attrList.add(attrName);
|
|
||||||
else this.attributes.set(object, new Set<string | number | symbol>().add(attrName))
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): Object {
|
|
||||||
if (this.value instanceof $State) return this.value.toJSON();
|
|
||||||
if (this.value instanceof Object) return $State.toJSON(this.value);
|
|
||||||
else return this.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
static toJSON(object: Object): Object {
|
|
||||||
const data = {};
|
|
||||||
for (let [key, value] of Object.entries(object)) {
|
|
||||||
if (value instanceof $State) value = value.toJSON();
|
|
||||||
else if (value instanceof Object) $State.toJSON(value);
|
|
||||||
Object.assign(data, {[key]: value})
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export type $StateArgument<T> = T | $State<T> | undefined;
|
|
68
lib/$Util.ts
@ -1,68 +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
|
|
||||||
//@ts-expect-error
|
|
||||||
? 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})`
|
|
||||||
}
|
|
||||||
}
|
|
@ -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';
|
|
@ -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 }
|
|
||||||
}
|
|
@ -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))}
|
|
||||||
}
|
|
@ -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() }
|
|
||||||
}
|
|
@ -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>
|
|
@ -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; }
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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'>;
|
|
@ -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)) }
|
|
||||||
}
|
|
@ -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 }
|
|
||||||
}
|
|
@ -1,118 +0,0 @@
|
|||||||
import { $HTMLElement, $HTMLElementOptions } from "./$HTMLElement";
|
|
||||||
import { $StateArgument } 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: $StateArgument<string>): this;
|
|
||||||
src(src?: $StateArgument<string>) { 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 }
|
|
||||||
|
|
||||||
}
|
|
@ -1,231 +0,0 @@
|
|||||||
import { $Element, $ElementOptions } from "./$Element";
|
|
||||||
import { $State, $StateArgument } from "../$State";
|
|
||||||
|
|
||||||
export interface $InputOptions extends $ElementOptions {}
|
|
||||||
export class $Input<T extends string | number = string> extends $Element<HTMLInputElement> {
|
|
||||||
constructor(options?: $InputOptions) {
|
|
||||||
super('input', options);
|
|
||||||
}
|
|
||||||
|
|
||||||
value(): T;
|
|
||||||
value(value: $StateArgument<T>): this;
|
|
||||||
value(value?: $StateArgument<T>) { return $.fluent(this, arguments, () => {
|
|
||||||
if (this.type() === 'number') return Number(this.dom.value);
|
|
||||||
return this.dom.value as T;
|
|
||||||
}, () => $.set(this.dom, 'value', value as $State<string> | string, (value$) => {
|
|
||||||
this.on('input', () => {
|
|
||||||
if (value$.attributes.has(this.dom) === false) return;
|
|
||||||
if (typeof value$.value === 'string') (value$ as $State<string>).set(`${this.value()}`)
|
|
||||||
if (typeof value$.value === 'number') (value$ as unknown as $State<number>).set(Number(this.value()))
|
|
||||||
})
|
|
||||||
}))}
|
|
||||||
|
|
||||||
type(): InputType;
|
|
||||||
type<T extends InputType>(type: T): $InputType<T>;
|
|
||||||
type<T extends InputType>(type?: T) { return $.fluent(this, arguments, () => this.dom.type, () => $.set(this.dom, 'type', type)) as unknown as $InputType<T> | InputType}
|
|
||||||
|
|
||||||
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))}
|
|
||||||
|
|
||||||
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))}
|
|
||||||
|
|
||||||
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))}
|
|
||||||
|
|
||||||
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 }
|
|
||||||
|
|
||||||
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))}
|
|
||||||
|
|
||||||
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))}
|
|
||||||
|
|
||||||
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 class $NumberInput extends $Input<number> {
|
|
||||||
constructor(options?: $InputOptions) {
|
|
||||||
super(options)
|
|
||||||
this.type('number')
|
|
||||||
}
|
|
||||||
|
|
||||||
static from($input: $Input) {
|
|
||||||
return $.mixin($Input, this) as $NumberInput;
|
|
||||||
}
|
|
||||||
stepDown() { this.dom.stepDown(); return this }
|
|
||||||
stepUp() { this.dom.stepUp(); return this }
|
|
||||||
|
|
||||||
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()))}
|
|
||||||
|
|
||||||
step(): number;
|
|
||||||
step(step: number): this;
|
|
||||||
step(step?: number) { return $.fluent(this, arguments, () => Number(this.dom.step), () => $.set(this.dom, 'step', step?.toString()))}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class $CheckInput extends $Input<string> {
|
|
||||||
constructor(options?: $InputOptions) {
|
|
||||||
super(options)
|
|
||||||
this.type('radio')
|
|
||||||
}
|
|
||||||
|
|
||||||
static from($input: $Input) {
|
|
||||||
return $.mixin($Input, this) as $CheckInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
checked(): boolean;
|
|
||||||
checked(boolean: boolean): this;
|
|
||||||
checked(boolean?: boolean) { return $.fluent(this, arguments, () => this.dom.checked, () => $.set(this.dom, 'checked', boolean))}
|
|
||||||
|
|
||||||
defaultChecked(): boolean;
|
|
||||||
defaultChecked(defaultChecked: boolean): this;
|
|
||||||
defaultChecked(defaultChecked?: boolean) { return $.fluent(this, arguments, () => this.dom.defaultChecked, () => $.set(this.dom, 'defaultChecked', defaultChecked))}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class $FileInput extends $Input<string> {
|
|
||||||
constructor(options?: $InputOptions) {
|
|
||||||
super(options)
|
|
||||||
this.type('file')
|
|
||||||
}
|
|
||||||
|
|
||||||
static from($input: $Input) {
|
|
||||||
return $.mixin($Input, this) as $CheckInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
multiple(): boolean;
|
|
||||||
multiple(multiple: boolean): this;
|
|
||||||
multiple(multiple?: boolean) { return $.fluent(this, arguments, () => this.dom.multiple, () => $.set(this.dom, 'multiple', multiple))}
|
|
||||||
|
|
||||||
accept(): string[]
|
|
||||||
accept(...filetype: string[]): this
|
|
||||||
accept(...filetype: string[]) { return $.fluent(this, arguments, () => this.dom.accept.split(','), () => this.dom.accept = filetype.toString() )}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type $InputType<T extends InputType> = T extends 'number' ? $NumberInput : T extends 'radio' | 'checkbox' ? $CheckInput : T extends 'file' ? $FileInput : $Input<string>;
|
|
@ -1,15 +0,0 @@
|
|||||||
import { $StateArgument } from "../$State";
|
|
||||||
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: $StateArgument<string>): this;
|
|
||||||
for(name?: $StateArgument<string>) { return $.fluent(this, arguments, () => this.dom.htmlFor, () => { $.set(this.dom, 'htmlFor', name)}) }
|
|
||||||
|
|
||||||
get form() { return this.dom.form }
|
|
||||||
get control() { return this.dom.control }
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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))}
|
|
||||||
}
|
|
@ -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 }
|
|
||||||
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
@ -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))}
|
|
||||||
}
|
|
@ -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)) }
|
|
||||||
}
|
|
@ -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]
|
|
||||||
}
|
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
22
package.json
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "elexis",
|
|
||||||
"description": "Build Web in Native JavaScript Syntax",
|
|
||||||
"version": "0.2.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"
|
|
||||||
}
|
|
@ -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
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|