Compare commits

..

2 Commits

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

254
$index.ts
View File

@ -1,254 +0,0 @@
import { $EventManager, $EventMap, $EventTarget, $FocusManager, $PointerManager, $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 { $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";
import { $Video } from "./lib/node/$Video";
import { $Window } from "./lib/$Window";
import { $KeyboardManager } from "./lib/$KeyboardManager";
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 $(window: Window): $Window;
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);
}
if (resolver instanceof Window) { return $Window.$ }
throw `$: NOT SUPPORT TARGET ELEMENT TYPE ('${resolver}')`
}
export namespace $ {
export let anchorHandler: null | (($a: $Anchor, e: Event) => void) = null;
export const TagNameElementMap = {
'html': $Container,
'head': $Container,
'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,
'select': $Select,
'option': $Option,
'optgroup': $OptGroup,
'textarea': $Textarea,
'video': $Video,
'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
: H extends HTMLVideoElement ? $Video
: $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): T | V {
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 handle callback when param `value` is $State object.
* @returns
*/
export function set<O extends Object, K extends keyof O>(
object: O,
key: K,
value: O[K] extends (...args: any) => any
? (undefined | [$StateArgument<Parameters<O[K]>>])
: (undefined | $StateArgument<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.value)
else object[key] = value.value;
if (handle) handle(value);
return;
}
if (object[key] instanceof Function) (object[key] as Function)(...value as any);
else object[key] = value as any;
}
export function state<T>(value: T, options?: $StateOption<T extends $State<infer K> ? K : T>) {
return new $State<T>(value, options as $StateOption<T>) as T extends $State<infer K> ? $State<K> : $State<T>;
}
export async function resize(object: Blob, size: number): Promise<string> {
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;
}
export function events<EM extends $EventMap>() { return new $EventManager<EM> }
export function pointers($node: $Node) { return new $PointerManager($node) }
export function keys($target: $EventTarget) { return new $KeyboardManager($target) }
export function focus() { return new $FocusManager() }
export function call<T>(fn: () => T): T { return fn() }
}
type BuildNodeFunction = (...args: any[]) => $Node;
type BuilderSelfFunction<K extends $Node> = (self: K) => void;
globalThis.$ = $;

2
.gitignore vendored
View File

@ -1,2 +0,0 @@
bun.lockb
node_modules

View File

@ -1,2 +0,0 @@
.gitignore
assets

View File

@ -1,91 +0,0 @@
<picture style="display: flex; justify-content: center">
<source media="(prefers-color-scheme: dark)" srcset="https://git.defaultkavy.com/defaultkavy/elexis/raw/branch/assets/logo_light.png">
<source media="(prefers-color-scheme: light)" srcset="https://git.defaultkavy.com/defaultkavy/elexis/raw/branch/assets/logo_dark.png">
<img src="https://git.defaultkavy.com/defaultkavy/elexis/raw/branch/assets/logo_dark.png" 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 happened 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://git.defaultkavy.com/elexis/router): Router for Single Page App.
2. [@elexis/layout](https://git.defaultkavy.com/elexis/layout): Build waterfall/justified layout with automatic compute content size and position.
3. [@elexis/view](https://git.defaultkavy.com/elexis/view): Multiple content switch handler.

BIN
icon_dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
icon_light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -1,65 +0,0 @@
declare global {
var $: import('./$index').$;
interface Array<T> {
detype<F extends any, O>(...types: F[]): Array<Exclude<T, F | undefined | void>>
}
interface Set<T> {
get array(): T[]
sort(handler: ((a: T, b: T) => number) | undefined): T[];
}
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 any, 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 true; else return false;
}) as Exclude<O, T | undefined | void>[];
}
Object.defineProperties(Set.prototype, {
array: { get: function <T>(this: Set<T>) { return Array.from(this)} }
})
Set.prototype.sort = function <T>(this: Set<T>, handler: ((a: T, b: T) => number) | undefined) { return this.array.sort(handler)}
export * from "./$index";
export * from "./lib/$NodeManager";
export * from "./lib/$EventManager";
export * from "./lib/$EventTarget";
export * from "./lib/$KeyboardManager";
export * from "./lib/$FocusManager";
export * from "./lib/$PointerManager";
export * from "./lib/$Window";
export * from "./lib/$State";
export * from "./lib/node/$Node";
export * from "./lib/node/$Anchor";
export * from "./lib/node/$Element";
export * from "./lib/node/$HTMLElement";
export * from "./lib/node/$Text";
export * from "./lib/node/$Container";
export * from "./lib/node/$Button";
export * from "./lib/node/$Form";
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";
export * from "./lib/node/$Media";
export * from "./lib/node/$Video";

View File

