@plohoj/html-editor 0.0.5 → 0.0.7
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 +254 -72
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
- package/src/index.ts +13 -5
- package/src/observable/await-element.ts +23 -26
- package/src/observable/await-elements.ts +20 -0
- package/src/observable/await-random-element.ts +22 -0
- package/src/observable/observe-pressed-keyboard-buttons.ts +30 -0
- package/src/observable/observe-query-selector-all.ts +8 -3
- package/src/observable/observe-query-selector.ts +13 -5
- package/src/operators/click-element.ts +7 -6
- package/src/operators/focus-element.ts +22 -0
- package/src/operators/merge-map-added-elements.ts +4 -4
- package/src/operators/merge-map-string-toggle.ts +3 -3
- package/src/operators/remove-element.ts +3 -3
- package/src/operators/restore-history.ts +51 -0
- package/src/operators/set-input-value.ts +3 -5
- package/src/utils/compose-restore-history.ts +61 -0
- package/src/utils/find-recursively.ts +181 -31
- package/src/utils/stubs.ts +7 -0
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { throttleTime, mergeMap, distinctUntilChanged, switchMap, filter, map, take,
|
|
2
|
-
import { Observable, concat, defer, EMPTY, of,
|
|
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, from, pipe, tap as tap$1, share, NEVER, takeUntil as takeUntil$1, ignoreElements, ReplaySubject } from 'rxjs';
|
|
3
3
|
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return
|
|
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 (
|
|
36
|
+
if (!filter(querySelectedElement)) {
|
|
37
37
|
continue;
|
|
38
38
|
}
|
|
39
39
|
filteredSelectedElement = querySelectedElement;
|
|
@@ -70,9 +70,17 @@ 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).pipe(debounceTime(options.debounceTime || 0, options.debounceScheduler), filter(changes => !!changes.target), map(changes => changes.target), take(1));
|
|
79
|
+
}
|
|
80
|
+
|
|
73
81
|
/** Returns changes (additions and deletions) of elements that match selectors, like an Rx stream. */
|
|
74
82
|
function observeQuerySelectorAll(query, options = {}) {
|
|
75
|
-
const { parent = document.documentElement, asRemovedWhen } = options;
|
|
83
|
+
const { parent = document.documentElement, asRemovedWhen, filter = trueStub, } = options;
|
|
76
84
|
const targetElements = new Set();
|
|
77
85
|
function checkChanges() {
|
|
78
86
|
const addedElements = new Set();
|
|
@@ -82,7 +90,7 @@ function observeQuerySelectorAll(query, options = {}) {
|
|
|
82
90
|
if (options.has && !querySelectedElement.querySelector(options.has)) {
|
|
83
91
|
continue;
|
|
84
92
|
}
|
|
85
|
-
if (
|
|
93
|
+
if (!filter(querySelectedElement)) {
|
|
86
94
|
continue;
|
|
87
95
|
}
|
|
88
96
|
if (targetElementsDiff.has(querySelectedElement)) {
|
|
@@ -130,21 +138,35 @@ function observeQuerySelectorAll(query, options = {}) {
|
|
|
130
138
|
}
|
|
131
139
|
|
|
132
140
|
/**
|
|
133
|
-
* Awaiting only
|
|
134
|
-
* The stream ends
|
|
141
|
+
* Awaiting only first elements changes to match the selector and returns it as an Rx stream.
|
|
142
|
+
* The stream ends after any element is found / added.
|
|
135
143
|
*/
|
|
136
|
-
function
|
|
137
|
-
return
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
function awaitElements(query, options = {}) {
|
|
145
|
+
return observeQuerySelectorAll(query, options).pipe(debounceTime(options.debounceTime || 0, options.debounceScheduler), filter(changes => !!changes.target), map(changes => changes.target), take(1));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function randomFromArray(array, from = 0, to = array.length) {
|
|
149
|
+
if (to < 0) {
|
|
150
|
+
to = array.length + to;
|
|
151
|
+
}
|
|
152
|
+
return array[Math.floor(Math.random() * (to - from)) + from];
|
|
153
|
+
}
|
|
154
|
+
|
|
140
155
|
/**
|
|
141
156
|
* 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
|
-
*
|
|
157
|
+
* If there are more than 1 elements, it will return a random one.
|
|
158
|
+
* The stream ends after the elements are found / added.
|
|
144
159
|
*/
|
|
145
|
-
function awaitRandomElement(query) {
|
|
146
|
-
return observeQuerySelectorAll(query)
|
|
147
|
-
|
|
160
|
+
function awaitRandomElement(query, options = {}) {
|
|
161
|
+
return observeQuerySelectorAll(query, options).pipe(debounceTime(options.debounceTime || 0, options.debounceScheduler), filter(changes => changes.target.length > 0), map(changes => randomFromArray(changes.target)), take(1));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function observePressedKeyboardButtons(options = {}) {
|
|
165
|
+
const target = options.target || window;
|
|
166
|
+
const set = new Set();
|
|
167
|
+
const addKey$ = merge(fromEvent(target, 'keydown'), fromEvent(target, 'keypress')).pipe(filter((event) => !set.has(event.key)), tap((event) => set.add(event.key)));
|
|
168
|
+
const keyRemove$ = fromEvent(target, 'keyup').pipe(filter((event) => set.has(event.key)), tap((event) => set.delete(event.key)));
|
|
169
|
+
return merge(addKey$, keyRemove$).pipe(map(() => set));
|
|
148
170
|
}
|
|
149
171
|
|
|
150
172
|
let pushStateSubscriber$;
|
|
@@ -175,7 +197,12 @@ const urlChange$ = new Observable(subscriber$ => {
|
|
|
175
197
|
}).pipe(distinctUntilChanged(), shareReplay(1));
|
|
176
198
|
|
|
177
199
|
function clickElementImmediately(element) {
|
|
178
|
-
|
|
200
|
+
if ('click' in element) {
|
|
201
|
+
element.click();
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
element.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
205
|
+
}
|
|
179
206
|
console.log(`Click: `, element);
|
|
180
207
|
}
|
|
181
208
|
function clickElement(element) {
|
|
@@ -183,7 +210,26 @@ function clickElement(element) {
|
|
|
183
210
|
clickElementImmediately(element);
|
|
184
211
|
}
|
|
185
212
|
else {
|
|
186
|
-
return
|
|
213
|
+
return tap((element) => clickElementImmediately(element));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function focusElementImmediately(element) {
|
|
218
|
+
if ('focus' in element) {
|
|
219
|
+
element.focus();
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
element.dispatchEvent(new FocusEvent('focus'));
|
|
223
|
+
element.dispatchEvent(new FocusEvent('focusin', { bubbles: true }));
|
|
224
|
+
}
|
|
225
|
+
console.log(`Focus: `, element);
|
|
226
|
+
}
|
|
227
|
+
function focusElement(element) {
|
|
228
|
+
if (element) {
|
|
229
|
+
focusElementImmediately(element);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
return tap((element) => focusElementImmediately(element));
|
|
187
233
|
}
|
|
188
234
|
}
|
|
189
235
|
|
|
@@ -200,20 +246,20 @@ function assuredArray(values) {
|
|
|
200
246
|
function mergeMapAddedElements(project, options) {
|
|
201
247
|
if (!(options === null || options === void 0 ? void 0 : options.isTakeUntilRemoved)) {
|
|
202
248
|
return source$ => source$.pipe(mergeMap(changes => {
|
|
203
|
-
|
|
249
|
+
const added = assuredArray(changes.added);
|
|
204
250
|
if (added.length === 0) {
|
|
205
251
|
return EMPTY;
|
|
206
252
|
}
|
|
207
|
-
|
|
253
|
+
const addedObservers = added.map(project);
|
|
208
254
|
return merge(...addedObservers);
|
|
209
255
|
}));
|
|
210
256
|
}
|
|
211
257
|
return source$ => source$.pipe(connect(connectedSource$ => connectedSource$.pipe(mergeMap(changes => {
|
|
212
|
-
|
|
258
|
+
const added = assuredArray(changes.added);
|
|
213
259
|
if (added.length === 0) {
|
|
214
260
|
return EMPTY;
|
|
215
261
|
}
|
|
216
|
-
|
|
262
|
+
const addedObservers = added.map(addedElement => from(project(addedElement)).pipe(takeUntil(connectedSource$.pipe(filter(changes => {
|
|
217
263
|
if (changes.removed instanceof Array) {
|
|
218
264
|
return changes.removed.indexOf(addedElement) != -1;
|
|
219
265
|
}
|
|
@@ -225,80 +271,219 @@ function mergeMapAddedElements(project, options) {
|
|
|
225
271
|
}))));
|
|
226
272
|
}
|
|
227
273
|
|
|
274
|
+
/** The operator creates a separate stream when the source string is validated. */
|
|
275
|
+
function mergeMapStringToggle(condition, project, options) {
|
|
276
|
+
const mapConditionFn = typeof condition === 'function' ? condition : (url) => condition.test(url);
|
|
277
|
+
let urlMatchToggler;
|
|
278
|
+
if (typeof project === 'function') {
|
|
279
|
+
urlMatchToggler = (isUrlMatch) => isUrlMatch ? from(project()) : EMPTY;
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
urlMatchToggler = (isUrlMatch) => isUrlMatch ? from(project) : EMPTY;
|
|
283
|
+
}
|
|
284
|
+
const mergeOperator = (options === null || options === void 0 ? void 0 : options.isTakeUntilToggle)
|
|
285
|
+
? switchMap(urlMatchToggler)
|
|
286
|
+
: mergeMap(urlMatchToggler);
|
|
287
|
+
return pipe(map(mapConditionFn), distinctUntilChanged(), mergeOperator);
|
|
288
|
+
}
|
|
289
|
+
|
|
228
290
|
function removeElement() {
|
|
229
|
-
return
|
|
291
|
+
return tap((element) => {
|
|
230
292
|
element.remove();
|
|
231
293
|
console.log(`Remove: `, element);
|
|
232
|
-
})
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function restoreHistory(options) {
|
|
298
|
+
return (source$) => {
|
|
299
|
+
let hasStory = false;
|
|
300
|
+
const observeCancel$ = options.cancelRestore
|
|
301
|
+
? defer(() => options.cancelRestore())
|
|
302
|
+
.pipe(tap$1(() => {
|
|
303
|
+
if (hasStory) {
|
|
304
|
+
options.removeStory();
|
|
305
|
+
}
|
|
306
|
+
}), share())
|
|
307
|
+
: NEVER;
|
|
308
|
+
const story$ = concat(defer(() => {
|
|
309
|
+
const story = options.getStory();
|
|
310
|
+
if (story === undefined) {
|
|
311
|
+
return EMPTY;
|
|
312
|
+
}
|
|
313
|
+
hasStory = true;
|
|
314
|
+
let rested$ = of(story);
|
|
315
|
+
if (options.cancelRestore) {
|
|
316
|
+
rested$ = rested$.pipe(takeUntil$1(observeCancel$));
|
|
317
|
+
}
|
|
318
|
+
return rested$;
|
|
319
|
+
}), source$.pipe(tap$1((data) => {
|
|
320
|
+
options.setStory(data);
|
|
321
|
+
hasStory = true;
|
|
322
|
+
})));
|
|
323
|
+
return merge(story$, observeCancel$.pipe(ignoreElements()));
|
|
324
|
+
};
|
|
233
325
|
}
|
|
234
326
|
|
|
235
327
|
function setInputValueImmediately(element, value) {
|
|
236
328
|
element.value = value;
|
|
237
329
|
element.focus();
|
|
238
|
-
element.dispatchEvent(new Event('input'
|
|
330
|
+
element.dispatchEvent(new Event('input'));
|
|
239
331
|
console.log(`Set value: `, { element, value });
|
|
240
332
|
}
|
|
241
333
|
function setInputValue(elementOrValue, value) {
|
|
242
334
|
if (typeof elementOrValue === 'string') {
|
|
243
|
-
return
|
|
335
|
+
return tap((element) => setInputValueImmediately(element, elementOrValue));
|
|
244
336
|
}
|
|
245
337
|
else {
|
|
246
338
|
setInputValueImmediately(elementOrValue, value);
|
|
247
339
|
}
|
|
248
340
|
}
|
|
249
341
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
342
|
+
function composeRestoreHistory(options) {
|
|
343
|
+
const cancelSubject$ = new ReplaySubject(1);
|
|
344
|
+
function cancelAll() {
|
|
345
|
+
cancelSubject$.next();
|
|
346
|
+
}
|
|
347
|
+
function convertOption(option) {
|
|
348
|
+
return restoreHistory(Object.assign(Object.assign({}, option), { cancelRestore: !!option.cancelRestore
|
|
349
|
+
? () => merge(cancelSubject$, option.cancelRestore())
|
|
350
|
+
: () => cancelSubject$ }));
|
|
351
|
+
}
|
|
352
|
+
let operators;
|
|
353
|
+
if (options instanceof Array) {
|
|
354
|
+
operators = options.map(convertOption);
|
|
256
355
|
}
|
|
257
356
|
else {
|
|
258
|
-
|
|
357
|
+
operators = {};
|
|
358
|
+
for (const key of Object.keys(options)) {
|
|
359
|
+
operators[key] = convertOption(options[key]);
|
|
360
|
+
}
|
|
259
361
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
362
|
+
return {
|
|
363
|
+
operators: operators,
|
|
364
|
+
cancelAll,
|
|
365
|
+
};
|
|
264
366
|
}
|
|
265
367
|
|
|
266
|
-
|
|
267
|
-
|
|
368
|
+
/** Recursive search on entity fields */
|
|
369
|
+
function findRecursively(obj, options = {}) {
|
|
370
|
+
var _a;
|
|
371
|
+
/** {value: path[]} */
|
|
372
|
+
const seen = new Map();
|
|
268
373
|
const searched = {};
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
374
|
+
const needSeen = new Set([{
|
|
375
|
+
value: obj,
|
|
376
|
+
depth: 0,
|
|
377
|
+
path: '',
|
|
378
|
+
}]);
|
|
379
|
+
const { matcher = falseStub, filter = trueStub, continueAfterGetterError = trueStub, stop = falseStub, minDepth = 1, maxDepth = Infinity, } = options;
|
|
380
|
+
const pathRegExpCheck = options.pathRegExp
|
|
381
|
+
? (path) => { var _a; return path ? (_a = options.pathRegExp) === null || _a === void 0 ? void 0 : _a.test(path) : false; }
|
|
382
|
+
: falseStub;
|
|
383
|
+
const fieldRegExpCheck = options.fieldRegExp
|
|
384
|
+
? (field) => { var _a; return typeof field === 'string' ? (_a = options.fieldRegExp) === null || _a === void 0 ? void 0 : _a.test(field) : false; }
|
|
385
|
+
: falseStub;
|
|
386
|
+
let needStop = false;
|
|
387
|
+
function checkForNeedSeen(options) {
|
|
388
|
+
if (options.depth > maxDepth) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const isPrimitiveOrFunction = options.value === null || typeof options.value !== 'object';
|
|
392
|
+
if (!isPrimitiveOrFunction) {
|
|
393
|
+
const seenPathsForValue = seen.get(options.value);
|
|
394
|
+
if (seenPathsForValue) {
|
|
395
|
+
// TODO windows or document
|
|
396
|
+
// TODO List of visited objects, but with the wrong path
|
|
397
|
+
const hasCircularPath = seenPathsForValue.some(path => options.path.includes(path));
|
|
398
|
+
if (hasCircularPath) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (filter(options)) {
|
|
403
|
+
needSeen.add(options);
|
|
404
|
+
if (seenPathsForValue) {
|
|
405
|
+
seenPathsForValue.push(options.path);
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
seen.set(options.value, [options.path]);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (options.depth < minDepth) {
|
|
272
413
|
return;
|
|
273
414
|
}
|
|
274
|
-
|
|
275
|
-
|
|
415
|
+
if (matcher(options) || pathRegExpCheck(options.path) || fieldRegExpCheck(options.field)) {
|
|
416
|
+
searched[options.path || '{}'] = options.value;
|
|
417
|
+
if (stop(searched)) {
|
|
418
|
+
needStop = true;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function iterateArrayLike(iterator, pathBuilder, incrementalDepth) {
|
|
423
|
+
for (const [fieldIndex, fieldValue] of iterator) {
|
|
424
|
+
checkForNeedSeen({
|
|
425
|
+
field: fieldIndex,
|
|
426
|
+
value: fieldValue,
|
|
427
|
+
path: pathBuilder(fieldIndex),
|
|
428
|
+
depth: incrementalDepth,
|
|
429
|
+
});
|
|
430
|
+
if (needStop) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
276
434
|
}
|
|
277
|
-
while (
|
|
278
|
-
const iterator =
|
|
435
|
+
while (!needStop) {
|
|
436
|
+
const iterator = needSeen.values().next();
|
|
279
437
|
if (iterator.done) {
|
|
280
438
|
break;
|
|
281
439
|
}
|
|
282
|
-
const path = iterator.value
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
if (
|
|
286
|
-
|
|
440
|
+
const { value, path, depth } = iterator.value;
|
|
441
|
+
const incrementalDepth = depth + 1;
|
|
442
|
+
needSeen.delete(iterator.value);
|
|
443
|
+
if (value instanceof Array) {
|
|
444
|
+
iterateArrayLike(value.entries(), (fieldIndex) => `${path}[${fieldIndex}]`, incrementalDepth);
|
|
445
|
+
}
|
|
446
|
+
else if (value instanceof Map) {
|
|
447
|
+
iterateArrayLike(value.entries(), (fieldIndex) => `${path}{${fieldIndex}}`, incrementalDepth);
|
|
287
448
|
}
|
|
288
|
-
if (
|
|
289
|
-
for (const [
|
|
290
|
-
|
|
291
|
-
|
|
449
|
+
else if (value instanceof Set) {
|
|
450
|
+
for (const [fieldIndex, fieldValue] of [...value].entries()) {
|
|
451
|
+
checkForNeedSeen({
|
|
452
|
+
field: fieldValue,
|
|
453
|
+
value: fieldValue,
|
|
454
|
+
path: `${path || ''}<${fieldIndex}>`,
|
|
455
|
+
depth: incrementalDepth,
|
|
456
|
+
});
|
|
457
|
+
if (needStop) {
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
292
460
|
}
|
|
293
461
|
}
|
|
294
|
-
else if (typeof
|
|
295
|
-
for (const key of Object.keys(
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
462
|
+
else if (typeof value === "object") {
|
|
463
|
+
for (const key of Object.keys(value)) {
|
|
464
|
+
let fieldValue;
|
|
465
|
+
if (((_a = Object.getOwnPropertyDescriptor(value, key)) === null || _a === void 0 ? void 0 : _a.get) && options.isIgnoreGetters) {
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
try {
|
|
469
|
+
fieldValue = value[key];
|
|
470
|
+
}
|
|
471
|
+
catch (error) {
|
|
472
|
+
if (!continueAfterGetterError(error)) {
|
|
473
|
+
needStop = true;
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
fieldValue = error;
|
|
477
|
+
}
|
|
478
|
+
checkForNeedSeen({
|
|
479
|
+
field: key,
|
|
480
|
+
value: fieldValue,
|
|
481
|
+
path: path ? `${path}.${key}` : key,
|
|
482
|
+
depth: incrementalDepth
|
|
483
|
+
});
|
|
484
|
+
if (needStop) {
|
|
485
|
+
break;
|
|
300
486
|
}
|
|
301
|
-
addToCheck(keyPath, value);
|
|
302
487
|
}
|
|
303
488
|
}
|
|
304
489
|
}
|
|
@@ -306,10 +491,7 @@ function findRecursively(obj, mather) {
|
|
|
306
491
|
return null;
|
|
307
492
|
}
|
|
308
493
|
return searched;
|
|
309
|
-
}
|
|
310
|
-
function findRecursivelyPropertyName(obj, propertyName) {
|
|
311
|
-
return findRecursively(obj, matchedPropertyName => matchedPropertyName === propertyName);
|
|
312
494
|
}
|
|
313
495
|
|
|
314
|
-
export { awaitElement, awaitRandomElement, clickElement, findRecursively,
|
|
496
|
+
export { awaitElement, awaitElements, awaitRandomElement, clickElement, composeRestoreHistory, findRecursively, focusElement, mergeMapAddedElements, mergeMapStringToggle, observeElementMutation, observePressedKeyboardButtons, observeQuerySelector, observeQuerySelectorAll, randomFromArray, removeElement, restoreHistory, setInputValue, urlChange$ };
|
|
315
497
|
//# 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.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "html-editor it's a tool for helping modification html elements",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -13,12 +13,13 @@
|
|
|
13
13
|
"license": "ISC",
|
|
14
14
|
"module": "./dist/index.js",
|
|
15
15
|
"types": "./src/index.ts",
|
|
16
|
+
"type": "module",
|
|
16
17
|
"dependencies": {
|
|
17
18
|
"rxjs": "^7.0.0"
|
|
18
19
|
},
|
|
19
20
|
"devDependencies": {
|
|
20
|
-
"rollup": "^
|
|
21
|
-
"rollup-plugin-typescript2": "^0.
|
|
22
|
-
"typescript": "^4.
|
|
21
|
+
"rollup": "^3.9.1",
|
|
22
|
+
"rollup-plugin-typescript2": "^0.34.1",
|
|
23
|
+
"typescript": "^4.9.4"
|
|
23
24
|
}
|
|
24
25
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
// Observable
|
|
2
|
-
export { awaitElement,
|
|
2
|
+
export { awaitElement, IAwaitElementOptions } from "./observable/await-element";
|
|
3
|
+
export { awaitElements } from './observable/await-elements';
|
|
4
|
+
export { awaitRandomElement } from './observable/await-random-element';
|
|
3
5
|
export { observeElementMutation } from "./observable/observe-mutation";
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
+
export { IObservePressedKeyboardButtonsOptions, observePressedKeyboardButtons } from './observable/observe-pressed-keyboard-buttons';
|
|
7
|
+
export { IObserveElementChange, IObserveQuerySelectorBaseOptions, IObserveQuerySelectorOptions, observeQuerySelector } from "./observable/observe-query-selector";
|
|
8
|
+
export { IObservedElementsChanges, observeQuerySelectorAll } from "./observable/observe-query-selector-all";
|
|
6
9
|
export { urlChange$ } from "./observable/url-change";
|
|
10
|
+
|
|
7
11
|
// Operators
|
|
8
12
|
export { clickElement } from "./operators/click-element";
|
|
13
|
+
export { focusElement } from './operators/focus-element';
|
|
9
14
|
export { mergeMapAddedElements } from "./operators/merge-map-added-elements";
|
|
15
|
+
export { mergeMapStringToggle } from "./operators/merge-map-string-toggle";
|
|
10
16
|
export { removeElement } from "./operators/remove-element";
|
|
17
|
+
export { IRestoredHistoryOption, restoreHistory } from './operators/restore-history';
|
|
11
18
|
export { setInputValue } from "./operators/set-input-value";
|
|
12
|
-
|
|
19
|
+
|
|
13
20
|
// Utils
|
|
14
|
-
export {
|
|
21
|
+
export { ComposedRestoredHistoryOperatorsArray, ComposedRestoredHistoryOperatorsList, ComposedRestoredHistoryOperatorsRecord, ComposedRestoredHistoryOptionList, composeRestoreHistory, IComposedRestoredHistory } from './utils/compose-restore-history';
|
|
22
|
+
export { findRecursively, FindRecursivelyContinue, FindRecursivelyMatcher, FindRecursivelyResult, IFindRecursivelyMatherOptions, IFindRecursivelyOptions } from "./utils/find-recursively";
|
|
15
23
|
export { randomFromArray } from "./utils/random-from-array";
|
|
@@ -1,32 +1,29 @@
|
|
|
1
|
-
import { Observable } from "rxjs";
|
|
2
|
-
import { filter, map, take } from "rxjs/operators";
|
|
3
|
-
import {
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
22
|
-
*
|
|
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
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
)
|
|
19
|
+
export function awaitElement<T extends Element = Element>(
|
|
20
|
+
query: string,
|
|
21
|
+
options: IAwaitElementOptions<T> = {},
|
|
22
|
+
): Observable<T> {
|
|
23
|
+
return observeQuerySelector<T>(query, options).pipe(
|
|
24
|
+
debounceTime(options.debounceTime || 0, options.debounceScheduler),
|
|
25
|
+
filter(changes => !!changes.target),
|
|
26
|
+
map(changes => changes.target!),
|
|
27
|
+
take(1),
|
|
28
|
+
);
|
|
32
29
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Observable } from "rxjs";
|
|
2
|
+
import { debounceTime, filter, map, take } from "rxjs/operators";
|
|
3
|
+
import { IAwaitElementOptions } from './await-element';
|
|
4
|
+
import { observeQuerySelectorAll } from './observe-query-selector-all';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Awaiting only first elements changes to match the selector and returns it as an Rx stream.
|
|
8
|
+
* The stream ends after any element is found / added.
|
|
9
|
+
*/
|
|
10
|
+
export function awaitElements<T extends Element = Element>(
|
|
11
|
+
query: string,
|
|
12
|
+
options: IAwaitElementOptions<T> = {},
|
|
13
|
+
): Observable<T[]> {
|
|
14
|
+
return observeQuerySelectorAll<T>(query, options).pipe(
|
|
15
|
+
debounceTime(options.debounceTime || 0, options.debounceScheduler),
|
|
16
|
+
filter(changes => !!changes.target),
|
|
17
|
+
map(changes => changes.target!),
|
|
18
|
+
take(1),
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
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).pipe(
|
|
17
|
+
debounceTime(options.debounceTime || 0, options.debounceScheduler),
|
|
18
|
+
filter(changes => changes.target.length > 0),
|
|
19
|
+
map(changes => randomFromArray(changes.target)),
|
|
20
|
+
take(1)
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
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').pipe(
|
|
23
|
+
filter((event: KeyboardEvent) => set.has(event.key)),
|
|
24
|
+
tap((event: KeyboardEvent) => set.delete(event.key)),
|
|
25
|
+
);
|
|
26
|
+
return merge(
|
|
27
|
+
addKey$,
|
|
28
|
+
keyRemove$,
|
|
29
|
+
).pipe(map(() => set));
|
|
30
|
+
}
|
|
@@ -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 {
|
|
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 (
|
|
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
|
|
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?:
|
|
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 {
|
|
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 (
|
|
61
|
+
if (!filter(querySelectedElement)) {
|
|
54
62
|
continue;
|
|
55
63
|
}
|
|
56
64
|
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import { MonoTypeOperatorFunction
|
|
1
|
+
import { MonoTypeOperatorFunction } from "rxjs";
|
|
2
2
|
import { tap } from "rxjs/operators";
|
|
3
3
|
|
|
4
4
|
function clickElementImmediately(element: Element): void {
|
|
5
|
-
|
|
5
|
+
if ('click' in element) {
|
|
6
|
+
(element as HTMLElement).click();
|
|
7
|
+
} else {
|
|
8
|
+
element.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
9
|
+
}
|
|
6
10
|
console.log(`Click: `, element);
|
|
7
11
|
}
|
|
8
12
|
|
|
9
|
-
|
|
10
13
|
export function clickElement<T extends Element>(): MonoTypeOperatorFunction<T>;
|
|
11
14
|
export function clickElement<T extends Element>(element: T): void;
|
|
12
15
|
export function clickElement<T extends Element>(element?: T): MonoTypeOperatorFunction<T> | void {
|
|
13
16
|
if (element) {
|
|
14
17
|
clickElementImmediately(element);
|
|
15
18
|
} else {
|
|
16
|
-
return
|
|
17
|
-
tap((element: T) => clickElementImmediately(element)),
|
|
18
|
-
);
|
|
19
|
+
return tap((element: T) => clickElementImmediately(element));
|
|
19
20
|
}
|
|
20
21
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { MonoTypeOperatorFunction } from "rxjs";
|
|
2
|
+
import { tap } from "rxjs/operators";
|
|
3
|
+
|
|
4
|
+
function focusElementImmediately(element: Element): void {
|
|
5
|
+
if ('focus' in element) {
|
|
6
|
+
(element as HTMLElement).focus();
|
|
7
|
+
} else {
|
|
8
|
+
element.dispatchEvent(new FocusEvent('focus'));
|
|
9
|
+
element.dispatchEvent(new FocusEvent('focusin', {bubbles: true}));
|
|
10
|
+
}
|
|
11
|
+
console.log(`Focus: `, element);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function focusElement<T extends Element>(): MonoTypeOperatorFunction<T>;
|
|
15
|
+
export function focusElement<T extends Element>(element: T): void;
|
|
16
|
+
export function focusElement<T extends Element>(element?: T): MonoTypeOperatorFunction<T> | void {
|
|
17
|
+
if (element) {
|
|
18
|
+
focusElementImmediately(element);
|
|
19
|
+
} else {
|
|
20
|
+
return tap((element: T) => focusElementImmediately(element));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -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
|
-
|
|
39
|
+
const added = assuredArray(changes.added);
|
|
40
40
|
if (added.length === 0) {
|
|
41
41
|
return EMPTY;
|
|
42
42
|
}
|
|
43
|
-
|
|
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
|
-
|
|
51
|
+
const added = assuredArray(changes.added);
|
|
52
52
|
if (added.length === 0) {
|
|
53
53
|
return EMPTY;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
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:
|
|
21
|
+
let urlMatchToggler: (isUrlMatch: boolean) => Observable<ObservedValueOf<O>>;
|
|
22
22
|
if (typeof project === 'function') {
|
|
23
|
-
urlMatchToggler = (isUrlMatch:
|
|
23
|
+
urlMatchToggler = (isUrlMatch: boolean) => isUrlMatch ? from(project()) : EMPTY;
|
|
24
24
|
} else {
|
|
25
|
-
urlMatchToggler = (isUrlMatch:
|
|
25
|
+
urlMatchToggler = (isUrlMatch: boolean) => isUrlMatch ? from(project) : EMPTY;
|
|
26
26
|
}
|
|
27
27
|
const mergeOperator = options?.isTakeUntilToggle
|
|
28
28
|
? switchMap(urlMatchToggler)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { MonoTypeOperatorFunction
|
|
1
|
+
import { MonoTypeOperatorFunction } from "rxjs";
|
|
2
2
|
import { tap } from "rxjs/operators";
|
|
3
3
|
|
|
4
4
|
export function removeElement<T extends Element>(): MonoTypeOperatorFunction<T> {
|
|
5
|
-
return
|
|
5
|
+
return tap((element) => {
|
|
6
6
|
element.remove();
|
|
7
7
|
console.log(`Remove: `, element);
|
|
8
|
-
})
|
|
8
|
+
});
|
|
9
9
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { concat, defer, EMPTY, ignoreElements, merge, MonoTypeOperatorFunction, NEVER, Observable, ObservableInput, of, share, 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
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { MonoTypeOperatorFunction
|
|
1
|
+
import { MonoTypeOperatorFunction } from "rxjs";
|
|
2
2
|
import { tap } from "rxjs/operators";
|
|
3
3
|
|
|
4
4
|
function setInputValueImmediately(element: HTMLInputElement, value: string): void {
|
|
5
5
|
element.value = value;
|
|
6
6
|
element.focus();
|
|
7
|
-
element.dispatchEvent(new Event('input'
|
|
7
|
+
element.dispatchEvent(new Event('input'));
|
|
8
8
|
console.log(`Set value: `, { element, value });
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -14,9 +14,7 @@ export function setInputValue(
|
|
|
14
14
|
elementOrValue: HTMLInputElement | string, value?: string
|
|
15
15
|
): MonoTypeOperatorFunction<HTMLInputElement> | void {
|
|
16
16
|
if (typeof elementOrValue === 'string') {
|
|
17
|
-
return
|
|
18
|
-
tap((element: HTMLInputElement) => setInputValueImmediately(element, elementOrValue))
|
|
19
|
-
);
|
|
17
|
+
return tap((element: HTMLInputElement) => setInputValueImmediately(element, elementOrValue));
|
|
20
18
|
} else {
|
|
21
19
|
setInputValueImmediately(elementOrValue, value!);
|
|
22
20
|
}
|
|
@@ -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,197 @@
|
|
|
1
|
-
|
|
1
|
+
import { falseStub, trueStub } from './stubs';
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
|
|
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>>();
|
|
5
62
|
const searched: Record<string, unknown> = {};
|
|
6
|
-
const
|
|
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;
|
|
7
83
|
|
|
8
|
-
function
|
|
9
|
-
if (
|
|
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) {
|
|
10
109
|
return;
|
|
11
110
|
}
|
|
12
|
-
|
|
13
|
-
|
|
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
|
+
}
|
|
14
135
|
}
|
|
15
136
|
|
|
16
|
-
while (
|
|
17
|
-
const iterator =
|
|
137
|
+
while (!needStop) {
|
|
138
|
+
const iterator = needSeen.values().next();
|
|
18
139
|
if (iterator.done) {
|
|
19
140
|
break;
|
|
20
141
|
}
|
|
21
|
-
const path = iterator.value
|
|
22
|
-
const
|
|
23
|
-
|
|
142
|
+
const {value, path, depth} = iterator.value;
|
|
143
|
+
const incrementalDepth = depth + 1;
|
|
144
|
+
needSeen.delete(iterator.value);
|
|
24
145
|
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
+
}
|
|
32
169
|
}
|
|
33
|
-
} else if (typeof
|
|
34
|
-
for (const key of Object.keys(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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;
|
|
39
194
|
}
|
|
40
|
-
addToCheck(keyPath, value);
|
|
41
195
|
}
|
|
42
196
|
}
|
|
43
197
|
}
|
|
@@ -47,7 +201,3 @@ export function findRecursively(obj: unknown, mather: RecursivelyFindMather): Re
|
|
|
47
201
|
}
|
|
48
202
|
return searched;
|
|
49
203
|
}
|
|
50
|
-
|
|
51
|
-
export function findRecursivelyPropertyName(obj: unknown, propertyName: string): Record<string, unknown> | null {
|
|
52
|
-
return findRecursively(obj, matchedPropertyName => matchedPropertyName === propertyName);
|
|
53
|
-
}
|