@realglebivanov/reactive 1.0.0 → 1.0.2

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.
@@ -0,0 +1,242 @@
1
+ interface Lifecycle {
2
+ mount(parentNode: Node): void;
3
+ activate(): void;
4
+ deactivate(): void;
5
+ unmount(): void;
6
+ }
7
+
8
+ type Task = () => void;
9
+ type TaskRunner = (task: Task) => void;
10
+ declare const buildDedupMicrotaskRunner: () => TaskRunner;
11
+ declare const dedupMicrotaskRunner: TaskRunner;
12
+ declare const buildMicrotaskRunner: () => TaskRunner;
13
+ declare const microtaskRunner: TaskRunner;
14
+
15
+ declare const observable: <T>(value: T, opts?: {
16
+ microtaskRunner: TaskRunner;
17
+ }) => ValueObservable<T>;
18
+ declare class ValueObservable<T> implements Observable<T>, Updatable<T> {
19
+ private value;
20
+ private opts;
21
+ private readonly observers;
22
+ constructor(value: T, opts: {
23
+ microtaskRunner: TaskRunner;
24
+ });
25
+ unsubscribeAll(): void;
26
+ unsubscribe(id: symbol): void;
27
+ subscribe(id: symbol, observer: Observer<T>): void;
28
+ subscribeInit(id: symbol, observer: Observer<T>): void;
29
+ update(updateFn: UpdateFn<T>): void;
30
+ private notify;
31
+ private notifyAll;
32
+ }
33
+
34
+ type ObservableValue<O extends Observable<any>> = O extends Observable<infer T> ? T : never;
35
+ type Values<Observables extends readonly Observable<any>[]> = {
36
+ [K in keyof Observables]: ObservableValue<Observables[K]>;
37
+ };
38
+ declare const mapObservable: <Observables extends readonly Observable<any>[], R>(mapFn: (...values: Values<Observables>) => R, ...observables: Observables) => MapObservable<Observables, R>;
39
+ declare class MapObservable<Observables extends readonly Observable<any>[], R> implements Observable<R> {
40
+ private mapFn;
41
+ private observables;
42
+ private observers;
43
+ private initializedIndices;
44
+ private ids;
45
+ private currentValues;
46
+ constructor(mapFn: (...values: Values<Observables>) => R, observables: Observables);
47
+ unsubscribeAll(): void;
48
+ unsubscribe(id: symbol): void;
49
+ subscribe(id: symbol, observer: Observer<R>): void;
50
+ subscribeInit(id: symbol, observer: Observer<R>): void;
51
+ private notifyObservers;
52
+ private innerSubscribe;
53
+ private innerUnubscribe;
54
+ }
55
+
56
+ declare const dedupObservable: <T>(innerObservable: Observable<T>, compareEqualFn?: CompareEqualFn<T>, cloneFn?: CloneFn<T>) => DedupObservable<T>;
57
+ type CompareEqualFn<T> = (a: T, b: T) => boolean;
58
+ type CloneFn<T> = (a: T) => T;
59
+ declare class DedupObservable<T> implements Observable<T> {
60
+ private innerObservable;
61
+ private compareEqualFn;
62
+ private cloneFn;
63
+ private id;
64
+ private currentValue;
65
+ private isInitialized;
66
+ private observers;
67
+ constructor(innerObservable: Observable<T>, compareEqualFn: CompareEqualFn<T>, cloneFn: CloneFn<T>);
68
+ unsubscribeAll(): void;
69
+ unsubscribe(id: symbol): void;
70
+ subscribe(id: symbol, observer: Observer<T>): void;
71
+ subscribeInit(id: symbol, observer: Observer<T>): void;
72
+ private innerSubscribe;
73
+ private updateValue;
74
+ private innerUnubscribe;
75
+ }
76
+
77
+ declare const scopedObservable: <T extends Observable<any>>(innerObservable: T) => ScopedObservable<T>;
78
+ type Value<O extends Observable<any>> = O extends Observable<infer T> ? T : never;
79
+ declare class ScopedObservable<T extends Observable<any>> implements Observable<Value<T>> {
80
+ private innerObservable;
81
+ private aliases;
82
+ constructor(innerObservable: T);
83
+ unsubscribeAll(): void;
84
+ unsubscribe(id: symbol): void;
85
+ subscribe(id: symbol, observer: Observer<Value<T>>): void;
86
+ subscribeInit(id: symbol, observer: Observer<Value<T>>): void;
87
+ update(this: ScopedObservable<Updatable<Value<T>> & Observable<Value<T>>>, updateFn: UpdateFn<Value<T>>): void;
88
+ }
89
+
90
+ type Observer<T> = (value: T) => void;
91
+ type UpdateFn<T> = (value: T) => T;
92
+ interface Observable<T> {
93
+ unsubscribeAll(): void;
94
+ unsubscribe(id: symbol): void;
95
+ subscribe(id: symbol, observer: Observer<T>): void;
96
+ subscribeInit(id: symbol, observer: Observer<T>): void;
97
+ }
98
+ interface Updatable<T> {
99
+ update(updateFn: UpdateFn<T>): void;
100
+ }
101
+
102
+ type ReactiveNode<T extends Node> = Lifecycle & T & ReactiveNodeSetters<T>;
103
+ interface ReactiveNodeSetters<T extends Node> {
104
+ clk: <R extends ReactiveNode<T>>(cb: EventListenerOrEventListenerObject) => R;
105
+ }
106
+ type TagReactiveNode<K extends keyof HTMLElementTagNameMap> = ReactiveNode<HTMLElementTagNameMap[K]> & TagReactiveNodeSetters<K>;
107
+ interface TagReactiveNodeSetters<K extends keyof HTMLElementTagNameMap> {
108
+ att: (name: string, value: string) => TagReactiveNode<K>;
109
+ att$: (name: string, value: Observable<string>) => TagReactiveNode<K>;
110
+ }
111
+ declare const toTagReactiveNode: <K extends keyof HTMLElementTagNameMap>(node: HTMLElementTagNameMap[K], handlers: Lifecycle[]) => TagReactiveNode<K>;
112
+ declare const toReactiveNode: <T extends Node>(node: T, handlers: Lifecycle[]) => ReactiveNode<T>;
113
+ declare const reactiveTextNode: (text: string) => ReactiveNode<Text>;
114
+
115
+ type InputChild<T extends Node, K extends keyof HTMLElementTagNameMap = keyof HTMLElementTagNameMap> = TagReactiveNode<K> | ReactiveNode<T> | string;
116
+ declare const tag: <K extends keyof HTMLElementTagNameMap>(name: K, ...inputChildren: InputChild<Node>[]) => TagReactiveNode<K>;
117
+ declare const tags: {
118
+ img: (src: string) => TagReactiveNode<"img">;
119
+ input: (type: string) => TagReactiveNode<"input">;
120
+ canvas: <T extends InputChild<Node>[]>(...children: T) => TagReactiveNode<"canvas">;
121
+ button: <T extends InputChild<Node>[]>(...children: T) => TagReactiveNode<"button">;
122
+ h1: <T extends InputChild<Node>[]>(...children: T) => TagReactiveNode<"h1">;
123
+ h2: <T extends InputChild<Node>[]>(...children: T) => TagReactiveNode<"h2">;
124
+ h3: <T extends InputChild<Node>[]>(...children: T) => TagReactiveNode<"h3">;
125
+ p: <T extends InputChild<Node>[]>(...children: T) => TagReactiveNode<"p">;
126
+ a: <T extends InputChild<Node>[]>(...children: T) => TagReactiveNode<"a">;
127
+ div: <T extends InputChild<Node>[]>(...children: T) => TagReactiveNode<"div">;
128
+ ul: <T extends InputChild<Node>[]>(...children: T) => TagReactiveNode<"ul">;
129
+ li: <T extends InputChild<Node>[]>(...children: T) => TagReactiveNode<"li">;
130
+ span: <T extends InputChild<Node>[]>(...children: T) => TagReactiveNode<"span">;
131
+ select: <T extends InputChild<Node>[]>(...children: T) => TagReactiveNode<"select">;
132
+ };
133
+
134
+ type Observables = Record<string, Observable<any>>;
135
+ type ScopedObservables<T extends Observables> = {
136
+ [K in keyof T]: ScopedObservable<T[K]>;
137
+ };
138
+ type RenderFn<O extends Observables, T extends Node, P> = (this: Context<T, P>, observables: ScopedObservables<O>) => ReactiveNode<T>;
139
+ type UserOpts<O1 extends Observables, O2 extends Observables, T extends Node, P> = Partial<Opts<O1, O2, T, P>> & {
140
+ render: RenderFn<O1 & O2, T, P>;
141
+ };
142
+ type Opts<O1 extends Observables, O2 extends Observables, T extends Node, P> = {
143
+ render: RenderFn<O1 & O2, T, P>;
144
+ observables: () => O1;
145
+ derivedObservables: (observables: O1) => O2;
146
+ cache: boolean;
147
+ props: P;
148
+ };
149
+ declare const component: <O1 extends Observables, O2 extends Observables, T extends Node, P>(opts: UserOpts<O1, O2, T, P>) => ReactiveNode<Comment>;
150
+ declare class Context<T extends Node, P> {
151
+ parent: Node;
152
+ props: P;
153
+ node: ReactiveNode<T> | undefined;
154
+ constructor(parent: Node, props: P);
155
+ }
156
+
157
+ type ReactiveNodeBuilder<T extends Node> = (() => ReactiveNode<T>);
158
+ type Params<A extends Node, B extends Node> = {
159
+ if$: Observable<boolean>;
160
+ then: ReactiveNodeBuilder<A> | string;
161
+ otherwise: ReactiveNodeBuilder<B> | string;
162
+ };
163
+ declare const cond: <A extends Node, B extends Node>({ if$, then, otherwise }: Params<A, B>) => ReactiveNode<Comment>;
164
+
165
+ type Key = string | number | boolean | symbol;
166
+ type KeyFn<K extends Key, T> = ((key: Key, value: T) => K);
167
+ type BuildFn<N extends Node, T> = ((key: Key, value: T) => ReactiveNode<N>);
168
+ type Collection<T> = Array<T> | Map<Key, T>;
169
+ declare class ReactiveItemCollection<K extends Key, T, N extends ReactiveNode<Node>> {
170
+ private keyFn;
171
+ private buildFn;
172
+ private generationId;
173
+ private items;
174
+ constructor(keyFn: KeyFn<K, T>, buildFn: BuildFn<N, T>);
175
+ deactivate(): void;
176
+ unmount(): void;
177
+ replace(anchor: Node, newItems: Collection<T>): void;
178
+ replaceKeys(anchor: Node, items: Collection<T>): void;
179
+ append(anchor: Node, newItems: Collection<T>): void;
180
+ remove(items: Collection<T>): void;
181
+ private getOrInsert;
182
+ private insertItem;
183
+ private removeStaleItems;
184
+ }
185
+
186
+ type Source$1<T> = Collection<T> | Event<T>;
187
+ type Event<T> = {
188
+ type: "replace";
189
+ items: Collection<T>;
190
+ } | {
191
+ type: "remove";
192
+ items: Collection<T>;
193
+ } | {
194
+ type: "append";
195
+ items: Collection<T>;
196
+ } | {
197
+ type: "replaceKeys";
198
+ items: Collection<T>;
199
+ };
200
+ declare const iterable: <K extends Key, T, N extends ReactiveNode<Node>>({ it$, buildFn, keyFn }: {
201
+ it$: Observable<Source$1<T>>;
202
+ buildFn: BuildFn<N, T>;
203
+ keyFn: KeyFn<K, T>;
204
+ }) => ReactiveNode<Comment>;
205
+
206
+ declare class ReactiveItem<T, N extends ReactiveNode<Node>> {
207
+ anchor: Node;
208
+ value: T;
209
+ node: N;
210
+ generationId?: number;
211
+ constructor(anchor: Node, value: T, node: N);
212
+ mount(refItem: ReactiveItem<T, N> | null): void;
213
+ activate(generationId: number): void;
214
+ deactivate(): void;
215
+ unmount(): void;
216
+ }
217
+
218
+ type Hole = Observable<string> | string;
219
+ declare const template: (strings: TemplateStringsArray, ...holes: Hole[]) => ReactiveNode<Comment>;
220
+
221
+ type RouteKey<T extends RouteCollection<Node>> = keyof T & string;
222
+ type RouteCollection<N extends Node> = Record<string, ReactiveNode<N>>;
223
+ type RouterOpts<T extends RouteCollection<Node>> = {
224
+ notFoundRoute: RouteKey<T>;
225
+ };
226
+ declare const router: <T extends RouteCollection<Node>>(routes: T, opts: RouterOpts<T>) => ReactiveNode<Comment>;
227
+
228
+ type Source<T> = Observable<Event<T>> & Updatable<Event<T>>;
229
+ declare class ReactiveArray<T> {
230
+ private items;
231
+ private observables;
232
+ constructor(items?: T[]);
233
+ get observable$(): Source<T>;
234
+ push(...items: T[]): void;
235
+ pop(): T | undefined;
236
+ remove(indices: number[]): void;
237
+ replace(items: T[]): void;
238
+ replaceKeys(items: Map<number, T>): void;
239
+ private emit;
240
+ }
241
+
242
+ export { type BuildFn, type Collection, type Event, type InputChild, type Key, type KeyFn, type Observable, type Observer, ReactiveArray, ReactiveItem, ReactiveItemCollection, type ReactiveNode, type ReactiveNodeSetters, ScopedObservable, type TagReactiveNode, type TagReactiveNodeSetters, type Task, type TaskRunner, type Updatable, type UpdateFn, buildDedupMicrotaskRunner, buildMicrotaskRunner, component, cond, dedupMicrotaskRunner, dedupObservable, iterable, mapObservable, microtaskRunner, observable, reactiveTextNode, router, scopedObservable, tag, tags, template, toReactiveNode, toTagReactiveNode };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ var c=(i=>(i[i.Active=0]="Active",i[i.Inactive=1]="Inactive",i[i.Mounted=2]="Mounted",i[i.Unmounted=3]="Unmounted",i))(c||{}),v=n=>{let e=3;return {mount:t=>{if(e!==3)return console.warn(`Mounting in status ${c[e]}`);for(let s of n)s.mount(t);e=2;},activate:()=>{if(e!==2&&e!==1)return console.warn(`Activating in status ${c[e]}`);for(let t of n)t.activate();e=0;},deactivate:()=>{if(e!==0)return console.warn(`Deactivating in status ${c[e]}`);for(let t of n)t.deactivate();e=1;},unmount(){if(e!==1)return console.warn(`Unmounting in status ${c[e]}`);for(let t of n)t.unmount();e=3;}}};var k=(n,e)=>{let t=[...e],s=L(t),i=K(t),o=v(t);return Object.assign(n,i,s,o)},d=(n,e)=>{let t=[...e],s=K(t),i=v(t);return Object.assign(n,s,i)},u=n=>{let e=document.createTextNode(n);return d(e,[{mount:s=>s.appendChild(e),activate:()=>{},deactivate:()=>{},unmount:()=>e.remove()}])},L=n=>({att:function(e,t){return this.setAttribute(e,t),this},att$:function(e,t){let s=Symbol(`Attribute: ${e}`);return n.push({mount:i=>{},activate:()=>t.subscribeInit(s,i=>this.setAttribute(e,i)),deactivate:()=>t.unsubscribe(s),unmount:()=>{}}),this}}),K=n=>({clk:function(e){return n.push({mount:t=>{},activate:()=>this.addEventListener("click",e),deactivate:()=>this.removeEventListener("click",e),unmount:()=>{}}),this}});var r=(n,...e)=>{let t=document.createElement(n),s=[];for(let i of e)if(typeof i=="string")s.push(u(i));else if(i instanceof Node)s.push(i);else throw new Error("Unsupported child type");return k(t,[{mount:i=>{i.appendChild(t);for(let o of s)o.mount(t);},activate:()=>{for(let i of s)i.activate();},deactivate:()=>{for(let i of s)i.deactivate();},unmount:()=>{for(let i of s)i.unmount();t.remove();}}])},z={img:n=>r("img").att("src",n),input:n=>r("input").att("type",n),canvas:(...n)=>r("canvas",...n),button:(...n)=>r("button",...n),h1:(...n)=>r("h1",...n),h2:(...n)=>r("h2",...n),h3:(...n)=>r("h3",...n),p:(...n)=>r("p",...n),a:(...n)=>r("a",...n),div:(...n)=>r("div",...n),ul:(...n)=>r("ul",...n),li:(...n)=>r("li",...n),span:(...n)=>r("span",...n),select:(...n)=>r("select",...n)};var A=()=>{let n=new Set,e=()=>queueMicrotask(()=>{let t=Array.from(n);n.clear();for(let s of t)s();});return t=>{n.has(t)||(n.add(t),!(n.size>1)&&e());}},I=A(),V=()=>{let n=[],e=()=>queueMicrotask(()=>{let t=n.toReversed();n.length=0;for(let s of t)s();});return t=>{n.push(t),n.length==1&&e();}},C=V();var S=(n,e={microtaskRunner:I})=>new p(n,e),p=class{constructor(e,t){this.value=e;this.opts=t;this.observers=new Map;}unsubscribeAll(){this.observers.clear();}unsubscribe(e){this.observers.delete(e);}subscribe(e,t){this.observers.has(e)&&console.warn("Duplicate observer id",e),this.observers.set(e,t);}subscribeInit(e,t){this.subscribe(e,t),this.notify(e,t);}update(e){this.value=e(this.value),this.opts.microtaskRunner(this.notifyAll.bind(this));}notify(e,t){try{this.observers.get(e)===t&&t(this.value);}catch(s){console.error(s);}}notifyAll(){for(let[e,t]of this.observers.entries())this.notify(e,t);}};var w=(n,...e)=>new h(n,e),h=class{constructor(e,t){this.mapFn=e;this.observables=t;this.observers=new Map;this.initializedIndices=new Set;this.ids=t.map(s=>Symbol("MapObservable")),this.currentValues=[];}unsubscribeAll(){this.observers.clear(),this.innerUnubscribe();}unsubscribe(e){this.observers.delete(e),this.innerUnubscribe();}subscribe(e,t){this.observers.set(e,t),this.innerSubscribe();}subscribeInit(e,t){this.subscribe(e,t);}notifyObservers(e){return t=>{if(this.currentValues[e]=t,this.initializedIndices.add(e),this.initializedIndices.size===this.currentValues.length)for(let s of this.observers.values())s(this.mapFn(...this.currentValues));}}innerSubscribe(){if(this.observers.size===1)for(let[e,t]of this.observables.entries())t.subscribeInit(this.ids[e],this.notifyObservers(e));}innerUnubscribe(){if(this.observers.size===0)for(let[e,t]of this.observables.entries())t.unsubscribe(this.ids[e]);}};var F=(n,e=(s,i)=>s==i,t=s=>s)=>new m(n,e,t),m=class{constructor(e,t,s){this.innerObservable=e;this.compareEqualFn=t;this.cloneFn=s;this.id=Symbol("DedupObservable");this.currentValue=void 0;this.isInitialized=false;this.observers=new Map;}unsubscribeAll(){this.observers.clear(),this.innerUnubscribe();}unsubscribe(e){this.observers.delete(e),this.innerUnubscribe();}subscribe(e,t){this.observers.set(e,t),this.innerSubscribe();}subscribeInit(e,t){this.subscribe(e,t);}innerSubscribe(){this.observers.size===1&&this.innerObservable.subscribeInit(this.id,this.updateValue.bind(this));}updateValue(e){if(!(this.isInitialized&&this.compareEqualFn(this.currentValue,e))){this.currentValue=this.cloneFn(e),this.isInitialized=true;for(let t of this.observers.values())t(this.currentValue);}}innerUnubscribe(){this.observers.size===0&&(this.currentValue=void 0,this.isInitialized=false,this.innerObservable.unsubscribe(this.id));}};var E=n=>new f(n),f=class{constructor(e){this.innerObservable=e;this.aliases=new Map;}unsubscribeAll(){for(let e of this.aliases.values())this.innerObservable.unsubscribe(e);this.aliases.clear();}unsubscribe(e){let t=this.aliases.get(e);t!==void 0&&(this.aliases.delete(e),this.innerObservable.unsubscribe(t));}subscribe(e,t){let s=Symbol("ScopedObservable");this.aliases.set(e,s),this.innerObservable.subscribe(s,t);}subscribeInit(e,t){let s=Symbol("ScopedObservable");this.aliases.set(e,s),this.innerObservable.subscribeInit(s,t);}update(e){"update"in this.innerObservable&&this.innerObservable.update(e);}};var P={observables:()=>({}),derivedObservables:()=>({}),cache:false,props:{}},Z=n=>new N(Object.assign({},P,n)).toReactiveNode(),T=class{constructor(e,t){this.parent=e;this.props=t;}},N=class{constructor(e){this.opts=e;}toReactiveNode(){return d(document.createComment("Component"),[{mount:e=>{var t;(this.node===void 0||!this.opts.cache)&&this.setupNode(e),(t=this.node)==null||t.mount(e);},activate:()=>{var e;return (e=this.node)==null?void 0:e.activate()},deactivate:()=>{var e;if((e=this.node)==null||e.deactivate(),this.observables!==void 0)for(let t in this.observables)this.observables[t].unsubscribeAll();},unmount:()=>{var e;(e=this.node)==null||e.unmount(),this.opts.cache||this.cleanUp();}}])}setupNode(e){this.observables=this.buildObservables(),this.context=new T(e,this.opts.props),this.node=this.opts.render.call(this.context,this.observables),this.context.node=this.node;}cleanUp(){this.context!==void 0&&(this.context.node=void 0),this.node=void 0,this.observables=void 0;}buildObservables(){let e=this.opts.observables(),t=this.opts.derivedObservables(e),s=Object.assign({},e,t);return this.toScoped(s)}toScoped(e){let t={};for(let s in e){let i=s;t[i]=E(e[i]);}return t}};var ne=({if$:n,then:e,otherwise:t})=>new y(F(n),e,t).toReactiveNode(),y=class{constructor(e,t,s){this.if$=e;this.then=t;this.otherwise=s;this.id=Symbol("Cond");}toReactiveNode(){let e=document.createComment("Cond"),t=s=>this.updateNode(e,s);return d(e,[{mount:s=>s.appendChild(e),activate:()=>this.if$.subscribeInit(this.id,t),deactivate:()=>{var s;this.if$.unsubscribe(this.id),(s=this.currentNode)==null||s.deactivate();},unmount:()=>{var s;(s=this.currentNode)==null||s.unmount(),this.currentNode=void 0,e.remove();}}])}updateNode(e,t){let s=t?this.buildNode(this.then):this.buildNode(this.otherwise);try{this.switchNode(e,s);}catch(i){console.error(i);}}buildNode(e){if(typeof e=="function")return e();if(typeof e=="string")return u(e);throw new Error("Then/otherwise should be either string or function")}switchNode(e,t){var s,i;(s=this.currentNode)==null||s.deactivate(),(i=this.currentNode)==null||i.unmount(),t.mount(e.parentNode),t.activate(),this.currentNode=t;}};var l=class{constructor(e,t,s){this.anchor=e;this.value=t;this.node=s;}mount(e){let t=this.anchor.parentNode,s=(e==null?void 0:e.node.nextSibling)||null;t!==null&&(this.node.parentNode===null&&this.node.mount(t),t.insertBefore(this.node,s));}activate(e){this.generationId===void 0&&this.node.activate(),this.generationId=e;}deactivate(){this.node.deactivate();}unmount(){this.node.unmount();}};var b=class{constructor(e,t){this.keyFn=e;this.buildFn=t;this.generationId=0;this.items=new Map;}deactivate(){for(let e of this.items.values())e.deactivate();}unmount(){for(let e of this.items.values())e.unmount();this.items.clear(),this.generationId=0;}replace(e,t){this.generationId++;let s=null;for(let[i,o]of t.entries()){let a=this.keyFn(i,o),g=this.getOrInsert(e,s,a,o);g.generationId=this.generationId,s=g;}this.removeStaleItems();}replaceKeys(e,t){for(let[s,i]of t.entries()){let o=this.keyFn(s,i),a=this.items.get(o);a!==void 0&&(this.insertItem(e,a,o,i),a.deactivate(),a.unmount());}}append(e,t){for(let[s,i]of t.entries()){let o=this.keyFn(s,i);this.insertItem(e,null,o,i);}}remove(e){for(let[t,s]of e.entries()){let i=this.keyFn(t,s),o=this.items.get(i);o!==void 0&&(o.deactivate(),o.unmount(),this.items.delete(i));}}getOrInsert(e,t,s,i){let o=this.items.get(s);return o===void 0?this.insertItem(e,t,s,i):o.value===i?o:(o.deactivate(),o.unmount(),this.insertItem(e,t,s,i))}insertItem(e,t,s,i){let o=this.buildFn(s,i),a=new l(e,i,o);return a.mount(t),a.activate(this.generationId),this.items.set(s,a),a}removeStaleItems(){for(let[e,t]of this.items.entries())t.generationId!==this.generationId&&(t.deactivate(),t.unmount(),this.items.delete(e));}};var le=({it$:n,buildFn:e,keyFn:t})=>new O(n,e,t).toReactiveNode(),O=class{constructor(e,t,s){this.id=Symbol("Iterable");this.it$=this.toEventObservable(e),this.items=new b(s,t);}toReactiveNode(){let e=document.createComment("Iterable"),t=s=>{switch(s.type){case "replace":return this.items.replace(e,s.items);case "replaceKeys":return this.items.replaceKeys(e,s.items);case "append":return this.items.append(e,s.items);case "remove":return this.items.remove(s.items);default:return console.warn("Unsupported event type",s)}};return d(e,[{mount:s=>s.appendChild(e),activate:()=>this.it$.subscribeInit(this.id,t),deactivate:()=>{this.it$.unsubscribe(this.id),this.items.deactivate();},unmount:()=>{this.items.unmount(),e.remove();}}])}toEventObservable(e){return w(t=>t instanceof Array||t instanceof Map?{type:"replace",items:t}:t,e)}};var pe=(n,...e)=>new x(n,e).toReactiveNode(),x=class{constructor(e,t){this.strings=e;this.holes=t;}toReactiveNode(){let e=this.buildNodes(),t=document.createComment("Template");return d(t,[{mount:s=>this.appendNodes(s,e),activate:()=>{for(let s of e)this.attachObservable(s);},deactivate:()=>{for(let s of e)this.detachObservable(s);},unmount:()=>this.removeNodes(e)}])}buildNodes(){return this.strings.map((e,t)=>{let s=this.holes[t],i={staticNodes:[document.createTextNode(e)],dynamicNode:void 0};return typeof s=="string"?i.staticNodes.push(document.createTextNode(s)):typeof s=="object"&&s!==null&&(i.dynamicNode={node:document.createTextNode(""),observerId:Symbol(`Template${t}`),observable:s}),i})}appendNodes(e,t){for(let{staticNodes:s,dynamicNode:i}of t){for(let o of s)e.appendChild(o);i!==void 0&&e.appendChild(i.node);}}attachObservable(e){if(e===void 0||e.dynamicNode===void 0)return;let{node:t,observerId:s,observable:i}=e.dynamicNode;i.subscribeInit(s,o=>t.data=o);}removeNodes(e){for(let{staticNodes:t,dynamicNode:s}of e){for(let i of t)i.remove();s!==void 0&&s.node.remove();}}detachObservable(e){if(e.dynamicNode===void 0)return;let{observerId:t,observable:s}=e.dynamicNode;s.unsubscribe(t);}};var Te=(n,e)=>new R(n,e).toReactiveNode(),R=class{constructor(e,t){this.routes=e;this.opts=t;this.hashChangeListener=()=>this.syncHash();}toReactiveNode(){let e=document.createComment("Router");return d(e,[{mount:t=>{if(this.anchor!==void 0)return console.warn("Router is already active");this.anchor=e,t.appendChild(e);},activate:()=>{this.syncHash(),window.addEventListener("hashchange",this.hashChangeListener);},deactivate:()=>{var t;window.removeEventListener("hashchange",this.hashChangeListener),(t=this.currentRoute)==null||t.deactivate();},unmount:()=>{var t;(t=this.currentRoute)==null||t.unmount(),this.currentRoute=void 0,e.remove();}}])}syncHash(){var t,s;let e=this.getNewRoute();e===this.currentRoute||e===void 0||((t=this.currentRoute)==null||t.deactivate(),(s=this.currentRoute)==null||s.unmount(),this.currentRoute=e,C(()=>{var o;let i=(o=this.anchor)==null?void 0:o.parentElement;i!=null&&(e.mount(i),e.activate());}));}getNewRoute(){let e=location.hash.slice(1)||"/",t=this.isRouteKey(e)?e:this.opts.notFoundRoute;return this.routes[t]}isRouteKey(e){return e in this.routes}};var M=class{constructor(e=[]){this.items=e;this.observables=[];}get observable$(){let e=S({type:"replace",items:this.items});return this.observables.push(new WeakRef(e)),e}push(...e){this.items.push(...e),this.emit({type:"append",items:e});}pop(){if(this.items.length===0)return;let e=this.items.length-1,t=this.items.pop();return this.emit({type:"remove",items:new Map().set(e,t)}),t}remove(e){if(e.length==0)return;let t=new Map;for(let s of e)s in this.items&&(t.set(s,this.items[s]),delete this.items[s]);this.emit({type:"remove",items:t});}replace(e){this.items=e,this.emit({type:"replace",items:e});}replaceKeys(e){for(let[t,s]of e.entries())t in this.items&&(this.items[t]=s);this.emit({type:"replaceKeys",items:e});}emit(e){var s;let t=i=>e;for(let i of this.observables)(s=i.deref())==null||s.update(t);}};export{M as ReactiveArray,l as ReactiveItem,b as ReactiveItemCollection,f as ScopedObservable,A as buildDedupMicrotaskRunner,V as buildMicrotaskRunner,Z as component,ne as cond,I as dedupMicrotaskRunner,F as dedupObservable,le as iterable,w as mapObservable,C as microtaskRunner,S as observable,u as reactiveTextNode,Te as router,E as scopedObservable,r as tag,z as tags,pe as template,d as toReactiveNode,k as toTagReactiveNode};//# sourceMappingURL=index.js.map
2
+ //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@realglebivanov/reactive",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "A simple and permissive observable-driven UI toolkit",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -42,5 +42,9 @@
42
42
  "devDependencies": {
43
43
  "tsup": "^8.5.1",
44
44
  "typescript": "^5.9.3"
45
- }
45
+ },
46
+ "files": [
47
+ "dist/index.js",
48
+ "dist/index.d.ts"
49
+ ]
46
50
  }