@ -1,94 +0,0 @@
import { $StateArgument } from "./$State";
import { $Form } from "./node/$Form";
export abstract class $HTMLElementAPIs<This = any> {
static create(...args: (keyof $HTMLElementAPIs)[]) {
const $template = class {};
Object.getOwnPropertyNames($HTMLElementAPIs.prototype).forEach(name => {
if (name === 'constructor') return;
if (!args.includes(name as keyof $HTMLElementAPIs)) return;
Object.defineProperty(
$template.prototype,
name,
Object.getOwnPropertyDescriptor($HTMLElementAPIs.prototype, name) || Object.create(null)
)
})
return $template;
}
disabled(): boolean;
disabled(disabled: $StateArgument<boolean>): This;
//@ts-ignore
disabled(disabled?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.disabled, () => $.set(this.dom, 'disabled', disabled)) }
//@ts-ignore
checkValidity(): boolean { return this.dom.checkValidity() }
//@ts-ignore
reportValidity(): boolean { return this.dom.reportValidity() }
formAction(): string;
formAction(action: string | undefined): This;
//@ts-ignore
formAction(action?: string) { return $.fluent(this, arguments, () => this.dom.formAction, () => $.set(this.dom, 'formAction', action))}
formEnctype(): string;
formEnctype(enctype: string | undefined): This;
//@ts-ignore
formEnctype(enctype?: string) { return $.fluent(this, arguments, () => this.dom.formEnctype, () => $.set(this.dom, 'formEnctype', enctype))}
formMethod(): string;
formMethod(method: string | undefined): This;
//@ts-ignore
formMethod(method?: string) { return $.fluent(this, arguments, () => this.dom.formMethod, () => $.set(this.dom, 'formMethod', method))}
formNoValidate(): boolean;
formNoValidate(boolean: boolean | undefined): This;
//@ts-ignore
formNoValidate(boolean?: boolean) { return $.fluent(this, arguments, () => this.dom.formNoValidate, () => $.set(this.dom, 'formNoValidate', boolean))}
formTarget(): string;
formTarget(target: string | undefined): This;
//@ts-ignore
formTarget(target?: string) { return $.fluent(this, arguments, () => this.dom.formTarget, () => $.set(this.dom, 'formTarget', target))}
autocomplete(): AutoFill;
//@ts-ignore
autocomplete(autocomplete: AutoFill | undefined): This;
//@ts-ignore
autocomplete(autocomplete?: AutoFill) { return $.fluent(this, arguments, () => this.dom.autocomplete as AutoFill, () => $.set(this.dom, 'autocomplete', autocomplete as AutoFillBase))}
name(): string;
name(name?: $StateArgument<string> | undefined): This;
//@ts-ignore
name(name?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.name, () => $.set(this.dom, 'name', name))}
maxLength(): number;
maxLength(maxLength: number): This;
//@ts-ignore
maxLength(maxLength?: number) { return $.fluent(this, arguments, () => this.dom.maxLength, () => $.set(this.dom, 'maxLength', maxLength))}
minLength(): number;
minLength(minLength: number): This;
//@ts-ignore
minLength(minLength?: number) { return $.fluent(this, arguments, () => this.dom.minLength, () => $.set(this.dom, 'minLength', minLength))}
required(): boolean;
required(required: boolean): This;
//@ts-ignore
required(required?: boolean) { return $.fluent(this, arguments, () => this.dom.required, () => $.set(this.dom, 'required', required))}
label(): string;
label(label: $StateArgument<string> | undefined): This;
//@ts-ignore
label(label?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.label, () => $.set(this.dom, 'label', label))}
//@ts-ignore
get form(): $Form | null { return this.dom.form ? $(this.dom.form) : null }
//@ts-ignore
get validationMessage(): string { return this.dom.validationMessage }
//@ts-ignore
get validity(): ValidityState { return this.dom.validity }
//@ts-ignore
get willValidate(): boolean { return this.dom.willValidate }
}
export type $HTMLElementAPIFilter<This, M extends keyof $HTMLElementAPIs> = Pick<$HTMLElementAPIs<This>, M>

View File

@ -1,32 +0,0 @@
export class $EventManager<EM extends $EventMap> {
private eventMap = new Map<string, Set<Function>>();
//@ts-expect-error
fire<K extends keyof EM>(type: K, ...args: EM[K]) {
this.eventMap.get(type as string)?.forEach(fn => fn(...args as []));
return this
}
//@ts-expect-error
on<K extends keyof EM>(type: K, callback: (...args: EM[K]) => any) {
const set = this.eventMap.get(type as string) ?? this.eventMap.set(type as string, new Set()).get(type as string);
set?.add(callback);
return this
}
//@ts-expect-error
off<K extends keyof EM>(type: K, callback: (...args: EM[K]) => any) {
this.eventMap.get(type as string)?.delete(callback);
return this
}
//@ts-expect-error
once<K extends keyof EM>(type: K, callback: (...args: EM[K]) => any) {
const onceFn = (...args: []) => {
this.eventMap.get(type as string)?.delete(onceFn);
//@ts-expect-error
callback(...args);
}
const set = this.eventMap.get(type as string) ?? this.eventMap.set(type as string, new Set()).get(type as string)
set?.add(onceFn);
return this;
}
}
export interface $EventMap {}

View File

@ -1,50 +0,0 @@
import { $EventManager, $EventMap } from "./$EventManager";
export abstract class $EventTarget<$EM extends $EventMap = $EventMap, EM extends GlobalEventHandlersEventMap = GlobalEventHandlersEventMap> {
private domEvents: Partial<{[key in keyof EM]: Map<Function, Function>}> = {};
readonly events = new $EventManager<$EM>();
abstract dom: EventTarget
//@ts-expect-error
on<K extends keyof $EM>(type: K | K[], callback: (...args: $EM[K]) => any): this;
on<K extends keyof EM>(type: K | K[], callback: (event: EM[K], $node: this) => any, options?: AddEventListenerOptions | boolean): this;
on<K extends string>(type: K | K[], callback: (event: Event, $node: this) => any, options?: AddEventListenerOptions | boolean): this;
on<K extends keyof EM>(types: K | K[], callback: (event: EM[K], $node: this) => any, options?: AddEventListenerOptions | boolean) {
types = $.orArrayResolve(types);
for (const type of types) {
if (!this.domEvents[type]) this.domEvents[type] = new Map()
const handler = (e: Event) => { callback(e as EM[K], this); }
this.domEvents[type].set(callback, handler);
this.events.on(type as any, callback);
this.dom.addEventListener(type as string, handler, options);
}
return this;
}
//@ts-expect-error
off<K extends keyof $EM>(type: K, callback: (...args: $EM[K]) => any): this;
off<K extends keyof EM>(type: K, callback: (event: EM[K], $node: this) => any, options?: AddEventListenerOptions | boolean): this;
off<K extends string>(type: K, callback: (event: Event, $node: this) => any, options?: AddEventListenerOptions | boolean): this;
off<K extends keyof EM>(type: K, callback: (event: EM[K], $node: this) => any, options?: AddEventListenerOptions | boolean) {
const middleCallback = this.domEvents[type]?.get(callback);
if (middleCallback) this.dom.removeEventListener(type as string, middleCallback as EventListener, options);
this.events.off(type as any, callback);
return this;
}
//@ts-expect-error
once<K extends keyof $EM>(type: K, callback: (...args: $EM[K]) => any): this;
once<K extends keyof EM>(type: K, callback: (event: EM[K], $node: this) => any, options?: AddEventListenerOptions | boolean): this;
once<K extends string>(type: K, callback: (event: Event, $node: this) => any, options?: AddEventListenerOptions | boolean): this;
once<K extends keyof EM>(type: K, callback: (event: EM[K], $node: this) => any, options?: AddEventListenerOptions | boolean) {
const onceFn = (event: Event) => {
this.dom.removeEventListener(type as string, onceFn, options)
callback(event as EM[K], this);
};
this.dom.addEventListener(type as string, onceFn, options);
this.events.once(type as any, callback);
return this;
}
trigger(event: string) { this.dom.dispatchEvent(new Event(event)); return }
}

View File

