amateras 0.10.1 → 0.11.0
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 +30 -25
- package/build/core.js +1 -0
- package/build/css.js +1 -0
- package/build/for.js +1 -0
- package/build/i18n.js +1 -0
- package/build/idb.js +1 -0
- package/build/if.js +1 -0
- package/build/import-map.js +1 -0
- package/build/markdown.js +1 -0
- package/build/match.js +1 -0
- package/build/meta.js +1 -0
- package/build/prefetch.js +1 -0
- package/build/router.js +1 -0
- package/build/signal.js +1 -0
- package/build/ui.js +1 -0
- package/build/utils.js +1 -0
- package/build/widget.js +1 -0
- package/package.json +9 -7
- package/packages/core/package.json +19 -0
- package/packages/core/src/env.browser.ts +21 -0
- package/packages/core/src/env.node.ts +21 -0
- package/packages/core/src/global.ts +5 -0
- package/packages/core/src/index.ts +184 -0
- package/packages/core/src/lib/hmr.ts +145 -0
- package/packages/core/src/lib/symbols.ts +2 -0
- package/packages/core/src/structure/ElementProto.ts +95 -0
- package/packages/core/src/structure/GlobalState.ts +9 -0
- package/packages/core/src/structure/NodeProto.ts +18 -0
- package/packages/core/src/structure/Proto.ts +90 -0
- package/packages/core/src/structure/ProxyProto.ts +20 -0
- package/packages/core/src/structure/TextProto.ts +22 -0
- package/packages/core/src/structure/WidgetEvent.ts +17 -0
- package/packages/css/README.md +128 -0
- package/packages/css/package.json +15 -0
- package/packages/css/src/ext/colors/amber.ts +25 -0
- package/packages/css/src/ext/colors/blackwhite.ts +13 -0
- package/packages/css/src/ext/colors/blue.ts +25 -0
- package/packages/css/src/ext/colors/cyan.ts +25 -0
- package/packages/css/src/ext/colors/emerald.ts +25 -0
- package/packages/css/src/ext/colors/fuchsia.ts +25 -0
- package/packages/css/src/ext/colors/gray.ts +25 -0
- package/packages/css/src/ext/colors/green.ts +25 -0
- package/packages/css/src/ext/colors/indigo.ts +25 -0
- package/packages/css/src/ext/colors/lime.ts +25 -0
- package/packages/css/src/ext/colors/neutral.ts +25 -0
- package/packages/css/src/ext/colors/orange.ts +25 -0
- package/packages/css/src/ext/colors/pink.ts +25 -0
- package/packages/css/src/ext/colors/purple.ts +25 -0
- package/packages/css/src/ext/colors/red.ts +25 -0
- package/packages/css/src/ext/colors/rose.ts +25 -0
- package/packages/css/src/ext/colors/sky.ts +25 -0
- package/packages/css/src/ext/colors/slate.ts +25 -0
- package/packages/css/src/ext/colors/stone.ts +25 -0
- package/packages/css/src/ext/colors/teal.ts +25 -0
- package/packages/css/src/ext/colors/violet.ts +25 -0
- package/packages/css/src/ext/colors/yellow.ts +25 -0
- package/packages/css/src/ext/colors/zinc.ts +25 -0
- package/packages/css/src/ext/colors.ts +23 -0
- package/packages/css/src/ext/keyframes.ts +37 -0
- package/packages/css/src/ext/property.ts +68 -0
- package/packages/css/src/ext/variable.ts +51 -0
- package/packages/css/src/index.ts +103 -0
- package/packages/css/src/lib/cache.ts +27 -0
- package/packages/css/src/lib/colorAssign.ts +6 -0
- package/packages/css/src/lib/createRule.ts +31 -0
- package/packages/css/src/lib/utils.ts +1 -0
- package/packages/css/src/structure/$CSS.ts +4 -0
- package/packages/css/src/structure/$CSSKeyframes.ts +13 -0
- package/packages/css/src/structure/$CSSProperty.ts +21 -0
- package/packages/css/src/structure/$CSSRule.ts +39 -0
- package/packages/css/src/structure/$CSSVariable.ts +34 -0
- package/packages/css/src/types.ts +300 -0
- package/packages/for/package.json +15 -0
- package/packages/for/src/global.ts +7 -0
- package/packages/for/src/index.ts +15 -0
- package/packages/for/src/structure/For.ts +74 -0
- package/packages/hmr/package.json +13 -0
- package/packages/hmr/src/index.ts +27 -0
- package/packages/i18n/README.md +73 -0
- package/packages/i18n/package.json +15 -0
- package/packages/i18n/src/index.ts +78 -0
- package/packages/i18n/src/structure/I18n.ts +51 -0
- package/packages/i18n/src/structure/I18nDictionary.ts +31 -0
- package/packages/i18n/src/structure/I18nTranslation.ts +51 -0
- package/packages/i18n/src/types.ts +77 -0
- package/packages/idb/README.md +127 -0
- package/packages/idb/package.json +16 -0
- package/packages/idb/src/core.ts +6 -0
- package/packages/idb/src/index.ts +17 -0
- package/packages/idb/src/lib/$IDBRequest.ts +8 -0
- package/packages/idb/src/structure/$IDB.ts +63 -0
- package/packages/idb/src/structure/$IDBCursor.ts +34 -0
- package/packages/idb/src/structure/$IDBIndex.ts +48 -0
- package/packages/idb/src/structure/$IDBStore.ts +103 -0
- package/packages/idb/src/structure/$IDBStoreBase.ts +30 -0
- package/packages/idb/src/structure/$IDBTransaction.ts +38 -0
- package/packages/idb/src/structure/builder/$IDBBuilder.ts +229 -0
- package/packages/idb/src/structure/builder/$IDBStoreBuilder.ts +100 -0
- package/packages/if/package.json +15 -0
- package/packages/if/src/global.ts +15 -0
- package/packages/if/src/index.ts +51 -0
- package/packages/if/src/structure/Condition.ts +44 -0
- package/packages/if/src/structure/ConditionStatement.ts +25 -0
- package/packages/if/src/structure/Else.ts +6 -0
- package/packages/if/src/structure/ElseIf.ts +6 -0
- package/packages/if/src/structure/If.ts +6 -0
- package/packages/markdown/README.md +53 -0
- package/packages/markdown/package.json +15 -0
- package/packages/markdown/src/index.ts +3 -0
- package/packages/markdown/src/lib/type.ts +26 -0
- package/packages/markdown/src/lib/util.ts +21 -0
- package/packages/markdown/src/structure/Markdown.ts +57 -0
- package/packages/markdown/src/structure/MarkdownLexer.ts +111 -0
- package/packages/markdown/src/structure/MarkdownParser.ts +34 -0
- package/packages/markdown/src/syntax/alert.ts +46 -0
- package/packages/markdown/src/syntax/blockquote.ts +35 -0
- package/packages/markdown/src/syntax/bold.ts +11 -0
- package/packages/markdown/src/syntax/code.ts +11 -0
- package/packages/markdown/src/syntax/codeblock.ts +44 -0
- package/packages/markdown/src/syntax/heading.ts +14 -0
- package/packages/markdown/src/syntax/horizontalRule.ts +11 -0
- package/packages/markdown/src/syntax/image.ts +23 -0
- package/packages/markdown/src/syntax/italic.ts +11 -0
- package/packages/markdown/src/syntax/link.ts +46 -0
- package/packages/markdown/src/syntax/list.ts +121 -0
- package/packages/markdown/src/syntax/table.ts +67 -0
- package/packages/markdown/src/syntax/text.ts +19 -0
- package/packages/match/package.json +15 -0
- package/packages/match/src/global.ts +14 -0
- package/packages/match/src/index.ts +33 -0
- package/packages/match/src/structure/Case.ts +15 -0
- package/packages/match/src/structure/Default.ts +12 -0
- package/packages/match/src/structure/Match.ts +78 -0
- package/packages/meta/package.json +14 -0
- package/packages/meta/src/index.ts +36 -0
- package/packages/meta/src/lib/resolveMeta.ts +27 -0
- package/packages/meta/src/types.ts +36 -0
- package/packages/prefetch/package.json +14 -0
- package/packages/prefetch/src/index.ts +70 -0
- package/packages/router/README.md +18 -0
- package/packages/router/package.json +16 -0
- package/packages/router/src/global.ts +22 -0
- package/packages/router/src/index.ts +106 -0
- package/packages/router/src/structure/Link.ts +17 -0
- package/packages/router/src/structure/NavLink.ts +19 -0
- package/packages/router/src/structure/Page.ts +30 -0
- package/packages/router/src/structure/Route.ts +123 -0
- package/packages/router/src/structure/RouteGroup.ts +24 -0
- package/packages/router/src/structure/RouteNode.ts +54 -0
- package/packages/router/src/structure/RouteSlot.ts +34 -0
- package/packages/router/src/structure/Router.ts +192 -0
- package/packages/router/src/structure/RouterConstructor.ts +18 -0
- package/packages/router/src/types.ts +41 -0
- package/packages/signal/README.md +93 -0
- package/packages/signal/package.json +15 -0
- package/packages/signal/src/index.ts +97 -0
- package/packages/signal/src/lib/track.ts +18 -0
- package/packages/signal/src/structure/Signal.ts +59 -0
- package/packages/ui/package.json +14 -0
- package/packages/ui/src/index.ts +4 -0
- package/packages/ui/src/lib/slideshowAnimations.ts +39 -0
- package/packages/ui/src/structure/Radio.ts +77 -0
- package/packages/ui/src/structure/Slide.ts +11 -0
- package/packages/ui/src/structure/Slideshow.ts +99 -0
- package/packages/utils/package.json +18 -0
- package/packages/utils/src/global.ts +39 -0
- package/packages/utils/src/index.bun.ts +3 -0
- package/packages/utils/src/index.ts +2 -0
- package/packages/utils/src/lib/debugger.ts +14 -0
- package/packages/utils/src/lib/utils.ts +119 -0
- package/packages/utils/src/structure/UID.ts +18 -0
- package/packages/widget/README.md +29 -0
- package/packages/widget/package.json +14 -0
- package/packages/widget/src/index.ts +82 -0
- package/packages/widget/src/structure/Widget.ts +42 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { onclient } from "@amateras/core";
|
|
2
|
+
import { Proto } from "@amateras/core";
|
|
3
|
+
import { _JSON_parse, _JSON_stringify, _null, _Object_entries, forEach, map, toURL } from "@amateras/utils";
|
|
4
|
+
import type { Widget } from "@amateras/widget";
|
|
5
|
+
import type { AsyncWidget, PageLayout, PathToParamsMap, RoutePath, ValidatePath } from "../types";
|
|
6
|
+
import type { Route } from "./Route";
|
|
7
|
+
import { RouteSlot } from "./RouteSlot";
|
|
8
|
+
|
|
9
|
+
type Mode = 1 | 2;
|
|
10
|
+
type RouterDicrection = 'forward' | 'back';
|
|
11
|
+
|
|
12
|
+
let index = 0;
|
|
13
|
+
const [PUSH, REPLACE] = [1, 2] as const;
|
|
14
|
+
const [FORWARD, BACK] = ['forward', 'back'] as const;
|
|
15
|
+
const SCROLL_KEY = '__scroll_history__';
|
|
16
|
+
const storage = onclient() ? sessionStorage : _null;
|
|
17
|
+
const _addEventListener = onclient() ? window.addEventListener : _null;
|
|
18
|
+
const _removeEventListener = onclient() ? window.removeEventListener : _null;
|
|
19
|
+
if (onclient()) history.scrollRestoration = 'manual';
|
|
20
|
+
|
|
21
|
+
type ScrollData = {[key: number]: { [id: string]: { x: number, y: number }}};
|
|
22
|
+
|
|
23
|
+
const scrollRecord = (e?: Event) => {
|
|
24
|
+
const data = RouterProto.scrollHistory;
|
|
25
|
+
if (e) {
|
|
26
|
+
let element = e.target as HTMLElement;
|
|
27
|
+
if (element.id === '') return;
|
|
28
|
+
data[index] = { [element.id]: { x: element.scrollLeft, y: element.scrollTop } };
|
|
29
|
+
} else {
|
|
30
|
+
forEach(_Object_entries(data), ([i]) => +i >= index && delete data[+i]);
|
|
31
|
+
}
|
|
32
|
+
storage?.setItem(SCROLL_KEY, _JSON_stringify(data));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class RouterProto extends Proto {
|
|
36
|
+
direction: RouterDicrection = FORWARD;
|
|
37
|
+
prev: URL | null = _null;
|
|
38
|
+
routes = new Map<string, Route>();
|
|
39
|
+
slot = new RouteSlot();
|
|
40
|
+
static routers = new Set<RouterProto>();
|
|
41
|
+
constructor() {
|
|
42
|
+
super(() => $(this.slot));
|
|
43
|
+
if (onclient()) RouterProto.routers.add(this);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
set href(url: URL) {
|
|
47
|
+
this.global.router.href = url;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
override build() {
|
|
51
|
+
if (onclient()) {
|
|
52
|
+
const resolve = () => {
|
|
53
|
+
const stateIndex = history.state?.index ?? 0;
|
|
54
|
+
if (index > stateIndex) this.direction = BACK;
|
|
55
|
+
if (index < stateIndex) this.direction = FORWARD;
|
|
56
|
+
index = stateIndex;
|
|
57
|
+
this.prev = this.href;
|
|
58
|
+
this.href = toURL(location.href);
|
|
59
|
+
this.resolve(location.href);
|
|
60
|
+
}
|
|
61
|
+
resolve();
|
|
62
|
+
_addEventListener?.('popstate', resolve);
|
|
63
|
+
_addEventListener?.('scroll', scrollRecord, {
|
|
64
|
+
capture: true,
|
|
65
|
+
passive: false
|
|
66
|
+
});
|
|
67
|
+
this.disposers.add(() => {
|
|
68
|
+
_removeEventListener?.('popstate', resolve);
|
|
69
|
+
_removeEventListener?.('scroll', scrollRecord, {
|
|
70
|
+
capture: true
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
return super.build();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async resolve(path: string | URL) {
|
|
78
|
+
if (!path) return;
|
|
79
|
+
let url = toURL(path);
|
|
80
|
+
for (let [,route] of this.routes) {
|
|
81
|
+
let routes = await route.resolve(url.pathname, this.slot, {});
|
|
82
|
+
// 一旦有一个 route 解析成功就跳过剩下的 routes
|
|
83
|
+
if (routes) {
|
|
84
|
+
// 实现 NavLink 自动检测 href 匹配当前 location
|
|
85
|
+
this.global.router.routes = routes;
|
|
86
|
+
let parentPaths: string[] = [''];
|
|
87
|
+
let paths: string[] = [];
|
|
88
|
+
forEach(routes, route => {
|
|
89
|
+
parentPaths = map(route.validPaths, validPath =>
|
|
90
|
+
map(parentPaths, path => path + validPath)
|
|
91
|
+
).flat()
|
|
92
|
+
paths.push(...parentPaths);
|
|
93
|
+
return parentPaths
|
|
94
|
+
})
|
|
95
|
+
this.global.router.matchPaths = paths;
|
|
96
|
+
break;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// NavLink 检测匹配
|
|
100
|
+
forEach(this.global.router.navlinks, navlink => navlink.checkActive())
|
|
101
|
+
// location 变更事件触发
|
|
102
|
+
RouterProto.dispatchEvent();
|
|
103
|
+
// restore scroll position
|
|
104
|
+
RouterProto.scrollRestoration();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
static open(path: string, target?: string) {
|
|
108
|
+
RouterProto.writeState(path, PUSH, target);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
static forward() {
|
|
112
|
+
history.forward();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
static back() {
|
|
116
|
+
history.back();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
static replace(path: string) {
|
|
120
|
+
RouterProto.writeState(path, REPLACE);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
static get scrollData(): ScrollData[number] {
|
|
124
|
+
return this.scrollHistory[index] ?? {}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
static get scrollHistory(): ScrollData {
|
|
128
|
+
return _JSON_parse(storage?.getItem(SCROLL_KEY) ?? '{}')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
static scrollRestoration() {
|
|
132
|
+
if (onclient()) {
|
|
133
|
+
let scrollData = RouterProto.scrollData ?? {x: 0, y: 0};
|
|
134
|
+
forEach(_Object_entries(scrollData), ([id, {x, y}]) => document.querySelector(`#${id}`)?.scrollTo(x, y));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private static writeState(path: string | URL | Nullish, mode: Mode, target?: string) {
|
|
139
|
+
if (!path) return;
|
|
140
|
+
let url = toURL(path);
|
|
141
|
+
if (onclient() && url.href === location.href) return;
|
|
142
|
+
if (target && target !== '_self') return open(url, target);
|
|
143
|
+
if (mode === PUSH) index++;
|
|
144
|
+
if (onclient()) scrollRecord();
|
|
145
|
+
forEach(this.routers, router => {
|
|
146
|
+
router.direction = FORWARD;
|
|
147
|
+
if (onclient()) {
|
|
148
|
+
router.prev = toURL(location.href);
|
|
149
|
+
history[mode === PUSH ? 'pushState' : 'replaceState']({index}, '', url);
|
|
150
|
+
}
|
|
151
|
+
router.href = url;
|
|
152
|
+
router.resolve(path)
|
|
153
|
+
})
|
|
154
|
+
RouterProto.dispatchEvent();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private static dispatchEvent() {
|
|
158
|
+
if (onclient()) window.dispatchEvent(new Event('pathchange'));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
declare global {
|
|
163
|
+
export interface GlobalEventHandlersEventMap {
|
|
164
|
+
pathchange: Event
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
export interface RouterProto {
|
|
170
|
+
route<_Path extends RoutePath, Props>(
|
|
171
|
+
path: ValidatePath<_Path, Props, _Path>,
|
|
172
|
+
widget: Widget<any, Props>,
|
|
173
|
+
handle?: (route: Route<'', _Path, PathToParamsMap<_Path>>) => void): void
|
|
174
|
+
|
|
175
|
+
route<_Path extends RoutePath, Props>(
|
|
176
|
+
path: ValidatePath<_Path, Props, _Path>,
|
|
177
|
+
widget: AsyncWidget<Props>,
|
|
178
|
+
handle?: (route: Route<'', _Path, PathToParamsMap<_Path>>) => void): void
|
|
179
|
+
|
|
180
|
+
route<
|
|
181
|
+
Path extends RoutePath,
|
|
182
|
+
Layout extends PageLayout<Path>
|
|
183
|
+
>(path: Path, layout: Layout, handle?: (route: Route<'', Path, PathToParamsMap<Path>>) => void): void
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
group<
|
|
187
|
+
Path extends RoutePath
|
|
188
|
+
>(path: Path, handle: (route: Route<'', Path, PathToParamsMap<Path>>) => void): void;
|
|
189
|
+
|
|
190
|
+
notFound(layout: Widget): void;
|
|
191
|
+
notFound(layout: PageLayout): void;
|
|
192
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { symbol_ProtoType } from "@amateras/core";
|
|
2
|
+
import { RouterProto } from "./Router";
|
|
3
|
+
|
|
4
|
+
export type RouterHandle = ($$: RouterProto) => void;
|
|
5
|
+
|
|
6
|
+
export interface Router {
|
|
7
|
+
new(): RouterProto;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const RouterConstructor = (handle: RouterHandle) => {
|
|
11
|
+
return class extends RouterProto {
|
|
12
|
+
static override [symbol_ProtoType] = 'Router';
|
|
13
|
+
constructor() {
|
|
14
|
+
super();
|
|
15
|
+
handle(this);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { RouteSlot } from "#structure/RouteSlot";
|
|
2
|
+
import type { Widget } from "@amateras/widget";
|
|
3
|
+
|
|
4
|
+
export type RoutePath = string;
|
|
5
|
+
|
|
6
|
+
export type RouteParams = { [key: string]: string }
|
|
7
|
+
|
|
8
|
+
export type PageLayout<Path extends RoutePath = any> = (context: { params: PathToParamsMap<Path>, slot: RouteSlot }) => void;
|
|
9
|
+
|
|
10
|
+
export type AsyncWidget<Params = any> = [() => Promise<{ default: Widget<any, Params> }>]
|
|
11
|
+
|
|
12
|
+
export type PathToParamsUnion<T extends RoutePath> =
|
|
13
|
+
T extends `${infer _Start}:${infer Param}/${infer Rest}`
|
|
14
|
+
? Param | PathToParamsUnion<Rest>
|
|
15
|
+
: T extends `${infer _Start}:${infer Param}`
|
|
16
|
+
? Param
|
|
17
|
+
: never;
|
|
18
|
+
|
|
19
|
+
export type ParamsArrayToParamsMap<ParamArray extends string[]> = ParamsUnionToMap<ParamArray[number]>
|
|
20
|
+
// type Test_ParamsArrayToParamsMap = ParamsArrayToParamsMap<['test1' | 'test2' | 'test3?' | 'test4']>
|
|
21
|
+
|
|
22
|
+
export type ParamsUnionToMap<T extends string> = Prettify<{
|
|
23
|
+
[P in T extends `${string}?` ? never : T]: string;
|
|
24
|
+
} & {
|
|
25
|
+
[P in T extends `${infer Key}?` ? Key : never]?: string;
|
|
26
|
+
}>
|
|
27
|
+
// type Test_ParamRequired = ParamsUnionToMap<'test1' | 'test2' | 'test3?' | 'test4'>
|
|
28
|
+
|
|
29
|
+
export type PathToParamsMap<Path extends RoutePath> = {
|
|
30
|
+
[P in PathToParamsUnion<Path>]: string
|
|
31
|
+
}
|
|
32
|
+
// type Test_PathToParamsMap = PathToParamsMap<'/path/:test1/path/@:test2/:test3/:test4'>
|
|
33
|
+
|
|
34
|
+
export type PathConcat<A extends string, B extends string, C extends string = ''> = `${A}${B}${C}`;
|
|
35
|
+
|
|
36
|
+
export type AliasRequired<Params, AliasParams> = Omit<Params, keyof AliasParams>;
|
|
37
|
+
|
|
38
|
+
export type ValidatePath<Path extends string, Props, FullPath extends string> =
|
|
39
|
+
RequiredKeys<Props> extends PathToParamsUnion<FullPath>
|
|
40
|
+
? Path
|
|
41
|
+
: `Error: (${FullPath}) Missing keys: ${Exclude<RequiredKeys<Props>, PathToParamsUnion<FullPath>> & string}`;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# amateras/signal
|
|
2
|
+
|
|
3
|
+
## Usage
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import 'amateras';
|
|
7
|
+
import 'amateras/signal';
|
|
8
|
+
|
|
9
|
+
// define a signal with value 0
|
|
10
|
+
const count$ = $.signal(0);
|
|
11
|
+
|
|
12
|
+
// this variable will be auto recalculate when count$ changes
|
|
13
|
+
const doubleCount$ = $.compute(() => count$() * 2);
|
|
14
|
+
|
|
15
|
+
// the console message will fired when count$ changes
|
|
16
|
+
$.effect(() => console.log( count$() ))
|
|
17
|
+
|
|
18
|
+
$(document.body).content([
|
|
19
|
+
// Display Counts
|
|
20
|
+
$('p').content( $`Counts: ${count$}` ),
|
|
21
|
+
|
|
22
|
+
// Display Double Counts
|
|
23
|
+
$('p').content( $`Double Counts: ${doubleCount$}` ),
|
|
24
|
+
|
|
25
|
+
// Create a button that make counts plus 1 on click
|
|
26
|
+
$('button').content('Add Count').on('click', () => count$.set(value => value + 1))
|
|
27
|
+
])
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Read and Write
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
const number$ = $.signal(0);
|
|
34
|
+
const string$ = $.singal('');
|
|
35
|
+
const boolean$ = $.signal(false);
|
|
36
|
+
const object$ = $.signal({ number: 1 });
|
|
37
|
+
|
|
38
|
+
// write value
|
|
39
|
+
number$.set(42);
|
|
40
|
+
string$.set('New Content');
|
|
41
|
+
boolean$.set(true);
|
|
42
|
+
object$.set({ number: 42 });
|
|
43
|
+
|
|
44
|
+
// read value
|
|
45
|
+
number$(); // 42
|
|
46
|
+
string$(); // 'New Content'
|
|
47
|
+
boolean$(); // true
|
|
48
|
+
object$(); // { number: 42 }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Use in attribute methods
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const src$ = $.signal('/image-1.png');
|
|
55
|
+
|
|
56
|
+
$(document.body).content([
|
|
57
|
+
// you can set signal variable in attribute
|
|
58
|
+
$('img').src( src$ ),
|
|
59
|
+
|
|
60
|
+
$('button').content('Change Image').on('click', () => src$.set('/image-2.png'))
|
|
61
|
+
])
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Reactive object
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
const user$ = $.signal({
|
|
68
|
+
name: 'Amateras',
|
|
69
|
+
age: 16,
|
|
70
|
+
avatar: {
|
|
71
|
+
url: '/amateras/avatar.png',
|
|
72
|
+
size: '350x350'
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
$(document.body).content([
|
|
77
|
+
// Display name and age
|
|
78
|
+
$('h1').content( $`${user$.name$} (${user$.age$})` ),
|
|
79
|
+
// Display avatar image
|
|
80
|
+
$('img').src( user$.avatar$.url$ ),
|
|
81
|
+
// Change the user$ when button is clicked
|
|
82
|
+
$('button')
|
|
83
|
+
.content('Change User')
|
|
84
|
+
.on('click', () => user$.set({
|
|
85
|
+
name: 'Tsukimi',
|
|
86
|
+
age: 10,
|
|
87
|
+
avatar: {
|
|
88
|
+
url: '/tsukimi/avatar.png',
|
|
89
|
+
size: '350x350'
|
|
90
|
+
}
|
|
91
|
+
}))
|
|
92
|
+
])
|
|
93
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@amateras/signal",
|
|
3
|
+
"peerDependencies": {
|
|
4
|
+
"@amateras/core": "workspace:*",
|
|
5
|
+
"@amateras/utils": "workspace:*"
|
|
6
|
+
},
|
|
7
|
+
"imports": {
|
|
8
|
+
"#structure/*": "./src/structure/*.ts",
|
|
9
|
+
"#lib/*": "./src/lib/*.ts",
|
|
10
|
+
"#node/*": "./src/node/*.ts"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./src/index.ts"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { track, trackSet, untrack, type UntrackFunction } from "#lib/track";
|
|
2
|
+
import { Signal } from "#structure/Signal";
|
|
3
|
+
import { Proto } from "@amateras/core";
|
|
4
|
+
import { TextProto } from "@amateras/core";
|
|
5
|
+
import { _instanceof, _Object_assign, isEqual, forEach, isBoolean } from "@amateras/utils";
|
|
6
|
+
|
|
7
|
+
declare global {
|
|
8
|
+
export function $<T>(signal: Signal<T>): Signal<T>;
|
|
9
|
+
|
|
10
|
+
export namespace $ {
|
|
11
|
+
export function signal<T>(value: T): Signal<T>;
|
|
12
|
+
export function effect(callback: (untrack: UntrackFunction) => void): void;
|
|
13
|
+
export function compute<T>(callback: (untrack: UntrackFunction) => T): Signal<T>;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_Object_assign($, {
|
|
18
|
+
signal(value: any) {
|
|
19
|
+
return new Signal(value);
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
effect(
|
|
23
|
+
callback: (
|
|
24
|
+
untrack: UntrackFunction
|
|
25
|
+
) => void
|
|
26
|
+
) {
|
|
27
|
+
track(callback);
|
|
28
|
+
forEach(trackSet, signal => signal.subscribe(_ => callback(untrack)));
|
|
29
|
+
trackSet.clear();
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
compute<T>(
|
|
33
|
+
callback: (
|
|
34
|
+
untrack: UntrackFunction
|
|
35
|
+
) => T
|
|
36
|
+
) {
|
|
37
|
+
let result = track(callback);
|
|
38
|
+
let compute = new Signal(result);
|
|
39
|
+
forEach(trackSet, signal => signal.subscribe(_ => compute.set(callback(untrack))));
|
|
40
|
+
trackSet.clear();
|
|
41
|
+
return compute
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
let toTextProto = (signal: Signal) => {
|
|
46
|
+
if (_instanceof(signal, Signal)) {
|
|
47
|
+
let proto = new TextProto(`${signal}`);
|
|
48
|
+
proto.ondom(node => {
|
|
49
|
+
let fn = (value: any) => node.textContent = `${value}`;
|
|
50
|
+
signal.subscribe(fn);
|
|
51
|
+
proto.disposers.add(() => signal.unsubscribe(fn));
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
let fn = (value: any) => proto.content = `${value}`;
|
|
55
|
+
signal.subscribe(fn);
|
|
56
|
+
fn(signal.value);
|
|
57
|
+
|
|
58
|
+
proto.parent = Proto.proto;
|
|
59
|
+
return proto;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let setAttr = (name: string, node: HTMLElement, signal: Signal) => {
|
|
64
|
+
//@ts-ignore
|
|
65
|
+
if (name in node) node[name] = signal.value;
|
|
66
|
+
else node.setAttribute(name, `${signal}`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
$.process.text.add(toTextProto)
|
|
70
|
+
$.process.craft.add(toTextProto)
|
|
71
|
+
$.process.attr.add((name, signal, proto) => {
|
|
72
|
+
if (_instanceof(signal, Signal)) {
|
|
73
|
+
if (proto.tagname === 'input') {
|
|
74
|
+
if (isEqual(name, ['value', 'checked'] as const)) {
|
|
75
|
+
proto.on('input', e => signal.set((e.currentTarget as HTMLInputElement)[name]));
|
|
76
|
+
let value = signal.value;
|
|
77
|
+
if (isBoolean(value)) value && proto.attr(name, '');
|
|
78
|
+
else proto.attr(name, `${value}`)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else
|
|
82
|
+
proto.ondom(node => {
|
|
83
|
+
let setNodeAttr = () => setAttr(name, node, signal);
|
|
84
|
+
signal.subscribe(setNodeAttr);
|
|
85
|
+
setNodeAttr();
|
|
86
|
+
proto.disposers.add(() => signal.unsubscribe(setNodeAttr))
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
let setProtoAttr = () => {
|
|
90
|
+
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
export * from "#structure/Signal";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Signal } from "#structure/Signal";
|
|
2
|
+
|
|
3
|
+
export type UntrackFunction = (fn: () => unknown) => ReturnType<typeof fn>
|
|
4
|
+
|
|
5
|
+
export let ontrack = false;
|
|
6
|
+
export let trackSet = new Set<Signal>();
|
|
7
|
+
export let untrack: UntrackFunction = fn => {
|
|
8
|
+
ontrack = false;
|
|
9
|
+
let result = fn();
|
|
10
|
+
ontrack = true
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
export let track = (callback: (untrack: UntrackFunction) => any) => {
|
|
14
|
+
ontrack = true;
|
|
15
|
+
let result = callback(untrack);
|
|
16
|
+
ontrack = false;
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { forEach, isFunction, isUndefined } from "@amateras/utils";
|
|
2
|
+
import { ontrack, trackSet } from "#lib/track";
|
|
3
|
+
|
|
4
|
+
let signalValueMap = new WeakMap<Signal, {value: any, subs: Set<(value: any) => void>}>();
|
|
5
|
+
let get = (signal: Signal) => signalValueMap.get(signal)!;
|
|
6
|
+
|
|
7
|
+
export interface Signal<T> {
|
|
8
|
+
(): T;
|
|
9
|
+
}
|
|
10
|
+
export class Signal<T = any> {
|
|
11
|
+
key: this;
|
|
12
|
+
constructor(value: T) {
|
|
13
|
+
const $state = () => {
|
|
14
|
+
if (ontrack) trackSet.add(this);
|
|
15
|
+
return get($state as this).value;
|
|
16
|
+
}
|
|
17
|
+
Object.setPrototypeOf($state, this);
|
|
18
|
+
signalValueMap.set($state as this, {value, subs: new Set()})
|
|
19
|
+
this.key = $state as this;
|
|
20
|
+
return $state as this
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get value(): T {
|
|
24
|
+
return get(this.key).value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get subs(): Set<(value: T) => void> {
|
|
28
|
+
return get(this.key).subs;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
set(resolver: T | ((oldValue: T) => T)) {
|
|
32
|
+
if (isFunction(resolver)) this.set(resolver(this.value));
|
|
33
|
+
else if (this.value !== resolver) {
|
|
34
|
+
get(this).value = resolver;
|
|
35
|
+
this.emit();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
modify(callback: (value: T) => void) {
|
|
40
|
+
callback(this.value);
|
|
41
|
+
this.emit();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
emit() {
|
|
45
|
+
forEach(get(this).subs, subs => subs(this.value));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
subscribe(callback: (value: T) => void) {
|
|
49
|
+
this.subs.add(callback);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
unsubscribe(callback: (value: T) => void) {
|
|
53
|
+
this.subs.delete(callback);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
toString(): string {
|
|
57
|
+
return `${this.value}`
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@amateras/ui",
|
|
3
|
+
"peerDependencies": {
|
|
4
|
+
"@amateras/core": "workspace:*",
|
|
5
|
+
"@amateras/utils": "workspace:*"
|
|
6
|
+
},
|
|
7
|
+
"imports": {
|
|
8
|
+
"#structure/*": "./src/structure/*.ts",
|
|
9
|
+
"#lib/*": "./src/lib/*.ts"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./src/index.ts"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { SlideshowAnimationHandle } from "#structure/Slideshow";
|
|
2
|
+
import { onclient } from "@amateras/core";
|
|
3
|
+
|
|
4
|
+
export const slideInOut = (
|
|
5
|
+
options?: {
|
|
6
|
+
duration?: number,
|
|
7
|
+
easing?: string,
|
|
8
|
+
direction?: 'left' | 'right' | 'up' | 'down'
|
|
9
|
+
}
|
|
10
|
+
): SlideshowAnimationHandle =>
|
|
11
|
+
(slideshow, newSlide, oldSlide) => {
|
|
12
|
+
slideshow.slide = newSlide;
|
|
13
|
+
if (!onclient()) return;
|
|
14
|
+
let newNodes = newSlide.toDOM();
|
|
15
|
+
slideshow.node?.append(...newNodes);
|
|
16
|
+
|
|
17
|
+
let animationOptions = {
|
|
18
|
+
duration: options?.duration ?? 500,
|
|
19
|
+
easing: options?.easing ?? 'ease'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let translate = $.match(options?.direction, $$ => $$
|
|
23
|
+
.case('up', () => [['0 100%', '0 0'], ['0 0', '0 -100%']])
|
|
24
|
+
.case('down', () => [['0 -100%', '0 0'], ['0 0', '0 100%']])
|
|
25
|
+
.case('left', () => [['100% 0', '0 0'], ['0 0', '-100% 0']])
|
|
26
|
+
.case('right', () => [['-100% 0', '0 0'], ['0 0', '100% 0']])
|
|
27
|
+
.default(() => [['100% 0', '0 0'], ['0 0', '-100% 0']])
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
newSlide.node?.animate({
|
|
31
|
+
translate: translate[0]
|
|
32
|
+
}, animationOptions)
|
|
33
|
+
|
|
34
|
+
const animation = oldSlide?.node?.animate({
|
|
35
|
+
translate: translate[1]
|
|
36
|
+
}, animationOptions)
|
|
37
|
+
|
|
38
|
+
if (animation) animation.onfinish = () => oldSlide?.node?.remove();
|
|
39
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ElementProto } from "@amateras/core";
|
|
2
|
+
import { _null, is } from "@amateras/utils";
|
|
3
|
+
import { UID } from "@amateras/utils";
|
|
4
|
+
|
|
5
|
+
export interface RadioGroupProps {
|
|
6
|
+
value?: any;
|
|
7
|
+
name?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class RadioGroup extends ElementProto {
|
|
11
|
+
value: any;
|
|
12
|
+
constructor({value, ...props}: $.Props<RadioGroupProps>, layout?: $.Layout<RadioGroup>) {
|
|
13
|
+
super('radio-group', props, layout);
|
|
14
|
+
this.value = value;
|
|
15
|
+
this.on('input', e => {
|
|
16
|
+
this.value = is(e.target, HTMLInputElement)?.value
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static {
|
|
21
|
+
$.style(RadioGroup, 'radio-group{display:block}')
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface RadioItemProps {
|
|
26
|
+
inputId?: string;
|
|
27
|
+
name?: string;
|
|
28
|
+
value: any;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class RadioItem<T> extends ElementProto {
|
|
32
|
+
inputId: string;
|
|
33
|
+
name: string | null;
|
|
34
|
+
value: T;
|
|
35
|
+
constructor({inputId, name, value, ...props}: $.Props<RadioItemProps>, layout?: $.Layout<RadioItem<T>>) {
|
|
36
|
+
super('radio-item', props, layout);
|
|
37
|
+
this.inputId = inputId ?? `input-${UID.persistInProto(this, 'radio-item')}`;
|
|
38
|
+
this.name = name ?? _null;
|
|
39
|
+
this.value = value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static {
|
|
43
|
+
$.style(RadioItem, 'radio-item{display:block}')
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class Radio extends ElementProto {
|
|
48
|
+
constructor(props: $.Props, layout?: $.Layout<Radio>) {
|
|
49
|
+
super('input', {type: 'radio', ...props}, layout);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override build(children?: boolean): this {
|
|
53
|
+
let selector = this.findAbove(proto => is(proto, RadioItem));
|
|
54
|
+
if (selector) {
|
|
55
|
+
this.attr('id', selector.inputId);
|
|
56
|
+
this.attr('name', selector.name);
|
|
57
|
+
}
|
|
58
|
+
return super.build(children);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export class Label extends ElementProto {
|
|
63
|
+
constructor(props: $.Props, layout?: $.Layout<Label>) {
|
|
64
|
+
super('label', props, layout);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
override build(children?: boolean): this {
|
|
68
|
+
let selector = this.findAbove(proto => is(proto, RadioItem));
|
|
69
|
+
if (selector) this.attr('for', selector.inputId)
|
|
70
|
+
return super.build(children);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const randomIdMap = new Map<string, Set<string>>();
|
|
75
|
+
function randomIdUnique() {
|
|
76
|
+
|
|
77
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ElementProto } from "@amateras/core";
|
|
2
|
+
|
|
3
|
+
export class Slide extends ElementProto {
|
|
4
|
+
constructor(props: $.Props, layout?: $.Layout<Slide>) {
|
|
5
|
+
super('slide', props, layout);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
static {
|
|
9
|
+
$.style(Slide, 'slide{display:block;height:100%;width:100%;position:absolute}')
|
|
10
|
+
}
|
|
11
|
+
}
|