@plohoj/html-editor 0.0.7 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +137 -123
- package/dist/index.js +256 -99
- package/dist/index.js.map +1 -1
- package/package.json +25 -25
- package/src/index.ts +25 -23
- package/src/observable/await-element.ts +47 -29
- package/src/observable/await-elements.ts +38 -20
- package/src/observable/await-random-element.ts +40 -22
- package/src/observable/observe-mutation.ts +10 -12
- package/src/observable/observe-pressed-keyboard-buttons.ts +29 -30
- package/src/observable/observe-query-selector-all.ts +154 -101
- package/src/observable/observe-query-selector.ts +176 -112
- package/src/observable/observe-url-changes.ts +23 -0
- package/src/observable/url-change.ts +37 -38
- package/src/operators/blur-element.ts +21 -0
- package/src/operators/click-element.ts +20 -21
- package/src/operators/focus-element.ts +21 -22
- package/src/operators/merge-map-added-elements.ts +120 -77
- package/src/operators/merge-map-by-condition.ts +124 -0
- package/src/operators/merge-map-by-string-condition.ts +57 -0
- package/src/operators/remove-element.ts +8 -9
- package/src/operators/restore-history.ts +53 -51
- package/src/operators/set-input-value.ts +20 -21
- package/src/utils/compose-restore-history.ts +61 -61
- package/src/utils/find-recursively.ts +203 -203
- package/src/utils/random-from-array.ts +6 -6
- package/src/utils/stubs.ts +7 -7
- package/src/operators/merge-map-string-toggle.ts +0 -36
|
@@ -1,61 +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
|
+
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,203 +1,203 @@
|
|
|
1
|
-
import { falseStub, trueStub } from './stubs';
|
|
2
|
-
|
|
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>>();
|
|
62
|
-
const searched: Record<string, unknown> = {};
|
|
63
|
-
const needSeen = new Set<IFindRecursivelyMatherOptions>([{
|
|
64
|
-
value: obj,
|
|
65
|
-
depth: 0,
|
|
66
|
-
path: '',
|
|
67
|
-
}]);
|
|
68
|
-
const {
|
|
69
|
-
matcher = falseStub,
|
|
70
|
-
filter = trueStub,
|
|
71
|
-
continueAfterGetterError = trueStub,
|
|
72
|
-
stop = falseStub,
|
|
73
|
-
minDepth = 1,
|
|
74
|
-
maxDepth = Infinity,
|
|
75
|
-
} = options;
|
|
76
|
-
const pathRegExpCheck = options.pathRegExp
|
|
77
|
-
? (path?: string) => path ? options.pathRegExp?.test(path) : false
|
|
78
|
-
: falseStub;
|
|
79
|
-
const fieldRegExpCheck = options.fieldRegExp
|
|
80
|
-
? (field?: unknown) => typeof field === 'string' ? options.fieldRegExp?.test(field) : false
|
|
81
|
-
: falseStub;
|
|
82
|
-
let needStop: boolean = false;
|
|
83
|
-
|
|
84
|
-
function checkForNeedSeen(options: IFindRecursivelyMatherOptions) {
|
|
85
|
-
if (options.depth > maxDepth) {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
const isPrimitiveOrFunction = options.value === null || typeof options.value !== 'object';
|
|
89
|
-
if (!isPrimitiveOrFunction) {
|
|
90
|
-
const seenPathsForValue: Array<string> | undefined = seen.get(options.value);
|
|
91
|
-
if (seenPathsForValue) {
|
|
92
|
-
// TODO windows or document
|
|
93
|
-
// TODO List of visited objects, but with the wrong path
|
|
94
|
-
const hasCircularPath = seenPathsForValue.some(path => options.path.includes(path));
|
|
95
|
-
if (hasCircularPath) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
if (filter(options)) {
|
|
100
|
-
needSeen.add(options);
|
|
101
|
-
if (seenPathsForValue) {
|
|
102
|
-
seenPathsForValue.push(options.path);
|
|
103
|
-
} else {
|
|
104
|
-
seen.set(options.value, [options.path]);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
if (options.depth < minDepth) {
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
if (matcher(options) || pathRegExpCheck(options.path) || fieldRegExpCheck(options.field)) {
|
|
112
|
-
searched[options.path || '{}'] = options.value;
|
|
113
|
-
if (stop(searched)) {
|
|
114
|
-
needStop = true;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function iterateArrayLike(
|
|
120
|
-
iterator: IterableIterator<[unknown, unknown]>,
|
|
121
|
-
pathBuilder: (fieldIndex: unknown) => string,
|
|
122
|
-
incrementalDepth: number,
|
|
123
|
-
): void {
|
|
124
|
-
for (const [fieldIndex, fieldValue] of iterator) {
|
|
125
|
-
checkForNeedSeen({
|
|
126
|
-
field: fieldIndex,
|
|
127
|
-
value: fieldValue,
|
|
128
|
-
path: pathBuilder(fieldIndex),
|
|
129
|
-
depth: incrementalDepth,
|
|
130
|
-
});
|
|
131
|
-
if (needStop) {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
while (!needStop) {
|
|
138
|
-
const iterator = needSeen.values().next();
|
|
139
|
-
if (iterator.done) {
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
142
|
-
const {value, path, depth} = iterator.value;
|
|
143
|
-
const incrementalDepth = depth + 1;
|
|
144
|
-
needSeen.delete(iterator.value);
|
|
145
|
-
|
|
146
|
-
if (value instanceof Array) {
|
|
147
|
-
iterateArrayLike(
|
|
148
|
-
(value as unknown[]).entries(),
|
|
149
|
-
(fieldIndex) => `${path}[${fieldIndex}]`,
|
|
150
|
-
incrementalDepth,
|
|
151
|
-
);
|
|
152
|
-
} else if (value instanceof Map) {
|
|
153
|
-
iterateArrayLike(
|
|
154
|
-
(value as Map<unknown, unknown>).entries(),
|
|
155
|
-
(fieldIndex) => `${path}{${fieldIndex}}`,
|
|
156
|
-
incrementalDepth,
|
|
157
|
-
);
|
|
158
|
-
} else if (value instanceof Set) {
|
|
159
|
-
for (const [fieldIndex, fieldValue] of ([...value] as unknown[]).entries()) {
|
|
160
|
-
checkForNeedSeen({
|
|
161
|
-
field: fieldValue,
|
|
162
|
-
value: fieldValue,
|
|
163
|
-
path: `${path || ''}<${fieldIndex}>`,
|
|
164
|
-
depth: incrementalDepth,
|
|
165
|
-
});
|
|
166
|
-
if (needStop) {
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
} else if (typeof value === "object") {
|
|
171
|
-
for (const key of Object.keys(value as object)) {
|
|
172
|
-
let fieldValue: unknown;
|
|
173
|
-
if (Object.getOwnPropertyDescriptor(value, key)?.get && options.isIgnoreGetters) {
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
try {
|
|
177
|
-
fieldValue = (value as any)[key];
|
|
178
|
-
} catch (error: unknown) {
|
|
179
|
-
if (!continueAfterGetterError(error)) {
|
|
180
|
-
needStop = true;
|
|
181
|
-
break;
|
|
182
|
-
}
|
|
183
|
-
fieldValue = error;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
checkForNeedSeen({
|
|
187
|
-
field: key,
|
|
188
|
-
value: fieldValue,
|
|
189
|
-
path: path ? `${path}.${key}` : key,
|
|
190
|
-
depth: incrementalDepth
|
|
191
|
-
});
|
|
192
|
-
if (needStop) {
|
|
193
|
-
break;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (Object.keys(searched).length === 0) {
|
|
200
|
-
return null;
|
|
201
|
-
}
|
|
202
|
-
return searched;
|
|
203
|
-
}
|
|
1
|
+
import { falseStub, trueStub } from './stubs';
|
|
2
|
+
|
|
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>>();
|
|
62
|
+
const searched: Record<string, unknown> = {};
|
|
63
|
+
const needSeen = new Set<IFindRecursivelyMatherOptions>([{
|
|
64
|
+
value: obj,
|
|
65
|
+
depth: 0,
|
|
66
|
+
path: '',
|
|
67
|
+
}]);
|
|
68
|
+
const {
|
|
69
|
+
matcher = falseStub,
|
|
70
|
+
filter = trueStub,
|
|
71
|
+
continueAfterGetterError = trueStub,
|
|
72
|
+
stop = falseStub,
|
|
73
|
+
minDepth = 1,
|
|
74
|
+
maxDepth = Infinity,
|
|
75
|
+
} = options;
|
|
76
|
+
const pathRegExpCheck = options.pathRegExp
|
|
77
|
+
? (path?: string) => path ? options.pathRegExp?.test(path) : false
|
|
78
|
+
: falseStub;
|
|
79
|
+
const fieldRegExpCheck = options.fieldRegExp
|
|
80
|
+
? (field?: unknown) => typeof field === 'string' ? options.fieldRegExp?.test(field) : false
|
|
81
|
+
: falseStub;
|
|
82
|
+
let needStop: boolean = false;
|
|
83
|
+
|
|
84
|
+
function checkForNeedSeen(options: IFindRecursivelyMatherOptions) {
|
|
85
|
+
if (options.depth > maxDepth) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const isPrimitiveOrFunction = options.value === null || typeof options.value !== 'object';
|
|
89
|
+
if (!isPrimitiveOrFunction) {
|
|
90
|
+
const seenPathsForValue: Array<string> | undefined = seen.get(options.value);
|
|
91
|
+
if (seenPathsForValue) {
|
|
92
|
+
// TODO windows or document
|
|
93
|
+
// TODO List of visited objects, but with the wrong path
|
|
94
|
+
const hasCircularPath = seenPathsForValue.some(path => options.path.includes(path));
|
|
95
|
+
if (hasCircularPath) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (filter(options)) {
|
|
100
|
+
needSeen.add(options);
|
|
101
|
+
if (seenPathsForValue) {
|
|
102
|
+
seenPathsForValue.push(options.path);
|
|
103
|
+
} else {
|
|
104
|
+
seen.set(options.value, [options.path]);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (options.depth < minDepth) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (matcher(options) || pathRegExpCheck(options.path) || fieldRegExpCheck(options.field)) {
|
|
112
|
+
searched[options.path || '{}'] = options.value;
|
|
113
|
+
if (stop(searched)) {
|
|
114
|
+
needStop = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function iterateArrayLike(
|
|
120
|
+
iterator: IterableIterator<[unknown, unknown]>,
|
|
121
|
+
pathBuilder: (fieldIndex: unknown) => string,
|
|
122
|
+
incrementalDepth: number,
|
|
123
|
+
): void {
|
|
124
|
+
for (const [fieldIndex, fieldValue] of iterator) {
|
|
125
|
+
checkForNeedSeen({
|
|
126
|
+
field: fieldIndex,
|
|
127
|
+
value: fieldValue,
|
|
128
|
+
path: pathBuilder(fieldIndex),
|
|
129
|
+
depth: incrementalDepth,
|
|
130
|
+
});
|
|
131
|
+
if (needStop) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
while (!needStop) {
|
|
138
|
+
const iterator = needSeen.values().next();
|
|
139
|
+
if (iterator.done) {
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
const {value, path, depth} = iterator.value;
|
|
143
|
+
const incrementalDepth = depth + 1;
|
|
144
|
+
needSeen.delete(iterator.value);
|
|
145
|
+
|
|
146
|
+
if (value instanceof Array) {
|
|
147
|
+
iterateArrayLike(
|
|
148
|
+
(value as unknown[]).entries(),
|
|
149
|
+
(fieldIndex) => `${path}[${fieldIndex}]`,
|
|
150
|
+
incrementalDepth,
|
|
151
|
+
);
|
|
152
|
+
} else if (value instanceof Map) {
|
|
153
|
+
iterateArrayLike(
|
|
154
|
+
(value as Map<unknown, unknown>).entries(),
|
|
155
|
+
(fieldIndex) => `${path}{${fieldIndex}}`,
|
|
156
|
+
incrementalDepth,
|
|
157
|
+
);
|
|
158
|
+
} else if (value instanceof Set) {
|
|
159
|
+
for (const [fieldIndex, fieldValue] of ([...value] as unknown[]).entries()) {
|
|
160
|
+
checkForNeedSeen({
|
|
161
|
+
field: fieldValue,
|
|
162
|
+
value: fieldValue,
|
|
163
|
+
path: `${path || ''}<${fieldIndex}>`,
|
|
164
|
+
depth: incrementalDepth,
|
|
165
|
+
});
|
|
166
|
+
if (needStop) {
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} else if (typeof value === "object") {
|
|
171
|
+
for (const key of Object.keys(value as object)) {
|
|
172
|
+
let fieldValue: unknown;
|
|
173
|
+
if (Object.getOwnPropertyDescriptor(value, key)?.get && options.isIgnoreGetters) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
fieldValue = (value as any)[key];
|
|
178
|
+
} catch (error: unknown) {
|
|
179
|
+
if (!continueAfterGetterError(error)) {
|
|
180
|
+
needStop = true;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
fieldValue = error;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
checkForNeedSeen({
|
|
187
|
+
field: key,
|
|
188
|
+
value: fieldValue,
|
|
189
|
+
path: path ? `${path}.${key}` : key,
|
|
190
|
+
depth: incrementalDepth
|
|
191
|
+
});
|
|
192
|
+
if (needStop) {
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (Object.keys(searched).length === 0) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
return searched;
|
|
203
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export function randomFromArray<T>(array: T[], from: number = 0, to: number = array.length): T {
|
|
2
|
-
if (to < 0) {
|
|
3
|
-
to = array.length + to;
|
|
4
|
-
}
|
|
5
|
-
return array[Math.floor(Math.random() * (to - from)) + from];
|
|
6
|
-
}
|
|
1
|
+
export function randomFromArray<T>(array: T[], from: number = 0, to: number = array.length): T {
|
|
2
|
+
if (to < 0) {
|
|
3
|
+
to = array.length + to;
|
|
4
|
+
}
|
|
5
|
+
return array[Math.floor(Math.random() * (to - from)) + from];
|
|
6
|
+
}
|
package/src/utils/stubs.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export function trueStub(): true {
|
|
2
|
-
return true;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export function falseStub(): false {
|
|
6
|
-
return false;
|
|
7
|
-
}
|
|
1
|
+
export function trueStub(): true {
|
|
2
|
+
return true;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function falseStub(): false {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { EMPTY, from, Observable, ObservableInput, ObservedValueOf, OperatorFunction, pipe } from "rxjs";
|
|
2
|
-
import { distinctUntilChanged, map, mergeMap, switchMap } from "rxjs/operators";
|
|
3
|
-
|
|
4
|
-
export interface IMergeMapStringToggleOptions {
|
|
5
|
-
/**
|
|
6
|
-
* If the `isTakeUntilToggle` parameter is equal to the `true` value,
|
|
7
|
-
* the stream will be interrupted as soon as the source string fails validation.
|
|
8
|
-
*
|
|
9
|
-
* If the `isTakeUntilToggle` parameter is equal to the `false` value, the stream will never be interrupted.
|
|
10
|
-
*/
|
|
11
|
-
isTakeUntilToggle?: boolean;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** The operator creates a separate stream when the source string is validated. */
|
|
15
|
-
export function mergeMapStringToggle<O extends ObservableInput<any>>(
|
|
16
|
-
condition: RegExp | ((url: string) => boolean),
|
|
17
|
-
project: (() => O) | O,
|
|
18
|
-
options?: IMergeMapStringToggleOptions,
|
|
19
|
-
): OperatorFunction<string, ObservedValueOf<O>> {
|
|
20
|
-
const mapConditionFn = typeof condition === 'function' ? condition : (url: string) => condition.test(url);
|
|
21
|
-
let urlMatchToggler: (isUrlMatch: boolean) => Observable<ObservedValueOf<O>>;
|
|
22
|
-
if (typeof project === 'function') {
|
|
23
|
-
urlMatchToggler = (isUrlMatch: boolean) => isUrlMatch ? from(project()) : EMPTY;
|
|
24
|
-
} else {
|
|
25
|
-
urlMatchToggler = (isUrlMatch: boolean) => isUrlMatch ? from(project) : EMPTY;
|
|
26
|
-
}
|
|
27
|
-
const mergeOperator = options?.isTakeUntilToggle
|
|
28
|
-
? switchMap(urlMatchToggler)
|
|
29
|
-
: mergeMap(urlMatchToggler);
|
|
30
|
-
|
|
31
|
-
return pipe(
|
|
32
|
-
map(mapConditionFn),
|
|
33
|
-
distinctUntilChanged(),
|
|
34
|
-
mergeOperator
|
|
35
|
-
)
|
|
36
|
-
}
|