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