##// END OF EJS Templates
added reduce() and next() methods to observable...
cin -
r116:aac297dda27d v1.6.0 default
parent child
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,19
1 {
2 // Используйте IntelliSense, чтобы узнать о возможных атрибутах.
3 // Наведите указатель мыши, чтобы просмотреть описания существующих атрибутов.
4 // Для получения дополнительной информации посетите: https://go.microsoft.com/fwlink/?linkid=830387
5 "version": "0.2.0",
6 "configurations": [
7 {
8 "type": "node",
9 "request": "launch",
10 "name": "Launch tests",
11 "skipFiles": [
12 "<node_internals>/**"
13 ],
14 "program": "${workspaceFolder}/djx/build/test/index.js",
15 "cwd": "${workspaceFolder}/djx/build/test",
16 "console": "integratedTerminal"
17 }
18 ]
19 } No newline at end of file
@@ -0,0 +1,58
1 import { PromiseOrValue } from "@implab/core-amd/interfaces";
2 import { isPromise } from "@implab/core-amd/safe";
3 import { observe, Observable } from "./observable";
4
5 export interface OrderedUpdate<T> {
6 /** The item is being updated */
7 readonly item: T;
8
9 /** The previous index of the item, -1 in case it is inserted */
10 readonly prevIndex: number;
11
12 /** The new index of the item, -1 in case it is deleted */
13 readonly newIndex: number;
14
15 }
16
17 export type QueryResults<T> = Observable<OrderedUpdate<T>>;
18
19 interface DjObservableResults<T> {
20 /**
21 * Allows observation of results
22 */
23 observe(listener: (object: T, previousIndex: number, newIndex: number) => void, includeUpdates?: boolean): {
24 remove(): void;
25 };
26 }
27
28 interface Queryable<T, A extends unknown[]> {
29 query(...args: A): PromiseOrValue<T[]>;
30 }
31
32 export const isObservableResults = <T>(v: object): v is DjObservableResults<T> =>
33 v && (typeof (v as { observe?: unknown; }).observe === "function");
34
35 export const query = <T, A extends unknown[]>(store: Queryable<T, A>, includeUpdates = true) =>
36 (...args: A) => {
37 return observe<OrderedUpdate<T>>(({ next, complete, error, isClosed }) => {
38 try {
39 const results = store.query(...args);
40 if (isPromise(results)) {
41 results.then(items => items.forEach((item, newIndex) => next({ item, newIndex, prevIndex: -1 })))
42 .then(undefined, error);
43 } else {
44 results.forEach((item, newIndex) => next({ item, newIndex, prevIndex: -1 }));
45 }
46
47 if (!isClosed() && isObservableResults<T>(results)) {
48 const h = results.observe((item, prevIndex, newIndex) => next({ item, prevIndex, newIndex }), includeUpdates);
49 return () => h.remove();
50 } else {
51 complete();
52 }
53 } catch (err) {
54 error(err);
55 }
56 });
57
58 };
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,43 +1,43
1 {
1 {
2 "name": "@implab/djx",
2 "name": "@implab/djx",
3 "version": "0.0.1-dev",
3 "version": "0.0.1-dev",
4 "description": "Supports using dojo version 1 with typescript and .tsx files",
4 "description": "Supports using dojo version 1 with typescript and .tsx files",
5 "keywords": [
5 "keywords": [
6 "dojo",
6 "dojo",
7 "tsx",
7 "tsx",
8 "typescript",
8 "typescript",
9 "widgets"
9 "widgets"
10 ],
10 ],
11 "author": "Implab team",
11 "author": "Implab team",
12 "license": "BSD-2-Clause",
12 "license": "BSD-2-Clause",
13 "repository": "https://code.implab.org/implab/implabjs-djx",
13 "repository": "https://code.implab.org/implab/implabjs-djx",
14 "publishConfig": {
14 "publishConfig": {
15 "access": "public"
15 "access": "public"
16 },
16 },
17 "peerDependencies": {
17 "peerDependencies": {
18 "@implab/core-amd": "^1.4.0",
18 "@implab/core-amd": "^1.4.6",
19 "dojo": "^1.10.0"
19 "dojo": "^1.10.0"
20 },
20 },
21 "devDependencies": {
21 "devDependencies": {
22 "@implab/core-amd": "^1.4.0",
22 "@implab/core-amd": "^1.4.6",
23 "@types/chai": "4.1.3",
23 "@types/chai": "4.1.3",
24 "@types/requirejs": "2.1.31",
24 "@types/requirejs": "2.1.31",
25 "@types/yaml": "1.2.0",
25 "@types/yaml": "1.2.0",
26 "@types/tap": "15.0.7",
26 "@types/tap": "15.0.7",
27 "rxjs": "7.5.6",
27 "rxjs": "7.5.6",
28 "dojo": "1.16.0",
28 "dojo": "1.16.0",
29 "@implab/dojo-typings": "1.0.3",
29 "@implab/dojo-typings": "1.0.3",
30 "@typescript-eslint/eslint-plugin": "^5.23.0",
30 "@typescript-eslint/eslint-plugin": "^5.23.0",
31 "@typescript-eslint/parser": "^5.23.0",
31 "@typescript-eslint/parser": "^5.23.0",
32 "eslint": "^8.23.0",
32 "eslint": "^8.23.0",
33 "eslint-config-standard": "^17.0.0",
33 "eslint-config-standard": "^17.0.0",
34 "eslint-plugin-import": "^2.26.0",
34 "eslint-plugin-import": "^2.26.0",
35 "eslint-plugin-n": "^15.2.0",
35 "eslint-plugin-n": "^15.2.0",
36 "eslint-plugin-promise": "^6.0.0",
36 "eslint-plugin-promise": "^6.0.0",
37 "eslint-plugin-react": "^7.29.4",
37 "eslint-plugin-react": "^7.29.4",
38 "requirejs": "2.3.6",
38 "requirejs": "2.3.6",
39 "typescript": "4.8.3",
39 "typescript": "4.8.3",
40 "yaml": "~1.7.2",
40 "yaml": "~1.7.2",
41 "tap": "16.3.0"
41 "tap": "16.3.0"
42 }
42 }
43 }
43 }
@@ -1,233 +1,306
1 import { PromiseOrValue } from "@implab/core-amd/interfaces";
1 import { Cancellation } from "@implab/core-amd/Cancellation";
2 import { isPromise } from "@implab/core-amd/safe";
2 import { ICancellation } from "@implab/core-amd/interfaces";
3
3
4 /**
4 /**
5 * The interface for the consumer of an observable sequence
5 * The interface for the consumer of an observable sequence
6 */
6 */
7 export interface Observer<T> {
7 export interface Observer<T> {
8 /**
8 /**
9 * Called for the next element in the sequence
9 * Called for the next element in the sequence
10 */
10 */
11 next: (value: T) => void;
11 next: (value: T) => void;
12
12
13 /**
13 /**
14 * Called once when the error occurs in the sequence.
14 * Called once when the error occurs in the sequence.
15 */
15 */
16 error: (e: unknown) => void;
16 error: (e: unknown) => void;
17
17
18 /**
18 /**
19 * Called once at the end of the sequence.
19 * Called once at the end of the sequence.
20 */
20 */
21 complete: () => void;
21 complete: () => void;
22 }
22 }
23
23
24 /**
24 /**
25 * The group of functions to feed an observable. These methods are provided to
25 * The group of functions to feed an observable. These methods are provided to
26 * the producer to generate a stream of events.
26 * the producer to generate a stream of events.
27 */
27 */
28 export type Sink<T> = {
28 export type Sink<T> = {
29 /**
29 /**
30 * Call to send the next element in the sequence
30 * Call to send the next element in the sequence
31 */
31 */
32 next: (value: T) => void;
32 next: (value: T) => void;
33
34 /**
35 * Call to notify about the error occurred in the sequence.
36 */
37 error: (e: unknown) => void;
33
38
34 /**
39 /**
35 * Call to notify about the error occurred in the sequence.
40 * Call to signal the end of the sequence.
36 */
41 */
37 error: (e: unknown) => void;
42 complete: () => void;
38
39 /**
40 * Call to signal the end of the sequence.
41 */
42 complete: () => void;
43
43
44 /**
44 /**
45 * Checks whether the sink is accepting new elements. It's safe to
45 * Checks whether the sink is accepting new elements. It's safe to
46 * send elements to the closed sink.
46 * send elements to the closed sink.
47 */
47 */
48 isClosed: () => boolean;
48 isClosed: () => boolean;
49 };
49 };
50
50
51 export type Producer<T> = (sink: Sink<T>) => (void | (() => void));
51 export type Producer<T> = (sink: Sink<T>) => (void | (() => void));
52
52
53 export interface Unsubscribable {
53 export interface Unsubscribable {
54 unsubscribe(): void;
54 unsubscribe(): void;
55 }
55 }
56
56
57 export const isUnsubsribable = (v: unknown): v is Unsubscribable =>
57 export const isUnsubsribable = (v: unknown): v is Unsubscribable =>
58 v !== null && v !== undefined && typeof (v as Unsubscribable).unsubscribe === "function";
58 v !== null && v !== undefined && typeof (v as Unsubscribable).unsubscribe === "function";
59
59
60 export const isSubsribable = <T = unknown>(v: unknown): v is Subscribable<T> =>
60 export const isSubsribable = <T = unknown>(v: unknown): v is Subscribable<T> =>
61 v !== null && v !== undefined && typeof (v as Subscribable<unknown>).subscribe === "function";
61 v !== null && v !== undefined && typeof (v as Subscribable<unknown>).subscribe === "function";
62
62
63 export interface Subscribable<T> {
63 export interface Subscribable<T> {
64 subscribe(consumer: Partial<Observer<T>>): Unsubscribable;
64 subscribe(consumer: Partial<Observer<T>>): Unsubscribable;
65 }
65 }
66
66
67 export type AccumulatorFn<T, A> = (acc: A, value: T) => A;
68
67 /** The observable source of items. */
69 /** The observable source of items. */
68 export interface Observable<T> extends Subscribable<T> {
70 export interface Observable<T> extends Subscribable<T> {
69 /** Transforms elements of the sequence with the specified mapper
71 /** Transforms elements of the sequence with the specified mapper
70 *
72 *
71 * @param mapper The mapper used to transform the values
73 * @param mapper The mapper used to transform the values
72 */
74 */
73 map<T2>(mapper: (value: T) => T2): Observable<T2>;
75 map<T2>(mapper: (value: T) => T2): Observable<T2>;
74
76
75 /** Filters elements of the sequence. The resulting sequence will
77 /** Filters elements of the sequence. The resulting sequence will
76 * contain only elements which match the specified predicate.
78 * contain only elements which match the specified predicate.
77 *
79 *
78 * @param predicate The filter predicate.
80 * @param predicate The filter predicate.
79 */
81 */
80 filter(predicate: (value: T) => boolean): Observable<T>;
82 filter(predicate: (value: T) => boolean): Observable<T>;
81
83
82 /** Applies accumulator to each value in the sequence and
84 /** Applies accumulator to each value in the sequence and
83 * emits the accumulated value for each source element
85 * emits the accumulated value for each source element
84 *
86 *
85 * @param accumulator
87 * @param accumulator
86 * @param initial
88 * @param initial
87 */
89 */
88 scan<A>(accumulator: (acc: A, value: T) => A, initial: A): Observable<A>;
90 scan<A>(accumulator: AccumulatorFn<T, A>, initial: A): Observable<A>;
91 scan(accumulator: AccumulatorFn<T, T>): Observable<T>;
89
92
93 /** Applies accumulator to each value in the sequence and
94 * emits the accumulated value at the end of the sequence
95 *
96 * @param accumulator
97 * @param initial
98 */
99 reduce<A>(accumulator: AccumulatorFn<T, A>, initial: A): Observable<A>;
100 reduce(accumulator: AccumulatorFn<T, T>): Observable<T>;
101
102 /** Concatenates the specified sequences with this observable
103 *
104 * @param seq sequences to concatenate with the current observable
105 */
90 cat(...seq: Subscribable<T>[]): Observable<T>;
106 cat(...seq: Subscribable<T>[]): Observable<T>;
91
107
92 pipe<U>(f: (source: Observable<T>) => Producer<U>): Observable<U>;
108 /** Pipes the specified operator to produce the new observable
109 * @param op The operator which consumes this observable and produces a new one
110 */
111 pipe<U>(op: (source: Observable<T>) => Producer<U>): Observable<U>;
112
113 /** Waits for the next event to occur and returns a promise for the next value
114 * @param ct Cancellation token to
115 */
116 next(ct?: ICancellation): Promise<T>;
93 }
117 }
94
118
95 const noop = () => { };
119 const noop = () => { };
96
120
97 const sink = <T>(consumer: Partial<Observer<T>>) => {
121 const sink = <T>(consumer: Partial<Observer<T>>) => {
98 const { next, error, complete } = consumer;
122 const { next, error, complete } = consumer;
99 return {
123 return {
100 next: next ? next.bind(consumer) : noop,
124 next: next ? next.bind(consumer) : noop,
101 error: error ? error.bind(consumer) : noop,
125 error: error ? error.bind(consumer) : noop,
102 complete: complete ? complete.bind(consumer) : noop,
126 complete: complete ? complete.bind(consumer) : noop,
103 isClosed: () => false
127 isClosed: () => false
104 };
128 };
105 };
129 };
106
130
107 /** Wraps the producer to handle tear down logic and subscription management
131 /** Wraps the producer to handle tear down logic and subscription management
108 *
132 *
109 * @param producer The producer to wrap
133 * @param producer The producer to wrap
110 * @returns The wrapper producer
134 * @returns The wrapper producer
111 */
135 */
112 const fuse = <T>(producer: Producer<T>) => ({ next, error, complete }: Sink<T>) => {
136 const fuse = <T>(producer: Producer<T>) => ({ next, error, complete }: Sink<T>) => {
113 let done = false;
137 let done = false;
114 let cleanup = noop;
138 let cleanup = noop;
115
139
116 const _fin = <A extends unknown[]>(fn: (...args: A) => void) =>
140 const _fin = <A extends unknown[]>(fn: (...args: A) => void) =>
117 (...args: A) => done ?
141 (...args: A) => done ?
118 void (0) :
142 void (0) :
119 (done = true, cleanup(), fn(...args));
143 (done = true, cleanup(), fn(...args));
120
144
121 const safeSink = {
145 const safeSink = {
122 next: (value: T) => { !done && next(value); },
146 next: (value: T) => { !done && next(value); },
123 error: _fin(error),
147 error: _fin(error),
124 complete: _fin(complete),
148 complete: _fin(complete),
125 isClosed: () => done
149 isClosed: () => done
126 };
150 };
127 cleanup = producer(safeSink) ?? noop;
151 cleanup = producer(safeSink) ?? noop;
128 return done ?
152 return done ?
129 (cleanup(), noop) :
153 (cleanup(), noop) :
130 _fin(noop);
154 _fin(noop);
131 };
155 };
132
156
133 const _observe = <T>(producer: Producer<T>): Observable<T> => ({
157 const _observe = <T>(producer: Producer<T>): Observable<T> => ({
134 subscribe: (consumer: Partial<Observer<T>>) => ({
158 subscribe: (consumer: Partial<Observer<T>>) => ({
135 unsubscribe: producer(sink(consumer)) ?? noop
159 unsubscribe: producer(sink(consumer)) ?? noop
136 }),
160 }),
161
137 map: (mapper) => _observe(({ next, ...rest }) =>
162 map: (mapper) => _observe(({ next, ...rest }) =>
138 producer({
163 producer({
139 next: next !== noop ? (v: T) => next(mapper(v)) : noop,
164 next: next !== noop ? (v: T) => next(mapper(v)) : noop,
140 ...rest
165 ...rest
141 })
166 })
142 ),
167 ),
168
143 filter: (predicate) => _observe(({ next, ...rest }) =>
169 filter: (predicate) => _observe(({ next, ...rest }) =>
144 producer({
170 producer({
145 next: next !== noop ? (v: T) => predicate(v) ? next(v) : void (0) : noop,
171 next: next !== noop ? (v: T) => predicate(v) ? next(v) : void (0) : noop,
146 ...rest
172 ...rest
147 })
173 })
148 ),
174 ),
149 scan: (accumulator, initial) => _observe(({ next, ...rest }) => {
175
150 let _acc = initial;
176 scan: <A>(...args: [AccumulatorFn<T, A>, A] | [AccumulatorFn<T, T>]) => _observe<T | A>(({ next, ...rest }) => {
151 return producer({
177 if (args.length === 1) {
152 next: next !== noop ? (v: T) => next(_acc = accumulator(_acc, v)) : noop,
178 const [accumulator] = args;
153 ...rest
179 let _acc: T;
154 });
180 let index = 0;
181 return producer({
182 next: next !== noop ? (v: T) => next(index++ === 0 ? _acc = v : _acc = accumulator(_acc, v)) : noop,
183 ...rest
184 });
185 } else {
186 const [accumulator, initial] = args;
187 let _acc = initial;
188 return producer({
189 next: next !== noop ? (v: T) => next(_acc = accumulator(_acc, v)) : noop,
190 ...rest
191 });
192 }
193 }),
194
195 reduce: <A>(...args: [AccumulatorFn<T, A>, A] | [AccumulatorFn<T, T>]) => _observe<T | A>(({ next, complete, error, ...rest }) => {
196 if (args.length === 1) {
197 const [accumulator] = args;
198 let _acc: T;
199 let index = 0;
200 return producer({
201 next: next !== noop ? (v: T) => {
202 _acc = index++ === 0 ? v : accumulator(_acc, v);
203 } : noop,
204 complete: () => {
205 if (index === 0) {
206 error(new Error("The sequence can't be empty"));
207 } else {
208 next(_acc);
209 complete();
210 }
211 },
212 error,
213 ...rest
214 });
215 } else {
216 const [accumulator, initial] = args;
217 let _acc = initial;
218 return producer({
219 next: next !== noop ? (v: T) => {
220 _acc = accumulator(_acc, v);
221 } : noop,
222 complete: () => {
223 next(_acc);
224 complete();
225 },
226 error,
227 ...rest
228 });
229 }
155 }),
230 }),
156
231
157 cat: (...seq) => _observe(({ next, complete: final, ...rest }) => {
232 cat: (...seq) => _observe(({ next, complete: final, ...rest }) => {
158 let cleanup: () => void;
233 let cleanup: () => void;
159 const complete = () => {
234 const complete = () => {
160 const continuation = seq.shift();
235 const continuation = seq.shift();
161 if (continuation) {
236 if (continuation) {
162 // if we have a next sequence, subscribe to it
237 // if we have a next sequence, subscribe to it
163 const subscription = continuation.subscribe({ next, complete, ...rest });
238 const subscription = continuation.subscribe({ next, complete, ...rest });
164 cleanup = subscription.unsubscribe.bind(subscription);
239 cleanup = subscription.unsubscribe.bind(subscription);
165 } else {
240 } else {
166 // otherwise notify the consumer about completion
241 // otherwise notify the consumer about completion
167 final();
242 final();
168 }
243 }
169 };
244 };
170
245
171 cleanup = producer({ next, complete, ...rest }) ?? noop;
246 cleanup = producer({ next, complete, ...rest }) ?? noop;
172
247
173 return () => cleanup();
248 return () => cleanup();
174 }),
249 }),
175
250
176 pipe: <U>(f: (source: Observable<T>) => Producer<U>) => observe(f(_observe(producer)))
251 pipe: <U>(op: (source: Observable<T>) => Producer<U>) => observe(op(_observe(producer))),
177 });
178
179 export interface OrderUpdate<T> {
180 /** The item is being updated */
181 item: T;
182
252
183 /** The previous index of the item, -1 in case it is inserted */
253 next: (ct?: ICancellation) => {
184 prevIndex: number;
254 const _ct = ct ?? Cancellation.none;
185
255 return new Promise<T>((resolve, reject) => {
186 /** The new index of the item, -1 in case it is deleted */
256 // wrap the producer to handle only single event
187 newIndex: number;
257 const once = fuse<T>(({ next, complete, error, isClosed }) => {
188 }
258 const h = _ct.register(error);
189
259
190 interface ObservableResults<T> {
260 // is the _ct fires it will call error() and isClosed() will return true
191 /**
261 const cleanup = !isClosed() ?
192 * Allows observation of results
262 producer({
193 */
263 next: v => (next(v), complete()),
194 observe(listener: (object: T, previousIndex: number, newIndex: number) => void, includeUpdates?: boolean): {
264 complete: () => error(new Error("The sequence is empty")),
195 remove(): void;
265 error,
196 };
266 isClosed
197 }
267 }) ?? noop :
268 noop;
198
269
199 interface Queryable<T, A extends unknown[]> {
270 return () => {
200 query(...args: A): PromiseOrValue<T[]>;
271 h.destroy();
201 }
272 cleanup();
273 };
274 });
202
275
203 export const isObservableResults = <T>(v: object): v is ObservableResults<T> =>
276 once({
204 v && (typeof (v as { observe?: unknown; }).observe === "function");
277 next: resolve,
278 error: reject,
279 complete: noop,
280 isClosed: () => false
281 });
282 });
283 }
284 });
205
285
206 export const observe = <T>(producer: Producer<T>) => _observe(fuse(producer));
286 export const observe = <T>(producer: Producer<T>) => _observe(fuse(producer));
207
287
208 export const empty = observe<never>(({ complete }) => complete());
288 export const streamArray = <T>(items: T[]) => _observe<T>(
289 ({ next, complete }) => (
290 items.forEach(next),
291 complete()
292 )
293 );
209
294
210 export const query = <T, A extends unknown[]>(store: Queryable<T, A>) =>
295 export const streamPromise = <T>(promise: PromiseLike<T>) => observe<T>(
211 (...args: A) => {
296 ({next, error, complete}) => void promise.then(v => (next(v), complete()), error)
212 return observe<OrderUpdate<T>>(({ next, complete, error }) => {
297 );
213 try {
214 const results = store.query(...args);
215 if (isPromise(results)) {
216 results.then(items => items.forEach((item, newIndex) => next({ item, newIndex, prevIndex: -1 })))
217 .then(undefined, error);
218 } else {
219 results.forEach((item, newIndex) => next({ item, newIndex, prevIndex: -1 }));
220 }
221
298
222 if (isObservableResults<T>(results)) {
299 export const of = <T>(...items: T[]) => _observe<T>(
223 const h = results.observe((item, prevIndex, newIndex) => next({ item, prevIndex, newIndex }));
300 ({ next, complete }) => (
224 return () => h.remove();
301 items.forEach(next),
225 } else {
302 complete()
226 complete();
303 )
227 }
304 );
228 } catch (err) {
229 error(err);
230 }
231 });
232
305
233 };
306 export const empty = _observe<never>(({ complete }) => complete()); No newline at end of file
@@ -1,187 +1,188
1 import { Constructor } from "@implab/core-amd/interfaces";
1 import { Constructor } from "@implab/core-amd/interfaces";
2 import { HtmlRendition } from "./tsx/HtmlRendition";
2 import { HtmlRendition } from "./tsx/HtmlRendition";
3 import { WidgetRendition } from "./tsx/WidgetRendition";
3 import { WidgetRendition } from "./tsx/WidgetRendition";
4 import { isElementNode, isWidget, isWidgetConstructor, Rendition } from "./tsx/traits";
4 import { isElementNode, isWidget, isWidgetConstructor, Rendition } from "./tsx/traits";
5 import { FunctionRendition } from "./tsx/FunctionRendition";
5 import { FunctionRendition } from "./tsx/FunctionRendition";
6 import Stateful = require("dojo/Stateful");
6 import Stateful = require("dojo/Stateful");
7 import _WidgetBase = require("dijit/_WidgetBase");
7 import _WidgetBase = require("dijit/_WidgetBase");
8 import { DjxWidgetBase } from "./tsx/DjxWidgetBase";
8 import { DjxWidgetBase } from "./tsx/DjxWidgetBase";
9 import { WatchRendition } from "./tsx/WatchRendition";
9 import { WatchRendition } from "./tsx/WatchRendition";
10 import { Observable, observe, OrderUpdate, Subscribable } from "./observable";
10 import { Observable, observe, Subscribable } from "./observable";
11 import djAttr = require("dojo/dom-attr");
11 import djAttr = require("dojo/dom-attr");
12 import djClass = require("dojo/dom-class");
12 import djClass = require("dojo/dom-class");
13 import { AnimationAttrs, WatchForRendition } from "./tsx/WatchForRendition";
13 import { AnimationAttrs, WatchForRendition } from "./tsx/WatchForRendition";
14 import { OrderedUpdate } from "./store";
14
15
15 export function createElement<T extends Constructor | string | ((props: object) => Element)>(elementType: T, ...args: unknown[]): Rendition {
16 export function createElement<T extends Constructor | string | ((props: object) => Element)>(elementType: T, ...args: unknown[]): Rendition {
16 if (typeof elementType === "string") {
17 if (typeof elementType === "string") {
17 const ctx = new HtmlRendition(elementType);
18 const ctx = new HtmlRendition(elementType);
18 if (args)
19 if (args)
19 args.forEach(x => ctx.visitNext(x));
20 args.forEach(x => ctx.visitNext(x));
20
21
21 return ctx;
22 return ctx;
22 } else if (isWidgetConstructor(elementType)) {
23 } else if (isWidgetConstructor(elementType)) {
23 const ctx = new WidgetRendition(elementType);
24 const ctx = new WidgetRendition(elementType);
24 if (args)
25 if (args)
25 args.forEach(x => ctx.visitNext(x));
26 args.forEach(x => ctx.visitNext(x));
26
27
27 return ctx;
28 return ctx;
28 } else if (typeof elementType === "function") {
29 } else if (typeof elementType === "function") {
29 const ctx = new FunctionRendition(elementType as (props: unknown) => Element);
30 const ctx = new FunctionRendition(elementType as (props: unknown) => Element);
30 if (args)
31 if (args)
31 args.forEach(x => ctx.visitNext(x));
32 args.forEach(x => ctx.visitNext(x));
32
33
33 return ctx;
34 return ctx;
34 } else {
35 } else {
35 throw new Error(`The element type '${String(elementType)}' is unsupported`);
36 throw new Error(`The element type '${String(elementType)}' is unsupported`);
36 }
37 }
37 }
38 }
38
39
39 export interface EventDetails<T = unknown> {
40 export interface EventDetails<T = unknown> {
40 detail: T;
41 detail: T;
41 }
42 }
42
43
43 export interface EventSelector {
44 export interface EventSelector {
44 selectorTarget: HTMLElement;
45 selectorTarget: HTMLElement;
45 target: HTMLElement;
46 target: HTMLElement;
46 }
47 }
47
48
48 export type DojoMouseEvent<T = unknown> = MouseEvent & EventSelector & EventDetails<T>;
49 export type DojoMouseEvent<T = unknown> = MouseEvent & EventSelector & EventDetails<T>;
49
50
50 type StatefulProps<T> = T extends Stateful<infer A> ? A :
51 type StatefulProps<T> = T extends Stateful<infer A> ? A :
51 T extends _WidgetBase ? T : never;
52 T extends _WidgetBase ? T : never;
52
53
53
54
54 /**
55 /**
55 * Observers the property and calls render callback each change.
56 * Observers the property and calls render callback each change.
56 *
57 *
57 * @param target The target object which property will be observed.
58 * @param target The target object which property will be observed.
58 * @param prop The name of the property.
59 * @param prop The name of the property.
59 * @param render The callback which will be called every time the value is changed
60 * @param render The callback which will be called every time the value is changed
60 * @returns Rendition which is created instantly
61 * @returns Rendition which is created instantly
61 */
62 */
62 export function watch<W extends _WidgetBase, K extends keyof W>(
63 export function watch<W extends _WidgetBase, K extends keyof W>(
63 target: W,
64 target: W,
64 prop: K,
65 prop: K,
65 render: (model: W[K]) => unknown
66 render: (model: W[K]) => unknown
66 ): Rendition;
67 ): Rendition;
67 /**
68 /**
68 * Observers the property and calls render callback each change.
69 * Observers the property and calls render callback each change.
69 *
70 *
70 * @param target The target object which property will be observed.
71 * @param target The target object which property will be observed.
71 * @param prop The name of the property.
72 * @param prop The name of the property.
72 * @param render The callback which will be called every time the value is changed
73 * @param render The callback which will be called every time the value is changed
73 * @returns Rendition which is created instantly
74 * @returns Rendition which is created instantly
74 */
75 */
75 export function watch<T extends Stateful, K extends keyof StatefulProps<T>>(
76 export function watch<T extends Stateful, K extends keyof StatefulProps<T>>(
76 target: T,
77 target: T,
77 prop: K,
78 prop: K,
78 render: (model: StatefulProps<T>[K]) => unknown
79 render: (model: StatefulProps<T>[K]) => unknown
79 ): Rendition;
80 ): Rendition;
80 export function watch<V>(subj: Subscribable<V>, render: (model: V) => unknown): Rendition;
81 export function watch<V>(subj: Subscribable<V>, render: (model: V) => unknown): Rendition;
81 export function watch(
82 export function watch(
82 ...args: [Stateful, string, (model: unknown) => unknown] |
83 ...args: [Stateful, string, (model: unknown) => unknown] |
83 [Subscribable<unknown>, (model: unknown) => unknown]
84 [Subscribable<unknown>, (model: unknown) => unknown]
84 ) {
85 ) {
85 if (args.length === 3) {
86 if (args.length === 3) {
86 const [target, prop, render] = args;
87 const [target, prop, render] = args;
87 return new WatchRendition(
88 return new WatchRendition(
88 render,
89 render,
89 observe(({next}) => {
90 observe(({next}) => {
90 const h = target.watch(
91 const h = target.watch(
91 prop,
92 prop,
92 (_prop, oldValue, newValue) => oldValue !== newValue && next(newValue)
93 (_prop, oldValue, newValue) => oldValue !== newValue && next(newValue)
93 );
94 );
94 next(target.get(prop));
95 next(target.get(prop));
95 return () => h.remove();
96 return () => h.remove();
96 })
97 })
97 );
98 );
98 } else {
99 } else {
99 const [subj, render] = args;
100 const [subj, render] = args;
100 return new WatchRendition(render, subj);
101 return new WatchRendition(render, subj);
101 }
102 }
102 }
103 }
103
104
104 export const watchFor = <T>(source: T[] | Subscribable<OrderUpdate<T>>, render: (item: T, index: number) => unknown, opts: AnimationAttrs = {}) => {
105 export const watchFor = <T>(source: T[] | Subscribable<OrderedUpdate<T>>, render: (item: T, index: number) => unknown, opts: AnimationAttrs = {}) => {
105 return new WatchForRendition({
106 return new WatchForRendition({
106 ...opts,
107 ...opts,
107 subject: source,
108 subject: source,
108 component: render
109 component: render
109 });
110 });
110 };
111 };
111
112
112
113
113 export const prop: {
114 export const prop: {
114 <T extends Stateful, K extends string & keyof StatefulProps<T>>(target: T, name: K): Observable<StatefulProps<T>[K]>;
115 <T extends Stateful, K extends string & keyof StatefulProps<T>>(target: T, name: K): Observable<StatefulProps<T>[K]>;
115 <T extends _WidgetBase, K extends keyof T>(target: T, name: K): Observable<T[K]>;
116 <T extends _WidgetBase, K extends keyof T>(target: T, name: K): Observable<T[K]>;
116 } = (target: Stateful, name: string) => {
117 } = (target: Stateful, name: string) => {
117 return observe(({next}) => {
118 return observe(({next}) => {
118 const h = target.watch(
119 const h = target.watch(
119 name,
120 name,
120 (_prop, oldValue, newValue) => oldValue !== newValue && next(newValue)
121 (_prop, oldValue, newValue) => oldValue !== newValue && next(newValue)
121 );
122 );
122 next(target.get(name));
123 next(target.get(name));
123 return () => h.remove();
124 return () => h.remove();
124 });
125 });
125 };
126 };
126
127
127 export const attach = <W extends DjxWidgetBase, K extends keyof W>(target: W, name: K) => (v: W[K]) => target.set(name, v);
128 export const attach = <W extends DjxWidgetBase, K extends keyof W>(target: W, name: K) => (v: W[K]) => target.set(name, v);
128
129
129 export const bind = <K extends string, T>(attr: K, subj: Subscribable<T>) => {
130 export const bind = <K extends string, T>(attr: K, subj: Subscribable<T>) => {
130 let h = { unsubscribe() { } };
131 let h = { unsubscribe() { } };
131
132
132 return (el: Element | { set(name: K, value: T): void; } | undefined) => {
133 return (el: Element | { set(name: K, value: T): void; } | undefined) => {
133 if (el) {
134 if (el) {
134 if (isElementNode(el)) {
135 if (isElementNode(el)) {
135 h = subj.subscribe({
136 h = subj.subscribe({
136 next: value => djAttr.set(el, attr, value)
137 next: value => djAttr.set(el, attr, value)
137 });
138 });
138 } else {
139 } else {
139 h = subj.subscribe({
140 h = subj.subscribe({
140 next: value => el.set(attr, value)
141 next: value => el.set(attr, value)
141 });
142 });
142 }
143 }
143 } else {
144 } else {
144 h.unsubscribe();
145 h.unsubscribe();
145 }
146 }
146 };
147 };
147 };
148 };
148
149
149 export const toggleClass = (className: string, subj: Subscribable<boolean>) => {
150 export const toggleClass = (className: string, subj: Subscribable<boolean>) => {
150 let h = { unsubscribe() { } };
151 let h = { unsubscribe() { } };
151 return (elOrWidget: HTMLElement | _WidgetBase | undefined) => {
152 return (elOrWidget: HTMLElement | _WidgetBase | undefined) => {
152 const el = isWidget(elOrWidget) ? elOrWidget.domNode : elOrWidget;
153 const el = isWidget(elOrWidget) ? elOrWidget.domNode : elOrWidget;
153 if (el) {
154 if (el) {
154 h = subj.subscribe({
155 h = subj.subscribe({
155 next: v => djClass.toggle(el, className, v)
156 next: v => djClass.toggle(el, className, v)
156 });
157 });
157 } else {
158 } else {
158 h.unsubscribe();
159 h.unsubscribe();
159 }
160 }
160 };
161 };
161 };
162 };
162
163
163 export const all = <T, A extends JSX.Ref<T>[]>(...cbs: A): JSX.Ref<T> => (arg: T | undefined) => cbs.forEach(cb => cb(arg));
164 export const all = <T, A extends JSX.Ref<T>[]>(...cbs: A): JSX.Ref<T> => (arg: T | undefined) => cbs.forEach(cb => cb(arg));
164
165
165 /** Decorates the method which will be registered as the handle for the specified event.
166 /** Decorates the method which will be registered as the handle for the specified event.
166 * This decorator can be applied to DjxWidgetBase subclass methods.
167 * This decorator can be applied to DjxWidgetBase subclass methods.
167 *
168 *
168 * ```
169 * ```
169 * @on("click")
170 * @on("click")
170 * _onClick(eventObj: MouseEvent) {
171 * _onClick(eventObj: MouseEvent) {
171 * // ...
172 * // ...
172 * }
173 * }
173 * ```
174 * ```
174 */
175 */
175 export const on = <E extends string>(...eventNames: E[]) =>
176 export const on = <E extends string>(...eventNames: E[]) =>
176 <K extends string,
177 <K extends string,
177 T extends DjxWidgetBase<object, { [p in E]: EV }>,
178 T extends DjxWidgetBase<object, { [p in E]: EV }>,
178 EV extends Event
179 EV extends Event
179 >(
180 >(
180 target: T,
181 target: T,
181 key: K,
182 key: K,
182 // eslint-disable-next-line @typescript-eslint/no-unused-vars
183 // eslint-disable-next-line @typescript-eslint/no-unused-vars
183 _descriptor: TypedPropertyDescriptor<(eventObj: EV) => void> | TypedPropertyDescriptor<() => void>
184 _descriptor: TypedPropertyDescriptor<(eventObj: EV) => void> | TypedPropertyDescriptor<() => void>
184 ) => {
185 ) => {
185 const handlers = eventNames.map(eventName => ({ eventName, handlerMethod: key }));
186 const handlers = eventNames.map(eventName => ({ eventName, handlerMethod: key }));
186 target._eventHandlers = target._eventHandlers ? target._eventHandlers.concat(handlers) : handlers;
187 target._eventHandlers = target._eventHandlers ? target._eventHandlers.concat(handlers) : handlers;
187 };
188 };
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now