@praxisjs/composables 0.1.4 → 1.0.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.
- package/CHANGELOG.md +54 -0
- package/dist/__tests__/browser.test.js +152 -59
- package/dist/__tests__/browser.test.js.map +1 -1
- package/dist/__tests__/dom.test.js +174 -47
- package/dist/__tests__/dom.test.js.map +1 -1
- package/dist/__tests__/utilities.test.js +155 -183
- package/dist/__tests__/utilities.test.js.map +1 -1
- package/dist/browser.d.ts +55 -12
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +105 -49
- package/dist/browser.js.map +1 -1
- package/dist/dom.d.ts +60 -25
- package/dist/dom.d.ts.map +1 -1
- package/dist/dom.js +108 -74
- package/dist/dom.js.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/utilities.d.ts +68 -26
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +102 -94
- package/dist/utilities.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/browser.test.ts +174 -65
- package/src/__tests__/dom.test.ts +207 -49
- package/src/__tests__/utilities.test.ts +187 -204
- package/src/browser.ts +134 -53
- package/src/dom.ts +129 -82
- package/src/index.ts +11 -18
- package/src/utilities.ts +144 -111
package/src/dom.ts
CHANGED
|
@@ -1,93 +1,140 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { Composable } from "@praxisjs/core";
|
|
2
|
+
import { signal, effect } from "@praxisjs/core/internal";
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
export class WindowSize extends Composable {
|
|
5
|
+
declare width: number;
|
|
6
|
+
declare height: number;
|
|
7
|
+
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
9
|
+
private _handler = () => {};
|
|
10
|
+
|
|
11
|
+
setup() {
|
|
12
|
+
const width = signal(window.innerWidth);
|
|
13
|
+
const height = signal(window.innerHeight);
|
|
14
|
+
this._handler = () => {
|
|
15
|
+
width.set(window.innerWidth);
|
|
16
|
+
height.set(window.innerHeight);
|
|
17
|
+
};
|
|
18
|
+
window.addEventListener("resize", this._handler);
|
|
19
|
+
return { width, height };
|
|
20
|
+
}
|
|
7
21
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const observer = new ResizeObserver(([entry]) => {
|
|
12
|
-
width.set(entry.contentRect.width);
|
|
13
|
-
height.set(entry.contentRect.height);
|
|
14
|
-
});
|
|
15
|
-
effect(() => {
|
|
16
|
-
if (ref.current) {
|
|
17
|
-
observer.observe(ref.current);
|
|
18
|
-
width.set(ref.current.offsetWidth);
|
|
19
|
-
height.set(ref.current.offsetHeight);
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
return {
|
|
23
|
-
width: computed(() => width()),
|
|
24
|
-
height: computed(() => height()),
|
|
25
|
-
stop: () => {
|
|
26
|
-
observer.disconnect();
|
|
27
|
-
},
|
|
28
|
-
};
|
|
22
|
+
onUnmount() {
|
|
23
|
+
window.removeEventListener("resize", this._handler);
|
|
24
|
+
}
|
|
29
25
|
}
|
|
30
26
|
|
|
31
|
-
export
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
27
|
+
export class ScrollPosition extends Composable {
|
|
28
|
+
declare x: number;
|
|
29
|
+
declare y: number;
|
|
30
|
+
|
|
31
|
+
private readonly _target: HTMLElement | Window;
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
33
|
+
private _handler = () => {};
|
|
34
|
+
|
|
35
|
+
constructor(target: HTMLElement | Window = window) {
|
|
36
|
+
super();
|
|
37
|
+
this._target = target;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setup() {
|
|
41
|
+
const x = signal(0);
|
|
42
|
+
const y = signal(0);
|
|
43
|
+
const t = this._target;
|
|
44
|
+
this._handler = () => {
|
|
45
|
+
x.set(t === window ? window.scrollX : (t as HTMLElement).scrollLeft);
|
|
46
|
+
y.set(t === window ? window.scrollY : (t as HTMLElement).scrollTop);
|
|
47
|
+
};
|
|
48
|
+
t.addEventListener("scroll", this._handler);
|
|
49
|
+
return { x, y };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
onUnmount() {
|
|
53
|
+
this._target.removeEventListener("scroll", this._handler);
|
|
54
|
+
}
|
|
39
55
|
}
|
|
40
56
|
|
|
41
|
-
export
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
export class ElementSize extends Composable {
|
|
58
|
+
declare width: number;
|
|
59
|
+
declare height: number;
|
|
60
|
+
|
|
61
|
+
private _observer?: ResizeObserver;
|
|
62
|
+
|
|
63
|
+
constructor(private readonly ref: { current: HTMLElement | null }) {
|
|
64
|
+
super();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
setup() {
|
|
68
|
+
const width = signal(0);
|
|
69
|
+
const height = signal(0);
|
|
70
|
+
this._observer = new ResizeObserver(([entry]) => {
|
|
71
|
+
width.set(entry.contentRect.width);
|
|
72
|
+
height.set(entry.contentRect.height);
|
|
73
|
+
});
|
|
74
|
+
effect(() => {
|
|
75
|
+
if (this.ref.current) {
|
|
76
|
+
this._observer?.observe(this.ref.current);
|
|
77
|
+
width.set(this.ref.current.offsetWidth);
|
|
78
|
+
height.set(this.ref.current.offsetHeight);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
return { width, height };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
onUnmount() {
|
|
85
|
+
this._observer?.disconnect();
|
|
86
|
+
}
|
|
53
87
|
}
|
|
54
88
|
|
|
55
|
-
export
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
89
|
+
export class Intersection extends Composable {
|
|
90
|
+
declare visible: boolean;
|
|
91
|
+
|
|
92
|
+
private _observer?: IntersectionObserver;
|
|
93
|
+
|
|
94
|
+
constructor(
|
|
95
|
+
private readonly ref: { current: HTMLElement | null },
|
|
96
|
+
private readonly options?: IntersectionObserverInit,
|
|
97
|
+
) {
|
|
98
|
+
super();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
setup() {
|
|
102
|
+
const visible = signal(false);
|
|
103
|
+
this._observer = new IntersectionObserver(([entry]) => {
|
|
104
|
+
visible.set(entry.isIntersecting);
|
|
105
|
+
}, this.options);
|
|
106
|
+
effect(() => {
|
|
107
|
+
if (this.ref.current) this._observer?.observe(this.ref.current);
|
|
108
|
+
});
|
|
109
|
+
return { visible };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
onUnmount() {
|
|
113
|
+
this._observer?.disconnect();
|
|
114
|
+
}
|
|
70
115
|
}
|
|
71
116
|
|
|
72
|
-
export
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
117
|
+
export class Focus extends Composable {
|
|
118
|
+
declare focused: boolean;
|
|
119
|
+
|
|
120
|
+
constructor(private readonly ref: { current: HTMLElement | null }) {
|
|
121
|
+
super();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
setup() {
|
|
125
|
+
const focused = signal(false);
|
|
126
|
+
effect(() => {
|
|
127
|
+
const el = this.ref.current;
|
|
128
|
+
if (!el) return;
|
|
129
|
+
const onFocus = () => { focused.set(true); };
|
|
130
|
+
const onBlur = () => { focused.set(false); };
|
|
131
|
+
el.addEventListener("focus", onFocus);
|
|
132
|
+
el.addEventListener("blur", onBlur);
|
|
133
|
+
return () => {
|
|
134
|
+
el.removeEventListener("focus", onFocus);
|
|
135
|
+
el.removeEventListener("blur", onBlur);
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
return { focused };
|
|
139
|
+
}
|
|
93
140
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,22 +1,15 @@
|
|
|
1
1
|
export {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
useFocus,
|
|
2
|
+
WindowSize,
|
|
3
|
+
ScrollPosition,
|
|
4
|
+
ElementSize,
|
|
5
|
+
Intersection,
|
|
6
|
+
Focus,
|
|
8
7
|
} from "./dom";
|
|
8
|
+
export { MediaQuery, ColorScheme, Mouse, KeyCombo, Idle } from "./browser";
|
|
9
9
|
export {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from "./browser";
|
|
16
|
-
export {
|
|
17
|
-
useClipboard,
|
|
18
|
-
useGeolocation,
|
|
19
|
-
useTimeAgo,
|
|
20
|
-
usePagination,
|
|
10
|
+
Clipboard,
|
|
11
|
+
Geolocation,
|
|
12
|
+
TimeAgo,
|
|
13
|
+
Pagination,
|
|
14
|
+
type PaginationOptions,
|
|
21
15
|
} from "./utilities";
|
|
22
|
-
export type { PaginationOptions } from "./utilities";
|
package/src/utilities.ts
CHANGED
|
@@ -1,123 +1,156 @@
|
|
|
1
|
+
import { Composable } from "@praxisjs/core";
|
|
1
2
|
import { signal, computed } from "@praxisjs/core/internal";
|
|
2
3
|
import type { Signal, Computed } from "@praxisjs/shared";
|
|
3
4
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
try {
|
|
9
|
-
await navigator.clipboard.writeText(text);
|
|
10
|
-
content.set(text);
|
|
11
|
-
copied.set(true);
|
|
12
|
-
setTimeout(() => {
|
|
13
|
-
copied.set(false);
|
|
14
|
-
}, resetDelay);
|
|
15
|
-
} catch {
|
|
16
|
-
console.warn("[useClipboard] Falha ao copiar");
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return {
|
|
20
|
-
copy,
|
|
21
|
-
copied: computed(() => copied()),
|
|
22
|
-
content: computed(() => content()),
|
|
23
|
-
};
|
|
5
|
+
export interface PaginationOptions {
|
|
6
|
+
total: number;
|
|
7
|
+
pageSize: number;
|
|
8
|
+
initial?: number;
|
|
24
9
|
}
|
|
25
10
|
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
11
|
+
export class Clipboard extends Composable {
|
|
12
|
+
declare copied: boolean;
|
|
13
|
+
declare content: string;
|
|
14
|
+
declare copy: (text: string) => Promise<void>;
|
|
15
|
+
|
|
16
|
+
constructor(private readonly resetDelay = 2000) {
|
|
17
|
+
super();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
setup() {
|
|
21
|
+
const copied = signal(false);
|
|
22
|
+
const content = signal("");
|
|
23
|
+
const resetDelay = this.resetDelay;
|
|
24
|
+
|
|
25
|
+
const copy = async (text: string): Promise<void> => {
|
|
26
|
+
try {
|
|
27
|
+
await navigator.clipboard.writeText(text);
|
|
28
|
+
content.set(text);
|
|
29
|
+
copied.set(true);
|
|
30
|
+
setTimeout(() => { copied.set(false); }, resetDelay);
|
|
31
|
+
} catch {
|
|
32
|
+
console.warn("[Clipboard] Falha ao copiar");
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return { copied, content, copy };
|
|
37
|
+
}
|
|
49
38
|
}
|
|
50
39
|
|
|
51
|
-
export
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
40
|
+
export class Geolocation extends Composable {
|
|
41
|
+
declare lat: number | null;
|
|
42
|
+
declare lng: number | null;
|
|
43
|
+
declare error: GeolocationPositionError | null;
|
|
44
|
+
declare loading: boolean;
|
|
45
|
+
|
|
46
|
+
constructor(private readonly options?: PositionOptions) {
|
|
47
|
+
super();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setup() {
|
|
51
|
+
const lat = signal<number | null>(null);
|
|
52
|
+
const lng = signal<number | null>(null);
|
|
53
|
+
const error = signal<GeolocationPositionError | null>(null);
|
|
54
|
+
const loading = signal(true);
|
|
55
|
+
|
|
56
|
+
navigator.geolocation.getCurrentPosition(
|
|
57
|
+
(pos) => {
|
|
58
|
+
lat.set(pos.coords.latitude);
|
|
59
|
+
lng.set(pos.coords.longitude);
|
|
60
|
+
loading.set(false);
|
|
61
|
+
},
|
|
62
|
+
(err) => {
|
|
63
|
+
error.set(err);
|
|
64
|
+
loading.set(false);
|
|
65
|
+
},
|
|
66
|
+
this.options,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return { lat, lng, error, loading };
|
|
70
|
+
}
|
|
77
71
|
}
|
|
78
72
|
|
|
79
|
-
export
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
73
|
+
export class TimeAgo extends Composable {
|
|
74
|
+
declare value: string;
|
|
75
|
+
|
|
76
|
+
private _interval?: ReturnType<typeof setInterval>;
|
|
77
|
+
|
|
78
|
+
constructor(
|
|
79
|
+
private readonly source:
|
|
80
|
+
| Signal<Date | number>
|
|
81
|
+
| Computed<Date | number>
|
|
82
|
+
| (() => Date | number),
|
|
83
|
+
private readonly locale = "pt-BR",
|
|
84
|
+
) {
|
|
85
|
+
super();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
setup() {
|
|
89
|
+
const tick = signal(Date.now());
|
|
90
|
+
this._interval = setInterval(() => { tick.set(Date.now()); }, 60_000);
|
|
91
|
+
|
|
92
|
+
const rtf = new Intl.RelativeTimeFormat(this.locale, { numeric: "auto" });
|
|
93
|
+
const read = this.source;
|
|
94
|
+
|
|
95
|
+
const value = computed(() => {
|
|
96
|
+
void tick();
|
|
97
|
+
const diff = new Date(read() as Date).getTime() - Date.now();
|
|
98
|
+
const abs = Math.abs(diff);
|
|
99
|
+
if (abs < 60_000) return rtf.format(Math.round(diff / 1000), "second");
|
|
100
|
+
if (abs < 3_600_000) return rtf.format(Math.round(diff / 60_000), "minute");
|
|
101
|
+
if (abs < 86_400_000) return rtf.format(Math.round(diff / 3_600_000), "hour");
|
|
102
|
+
if (abs < 2_592_000_000) return rtf.format(Math.round(diff / 86_400_000), "day");
|
|
103
|
+
return rtf.format(Math.round(diff / 2_592_000_000), "month");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return { value };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
onUnmount() {
|
|
110
|
+
clearInterval(this._interval);
|
|
111
|
+
}
|
|
83
112
|
}
|
|
84
113
|
|
|
85
|
-
export
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
_page.
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
export class Pagination extends Composable {
|
|
115
|
+
declare page: number;
|
|
116
|
+
declare totalPages: number;
|
|
117
|
+
declare offset: number;
|
|
118
|
+
declare hasNext: boolean;
|
|
119
|
+
declare hasPrev: boolean;
|
|
120
|
+
declare pageSize: number;
|
|
121
|
+
declare pages: number[];
|
|
122
|
+
declare next: () => void;
|
|
123
|
+
declare prev: () => void;
|
|
124
|
+
declare goTo: (p: number) => void;
|
|
125
|
+
declare first: () => void;
|
|
126
|
+
declare last: () => void;
|
|
127
|
+
|
|
128
|
+
constructor(private readonly options: PaginationOptions) {
|
|
129
|
+
super();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
setup() {
|
|
133
|
+
const opts = this.options;
|
|
134
|
+
const _page = signal(opts.initial ?? 1);
|
|
135
|
+
const totalPages = computed(() => Math.ceil(opts.total / opts.pageSize));
|
|
136
|
+
const page = computed(() => Math.min(_page(), totalPages()));
|
|
137
|
+
const offset = computed(() => (page() - 1) * opts.pageSize);
|
|
138
|
+
const hasNext = computed(() => page() < totalPages());
|
|
139
|
+
const hasPrev = computed(() => page() > 1);
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
page,
|
|
143
|
+
totalPages,
|
|
144
|
+
offset,
|
|
145
|
+
hasNext,
|
|
146
|
+
hasPrev,
|
|
147
|
+
pageSize: computed(() => opts.pageSize),
|
|
148
|
+
pages: computed(() => Array.from({ length: totalPages() }, (_, i) => i + 1)),
|
|
149
|
+
next: () => { if (hasNext()) _page.update((p) => p + 1); },
|
|
150
|
+
prev: () => { if (hasPrev()) _page.update((p) => p - 1); },
|
|
151
|
+
goTo: (p: number) => { _page.set(Math.max(1, Math.min(p, totalPages()))); },
|
|
152
|
+
first: () => { _page.set(1); },
|
|
153
|
+
last: () => { _page.set(totalPages()); },
|
|
154
|
+
};
|
|
155
|
+
}
|
|
123
156
|
}
|