@ -1,175 +0,0 @@
import { $Element } from "..";
export class $FocusManager {
layerMap = new Map<number, $FocusLayer>();
currentLayer?: $FocusLayer;
historyList: $FocusLayer[] = [];
constructor() {}
layer(id: number) {
const layer = this.layerMap.get(id) ?? new $FocusLayer(id);
this.layerMap.set(layer.id, layer);
return layer;
}
next() { return this.select($FocusNavigation.Next) }
prev() { return this.select($FocusNavigation.Prev) }
up() { return this.select($FocusNavigation.Up) }
down() { return this.select($FocusNavigation.Down) }
right() { return this.select($FocusNavigation.Right) }
left() { return this.select($FocusNavigation.Left) }
blur() {
this.currentLayer?.blur();
return this;
}
select(navigation: $FocusNavigation) {
this.currentLayer = this.currentLayer ?? [...this.layerMap.values()].at(0);
if (!this.currentLayer) return this;
const $focused = this.currentLayer.currentFocus;
const eleList = this.currentLayer.elementSet.array;
if (!$focused) { this.currentLayer.focus(this.currentLayer.beforeBlur ?? eleList.at(0)); return this; }
const eleIndex = eleList.indexOf($focused)
switch (navigation) {
case $FocusNavigation.Next:
case $FocusNavigation.Prev: {
let targetIndex = navigation === 0 ? eleIndex + 1 : eleIndex - 1;
if (targetIndex === eleList.length && this.currentLayer.loop()) targetIndex = 0;
else if (targetIndex === -1 && !this.currentLayer.loop()) targetIndex = 0;
this.currentLayer.focus(eleList.at(targetIndex));
break;
}
case $FocusNavigation.Down:
case $FocusNavigation.Left:
case $FocusNavigation.Right:
case $FocusNavigation.Up: {
const focusedPosition = $focused.coordinate();
if (!focusedPosition) break;
const focusedCoordinate = $.call(() => {
switch (navigation) {
case $FocusNavigation.Up: return {y: focusedPosition.y, x: focusedPosition.x / 2}
case $FocusNavigation.Down: return {y: focusedPosition.y + focusedPosition.height, x: focusedPosition.x / 2}
case $FocusNavigation.Left: return {y: focusedPosition.y / 2, x: focusedPosition.x}
case $FocusNavigation.Right: return {y: focusedPosition.y / 2, x: focusedPosition.x + focusedPosition.width}
}
})
const eleInfoList = eleList.map($ele => {
if ($ele === $focused) return;
const elePosition = $ele.coordinate();
if (!elePosition) return;
const eleCoordinate = $.call(() => {
switch (navigation) {
case $FocusNavigation.Up: return {y: elePosition.y + elePosition.height, x: elePosition.x / 2};
case $FocusNavigation.Down: return {y: elePosition.y, x: elePosition.x / 2};
case $FocusNavigation.Left: return {y: elePosition.y / 2, x: elePosition.x + elePosition.width};
case $FocusNavigation.Right: return {y: elePosition.y / 2, x: elePosition.x};
}
})
return {
$ele, elePosition,
distance: Math.sqrt((eleCoordinate.x - focusedCoordinate.x) ** 2 + (eleCoordinate.y - focusedCoordinate.y) ** 2)
}
}).detype(undefined).filter(({elePosition}) => {
switch (navigation) {
case $FocusNavigation.Up: if (elePosition.y + elePosition.height >= focusedPosition.y) return false; break;
case $FocusNavigation.Down: if (elePosition.y <= focusedPosition.y + focusedPosition.height) return false; break;
case $FocusNavigation.Left: if (elePosition.x + elePosition.width >= focusedPosition.x) return false; break;
case $FocusNavigation.Right: if (elePosition.x <= focusedPosition.x + focusedPosition.width) return false; break;
}
return true;
})
const $target = eleInfoList.sort((a, b) => a.distance - b.distance).at(0)?.$ele;
this.currentLayer.focus($target);
}
}
return this;
}
}
export enum $FocusNavigation { Next, Prev, Up, Down, Right, Left }
export class $FocusLayer {
id: number;
elementSet = new Set<$Element>();
entrySet = new Set<$Element>();
beforeBlur?: $Element;
currentFocus?: $Element;
private __$property__ = {
loop: true,
scrollThreshold: 0
}
constructor(id: number) {
this.id = id
this.add = this.add.bind(this);
this.entry = this.entry.bind(this);
}
add($elements: OrArray<$Element>) {
$.orArrayResolve($elements).forEach($element => {
this.elementSet.add($element);
$element.tabIndex(0);
});
return this;
}
remove($element: $Element) {
this.elementSet.delete($element);
return this;
}
entry($elements: OrArray<$Element>) {
$.orArrayResolve($elements).forEach(this.entrySet.add.bind(this.entrySet))
return this;
}
focus($element: $Element | undefined) {
if (!$element) return this;
$element.hide(false);
const {scrollTop, scrollLeft} = document.documentElement;
const position = $.call(() => {
const rect = $element.domRect()
return {
left: rect.left + scrollLeft,
top: rect.top + scrollTop,
right: rect.right + scrollLeft,
bottom: rect.bottom + scrollTop,
height: rect.height,
width: rect.width
}
})
const {scrollThreshold} = this.__$property__;
this.blur();
this.currentFocus = $element;
if (scrollTop > position.top - scrollThreshold // scroll after item threshold
|| scrollTop > position.bottom + scrollThreshold
) document.documentElement.scrollTo({left: position.left - scrollThreshold, top: position.top - scrollThreshold});
if (scrollTop + innerHeight < position.top + scrollThreshold // scroll before item
|| scrollTop + innerHeight < position.bottom + scrollThreshold
) document.documentElement.scrollTo({left: position.left - scrollThreshold, top: (position.bottom - innerHeight) + scrollThreshold});
$element.attribute('focus', '')
$element.focus({preventScroll: true});
return this;
}
blur() {
if (!this.currentFocus) return this;
this.beforeBlur = this.currentFocus;
this.currentFocus.attribute('focus', null);
this.currentFocus?.blur();
this.currentFocus = undefined;
return this;
}
removeAll() {
this.elementSet.clear();
return this;
}
loop(): boolean;
loop(boolean: boolean): this;
loop(boolean?: boolean) { return $.fluent(this, arguments, () => this.__$property__.loop, () => $.set(this.__$property__, 'loop', boolean)) }
scrollThreshold(): number;
scrollThreshold(number: number): this;
scrollThreshold(number?: number) { return $.fluent(this, arguments, () => this.__$property__.scrollThreshold, () => $.set(this.__$property__, 'scrollThreshold', number)) }
}

View File

