amateras 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # Amateras
2
+ Amateras is a DOM Utility library.
3
+
4
+ ## Build DOM tree in JS
5
+ ```ts
6
+ import 'amateras';
7
+
8
+ $(document.body).content([
9
+ $('h1').attr({ class: 'title' }).content('Hello, World')
10
+ ])
11
+ ```
12
+
13
+ ## Style in JS
14
+ ```ts
15
+ import 'amateras';
16
+ import 'amateras/css';
17
+
18
+ const paragraphStyle = $.css({
19
+ border: '2px solid black',
20
+ padding: '0.4rem',
21
+ textAlign: 'center'
22
+ })
23
+
24
+ $('p').css(paragraphStyle).content([
25
+ 'Amateras is a ',
26
+ $('span').css({ color: 'blue', fontWeight: 700 }).content('DOM Utility Library.')
27
+ ])
28
+ ```
29
+
30
+ ## DOM Operating
31
+ ```ts
32
+ import 'amateras';
33
+
34
+ const count$ = $.signal(0);
35
+
36
+ $(document.body).content([
37
+ $('button').content('Click me')
38
+ .class('class1', 'class2')
39
+ .on('click', () => count$(oldValue => oldValue + 1)),
40
+ $('p').content($`Clicked ${count$} times.`)
41
+ ])
42
+ ```
43
+
44
+ ## State Management
45
+ ```ts
46
+ import 'amateras'
47
+
48
+ const count$ = $.signal(0);
49
+ const doubleCount$ = $.compute(() => count() * 2);
50
+
51
+ setInterval(() => count$(oldValue => oldValue + 1), 1000);
52
+
53
+ $(document.body).content([
54
+ $('p').content($`Count: ${count$}`),
55
+ $('p').content($`Double: ${doubleCount$}`)
56
+ ])
57
+ ```
58
+
59
+ ## Custom Components
60
+ ```ts
61
+ import 'amateras';
62
+ import 'amateras/html';
63
+
64
+ function NameCard(name: string, avatarURL: string) {
65
+ return $('div')
66
+ .css({
67
+ borderRadius: '1rem',
68
+ background: '#1e1e1e',
69
+ display: 'flex',
70
+ overflow: 'hidden'
71
+ })
72
+ .content([
73
+ $('img').src(avatarURL),
74
+ $('div').css({ padding: '1rem' }).content([
75
+ $('h2').css({ color: '#e1e1e1' }).content(name)
76
+ ])
77
+ ])
78
+ }
79
+
80
+ $(document.body).content([
81
+ $(NameCard, 'The dancing man', 'https://media.tenor.com/x8v1oNUOmg4AAAAM/rickroll-roll.gif')
82
+ ])
83
+ ```
@@ -0,0 +1,12 @@
1
+ export class $CSSDeclaration {
2
+ key: string;
3
+ value: string;
4
+ constructor(key: string, value: string) {
5
+ this.key = key;
6
+ this.value = value;
7
+ }
8
+
9
+ get property() {
10
+ return this.key.replaceAll(/([A-Z])/g, (_, s1) => `-${s1}`).toLowerCase();
11
+ }
12
+ }
@@ -0,0 +1,13 @@
1
+ import { $CSSRule } from "#css/$CSSRule";
2
+
3
+ export class $CSSMediaRule extends $CSSRule {
4
+ condition: string;
5
+ constructor(condition: string) {
6
+ super();
7
+ this.condition = condition;
8
+ }
9
+
10
+ toString() {
11
+ return `@media ${this.condition} { ${[...this.rules.values()].map(rule => rule.toString()).join('\n')} }`
12
+ }
13
+ }
@@ -0,0 +1,7 @@
1
+ export abstract class $CSSRule {
2
+ rules = new Set<$CSSRule>();
3
+ constructor() {
4
+ }
5
+
6
+ abstract toString(): string;
7
+ }
@@ -0,0 +1,20 @@
1
+ import type { $CSSDeclaration } from "#css/$CSSDeclaration";
2
+ import { $CSSRule } from "#css/$CSSRule";
3
+
4
+ export class $CSSStyleRule extends $CSSRule {
5
+ context: string[] = [];
6
+ declarations = new Map<string, $CSSDeclaration>();
7
+ className: string = '';
8
+ constructor(context: string[] = []) {
9
+ super();
10
+ this.context = context;
11
+ }
12
+
13
+ toString() {
14
+ return `${this.selector} { ${[...this.declarations.values()].map(dec => `${dec.property}: ${dec.value};`).join('\n')} }`
15
+ }
16
+
17
+ get selector() {
18
+ return this.context.join(' ');
19
+ }
20
+ }
package/ext/css/css.ts ADDED
@@ -0,0 +1,101 @@
1
+ import { $CSSDeclaration } from "#css/$CSSDeclaration";
2
+ import { $CSSMediaRule } from "#css/$CSSMediaRule";
3
+ import { $CSSRule } from "#css/$CSSRule";
4
+ import { $CSSStyleRule } from "#css/$CSSStyleRule";
5
+ import { randomId } from "#lib/randomId";
6
+ import { $Element } from "#node/$Element";
7
+
8
+ declare module '#core' {
9
+ export namespace $ {
10
+ export function css(options: $CSSOptions): $CSSStyleRule
11
+ export function CSS(options: $CSSSelector | $CSSMediaSelector): void
12
+ }
13
+ }
14
+
15
+ declare module '#node/$Element' {
16
+ export interface $Element {
17
+ css(...options: $CSSOptions[]): this;
18
+ }
19
+ }
20
+
21
+ Object.defineProperty($, 'css', {
22
+ value: function (options: $CSSOptions) {
23
+ if (options instanceof $CSSRule) return options;
24
+ const className = $CSS.generateClassName();
25
+ const rule = $CSS.createStyleRule(options, [className]);
26
+ rule.className = className;
27
+ return $CSS.insertRule( rule );
28
+ }
29
+ })
30
+
31
+ Object.defineProperty($, 'CSS', {
32
+ value: function (options: $CSSSelector | $CSSMediaRule) {
33
+ return Object.entries(options).map(([selector, declarations]) => {
34
+ return $CSS.insertRule( $CSS.createRule(selector, declarations) );
35
+ })
36
+ }
37
+ })
38
+
39
+ Object.defineProperty($Element.prototype, 'css', {
40
+ value: function (this: $Element, ...options: $CSSOptions[]) {
41
+ options.forEach(options => {
42
+ const rule = $.css(options);
43
+ this.addClass(rule.context[0]?.slice(1) as string);
44
+ })
45
+ return this;
46
+ }
47
+ })
48
+
49
+ export namespace $CSS {
50
+ export const stylesheet = new CSSStyleSheet();
51
+ const generatedIds = new Set<string>();
52
+ document.adoptedStyleSheets.push($CSS.stylesheet);
53
+ export function generateClassName(): string {
54
+ const id = randomId();
55
+ if (generatedIds.has(id)) return generateClassName();
56
+ generatedIds.add(id);
57
+ return `.${id}`;
58
+ }
59
+
60
+ export function createStyleRule<T extends $CSSRule>(options: T, context?: string[]): T;
61
+ export function createStyleRule<T extends $CSSOptions>(options: T, context?: string[]): $CSSStyleRule;
62
+ export function createStyleRule<T extends $CSSOptions>(options: T, context: string[] = []) {
63
+ if (options instanceof $CSSRule) return options;
64
+ const rule = new $CSSStyleRule(context);
65
+ for (const [key, value] of Object.entries(options)) {
66
+ if (value === undefined) continue;
67
+ if (value instanceof Object) rule.rules.add( createRule(key, value, context) )
68
+ else {
69
+ const declaration = new $CSSDeclaration(key, value);
70
+ rule.declarations.set(declaration.key, declaration);
71
+ }
72
+ }
73
+ return rule;
74
+ }
75
+
76
+ export function createRule(selector: string, options: $CSSOptions, context: string[] = []) {
77
+ if (selector.startsWith('@media')) return createMediaRule(selector.replace('@media ', ''), options, context);
78
+ else return createStyleRule(options, [...context, selector],);
79
+ }
80
+
81
+ export function createMediaRule(key: string, options: $CSSOptions, context: string[] = []) {
82
+ const rule = new $CSSMediaRule(key);
83
+ rule.rules.add(createStyleRule(options, context));
84
+ return rule;
85
+ }
86
+
87
+ export function insertRule(rule: $CSSRule) {
88
+ if (rule instanceof $CSSStyleRule && !CSS.supports(`selector(${rule.selector})`)) return rule;
89
+ $CSS.stylesheet.insertRule(rule.toString(), $CSS.stylesheet.cssRules.length);
90
+ if (rule instanceof $CSSMediaRule) return;
91
+ rule.rules.forEach(rule => insertRule(rule))
92
+ return rule;
93
+ }
94
+ }
95
+
96
+ type $CSSOptions = $CSSDeclarations | $CSSSelector | $CSSRule | $CSSMediaSelector;
97
+ type $CSSDeclarations = { [key in keyof CSSStyleDeclaration]?: string | number }
98
+ type $CSSSelector = { [key: string & {}]: $CSSOptions }
99
+ type $CSSMediaSelector = { [key: `@${string}`]: $CSSOptions }
100
+
101
+ console.debug(document.adoptedStyleSheets[0])
@@ -0,0 +1,59 @@
1
+ import '#core';
2
+ import { $Node } from '#node/$Node';
3
+ import { $HTMLElement } from './node/$HTMLElement';
4
+ import './node/type';
5
+ import { assignHelper } from '#lib/assignHelper';
6
+ export * from './node/type';
7
+ export * from './node/$HTMLElement';
8
+
9
+ // create element classes
10
+ export const [
11
+ $Input,
12
+ $Anchor,
13
+ $Image,
14
+ $Canvas,
15
+ $Dialog,
16
+ $Form,
17
+ $Label,
18
+ $Media,
19
+ $Select,
20
+ $Option,
21
+ $OptGroup,
22
+ $TextArea,
23
+ ] = [
24
+ $HTMLElementBuilder<HTMLInputElement>('input'),
25
+ $HTMLElementBuilder<HTMLAnchorElement>('a'),
26
+ $HTMLElementBuilder<HTMLImageElement>('img'),
27
+ $HTMLElementBuilder<HTMLCanvasElement>('canvas'),
28
+ $HTMLElementBuilder<HTMLDialogElement>('dialog'),
29
+ $HTMLElementBuilder<HTMLFormElement>('form'),
30
+ $HTMLElementBuilder<HTMLLabelElement>('label'),
31
+ $HTMLElementBuilder<HTMLMediaElement>('media'),
32
+ $HTMLElementBuilder<HTMLSelectElement>('select'),
33
+ $HTMLElementBuilder<HTMLOptionElement>('option'),
34
+ $HTMLElementBuilder<HTMLOptGroupElement>('optgroup'),
35
+ $HTMLElementBuilder<HTMLTextAreaElement>('textarea'),
36
+ ]
37
+
38
+ const targets: [object: Constructor<Node>, target: Constructor<$Node>, tagname?: string][] = [
39
+ [HTMLElement, $HTMLElement],
40
+ [HTMLInputElement, $Input, 'input'],
41
+ [HTMLAnchorElement, $Anchor, 'a'],
42
+ [HTMLImageElement, $Image, 'img'],
43
+ [HTMLCanvasElement, $Canvas, 'canvas'],
44
+ [HTMLDialogElement, $Dialog, 'dialog'],
45
+ [HTMLFormElement, $Form, 'form'],
46
+ [HTMLLabelElement, $Label, 'label'],
47
+ [HTMLMediaElement, $Media, 'media'],
48
+ [HTMLSelectElement, $Select, 'select'],
49
+ [HTMLOptionElement, $Option, 'option'],
50
+ [HTMLOptGroupElement, $OptGroup, 'optgroup'],
51
+ [HTMLTextAreaElement, $TextArea, 'textarea'],
52
+ ];
53
+ assignHelper(targets);
54
+
55
+ function $HTMLElementBuilder<Ele extends HTMLElement>(tagName: string) {
56
+ return class extends $HTMLElement<Ele> {
57
+ constructor() { super(tagName) }
58
+ }
59
+ }
@@ -0,0 +1,7 @@
1
+ import { $Element } from "#node/$Element";
2
+
3
+ export class $HTMLElement<Ele extends HTMLElement = HTMLElement> extends $Element<Ele> {
4
+ constructor(resolver: string | Ele) {
5
+ super(resolver);
6
+ }
7
+ }
@@ -0,0 +1,96 @@
1
+ import type { $HTMLElement } from "./$HTMLElement";
2
+ import type { Signal } from "#structure/Signal";
3
+
4
+ type $Parameter<T> = T | undefined | Signal<T> | Signal<T | undefined>
5
+
6
+ declare module '#node/$Element' {
7
+ export interface $Element<Ele extends Element> {
8
+ /** {@link Element.attributes} */
9
+ readonly attributes: NamedNodeMap;
10
+ /** {@link Element.clientHeight} */
11
+ readonly clientHeight: number;
12
+ /** {@link Element.clientLeft} */
13
+ readonly clientLeft: number;
14
+ /** {@link Element.clientTop} */
15
+ readonly clientTop: number;
16
+ /** {@link Element.clientWidth} */
17
+ readonly clientWidth: number;
18
+ /** {@link Element.currentCSSZoom} */
19
+ readonly currentCSSZoom: number;
20
+ /** {@link Element.localName} */
21
+ readonly localName: string;
22
+ /** {@link Element.namespaceURI} */
23
+ readonly namespaceURI: string | null;
24
+ /** {@link Element.prefix} */
25
+ readonly prefix: string | null;
26
+ /** {@link Element.ownerDocument} */
27
+ readonly ownerDocument: Document;
28
+ /** {@link Element.scrollHeight} */
29
+ readonly scrollHeight: number;
30
+ /** {@link Element.scrollWidth} */
31
+ readonly scrollWidth: number;
32
+ /** {@link Element.shadowRoot} */
33
+ readonly shadowRoot: ShadowRoot | null;
34
+ /** {@link Element.tagName} */
35
+ readonly tagName: string;
36
+
37
+ /** {@link Element.classList} */
38
+ classList(): DOMTokenList;
39
+ classList(value: $Parameter<string>): this;
40
+ /** {@link Element.className} */
41
+ className(): string;
42
+ className(value: $Parameter<string>): this;
43
+ /** {@link Element.id} */
44
+ id(): string;
45
+ id(id: $Parameter<string>): this;
46
+ /** {@link Element.innerHTML} */
47
+ innerHTML(): string;
48
+ innerHTML(innerHTML: $Parameter<string>): this;
49
+ /** {@link Element.outerHTML} */
50
+ outerHTML(): string;
51
+ outerHTML(outerHTML: $Parameter<string>): this;
52
+ /** {@link Element.part} */
53
+ part(): DOMTokenList;
54
+ part(part: $Parameter<string>): this;
55
+ /** {@link Element.scrollLeft} */
56
+ scrollLeft(): number;
57
+ scrollLeft(scrollLeft: $Parameter<number>): this;
58
+ /** {@link Element.scrollTop} */
59
+ scrollTop(): number;
60
+ scrollTop(scrollTop: $Parameter<number>): this;
61
+ /** {@link Element.slot} */
62
+ slot(): string;
63
+ slot(slot: $Parameter<string>): this;
64
+ }
65
+ }
66
+
67
+ export interface $Input extends $HTMLElement<HTMLInputElement> {}
68
+ export interface $Anchor extends $HTMLElement<HTMLAnchorElement> {}
69
+ export interface $Image extends $HTMLElement<HTMLImageElement> {
70
+ src(src: string): this;
71
+ src(): string;
72
+ }
73
+ export interface $Canvas extends $HTMLElement<HTMLCanvasElement> {}
74
+ export interface $Dialog extends $HTMLElement<HTMLDialogElement> {}
75
+ export interface $Form extends $HTMLElement<HTMLFormElement> {}
76
+ export interface $Label extends $HTMLElement<HTMLLabelElement> {}
77
+ export interface $Media extends $HTMLElement<HTMLMediaElement> {}
78
+ export interface $Select extends $HTMLElement<HTMLSelectElement> {}
79
+ export interface $Option extends $HTMLElement<HTMLOptionElement> {}
80
+ export interface $OptGroup extends $HTMLElement<HTMLOptGroupElement> {}
81
+ export interface $TextArea extends $HTMLElement<HTMLTextAreaElement> {}
82
+
83
+ declare module '#core' {
84
+ export function $(tagname: 'input'): $Input
85
+ export function $(tagname: 'anchor'): $Anchor
86
+ export function $(tagname: 'img'): $Image
87
+ export function $(tagname: 'dialog'): $Dialog
88
+ export function $(tagname: 'form'): $Form
89
+ export function $(tagname: 'label'): $Label
90
+ export function $(tagname: 'media'): $Media
91
+ export function $(tagname: 'select'): $Select
92
+ export function $(tagname: 'option'): $Option
93
+ export function $(tagname: 'otpgroup'): $OptGroup
94
+ export function $(tagname: 'textarea'): $TextArea
95
+ export function $(tagname: string): $HTMLElement
96
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "amateras",
3
+ "version": "0.0.1",
4
+ "description": "Amateras is a DOM Utility library.",
5
+ "module": "index.ts",
6
+ "type": "module",
7
+ "author": {
8
+ "name": "defaultkavy",
9
+ "email": "defaultkavy@gmail.com",
10
+ "url": "https://github.com/defaultkavy"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/defaultkavy/amateras/issues"
14
+ },
15
+ "homepage": "https://github.com/defaultkavy/amateras",
16
+ "license": "MIT",
17
+ "imports": {
18
+ "#core": "./src/core.ts",
19
+ "#node/*": "./src/node/*.ts",
20
+ "#lib/*": "./src/lib/*.ts",
21
+ "#structure/*": "./src/structure/*.ts",
22
+ "#html": "./ext/html/html.ts",
23
+ "#css": "./ext/css/css.ts",
24
+ "#css/*": "./ext/css/*.ts"
25
+ },
26
+ "exports": {
27
+ ".": "./src/index.ts",
28
+ "./core": "./src/core.ts",
29
+ "./node": "./src/node/node.ts",
30
+ "./node/*": "./src/node/*.ts",
31
+ "./lib/*": "./src/lib/*.ts",
32
+ "./structure/*": "./src/structure/*.ts",
33
+ "./html": "./ext/html/html.ts",
34
+ "./css": "./ext/css/css.ts",
35
+ "./css/*": "./ext/css/*.ts"
36
+ }
37
+ }
package/src/core.ts ADDED
@@ -0,0 +1,84 @@
1
+ import './global';
2
+ import { Signal } from "#structure/Signal";
3
+ import { $Element } from "#node/$Element";
4
+ import { $Node, type $NodeContentTypes } from '#node/$Node';
5
+ import '#node/node';
6
+
7
+ const tagNameMap: {[key: string]: Constructor<$Node>} = {}
8
+ export function $<K extends (...args: any[]) => $Node>(fn: K, ...args: Parameters<K>): ReturnType<K>;
9
+ export function $<K extends $NodeContentTypes | undefined | void, F extends () => K, P extends Parameters<F>>(fn: F, ...args: any[]): K;
10
+ export function $<K extends $Node, T extends Constructor<K>, P extends ConstructorParameters<T>>(construct: T, ...args: P): K;
11
+ export function $<K extends TemplateStringsArray>(string: K, ...values: any[]): $NodeContentTypes[];
12
+ export function $<K extends $Node>($node: K): K;
13
+ export function $<K extends Element>(element: K): $Element<K>;
14
+ export function $<K extends keyof HTMLElementTagNameMap>(tagname: K): $Element<HTMLElementTagNameMap[K]>
15
+ export function $(tagname: string): $Element<HTMLElement>
16
+ export function $(resolver: string | HTMLElement | $Node | Function | TemplateStringsArray, ...args: any[]) {
17
+ if (resolver instanceof $Node) return resolver;
18
+ if (typeof resolver === 'string' && tagNameMap[resolver]) return new tagNameMap[resolver]();
19
+ if (typeof resolver === 'function')
20
+ if (resolver.prototype?.constructor) return resolver.prototype.constructor(...args);
21
+ else return resolver(...args);
22
+ if (resolver instanceof Array) {
23
+ const iterate = args.values();
24
+ return resolver.map(str => [str ?? undefined, iterate.next().value]).flat().filter(item => item);
25
+ }
26
+ if (resolver instanceof Node && resolver.$ instanceof $Node) return resolver.$;
27
+ return new $Element(resolver);
28
+ }
29
+
30
+ export namespace $ {
31
+ type SignalProcess<T> = T extends Array<any> ? {} : T extends object ? { [key in keyof T as `${string & key}$`]: SignalFunction<T[key]> } : {};
32
+ export type SignalFunction<T> = ({(value: ((newValue: T) => T) | T): SignalFunction<T>, (): T}) & { signal: Signal<T> } & SignalProcess<T>;
33
+ export function signal<T>(value: T): SignalFunction<T>
34
+ export function signal<T>(value: T) {
35
+ const signal = new Signal<T>(value);
36
+ const signalFn = function (newValue?: T) {
37
+ if (!arguments.length) return signal.value();
38
+ if (newValue !== undefined) signal.value(newValue);
39
+ return signalFn;
40
+ }
41
+ Object.defineProperty(signalFn, 'signal', { value: signal });
42
+ if (value instanceof Object) {
43
+ for (const [key, val] of Object.entries(value)) {
44
+ const val$ = $.signal(val);
45
+ val$.signal.subscribe(newValue => { value[key as keyof typeof value] = newValue; signal.emit() });
46
+ Object.defineProperty(signalFn, `${key}$`, {value: val$});
47
+ }
48
+ }
49
+ return signalFn as unknown as SignalFunction<T>
50
+ }
51
+
52
+ export type ComputeFunction<T> = ({(): T}) & { signal: Signal<T> };
53
+ export function compute<T>(process: () => T) {
54
+ let subscribed = false;
55
+ const signalFn: SignalFunction<any> = $.signal(null);
56
+ function computeFn() {
57
+ if (!subscribed) return signalFn(subscribe())();
58
+ else return signalFn(process())();
59
+ }
60
+ function subscribe () {
61
+ const signalHandler = (signal: Signal<any>) => {
62
+ signal.subscribe(() => signalFn(process()))
63
+ }
64
+ Signal.listeners.add(signalHandler);
65
+ const result = process();
66
+ Signal.listeners.delete(signalHandler);
67
+ subscribed = true;
68
+ return result;
69
+ }
70
+ Object.defineProperty(computeFn, 'signal', { value: signalFn.signal });
71
+ return computeFn as ComputeFunction<T>
72
+ }
73
+
74
+ export function registerTagName(tagname: string, $node: Constructor<$Node>) {
75
+ tagNameMap[tagname] = $node;
76
+ return $;
77
+ }
78
+
79
+ export function orArrayResolver<T>(item: OrArray<T>): T[] {
80
+ return item instanceof Array ? item : [item];
81
+ }
82
+ }
83
+ export type $ = typeof $;
84
+ globalThis.$ = $;
package/src/global.ts ADDED
@@ -0,0 +1,22 @@
1
+ import type { $Element } from '#node/$Element';
2
+ import type { $Node } from '#node/$Node';
3
+ import * as core from '#core';
4
+
5
+ declare global {
6
+ export import $ = core.$;
7
+ type Nullish = null | undefined;
8
+ type OrArray<T> = T | T[];
9
+ type OrMatrix<T> = T | OrMatrix<T>[];
10
+ type OrPromise<T> = T | Promise<T>;
11
+ type OrNullish<T> = T | Nullish;
12
+ type Constructor<T> = { new (...args: any[]): T }
13
+ type Prettify<T> = {
14
+ [K in keyof T]: T[K];
15
+ } & {};
16
+ interface Node {
17
+ readonly $: $Node
18
+ }
19
+ interface Element {
20
+ readonly $: $Element
21
+ }
22
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ import '#core';
2
+ export * from '#core';
3
+ export * from '#structure/Signal';
@@ -0,0 +1,37 @@
1
+ import { Signal } from "../structure/Signal";
2
+
3
+ export function assign(target: any, {set, get, fn}: {
4
+ set?: string[],
5
+ get?: string[],
6
+ fn?: string[]
7
+ }) {
8
+ const filterAndMap = (type: 'get' | 'set' | 'fn', arr: string[] | undefined) => {
9
+ return arr?.map(prop => [type, prop]) ?? []
10
+ }
11
+ const list = [...filterAndMap('get', get), ...filterAndMap('set', set), ...filterAndMap('fn', fn)] as [string, string][];
12
+ for (const [type, prop] of list) {
13
+ Object.defineProperty(target.prototype, prop, {
14
+ ...(type === 'get' ? {
15
+ get() { return this.node[prop as any] }
16
+ } : {
17
+ writable: true,
18
+ ...(type === 'set' ? {
19
+ // set
20
+ value: function (this, args: any) {
21
+ if (!arguments.length) return this.node[prop];
22
+ const set = (value: any) => value !== undefined && (this.node[prop] = value);
23
+ if (args instanceof Signal) args = args.subscribe(set).value();
24
+ set(args)
25
+ return this;
26
+ }
27
+ } : {
28
+ // fn
29
+ value: function (this, ...args : any[]) {
30
+ return this.node[prop](...args) ?? this;
31
+ }
32
+ })
33
+ }),
34
+
35
+ })
36
+ }
37
+ }
@@ -0,0 +1,18 @@
1
+ import type { $Node } from "#node/$Node";
2
+ import { assign } from "./assign";
3
+
4
+ export function assignHelper(targets: [object: Constructor<Node>, target: Constructor<$Node>, tagname?: string][]) {
5
+ for (const [object, target, tagname] of targets) {
6
+ const {set, get, fn} = { set: [] as string[], get: [] as string[], fn: [] as string[] }
7
+ // assign native object properties to target
8
+ for (const [prop, value] of Object.entries(Object.getOwnPropertyDescriptors(object.prototype))) {
9
+ if (prop in target.prototype) continue;
10
+ if (value.get && !value.set) get.push(prop);
11
+ else if (value.value) fn.push(prop);
12
+ else if (value.get && value.set) set.push(prop);
13
+ }
14
+ assign(target, {set, get, fn})
15
+ // register tagname
16
+ if (tagname) $.registerTagName(tagname, target)
17
+ }
18
+ }
@@ -0,0 +1,9 @@
1
+ const LOWER = 'abcdefghijklmnopqrstuvwxyz';
2
+ const UPPER = LOWER.toUpperCase();
3
+ export function randomId(options?: {length?: number, case?: 'any' | 'lower' | 'upper'}): string {
4
+ options = {length: 5, case: 'any', ...options};
5
+ const char = options.case === 'any' ? LOWER + UPPER : options.case === 'lower' ? LOWER : UPPER;
6
+ return Array.from({length: options.length as number}, (_, i) => {
7
+ const rand = Math.round(Math.random() * char.length); return char[rand]
8
+ }).join('');
9
+ }
@@ -0,0 +1,3 @@
1
+ export async function sleep(ms: number) {
2
+ return new Promise(resolve => setTimeout(resolve, ms))
3
+ }
@@ -0,0 +1,59 @@
1
+ import { Signal } from "#structure/Signal";
2
+ import { $Node } from "#node/$Node";
3
+
4
+ export class $Element<Ele extends Element = Element> extends $Node {
5
+ listeners = new Map<Function, (event: Event) => void>;
6
+ declare node: Ele
7
+ constructor(resolver: Ele | string) {
8
+ super(resolver instanceof Element ? resolver : document.createElement(resolver) as unknown as Ele)
9
+ //@ts-expect-error
10
+ this.node.$ = this;
11
+ }
12
+
13
+ attr(): {[key: string]: string};
14
+ attr(obj: {[key: string]: string | number | boolean | Signal<any>}): this;
15
+ attr(obj?: {[key: string]: string | number | boolean | Signal<any>}) {
16
+ if (!arguments.length) return Object.fromEntries(Array.from(this.node.attributes).map(attr => [attr.name, attr.value]));
17
+ if (obj) for (let [key, value] of Object.entries(obj)) {
18
+ const set = (value: any) => value !== undefined && this.node.setAttribute(key, `${value}`)
19
+ if (value instanceof Signal) value = value.subscribe(set).value();
20
+ set(value);
21
+ }
22
+ return this;
23
+ }
24
+
25
+ class(...token: string[]) {
26
+ this.node.classList = token.join(' ');
27
+ return this;
28
+ }
29
+
30
+ addClass(...token: string[]) {
31
+ this.node.classList.add(...token);
32
+ return this;
33
+ }
34
+
35
+ removeClass(...token: string[]) {
36
+ this.node.classList.remove(...token);
37
+ return this;
38
+ }
39
+
40
+ use(callback: ($ele: this) => void) {
41
+ callback(this);
42
+ return this;
43
+ }
44
+
45
+ on<K extends keyof HTMLElementEventMap>(type: K, listener: ($node: this, event: Event) => void, options?: boolean | AddEventListenerOptions) {
46
+ const handler = (event: Event) => listener(this, event);
47
+ this.listeners.set(listener, handler);
48
+ this.node.addEventListener(type, handler, options);
49
+ return this;
50
+ }
51
+
52
+ off<K extends keyof HTMLElementEventMap>(type: K, listener: ($node: this, event: Event) => void, options?: boolean | EventListenerOptions) {
53
+ const handler = this.listeners.get(listener);
54
+ if (handler) this.node.removeEventListener(type, handler, options);
55
+ this.listeners.delete(listener);
56
+ return this;
57
+ }
58
+ once() {}
59
+ }
@@ -0,0 +1,80 @@
1
+ import { Signal } from "#structure/Signal";
2
+
3
+ export class $Node {
4
+ node: Node;
5
+ constructor(node: Node) {
6
+ this.node = node;
7
+ //@ts-expect-error
8
+ this.node.$ = this;
9
+ }
10
+
11
+ content(children: $NodeContentResolver<this>) {
12
+ this.node.childNodes.forEach(node => node.remove());
13
+ return this.insert(children);
14
+ }
15
+
16
+ insert(resolver: $NodeContentResolver<this>, position = -1) {
17
+ if (resolver instanceof Function) {
18
+ const content = resolver(this);
19
+ if (content instanceof Promise) content.then(content => $Node.insertChild(this.node, content, position))
20
+ else $Node.insertChild(this.node, content, position);
21
+ } else $Node.insertChild(this.node, resolver, position);
22
+ return this;
23
+ }
24
+
25
+ await<T>(promise: Promise<T>, callback: ($node: this, result: T) => void): this {
26
+ promise.then(result => callback(this, result));
27
+ return this;
28
+ }
29
+
30
+ replace($node: $NodeContentTypes | undefined | null) {
31
+ if (!$node) return this;
32
+ this.node.parentNode?.replaceChild($Node.processContent($node), this.node);
33
+ return this;
34
+ }
35
+
36
+ static insertChild(node: Node, children: OrArray<OrPromise<$NodeContentTypes>>, position = -1) {
37
+ children = $.orArrayResolver(children);
38
+ // get child node at position
39
+ const positionChild = Array.from(node.childNodes).filter(node => node.nodeType !== node.TEXT_NODE).at(position);
40
+ // insert node helper function for depend position
41
+ const append = (child: Node | undefined | null) => {
42
+ if (!child) return;
43
+ if (!positionChild) node.appendChild(child);
44
+ else node.insertBefore(child, position < 0 ? positionChild.nextSibling : positionChild);
45
+ }
46
+ // process nodes
47
+ for (const child of children) if (child !== undefined) append($Node.processContent(child))
48
+ }
49
+
50
+ static processContent(content: $NodeContentTypes): Node;
51
+ static processContent(content: undefined | null): undefined | null;
52
+ static processContent(content: OrPromise<undefined | null>): undefined | null;
53
+ static processContent(content: OrPromise<$NodeContentTypes>): Node;
54
+ static processContent(content: OrPromise<$NodeContentTypes | undefined | null>): Node | undefined | null
55
+ static processContent(content: OrPromise<$NodeContentTypes | undefined | null>) {
56
+ if (content === undefined) return undefined;
57
+ if (content === null) return null;
58
+ // is $Element
59
+ if (content instanceof $Node) return content.node;
60
+ // is Promise
61
+ if (content instanceof Promise) {
62
+ const async = $('async').await(content, ($async, $child) => $async.replace($child));
63
+ return async.node;
64
+ }
65
+ // is SignalFunction
66
+ if (content instanceof Function && (content as $.SignalFunction<any>).signal instanceof Signal) {
67
+ const text = new Text();
68
+ const set = (value: any) => text.textContent = value instanceof Object ? JSON.stringify(value) : value;
69
+ (content as $.SignalFunction<any>).signal.subscribe(set);
70
+ set((content as $.SignalFunction<any>)());
71
+ return text;
72
+ }
73
+ // is string | number | boolean
74
+ return new Text(`${content}`);
75
+ }
76
+ }
77
+
78
+ export type $NodeContentTypes = $Node | string | number | boolean | $.SignalFunction<any>;
79
+ export type $NodeContentHandler<T extends $Node> = ($node: T) => OrPromise<OrArray<$NodeContentTypes>>;
80
+ export type $NodeContentResolver<T extends $Node> = $NodeContentHandler<T> | OrArray<OrPromise<$NodeContentTypes>>
@@ -0,0 +1,11 @@
1
+ import { assignHelper } from '#lib/assignHelper';
2
+ import { $Element } from '#node/$Element';
3
+ import { $Node } from './$Node';
4
+
5
+ export * from './$Element';
6
+ export * from './$Node';
7
+
8
+ assignHelper([
9
+ [Node, $Node],
10
+ [Element, $Element],
11
+ ])
@@ -0,0 +1,40 @@
1
+ export class Signal<T> {
2
+ #value: T;
3
+ subscribers = new Set<(value: T) => void>();
4
+ static listeners = new Set<(signal: Signal<any>) => void>();
5
+ constructor(value: T) {
6
+ this.#value = value;
7
+ }
8
+
9
+ value(): T;
10
+ value(newValue: T): this;
11
+ value(callback: (oldValue: T) => T): this;
12
+ value(resolver?: T | ((oldValue: T) => T)) {
13
+ if (!arguments.length) {
14
+ Signal.listeners.forEach(fn => fn(this));
15
+ return this.#value;
16
+ }
17
+ if (resolver instanceof Function) this.value(resolver(this.#value));
18
+ else if (resolver !== undefined) {
19
+ this.#value = resolver;
20
+ this.emit();
21
+ }
22
+ return this;
23
+ }
24
+
25
+ emit() {
26
+ this.subscribers.forEach(subs => subs(this.#value))
27
+ return this;
28
+ }
29
+
30
+ subscribe(callback: (value: T) => void) {
31
+ this.subscribers.add(callback);
32
+ return this;
33
+ }
34
+
35
+ unsubscribe(callback: (value: T) => void) {
36
+ this.subscribers.delete(callback);
37
+ return this;
38
+ }
39
+
40
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["ESNext", "DOM"],
5
+ "target": "ESNext",
6
+ "module": "ESNext",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+
11
+ // Bundler mode
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "noEmit": true,
16
+
17
+ // Best practices
18
+ "strict": true,
19
+ "skipLibCheck": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "noUncheckedIndexedAccess": true,
22
+
23
+ // Some stricter flags (disabled by default)
24
+ "noUnusedLocals": false,
25
+ "noUnusedParameters": false,
26
+ "noPropertyAccessFromIndexSignature": false
27
+ },
28
+ "exclude": ["dist"]
29
+ }