package/.tool-versions DELETED
@@ -1 +0,0 @@
1
- nodejs 25.4.0
package/public/Kasha.png DELETED
Binary file
Binary file
package/public/index.html DELETED
@@ -1,13 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
-
4
- <head>
5
- <title>Grecha.js</title>
6
- </head>
7
-
8
- <body>
9
- <div id="entry"></div>
10
- <script src="./example.js" type="module"></script>
11
- </body>
12
-
13
- </html>
package/src/example.ts DELETED
@@ -1,133 +0,0 @@
1
- import {
2
- observable,
3
- cond,
4
- template,
5
- iterable,
6
- component,
7
- mapObservable,
8
- router,
9
- tags,
10
- dedupObservable
11
- } from "@realglebivanov/reactive";
12
-
13
- import { ReactiveArray } from "@realglebivanov/reactive";
14
-
15
- const LOREM = `
16
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
17
- Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
18
- Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
19
- Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`;
20
-
21
- const { a, p, h1, h2, div, span, button, ul, li, img, input } = tags;
22
-
23
- const shoppingItems = new ReactiveArray([
24
- { name: "milk", price$: observable("1.99") },
25
- { name: "sour cream", price$: observable("2.99") },
26
- { name: "cheese", price$: observable("0.99") }
27
- ]);
28
-
29
- const counter = () => component({
30
- cache: true,
31
- props: { counter: "Counter" },
32
- observables: () => ({
33
- count$: observable(0),
34
- hard$: observable(false),
35
- veryHard$: observable(true)
36
- }),
37
- derivedObservables: ({ count$, hard$ }) => ({
38
- imageSource$: mapObservable(
39
- (hard) => hard ? "KashaHard.gif" : "Kasha.png",
40
- dedupObservable(hard$)),
41
- hexCounter$: mapObservable((x) => x.toString(2), count$)
42
- }),
43
- render: function ({ count$, hard$, veryHard$, imageSource$, hexCounter$ }) {
44
- const onClick = () => {
45
- count$.update((count) => count + 1);
46
- hard$.update((hard) => !hard);
47
- };
48
-
49
- return div(
50
- h2(cond({
51
- if$: mapObservable(
52
- (hard, veryHard) => hard && veryHard, hard$, veryHard$),
53
- then: "Rock hard, baby",
54
- otherwise: "Wood needed"
55
- })),
56
- div(span(template`${this.props.counter}: ${hexCounter$}`)),
57
- div(img("Kasha.png").att$("src", imageSource$).clk(onClick))
58
- );
59
- }
60
- });
61
-
62
- const shoppingForm = () => component({
63
- render: () => div(
64
- div(span('Name: '), input('text').att('id', 'itemName')),
65
- div(span('Price: '), input('text').att('id', 'itemPrice')),
66
- button(span('Add')).clk(() => {
67
- const itemName = document.getElementById('itemName') as HTMLInputElement;
68
- const itemPrice = document.getElementById('itemPrice') as HTMLInputElement;
69
-
70
- if (itemName.value == "" || itemPrice.value == "") return;
71
-
72
- shoppingItems.push({
73
- name: itemName.value,
74
- price$: observable(itemPrice.value)
75
- });
76
-
77
- itemName.value = "";
78
- itemPrice.value = "";
79
- })
80
- )
81
- });
82
-
83
- const shoppingList = () => component({
84
- observables: () => ({ shoppingItems$: shoppingItems.observable$ }),
85
- render: ({ shoppingItems$ }) => div(
86
- h2("Shopping items"),
87
- ul(
88
- iterable({
89
- it$: shoppingItems$,
90
- buildFn: (_, item) => li(span(template`${item.name} - ${item.price$}`)),
91
- keyFn: (_, item) => item.name,
92
- })
93
- )
94
- )
95
- });
96
-
97
- const exampleRouter = router({
98
- "/": div(
99
- h1("Grecha.js"),
100
- div(a("Foo").att("href", "#/foo")),
101
- div(a("Bar").att("href", "#/bar")),
102
- counter(),
103
- shoppingList(),
104
- shoppingForm()
105
- ),
106
- "/foo": component({
107
- observables: () => ({ count$: observable(0) }),
108
- derivedObservables: ({ count$ }) => ({
109
- paragraphStyle$: mapObservable(
110
- (count) => `color: ${numberToHexColor(count * 999999)}`, count$)
111
- }),
112
- render: ({ count$, paragraphStyle$ }) => div(
113
- h1("Foo"),
114
- p(LOREM).att$("style", paragraphStyle$),
115
- button("Change color").clk(() => count$.update((x) => x + 1)),
116
- div(a("Home").att("href", "#")),
117
- )
118
- }),
119
- "/bar": div(
120
- h1("Bar"),
121
- p(LOREM),
122
- div(a("Home").att("href", "#"))
123
- )
124
- }, { notFoundRoute: "/" });
125
-
126
- function numberToHexColor(number: number) {
127
- let hex = (number % 0xffffff).toString(16);
128
- while (hex.length < 6) hex = "0" + hex;
129
- return "#" + hex;
130
- }
131
-
132
- exampleRouter.mount(document.getElementById('entry')!);
133
- exampleRouter.activate();
package/src/index.ts DELETED
@@ -1,6 +0,0 @@
1
- export * from './tag';
2
- export * from './observables';
3
- export * from './nodes';
4
- export * from './router';
5
- export * from './task';
6
- export * from './reactive';
package/src/lifecycle.ts DELETED
@@ -1,44 +0,0 @@
1
- export interface Lifecycle {
2
- mount(parentNode: Node): void,
3
- activate(): void,
4
- deactivate(): void,
5
- unmount(): void
6
- }
7
-
8
- export enum ReactiveNodeStatus {
9
- Active,
10
- Inactive,
11
- Mounted,
12
- Unmounted
13
- };
14
-
15
- export const buildLifecycleHooks = (handlers: Lifecycle[]): Lifecycle => {
16
- let status = ReactiveNodeStatus.Unmounted;
17
-
18
- return {
19
- mount: (parentNode: Node) => {
20
- if (status !== ReactiveNodeStatus.Unmounted)
21
- return console.warn(`Mounting in status ${ReactiveNodeStatus[status]}`);
22
- for (const handler of handlers) handler.mount(parentNode);
23
- status = ReactiveNodeStatus.Mounted;
24
- },
25
- activate: () => {
26
- if (status !== ReactiveNodeStatus.Mounted && status !== ReactiveNodeStatus.Inactive)
27
- return console.warn(`Activating in status ${ReactiveNodeStatus[status]}`);
28
- for (const handler of handlers) handler.activate();
29
- status = ReactiveNodeStatus.Active;
30
- },
31
- deactivate: () => {
32
- if (status !== ReactiveNodeStatus.Active)
33
- return console.warn(`Deactivating in status ${ReactiveNodeStatus[status]}`);
34
- for (const handler of handlers) handler.deactivate()
35
- status = ReactiveNodeStatus.Inactive;
36
- },
37
- unmount() {
38
- if (status !== ReactiveNodeStatus.Inactive)
39
- return console.warn(`Unmounting in status ${ReactiveNodeStatus[status]}`);
40
- for (const handler of handlers) handler.unmount();
41
- status = ReactiveNodeStatus.Unmounted;
42
- },
43
- };
44
- };
@@ -1,107 +0,0 @@
1
- import {
2
- scopedObservable,
3
- ScopedObservable,
4
- type Observable
5
- } from "../observables";
6
- import { toReactiveNode, type ReactiveNode } from "./reactive";
7
-
8
- type Observables = Record<string, Observable<any>>;
9
- type ScopedObservables<T extends Observables> = {
10
- [K in keyof T]: ScopedObservable<T[K]>
11
- };
12
-
13
- type RenderFn<O extends Observables, T extends Node, P> =
14
- (this: Context<T, P>, observables: ScopedObservables<O>) => ReactiveNode<T>;
15
-
16
- type UserOpts<O1 extends Observables, O2 extends Observables, T extends Node, P> =
17
- Partial<Opts<O1, O2, T, P>> & { render: RenderFn<O1 & O2, T, P> };
18
-
19
- type Opts<O1 extends Observables, O2 extends Observables, T extends Node, P> = {
20
- render: RenderFn<O1 & O2, T, P>,
21
- observables: () => O1,
22
- derivedObservables: (observables: O1) => O2,
23
- cache: boolean,
24
- props: P
25
- };
26
-
27
- const defaultOpts = {
28
- observables: () => ({}),
29
- derivedObservables: () => ({}),
30
- cache: false,
31
- props: {}
32
- };
33
-
34
- export const component = <
35
- O1 extends Observables,
36
- O2 extends Observables,
37
- T extends Node,
38
- P
39
- >(opts: UserOpts<O1, O2, T, P>): ReactiveNode<Comment> => new Component<O1, O2, T, P>(
40
- Object.assign({}, defaultOpts, opts)
41
- ).toReactiveNode();
42
-
43
- class Context<T extends Node, P> {
44
- node: ReactiveNode<T> | undefined;
45
- constructor(public parent: Node, public props: P) { }
46
- }
47
-
48
- class Component<O1 extends Observables, O2 extends Observables, T extends Node, P> {
49
- private node: ReactiveNode<T> | undefined;
50
- private observables: ScopedObservables<O1 & O2> | undefined;
51
- private context: Context<T, P> | undefined;
52
-
53
- constructor(private opts: Opts<O1, O2, T, P>) { }
54
-
55
- toReactiveNode() {
56
- return toReactiveNode(document.createComment('Component'), [{
57
- mount: (parentNode: Node) => {
58
- if (this.node === undefined || !this.opts.cache)
59
- this.setupNode(parentNode);
60
- this.node?.mount(parentNode);
61
- },
62
- activate: () => this.node?.activate(),
63
- deactivate: () => {
64
- this.node?.deactivate();
65
- if (this.observables === undefined) return;
66
- for (const key in this.observables)
67
- this.observables[key as keyof O1 & O2].unsubscribeAll();
68
- },
69
- unmount: () => {
70
- this.node?.unmount();
71
- if (!this.opts.cache) this.cleanUp();
72
- }
73
- }]);
74
- }
75
-
76
- private setupNode(parentNode: Node) {
77
- this.observables = this.buildObservables();
78
- this.context = new Context(parentNode, this.opts.props);
79
- this.node = this.opts.render.call(this.context, this.observables);
80
- this.context.node = this.node;
81
- }
82
-
83
- private cleanUp() {
84
- if (this.context !== undefined) this.context.node = undefined;
85
- this.node = undefined;
86
- this.observables = undefined;
87
- }
88
-
89
- private buildObservables() {
90
- const coreObservables = this.opts.observables();
91
- const derivedObservables = this.opts.derivedObservables(coreObservables);
92
- const observables =
93
- Object.assign({}, coreObservables, derivedObservables);
94
- return this.toScoped<O1 & O2>(observables);
95
- }
96
-
97
- private toScoped<O extends Observables>(observables: O): ScopedObservables<O> {
98
- const scopedObservables: Partial<ScopedObservables<O>> = {};
99
-
100
- for (const key in observables) {
101
- const k: keyof O = key;
102
- scopedObservables[k] = scopedObservable(observables[k]);
103
- }
104
-
105
- return scopedObservables as ScopedObservables<O>;
106
- }
107
- }