@ -1,52 +0,0 @@
import { $EventTarget } from "./$EventTarget";
import { $Util } from "./$Util";
export class $KeyboardManager {
keyMap = new Map<string, $KeyboardEventMap>();
protected conditional?: ((event: KeyboardEvent) => boolean | undefined);
constructor($element: $EventTarget) {
$element.on('keydown', e => { if (this.conditional && !this.conditional(e)) return; this.keyMap.get(e.key)?.keydown.forEach(fn => fn(e)) })
$element.on('keyup', e => { if (this.conditional && !this.conditional(e)) return; this.keyMap.get(e.key)?.keyup.forEach(fn => fn(e)) })
$element.on('keypress', e => { if (this.conditional && !this.conditional(e)) return; this.keyMap.get(e.key)?.keypress.forEach(fn => fn(e)) })
}
if(callback: (event: KeyboardEvent) => boolean | undefined) {
this.conditional = callback;
return this;
}
assigns(keys: OrArray<string>, on: OrArray<$KeyboardEventType>, callback: $KeyboardEventHandler) {
keys = $Util.orArrayResolve(keys);
on = $Util.orArrayResolve(on);
for (const key of keys) {
const eventData: $KeyboardEventMap = this.keyMap.get(key) ?? {keydown: new Set(), keypress: new Set(), keyup: new Set()};
for (const event of on) {
eventData[event].add(callback);
}
this.keyMap.set(key, eventData);
}
return this;
}
unassign(keys: OrArray<string>, on?: OrArray<$KeyboardEventType>, callback?: (event: KeyboardEvent) => void) {
keys = $Util.orArrayResolve(keys);
on = on ? $Util.orArrayResolve(on) : ['keydown', 'keypress', 'keyup'];
for (const key of keys) {
const eventData: $KeyboardEventMap = this.keyMap.get(key) ?? {keydown: new Set(), keypress: new Set(), keyup: new Set()};
for (const event of on) {
if (callback) eventData[event].delete(callback);
else eventData[event].clear();
}
this.keyMap.set(key, eventData);
}
return this;
}
keydown(keys: OrArray<string>, callback: $KeyboardEventHandler) { this.assigns(keys, 'keydown', callback); return this; }
keyup(keys: OrArray<string>, callback: $KeyboardEventHandler) { this.assigns(keys, 'keyup', callback); return this; }
keypress(keys: OrArray<string>, callback: $KeyboardEventHandler) { this.assigns(keys, 'keypress', callback); return this; }
}
export type $KeyboardEventType = 'keydown' | 'keyup' | 'keypress';
export type $KeyboardEventHandler = (event: KeyboardEvent) => void;
type $KeyboardEventMap = {[key in $KeyboardEventType]: Set<$KeyboardEventHandler>};

View File

@ -1,71 +0,0 @@
import { $Container } from "./node/$Container";
import { $Node } from "./node/$Node";
export class $NodeManager {
readonly $container: $Container;
readonly childList = new Set<$Node>
constructor(container: $Container) {
this.$container = container;
}
add(element: $Node, position = -1) {
if (position === -1 || this.childList.size - 1 === position) {
this.childList.add(element);
} else {
const children = [...this.childList]
children.splice(position, 0, element);
this.childList.clear();
children.forEach(child => this.childList.add(child));
}
(element as Mutable<$Node>).parent = this.$container;
}
remove(element: $Node) {
if (!this.childList.has(element)) return this;
this.childList.delete(element);
(element as Mutable<$Node>).parent = undefined;
return this;
}
removeAll(render = true) {
this.childList.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.childList.clear();
array.forEach(node => this.childList.add(node));
(replace as Mutable<$Node>).parent = this.$container;
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.$.hide()) this.dom.append(dom); domList.shift();}
else if (dom !== node) {
if (!dom.$.hide()) { this.dom.insertBefore(dom, node); appendedNodeList.push(dom) }
domList.shift();
}
else {
if (dom.$.hide()) this.dom.removeChild(dom);
domList.shift(); nodeList.shift();
}
}
}
indexOf(target: $Node) {
return this.array.indexOf(target);
}
get array() {return [...this.childList.values()]};
get dom() {return this.$container.dom}
}

View File

@ -1,105 +0,0 @@
import { $EventManager, $EventMap } from "./$EventManager";
import { type $Node } from "./node/$Node";
export class $PointerManager extends $EventManager<$PointerManagerEventMap> {
$node: $Node;
map = new Map<number, $Pointer>();
constructor($node: $Node) {
super();
this.$node = $node;
this.$node.on('pointerdown', (e) => this.down(e))
this.$node.on('pointerup', (e) => this.up(e))
this.$node.on('pointermove', (e) => this.move(e))
this.$node.on('pointercancel', (e) => this.cancel(e))
}
protected down(e: PointerEvent) {
const pointer = new $Pointer(this, this.toData(e), $(e.target!))
this.map.set(pointer.id, pointer);
this.fire('down', pointer, e);
}
protected up(e: PointerEvent) {
const pointer = this.map.get(e.pointerId);
if (!pointer) return;
this.map.delete(e.pointerId);
this.fire('up', pointer, e);
}
protected move(e: PointerEvent) {
const pointer = this.map.get(e.pointerId);
if (!pointer) return;
this.map.set(pointer.id, pointer);
pointer.update(this.toData(e));
this.fire('move', pointer, e);
}
protected cancel(e: PointerEvent) {
const pointer = this.map.get(e.pointerId);
if (!pointer) return;
pointer.update(this.toData(e));
this.map.delete(pointer.id);
this.fire('cancel', pointer, e);
}
protected toData(e: PointerEvent): $PointerData {
return {
id: e.pointerId,
type: e.pointerType as PointerType,
width: e.width,
height: e.height,
x: e.x,
y: e.y,
movement_x: e.movementX,
movement_y: e.movementY
}
}
}
export interface $PointerManagerEventMap extends $EventMap {
up: [$Pointer, MouseEvent];
down: [$Pointer, MouseEvent];
move: [$Pointer, MouseEvent];
cancel: [$Pointer, MouseEvent];
}
export interface $Pointer extends $PointerData {}
export class $Pointer {
initial_x: number;
initial_y: number;
$target: $Node;
protected manager: $PointerManager;
constructor(manager: $PointerManager, data: $PointerData, target: $Node) {
Object.assign(this, data);
this.manager = manager;
this.$target = target;
this.initial_x = data.x;
this.initial_y = data.y;
}
get move_x() { return this.x - this.initial_x }
get move_y() { return this.y - this.initial_y }
update(data: $PointerData) {
Object.assign(this, data);
return this;
}
delete() {
this.manager.map.delete(this.id);
return this;
}
}
export interface $PointerData {
id: number;
type: PointerType;
width: number;
height: number;
x: number;
y: number;
movement_x: number;
movement_y: number;
}
export type PointerType = 'mouse' | 'pen' | 'touch'

