amateras 0.13.2 → 0.14.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.
Files changed (89) hide show
  1. package/README.md +39 -38
  2. package/build/core.js +1 -1
  3. package/build/css-keyframes.js +1 -0
  4. package/build/css-property.js +1 -0
  5. package/build/css-variable.js +1 -0
  6. package/build/for.js +1 -1
  7. package/build/i18n.js +1 -1
  8. package/build/if.js +1 -1
  9. package/build/import-map.js +1 -1
  10. package/build/match.js +1 -1
  11. package/build/meta.js +1 -1
  12. package/build/prefetch.js +1 -1
  13. package/build/router.js +1 -1
  14. package/build/signal.js +1 -1
  15. package/build/store.js +1 -1
  16. package/build/ui.js +1 -1
  17. package/build/utils.js +1 -1
  18. package/build/widget.js +1 -1
  19. package/package.json +3 -2
  20. package/packages/core/src/index.ts +86 -31
  21. package/packages/core/src/lib/hmr.ts +4 -4
  22. package/packages/core/src/structure/ElementProto.ts +33 -11
  23. package/packages/core/src/structure/GlobalState.ts +13 -4
  24. package/packages/core/src/structure/NodeProto.ts +2 -4
  25. package/packages/core/src/structure/Proto.ts +37 -23
  26. package/packages/core/src/structure/TextProto.ts +1 -2
  27. package/packages/css/README.md +18 -15
  28. package/packages/css/src/ext/property.ts +2 -3
  29. package/packages/css/src/index.ts +1 -1
  30. package/packages/css/src/structure/$CSSProperty.ts +4 -0
  31. package/packages/css/src/structure/$CSSVariable.ts +1 -1
  32. package/packages/css/src/types.ts +5 -0
  33. package/packages/for/src/global.ts +12 -3
  34. package/packages/for/src/structure/For.ts +5 -3
  35. package/packages/i18n/README.md +16 -24
  36. package/packages/i18n/src/index.ts +26 -5
  37. package/packages/i18n/src/structure/I18n.ts +2 -4
  38. package/packages/i18n/src/structure/I18nSession.ts +4 -2
  39. package/packages/i18n/src/structure/I18nTranslation.ts +15 -26
  40. package/packages/idb/src/structure/$IDBStore.ts +2 -2
  41. package/packages/if/src/global.ts +15 -4
  42. package/packages/if/src/index.ts +18 -8
  43. package/packages/if/src/structure/Condition.ts +16 -13
  44. package/packages/if/src/structure/ConditionStatement.ts +9 -9
  45. package/packages/match/src/global.ts +9 -3
  46. package/packages/match/src/structure/Match.ts +1 -1
  47. package/packages/meta/src/index.ts +4 -5
  48. package/packages/prefetch/src/index.ts +30 -9
  49. package/packages/router/src/global.ts +17 -4
  50. package/packages/router/src/index.ts +25 -18
  51. package/packages/router/src/structure/Route.ts +2 -1
  52. package/packages/router/src/structure/RouteNode.ts +8 -6
  53. package/packages/router/src/structure/RouteSlot.ts +15 -2
  54. package/packages/router/src/structure/Router.ts +28 -19
  55. package/packages/router/src/structure/RouterConstructor.ts +5 -5
  56. package/packages/router/src/types.ts +2 -2
  57. package/packages/signal/README.md +28 -48
  58. package/packages/signal/src/index.ts +61 -38
  59. package/packages/signal/src/structure/Signal.ts +40 -7
  60. package/packages/store/src/structure/Store.ts +1 -1
  61. package/packages/ui/package.json +2 -1
  62. package/packages/ui/src/icon/check.svg.ts +1 -0
  63. package/packages/ui/src/icon/x.svg.ts +1 -0
  64. package/packages/ui/src/index.ts +9 -2
  65. package/packages/ui/src/lib/combobox_style.ts +20 -0
  66. package/packages/ui/src/lib/hover.ts +2 -0
  67. package/packages/ui/src/structure/Badge.ts +10 -1
  68. package/packages/ui/src/structure/Button.ts +54 -27
  69. package/packages/ui/src/structure/Card.ts +3 -4
  70. package/packages/ui/src/structure/Combobox/Combobox.ts +312 -0
  71. package/packages/ui/src/structure/Combobox/ComboboxChips.ts +178 -0
  72. package/packages/ui/src/structure/Combobox/ComboboxList.ts +209 -0
  73. package/packages/ui/src/structure/ContextMenu.ts +89 -0
  74. package/packages/ui/src/structure/Field.ts +109 -0
  75. package/packages/ui/src/structure/Input.ts +29 -0
  76. package/packages/ui/src/structure/Select/Select.ts +18 -8
  77. package/packages/ui/src/structure/Select/SelectContent.ts +6 -1
  78. package/packages/ui/src/structure/Select/SelectItem.ts +2 -1
  79. package/packages/ui/src/structure/Slideshow.ts +2 -2
  80. package/packages/ui/src/structure/Switch.ts +45 -0
  81. package/packages/ui/src/structure/Tabs.ts +3 -3
  82. package/packages/ui/src/structure/Toggle.ts +155 -0
  83. package/packages/ui/src/structure/Waterfall.ts +1 -1
  84. package/packages/ui/src/structure/WaterfallItem.ts +1 -1
  85. package/packages/utils/src/lib/utils.ts +30 -8
  86. package/packages/utils/src/structure/UID.ts +1 -1
  87. package/packages/widget/src/index.ts +29 -9
  88. package/packages/widget/src/structure/Widget.ts +7 -3
  89. package/packages/ui/src/structure/TextBlock.ts +0 -11
