amateras 0.3.0 → 0.4.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.
@@ -1,6 +1,6 @@
1
1
  import { _Object_assign } from "../../../../src/lib/native";
2
2
 
3
- export function colorAssign(key: string, colors: {[key: number]: string}) {
3
+ export const colorAssign = (key: string, colors: {[key: number]: string}) => {
4
4
  if (!$.color) _Object_assign($, {color: {}});
5
5
  _Object_assign($.color, {[key]: colors})
6
6
  }
@@ -0,0 +1,13 @@
1
+ import { _Array_from, _instanceof } from "amateras/lib/native";
2
+ import { $CSSRule } from "#structure/$CSSRule";
3
+
4
+ export class $CSSContainerRule extends $CSSRule {
5
+ condition: string;
6
+ name: string;
7
+ constructor(selector: string) {
8
+ super(selector);
9
+ const [_, name, condition] = selector.match(/@container (.+?) (.+)/) as [string, string, string]
10
+ this.name = name;
11
+ this.condition = condition;
12
+ }
13
+ }
@@ -8,6 +8,6 @@ export abstract class $CSSRule {
8
8
  }
9
9
 
10
10
  get options(): {[key: string]: any} {
11
- return _Object_fromEntries(_Array_from(this.rules).map(rule => [rule.selector, rule]))
11
+ return _Object_fromEntries(_Array_from(this.rules).map(rule => [rule.selector, rule.options]))
12
12
  }
13
13
  }
@@ -8,13 +8,6 @@ export class $CSSStyleRule extends $CSSRule {
8
8
  super(selector);
9
9
  }
10
10
 
11
- clone(selector: string) {
12
- const rule = new $CSSStyleRule(selector)
13
- rule.declarations = this.declarations;
14
- rule.rules = this.rules;
15
- return rule
16
- }
17
-
18
11
  get options(): {[key: string]: any} {
19
12
  return {..._Object_fromEntries(_Array_from(this.declarations).map(([_, dec]) => [dec.key, dec])), ...super.options}
20
13
  }