View File

@ -1,76 +0,0 @@
export interface $StateOption<T> {
format: (value: T) => string;
}
export class $State<T> {
protected _value!: T | $State<T>;
readonly attributes = new Map<Object, Set<string | number | symbol>>();
readonly linkStates = new Set<$State<T>>;
options: Partial<$StateOption<T>> = {}
constructor(value: T, options?: $StateOption<T>) {
this.set(value);
if (options) this.options = options;
}
set(value: T | $State<T>) {
this._value = value;
if (value instanceof $State) value.linkStates.add(this as any);
this.update();
this.linkStates.forEach($state => $state.update());
}
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;
}
protected update() {
// update element content for eatch attributes
for (const [node, attrList] of this.attributes.entries()) {
for (const attr of attrList) {
//@ts-expect-error
if (node[attr] instanceof Function) {
//@ts-expect-error
if (this.options.format) node[attr](this.options.format(this.value))
//@ts-expect-error
else node[attr](this.value)
}
else if (attr in node) {
//@ts-expect-error
node[attr] = 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))
}
convert(fn: (value: T) => string) {
return new $State<T>(this as any, {format: fn});
}
get value(): T {
return this._value instanceof $State ? this._value.value as T : this._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}`
}
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();
}
};
export type $StateArgument<T> = $State<T> | undefined | (T extends (infer R)[] ? R : T);

View File

@ -1,71 +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() === 'head') return new $Container('head', {dom: element as HTMLHeadElement});
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
? new $Container(element.tagName, {dom: element})
: 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})`
}
}

View File

@ -1,9 +0,0 @@
import { $EventManager, $EventMap } from "./$EventManager";
import { $EventTarget } from "./$EventTarget";
export class $Window<EM extends $WindowEventMap = $WindowEventMap> extends $EventTarget<EM, WindowEventMap> {
static $ = new $Window();
readonly dom = window;
}
export interface $WindowEventMap extends $EventMap {}

View File

@ -1,27 +0,0 @@
import { $StateArgument } from "../$State";
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 ($.anchorHandler && !!this.href()) {
e.preventDefault();
$.anchorHandler(this, e);
}
})
}
/**Set URL of anchor element. */
href(): string;
href(url: $StateArgument<string>): this;
href(url?: $StateArgument<string>) { return $.fluent(this, arguments, () => this.dom.href, () => $.set(this.dom, 'href', url)) }
/**Link open with this window, new tab or other */
target(): $AnchorTarget | undefined;
target(target: $AnchorTarget | undefined): this;
target(target?: $AnchorTarget | undefined) { return $.fluent(this, arguments, () => (this.dom.target ?? undefined) as $AnchorTarget | undefined, () => {if (target) this.dom.target = target}) }
}
export type $AnchorTarget = '_blank' | '_self' | '_parent' | '_top';

View File

@ -1,32 +0,0 @@
import { $State } from "../$State";
import { $Container, $ContainerContentType, $ContainerOptions } from "./$Container";
import { $Node } from "./$Node";
import { $Text } from "./$Text";
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: Promise<T | $ContainerContentType> | (($self: this) => Promise<T | $ContainerContentType>)) {
if ($node instanceof Function) $node(this).then($node => this._loaded($node));
else $node.then($node => this._loaded($node));
return this as $Async<T>
}
protected _loaded($node: $ContainerContentType) {
this.#loaded = true;
if (typeof $node === 'string') this.replace(new $Text($node));
else if ($node instanceof $State) {
const ele = new $Text($node.toString());
$node.use(ele, 'content');
this.replace(ele);
}
else if ($node === null || $node === undefined) this.replace(new $Text(String($node)));
else this.replace($node)
this.dom.dispatchEvent(new Event('load'))
}
get loaded() { return this.#loaded }
}

View File

@ -1,16 +0,0 @@
import { $Container, $ContainerOptions } from "./$Container";
import { $Util } from "../$Util";
import { $HTMLElementAPIFilter, $HTMLElementAPIs } from "../$ElementTemplate";
export interface $ButtonOptions extends $ContainerOptions {}
export class $Button extends $Container<HTMLButtonElement> {
constructor(options?: $ButtonOptions) {
super('button', options);
}
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))}
}
export interface $Button extends $HTMLElementAPIFilter<$Button, 'disabled' | 'checkValidity' | 'formAction' | 'formEnctype' | 'formMethod' | 'formNoValidate' | 'formTarget' | 'reportValidity'> {}
$Util.mixin($Button, $HTMLElementAPIs.create('disabled', 'checkValidity', 'formAction', 'formEnctype', 'formMethod', 'formNoValidate', 'formTarget', 'reportValidity'))

View File

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

View File

@ -1,83 +0,0 @@
import { $Element } from "./$Element";
import { $NodeManager } from "../$NodeManager";
import { $Node } from "./$Node";
import { $State, $StateArgument } from "../$State";
import { $Text } from "./$Text";
import { $HTMLElement, $HTMLElementEventMap, $HTMLElementOptions } from "./$HTMLElement";
export interface $ContainerOptions extends $HTMLElementOptions {}
export class $Container<H extends HTMLElement = HTMLElement, EM extends $ContainerEventMap = $ContainerEventMap> extends $HTMLElement<H, EM> {
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);
})}
private __position_cursor = 0;
/**Insert element to this element */
insert(children: $ContainerContentBuilder<this>, position = -1): this { return $.fluent(this, arguments, () => this, async () => {
if (children instanceof Function) { // resolve function and promise
let cache = children(this);
if (cache instanceof Promise) children = await cache;
else children = cache;
} else if (children instanceof Promise) { children = await children }
children = $.orArrayResolve(children);
// Set position cursor depend negative or positive number, position will count from last index when position is negative.
this.__position_cursor = position < 0 ? this.children.array.length + position : position;
for (const child of children) {
if (child === undefined || child === null) continue; // skip
if (child instanceof Array) this.insert(child, this.__position_cursor); // insert element group at this position
else if (typeof child === 'string') this.children.add(new $Text(child), position); // turn string into $Text element
else if (child instanceof $State) {
const ele = new $Text(child.toString()); // turn $State object into $Text element
child.use(ele, 'content'); // bind $Text elelment and function name to $State
this.children.add(ele, position);
}
else if (child instanceof Promise) {
const $Async = (await import('./$Async')).$Async; // import $Async avoid extends error
const ele = new $Async().await(child) // using $Async.await resolve promise element
this.children.add(ele, position); // insert $Async element at this position, leave a position for promised element
}
else this.children.add(child, position); // insert $Node element directly
this.__position_cursor += 1; // increase position count
}
this.children.render(); // start to render dom tree
})}
/**Remove all children elemetn from this element */
clear() {
this.children.removeAll();
return this;
}
$<E extends $Element = $Element>(query: `::${string}`): E[];
$<E extends $Element = $Element>(query: `:${string}`): E | null;
$(query: string) {
if (query.startsWith('::')) return Array.from(document.querySelectorAll(query.replace(/^::/, ''))).map(dom => $(dom));
else if (query.startsWith(':')) return $(document.querySelector(query.replace(/^:/, '')));
}
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> = $ContainerContentGroup | (($node: P) => OrPromise<$ContainerContentGroup>)
export type $ContainerContentGroup = OrMatrix<OrPromise<$ContainerContentType>>
export type $ContainerContentType = $Node | string | undefined | $State<any> | null
export interface $ContainerEventMap extends $HTMLElementEventMap {}

