@plohoj/html-editor 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
- import { throttleTime, mergeMap, distinctUntilChanged, switchMap, filter, map, take, shareReplay, tap, connect, takeUntil } from 'rxjs/operators';
2
- import { Observable, concat, defer, EMPTY, of, pipe, merge, from } from 'rxjs';
1
+ import { throttleTime, mergeMap, distinctUntilChanged, switchMap, debounceTime, filter, map, take, tap, shareReplay, connect, takeUntil } from 'rxjs/operators';
2
+ import { Observable, concat, defer, EMPTY, of, merge, fromEvent, pipe, from, tap as tap$1, share, NEVER, takeUntil as takeUntil$1, ignoreElements, ReplaySubject } from 'rxjs';
3
3
 
4
- function randomFromArray(array, from = 0, to = array.length) {
5
- if (to < 0) {
6
- to = array.length + to;
7
- }
8
- return array[Math.floor(Math.random() * (to - from)) + from];
4
+ function trueStub() {
5
+ return true;
6
+ }
7
+ function falseStub() {
8
+ return false;
9
9
  }
10
10
 
11
11
  /**
@@ -23,7 +23,7 @@ function observeElementMutation(node, options) {
23
23
  * Returns change (addition and deletion) of element that match selectors, like an Rx stream.
24
24
  */
25
25
  function observeQuerySelector(query, options = {}) {
26
- const { parent = document.documentElement, asRemovedWhen } = options;
26
+ const { parent = document.documentElement, asRemovedWhen, filter = trueStub, } = options;
27
27
  let targetElement;
28
28
  function checkChanges() {
29
29
  const querySelectedElements = parent.querySelectorAll(query);
@@ -33,7 +33,7 @@ function observeQuerySelector(query, options = {}) {
33
33
  if (options.has && !querySelectedElement.querySelector(options.has)) {
34
34
  continue;
35
35
  }
36
- if (options.filter && !options.filter(querySelectedElement)) {
36
+ if (!filter(querySelectedElement)) {
37
37
  continue;
38
38
  }
39
39
  filteredSelectedElement = querySelectedElement;
@@ -70,9 +70,25 @@ function observeQuerySelector(query, options = {}) {
70
70
  return observeQuerySelector$;
71
71
  }
72
72
 
73
+ /**
74
+ * Awaiting only one element to match the selector and returns it as an Rx stream.
75
+ * The stream ends after one element is found / added.
76
+ */
77
+ function awaitElement(query, options = {}) {
78
+ return observeQuerySelector(query, options)
79
+ .pipe(debounceTime(options.debounceTime || 0, options.debounceScheduler), filter(changes => !!changes.target), map(changes => changes.target), take(1));
80
+ }
81
+
82
+ function randomFromArray(array, from = 0, to = array.length) {
83
+ if (to < 0) {
84
+ to = array.length + to;
85
+ }
86
+ return array[Math.floor(Math.random() * (to - from)) + from];
87
+ }
88
+
73
89
  /** Returns changes (additions and deletions) of elements that match selectors, like an Rx stream. */
74
90
  function observeQuerySelectorAll(query, options = {}) {
75
- const { parent = document.documentElement, asRemovedWhen } = options;
91
+ const { parent = document.documentElement, asRemovedWhen, filter = trueStub, } = options;
76
92
  const targetElements = new Set();
77
93
  function checkChanges() {
78
94
  const addedElements = new Set();
@@ -82,7 +98,7 @@ function observeQuerySelectorAll(query, options = {}) {
82
98
  if (options.has && !querySelectedElement.querySelector(options.has)) {
83
99
  continue;
84
100
  }
85
- if (options.filter && !options.filter(querySelectedElement)) {
101
+ if (!filter(querySelectedElement)) {
86
102
  continue;
87
103
  }
88
104
  if (targetElementsDiff.has(querySelectedElement)) {
@@ -129,22 +145,23 @@ function observeQuerySelectorAll(query, options = {}) {
129
145
  return observeQuerySelectorAll$;
130
146
  }
131
147
 
132
- /**
133
- * Awaiting only one element to match the selector and returns it as an Rx stream.
134
- * The stream ends immediately after one element is found / added.
135
- */
136
- function awaitElement(query) {
137
- return observeQuerySelector(query)
138
- .pipe(filter(changes => !!changes.target), map(changes => changes.target), take(1));
139
- }
140
148
  /**
141
149
  * Awaiting Expects at least one element to match the selector and returns it as an Rx stream.
142
- * If there are more than 1 elements,
143
- * it will return a random one. The stream ends immediately after the elements are found / added.
150
+ * If there are more than 1 elements, it will return a random one.
151
+ * The stream ends after the elements are found / added.
144
152
  */
145
- function awaitRandomElement(query) {
146
- return observeQuerySelectorAll(query)
147
- .pipe(filter(changes => !!changes.target), map(changes => randomFromArray(changes.target)), take(1));
153
+ function awaitRandomElement(query, options = {}) {
154
+ return observeQuerySelectorAll(query, options)
155
+ .pipe(debounceTime(options.debounceTime || 0, options.debounceScheduler), filter(changes => changes.target.length > 0), map(changes => randomFromArray(changes.target)), take(1));
156
+ }
157
+
158
+ function observePressedKeyboardButtons(options = {}) {
159
+ const target = options.target || window;
160
+ const set = new Set();
161
+ const addKey$ = merge(fromEvent(target, 'keydown'), fromEvent(target, 'keypress')).pipe(filter((event) => !set.has(event.key)), tap((event) => set.add(event.key)));
162
+ const keyRemove$ = fromEvent(target, 'keyup')
163
+ .pipe(filter((event) => set.has(event.key)), tap((event) => set.delete(event.key)));
164
+ return merge(addKey$, keyRemove$).pipe(map(() => set));
148
165
  }
149
166
 
150
167
  let pushStateSubscriber$;
@@ -196,24 +213,28 @@ function assuredArray(values) {
196
213
  }
197
214
  return [];
198
215
  }
199
- /** Conversion operator to a new stream for each new added element */
200
- function mergeMapAddedElements(project, options) {
201
- if (!(options === null || options === void 0 ? void 0 : options.isTakeUntilRemoved)) {
216
+ function mergeMapAddedElements(projectOrOptions, options) {
217
+ const project = typeof projectOrOptions === 'function'
218
+ ? projectOrOptions
219
+ : (element) => of(element);
220
+ options = typeof projectOrOptions === 'object' ? projectOrOptions : options;
221
+ const { isTakeUntilRemoved = false } = options || {};
222
+ if (!isTakeUntilRemoved) {
202
223
  return source$ => source$.pipe(mergeMap(changes => {
203
- let added = assuredArray(changes.added);
224
+ const added = assuredArray(changes.added);
204
225
  if (added.length === 0) {
205
226
  return EMPTY;
206
227
  }
207
- let addedObservers = added.map(project);
228
+ const addedObservers = added.map(project);
208
229
  return merge(...addedObservers);
209
230
  }));
210
231
  }
211
232
  return source$ => source$.pipe(connect(connectedSource$ => connectedSource$.pipe(mergeMap(changes => {
212
- let added = assuredArray(changes.added);
233
+ const added = assuredArray(changes.added);
213
234
  if (added.length === 0) {
214
235
  return EMPTY;
215
236
  }
216
- let addedObservers = added.map(addedElement => from(project(addedElement)).pipe(takeUntil(connectedSource$.pipe(filter(changes => {
237
+ const addedObservers = added.map(addedElement => from(project(addedElement)).pipe(takeUntil(connectedSource$.pipe(filter(changes => {
217
238
  if (changes.removed instanceof Array) {
218
239
  return changes.removed.indexOf(addedElement) != -1;
219
240
  }
@@ -225,6 +246,22 @@ function mergeMapAddedElements(project, options) {
225
246
  }))));
226
247
  }
227
248
 
249
+ /** The operator creates a separate stream when the source string is validated. */
250
+ function mergeMapStringToggle(condition, project, options) {
251
+ const mapConditionFn = typeof condition === 'function' ? condition : (url) => condition.test(url);
252
+ let urlMatchToggler;
253
+ if (typeof project === 'function') {
254
+ urlMatchToggler = (isUrlMatch) => isUrlMatch ? from(project()) : EMPTY;
255
+ }
256
+ else {
257
+ urlMatchToggler = (isUrlMatch) => isUrlMatch ? from(project) : EMPTY;
258
+ }
259
+ const mergeOperator = (options === null || options === void 0 ? void 0 : options.isTakeUntilToggle)
260
+ ? switchMap(urlMatchToggler)
261
+ : mergeMap(urlMatchToggler);
262
+ return pipe(map(mapConditionFn), distinctUntilChanged(), mergeOperator);
263
+ }
264
+
228
265
  function removeElement() {
229
266
  return pipe(tap((element) => {
230
267
  element.remove();
@@ -232,6 +269,36 @@ function removeElement() {
232
269
  }));
233
270
  }
234
271
 
272
+ function restoreHistory(options) {
273
+ return (source$) => {
274
+ let hasStory = false;
275
+ const observeCancel$ = options.cancelRestore
276
+ ? defer(() => options.cancelRestore())
277
+ .pipe(tap$1(() => {
278
+ if (hasStory) {
279
+ options.removeStory();
280
+ }
281
+ }), share())
282
+ : NEVER;
283
+ const story$ = concat(defer(() => {
284
+ const story = options.getStory();
285
+ if (story === undefined) {
286
+ return EMPTY;
287
+ }
288
+ hasStory = true;
289
+ let rested$ = of(story);
290
+ if (options.cancelRestore) {
291
+ rested$ = rested$.pipe(takeUntil$1(observeCancel$));
292
+ }
293
+ return rested$;
294
+ }), source$.pipe(tap$1((data) => {
295
+ options.setStory(data);
296
+ hasStory = true;
297
+ })));
298
+ return merge(story$, observeCancel$.pipe(ignoreElements()));
299
+ };
300
+ }
301
+
235
302
  function setInputValueImmediately(element, value) {
236
303
  element.value = value;
237
304
  element.focus();
@@ -247,58 +314,150 @@ function setInputValue(elementOrValue, value) {
247
314
  }
248
315
  }
249
316
 
250
- /** The operator creates a separate stream when the source string is validated. */
251
- function mergeMapStringToggle(condition, project, options) {
252
- const mapConditionFn = typeof condition === 'function' ? condition : (url) => condition.test(url);
253
- let urlMatchToggler;
254
- if (typeof project === 'function') {
255
- urlMatchToggler = (isUrlMatch) => isUrlMatch ? from(project()) : EMPTY;
317
+ function composeRestoreHistory(options) {
318
+ const cancelSubject$ = new ReplaySubject(1);
319
+ function cancelAll() {
320
+ cancelSubject$.next();
321
+ }
322
+ function convertOption(option) {
323
+ return restoreHistory(Object.assign(Object.assign({}, option), { cancelRestore: !!option.cancelRestore
324
+ ? () => merge(cancelSubject$, option.cancelRestore())
325
+ : () => cancelSubject$ }));
326
+ }
327
+ let operators;
328
+ if (options instanceof Array) {
329
+ operators = options.map(convertOption);
256
330
  }
257
331
  else {
258
- urlMatchToggler = (isUrlMatch) => isUrlMatch ? from(project) : EMPTY;
332
+ operators = {};
333
+ for (const key of Object.keys(options)) {
334
+ operators[key] = convertOption(options[key]);
335
+ }
259
336
  }
260
- const mergeOperator = (options === null || options === void 0 ? void 0 : options.isTakeUntilToggle)
261
- ? switchMap(urlMatchToggler)
262
- : mergeMap(urlMatchToggler);
263
- return pipe(map(mapConditionFn), distinctUntilChanged(), mergeOperator);
337
+ return {
338
+ operators: operators,
339
+ cancelAll,
340
+ };
264
341
  }
265
342
 
266
- function findRecursively(obj, mather) {
267
- const seen = new Set();
343
+ /** Recursive search on entity fields */
344
+ function findRecursively(obj, options = {}) {
345
+ var _a;
346
+ /** {value: path[]} */
347
+ const seen = new Map();
268
348
  const searched = {};
269
- const needFind = new Set([[undefined, obj]]);
270
- function addToCheck(path, value) {
271
- if (seen.has(value)) {
349
+ const needSeen = new Set([{
350
+ value: obj,
351
+ depth: 0,
352
+ }]);
353
+ const { matcher = falseStub, filter = trueStub, continueAfterGetterError = trueStub, stop = falseStub, minDepth = 1, maxDepth = Infinity, } = options;
354
+ const pathRegExpCheck = options.pathRegExp
355
+ ? (path) => { var _a; return path ? (_a = options.pathRegExp) === null || _a === void 0 ? void 0 : _a.test(path) : false; }
356
+ : falseStub;
357
+ const fieldRegExpCheck = options.fieldRegExp
358
+ ? (field) => { var _a; return typeof field === 'string' ? (_a = options.fieldRegExp) === null || _a === void 0 ? void 0 : _a.test(field) : false; }
359
+ : falseStub;
360
+ let needStop = false;
361
+ function checkForNeedSeen(options) {
362
+ if (options.depth > maxDepth) {
363
+ return;
364
+ }
365
+ const isPrimitiveOrFunction = options.value === null || typeof options.value !== 'object';
366
+ if (!isPrimitiveOrFunction) {
367
+ const seenPathsForValue = seen.get(options.value);
368
+ if (seenPathsForValue) {
369
+ // TODO windows or document
370
+ // TODO List of visited objects, but with the wrong path
371
+ const hasCircularPath = seenPathsForValue.some(path => options.path && (path === null || path === void 0 ? void 0 : path.includes(options.path)));
372
+ if (hasCircularPath) {
373
+ return;
374
+ }
375
+ }
376
+ if (filter(options)) {
377
+ needSeen.add(options);
378
+ if (seenPathsForValue) {
379
+ seenPathsForValue.push(options.path);
380
+ }
381
+ else {
382
+ seen.set(options.value, [options.path]);
383
+ }
384
+ }
385
+ }
386
+ if (options.depth < minDepth) {
272
387
  return;
273
388
  }
274
- needFind.add([path, value]);
275
- seen.add(value);
389
+ if (matcher(options) || pathRegExpCheck(options.path) || fieldRegExpCheck(options.field)) {
390
+ searched[options.path || '{}'] = options.value;
391
+ if (stop(searched)) {
392
+ needStop = true;
393
+ }
394
+ }
276
395
  }
277
- while (true) {
278
- const iterator = needFind.values().next();
396
+ function iterateArrayLike(iterator, pathBuilder, incrementalDepth) {
397
+ for (const [fieldIndex, fieldValue] of iterator) {
398
+ checkForNeedSeen({
399
+ field: fieldIndex,
400
+ value: fieldValue,
401
+ path: pathBuilder(fieldIndex),
402
+ depth: incrementalDepth,
403
+ });
404
+ if (needStop) {
405
+ return;
406
+ }
407
+ }
408
+ }
409
+ while (!needStop) {
410
+ const iterator = needSeen.values().next();
279
411
  if (iterator.done) {
280
412
  break;
281
413
  }
282
- const path = iterator.value[0];
283
- const iteratedObj = iterator.value[1];
284
- needFind.delete(iterator.value);
285
- if (!iteratedObj) {
286
- continue;
414
+ const { value, path, depth } = iterator.value;
415
+ const incrementalDepth = depth + 1;
416
+ needSeen.delete(iterator.value);
417
+ if (value instanceof Array) {
418
+ iterateArrayLike(value.entries(), (fieldIndex) => `${path || ''}[${fieldIndex}]`, incrementalDepth);
419
+ }
420
+ if (value instanceof Map) {
421
+ iterateArrayLike(value.entries(), (fieldIndex) => `${path || ''}{${fieldIndex}}`, incrementalDepth);
287
422
  }
288
- if (iteratedObj instanceof Array) {
289
- for (const [index, value] of iteratedObj.entries()) {
290
- if (value)
291
- addToCheck(`${path || ''}[${index}]`, value);
423
+ if (value instanceof Set) {
424
+ for (const [fieldIndex, fieldValue] of [...value].entries()) {
425
+ checkForNeedSeen({
426
+ field: fieldValue,
427
+ value: fieldValue,
428
+ path: `${path || ''}<${fieldIndex}>`,
429
+ depth: incrementalDepth,
430
+ });
431
+ if (needStop) {
432
+ break;
433
+ }
292
434
  }
293
435
  }
294
- else if (typeof iteratedObj === "object") {
295
- for (const key of Object.keys(iteratedObj)) {
296
- const value = iteratedObj[key];
297
- const keyPath = path ? `${path}.${key}` : key;
298
- if (mather(key, value, keyPath)) {
299
- searched[keyPath] = value;
436
+ else if (typeof value === "object") {
437
+ for (const key of Object.keys(value)) {
438
+ let fieldValue;
439
+ if (((_a = Object.getOwnPropertyDescriptor(value, key)) === null || _a === void 0 ? void 0 : _a.get) && options.isIgnoreGetters) {
440
+ continue;
441
+ }
442
+ try {
443
+ fieldValue = value[key];
444
+ }
445
+ catch (error) {
446
+ if (!continueAfterGetterError(error)) {
447
+ needStop = true;
448
+ break;
449
+ }
450
+ fieldValue = error;
451
+ }
452
+ checkForNeedSeen({
453
+ field: key,
454
+ value: fieldValue,
455
+ path: path ? `${path}.${key}` : key,
456
+ depth: incrementalDepth
457
+ });
458
+ if (needStop) {
459
+ break;
300
460
  }
301
- addToCheck(keyPath, value);
302
461
  }
303
462
  }
304
463
  }
@@ -306,10 +465,7 @@ function findRecursively(obj, mather) {
306
465
  return null;
307
466
  }
308
467
  return searched;
309
- }
310
- function findRecursivelyPropertyName(obj, propertyName) {
311
- return findRecursively(obj, matchedPropertyName => matchedPropertyName === propertyName);
312
468
  }
313
469
 
314
- export { awaitElement, awaitRandomElement, clickElement, findRecursively, findRecursivelyPropertyName, mergeMapAddedElements, mergeMapStringToggle, observeElementMutation, observeQuerySelector, observeQuerySelectorAll, randomFromArray, removeElement, setInputValue, urlChange$ };
470
+ export { awaitElement, awaitRandomElement, clickElement, composeRestoreHistory, findRecursively, mergeMapAddedElements, mergeMapStringToggle, observeElementMutation, observePressedKeyboardButtons, observeQuerySelector, observeQuerySelectorAll, randomFromArray, removeElement, restoreHistory, setInputValue, urlChange$ };
315
471
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plohoj/html-editor",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "html-editor it's a tool for helping modification html elements",
5
5
  "repository": {
6
6
  "type": "git",
package/src/index.ts CHANGED
@@ -1,15 +1,19 @@
1
1
  // Observable
2
- export { awaitElement, awaitRandomElement } from "./observable/await-element";
2
+ export { awaitElement, IAwaitElementOptions } from "./observable/await-element";
3
+ export { awaitRandomElement } from './observable/await-random-element';
3
4
  export { observeElementMutation } from "./observable/observe-mutation";
4
- export { observeQuerySelector } from "./observable/observe-query-selector";
5
- export { observeQuerySelectorAll } from "./observable/observe-query-selector-all";
5
+ export { IObservePressedKeyboardButtonsOptions, observePressedKeyboardButtons } from './observable/observe-pressed-keyboard-buttons';
6
+ export { IObserveElementChange, IObserveQuerySelectorBaseOptions, IObserveQuerySelectorOptions, observeQuerySelector } from "./observable/observe-query-selector";
7
+ export { IObservedElementsChanges, observeQuerySelectorAll } from "./observable/observe-query-selector-all";
6
8
  export { urlChange$ } from "./observable/url-change";
7
9
  // Operators
8
10
  export { clickElement } from "./operators/click-element";
9
11
  export { mergeMapAddedElements } from "./operators/merge-map-added-elements";
12
+ export { mergeMapStringToggle } from "./operators/merge-map-string-toggle";
10
13
  export { removeElement } from "./operators/remove-element";
14
+ export { IRestoredHistoryOption, restoreHistory } from './operators/restore-history';
11
15
  export { setInputValue } from "./operators/set-input-value";
12
- export { mergeMapStringToggle } from "./operators/merge-map-string-toggle";
13
16
  // Utils
14
- export { findRecursively, findRecursivelyPropertyName, RecursivelyFindMather } from "./utils/find-recursively";
17
+ export { ComposedRestoredHistoryOperatorsArray, ComposedRestoredHistoryOperatorsList, ComposedRestoredHistoryOperatorsRecord, ComposedRestoredHistoryOptionList, composeRestoreHistory, IComposedRestoredHistory } from './utils/compose-restore-history';
18
+ export { findRecursively, FindRecursivelyContinue, FindRecursivelyMatcher, FindRecursivelyResult, IFindRecursivelyMatherOptions, IFindRecursivelyOptions } from "./utils/find-recursively";
15
19
  export { randomFromArray } from "./utils/random-from-array";
@@ -1,32 +1,30 @@
1
- import { Observable } from "rxjs";
2
- import { filter, map, take } from "rxjs/operators";
3
- import { randomFromArray } from "../utils/random-from-array";
4
- import { observeQuerySelector } from "./observe-query-selector";
5
- import { observeQuerySelectorAll } from "./observe-query-selector-all";
1
+ import { Observable, SchedulerLike } from "rxjs";
2
+ import { debounceTime, filter, map, take } from "rxjs/operators";
3
+ import { IObserveQuerySelectorBaseOptions, observeQuerySelector } from "./observe-query-selector";
6
4
 
7
- /**
8
- * Awaiting only one element to match the selector and returns it as an Rx stream.
9
- * The stream ends immediately after one element is found / added.
10
- */
11
- export function awaitElement<T extends Element = Element>(query: string): Observable<T> {
12
- return observeQuerySelector<T>(query)
13
- .pipe(
14
- filter(changes => !!changes.target),
15
- map(changes => changes.target!),
16
- take(1),
17
- );
5
+ export interface IAwaitElementOptions<T extends Element = Element> extends IObserveQuerySelectorBaseOptions<T> {
6
+ /**
7
+ * The time to wait for elements changes.
8
+ * If during the waiting time the elements have changed the timer will be reset.
9
+ * @default 0
10
+ */
11
+ debounceTime?: number;
12
+ debounceScheduler?: SchedulerLike;
18
13
  }
19
14
 
20
15
  /**
21
- * Awaiting Expects at least one element to match the selector and returns it as an Rx stream.
22
- * If there are more than 1 elements,
23
- * it will return a random one. The stream ends immediately after the elements are found / added.
16
+ * Awaiting only one element to match the selector and returns it as an Rx stream.
17
+ * The stream ends after one element is found / added.
24
18
  */
25
- export function awaitRandomElement<T extends Element = Element>(query: string): Observable<T> {
26
- return observeQuerySelectorAll<T>(query)
19
+ export function awaitElement<T extends Element = Element>(
20
+ query: string,
21
+ options: IAwaitElementOptions<T> = {},
22
+ ): Observable<T> {
23
+ return observeQuerySelector<T>(query, options)
27
24
  .pipe(
25
+ debounceTime(options.debounceTime || 0, options.debounceScheduler),
28
26
  filter(changes => !!changes.target),
29
- map(changes => randomFromArray(changes.target)),
27
+ map(changes => changes.target!),
30
28
  take(1),
31
29
  );
32
30
  }
@@ -0,0 +1,23 @@
1
+ import { Observable } from "rxjs";
2
+ import { debounceTime, filter, map, take } from "rxjs/operators";
3
+ import { randomFromArray } from "../utils/random-from-array";
4
+ import { IAwaitElementOptions } from './await-element';
5
+ import { observeQuerySelectorAll } from "./observe-query-selector-all";
6
+
7
+ /**
8
+ * Awaiting Expects at least one element to match the selector and returns it as an Rx stream.
9
+ * If there are more than 1 elements, it will return a random one.
10
+ * The stream ends after the elements are found / added.
11
+ */
12
+ export function awaitRandomElement<T extends Element = Element>(
13
+ query: string,
14
+ options: IAwaitElementOptions<T> = {},
15
+ ): Observable<T> {
16
+ return observeQuerySelectorAll<T>(query, options)
17
+ .pipe(
18
+ debounceTime(options.debounceTime || 0, options.debounceScheduler),
19
+ filter(changes => changes.target.length > 0),
20
+ map(changes => randomFromArray(changes.target)),
21
+ take(1)
22
+ );
23
+ }
@@ -0,0 +1,31 @@
1
+ import { fromEvent, merge, Observable } from 'rxjs';
2
+ import { filter, map, tap } from 'rxjs/operators';
3
+ import { HasEventTargetAddRemove } from 'rxjs/internal/observable/fromEvent';
4
+
5
+ export interface IObservePressedKeyboardButtonsOptions {
6
+ /** @default window */
7
+ target?: HasEventTargetAddRemove<KeyboardEvent>
8
+ }
9
+
10
+ export function observePressedKeyboardButtons(
11
+ options: IObservePressedKeyboardButtonsOptions = {}
12
+ ): Observable<Set<string>> {
13
+ const target = options.target || window;
14
+ const set = new Set<string>();
15
+ const addKey$ = merge(
16
+ fromEvent(target, 'keydown'),
17
+ fromEvent(target, 'keypress'),
18
+ ).pipe(
19
+ filter((event: KeyboardEvent) => !set.has(event.key)),
20
+ tap((event: KeyboardEvent) => set.add(event.key)),
21
+ )
22
+ const keyRemove$ = fromEvent(target, 'keyup')
23
+ .pipe(
24
+ filter((event: KeyboardEvent) => set.has(event.key)),
25
+ tap((event: KeyboardEvent) => set.delete(event.key)),
26
+ );
27
+ return merge(
28
+ addKey$,
29
+ keyRemove$,
30
+ ).pipe(map(() => set));
31
+ }
@@ -1,6 +1,7 @@
1
1
 
2
2
  import { concat, defer, EMPTY, Observable, of } from "rxjs";
3
3
  import { distinctUntilChanged, mergeMap, switchMap, throttleTime } from "rxjs/operators";
4
+ import { trueStub } from '../utils/stubs';
4
5
  import { observeElementMutation } from "./observe-mutation";
5
6
  import { IObserveQuerySelectorOptions } from "./observe-query-selector";
6
7
 
@@ -16,9 +17,13 @@ export interface IObservedElementsChanges<T extends Element = Element> {
16
17
  /** Returns changes (additions and deletions) of elements that match selectors, like an Rx stream. */
17
18
  export function observeQuerySelectorAll<T extends Element = Element>(
18
19
  query: string,
19
- options: IObserveQuerySelectorOptions = {},
20
+ options: IObserveQuerySelectorOptions<T> = {},
20
21
  ): Observable<IObservedElementsChanges<T>> {
21
- const { parent = document.documentElement, asRemovedWhen } = options;
22
+ const {
23
+ parent = document.documentElement,
24
+ asRemovedWhen,
25
+ filter = trueStub,
26
+ } = options;
22
27
  const targetElements = new Set<T>();
23
28
 
24
29
  function checkChanges(): Observable<IObservedElementsChanges<T>> {
@@ -30,7 +35,7 @@ export function observeQuerySelectorAll<T extends Element = Element>(
30
35
  if (options.has && !querySelectedElement.querySelector(options.has)) {
31
36
  continue;
32
37
  }
33
- if (options.filter && !options.filter(querySelectedElement)) {
38
+ if (!filter(querySelectedElement)) {
34
39
  continue;
35
40
  }
36
41
 
@@ -1,18 +1,22 @@
1
1
 
2
2
  import { concat, defer, EMPTY, Observable, of } from "rxjs";
3
3
  import { distinctUntilChanged, mergeMap, switchMap, throttleTime } from "rxjs/operators";
4
+ import { trueStub } from '../utils/stubs';
4
5
  import { observeElementMutation } from "./observe-mutation";
5
6
 
6
- export interface IObserveQuerySelectorOptions<T extends Element = Element> {
7
+ export interface IObserveQuerySelectorBaseOptions<T extends Element = Element> {
7
8
  /**
8
9
  * The parent element within which changes are tracked.
9
10
  * @default document.documentElement
10
11
  */
11
- parent?: T;
12
+ parent?: Element;
12
13
  /** Checks if the added element has any child elements that match the selectors. */
13
14
  has?: string;
14
15
  /** Custom validation of each item */
15
16
  filter?: (element: T) => boolean;
17
+ }
18
+
19
+ export interface IObserveQuerySelectorOptions<T extends Element = Element> extends IObserveQuerySelectorBaseOptions<T> {
16
20
  /**
17
21
  * When the `asRemovedWhen` parameter emits a` true` value,
18
22
  * all currently added items will be returned as removed.
@@ -36,9 +40,13 @@ export interface IObserveElementChange<T extends Element = Element> {
36
40
  */
37
41
  export function observeQuerySelector<T extends Element = Element>(
38
42
  query: string,
39
- options: IObserveQuerySelectorOptions = {},
43
+ options: IObserveQuerySelectorOptions<T> = {},
40
44
  ): Observable<IObserveElementChange<T>> {
41
- const { parent = document.documentElement, asRemovedWhen } = options;
45
+ const {
46
+ parent = document.documentElement,
47
+ asRemovedWhen,
48
+ filter = trueStub,
49
+ } = options;
42
50
  let targetElement: T | undefined;
43
51
 
44
52
  function checkChanges(): Observable<IObserveElementChange<T>> {
@@ -50,7 +58,7 @@ export function observeQuerySelector<T extends Element = Element>(
50
58
  if (options.has && !querySelectedElement.querySelector(options.has)) {
51
59
  continue;
52
60
  }
53
- if (options.filter && !options.filter(querySelectedElement)) {
61
+ if (!filter(querySelectedElement)) {
54
62
  continue;
55
63
  }
56
64
 
@@ -36,11 +36,11 @@ export function mergeMapAddedElements<T extends Element, O extends ObservableInp
36
36
  if (!options?.isTakeUntilRemoved) {
37
37
  return source$ => source$.pipe(
38
38
  mergeMap(changes => {
39
- let added = assuredArray(changes.added);
39
+ const added = assuredArray(changes.added);
40
40
  if (added.length === 0) {
41
41
  return EMPTY;
42
42
  }
43
- let addedObservers = added.map(project);
43
+ const addedObservers = added.map(project);
44
44
  return merge(...addedObservers);
45
45
  })
46
46
  );
@@ -48,12 +48,12 @@ export function mergeMapAddedElements<T extends Element, O extends ObservableInp
48
48
  return source$ => source$.pipe(
49
49
  connect(connectedSource$ => connectedSource$.pipe(
50
50
  mergeMap(changes => {
51
- let added = assuredArray(changes.added);
51
+ const added = assuredArray(changes.added);
52
52
  if (added.length === 0) {
53
53
  return EMPTY;
54
54
  }
55
55
 
56
- let addedObservers = added.map(addedElement =>
56
+ const addedObservers = added.map(addedElement =>
57
57
  from(
58
58
  project(addedElement)
59
59
  ).pipe(
@@ -18,11 +18,11 @@ export function mergeMapStringToggle<O extends ObservableInput<any>>(
18
18
  options?: IMergeMapStringToggleOptions,
19
19
  ): OperatorFunction<string, ObservedValueOf<O>> {
20
20
  const mapConditionFn = typeof condition === 'function' ? condition : (url: string) => condition.test(url);
21
- let urlMatchToggler: (isUrlMatch: Boolean) => Observable<ObservedValueOf<O>>;
21
+ let urlMatchToggler: (isUrlMatch: boolean) => Observable<ObservedValueOf<O>>;
22
22
  if (typeof project === 'function') {
23
- urlMatchToggler = (isUrlMatch: Boolean) => isUrlMatch ? from(project()) : EMPTY
23
+ urlMatchToggler = (isUrlMatch: boolean) => isUrlMatch ? from(project()) : EMPTY;
24
24
  } else {
25
- urlMatchToggler = (isUrlMatch: Boolean) => isUrlMatch ? from(project) : EMPTY
25
+ urlMatchToggler = (isUrlMatch: boolean) => isUrlMatch ? from(project) : EMPTY;
26
26
  }
27
27
  const mergeOperator = options?.isTakeUntilToggle
28
28
  ? switchMap(urlMatchToggler)
@@ -0,0 +1,51 @@
1
+ import { concat, defer, EMPTY, ignoreElements, merge, MonoTypeOperatorFunction, NEVER, Observable, ObservableInput, of, share, take, takeUntil, tap } from 'rxjs';
2
+
3
+ export interface IRestoredHistoryOption<T = unknown> {
4
+ getStory(): T | undefined;
5
+ setStory(value: T): void;
6
+ removeStory(): void;
7
+ cancelRestore?: () => ObservableInput<unknown>;
8
+ };
9
+
10
+ export function restoreHistory<T>(
11
+ options: IRestoredHistoryOption<T>
12
+ ): MonoTypeOperatorFunction<T> {
13
+ return (source$: Observable<T>) => {
14
+ let hasStory = false;
15
+ const observeCancel$ = options.cancelRestore
16
+ ? defer(() => options.cancelRestore!())
17
+ .pipe(
18
+ tap(() => {
19
+ if (hasStory) {
20
+ options.removeStory();
21
+ }
22
+ }),
23
+ share(),
24
+ )
25
+ : NEVER;
26
+ const story$ = concat(
27
+ defer(() => {
28
+ const story = options.getStory();
29
+ if (story === undefined) {
30
+ return EMPTY;
31
+ }
32
+ hasStory = true;
33
+ let rested$ = of(story);
34
+ if (options.cancelRestore) {
35
+ rested$ = rested$.pipe(takeUntil(observeCancel$))
36
+ }
37
+ return rested$;
38
+ }),
39
+ source$.pipe(
40
+ tap((data: T) => {
41
+ options.setStory(data);
42
+ hasStory = true;
43
+ }),
44
+ ),
45
+ );
46
+ return merge(
47
+ story$,
48
+ observeCancel$.pipe(ignoreElements())
49
+ );
50
+ }
51
+ }
@@ -0,0 +1,61 @@
1
+ import { merge, MonoTypeOperatorFunction, ReplaySubject } from 'rxjs';
2
+ import { IRestoredHistoryOption, restoreHistory } from '../operators/restore-history';
3
+
4
+ export type ComposedRestoredHistoryOptionList<T extends IRestoredHistoryOption = IRestoredHistoryOption> = readonly T[] | Record<string, T>;
5
+
6
+ export type ComposedRestoredHistoryOperatorsRecord<T extends Record<string, IRestoredHistoryOption>> = {
7
+ [P in keyof T]: T[P] extends IRestoredHistoryOption<infer Input>
8
+ ? MonoTypeOperatorFunction<Input>
9
+ : never;
10
+ }
11
+
12
+ export type ComposedRestoredHistoryOperatorsArray<T extends readonly IRestoredHistoryOption[]> = {
13
+ [P in keyof T]: T[P] extends IRestoredHistoryOption<infer Input>
14
+ ? MonoTypeOperatorFunction<Input>
15
+ : never;
16
+ }
17
+
18
+ export type ComposedRestoredHistoryOperatorsList<T extends ComposedRestoredHistoryOptionList>
19
+ = T extends Array<any>
20
+ ? ComposedRestoredHistoryOperatorsArray<T>
21
+ : T extends Record<string, any>
22
+ ? ComposedRestoredHistoryOperatorsRecord<T>
23
+ : never;
24
+
25
+ export interface IComposedRestoredHistory<T extends ComposedRestoredHistoryOptionList> {
26
+ operators: ComposedRestoredHistoryOperatorsList<T>;
27
+ cancelAll: () => void;
28
+ }
29
+
30
+ export function composeRestoreHistory<T extends ComposedRestoredHistoryOptionList>(
31
+ options: T,
32
+ ): IComposedRestoredHistory<T> {
33
+ const cancelSubject$ = new ReplaySubject<void>(1);
34
+ function cancelAll(): void {
35
+ cancelSubject$.next();
36
+ }
37
+ function convertOption(option: IRestoredHistoryOption): MonoTypeOperatorFunction<unknown> {
38
+ return restoreHistory({
39
+ ...option,
40
+ cancelRestore: !!option.cancelRestore
41
+ ? () => merge(
42
+ cancelSubject$,
43
+ option.cancelRestore!(),
44
+ )
45
+ : () => cancelSubject$,
46
+ });
47
+ }
48
+ let operators: ComposedRestoredHistoryOperatorsList<T>;
49
+ if (options instanceof Array) {
50
+ operators = options.map(convertOption) as readonly MonoTypeOperatorFunction<any>[] as ComposedRestoredHistoryOperatorsList<T>;
51
+ } else {
52
+ operators = {} as Record<string, MonoTypeOperatorFunction<any>> as ComposedRestoredHistoryOperatorsList<T>;
53
+ for (const key of Object.keys(options)) {
54
+ (operators as Record<string, MonoTypeOperatorFunction<any>>)[key] = convertOption(options[key]);
55
+ }
56
+ }
57
+ return {
58
+ operators: operators as ComposedRestoredHistoryOperatorsList<T>,
59
+ cancelAll,
60
+ };
61
+ }
@@ -1,43 +1,196 @@
1
- export type RecursivelyFindMather = (propertyName: string, value: unknown, path: string) => boolean;
1
+ import { falseStub, trueStub } from './stubs';
2
2
 
3
- export function findRecursively(obj: unknown, mather: RecursivelyFindMather): Record<string, unknown> | null {
4
- const seen = new Set<unknown>();
3
+ export interface IFindRecursivelyMatherOptions {
4
+ field?: unknown;
5
+ value: unknown;
6
+ path?: string;
7
+ depth: number;
8
+ }
9
+
10
+ export type FindRecursivelyMatcher = (options: Readonly<IFindRecursivelyMatherOptions>) => boolean;
11
+ export type FindRecursivelyContinue = (result: Readonly<FindRecursivelyResult>) => boolean;
12
+ export type FindRecursivelyResult = Record<string, unknown> | null;
13
+
14
+ export interface IFindRecursivelyOptions {
15
+ /**
16
+ * Checking a field for compliance with conditions
17
+ * If the method returns `true`, then the field will be added to the result list
18
+ */
19
+ matcher?: FindRecursivelyMatcher;
20
+ /**
21
+ * Filtering out the need for recursive field validation.
22
+ * * If the method returns `true`, then all child fields will be checked recursively.
23
+ * * If the method returns `false`, then the field is excluded from recursive validation.
24
+ */
25
+ filter?: FindRecursivelyMatcher;
26
+ /**
27
+ * Checking whether to continue searching. This check occurs after each new found field.
28
+ * * If the method returns `true`, then the recursive search terminates immediately.
29
+ * * If the method returns `false`, then the recursive search will continue.
30
+ */
31
+ stop?: FindRecursivelyContinue;
32
+ /** Regular expression to match with a constructed path */
33
+ pathRegExp?: RegExp;
34
+ /** Regular expression to match with a constructed path */
35
+ fieldRegExp?: RegExp;
36
+ /** Maximum depth of recursive search */
37
+ maxDepth?: number;
38
+ //** Minimum depth of recursive search */
39
+ minDepth?: number;
40
+ /**
41
+ * Checking getter fields
42
+ * * If the value is `true`, then the field getter will not be called to get the result.
43
+ * Accordingly, the result of the getter will not be checked.
44
+ * * If the value is `false`, then the field getter will be called to get the result.
45
+ * Getter result will be checked
46
+ */
47
+ isIgnoreGetters?: boolean;
48
+ /**
49
+ * Error handling when getting a result from a getter method.
50
+ * * If the method returns `true`, then the recursive search will continue.
51
+ * * If the method returns `false`, then the recursive search terminates immediately.
52
+ */
53
+ continueAfterGetterError?: (error: unknown) => boolean;
54
+ }
55
+
56
+
57
+
58
+ /** Recursive search on entity fields */
59
+ export function findRecursively(obj: unknown, options: IFindRecursivelyOptions = {}): FindRecursivelyResult {
60
+ /** {value: path[]} */
61
+ const seen = new Map<unknown, Array<string | undefined>>();
5
62
  const searched: Record<string, unknown> = {};
6
- const needFind = new Set<[path: string | undefined, value: unknown]>([[undefined, obj]]);
63
+ const needSeen = new Set<IFindRecursivelyMatherOptions>([{
64
+ value: obj,
65
+ depth: 0,
66
+ }]);
67
+ const {
68
+ matcher = falseStub,
69
+ filter = trueStub,
70
+ continueAfterGetterError = trueStub,
71
+ stop = falseStub,
72
+ minDepth = 1,
73
+ maxDepth = Infinity,
74
+ } = options;
75
+ const pathRegExpCheck = options.pathRegExp
76
+ ? (path?: string) => path ? options.pathRegExp?.test(path) : false
77
+ : falseStub;
78
+ const fieldRegExpCheck = options.fieldRegExp
79
+ ? (field?: unknown) => typeof field === 'string' ? options.fieldRegExp?.test(field) : false
80
+ : falseStub;
81
+ let needStop: boolean = false;
7
82
 
8
- function addToCheck(path: string, value: unknown) {
9
- if (seen.has(value) ) {
83
+ function checkForNeedSeen(options: IFindRecursivelyMatherOptions) {
84
+ if (options.depth > maxDepth) {
85
+ return;
86
+ }
87
+ const isPrimitiveOrFunction = options.value === null || typeof options.value !== 'object';
88
+ if (!isPrimitiveOrFunction) {
89
+ const seenPathsForValue: Array<string | undefined> | undefined = seen.get(options.value);
90
+ if (seenPathsForValue) {
91
+ // TODO windows or document
92
+ // TODO List of visited objects, but with the wrong path
93
+ const hasCircularPath = seenPathsForValue.some(path => options.path && path?.includes(options.path));
94
+ if (hasCircularPath) {
95
+ return;
96
+ }
97
+ }
98
+ if (filter(options)) {
99
+ needSeen.add(options);
100
+ if (seenPathsForValue) {
101
+ seenPathsForValue.push(options.path);
102
+ } else {
103
+ seen.set(options.value, [options.path]);
104
+ }
105
+ }
106
+ }
107
+ if (options.depth < minDepth) {
10
108
  return;
11
109
  }
12
- needFind.add([path, value]);
13
- seen.add(value);
110
+ if (matcher(options) || pathRegExpCheck(options.path) || fieldRegExpCheck(options.field)) {
111
+ searched[options.path || '{}'] = options.value;
112
+ if (stop(searched)) {
113
+ needStop = true;
114
+ }
115
+ }
116
+ }
117
+
118
+ function iterateArrayLike(
119
+ iterator: IterableIterator<[unknown, unknown]>,
120
+ pathBuilder: (fieldIndex: unknown) => string,
121
+ incrementalDepth: number,
122
+ ): void {
123
+ for (const [fieldIndex, fieldValue] of iterator) {
124
+ checkForNeedSeen({
125
+ field: fieldIndex,
126
+ value: fieldValue,
127
+ path: pathBuilder(fieldIndex),
128
+ depth: incrementalDepth,
129
+ });
130
+ if (needStop) {
131
+ return;
132
+ }
133
+ }
14
134
  }
15
135
 
16
- while (true) {
17
- const iterator = needFind.values().next();
136
+ while (!needStop) {
137
+ const iterator = needSeen.values().next();
18
138
  if (iterator.done) {
19
139
  break;
20
140
  }
21
- const path = iterator.value[0];
22
- const iteratedObj = iterator.value[1];
23
- needFind.delete(iterator.value);
141
+ const {value, path, depth} = iterator.value;
142
+ const incrementalDepth = depth + 1;
143
+ needSeen.delete(iterator.value);
24
144
 
25
- if (!iteratedObj) {
26
- continue;
27
- }
28
- if (iteratedObj instanceof Array) {
29
- for (const [index, value] of (iteratedObj as unknown[]).entries()) {
30
- if (value )
31
- addToCheck(`${path || ''}[${index}]`, value);
145
+ if (value instanceof Array) {
146
+ iterateArrayLike(
147
+ (value as unknown[]).entries(),
148
+ (fieldIndex) => `${path || ''}[${fieldIndex}]`,
149
+ incrementalDepth,
150
+ );
151
+ } if (value instanceof Map) {
152
+ iterateArrayLike(
153
+ (value as Map<unknown, unknown>).entries(),
154
+ (fieldIndex) => `${path || ''}{${fieldIndex}}`,
155
+ incrementalDepth,
156
+ );
157
+ } if (value instanceof Set) {
158
+ for (const [fieldIndex, fieldValue] of ([...value] as unknown[]).entries()) {
159
+ checkForNeedSeen({
160
+ field: fieldValue,
161
+ value: fieldValue,
162
+ path: `${path || ''}<${fieldIndex}>`,
163
+ depth: incrementalDepth,
164
+ });
165
+ if (needStop) {
166
+ break;
167
+ }
32
168
  }
33
- } else if (typeof iteratedObj === "object") {
34
- for (const key of Object.keys(iteratedObj as object)) {
35
- const value: unknown = (iteratedObj as any)[key];
36
- const keyPath = path ? `${path}.${key}` : key;
37
- if (mather(key, value, keyPath)) {
38
- searched[keyPath] = value;
169
+ } else if (typeof value === "object") {
170
+ for (const key of Object.keys(value as object)) {
171
+ let fieldValue: unknown;
172
+ if (Object.getOwnPropertyDescriptor(value, key)?.get && options.isIgnoreGetters) {
173
+ continue;
174
+ }
175
+ try {
176
+ fieldValue = (value as any)[key];
177
+ } catch (error: unknown) {
178
+ if (!continueAfterGetterError(error)) {
179
+ needStop = true;
180
+ break;
181
+ }
182
+ fieldValue = error;
183
+ }
184
+
185
+ checkForNeedSeen({
186
+ field: key,
187
+ value: fieldValue,
188
+ path: path ? `${path}.${key}` : key,
189
+ depth: incrementalDepth
190
+ });
191
+ if (needStop) {
192
+ break;
39
193
  }
40
- addToCheck(keyPath, value);
41
194
  }
42
195
  }
43
196
  }
@@ -47,7 +200,3 @@ export function findRecursively(obj: unknown, mather: RecursivelyFindMather): Re
47
200
  }
48
201
  return searched;
49
202
  }
50
-
51
- export function findRecursivelyPropertyName(obj: unknown, propertyName: string): Record<string, unknown> | null {
52
- return findRecursively(obj, matchedPropertyName => matchedPropertyName === propertyName);
53
- }
@@ -0,0 +1,7 @@
1
+ export function trueStub(): true {
2
+ return true;
3
+ }
4
+
5
+ export function falseStub(): false {
6
+ return false;
7
+ }