@@ -1,7 +1,7 @@
1
- export class $CSSVariable {
1
+ export class $CSSVariable<V = string> {
2
2
  key: string;
3
- value: string;
4
- constructor(key: string, value: string) {
3
+ value: V;
4
+ constructor(key: string, value: V) {
5
5
  this.key = key;
6
6
  this.value = value;
7
7
  }
package/ext/html/html.ts CHANGED
@@ -10,16 +10,4 @@ import './node/$Media';
10
10
  import './node/$OptGroup';
11
11
  import './node/$Option';
12
12
  import './node/$Select';
13
- import './node/$TextArea';
14
- export * from './node/$Anchor';
15
- export * from './node/$Canvas';
16
- export * from './node/$Dialog';
17
- export * from './node/$Form';
18
- export * from './node/$Image';
19
- export * from './node/$Input';
20
- export * from './node/$Label';
21
- export * from './node/$Media';
22
- export * from './node/$OptGroup';
23
- export * from './node/$Option';
24
- export * from './node/$Select';
25
- export * from './node/$TextArea';
13
+ import './node/$TextArea';
@@ -0,0 +1,53 @@
1
+ # amateras/i18n
2
+
3
+ ## Usage
4
+ ```ts
5
+ import 'amateras';
6
+ import 'amateras/i18n';
7
+
8
+ const $t = $.i18n()
9
+ // add 'en' locale dictionary context
10
+ .add('en', {
11
+ homepage: {
12
+ _: 'Home',
13
+ hello: 'Hello, $name$!',
14
+ }
15
+ })
16
+ // set 'en' as locale language
17
+ .locale('en')
18
+
19
+ $(document.body).content([
20
+ $('h1').content( $t('homepage') )
21
+ // <h1><text>Home</text></h1>
22
+ $t('homepage.hello', {name: 'Amateras'})
23
+ // <text>Hello, Amateras!</text>
24
+ ])
25
+ ```
26
+
27
+ ## Change Language
28
+ ```ts
29
+ $t.locale('zh')
30
+ // all translation text will be updated
31
+ ```
32
+
33
+ ## Import Dictionary Context
34
+
35
+ ```ts
36
+ // ./i18n/en.ts
37
+ export default {
38
+ hello: 'Hello, $name$!'
39
+ }
40
+
41
+ // ./i18n/zh.ts
42
+ export default {
43
+ hello: '您好,$name$!'
44
+ }
45
+
46
+ // ./entry_file.ts
47
+ const $t = $.i18n()
48
+ .add('en', () => import('./i18n/en.ts'))
49
+ .add('zh', () => import('./i18n/zh.ts'))
50
+ // set 'zh' as locale language
51
+ // and fetch file automatically from path
52
+ .locale('zh');
53
+ ```
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@amateras/i18n",
3
+ "peerDependencies": {
4
+ "amateras": "../../"
5
+ },
6
+ "imports": {
7
+ "#structure/*": "./src/structure/*.ts",
8
+ "#node/*": "./src/node/*.ts"
9
+ }
10
+ }
@@ -0,0 +1,54 @@
1
+ import { _Array_from, _instanceof, _Object_assign } from "amateras/lib/native"
2
+ import { $ } from "amateras/core"
3
+ import { I18n } from "#structure/I18n"
4
+ import type { I18nText as _I18nText, I18nTextOptions } from "#node/I18nText";
5
+ import { I18nDictionary, type I18nDictionaryContext, type I18nDictionaryContextImporter } from "#structure/I18nDictionary";
6
+
7
+ _Object_assign($, {
8
+ i18n(defaultLocale: string) {
9
+ const i18n = new I18n(defaultLocale);
10
+ const i18nFn = (key: string, options?: I18nTextOptions) => i18n.translate(key, options);
11
+ _Object_assign(i18nFn, {
12
+ i18n,
13
+ locale(locale: string) {
14
+ if (!arguments.length) return i18n.locale();
15
+ i18n.locale(locale);
16
+ return this;
17
+ },
18
+ add(lang: string, context: I18nDictionaryContext | I18nDictionaryContextImporter) {
19
+ i18n.map.set(lang, new I18nDictionary(context));
20
+ return this;
21
+ },
22
+ delete(lang: string) {
23
+ i18n.map.delete(lang);
24
+ return this;
25
+ }
26
+ })
27
+ return i18nFn
28
+ }
29
+ })
30
+
31
+ type ResolvedAsyncDictionary<F extends I18nDictionaryContextImporter> = Awaited<ReturnType<F>>['default'];
32
+
33
+ type DeepKeys<T> = T extends I18nDictionaryContext
34
+ ? {
35
+ [K in keyof T]: K extends string
36
+ ? K extends '_' ? never : `${K}` | `${K}.${DeepKeys<T[K]>}`
37
+ : never;
38
+ }[keyof T]
39
+ : never;
40
+
41
+ declare module "amateras/core" {
42
+ export namespace $ {
43
+ export interface I18nFunction<D extends I18nDictionaryContext = {}> {
44
+ (path: DeepKeys<D>, ...args: any[]): I18nText;
45
+ i18n: I18n;
46
+ locale(): string;
47
+ locale(lang?: $Parameter<string>): this;
48
+ add<F extends I18nDictionaryContext | I18nDictionaryContextImporter>(lang: string, dictionary: F): I18nFunction<D | (F extends I18nDictionaryContextImporter ? ResolvedAsyncDictionary<F> : F)>;
49
+ delete(lang: string): this;
50
+ }
51
+ export function i18n(defaultLocale: string): I18nFunction;
52
+ export type I18nText = _I18nText;
53
+ }
54
+ }
@@ -0,0 +1,35 @@
1
+ import { _Array_from, isUndefined } from "amateras/lib/native";
2
+ import { $HTMLElement } from "amateras/node/$HTMLElement";
3
+ import type { I18n } from "#structure/I18n";
4
+
5
+ export class I18nText extends $HTMLElement<HTMLElement, { i18nupdate: Event }> {
6
+ i18n: I18n;
7
+ key: string;
8
+ options: I18nTextOptions | undefined;
9
+ constructor(i18n: I18n, key: string, options?: I18nTextOptions) {
10
+ super('text');
11
+ this.i18n = i18n;
12
+ this.key = key;
13
+ this.options = options;
14
+ i18n.locale$.signal.subscribe(() => this.update())
15
+ this.update();
16
+ }
17
+
18
+ async update() {
19
+ update: {
20
+ const {key, i18n} = this;
21
+ const dictionary = i18n.dictionary();
22
+ if (!dictionary) {this.content(key); break update}
23
+ const target = await dictionary.find(key);
24
+ if (isUndefined(target)) break update;
25
+ const snippets = target.split(/\$[a-zA-Z0-9_]+\$/);
26
+ if (snippets.length === 1 || !this.options) {this.content(target); break update}
27
+ const matches = target.matchAll(/(\$([a-zA-Z0-9_]+)\$)/g);
28
+ this.content(snippets.map(text => [text, this.options?.[matches.next().value?.at(2)!] ?? null]));
29
+ }
30
+ this.dispatchEvent(new Event('i18nupdate'));
31
+ return this;
32
+ }
33
+ }
34
+
35
+ export type I18nTextOptions = {[key: string]: any}
@@ -0,0 +1,40 @@
1
+ import { _instanceof } from "amateras/lib/native";
2
+ import { I18nText, type I18nTextOptions } from "#node/I18nText";
3
+ import { I18nDictionary } from "#structure/I18nDictionary";
4
+
5
+ export class I18n {
6
+ locale$ = $.signal<string>('');
7
+ map = new Map<string, I18nDictionary>();
8
+ #defaultLocale: string;
9
+ constructor(defaultLocale: string) {
10
+ this.#defaultLocale = defaultLocale;
11
+ this.locale$.set(defaultLocale);
12
+ }
13
+
14
+ defaultLocale(): string;
15
+ defaultLocale(locale: string): this;
16
+ defaultLocale(locale?: string) {
17
+ if (!arguments.length) return this.#defaultLocale;
18
+ if (locale) this.locale$.set(locale);
19
+ return this;
20
+ }
21
+
22
+ locale(): string;
23
+ locale(locale: string): this;
24
+ locale(locale?: string) {
25
+ if (!arguments.length) return this.locale$();
26
+ if (locale) this.locale$.set(locale)
27
+ return this;
28
+ }
29
+
30
+ dictionary(locale = this.locale$()) {
31
+ if (!locale) return null;
32
+ const dictionary = this.map.get(locale);
33
+ return dictionary;
34
+ }
35
+
36
+ translate(key: string, options?: I18nTextOptions) {
37
+ return new I18nText(this, key, options);
38
+ }
39
+ }
40
+
@@ -0,0 +1,31 @@
1
+ import { _instanceof, isObject } from "amateras/lib/native";
2
+
3
+ export class I18nDictionary {
4
+ #context: I18nDictionaryContext | Promise<I18nDictionaryContext> | null = null;
5
+ #fetch: I18nDictionaryContextImporter | null = null;
6
+ constructor(resolver: I18nDictionaryContext | I18nDictionaryContextImporter) {
7
+ if (_instanceof(resolver, Function)) this.#fetch = resolver;
8
+ else this.#context = resolver;
9
+ }
10
+
11
+ async context(): Promise<I18nDictionaryContext> {
12
+ if (this.#context) return await this.#context;
13
+ if (!this.#fetch) throw 'I18n Context Fetch Error';
14
+ return this.#context = this.#fetch().then((module) => module.default);
15
+ }
16
+
17
+ async find(path: string, context?: I18nDictionaryContext): Promise<string | undefined> {
18
+ if (!context) context = await this.context();
19
+ const [snippet, ...rest] = path.split('.') as [string, ...string[]];
20
+ const target = context[snippet];
21
+ if (isObject(target)) {
22
+ if (rest.length) return this.find(rest.join('.'), target);
23
+ else return target['_'] as string;
24
+ }
25
+ if (rest.length) return path;
26
+ else return target;
27
+ }
28
+ }
29
+
30
+ export type I18nDictionaryContext = {[key: string]: string | I18nDictionaryContext}
31
+ export type I18nDictionaryContextImporter = () => Promise<{default: I18nDictionaryContext}>
@@ -1,5 +1,5 @@
1
1
  import type { AnchorTarget } from "#html/$Anchor";
2
- import { _Object_assign, forEach } from "#lib/native";
2
+ import { _bind, _Object_assign, forEach } from "#lib/native";
3
3
  import type { $NodeContentResolver } from "#node/$Node";
4
4
  import type { Page } from "./node/Page";
5
5
  import { Route } from "./node/Route";
@@ -21,18 +21,25 @@ declare module 'amateras/core' {
21
21
  export function forward(): typeof Router;
22
22
  }
23
23
  }
24
+
25
+ declare global {
26
+ interface GlobalEventHandlersEventMap {
27
+ 'routeopen': Event;
28
+ }
29
+ }
30
+
24
31
  // assign methods
25
32
  _Object_assign($, {
26
- open: Router.open.bind(Router),
27
- replace: Router.replace.bind(Router),
28
- back: Router.back.bind(Router),
29
- forward: Router.forward.bind(Router)
33
+ open: _bind(Router.open, Router),
34
+ replace: _bind(Router.replace, Router),
35
+ back: _bind(Router.back, Router),
36
+ forward: _bind(Router.forward, Router)
30
37
  });
31
38
  // define styles
32
39
  forEach([
33
40
  `router{display:block}`,
34
41
  `page{display:block}`
35
- ], rule => $.stylesheet.insertRule(rule));
42
+ ], $.style);
36
43
  // assign nodes
37
44
  $.assign([
38
45
  ['router', Router],
@@ -1,3 +1,4 @@
1
+ import { chain } from "#lib/chain";
1
2
  import { isUndefined } from "#lib/native";
2
3
  import { $HTMLElement } from "#node/$HTMLElement";
3
4
  import type { RouteData } from "..";
@@ -9,6 +10,7 @@ export class Page<R extends Route<any> = any, Data extends RouteData = any> exte
9
10
  params: Data['params'];
10
11
  query: Data['query'];
11
12
  #pageTitle: null | string = null;
13
+ initial = false;
12
14
  constructor(route: R, data?: {params: any, query: any}) {
13
15
  super('page');
14
16
  this.route = route;
@@ -20,8 +22,6 @@ export class Page<R extends Route<any> = any, Data extends RouteData = any> exte
20
22
  pageTitle(): string | null;
21
23
  pageTitle(title: string | null): this;
22
24
  pageTitle(title?: string | null) {
23
- if (!arguments.length) return this.#pageTitle;
24
- if (!isUndefined(title)) this.#pageTitle = title;
25
- return this;
25
+ return chain(this, arguments, () => this.#pageTitle, title, title => this.#pageTitle = title)
26
26
  }
27
27
  }
@@ -1,5 +1,5 @@
1
1
  import { _instanceof, _Object_fromEntries, _Array_from } from "#lib/native";
2
- import { $Element } from "#node/node";
2
+ import { $Element } from "#node/$Element";
3
3
  import type { AsyncRoute, RouteBuilder, RouteDataResolver } from "..";
4
4
  import { Page } from "./Page";
5
5
 
@@ -37,6 +37,7 @@ export class Route<Path extends string = string> extends BaseRouteNode<Path> {
37
37
  async build(data: {params: any, query: any} = {params: {}, query: {}}, page?: Page) {
38
38
  page = page ?? new Page(this, data);
39
39
  page.params = data.params;
40
+ page.initial = true;
40
41
  let resolver: any = this.builder(page);
41
42
  if (_instanceof(resolver, Promise)) {
42
43
  const result = await resolver as any;
@@ -1,24 +1,32 @@
1
1
  import type { AnchorTarget } from "#html/$Anchor";
2
- import { _Array_from, _document, _instanceof, _Object_fromEntries, forEach } from "#lib/native";
2
+ import { _document } from "#lib/env";
3
+ import { _Array_from, _instanceof, _JSON_parse, _JSON_stringify, _Object_entries, _Object_fromEntries, forEach, startsWith } from "#lib/native";
3
4
  import { Page } from "./Page";
4
5
  import { BaseRouteNode, Route } from "./Route";
5
6
 
6
7
  // history index
7
8
  let index = 0;
9
+ const _addEventListener = addEventListener;
8
10
  const _location = location;
9
11
  const {origin} = _location;
10
12
  const _history = history;
13
+ const _sessionStorage = sessionStorage;
11
14
  const documentElement = _document.documentElement;
12
15
  const [PUSH, REPLACE] = [1, 2] as const;
13
16
  const [FORWARD, BACK] = ['forward', 'back'] as const;
14
-
15
- // disable browser scroll restoration
16
- _history.scrollRestoration = 'manual';
17
-
17
+ const scrollStorageKey = '__scroll__';
18
18
  /** convert path string to URL object */
19
19
  const toURL = (path: string | URL) =>
20
- _instanceof(path, URL) ? path : path.startsWith('http') ? new URL(path) : new URL(path.startsWith(origin) ? path : origin + path);
20
+ _instanceof(path, URL) ? path : startsWith(path, 'http') ? new URL(path) : new URL(startsWith(path, origin) ? path : origin + path);
21
21
 
22
+ type ScrollData = {[key: number]: {x: number, y: number}};
23
+ const scrollRecord = (e?: Event) => {
24
+ const data = _JSON_parse(_sessionStorage.getItem(scrollStorageKey) ?? '{}') as ScrollData;
25
+ data[index] = { x: documentElement.scrollLeft, y: documentElement.scrollTop };
26
+ // e is Event when called from scroll or beforeload
27
+ if (!e) forEach(_Object_entries(data), ([i]) => +i > index && delete data[+i])
28
+ _sessionStorage.setItem(scrollStorageKey, _JSON_stringify(data));
29
+ }
22
30
  /** handle history state with push and replace state. */
23
31
  const historyHandler = async (path: string | URL | Nullish, mode: 1 | 2, target?: AnchorTarget) => {
24
32
  if (!path) return;
@@ -26,16 +34,14 @@ const historyHandler = async (path: string | URL | Nullish, mode: 1 | 2, target?
26
34
  if (url.href === _location.href) return;
27
35
  if (target && target !== '_self') return open(url, target);
28
36
  if (url.origin !== origin) return open(url, target);
29
- _history.replaceState({
30
- index: index,
31
- x: documentElement.scrollLeft,
32
- y: documentElement.scrollTop
33
- }, '', _location.href);
37
+ scrollRecord();
34
38
  if (mode === PUSH) index += 1;
35
39
  Router.direction = FORWARD;
36
- history[mode === PUSH ? 'pushState' : 'replaceState']({index}, '' , url)
37
- for (let router of Router.routers) router.routes.size && await router.resolve(path)
40
+ _history[mode === PUSH ? 'pushState' : 'replaceState']({index}, '' , url);
41
+ for (let router of Router.routers) router.routes.size && await router.resolve(path);
38
42
  }
43
+ // disable browser scroll restoration
44
+ _history.scrollRestoration = 'manual';
39
45
 
40
46
  export class Router extends BaseRouteNode<''> {
41
47
  static pageRouters = new Map<Page, Router>();
@@ -88,7 +94,7 @@ export class Router extends BaseRouteNode<''> {
88
94
  if (routeSnippet.includes(':')) {
89
95
  if (targetSnippet === '/') continue routeLoop;
90
96
  const [prefix, paramName] = routeSnippet.split(':') as [string, string];
91
- if (!targetSnippet.startsWith(prefix)) continue routeLoop;
97
+ if (!startsWith(targetSnippet, prefix)) continue routeLoop;
92
98
  routeData.params[paramName] = targetSnippet.replace(`${prefix}`, '');
93
99
  pass();
94
100
  continue splitLoop;
@@ -105,21 +111,20 @@ export class Router extends BaseRouteNode<''> {
105
111
  const targetRoutes = determineRoute(this, pathname + '/', hash);
106
112
  // build pages
107
113
  let prevPage: null | Page = null, prevRoute: BaseRouteNode<any> = this;
114
+ const appendPage = (prevRouter: Router | undefined, page: Page) => page.parentNode !== prevRouter?.node && prevRouter?.content(page);
115
+
108
116
  for (const [route, pathId] of targetRoutes) {
109
117
  const page = this.pageMap.get(pathId) ?? new Page(route ?? prevRoute.routes.get('404') ?? new Route('404', () => null), routeData);
110
- await route?.build(routeData, page);
118
+ if (!page.initial) await route?.build(routeData, page);
111
119
  _document && (_document.title = page.pageTitle() ?? _document.title);
112
120
  this.pageMap.set(pathId, page);
113
-
114
- if (href === _location.href) {
115
- if (prevPage) Router.pageRouters.get(prevPage)?.content(page);
116
- else this.content(page);
117
- }
121
+ if (href === _location.href) appendPage(prevPage ? Router.pageRouters.get(prevPage) : this, page);
118
122
  prevPage = page;
119
123
  if (route) prevRoute = route;
120
124
  }
121
- let { x, y } = _history.state ?? {x: 0, y: 0};
125
+ let { x, y } = Router.scroll ?? {x: 0, y: 0};
122
126
  scrollTo(x, y);
127
+ this.dispatchEvent(new Event('routeopen', {bubbles: true}));
123
128
  return this;
124
129
  }
125
130
 
@@ -131,8 +136,14 @@ export class Router extends BaseRouteNode<''> {
131
136
  index = stateIndex;
132
137
  this.resolve(_location.href);
133
138
  }
134
- addEventListener('popstate', resolve);
139
+ _addEventListener('popstate', resolve);
140
+ _addEventListener('beforeunload', scrollRecord);
141
+ _addEventListener('scroll', scrollRecord, false);
135
142
  resolve();
136
143
  return this;
137
144
  }
145
+
146
+ static get scroll(): ScrollData[number] {
147
+ return _JSON_parse(_sessionStorage.getItem(scrollStorageKey) ?? '{}')[index] ?? {x: 0, y: 0}
148
+ }
138
149
  }
package/ext/ssr/index.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import './env';
2
2
  import 'amateras';
3
- import { _Array_from, _document, _instanceof, _Object_assign, _Object_defineProperty, forEach } from "amateras/lib/native";
4
- import { $Element, $Node, $Text } from "amateras/node";
3
+ import { _Array_from, _instanceof, _Object_assign, _Object_defineProperty, forEach } from "amateras/lib/native";
4
+ import { $Element } from 'amateras/node/$Element';
5
5
  import { BROWSER, NODE } from 'esm-env';
6
+ import { $Node, $Text } from 'amateras/node/$Node';
7
+ import { _document } from '../../src/lib/env';
6
8
 
7
9
  declare module 'amateras/core' {
8
10
  export namespace $ {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amateras",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Amateras is a DOM Utility library.",
5
5
  "module": "index.ts",
6
6
  "type": "module",
@@ -25,7 +25,6 @@
25
25
  "exports": {
26
26
  ".": "./src/index.ts",
27
27
  "./core": "./src/core.ts",
28
- "./node": "./src/node/node.ts",
29
28
  "./node/*": "./src/node/*.ts",
30
29
  "./lib/*": "./src/lib/*.ts",
31
30
  "./structure/*": "./src/structure/*.ts",
@@ -35,12 +34,10 @@
35
34
  "./css/colors": "./ext/css/src/lib/colors.ts",
36
35
  "./css/color/*": "./ext/css/src/lib/colors/*.ts",
37
36
  "./router": "./ext/router/index.ts",
38
- "./ssr": "./ext/ssr/index.ts"
37
+ "./ssr": "./ext/ssr/index.ts",
38
+ "./i18n": "./ext/i18n/src/index.ts"
39
39
  },
40
40
  "workspaces": [
41
41
  "./ext/*"
42
- ],
43
- "peerDependencies": {
44
- "@amateras/css": "./ext/css"
45
- }
42
+ ]
46
43
  }
package/src/core.ts CHANGED
@@ -1,27 +1,36 @@
1
1
  import './global';
2
+ import './node';
2
3
  import { Signal } from "#structure/Signal";
3
4
  import { $Element, type $Event } from "#node/$Element";
4
5
  import { $Node, type $NodeContentResolver, type $NodeContentTypes } from '#node/$Node';
5
- import '#node/node';
6
- import { _instanceof, isString, isFunction, _Object_assign, isObject, isNull, _Object_entries, _Object_defineProperty, forEach, isNumber, _Array_from, _document } from '#lib/native';
7
- import type { $HTMLElement } from '#node/$HTMLElement';
6
+ import { _instanceof, isString, isFunction, _Object_assign, isObject, isNull, _Object_entries, _Object_defineProperty, forEach, isNumber, _Array_from, isUndefined, _bind, _null } from '#lib/native';
7
+ import { $HTMLElement } from '#node/$HTMLElement';
8
+ import { _document } from '#lib/env';
8
9
 
9
10
  const nodeNameMap: {[key: string]: Constructor<$Node>} = {}
11
+ const _stylesheet = new CSSStyleSheet();
10
12
 
11
13
  export function $<F extends (...args: any[]) => $NodeContentResolver<$Node>, N extends number>(number: N, fn: F, ...args: Parameters<F>): Repeat<ReturnType<F>, N>;
12
14
  export function $<F extends (...args: any[]) => $NodeContentResolver<$Node>>(fn: F, ...args: Parameters<F>): ReturnType<F>;
13
15
  export function $<T extends Constructor<$Node>, P extends ConstructorParameters<T>, N extends number>(number: N, construct: T, ...args: P): Repeat<InstanceType<T>, N>;
14
16
  export function $<T extends Constructor<$Node>, P extends ConstructorParameters<T>>(construct: T, ...args: P): InstanceType<T>;
17
+ export function $(nodes: NodeListOf<Node | ChildNode>): $Node[];
15
18
  export function $<N extends $Node>($node: N, ...args: any[]): N;
19
+ export function $<N extends $Node>($node: N | null | undefined, ...args: any[]): N | null | undefined;
16
20
  export function $<H extends HTMLElement>(element: H, ...args: any[]): $HTMLElement<H>;
21
+ export function $<H extends HTMLElement>(element: H | null | undefined, ...args: any[]): $HTMLElement<H> | null | undefined;
17
22
  export function $<E extends Element>(element: E, ...args: any[]): $Element<E>;
23
+ export function $<E extends Element>(element: E | null | undefined, ...args: any[]): $Element<E> | null | undefined;
24
+ export function $<N extends Node | EventTarget>(node: N, ...args: any[]): $Node;
25
+ export function $<N extends Node | EventTarget>(node: N | null | undefined, ...args: any[]): $Node | null | undefined;
18
26
  export function $<K extends TemplateStringsArray>(string: K, ...values: any[]): $NodeContentTypes[];
19
27
  export function $<K extends keyof HTMLElementTagNameMap, N extends number>(number: N, tagname: K): Repeat<$HTMLElement<HTMLElementTagNameMap[K]>, N>;
20
28
  export function $<K extends keyof HTMLElementTagNameMap>(tagname: K): $HTMLElement<HTMLElementTagNameMap[K]>;
21
- export function $<Ev extends $Event<$Element, Event>>(event: Ev): Ev['target']['$'];
29
+ export function $<Ev extends $Event<$Element, Event>>(event: Ev): Ev['currentTarget']['$'];
22
30
  export function $<N extends number>(number: N, tagname: string): Repeat<$HTMLElement<HTMLElement>, N>;
23
31
  export function $(tagname: string): $HTMLElement<HTMLElement>
24
- export function $(resolver: string | number | Element | $Node | Function | TemplateStringsArray | Event, ...args: any[]) {
32
+ export function $(resolver: string | number | null | undefined | Element | HTMLElement | $Node | Function | TemplateStringsArray | Event | NodeListOf<Node | ChildNode>, ...args: any[]) {
33
+ if (isNull(resolver) || isUndefined(resolver)) return null;
25
34
  if (_instanceof(resolver, $Node)) return resolver;
26
35
  if (isString(resolver) && nodeNameMap[resolver]) return new nodeNameMap[resolver](...args);
27
36
  if (isFunction(resolver))
@@ -34,16 +43,19 @@ export function $(resolver: string | number | Element | $Node | Function | Templ
34
43
  if (_instanceof(resolver, Node) && _instanceof(resolver.$, $Node)) return resolver.$;
35
44
  if (_instanceof(resolver, Event)) return $(resolver.currentTarget as Element);
36
45
  if (isNumber(resolver)) return _Array_from({length: resolver}).map(_ => $(args[0], ...args.slice(1)));
37
- return new $Element(resolver);
46
+ if (_instanceof(resolver, HTMLElement)) return new $HTMLElement(resolver);
47
+ if (_instanceof(resolver, Element)) return new $Element(resolver);
48
+ if (_instanceof(resolver, NodeList)) return _Array_from(resolver).map($)
49
+ return new $HTMLElement(resolver);
38
50
  }
39
51
 
40
52
  export namespace $ {
41
- export const stylesheet = new CSSStyleSheet();
42
- _document.adoptedStyleSheets.push(stylesheet);
53
+ export const stylesheet = _stylesheet;
54
+ _document.adoptedStyleSheets.push(_stylesheet);
55
+ export const style = _bind(_stylesheet.insertRule, _stylesheet);
43
56
  type SignalProcess<T> = T extends Array<any> ? {} : T extends object ? { [key in keyof T as `${string & key}$`]: SignalFunction<T[key]> } : {};
44
57
  export type SignalFunction<T> = {signal: Signal<T>, set: (newValue: T | ((oldValue: T) => T)) => SignalFunction<T>} & (() => T) & SignalProcess<T>;
45
- export function signal<T>(value: T): SignalFunction<T>
46
- export function signal<T>(value: T) {
58
+ export const signal = <T>(value: T): SignalFunction<T> => {
47
59
  const signal = new Signal<T>(value);
48
60
  const signalFn = function () { return signal.value(); }
49
61
  _Object_assign(signalFn, {
@@ -61,14 +73,14 @@ export namespace $ {
61
73
  }
62
74
 
63
75
  export type ComputeFunction<T> = ({(): T}) & { signal: Signal<T> };
64
- export function compute<T>(process: () => T) {
76
+ export const compute = <T>(process: () => T): ComputeFunction<T> => {
65
77
  let subscribed = false;
66
- const signalFn: SignalFunction<any> = signal(null);
67
- function computeFn() {
78
+ const signalFn: SignalFunction<any> = signal(_null);
79
+ const computeFn = () => {
68
80
  if (!subscribed) return signalFn.set(subscribe())();
69
81
  else return signalFn.set(process())();
70
82
  }
71
- function subscribe () {
83
+ const subscribe = () => {
72
84
  const signalHandler = (signal: Signal<any>) => {
73
85
  signal.subscribe(() => signalFn.set(process()))
74
86
  }
@@ -82,21 +94,19 @@ export namespace $ {
82
94
  return computeFn as ComputeFunction<T>
83
95
  }
84
96
 
85
- export function assign(resolver: [nodeName: string, $node: Constructor<$Node>][]): void;
86
- export function assign(nodeName: string, $node: Constructor<$Node>): void;
87
- export function assign(resolver: string | [nodeName: string, $node: Constructor<$Node>][], $node?: Constructor<$Node>) {
97
+ type assign = {
98
+ (resolver: [nodeName: string, $node: Constructor<$Node>][]): $;
99
+ (nodeName: string, $node: Constructor<$Node>): $;
100
+ }
101
+ export const assign: assign = (resolver: string | [nodeName: string, $node: Constructor<$Node>][], $node?: Constructor<$Node>) => {
88
102
  if (isString(resolver)) $node && (nodeNameMap[resolver] = $node);
89
103
  else forEach(resolver, ([nodeName, $node]) => nodeNameMap[nodeName] = $node);
90
104
  return $;
91
105
  }
92
106
 
93
- export function toArray<T>(item: OrArray<T>): T[] {
94
- return _instanceof(item, Array) ? item : [item];
95
- }
107
+ export const toArray = <T>(item: OrArray<T>): T[] => _instanceof(item, Array) ? item : [item];
96
108
 
97
- export function span(content: string) {
98
- return $('span').content(content);
99
- }
109
+ export const span = (content: string) => $('span').content(content);
100
110
  }
101
111
  export type $ = typeof $;
102
112
  globalThis.$ = $;