View File

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

View File

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

View File

@ -1,116 +0,0 @@
import { $Node, $NodeEventMap } from "./$Node";
export interface $ElementOptions {
id?: string;
class?: string[];
dom?: HTMLElement | SVGElement;
tagname?: string;
}
export class $Element<H extends HTMLElement | SVGElement = HTMLElement, $EM extends $ElementEventMap = $ElementEventMap, EM extends HTMLElementEventMap = HTMLElementEventMap> extends $Node<H, $EM, EM> {
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(options?.tagname ?? 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(options?: FocusOptions) { this.dom.focus(options); 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) animation.onfinish = () => callback(animation);
return animation;
}
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'>;
export interface $ElementEventMap extends $NodeEventMap {}

View File

@ -1,43 +0,0 @@
import { $HTMLElementAPIFilter, $HTMLElementAPIs } from "../$ElementTemplate";
import { $Util } from "../$Util";
import { $Container, $ContainerOptions } from "./$Container";
export interface $FormOptions extends $ContainerOptions {}
export class $Form extends $Container<HTMLFormElement> {
constructor(options?: $FormOptions) {
super('form', options);
}
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 }
get length() { return this.dom.length }
get elements() { return Array.from(this.dom.elements).map(ele => $(ele)) }
}
export interface $Form extends $HTMLElementAPIFilter<$Form, 'checkValidity' | 'reportValidity' | 'autocomplete'> {}
$Util.mixin($Form, $HTMLElementAPIs.create('checkValidity', 'reportValidity', 'autocomplete'))

View File

@ -1,67 +0,0 @@
import { $Element, $ElementEventMap, $ElementOptions } from "./$Element";
export interface $HTMLElementOptions extends $ElementOptions {}
export class $HTMLElement<H extends HTMLElement = HTMLElement, $EM extends $HTMLElementEventMap = $HTMLElementEventMap> extends $Element<H, $EM> {
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 }
}
export interface $HTMLElementEventMap extends $ElementEventMap {}

View File

@ -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 }
}

View File

@ -1,184 +0,0 @@
import { $State, $StateArgument } from "../$State";
import { $HTMLElementAPIFilter, $HTMLElementAPIs } from "../$ElementTemplate";
import { $Util } from "../$Util";
import { $HTMLElement, $HTMLElementOptions } from "./$HTMLElement";
export interface $InputOptions extends $HTMLElementOptions {}
export class $Input<T extends string | number = string> extends $HTMLElement<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))}
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))}
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))}
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 }
get files() { return this.dom.files }
get webkitEntries() { return this.dom.webkitEntries }
get labels() { return Array.from(this.dom.labels ?? []).map(label => $(label)) }
}
export interface $Input extends $HTMLElementAPIFilter<$Input, 'checkValidity' | 'reportValidity' | 'autocomplete' | 'name' | 'form' | 'required' | 'validationMessage' | 'validity' | 'willValidate' | 'formAction' | 'formEnctype' | 'formMethod' | 'formNoValidate' | 'formTarget'> {}
$Util.mixin($Input, $HTMLElementAPIs.create('checkValidity', 'reportValidity', 'autocomplete', 'name', 'form', 'required', 'validationMessage', 'validity', 'willValidate', 'formAction', 'formEnctype', 'formMethod', 'formNoValidate', 'formTarget'))
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 $FileInput;
}
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>;
$Util.mixin($Input, [$NumberInput, $CheckInput, $FileInput])

View File

@ -1,19 +0,0 @@
import { $HTMLElementAPIFilter, $HTMLElementAPIs } from "../$ElementTemplate";
import { $StateArgument } from "../$State";
import { $Util } from "../$Util";
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 control() { return this.dom.control }
}
export interface $Label extends $HTMLElementAPIFilter<$Label, 'form'> {}
$Util.mixin($Label, $HTMLElementAPIs.create('form',))

View File

