@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.
@@ -1,77 +1,120 @@
1
- import { EMPTY, from, merge, ObservableInput, ObservedValueOf, OperatorFunction } from "rxjs";
2
- import { connect, filter, mergeMap, takeUntil } from "rxjs/operators";
3
- import { IObserveElementChange } from "../observable/observe-query-selector";
4
- import { IObservedElementsChanges } from "../observable/observe-query-selector-all";
5
-
6
- export interface IMapElementChangeOptions {
7
- /**
8
- * If the `isTakeUntilRemoved` parameter is equal to the `true` value,
9
- * then each thread will be interrupted after the element is removed.
10
- * Only the stream that belongs to the deleted element is interrupted.
11
- *
12
- * If the `isTakeUntilRemoved` parameter is equal to the `false` value,
13
- * then the converted streams will not be interrupted.
14
- */
15
- isTakeUntilRemoved?: boolean;
16
- }
17
-
18
- function assuredArray<T>(values?: T | T[]): T[] {
19
- if (values instanceof Array) {
20
- return values
21
- }
22
- if (values) {
23
- return [values];
24
- }
25
- return [];
26
- }
27
-
28
- /** Conversion operator to a new stream for each new added element */
29
- export function mergeMapAddedElements<T extends Element, O extends ObservableInput<any>>(
30
- project: (element: T) => O,
31
- options?: IMapElementChangeOptions,
32
- ): OperatorFunction<
33
- IObservedElementsChanges<T> | IObserveElementChange<T>,
34
- ObservedValueOf<O>
35
- > {
36
- if (!options?.isTakeUntilRemoved) {
37
- return source$ => source$.pipe(
38
- mergeMap(changes => {
39
- const added = assuredArray(changes.added);
40
- if (added.length === 0) {
41
- return EMPTY;
42
- }
43
- const addedObservers = added.map(project);
44
- return merge(...addedObservers);
45
- })
46
- );
47
- }
48
- return source$ => source$.pipe(
49
- connect(connectedSource$ => connectedSource$.pipe(
50
- mergeMap(changes => {
51
- const added = assuredArray(changes.added);
52
- if (added.length === 0) {
53
- return EMPTY;
54
- }
55
-
56
- const addedObservers = added.map(addedElement =>
57
- from(
58
- project(addedElement)
59
- ).pipe(
60
- takeUntil(
61
- connectedSource$.pipe(
62
- filter(changes => {
63
- if (changes.removed instanceof Array) {
64
- return changes.removed.indexOf(addedElement) != -1;
65
- } else {
66
- return changes.removed === addedElement;
67
- }
68
- }),
69
- ),
70
- ),
71
- ) as O,
72
- );
73
- return merge(...addedObservers);
74
- })
75
- )),
76
- );
77
- }
1
+ import { connect, EMPTY, filter, merge, mergeMap, Observable, OperatorFunction, takeUntil } from "rxjs";
2
+ import type { observeQuerySelector, IObservedElementChange, IObserveQuerySelectorOptions } from "../observable/observe-query-selector";
3
+ import type { observeQuerySelectorAll, IObservedElementsChanges } from "../observable/observe-query-selector-all";
4
+
5
+ export interface IMergeMapElementChangeOptions<E extends Element = Element, O = unknown> {
6
+ /**
7
+ * The function that will be called to generate a new stream
8
+ * for each element discovered the first time after it is added.
9
+ */
10
+ project: (element: E) => Observable<O>;
11
+ /**
12
+ * Determines when a stream from the {@link project} function should be terminated:
13
+ * * `'removed'`: As soon as the element is removed.
14
+ * * `'added'`: After the element is removed and (new one will discovered or have already been discovered before).
15
+ * * `'always'`: streams will not be interrupted, after the element is removed.
16
+ *
17
+ * @default 'removed'
18
+ */
19
+ takeUntil?: 'removed' | 'added' | 'always';
20
+ }
21
+
22
+ function assuredArray<T>(values?: T | T[]): T[] {
23
+ if (values instanceof Array) {
24
+ return values
25
+ }
26
+ if (values) {
27
+ return [values];
28
+ }
29
+ return [];
30
+ }
31
+
32
+ /**
33
+ * Conversion operator to a new stream for each new added element.
34
+ *
35
+ * It can be more convenient to use the {@link IObserveQuerySelectorOptions.project | project} option in
36
+ * {@link observeQuerySelectorAll} and {@link observeQuerySelector} functions.
37
+ */
38
+ export function mergeMapAddedElements<E extends Element, O = unknown>(
39
+ options: IMergeMapElementChangeOptions<E, O>,
40
+ ): OperatorFunction<IObservedElementsChanges<E> | IObservedElementChange<E>, O>;
41
+ export function mergeMapAddedElements<E extends Element, O = unknown>(
42
+ project: (element: E) => Observable<O>,
43
+ options?: Omit<IMergeMapElementChangeOptions, 'project'>,
44
+ ): OperatorFunction<IObservedElementsChanges<E> | IObservedElementChange<E>, O>;
45
+ export function mergeMapAddedElements<E extends Element, O = unknown>(
46
+ projectOrOptions: ((element: E) => Observable<O>) | IMergeMapElementChangeOptions<E, O>,
47
+ options?: Omit<IMergeMapElementChangeOptions, 'project'>,
48
+ ): OperatorFunction<IObservedElementsChanges<E> | IObservedElementChange<E>, O> {
49
+ // #region Options parsing
50
+ let project: (element: E) => Observable<O>;
51
+ let stableOptions: Omit<IMergeMapElementChangeOptions, 'project'>;
52
+ if (typeof projectOrOptions === 'function') {
53
+ project = projectOrOptions;
54
+ stableOptions = options || {};
55
+ } else {
56
+ project = projectOrOptions.project;
57
+ stableOptions = projectOrOptions;
58
+ }
59
+ const { takeUntil: takeUntilOption = 'removed' } = stableOptions;
60
+ // #endregion
61
+
62
+ if (takeUntilOption === 'always') {
63
+ return source$ => source$.pipe(
64
+ mergeMap(changes => {
65
+ const added = assuredArray(changes.added);
66
+ if (added.length === 0) {
67
+ return EMPTY;
68
+ }
69
+ const addedObservers = added.map(project);
70
+ return merge(...addedObservers);
71
+ })
72
+ );
73
+ }
74
+ return source$ => source$.pipe(
75
+ connect(connectedSource$ =>connectedSource$.pipe(
76
+ mergeMap(changes => {
77
+ const added = assuredArray(changes.added);
78
+ if (added.length === 0) {
79
+ return EMPTY;
80
+ }
81
+
82
+ const addedObservers = added.map(addedElement => {
83
+ let takeUntil$: Observable<unknown> | undefined;
84
+ if (takeUntilOption === 'removed') {
85
+ takeUntil$ = connectedSource$.pipe(
86
+ filter(connectedChanges => connectedChanges.removed instanceof Array
87
+ ? connectedChanges.removed.indexOf(addedElement) !== -1
88
+ : connectedChanges.removed === addedElement
89
+ ),
90
+ );
91
+ } else {
92
+ let wasRemoved = false
93
+ takeUntil$ = connectedSource$.pipe(
94
+ filter(connectedChanges => {
95
+ if (!wasRemoved) {
96
+ wasRemoved = connectedChanges.removed instanceof Array
97
+ ? connectedChanges.removed.indexOf(addedElement) !== -1
98
+ : connectedChanges.removed === addedElement;
99
+ }
100
+ if (wasRemoved) {
101
+ const hasTarget = connectedChanges.target instanceof Array
102
+ ? connectedChanges.target.indexOf(addedElement) !== -1
103
+ : connectedChanges.target === addedElement;
104
+ return hasTarget;
105
+ }
106
+ return false;
107
+ })
108
+ );
109
+ }
110
+
111
+ return project(addedElement).pipe(
112
+ takeUntil(takeUntil$),
113
+ );
114
+ });
115
+
116
+ return merge(...addedObservers);
117
+ })
118
+ )),
119
+ );
120
+ }
@@ -0,0 +1,124 @@
1
+ import { connect, EMPTY, filter, map, mergeMap, Observable, of, OperatorFunction, takeUntil } from "rxjs";
2
+
3
+ export interface IMergeMapByConditionOptions<I = unknown, O = unknown> {
4
+ /** The condition that the source must satisfy */
5
+ condition: (value: I) => boolean;
6
+ /**
7
+ * The function that will be called to generate a new stream
8
+ * if the source changes and satisfies the {@link condition}.
9
+ */
10
+ project?: (source: I) => Observable<O>;
11
+ /**
12
+ * Determines when the {@link project} function will be called to create a new stream:
13
+ * * `'all'`: For each source changes that satisfies the {@link condition}.
14
+ * * `'new'`: Only if the previous source failed the {@link condition} check.
15
+ *
16
+ * @default 'new'
17
+ */
18
+ takeFor?: 'all' | 'new';
19
+ /**
20
+ * Determines when a stream from the {@link project} function should be terminated:
21
+ * * `'fail'`: As soon as the source failed the {@link condition} check.
22
+ * * `'pass'`: After the source failed the {@link condition} check and then successfully passes again.
23
+ * * `'always'`: It does not depend on whether the source passes the {@link condition} check or not.
24
+ *
25
+ * @default 'fail'
26
+ */
27
+ takeUntil?: 'fail' | 'pass' | 'always';
28
+ }
29
+
30
+ /** The operator creates a separate stream when the source is validated. */
31
+ export function mergeMapByCondition<I>(
32
+ options: IMergeMapByConditionOptions<I> & { project?: undefined },
33
+ ): OperatorFunction<I, I>;
34
+ export function mergeMapByCondition<I, O>(
35
+ options: IMergeMapByConditionOptions<I, O>,
36
+ ): OperatorFunction<I, O>;
37
+ export function mergeMapByCondition<I>(
38
+ condition: (value: I) => boolean,
39
+ project?: undefined,
40
+ options?: Omit<IMergeMapByConditionOptions, 'condition' | 'project'>,
41
+ ): OperatorFunction<I, I>;
42
+ export function mergeMapByCondition<I, O>(
43
+ condition: (value: I) => boolean,
44
+ project: (source: I) => Observable<O>,
45
+ options?: Omit<IMergeMapByConditionOptions, 'condition' | 'project'>,
46
+ ): OperatorFunction<I, O>;
47
+ export function mergeMapByCondition<I, O>(
48
+ conditionOrOptions: ((value: I) => boolean) | IMergeMapByConditionOptions<I, O>,
49
+ project?: ((source: I) => Observable<O>),
50
+ options?: Omit<IMergeMapByConditionOptions, 'condition' | 'project'>,
51
+ ): OperatorFunction<I, I | O> {
52
+ // #region Options parsing
53
+ let stableOptions: Omit<IMergeMapByConditionOptions, 'condition' | 'project'>;
54
+ let conditionFn: (value: I) => boolean;
55
+ let stableProject: ((source: I) => Observable<O>) | undefined;
56
+ if ('condition' in conditionOrOptions) {
57
+ stableOptions = conditionOrOptions;
58
+ conditionFn = conditionOrOptions.condition;
59
+ stableProject = conditionOrOptions.project;
60
+ } else {
61
+ conditionFn = conditionOrOptions;
62
+ stableOptions = options || {};
63
+ stableProject = project;
64
+ }
65
+ const {
66
+ takeFor = 'new',
67
+ takeUntil: takeUntilOption = 'fail',
68
+ } = stableOptions;
69
+ // #endregion
70
+
71
+ let isPrevConditionPass = false;
72
+
73
+ return source$ => source$.pipe(
74
+ map(source => ({ source, isConditionPassed: conditionFn(source) })),
75
+ connect(connectedSource$ => {
76
+ let takeUntil$: Observable<unknown> | undefined;
77
+ if (takeUntilOption === 'fail') {
78
+ takeUntil$ = connectedSource$.pipe(
79
+ filter(({ isConditionPassed }) => isConditionPassed)
80
+ );
81
+ } else if (takeUntilOption === 'pass') {
82
+ let wasFailed = false
83
+ takeUntil$ = connectedSource$.pipe(
84
+ filter(({ isConditionPassed }) => {
85
+ if (isConditionPassed) {
86
+ if (wasFailed) {
87
+ return true;
88
+ }
89
+ } else {
90
+ wasFailed = true;
91
+ }
92
+ return false;
93
+ })
94
+ );
95
+ }
96
+
97
+ return connectedSource$.pipe(
98
+ mergeMap(({ source, isConditionPassed }) => {
99
+ if (isConditionPassed) {
100
+ const isCreateNewProject = takeFor === 'all' || !isPrevConditionPass;
101
+ isPrevConditionPass = true;
102
+
103
+ if (isCreateNewProject) {
104
+ if (!stableProject) {
105
+ return of(source);
106
+ }
107
+
108
+ let project$ = stableProject(source);
109
+ if (takeUntil$) {
110
+ project$ = project$.pipe(
111
+ takeUntil(takeUntil$)
112
+ )
113
+ }
114
+ return project$;
115
+ }
116
+ } else {
117
+ isPrevConditionPass = false;
118
+ }
119
+ return EMPTY;
120
+ }),
121
+ );
122
+ }),
123
+ );
124
+ }
@@ -0,0 +1,57 @@
1
+ import { Observable, OperatorFunction } from "rxjs";
2
+ import { IMergeMapByConditionOptions, mergeMapByCondition } from './merge-map-by-condition';
3
+ import type { IObserveUrlChangesOptions, observeUrlChanges } from '../observable/observe-url-changes';
4
+
5
+ export interface IMergeMapByStringConditionOptions<O = unknown>
6
+ extends Omit<IMergeMapByConditionOptions<string, O>, 'condition'> {
7
+
8
+ /** The condition that the source string must satisfy */
9
+ condition: RegExp | ((value: string) => boolean);
10
+ }
11
+
12
+ export function getStringConditionFunction(
13
+ condition: RegExp | ((value: string) => boolean)
14
+ ): (value: string) => boolean {
15
+ return typeof condition === 'function' ? condition : (value: string) => condition.test(value);
16
+ }
17
+
18
+ /**
19
+ * The operator creates a separate stream when the source string is validated.
20
+ *
21
+ * It can be more convenient to use the {@link IObserveUrlChangesOptions.project | project} option in
22
+ * {@link observeUrlChanges} function.
23
+ */
24
+ export function mergeMapStringCondition(
25
+ options: IMergeMapByStringConditionOptions & { project?: undefined },
26
+ ): OperatorFunction<string, string>;
27
+ export function mergeMapStringCondition<O>(
28
+ options: IMergeMapByStringConditionOptions<O>,
29
+ ): OperatorFunction<string, O>;
30
+ export function mergeMapStringCondition(
31
+ condition: RegExp | ((value: string) => boolean),
32
+ project?: undefined,
33
+ options?: Omit<IMergeMapByStringConditionOptions, 'condition' | 'project'>,
34
+ ): OperatorFunction<string, string>;
35
+ export function mergeMapStringCondition<O>(
36
+ condition: RegExp | ((value: string) => boolean),
37
+ project: (url: string) => Observable<O>,
38
+ options?: Omit<IMergeMapByStringConditionOptions, 'condition' | 'project'>,
39
+ ): OperatorFunction<string, O>;
40
+ export function mergeMapStringCondition<O>(
41
+ conditionOrOptions: RegExp | ((value: string) => boolean) | IMergeMapByStringConditionOptions<O>,
42
+ project?: ((url: string) => Observable<O>),
43
+ options?: Omit<IMergeMapByStringConditionOptions, 'condition' | 'project'>,
44
+ ): OperatorFunction<string, O | string> {
45
+ if ('condition' in conditionOrOptions) {
46
+ return mergeMapByCondition({
47
+ ...conditionOrOptions,
48
+ condition: getStringConditionFunction(conditionOrOptions.condition),
49
+ });
50
+ } else {
51
+ return mergeMapByCondition({
52
+ ...options,
53
+ condition: getStringConditionFunction(conditionOrOptions),
54
+ project,
55
+ });
56
+ }
57
+ }
@@ -1,9 +1,8 @@
1
- import { MonoTypeOperatorFunction } from "rxjs";
2
- import { tap } from "rxjs/operators";
3
-
4
- export function removeElement<T extends Element>(): MonoTypeOperatorFunction<T> {
5
- return tap((element) => {
6
- element.remove();
7
- console.log(`Remove: `, element);
8
- });
9
- }
1
+ import { MonoTypeOperatorFunction, tap } from "rxjs";
2
+
3
+ export function removeElement<T extends Element>(): MonoTypeOperatorFunction<T> {
4
+ return tap((element) => {
5
+ element.remove();
6
+ console.log(`Remove: `, element);
7
+ });
8
+ }
@@ -1,51 +1,53 @@
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
+ 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
+ /** Setting a new value that was thrown by the thread */
6
+ setStory(value: T): void;
7
+ /** Called if {@link cancelRestore} threw an event */
8
+ removeStory(): void;
9
+ cancelRestore?: () => ObservableInput<unknown>;
10
+ };
11
+
12
+ export function restoreHistory<T>(
13
+ options: IRestoredHistoryOption<T>
14
+ ): MonoTypeOperatorFunction<T> {
15
+ return (source$: Observable<T>) => {
16
+ let hasStory = false;
17
+ const observeCancel$ = options.cancelRestore
18
+ ? defer(() => options.cancelRestore!())
19
+ .pipe(
20
+ tap(() => {
21
+ if (hasStory) {
22
+ options.removeStory();
23
+ }
24
+ }),
25
+ share(),
26
+ )
27
+ : NEVER;
28
+ const story$ = concat(
29
+ defer(() => {
30
+ const story = options.getStory();
31
+ if (story === undefined) {
32
+ return EMPTY;
33
+ }
34
+ hasStory = true;
35
+ let rested$ = of(story);
36
+ if (options.cancelRestore) {
37
+ rested$ = rested$.pipe(takeUntil(observeCancel$))
38
+ }
39
+ return rested$;
40
+ }),
41
+ source$.pipe(
42
+ tap((data: T) => {
43
+ options.setStory(data);
44
+ hasStory = true;
45
+ }),
46
+ ),
47
+ );
48
+ return merge(
49
+ story$,
50
+ observeCancel$.pipe(ignoreElements())
51
+ );
52
+ }
53
+ }
@@ -1,21 +1,20 @@
1
- import { MonoTypeOperatorFunction } from "rxjs";
2
- import { tap } from "rxjs/operators";
3
-
4
- function setInputValueImmediately(element: HTMLInputElement, value: string): void {
5
- element.value = value;
6
- element.focus();
7
- element.dispatchEvent(new Event('input'));
8
- console.log(`Set value: `, { element, value });
9
- }
10
-
11
- export function setInputValue(value: string): MonoTypeOperatorFunction<HTMLInputElement>;
12
- export function setInputValue(element: HTMLInputElement, value: string): void;
13
- export function setInputValue(
14
- elementOrValue: HTMLInputElement | string, value?: string
15
- ): MonoTypeOperatorFunction<HTMLInputElement> | void {
16
- if (typeof elementOrValue === 'string') {
17
- return tap((element: HTMLInputElement) => setInputValueImmediately(element, elementOrValue));
18
- } else {
19
- setInputValueImmediately(elementOrValue, value!);
20
- }
21
- }
1
+ import { MonoTypeOperatorFunction, tap } from "rxjs";
2
+
3
+ function setInputValueImmediately(element: HTMLInputElement, value: string): void {
4
+ element.value = value;
5
+ element.focus();
6
+ element.dispatchEvent(new Event('input'));
7
+ console.log(`Set value: `, { element, value });
8
+ }
9
+
10
+ export function setInputValue(value: string): MonoTypeOperatorFunction<HTMLInputElement>;
11
+ export function setInputValue(element: HTMLInputElement, value: string): void;
12
+ export function setInputValue(
13
+ elementOrValue: HTMLInputElement | string, value?: string
14
+ ): MonoTypeOperatorFunction<HTMLInputElement> | void {
15
+ if (typeof elementOrValue === 'string') {
16
+ return tap((element: HTMLInputElement) => setInputValueImmediately(element, elementOrValue));
17
+ } else {
18
+ setInputValueImmediately(elementOrValue, value!);
19
+ }
20
+ }