@@ -1,18 +1,19 @@
1
1
  import * as _Link from "#structure/Link";
2
2
  import * as _NavLink from "#structure/NavLink";
3
3
  import * as _Router from "#structure/Router";
4
- import type { Router, RouterHandle } from "#structure/RouterConstructor";
4
+ import type { RouterConstructor, RouterHandle } from "#structure/RouterConstructor";
5
+ import type { RouteSlot } from "#structure/RouteSlot";
6
+ import type { Proto } from "@amateras/core";
5
7
 
6
8
  declare global {
7
9
  export var Link: typeof _Link.Link;
8
10
  export var NavLink: typeof _NavLink.NavLink;
9
- export function $(Router: Router): _Router.RouterProto;
10
11
 
11
12
  export type Link = _Link.Link;
12
13
  export type NavLink = _NavLink.NavLink;
13
14
 
14
15
  export namespace $ {
15
- export function router(handle: RouterHandle): Router;
16
+ export function router(handle: RouterHandle): RouterConstructor;
16
17
 
17
18
  export function open(path: string, target?: string): void;
18
19
  export function forward(): void;
@@ -20,6 +21,18 @@ declare global {
20
21
  export function replace(path: string): void;
21
22
  export function scrollRestoration(): void;
22
23
 
23
- export function title(title: OrPromise<string>): void;
24
+ export function title(title: OrPromise<string>, parent?: Proto | null): void;
25
+
26
+ interface ProtoEventMap<P extends Proto> {
27
+ pageswitch: [slot: RouteSlot, direction: _Router.RouterDicrection]
28
+ }
29
+
30
+ interface Overload<I> {
31
+ router: [
32
+ input: [RouterConstructor],
33
+ output: [_Router.Router],
34
+ args: []
35
+ ]
36
+ }
24
37
  }
25
38
  }
@@ -4,7 +4,7 @@ import { Page } from '#structure/Page';
4
4
  import { Route } from '#structure/Route';
5
5
  import { RouteGroup } from '#structure/RouteGroup';
6
6
  import { RouteNode } from '#structure/RouteNode';
7
- import { RouterProto } from '#structure/Router';
7
+ import { Router } from '#structure/Router';
8
8
  import { RouterConstructor } from '#structure/RouterConstructor';
9
9
  import { symbol_ProtoType } from '@amateras/core';
10
10
  import { GlobalState } from '@amateras/core';
@@ -17,25 +17,27 @@ declare module "@amateras/core" {
17
17
  export interface GlobalState {
18
18
  title: string | null
19
19
  router: {
20
- routers: Set<RouterProto>;
20
+ routers: Set<Router>;
21
21
  resolve: (path: string) => Promise<void>[];
22
22
  href: URL;
23
23
  routes: Route[];
24
24
  matchPaths: string[];
25
25
  navlinks: Set<NavLink>;
26
+ scrollQueue: Set<Promise<any>>;
27
+ postScrollRestoration(promise: Promise<any>): void;
26
28
  }
27
29
  }
28
30
  }
29
31
 
30
32
  let routePlannerPrototype = {
31
- route(this: Route | RouterProto, path: string, layout: PageLayout<string>, handle?: (route: Route) => void) {
33
+ route(this: Route | Router, path: string, layout: PageLayout<string>, handle?: (route: Route) => void) {
32
34
  let route = new RouteNode(path, layout);
33
35
  this.routes.set(path, route);
34
36
  handle?.(route);
35
37
  return this;
36
38
  },
37
39
 
38
- group(this: Route | RouterProto, path: string, handle?: (route: Route) => void) {
40
+ group(this: Route | Router, path: string, handle?: (route: Route) => void) {
39
41
  let group = new RouteGroup(path);
40
42
  this.routes.set(path, group);
41
43
  handle?.(group);
@@ -48,19 +50,24 @@ let routePlannerPrototype = {
48
50
  }
49
51
 
50
52
  _Object_assign(Route.prototype, routePlannerPrototype);
51
- _Object_assign(RouterProto.prototype, routePlannerPrototype);
52
- _Object_assign(GlobalState.prototype, {
53
+ _Object_assign(Router.prototype, routePlannerPrototype);
54
+ GlobalState.assign(() => ({
53
55
  router: {
54
- routers: new Set<RouterProto>(),
56
+ routers: new Set<Router>(),
55
57
  resolve(this, path: string) {
56
58
  return map(this.routers, router => router.resolve(path));
57
59
  },
58
60
  href: new URL('http://localhost'),
59
61
  routes: [],
60
62
  matchPaths: [],
61
- navlinks: new Set()
63
+ navlinks: new Set(),
64
+ scrollQueue: new Set<Promise<any>>(),
65
+ postScrollRestoration(promise: Promise<any>) {
66
+ promise.finally(() => this.scrollQueue.delete(promise));
67
+ this.scrollQueue.add(promise);
68
+ }
62
69
  }
63
- })
70
+ }))
64
71
 
65
72
  GlobalState.disposers.add(({router}) => {
66
73
  router.routers.clear();
@@ -70,15 +77,15 @@ GlobalState.disposers.add(({router}) => {
70
77
  })
71
78
 
72
79
  _Object_assign($, {
73
- router: (handle: ($$: RouterProto) => void) => RouterConstructor(handle),
74
- open: RouterProto.open,
75
- replace: RouterProto.replace,
76
- back: RouterProto.back,
77
- forward: RouterProto.forward,
78
- scrollRestoration: RouterProto.scrollRestoration,
80
+ router: (handle: ($$: Router) => void) => RouterConstructor(handle),
81
+ open: Router.open,
82
+ replace: Router.replace,
83
+ back: Router.back,
84
+ forward: Router.forward,
85
+ scrollRestoration: Router.scrollRestoration,
79
86
 
80
- title(title: OrPromise<string>) {
81
- let page = Proto.proto?.findAbove<Page>(proto => is(proto, Page));
87
+ title(title: OrPromise<string>, parent: Proto | null = Proto.proto) {
88
+ let page = parent?.findAbove<Page>(proto => is(proto, Page));
82
89
  if (page) {
83
90
  page.title = title;
84
91
  page.updateTitle();
@@ -92,7 +99,7 @@ globalThis.NavLink = NavLink;
92
99
  $.process.craft.add((value) => {
93
100
  if (isFunction(value) && value[symbol_ProtoType] === 'Router') {
94
101
  let proto = Proto.proto;
95
- let router = new value() as RouterProto;
102
+ let router = new value() as Router;
96
103
  proto?.global.router.routers.add(router);
97
104
  return router;
98
105
  }
@@ -87,9 +87,10 @@ export abstract class Route<ParentPath extends RoutePath = any, Path extends Rou
87
87
  _Path extends RoutePath,
88
88
  _Params extends AliasRequired<Params, PathToParamsMap<_Path>>,
89
89
  Required extends keyof _Params extends [never] ? [] : [_Params | (() => _Params)]
90
- >(path: _Path, ...required: Required): void;
90
+ >(path: _Path, ...required: Required): this;
91
91
  alias(path: string, required?: RouteParams | (() => RouteParams)) {
92
92
  this.paths.set(path, required)
93
+ return this;
93
94
  }
94
95
  }
95
96
 
@@ -1,6 +1,6 @@
1
- import { Proto, symbol_ProtoType } from "@amateras/core";
1
+ import { onclient, Proto, symbol_ProtoType } from "@amateras/core";
2
2
  import { _null, isArray } from "@amateras/utils";
3
- import type { Widget } from "@amateras/widget";
3
+ import type { WidgetConstructor } from "@amateras/widget";
4
4
  import type { AsyncWidget, PageLayout } from "../types";
5
5
  import { Page } from "./Page";
6
6
  import { Route } from "./Route";
@@ -9,8 +9,8 @@ import type { RouteSlot } from "./RouteSlot";
9
9
  export class RouteNode extends Route {
10
10
  pages = new Map<string, Page>();
11
11
  page: Page | null = _null;
12
- #layout: Widget | PageLayout | AsyncWidget;
13
- constructor(path: string, layout: Widget | PageLayout | AsyncWidget) {
12
+ #layout: WidgetConstructor | PageLayout | AsyncWidget;
13
+ constructor(path: string, layout: WidgetConstructor | PageLayout | AsyncWidget) {
14
14
  super(path);
15
15
  this.#layout = layout;
16
16
  }
@@ -36,12 +36,14 @@ export class RouteNode extends Route {
36
36
  let layout = this.#layout;
37
37
  let _layout;
38
38
  if (isArray(layout)) {
39
- let widget = await layout[0]().then(mod => mod.default);
39
+ let promise = layout[0]()
40
+ if (onclient()) promise.catch(() => location.reload());
41
+ let widget = await promise.then(mod => mod.default);
40
42
  _layout = () => $(widget, params, () => $(page!.slot));
41
43
  } else {
42
44
  //@ts-ignore
43
45
  _layout = this.#layout[symbol_ProtoType] === 'Widget' // is widget constructor
44
- ? () => $(this.#layout as Widget, params, () => $(page!.slot))
46
+ ? () => $(this.#layout as WidgetConstructor, params, () => $(page!.slot))
45
47
  : this.#layout as PageLayout;
46
48
  }
47
49
  $.context(Proto, slot, () => {
@@ -2,26 +2,39 @@ import { onclient } from "@amateras/core";
2
2
  import { ProxyProto } from "@amateras/core";
3
3
  import { _null } from "@amateras/utils";
4
4
  import { Page } from "./Page";
5
+ import { Router } from "./Router";
5
6
 
6
7
  export class RouteSlot extends ProxyProto {
7
8
  page: Page | null = _null;
9
+ prevPage: Page | null = _null;
8
10
  constructor() {
9
11
  super();
10
12
  }
11
13
 
12
14
  switch(page: Page) {
13
15
  if (this.page === page) return;
16
+ this.prevPage = this.page;
14
17
  this.clear();
15
18
  this.layout = () => $(page);
16
19
  this.append(page);
17
- if (this.page !== page) this.page?.removeNode();
18
20
  this.page = page;
19
21
  if (!page.builded) page.build();
20
22
  page.updateTitle();
21
-
23
+
24
+ if (!this.dispatch('pageswitch', [this, Router.direction], {bubbles: true})) this.render();
25
+ }
26
+
27
+ render() {
22
28
  if (onclient()) {
29
+ this.prevPage?.removeNode();
23
30
  let nodes = this.toDOM();
24
31
  this.node?.replaceWith(...nodes);
25
32
  }
26
33
  }
34
+
35
+ override dispose(): void {
36
+ super.dispose();
37
+ this.page = _null;
38
+ this.prevPage = _null;
39
+ }
27
40
  }
@@ -7,7 +7,7 @@ import type { Route } from "./Route";
7
7
  import { RouteSlot } from "./RouteSlot";
8
8
 
9
9
  type Mode = 1 | 2;
10
- type RouterDicrection = 'forward' | 'back';
10
+ export type RouterDicrection = 'forward' | 'back';
11
11
 
12
12
  let index = 0;
13
13
  const [PUSH, REPLACE] = [1, 2] as const;
@@ -21,7 +21,7 @@ if (onclient()) history.scrollRestoration = 'manual';
21
21
  type ScrollData = {[key: number]: { [id: string]: { x: number, y: number }}};
22
22
 
23
23
  const scrollRecord = (e?: Event) => {
24
- const data = RouterProto.scrollHistory;
24
+ const data = Router.scrollHistory;
25
25
  if (e) {
26
26
  let element = e.target as HTMLElement;
27
27
  if (element.nodeName === '#document') {
@@ -34,15 +34,16 @@ const scrollRecord = (e?: Event) => {
34
34
  storage?.setItem(SCROLL_KEY, _JSON_stringify(data));
35
35
  }
36
36
 
37
- export class RouterProto extends Proto {
38
- direction: RouterDicrection = FORWARD;
37
+ export class Router extends Proto {
38
+ static direction: RouterDicrection = FORWARD;
39
39
  prev: URL | null = _null;
40
+ url: URL | null = _null;
40
41
  routes = new Map<string, Route>();
41
42
  slot = new RouteSlot();
42
- static routers = new Set<RouterProto>();
43
+ static routers = new Set<Router>();
43
44
  constructor() {
44
45
  super(() => $(this.slot));
45
- if (onclient()) RouterProto.routers.add(this);
46
+ if (onclient()) Router.routers.add(this);
46
47
  }
47
48
 
48
49
  set href(url: URL) {
@@ -53,8 +54,8 @@ export class RouterProto extends Proto {
53
54
  if (onclient()) {
54
55
  const resolve = () => {
55
56
  const stateIndex = history.state?.index ?? 0;
56
- if (index > stateIndex) this.direction = BACK;
57
- if (index < stateIndex) this.direction = FORWARD;
57
+ if (index > stateIndex) Router.direction = BACK;
58
+ if (index < stateIndex) Router.direction = FORWARD;
58
59
  index = stateIndex;
59
60
  this.prev = this.href;
60
61
  this.href = toURL(location.href);
@@ -66,7 +67,7 @@ export class RouterProto extends Proto {
66
67
  capture: true,
67
68
  passive: false
68
69
  });
69
- this.ondispose(() => {
70
+ this.listen('dispose', () => {
70
71
  _removeEventListener?.('popstate', resolve);
71
72
  _removeEventListener?.('scroll', scrollRecord, {
72
73
  capture: true
@@ -79,6 +80,7 @@ export class RouterProto extends Proto {
79
80
  async resolve(path: string | URL) {
80
81
  if (!path) return;
81
82
  let url = toURL(path);
83
+ this.global.router.scrollQueue.clear();
82
84
  for (let [,route] of this.routes) {
83
85
  let routes = await route.resolve(url.pathname, this.slot, {});
84
86
  // 一旦有一个 route 解析成功就跳过剩下的 routes
@@ -98,17 +100,21 @@ export class RouterProto extends Proto {
98
100
  break;
99
101
  };
100
102
  }
103
+ this.url = url;
101
104
  // NavLink 检测匹配
102
105
  forEach(this.global.router.navlinks, navlink => navlink.checkActive())
103
106
  // location 变更事件触发
104
- RouterProto.dispatchEvent();
107
+ Router.dispatchEvent();
105
108
  // restore scroll position
106
- RouterProto.scrollRestoration();
109
+ Promise.all(this.global.router.scrollQueue).then(() => {
110
+ // make sure after scroll queue promises resolved is still the same page
111
+ if (url === this.url) Router.scrollRestoration()
112
+ });
107
113
  }
108
114
 
109
115
  static open(path: string, target: string = '_self') {
110
116
  if (toURL(path).origin !== origin) open(path, target);
111
- else RouterProto.writeState(path, PUSH, target);
117
+ else Router.writeState(path, PUSH, target);
112
118
  }
113
119
 
114
120
  static forward() {
@@ -120,7 +126,7 @@ export class RouterProto extends Proto {
120
126
  }
121
127
 
122
128
  static replace(path: string) {
123
- RouterProto.writeState(path, REPLACE);
129
+ Router.writeState(path, REPLACE);
124
130
  }
125
131
 
126
132
  static get scrollData(): ScrollData[number] {
@@ -133,14 +139,17 @@ export class RouterProto extends Proto {
133
139
 
134
140
  static scrollRestoration() {
135
141
  if (onclient()) {
136
- let scrollData = RouterProto.scrollData ?? {x: 0, y: 0};
142
+ let scrollData = Router.scrollData ?? {x: 0, y: 0};
137
143
  let scrollDataElements = _Object_entries(scrollData);
138
144
  if (scrollDataElements.length)
139
145
  forEach(scrollDataElements, ([id, {x, y}]) => {
140
146
  if (id === '#document') window.scrollTo(x, y);
141
- else document.querySelector(`#${id}`)?.scrollTo(x, y)
147
+ else document.getElementById(id)?.scrollTo(x, y)
142
148
  });
143
- else window.scrollTo(0, 0)
149
+ else {
150
+ document.querySelectorAll('*').forEach(el => el.scrollTo(0, 0))
151
+ window.scrollTo(0, 0)
152
+ }
144
153
  }
145
154
  }
146
155
 
@@ -152,7 +161,7 @@ export class RouterProto extends Proto {
152
161
  if (mode === PUSH) index++;
153
162
  if (onclient()) scrollRecord();
154
163
  forEach(this.routers, router => {
155
- router.direction = FORWARD;
164
+ Router.direction = FORWARD;
156
165
  if (onclient()) {
157
166
  router.prev = toURL(location.href);
158
167
  history[mode === PUSH ? 'pushState' : 'replaceState']({index}, '', url);
@@ -160,7 +169,7 @@ export class RouterProto extends Proto {
160
169
  router.href = url;
161
170
  router.resolve(path)
162
171
  })
163
- RouterProto.dispatchEvent();
172
+ Router.dispatchEvent();
164
173
  }
165
174
 
166
175
  private static dispatchEvent() {
@@ -175,7 +184,7 @@ declare global {
175
184
  }
176
185
 
177
186
 
178
- export interface RouterProto {
187
+ export interface Router {
179
188
  route<_Path extends RoutePath, Props>(
180
189
  path: ValidatePath<_Path, Props, _Path>,
181
190
  widget: Widget<Props>,
@@ -1,14 +1,14 @@
1
1
  import { symbol_ProtoType } from "@amateras/core";
2
- import { RouterProto } from "./Router";
2
+ import { Router } from "./Router";
3
3
 
4
- export type RouterHandle = ($$: RouterProto) => void;
4
+ export type RouterHandle = ($$: Router) => void;
5
5
 
6
- export interface Router {
7
- new(): RouterProto;
6
+ export interface RouterConstructor {
7
+ new(): Router;
8
8
  }
9
9
 
10
10
  export const RouterConstructor = (handle: RouterHandle) => {
11
- return class extends RouterProto {
11
+ return class extends Router {
12
12
  static override [symbol_ProtoType] = 'Router';
13
13
  constructor() {
14
14
  super();
@@ -1,5 +1,5 @@
1
1
  import type { RouteSlot } from "#structure/RouteSlot";
2
- import type { Widget } from "../../widget/src";
2
+ import type { WidgetConstructor } from "@amateras/widget";
3
3
 
4
4
  export type RoutePath = string;
5
5
 
@@ -7,7 +7,7 @@ export type RouteParams = { [key: string]: string }
7
7
 
8
8
  export type PageLayout<Path extends RoutePath = any> = (context: { params: PathToParamsMap<Path>, slot: RouteSlot }) => void;
9
9
 
10
- export type AsyncWidget<Params = any> = [() => Promise<{ default: Widget<Params> }>]
10
+ export type AsyncWidget<Params = any> = [() => Promise<{ default: WidgetConstructor<Params> }>]
11
11
 
12
12
  export type PathToParamsUnion<T extends RoutePath> =
13
13
  T extends `${infer _Start}:${infer Param}/${infer Rest}`
@@ -1,34 +1,12 @@
1
1
  # amateras/signal
2
2
 
3
- ## Usage
4
-
3
+ ## Import
5
4
  ```ts
6
5
  import 'amateras';
7
6
  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
7
  ```
29
8
 
30
9
  ## Read and Write
31
-
32
10
  ```ts
33
11
  const number$ = $.signal(0);
34
12
  const string$ = $.singal('');
@@ -48,21 +26,22 @@ boolean$(); // true
48
26
  object$(); // { number: 42 }
49
27
  ```
50
28
 
51
- ## Use in attribute methods
52
-
29
+ ## Set as Attribute Value
53
30
  ```ts
54
31
  const src$ = $.signal('/image-1.png');
55
32
 
56
- $(document.body).content([
33
+ $('div', $$ => {
57
34
  // you can set signal variable in attribute
58
- $('img').src( src$ ),
35
+ $('img', { src: src$ }),
59
36
 
60
- $('button').content('Change Image').on('click', () => src$.set('/image-2.png'))
61
- ])
37
+ $('button', $$ => {
38
+ $$.on('click', () => src$.set('/image-2.png'))
39
+ $`Change Image`
40
+ })
41
+ })
62
42
  ```
63
43
 
64
- ## Reactive object
65
-
44
+ ## Signal Object
66
45
  ```ts
67
46
  const user$ = $.signal({
68
47
  name: 'Amateras',
@@ -73,21 +52,22 @@ const user$ = $.signal({
73
52
  }
74
53
  })
75
54
 
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
- ])
55
+ user$.name$() // "Amateras"
56
+ user$.age$() // 16
57
+ user$.avatar$.url() // "/amateras/avatar.png"
58
+ ```
59
+
60
+ ## Compute and Effect
61
+ ```ts
62
+ const count$ = $.signal(0);
63
+ const double$ = $.compute(() => count$() + 1);
64
+
65
+ $.effect(() => {
66
+ console.log(`Count is ${count$()}`)
67
+ })
68
+
69
+ $('button', $$ => {
70
+ $$.on('click', () => count$.set(value => value + 1))
71
+ $`You have clicked this button in half of ${double$} times.`
72
+ })
93
73
  ```