Skip to content

Commit f201909

Browse files
committed
Add new behaviors, utils.
1 parent bed12a3 commit f201909

File tree

14 files changed

+165
-61
lines changed

14 files changed

+165
-61
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ CHANGELOG
44
Next release
55
------------
66

7-
* Add few behaviors for dom events (copy/confirm).
7+
* Add Sentry helpers.
8+
* Add few behaviors for dom events (copy/confirm/processing).
89
* Add helpers for work with attributes and states of elements.
910
* Add UI notifications with adapters (toastr, notyf).
1011

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
"./observability/*": {
2222
"types": "./dist/observability/*.d.ts",
2323
"import": "./dist/observability/*.js"
24+
},
25+
"./ui/*": {
26+
"types": "./dist/ui/*.d.ts",
27+
"import": "./dist/ui/*.js"
2428
}
2529
},
2630
"files": [

rollup.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export default [
1414
'./src/behaviors/index.ts',
1515
'./src/browser/index.ts',
1616
'./src/observability/index.ts',
17+
'./src/ui/index.ts',
1718
],
1819
preserveEntrySignatures: 'exports-only',
1920
output: {

src/behaviors/confirm.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { navigateTo } from '../browser/navigation';
2+
import { DomEventCallback } from '../dom';
23

34
export const kAttrHref = 'data-href';
45
export const kAttrConfirm = 'data-confirm';
56

6-
export function confirmBehavior(element: HTMLElement, event: Event): void {
7+
export const confirmBehavior: DomEventCallback<HTMLElement> = (element: HTMLElement, event: Event): void => {
78
event.preventDefault();
89

910
const href = element.getAttribute(kAttrHref);
@@ -16,4 +17,4 @@ export function confirmBehavior(element: HTMLElement, event: Event): void {
1617
if (confirm(message)) {
1718
navigateTo(href);
1819
}
19-
}
20+
};

src/behaviors/copy.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { copyToClipboard } from '../browser';
2-
import { readStringAttribute } from '../dom';
2+
import { DomEventCallback, readStringAttribute } from '../dom';
33

44
export type CopyBehaviorOptions = {
55
success?: (text: string, element: HTMLElement) => void;
@@ -8,23 +8,21 @@ export type CopyBehaviorOptions = {
88
export const kAttrCopy = 'data-copy';
99
export const kAttrMessage = 'data-copy-message';
1010

11-
export async function copyBehavior(element: HTMLElement, event: Event, options?: CopyBehaviorOptions) {
11+
const copyBehavior: DomEventCallback<HTMLElement> = async (element: HTMLElement, event: Event) => {
1212
if (element.nodeName.toLowerCase() === 'a' && element.getAttribute('href') === '#') {
1313
event.preventDefault();
1414
}
1515

1616
const data = readStringAttribute(element, kAttrCopy);
17-
const message = readStringAttribute(element, kAttrMessage, 'Success copy to clipboard.');
1817

1918
await copyToClipboard(data);
19+
};
2020

21-
if (options && options.success) {
22-
options.success(message, element);
23-
}
24-
}
25-
26-
export function createCopyBehavior(options: CopyBehaviorOptions) {
21+
export const createCopyBehavior = (options?: CopyBehaviorOptions): DomEventCallback<HTMLElement> => {
2722
return async (el: HTMLElement, ev: Event) => {
28-
await copyBehavior(el, ev, options);
23+
await copyBehavior(el, ev);
24+
25+
const message = readStringAttribute(el, kAttrMessage, 'Success copy to clipboard.');
26+
options?.success?.(message, el);
2927
};
30-
}
28+
};

src/behaviors/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './confirm';
22
export * from './copy';
3+
export * from './processing';

src/behaviors/processing.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { addClass, disableElement, DomChangeCallback, enableElement, readBoolAttribute, readStringAttribute, removeClass } from '../dom';
2+
import { createSpinner } from '../ui';
3+
4+
type ProcessingMode = 'prepend' | 'replace';
5+
6+
export const kProcessingAttribute = 'data-processing';
7+
export const kProcessingModeAttribute = 'data-processing-mode';
8+
9+
export const processingBehavior: DomChangeCallback<HTMLElement> = (element): void => {
10+
const processing = readBoolAttribute(element, kProcessingAttribute);
11+
const mode: ProcessingMode = <ProcessingMode>readStringAttribute(element, kProcessingModeAttribute, 'prepend');
12+
13+
if (processing) {
14+
if (element.querySelector('[role="status"]')) {
15+
// Spinner already exist. Nothing action.
16+
return;
17+
}
18+
19+
const spinner = createSpinner('small');
20+
21+
if (mode === 'prepend') {
22+
element.prepend(spinner);
23+
} else {
24+
element.setAttribute('data-html', element.innerHTML);
25+
element.innerHTML = spinner.outerHTML;
26+
}
27+
28+
disableElement(element);
29+
30+
if (element.tagName.toLowerCase() === 'a') {
31+
addClass(element, 'disabled');
32+
}
33+
} else {
34+
enableElement(element);
35+
36+
(<HTMLSpanElement | null>element.querySelector('[role="status"]'))?.remove();
37+
38+
if (element.tagName.toLowerCase() === 'a') {
39+
removeClass(element, 'disabled');
40+
}
41+
42+
if (mode === 'replace') {
43+
element.innerHTML = readStringAttribute(element, 'data-html');
44+
element.removeAttribute('data-html');
45+
}
46+
}
47+
};

src/dom/changes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ type SelectorsDomChangeOptions = BaseDomChangeOptions & {
1717

1818
type DomChangeOptions = SelectorsDomChangeOptions | SelectorDomChangeOptions;
1919

20-
export type DomChangeCallback = (target: Element, item?: MutationRecord) => void;
21-
export type DomChangeCallbacks = Record<string, DomChangeCallback>;
20+
export type DomChangeCallback<T extends Element = Element> = (target: T, item?: MutationRecord) => void;
21+
export type DomChangeCallbacks<T extends Element = Element> = Record<string, DomChangeCallback<T>>;
2222

2323
export function onDomChanges(options: SelectorDomChangeOptions, callback: DomChangeCallback): void;
2424
export function onDomChanges(options: SelectorsDomChangeOptions): void;

src/dom/events.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { onDomReady } from './ready';
22

3-
export type EventHandler<E extends Element = Element> = (element: E, event: Event) => void;
4-
export type EventHandlers<E extends Element = Element> = Record<string, EventHandler<E>>;
3+
export type DomEventCallback<E extends Element = Element> = (element: E, event: Event) => void | Promise<void>;
4+
export type DomEventCallbacks<E extends Element = Element> = Record<string, DomEventCallback<E>>;
55

66
type EventName = 'click' | 'change';
77
type Selector = string;
@@ -16,14 +16,14 @@ type SelectorListenOptions = BaseListenOptions & {
1616
}
1717

1818
type SelectorsListenOptions<E extends Element = Element> = BaseListenOptions & {
19-
readonly selectors: EventHandlers<E>;
19+
readonly selectors: DomEventCallbacks<E>;
2020
selector?: never;
2121
}
2222

23-
export function onDomEvents<E extends Element = Element>(eventName: EventName, options: SelectorListenOptions | string, handler: EventHandler<E>): void;
23+
export function onDomEvents<E extends Element = Element>(eventName: EventName, options: SelectorListenOptions | string, handler: DomEventCallback<E>): void;
2424
export function onDomEvents<E extends Element = Element>(eventName: EventName, options: SelectorsListenOptions<E>): void;
2525

26-
export function onDomEvents<E extends Element = Element>(eventName: EventName, options: SelectorListenOptions | SelectorsListenOptions<E> | string, handler?: EventHandler<E>): void {
26+
export function onDomEvents<E extends Element = Element>(eventName: EventName, options: SelectorListenOptions | SelectorsListenOptions<E> | string, handler?: DomEventCallback<E>): void {
2727
if (typeof options === 'string') {
2828
options = <SelectorListenOptions>{
2929
selector: options,
@@ -34,20 +34,20 @@ export function onDomEvents<E extends Element = Element>(eventName: EventName, o
3434
throw new Error('Only one option must be provided: either selectors or callback, not both.');
3535
}
3636

37-
let selectors: EventHandlers;
37+
let selectors: DomEventCallbacks;
3838

3939
if (options.selectors) {
40-
selectors = options.selectors as EventHandlers;
40+
selectors = options.selectors as DomEventCallbacks;
4141
} else {
4242
if (!handler) {
4343
throw new Error('Missed handler');
4444
}
4545

4646
selectors = {};
47-
selectors[options.selector] = handler as EventHandler;
47+
selectors[options.selector] = handler as DomEventCallback;
4848
}
4949

50-
const findHandler = (selectors: EventHandlers, element: HTMLElement): [HTMLElement, EventHandler] | null => {
50+
const findHandler = (selectors: DomEventCallbacks, element: HTMLElement): [HTMLElement, DomEventCallback] | null => {
5151
for (const [selector, handler] of Object.entries(selectors)) {
5252
if (element.matches(selector)) {
5353
return [element, handler];

src/ui/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './spinner';

0 commit comments

Comments
 (0)