@plohoj/html-editor 0.0.6 → 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,112 +1,176 @@
1
-
2
- import { concat, defer, EMPTY, Observable, of } from "rxjs";
3
- import { distinctUntilChanged, mergeMap, switchMap, throttleTime } from "rxjs/operators";
4
- import { trueStub } from '../utils/stubs';
5
- import { observeElementMutation } from "./observe-mutation";
6
-
7
- export interface IObserveQuerySelectorBaseOptions<T extends Element = Element> {
8
- /**
9
- * The parent element within which changes are tracked.
10
- * @default document.documentElement
11
- */
12
- parent?: Element;
13
- /** Checks if the added element has any child elements that match the selectors. */
14
- has?: string;
15
- /** Custom validation of each item */
16
- filter?: (element: T) => boolean;
17
- }
18
-
19
- export interface IObserveQuerySelectorOptions<T extends Element = Element> extends IObserveQuerySelectorBaseOptions<T> {
20
- /**
21
- * When the `asRemovedWhen` parameter emits a` true` value,
22
- * all currently added items will be returned as removed.
23
- * When the `asRemovedWhen` parameter emits a `false` value,
24
- * the search will resume and all items will again be returned as added.
25
- */
26
- asRemovedWhen?: Observable<Boolean>;
27
- }
28
-
29
- export interface IObserveElementChange<T extends Element = Element> {
30
- /** Element that satisfy the filtering condition. */
31
- target?: T;
32
- /** New element that have been added since the last emit. */
33
- added?: T;
34
- /** Element that have been removed since the last emit. */
35
- removed?: T;
36
- }
37
-
38
- /**
39
- * Returns change (addition and deletion) of element that match selectors, like an Rx stream.
40
- */
41
- export function observeQuerySelector<T extends Element = Element>(
42
- query: string,
43
- options: IObserveQuerySelectorOptions<T> = {},
44
- ): Observable<IObserveElementChange<T>> {
45
- const {
46
- parent = document.documentElement,
47
- asRemovedWhen,
48
- filter = trueStub,
49
- } = options;
50
- let targetElement: T | undefined;
51
-
52
- function checkChanges(): Observable<IObserveElementChange<T>> {
53
- const querySelectedElements: NodeListOf<T> = parent.querySelectorAll<T>(query);
54
- let filteredSelectedElement: T | undefined;
55
- const changes: IObserveElementChange<T> = {};
56
-
57
- for (const querySelectedElement of querySelectedElements) {
58
- if (options.has && !querySelectedElement.querySelector(options.has)) {
59
- continue;
60
- }
61
- if (!filter(querySelectedElement)) {
62
- continue;
63
- }
64
-
65
- filteredSelectedElement = querySelectedElement;
66
- break;
67
- }
68
-
69
- if (filteredSelectedElement === targetElement) {
70
- return EMPTY;
71
- }
72
- if (targetElement) {
73
- changes.removed = targetElement;
74
- }
75
- if (filteredSelectedElement) {
76
- changes.added = filteredSelectedElement;
77
- }
78
- changes.target = filteredSelectedElement;
79
- targetElement = filteredSelectedElement;
80
- return of(changes);
81
- }
82
-
83
- const observeQuerySelector$ = concat(
84
- defer(() => checkChanges()),
85
- observeElementMutation(parent, {subtree: true, childList: true}).pipe(
86
- throttleTime(0, undefined, {leading: true, trailing: true}),
87
- mergeMap(checkChanges),
88
- )
89
- )
90
-
91
- if (asRemovedWhen) {
92
- const removedObserver$ = defer(() => {
93
- if (!targetElement) {
94
- return EMPTY;
95
- }
96
- const changes: IObserveElementChange<T> = {
97
- removed: targetElement,
98
- };
99
- targetElement = undefined;
100
- return of(changes);
101
- });
102
-
103
- const observeQuerySelectorWithRemovedWhen$ = asRemovedWhen.pipe(
104
- distinctUntilChanged(),
105
- switchMap(asRemoved => asRemoved ? removedObserver$ : observeQuerySelector$),
106
- );
107
-
108
- return observeQuerySelectorWithRemovedWhen$;
109
- }
110
-
111
- return observeQuerySelector$;
112
- }
1
+
2
+ import { concat, defer, distinctUntilChanged, EMPTY, mergeMap, Observable, of, switchMap, throttleTime } from "rxjs";
3
+ import { IMergeMapElementChangeOptions, mergeMapAddedElements } from '../operators/merge-map-added-elements';
4
+ import { trueStub } from '../utils/stubs';
5
+ import { observeElementMutation } from "./observe-mutation";
6
+
7
+ export interface IObserveQuerySelectorBaseOptions<T extends Element = Element> {
8
+ query: string;
9
+ /**
10
+ * The parent element within which changes are tracked.
11
+ * @default document.documentElement
12
+ */
13
+ parent?: Element;
14
+ /** Checks if the added element has any child elements that match the query selectors. */
15
+ has?: string;
16
+ /** Custom validation of each item */
17
+ filter?: (element: T) => boolean;
18
+ }
19
+
20
+ export interface IObserveQuerySelectorOptions<E extends Element = Element, O = unknown>
21
+ extends IObserveQuerySelectorBaseOptions<E>, Omit<IMergeMapElementChangeOptions<E, O>, 'project'> {
22
+ /**
23
+ * * When the {@link asRemovedWhen} option emits a `true` value,
24
+ * all currently added items will be returned as removed.
25
+ * * When the {@link asRemovedWhen} option emits a `false` value,
26
+ * the search will resume and all items will again be returned as added.
27
+ */
28
+ asRemovedWhen?: Observable<Boolean>;
29
+ /**
30
+ * The function that will be called to generate a new stream
31
+ * for each element discovered the first time after it is added.
32
+ */
33
+ project?: (element: E) => Observable<O>;
34
+ /**
35
+ * The {@link tap} function will be called once for each element discovered the first time after it is added.
36
+ * The {@link tap} function is called before the {@link project} function is called.
37
+ */
38
+ tap?: (element: E) => void,
39
+ }
40
+
41
+ export interface IObservedElementChange<T extends Element = Element> {
42
+ /** Element that satisfy the filtering condition. */
43
+ target?: T;
44
+ /** New element that have been added since the last emit. */
45
+ added?: T;
46
+ /** Element that have been removed since the last emit. */
47
+ removed?: T;
48
+ }
49
+
50
+ /** Observation changes (addition and deletion) of elements that match to query selectors as an Rx stream. */
51
+ export function observeQuerySelector<E extends Element = Element>(
52
+ options: IObserveQuerySelectorOptions<E> & { project?: undefined },
53
+ ): Observable<IObservedElementChange<E>>;
54
+ export function observeQuerySelector<E extends Element = Element, O = unknown>(
55
+ options: IObserveQuerySelectorOptions<E, O>,
56
+ ): Observable<O>;
57
+ export function observeQuerySelector<E extends Element = Element>(
58
+ query: string,
59
+ options?: Omit<IObserveQuerySelectorOptions<E>, 'query'> & { project?: undefined },
60
+ ): Observable<IObservedElementChange<E>>;
61
+ export function observeQuerySelector<E extends Element = Element, O = unknown>(
62
+ query: string,
63
+ options: Omit<IObserveQuerySelectorOptions<E, O>, 'query'>,
64
+ ): Observable<O>;
65
+ export function observeQuerySelector<E extends Element = Element, O = unknown>(
66
+ query: string,
67
+ project: ((element: E) => Observable<O>),
68
+ options: Omit<IObserveQuerySelectorOptions<E>, 'query' | 'project'>,
69
+ ): Observable<O>;
70
+ export function observeQuerySelector<E extends Element = Element, O = unknown>(
71
+ queryOrOptions: string | IObserveQuerySelectorOptions<E, O>,
72
+ projectOrOptions?: ((element: E) => Observable<O>) | Omit<IObserveQuerySelectorOptions<E, O>, 'query'>,
73
+ options?: Omit<IObserveQuerySelectorOptions<E>, 'query' | 'project'>
74
+ ): Observable<IObservedElementChange<E> | O> {
75
+ // #region Options parsing
76
+ let query: string;
77
+ let project: ((element: E) => Observable<O>) | undefined;
78
+ let stableOptions: Omit<IObserveQuerySelectorOptions<E>, 'query' | 'project'>;
79
+ if (typeof queryOrOptions === 'string') {
80
+ query = queryOrOptions;
81
+ if (typeof projectOrOptions === 'function') {
82
+ project = projectOrOptions;
83
+ stableOptions = options || {};
84
+ } else {
85
+ stableOptions = projectOrOptions || {};
86
+ project = projectOrOptions?.project;
87
+ }
88
+ } else {
89
+ stableOptions = queryOrOptions;
90
+ query = queryOrOptions.query;
91
+ project = queryOrOptions?.project;
92
+ }
93
+ const {
94
+ parent = document.documentElement,
95
+ asRemovedWhen,
96
+ filter = trueStub,
97
+ has,
98
+ tap,
99
+ ...restOfStableOptions
100
+ } = stableOptions;
101
+ // #endregion
102
+
103
+ let targetElement: E | undefined;
104
+
105
+ function checkChanges(): Observable<IObservedElementChange<E>> {
106
+ const querySelectedElements: NodeListOf<E> = parent.querySelectorAll<E>(query);
107
+ let filteredSelectedElement: E | undefined;
108
+ const changes: IObservedElementChange<E> = {};
109
+
110
+ for (const querySelectedElement of querySelectedElements) {
111
+ if (has && !querySelectedElement.querySelector(has)) {
112
+ continue;
113
+ }
114
+ if (!filter(querySelectedElement)) {
115
+ continue;
116
+ }
117
+
118
+ filteredSelectedElement = querySelectedElement;
119
+ break;
120
+ }
121
+
122
+ if (filteredSelectedElement === targetElement) {
123
+ return EMPTY;
124
+ }
125
+ if (targetElement) {
126
+ changes.removed = targetElement;
127
+ }
128
+ if (filteredSelectedElement) {
129
+ changes.added = filteredSelectedElement;
130
+ tap?.(filteredSelectedElement);
131
+ }
132
+ changes.target = filteredSelectedElement;
133
+ targetElement = filteredSelectedElement;
134
+
135
+ return of(changes);
136
+ }
137
+
138
+ const observeQuerySelector$: Observable<IObservedElementChange<E>> = concat(
139
+ defer(() => checkChanges()),
140
+ observeElementMutation(parent, { subtree: true, childList: true }).pipe(
141
+ throttleTime(0, undefined, { leading: true, trailing: true }),
142
+ mergeMap(checkChanges),
143
+ )
144
+ )
145
+
146
+ let observeQuerySelectorWithRemovedWhen$ = observeQuerySelector$;
147
+
148
+ if (asRemovedWhen) {
149
+ const removedObserver$ = defer(() => {
150
+ if (!targetElement) {
151
+ return EMPTY;
152
+ }
153
+ const changes: IObservedElementChange<E> = {
154
+ removed: targetElement,
155
+ };
156
+ targetElement = undefined;
157
+ return of(changes);
158
+ });
159
+
160
+ observeQuerySelectorWithRemovedWhen$ = asRemovedWhen.pipe(
161
+ distinctUntilChanged(),
162
+ switchMap(asRemoved => asRemoved ? removedObserver$ : observeQuerySelector$),
163
+ );
164
+ }
165
+
166
+ if (project) {
167
+ return observeQuerySelectorWithRemovedWhen$.pipe(
168
+ mergeMapAddedElements({
169
+ project,
170
+ ...restOfStableOptions,
171
+ }),
172
+ );
173
+ }
174
+
175
+ return observeQuerySelectorWithRemovedWhen$;
176
+ }
@@ -0,0 +1,23 @@
1
+ import { Observable } from 'rxjs';
2
+ import { IMergeMapByStringConditionOptions, mergeMapStringCondition } from '../operators/merge-map-by-string-condition';
3
+ import { urlChange$ } from './url-change';
4
+
5
+ export interface IObserveUrlChangesOptions<T = unknown> extends Partial<IMergeMapByStringConditionOptions<T>> {}
6
+
7
+ /** Observation of `URL` changes that satisfy the conditions. */
8
+ export function observeUrlChanges(
9
+ options?: IObserveUrlChangesOptions<unknown> & { project?: undefined }
10
+ ): Observable<string>;
11
+ export function observeUrlChanges<O>(
12
+ options: IObserveUrlChangesOptions<O>
13
+ ): Observable<O>;
14
+ export function observeUrlChanges<O>(
15
+ options: IObserveUrlChangesOptions<O> = {}
16
+ ): Observable<O | string> {
17
+ return urlChange$.pipe(
18
+ mergeMapStringCondition({
19
+ ...options,
20
+ condition: options.condition || (() => true),
21
+ })
22
+ );
23
+ }
@@ -1,38 +1,37 @@
1
- import { Observable, Subscriber } from "rxjs";
2
- import { distinctUntilChanged, shareReplay } from "rxjs/operators";
3
-
4
- let pushStateSubscriber$: Subscriber<string> | undefined;
5
- let isPushStateWasInjected = false;
6
-
7
- function injectPushStateHandler(): void {
8
- if (isPushStateWasInjected) {
9
- return;
10
- }
11
- const pushState = history.pushState;
12
- history.pushState = function (...args) {
13
- pushState.apply(this, args);
14
- pushStateSubscriber$?.next(location.href);
15
- };
16
- }
17
-
18
- /**
19
- * Emit new location url when the URL is changes
20
- */
21
- export const urlChange$ = new Observable<string>(subscriber$ => {
22
- function updateURL(): void {
23
- subscriber$.next(location.href)
24
- }
25
- window.addEventListener('hashchange', updateURL);
26
- window.addEventListener('popstate', updateURL);
27
- pushStateSubscriber$ = subscriber$;
28
- subscriber$.next(location.href);
29
- injectPushStateHandler();
30
- return () => {
31
- pushStateSubscriber$ = undefined;
32
- window.removeEventListener('hashchange', updateURL);
33
- window.removeEventListener('popstate', updateURL);
34
- }
35
- }).pipe(
36
- distinctUntilChanged(),
37
- shareReplay(1),
38
- );
1
+ import { Observable, Subscriber, distinctUntilChanged, shareReplay } from "rxjs";
2
+
3
+ let pushStateSubscriber$: Subscriber<string> | undefined;
4
+ let isPushStateWasInjected = false;
5
+
6
+ function injectPushStateHandler(): void {
7
+ if (isPushStateWasInjected) {
8
+ return;
9
+ }
10
+ const pushState = history.pushState;
11
+ history.pushState = function (...args) {
12
+ pushState.apply(this, args);
13
+ pushStateSubscriber$?.next(location.href);
14
+ };
15
+ }
16
+
17
+ /**
18
+ * Emit new location url when the URL is changes
19
+ */
20
+ export const urlChange$ = new Observable<string>(subscriber$ => {
21
+ function updateURL(): void {
22
+ subscriber$.next(location.href)
23
+ }
24
+ window.addEventListener('hashchange', updateURL);
25
+ window.addEventListener('popstate', updateURL);
26
+ pushStateSubscriber$ = subscriber$;
27
+ subscriber$.next(location.href);
28
+ injectPushStateHandler();
29
+ return () => {
30
+ pushStateSubscriber$ = undefined;
31
+ window.removeEventListener('hashchange', updateURL);
32
+ window.removeEventListener('popstate', updateURL);
33
+ }
34
+ }).pipe(
35
+ distinctUntilChanged(),
36
+ shareReplay(1),
37
+ );
@@ -0,0 +1,21 @@
1
+ import { MonoTypeOperatorFunction, tap } from "rxjs";
2
+
3
+ function blurElementImmediately(element: Element): void {
4
+ if ('blur' in element) {
5
+ (element as HTMLElement).blur();
6
+ } else {
7
+ element.dispatchEvent(new FocusEvent('blur'));
8
+ element.dispatchEvent(new FocusEvent('focusout', {bubbles: true}));
9
+ }
10
+ console.log(`Blur: `, element);
11
+ }
12
+
13
+ export function blurElement<T extends Element>(): MonoTypeOperatorFunction<T>;
14
+ export function blurElement<T extends Element>(element: T): void;
15
+ export function blurElement<T extends Element>(element?: T): MonoTypeOperatorFunction<T> | void {
16
+ if (element) {
17
+ blurElementImmediately(element);
18
+ } else {
19
+ return tap((element: T) => blurElementImmediately(element));
20
+ }
21
+ }
@@ -1,20 +1,20 @@
1
- import { MonoTypeOperatorFunction, pipe } from "rxjs";
2
- import { tap } from "rxjs/operators";
3
-
4
- function clickElementImmediately(element: Element): void {
5
- element.dispatchEvent(new MouseEvent('click', { bubbles: true }));
6
- console.log(`Click: `, element);
7
- }
8
-
9
-
10
- export function clickElement<T extends Element>(): MonoTypeOperatorFunction<T>;
11
- export function clickElement<T extends Element>(element: T): void;
12
- export function clickElement<T extends Element>(element?: T): MonoTypeOperatorFunction<T> | void {
13
- if (element) {
14
- clickElementImmediately(element);
15
- } else {
16
- return pipe(
17
- tap((element: T) => clickElementImmediately(element)),
18
- );
19
- }
20
- }
1
+ import { MonoTypeOperatorFunction, tap } from "rxjs";
2
+
3
+ function clickElementImmediately(element: Element): void {
4
+ if ('click' in element) {
5
+ (element as HTMLElement).click();
6
+ } else {
7
+ element.dispatchEvent(new MouseEvent('click', { bubbles: true }));
8
+ }
9
+ console.log(`Click: `, element);
10
+ }
11
+
12
+ export function clickElement<T extends Element>(): MonoTypeOperatorFunction<T>;
13
+ export function clickElement<T extends Element>(element: T): void;
14
+ export function clickElement<T extends Element>(element?: T): MonoTypeOperatorFunction<T> | void {
15
+ if (element) {
16
+ clickElementImmediately(element);
17
+ } else {
18
+ return tap((element: T) => clickElementImmediately(element));
19
+ }
20
+ }
@@ -0,0 +1,21 @@
1
+ import { MonoTypeOperatorFunction, tap } from "rxjs";
2
+
3
+ function focusElementImmediately(element: Element): void {
4
+ if ('focus' in element) {
5
+ (element as HTMLElement).focus();
6
+ } else {
7
+ element.dispatchEvent(new FocusEvent('focus'));
8
+ element.dispatchEvent(new FocusEvent('focusin', {bubbles: true}));
9
+ }
10
+ console.log(`Focus: `, element);
11
+ }
12
+
13
+ export function focusElement<T extends Element>(): MonoTypeOperatorFunction<T>;
14
+ export function focusElement<T extends Element>(element: T): void;
15
+ export function focusElement<T extends Element>(element?: T): MonoTypeOperatorFunction<T> | void {
16
+ if (element) {
17
+ focusElementImmediately(element);
18
+ } else {
19
+ return tap((element: T) => focusElementImmediately(element));
20
+ }
21
+ }