Skip to content

Commit 11b1a42

Browse files
committed
Add form/file/visibility/path helpers.
1 parent c0ca4bb commit 11b1a42

32 files changed

+1763
-76
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
CHANGELOG
22
=========
33

4+
Next release
5+
------------
6+
7+
* Add form control helpers.
8+
* Add file helpers.
9+
410
v1.0.4
511
------
612

eslint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export default [
2727
'semi': ['error', 'always'],
2828
'quotes': ['error', 'single'],
2929
'comma-dangle': ['error', 'always-multiline'],
30-
'indent': ['error', 4],
30+
'indent': ['error', 4, { SwitchCase: 1 }],
3131
'object-curly-spacing': ['error', 'always'],
3232
'arrow-parens': ['error', 'always'],
3333
'eqeqeq': ['error', 'always'],

package.json

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
"description": "The helpers for easy manipulate with dom.",
77
"license": "MIT",
88
"exports": {
9-
"./dom": {
10-
"types": "./dist/dom/index.d.ts",
11-
"import": "./dist/dom/index.js"
9+
"./browser": {
10+
"types": "./dist/browser/index.d.ts",
11+
"import": "./dist/browser/index.js"
1212
},
13-
"./dom/*": {
14-
"types": "./dist/dom/*.d.ts",
15-
"import": "./dist/dom/*.js"
13+
"./browser/*": {
14+
"types": "./dist/browser/*.d.ts",
15+
"import": "./dist/browser/*.js"
1616
},
1717
"./behaviors": {
1818
"types": "./dist/behaviors/index.d.ts",
@@ -22,13 +22,21 @@
2222
"types": "./dist/behaviors/*.d.ts",
2323
"import": "./dist/behaviors/*.js"
2424
},
25-
"./browser": {
26-
"types": "./dist/browser/index.d.ts",
27-
"import": "./dist/browser/index.js"
25+
"./core": {
26+
"types": "./dist/core/index.d.ts",
27+
"import": "./dist/core/index.js"
2828
},
29-
"./browser/*": {
30-
"types": "./dist/browser/*.d.ts",
31-
"import": "./dist/browser/*.js"
29+
"./core/*": {
30+
"types": "./dist/core/*.d.ts",
31+
"import": "./dist/core/*.js"
32+
},
33+
"./dom": {
34+
"types": "./dist/dom/index.d.ts",
35+
"import": "./dist/dom/index.js"
36+
},
37+
"./dom/*": {
38+
"types": "./dist/dom/*.d.ts",
39+
"import": "./dist/dom/*.js"
3240
},
3341
"./observability": {
3442
"types": "./dist/observability/index.d.ts",

rollup.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ const externals = [
1010
export default [
1111
{
1212
input: [
13-
'./src/dom/index.ts',
1413
'./src/behaviors/index.ts',
1514
'./src/browser/index.ts',
15+
'./src/core/index.ts',
16+
'./src/dom/index.ts',
1617
'./src/observability/index.ts',
1718
'./src/ui/index.ts',
1819
],

src/browser/file.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export function readFileAsArrayBuffer(file: File): Promise<ArrayBuffer> {
2+
return new Promise((resolve, reject) => {
3+
const reader = new FileReader();
4+
5+
reader.onload = () => resolve(reader.result as ArrayBuffer);
6+
reader.onerror = () => reject(reader.error);
7+
8+
reader.readAsArrayBuffer(file);
9+
});
10+
}

src/browser/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
export * from './network';
21
export * from './clipboard';
2+
export * from './file';
3+
export * from './network';
34
export * from './notifications';
45
export * from './request';
6+
export * from './visibility';

src/browser/visibility.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
export type ElementVisibilityCallback<T extends Element = Element> = (element: T, visible: boolean) => void;
2+
3+
type ObserverEntry = {
4+
callback: ElementVisibilityCallback;
5+
once: boolean;
6+
};
7+
8+
type ObserveVisibilityOptions = {
9+
readonly once?: boolean;
10+
}
11+
12+
const observers = new Map<Element, ObserverEntry>();
13+
14+
const intersectionObserver = new IntersectionObserver((entries) => {
15+
for (const entry of entries) {
16+
const observer = observers.get(entry.target);
17+
18+
if (!observer) {
19+
continue;
20+
}
21+
22+
observer.callback(entry.target, entry.isIntersecting);
23+
24+
if (observer.once && entry.isIntersecting) {
25+
intersectionObserver.unobserve(entry.target);
26+
observers.delete(entry.target);
27+
}
28+
}
29+
});
30+
31+
export function observeElementVisibility<T extends Element = Element>(element: T, callback: ElementVisibilityCallback<T>, options?: ObserveVisibilityOptions): () => void {
32+
observers.set(element, {
33+
callback: callback as ElementVisibilityCallback,
34+
once: options?.once ?? false,
35+
});
36+
37+
intersectionObserver.observe(element);
38+
39+
return () => {
40+
intersectionObserver.unobserve(element);
41+
observers.delete(element);
42+
};
43+
}

src/core/config.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
let store: Map<string, unknown> = new Map();
2+
let waiters: Map<string, [Promise<unknown>, (value: unknown) => void]> = new Map();
3+
4+
export function setConfig(key: string, value: unknown): void {
5+
store.set(key, value);
6+
7+
const waiterEntry = waiters.get(key);
8+
9+
if (waiterEntry) {
10+
waiterEntry[1](value);
11+
waiters.delete(key);
12+
}
13+
}
14+
15+
export async function getConfig<T = unknown>(key: string, timeout = 1000): Promise<T> {
16+
if (store.has(key)) {
17+
return store.get(key) as T;
18+
}
19+
20+
const existence = waiters.get(key);
21+
22+
if (existence) {
23+
return existence[0] as T;
24+
}
25+
26+
let timer: ReturnType<typeof setTimeout> | undefined;
27+
28+
let wrappedResolve!: (value: unknown) => void;
29+
30+
const promise = new Promise<T>((resolve, reject) => {
31+
timer = setTimeout(() => {
32+
waiters.delete(key);
33+
reject(new Error(`Failed to read config "${key}" during timeout (${timeout}ms).`));
34+
}, timeout);
35+
36+
wrappedResolve = (value: unknown) => {
37+
clearTimeout(timer);
38+
resolve(value as T);
39+
};
40+
});
41+
42+
waiters.set(key, [promise as Promise<unknown>, wrappedResolve]);
43+
44+
return promise;
45+
}
46+
47+
export function hasConfig(key: string): boolean {
48+
return store.has(key);
49+
}
50+
51+
export const __test__ = import.meta.env?.MODE === 'test'
52+
? {
53+
reset: () => {
54+
store = new Map();
55+
waiters = new Map();
56+
},
57+
}
58+
: undefined;

src/core/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './config';
2+
export * from './observable';
3+
export * from './path';

src/core/observable.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export class Observable<T> {
2+
private listeners = new Set<(data: T) => void>();
3+
4+
subscribe(callback: (data: T) => void): () => void {
5+
this.listeners.add(callback);
6+
7+
return () => {
8+
this.listeners.delete(callback);
9+
};
10+
}
11+
12+
emit(data: T): void {
13+
for (const callback of this.listeners) {
14+
callback(data);
15+
}
16+
}
17+
}

0 commit comments

Comments
 (0)