@ -1,107 +0,0 @@
import { $StateArgument } from "../$State";
import { $ElementOptions } from "./$Element";
import { $HTMLElement } from "./$HTMLElement";
export interface $MediaOptions extends $ElementOptions {}
export class $Media<H extends HTMLMediaElement> extends $HTMLElement<H> {
constructor(tagname: string, options?: $MediaOptions) {
super(tagname, options);
}
autoplay(): boolean;
autoplay(autoplay: $StateArgument<boolean>): this;
autoplay(autoplay?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.autoplay, () => $.set<HTMLMediaElement, 'autoplay'>(this.dom, 'autoplay', autoplay))}
get buffered() { return this.dom.buffered }
controls(): boolean;
controls(controls: $StateArgument<boolean>): this;
controls(controls?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.controls, () => $.set<HTMLMediaElement, 'controls'>(this.dom, 'controls', controls))}
crossOrigin(): string | null;
crossOrigin(crossOrigin: $StateArgument<string | null>): this;
crossOrigin(crossOrigin?: $StateArgument<string | null>) { return $.fluent(this, arguments, () => this.dom.crossOrigin, () => $.set<HTMLMediaElement, 'crossOrigin'>(this.dom, 'crossOrigin', crossOrigin))}
get currentSrc() { return this.dom.currentSrc };
currentTime(): number;
currentTime(currentTime: $StateArgument<number>): this;
currentTime(currentTime?: $StateArgument<number>) { return $.fluent(this, arguments, () => this.dom.currentTime, () => $.set<HTMLMediaElement, 'currentTime'>(this.dom, 'currentTime', currentTime))}
defaultMuted(): boolean;
defaultMuted(defaultMuted: $StateArgument<boolean>): this;
defaultMuted(defaultMuted?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.defaultMuted, () => $.set<HTMLMediaElement, 'defaultMuted'>(this.dom, 'defaultMuted', defaultMuted))}
defaultPlaybackRate(): number;
defaultPlaybackRate(defaultPlaybackRate: $StateArgument<number>): this;
defaultPlaybackRate(defaultPlaybackRate?: $StateArgument<number>) { return $.fluent(this, arguments, () => this.dom.defaultPlaybackRate, () => $.set<HTMLMediaElement, 'defaultPlaybackRate'>(this.dom, 'defaultPlaybackRate', defaultPlaybackRate))}
disableRemotePlayback(): boolean;
disableRemotePlayback(disableRemotePlayback: $StateArgument<boolean>): this;
disableRemotePlayback(disableRemotePlayback?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.disableRemotePlayback, () => $.set<HTMLMediaElement, 'disableRemotePlayback'>(this.dom, 'disableRemotePlayback', disableRemotePlayback))}
get duration() { return this.dom.duration }
get ended() { return this.dom.ended }
get error() { return this.dom.error }
loop(): boolean;
loop(loop: $StateArgument<boolean>): this;
loop(loop?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.loop, () => $.set<HTMLMediaElement, 'loop'>(this.dom, 'loop', loop))}
mediaKeys(): MediaKeys | null;
mediaKeys(mediaKeys: $StateArgument<[MediaKeys | null]>): this;
mediaKeys(mediaKeys?: $StateArgument<[MediaKeys | null]>) { return $.fluent(this, arguments, () => this.dom.mediaKeys, () => $.set<HTMLMediaElement, 'setMediaKeys'>(this.dom, 'setMediaKeys', [mediaKeys]))}
muted(): boolean;
muted(muted: $StateArgument<boolean>): this;
muted(muted?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.muted, () => $.set<HTMLMediaElement, 'muted'>(this.dom, 'muted', muted))}
get networkState() { return this.dom.networkState }
get paused() { return this.dom.paused }
playbackRate(): number;
playbackRate(playbackRate: $StateArgument<number>): this;
playbackRate(playbackRate?: $StateArgument<number>) { return $.fluent(this, arguments, () => this.dom.playbackRate, () => $.set<HTMLMediaElement, 'playbackRate'>(this.dom, 'playbackRate', playbackRate))}
get played() { return this.dom.played }
preload(): this['dom']['preload'];
preload(preload: $StateArgument<this['dom']['preload']>): this;
preload(preload?: $StateArgument<this['dom']['preload']>) { return $.fluent(this, arguments, () => this.dom.preload, () => $.set<HTMLMediaElement, 'preload'>(this.dom, 'preload', preload))}
preservesPitch(): boolean;
preservesPitch(preservesPitch: $StateArgument<boolean>): this;
preservesPitch(preservesPitch?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.preservesPitch, () => $.set<HTMLMediaElement, 'preservesPitch'>(this.dom, 'preservesPitch', preservesPitch))}
get readyState() { return this.dom.readyState }
get remote() { return this.dom.remote }
get seekable() { return this.dom.seekable }
get seeking() { return this.dom.seeking }
sinkId(): string;
sinkId(sinkId: $StateArgument<[string]>): this;
sinkId(sinkId?: $StateArgument<[string]>) { return $.fluent(this, arguments, () => this.dom.sinkId, () => $.set<HTMLMediaElement, 'setSinkId'>(this.dom, 'setSinkId', [sinkId]))}
src(): string;
src(src: $StateArgument<string>): this;
src(src?: $StateArgument<string>) { return $.fluent(this, arguments, () => this.dom.src, () => $.set<HTMLMediaElement, 'src'>(this.dom, 'src', src))}
srcObject(): MediaProvider | null;
srcObject(srcObject: $StateArgument<MediaProvider | null>): this;
srcObject(srcObject?: $StateArgument<MediaProvider | null>) { return $.fluent(this, arguments, () => this.dom.srcObject, () => $.set<HTMLMediaElement, 'srcObject'>(this.dom, 'srcObject', srcObject))}
get textTracks() { return this.dom.textTracks }
volume(): number;
volume(volume: $StateArgument<number>): this;
volume(volume?: $StateArgument<number>) { return $.fluent(this, arguments, () => this.dom.volume, () => $.set<HTMLMediaElement, 'volume'>(this.dom, 'volume', volume))}
addTextTrack(kind: TextTrackKind, label?: string, language?: string) { return this.dom.addTextTrack(kind, label, language)}
canPlayType(type: string) { return this.dom.canPlayType(type) }
fastSeek(time: number) { this.dom.fastSeek(time); return this }
load() { this.dom.load(); return this }
pause() { this.dom.pause(); return this }
async play() { await this.dom.play(); return this}
get isPlaying() { return this.currentTime() > 0 && !this.paused && !this.ended && this.readyState > 2 }
}

View File

@ -1,75 +0,0 @@
import { $EventTarget } from "../$EventTarget";
import { $, $Element, $EventManager, $State, $HTMLElement, $Container } from "../../index";
export abstract class $Node<N extends Node = Node, $EM extends $NodeEventMap = $NodeEventMap, EM extends GlobalEventHandlersEventMap = GlobalEventHandlersEventMap> extends $EventTarget<$EM, EM> {
abstract readonly dom: N;
protected __$property__ = {
hidden: false,
coordinate: undefined as $NodeCoordinate | undefined
}
readonly parent?: $Container;
hide(): boolean;
hide(hide?: boolean | $State<boolean>, render?: boolean): this;
hide(hide?: boolean | $State<boolean>, render = true) { return $.fluent(this, arguments, () => this.__$property__.hidden, () => {
if (hide === undefined) return;
if (hide instanceof $State) { this.__$property__.hidden = hide.value; hide.use(this, 'hide')}
else this.__$property__.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)
}
coordinate(): $NodeCoordinate | undefined;
coordinate(coordinate: $NodeCoordinate): this;
coordinate(coordinate?: $NodeCoordinate) { return $.fluent(this, arguments, () => this.__$property__.coordinate, () => $.set(this.__$property__, 'coordinate', coordinate))}
self(callback: OrArray<($node: this) => void>) { $.orArrayResolve(callback).forEach(fn => fn(this)); return this; }
inDOM() { return document.contains(this.dom); }
isElement(): this is $Element {
if (this instanceof $Element) return true;
else return false;
}
get element(): $Element | null {
if (this instanceof $Element) return this;
else return null;
}
get htmlElement(): $HTMLElement | null {
if (this instanceof $HTMLElement) return this;
else return null;
}
}
export interface $NodeCoordinate {
x: number;
y: number;
height: number;
width: number;
}
export interface $NodeEventMap {
}
type $HTMLElementEventMap<$N> = {
[keys in keyof HTMLElementEventMap]: [event: HTMLElementEventMap[keys], $this: $N];
};

View File

@ -1,18 +0,0 @@
import { $Container, $ContainerOptions } from "./$Container";
import { $StateArgument } from "../$State";
import { $HTMLElementAPIFilter, $HTMLElementAPIs } from "../$ElementTemplate";
import { $Util } from "../$Util";
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))}
}
export interface $OptGroup extends $HTMLElementAPIFilter<$OptGroup, 'disabled' | 'label'> {}
$Util.mixin($OptGroup, $HTMLElementAPIs.create('disabled', 'label'))

View File

@ -1,34 +0,0 @@
import { $Container, $ContainerOptions } from "./$Container";
import { $State, $StateArgument } from "../$State";
import { $HTMLElementAPIFilter, $HTMLElementAPIs } from "../$ElementTemplate";
import { $Util } from "../$Util";
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))}
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 }
}
export interface $Option extends $HTMLElementAPIFilter<$Option, 'form' | 'disabled' | 'label'> {}
$Util.mixin($Option, $HTMLElementAPIs.create('form', 'disabled', 'label'))

View File

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

View File

@ -1,48 +0,0 @@
import { $Container, $ContainerOptions } from "./$Container";
import { $OptGroup } from "./$OptGroup";
import { $Option } from "./$Option";
import { $State, $StateArgument } from "../$State";
import { $HTMLElementAPIFilter, $HTMLElementAPIs } from "../$ElementTemplate";
import { $Util } from "../$Util";
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)) }
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))}
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)) }
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 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()))
})
}))}
get labels() { return Array.from(this.dom.labels ?? []).map(label => $(label)) }
}
export interface $Select extends $HTMLElementAPIFilter<$Select, 'checkValidity' | 'reportValidity' | 'autocomplete' | 'name' | 'form' | 'required' | 'disabled' | 'validationMessage' | 'validity' | 'willValidate'> {}
$Util.mixin($Select, $HTMLElementAPIs.create('checkValidity', 'reportValidity', 'autocomplete', 'name', 'form', 'required', 'disabled', 'validationMessage', 'validity', 'willValidate'))
export type $SelectContentType = $Option | $OptGroup | undefined;

View File

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

View File

@ -1,75 +0,0 @@
import { $Container, $ContainerOptions } from "./$Container";
import { $StateArgument } from "../$State";
import { $HTMLElementAPIFilter, $HTMLElementAPIs } from "../$ElementTemplate";
import { $Util } from "../$Util";
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))}
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))}
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))}
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))}
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 }
get labels() { return Array.from(this.dom.labels ?? []).map(label => $(label)) }
}
export interface $Textarea extends $HTMLElementAPIFilter<$Textarea, 'checkValidity' | 'reportValidity' | 'autocomplete' | 'name' | 'form' | 'required' | 'disabled' | 'minLength' | 'maxLength' | 'validationMessage' | 'validity' | 'willValidate'> {}
$Util.mixin($Textarea, $HTMLElementAPIs.create('checkValidity', 'reportValidity', 'autocomplete', 'name', 'form', 'required', 'disabled', 'minLength', 'maxLength', 'validationMessage', 'validity', 'willValidate'))

View File

@ -1,37 +0,0 @@
import { $StateArgument } from "../$State";
import { $Media, $MediaOptions } from "./$Media";
export interface $VideoOptions extends $MediaOptions {}
export class $Video extends $Media<HTMLVideoElement> {
constructor(options?: $VideoOptions) {
super('video', options)
}
disablePictureInPicture(): boolean;
disablePictureInPicture(disablePictureInPicture: $StateArgument<boolean>): this;
disablePictureInPicture(disablePictureInPicture?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.disablePictureInPicture, () => $.set(this.dom, 'disablePictureInPicture', disablePictureInPicture))}
height(): number;
height(height: $StateArgument<number>): this;
height(height?: $StateArgument<number>) { return $.fluent(this, arguments, () => this.dom.height, () => $.set(this.dom, 'height', height))}
width(): number;
width(width: $StateArgument<number>): this;
width(width?: $StateArgument<number>) { return $.fluent(this, arguments, () => this.dom.width, () => $.set(this.dom, 'width', width))}
playsInline(): boolean;
playsInline(playsInline: $StateArgument<boolean>): this;
playsInline(playsInline?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.playsInline, () => $.set(this.dom, 'playsInline', playsInline))}
poster(): string;
poster(poster: $StateArgument<string>): this;
poster(poster?: $StateArgument<string>) { return $.fluent(this, arguments, () => this.dom.poster, () => $.set(this.dom, 'poster', poster))}
get videoHeight() { return this.dom.videoHeight }
get videoWidth() { return this.dom.videoWidth }
cancelVideoFrameCallback(handle: number) { this.dom.cancelVideoFrameCallback(handle); return this }
getVideoPlaybackQuality() { return this.dom.getVideoPlaybackQuality() }
requestPictureInPicture() { return this.dom.requestPictureInPicture() }
requestVideoFrameCallback(callback: VideoFrameRequestCallback) { return this.dom.requestVideoFrameCallback(callback) }
}

BIN
logo_dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
logo_light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -1,22 +0,0 @@
{
"name": "elexis",
"description": "Build Web in Native JavaScript Syntax",
"version": "0.3.0",
"author": {
"name": "defaultkavy",
"email": "defaultkavy@gmail.com",
"url": "https://git.defaultkavy.com/defaultkavy"
},
"repository": {
"type": "git",
"url": "git+https://git.defaultkavy.com/defaultkavy/elexis.git"
},
"module": "index.ts",
"bugs": {
"url": "https://git.defaultkavy.com/defaultkavy/elexis/issues"
},
"homepage": "https://git.defaultkavy.com/defaultkavy/elexis",
"keywords": ["web", "front-end", "lib", "fluent", "framework"],
"license": "ISC",
"type": "module"
}

View File

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